mirror of https://github.com/ethereum/go-ethereum
miner: refactor the miner, make the pending block on demand (#28623)
* miner: untangle miner * miner: use common.hash instead of *types.header * cmd/geth: deprecate --mine * eth: get rid of most miner api * console: get rid of coinbase in welcome message * miner/stress: get rid of the miner stress test * eth: get rid of miner.setEtherbase * ethstats: remove miner and hashrate flags * ethstats: remove miner and hashrate flags * cmd: rename pendingBlockProducer to miner.pending.feeRecipient flag * miner: use pendingFeeRecipient instead of etherbase * miner: add mutex to protect the pending block * miner: add mutex to protect the pending block * eth: get rid of etherbase mentions * miner: no need to lock the coinbase * eth, miner: fix linter --------- Co-authored-by: Martin Holst Swende <martin@swende.se> Co-authored-by: Péter Szilágyi <peterke@gmail.com>pull/29178/head
parent
6e379b6fc7
commit
d8e0807da2
@ -1,52 +0,0 @@ |
|||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package eth |
|
||||||
|
|
||||||
import ( |
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil" |
|
||||||
) |
|
||||||
|
|
||||||
// EthereumAPI provides an API to access Ethereum full node-related information.
|
|
||||||
type EthereumAPI struct { |
|
||||||
e *Ethereum |
|
||||||
} |
|
||||||
|
|
||||||
// NewEthereumAPI creates a new Ethereum protocol API for full nodes.
|
|
||||||
func NewEthereumAPI(e *Ethereum) *EthereumAPI { |
|
||||||
return &EthereumAPI{e} |
|
||||||
} |
|
||||||
|
|
||||||
// Etherbase is the address that mining rewards will be sent to.
|
|
||||||
func (api *EthereumAPI) Etherbase() (common.Address, error) { |
|
||||||
return api.e.Etherbase() |
|
||||||
} |
|
||||||
|
|
||||||
// Coinbase is the address that mining rewards will be sent to (alias for Etherbase).
|
|
||||||
func (api *EthereumAPI) Coinbase() (common.Address, error) { |
|
||||||
return api.Etherbase() |
|
||||||
} |
|
||||||
|
|
||||||
// Hashrate returns the POW hashrate.
|
|
||||||
func (api *EthereumAPI) Hashrate() hexutil.Uint64 { |
|
||||||
return hexutil.Uint64(api.e.Miner().Hashrate()) |
|
||||||
} |
|
||||||
|
|
||||||
// Mining returns an indication if this node is currently mining.
|
|
||||||
func (api *EthereumAPI) Mining() bool { |
|
||||||
return api.e.IsMining() |
|
||||||
} |
|
@ -0,0 +1,67 @@ |
|||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package miner |
||||||
|
|
||||||
|
import ( |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
) |
||||||
|
|
||||||
|
// pendingTTL indicates the period of time a generated pending block should
|
||||||
|
// exist to serve RPC requests before being discarded if the parent block
|
||||||
|
// has not changed yet. The value is chosen to align with the recommit interval.
|
||||||
|
const pendingTTL = 2 * time.Second |
||||||
|
|
||||||
|
// pending wraps a pending block with additional metadata.
|
||||||
|
type pending struct { |
||||||
|
created time.Time |
||||||
|
parentHash common.Hash |
||||||
|
result *newPayloadResult |
||||||
|
lock sync.Mutex |
||||||
|
} |
||||||
|
|
||||||
|
// resolve retrieves the cached pending result if it's available. Nothing will be
|
||||||
|
// returned if the parentHash is not matched or the result is already too old.
|
||||||
|
//
|
||||||
|
// Note, don't modify the returned payload result.
|
||||||
|
func (p *pending) resolve(parentHash common.Hash) *newPayloadResult { |
||||||
|
p.lock.Lock() |
||||||
|
defer p.lock.Unlock() |
||||||
|
|
||||||
|
if p.result == nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
if parentHash != p.parentHash { |
||||||
|
return nil |
||||||
|
} |
||||||
|
if time.Since(p.created) > pendingTTL { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return p.result |
||||||
|
} |
||||||
|
|
||||||
|
// update refreshes the cached pending block with newly created one.
|
||||||
|
func (p *pending) update(parent common.Hash, result *newPayloadResult) { |
||||||
|
p.lock.Lock() |
||||||
|
defer p.lock.Unlock() |
||||||
|
|
||||||
|
p.parentHash = parent |
||||||
|
p.result = result |
||||||
|
p.created = time.Now() |
||||||
|
} |
@ -1,223 +0,0 @@ |
|||||||
// Copyright 2018 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// This file contains a miner stress test based on the Clique consensus engine.
|
|
||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"crypto/ecdsa" |
|
||||||
"math/big" |
|
||||||
"math/rand" |
|
||||||
"os" |
|
||||||
"os/signal" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/keystore" |
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/common/fdlimit" |
|
||||||
"github.com/ethereum/go-ethereum/core" |
|
||||||
"github.com/ethereum/go-ethereum/core/txpool/legacypool" |
|
||||||
"github.com/ethereum/go-ethereum/core/types" |
|
||||||
"github.com/ethereum/go-ethereum/crypto" |
|
||||||
"github.com/ethereum/go-ethereum/eth" |
|
||||||
"github.com/ethereum/go-ethereum/eth/downloader" |
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig" |
|
||||||
"github.com/ethereum/go-ethereum/log" |
|
||||||
"github.com/ethereum/go-ethereum/miner" |
|
||||||
"github.com/ethereum/go-ethereum/node" |
|
||||||
"github.com/ethereum/go-ethereum/p2p" |
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode" |
|
||||||
"github.com/ethereum/go-ethereum/params" |
|
||||||
) |
|
||||||
|
|
||||||
func main() { |
|
||||||
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true))) |
|
||||||
fdlimit.Raise(2048) |
|
||||||
|
|
||||||
// Generate a batch of accounts to seal and fund with
|
|
||||||
faucets := make([]*ecdsa.PrivateKey, 128) |
|
||||||
for i := 0; i < len(faucets); i++ { |
|
||||||
faucets[i], _ = crypto.GenerateKey() |
|
||||||
} |
|
||||||
sealers := make([]*ecdsa.PrivateKey, 4) |
|
||||||
for i := 0; i < len(sealers); i++ { |
|
||||||
sealers[i], _ = crypto.GenerateKey() |
|
||||||
} |
|
||||||
// Create a Clique network based off of the Sepolia config
|
|
||||||
genesis := makeGenesis(faucets, sealers) |
|
||||||
|
|
||||||
// Handle interrupts.
|
|
||||||
interruptCh := make(chan os.Signal, 5) |
|
||||||
signal.Notify(interruptCh, os.Interrupt) |
|
||||||
|
|
||||||
var ( |
|
||||||
stacks []*node.Node |
|
||||||
nodes []*eth.Ethereum |
|
||||||
enodes []*enode.Node |
|
||||||
) |
|
||||||
for _, sealer := range sealers { |
|
||||||
// Start the node and wait until it's up
|
|
||||||
stack, ethBackend, err := makeSealer(genesis) |
|
||||||
if err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
defer stack.Close() |
|
||||||
|
|
||||||
for stack.Server().NodeInfo().Ports.Listener == 0 { |
|
||||||
time.Sleep(250 * time.Millisecond) |
|
||||||
} |
|
||||||
// Connect the node to all the previous ones
|
|
||||||
for _, n := range enodes { |
|
||||||
stack.Server().AddPeer(n) |
|
||||||
} |
|
||||||
// Start tracking the node and its enode
|
|
||||||
stacks = append(stacks, stack) |
|
||||||
nodes = append(nodes, ethBackend) |
|
||||||
enodes = append(enodes, stack.Server().Self()) |
|
||||||
|
|
||||||
// Inject the signer key and start sealing with it
|
|
||||||
ks := keystore.NewKeyStore(stack.KeyStoreDir(), keystore.LightScryptN, keystore.LightScryptP) |
|
||||||
signer, err := ks.ImportECDSA(sealer, "") |
|
||||||
if err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
if err := ks.Unlock(signer, ""); err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
stack.AccountManager().AddBackend(ks) |
|
||||||
} |
|
||||||
|
|
||||||
// Iterate over all the nodes and start signing on them
|
|
||||||
time.Sleep(3 * time.Second) |
|
||||||
for _, node := range nodes { |
|
||||||
if err := node.StartMining(); err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
} |
|
||||||
time.Sleep(3 * time.Second) |
|
||||||
|
|
||||||
// Start injecting transactions from the faucet like crazy
|
|
||||||
nonces := make([]uint64, len(faucets)) |
|
||||||
for { |
|
||||||
// Stop when interrupted.
|
|
||||||
select { |
|
||||||
case <-interruptCh: |
|
||||||
for _, node := range stacks { |
|
||||||
node.Close() |
|
||||||
} |
|
||||||
return |
|
||||||
default: |
|
||||||
} |
|
||||||
|
|
||||||
// Pick a random signer node
|
|
||||||
index := rand.Intn(len(faucets)) |
|
||||||
backend := nodes[index%len(nodes)] |
|
||||||
|
|
||||||
// Create a self transaction and inject into the pool
|
|
||||||
tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000), nil), types.HomesteadSigner{}, faucets[index]) |
|
||||||
if err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
if err := backend.TxPool().Add([]*types.Transaction{tx}, true, false); err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
nonces[index]++ |
|
||||||
|
|
||||||
// Wait if we're too saturated
|
|
||||||
if pend, _ := backend.TxPool().Stats(); pend > 2048 { |
|
||||||
time.Sleep(100 * time.Millisecond) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// makeGenesis creates a custom Clique genesis block based on some pre-defined
|
|
||||||
// signer and faucet accounts.
|
|
||||||
func makeGenesis(faucets []*ecdsa.PrivateKey, sealers []*ecdsa.PrivateKey) *core.Genesis { |
|
||||||
// Create a Clique network based off of the Sepolia config
|
|
||||||
genesis := core.DefaultSepoliaGenesisBlock() |
|
||||||
genesis.GasLimit = 25000000 |
|
||||||
|
|
||||||
genesis.Config.ChainID = big.NewInt(18) |
|
||||||
genesis.Config.Clique.Period = 1 |
|
||||||
|
|
||||||
genesis.Alloc = types.GenesisAlloc{} |
|
||||||
for _, faucet := range faucets { |
|
||||||
genesis.Alloc[crypto.PubkeyToAddress(faucet.PublicKey)] = types.Account{ |
|
||||||
Balance: new(big.Int).Exp(big.NewInt(2), big.NewInt(128), nil), |
|
||||||
} |
|
||||||
} |
|
||||||
// Sort the signers and embed into the extra-data section
|
|
||||||
signers := make([]common.Address, len(sealers)) |
|
||||||
for i, sealer := range sealers { |
|
||||||
signers[i] = crypto.PubkeyToAddress(sealer.PublicKey) |
|
||||||
} |
|
||||||
for i := 0; i < len(signers); i++ { |
|
||||||
for j := i + 1; j < len(signers); j++ { |
|
||||||
if bytes.Compare(signers[i][:], signers[j][:]) > 0 { |
|
||||||
signers[i], signers[j] = signers[j], signers[i] |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
genesis.ExtraData = make([]byte, 32+len(signers)*common.AddressLength+65) |
|
||||||
for i, signer := range signers { |
|
||||||
copy(genesis.ExtraData[32+i*common.AddressLength:], signer[:]) |
|
||||||
} |
|
||||||
// Return the genesis block for initialization
|
|
||||||
return genesis |
|
||||||
} |
|
||||||
|
|
||||||
func makeSealer(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { |
|
||||||
// Define the basic configurations for the Ethereum node
|
|
||||||
datadir, _ := os.MkdirTemp("", "") |
|
||||||
|
|
||||||
config := &node.Config{ |
|
||||||
Name: "geth", |
|
||||||
Version: params.Version, |
|
||||||
DataDir: datadir, |
|
||||||
P2P: p2p.Config{ |
|
||||||
ListenAddr: "0.0.0.0:0", |
|
||||||
NoDiscovery: true, |
|
||||||
MaxPeers: 25, |
|
||||||
}, |
|
||||||
} |
|
||||||
// Start the node and configure a full Ethereum node on it
|
|
||||||
stack, err := node.New(config) |
|
||||||
if err != nil { |
|
||||||
return nil, nil, err |
|
||||||
} |
|
||||||
// Create and register the backend
|
|
||||||
ethBackend, err := eth.New(stack, ðconfig.Config{ |
|
||||||
Genesis: genesis, |
|
||||||
NetworkId: genesis.Config.ChainID.Uint64(), |
|
||||||
SyncMode: downloader.FullSync, |
|
||||||
DatabaseCache: 256, |
|
||||||
DatabaseHandles: 256, |
|
||||||
TxPool: legacypool.DefaultConfig, |
|
||||||
GPO: ethconfig.Defaults.GPO, |
|
||||||
Miner: miner.Config{ |
|
||||||
GasCeil: genesis.GasLimit * 11 / 10, |
|
||||||
GasPrice: big.NewInt(1), |
|
||||||
Recommit: time.Second, |
|
||||||
}, |
|
||||||
}) |
|
||||||
if err != nil { |
|
||||||
return nil, nil, err |
|
||||||
} |
|
||||||
|
|
||||||
err = stack.Start() |
|
||||||
return stack, ethBackend, err |
|
||||||
} |
|
File diff suppressed because it is too large
Load Diff
@ -1,510 +0,0 @@ |
|||||||
// Copyright 2018 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package miner |
|
||||||
|
|
||||||
import ( |
|
||||||
"math/big" |
|
||||||
"sync/atomic" |
|
||||||
"testing" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts" |
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/consensus" |
|
||||||
"github.com/ethereum/go-ethereum/consensus/clique" |
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash" |
|
||||||
"github.com/ethereum/go-ethereum/core" |
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb" |
|
||||||
"github.com/ethereum/go-ethereum/core/txpool" |
|
||||||
"github.com/ethereum/go-ethereum/core/txpool/legacypool" |
|
||||||
"github.com/ethereum/go-ethereum/core/types" |
|
||||||
"github.com/ethereum/go-ethereum/core/vm" |
|
||||||
"github.com/ethereum/go-ethereum/crypto" |
|
||||||
"github.com/ethereum/go-ethereum/ethdb" |
|
||||||
"github.com/ethereum/go-ethereum/event" |
|
||||||
"github.com/ethereum/go-ethereum/params" |
|
||||||
"github.com/holiman/uint256" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
// testCode is the testing contract binary code which will initialises some
|
|
||||||
// variables in constructor
|
|
||||||
testCode = "0x60806040527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0060005534801561003457600080fd5b5060fc806100436000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c80630c4dae8814603757806398a213cf146053575b600080fd5b603d607e565b6040518082815260200191505060405180910390f35b607c60048036036020811015606757600080fd5b81019080803590602001909291905050506084565b005b60005481565b806000819055507fe9e44f9f7da8c559de847a3232b57364adc0354f15a2cd8dc636d54396f9587a6000546040518082815260200191505060405180910390a15056fea265627a7a723058208ae31d9424f2d0bc2a3da1a5dd659db2d71ec322a17db8f87e19e209e3a1ff4a64736f6c634300050a0032" |
|
||||||
|
|
||||||
// testGas is the gas required for contract deployment.
|
|
||||||
testGas = 144109 |
|
||||||
) |
|
||||||
|
|
||||||
var ( |
|
||||||
// Test chain configurations
|
|
||||||
testTxPoolConfig legacypool.Config |
|
||||||
ethashChainConfig *params.ChainConfig |
|
||||||
cliqueChainConfig *params.ChainConfig |
|
||||||
|
|
||||||
// Test accounts
|
|
||||||
testBankKey, _ = crypto.GenerateKey() |
|
||||||
testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) |
|
||||||
testBankFunds = big.NewInt(1000000000000000000) |
|
||||||
|
|
||||||
testUserKey, _ = crypto.GenerateKey() |
|
||||||
testUserAddress = crypto.PubkeyToAddress(testUserKey.PublicKey) |
|
||||||
|
|
||||||
// Test transactions
|
|
||||||
pendingTxs []*types.Transaction |
|
||||||
newTxs []*types.Transaction |
|
||||||
|
|
||||||
testConfig = &Config{ |
|
||||||
Recommit: time.Second, |
|
||||||
GasCeil: params.GenesisGasLimit, |
|
||||||
} |
|
||||||
) |
|
||||||
|
|
||||||
func init() { |
|
||||||
testTxPoolConfig = legacypool.DefaultConfig |
|
||||||
testTxPoolConfig.Journal = "" |
|
||||||
ethashChainConfig = new(params.ChainConfig) |
|
||||||
*ethashChainConfig = *params.TestChainConfig |
|
||||||
cliqueChainConfig = new(params.ChainConfig) |
|
||||||
*cliqueChainConfig = *params.TestChainConfig |
|
||||||
cliqueChainConfig.Clique = ¶ms.CliqueConfig{ |
|
||||||
Period: 10, |
|
||||||
Epoch: 30000, |
|
||||||
} |
|
||||||
|
|
||||||
signer := types.LatestSigner(params.TestChainConfig) |
|
||||||
tx1 := types.MustSignNewTx(testBankKey, signer, &types.AccessListTx{ |
|
||||||
ChainID: params.TestChainConfig.ChainID, |
|
||||||
Nonce: 0, |
|
||||||
To: &testUserAddress, |
|
||||||
Value: big.NewInt(1000), |
|
||||||
Gas: params.TxGas, |
|
||||||
GasPrice: big.NewInt(params.InitialBaseFee), |
|
||||||
}) |
|
||||||
pendingTxs = append(pendingTxs, tx1) |
|
||||||
|
|
||||||
tx2 := types.MustSignNewTx(testBankKey, signer, &types.LegacyTx{ |
|
||||||
Nonce: 1, |
|
||||||
To: &testUserAddress, |
|
||||||
Value: big.NewInt(1000), |
|
||||||
Gas: params.TxGas, |
|
||||||
GasPrice: big.NewInt(params.InitialBaseFee), |
|
||||||
}) |
|
||||||
newTxs = append(newTxs, tx2) |
|
||||||
} |
|
||||||
|
|
||||||
// testWorkerBackend implements worker.Backend interfaces and wraps all information needed during the testing.
|
|
||||||
type testWorkerBackend struct { |
|
||||||
db ethdb.Database |
|
||||||
txPool *txpool.TxPool |
|
||||||
chain *core.BlockChain |
|
||||||
genesis *core.Genesis |
|
||||||
} |
|
||||||
|
|
||||||
func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, n int) *testWorkerBackend { |
|
||||||
var gspec = &core.Genesis{ |
|
||||||
Config: chainConfig, |
|
||||||
Alloc: types.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, |
|
||||||
} |
|
||||||
switch e := engine.(type) { |
|
||||||
case *clique.Clique: |
|
||||||
gspec.ExtraData = make([]byte, 32+common.AddressLength+crypto.SignatureLength) |
|
||||||
copy(gspec.ExtraData[32:32+common.AddressLength], testBankAddress.Bytes()) |
|
||||||
e.Authorize(testBankAddress, func(account accounts.Account, s string, data []byte) ([]byte, error) { |
|
||||||
return crypto.Sign(crypto.Keccak256(data), testBankKey) |
|
||||||
}) |
|
||||||
case *ethash.Ethash: |
|
||||||
default: |
|
||||||
t.Fatalf("unexpected consensus engine type: %T", engine) |
|
||||||
} |
|
||||||
chain, err := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec, nil, engine, vm.Config{}, nil, nil) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("core.NewBlockChain failed: %v", err) |
|
||||||
} |
|
||||||
pool := legacypool.New(testTxPoolConfig, chain) |
|
||||||
txpool, _ := txpool.New(testTxPoolConfig.PriceLimit, chain, []txpool.SubPool{pool}) |
|
||||||
|
|
||||||
return &testWorkerBackend{ |
|
||||||
db: db, |
|
||||||
chain: chain, |
|
||||||
txPool: txpool, |
|
||||||
genesis: gspec, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (b *testWorkerBackend) BlockChain() *core.BlockChain { return b.chain } |
|
||||||
func (b *testWorkerBackend) TxPool() *txpool.TxPool { return b.txPool } |
|
||||||
|
|
||||||
func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction { |
|
||||||
var tx *types.Transaction |
|
||||||
gasPrice := big.NewInt(10 * params.InitialBaseFee) |
|
||||||
if creation { |
|
||||||
tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(testBankAddress), big.NewInt(0), testGas, gasPrice, common.FromHex(testCode)), types.HomesteadSigner{}, testBankKey) |
|
||||||
} else { |
|
||||||
tx, _ = types.SignTx(types.NewTransaction(b.txPool.Nonce(testBankAddress), testUserAddress, big.NewInt(1000), params.TxGas, gasPrice, nil), types.HomesteadSigner{}, testBankKey) |
|
||||||
} |
|
||||||
return tx |
|
||||||
} |
|
||||||
|
|
||||||
func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*worker, *testWorkerBackend) { |
|
||||||
backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks) |
|
||||||
backend.txPool.Add(pendingTxs, true, false) |
|
||||||
w := newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false) |
|
||||||
w.setEtherbase(testBankAddress) |
|
||||||
return w, backend |
|
||||||
} |
|
||||||
|
|
||||||
func TestGenerateAndImportBlock(t *testing.T) { |
|
||||||
t.Parallel() |
|
||||||
var ( |
|
||||||
db = rawdb.NewMemoryDatabase() |
|
||||||
config = *params.AllCliqueProtocolChanges |
|
||||||
) |
|
||||||
config.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} |
|
||||||
engine := clique.New(config.Clique, db) |
|
||||||
|
|
||||||
w, b := newTestWorker(t, &config, engine, db, 0) |
|
||||||
defer w.close() |
|
||||||
|
|
||||||
// This test chain imports the mined blocks.
|
|
||||||
chain, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, b.genesis, nil, engine, vm.Config{}, nil, nil) |
|
||||||
defer chain.Stop() |
|
||||||
|
|
||||||
// Ignore empty commit here for less noise.
|
|
||||||
w.skipSealHook = func(task *task) bool { |
|
||||||
return len(task.receipts) == 0 |
|
||||||
} |
|
||||||
|
|
||||||
// Wait for mined blocks.
|
|
||||||
sub := w.mux.Subscribe(core.NewMinedBlockEvent{}) |
|
||||||
defer sub.Unsubscribe() |
|
||||||
|
|
||||||
// Start mining!
|
|
||||||
w.start() |
|
||||||
|
|
||||||
for i := 0; i < 5; i++ { |
|
||||||
b.txPool.Add([]*types.Transaction{b.newRandomTx(true)}, true, false) |
|
||||||
b.txPool.Add([]*types.Transaction{b.newRandomTx(false)}, true, false) |
|
||||||
|
|
||||||
select { |
|
||||||
case ev := <-sub.Chan(): |
|
||||||
block := ev.Data.(core.NewMinedBlockEvent).Block |
|
||||||
if _, err := chain.InsertChain([]*types.Block{block}); err != nil { |
|
||||||
t.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err) |
|
||||||
} |
|
||||||
case <-time.After(3 * time.Second): // Worker needs 1s to include new changes.
|
|
||||||
t.Fatalf("timeout") |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestEmptyWorkEthash(t *testing.T) { |
|
||||||
t.Parallel() |
|
||||||
testEmptyWork(t, ethashChainConfig, ethash.NewFaker()) |
|
||||||
} |
|
||||||
func TestEmptyWorkClique(t *testing.T) { |
|
||||||
t.Parallel() |
|
||||||
testEmptyWork(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase())) |
|
||||||
} |
|
||||||
|
|
||||||
func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) { |
|
||||||
defer engine.Close() |
|
||||||
|
|
||||||
w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) |
|
||||||
defer w.close() |
|
||||||
|
|
||||||
taskCh := make(chan struct{}, 2) |
|
||||||
checkEqual := func(t *testing.T, task *task) { |
|
||||||
// The work should contain 1 tx
|
|
||||||
receiptLen, balance := 1, uint256.NewInt(1000) |
|
||||||
if len(task.receipts) != receiptLen { |
|
||||||
t.Fatalf("receipt number mismatch: have %d, want %d", len(task.receipts), receiptLen) |
|
||||||
} |
|
||||||
if task.state.GetBalance(testUserAddress).Cmp(balance) != 0 { |
|
||||||
t.Fatalf("account balance mismatch: have %d, want %d", task.state.GetBalance(testUserAddress), balance) |
|
||||||
} |
|
||||||
} |
|
||||||
w.newTaskHook = func(task *task) { |
|
||||||
if task.block.NumberU64() == 1 { |
|
||||||
checkEqual(t, task) |
|
||||||
taskCh <- struct{}{} |
|
||||||
} |
|
||||||
} |
|
||||||
w.skipSealHook = func(task *task) bool { return true } |
|
||||||
w.fullTaskHook = func() { |
|
||||||
time.Sleep(100 * time.Millisecond) |
|
||||||
} |
|
||||||
w.start() // Start mining!
|
|
||||||
select { |
|
||||||
case <-taskCh: |
|
||||||
case <-time.NewTimer(3 * time.Second).C: |
|
||||||
t.Error("new task timeout") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestAdjustIntervalEthash(t *testing.T) { |
|
||||||
t.Parallel() |
|
||||||
testAdjustInterval(t, ethashChainConfig, ethash.NewFaker()) |
|
||||||
} |
|
||||||
|
|
||||||
func TestAdjustIntervalClique(t *testing.T) { |
|
||||||
t.Parallel() |
|
||||||
testAdjustInterval(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase())) |
|
||||||
} |
|
||||||
|
|
||||||
func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) { |
|
||||||
defer engine.Close() |
|
||||||
|
|
||||||
w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) |
|
||||||
defer w.close() |
|
||||||
|
|
||||||
w.skipSealHook = func(task *task) bool { |
|
||||||
return true |
|
||||||
} |
|
||||||
w.fullTaskHook = func() { |
|
||||||
time.Sleep(100 * time.Millisecond) |
|
||||||
} |
|
||||||
var ( |
|
||||||
progress = make(chan struct{}, 10) |
|
||||||
result = make([]float64, 0, 10) |
|
||||||
index = 0 |
|
||||||
start atomic.Bool |
|
||||||
) |
|
||||||
w.resubmitHook = func(minInterval time.Duration, recommitInterval time.Duration) { |
|
||||||
// Short circuit if interval checking hasn't started.
|
|
||||||
if !start.Load() { |
|
||||||
return |
|
||||||
} |
|
||||||
var wantMinInterval, wantRecommitInterval time.Duration |
|
||||||
|
|
||||||
switch index { |
|
||||||
case 0: |
|
||||||
wantMinInterval, wantRecommitInterval = 3*time.Second, 3*time.Second |
|
||||||
case 1: |
|
||||||
origin := float64(3 * time.Second.Nanoseconds()) |
|
||||||
estimate := origin*(1-intervalAdjustRatio) + intervalAdjustRatio*(origin/0.8+intervalAdjustBias) |
|
||||||
wantMinInterval, wantRecommitInterval = 3*time.Second, time.Duration(estimate)*time.Nanosecond |
|
||||||
case 2: |
|
||||||
estimate := result[index-1] |
|
||||||
min := float64(3 * time.Second.Nanoseconds()) |
|
||||||
estimate = estimate*(1-intervalAdjustRatio) + intervalAdjustRatio*(min-intervalAdjustBias) |
|
||||||
wantMinInterval, wantRecommitInterval = 3*time.Second, time.Duration(estimate)*time.Nanosecond |
|
||||||
case 3: |
|
||||||
wantMinInterval, wantRecommitInterval = time.Second, time.Second |
|
||||||
} |
|
||||||
|
|
||||||
// Check interval
|
|
||||||
if minInterval != wantMinInterval { |
|
||||||
t.Errorf("resubmit min interval mismatch: have %v, want %v ", minInterval, wantMinInterval) |
|
||||||
} |
|
||||||
if recommitInterval != wantRecommitInterval { |
|
||||||
t.Errorf("resubmit interval mismatch: have %v, want %v", recommitInterval, wantRecommitInterval) |
|
||||||
} |
|
||||||
result = append(result, float64(recommitInterval.Nanoseconds())) |
|
||||||
index += 1 |
|
||||||
progress <- struct{}{} |
|
||||||
} |
|
||||||
w.start() |
|
||||||
|
|
||||||
time.Sleep(time.Second) // Ensure two tasks have been submitted due to start opt
|
|
||||||
start.Store(true) |
|
||||||
|
|
||||||
w.setRecommitInterval(3 * time.Second) |
|
||||||
select { |
|
||||||
case <-progress: |
|
||||||
case <-time.NewTimer(time.Second).C: |
|
||||||
t.Error("interval reset timeout") |
|
||||||
} |
|
||||||
|
|
||||||
w.resubmitAdjustCh <- &intervalAdjust{inc: true, ratio: 0.8} |
|
||||||
select { |
|
||||||
case <-progress: |
|
||||||
case <-time.NewTimer(time.Second).C: |
|
||||||
t.Error("interval reset timeout") |
|
||||||
} |
|
||||||
|
|
||||||
w.resubmitAdjustCh <- &intervalAdjust{inc: false} |
|
||||||
select { |
|
||||||
case <-progress: |
|
||||||
case <-time.NewTimer(time.Second).C: |
|
||||||
t.Error("interval reset timeout") |
|
||||||
} |
|
||||||
|
|
||||||
w.setRecommitInterval(500 * time.Millisecond) |
|
||||||
select { |
|
||||||
case <-progress: |
|
||||||
case <-time.NewTimer(time.Second).C: |
|
||||||
t.Error("interval reset timeout") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestGetSealingWorkEthash(t *testing.T) { |
|
||||||
t.Parallel() |
|
||||||
testGetSealingWork(t, ethashChainConfig, ethash.NewFaker()) |
|
||||||
} |
|
||||||
|
|
||||||
func TestGetSealingWorkClique(t *testing.T) { |
|
||||||
t.Parallel() |
|
||||||
testGetSealingWork(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase())) |
|
||||||
} |
|
||||||
|
|
||||||
func TestGetSealingWorkPostMerge(t *testing.T) { |
|
||||||
t.Parallel() |
|
||||||
local := new(params.ChainConfig) |
|
||||||
*local = *ethashChainConfig |
|
||||||
local.TerminalTotalDifficulty = big.NewInt(0) |
|
||||||
testGetSealingWork(t, local, ethash.NewFaker()) |
|
||||||
} |
|
||||||
|
|
||||||
func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) { |
|
||||||
defer engine.Close() |
|
||||||
|
|
||||||
w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) |
|
||||||
defer w.close() |
|
||||||
|
|
||||||
w.setExtra([]byte{0x01, 0x02}) |
|
||||||
|
|
||||||
w.skipSealHook = func(task *task) bool { |
|
||||||
return true |
|
||||||
} |
|
||||||
w.fullTaskHook = func() { |
|
||||||
time.Sleep(100 * time.Millisecond) |
|
||||||
} |
|
||||||
timestamp := uint64(time.Now().Unix()) |
|
||||||
assertBlock := func(block *types.Block, number uint64, coinbase common.Address, random common.Hash) { |
|
||||||
if block.Time() != timestamp { |
|
||||||
// Sometime the timestamp will be mutated if the timestamp
|
|
||||||
// is even smaller than parent block's. It's OK.
|
|
||||||
t.Logf("Invalid timestamp, want %d, get %d", timestamp, block.Time()) |
|
||||||
} |
|
||||||
_, isClique := engine.(*clique.Clique) |
|
||||||
if !isClique { |
|
||||||
if len(block.Extra()) != 2 { |
|
||||||
t.Error("Unexpected extra field") |
|
||||||
} |
|
||||||
if block.Coinbase() != coinbase { |
|
||||||
t.Errorf("Unexpected coinbase got %x want %x", block.Coinbase(), coinbase) |
|
||||||
} |
|
||||||
} else { |
|
||||||
if block.Coinbase() != (common.Address{}) { |
|
||||||
t.Error("Unexpected coinbase") |
|
||||||
} |
|
||||||
} |
|
||||||
if !isClique { |
|
||||||
if block.MixDigest() != random { |
|
||||||
t.Error("Unexpected mix digest") |
|
||||||
} |
|
||||||
} |
|
||||||
if block.Nonce() != 0 { |
|
||||||
t.Error("Unexpected block nonce") |
|
||||||
} |
|
||||||
if block.NumberU64() != number { |
|
||||||
t.Errorf("Mismatched block number, want %d got %d", number, block.NumberU64()) |
|
||||||
} |
|
||||||
} |
|
||||||
var cases = []struct { |
|
||||||
parent common.Hash |
|
||||||
coinbase common.Address |
|
||||||
random common.Hash |
|
||||||
expectNumber uint64 |
|
||||||
expectErr bool |
|
||||||
}{ |
|
||||||
{ |
|
||||||
b.chain.Genesis().Hash(), |
|
||||||
common.HexToAddress("0xdeadbeef"), |
|
||||||
common.HexToHash("0xcafebabe"), |
|
||||||
uint64(1), |
|
||||||
false, |
|
||||||
}, |
|
||||||
{ |
|
||||||
b.chain.CurrentBlock().Hash(), |
|
||||||
common.HexToAddress("0xdeadbeef"), |
|
||||||
common.HexToHash("0xcafebabe"), |
|
||||||
b.chain.CurrentBlock().Number.Uint64() + 1, |
|
||||||
false, |
|
||||||
}, |
|
||||||
{ |
|
||||||
b.chain.CurrentBlock().Hash(), |
|
||||||
common.Address{}, |
|
||||||
common.HexToHash("0xcafebabe"), |
|
||||||
b.chain.CurrentBlock().Number.Uint64() + 1, |
|
||||||
false, |
|
||||||
}, |
|
||||||
{ |
|
||||||
b.chain.CurrentBlock().Hash(), |
|
||||||
common.Address{}, |
|
||||||
common.Hash{}, |
|
||||||
b.chain.CurrentBlock().Number.Uint64() + 1, |
|
||||||
false, |
|
||||||
}, |
|
||||||
{ |
|
||||||
common.HexToHash("0xdeadbeef"), |
|
||||||
common.HexToAddress("0xdeadbeef"), |
|
||||||
common.HexToHash("0xcafebabe"), |
|
||||||
0, |
|
||||||
true, |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
// This API should work even when the automatic sealing is not enabled
|
|
||||||
for _, c := range cases { |
|
||||||
r := w.getSealingBlock(&generateParams{ |
|
||||||
parentHash: c.parent, |
|
||||||
timestamp: timestamp, |
|
||||||
coinbase: c.coinbase, |
|
||||||
random: c.random, |
|
||||||
withdrawals: nil, |
|
||||||
beaconRoot: nil, |
|
||||||
noTxs: false, |
|
||||||
forceTime: true, |
|
||||||
}) |
|
||||||
if c.expectErr { |
|
||||||
if r.err == nil { |
|
||||||
t.Error("Expect error but get nil") |
|
||||||
} |
|
||||||
} else { |
|
||||||
if r.err != nil { |
|
||||||
t.Errorf("Unexpected error %v", r.err) |
|
||||||
} |
|
||||||
assertBlock(r.block, c.expectNumber, c.coinbase, c.random) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// This API should work even when the automatic sealing is enabled
|
|
||||||
w.start() |
|
||||||
for _, c := range cases { |
|
||||||
r := w.getSealingBlock(&generateParams{ |
|
||||||
parentHash: c.parent, |
|
||||||
timestamp: timestamp, |
|
||||||
coinbase: c.coinbase, |
|
||||||
random: c.random, |
|
||||||
withdrawals: nil, |
|
||||||
beaconRoot: nil, |
|
||||||
noTxs: false, |
|
||||||
forceTime: true, |
|
||||||
}) |
|
||||||
if c.expectErr { |
|
||||||
if r.err == nil { |
|
||||||
t.Error("Expect error but get nil") |
|
||||||
} |
|
||||||
} else { |
|
||||||
if r.err != nil { |
|
||||||
t.Errorf("Unexpected error %v", r.err) |
|
||||||
} |
|
||||||
assertBlock(r.block, c.expectNumber, c.coinbase, c.random) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue