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