diff --git a/eth/api.go b/eth/api.go
deleted file mode 100644
index 44e934fd04..0000000000
--- a/eth/api.go
+++ /dev/null
@@ -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 .
-
-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()
-}
diff --git a/eth/api_backend.go b/eth/api_backend.go
deleted file mode 100644
index 84eb200095..0000000000
--- a/eth/api_backend.go
+++ /dev/null
@@ -1,417 +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 .
-
-package eth
-
-import (
- "context"
- "errors"
- "math/big"
- "time"
-
- "github.com/ethereum/go-ethereum"
- "github.com/ethereum/go-ethereum/accounts"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/consensus"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/bloombits"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/eth/gasprice"
- "github.com/ethereum/go-ethereum/eth/tracers"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/miner"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rpc"
-)
-
-// EthAPIBackend implements ethapi.Backend and tracers.Backend for full nodes
-type EthAPIBackend struct {
- extRPCEnabled bool
- allowUnprotectedTxs bool
- eth *Ethereum
- gpo *gasprice.Oracle
-}
-
-// ChainConfig returns the active chain configuration.
-func (b *EthAPIBackend) ChainConfig() *params.ChainConfig {
- return b.eth.blockchain.Config()
-}
-
-func (b *EthAPIBackend) CurrentBlock() *types.Header {
- return b.eth.blockchain.CurrentBlock()
-}
-
-func (b *EthAPIBackend) SetHead(number uint64) {
- b.eth.handler.downloader.Cancel()
- b.eth.blockchain.SetHead(number)
-}
-
-func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
- // Pending block is only known by the miner
- if number == rpc.PendingBlockNumber {
- block := b.eth.miner.PendingBlock()
- if block == nil {
- return nil, errors.New("pending block is not available")
- }
- return block.Header(), nil
- }
- // Otherwise resolve and return the block
- if number == rpc.LatestBlockNumber {
- return b.eth.blockchain.CurrentBlock(), nil
- }
- if number == rpc.FinalizedBlockNumber {
- block := b.eth.blockchain.CurrentFinalBlock()
- if block == nil {
- return nil, errors.New("finalized block not found")
- }
- return block, nil
- }
- if number == rpc.SafeBlockNumber {
- block := b.eth.blockchain.CurrentSafeBlock()
- if block == nil {
- return nil, errors.New("safe block not found")
- }
- return block, nil
- }
- return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil
-}
-
-func (b *EthAPIBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) {
- if blockNr, ok := blockNrOrHash.Number(); ok {
- return b.HeaderByNumber(ctx, blockNr)
- }
- if hash, ok := blockNrOrHash.Hash(); ok {
- header := b.eth.blockchain.GetHeaderByHash(hash)
- if header == nil {
- return nil, errors.New("header for hash not found")
- }
- if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
- return nil, errors.New("hash is not currently canonical")
- }
- return header, nil
- }
- return nil, errors.New("invalid arguments; neither block nor hash specified")
-}
-
-func (b *EthAPIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
- return b.eth.blockchain.GetHeaderByHash(hash), nil
-}
-
-func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
- // Pending block is only known by the miner
- if number == rpc.PendingBlockNumber {
- block := b.eth.miner.PendingBlock()
- if block == nil {
- return nil, errors.New("pending block is not available")
- }
- return block, nil
- }
- // Otherwise resolve and return the block
- if number == rpc.LatestBlockNumber {
- header := b.eth.blockchain.CurrentBlock()
- return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil
- }
- if number == rpc.FinalizedBlockNumber {
- header := b.eth.blockchain.CurrentFinalBlock()
- if header == nil {
- return nil, errors.New("finalized block not found")
- }
- return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil
- }
- if number == rpc.SafeBlockNumber {
- header := b.eth.blockchain.CurrentSafeBlock()
- if header == nil {
- return nil, errors.New("safe block not found")
- }
- return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil
- }
- return b.eth.blockchain.GetBlockByNumber(uint64(number)), nil
-}
-
-func (b *EthAPIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
- return b.eth.blockchain.GetBlockByHash(hash), nil
-}
-
-// GetBody returns body of a block. It does not resolve special block numbers.
-func (b *EthAPIBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) {
- if number < 0 || hash == (common.Hash{}) {
- return nil, errors.New("invalid arguments; expect hash and no special block numbers")
- }
- if body := b.eth.blockchain.GetBody(hash); body != nil {
- return body, nil
- }
- return nil, errors.New("block body not found")
-}
-
-func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) {
- if blockNr, ok := blockNrOrHash.Number(); ok {
- return b.BlockByNumber(ctx, blockNr)
- }
- if hash, ok := blockNrOrHash.Hash(); ok {
- header := b.eth.blockchain.GetHeaderByHash(hash)
- if header == nil {
- return nil, errors.New("header for hash not found")
- }
- if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
- return nil, errors.New("hash is not currently canonical")
- }
- block := b.eth.blockchain.GetBlock(hash, header.Number.Uint64())
- if block == nil {
- return nil, errors.New("header found, but block body is missing")
- }
- return block, nil
- }
- return nil, errors.New("invalid arguments; neither block nor hash specified")
-}
-
-func (b *EthAPIBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
- return b.eth.miner.PendingBlockAndReceipts()
-}
-
-func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
- // Pending state is only known by the miner
- if number == rpc.PendingBlockNumber {
- block, state := b.eth.miner.Pending()
- if block == nil || state == nil {
- return nil, nil, errors.New("pending state is not available")
- }
- return state, block.Header(), nil
- }
- // Otherwise resolve the block number and return its state
- header, err := b.HeaderByNumber(ctx, number)
- if err != nil {
- return nil, nil, err
- }
- if header == nil {
- return nil, nil, errors.New("header not found")
- }
- stateDb, err := b.eth.BlockChain().StateAt(header.Root)
- if err != nil {
- return nil, nil, err
- }
- return stateDb, header, nil
-}
-
-func (b *EthAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) {
- if blockNr, ok := blockNrOrHash.Number(); ok {
- return b.StateAndHeaderByNumber(ctx, blockNr)
- }
- if hash, ok := blockNrOrHash.Hash(); ok {
- header, err := b.HeaderByHash(ctx, hash)
- if err != nil {
- return nil, nil, err
- }
- if header == nil {
- return nil, nil, errors.New("header for hash not found")
- }
- if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
- return nil, nil, errors.New("hash is not currently canonical")
- }
- stateDb, err := b.eth.BlockChain().StateAt(header.Root)
- if err != nil {
- return nil, nil, err
- }
- return stateDb, header, nil
- }
- return nil, nil, errors.New("invalid arguments; neither block nor hash specified")
-}
-
-func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
- return b.eth.blockchain.GetReceiptsByHash(hash), nil
-}
-
-func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) {
- return rawdb.ReadLogs(b.eth.chainDb, hash, number), nil
-}
-
-func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int {
- if header := b.eth.blockchain.GetHeaderByHash(hash); header != nil {
- return b.eth.blockchain.GetTd(hash, header.Number.Uint64())
- }
- return nil
-}
-
-func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM {
- if vmConfig == nil {
- vmConfig = b.eth.blockchain.GetVMConfig()
- }
- txContext := core.NewEVMTxContext(msg)
- var context vm.BlockContext
- if blockCtx != nil {
- context = *blockCtx
- } else {
- context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil)
- }
- return vm.NewEVM(context, txContext, state, b.eth.blockchain.Config(), *vmConfig)
-}
-
-func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
- return b.eth.BlockChain().SubscribeRemovedLogsEvent(ch)
-}
-
-func (b *EthAPIBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription {
- return b.eth.miner.SubscribePendingLogs(ch)
-}
-
-func (b *EthAPIBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
- return b.eth.BlockChain().SubscribeChainEvent(ch)
-}
-
-func (b *EthAPIBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
- return b.eth.BlockChain().SubscribeChainHeadEvent(ch)
-}
-
-func (b *EthAPIBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription {
- return b.eth.BlockChain().SubscribeChainSideEvent(ch)
-}
-
-func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
- return b.eth.BlockChain().SubscribeLogsEvent(ch)
-}
-
-func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
- return b.eth.txPool.Add([]*types.Transaction{signedTx}, true, false)[0]
-}
-
-func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) {
- pending := b.eth.txPool.Pending(false)
- var txs types.Transactions
- for _, batch := range pending {
- for _, lazy := range batch {
- if tx := lazy.Resolve(); tx != nil {
- txs = append(txs, tx)
- }
- }
- }
- return txs, nil
-}
-
-func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction {
- return b.eth.txPool.Get(hash)
-}
-
-func (b *EthAPIBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
- tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.eth.ChainDb(), txHash)
- return tx, blockHash, blockNumber, index, nil
-}
-
-func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
- return b.eth.txPool.Nonce(addr), nil
-}
-
-func (b *EthAPIBackend) Stats() (runnable int, blocked int) {
- return b.eth.txPool.Stats()
-}
-
-func (b *EthAPIBackend) TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) {
- return b.eth.txPool.Content()
-}
-
-func (b *EthAPIBackend) TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) {
- return b.eth.txPool.ContentFrom(addr)
-}
-
-func (b *EthAPIBackend) TxPool() *txpool.TxPool {
- return b.eth.txPool
-}
-
-func (b *EthAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
- return b.eth.txPool.SubscribeTransactions(ch, true)
-}
-
-func (b *EthAPIBackend) SyncProgress() ethereum.SyncProgress {
- return b.eth.Downloader().Progress()
-}
-
-func (b *EthAPIBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
- return b.gpo.SuggestTipCap(ctx)
-}
-
-func (b *EthAPIBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock *big.Int, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) {
- return b.gpo.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles)
-}
-
-func (b *EthAPIBackend) ChainDb() ethdb.Database {
- return b.eth.ChainDb()
-}
-
-func (b *EthAPIBackend) EventMux() *event.TypeMux {
- return b.eth.EventMux()
-}
-
-func (b *EthAPIBackend) AccountManager() *accounts.Manager {
- return b.eth.AccountManager()
-}
-
-func (b *EthAPIBackend) ExtRPCEnabled() bool {
- return b.extRPCEnabled
-}
-
-func (b *EthAPIBackend) UnprotectedAllowed() bool {
- return b.allowUnprotectedTxs
-}
-
-func (b *EthAPIBackend) RPCGasCap() uint64 {
- return b.eth.config.RPCGasCap
-}
-
-func (b *EthAPIBackend) RPCEVMTimeout() time.Duration {
- return b.eth.config.RPCEVMTimeout
-}
-
-func (b *EthAPIBackend) RPCTxFeeCap() float64 {
- return b.eth.config.RPCTxFeeCap
-}
-
-func (b *EthAPIBackend) BloomStatus() (uint64, uint64) {
- sections, _, _ := b.eth.bloomIndexer.Sections()
- return params.BloomBitsBlocks, sections
-}
-
-func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) {
- for i := 0; i < bloomFilterThreads; i++ {
- go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests)
- }
-}
-
-func (b *EthAPIBackend) Engine() consensus.Engine {
- return b.eth.engine
-}
-
-func (b *EthAPIBackend) CurrentHeader() *types.Header {
- return b.eth.blockchain.CurrentHeader()
-}
-
-func (b *EthAPIBackend) Miner() *miner.Miner {
- return b.eth.Miner()
-}
-
-func (b *EthAPIBackend) StartMining() error {
- return b.eth.StartMining()
-}
-
-func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) {
- return b.eth.stateAtBlock(ctx, block, reexec, base, readOnly, preferDisk)
-}
-
-func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
- return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
-}
diff --git a/eth/api_debug_test.go b/eth/api_debug_test.go
deleted file mode 100644
index 184b90dd09..0000000000
--- a/eth/api_debug_test.go
+++ /dev/null
@@ -1,222 +0,0 @@
-// Copyright 2017 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 .
-
-package eth
-
-import (
- "bytes"
- "fmt"
- "math/big"
- "reflect"
- "strings"
- "testing"
-
- "github.com/davecgh/go-spew/spew"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/trie"
- "golang.org/x/exp/slices"
-)
-
-var dumper = spew.ConfigState{Indent: " "}
-
-func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, start common.Hash, requestedNum int, expectedNum int) state.Dump {
- result := statedb.RawDump(&state.DumpConfig{
- SkipCode: true,
- SkipStorage: true,
- OnlyWithAddresses: false,
- Start: start.Bytes(),
- Max: uint64(requestedNum),
- })
-
- if len(result.Accounts) != expectedNum {
- t.Fatalf("expected %d results, got %d", expectedNum, len(result.Accounts))
- }
- for addr, acc := range result.Accounts {
- if strings.HasSuffix(addr, "pre") || acc.Address == nil {
- t.Fatalf("account without prestate (address) returned: %v", addr)
- }
- if !statedb.Exist(*acc.Address) {
- t.Fatalf("account not found in state %s", acc.Address.Hex())
- }
- }
- return result
-}
-
-func TestAccountRange(t *testing.T) {
- t.Parallel()
-
- var (
- statedb = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), &trie.Config{Preimages: true})
- sdb, _ = state.New(types.EmptyRootHash, statedb, nil)
- addrs = [AccountRangeMaxResults * 2]common.Address{}
- m = map[common.Address]bool{}
- )
-
- for i := range addrs {
- hash := common.HexToHash(fmt.Sprintf("%x", i))
- addr := common.BytesToAddress(crypto.Keccak256Hash(hash.Bytes()).Bytes())
- addrs[i] = addr
- sdb.SetBalance(addrs[i], big.NewInt(1))
- if _, ok := m[addr]; ok {
- t.Fatalf("bad")
- } else {
- m[addr] = true
- }
- }
- root, _ := sdb.Commit(0, true)
- sdb, _ = state.New(root, statedb, nil)
-
- trie, err := statedb.OpenTrie(root)
- if err != nil {
- t.Fatal(err)
- }
- accountRangeTest(t, &trie, sdb, common.Hash{}, AccountRangeMaxResults/2, AccountRangeMaxResults/2)
- // test pagination
- firstResult := accountRangeTest(t, &trie, sdb, common.Hash{}, AccountRangeMaxResults, AccountRangeMaxResults)
- secondResult := accountRangeTest(t, &trie, sdb, common.BytesToHash(firstResult.Next), AccountRangeMaxResults, AccountRangeMaxResults)
-
- hList := make([]common.Hash, 0)
- for addr1, acc := range firstResult.Accounts {
- // If address is non-available, then it makes no sense to compare
- // them as they might be two different accounts.
- if acc.Address == nil {
- continue
- }
- if _, duplicate := secondResult.Accounts[addr1]; duplicate {
- t.Fatalf("pagination test failed: results should not overlap")
- }
- hList = append(hList, crypto.Keccak256Hash(acc.Address.Bytes()))
- }
- // Test to see if it's possible to recover from the middle of the previous
- // set and get an even split between the first and second sets.
- slices.SortFunc(hList, common.Hash.Cmp)
- middleH := hList[AccountRangeMaxResults/2]
- middleResult := accountRangeTest(t, &trie, sdb, middleH, AccountRangeMaxResults, AccountRangeMaxResults)
- missing, infirst, insecond := 0, 0, 0
- for h := range middleResult.Accounts {
- if _, ok := firstResult.Accounts[h]; ok {
- infirst++
- } else if _, ok := secondResult.Accounts[h]; ok {
- insecond++
- } else {
- missing++
- }
- }
- if missing != 0 {
- t.Fatalf("%d hashes in the 'middle' set were neither in the first not the second set", missing)
- }
- if infirst != AccountRangeMaxResults/2 {
- t.Fatalf("Imbalance in the number of first-test results: %d != %d", infirst, AccountRangeMaxResults/2)
- }
- if insecond != AccountRangeMaxResults/2 {
- t.Fatalf("Imbalance in the number of second-test results: %d != %d", insecond, AccountRangeMaxResults/2)
- }
-}
-
-func TestEmptyAccountRange(t *testing.T) {
- t.Parallel()
-
- var (
- statedb = state.NewDatabase(rawdb.NewMemoryDatabase())
- st, _ = state.New(types.EmptyRootHash, statedb, nil)
- )
- // Commit(although nothing to flush) and re-init the statedb
- st.Commit(0, true)
- st, _ = state.New(types.EmptyRootHash, statedb, nil)
-
- results := st.RawDump(&state.DumpConfig{
- SkipCode: true,
- SkipStorage: true,
- OnlyWithAddresses: true,
- Max: uint64(AccountRangeMaxResults),
- })
- if bytes.Equal(results.Next, (common.Hash{}).Bytes()) {
- t.Fatalf("Empty results should not return a second page")
- }
- if len(results.Accounts) != 0 {
- t.Fatalf("Empty state should not return addresses: %v", results.Accounts)
- }
-}
-
-func TestStorageRangeAt(t *testing.T) {
- t.Parallel()
-
- // Create a state where account 0x010000... has a few storage entries.
- var (
- db = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), &trie.Config{Preimages: true})
- sdb, _ = state.New(types.EmptyRootHash, db, nil)
- addr = common.Address{0x01}
- keys = []common.Hash{ // hashes of Keys of storage
- common.HexToHash("340dd630ad21bf010b4e676dbfa9ba9a02175262d1fa356232cfde6cb5b47ef2"),
- common.HexToHash("426fcb404ab2d5d8e61a3d918108006bbb0a9be65e92235bb10eefbdb6dcd053"),
- common.HexToHash("48078cfed56339ea54962e72c37c7f588fc4f8e5bc173827ba75cb10a63a96a5"),
- common.HexToHash("5723d2c3a83af9b735e3b7f21531e5623d183a9095a56604ead41f3582fdfb75"),
- }
- storage = storageMap{
- keys[0]: {Key: &common.Hash{0x02}, Value: common.Hash{0x01}},
- keys[1]: {Key: &common.Hash{0x04}, Value: common.Hash{0x02}},
- keys[2]: {Key: &common.Hash{0x01}, Value: common.Hash{0x03}},
- keys[3]: {Key: &common.Hash{0x03}, Value: common.Hash{0x04}},
- }
- )
- for _, entry := range storage {
- sdb.SetState(addr, *entry.Key, entry.Value)
- }
- root, _ := sdb.Commit(0, false)
- sdb, _ = state.New(root, db, nil)
-
- // Check a few combinations of limit and start/end.
- tests := []struct {
- start []byte
- limit int
- want StorageRangeResult
- }{
- {
- start: []byte{}, limit: 0,
- want: StorageRangeResult{storageMap{}, &keys[0]},
- },
- {
- start: []byte{}, limit: 100,
- want: StorageRangeResult{storage, nil},
- },
- {
- start: []byte{}, limit: 2,
- want: StorageRangeResult{storageMap{keys[0]: storage[keys[0]], keys[1]: storage[keys[1]]}, &keys[2]},
- },
- {
- start: []byte{0x00}, limit: 4,
- want: StorageRangeResult{storage, nil},
- },
- {
- start: []byte{0x40}, limit: 2,
- want: StorageRangeResult{storageMap{keys[1]: storage[keys[1]], keys[2]: storage[keys[2]]}, &keys[3]},
- },
- }
- for _, test := range tests {
- result, err := storageRangeAt(sdb, root, addr, test.start, test.limit)
- if err != nil {
- t.Error(err)
- }
- if !reflect.DeepEqual(result, test.want) {
- t.Fatalf("wrong result for range %#x.., limit %d:\ngot %s\nwant %s",
- test.start, test.limit, dumper.Sdump(result), dumper.Sdump(&test.want))
- }
- }
-}
diff --git a/eth/api_miner.go b/eth/api_miner.go
deleted file mode 100644
index 477531d494..0000000000
--- a/eth/api_miner.go
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2023 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 .
-
-package eth
-
-import (
- "math/big"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/hexutil"
-)
-
-// MinerAPI provides an API to control the miner.
-type MinerAPI struct {
- e *Ethereum
-}
-
-// NewMinerAPI create a new MinerAPI instance.
-func NewMinerAPI(e *Ethereum) *MinerAPI {
- return &MinerAPI{e}
-}
-
-// Start starts the miner with the given number of threads. If threads is nil,
-// the number of workers started is equal to the number of logical CPUs that are
-// usable by this process. If mining is already running, this method adjust the
-// number of threads allowed to use and updates the minimum price required by the
-// transaction pool.
-func (api *MinerAPI) Start() error {
- return api.e.StartMining()
-}
-
-// Stop terminates the miner, both at the consensus engine level as well as at
-// the block creation level.
-func (api *MinerAPI) Stop() {
- api.e.StopMining()
-}
-
-// SetExtra sets the extra data string that is included when this miner mines a block.
-func (api *MinerAPI) SetExtra(extra string) (bool, error) {
- if err := api.e.Miner().SetExtra([]byte(extra)); err != nil {
- return false, err
- }
- return true, nil
-}
-
-// SetGasPrice sets the minimum accepted gas price for the miner.
-func (api *MinerAPI) SetGasPrice(gasPrice hexutil.Big) bool {
- api.e.lock.Lock()
- api.e.gasPrice = (*big.Int)(&gasPrice)
- api.e.lock.Unlock()
-
- api.e.txPool.SetGasTip((*big.Int)(&gasPrice))
- return true
-}
-
-// SetGasLimit sets the gaslimit to target towards during mining.
-func (api *MinerAPI) SetGasLimit(gasLimit hexutil.Uint64) bool {
- api.e.Miner().SetGasCeil(uint64(gasLimit))
- return true
-}
-
-// SetEtherbase sets the etherbase of the miner.
-func (api *MinerAPI) SetEtherbase(etherbase common.Address) bool {
- api.e.SetEtherbase(etherbase)
- return true
-}
-
-// SetRecommitInterval updates the interval for miner sealing work recommitting.
-func (api *MinerAPI) SetRecommitInterval(interval int) {
- api.e.Miner().SetRecommitInterval(time.Duration(interval) * time.Millisecond)
-}
diff --git a/eth/backend.go b/eth/backend.go
deleted file mode 100644
index 774ffaf248..0000000000
--- a/eth/backend.go
+++ /dev/null
@@ -1,552 +0,0 @@
-// Copyright 2014 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 .
-
-// Package eth implements the Ethereum protocol.
-package eth
-
-import (
- "errors"
- "fmt"
- "math/big"
- "runtime"
- "sync"
-
- "github.com/ethereum/go-ethereum/accounts"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/consensus"
- "github.com/ethereum/go-ethereum/consensus/beacon"
- "github.com/ethereum/go-ethereum/consensus/clique"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/bloombits"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/state/pruner"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/txpool/blobpool"
- "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/eth/downloader"
- "github.com/ethereum/go-ethereum/eth/ethconfig"
- "github.com/ethereum/go-ethereum/eth/gasprice"
- "github.com/ethereum/go-ethereum/eth/protocols/eth"
- "github.com/ethereum/go-ethereum/eth/protocols/snap"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/internal/ethapi"
- "github.com/ethereum/go-ethereum/internal/shutdowncheck"
- "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/dnsdisc"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/rpc"
-)
-
-// Config contains the configuration options of the ETH protocol.
-// Deprecated: use ethconfig.Config instead.
-type Config = ethconfig.Config
-
-// Ethereum implements the Ethereum full node service.
-type Ethereum struct {
- config *ethconfig.Config
-
- // Handlers
- txPool *txpool.TxPool
-
- blockchain *core.BlockChain
- handler *handler
- ethDialCandidates enode.Iterator
- snapDialCandidates enode.Iterator
- merger *consensus.Merger
-
- // DB interfaces
- chainDb ethdb.Database // Block chain database
-
- eventMux *event.TypeMux
- engine consensus.Engine
- accountManager *accounts.Manager
-
- bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
- bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports
- closeBloomHandler chan struct{}
-
- APIBackend *EthAPIBackend
-
- miner *miner.Miner
- gasPrice *big.Int
- etherbase common.Address
-
- networkID uint64
- netRPCService *ethapi.NetAPI
-
- p2pServer *p2p.Server
-
- lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase)
-
- shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully
-}
-
-// New creates a new Ethereum object (including the
-// initialisation of the common Ethereum object)
-func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
- // Ensure configuration values are compatible and sane
- if config.SyncMode == downloader.LightSync {
- return nil, errors.New("can't run eth.Ethereum in light sync mode, light mode has been deprecated")
- }
- if !config.SyncMode.IsValid() {
- return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode)
- }
- if config.Miner.GasPrice == nil || config.Miner.GasPrice.Cmp(common.Big0) <= 0 {
- log.Warn("Sanitizing invalid miner gas price", "provided", config.Miner.GasPrice, "updated", ethconfig.Defaults.Miner.GasPrice)
- config.Miner.GasPrice = new(big.Int).Set(ethconfig.Defaults.Miner.GasPrice)
- }
- if config.NoPruning && config.TrieDirtyCache > 0 {
- if config.SnapshotCache > 0 {
- config.TrieCleanCache += config.TrieDirtyCache * 3 / 5
- config.SnapshotCache += config.TrieDirtyCache * 2 / 5
- } else {
- config.TrieCleanCache += config.TrieDirtyCache
- }
- config.TrieDirtyCache = 0
- }
- log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024)
-
- // Assemble the Ethereum object
- chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false)
- if err != nil {
- return nil, err
- }
- scheme, err := rawdb.ParseStateScheme(config.StateScheme, chainDb)
- if err != nil {
- return nil, err
- }
- // Try to recover offline state pruning only in hash-based.
- if scheme == rawdb.HashScheme {
- if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb); err != nil {
- log.Error("Failed to recover state", "error", err)
- }
- }
- // Transfer mining-related config to the ethash config.
- chainConfig, err := core.LoadChainConfig(chainDb, config.Genesis)
- if err != nil {
- return nil, err
- }
- engine, err := ethconfig.CreateConsensusEngine(chainConfig, chainDb)
- if err != nil {
- return nil, err
- }
- networkID := config.NetworkId
- if networkID == 0 {
- networkID = chainConfig.ChainID.Uint64()
- }
- eth := &Ethereum{
- config: config,
- merger: consensus.NewMerger(chainDb),
- chainDb: chainDb,
- eventMux: stack.EventMux(),
- accountManager: stack.AccountManager(),
- engine: engine,
- closeBloomHandler: make(chan struct{}),
- networkID: networkID,
- gasPrice: config.Miner.GasPrice,
- etherbase: config.Miner.Etherbase,
- bloomRequests: make(chan chan *bloombits.Retrieval),
- bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms),
- p2pServer: stack.Server(),
- shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb),
- }
- bcVersion := rawdb.ReadDatabaseVersion(chainDb)
- var dbVer = ""
- if bcVersion != nil {
- dbVer = fmt.Sprintf("%d", *bcVersion)
- }
- log.Info("Initialising Ethereum protocol", "network", networkID, "dbversion", dbVer)
-
- if !config.SkipBcVersionCheck {
- if bcVersion != nil && *bcVersion > core.BlockChainVersion {
- return nil, fmt.Errorf("database version is v%d, Geth %s only supports v%d", *bcVersion, params.VersionWithMeta, core.BlockChainVersion)
- } else if bcVersion == nil || *bcVersion < core.BlockChainVersion {
- if bcVersion != nil { // only print warning on upgrade, not on init
- log.Warn("Upgrade blockchain database version", "from", dbVer, "to", core.BlockChainVersion)
- }
- rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion)
- }
- }
- var (
- vmConfig = vm.Config{
- EnablePreimageRecording: config.EnablePreimageRecording,
- }
- cacheConfig = &core.CacheConfig{
- TrieCleanLimit: config.TrieCleanCache,
- TrieCleanNoPrefetch: config.NoPrefetch,
- TrieDirtyLimit: config.TrieDirtyCache,
- TrieDirtyDisabled: config.NoPruning,
- TrieTimeLimit: config.TrieTimeout,
- SnapshotLimit: config.SnapshotCache,
- Preimages: config.Preimages,
- StateHistory: config.StateHistory,
- StateScheme: scheme,
- }
- )
- // Override the chain config with provided settings.
- var overrides core.ChainOverrides
- if config.OverrideCancun != nil {
- overrides.OverrideCancun = config.OverrideCancun
- }
- if config.OverrideVerkle != nil {
- overrides.OverrideVerkle = config.OverrideVerkle
- }
- eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, eth.engine, vmConfig, eth.shouldPreserve, &config.TransactionHistory)
- if err != nil {
- return nil, err
- }
- eth.bloomIndexer.Start(eth.blockchain)
-
- if config.BlobPool.Datadir != "" {
- config.BlobPool.Datadir = stack.ResolvePath(config.BlobPool.Datadir)
- }
- blobPool := blobpool.New(config.BlobPool, eth.blockchain)
-
- if config.TxPool.Journal != "" {
- config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal)
- }
- legacyPool := legacypool.New(config.TxPool, eth.blockchain)
-
- eth.txPool, err = txpool.New(new(big.Int).SetUint64(config.TxPool.PriceLimit), eth.blockchain, []txpool.SubPool{legacyPool, blobPool})
- if err != nil {
- return nil, err
- }
- // Permit the downloader to use the trie cache allowance during fast sync
- cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit
- if eth.handler, err = newHandler(&handlerConfig{
- Database: chainDb,
- Chain: eth.blockchain,
- TxPool: eth.txPool,
- Merger: eth.merger,
- Network: networkID,
- Sync: config.SyncMode,
- BloomCache: uint64(cacheLimit),
- EventMux: eth.eventMux,
- RequiredBlocks: config.RequiredBlocks,
- }); err != nil {
- return nil, err
- }
-
- eth.miner = miner.New(eth, &config.Miner, eth.blockchain.Config(), eth.EventMux(), eth.engine, eth.isLocalBlock)
- eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
-
- eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil}
- if eth.APIBackend.allowUnprotectedTxs {
- log.Info("Unprotected transactions allowed")
- }
- gpoParams := config.GPO
- if gpoParams.Default == nil {
- gpoParams.Default = config.Miner.GasPrice
- }
- eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams)
-
- // Setup DNS discovery iterators.
- dnsclient := dnsdisc.NewClient(dnsdisc.Config{})
- eth.ethDialCandidates, err = dnsclient.NewIterator(eth.config.EthDiscoveryURLs...)
- if err != nil {
- return nil, err
- }
- eth.snapDialCandidates, err = dnsclient.NewIterator(eth.config.SnapDiscoveryURLs...)
- if err != nil {
- return nil, err
- }
-
- // Start the RPC service
- eth.netRPCService = ethapi.NewNetAPI(eth.p2pServer, networkID)
-
- // Register the backend on the node
- stack.RegisterAPIs(eth.APIs())
- stack.RegisterProtocols(eth.Protocols())
- stack.RegisterLifecycle(eth)
-
- // Successful startup; push a marker and check previous unclean shutdowns.
- eth.shutdownTracker.MarkStartup()
-
- return eth, nil
-}
-
-func makeExtraData(extra []byte) []byte {
- if len(extra) == 0 {
- // create default extradata
- extra, _ = rlp.EncodeToBytes([]interface{}{
- uint(params.VersionMajor<<16 | params.VersionMinor<<8 | params.VersionPatch),
- "geth",
- runtime.Version(),
- runtime.GOOS,
- })
- }
- if uint64(len(extra)) > params.MaximumExtraDataSize {
- log.Warn("Miner extra data exceed limit", "extra", hexutil.Bytes(extra), "limit", params.MaximumExtraDataSize)
- extra = nil
- }
- return extra
-}
-
-// APIs return the collection of RPC services the ethereum package offers.
-// NOTE, some of these services probably need to be moved to somewhere else.
-func (s *Ethereum) APIs() []rpc.API {
- apis := ethapi.GetAPIs(s.APIBackend)
-
- // Append any APIs exposed explicitly by the consensus engine
- apis = append(apis, s.engine.APIs(s.BlockChain())...)
-
- // Append all the local APIs and return
- return append(apis, []rpc.API{
- {
- Namespace: "eth",
- Service: NewEthereumAPI(s),
- }, {
- Namespace: "miner",
- Service: NewMinerAPI(s),
- }, {
- Namespace: "eth",
- Service: downloader.NewDownloaderAPI(s.handler.downloader, s.eventMux),
- }, {
- Namespace: "admin",
- Service: NewAdminAPI(s),
- }, {
- Namespace: "debug",
- Service: NewDebugAPI(s),
- }, {
- Namespace: "net",
- Service: s.netRPCService,
- },
- }...)
-}
-
-func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) {
- s.blockchain.ResetWithGenesisBlock(gb)
-}
-
-func (s *Ethereum) Etherbase() (eb common.Address, err error) {
- s.lock.RLock()
- etherbase := s.etherbase
- s.lock.RUnlock()
-
- if etherbase != (common.Address{}) {
- return etherbase, nil
- }
- return common.Address{}, errors.New("etherbase must be explicitly specified")
-}
-
-// isLocalBlock checks whether the specified block is mined
-// by local miner accounts.
-//
-// We regard two types of accounts as local miner account: etherbase
-// and accounts specified via `txpool.locals` flag.
-func (s *Ethereum) isLocalBlock(header *types.Header) bool {
- author, err := s.engine.Author(header)
- if err != nil {
- log.Warn("Failed to retrieve block author", "number", header.Number.Uint64(), "hash", header.Hash(), "err", err)
- return false
- }
- // Check whether the given address is etherbase.
- s.lock.RLock()
- etherbase := s.etherbase
- s.lock.RUnlock()
- if author == etherbase {
- return true
- }
- // Check whether the given address is specified by `txpool.local`
- // CLI flag.
- for _, account := range s.config.TxPool.Locals {
- if account == author {
- return true
- }
- }
- return false
-}
-
-// shouldPreserve checks whether we should preserve the given block
-// during the chain reorg depending on whether the author of block
-// is a local account.
-func (s *Ethereum) shouldPreserve(header *types.Header) bool {
- // The reason we need to disable the self-reorg preserving for clique
- // is it can be probable to introduce a deadlock.
- //
- // e.g. If there are 7 available signers
- //
- // r1 A
- // r2 B
- // r3 C
- // r4 D
- // r5 A [X] F G
- // r6 [X]
- //
- // In the round5, the in-turn signer E is offline, so the worst case
- // is A, F and G sign the block of round5 and reject the block of opponents
- // and in the round6, the last available signer B is offline, the whole
- // network is stuck.
- if _, ok := s.engine.(*clique.Clique); ok {
- return false
- }
- return s.isLocalBlock(header)
-}
-
-// SetEtherbase sets the mining reward address.
-func (s *Ethereum) SetEtherbase(etherbase common.Address) {
- s.lock.Lock()
- s.etherbase = etherbase
- s.lock.Unlock()
-
- s.miner.SetEtherbase(etherbase)
-}
-
-// StartMining starts the miner with the given number of CPU threads. If mining
-// is already running, this method adjust the number of threads allowed to use
-// and updates the minimum price required by the transaction pool.
-func (s *Ethereum) StartMining() error {
- // If the miner was not running, initialize it
- if !s.IsMining() {
- // Propagate the initial price point to the transaction pool
- s.lock.RLock()
- price := s.gasPrice
- s.lock.RUnlock()
- s.txPool.SetGasTip(price)
-
- // Configure the local mining address
- eb, err := s.Etherbase()
- if err != nil {
- log.Error("Cannot start mining without etherbase", "err", err)
- return fmt.Errorf("etherbase missing: %v", err)
- }
- var cli *clique.Clique
- if c, ok := s.engine.(*clique.Clique); ok {
- cli = c
- } else if cl, ok := s.engine.(*beacon.Beacon); ok {
- if c, ok := cl.InnerEngine().(*clique.Clique); ok {
- cli = c
- }
- }
- if cli != nil {
- wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
- if wallet == nil || err != nil {
- log.Error("Etherbase account unavailable locally", "err", err)
- return fmt.Errorf("signer missing: %v", err)
- }
- cli.Authorize(eb, wallet.SignData)
- }
- // If mining is started, we can disable the transaction rejection mechanism
- // introduced to speed sync times.
- s.handler.enableSyncedFeatures()
-
- go s.miner.Start()
- }
- return nil
-}
-
-// StopMining terminates the miner, both at the consensus engine level as well as
-// at the block creation level.
-func (s *Ethereum) StopMining() {
- // Update the thread count within the consensus engine
- type threaded interface {
- SetThreads(threads int)
- }
- if th, ok := s.engine.(threaded); ok {
- th.SetThreads(-1)
- }
- // Stop the block creating itself
- s.miner.Stop()
-}
-
-func (s *Ethereum) IsMining() bool { return s.miner.Mining() }
-func (s *Ethereum) Miner() *miner.Miner { return s.miner }
-
-func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager }
-func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain }
-func (s *Ethereum) TxPool() *txpool.TxPool { return s.txPool }
-func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux }
-func (s *Ethereum) Engine() consensus.Engine { return s.engine }
-func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb }
-func (s *Ethereum) IsListening() bool { return true } // Always listening
-func (s *Ethereum) Downloader() *downloader.Downloader { return s.handler.downloader }
-func (s *Ethereum) Synced() bool { return s.handler.synced.Load() }
-func (s *Ethereum) SetSynced() { s.handler.enableSyncedFeatures() }
-func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning }
-func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer }
-func (s *Ethereum) Merger() *consensus.Merger { return s.merger }
-func (s *Ethereum) SyncMode() downloader.SyncMode {
- mode, _ := s.handler.chainSync.modeAndLocalHead()
- return mode
-}
-
-// Protocols returns all the currently configured
-// network protocols to start.
-func (s *Ethereum) Protocols() []p2p.Protocol {
- protos := eth.MakeProtocols((*ethHandler)(s.handler), s.networkID, s.ethDialCandidates)
- if s.config.SnapshotCache > 0 {
- protos = append(protos, snap.MakeProtocols((*snapHandler)(s.handler), s.snapDialCandidates)...)
- }
- return protos
-}
-
-// Start implements node.Lifecycle, starting all internal goroutines needed by the
-// Ethereum protocol implementation.
-func (s *Ethereum) Start() error {
- eth.StartENRUpdater(s.blockchain, s.p2pServer.LocalNode())
-
- // Start the bloom bits servicing goroutines
- s.startBloomHandlers(params.BloomBitsBlocks)
-
- // Regularly update shutdown marker
- s.shutdownTracker.Start()
-
- // Figure out a max peers count based on the server limits
- maxPeers := s.p2pServer.MaxPeers
- if s.config.LightServ > 0 {
- if s.config.LightPeers >= s.p2pServer.MaxPeers {
- return fmt.Errorf("invalid peer config: light peer count (%d) >= total peer count (%d)", s.config.LightPeers, s.p2pServer.MaxPeers)
- }
- maxPeers -= s.config.LightPeers
- }
- // Start the networking layer and the light server if requested
- s.handler.Start(maxPeers)
- return nil
-}
-
-// Stop implements node.Lifecycle, terminating all internal goroutines used by the
-// Ethereum protocol.
-func (s *Ethereum) Stop() error {
- // Stop all the peer-related stuff first.
- s.ethDialCandidates.Close()
- s.snapDialCandidates.Close()
- s.handler.Stop()
-
- // Then stop everything else.
- s.bloomIndexer.Close()
- close(s.closeBloomHandler)
- s.txPool.Close()
- s.miner.Close()
- s.blockchain.Stop()
- s.engine.Close()
-
- // Clean shutdown marker as the last thing before closing db
- s.shutdownTracker.Stop()
-
- s.chainDb.Close()
- s.eventMux.Stop()
-
- return nil
-}
diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go
deleted file mode 100644
index 37b0248f28..0000000000
--- a/eth/catalyst/api.go
+++ /dev/null
@@ -1,837 +0,0 @@
-// Copyright 2021 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 .
-
-// Package catalyst implements the temporary eth1/eth2 RPC integration.
-package catalyst
-
-import (
- "errors"
- "fmt"
- "math/big"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/beacon/engine"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/eth"
- "github.com/ethereum/go-ethereum/eth/downloader"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/miner"
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/rpc"
-)
-
-// Register adds the engine API to the full node.
-func Register(stack *node.Node, backend *eth.Ethereum) error {
- log.Warn("Engine API enabled", "protocol", "eth")
- stack.RegisterAPIs([]rpc.API{
- {
- Namespace: "engine",
- Service: NewConsensusAPI(backend),
- Authenticated: true,
- },
- })
- return nil
-}
-
-const (
- // invalidBlockHitEviction is the number of times an invalid block can be
- // referenced in forkchoice update or new payload before it is attempted
- // to be reprocessed again.
- invalidBlockHitEviction = 128
-
- // invalidTipsetsCap is the max number of recent block hashes tracked that
- // have lead to some bad ancestor block. It's just an OOM protection.
- invalidTipsetsCap = 512
-
- // beaconUpdateStartupTimeout is the time to wait for a beacon client to get
- // attached before starting to issue warnings.
- beaconUpdateStartupTimeout = 30 * time.Second
-
- // beaconUpdateConsensusTimeout is the max time allowed for a beacon client
- // to send a consensus update before it's considered offline and the user is
- // warned.
- beaconUpdateConsensusTimeout = 2 * time.Minute
-
- // beaconUpdateWarnFrequency is the frequency at which to warn the user that
- // the beacon client is offline.
- beaconUpdateWarnFrequency = 5 * time.Minute
-)
-
-// All methods provided over the engine endpoint.
-var caps = []string{
- "engine_forkchoiceUpdatedV1",
- "engine_forkchoiceUpdatedV2",
- "engine_forkchoiceUpdatedV3",
- "engine_exchangeTransitionConfigurationV1",
- "engine_getPayloadV1",
- "engine_getPayloadV2",
- "engine_getPayloadV3",
- "engine_newPayloadV1",
- "engine_newPayloadV2",
- "engine_newPayloadV3",
- "engine_getPayloadBodiesByHashV1",
- "engine_getPayloadBodiesByRangeV1",
-}
-
-type ConsensusAPI struct {
- eth *eth.Ethereum
-
- remoteBlocks *headerQueue // Cache of remote payloads received
- localBlocks *payloadQueue // Cache of local payloads generated
-
- // The forkchoice update and new payload method require us to return the
- // latest valid hash in an invalid chain. To support that return, we need
- // to track historical bad blocks as well as bad tipsets in case a chain
- // is constantly built on it.
- //
- // There are a few important caveats in this mechanism:
- // - The bad block tracking is ephemeral, in-memory only. We must never
- // persist any bad block information to disk as a bug in Geth could end
- // up blocking a valid chain, even if a later Geth update would accept
- // it.
- // - Bad blocks will get forgotten after a certain threshold of import
- // attempts and will be retried. The rationale is that if the network
- // really-really-really tries to feed us a block, we should give it a
- // new chance, perhaps us being racey instead of the block being legit
- // bad (this happened in Geth at a point with import vs. pending race).
- // - Tracking all the blocks built on top of the bad one could be a bit
- // problematic, so we will only track the head chain segment of a bad
- // chain to allow discarding progressing bad chains and side chains,
- // without tracking too much bad data.
- invalidBlocksHits map[common.Hash]int // Ephemeral cache to track invalid blocks and their hit count
- invalidTipsets map[common.Hash]*types.Header // Ephemeral cache to track invalid tipsets and their bad ancestor
- invalidLock sync.Mutex // Protects the invalid maps from concurrent access
-
- // Geth can appear to be stuck or do strange things if the beacon client is
- // offline or is sending us strange data. Stash some update stats away so
- // that we can warn the user and not have them open issues on our tracker.
- lastTransitionUpdate time.Time
- lastTransitionLock sync.Mutex
- lastForkchoiceUpdate time.Time
- lastForkchoiceLock sync.Mutex
- lastNewPayloadUpdate time.Time
- lastNewPayloadLock sync.Mutex
-
- forkchoiceLock sync.Mutex // Lock for the forkChoiceUpdated method
- newPayloadLock sync.Mutex // Lock for the NewPayload method
-}
-
-// NewConsensusAPI creates a new consensus api for the given backend.
-// The underlying blockchain needs to have a valid terminal total difficulty set.
-func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI {
- api := newConsensusAPIWithoutHeartbeat(eth)
- go api.heartbeat()
- return api
-}
-
-// newConsensusAPIWithoutHeartbeat creates a new consensus api for the SimulatedBeacon Node.
-func newConsensusAPIWithoutHeartbeat(eth *eth.Ethereum) *ConsensusAPI {
- if eth.BlockChain().Config().TerminalTotalDifficulty == nil {
- log.Warn("Engine API started but chain not configured for merge yet")
- }
- api := &ConsensusAPI{
- eth: eth,
- remoteBlocks: newHeaderQueue(),
- localBlocks: newPayloadQueue(),
- invalidBlocksHits: make(map[common.Hash]int),
- invalidTipsets: make(map[common.Hash]*types.Header),
- }
- eth.Downloader().SetBadBlockCallback(api.setInvalidAncestor)
- return api
-}
-
-// ForkchoiceUpdatedV1 has several responsibilities:
-//
-// We try to set our blockchain to the headBlock.
-//
-// If the method is called with an empty head block: we return success, which can be used
-// to check if the engine API is enabled.
-//
-// If the total difficulty was not reached: we return INVALID.
-//
-// If the finalizedBlockHash is set: we check if we have the finalizedBlockHash in our db,
-// if not we start a sync.
-//
-// If there are payloadAttributes: we try to assemble a block with the payloadAttributes
-// and return its payloadID.
-func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
- if payloadAttributes != nil {
- if payloadAttributes.Withdrawals != nil {
- return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("withdrawals not supported in V1"))
- }
- if api.eth.BlockChain().Config().IsShanghai(api.eth.BlockChain().Config().LondonBlock, payloadAttributes.Timestamp) {
- return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("forkChoiceUpdateV1 called post-shanghai"))
- }
- }
- return api.forkchoiceUpdated(update, payloadAttributes)
-}
-
-// ForkchoiceUpdatedV2 is equivalent to V1 with the addition of withdrawals in the payload attributes.
-func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
- if payloadAttributes != nil {
- if err := api.verifyPayloadAttributes(payloadAttributes); err != nil {
- return engine.STATUS_INVALID, engine.InvalidParams.With(err)
- }
- }
- return api.forkchoiceUpdated(update, payloadAttributes)
-}
-
-// ForkchoiceUpdatedV3 is equivalent to V2 with the addition of parent beacon block root in the payload attributes.
-func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
- if payloadAttributes != nil {
- if err := api.verifyPayloadAttributes(payloadAttributes); err != nil {
- return engine.STATUS_INVALID, engine.InvalidParams.With(err)
- }
- }
- return api.forkchoiceUpdated(update, payloadAttributes)
-}
-
-func (api *ConsensusAPI) verifyPayloadAttributes(attr *engine.PayloadAttributes) error {
- c := api.eth.BlockChain().Config()
-
- // Verify withdrawals attribute for Shanghai.
- if err := checkAttribute(c.IsShanghai, attr.Withdrawals != nil, c.LondonBlock, attr.Timestamp); err != nil {
- return fmt.Errorf("invalid withdrawals: %w", err)
- }
- // Verify beacon root attribute for Cancun.
- if err := checkAttribute(c.IsCancun, attr.BeaconRoot != nil, c.LondonBlock, attr.Timestamp); err != nil {
- return fmt.Errorf("invalid parent beacon block root: %w", err)
- }
- return nil
-}
-
-func checkAttribute(active func(*big.Int, uint64) bool, exists bool, block *big.Int, time uint64) error {
- if active(block, time) && !exists {
- return errors.New("fork active, missing expected attribute")
- }
- if !active(block, time) && exists {
- return errors.New("fork inactive, unexpected attribute set")
- }
- return nil
-}
-
-func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
- api.forkchoiceLock.Lock()
- defer api.forkchoiceLock.Unlock()
-
- log.Trace("Engine API request received", "method", "ForkchoiceUpdated", "head", update.HeadBlockHash, "finalized", update.FinalizedBlockHash, "safe", update.SafeBlockHash)
- if update.HeadBlockHash == (common.Hash{}) {
- log.Warn("Forkchoice requested update to zero hash")
- return engine.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this?
- }
- // Stash away the last update to warn the user if the beacon client goes offline
- api.lastForkchoiceLock.Lock()
- api.lastForkchoiceUpdate = time.Now()
- api.lastForkchoiceLock.Unlock()
-
- // Check whether we have the block yet in our database or not. If not, we'll
- // need to either trigger a sync, or to reject this forkchoice update for a
- // reason.
- block := api.eth.BlockChain().GetBlockByHash(update.HeadBlockHash)
- if block == nil {
- // If this block was previously invalidated, keep rejecting it here too
- if res := api.checkInvalidAncestor(update.HeadBlockHash, update.HeadBlockHash); res != nil {
- return engine.ForkChoiceResponse{PayloadStatus: *res, PayloadID: nil}, nil
- }
- // If the head hash is unknown (was not given to us in a newPayload request),
- // we cannot resolve the header, so not much to do. This could be extended in
- // the future to resolve from the `eth` network, but it's an unexpected case
- // that should be fixed, not papered over.
- header := api.remoteBlocks.get(update.HeadBlockHash)
- if header == nil {
- log.Warn("Forkchoice requested unknown head", "hash", update.HeadBlockHash)
- return engine.STATUS_SYNCING, nil
- }
- // If the finalized hash is known, we can direct the downloader to move
- // potentially more data to the freezer from the get go.
- finalized := api.remoteBlocks.get(update.FinalizedBlockHash)
-
- // Header advertised via a past newPayload request. Start syncing to it.
- // Before we do however, make sure any legacy sync in switched off so we
- // don't accidentally have 2 cycles running.
- if merger := api.eth.Merger(); !merger.TDDReached() {
- merger.ReachTTD()
- api.eth.Downloader().Cancel()
- }
- context := []interface{}{"number", header.Number, "hash", header.Hash()}
- if update.FinalizedBlockHash != (common.Hash{}) {
- if finalized == nil {
- context = append(context, []interface{}{"finalized", "unknown"}...)
- } else {
- context = append(context, []interface{}{"finalized", finalized.Number}...)
- }
- }
- log.Info("Forkchoice requested sync to new head", context...)
- if err := api.eth.Downloader().BeaconSync(api.eth.SyncMode(), header, finalized); err != nil {
- return engine.STATUS_SYNCING, err
- }
- return engine.STATUS_SYNCING, nil
- }
- // Block is known locally, just sanity check that the beacon client does not
- // attempt to push us back to before the merge.
- if block.Difficulty().BitLen() > 0 || block.NumberU64() == 0 {
- var (
- td = api.eth.BlockChain().GetTd(update.HeadBlockHash, block.NumberU64())
- ptd = api.eth.BlockChain().GetTd(block.ParentHash(), block.NumberU64()-1)
- ttd = api.eth.BlockChain().Config().TerminalTotalDifficulty
- )
- if td == nil || (block.NumberU64() > 0 && ptd == nil) {
- log.Error("TDs unavailable for TTD check", "number", block.NumberU64(), "hash", update.HeadBlockHash, "td", td, "parent", block.ParentHash(), "ptd", ptd)
- return engine.STATUS_INVALID, errors.New("TDs unavailable for TDD check")
- }
- if td.Cmp(ttd) < 0 {
- log.Error("Refusing beacon update to pre-merge", "number", block.NumberU64(), "hash", update.HeadBlockHash, "diff", block.Difficulty(), "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)))
- return engine.ForkChoiceResponse{PayloadStatus: engine.INVALID_TERMINAL_BLOCK, PayloadID: nil}, nil
- }
- if block.NumberU64() > 0 && ptd.Cmp(ttd) >= 0 {
- log.Error("Parent block is already post-ttd", "number", block.NumberU64(), "hash", update.HeadBlockHash, "diff", block.Difficulty(), "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)))
- return engine.ForkChoiceResponse{PayloadStatus: engine.INVALID_TERMINAL_BLOCK, PayloadID: nil}, nil
- }
- }
- valid := func(id *engine.PayloadID) engine.ForkChoiceResponse {
- return engine.ForkChoiceResponse{
- PayloadStatus: engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &update.HeadBlockHash},
- PayloadID: id,
- }
- }
- if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) != update.HeadBlockHash {
- // Block is not canonical, set head.
- if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil {
- return engine.ForkChoiceResponse{PayloadStatus: engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &latestValid}}, err
- }
- } else if api.eth.BlockChain().CurrentBlock().Hash() == update.HeadBlockHash {
- // If the specified head matches with our local head, do nothing and keep
- // generating the payload. It's a special corner case that a few slots are
- // missing and we are requested to generate the payload in slot.
- } else {
- // If the head block is already in our canonical chain, the beacon client is
- // probably resyncing. Ignore the update.
- log.Info("Ignoring beacon update to old head", "number", block.NumberU64(), "hash", update.HeadBlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)), "have", api.eth.BlockChain().CurrentBlock().Number)
- return valid(nil), nil
- }
- api.eth.SetSynced()
-
- // If the beacon client also advertised a finalized block, mark the local
- // chain final and completely in PoS mode.
- if update.FinalizedBlockHash != (common.Hash{}) {
- if merger := api.eth.Merger(); !merger.PoSFinalized() {
- merger.FinalizePoS()
- }
- // If the finalized block is not in our canonical tree, somethings wrong
- finalBlock := api.eth.BlockChain().GetBlockByHash(update.FinalizedBlockHash)
- if finalBlock == nil {
- log.Warn("Final block not available in database", "hash", update.FinalizedBlockHash)
- return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("final block not available in database"))
- } else if rawdb.ReadCanonicalHash(api.eth.ChainDb(), finalBlock.NumberU64()) != update.FinalizedBlockHash {
- log.Warn("Final block not in canonical chain", "number", block.NumberU64(), "hash", update.HeadBlockHash)
- return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("final block not in canonical chain"))
- }
- // Set the finalized block
- api.eth.BlockChain().SetFinalized(finalBlock.Header())
- }
- // Check if the safe block hash is in our canonical tree, if not somethings wrong
- if update.SafeBlockHash != (common.Hash{}) {
- safeBlock := api.eth.BlockChain().GetBlockByHash(update.SafeBlockHash)
- if safeBlock == nil {
- log.Warn("Safe block not available in database")
- return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("safe block not available in database"))
- }
- if rawdb.ReadCanonicalHash(api.eth.ChainDb(), safeBlock.NumberU64()) != update.SafeBlockHash {
- log.Warn("Safe block not in canonical chain")
- return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("safe block not in canonical chain"))
- }
- // Set the safe block
- api.eth.BlockChain().SetSafe(safeBlock.Header())
- }
- // If payload generation was requested, create a new block to be potentially
- // sealed by the beacon client. The payload will be requested later, and we
- // will replace it arbitrarily many times in between.
- if payloadAttributes != nil {
- args := &miner.BuildPayloadArgs{
- Parent: update.HeadBlockHash,
- Timestamp: payloadAttributes.Timestamp,
- FeeRecipient: payloadAttributes.SuggestedFeeRecipient,
- Random: payloadAttributes.Random,
- Withdrawals: payloadAttributes.Withdrawals,
- BeaconRoot: payloadAttributes.BeaconRoot,
- }
- id := args.Id()
- // If we already are busy generating this work, then we do not need
- // to start a second process.
- if api.localBlocks.has(id) {
- return valid(&id), nil
- }
- payload, err := api.eth.Miner().BuildPayload(args)
- if err != nil {
- log.Error("Failed to build payload", "err", err)
- return valid(nil), engine.InvalidPayloadAttributes.With(err)
- }
- api.localBlocks.put(id, payload)
- return valid(&id), nil
- }
- return valid(nil), nil
-}
-
-// ExchangeTransitionConfigurationV1 checks the given configuration against
-// the configuration of the node.
-func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config engine.TransitionConfigurationV1) (*engine.TransitionConfigurationV1, error) {
- log.Trace("Engine API request received", "method", "ExchangeTransitionConfiguration", "ttd", config.TerminalTotalDifficulty)
- if config.TerminalTotalDifficulty == nil {
- return nil, errors.New("invalid terminal total difficulty")
- }
- // Stash away the last update to warn the user if the beacon client goes offline
- api.lastTransitionLock.Lock()
- api.lastTransitionUpdate = time.Now()
- api.lastTransitionLock.Unlock()
-
- ttd := api.eth.BlockChain().Config().TerminalTotalDifficulty
- if ttd == nil || ttd.Cmp(config.TerminalTotalDifficulty.ToInt()) != 0 {
- log.Warn("Invalid TTD configured", "geth", ttd, "beacon", config.TerminalTotalDifficulty)
- return nil, fmt.Errorf("invalid ttd: execution %v consensus %v", ttd, config.TerminalTotalDifficulty)
- }
- if config.TerminalBlockHash != (common.Hash{}) {
- if hash := api.eth.BlockChain().GetCanonicalHash(uint64(config.TerminalBlockNumber)); hash == config.TerminalBlockHash {
- return &engine.TransitionConfigurationV1{
- TerminalTotalDifficulty: (*hexutil.Big)(ttd),
- TerminalBlockHash: config.TerminalBlockHash,
- TerminalBlockNumber: config.TerminalBlockNumber,
- }, nil
- }
- return nil, errors.New("invalid terminal block hash")
- }
- return &engine.TransitionConfigurationV1{TerminalTotalDifficulty: (*hexutil.Big)(ttd)}, nil
-}
-
-// GetPayloadV1 returns a cached payload by id.
-func (api *ConsensusAPI) GetPayloadV1(payloadID engine.PayloadID) (*engine.ExecutableData, error) {
- data, err := api.getPayload(payloadID, false)
- if err != nil {
- return nil, err
- }
- return data.ExecutionPayload, nil
-}
-
-// GetPayloadV2 returns a cached payload by id.
-func (api *ConsensusAPI) GetPayloadV2(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
- return api.getPayload(payloadID, false)
-}
-
-// GetPayloadV3 returns a cached payload by id.
-func (api *ConsensusAPI) GetPayloadV3(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
- return api.getPayload(payloadID, false)
-}
-
-func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*engine.ExecutionPayloadEnvelope, error) {
- log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID)
- data := api.localBlocks.get(payloadID, full)
- if data == nil {
- return nil, engine.UnknownPayload
- }
- return data, nil
-}
-
-// NewPayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
-func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
- if params.Withdrawals != nil {
- return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1"))
- }
- return api.newPayload(params, nil, nil)
-}
-
-// NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
-func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
- if api.eth.BlockChain().Config().IsShanghai(new(big.Int).SetUint64(params.Number), params.Timestamp) {
- if params.Withdrawals == nil {
- return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
- }
- } else if params.Withdrawals != nil {
- return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai"))
- }
- if api.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(params.Number), params.Timestamp) {
- return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("newPayloadV2 called post-cancun"))
- }
- return api.newPayload(params, nil, nil)
-}
-
-// NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
-func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
- if params.ExcessBlobGas == nil {
- return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
- }
- if params.BlobGasUsed == nil {
- return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil params.BlobGasUsed post-cancun"))
- }
- if versionedHashes == nil {
- return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun"))
- }
- if beaconRoot == nil {
- return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil parentBeaconBlockRoot post-cancun"))
- }
-
- if !api.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(params.Number), params.Timestamp) {
- return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV3 called pre-cancun"))
- }
-
- return api.newPayload(params, versionedHashes, beaconRoot)
-}
-
-func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
- // The locking here is, strictly, not required. Without these locks, this can happen:
- //
- // 1. NewPayload( execdata-N ) is invoked from the CL. It goes all the way down to
- // api.eth.BlockChain().InsertBlockWithoutSetHead, where it is blocked on
- // e.g database compaction.
- // 2. The call times out on the CL layer, which issues another NewPayload (execdata-N) call.
- // Similarly, this also get stuck on the same place. Importantly, since the
- // first call has not gone through, the early checks for "do we already have this block"
- // will all return false.
- // 3. When the db compaction ends, then N calls inserting the same payload are processed
- // sequentially.
- // Hence, we use a lock here, to be sure that the previous call has finished before we
- // check whether we already have the block locally.
- api.newPayloadLock.Lock()
- defer api.newPayloadLock.Unlock()
-
- log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash)
- block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot)
- if err != nil {
- log.Warn("Invalid NewPayload params", "params", params, "error", err)
- return api.invalid(err, nil), nil
- }
- // Stash away the last update to warn the user if the beacon client goes offline
- api.lastNewPayloadLock.Lock()
- api.lastNewPayloadUpdate = time.Now()
- api.lastNewPayloadLock.Unlock()
-
- // If we already have the block locally, ignore the entire execution and just
- // return a fake success.
- if block := api.eth.BlockChain().GetBlockByHash(params.BlockHash); block != nil {
- log.Warn("Ignoring already known beacon payload", "number", params.Number, "hash", params.BlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)))
- hash := block.Hash()
- return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil
- }
- // If this block was rejected previously, keep rejecting it
- if res := api.checkInvalidAncestor(block.Hash(), block.Hash()); res != nil {
- return *res, nil
- }
- // If the parent is missing, we - in theory - could trigger a sync, but that
- // would also entail a reorg. That is problematic if multiple sibling blocks
- // are being fed to us, and even more so, if some semi-distant uncle shortens
- // our live chain. As such, payload execution will not permit reorgs and thus
- // will not trigger a sync cycle. That is fine though, if we get a fork choice
- // update after legit payload executions.
- parent := api.eth.BlockChain().GetBlock(block.ParentHash(), block.NumberU64()-1)
- if parent == nil {
- return api.delayPayloadImport(block)
- }
- // We have an existing parent, do some sanity checks to avoid the beacon client
- // triggering too early
- var (
- ptd = api.eth.BlockChain().GetTd(parent.Hash(), parent.NumberU64())
- ttd = api.eth.BlockChain().Config().TerminalTotalDifficulty
- gptd = api.eth.BlockChain().GetTd(parent.ParentHash(), parent.NumberU64()-1)
- )
- if ptd.Cmp(ttd) < 0 {
- log.Warn("Ignoring pre-merge payload", "number", params.Number, "hash", params.BlockHash, "td", ptd, "ttd", ttd)
- return engine.INVALID_TERMINAL_BLOCK, nil
- }
- if parent.Difficulty().BitLen() > 0 && gptd != nil && gptd.Cmp(ttd) >= 0 {
- log.Error("Ignoring pre-merge parent block", "number", params.Number, "hash", params.BlockHash, "td", ptd, "ttd", ttd)
- return engine.INVALID_TERMINAL_BLOCK, nil
- }
- if block.Time() <= parent.Time() {
- log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time())
- return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil
- }
- // Another corner case: if the node is in snap sync mode, but the CL client
- // tries to make it import a block. That should be denied as pushing something
- // into the database directly will conflict with the assumptions of snap sync
- // that it has an empty db that it can fill itself.
- if api.eth.SyncMode() != downloader.FullSync {
- return api.delayPayloadImport(block)
- }
- if !api.eth.BlockChain().HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
- api.remoteBlocks.put(block.Hash(), block.Header())
- log.Warn("State not available, ignoring new payload")
- return engine.PayloadStatusV1{Status: engine.ACCEPTED}, nil
- }
- log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number)
- if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil {
- log.Warn("NewPayloadV1: inserting block failed", "error", err)
-
- api.invalidLock.Lock()
- api.invalidBlocksHits[block.Hash()] = 1
- api.invalidTipsets[block.Hash()] = block.Header()
- api.invalidLock.Unlock()
-
- return api.invalid(err, parent.Header()), nil
- }
- // We've accepted a valid payload from the beacon client. Mark the local
- // chain transitions to notify other subsystems (e.g. downloader) of the
- // behavioral change.
- if merger := api.eth.Merger(); !merger.TDDReached() {
- merger.ReachTTD()
- api.eth.Downloader().Cancel()
- }
- hash := block.Hash()
- return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil
-}
-
-// delayPayloadImport stashes the given block away for import at a later time,
-// either via a forkchoice update or a sync extension. This method is meant to
-// be called by the newpayload command when the block seems to be ok, but some
-// prerequisite prevents it from being processed (e.g. no parent, or snap sync).
-func (api *ConsensusAPI) delayPayloadImport(block *types.Block) (engine.PayloadStatusV1, error) {
- // Sanity check that this block's parent is not on a previously invalidated
- // chain. If it is, mark the block as invalid too.
- if res := api.checkInvalidAncestor(block.ParentHash(), block.Hash()); res != nil {
- return *res, nil
- }
- // Stash the block away for a potential forced forkchoice update to it
- // at a later time.
- api.remoteBlocks.put(block.Hash(), block.Header())
-
- // Although we don't want to trigger a sync, if there is one already in
- // progress, try to extend if with the current payload request to relieve
- // some strain from the forkchoice update.
- err := api.eth.Downloader().BeaconExtend(api.eth.SyncMode(), block.Header())
- if err == nil {
- log.Debug("Payload accepted for sync extension", "number", block.NumberU64(), "hash", block.Hash())
- return engine.PayloadStatusV1{Status: engine.SYNCING}, nil
- }
- // Either no beacon sync was started yet, or it rejected the delivered
- // payload as non-integratable on top of the existing sync. We'll just
- // have to rely on the beacon client to forcefully update the head with
- // a forkchoice update request.
- if api.eth.SyncMode() == downloader.FullSync {
- // In full sync mode, failure to import a well-formed block can only mean
- // that the parent state is missing and the syncer rejected extending the
- // current cycle with the new payload.
- log.Warn("Ignoring payload with missing parent", "number", block.NumberU64(), "hash", block.Hash(), "parent", block.ParentHash(), "reason", err)
- } else {
- // In non-full sync mode (i.e. snap sync) all payloads are rejected until
- // snap sync terminates as snap sync relies on direct database injections
- // and cannot afford concurrent out-if-band modifications via imports.
- log.Warn("Ignoring payload while snap syncing", "number", block.NumberU64(), "hash", block.Hash(), "reason", err)
- }
- return engine.PayloadStatusV1{Status: engine.SYNCING}, nil
-}
-
-// setInvalidAncestor is a callback for the downloader to notify us if a bad block
-// is encountered during the async sync.
-func (api *ConsensusAPI) setInvalidAncestor(invalid *types.Header, origin *types.Header) {
- api.invalidLock.Lock()
- defer api.invalidLock.Unlock()
-
- api.invalidTipsets[origin.Hash()] = invalid
- api.invalidBlocksHits[invalid.Hash()]++
-}
-
-// checkInvalidAncestor checks whether the specified chain end links to a known
-// bad ancestor. If yes, it constructs the payload failure response to return.
-func (api *ConsensusAPI) checkInvalidAncestor(check common.Hash, head common.Hash) *engine.PayloadStatusV1 {
- api.invalidLock.Lock()
- defer api.invalidLock.Unlock()
-
- // If the hash to check is unknown, return valid
- invalid, ok := api.invalidTipsets[check]
- if !ok {
- return nil
- }
- // If the bad hash was hit too many times, evict it and try to reprocess in
- // the hopes that we have a data race that we can exit out of.
- badHash := invalid.Hash()
-
- api.invalidBlocksHits[badHash]++
- if api.invalidBlocksHits[badHash] >= invalidBlockHitEviction {
- log.Warn("Too many bad block import attempt, trying", "number", invalid.Number, "hash", badHash)
- delete(api.invalidBlocksHits, badHash)
-
- for descendant, badHeader := range api.invalidTipsets {
- if badHeader.Hash() == badHash {
- delete(api.invalidTipsets, descendant)
- }
- }
- return nil
- }
- // Not too many failures yet, mark the head of the invalid chain as invalid
- if check != head {
- log.Warn("Marked new chain head as invalid", "hash", head, "badnumber", invalid.Number, "badhash", badHash)
- for len(api.invalidTipsets) >= invalidTipsetsCap {
- for key := range api.invalidTipsets {
- delete(api.invalidTipsets, key)
- break
- }
- }
- api.invalidTipsets[head] = invalid
- }
- // If the last valid hash is the terminal pow block, return 0x0 for latest valid hash
- lastValid := &invalid.ParentHash
- if header := api.eth.BlockChain().GetHeader(invalid.ParentHash, invalid.Number.Uint64()-1); header != nil && header.Difficulty.Sign() != 0 {
- lastValid = &common.Hash{}
- }
- failure := "links to previously rejected block"
- return &engine.PayloadStatusV1{
- Status: engine.INVALID,
- LatestValidHash: lastValid,
- ValidationError: &failure,
- }
-}
-
-// invalid returns a response "INVALID" with the latest valid hash supplied by latest.
-func (api *ConsensusAPI) invalid(err error, latestValid *types.Header) engine.PayloadStatusV1 {
- var currentHash *common.Hash
- if latestValid != nil {
- if latestValid.Difficulty.BitLen() != 0 {
- // Set latest valid hash to 0x0 if parent is PoW block
- currentHash = &common.Hash{}
- } else {
- // Otherwise set latest valid hash to parent hash
- h := latestValid.Hash()
- currentHash = &h
- }
- }
- errorMsg := err.Error()
- return engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: currentHash, ValidationError: &errorMsg}
-}
-
-// heartbeat loops indefinitely, and checks if there have been beacon client updates
-// received in the last while. If not - or if they but strange ones - it warns the
-// user that something might be off with their consensus node.
-//
-// TODO(karalabe): Spin this goroutine down somehow
-func (api *ConsensusAPI) heartbeat() {
- // Sleep a bit on startup since there's obviously no beacon client yet
- // attached, so no need to print scary warnings to the user.
- time.Sleep(beaconUpdateStartupTimeout)
-
- // If the network is not yet merged/merging, don't bother continuing.
- if api.eth.BlockChain().Config().TerminalTotalDifficulty == nil {
- return
- }
-
- var offlineLogged time.Time
-
- for {
- // Sleep a bit and retrieve the last known consensus updates
- time.Sleep(5 * time.Second)
-
- api.lastTransitionLock.Lock()
- lastTransitionUpdate := api.lastTransitionUpdate
- api.lastTransitionLock.Unlock()
-
- api.lastForkchoiceLock.Lock()
- lastForkchoiceUpdate := api.lastForkchoiceUpdate
- api.lastForkchoiceLock.Unlock()
-
- api.lastNewPayloadLock.Lock()
- lastNewPayloadUpdate := api.lastNewPayloadUpdate
- api.lastNewPayloadLock.Unlock()
-
- // If there have been no updates for the past while, warn the user
- // that the beacon client is probably offline
- if api.eth.BlockChain().Config().TerminalTotalDifficultyPassed || api.eth.Merger().TDDReached() {
- if time.Since(lastForkchoiceUpdate) <= beaconUpdateConsensusTimeout || time.Since(lastNewPayloadUpdate) <= beaconUpdateConsensusTimeout {
- offlineLogged = time.Time{}
- continue
- }
-
- if time.Since(offlineLogged) > beaconUpdateWarnFrequency {
- if lastForkchoiceUpdate.IsZero() && lastNewPayloadUpdate.IsZero() {
- if lastTransitionUpdate.IsZero() {
- log.Warn("Post-merge network, but no beacon client seen. Please launch one to follow the chain!")
- } else {
- log.Warn("Beacon client online, but never received consensus updates. Please ensure your beacon client is operational to follow the chain!")
- }
- } else {
- log.Warn("Beacon client online, but no consensus updates received in a while. Please fix your beacon client to follow the chain!")
- }
- offlineLogged = time.Now()
- }
- continue
- }
- }
-}
-
-// ExchangeCapabilities returns the current methods provided by this node.
-func (api *ConsensusAPI) ExchangeCapabilities([]string) []string {
- return caps
-}
-
-// GetPayloadBodiesByHashV1 implements engine_getPayloadBodiesByHashV1 which allows for retrieval of a list
-// of block bodies by the engine api.
-func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*engine.ExecutionPayloadBodyV1 {
- bodies := make([]*engine.ExecutionPayloadBodyV1, len(hashes))
- for i, hash := range hashes {
- block := api.eth.BlockChain().GetBlockByHash(hash)
- bodies[i] = getBody(block)
- }
- return bodies
-}
-
-// GetPayloadBodiesByRangeV1 implements engine_getPayloadBodiesByRangeV1 which allows for retrieval of a range
-// of block bodies by the engine api.
-func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBodyV1, error) {
- if start == 0 || count == 0 {
- return nil, engine.InvalidParams.With(fmt.Errorf("invalid start or count, start: %v count: %v", start, count))
- }
- if count > 1024 {
- return nil, engine.TooLargeRequest.With(fmt.Errorf("requested count too large: %v", count))
- }
- // limit count up until current
- current := api.eth.BlockChain().CurrentBlock().Number.Uint64()
- last := uint64(start) + uint64(count) - 1
- if last > current {
- last = current
- }
- bodies := make([]*engine.ExecutionPayloadBodyV1, 0, uint64(count))
- for i := uint64(start); i <= last; i++ {
- block := api.eth.BlockChain().GetBlockByNumber(i)
- bodies = append(bodies, getBody(block))
- }
- return bodies, nil
-}
-
-func getBody(block *types.Block) *engine.ExecutionPayloadBodyV1 {
- if block == nil {
- return nil
- }
-
- var (
- body = block.Body()
- txs = make([]hexutil.Bytes, len(body.Transactions))
- withdrawals = body.Withdrawals
- )
-
- for j, tx := range body.Transactions {
- data, _ := tx.MarshalBinary()
- txs[j] = hexutil.Bytes(data)
- }
-
- // Post-shanghai withdrawals MUST be set to empty slice instead of nil
- if withdrawals == nil && block.Header().WithdrawalsHash != nil {
- withdrawals = make([]*types.Withdrawal, 0)
- }
-
- return &engine.ExecutionPayloadBodyV1{
- TransactionData: txs,
- Withdrawals: withdrawals,
- }
-}
diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go
deleted file mode 100644
index c875c485dd..0000000000
--- a/eth/catalyst/api_test.go
+++ /dev/null
@@ -1,1644 +0,0 @@
-// Copyright 2021 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 .
-
-package catalyst
-
-import (
- "bytes"
- "context"
- crand "crypto/rand"
- "fmt"
- "math/big"
- "math/rand"
- "reflect"
- "sync"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/beacon/engine"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/consensus"
- beaconConsensus "github.com/ethereum/go-ethereum/consensus/beacon"
- "github.com/ethereum/go-ethereum/consensus/ethash"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/crypto/kzg4844"
- "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/params"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/ethereum/go-ethereum/trie"
- "github.com/mattn/go-colorable"
-)
-
-var (
- // testKey is a private key to use for funding a tester account.
- testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
-
- // testAddr is the Ethereum address of the tester account.
- testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
-
- testBalance = big.NewInt(2e18)
-)
-
-func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) {
- config := *params.AllEthashProtocolChanges
- engine := consensus.Engine(beaconConsensus.New(ethash.NewFaker()))
- if merged {
- config.TerminalTotalDifficulty = common.Big0
- config.TerminalTotalDifficultyPassed = true
- engine = beaconConsensus.NewFaker()
- }
- genesis := &core.Genesis{
- Config: &config,
- Alloc: core.GenesisAlloc{
- testAddr: {Balance: testBalance},
- params.BeaconRootsStorageAddress: {Balance: common.Big0, Code: common.Hex2Bytes("3373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5ffd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b6201800042064281555f359062018000015500")},
- },
- ExtraData: []byte("test genesis"),
- Timestamp: 9000,
- BaseFee: big.NewInt(params.InitialBaseFee),
- Difficulty: big.NewInt(0),
- }
- testNonce := uint64(0)
- generate := func(i int, g *core.BlockGen) {
- g.OffsetTime(5)
- g.SetExtra([]byte("test"))
- tx, _ := types.SignTx(types.NewTransaction(testNonce, common.HexToAddress("0x9a9070028361F7AAbeB3f2F2Dc07F82C4a98A02a"), big.NewInt(1), params.TxGas, big.NewInt(params.InitialBaseFee*2), nil), types.LatestSigner(&config), testKey)
- g.AddTx(tx)
- testNonce++
- }
- _, blocks, _ := core.GenerateChainWithGenesis(genesis, engine, n, generate)
-
- if !merged {
- totalDifficulty := big.NewInt(0)
- for _, b := range blocks {
- totalDifficulty.Add(totalDifficulty, b.Difficulty())
- }
- config.TerminalTotalDifficulty = totalDifficulty
- }
-
- return genesis, blocks
-}
-
-func TestEth2AssembleBlock(t *testing.T) {
- genesis, blocks := generateMergeChain(10, false)
- n, ethservice := startEthService(t, genesis, blocks)
- defer n.Close()
-
- api := NewConsensusAPI(ethservice)
- signer := types.NewEIP155Signer(ethservice.BlockChain().Config().ChainID)
- tx, err := types.SignTx(types.NewTransaction(uint64(10), blocks[9].Coinbase(), big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey)
- if err != nil {
- t.Fatalf("error signing transaction, err=%v", err)
- }
- ethservice.TxPool().Add([]*types.Transaction{tx}, true, false)
- blockParams := engine.PayloadAttributes{
- Timestamp: blocks[9].Time() + 5,
- }
- // The miner needs to pick up on the txs in the pool, so a few retries might be
- // needed.
- if _, testErr := assembleWithTransactions(api, blocks[9].Hash(), &blockParams, 1); testErr != nil {
- t.Fatal(testErr)
- }
-}
-
-// assembleWithTransactions tries to assemble a block, retrying until it has 'want',
-// number of transactions in it, or it has retried three times.
-func assembleWithTransactions(api *ConsensusAPI, parentHash common.Hash, params *engine.PayloadAttributes, want int) (execData *engine.ExecutableData, err error) {
- for retries := 3; retries > 0; retries-- {
- execData, err = assembleBlock(api, parentHash, params)
- if err != nil {
- return nil, err
- }
- if have, want := len(execData.Transactions), want; have != want {
- err = fmt.Errorf("invalid number of transactions, have %d want %d", have, want)
- continue
- }
- return execData, nil
- }
- return nil, err
-}
-
-func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) {
- genesis, blocks := generateMergeChain(10, false)
- n, ethservice := startEthService(t, genesis, blocks[:9])
- defer n.Close()
-
- api := NewConsensusAPI(ethservice)
-
- // Put the 10th block's tx in the pool and produce a new block
- txs := blocks[9].Transactions()
- api.eth.TxPool().Add(txs, false, true)
- blockParams := engine.PayloadAttributes{
- Timestamp: blocks[8].Time() + 5,
- }
- // The miner needs to pick up on the txs in the pool, so a few retries might be
- // needed.
- if _, err := assembleWithTransactions(api, blocks[8].Hash(), &blockParams, blocks[9].Transactions().Len()); err != nil {
- t.Fatal(err)
- }
-}
-
-func TestSetHeadBeforeTotalDifficulty(t *testing.T) {
- genesis, blocks := generateMergeChain(10, false)
- n, ethservice := startEthService(t, genesis, blocks)
- defer n.Close()
-
- api := NewConsensusAPI(ethservice)
- fcState := engine.ForkchoiceStateV1{
- HeadBlockHash: blocks[5].Hash(),
- SafeBlockHash: common.Hash{},
- FinalizedBlockHash: common.Hash{},
- }
- if resp, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
- t.Errorf("fork choice updated should not error: %v", err)
- } else if resp.PayloadStatus.Status != engine.INVALID_TERMINAL_BLOCK.Status {
- t.Errorf("fork choice updated before total terminal difficulty should be INVALID")
- }
-}
-
-func TestEth2PrepareAndGetPayload(t *testing.T) {
- genesis, blocks := generateMergeChain(10, false)
- // We need to properly set the terminal total difficulty
- genesis.Config.TerminalTotalDifficulty.Sub(genesis.Config.TerminalTotalDifficulty, blocks[9].Difficulty())
- n, ethservice := startEthService(t, genesis, blocks[:9])
- defer n.Close()
-
- api := NewConsensusAPI(ethservice)
-
- // Put the 10th block's tx in the pool and produce a new block
- txs := blocks[9].Transactions()
- ethservice.TxPool().Add(txs, true, false)
- blockParams := engine.PayloadAttributes{
- Timestamp: blocks[8].Time() + 5,
- }
- fcState := engine.ForkchoiceStateV1{
- HeadBlockHash: blocks[8].Hash(),
- SafeBlockHash: common.Hash{},
- FinalizedBlockHash: common.Hash{},
- }
- _, err := api.ForkchoiceUpdatedV1(fcState, &blockParams)
- if err != nil {
- t.Fatalf("error preparing payload, err=%v", err)
- }
- // give the payload some time to be built
- time.Sleep(100 * time.Millisecond)
- payloadID := (&miner.BuildPayloadArgs{
- Parent: fcState.HeadBlockHash,
- Timestamp: blockParams.Timestamp,
- FeeRecipient: blockParams.SuggestedFeeRecipient,
- Random: blockParams.Random,
- BeaconRoot: blockParams.BeaconRoot,
- }).Id()
- execData, err := api.GetPayloadV1(payloadID)
- if err != nil {
- t.Fatalf("error getting payload, err=%v", err)
- }
- if len(execData.Transactions) != blocks[9].Transactions().Len() {
- t.Fatalf("invalid number of transactions %d != 1", len(execData.Transactions))
- }
- // Test invalid payloadID
- var invPayload engine.PayloadID
- copy(invPayload[:], payloadID[:])
- invPayload[0] = ^invPayload[0]
- _, err = api.GetPayloadV1(invPayload)
- if err == nil {
- t.Fatal("expected error retrieving invalid payload")
- }
-}
-
-func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan core.RemovedLogsEvent, wantNew, wantRemoved int) {
- t.Helper()
-
- if len(logsCh) != wantNew {
- t.Fatalf("wrong number of log events: got %d, want %d", len(logsCh), wantNew)
- }
- if len(rmLogsCh) != wantRemoved {
- t.Fatalf("wrong number of removed log events: got %d, want %d", len(rmLogsCh), wantRemoved)
- }
- // Drain events.
- for i := 0; i < len(logsCh); i++ {
- <-logsCh
- }
- for i := 0; i < len(rmLogsCh); i++ {
- <-rmLogsCh
- }
-}
-
-func TestInvalidPayloadTimestamp(t *testing.T) {
- genesis, preMergeBlocks := generateMergeChain(10, false)
- n, ethservice := startEthService(t, genesis, preMergeBlocks)
- defer n.Close()
- var (
- api = NewConsensusAPI(ethservice)
- parent = ethservice.BlockChain().CurrentBlock()
- )
- tests := []struct {
- time uint64
- shouldErr bool
- }{
- {0, true},
- {parent.Time, true},
- {parent.Time - 1, true},
-
- // TODO (MariusVanDerWijden) following tests are currently broken,
- // fixed in upcoming merge-kiln-v2 pr
- //{parent.Time() + 1, false},
- //{uint64(time.Now().Unix()) + uint64(time.Minute), false},
- }
-
- for i, test := range tests {
- t.Run(fmt.Sprintf("Timestamp test: %v", i), func(t *testing.T) {
- params := engine.PayloadAttributes{
- Timestamp: test.time,
- Random: crypto.Keccak256Hash([]byte{byte(123)}),
- SuggestedFeeRecipient: parent.Coinbase,
- }
- fcState := engine.ForkchoiceStateV1{
- HeadBlockHash: parent.Hash(),
- SafeBlockHash: common.Hash{},
- FinalizedBlockHash: common.Hash{},
- }
- _, err := api.ForkchoiceUpdatedV1(fcState, ¶ms)
- if test.shouldErr && err == nil {
- t.Fatalf("expected error preparing payload with invalid timestamp, err=%v", err)
- } else if !test.shouldErr && err != nil {
- t.Fatalf("error preparing payload with valid timestamp, err=%v", err)
- }
- })
- }
-}
-
-func TestEth2NewBlock(t *testing.T) {
- genesis, preMergeBlocks := generateMergeChain(10, false)
- n, ethservice := startEthService(t, genesis, preMergeBlocks)
- defer n.Close()
-
- var (
- api = NewConsensusAPI(ethservice)
- parent = preMergeBlocks[len(preMergeBlocks)-1]
-
- // This EVM code generates a log when the contract is created.
- logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
- )
- // The event channels.
- newLogCh := make(chan []*types.Log, 10)
- rmLogsCh := make(chan core.RemovedLogsEvent, 10)
- ethservice.BlockChain().SubscribeLogsEvent(newLogCh)
- ethservice.BlockChain().SubscribeRemovedLogsEvent(rmLogsCh)
-
- for i := 0; i < 10; i++ {
- statedb, _ := ethservice.BlockChain().StateAt(parent.Root())
- nonce := statedb.GetNonce(testAddr)
- tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
- ethservice.TxPool().Add([]*types.Transaction{tx}, true, false)
-
- execData, err := assembleWithTransactions(api, parent.Hash(), &engine.PayloadAttributes{
- Timestamp: parent.Time() + 5,
- }, 1)
- if err != nil {
- t.Fatalf("Failed to create the executable data %v", err)
- }
- block, err := engine.ExecutableDataToBlock(*execData, nil, nil)
- if err != nil {
- t.Fatalf("Failed to convert executable data to block %v", err)
- }
- newResp, err := api.NewPayloadV1(*execData)
- switch {
- case err != nil:
- t.Fatalf("Failed to insert block: %v", err)
- case newResp.Status != "VALID":
- t.Fatalf("Failed to insert block: %v", newResp.Status)
- case ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64()-1:
- t.Fatalf("Chain head shouldn't be updated")
- }
- checkLogEvents(t, newLogCh, rmLogsCh, 0, 0)
- fcState := engine.ForkchoiceStateV1{
- HeadBlockHash: block.Hash(),
- SafeBlockHash: block.Hash(),
- FinalizedBlockHash: block.Hash(),
- }
- if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
- t.Fatalf("Failed to insert block: %v", err)
- }
- if have, want := ethservice.BlockChain().CurrentBlock().Number.Uint64(), block.NumberU64(); have != want {
- t.Fatalf("Chain head should be updated, have %d want %d", have, want)
- }
- checkLogEvents(t, newLogCh, rmLogsCh, 1, 0)
-
- parent = block
- }
-
- // Introduce fork chain
- var (
- head = ethservice.BlockChain().CurrentBlock().Number.Uint64()
- )
- parent = preMergeBlocks[len(preMergeBlocks)-1]
- for i := 0; i < 10; i++ {
- execData, err := assembleBlock(api, parent.Hash(), &engine.PayloadAttributes{
- Timestamp: parent.Time() + 6,
- })
- if err != nil {
- t.Fatalf("Failed to create the executable data %v", err)
- }
- block, err := engine.ExecutableDataToBlock(*execData, nil, nil)
- if err != nil {
- t.Fatalf("Failed to convert executable data to block %v", err)
- }
- newResp, err := api.NewPayloadV1(*execData)
- if err != nil || newResp.Status != "VALID" {
- t.Fatalf("Failed to insert block: %v", err)
- }
- if ethservice.BlockChain().CurrentBlock().Number.Uint64() != head {
- t.Fatalf("Chain head shouldn't be updated")
- }
-
- fcState := engine.ForkchoiceStateV1{
- HeadBlockHash: block.Hash(),
- SafeBlockHash: block.Hash(),
- FinalizedBlockHash: block.Hash(),
- }
- if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
- t.Fatalf("Failed to insert block: %v", err)
- }
- if ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64() {
- t.Fatalf("Chain head should be updated")
- }
- parent, head = block, block.NumberU64()
- }
-}
-
-func TestEth2DeepReorg(t *testing.T) {
- // TODO (MariusVanDerWijden) TestEth2DeepReorg is currently broken, because it tries to reorg
- // before the totalTerminalDifficulty threshold
- /*
- genesis, preMergeBlocks := generateMergeChain(core.TriesInMemory * 2, false)
- n, ethservice := startEthService(t, genesis, preMergeBlocks)
- defer n.Close()
-
- var (
- api = NewConsensusAPI(ethservice, nil)
- parent = preMergeBlocks[len(preMergeBlocks)-core.TriesInMemory-1]
- head = ethservice.BlockChain().CurrentBlock().Number.Uint64()()
- )
- if ethservice.BlockChain().HasBlockAndState(parent.Hash(), parent.NumberU64()) {
- t.Errorf("Block %d not pruned", parent.NumberU64())
- }
- for i := 0; i < 10; i++ {
- execData, err := api.assembleBlock(AssembleBlockParams{
- ParentHash: parent.Hash(),
- Timestamp: parent.Time() + 5,
- })
- if err != nil {
- t.Fatalf("Failed to create the executable data %v", err)
- }
- block, err := ExecutableDataToBlock(ethservice.BlockChain().Config(), parent.Header(), *execData)
- if err != nil {
- t.Fatalf("Failed to convert executable data to block %v", err)
- }
- newResp, err := api.ExecutePayload(*execData)
- if err != nil || newResp.Status != "VALID" {
- t.Fatalf("Failed to insert block: %v", err)
- }
- if ethservice.BlockChain().CurrentBlock().Number.Uint64()() != head {
- t.Fatalf("Chain head shouldn't be updated")
- }
- if err := api.setHead(block.Hash()); err != nil {
- t.Fatalf("Failed to set head: %v", err)
- }
- if ethservice.BlockChain().CurrentBlock().Number.Uint64()() != block.NumberU64() {
- t.Fatalf("Chain head should be updated")
- }
- parent, head = block, block.NumberU64()
- }
- */
-}
-
-// startEthService creates a full node instance for testing.
-func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) (*node.Node, *eth.Ethereum) {
- t.Helper()
-
- n, err := node.New(&node.Config{
- P2P: p2p.Config{
- ListenAddr: "0.0.0.0:0",
- NoDiscovery: true,
- MaxPeers: 25,
- }})
- if err != nil {
- t.Fatal("can't create node:", err)
- }
-
- ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256}
- ethservice, err := eth.New(n, ethcfg)
- if err != nil {
- t.Fatal("can't create eth service:", err)
- }
- if err := n.Start(); err != nil {
- t.Fatal("can't start node:", err)
- }
- if _, err := ethservice.BlockChain().InsertChain(blocks); err != nil {
- n.Close()
- t.Fatal("can't import test blocks:", err)
- }
-
- ethservice.SetEtherbase(testAddr)
- ethservice.SetSynced()
- return n, ethservice
-}
-
-func TestFullAPI(t *testing.T) {
- genesis, preMergeBlocks := generateMergeChain(10, false)
- n, ethservice := startEthService(t, genesis, preMergeBlocks)
- defer n.Close()
- var (
- parent = ethservice.BlockChain().CurrentBlock()
- // This EVM code generates a log when the contract is created.
- logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
- )
-
- callback := func(parent *types.Header) {
- statedb, _ := ethservice.BlockChain().StateAt(parent.Root)
- nonce := statedb.GetNonce(testAddr)
- tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
- ethservice.TxPool().Add([]*types.Transaction{tx}, true, false)
- }
-
- setupBlocks(t, ethservice, 10, parent, callback, nil)
-}
-
-func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Header, callback func(parent *types.Header), withdrawals [][]*types.Withdrawal) []*types.Header {
- api := NewConsensusAPI(ethservice)
- var blocks []*types.Header
- for i := 0; i < n; i++ {
- callback(parent)
- var w []*types.Withdrawal
- if withdrawals != nil {
- w = withdrawals[i]
- }
-
- payload := getNewPayload(t, api, parent, w)
- execResp, err := api.NewPayloadV2(*payload)
- if err != nil {
- t.Fatalf("can't execute payload: %v", err)
- }
- if execResp.Status != engine.VALID {
- t.Fatalf("invalid status: %v", execResp.Status)
- }
- fcState := engine.ForkchoiceStateV1{
- HeadBlockHash: payload.BlockHash,
- SafeBlockHash: payload.ParentHash,
- FinalizedBlockHash: payload.ParentHash,
- }
- if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
- t.Fatalf("Failed to insert block: %v", err)
- }
- if ethservice.BlockChain().CurrentBlock().Number.Uint64() != payload.Number {
- t.Fatal("Chain head should be updated")
- }
- if ethservice.BlockChain().CurrentFinalBlock().Number.Uint64() != payload.Number-1 {
- t.Fatal("Finalized block should be updated")
- }
- parent = ethservice.BlockChain().CurrentBlock()
- blocks = append(blocks, parent)
- }
- return blocks
-}
-
-func TestExchangeTransitionConfig(t *testing.T) {
- genesis, preMergeBlocks := generateMergeChain(10, false)
- n, ethservice := startEthService(t, genesis, preMergeBlocks)
- defer n.Close()
-
- // invalid ttd
- api := NewConsensusAPI(ethservice)
- config := engine.TransitionConfigurationV1{
- TerminalTotalDifficulty: (*hexutil.Big)(big.NewInt(0)),
- TerminalBlockHash: common.Hash{},
- TerminalBlockNumber: 0,
- }
- if _, err := api.ExchangeTransitionConfigurationV1(config); err == nil {
- t.Fatal("expected error on invalid config, invalid ttd")
- }
- // invalid terminal block hash
- config = engine.TransitionConfigurationV1{
- TerminalTotalDifficulty: (*hexutil.Big)(genesis.Config.TerminalTotalDifficulty),
- TerminalBlockHash: common.Hash{1},
- TerminalBlockNumber: 0,
- }
- if _, err := api.ExchangeTransitionConfigurationV1(config); err == nil {
- t.Fatal("expected error on invalid config, invalid hash")
- }
- // valid config
- config = engine.TransitionConfigurationV1{
- TerminalTotalDifficulty: (*hexutil.Big)(genesis.Config.TerminalTotalDifficulty),
- TerminalBlockHash: common.Hash{},
- TerminalBlockNumber: 0,
- }
- if _, err := api.ExchangeTransitionConfigurationV1(config); err != nil {
- t.Fatalf("expected no error on valid config, got %v", err)
- }
- // valid config
- config = engine.TransitionConfigurationV1{
- TerminalTotalDifficulty: (*hexutil.Big)(genesis.Config.TerminalTotalDifficulty),
- TerminalBlockHash: preMergeBlocks[5].Hash(),
- TerminalBlockNumber: 6,
- }
- if _, err := api.ExchangeTransitionConfigurationV1(config); err != nil {
- t.Fatalf("expected no error on valid config, got %v", err)
- }
-}
-
-/*
-TestNewPayloadOnInvalidChain sets up a valid chain and tries to feed blocks
-from an invalid chain to test if latestValidHash (LVH) works correctly.
-
-We set up the following chain where P1 ... Pn and P1” are valid while
-P1' is invalid.
-We expect
-(1) The LVH to point to the current inserted payload if it was valid.
-(2) The LVH to point to the valid parent on an invalid payload (if the parent is available).
-(3) If the parent is unavailable, the LVH should not be set.
-
- CommonAncestor◄─▲── P1 ◄── P2 ◄─ P3 ◄─ ... ◄─ Pn
- │
- └── P1' ◄─ P2' ◄─ P3' ◄─ ... ◄─ Pn'
- │
- └── P1''
-*/
-func TestNewPayloadOnInvalidChain(t *testing.T) {
- genesis, preMergeBlocks := generateMergeChain(10, false)
- n, ethservice := startEthService(t, genesis, preMergeBlocks)
- defer n.Close()
-
- var (
- api = NewConsensusAPI(ethservice)
- parent = ethservice.BlockChain().CurrentBlock()
- signer = types.LatestSigner(ethservice.BlockChain().Config())
- // This EVM code generates a log when the contract is created.
- logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
- )
- for i := 0; i < 10; i++ {
- statedb, _ := ethservice.BlockChain().StateAt(parent.Root)
- tx := types.MustSignNewTx(testKey, signer, &types.LegacyTx{
- Nonce: statedb.GetNonce(testAddr),
- Value: new(big.Int),
- Gas: 1000000,
- GasPrice: big.NewInt(2 * params.InitialBaseFee),
- Data: logCode,
- })
- ethservice.TxPool().Add([]*types.Transaction{tx}, false, true)
- var (
- params = engine.PayloadAttributes{
- Timestamp: parent.Time + 1,
- Random: crypto.Keccak256Hash([]byte{byte(i)}),
- SuggestedFeeRecipient: parent.Coinbase,
- }
- fcState = engine.ForkchoiceStateV1{
- HeadBlockHash: parent.Hash(),
- SafeBlockHash: common.Hash{},
- FinalizedBlockHash: common.Hash{},
- }
- payload *engine.ExecutableData
- resp engine.ForkChoiceResponse
- err error
- )
- for i := 0; ; i++ {
- if resp, err = api.ForkchoiceUpdatedV1(fcState, ¶ms); err != nil {
- t.Fatalf("error preparing payload, err=%v", err)
- }
- if resp.PayloadStatus.Status != engine.VALID {
- t.Fatalf("error preparing payload, invalid status: %v", resp.PayloadStatus.Status)
- }
- // give the payload some time to be built
- time.Sleep(50 * time.Millisecond)
- if payload, err = api.GetPayloadV1(*resp.PayloadID); err != nil {
- t.Fatalf("can't get payload: %v", err)
- }
- if len(payload.Transactions) > 0 {
- break
- }
- // No luck this time we need to update the params and try again.
- params.Timestamp = params.Timestamp + 1
- if i > 10 {
- t.Fatalf("payload should not be empty")
- }
- }
- execResp, err := api.NewPayloadV1(*payload)
- if err != nil {
- t.Fatalf("can't execute payload: %v", err)
- }
- if execResp.Status != engine.VALID {
- t.Fatalf("invalid status: %v", execResp.Status)
- }
- fcState = engine.ForkchoiceStateV1{
- HeadBlockHash: payload.BlockHash,
- SafeBlockHash: payload.ParentHash,
- FinalizedBlockHash: payload.ParentHash,
- }
- if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
- t.Fatalf("Failed to insert block: %v", err)
- }
- if ethservice.BlockChain().CurrentBlock().Number.Uint64() != payload.Number {
- t.Fatalf("Chain head should be updated")
- }
- parent = ethservice.BlockChain().CurrentBlock()
- }
-}
-
-func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *engine.PayloadAttributes) (*engine.ExecutableData, error) {
- args := &miner.BuildPayloadArgs{
- Parent: parentHash,
- Timestamp: params.Timestamp,
- FeeRecipient: params.SuggestedFeeRecipient,
- Random: params.Random,
- Withdrawals: params.Withdrawals,
- BeaconRoot: params.BeaconRoot,
- }
- payload, err := api.eth.Miner().BuildPayload(args)
- if err != nil {
- return nil, err
- }
- return payload.ResolveFull().ExecutionPayload, nil
-}
-
-func TestEmptyBlocks(t *testing.T) {
- genesis, preMergeBlocks := generateMergeChain(10, false)
- n, ethservice := startEthService(t, genesis, preMergeBlocks)
- defer n.Close()
-
- commonAncestor := ethservice.BlockChain().CurrentBlock()
- api := NewConsensusAPI(ethservice)
-
- // Setup 10 blocks on the canonical chain
- setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil)
-
- // (1) check LatestValidHash by sending a normal payload (P1'')
- payload := getNewPayload(t, api, commonAncestor, nil)
-
- status, err := api.NewPayloadV1(*payload)
- if err != nil {
- t.Fatal(err)
- }
- if status.Status != engine.VALID {
- t.Errorf("invalid status: expected VALID got: %v", status.Status)
- }
- if !bytes.Equal(status.LatestValidHash[:], payload.BlockHash[:]) {
- t.Fatalf("invalid LVH: got %v want %v", status.LatestValidHash, payload.BlockHash)
- }
-
- // (2) Now send P1' which is invalid
- payload = getNewPayload(t, api, commonAncestor, nil)
- payload.GasUsed += 1
- payload = setBlockhash(payload)
- // Now latestValidHash should be the common ancestor
- status, err = api.NewPayloadV1(*payload)
- if err != nil {
- t.Fatal(err)
- }
- if status.Status != engine.INVALID {
- t.Errorf("invalid status: expected INVALID got: %v", status.Status)
- }
- // Expect 0x0 on INVALID block on top of PoW block
- expected := common.Hash{}
- if !bytes.Equal(status.LatestValidHash[:], expected[:]) {
- t.Fatalf("invalid LVH: got %v want %v", status.LatestValidHash, expected)
- }
-
- // (3) Now send a payload with unknown parent
- payload = getNewPayload(t, api, commonAncestor, nil)
- payload.ParentHash = common.Hash{1}
- payload = setBlockhash(payload)
- // Now latestValidHash should be the common ancestor
- status, err = api.NewPayloadV1(*payload)
- if err != nil {
- t.Fatal(err)
- }
- if status.Status != engine.SYNCING {
- t.Errorf("invalid status: expected SYNCING got: %v", status.Status)
- }
- if status.LatestValidHash != nil {
- t.Fatalf("invalid LVH: got %v wanted nil", status.LatestValidHash)
- }
-}
-
-func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Header, withdrawals []*types.Withdrawal) *engine.ExecutableData {
- params := engine.PayloadAttributes{
- Timestamp: parent.Time + 1,
- Random: crypto.Keccak256Hash([]byte{byte(1)}),
- SuggestedFeeRecipient: parent.Coinbase,
- Withdrawals: withdrawals,
- }
-
- payload, err := assembleBlock(api, parent.Hash(), ¶ms)
- if err != nil {
- t.Fatal(err)
- }
- return payload
-}
-
-// setBlockhash sets the blockhash of a modified ExecutableData.
-// Can be used to make modified payloads look valid.
-func setBlockhash(data *engine.ExecutableData) *engine.ExecutableData {
- txs, _ := decodeTransactions(data.Transactions)
- number := big.NewInt(0)
- number.SetUint64(data.Number)
- header := &types.Header{
- ParentHash: data.ParentHash,
- UncleHash: types.EmptyUncleHash,
- Coinbase: data.FeeRecipient,
- Root: data.StateRoot,
- TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
- ReceiptHash: data.ReceiptsRoot,
- Bloom: types.BytesToBloom(data.LogsBloom),
- Difficulty: common.Big0,
- Number: number,
- GasLimit: data.GasLimit,
- GasUsed: data.GasUsed,
- Time: data.Timestamp,
- BaseFee: data.BaseFeePerGas,
- Extra: data.ExtraData,
- MixDigest: data.Random,
- }
- block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */)
- data.BlockHash = block.Hash()
- return data
-}
-
-func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
- var txs = make([]*types.Transaction, len(enc))
- for i, encTx := range enc {
- var tx types.Transaction
- if err := tx.UnmarshalBinary(encTx); err != nil {
- return nil, fmt.Errorf("invalid transaction %d: %v", i, err)
- }
- txs[i] = &tx
- }
- return txs, nil
-}
-
-func TestTrickRemoteBlockCache(t *testing.T) {
- // Setup two nodes
- genesis, preMergeBlocks := generateMergeChain(10, false)
- nodeA, ethserviceA := startEthService(t, genesis, preMergeBlocks)
- nodeB, ethserviceB := startEthService(t, genesis, preMergeBlocks)
- defer nodeA.Close()
- defer nodeB.Close()
- for nodeB.Server().NodeInfo().Ports.Listener == 0 {
- time.Sleep(250 * time.Millisecond)
- }
- nodeA.Server().AddPeer(nodeB.Server().Self())
- nodeB.Server().AddPeer(nodeA.Server().Self())
- apiA := NewConsensusAPI(ethserviceA)
- apiB := NewConsensusAPI(ethserviceB)
-
- commonAncestor := ethserviceA.BlockChain().CurrentBlock()
-
- // Setup 10 blocks on the canonical chain
- setupBlocks(t, ethserviceA, 10, commonAncestor, func(parent *types.Header) {}, nil)
- commonAncestor = ethserviceA.BlockChain().CurrentBlock()
-
- var invalidChain []*engine.ExecutableData
- // create a valid payload (P1)
- //payload1 := getNewPayload(t, apiA, commonAncestor)
- //invalidChain = append(invalidChain, payload1)
-
- // create an invalid payload2 (P2)
- payload2 := getNewPayload(t, apiA, commonAncestor, nil)
- //payload2.ParentHash = payload1.BlockHash
- payload2.GasUsed += 1
- payload2 = setBlockhash(payload2)
- invalidChain = append(invalidChain, payload2)
-
- head := payload2
- // create some valid payloads on top
- for i := 0; i < 10; i++ {
- payload := getNewPayload(t, apiA, commonAncestor, nil)
- payload.ParentHash = head.BlockHash
- payload = setBlockhash(payload)
- invalidChain = append(invalidChain, payload)
- head = payload
- }
-
- // feed the payloads to node B
- for _, payload := range invalidChain {
- status, err := apiB.NewPayloadV1(*payload)
- if err != nil {
- panic(err)
- }
- if status.Status == engine.VALID {
- t.Error("invalid status: VALID on an invalid chain")
- }
- // Now reorg to the head of the invalid chain
- resp, err := apiB.ForkchoiceUpdatedV1(engine.ForkchoiceStateV1{HeadBlockHash: payload.BlockHash, SafeBlockHash: payload.BlockHash, FinalizedBlockHash: payload.ParentHash}, nil)
- if err != nil {
- t.Fatal(err)
- }
- if resp.PayloadStatus.Status == engine.VALID {
- t.Error("invalid status: VALID on an invalid chain")
- }
- time.Sleep(100 * time.Millisecond)
- }
-}
-
-func TestInvalidBloom(t *testing.T) {
- genesis, preMergeBlocks := generateMergeChain(10, false)
- n, ethservice := startEthService(t, genesis, preMergeBlocks)
- ethservice.Merger().ReachTTD()
- defer n.Close()
-
- commonAncestor := ethservice.BlockChain().CurrentBlock()
- api := NewConsensusAPI(ethservice)
-
- // Setup 10 blocks on the canonical chain
- setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil)
-
- // (1) check LatestValidHash by sending a normal payload (P1'')
- payload := getNewPayload(t, api, commonAncestor, nil)
- payload.LogsBloom = append(payload.LogsBloom, byte(1))
- status, err := api.NewPayloadV1(*payload)
- if err != nil {
- t.Fatal(err)
- }
- if status.Status != engine.INVALID {
- t.Errorf("invalid status: expected INVALID got: %v", status.Status)
- }
-}
-
-func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) {
- genesis, preMergeBlocks := generateMergeChain(100, false)
- n, ethservice := startEthService(t, genesis, preMergeBlocks)
- defer n.Close()
- api := NewConsensusAPI(ethservice)
-
- // Test parent already post TTD in FCU
- parent := preMergeBlocks[len(preMergeBlocks)-2]
- fcState := engine.ForkchoiceStateV1{
- HeadBlockHash: parent.Hash(),
- SafeBlockHash: common.Hash{},
- FinalizedBlockHash: common.Hash{},
- }
- resp, err := api.ForkchoiceUpdatedV1(fcState, nil)
- if err != nil {
- t.Fatalf("error sending forkchoice, err=%v", err)
- }
- if resp.PayloadStatus != engine.INVALID_TERMINAL_BLOCK {
- t.Fatalf("error sending invalid forkchoice, invalid status: %v", resp.PayloadStatus.Status)
- }
-
- // Test parent already post TTD in NewPayload
- args := &miner.BuildPayloadArgs{
- Parent: parent.Hash(),
- Timestamp: parent.Time() + 1,
- Random: crypto.Keccak256Hash([]byte{byte(1)}),
- FeeRecipient: parent.Coinbase(),
- }
- payload, err := api.eth.Miner().BuildPayload(args)
- if err != nil {
- t.Fatalf("error preparing payload, err=%v", err)
- }
- data := *payload.Resolve().ExecutionPayload
- // We need to recompute the blockhash, since the miner computes a wrong (correct) blockhash
- txs, _ := decodeTransactions(data.Transactions)
- header := &types.Header{
- ParentHash: data.ParentHash,
- UncleHash: types.EmptyUncleHash,
- Coinbase: data.FeeRecipient,
- Root: data.StateRoot,
- TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
- ReceiptHash: data.ReceiptsRoot,
- Bloom: types.BytesToBloom(data.LogsBloom),
- Difficulty: common.Big0,
- Number: new(big.Int).SetUint64(data.Number),
- GasLimit: data.GasLimit,
- GasUsed: data.GasUsed,
- Time: data.Timestamp,
- BaseFee: data.BaseFeePerGas,
- Extra: data.ExtraData,
- MixDigest: data.Random,
- }
- block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */)
- data.BlockHash = block.Hash()
- // Send the new payload
- resp2, err := api.NewPayloadV1(data)
- if err != nil {
- t.Fatalf("error sending NewPayload, err=%v", err)
- }
- if resp2 != engine.INVALID_TERMINAL_BLOCK {
- t.Fatalf("error sending invalid forkchoice, invalid status: %v", resp.PayloadStatus.Status)
- }
-}
-
-// TestSimultaneousNewBlock does several parallel inserts, both as
-// newPayLoad and forkchoiceUpdate. This is to test that the api behaves
-// well even of the caller is not being 'serial'.
-func TestSimultaneousNewBlock(t *testing.T) {
- genesis, preMergeBlocks := generateMergeChain(10, false)
- n, ethservice := startEthService(t, genesis, preMergeBlocks)
- defer n.Close()
-
- var (
- api = NewConsensusAPI(ethservice)
- parent = preMergeBlocks[len(preMergeBlocks)-1]
- )
- for i := 0; i < 10; i++ {
- execData, err := assembleBlock(api, parent.Hash(), &engine.PayloadAttributes{
- Timestamp: parent.Time() + 5,
- })
- if err != nil {
- t.Fatalf("Failed to create the executable data %v", err)
- }
- // Insert it 10 times in parallel. Should be ignored.
- {
- var (
- wg sync.WaitGroup
- testErr error
- errMu sync.Mutex
- )
- wg.Add(10)
- for ii := 0; ii < 10; ii++ {
- go func() {
- defer wg.Done()
- if newResp, err := api.NewPayloadV1(*execData); err != nil {
- errMu.Lock()
- testErr = fmt.Errorf("Failed to insert block: %w", err)
- errMu.Unlock()
- } else if newResp.Status != "VALID" {
- errMu.Lock()
- testErr = fmt.Errorf("Failed to insert block: %v", newResp.Status)
- errMu.Unlock()
- }
- }()
- }
- wg.Wait()
- if testErr != nil {
- t.Fatal(testErr)
- }
- }
- block, err := engine.ExecutableDataToBlock(*execData, nil, nil)
- if err != nil {
- t.Fatalf("Failed to convert executable data to block %v", err)
- }
- if ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64()-1 {
- t.Fatalf("Chain head shouldn't be updated")
- }
- fcState := engine.ForkchoiceStateV1{
- HeadBlockHash: block.Hash(),
- SafeBlockHash: block.Hash(),
- FinalizedBlockHash: block.Hash(),
- }
- {
- var (
- wg sync.WaitGroup
- testErr error
- errMu sync.Mutex
- )
- wg.Add(10)
- // Do each FCU 10 times
- for ii := 0; ii < 10; ii++ {
- go func() {
- defer wg.Done()
- if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
- errMu.Lock()
- testErr = fmt.Errorf("Failed to insert block: %w", err)
- errMu.Unlock()
- }
- }()
- }
- wg.Wait()
- if testErr != nil {
- t.Fatal(testErr)
- }
- }
- if have, want := ethservice.BlockChain().CurrentBlock().Number.Uint64(), block.NumberU64(); have != want {
- t.Fatalf("Chain head should be updated, have %d want %d", have, want)
- }
- parent = block
- }
-}
-
-// TestWithdrawals creates and verifies two post-Shanghai blocks. The first
-// includes zero withdrawals and the second includes two.
-func TestWithdrawals(t *testing.T) {
- genesis, blocks := generateMergeChain(10, true)
- // Set shanghai time to last block + 5 seconds (first post-merge block)
- time := blocks[len(blocks)-1].Time() + 5
- genesis.Config.ShanghaiTime = &time
-
- n, ethservice := startEthService(t, genesis, blocks)
- ethservice.Merger().ReachTTD()
- defer n.Close()
-
- api := NewConsensusAPI(ethservice)
-
- // 10: Build Shanghai block with no withdrawals.
- parent := ethservice.BlockChain().CurrentHeader()
- blockParams := engine.PayloadAttributes{
- Timestamp: parent.Time + 5,
- Withdrawals: make([]*types.Withdrawal, 0),
- }
- fcState := engine.ForkchoiceStateV1{
- HeadBlockHash: parent.Hash(),
- }
- resp, err := api.ForkchoiceUpdatedV2(fcState, &blockParams)
- if err != nil {
- t.Fatalf("error preparing payload, err=%v", err)
- }
- if resp.PayloadStatus.Status != engine.VALID {
- t.Fatalf("unexpected status (got: %s, want: %s)", resp.PayloadStatus.Status, engine.VALID)
- }
-
- // 10: verify state root is the same as parent
- payloadID := (&miner.BuildPayloadArgs{
- Parent: fcState.HeadBlockHash,
- Timestamp: blockParams.Timestamp,
- FeeRecipient: blockParams.SuggestedFeeRecipient,
- Random: blockParams.Random,
- Withdrawals: blockParams.Withdrawals,
- BeaconRoot: blockParams.BeaconRoot,
- }).Id()
- execData, err := api.GetPayloadV2(payloadID)
- if err != nil {
- t.Fatalf("error getting payload, err=%v", err)
- }
- if execData.ExecutionPayload.StateRoot != parent.Root {
- t.Fatalf("mismatch state roots (got: %s, want: %s)", execData.ExecutionPayload.StateRoot, blocks[8].Root())
- }
-
- // 10: verify locally built block
- if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil {
- t.Fatalf("error validating payload: %v", err)
- } else if status.Status != engine.VALID {
- t.Fatalf("invalid payload")
- }
-
- // 11: build shanghai block with withdrawal
- aa := common.Address{0xaa}
- bb := common.Address{0xbb}
- blockParams = engine.PayloadAttributes{
- Timestamp: execData.ExecutionPayload.Timestamp + 5,
- Withdrawals: []*types.Withdrawal{
- {
- Index: 0,
- Address: aa,
- Amount: 32,
- },
- {
- Index: 1,
- Address: bb,
- Amount: 33,
- },
- },
- }
- fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
- _, err = api.ForkchoiceUpdatedV2(fcState, &blockParams)
- if err != nil {
- t.Fatalf("error preparing payload, err=%v", err)
- }
-
- // 11: verify locally build block.
- payloadID = (&miner.BuildPayloadArgs{
- Parent: fcState.HeadBlockHash,
- Timestamp: blockParams.Timestamp,
- FeeRecipient: blockParams.SuggestedFeeRecipient,
- Random: blockParams.Random,
- Withdrawals: blockParams.Withdrawals,
- BeaconRoot: blockParams.BeaconRoot,
- }).Id()
- execData, err = api.GetPayloadV2(payloadID)
- if err != nil {
- t.Fatalf("error getting payload, err=%v", err)
- }
- if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil {
- t.Fatalf("error validating payload: %v", err)
- } else if status.Status != engine.VALID {
- t.Fatalf("invalid payload")
- }
-
- // 11: set block as head.
- fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
- _, err = api.ForkchoiceUpdatedV2(fcState, nil)
- if err != nil {
- t.Fatalf("error preparing payload, err=%v", err)
- }
-
- // 11: verify withdrawals were processed.
- db, _, err := ethservice.APIBackend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(execData.ExecutionPayload.Number))
- if err != nil {
- t.Fatalf("unable to load db: %v", err)
- }
- for i, w := range blockParams.Withdrawals {
- // w.Amount is in gwei, balance in wei
- if db.GetBalance(w.Address).Uint64() != w.Amount*params.GWei {
- t.Fatalf("failed to process withdrawal %d", i)
- }
- }
-}
-
-func TestNilWithdrawals(t *testing.T) {
- genesis, blocks := generateMergeChain(10, true)
- // Set shanghai time to last block + 4 seconds (first post-merge block)
- time := blocks[len(blocks)-1].Time() + 4
- genesis.Config.ShanghaiTime = &time
-
- n, ethservice := startEthService(t, genesis, blocks)
- ethservice.Merger().ReachTTD()
- defer n.Close()
-
- api := NewConsensusAPI(ethservice)
- parent := ethservice.BlockChain().CurrentHeader()
- aa := common.Address{0xaa}
-
- type test struct {
- blockParams engine.PayloadAttributes
- wantErr bool
- }
- tests := []test{
- // Before Shanghai
- {
- blockParams: engine.PayloadAttributes{
- Timestamp: parent.Time + 2,
- Withdrawals: nil,
- },
- wantErr: false,
- },
- {
- blockParams: engine.PayloadAttributes{
- Timestamp: parent.Time + 2,
- Withdrawals: make([]*types.Withdrawal, 0),
- },
- wantErr: true,
- },
- {
- blockParams: engine.PayloadAttributes{
- Timestamp: parent.Time + 2,
- Withdrawals: []*types.Withdrawal{
- {
- Index: 0,
- Address: aa,
- Amount: 32,
- },
- },
- },
- wantErr: true,
- },
- // After Shanghai
- {
- blockParams: engine.PayloadAttributes{
- Timestamp: parent.Time + 5,
- Withdrawals: nil,
- },
- wantErr: true,
- },
- {
- blockParams: engine.PayloadAttributes{
- Timestamp: parent.Time + 5,
- Withdrawals: make([]*types.Withdrawal, 0),
- },
- wantErr: false,
- },
- {
- blockParams: engine.PayloadAttributes{
- Timestamp: parent.Time + 5,
- Withdrawals: []*types.Withdrawal{
- {
- Index: 0,
- Address: aa,
- Amount: 32,
- },
- },
- },
- wantErr: false,
- },
- }
-
- fcState := engine.ForkchoiceStateV1{
- HeadBlockHash: parent.Hash(),
- }
-
- for _, test := range tests {
- _, err := api.ForkchoiceUpdatedV2(fcState, &test.blockParams)
- if test.wantErr {
- if err == nil {
- t.Fatal("wanted error on fcuv2 with invalid withdrawals")
- }
- continue
- }
- if err != nil {
- t.Fatalf("error preparing payload, err=%v", err)
- }
-
- // 11: verify locally build block.
- payloadID := (&miner.BuildPayloadArgs{
- Parent: fcState.HeadBlockHash,
- Timestamp: test.blockParams.Timestamp,
- FeeRecipient: test.blockParams.SuggestedFeeRecipient,
- Random: test.blockParams.Random,
- BeaconRoot: test.blockParams.BeaconRoot,
- }).Id()
- execData, err := api.GetPayloadV2(payloadID)
- if err != nil {
- t.Fatalf("error getting payload, err=%v", err)
- }
- if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil {
- t.Fatalf("error validating payload: %v", err)
- } else if status.Status != engine.VALID {
- t.Fatalf("invalid payload")
- }
- }
-}
-
-func setupBodies(t *testing.T) (*node.Node, *eth.Ethereum, []*types.Block) {
- genesis, blocks := generateMergeChain(10, true)
- // enable shanghai on the last block
- time := blocks[len(blocks)-1].Header().Time + 1
- genesis.Config.ShanghaiTime = &time
- n, ethservice := startEthService(t, genesis, blocks)
-
- var (
- parent = ethservice.BlockChain().CurrentBlock()
- // This EVM code generates a log when the contract is created.
- logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
- )
-
- callback := func(parent *types.Header) {
- statedb, _ := ethservice.BlockChain().StateAt(parent.Root)
- nonce := statedb.GetNonce(testAddr)
- tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
- ethservice.TxPool().Add([]*types.Transaction{tx}, false, false)
- }
-
- withdrawals := make([][]*types.Withdrawal, 10)
- withdrawals[0] = nil // should be filtered out by miner
- withdrawals[1] = make([]*types.Withdrawal, 0)
- for i := 2; i < len(withdrawals); i++ {
- addr := make([]byte, 20)
- crand.Read(addr)
- withdrawals[i] = []*types.Withdrawal{
- {Index: rand.Uint64(), Validator: rand.Uint64(), Amount: rand.Uint64(), Address: common.BytesToAddress(addr)},
- }
- }
-
- postShanghaiHeaders := setupBlocks(t, ethservice, 10, parent, callback, withdrawals)
- postShanghaiBlocks := make([]*types.Block, len(postShanghaiHeaders))
- for i, header := range postShanghaiHeaders {
- postShanghaiBlocks[i] = ethservice.BlockChain().GetBlock(header.Hash(), header.Number.Uint64())
- }
- return n, ethservice, append(blocks, postShanghaiBlocks...)
-}
-
-func allHashes(blocks []*types.Block) []common.Hash {
- var hashes []common.Hash
- for _, b := range blocks {
- hashes = append(hashes, b.Hash())
- }
- return hashes
-}
-func allBodies(blocks []*types.Block) []*types.Body {
- var bodies []*types.Body
- for _, b := range blocks {
- bodies = append(bodies, b.Body())
- }
- return bodies
-}
-
-func TestGetBlockBodiesByHash(t *testing.T) {
- node, eth, blocks := setupBodies(t)
- api := NewConsensusAPI(eth)
- defer node.Close()
-
- tests := []struct {
- results []*types.Body
- hashes []common.Hash
- }{
- // First pow block
- {
- results: []*types.Body{eth.BlockChain().GetBlockByNumber(0).Body()},
- hashes: []common.Hash{eth.BlockChain().GetBlockByNumber(0).Hash()},
- },
- // Last pow block
- {
- results: []*types.Body{blocks[9].Body()},
- hashes: []common.Hash{blocks[9].Hash()},
- },
- // First post-merge block
- {
- results: []*types.Body{blocks[10].Body()},
- hashes: []common.Hash{blocks[10].Hash()},
- },
- // Pre & post merge blocks
- {
- results: []*types.Body{blocks[0].Body(), blocks[9].Body(), blocks[14].Body()},
- hashes: []common.Hash{blocks[0].Hash(), blocks[9].Hash(), blocks[14].Hash()},
- },
- // unavailable block
- {
- results: []*types.Body{blocks[0].Body(), nil, blocks[14].Body()},
- hashes: []common.Hash{blocks[0].Hash(), {1, 2}, blocks[14].Hash()},
- },
- // same block multiple times
- {
- results: []*types.Body{blocks[0].Body(), nil, blocks[0].Body(), blocks[0].Body()},
- hashes: []common.Hash{blocks[0].Hash(), {1, 2}, blocks[0].Hash(), blocks[0].Hash()},
- },
- // all blocks
- {
- results: allBodies(blocks),
- hashes: allHashes(blocks),
- },
- }
-
- for k, test := range tests {
- result := api.GetPayloadBodiesByHashV1(test.hashes)
- for i, r := range result {
- if !equalBody(test.results[i], r) {
- t.Fatalf("test %v: invalid response: expected %+v got %+v", k, test.results[i], r)
- }
- }
- }
-}
-
-func TestGetBlockBodiesByRange(t *testing.T) {
- node, eth, blocks := setupBodies(t)
- api := NewConsensusAPI(eth)
- defer node.Close()
-
- tests := []struct {
- results []*types.Body
- start hexutil.Uint64
- count hexutil.Uint64
- }{
- {
- results: []*types.Body{blocks[9].Body()},
- start: 10,
- count: 1,
- },
- // Genesis
- {
- results: []*types.Body{blocks[0].Body()},
- start: 1,
- count: 1,
- },
- // First post-merge block
- {
- results: []*types.Body{blocks[9].Body()},
- start: 10,
- count: 1,
- },
- // Pre & post merge blocks
- {
- results: []*types.Body{blocks[7].Body(), blocks[8].Body(), blocks[9].Body(), blocks[10].Body()},
- start: 8,
- count: 4,
- },
- // unavailable block
- {
- results: []*types.Body{blocks[18].Body(), blocks[19].Body()},
- start: 19,
- count: 3,
- },
- // unavailable block
- {
- results: []*types.Body{blocks[19].Body()},
- start: 20,
- count: 2,
- },
- {
- results: []*types.Body{blocks[19].Body()},
- start: 20,
- count: 1,
- },
- // whole range unavailable
- {
- results: make([]*types.Body, 0),
- start: 22,
- count: 2,
- },
- // allBlocks
- {
- results: allBodies(blocks),
- start: 1,
- count: hexutil.Uint64(len(blocks)),
- },
- }
-
- for k, test := range tests {
- result, err := api.GetPayloadBodiesByRangeV1(test.start, test.count)
- if err != nil {
- t.Fatal(err)
- }
- if len(result) == len(test.results) {
- for i, r := range result {
- if !equalBody(test.results[i], r) {
- t.Fatalf("test %d: invalid response: expected \n%+v\ngot\n%+v", k, test.results[i], r)
- }
- }
- } else {
- t.Fatalf("test %d: invalid length want %v got %v", k, len(test.results), len(result))
- }
- }
-}
-
-func TestGetBlockBodiesByRangeInvalidParams(t *testing.T) {
- node, eth, _ := setupBodies(t)
- api := NewConsensusAPI(eth)
- defer node.Close()
- tests := []struct {
- start hexutil.Uint64
- count hexutil.Uint64
- want *engine.EngineAPIError
- }{
- // Genesis
- {
- start: 0,
- count: 1,
- want: engine.InvalidParams,
- },
- // No block requested
- {
- start: 1,
- count: 0,
- want: engine.InvalidParams,
- },
- // Genesis & no block
- {
- start: 0,
- count: 0,
- want: engine.InvalidParams,
- },
- // More than 1024 blocks
- {
- start: 1,
- count: 1025,
- want: engine.TooLargeRequest,
- },
- }
- for i, tc := range tests {
- result, err := api.GetPayloadBodiesByRangeV1(tc.start, tc.count)
- if err == nil {
- t.Fatalf("test %d: expected error, got %v", i, result)
- }
- if have, want := err.Error(), tc.want.Error(); have != want {
- t.Fatalf("test %d: have %s, want %s", i, have, want)
- }
- }
-}
-
-func equalBody(a *types.Body, b *engine.ExecutionPayloadBodyV1) bool {
- if a == nil && b == nil {
- return true
- } else if a == nil || b == nil {
- return false
- }
- if len(a.Transactions) != len(b.TransactionData) {
- return false
- }
- for i, tx := range a.Transactions {
- data, _ := tx.MarshalBinary()
- if !bytes.Equal(data, b.TransactionData[i]) {
- return false
- }
- }
- return reflect.DeepEqual(a.Withdrawals, b.Withdrawals)
-}
-
-func TestBlockToPayloadWithBlobs(t *testing.T) {
- header := types.Header{}
- var txs []*types.Transaction
-
- inner := types.BlobTx{
- BlobHashes: make([]common.Hash, 1),
- }
-
- txs = append(txs, types.NewTx(&inner))
- sidecars := []*types.BlobTxSidecar{
- {
- Blobs: make([]kzg4844.Blob, 1),
- Commitments: make([]kzg4844.Commitment, 1),
- Proofs: make([]kzg4844.Proof, 1),
- },
- }
-
- block := types.NewBlock(&header, txs, nil, nil, trie.NewStackTrie(nil))
- envelope := engine.BlockToExecutableData(block, nil, sidecars)
- var want int
- for _, tx := range txs {
- want += len(tx.BlobHashes())
- }
- if got := len(envelope.BlobsBundle.Commitments); got != want {
- t.Fatalf("invalid number of commitments: got %v, want %v", got, want)
- }
- if got := len(envelope.BlobsBundle.Proofs); got != want {
- t.Fatalf("invalid number of proofs: got %v, want %v", got, want)
- }
- if got := len(envelope.BlobsBundle.Blobs); got != want {
- t.Fatalf("invalid number of blobs: got %v, want %v", got, want)
- }
- _, err := engine.ExecutableDataToBlock(*envelope.ExecutionPayload, make([]common.Hash, 1), nil)
- if err != nil {
- t.Error(err)
- }
-}
-
-// This checks that beaconRoot is applied to the state from the engine API.
-func TestParentBeaconBlockRoot(t *testing.T) {
- log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(colorable.NewColorableStderr(), log.LevelTrace, true)))
-
- genesis, blocks := generateMergeChain(10, true)
-
- // Set cancun time to last block + 5 seconds
- time := blocks[len(blocks)-1].Time() + 5
- genesis.Config.ShanghaiTime = &time
- genesis.Config.CancunTime = &time
-
- n, ethservice := startEthService(t, genesis, blocks)
- ethservice.Merger().ReachTTD()
- defer n.Close()
-
- api := NewConsensusAPI(ethservice)
-
- // 11: Build Shanghai block with no withdrawals.
- parent := ethservice.BlockChain().CurrentHeader()
- blockParams := engine.PayloadAttributes{
- Timestamp: parent.Time + 5,
- Withdrawals: make([]*types.Withdrawal, 0),
- BeaconRoot: &common.Hash{42},
- }
- fcState := engine.ForkchoiceStateV1{
- HeadBlockHash: parent.Hash(),
- }
- resp, err := api.ForkchoiceUpdatedV2(fcState, &blockParams)
- if err != nil {
- t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData())
- }
- if resp.PayloadStatus.Status != engine.VALID {
- t.Fatalf("unexpected status (got: %s, want: %s)", resp.PayloadStatus.Status, engine.VALID)
- }
-
- // 11: verify state root is the same as parent
- payloadID := (&miner.BuildPayloadArgs{
- Parent: fcState.HeadBlockHash,
- Timestamp: blockParams.Timestamp,
- FeeRecipient: blockParams.SuggestedFeeRecipient,
- Random: blockParams.Random,
- Withdrawals: blockParams.Withdrawals,
- BeaconRoot: blockParams.BeaconRoot,
- }).Id()
- execData, err := api.GetPayloadV3(payloadID)
- if err != nil {
- t.Fatalf("error getting payload, err=%v", err)
- }
-
- // 11: verify locally built block
- if status, err := api.NewPayloadV3(*execData.ExecutionPayload, []common.Hash{}, &common.Hash{42}); err != nil {
- t.Fatalf("error validating payload: %v", err)
- } else if status.Status != engine.VALID {
- t.Fatalf("invalid payload")
- }
-
- fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
- resp, err = api.ForkchoiceUpdatedV3(fcState, nil)
- if err != nil {
- t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData())
- }
- if resp.PayloadStatus.Status != engine.VALID {
- t.Fatalf("unexpected status (got: %s, want: %s)", resp.PayloadStatus.Status, engine.VALID)
- }
-
- // 11: verify beacon root was processed.
- db, _, err := ethservice.APIBackend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(execData.ExecutionPayload.Number))
- if err != nil {
- t.Fatalf("unable to load db: %v", err)
- }
- var (
- timeIdx = common.BigToHash(big.NewInt(int64(execData.ExecutionPayload.Timestamp % 98304)))
- rootIdx = common.BigToHash(big.NewInt(int64((execData.ExecutionPayload.Timestamp % 98304) + 98304)))
- )
-
- if num := db.GetState(params.BeaconRootsStorageAddress, timeIdx); num != timeIdx {
- t.Fatalf("incorrect number stored: want %s, got %s", timeIdx, num)
- }
- if root := db.GetState(params.BeaconRootsStorageAddress, rootIdx); root != *blockParams.BeaconRoot {
- t.Fatalf("incorrect root stored: want %s, got %s", *blockParams.BeaconRoot, root)
- }
-}
diff --git a/eth/catalyst/queue.go b/eth/catalyst/queue.go
deleted file mode 100644
index 634dc1b2e6..0000000000
--- a/eth/catalyst/queue.go
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2022 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 .
-
-package catalyst
-
-import (
- "sync"
-
- "github.com/ethereum/go-ethereum/beacon/engine"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/miner"
-)
-
-// maxTrackedPayloads is the maximum number of prepared payloads the execution
-// engine tracks before evicting old ones. Ideally we should only ever track the
-// latest one; but have a slight wiggle room for non-ideal conditions.
-const maxTrackedPayloads = 10
-
-// maxTrackedHeaders is the maximum number of executed payloads the execution
-// engine tracks before evicting old ones. These are tracked outside the chain
-// during initial sync to allow ForkchoiceUpdate to reference past blocks via
-// hashes only. For the sync target it would be enough to track only the latest
-// header, but snap sync also needs the latest finalized height for the ancient
-// limit.
-const maxTrackedHeaders = 96
-
-// payloadQueueItem represents an id->payload tuple to store until it's retrieved
-// or evicted.
-type payloadQueueItem struct {
- id engine.PayloadID
- payload *miner.Payload
-}
-
-// payloadQueue tracks the latest handful of constructed payloads to be retrieved
-// by the beacon chain if block production is requested.
-type payloadQueue struct {
- payloads []*payloadQueueItem
- lock sync.RWMutex
-}
-
-// newPayloadQueue creates a pre-initialized queue with a fixed number of slots
-// all containing empty items.
-func newPayloadQueue() *payloadQueue {
- return &payloadQueue{
- payloads: make([]*payloadQueueItem, maxTrackedPayloads),
- }
-}
-
-// put inserts a new payload into the queue at the given id.
-func (q *payloadQueue) put(id engine.PayloadID, payload *miner.Payload) {
- q.lock.Lock()
- defer q.lock.Unlock()
-
- copy(q.payloads[1:], q.payloads)
- q.payloads[0] = &payloadQueueItem{
- id: id,
- payload: payload,
- }
-}
-
-// get retrieves a previously stored payload item or nil if it does not exist.
-func (q *payloadQueue) get(id engine.PayloadID, full bool) *engine.ExecutionPayloadEnvelope {
- q.lock.RLock()
- defer q.lock.RUnlock()
-
- for _, item := range q.payloads {
- if item == nil {
- return nil // no more items
- }
- if item.id == id {
- if !full {
- return item.payload.Resolve()
- }
- return item.payload.ResolveFull()
- }
- }
- return nil
-}
-
-// has checks if a particular payload is already tracked.
-func (q *payloadQueue) has(id engine.PayloadID) bool {
- q.lock.RLock()
- defer q.lock.RUnlock()
-
- for _, item := range q.payloads {
- if item == nil {
- return false
- }
- if item.id == id {
- return true
- }
- }
- return false
-}
-
-// headerQueueItem represents an hash->header tuple to store until it's retrieved
-// or evicted.
-type headerQueueItem struct {
- hash common.Hash
- header *types.Header
-}
-
-// headerQueue tracks the latest handful of constructed headers to be retrieved
-// by the beacon chain if block production is requested.
-type headerQueue struct {
- headers []*headerQueueItem
- lock sync.RWMutex
-}
-
-// newHeaderQueue creates a pre-initialized queue with a fixed number of slots
-// all containing empty items.
-func newHeaderQueue() *headerQueue {
- return &headerQueue{
- headers: make([]*headerQueueItem, maxTrackedHeaders),
- }
-}
-
-// put inserts a new header into the queue at the given hash.
-func (q *headerQueue) put(hash common.Hash, data *types.Header) {
- q.lock.Lock()
- defer q.lock.Unlock()
-
- copy(q.headers[1:], q.headers)
- q.headers[0] = &headerQueueItem{
- hash: hash,
- header: data,
- }
-}
-
-// get retrieves a previously stored header item or nil if it does not exist.
-func (q *headerQueue) get(hash common.Hash) *types.Header {
- q.lock.RLock()
- defer q.lock.RUnlock()
-
- for _, item := range q.headers {
- if item == nil {
- return nil // no more items
- }
- if item.hash == hash {
- return item.header
- }
- }
- return nil
-}
diff --git a/eth/catalyst/simulated_beacon.go b/eth/catalyst/simulated_beacon.go
deleted file mode 100644
index d8b8641e6a..0000000000
--- a/eth/catalyst/simulated_beacon.go
+++ /dev/null
@@ -1,272 +0,0 @@
-// Copyright 2023 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 .
-
-package catalyst
-
-import (
- "crypto/rand"
- "errors"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/beacon/engine"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/eth"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/rpc"
-)
-
-const devEpochLength = 32
-
-// withdrawalQueue implements a FIFO queue which holds withdrawals that are
-// pending inclusion.
-type withdrawalQueue struct {
- pending chan *types.Withdrawal
-}
-
-// add queues a withdrawal for future inclusion.
-func (w *withdrawalQueue) add(withdrawal *types.Withdrawal) error {
- select {
- case w.pending <- withdrawal:
- break
- default:
- return errors.New("withdrawal queue full")
- }
- return nil
-}
-
-// gatherPending returns a number of queued withdrawals up to a maximum count.
-func (w *withdrawalQueue) gatherPending(maxCount int) []*types.Withdrawal {
- withdrawals := []*types.Withdrawal{}
- for {
- select {
- case withdrawal := <-w.pending:
- withdrawals = append(withdrawals, withdrawal)
- if len(withdrawals) == maxCount {
- break
- }
- default:
- return withdrawals
- }
- }
-}
-
-type SimulatedBeacon struct {
- shutdownCh chan struct{}
- eth *eth.Ethereum
- period uint64
- withdrawals withdrawalQueue
-
- feeRecipient common.Address
- feeRecipientLock sync.Mutex // lock gates concurrent access to the feeRecipient
-
- engineAPI *ConsensusAPI
- curForkchoiceState engine.ForkchoiceStateV1
- lastBlockTime uint64
-}
-
-func NewSimulatedBeacon(period uint64, eth *eth.Ethereum) (*SimulatedBeacon, error) {
- block := eth.BlockChain().CurrentBlock()
- current := engine.ForkchoiceStateV1{
- HeadBlockHash: block.Hash(),
- SafeBlockHash: block.Hash(),
- FinalizedBlockHash: block.Hash(),
- }
- engineAPI := newConsensusAPIWithoutHeartbeat(eth)
-
- // if genesis block, send forkchoiceUpdated to trigger transition to PoS
- if block.Number.Sign() == 0 {
- if _, err := engineAPI.ForkchoiceUpdatedV2(current, nil); err != nil {
- return nil, err
- }
- }
- return &SimulatedBeacon{
- eth: eth,
- period: period,
- shutdownCh: make(chan struct{}),
- engineAPI: engineAPI,
- lastBlockTime: block.Time,
- curForkchoiceState: current,
- withdrawals: withdrawalQueue{make(chan *types.Withdrawal, 20)},
- }, nil
-}
-
-func (c *SimulatedBeacon) setFeeRecipient(feeRecipient common.Address) {
- c.feeRecipientLock.Lock()
- c.feeRecipient = feeRecipient
- c.feeRecipientLock.Unlock()
-}
-
-// Start invokes the SimulatedBeacon life-cycle function in a goroutine.
-func (c *SimulatedBeacon) Start() error {
- if c.period == 0 {
- go c.loopOnDemand()
- } else {
- go c.loop()
- }
- return nil
-}
-
-// Stop halts the SimulatedBeacon service.
-func (c *SimulatedBeacon) Stop() error {
- close(c.shutdownCh)
- return nil
-}
-
-// sealBlock initiates payload building for a new block and creates a new block
-// with the completed payload.
-func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal) error {
- tstamp := uint64(time.Now().Unix())
- if tstamp <= c.lastBlockTime {
- tstamp = c.lastBlockTime + 1
- }
- c.feeRecipientLock.Lock()
- feeRecipient := c.feeRecipient
- c.feeRecipientLock.Unlock()
-
- // Reset to CurrentBlock in case of the chain was rewound
- if header := c.eth.BlockChain().CurrentBlock(); c.curForkchoiceState.HeadBlockHash != header.Hash() {
- finalizedHash := c.finalizedBlockHash(header.Number.Uint64())
- c.setCurrentState(header.Hash(), *finalizedHash)
- }
-
- var random [32]byte
- rand.Read(random[:])
- fcResponse, err := c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, &engine.PayloadAttributes{
- Timestamp: tstamp,
- SuggestedFeeRecipient: feeRecipient,
- Withdrawals: withdrawals,
- Random: random,
- })
- if err != nil {
- return err
- }
- if fcResponse == engine.STATUS_SYNCING {
- return errors.New("chain rewind prevented invocation of payload creation")
- }
-
- envelope, err := c.engineAPI.getPayload(*fcResponse.PayloadID, true)
- if err != nil {
- return err
- }
- payload := envelope.ExecutionPayload
-
- var finalizedHash common.Hash
- if payload.Number%devEpochLength == 0 {
- finalizedHash = payload.BlockHash
- } else {
- if fh := c.finalizedBlockHash(payload.Number); fh == nil {
- return errors.New("chain rewind interrupted calculation of finalized block hash")
- } else {
- finalizedHash = *fh
- }
- }
-
- // Mark the payload as canon
- if _, err = c.engineAPI.NewPayloadV2(*payload); err != nil {
- return err
- }
- c.setCurrentState(payload.BlockHash, finalizedHash)
- // Mark the block containing the payload as canonical
- if _, err = c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, nil); err != nil {
- return err
- }
- c.lastBlockTime = payload.Timestamp
- return nil
-}
-
-// loopOnDemand runs the block production loop for "on-demand" configuration (period = 0)
-func (c *SimulatedBeacon) loopOnDemand() {
- var (
- newTxs = make(chan core.NewTxsEvent)
- sub = c.eth.TxPool().SubscribeTransactions(newTxs, true)
- )
- defer sub.Unsubscribe()
-
- for {
- select {
- case <-c.shutdownCh:
- return
- case w := <-c.withdrawals.pending:
- withdrawals := append(c.withdrawals.gatherPending(9), w)
- if err := c.sealBlock(withdrawals); err != nil {
- log.Warn("Error performing sealing work", "err", err)
- }
- case <-newTxs:
- withdrawals := c.withdrawals.gatherPending(10)
- if err := c.sealBlock(withdrawals); err != nil {
- log.Warn("Error performing sealing work", "err", err)
- }
- }
- }
-}
-
-// loop runs the block production loop for non-zero period configuration
-func (c *SimulatedBeacon) loop() {
- timer := time.NewTimer(0)
- for {
- select {
- case <-c.shutdownCh:
- return
- case <-timer.C:
- withdrawals := c.withdrawals.gatherPending(10)
- if err := c.sealBlock(withdrawals); err != nil {
- log.Warn("Error performing sealing work", "err", err)
- } else {
- timer.Reset(time.Second * time.Duration(c.period))
- }
- }
- }
-}
-
-// finalizedBlockHash returns the block hash of the finalized block corresponding to the given number
-// or nil if doesn't exist in the chain.
-func (c *SimulatedBeacon) finalizedBlockHash(number uint64) *common.Hash {
- var finalizedNumber uint64
- if number%devEpochLength == 0 {
- finalizedNumber = number
- } else {
- finalizedNumber = (number - 1) / devEpochLength * devEpochLength
- }
-
- if finalizedBlock := c.eth.BlockChain().GetBlockByNumber(finalizedNumber); finalizedBlock != nil {
- fh := finalizedBlock.Hash()
- return &fh
- }
- return nil
-}
-
-// setCurrentState sets the current forkchoice state
-func (c *SimulatedBeacon) setCurrentState(headHash, finalizedHash common.Hash) {
- c.curForkchoiceState = engine.ForkchoiceStateV1{
- HeadBlockHash: headHash,
- SafeBlockHash: headHash,
- FinalizedBlockHash: finalizedHash,
- }
-}
-
-func RegisterSimulatedBeaconAPIs(stack *node.Node, sim *SimulatedBeacon) {
- stack.RegisterAPIs([]rpc.API{
- {
- Namespace: "dev",
- Service: &api{sim},
- Version: "1.0",
- },
- })
-}
diff --git a/eth/catalyst/simulated_beacon_api.go b/eth/catalyst/simulated_beacon_api.go
deleted file mode 100644
index 93670257f6..0000000000
--- a/eth/catalyst/simulated_beacon_api.go
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2023 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 .
-
-package catalyst
-
-import (
- "context"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/types"
-)
-
-type api struct {
- simBeacon *SimulatedBeacon
-}
-
-func (a *api) AddWithdrawal(ctx context.Context, withdrawal *types.Withdrawal) error {
- return a.simBeacon.withdrawals.add(withdrawal)
-}
-
-func (a *api) SetFeeRecipient(ctx context.Context, feeRecipient common.Address) {
- a.simBeacon.setFeeRecipient(feeRecipient)
-}
diff --git a/eth/catalyst/simulated_beacon_test.go b/eth/catalyst/simulated_beacon_test.go
deleted file mode 100644
index 6fa97ad87a..0000000000
--- a/eth/catalyst/simulated_beacon_test.go
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright 2023 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 .
-
-package catalyst
-
-import (
- "context"
- "math/big"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
- "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/node"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/params"
-)
-
-func startSimulatedBeaconEthService(t *testing.T, genesis *core.Genesis) (*node.Node, *eth.Ethereum, *SimulatedBeacon) {
- t.Helper()
-
- n, err := node.New(&node.Config{
- P2P: p2p.Config{
- ListenAddr: "127.0.0.1:8545",
- NoDiscovery: true,
- MaxPeers: 0,
- },
- })
- if err != nil {
- t.Fatal("can't create node:", err)
- }
-
- ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256}
- ethservice, err := eth.New(n, ethcfg)
- if err != nil {
- t.Fatal("can't create eth service:", err)
- }
-
- simBeacon, err := NewSimulatedBeacon(1, ethservice)
- if err != nil {
- t.Fatal("can't create simulated beacon:", err)
- }
-
- n.RegisterLifecycle(simBeacon)
-
- if err := n.Start(); err != nil {
- t.Fatal("can't start node:", err)
- }
-
- ethservice.SetSynced()
- return n, ethservice, simBeacon
-}
-
-// send 20 transactions, >10 withdrawals and ensure they are included in order
-// send enough transactions to fill multiple blocks
-func TestSimulatedBeaconSendWithdrawals(t *testing.T) {
- var withdrawals []types.Withdrawal
- txs := make(map[common.Hash]types.Transaction)
-
- var (
- // testKey is a private key to use for funding a tester account.
- testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
-
- // testAddr is the Ethereum address of the tester account.
- testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
- )
-
- // short period (1 second) for testing purposes
- var gasLimit uint64 = 10_000_000
- genesis := core.DeveloperGenesisBlock(gasLimit, &testAddr)
- node, ethService, mock := startSimulatedBeaconEthService(t, genesis)
- _ = mock
- defer node.Close()
-
- chainHeadCh := make(chan core.ChainHeadEvent, 10)
- subscription := ethService.BlockChain().SubscribeChainHeadEvent(chainHeadCh)
- defer subscription.Unsubscribe()
-
- // generate some withdrawals
- for i := 0; i < 20; i++ {
- withdrawals = append(withdrawals, types.Withdrawal{Index: uint64(i)})
- if err := mock.withdrawals.add(&withdrawals[i]); err != nil {
- t.Fatal("addWithdrawal failed", err)
- }
- }
-
- // generate a bunch of transactions
- signer := types.NewEIP155Signer(ethService.BlockChain().Config().ChainID)
- for i := 0; i < 20; i++ {
- tx, err := types.SignTx(types.NewTransaction(uint64(i), common.Address{}, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey)
- if err != nil {
- t.Fatalf("error signing transaction, err=%v", err)
- }
- txs[tx.Hash()] = *tx
-
- if err := ethService.APIBackend.SendTx(context.Background(), tx); err != nil {
- t.Fatal("SendTx failed", err)
- }
- }
-
- includedTxs := make(map[common.Hash]struct{})
- var includedWithdrawals []uint64
-
- timer := time.NewTimer(12 * time.Second)
- for {
- select {
- case evt := <-chainHeadCh:
- for _, includedTx := range evt.Block.Transactions() {
- includedTxs[includedTx.Hash()] = struct{}{}
- }
- for _, includedWithdrawal := range evt.Block.Withdrawals() {
- includedWithdrawals = append(includedWithdrawals, includedWithdrawal.Index)
- }
-
- // ensure all withdrawals/txs included. this will take two blocks b/c number of withdrawals > 10
- if len(includedTxs) == len(txs) && len(includedWithdrawals) == len(withdrawals) && evt.Block.Number().Cmp(big.NewInt(2)) == 0 {
- return
- }
- case <-timer.C:
- t.Fatal("timed out without including all withdrawals/txs")
- }
- }
-}
diff --git a/eth/catalyst/tester.go b/eth/catalyst/tester.go
deleted file mode 100644
index 0922ac0ba6..0000000000
--- a/eth/catalyst/tester.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2022 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 .
-
-package catalyst
-
-import (
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/eth"
- "github.com/ethereum/go-ethereum/eth/downloader"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/node"
-)
-
-// FullSyncTester is an auxiliary service that allows Geth to perform full sync
-// alone without consensus-layer attached. Users must specify a valid block hash
-// as the sync target.
-//
-// This tester can be applied to different networks, no matter it's pre-merge or
-// post-merge, but only for full-sync.
-type FullSyncTester struct {
- stack *node.Node
- backend *eth.Ethereum
- target common.Hash
- closed chan struct{}
- wg sync.WaitGroup
-}
-
-// RegisterFullSyncTester registers the full-sync tester service into the node
-// stack for launching and stopping the service controlled by node.
-func RegisterFullSyncTester(stack *node.Node, backend *eth.Ethereum, target common.Hash) (*FullSyncTester, error) {
- cl := &FullSyncTester{
- stack: stack,
- backend: backend,
- target: target,
- closed: make(chan struct{}),
- }
- stack.RegisterLifecycle(cl)
- return cl, nil
-}
-
-// Start launches the beacon sync with provided sync target.
-func (tester *FullSyncTester) Start() error {
- tester.wg.Add(1)
- go func() {
- defer tester.wg.Done()
-
- // Trigger beacon sync with the provided block hash as trusted
- // chain head.
- err := tester.backend.Downloader().BeaconDevSync(downloader.FullSync, tester.target, tester.closed)
- if err != nil {
- log.Info("Failed to trigger beacon sync", "err", err)
- }
-
- ticker := time.NewTicker(time.Second * 5)
- defer ticker.Stop()
-
- for {
- select {
- case <-ticker.C:
- // Stop in case the target block is already stored locally.
- if block := tester.backend.BlockChain().GetBlockByHash(tester.target); block != nil {
- log.Info("Full-sync target reached", "number", block.NumberU64(), "hash", block.Hash())
- go tester.stack.Close() // async since we need to close ourselves
- return
- }
-
- case <-tester.closed:
- return
- }
- }
- }()
- return nil
-}
-
-// Stop stops the full-sync tester to stop all background activities.
-// This function can only be called for one time.
-func (tester *FullSyncTester) Stop() error {
- close(tester.closed)
- tester.wg.Wait()
- return nil
-}
diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go
deleted file mode 100644
index ad664afb5b..0000000000
--- a/eth/ethconfig/config.go
+++ /dev/null
@@ -1,179 +0,0 @@
-// Copyright 2021 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 .
-
-// Package ethconfig contains the configuration of the ETH and LES protocols.
-package ethconfig
-
-import (
- "errors"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/consensus"
- "github.com/ethereum/go-ethereum/consensus/beacon"
- "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/txpool/blobpool"
- "github.com/ethereum/go-ethereum/core/txpool/legacypool"
- "github.com/ethereum/go-ethereum/eth/downloader"
- "github.com/ethereum/go-ethereum/eth/gasprice"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/miner"
- "github.com/ethereum/go-ethereum/params"
-)
-
-// FullNodeGPO contains default gasprice oracle settings for full node.
-var FullNodeGPO = gasprice.Config{
- Blocks: 20,
- Percentile: 60,
- MaxHeaderHistory: 1024,
- MaxBlockHistory: 1024,
- MaxPrice: gasprice.DefaultMaxPrice,
- IgnorePrice: gasprice.DefaultIgnorePrice,
-}
-
-// Defaults contains default settings for use on the Ethereum main net.
-var Defaults = Config{
- SyncMode: downloader.SnapSync,
- NetworkId: 0, // enable auto configuration of networkID == chainID
- TxLookupLimit: 2350000,
- TransactionHistory: 2350000,
- StateHistory: params.FullImmutabilityThreshold,
- LightPeers: 100,
- DatabaseCache: 512,
- TrieCleanCache: 154,
- TrieDirtyCache: 256,
- TrieTimeout: 60 * time.Minute,
- SnapshotCache: 102,
- FilterLogCacheSize: 32,
- Miner: miner.DefaultConfig,
- TxPool: legacypool.DefaultConfig,
- BlobPool: blobpool.DefaultConfig,
- RPCGasCap: 50000000,
- RPCEVMTimeout: 5 * time.Second,
- GPO: FullNodeGPO,
- RPCTxFeeCap: 1, // 1 ether
-}
-
-//go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go
-
-// Config contains configuration options for ETH and LES protocols.
-type Config struct {
- // The genesis block, which is inserted if the database is empty.
- // If nil, the Ethereum main net block is used.
- Genesis *core.Genesis `toml:",omitempty"`
-
- // Network ID separates blockchains on the peer-to-peer networking level. When left
- // zero, the chain ID is used as network ID.
- NetworkId uint64
- SyncMode downloader.SyncMode
-
- // This can be set to list of enrtree:// URLs which will be queried for
- // for nodes to connect to.
- EthDiscoveryURLs []string
- SnapDiscoveryURLs []string
-
- NoPruning bool // Whether to disable pruning and flush everything to disk
- NoPrefetch bool // Whether to disable prefetching and only load state on demand
-
- // Deprecated, use 'TransactionHistory' instead.
- TxLookupLimit uint64 `toml:",omitempty"` // The maximum number of blocks from head whose tx indices are reserved.
- TransactionHistory uint64 `toml:",omitempty"` // The maximum number of blocks from head whose tx indices are reserved.
- StateHistory uint64 `toml:",omitempty"` // The maximum number of blocks from head whose state histories are reserved.
-
- // State scheme represents the scheme used to store ethereum states and trie
- // nodes on top. It can be 'hash', 'path', or none which means use the scheme
- // consistent with persistent state.
- StateScheme string `toml:",omitempty"`
-
- // RequiredBlocks is a set of block number -> hash mappings which must be in the
- // canonical chain of all remote peers. Setting the option makes geth verify the
- // presence of these blocks for every new peer connection.
- RequiredBlocks map[uint64]common.Hash `toml:"-"`
-
- // Light client options
- LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests
- LightIngress int `toml:",omitempty"` // Incoming bandwidth limit for light servers
- LightEgress int `toml:",omitempty"` // Outgoing bandwidth limit for light servers
- LightPeers int `toml:",omitempty"` // Maximum number of LES client peers
- LightNoPrune bool `toml:",omitempty"` // Whether to disable light chain pruning
- LightNoSyncServe bool `toml:",omitempty"` // Whether to serve light clients before syncing
-
- // Database options
- SkipBcVersionCheck bool `toml:"-"`
- DatabaseHandles int `toml:"-"`
- DatabaseCache int
- DatabaseFreezer string
-
- TrieCleanCache int
- TrieDirtyCache int
- TrieTimeout time.Duration
- SnapshotCache int
- Preimages bool
-
- // This is the number of blocks for which logs will be cached in the filter system.
- FilterLogCacheSize int
-
- // Mining options
- Miner miner.Config
-
- // Transaction pool options
- TxPool legacypool.Config
- BlobPool blobpool.Config
-
- // Gas Price Oracle options
- GPO gasprice.Config
-
- // Enables tracking of SHA3 preimages in the VM
- EnablePreimageRecording bool
-
- // Miscellaneous options
- DocRoot string `toml:"-"`
-
- // RPCGasCap is the global gas cap for eth-call variants.
- RPCGasCap uint64
-
- // RPCEVMTimeout is the global timeout for eth-call.
- RPCEVMTimeout time.Duration
-
- // RPCTxFeeCap is the global transaction fee(price * gaslimit) cap for
- // send-transaction variants. The unit is ether.
- RPCTxFeeCap float64
-
- // OverrideCancun (TODO: remove after the fork)
- OverrideCancun *uint64 `toml:",omitempty"`
-
- // OverrideVerkle (TODO: remove after the fork)
- OverrideVerkle *uint64 `toml:",omitempty"`
-}
-
-// CreateConsensusEngine creates a consensus engine for the given chain config.
-// Clique is allowed for now to live standalone, but ethash is forbidden and can
-// only exist on already merged networks.
-func CreateConsensusEngine(config *params.ChainConfig, db ethdb.Database) (consensus.Engine, error) {
- // If proof-of-authority is requested, set it up
- if config.Clique != nil {
- return beacon.New(clique.New(config.Clique, db)), nil
- }
- // If defaulting to proof-of-work, enforce an already merged network since
- // we cannot run PoW algorithms anymore, so we cannot even follow a chain
- // not coordinated by a beacon node.
- if !config.TerminalTotalDifficultyPassed {
- return nil, errors.New("ethash is only supported as a historical component of already merged networks")
- }
- return beacon.New(ethash.NewFaker()), nil
-}
diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go
deleted file mode 100644
index 2abddc9e0d..0000000000
--- a/eth/ethconfig/gen_config.go
+++ /dev/null
@@ -1,268 +0,0 @@
-// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
-
-package ethconfig
-
-import (
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/txpool/blobpool"
- "github.com/ethereum/go-ethereum/core/txpool/legacypool"
- "github.com/ethereum/go-ethereum/eth/downloader"
- "github.com/ethereum/go-ethereum/eth/gasprice"
- "github.com/ethereum/go-ethereum/miner"
-)
-
-// MarshalTOML marshals as TOML.
-func (c Config) MarshalTOML() (interface{}, error) {
- type Config struct {
- Genesis *core.Genesis `toml:",omitempty"`
- NetworkId uint64
- SyncMode downloader.SyncMode
- EthDiscoveryURLs []string
- SnapDiscoveryURLs []string
- NoPruning bool
- NoPrefetch bool
- TxLookupLimit uint64 `toml:",omitempty"`
- TransactionHistory uint64 `toml:",omitempty"`
- StateHistory uint64 `toml:",omitempty"`
- StateScheme string `toml:",omitempty"`
- RequiredBlocks map[uint64]common.Hash `toml:"-"`
- LightServ int `toml:",omitempty"`
- LightIngress int `toml:",omitempty"`
- LightEgress int `toml:",omitempty"`
- LightPeers int `toml:",omitempty"`
- LightNoPrune bool `toml:",omitempty"`
- LightNoSyncServe bool `toml:",omitempty"`
- SkipBcVersionCheck bool `toml:"-"`
- DatabaseHandles int `toml:"-"`
- DatabaseCache int
- DatabaseFreezer string
- TrieCleanCache int
- TrieDirtyCache int
- TrieTimeout time.Duration
- SnapshotCache int
- Preimages bool
- FilterLogCacheSize int
- Miner miner.Config
- TxPool legacypool.Config
- BlobPool blobpool.Config
- GPO gasprice.Config
- EnablePreimageRecording bool
- DocRoot string `toml:"-"`
- RPCGasCap uint64
- RPCEVMTimeout time.Duration
- RPCTxFeeCap float64
- OverrideCancun *uint64 `toml:",omitempty"`
- OverrideVerkle *uint64 `toml:",omitempty"`
- }
- var enc Config
- enc.Genesis = c.Genesis
- enc.NetworkId = c.NetworkId
- enc.SyncMode = c.SyncMode
- enc.EthDiscoveryURLs = c.EthDiscoveryURLs
- enc.SnapDiscoveryURLs = c.SnapDiscoveryURLs
- enc.NoPruning = c.NoPruning
- enc.NoPrefetch = c.NoPrefetch
- enc.TxLookupLimit = c.TxLookupLimit
- enc.TransactionHistory = c.TransactionHistory
- enc.StateHistory = c.StateHistory
- enc.StateScheme = c.StateScheme
- enc.RequiredBlocks = c.RequiredBlocks
- enc.LightServ = c.LightServ
- enc.LightIngress = c.LightIngress
- enc.LightEgress = c.LightEgress
- enc.LightPeers = c.LightPeers
- enc.LightNoPrune = c.LightNoPrune
- enc.LightNoSyncServe = c.LightNoSyncServe
- enc.SkipBcVersionCheck = c.SkipBcVersionCheck
- enc.DatabaseHandles = c.DatabaseHandles
- enc.DatabaseCache = c.DatabaseCache
- enc.DatabaseFreezer = c.DatabaseFreezer
- enc.TrieCleanCache = c.TrieCleanCache
- enc.TrieDirtyCache = c.TrieDirtyCache
- enc.TrieTimeout = c.TrieTimeout
- enc.SnapshotCache = c.SnapshotCache
- enc.Preimages = c.Preimages
- enc.FilterLogCacheSize = c.FilterLogCacheSize
- enc.Miner = c.Miner
- enc.TxPool = c.TxPool
- enc.BlobPool = c.BlobPool
- enc.GPO = c.GPO
- enc.EnablePreimageRecording = c.EnablePreimageRecording
- enc.DocRoot = c.DocRoot
- enc.RPCGasCap = c.RPCGasCap
- enc.RPCEVMTimeout = c.RPCEVMTimeout
- enc.RPCTxFeeCap = c.RPCTxFeeCap
- enc.OverrideCancun = c.OverrideCancun
- enc.OverrideVerkle = c.OverrideVerkle
- return &enc, nil
-}
-
-// UnmarshalTOML unmarshals from TOML.
-func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
- type Config struct {
- Genesis *core.Genesis `toml:",omitempty"`
- NetworkId *uint64
- SyncMode *downloader.SyncMode
- EthDiscoveryURLs []string
- SnapDiscoveryURLs []string
- NoPruning *bool
- NoPrefetch *bool
- TxLookupLimit *uint64 `toml:",omitempty"`
- TransactionHistory *uint64 `toml:",omitempty"`
- StateHistory *uint64 `toml:",omitempty"`
- StateScheme *string `toml:",omitempty"`
- RequiredBlocks map[uint64]common.Hash `toml:"-"`
- LightServ *int `toml:",omitempty"`
- LightIngress *int `toml:",omitempty"`
- LightEgress *int `toml:",omitempty"`
- LightPeers *int `toml:",omitempty"`
- LightNoPrune *bool `toml:",omitempty"`
- LightNoSyncServe *bool `toml:",omitempty"`
- SkipBcVersionCheck *bool `toml:"-"`
- DatabaseHandles *int `toml:"-"`
- DatabaseCache *int
- DatabaseFreezer *string
- TrieCleanCache *int
- TrieDirtyCache *int
- TrieTimeout *time.Duration
- SnapshotCache *int
- Preimages *bool
- FilterLogCacheSize *int
- Miner *miner.Config
- TxPool *legacypool.Config
- BlobPool *blobpool.Config
- GPO *gasprice.Config
- EnablePreimageRecording *bool
- DocRoot *string `toml:"-"`
- RPCGasCap *uint64
- RPCEVMTimeout *time.Duration
- RPCTxFeeCap *float64
- OverrideCancun *uint64 `toml:",omitempty"`
- OverrideVerkle *uint64 `toml:",omitempty"`
- }
- var dec Config
- if err := unmarshal(&dec); err != nil {
- return err
- }
- if dec.Genesis != nil {
- c.Genesis = dec.Genesis
- }
- if dec.NetworkId != nil {
- c.NetworkId = *dec.NetworkId
- }
- if dec.SyncMode != nil {
- c.SyncMode = *dec.SyncMode
- }
- if dec.EthDiscoveryURLs != nil {
- c.EthDiscoveryURLs = dec.EthDiscoveryURLs
- }
- if dec.SnapDiscoveryURLs != nil {
- c.SnapDiscoveryURLs = dec.SnapDiscoveryURLs
- }
- if dec.NoPruning != nil {
- c.NoPruning = *dec.NoPruning
- }
- if dec.NoPrefetch != nil {
- c.NoPrefetch = *dec.NoPrefetch
- }
- if dec.TxLookupLimit != nil {
- c.TxLookupLimit = *dec.TxLookupLimit
- }
- if dec.TransactionHistory != nil {
- c.TransactionHistory = *dec.TransactionHistory
- }
- if dec.StateHistory != nil {
- c.StateHistory = *dec.StateHistory
- }
- if dec.StateScheme != nil {
- c.StateScheme = *dec.StateScheme
- }
- if dec.RequiredBlocks != nil {
- c.RequiredBlocks = dec.RequiredBlocks
- }
- if dec.LightServ != nil {
- c.LightServ = *dec.LightServ
- }
- if dec.LightIngress != nil {
- c.LightIngress = *dec.LightIngress
- }
- if dec.LightEgress != nil {
- c.LightEgress = *dec.LightEgress
- }
- if dec.LightPeers != nil {
- c.LightPeers = *dec.LightPeers
- }
- if dec.LightNoPrune != nil {
- c.LightNoPrune = *dec.LightNoPrune
- }
- if dec.LightNoSyncServe != nil {
- c.LightNoSyncServe = *dec.LightNoSyncServe
- }
- if dec.SkipBcVersionCheck != nil {
- c.SkipBcVersionCheck = *dec.SkipBcVersionCheck
- }
- if dec.DatabaseHandles != nil {
- c.DatabaseHandles = *dec.DatabaseHandles
- }
- if dec.DatabaseCache != nil {
- c.DatabaseCache = *dec.DatabaseCache
- }
- if dec.DatabaseFreezer != nil {
- c.DatabaseFreezer = *dec.DatabaseFreezer
- }
- if dec.TrieCleanCache != nil {
- c.TrieCleanCache = *dec.TrieCleanCache
- }
- if dec.TrieDirtyCache != nil {
- c.TrieDirtyCache = *dec.TrieDirtyCache
- }
- if dec.TrieTimeout != nil {
- c.TrieTimeout = *dec.TrieTimeout
- }
- if dec.SnapshotCache != nil {
- c.SnapshotCache = *dec.SnapshotCache
- }
- if dec.Preimages != nil {
- c.Preimages = *dec.Preimages
- }
- if dec.FilterLogCacheSize != nil {
- c.FilterLogCacheSize = *dec.FilterLogCacheSize
- }
- if dec.Miner != nil {
- c.Miner = *dec.Miner
- }
- if dec.TxPool != nil {
- c.TxPool = *dec.TxPool
- }
- if dec.BlobPool != nil {
- c.BlobPool = *dec.BlobPool
- }
- if dec.GPO != nil {
- c.GPO = *dec.GPO
- }
- if dec.EnablePreimageRecording != nil {
- c.EnablePreimageRecording = *dec.EnablePreimageRecording
- }
- if dec.DocRoot != nil {
- c.DocRoot = *dec.DocRoot
- }
- if dec.RPCGasCap != nil {
- c.RPCGasCap = *dec.RPCGasCap
- }
- if dec.RPCEVMTimeout != nil {
- c.RPCEVMTimeout = *dec.RPCEVMTimeout
- }
- if dec.RPCTxFeeCap != nil {
- c.RPCTxFeeCap = *dec.RPCTxFeeCap
- }
- if dec.OverrideCancun != nil {
- c.OverrideCancun = dec.OverrideCancun
- }
- if dec.OverrideVerkle != nil {
- c.OverrideVerkle = dec.OverrideVerkle
- }
- return nil
-}
diff --git a/eth/fetcher/block_fetcher.go b/eth/fetcher/block_fetcher.go
deleted file mode 100644
index 126eaaea7f..0000000000
--- a/eth/fetcher/block_fetcher.go
+++ /dev/null
@@ -1,939 +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 .
-
-// Package fetcher contains the announcement based header, blocks or transaction synchronisation.
-package fetcher
-
-import (
- "errors"
- "math/rand"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/prque"
- "github.com/ethereum/go-ethereum/consensus"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/eth/protocols/eth"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/trie"
-)
-
-const (
- lightTimeout = time.Millisecond // Time allowance before an announced header is explicitly requested
- arriveTimeout = 500 * time.Millisecond // Time allowance before an announced block/transaction is explicitly requested
- gatherSlack = 100 * time.Millisecond // Interval used to collate almost-expired announces with fetches
- fetchTimeout = 5 * time.Second // Maximum allotted time to return an explicitly requested block/transaction
-)
-
-const (
- maxUncleDist = 7 // Maximum allowed backward distance from the chain head
- maxQueueDist = 32 // Maximum allowed distance from the chain head to queue
- hashLimit = 256 // Maximum number of unique blocks or headers a peer may have announced
- blockLimit = 64 // Maximum number of unique blocks a peer may have delivered
-)
-
-var (
- blockAnnounceInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/announces/in", nil)
- blockAnnounceOutTimer = metrics.NewRegisteredTimer("eth/fetcher/block/announces/out", nil)
- blockAnnounceDropMeter = metrics.NewRegisteredMeter("eth/fetcher/block/announces/drop", nil)
- blockAnnounceDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/block/announces/dos", nil)
-
- blockBroadcastInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/broadcasts/in", nil)
- blockBroadcastOutTimer = metrics.NewRegisteredTimer("eth/fetcher/block/broadcasts/out", nil)
- blockBroadcastDropMeter = metrics.NewRegisteredMeter("eth/fetcher/block/broadcasts/drop", nil)
- blockBroadcastDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/block/broadcasts/dos", nil)
-
- headerFetchMeter = metrics.NewRegisteredMeter("eth/fetcher/block/headers", nil)
- bodyFetchMeter = metrics.NewRegisteredMeter("eth/fetcher/block/bodies", nil)
-
- headerFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/headers/in", nil)
- headerFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/headers/out", nil)
- bodyFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/bodies/in", nil)
- bodyFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/bodies/out", nil)
-)
-
-var errTerminated = errors.New("terminated")
-
-// HeaderRetrievalFn is a callback type for retrieving a header from the local chain.
-type HeaderRetrievalFn func(common.Hash) *types.Header
-
-// blockRetrievalFn is a callback type for retrieving a block from the local chain.
-type blockRetrievalFn func(common.Hash) *types.Block
-
-// headerRequesterFn is a callback type for sending a header retrieval request.
-type headerRequesterFn func(common.Hash, chan *eth.Response) (*eth.Request, error)
-
-// bodyRequesterFn is a callback type for sending a body retrieval request.
-type bodyRequesterFn func([]common.Hash, chan *eth.Response) (*eth.Request, error)
-
-// headerVerifierFn is a callback type to verify a block's header for fast propagation.
-type headerVerifierFn func(header *types.Header) error
-
-// blockBroadcasterFn is a callback type for broadcasting a block to connected peers.
-type blockBroadcasterFn func(block *types.Block, propagate bool)
-
-// chainHeightFn is a callback type to retrieve the current chain height.
-type chainHeightFn func() uint64
-
-// headersInsertFn is a callback type to insert a batch of headers into the local chain.
-type headersInsertFn func(headers []*types.Header) (int, error)
-
-// chainInsertFn is a callback type to insert a batch of blocks into the local chain.
-type chainInsertFn func(types.Blocks) (int, error)
-
-// peerDropFn is a callback type for dropping a peer detected as malicious.
-type peerDropFn func(id string)
-
-// blockAnnounce is the hash notification of the availability of a new block in the
-// network.
-type blockAnnounce struct {
- hash common.Hash // Hash of the block being announced
- number uint64 // Number of the block being announced (0 = unknown | old protocol)
- header *types.Header // Header of the block partially reassembled (new protocol)
- time time.Time // Timestamp of the announcement
-
- origin string // Identifier of the peer originating the notification
-
- fetchHeader headerRequesterFn // Fetcher function to retrieve the header of an announced block
- fetchBodies bodyRequesterFn // Fetcher function to retrieve the body of an announced block
-}
-
-// headerFilterTask represents a batch of headers needing fetcher filtering.
-type headerFilterTask struct {
- peer string // The source peer of block headers
- headers []*types.Header // Collection of headers to filter
- time time.Time // Arrival time of the headers
-}
-
-// bodyFilterTask represents a batch of block bodies (transactions and uncles)
-// needing fetcher filtering.
-type bodyFilterTask struct {
- peer string // The source peer of block bodies
- transactions [][]*types.Transaction // Collection of transactions per block bodies
- uncles [][]*types.Header // Collection of uncles per block bodies
- time time.Time // Arrival time of the blocks' contents
-}
-
-// blockOrHeaderInject represents a schedules import operation.
-type blockOrHeaderInject struct {
- origin string
-
- header *types.Header // Used for light mode fetcher which only cares about header.
- block *types.Block // Used for normal mode fetcher which imports full block.
-}
-
-// number returns the block number of the injected object.
-func (inject *blockOrHeaderInject) number() uint64 {
- if inject.header != nil {
- return inject.header.Number.Uint64()
- }
- return inject.block.NumberU64()
-}
-
-// number returns the block hash of the injected object.
-func (inject *blockOrHeaderInject) hash() common.Hash {
- if inject.header != nil {
- return inject.header.Hash()
- }
- return inject.block.Hash()
-}
-
-// BlockFetcher is responsible for accumulating block announcements from various peers
-// and scheduling them for retrieval.
-type BlockFetcher struct {
- light bool // The indicator whether it's a light fetcher or normal one.
-
- // Various event channels
- notify chan *blockAnnounce
- inject chan *blockOrHeaderInject
-
- headerFilter chan chan *headerFilterTask
- bodyFilter chan chan *bodyFilterTask
-
- done chan common.Hash
- quit chan struct{}
-
- // Announce states
- announces map[string]int // Per peer blockAnnounce counts to prevent memory exhaustion
- announced map[common.Hash][]*blockAnnounce // Announced blocks, scheduled for fetching
- fetching map[common.Hash]*blockAnnounce // Announced blocks, currently fetching
- fetched map[common.Hash][]*blockAnnounce // Blocks with headers fetched, scheduled for body retrieval
- completing map[common.Hash]*blockAnnounce // Blocks with headers, currently body-completing
-
- // Block cache
- queue *prque.Prque[int64, *blockOrHeaderInject] // Queue containing the import operations (block number sorted)
- queues map[string]int // Per peer block counts to prevent memory exhaustion
- queued map[common.Hash]*blockOrHeaderInject // Set of already queued blocks (to dedup imports)
-
- // Callbacks
- getHeader HeaderRetrievalFn // Retrieves a header from the local chain
- getBlock blockRetrievalFn // Retrieves a block from the local chain
- verifyHeader headerVerifierFn // Checks if a block's headers have a valid proof of work
- broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers
- chainHeight chainHeightFn // Retrieves the current chain's height
- insertHeaders headersInsertFn // Injects a batch of headers into the chain
- insertChain chainInsertFn // Injects a batch of blocks into the chain
- dropPeer peerDropFn // Drops a peer for misbehaving
-
- // Testing hooks
- announceChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a hash from the blockAnnounce list
- queueChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a block from the import queue
- fetchingHook func([]common.Hash) // Method to call upon starting a block (eth/61) or header (eth/62) fetch
- completingHook func([]common.Hash) // Method to call upon starting a block body fetch (eth/62)
- importedHook func(*types.Header, *types.Block) // Method to call upon successful header or block import (both eth/61 and eth/62)
-}
-
-// NewBlockFetcher creates a block fetcher to retrieve blocks based on hash announcements.
-func NewBlockFetcher(light bool, getHeader HeaderRetrievalFn, getBlock blockRetrievalFn, verifyHeader headerVerifierFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertHeaders headersInsertFn, insertChain chainInsertFn, dropPeer peerDropFn) *BlockFetcher {
- return &BlockFetcher{
- light: light,
- notify: make(chan *blockAnnounce),
- inject: make(chan *blockOrHeaderInject),
- headerFilter: make(chan chan *headerFilterTask),
- bodyFilter: make(chan chan *bodyFilterTask),
- done: make(chan common.Hash),
- quit: make(chan struct{}),
- announces: make(map[string]int),
- announced: make(map[common.Hash][]*blockAnnounce),
- fetching: make(map[common.Hash]*blockAnnounce),
- fetched: make(map[common.Hash][]*blockAnnounce),
- completing: make(map[common.Hash]*blockAnnounce),
- queue: prque.New[int64, *blockOrHeaderInject](nil),
- queues: make(map[string]int),
- queued: make(map[common.Hash]*blockOrHeaderInject),
- getHeader: getHeader,
- getBlock: getBlock,
- verifyHeader: verifyHeader,
- broadcastBlock: broadcastBlock,
- chainHeight: chainHeight,
- insertHeaders: insertHeaders,
- insertChain: insertChain,
- dropPeer: dropPeer,
- }
-}
-
-// Start boots up the announcement based synchroniser, accepting and processing
-// hash notifications and block fetches until termination requested.
-func (f *BlockFetcher) Start() {
- go f.loop()
-}
-
-// Stop terminates the announcement based synchroniser, canceling all pending
-// operations.
-func (f *BlockFetcher) Stop() {
- close(f.quit)
-}
-
-// Notify announces the fetcher of the potential availability of a new block in
-// the network.
-func (f *BlockFetcher) Notify(peer string, hash common.Hash, number uint64, time time.Time,
- headerFetcher headerRequesterFn, bodyFetcher bodyRequesterFn) error {
- block := &blockAnnounce{
- hash: hash,
- number: number,
- time: time,
- origin: peer,
- fetchHeader: headerFetcher,
- fetchBodies: bodyFetcher,
- }
- select {
- case f.notify <- block:
- return nil
- case <-f.quit:
- return errTerminated
- }
-}
-
-// Enqueue tries to fill gaps the fetcher's future import queue.
-func (f *BlockFetcher) Enqueue(peer string, block *types.Block) error {
- op := &blockOrHeaderInject{
- origin: peer,
- block: block,
- }
- select {
- case f.inject <- op:
- return nil
- case <-f.quit:
- return errTerminated
- }
-}
-
-// FilterHeaders extracts all the headers that were explicitly requested by the fetcher,
-// returning those that should be handled differently.
-func (f *BlockFetcher) FilterHeaders(peer string, headers []*types.Header, time time.Time) []*types.Header {
- log.Trace("Filtering headers", "peer", peer, "headers", len(headers))
-
- // Send the filter channel to the fetcher
- filter := make(chan *headerFilterTask)
-
- select {
- case f.headerFilter <- filter:
- case <-f.quit:
- return nil
- }
- // Request the filtering of the header list
- select {
- case filter <- &headerFilterTask{peer: peer, headers: headers, time: time}:
- case <-f.quit:
- return nil
- }
- // Retrieve the headers remaining after filtering
- select {
- case task := <-filter:
- return task.headers
- case <-f.quit:
- return nil
- }
-}
-
-// FilterBodies extracts all the block bodies that were explicitly requested by
-// the fetcher, returning those that should be handled differently.
-func (f *BlockFetcher) FilterBodies(peer string, transactions [][]*types.Transaction, uncles [][]*types.Header, time time.Time) ([][]*types.Transaction, [][]*types.Header) {
- log.Trace("Filtering bodies", "peer", peer, "txs", len(transactions), "uncles", len(uncles))
-
- // Send the filter channel to the fetcher
- filter := make(chan *bodyFilterTask)
-
- select {
- case f.bodyFilter <- filter:
- case <-f.quit:
- return nil, nil
- }
- // Request the filtering of the body list
- select {
- case filter <- &bodyFilterTask{peer: peer, transactions: transactions, uncles: uncles, time: time}:
- case <-f.quit:
- return nil, nil
- }
- // Retrieve the bodies remaining after filtering
- select {
- case task := <-filter:
- return task.transactions, task.uncles
- case <-f.quit:
- return nil, nil
- }
-}
-
-// Loop is the main fetcher loop, checking and processing various notification
-// events.
-func (f *BlockFetcher) loop() {
- // Iterate the block fetching until a quit is requested
- var (
- fetchTimer = time.NewTimer(0)
- completeTimer = time.NewTimer(0)
- )
- <-fetchTimer.C // clear out the channel
- <-completeTimer.C
- defer fetchTimer.Stop()
- defer completeTimer.Stop()
-
- for {
- // Clean up any expired block fetches
- for hash, announce := range f.fetching {
- if time.Since(announce.time) > fetchTimeout {
- f.forgetHash(hash)
- }
- }
- // Import any queued blocks that could potentially fit
- height := f.chainHeight()
- for !f.queue.Empty() {
- op := f.queue.PopItem()
- hash := op.hash()
- if f.queueChangeHook != nil {
- f.queueChangeHook(hash, false)
- }
- // If too high up the chain or phase, continue later
- number := op.number()
- if number > height+1 {
- f.queue.Push(op, -int64(number))
- if f.queueChangeHook != nil {
- f.queueChangeHook(hash, true)
- }
- break
- }
- // Otherwise if fresh and still unknown, try and import
- if (number+maxUncleDist < height) || (f.light && f.getHeader(hash) != nil) || (!f.light && f.getBlock(hash) != nil) {
- f.forgetBlock(hash)
- continue
- }
- if f.light {
- f.importHeaders(op.origin, op.header)
- } else {
- f.importBlocks(op.origin, op.block)
- }
- }
- // Wait for an outside event to occur
- select {
- case <-f.quit:
- // BlockFetcher terminating, abort all operations
- return
-
- case notification := <-f.notify:
- // A block was announced, make sure the peer isn't DOSing us
- blockAnnounceInMeter.Mark(1)
-
- count := f.announces[notification.origin] + 1
- if count > hashLimit {
- log.Debug("Peer exceeded outstanding announces", "peer", notification.origin, "limit", hashLimit)
- blockAnnounceDOSMeter.Mark(1)
- break
- }
- if notification.number == 0 {
- break
- }
- // If we have a valid block number, check that it's potentially useful
- if dist := int64(notification.number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist {
- log.Debug("Peer discarded announcement", "peer", notification.origin, "number", notification.number, "hash", notification.hash, "distance", dist)
- blockAnnounceDropMeter.Mark(1)
- break
- }
- // All is well, schedule the announce if block's not yet downloading
- if _, ok := f.fetching[notification.hash]; ok {
- break
- }
- if _, ok := f.completing[notification.hash]; ok {
- break
- }
- f.announces[notification.origin] = count
- f.announced[notification.hash] = append(f.announced[notification.hash], notification)
- if f.announceChangeHook != nil && len(f.announced[notification.hash]) == 1 {
- f.announceChangeHook(notification.hash, true)
- }
- if len(f.announced) == 1 {
- f.rescheduleFetch(fetchTimer)
- }
-
- case op := <-f.inject:
- // A direct block insertion was requested, try and fill any pending gaps
- blockBroadcastInMeter.Mark(1)
-
- // Now only direct block injection is allowed, drop the header injection
- // here silently if we receive.
- if f.light {
- continue
- }
- f.enqueue(op.origin, nil, op.block)
-
- case hash := <-f.done:
- // A pending import finished, remove all traces of the notification
- f.forgetHash(hash)
- f.forgetBlock(hash)
-
- case <-fetchTimer.C:
- // At least one block's timer ran out, check for needing retrieval
- request := make(map[string][]common.Hash)
-
- for hash, announces := range f.announced {
- // In current LES protocol(les2/les3), only header announce is
- // available, no need to wait too much time for header broadcast.
- timeout := arriveTimeout - gatherSlack
- if f.light {
- timeout = 0
- }
- if time.Since(announces[0].time) > timeout {
- // Pick a random peer to retrieve from, reset all others
- announce := announces[rand.Intn(len(announces))]
- f.forgetHash(hash)
-
- // If the block still didn't arrive, queue for fetching
- if (f.light && f.getHeader(hash) == nil) || (!f.light && f.getBlock(hash) == nil) {
- request[announce.origin] = append(request[announce.origin], hash)
- f.fetching[hash] = announce
- }
- }
- }
- // Send out all block header requests
- for peer, hashes := range request {
- log.Trace("Fetching scheduled headers", "peer", peer, "list", hashes)
-
- // Create a closure of the fetch and schedule in on a new thread
- fetchHeader, hashes := f.fetching[hashes[0]].fetchHeader, hashes
- go func(peer string) {
- if f.fetchingHook != nil {
- f.fetchingHook(hashes)
- }
- for _, hash := range hashes {
- headerFetchMeter.Mark(1)
- go func(hash common.Hash) {
- resCh := make(chan *eth.Response)
-
- req, err := fetchHeader(hash, resCh)
- if err != nil {
- return // Legacy code, yolo
- }
- defer req.Close()
-
- timeout := time.NewTimer(2 * fetchTimeout) // 2x leeway before dropping the peer
- defer timeout.Stop()
-
- select {
- case res := <-resCh:
- res.Done <- nil
- f.FilterHeaders(peer, *res.Res.(*eth.BlockHeadersRequest), time.Now())
-
- case <-timeout.C:
- // The peer didn't respond in time. The request
- // was already rescheduled at this point, we were
- // waiting for a catchup. With an unresponsive
- // peer however, it's a protocol violation.
- f.dropPeer(peer)
- }
- }(hash)
- }
- }(peer)
- }
- // Schedule the next fetch if blocks are still pending
- f.rescheduleFetch(fetchTimer)
-
- case <-completeTimer.C:
- // At least one header's timer ran out, retrieve everything
- request := make(map[string][]common.Hash)
-
- for hash, announces := range f.fetched {
- // Pick a random peer to retrieve from, reset all others
- announce := announces[rand.Intn(len(announces))]
- f.forgetHash(hash)
-
- // If the block still didn't arrive, queue for completion
- if f.getBlock(hash) == nil {
- request[announce.origin] = append(request[announce.origin], hash)
- f.completing[hash] = announce
- }
- }
- // Send out all block body requests
- for peer, hashes := range request {
- log.Trace("Fetching scheduled bodies", "peer", peer, "list", hashes)
-
- // Create a closure of the fetch and schedule in on a new thread
- if f.completingHook != nil {
- f.completingHook(hashes)
- }
- fetchBodies := f.completing[hashes[0]].fetchBodies
- bodyFetchMeter.Mark(int64(len(hashes)))
-
- go func(peer string, hashes []common.Hash) {
- resCh := make(chan *eth.Response)
-
- req, err := fetchBodies(hashes, resCh)
- if err != nil {
- return // Legacy code, yolo
- }
- defer req.Close()
-
- timeout := time.NewTimer(2 * fetchTimeout) // 2x leeway before dropping the peer
- defer timeout.Stop()
-
- select {
- case res := <-resCh:
- res.Done <- nil
- // Ignoring withdrawals here, since the block fetcher is not used post-merge.
- txs, uncles, _ := res.Res.(*eth.BlockBodiesResponse).Unpack()
- f.FilterBodies(peer, txs, uncles, time.Now())
-
- case <-timeout.C:
- // The peer didn't respond in time. The request
- // was already rescheduled at this point, we were
- // waiting for a catchup. With an unresponsive
- // peer however, it's a protocol violation.
- f.dropPeer(peer)
- }
- }(peer, hashes)
- }
- // Schedule the next fetch if blocks are still pending
- f.rescheduleComplete(completeTimer)
-
- case filter := <-f.headerFilter:
- // Headers arrived from a remote peer. Extract those that were explicitly
- // requested by the fetcher, and return everything else so it's delivered
- // to other parts of the system.
- var task *headerFilterTask
- select {
- case task = <-filter:
- case <-f.quit:
- return
- }
- headerFilterInMeter.Mark(int64(len(task.headers)))
-
- // Split the batch of headers into unknown ones (to return to the caller),
- // known incomplete ones (requiring body retrievals) and completed blocks.
- unknown, incomplete, complete, lightHeaders := []*types.Header{}, []*blockAnnounce{}, []*types.Block{}, []*blockAnnounce{}
- for _, header := range task.headers {
- hash := header.Hash()
-
- // Filter fetcher-requested headers from other synchronisation algorithms
- if announce := f.fetching[hash]; announce != nil && announce.origin == task.peer && f.fetched[hash] == nil && f.completing[hash] == nil && f.queued[hash] == nil {
- // If the delivered header does not match the promised number, drop the announcer
- if header.Number.Uint64() != announce.number {
- log.Trace("Invalid block number fetched", "peer", announce.origin, "hash", header.Hash(), "announced", announce.number, "provided", header.Number)
- f.dropPeer(announce.origin)
- f.forgetHash(hash)
- continue
- }
- // Collect all headers only if we are running in light
- // mode and the headers are not imported by other means.
- if f.light {
- if f.getHeader(hash) == nil {
- announce.header = header
- lightHeaders = append(lightHeaders, announce)
- }
- f.forgetHash(hash)
- continue
- }
- // Only keep if not imported by other means
- if f.getBlock(hash) == nil {
- announce.header = header
- announce.time = task.time
-
- // If the block is empty (header only), short circuit into the final import queue
- if header.TxHash == types.EmptyTxsHash && header.UncleHash == types.EmptyUncleHash {
- log.Trace("Block empty, skipping body retrieval", "peer", announce.origin, "number", header.Number, "hash", header.Hash())
-
- block := types.NewBlockWithHeader(header)
- block.ReceivedAt = task.time
-
- complete = append(complete, block)
- f.completing[hash] = announce
- continue
- }
- // Otherwise add to the list of blocks needing completion
- incomplete = append(incomplete, announce)
- } else {
- log.Trace("Block already imported, discarding header", "peer", announce.origin, "number", header.Number, "hash", header.Hash())
- f.forgetHash(hash)
- }
- } else {
- // BlockFetcher doesn't know about it, add to the return list
- unknown = append(unknown, header)
- }
- }
- headerFilterOutMeter.Mark(int64(len(unknown)))
- select {
- case filter <- &headerFilterTask{headers: unknown, time: task.time}:
- case <-f.quit:
- return
- }
- // Schedule the retrieved headers for body completion
- for _, announce := range incomplete {
- hash := announce.header.Hash()
- if _, ok := f.completing[hash]; ok {
- continue
- }
- f.fetched[hash] = append(f.fetched[hash], announce)
- if len(f.fetched) == 1 {
- f.rescheduleComplete(completeTimer)
- }
- }
- // Schedule the header for light fetcher import
- for _, announce := range lightHeaders {
- f.enqueue(announce.origin, announce.header, nil)
- }
- // Schedule the header-only blocks for import
- for _, block := range complete {
- if announce := f.completing[block.Hash()]; announce != nil {
- f.enqueue(announce.origin, nil, block)
- }
- }
-
- case filter := <-f.bodyFilter:
- // Block bodies arrived, extract any explicitly requested blocks, return the rest
- var task *bodyFilterTask
- select {
- case task = <-filter:
- case <-f.quit:
- return
- }
- bodyFilterInMeter.Mark(int64(len(task.transactions)))
- blocks := []*types.Block{}
- // abort early if there's nothing explicitly requested
- if len(f.completing) > 0 {
- for i := 0; i < len(task.transactions) && i < len(task.uncles); i++ {
- // Match up a body to any possible completion request
- var (
- matched = false
- uncleHash common.Hash // calculated lazily and reused
- txnHash common.Hash // calculated lazily and reused
- )
- for hash, announce := range f.completing {
- if f.queued[hash] != nil || announce.origin != task.peer {
- continue
- }
- if uncleHash == (common.Hash{}) {
- uncleHash = types.CalcUncleHash(task.uncles[i])
- }
- if uncleHash != announce.header.UncleHash {
- continue
- }
- if txnHash == (common.Hash{}) {
- txnHash = types.DeriveSha(types.Transactions(task.transactions[i]), trie.NewStackTrie(nil))
- }
- if txnHash != announce.header.TxHash {
- continue
- }
- // Mark the body matched, reassemble if still unknown
- matched = true
- if f.getBlock(hash) == nil {
- block := types.NewBlockWithHeader(announce.header).WithBody(task.transactions[i], task.uncles[i])
- block.ReceivedAt = task.time
- blocks = append(blocks, block)
- } else {
- f.forgetHash(hash)
- }
- }
- if matched {
- task.transactions = append(task.transactions[:i], task.transactions[i+1:]...)
- task.uncles = append(task.uncles[:i], task.uncles[i+1:]...)
- i--
- continue
- }
- }
- }
- bodyFilterOutMeter.Mark(int64(len(task.transactions)))
- select {
- case filter <- task:
- case <-f.quit:
- return
- }
- // Schedule the retrieved blocks for ordered import
- for _, block := range blocks {
- if announce := f.completing[block.Hash()]; announce != nil {
- f.enqueue(announce.origin, nil, block)
- }
- }
- }
- }
-}
-
-// rescheduleFetch resets the specified fetch timer to the next blockAnnounce timeout.
-func (f *BlockFetcher) rescheduleFetch(fetch *time.Timer) {
- // Short circuit if no blocks are announced
- if len(f.announced) == 0 {
- return
- }
- // Schedule announcement retrieval quickly for light mode
- // since server won't send any headers to client.
- if f.light {
- fetch.Reset(lightTimeout)
- return
- }
- // Otherwise find the earliest expiring announcement
- earliest := time.Now()
- for _, announces := range f.announced {
- if earliest.After(announces[0].time) {
- earliest = announces[0].time
- }
- }
- fetch.Reset(arriveTimeout - time.Since(earliest))
-}
-
-// rescheduleComplete resets the specified completion timer to the next fetch timeout.
-func (f *BlockFetcher) rescheduleComplete(complete *time.Timer) {
- // Short circuit if no headers are fetched
- if len(f.fetched) == 0 {
- return
- }
- // Otherwise find the earliest expiring announcement
- earliest := time.Now()
- for _, announces := range f.fetched {
- if earliest.After(announces[0].time) {
- earliest = announces[0].time
- }
- }
- complete.Reset(gatherSlack - time.Since(earliest))
-}
-
-// enqueue schedules a new header or block import operation, if the component
-// to be imported has not yet been seen.
-func (f *BlockFetcher) enqueue(peer string, header *types.Header, block *types.Block) {
- var (
- hash common.Hash
- number uint64
- )
- if header != nil {
- hash, number = header.Hash(), header.Number.Uint64()
- } else {
- hash, number = block.Hash(), block.NumberU64()
- }
- // Ensure the peer isn't DOSing us
- count := f.queues[peer] + 1
- if count > blockLimit {
- log.Debug("Discarded delivered header or block, exceeded allowance", "peer", peer, "number", number, "hash", hash, "limit", blockLimit)
- blockBroadcastDOSMeter.Mark(1)
- f.forgetHash(hash)
- return
- }
- // Discard any past or too distant blocks
- if dist := int64(number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist {
- log.Debug("Discarded delivered header or block, too far away", "peer", peer, "number", number, "hash", hash, "distance", dist)
- blockBroadcastDropMeter.Mark(1)
- f.forgetHash(hash)
- return
- }
- // Schedule the block for future importing
- if _, ok := f.queued[hash]; !ok {
- op := &blockOrHeaderInject{origin: peer}
- if header != nil {
- op.header = header
- } else {
- op.block = block
- }
- f.queues[peer] = count
- f.queued[hash] = op
- f.queue.Push(op, -int64(number))
- if f.queueChangeHook != nil {
- f.queueChangeHook(hash, true)
- }
- log.Debug("Queued delivered header or block", "peer", peer, "number", number, "hash", hash, "queued", f.queue.Size())
- }
-}
-
-// importHeaders spawns a new goroutine to run a header insertion into the chain.
-// If the header's number is at the same height as the current import phase, it
-// updates the phase states accordingly.
-func (f *BlockFetcher) importHeaders(peer string, header *types.Header) {
- hash := header.Hash()
- log.Debug("Importing propagated header", "peer", peer, "number", header.Number, "hash", hash)
-
- go func() {
- defer func() { f.done <- hash }()
- // If the parent's unknown, abort insertion
- parent := f.getHeader(header.ParentHash)
- if parent == nil {
- log.Debug("Unknown parent of propagated header", "peer", peer, "number", header.Number, "hash", hash, "parent", header.ParentHash)
- return
- }
- // Validate the header and if something went wrong, drop the peer
- if err := f.verifyHeader(header); err != nil && err != consensus.ErrFutureBlock {
- log.Debug("Propagated header verification failed", "peer", peer, "number", header.Number, "hash", hash, "err", err)
- f.dropPeer(peer)
- return
- }
- // Run the actual import and log any issues
- if _, err := f.insertHeaders([]*types.Header{header}); err != nil {
- log.Debug("Propagated header import failed", "peer", peer, "number", header.Number, "hash", hash, "err", err)
- return
- }
- // Invoke the testing hook if needed
- if f.importedHook != nil {
- f.importedHook(header, nil)
- }
- }()
-}
-
-// importBlocks spawns a new goroutine to run a block insertion into the chain. If the
-// block's number is at the same height as the current import phase, it updates
-// the phase states accordingly.
-func (f *BlockFetcher) importBlocks(peer string, block *types.Block) {
- hash := block.Hash()
-
- // Run the import on a new thread
- log.Debug("Importing propagated block", "peer", peer, "number", block.Number(), "hash", hash)
- go func() {
- defer func() { f.done <- hash }()
-
- // If the parent's unknown, abort insertion
- parent := f.getBlock(block.ParentHash())
- if parent == nil {
- log.Debug("Unknown parent of propagated block", "peer", peer, "number", block.Number(), "hash", hash, "parent", block.ParentHash())
- return
- }
- // Quickly validate the header and propagate the block if it passes
- switch err := f.verifyHeader(block.Header()); err {
- case nil:
- // All ok, quickly propagate to our peers
- blockBroadcastOutTimer.UpdateSince(block.ReceivedAt)
- go f.broadcastBlock(block, true)
-
- case consensus.ErrFutureBlock:
- // Weird future block, don't fail, but neither propagate
-
- default:
- // Something went very wrong, drop the peer
- log.Debug("Propagated block verification failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
- f.dropPeer(peer)
- return
- }
- // Run the actual import and log any issues
- if _, err := f.insertChain(types.Blocks{block}); err != nil {
- log.Debug("Propagated block import failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
- return
- }
- // If import succeeded, broadcast the block
- blockAnnounceOutTimer.UpdateSince(block.ReceivedAt)
- go f.broadcastBlock(block, false)
-
- // Invoke the testing hook if needed
- if f.importedHook != nil {
- f.importedHook(nil, block)
- }
- }()
-}
-
-// forgetHash removes all traces of a block announcement from the fetcher's
-// internal state.
-func (f *BlockFetcher) forgetHash(hash common.Hash) {
- // Remove all pending announces and decrement DOS counters
- if announceMap, ok := f.announced[hash]; ok {
- for _, announce := range announceMap {
- f.announces[announce.origin]--
- if f.announces[announce.origin] <= 0 {
- delete(f.announces, announce.origin)
- }
- }
- delete(f.announced, hash)
- if f.announceChangeHook != nil {
- f.announceChangeHook(hash, false)
- }
- }
- // Remove any pending fetches and decrement the DOS counters
- if announce := f.fetching[hash]; announce != nil {
- f.announces[announce.origin]--
- if f.announces[announce.origin] <= 0 {
- delete(f.announces, announce.origin)
- }
- delete(f.fetching, hash)
- }
-
- // Remove any pending completion requests and decrement the DOS counters
- for _, announce := range f.fetched[hash] {
- f.announces[announce.origin]--
- if f.announces[announce.origin] <= 0 {
- delete(f.announces, announce.origin)
- }
- }
- delete(f.fetched, hash)
-
- // Remove any pending completions and decrement the DOS counters
- if announce := f.completing[hash]; announce != nil {
- f.announces[announce.origin]--
- if f.announces[announce.origin] <= 0 {
- delete(f.announces, announce.origin)
- }
- delete(f.completing, hash)
- }
-}
-
-// forgetBlock removes all traces of a queued block from the fetcher's internal
-// state.
-func (f *BlockFetcher) forgetBlock(hash common.Hash) {
- if insert := f.queued[hash]; insert != nil {
- f.queues[insert.origin]--
- if f.queues[insert.origin] == 0 {
- delete(f.queues, insert.origin)
- }
- delete(f.queued, hash)
- }
-}
diff --git a/eth/fetcher/block_fetcher_test.go b/eth/fetcher/block_fetcher_test.go
deleted file mode 100644
index 6927300b1d..0000000000
--- a/eth/fetcher/block_fetcher_test.go
+++ /dev/null
@@ -1,948 +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 .
-
-package fetcher
-
-import (
- "errors"
- "math/big"
- "sync"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "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/types"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/eth/protocols/eth"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/trie"
-)
-
-var (
- testdb = rawdb.NewMemoryDatabase()
- testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
- testAddress = crypto.PubkeyToAddress(testKey.PublicKey)
- gspec = &core.Genesis{
- Config: params.TestChainConfig,
- Alloc: core.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}},
- BaseFee: big.NewInt(params.InitialBaseFee),
- }
- genesis = gspec.MustCommit(testdb, trie.NewDatabase(testdb, trie.HashDefaults))
- unknownBlock = types.NewBlock(&types.Header{Root: types.EmptyRootHash, GasLimit: params.GenesisGasLimit, BaseFee: big.NewInt(params.InitialBaseFee)}, nil, nil, nil, trie.NewStackTrie(nil))
-)
-
-// makeChain creates a chain of n blocks starting at and including parent.
-// the returned hash chain is ordered head->parent. In addition, every 3rd block
-// contains a transaction and every 5th an uncle to allow testing correct block
-// reassembly.
-func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common.Hash]*types.Block) {
- blocks, _ := core.GenerateChain(gspec.Config, parent, ethash.NewFaker(), testdb, n, func(i int, block *core.BlockGen) {
- block.SetCoinbase(common.Address{seed})
-
- // If the block number is multiple of 3, send a bonus transaction to the miner
- if parent == genesis && i%3 == 0 {
- signer := types.MakeSigner(params.TestChainConfig, block.Number(), block.Timestamp())
- tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey)
- if err != nil {
- panic(err)
- }
- block.AddTx(tx)
- }
- // If the block number is a multiple of 5, add a bonus uncle to the block
- if i > 0 && i%5 == 0 {
- block.AddUncle(&types.Header{ParentHash: block.PrevBlock(i - 2).Hash(), Number: big.NewInt(int64(i - 1))})
- }
- })
- hashes := make([]common.Hash, n+1)
- hashes[len(hashes)-1] = parent.Hash()
- blockm := make(map[common.Hash]*types.Block, n+1)
- blockm[parent.Hash()] = parent
- for i, b := range blocks {
- hashes[len(hashes)-i-2] = b.Hash()
- blockm[b.Hash()] = b
- }
- return hashes, blockm
-}
-
-// fetcherTester is a test simulator for mocking out local block chain.
-type fetcherTester struct {
- fetcher *BlockFetcher
-
- hashes []common.Hash // Hash chain belonging to the tester
- headers map[common.Hash]*types.Header // Headers belonging to the tester
- blocks map[common.Hash]*types.Block // Blocks belonging to the tester
- drops map[string]bool // Map of peers dropped by the fetcher
-
- lock sync.RWMutex
-}
-
-// newTester creates a new fetcher test mocker.
-func newTester(light bool) *fetcherTester {
- tester := &fetcherTester{
- hashes: []common.Hash{genesis.Hash()},
- headers: map[common.Hash]*types.Header{genesis.Hash(): genesis.Header()},
- blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis},
- drops: make(map[string]bool),
- }
- tester.fetcher = NewBlockFetcher(light, tester.getHeader, tester.getBlock, tester.verifyHeader, tester.broadcastBlock, tester.chainHeight, tester.insertHeaders, tester.insertChain, tester.dropPeer)
- tester.fetcher.Start()
-
- return tester
-}
-
-// getHeader retrieves a header from the tester's block chain.
-func (f *fetcherTester) getHeader(hash common.Hash) *types.Header {
- f.lock.RLock()
- defer f.lock.RUnlock()
-
- return f.headers[hash]
-}
-
-// getBlock retrieves a block from the tester's block chain.
-func (f *fetcherTester) getBlock(hash common.Hash) *types.Block {
- f.lock.RLock()
- defer f.lock.RUnlock()
-
- return f.blocks[hash]
-}
-
-// verifyHeader is a nop placeholder for the block header verification.
-func (f *fetcherTester) verifyHeader(header *types.Header) error {
- return nil
-}
-
-// broadcastBlock is a nop placeholder for the block broadcasting.
-func (f *fetcherTester) broadcastBlock(block *types.Block, propagate bool) {
-}
-
-// chainHeight retrieves the current height (block number) of the chain.
-func (f *fetcherTester) chainHeight() uint64 {
- f.lock.RLock()
- defer f.lock.RUnlock()
-
- if f.fetcher.light {
- return f.headers[f.hashes[len(f.hashes)-1]].Number.Uint64()
- }
- return f.blocks[f.hashes[len(f.hashes)-1]].NumberU64()
-}
-
-// insertChain injects a new headers into the simulated chain.
-func (f *fetcherTester) insertHeaders(headers []*types.Header) (int, error) {
- f.lock.Lock()
- defer f.lock.Unlock()
-
- for i, header := range headers {
- // Make sure the parent in known
- if _, ok := f.headers[header.ParentHash]; !ok {
- return i, errors.New("unknown parent")
- }
- // Discard any new blocks if the same height already exists
- if header.Number.Uint64() <= f.headers[f.hashes[len(f.hashes)-1]].Number.Uint64() {
- return i, nil
- }
- // Otherwise build our current chain
- f.hashes = append(f.hashes, header.Hash())
- f.headers[header.Hash()] = header
- }
- return 0, nil
-}
-
-// insertChain injects a new blocks into the simulated chain.
-func (f *fetcherTester) insertChain(blocks types.Blocks) (int, error) {
- f.lock.Lock()
- defer f.lock.Unlock()
-
- for i, block := range blocks {
- // Make sure the parent in known
- if _, ok := f.blocks[block.ParentHash()]; !ok {
- return i, errors.New("unknown parent")
- }
- // Discard any new blocks if the same height already exists
- if block.NumberU64() <= f.blocks[f.hashes[len(f.hashes)-1]].NumberU64() {
- return i, nil
- }
- // Otherwise build our current chain
- f.hashes = append(f.hashes, block.Hash())
- f.blocks[block.Hash()] = block
- }
- return 0, nil
-}
-
-// dropPeer is an emulator for the peer removal, simply accumulating the various
-// peers dropped by the fetcher.
-func (f *fetcherTester) dropPeer(peer string) {
- f.lock.Lock()
- defer f.lock.Unlock()
-
- f.drops[peer] = true
-}
-
-// makeHeaderFetcher retrieves a block header fetcher associated with a simulated peer.
-func (f *fetcherTester) makeHeaderFetcher(peer string, blocks map[common.Hash]*types.Block, drift time.Duration) headerRequesterFn {
- closure := make(map[common.Hash]*types.Block)
- for hash, block := range blocks {
- closure[hash] = block
- }
- // Create a function that return a header from the closure
- return func(hash common.Hash, sink chan *eth.Response) (*eth.Request, error) {
- // Gather the blocks to return
- headers := make([]*types.Header, 0, 1)
- if block, ok := closure[hash]; ok {
- headers = append(headers, block.Header())
- }
- // Return on a new thread
- req := ð.Request{
- Peer: peer,
- }
- res := ð.Response{
- Req: req,
- Res: (*eth.BlockHeadersRequest)(&headers),
- Time: drift,
- Done: make(chan error, 1), // Ignore the returned status
- }
- go func() {
- sink <- res
- }()
- return req, nil
- }
-}
-
-// makeBodyFetcher retrieves a block body fetcher associated with a simulated peer.
-func (f *fetcherTester) makeBodyFetcher(peer string, blocks map[common.Hash]*types.Block, drift time.Duration) bodyRequesterFn {
- closure := make(map[common.Hash]*types.Block)
- for hash, block := range blocks {
- closure[hash] = block
- }
- // Create a function that returns blocks from the closure
- return func(hashes []common.Hash, sink chan *eth.Response) (*eth.Request, error) {
- // Gather the block bodies to return
- transactions := make([][]*types.Transaction, 0, len(hashes))
- uncles := make([][]*types.Header, 0, len(hashes))
-
- for _, hash := range hashes {
- if block, ok := closure[hash]; ok {
- transactions = append(transactions, block.Transactions())
- uncles = append(uncles, block.Uncles())
- }
- }
- // Return on a new thread
- bodies := make([]*eth.BlockBody, len(transactions))
- for i, txs := range transactions {
- bodies[i] = ð.BlockBody{
- Transactions: txs,
- Uncles: uncles[i],
- }
- }
- req := ð.Request{
- Peer: peer,
- }
- res := ð.Response{
- Req: req,
- Res: (*eth.BlockBodiesResponse)(&bodies),
- Time: drift,
- Done: make(chan error, 1), // Ignore the returned status
- }
- go func() {
- sink <- res
- }()
- return req, nil
- }
-}
-
-// verifyFetchingEvent verifies that one single event arrive on a fetching channel.
-func verifyFetchingEvent(t *testing.T, fetching chan []common.Hash, arrive bool) {
- t.Helper()
-
- if arrive {
- select {
- case <-fetching:
- case <-time.After(time.Second):
- t.Fatalf("fetching timeout")
- }
- } else {
- select {
- case <-fetching:
- t.Fatalf("fetching invoked")
- case <-time.After(10 * time.Millisecond):
- }
- }
-}
-
-// verifyCompletingEvent verifies that one single event arrive on an completing channel.
-func verifyCompletingEvent(t *testing.T, completing chan []common.Hash, arrive bool) {
- t.Helper()
-
- if arrive {
- select {
- case <-completing:
- case <-time.After(time.Second):
- t.Fatalf("completing timeout")
- }
- } else {
- select {
- case <-completing:
- t.Fatalf("completing invoked")
- case <-time.After(10 * time.Millisecond):
- }
- }
-}
-
-// verifyImportEvent verifies that one single event arrive on an import channel.
-func verifyImportEvent(t *testing.T, imported chan interface{}, arrive bool) {
- t.Helper()
-
- if arrive {
- select {
- case <-imported:
- case <-time.After(time.Second):
- t.Fatalf("import timeout")
- }
- } else {
- select {
- case <-imported:
- t.Fatalf("import invoked")
- case <-time.After(20 * time.Millisecond):
- }
- }
-}
-
-// verifyImportCount verifies that exactly count number of events arrive on an
-// import hook channel.
-func verifyImportCount(t *testing.T, imported chan interface{}, count int) {
- t.Helper()
-
- for i := 0; i < count; i++ {
- select {
- case <-imported:
- case <-time.After(time.Second):
- t.Fatalf("block %d: import timeout", i+1)
- }
- }
- verifyImportDone(t, imported)
-}
-
-// verifyImportDone verifies that no more events are arriving on an import channel.
-func verifyImportDone(t *testing.T, imported chan interface{}) {
- t.Helper()
-
- select {
- case <-imported:
- t.Fatalf("extra block imported")
- case <-time.After(50 * time.Millisecond):
- }
-}
-
-// verifyChainHeight verifies the chain height is as expected.
-func verifyChainHeight(t *testing.T, fetcher *fetcherTester, height uint64) {
- t.Helper()
-
- if fetcher.chainHeight() != height {
- t.Fatalf("chain height mismatch, got %d, want %d", fetcher.chainHeight(), height)
- }
-}
-
-// Tests that a fetcher accepts block/header announcements and initiates retrievals
-// for them, successfully importing into the local chain.
-func TestFullSequentialAnnouncements(t *testing.T) { testSequentialAnnouncements(t, false) }
-func TestLightSequentialAnnouncements(t *testing.T) { testSequentialAnnouncements(t, true) }
-
-func testSequentialAnnouncements(t *testing.T, light bool) {
- // Create a chain of blocks to import
- targetBlocks := 4 * hashLimit
- hashes, blocks := makeChain(targetBlocks, 0, genesis)
-
- tester := newTester(light)
- defer tester.fetcher.Stop()
- headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
- bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
-
- // Iteratively announce blocks until all are imported
- imported := make(chan interface{})
- tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
- if light {
- if header == nil {
- t.Fatalf("Fetcher try to import empty header")
- }
- imported <- header
- } else {
- if block == nil {
- t.Fatalf("Fetcher try to import empty block")
- }
- imported <- block
- }
- }
- for i := len(hashes) - 2; i >= 0; i-- {
- tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
- verifyImportEvent(t, imported, true)
- }
- verifyImportDone(t, imported)
- verifyChainHeight(t, tester, uint64(len(hashes)-1))
-}
-
-// Tests that if blocks are announced by multiple peers (or even the same buggy
-// peer), they will only get downloaded at most once.
-func TestFullConcurrentAnnouncements(t *testing.T) { testConcurrentAnnouncements(t, false) }
-func TestLightConcurrentAnnouncements(t *testing.T) { testConcurrentAnnouncements(t, true) }
-
-func testConcurrentAnnouncements(t *testing.T, light bool) {
- // Create a chain of blocks to import
- targetBlocks := 4 * hashLimit
- hashes, blocks := makeChain(targetBlocks, 0, genesis)
-
- // Assemble a tester with a built in counter for the requests
- tester := newTester(light)
- firstHeaderFetcher := tester.makeHeaderFetcher("first", blocks, -gatherSlack)
- firstBodyFetcher := tester.makeBodyFetcher("first", blocks, 0)
- secondHeaderFetcher := tester.makeHeaderFetcher("second", blocks, -gatherSlack)
- secondBodyFetcher := tester.makeBodyFetcher("second", blocks, 0)
-
- var counter atomic.Uint32
- firstHeaderWrapper := func(hash common.Hash, sink chan *eth.Response) (*eth.Request, error) {
- counter.Add(1)
- return firstHeaderFetcher(hash, sink)
- }
- secondHeaderWrapper := func(hash common.Hash, sink chan *eth.Response) (*eth.Request, error) {
- counter.Add(1)
- return secondHeaderFetcher(hash, sink)
- }
- // Iteratively announce blocks until all are imported
- imported := make(chan interface{})
- tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
- if light {
- if header == nil {
- t.Fatalf("Fetcher try to import empty header")
- }
- imported <- header
- } else {
- if block == nil {
- t.Fatalf("Fetcher try to import empty block")
- }
- imported <- block
- }
- }
- for i := len(hashes) - 2; i >= 0; i-- {
- tester.fetcher.Notify("first", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), firstHeaderWrapper, firstBodyFetcher)
- tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout+time.Millisecond), secondHeaderWrapper, secondBodyFetcher)
- tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout-time.Millisecond), secondHeaderWrapper, secondBodyFetcher)
- verifyImportEvent(t, imported, true)
- }
- verifyImportDone(t, imported)
-
- // Make sure no blocks were retrieved twice
- if c := int(counter.Load()); c != targetBlocks {
- t.Fatalf("retrieval count mismatch: have %v, want %v", c, targetBlocks)
- }
- verifyChainHeight(t, tester, uint64(len(hashes)-1))
-}
-
-// Tests that announcements arriving while a previous is being fetched still
-// results in a valid import.
-func TestFullOverlappingAnnouncements(t *testing.T) { testOverlappingAnnouncements(t, false) }
-func TestLightOverlappingAnnouncements(t *testing.T) { testOverlappingAnnouncements(t, true) }
-
-func testOverlappingAnnouncements(t *testing.T, light bool) {
- // Create a chain of blocks to import
- targetBlocks := 4 * hashLimit
- hashes, blocks := makeChain(targetBlocks, 0, genesis)
-
- tester := newTester(light)
- headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
- bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
-
- // Iteratively announce blocks, but overlap them continuously
- overlap := 16
- imported := make(chan interface{}, len(hashes)-1)
- for i := 0; i < overlap; i++ {
- imported <- nil
- }
- tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
- if light {
- if header == nil {
- t.Fatalf("Fetcher try to import empty header")
- }
- imported <- header
- } else {
- if block == nil {
- t.Fatalf("Fetcher try to import empty block")
- }
- imported <- block
- }
- }
-
- for i := len(hashes) - 2; i >= 0; i-- {
- tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
- select {
- case <-imported:
- case <-time.After(time.Second):
- t.Fatalf("block %d: import timeout", len(hashes)-i)
- }
- }
- // Wait for all the imports to complete and check count
- verifyImportCount(t, imported, overlap)
- verifyChainHeight(t, tester, uint64(len(hashes)-1))
-}
-
-// Tests that announces already being retrieved will not be duplicated.
-func TestFullPendingDeduplication(t *testing.T) { testPendingDeduplication(t, false) }
-func TestLightPendingDeduplication(t *testing.T) { testPendingDeduplication(t, true) }
-
-func testPendingDeduplication(t *testing.T, light bool) {
- // Create a hash and corresponding block
- hashes, blocks := makeChain(1, 0, genesis)
-
- // Assemble a tester with a built in counter and delayed fetcher
- tester := newTester(light)
- headerFetcher := tester.makeHeaderFetcher("repeater", blocks, -gatherSlack)
- bodyFetcher := tester.makeBodyFetcher("repeater", blocks, 0)
-
- delay := 50 * time.Millisecond
- var counter atomic.Uint32
- headerWrapper := func(hash common.Hash, sink chan *eth.Response) (*eth.Request, error) {
- counter.Add(1)
-
- // Simulate a long running fetch
- resink := make(chan *eth.Response)
- req, err := headerFetcher(hash, resink)
- if err == nil {
- go func() {
- res := <-resink
- time.Sleep(delay)
- sink <- res
- }()
- }
- return req, err
- }
- checkNonExist := func() bool {
- return tester.getBlock(hashes[0]) == nil
- }
- if light {
- checkNonExist = func() bool {
- return tester.getHeader(hashes[0]) == nil
- }
- }
- // Announce the same block many times until it's fetched (wait for any pending ops)
- for checkNonExist() {
- tester.fetcher.Notify("repeater", hashes[0], 1, time.Now().Add(-arriveTimeout), headerWrapper, bodyFetcher)
- time.Sleep(time.Millisecond)
- }
- time.Sleep(delay)
-
- // Check that all blocks were imported and none fetched twice
- if c := counter.Load(); c != 1 {
- t.Fatalf("retrieval count mismatch: have %v, want %v", c, 1)
- }
- verifyChainHeight(t, tester, 1)
-}
-
-// Tests that announcements retrieved in a random order are cached and eventually
-// imported when all the gaps are filled in.
-func TestFullRandomArrivalImport(t *testing.T) { testRandomArrivalImport(t, false) }
-func TestLightRandomArrivalImport(t *testing.T) { testRandomArrivalImport(t, true) }
-
-func testRandomArrivalImport(t *testing.T, light bool) {
- // Create a chain of blocks to import, and choose one to delay
- targetBlocks := maxQueueDist
- hashes, blocks := makeChain(targetBlocks, 0, genesis)
- skip := targetBlocks / 2
-
- tester := newTester(light)
- headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
- bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
-
- // Iteratively announce blocks, skipping one entry
- imported := make(chan interface{}, len(hashes)-1)
- tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
- if light {
- if header == nil {
- t.Fatalf("Fetcher try to import empty header")
- }
- imported <- header
- } else {
- if block == nil {
- t.Fatalf("Fetcher try to import empty block")
- }
- imported <- block
- }
- }
- for i := len(hashes) - 1; i >= 0; i-- {
- if i != skip {
- tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
- time.Sleep(time.Millisecond)
- }
- }
- // Finally announce the skipped entry and check full import
- tester.fetcher.Notify("valid", hashes[skip], uint64(len(hashes)-skip-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
- verifyImportCount(t, imported, len(hashes)-1)
- verifyChainHeight(t, tester, uint64(len(hashes)-1))
-}
-
-// Tests that direct block enqueues (due to block propagation vs. hash announce)
-// are correctly schedule, filling and import queue gaps.
-func TestQueueGapFill(t *testing.T) {
- // Create a chain of blocks to import, and choose one to not announce at all
- targetBlocks := maxQueueDist
- hashes, blocks := makeChain(targetBlocks, 0, genesis)
- skip := targetBlocks / 2
-
- tester := newTester(false)
- headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
- bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
-
- // Iteratively announce blocks, skipping one entry
- imported := make(chan interface{}, len(hashes)-1)
- tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block }
-
- for i := len(hashes) - 1; i >= 0; i-- {
- if i != skip {
- tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
- time.Sleep(time.Millisecond)
- }
- }
- // Fill the missing block directly as if propagated
- tester.fetcher.Enqueue("valid", blocks[hashes[skip]])
- verifyImportCount(t, imported, len(hashes)-1)
- verifyChainHeight(t, tester, uint64(len(hashes)-1))
-}
-
-// Tests that blocks arriving from various sources (multiple propagations, hash
-// announces, etc) do not get scheduled for import multiple times.
-func TestImportDeduplication(t *testing.T) {
- // Create two blocks to import (one for duplication, the other for stalling)
- hashes, blocks := makeChain(2, 0, genesis)
-
- // Create the tester and wrap the importer with a counter
- tester := newTester(false)
- headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
- bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
-
- var counter atomic.Uint32
- tester.fetcher.insertChain = func(blocks types.Blocks) (int, error) {
- counter.Add(uint32(len(blocks)))
- return tester.insertChain(blocks)
- }
- // Instrument the fetching and imported events
- fetching := make(chan []common.Hash)
- imported := make(chan interface{}, len(hashes)-1)
- tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- hashes }
- tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block }
-
- // Announce the duplicating block, wait for retrieval, and also propagate directly
- tester.fetcher.Notify("valid", hashes[0], 1, time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
- <-fetching
-
- tester.fetcher.Enqueue("valid", blocks[hashes[0]])
- tester.fetcher.Enqueue("valid", blocks[hashes[0]])
- tester.fetcher.Enqueue("valid", blocks[hashes[0]])
-
- // Fill the missing block directly as if propagated, and check import uniqueness
- tester.fetcher.Enqueue("valid", blocks[hashes[1]])
- verifyImportCount(t, imported, 2)
-
- if c := counter.Load(); c != 2 {
- t.Fatalf("import invocation count mismatch: have %v, want %v", c, 2)
- }
-}
-
-// Tests that blocks with numbers much lower or higher than out current head get
-// discarded to prevent wasting resources on useless blocks from faulty peers.
-func TestDistantPropagationDiscarding(t *testing.T) {
- // Create a long chain to import and define the discard boundaries
- hashes, blocks := makeChain(3*maxQueueDist, 0, genesis)
- head := hashes[len(hashes)/2]
-
- low, high := len(hashes)/2+maxUncleDist+1, len(hashes)/2-maxQueueDist-1
-
- // Create a tester and simulate a head block being the middle of the above chain
- tester := newTester(false)
-
- tester.lock.Lock()
- tester.hashes = []common.Hash{head}
- tester.blocks = map[common.Hash]*types.Block{head: blocks[head]}
- tester.lock.Unlock()
-
- // Ensure that a block with a lower number than the threshold is discarded
- tester.fetcher.Enqueue("lower", blocks[hashes[low]])
- time.Sleep(10 * time.Millisecond)
- if !tester.fetcher.queue.Empty() {
- t.Fatalf("fetcher queued stale block")
- }
- // Ensure that a block with a higher number than the threshold is discarded
- tester.fetcher.Enqueue("higher", blocks[hashes[high]])
- time.Sleep(10 * time.Millisecond)
- if !tester.fetcher.queue.Empty() {
- t.Fatalf("fetcher queued future block")
- }
-}
-
-// Tests that announcements with numbers much lower or higher than out current
-// head get discarded to prevent wasting resources on useless blocks from faulty
-// peers.
-func TestFullDistantAnnouncementDiscarding(t *testing.T) { testDistantAnnouncementDiscarding(t, false) }
-func TestLightDistantAnnouncementDiscarding(t *testing.T) { testDistantAnnouncementDiscarding(t, true) }
-
-func testDistantAnnouncementDiscarding(t *testing.T, light bool) {
- // Create a long chain to import and define the discard boundaries
- hashes, blocks := makeChain(3*maxQueueDist, 0, genesis)
- head := hashes[len(hashes)/2]
-
- low, high := len(hashes)/2+maxUncleDist+1, len(hashes)/2-maxQueueDist-1
-
- // Create a tester and simulate a head block being the middle of the above chain
- tester := newTester(light)
-
- tester.lock.Lock()
- tester.hashes = []common.Hash{head}
- tester.headers = map[common.Hash]*types.Header{head: blocks[head].Header()}
- tester.blocks = map[common.Hash]*types.Block{head: blocks[head]}
- tester.lock.Unlock()
-
- headerFetcher := tester.makeHeaderFetcher("lower", blocks, -gatherSlack)
- bodyFetcher := tester.makeBodyFetcher("lower", blocks, 0)
-
- fetching := make(chan struct{}, 2)
- tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- struct{}{} }
-
- // Ensure that a block with a lower number than the threshold is discarded
- tester.fetcher.Notify("lower", hashes[low], blocks[hashes[low]].NumberU64(), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
- select {
- case <-time.After(50 * time.Millisecond):
- case <-fetching:
- t.Fatalf("fetcher requested stale header")
- }
- // Ensure that a block with a higher number than the threshold is discarded
- tester.fetcher.Notify("higher", hashes[high], blocks[hashes[high]].NumberU64(), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
- select {
- case <-time.After(50 * time.Millisecond):
- case <-fetching:
- t.Fatalf("fetcher requested future header")
- }
-}
-
-// Tests that peers announcing blocks with invalid numbers (i.e. not matching
-// the headers provided afterwards) get dropped as malicious.
-func TestFullInvalidNumberAnnouncement(t *testing.T) { testInvalidNumberAnnouncement(t, false) }
-func TestLightInvalidNumberAnnouncement(t *testing.T) { testInvalidNumberAnnouncement(t, true) }
-
-func testInvalidNumberAnnouncement(t *testing.T, light bool) {
- // Create a single block to import and check numbers against
- hashes, blocks := makeChain(1, 0, genesis)
-
- tester := newTester(light)
- badHeaderFetcher := tester.makeHeaderFetcher("bad", blocks, -gatherSlack)
- badBodyFetcher := tester.makeBodyFetcher("bad", blocks, 0)
-
- imported := make(chan interface{})
- announced := make(chan interface{}, 2)
- tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
- if light {
- if header == nil {
- t.Fatalf("Fetcher try to import empty header")
- }
- imported <- header
- } else {
- if block == nil {
- t.Fatalf("Fetcher try to import empty block")
- }
- imported <- block
- }
- }
- // Announce a block with a bad number, check for immediate drop
- tester.fetcher.announceChangeHook = func(hash common.Hash, b bool) {
- announced <- nil
- }
- tester.fetcher.Notify("bad", hashes[0], 2, time.Now().Add(-arriveTimeout), badHeaderFetcher, badBodyFetcher)
- verifyAnnounce := func() {
- for i := 0; i < 2; i++ {
- select {
- case <-announced:
- continue
- case <-time.After(1 * time.Second):
- t.Fatal("announce timeout")
- return
- }
- }
- }
- verifyAnnounce()
- verifyImportEvent(t, imported, false)
- tester.lock.RLock()
- dropped := tester.drops["bad"]
- tester.lock.RUnlock()
-
- if !dropped {
- t.Fatalf("peer with invalid numbered announcement not dropped")
- }
- goodHeaderFetcher := tester.makeHeaderFetcher("good", blocks, -gatherSlack)
- goodBodyFetcher := tester.makeBodyFetcher("good", blocks, 0)
- // Make sure a good announcement passes without a drop
- tester.fetcher.Notify("good", hashes[0], 1, time.Now().Add(-arriveTimeout), goodHeaderFetcher, goodBodyFetcher)
- verifyAnnounce()
- verifyImportEvent(t, imported, true)
-
- tester.lock.RLock()
- dropped = tester.drops["good"]
- tester.lock.RUnlock()
-
- if dropped {
- t.Fatalf("peer with valid numbered announcement dropped")
- }
- verifyImportDone(t, imported)
-}
-
-// Tests that if a block is empty (i.e. header only), no body request should be
-// made, and instead the header should be assembled into a whole block in itself.
-func TestEmptyBlockShortCircuit(t *testing.T) {
- // Create a chain of blocks to import
- hashes, blocks := makeChain(32, 0, genesis)
-
- tester := newTester(false)
- defer tester.fetcher.Stop()
- headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
- bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
-
- // Add a monitoring hook for all internal events
- fetching := make(chan []common.Hash)
- tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- hashes }
-
- completing := make(chan []common.Hash)
- tester.fetcher.completingHook = func(hashes []common.Hash) { completing <- hashes }
-
- imported := make(chan interface{})
- tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
- if block == nil {
- t.Fatalf("Fetcher try to import empty block")
- }
- imported <- block
- }
- // Iteratively announce blocks until all are imported
- for i := len(hashes) - 2; i >= 0; i-- {
- tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
-
- // All announces should fetch the header
- verifyFetchingEvent(t, fetching, true)
-
- // Only blocks with data contents should request bodies
- verifyCompletingEvent(t, completing, len(blocks[hashes[i]].Transactions()) > 0 || len(blocks[hashes[i]].Uncles()) > 0)
-
- // Irrelevant of the construct, import should succeed
- verifyImportEvent(t, imported, true)
- }
- verifyImportDone(t, imported)
-}
-
-// Tests that a peer is unable to use unbounded memory with sending infinite
-// block announcements to a node, but that even in the face of such an attack,
-// the fetcher remains operational.
-func TestHashMemoryExhaustionAttack(t *testing.T) {
- // Create a tester with instrumented import hooks
- tester := newTester(false)
-
- imported, announces := make(chan interface{}), atomic.Int32{}
- tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block }
- tester.fetcher.announceChangeHook = func(hash common.Hash, added bool) {
- if added {
- announces.Add(1)
- } else {
- announces.Add(-1)
- }
- }
- // Create a valid chain and an infinite junk chain
- targetBlocks := hashLimit + 2*maxQueueDist
- hashes, blocks := makeChain(targetBlocks, 0, genesis)
- validHeaderFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
- validBodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
-
- attack, _ := makeChain(targetBlocks, 0, unknownBlock)
- attackerHeaderFetcher := tester.makeHeaderFetcher("attacker", nil, -gatherSlack)
- attackerBodyFetcher := tester.makeBodyFetcher("attacker", nil, 0)
-
- // Feed the tester a huge hashset from the attacker, and a limited from the valid peer
- for i := 0; i < len(attack); i++ {
- if i < maxQueueDist {
- tester.fetcher.Notify("valid", hashes[len(hashes)-2-i], uint64(i+1), time.Now(), validHeaderFetcher, validBodyFetcher)
- }
- tester.fetcher.Notify("attacker", attack[i], 1 /* don't distance drop */, time.Now(), attackerHeaderFetcher, attackerBodyFetcher)
- }
- if count := announces.Load(); count != hashLimit+maxQueueDist {
- t.Fatalf("queued announce count mismatch: have %d, want %d", count, hashLimit+maxQueueDist)
- }
- // Wait for fetches to complete
- verifyImportCount(t, imported, maxQueueDist)
-
- // Feed the remaining valid hashes to ensure DOS protection state remains clean
- for i := len(hashes) - maxQueueDist - 2; i >= 0; i-- {
- tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), validHeaderFetcher, validBodyFetcher)
- verifyImportEvent(t, imported, true)
- }
- verifyImportDone(t, imported)
-}
-
-// Tests that blocks sent to the fetcher (either through propagation or via hash
-// announces and retrievals) don't pile up indefinitely, exhausting available
-// system memory.
-func TestBlockMemoryExhaustionAttack(t *testing.T) {
- // Create a tester with instrumented import hooks
- tester := newTester(false)
-
- imported, enqueued := make(chan interface{}), atomic.Int32{}
- tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block }
- tester.fetcher.queueChangeHook = func(hash common.Hash, added bool) {
- if added {
- enqueued.Add(1)
- } else {
- enqueued.Add(-1)
- }
- }
- // Create a valid chain and a batch of dangling (but in range) blocks
- targetBlocks := hashLimit + 2*maxQueueDist
- hashes, blocks := makeChain(targetBlocks, 0, genesis)
- attack := make(map[common.Hash]*types.Block)
- for i := byte(0); len(attack) < blockLimit+2*maxQueueDist; i++ {
- hashes, blocks := makeChain(maxQueueDist-1, i, unknownBlock)
- for _, hash := range hashes[:maxQueueDist-2] {
- attack[hash] = blocks[hash]
- }
- }
- // Try to feed all the attacker blocks make sure only a limited batch is accepted
- for _, block := range attack {
- tester.fetcher.Enqueue("attacker", block)
- }
- time.Sleep(200 * time.Millisecond)
- if queued := enqueued.Load(); queued != blockLimit {
- t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit)
- }
- // Queue up a batch of valid blocks, and check that a new peer is allowed to do so
- for i := 0; i < maxQueueDist-1; i++ {
- tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-3-i]])
- }
- time.Sleep(100 * time.Millisecond)
- if queued := enqueued.Load(); queued != blockLimit+maxQueueDist-1 {
- t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit+maxQueueDist-1)
- }
- // Insert the missing piece (and sanity check the import)
- tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-2]])
- verifyImportCount(t, imported, maxQueueDist)
-
- // Insert the remaining blocks in chunks to ensure clean DOS protection
- for i := maxQueueDist; i < len(hashes)-1; i++ {
- tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-2-i]])
- verifyImportEvent(t, imported, true)
- }
- verifyImportDone(t, imported)
-}
diff --git a/eth/fetcher/tx_fetcher.go b/eth/fetcher/tx_fetcher.go
deleted file mode 100644
index ea7892d8d8..0000000000
--- a/eth/fetcher/tx_fetcher.go
+++ /dev/null
@@ -1,1003 +0,0 @@
-// Copyright 2019 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 .
-
-package fetcher
-
-import (
- "bytes"
- "errors"
- "fmt"
- "math"
- mrand "math/rand"
- "sort"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/lru"
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
-)
-
-const (
- // maxTxAnnounces is the maximum number of unique transaction a peer
- // can announce in a short time.
- maxTxAnnounces = 4096
-
- // maxTxRetrievals is the maximum number of transactions that can be fetched
- // in one request. The rationale for picking 256 is to have a reasonabe lower
- // bound for the transferred data (don't waste RTTs, transfer more meaningful
- // batch sizes), but also have an upper bound on the sequentiality to allow
- // using our entire peerset for deliveries.
- //
- // This number also acts as a failsafe against malicious announces which might
- // cause us to request more data than we'd expect.
- maxTxRetrievals = 256
-
- // maxTxRetrievalSize is the max number of bytes that delivered transactions
- // should weigh according to the announcements. The 128KB was chosen to limit
- // retrieving a maximum of one blob transaction at a time to minimize hogging
- // a connection between two peers.
- maxTxRetrievalSize = 128 * 1024
-
- // maxTxUnderpricedSetSize is the size of the underpriced transaction set that
- // is used to track recent transactions that have been dropped so we don't
- // re-request them.
- maxTxUnderpricedSetSize = 32768
-
- // maxTxUnderpricedTimeout is the max time a transaction should be stuck in the underpriced set.
- maxTxUnderpricedTimeout = 5 * time.Minute
-
- // txArriveTimeout is the time allowance before an announced transaction is
- // explicitly requested.
- txArriveTimeout = 500 * time.Millisecond
-
- // txGatherSlack is the interval used to collate almost-expired announces
- // with network fetches.
- txGatherSlack = 100 * time.Millisecond
-)
-
-var (
- // txFetchTimeout is the maximum allotted time to return an explicitly
- // requested transaction.
- txFetchTimeout = 5 * time.Second
-)
-
-var (
- txAnnounceInMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/announces/in", nil)
- txAnnounceKnownMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/announces/known", nil)
- txAnnounceUnderpricedMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/announces/underpriced", nil)
- txAnnounceDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/announces/dos", nil)
-
- txBroadcastInMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/broadcasts/in", nil)
- txBroadcastKnownMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/broadcasts/known", nil)
- txBroadcastUnderpricedMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/broadcasts/underpriced", nil)
- txBroadcastOtherRejectMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/broadcasts/otherreject", nil)
-
- txRequestOutMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/request/out", nil)
- txRequestFailMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/request/fail", nil)
- txRequestDoneMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/request/done", nil)
- txRequestTimeoutMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/request/timeout", nil)
-
- txReplyInMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/replies/in", nil)
- txReplyKnownMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/replies/known", nil)
- txReplyUnderpricedMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/replies/underpriced", nil)
- txReplyOtherRejectMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/replies/otherreject", nil)
-
- txFetcherWaitingPeers = metrics.NewRegisteredGauge("eth/fetcher/transaction/waiting/peers", nil)
- txFetcherWaitingHashes = metrics.NewRegisteredGauge("eth/fetcher/transaction/waiting/hashes", nil)
- txFetcherQueueingPeers = metrics.NewRegisteredGauge("eth/fetcher/transaction/queueing/peers", nil)
- txFetcherQueueingHashes = metrics.NewRegisteredGauge("eth/fetcher/transaction/queueing/hashes", nil)
- txFetcherFetchingPeers = metrics.NewRegisteredGauge("eth/fetcher/transaction/fetching/peers", nil)
- txFetcherFetchingHashes = metrics.NewRegisteredGauge("eth/fetcher/transaction/fetching/hashes", nil)
-)
-
-// txAnnounce is the notification of the availability of a batch
-// of new transactions in the network.
-type txAnnounce struct {
- origin string // Identifier of the peer originating the notification
- hashes []common.Hash // Batch of transaction hashes being announced
- metas []*txMetadata // Batch of metadatas associated with the hashes (nil before eth/68)
-}
-
-// txMetadata is a set of extra data transmitted along the announcement for better
-// fetch scheduling.
-type txMetadata struct {
- kind byte // Transaction consensus type
- size uint32 // Transaction size in bytes
-}
-
-// txRequest represents an in-flight transaction retrieval request destined to
-// a specific peers.
-type txRequest struct {
- hashes []common.Hash // Transactions having been requested
- stolen map[common.Hash]struct{} // Deliveries by someone else (don't re-request)
- time mclock.AbsTime // Timestamp of the request
-}
-
-// txDelivery is the notification that a batch of transactions have been added
-// to the pool and should be untracked.
-type txDelivery struct {
- origin string // Identifier of the peer originating the notification
- hashes []common.Hash // Batch of transaction hashes having been delivered
- metas []txMetadata // Batch of metadatas associated with the delivered hashes
- direct bool // Whether this is a direct reply or a broadcast
-}
-
-// txDrop is the notification that a peer has disconnected.
-type txDrop struct {
- peer string
-}
-
-// TxFetcher is responsible for retrieving new transaction based on announcements.
-//
-// The fetcher operates in 3 stages:
-// - Transactions that are newly discovered are moved into a wait list.
-// - After ~500ms passes, transactions from the wait list that have not been
-// broadcast to us in whole are moved into a queueing area.
-// - When a connected peer doesn't have in-flight retrieval requests, any
-// transaction queued up (and announced by the peer) are allocated to the
-// peer and moved into a fetching status until it's fulfilled or fails.
-//
-// The invariants of the fetcher are:
-// - Each tracked transaction (hash) must only be present in one of the
-// three stages. This ensures that the fetcher operates akin to a finite
-// state automata and there's do data leak.
-// - Each peer that announced transactions may be scheduled retrievals, but
-// only ever one concurrently. This ensures we can immediately know what is
-// missing from a reply and reschedule it.
-type TxFetcher struct {
- notify chan *txAnnounce
- cleanup chan *txDelivery
- drop chan *txDrop
- quit chan struct{}
-
- underpriced *lru.Cache[common.Hash, time.Time] // Transactions discarded as too cheap (don't re-fetch)
-
- // Stage 1: Waiting lists for newly discovered transactions that might be
- // broadcast without needing explicit request/reply round trips.
- waitlist map[common.Hash]map[string]struct{} // Transactions waiting for an potential broadcast
- waittime map[common.Hash]mclock.AbsTime // Timestamps when transactions were added to the waitlist
- waitslots map[string]map[common.Hash]*txMetadata // Waiting announcements grouped by peer (DoS protection)
-
- // Stage 2: Queue of transactions that waiting to be allocated to some peer
- // to be retrieved directly.
- announces map[string]map[common.Hash]*txMetadata // Set of announced transactions, grouped by origin peer
- announced map[common.Hash]map[string]struct{} // Set of download locations, grouped by transaction hash
-
- // Stage 3: Set of transactions currently being retrieved, some which may be
- // fulfilled and some rescheduled. Note, this step shares 'announces' from the
- // previous stage to avoid having to duplicate (need it for DoS checks).
- fetching map[common.Hash]string // Transaction set currently being retrieved
- requests map[string]*txRequest // In-flight transaction retrievals
- alternates map[common.Hash]map[string]struct{} // In-flight transaction alternate origins if retrieval fails
-
- // Callbacks
- hasTx func(common.Hash) bool // Retrieves a tx from the local txpool
- addTxs func([]*types.Transaction) []error // Insert a batch of transactions into local txpool
- fetchTxs func(string, []common.Hash) error // Retrieves a set of txs from a remote peer
- dropPeer func(string) // Drops a peer in case of announcement violation
-
- step chan struct{} // Notification channel when the fetcher loop iterates
- clock mclock.Clock // Time wrapper to simulate in tests
- rand *mrand.Rand // Randomizer to use in tests instead of map range loops (soft-random)
-}
-
-// NewTxFetcher creates a transaction fetcher to retrieve transaction
-// based on hash announcements.
-func NewTxFetcher(hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string)) *TxFetcher {
- return NewTxFetcherForTests(hasTx, addTxs, fetchTxs, dropPeer, mclock.System{}, nil)
-}
-
-// NewTxFetcherForTests is a testing method to mock out the realtime clock with
-// a simulated version and the internal randomness with a deterministic one.
-func NewTxFetcherForTests(
- hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string),
- clock mclock.Clock, rand *mrand.Rand) *TxFetcher {
- return &TxFetcher{
- notify: make(chan *txAnnounce),
- cleanup: make(chan *txDelivery),
- drop: make(chan *txDrop),
- quit: make(chan struct{}),
- waitlist: make(map[common.Hash]map[string]struct{}),
- waittime: make(map[common.Hash]mclock.AbsTime),
- waitslots: make(map[string]map[common.Hash]*txMetadata),
- announces: make(map[string]map[common.Hash]*txMetadata),
- announced: make(map[common.Hash]map[string]struct{}),
- fetching: make(map[common.Hash]string),
- requests: make(map[string]*txRequest),
- alternates: make(map[common.Hash]map[string]struct{}),
- underpriced: lru.NewCache[common.Hash, time.Time](maxTxUnderpricedSetSize),
- hasTx: hasTx,
- addTxs: addTxs,
- fetchTxs: fetchTxs,
- dropPeer: dropPeer,
- clock: clock,
- rand: rand,
- }
-}
-
-// Notify announces the fetcher of the potential availability of a new batch of
-// transactions in the network.
-func (f *TxFetcher) Notify(peer string, types []byte, sizes []uint32, hashes []common.Hash) error {
- // Keep track of all the announced transactions
- txAnnounceInMeter.Mark(int64(len(hashes)))
-
- // Skip any transaction announcements that we already know of, or that we've
- // previously marked as cheap and discarded. This check is of course racy,
- // because multiple concurrent notifies will still manage to pass it, but it's
- // still valuable to check here because it runs concurrent to the internal
- // loop, so anything caught here is time saved internally.
- var (
- unknownHashes = make([]common.Hash, 0, len(hashes))
- unknownMetas = make([]*txMetadata, 0, len(hashes))
-
- duplicate int64
- underpriced int64
- )
- for i, hash := range hashes {
- switch {
- case f.hasTx(hash):
- duplicate++
- case f.isKnownUnderpriced(hash):
- underpriced++
- default:
- unknownHashes = append(unknownHashes, hash)
- if types == nil {
- unknownMetas = append(unknownMetas, nil)
- } else {
- unknownMetas = append(unknownMetas, &txMetadata{kind: types[i], size: sizes[i]})
- }
- }
- }
- txAnnounceKnownMeter.Mark(duplicate)
- txAnnounceUnderpricedMeter.Mark(underpriced)
-
- // If anything's left to announce, push it into the internal loop
- if len(unknownHashes) == 0 {
- return nil
- }
- announce := &txAnnounce{origin: peer, hashes: unknownHashes, metas: unknownMetas}
- select {
- case f.notify <- announce:
- return nil
- case <-f.quit:
- return errTerminated
- }
-}
-
-// isKnownUnderpriced reports whether a transaction hash was recently found to be underpriced.
-func (f *TxFetcher) isKnownUnderpriced(hash common.Hash) bool {
- prevTime, ok := f.underpriced.Peek(hash)
- if ok && prevTime.Before(time.Now().Add(-maxTxUnderpricedTimeout)) {
- f.underpriced.Remove(hash)
- return false
- }
- return ok
-}
-
-// Enqueue imports a batch of received transaction into the transaction pool
-// and the fetcher. This method may be called by both transaction broadcasts and
-// direct request replies. The differentiation is important so the fetcher can
-// re-schedule missing transactions as soon as possible.
-func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool) error {
- var (
- inMeter = txReplyInMeter
- knownMeter = txReplyKnownMeter
- underpricedMeter = txReplyUnderpricedMeter
- otherRejectMeter = txReplyOtherRejectMeter
- )
- if !direct {
- inMeter = txBroadcastInMeter
- knownMeter = txBroadcastKnownMeter
- underpricedMeter = txBroadcastUnderpricedMeter
- otherRejectMeter = txBroadcastOtherRejectMeter
- }
- // Keep track of all the propagated transactions
- inMeter.Mark(int64(len(txs)))
-
- // Push all the transactions into the pool, tracking underpriced ones to avoid
- // re-requesting them and dropping the peer in case of malicious transfers.
- var (
- added = make([]common.Hash, 0, len(txs))
- metas = make([]txMetadata, 0, len(txs))
- )
- // proceed in batches
- for i := 0; i < len(txs); i += 128 {
- end := i + 128
- if end > len(txs) {
- end = len(txs)
- }
- var (
- duplicate int64
- underpriced int64
- otherreject int64
- )
- batch := txs[i:end]
-
- for j, err := range f.addTxs(batch) {
- // Track the transaction hash if the price is too low for us.
- // Avoid re-request this transaction when we receive another
- // announcement.
- if errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced) {
- f.underpriced.Add(batch[j].Hash(), batch[j].Time())
- }
- // Track a few interesting failure types
- switch {
- case err == nil: // Noop, but need to handle to not count these
-
- case errors.Is(err, txpool.ErrAlreadyKnown):
- duplicate++
-
- case errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced):
- underpriced++
-
- default:
- otherreject++
- }
- added = append(added, batch[j].Hash())
- metas = append(metas, txMetadata{
- kind: batch[j].Type(),
- size: uint32(batch[j].Size()),
- })
- }
- knownMeter.Mark(duplicate)
- underpricedMeter.Mark(underpriced)
- otherRejectMeter.Mark(otherreject)
-
- // If 'other reject' is >25% of the deliveries in any batch, sleep a bit.
- if otherreject > 128/4 {
- time.Sleep(200 * time.Millisecond)
- log.Debug("Peer delivering stale transactions", "peer", peer, "rejected", otherreject)
- }
- }
- select {
- case f.cleanup <- &txDelivery{origin: peer, hashes: added, metas: metas, direct: direct}:
- return nil
- case <-f.quit:
- return errTerminated
- }
-}
-
-// Drop should be called when a peer disconnects. It cleans up all the internal
-// data structures of the given node.
-func (f *TxFetcher) Drop(peer string) error {
- select {
- case f.drop <- &txDrop{peer: peer}:
- return nil
- case <-f.quit:
- return errTerminated
- }
-}
-
-// Start boots up the announcement based synchroniser, accepting and processing
-// hash notifications and block fetches until termination requested.
-func (f *TxFetcher) Start() {
- go f.loop()
-}
-
-// Stop terminates the announcement based synchroniser, canceling all pending
-// operations.
-func (f *TxFetcher) Stop() {
- close(f.quit)
-}
-
-func (f *TxFetcher) loop() {
- var (
- waitTimer = new(mclock.Timer)
- timeoutTimer = new(mclock.Timer)
-
- waitTrigger = make(chan struct{}, 1)
- timeoutTrigger = make(chan struct{}, 1)
- )
- for {
- select {
- case ann := <-f.notify:
- // Drop part of the new announcements if there are too many accumulated.
- // Note, we could but do not filter already known transactions here as
- // the probability of something arriving between this call and the pre-
- // filter outside is essentially zero.
- used := len(f.waitslots[ann.origin]) + len(f.announces[ann.origin])
- if used >= maxTxAnnounces {
- // This can happen if a set of transactions are requested but not
- // all fulfilled, so the remainder are rescheduled without the cap
- // check. Should be fine as the limit is in the thousands and the
- // request size in the hundreds.
- txAnnounceDOSMeter.Mark(int64(len(ann.hashes)))
- break
- }
- want := used + len(ann.hashes)
- if want > maxTxAnnounces {
- txAnnounceDOSMeter.Mark(int64(want - maxTxAnnounces))
-
- ann.hashes = ann.hashes[:want-maxTxAnnounces]
- ann.metas = ann.metas[:want-maxTxAnnounces]
- }
- // All is well, schedule the remainder of the transactions
- idleWait := len(f.waittime) == 0
- _, oldPeer := f.announces[ann.origin]
-
- for i, hash := range ann.hashes {
- // If the transaction is already downloading, add it to the list
- // of possible alternates (in case the current retrieval fails) and
- // also account it for the peer.
- if f.alternates[hash] != nil {
- f.alternates[hash][ann.origin] = struct{}{}
-
- // Stage 2 and 3 share the set of origins per tx
- if announces := f.announces[ann.origin]; announces != nil {
- announces[hash] = ann.metas[i]
- } else {
- f.announces[ann.origin] = map[common.Hash]*txMetadata{hash: ann.metas[i]}
- }
- continue
- }
- // If the transaction is not downloading, but is already queued
- // from a different peer, track it for the new peer too.
- if f.announced[hash] != nil {
- f.announced[hash][ann.origin] = struct{}{}
-
- // Stage 2 and 3 share the set of origins per tx
- if announces := f.announces[ann.origin]; announces != nil {
- announces[hash] = ann.metas[i]
- } else {
- f.announces[ann.origin] = map[common.Hash]*txMetadata{hash: ann.metas[i]}
- }
- continue
- }
- // If the transaction is already known to the fetcher, but not
- // yet downloading, add the peer as an alternate origin in the
- // waiting list.
- if f.waitlist[hash] != nil {
- // Ignore double announcements from the same peer. This is
- // especially important if metadata is also passed along to
- // prevent malicious peers flip-flopping good/bad values.
- if _, ok := f.waitlist[hash][ann.origin]; ok {
- continue
- }
- f.waitlist[hash][ann.origin] = struct{}{}
-
- if waitslots := f.waitslots[ann.origin]; waitslots != nil {
- waitslots[hash] = ann.metas[i]
- } else {
- f.waitslots[ann.origin] = map[common.Hash]*txMetadata{hash: ann.metas[i]}
- }
- continue
- }
- // Transaction unknown to the fetcher, insert it into the waiting list
- f.waitlist[hash] = map[string]struct{}{ann.origin: {}}
- f.waittime[hash] = f.clock.Now()
-
- if waitslots := f.waitslots[ann.origin]; waitslots != nil {
- waitslots[hash] = ann.metas[i]
- } else {
- f.waitslots[ann.origin] = map[common.Hash]*txMetadata{hash: ann.metas[i]}
- }
- }
- // If a new item was added to the waitlist, schedule it into the fetcher
- if idleWait && len(f.waittime) > 0 {
- f.rescheduleWait(waitTimer, waitTrigger)
- }
- // If this peer is new and announced something already queued, maybe
- // request transactions from them
- if !oldPeer && len(f.announces[ann.origin]) > 0 {
- f.scheduleFetches(timeoutTimer, timeoutTrigger, map[string]struct{}{ann.origin: {}})
- }
-
- case <-waitTrigger:
- // At least one transaction's waiting time ran out, push all expired
- // ones into the retrieval queues
- actives := make(map[string]struct{})
- for hash, instance := range f.waittime {
- if time.Duration(f.clock.Now()-instance)+txGatherSlack > txArriveTimeout {
- // Transaction expired without propagation, schedule for retrieval
- if f.announced[hash] != nil {
- panic("announce tracker already contains waitlist item")
- }
- f.announced[hash] = f.waitlist[hash]
- for peer := range f.waitlist[hash] {
- if announces := f.announces[peer]; announces != nil {
- announces[hash] = f.waitslots[peer][hash]
- } else {
- f.announces[peer] = map[common.Hash]*txMetadata{hash: f.waitslots[peer][hash]}
- }
- delete(f.waitslots[peer], hash)
- if len(f.waitslots[peer]) == 0 {
- delete(f.waitslots, peer)
- }
- actives[peer] = struct{}{}
- }
- delete(f.waittime, hash)
- delete(f.waitlist, hash)
- }
- }
- // If transactions are still waiting for propagation, reschedule the wait timer
- if len(f.waittime) > 0 {
- f.rescheduleWait(waitTimer, waitTrigger)
- }
- // If any peers became active and are idle, request transactions from them
- if len(actives) > 0 {
- f.scheduleFetches(timeoutTimer, timeoutTrigger, actives)
- }
-
- case <-timeoutTrigger:
- // Clean up any expired retrievals and avoid re-requesting them from the
- // same peer (either overloaded or malicious, useless in both cases). We
- // could also penalize (Drop), but there's nothing to gain, and if could
- // possibly further increase the load on it.
- for peer, req := range f.requests {
- if time.Duration(f.clock.Now()-req.time)+txGatherSlack > txFetchTimeout {
- txRequestTimeoutMeter.Mark(int64(len(req.hashes)))
-
- // Reschedule all the not-yet-delivered fetches to alternate peers
- for _, hash := range req.hashes {
- // Skip rescheduling hashes already delivered by someone else
- if req.stolen != nil {
- if _, ok := req.stolen[hash]; ok {
- continue
- }
- }
- // Move the delivery back from fetching to queued
- if _, ok := f.announced[hash]; ok {
- panic("announced tracker already contains alternate item")
- }
- if f.alternates[hash] != nil { // nil if tx was broadcast during fetch
- f.announced[hash] = f.alternates[hash]
- }
- delete(f.announced[hash], peer)
- if len(f.announced[hash]) == 0 {
- delete(f.announced, hash)
- }
- delete(f.announces[peer], hash)
- delete(f.alternates, hash)
- delete(f.fetching, hash)
- }
- if len(f.announces[peer]) == 0 {
- delete(f.announces, peer)
- }
- // Keep track of the request as dangling, but never expire
- f.requests[peer].hashes = nil
- }
- }
- // Schedule a new transaction retrieval
- f.scheduleFetches(timeoutTimer, timeoutTrigger, nil)
-
- // No idea if we scheduled something or not, trigger the timer if needed
- // TODO(karalabe): this is kind of lame, can't we dump it into scheduleFetches somehow?
- f.rescheduleTimeout(timeoutTimer, timeoutTrigger)
-
- case delivery := <-f.cleanup:
- // Independent if the delivery was direct or broadcast, remove all
- // traces of the hash from internal trackers. That said, compare any
- // advertised metadata with the real ones and drop bad peers.
- for i, hash := range delivery.hashes {
- if _, ok := f.waitlist[hash]; ok {
- for peer, txset := range f.waitslots {
- if meta := txset[hash]; meta != nil {
- if delivery.metas[i].kind != meta.kind {
- log.Warn("Announced transaction type mismatch", "peer", peer, "tx", hash, "type", delivery.metas[i].kind, "ann", meta.kind)
- f.dropPeer(peer)
- } else if delivery.metas[i].size != meta.size {
- if math.Abs(float64(delivery.metas[i].size)-float64(meta.size)) > 8 {
- log.Warn("Announced transaction size mismatch", "peer", peer, "tx", hash, "size", delivery.metas[i].size, "ann", meta.size)
-
- // Normally we should drop a peer considering this is a protocol violation.
- // However, due to the RLP vs consensus format messyness, allow a few bytes
- // wiggle-room where we only warn, but don't drop.
- //
- // TODO(karalabe): Get rid of this relaxation when clients are proven stable.
- f.dropPeer(peer)
- }
- }
- }
- delete(txset, hash)
- if len(txset) == 0 {
- delete(f.waitslots, peer)
- }
- }
- delete(f.waitlist, hash)
- delete(f.waittime, hash)
- } else {
- for peer, txset := range f.announces {
- if meta := txset[hash]; meta != nil {
- if delivery.metas[i].kind != meta.kind {
- log.Warn("Announced transaction type mismatch", "peer", peer, "tx", hash, "type", delivery.metas[i].kind, "ann", meta.kind)
- f.dropPeer(peer)
- } else if delivery.metas[i].size != meta.size {
- if math.Abs(float64(delivery.metas[i].size)-float64(meta.size)) > 8 {
- log.Warn("Announced transaction size mismatch", "peer", peer, "tx", hash, "size", delivery.metas[i].size, "ann", meta.size)
-
- // Normally we should drop a peer considering this is a protocol violation.
- // However, due to the RLP vs consensus format messyness, allow a few bytes
- // wiggle-room where we only warn, but don't drop.
- //
- // TODO(karalabe): Get rid of this relaxation when clients are proven stable.
- f.dropPeer(peer)
- }
- }
- }
- delete(txset, hash)
- if len(txset) == 0 {
- delete(f.announces, peer)
- }
- }
- delete(f.announced, hash)
- delete(f.alternates, hash)
-
- // If a transaction currently being fetched from a different
- // origin was delivered (delivery stolen), mark it so the
- // actual delivery won't double schedule it.
- if origin, ok := f.fetching[hash]; ok && (origin != delivery.origin || !delivery.direct) {
- stolen := f.requests[origin].stolen
- if stolen == nil {
- f.requests[origin].stolen = make(map[common.Hash]struct{})
- stolen = f.requests[origin].stolen
- }
- stolen[hash] = struct{}{}
- }
- delete(f.fetching, hash)
- }
- }
- // In case of a direct delivery, also reschedule anything missing
- // from the original query
- if delivery.direct {
- // Mark the requesting successful (independent of individual status)
- txRequestDoneMeter.Mark(int64(len(delivery.hashes)))
-
- // Make sure something was pending, nuke it
- req := f.requests[delivery.origin]
- if req == nil {
- log.Warn("Unexpected transaction delivery", "peer", delivery.origin)
- break
- }
- delete(f.requests, delivery.origin)
-
- // Anything not delivered should be re-scheduled (with or without
- // this peer, depending on the response cutoff)
- delivered := make(map[common.Hash]struct{})
- for _, hash := range delivery.hashes {
- delivered[hash] = struct{}{}
- }
- cutoff := len(req.hashes) // If nothing is delivered, assume everything is missing, don't retry!!!
- for i, hash := range req.hashes {
- if _, ok := delivered[hash]; ok {
- cutoff = i
- }
- }
- // Reschedule missing hashes from alternates, not-fulfilled from alt+self
- for i, hash := range req.hashes {
- // Skip rescheduling hashes already delivered by someone else
- if req.stolen != nil {
- if _, ok := req.stolen[hash]; ok {
- continue
- }
- }
- if _, ok := delivered[hash]; !ok {
- if i < cutoff {
- delete(f.alternates[hash], delivery.origin)
- delete(f.announces[delivery.origin], hash)
- if len(f.announces[delivery.origin]) == 0 {
- delete(f.announces, delivery.origin)
- }
- }
- if len(f.alternates[hash]) > 0 {
- if _, ok := f.announced[hash]; ok {
- panic(fmt.Sprintf("announced tracker already contains alternate item: %v", f.announced[hash]))
- }
- f.announced[hash] = f.alternates[hash]
- }
- }
- delete(f.alternates, hash)
- delete(f.fetching, hash)
- }
- // Something was delivered, try to reschedule requests
- f.scheduleFetches(timeoutTimer, timeoutTrigger, nil) // Partial delivery may enable others to deliver too
- }
-
- case drop := <-f.drop:
- // A peer was dropped, remove all traces of it
- if _, ok := f.waitslots[drop.peer]; ok {
- for hash := range f.waitslots[drop.peer] {
- delete(f.waitlist[hash], drop.peer)
- if len(f.waitlist[hash]) == 0 {
- delete(f.waitlist, hash)
- delete(f.waittime, hash)
- }
- }
- delete(f.waitslots, drop.peer)
- if len(f.waitlist) > 0 {
- f.rescheduleWait(waitTimer, waitTrigger)
- }
- }
- // Clean up any active requests
- var request *txRequest
- if request = f.requests[drop.peer]; request != nil {
- for _, hash := range request.hashes {
- // Skip rescheduling hashes already delivered by someone else
- if request.stolen != nil {
- if _, ok := request.stolen[hash]; ok {
- continue
- }
- }
- // Undelivered hash, reschedule if there's an alternative origin available
- delete(f.alternates[hash], drop.peer)
- if len(f.alternates[hash]) == 0 {
- delete(f.alternates, hash)
- } else {
- f.announced[hash] = f.alternates[hash]
- delete(f.alternates, hash)
- }
- delete(f.fetching, hash)
- }
- delete(f.requests, drop.peer)
- }
- // Clean up general announcement tracking
- if _, ok := f.announces[drop.peer]; ok {
- for hash := range f.announces[drop.peer] {
- delete(f.announced[hash], drop.peer)
- if len(f.announced[hash]) == 0 {
- delete(f.announced, hash)
- }
- }
- delete(f.announces, drop.peer)
- }
- // If a request was cancelled, check if anything needs to be rescheduled
- if request != nil {
- f.scheduleFetches(timeoutTimer, timeoutTrigger, nil)
- f.rescheduleTimeout(timeoutTimer, timeoutTrigger)
- }
-
- case <-f.quit:
- return
- }
- // No idea what happened, but bump some sanity metrics
- txFetcherWaitingPeers.Update(int64(len(f.waitslots)))
- txFetcherWaitingHashes.Update(int64(len(f.waitlist)))
- txFetcherQueueingPeers.Update(int64(len(f.announces) - len(f.requests)))
- txFetcherQueueingHashes.Update(int64(len(f.announced)))
- txFetcherFetchingPeers.Update(int64(len(f.requests)))
- txFetcherFetchingHashes.Update(int64(len(f.fetching)))
-
- // Loop did something, ping the step notifier if needed (tests)
- if f.step != nil {
- f.step <- struct{}{}
- }
- }
-}
-
-// rescheduleWait iterates over all the transactions currently in the waitlist
-// and schedules the movement into the fetcher for the earliest.
-//
-// The method has a granularity of 'gatherSlack', since there's not much point in
-// spinning over all the transactions just to maybe find one that should trigger
-// a few ms earlier.
-func (f *TxFetcher) rescheduleWait(timer *mclock.Timer, trigger chan struct{}) {
- if *timer != nil {
- (*timer).Stop()
- }
- now := f.clock.Now()
-
- earliest := now
- for _, instance := range f.waittime {
- if earliest > instance {
- earliest = instance
- if txArriveTimeout-time.Duration(now-earliest) < gatherSlack {
- break
- }
- }
- }
- *timer = f.clock.AfterFunc(txArriveTimeout-time.Duration(now-earliest), func() {
- trigger <- struct{}{}
- })
-}
-
-// rescheduleTimeout iterates over all the transactions currently in flight and
-// schedules a cleanup run when the first would trigger.
-//
-// The method has a granularity of 'gatherSlack', since there's not much point in
-// spinning over all the transactions just to maybe find one that should trigger
-// a few ms earlier.
-//
-// This method is a bit "flaky" "by design". In theory the timeout timer only ever
-// should be rescheduled if some request is pending. In practice, a timeout will
-// cause the timer to be rescheduled every 5 secs (until the peer comes through or
-// disconnects). This is a limitation of the fetcher code because we don't trac
-// pending requests and timed out requests separately. Without double tracking, if
-// we simply didn't reschedule the timer on all-timeout then the timer would never
-// be set again since len(request) > 0 => something's running.
-func (f *TxFetcher) rescheduleTimeout(timer *mclock.Timer, trigger chan struct{}) {
- if *timer != nil {
- (*timer).Stop()
- }
- now := f.clock.Now()
-
- earliest := now
- for _, req := range f.requests {
- // If this request already timed out, skip it altogether
- if req.hashes == nil {
- continue
- }
- if earliest > req.time {
- earliest = req.time
- if txFetchTimeout-time.Duration(now-earliest) < gatherSlack {
- break
- }
- }
- }
- *timer = f.clock.AfterFunc(txFetchTimeout-time.Duration(now-earliest), func() {
- trigger <- struct{}{}
- })
-}
-
-// scheduleFetches starts a batch of retrievals for all available idle peers.
-func (f *TxFetcher) scheduleFetches(timer *mclock.Timer, timeout chan struct{}, whitelist map[string]struct{}) {
- // Gather the set of peers we want to retrieve from (default to all)
- actives := whitelist
- if actives == nil {
- actives = make(map[string]struct{})
- for peer := range f.announces {
- actives[peer] = struct{}{}
- }
- }
- if len(actives) == 0 {
- return
- }
- // For each active peer, try to schedule some transaction fetches
- idle := len(f.requests) == 0
-
- f.forEachPeer(actives, func(peer string) {
- if f.requests[peer] != nil {
- return // continue in the for-each
- }
- if len(f.announces[peer]) == 0 {
- return // continue in the for-each
- }
- var (
- hashes = make([]common.Hash, 0, maxTxRetrievals)
- bytes uint64
- )
- f.forEachAnnounce(f.announces[peer], func(hash common.Hash, meta *txMetadata) bool {
- // If the transaction is already fetching, skip to the next one
- if _, ok := f.fetching[hash]; ok {
- return true
- }
- // Mark the hash as fetching and stash away possible alternates
- f.fetching[hash] = peer
-
- if _, ok := f.alternates[hash]; ok {
- panic(fmt.Sprintf("alternate tracker already contains fetching item: %v", f.alternates[hash]))
- }
- f.alternates[hash] = f.announced[hash]
- delete(f.announced, hash)
-
- // Accumulate the hash and stop if the limit was reached
- hashes = append(hashes, hash)
- if len(hashes) >= maxTxRetrievals {
- return false // break in the for-each
- }
- if meta != nil { // Only set eth/68 and upwards
- bytes += uint64(meta.size)
- if bytes >= maxTxRetrievalSize {
- return false
- }
- }
- return true // scheduled, try to add more
- })
- // If any hashes were allocated, request them from the peer
- if len(hashes) > 0 {
- f.requests[peer] = &txRequest{hashes: hashes, time: f.clock.Now()}
- txRequestOutMeter.Mark(int64(len(hashes)))
-
- go func(peer string, hashes []common.Hash) {
- // Try to fetch the transactions, but in case of a request
- // failure (e.g. peer disconnected), reschedule the hashes.
- if err := f.fetchTxs(peer, hashes); err != nil {
- txRequestFailMeter.Mark(int64(len(hashes)))
- f.Drop(peer)
- }
- }(peer, hashes)
- }
- })
- // If a new request was fired, schedule a timeout timer
- if idle && len(f.requests) > 0 {
- f.rescheduleTimeout(timer, timeout)
- }
-}
-
-// forEachPeer does a range loop over a map of peers in production, but during
-// testing it does a deterministic sorted random to allow reproducing issues.
-func (f *TxFetcher) forEachPeer(peers map[string]struct{}, do func(peer string)) {
- // If we're running production, use whatever Go's map gives us
- if f.rand == nil {
- for peer := range peers {
- do(peer)
- }
- return
- }
- // We're running the test suite, make iteration deterministic
- list := make([]string, 0, len(peers))
- for peer := range peers {
- list = append(list, peer)
- }
- sort.Strings(list)
- rotateStrings(list, f.rand.Intn(len(list)))
- for _, peer := range list {
- do(peer)
- }
-}
-
-// forEachAnnounce does a range loop over a map of announcements in production,
-// but during testing it does a deterministic sorted random to allow reproducing
-// issues.
-func (f *TxFetcher) forEachAnnounce(announces map[common.Hash]*txMetadata, do func(hash common.Hash, meta *txMetadata) bool) {
- // If we're running production, use whatever Go's map gives us
- if f.rand == nil {
- for hash, meta := range announces {
- if !do(hash, meta) {
- return
- }
- }
- return
- }
- // We're running the test suite, make iteration deterministic
- list := make([]common.Hash, 0, len(announces))
- for hash := range announces {
- list = append(list, hash)
- }
- sortHashes(list)
- rotateHashes(list, f.rand.Intn(len(list)))
- for _, hash := range list {
- if !do(hash, announces[hash]) {
- return
- }
- }
-}
-
-// rotateStrings rotates the contents of a slice by n steps. This method is only
-// used in tests to simulate random map iteration but keep it deterministic.
-func rotateStrings(slice []string, n int) {
- orig := make([]string, len(slice))
- copy(orig, slice)
-
- for i := 0; i < len(orig); i++ {
- slice[i] = orig[(i+n)%len(orig)]
- }
-}
-
-// sortHashes sorts a slice of hashes. This method is only used in tests in order
-// to simulate random map iteration but keep it deterministic.
-func sortHashes(slice []common.Hash) {
- for i := 0; i < len(slice); i++ {
- for j := i + 1; j < len(slice); j++ {
- if bytes.Compare(slice[i][:], slice[j][:]) > 0 {
- slice[i], slice[j] = slice[j], slice[i]
- }
- }
- }
-}
-
-// rotateHashes rotates the contents of a slice by n steps. This method is only
-// used in tests to simulate random map iteration but keep it deterministic.
-func rotateHashes(slice []common.Hash, n int) {
- orig := make([]common.Hash, len(slice))
- copy(orig, slice)
-
- for i := 0; i < len(orig); i++ {
- slice[i] = orig[(i+n)%len(orig)]
- }
-}
diff --git a/eth/fetcher/tx_fetcher_test.go b/eth/fetcher/tx_fetcher_test.go
deleted file mode 100644
index 4a62e579b6..0000000000
--- a/eth/fetcher/tx_fetcher_test.go
+++ /dev/null
@@ -1,2030 +0,0 @@
-// Copyright 2019 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 .
-
-package fetcher
-
-import (
- "errors"
- "math/big"
- "math/rand"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/params"
-)
-
-var (
- // testTxs is a set of transactions to use during testing that have meaningful hashes.
- testTxs = []*types.Transaction{
- types.NewTransaction(5577006791947779410, common.Address{0x0f}, new(big.Int), 0, new(big.Int), nil),
- types.NewTransaction(15352856648520921629, common.Address{0xbb}, new(big.Int), 0, new(big.Int), nil),
- types.NewTransaction(3916589616287113937, common.Address{0x86}, new(big.Int), 0, new(big.Int), nil),
- types.NewTransaction(9828766684487745566, common.Address{0xac}, new(big.Int), 0, new(big.Int), nil),
- }
- // testTxsHashes is the hashes of the test transactions above
- testTxsHashes = []common.Hash{testTxs[0].Hash(), testTxs[1].Hash(), testTxs[2].Hash(), testTxs[3].Hash()}
-)
-
-type announce struct {
- hash common.Hash
- kind *byte
- size *uint32
-}
-
-func typeptr(t byte) *byte { return &t }
-func sizeptr(n uint32) *uint32 { return &n }
-
-type doTxNotify struct {
- peer string
- hashes []common.Hash
- types []byte
- sizes []uint32
-}
-type doTxEnqueue struct {
- peer string
- txs []*types.Transaction
- direct bool
-}
-type doWait struct {
- time time.Duration
- step bool
-}
-type doDrop string
-type doFunc func()
-
-type isWaitingWithMeta map[string][]announce
-type isWaiting map[string][]common.Hash
-
-type isScheduledWithMeta struct {
- tracking map[string][]announce
- fetching map[string][]common.Hash
- dangling map[string][]common.Hash
-}
-type isScheduled struct {
- tracking map[string][]common.Hash
- fetching map[string][]common.Hash
- dangling map[string][]common.Hash
-}
-type isUnderpriced int
-
-// txFetcherTest represents a test scenario that can be executed by the test
-// runner.
-type txFetcherTest struct {
- init func() *TxFetcher
- steps []interface{}
-}
-
-// Tests that transaction announcements are added to a waitlist, and none
-// of them are scheduled for retrieval until the wait expires.
-func TestTransactionFetcherWaiting(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- nil,
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Initial announcement to get something into the waitlist
- doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}},
- isWaiting(map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- }),
- // Announce from a new peer to check that no overwrite happens
- doTxNotify{peer: "B", hashes: []common.Hash{{0x03}, {0x04}}},
- isWaiting(map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- "B": {{0x03}, {0x04}},
- }),
- // Announce clashing hashes but unique new peer
- doTxNotify{peer: "C", hashes: []common.Hash{{0x01}, {0x04}}},
- isWaiting(map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- "B": {{0x03}, {0x04}},
- "C": {{0x01}, {0x04}},
- }),
- // Announce existing and clashing hashes from existing peer
- doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x03}, {0x05}}},
- isWaiting(map[string][]common.Hash{
- "A": {{0x01}, {0x02}, {0x03}, {0x05}},
- "B": {{0x03}, {0x04}},
- "C": {{0x01}, {0x04}},
- }),
- isScheduled{tracking: nil, fetching: nil},
-
- // Wait for the arrival timeout which should move all expired items
- // from the wait list to the scheduler
- doWait{time: txArriveTimeout, step: true},
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {{0x01}, {0x02}, {0x03}, {0x05}},
- "B": {{0x03}, {0x04}},
- "C": {{0x01}, {0x04}},
- },
- fetching: map[string][]common.Hash{ // Depends on deterministic test randomizer
- "A": {{0x02}, {0x03}, {0x05}},
- "C": {{0x01}, {0x04}},
- },
- },
- // Queue up a non-fetchable transaction and then trigger it with a new
- // peer (weird case to test 1 line in the fetcher)
- doTxNotify{peer: "C", hashes: []common.Hash{{0x06}, {0x07}}},
- isWaiting(map[string][]common.Hash{
- "C": {{0x06}, {0x07}},
- }),
- doWait{time: txArriveTimeout, step: true},
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {{0x01}, {0x02}, {0x03}, {0x05}},
- "B": {{0x03}, {0x04}},
- "C": {{0x01}, {0x04}, {0x06}, {0x07}},
- },
- fetching: map[string][]common.Hash{
- "A": {{0x02}, {0x03}, {0x05}},
- "C": {{0x01}, {0x04}},
- },
- },
- doTxNotify{peer: "D", hashes: []common.Hash{{0x06}, {0x07}}},
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {{0x01}, {0x02}, {0x03}, {0x05}},
- "B": {{0x03}, {0x04}},
- "C": {{0x01}, {0x04}, {0x06}, {0x07}},
- "D": {{0x06}, {0x07}},
- },
- fetching: map[string][]common.Hash{
- "A": {{0x02}, {0x03}, {0x05}},
- "C": {{0x01}, {0x04}},
- "D": {{0x06}, {0x07}},
- },
- },
- },
- })
-}
-
-// Tests that transaction announcements with associated metadata are added to a
-// waitlist, and none of them are scheduled for retrieval until the wait expires.
-//
-// This test is an extended version of TestTransactionFetcherWaiting. It's mostly
-// to cover the metadata checks without bloating up the basic behavioral tests
-// with all the useless extra fields.
-func TestTransactionFetcherWaitingWithMeta(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- nil,
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Initial announcement to get something into the waitlist
- doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{111, 222}},
- isWaitingWithMeta(map[string][]announce{
- "A": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
- {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)},
- },
- }),
- // Announce from a new peer to check that no overwrite happens
- doTxNotify{peer: "B", hashes: []common.Hash{{0x03}, {0x04}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{333, 444}},
- isWaitingWithMeta(map[string][]announce{
- "A": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
- {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)},
- },
- "B": {
- {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
- {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
- },
- }),
- // Announce clashing hashes but unique new peer
- doTxNotify{peer: "C", hashes: []common.Hash{{0x01}, {0x04}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{111, 444}},
- isWaitingWithMeta(map[string][]announce{
- "A": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
- {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)},
- },
- "B": {
- {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
- {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
- },
- "C": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
- {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
- },
- }),
- // Announce existing and clashing hashes from existing peer. Clashes
- // should not overwrite previous announcements.
- doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x03}, {0x05}}, types: []byte{types.LegacyTxType, types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{999, 333, 555}},
- isWaitingWithMeta(map[string][]announce{
- "A": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
- {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)},
- {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
- {common.Hash{0x05}, typeptr(types.LegacyTxType), sizeptr(555)},
- },
- "B": {
- {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
- {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
- },
- "C": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
- {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
- },
- }),
- // Announce clashing hashes with conflicting metadata. Somebody will
- // be in the wrong, but we don't know yet who.
- doTxNotify{peer: "D", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{types.LegacyTxType, types.BlobTxType}, sizes: []uint32{999, 222}},
- isWaitingWithMeta(map[string][]announce{
- "A": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
- {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)},
- {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
- {common.Hash{0x05}, typeptr(types.LegacyTxType), sizeptr(555)},
- },
- "B": {
- {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
- {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
- },
- "C": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
- {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
- },
- "D": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(999)},
- {common.Hash{0x02}, typeptr(types.BlobTxType), sizeptr(222)},
- },
- }),
- isScheduled{tracking: nil, fetching: nil},
-
- // Wait for the arrival timeout which should move all expired items
- // from the wait list to the scheduler
- doWait{time: txArriveTimeout, step: true},
- isWaiting(nil),
- isScheduledWithMeta{
- tracking: map[string][]announce{
- "A": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
- {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)},
- {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
- {common.Hash{0x05}, typeptr(types.LegacyTxType), sizeptr(555)},
- },
- "B": {
- {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
- {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
- },
- "C": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
- {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
- },
- "D": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(999)},
- {common.Hash{0x02}, typeptr(types.BlobTxType), sizeptr(222)},
- },
- },
- fetching: map[string][]common.Hash{ // Depends on deterministic test randomizer
- "A": {{0x03}, {0x05}},
- "C": {{0x01}, {0x04}},
- "D": {{0x02}},
- },
- },
- // Queue up a non-fetchable transaction and then trigger it with a new
- // peer (weird case to test 1 line in the fetcher)
- doTxNotify{peer: "C", hashes: []common.Hash{{0x06}, {0x07}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{666, 777}},
- isWaitingWithMeta(map[string][]announce{
- "C": {
- {common.Hash{0x06}, typeptr(types.LegacyTxType), sizeptr(666)},
- {common.Hash{0x07}, typeptr(types.LegacyTxType), sizeptr(777)},
- },
- }),
- doWait{time: txArriveTimeout, step: true},
- isScheduledWithMeta{
- tracking: map[string][]announce{
- "A": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
- {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)},
- {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
- {common.Hash{0x05}, typeptr(types.LegacyTxType), sizeptr(555)},
- },
- "B": {
- {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
- {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
- },
- "C": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
- {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
- {common.Hash{0x06}, typeptr(types.LegacyTxType), sizeptr(666)},
- {common.Hash{0x07}, typeptr(types.LegacyTxType), sizeptr(777)},
- },
- "D": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(999)},
- {common.Hash{0x02}, typeptr(types.BlobTxType), sizeptr(222)},
- },
- },
- fetching: map[string][]common.Hash{
- "A": {{0x03}, {0x05}},
- "C": {{0x01}, {0x04}},
- "D": {{0x02}},
- },
- },
- doTxNotify{peer: "E", hashes: []common.Hash{{0x06}, {0x07}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{666, 777}},
- isScheduledWithMeta{
- tracking: map[string][]announce{
- "A": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
- {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)},
- {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
- {common.Hash{0x05}, typeptr(types.LegacyTxType), sizeptr(555)},
- },
- "B": {
- {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
- {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
- },
- "C": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
- {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
- {common.Hash{0x06}, typeptr(types.LegacyTxType), sizeptr(666)},
- {common.Hash{0x07}, typeptr(types.LegacyTxType), sizeptr(777)},
- },
- "D": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(999)},
- {common.Hash{0x02}, typeptr(types.BlobTxType), sizeptr(222)},
- },
- "E": {
- {common.Hash{0x06}, typeptr(types.LegacyTxType), sizeptr(666)},
- {common.Hash{0x07}, typeptr(types.LegacyTxType), sizeptr(777)},
- },
- },
- fetching: map[string][]common.Hash{
- "A": {{0x03}, {0x05}},
- "C": {{0x01}, {0x04}},
- "D": {{0x02}},
- "E": {{0x06}, {0x07}},
- },
- },
- },
- })
-}
-
-// Tests that transaction announcements skip the waiting list if they are
-// already scheduled.
-func TestTransactionFetcherSkipWaiting(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- nil,
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Push an initial announcement through to the scheduled stage
- doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}},
- isWaiting(map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- }),
- isScheduled{tracking: nil, fetching: nil},
-
- doWait{time: txArriveTimeout, step: true},
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- },
- fetching: map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- },
- },
- // Announce overlaps from the same peer, ensure the new ones end up
- // in stage one, and clashing ones don't get double tracked
- doTxNotify{peer: "A", hashes: []common.Hash{{0x02}, {0x03}}},
- isWaiting(map[string][]common.Hash{
- "A": {{0x03}},
- }),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- },
- fetching: map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- },
- },
- // Announce overlaps from a new peer, ensure new transactions end up
- // in stage one and clashing ones get tracked for the new peer
- doTxNotify{peer: "B", hashes: []common.Hash{{0x02}, {0x03}, {0x04}}},
- isWaiting(map[string][]common.Hash{
- "A": {{0x03}},
- "B": {{0x03}, {0x04}},
- }),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- "B": {{0x02}},
- },
- fetching: map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- },
- },
- },
- })
-}
-
-// Tests that only a single transaction request gets scheduled to a peer
-// and subsequent announces block or get allotted to someone else.
-func TestTransactionFetcherSingletonRequesting(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- nil,
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Push an initial announcement through to the scheduled stage
- doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}},
- isWaiting(map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- }),
- isScheduled{tracking: nil, fetching: nil},
-
- doWait{time: txArriveTimeout, step: true},
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- },
- fetching: map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- },
- },
- // Announce a new set of transactions from the same peer and ensure
- // they do not start fetching since the peer is already busy
- doTxNotify{peer: "A", hashes: []common.Hash{{0x03}, {0x04}}},
- isWaiting(map[string][]common.Hash{
- "A": {{0x03}, {0x04}},
- }),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- },
- fetching: map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- },
- },
- doWait{time: txArriveTimeout, step: true},
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {{0x01}, {0x02}, {0x03}, {0x04}},
- },
- fetching: map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- },
- },
- // Announce a duplicate set of transactions from a new peer and ensure
- // uniquely new ones start downloading, even if clashing.
- doTxNotify{peer: "B", hashes: []common.Hash{{0x02}, {0x03}, {0x05}, {0x06}}},
- isWaiting(map[string][]common.Hash{
- "B": {{0x05}, {0x06}},
- }),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {{0x01}, {0x02}, {0x03}, {0x04}},
- "B": {{0x02}, {0x03}},
- },
- fetching: map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- "B": {{0x03}},
- },
- },
- },
- })
-}
-
-// Tests that if a transaction retrieval fails, all the transactions get
-// instantly schedule back to someone else or the announcements dropped
-// if no alternate source is available.
-func TestTransactionFetcherFailedRescheduling(t *testing.T) {
- // Create a channel to control when tx requests can fail
- proceed := make(chan struct{})
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- nil,
- func(origin string, hashes []common.Hash) error {
- <-proceed
- return errors.New("peer disconnected")
- },
- nil,
- )
- },
- steps: []interface{}{
- // Push an initial announcement through to the scheduled stage
- doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}},
- isWaiting(map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- }),
- isScheduled{tracking: nil, fetching: nil},
-
- doWait{time: txArriveTimeout, step: true},
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- },
- fetching: map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- },
- },
- // While the original peer is stuck in the request, push in an second
- // data source.
- doTxNotify{peer: "B", hashes: []common.Hash{{0x02}}},
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- "B": {{0x02}},
- },
- fetching: map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- },
- },
- // Wait until the original request fails and check that transactions
- // are either rescheduled or dropped
- doFunc(func() {
- proceed <- struct{}{} // Allow peer A to return the failure
- }),
- doWait{time: 0, step: true},
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "B": {{0x02}},
- },
- fetching: map[string][]common.Hash{
- "B": {{0x02}},
- },
- },
- doFunc(func() {
- proceed <- struct{}{} // Allow peer B to return the failure
- }),
- doWait{time: 0, step: true},
- isWaiting(nil),
- isScheduled{nil, nil, nil},
- },
- })
-}
-
-// Tests that if a transaction retrieval succeeds, all alternate origins
-// are cleaned up.
-func TestTransactionFetcherCleanup(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- func(txs []*types.Transaction) []error {
- return make([]error, len(txs))
- },
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Push an initial announcement through to the scheduled stage
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
- isWaiting(map[string][]common.Hash{
- "A": {testTxsHashes[0]},
- }),
- isScheduled{tracking: nil, fetching: nil},
-
- doWait{time: txArriveTimeout, step: true},
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {testTxsHashes[0]},
- },
- fetching: map[string][]common.Hash{
- "A": {testTxsHashes[0]},
- },
- },
- // Request should be delivered
- doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}, direct: true},
- isScheduled{nil, nil, nil},
- },
- })
-}
-
-// Tests that if a transaction retrieval succeeds, but the response is empty (no
-// transactions available, then all are nuked instead of being rescheduled (yes,
-// this was a bug)).
-func TestTransactionFetcherCleanupEmpty(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- func(txs []*types.Transaction) []error {
- return make([]error, len(txs))
- },
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Push an initial announcement through to the scheduled stage
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
- isWaiting(map[string][]common.Hash{
- "A": {testTxsHashes[0]},
- }),
- isScheduled{tracking: nil, fetching: nil},
-
- doWait{time: txArriveTimeout, step: true},
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {testTxsHashes[0]},
- },
- fetching: map[string][]common.Hash{
- "A": {testTxsHashes[0]},
- },
- },
- // Deliver an empty response and ensure the transaction is cleared, not rescheduled
- doTxEnqueue{peer: "A", txs: []*types.Transaction{}, direct: true},
- isScheduled{nil, nil, nil},
- },
- })
-}
-
-// Tests that non-returned transactions are either re-scheduled from a
-// different peer, or self if they are after the cutoff point.
-func TestTransactionFetcherMissingRescheduling(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- func(txs []*types.Transaction) []error {
- return make([]error, len(txs))
- },
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Push an initial announcement through to the scheduled stage
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0], testTxsHashes[1], testTxsHashes[2]}},
- isWaiting(map[string][]common.Hash{
- "A": {testTxsHashes[0], testTxsHashes[1], testTxsHashes[2]},
- }),
- isScheduled{tracking: nil, fetching: nil},
-
- doWait{time: txArriveTimeout, step: true},
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {testTxsHashes[0], testTxsHashes[1], testTxsHashes[2]},
- },
- fetching: map[string][]common.Hash{
- "A": {testTxsHashes[0], testTxsHashes[1], testTxsHashes[2]},
- },
- },
- // Deliver the middle transaction requested, the one before which
- // should be dropped and the one after re-requested.
- doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}, direct: true}, // This depends on the deterministic random
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {testTxsHashes[2]},
- },
- fetching: map[string][]common.Hash{
- "A": {testTxsHashes[2]},
- },
- },
- },
- })
-}
-
-// Tests that out of two transactions, if one is missing and the last is
-// delivered, the peer gets properly cleaned out from the internal state.
-func TestTransactionFetcherMissingCleanup(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- func(txs []*types.Transaction) []error {
- return make([]error, len(txs))
- },
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Push an initial announcement through to the scheduled stage
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0], testTxsHashes[1]}},
- isWaiting(map[string][]common.Hash{
- "A": {testTxsHashes[0], testTxsHashes[1]},
- }),
- isScheduled{tracking: nil, fetching: nil},
-
- doWait{time: txArriveTimeout, step: true},
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {testTxsHashes[0], testTxsHashes[1]},
- },
- fetching: map[string][]common.Hash{
- "A": {testTxsHashes[0], testTxsHashes[1]},
- },
- },
- // Deliver the middle transaction requested, the one before which
- // should be dropped and the one after re-requested.
- doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[1]}, direct: true}, // This depends on the deterministic random
- isScheduled{nil, nil, nil},
- },
- })
-}
-
-// Tests that transaction broadcasts properly clean up announcements.
-func TestTransactionFetcherBroadcasts(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- func(txs []*types.Transaction) []error {
- return make([]error, len(txs))
- },
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Set up three transactions to be in different stats, waiting, queued and fetching
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
- doWait{time: txArriveTimeout, step: true},
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[1]}},
- doWait{time: txArriveTimeout, step: true},
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[2]}},
-
- isWaiting(map[string][]common.Hash{
- "A": {testTxsHashes[2]},
- }),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {testTxsHashes[0], testTxsHashes[1]},
- },
- fetching: map[string][]common.Hash{
- "A": {testTxsHashes[0]},
- },
- },
- // Broadcast all the transactions and ensure everything gets cleaned
- // up, but the dangling request is left alone to avoid doing multiple
- // concurrent requests.
- doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0], testTxs[1], testTxs[2]}, direct: false},
- isWaiting(nil),
- isScheduled{
- tracking: nil,
- fetching: nil,
- dangling: map[string][]common.Hash{
- "A": {testTxsHashes[0]},
- },
- },
- // Deliver the requested hashes
- doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0], testTxs[1], testTxs[2]}, direct: true},
- isScheduled{nil, nil, nil},
- },
- })
-}
-
-// Tests that the waiting list timers properly reset and reschedule.
-func TestTransactionFetcherWaitTimerResets(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- nil,
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}},
- isWaiting(map[string][]common.Hash{
- "A": {{0x01}},
- }),
- isScheduled{nil, nil, nil},
- doWait{time: txArriveTimeout / 2, step: false},
- isWaiting(map[string][]common.Hash{
- "A": {{0x01}},
- }),
- isScheduled{nil, nil, nil},
-
- doTxNotify{peer: "A", hashes: []common.Hash{{0x02}}},
- isWaiting(map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- }),
- isScheduled{nil, nil, nil},
- doWait{time: txArriveTimeout / 2, step: true},
- isWaiting(map[string][]common.Hash{
- "A": {{0x02}},
- }),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {{0x01}},
- },
- fetching: map[string][]common.Hash{
- "A": {{0x01}},
- },
- },
-
- doWait{time: txArriveTimeout / 2, step: true},
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- },
- fetching: map[string][]common.Hash{
- "A": {{0x01}},
- },
- },
- },
- })
-}
-
-// Tests that if a transaction request is not replied to, it will time
-// out and be re-scheduled for someone else.
-func TestTransactionFetcherTimeoutRescheduling(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- func(txs []*types.Transaction) []error {
- return make([]error, len(txs))
- },
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Push an initial announcement through to the scheduled stage
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
- isWaiting(map[string][]common.Hash{
- "A": {testTxsHashes[0]},
- }),
- isScheduled{tracking: nil, fetching: nil},
-
- doWait{time: txArriveTimeout, step: true},
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {testTxsHashes[0]},
- },
- fetching: map[string][]common.Hash{
- "A": {testTxsHashes[0]},
- },
- },
- // Wait until the delivery times out, everything should be cleaned up
- doWait{time: txFetchTimeout, step: true},
- isWaiting(nil),
- isScheduled{
- tracking: nil,
- fetching: nil,
- dangling: map[string][]common.Hash{
- "A": {},
- },
- },
- // Ensure that followup announcements don't get scheduled
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[1]}},
- doWait{time: txArriveTimeout, step: true},
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {testTxsHashes[1]},
- },
- fetching: nil,
- dangling: map[string][]common.Hash{
- "A": {},
- },
- },
- // If the dangling request arrives a bit later, do not choke
- doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}, direct: true},
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {testTxsHashes[1]},
- },
- fetching: map[string][]common.Hash{
- "A": {testTxsHashes[1]},
- },
- },
- },
- })
-}
-
-// Tests that the fetching timeout timers properly reset and reschedule.
-func TestTransactionFetcherTimeoutTimerResets(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- nil,
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}},
- doWait{time: txArriveTimeout, step: true},
- doTxNotify{peer: "B", hashes: []common.Hash{{0x02}}},
- doWait{time: txArriveTimeout, step: true},
-
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {{0x01}},
- "B": {{0x02}},
- },
- fetching: map[string][]common.Hash{
- "A": {{0x01}},
- "B": {{0x02}},
- },
- },
- doWait{time: txFetchTimeout - txArriveTimeout, step: true},
- isScheduled{
- tracking: map[string][]common.Hash{
- "B": {{0x02}},
- },
- fetching: map[string][]common.Hash{
- "B": {{0x02}},
- },
- dangling: map[string][]common.Hash{
- "A": {},
- },
- },
- doWait{time: txArriveTimeout, step: true},
- isScheduled{
- tracking: nil,
- fetching: nil,
- dangling: map[string][]common.Hash{
- "A": {},
- "B": {},
- },
- },
- },
- })
-}
-
-// Tests that if thousands of transactions are announced, only a small
-// number of them will be requested at a time.
-func TestTransactionFetcherRateLimiting(t *testing.T) {
- // Create a slew of transactions and announce them
- var hashes []common.Hash
- for i := 0; i < maxTxAnnounces; i++ {
- hashes = append(hashes, common.Hash{byte(i / 256), byte(i % 256)})
- }
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- nil,
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Announce all the transactions, wait a bit and ensure only a small
- // percentage gets requested
- doTxNotify{peer: "A", hashes: hashes},
- doWait{time: txArriveTimeout, step: true},
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": hashes,
- },
- fetching: map[string][]common.Hash{
- "A": hashes[1643 : 1643+maxTxRetrievals],
- },
- },
- },
- })
-}
-
-// Tests that if huge transactions are announced, only a small number of them will
-// be requested at a time, to keep the responses below a reasonable level.
-func TestTransactionFetcherBandwidthLimiting(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- nil,
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Announce mid size transactions from A to verify that multiple
- // ones can be piled into a single request.
- doTxNotify{peer: "A",
- hashes: []common.Hash{{0x01}, {0x02}, {0x03}, {0x04}},
- types: []byte{types.LegacyTxType, types.LegacyTxType, types.LegacyTxType, types.LegacyTxType},
- sizes: []uint32{48 * 1024, 48 * 1024, 48 * 1024, 48 * 1024},
- },
- // Announce exactly on the limit transactions to see that only one
- // gets requested
- doTxNotify{peer: "B",
- hashes: []common.Hash{{0x05}, {0x06}},
- types: []byte{types.LegacyTxType, types.LegacyTxType},
- sizes: []uint32{maxTxRetrievalSize, maxTxRetrievalSize},
- },
- // Announce oversized blob transactions to see that overflows are ok
- doTxNotify{peer: "C",
- hashes: []common.Hash{{0x07}, {0x08}},
- types: []byte{types.BlobTxType, types.BlobTxType},
- sizes: []uint32{params.MaxBlobGasPerBlock, params.MaxBlobGasPerBlock},
- },
- doWait{time: txArriveTimeout, step: true},
- isWaiting(nil),
- isScheduledWithMeta{
- tracking: map[string][]announce{
- "A": {
- {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(48 * 1024)},
- {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(48 * 1024)},
- {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(48 * 1024)},
- {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(48 * 1024)},
- },
- "B": {
- {common.Hash{0x05}, typeptr(types.LegacyTxType), sizeptr(maxTxRetrievalSize)},
- {common.Hash{0x06}, typeptr(types.LegacyTxType), sizeptr(maxTxRetrievalSize)},
- },
- "C": {
- {common.Hash{0x07}, typeptr(types.BlobTxType), sizeptr(params.MaxBlobGasPerBlock)},
- {common.Hash{0x08}, typeptr(types.BlobTxType), sizeptr(params.MaxBlobGasPerBlock)},
- },
- },
- fetching: map[string][]common.Hash{
- "A": {{0x02}, {0x03}, {0x04}},
- "B": {{0x06}},
- "C": {{0x08}},
- },
- },
- },
- })
-}
-
-// Tests that then number of transactions a peer is allowed to announce and/or
-// request at the same time is hard capped.
-func TestTransactionFetcherDoSProtection(t *testing.T) {
- // Create a slew of transactions and to announce them
- var hashesA []common.Hash
- for i := 0; i < maxTxAnnounces+1; i++ {
- hashesA = append(hashesA, common.Hash{0x01, byte(i / 256), byte(i % 256)})
- }
- var hashesB []common.Hash
- for i := 0; i < maxTxAnnounces+1; i++ {
- hashesB = append(hashesB, common.Hash{0x02, byte(i / 256), byte(i % 256)})
- }
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- nil,
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Announce half of the transaction and wait for them to be scheduled
- doTxNotify{peer: "A", hashes: hashesA[:maxTxAnnounces/2]},
- doTxNotify{peer: "B", hashes: hashesB[:maxTxAnnounces/2-1]},
- doWait{time: txArriveTimeout, step: true},
-
- // Announce the second half and keep them in the wait list
- doTxNotify{peer: "A", hashes: hashesA[maxTxAnnounces/2 : maxTxAnnounces]},
- doTxNotify{peer: "B", hashes: hashesB[maxTxAnnounces/2-1 : maxTxAnnounces-1]},
-
- // Ensure the hashes are split half and half
- isWaiting(map[string][]common.Hash{
- "A": hashesA[maxTxAnnounces/2 : maxTxAnnounces],
- "B": hashesB[maxTxAnnounces/2-1 : maxTxAnnounces-1],
- }),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": hashesA[:maxTxAnnounces/2],
- "B": hashesB[:maxTxAnnounces/2-1],
- },
- fetching: map[string][]common.Hash{
- "A": hashesA[1643 : 1643+maxTxRetrievals],
- "B": append(append([]common.Hash{}, hashesB[maxTxAnnounces/2-3:maxTxAnnounces/2-1]...), hashesB[:maxTxRetrievals-2]...),
- },
- },
- // Ensure that adding even one more hash results in dropping the hash
- doTxNotify{peer: "A", hashes: []common.Hash{hashesA[maxTxAnnounces]}},
- doTxNotify{peer: "B", hashes: hashesB[maxTxAnnounces-1 : maxTxAnnounces+1]},
-
- isWaiting(map[string][]common.Hash{
- "A": hashesA[maxTxAnnounces/2 : maxTxAnnounces],
- "B": hashesB[maxTxAnnounces/2-1 : maxTxAnnounces],
- }),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": hashesA[:maxTxAnnounces/2],
- "B": hashesB[:maxTxAnnounces/2-1],
- },
- fetching: map[string][]common.Hash{
- "A": hashesA[1643 : 1643+maxTxRetrievals],
- "B": append(append([]common.Hash{}, hashesB[maxTxAnnounces/2-3:maxTxAnnounces/2-1]...), hashesB[:maxTxRetrievals-2]...),
- },
- },
- },
- })
-}
-
-// Tests that underpriced transactions don't get rescheduled after being rejected.
-func TestTransactionFetcherUnderpricedDedup(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- func(txs []*types.Transaction) []error {
- errs := make([]error, len(txs))
- for i := 0; i < len(errs); i++ {
- if i%2 == 0 {
- errs[i] = txpool.ErrUnderpriced
- } else {
- errs[i] = txpool.ErrReplaceUnderpriced
- }
- }
- return errs
- },
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Deliver a transaction through the fetcher, but reject as underpriced
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0], testTxsHashes[1]}},
- doWait{time: txArriveTimeout, step: true},
- doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0], testTxs[1]}, direct: true},
- isScheduled{nil, nil, nil},
-
- // Try to announce the transaction again, ensure it's not scheduled back
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0], testTxsHashes[1], testTxsHashes[2]}}, // [2] is needed to force a step in the fetcher
- isWaiting(map[string][]common.Hash{
- "A": {testTxsHashes[2]},
- }),
- isScheduled{nil, nil, nil},
- },
- })
-}
-
-// Tests that underpriced transactions don't get rescheduled after being rejected,
-// but at the same time there's a hard cap on the number of transactions that are
-// tracked.
-func TestTransactionFetcherUnderpricedDoSProtection(t *testing.T) {
- // Temporarily disable fetch timeouts as they massively mess up the simulated clock
- defer func(timeout time.Duration) { txFetchTimeout = timeout }(txFetchTimeout)
- txFetchTimeout = 24 * time.Hour
-
- // Create a slew of transactions to max out the underpriced set
- var txs []*types.Transaction
- for i := 0; i < maxTxUnderpricedSetSize+1; i++ {
- txs = append(txs, types.NewTransaction(rand.Uint64(), common.Address{byte(rand.Intn(256))}, new(big.Int), 0, new(big.Int), nil))
- }
- hashes := make([]common.Hash, len(txs))
- for i, tx := range txs {
- hashes[i] = tx.Hash()
- }
- // Generate a set of steps to announce and deliver the entire set of transactions
- var steps []interface{}
- for i := 0; i < maxTxUnderpricedSetSize/maxTxRetrievals; i++ {
- steps = append(steps, doTxNotify{peer: "A", hashes: hashes[i*maxTxRetrievals : (i+1)*maxTxRetrievals]})
- steps = append(steps, isWaiting(map[string][]common.Hash{
- "A": hashes[i*maxTxRetrievals : (i+1)*maxTxRetrievals],
- }))
- steps = append(steps, doWait{time: txArriveTimeout, step: true})
- steps = append(steps, isScheduled{
- tracking: map[string][]common.Hash{
- "A": hashes[i*maxTxRetrievals : (i+1)*maxTxRetrievals],
- },
- fetching: map[string][]common.Hash{
- "A": hashes[i*maxTxRetrievals : (i+1)*maxTxRetrievals],
- },
- })
- steps = append(steps, doTxEnqueue{peer: "A", txs: txs[i*maxTxRetrievals : (i+1)*maxTxRetrievals], direct: true})
- steps = append(steps, isWaiting(nil))
- steps = append(steps, isScheduled{nil, nil, nil})
- steps = append(steps, isUnderpriced((i+1)*maxTxRetrievals))
- }
- testTransactionFetcher(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- func(txs []*types.Transaction) []error {
- errs := make([]error, len(txs))
- for i := 0; i < len(errs); i++ {
- errs[i] = txpool.ErrUnderpriced
- }
- return errs
- },
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: append(steps, []interface{}{
- // The preparation of the test has already been done in `steps`, add the last check
- doTxNotify{peer: "A", hashes: []common.Hash{hashes[maxTxUnderpricedSetSize]}},
- doWait{time: txArriveTimeout, step: true},
- doTxEnqueue{peer: "A", txs: []*types.Transaction{txs[maxTxUnderpricedSetSize]}, direct: true},
- isUnderpriced(maxTxUnderpricedSetSize),
- }...),
- })
-}
-
-// Tests that unexpected deliveries don't corrupt the internal state.
-func TestTransactionFetcherOutOfBoundDeliveries(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- func(txs []*types.Transaction) []error {
- return make([]error, len(txs))
- },
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Deliver something out of the blue
- isWaiting(nil),
- isScheduled{nil, nil, nil},
- doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}, direct: false},
- isWaiting(nil),
- isScheduled{nil, nil, nil},
-
- // Set up a few hashes into various stages
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
- doWait{time: txArriveTimeout, step: true},
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[1]}},
- doWait{time: txArriveTimeout, step: true},
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[2]}},
-
- isWaiting(map[string][]common.Hash{
- "A": {testTxsHashes[2]},
- }),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {testTxsHashes[0], testTxsHashes[1]},
- },
- fetching: map[string][]common.Hash{
- "A": {testTxsHashes[0]},
- },
- },
- // Deliver everything and more out of the blue
- doTxEnqueue{peer: "B", txs: []*types.Transaction{testTxs[0], testTxs[1], testTxs[2], testTxs[3]}, direct: true},
- isWaiting(nil),
- isScheduled{
- tracking: nil,
- fetching: nil,
- dangling: map[string][]common.Hash{
- "A": {testTxsHashes[0]},
- },
- },
- },
- })
-}
-
-// Tests that dropping a peer cleans out all internal data structures in all the
-// live or dangling stages.
-func TestTransactionFetcherDrop(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- func(txs []*types.Transaction) []error {
- return make([]error, len(txs))
- },
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Set up a few hashes into various stages
- doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}},
- doWait{time: txArriveTimeout, step: true},
- doTxNotify{peer: "A", hashes: []common.Hash{{0x02}}},
- doWait{time: txArriveTimeout, step: true},
- doTxNotify{peer: "A", hashes: []common.Hash{{0x03}}},
-
- isWaiting(map[string][]common.Hash{
- "A": {{0x03}},
- }),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {{0x01}, {0x02}},
- },
- fetching: map[string][]common.Hash{
- "A": {{0x01}},
- },
- },
- // Drop the peer and ensure everything's cleaned out
- doDrop("A"),
- isWaiting(nil),
- isScheduled{nil, nil, nil},
-
- // Push the node into a dangling (timeout) state
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
- doWait{time: txArriveTimeout, step: true},
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {testTxsHashes[0]},
- },
- fetching: map[string][]common.Hash{
- "A": {testTxsHashes[0]},
- },
- },
- doWait{time: txFetchTimeout, step: true},
- isWaiting(nil),
- isScheduled{
- tracking: nil,
- fetching: nil,
- dangling: map[string][]common.Hash{
- "A": {},
- },
- },
- // Drop the peer and ensure everything's cleaned out
- doDrop("A"),
- isWaiting(nil),
- isScheduled{nil, nil, nil},
- },
- })
-}
-
-// Tests that dropping a peer instantly reschedules failed announcements to any
-// available peer.
-func TestTransactionFetcherDropRescheduling(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- func(txs []*types.Transaction) []error {
- return make([]error, len(txs))
- },
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Set up a few hashes into various stages
- doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}},
- doWait{time: txArriveTimeout, step: true},
- doTxNotify{peer: "B", hashes: []common.Hash{{0x01}}},
-
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "A": {{0x01}},
- "B": {{0x01}},
- },
- fetching: map[string][]common.Hash{
- "A": {{0x01}},
- },
- },
- // Drop the peer and ensure everything's cleaned out
- doDrop("A"),
- isWaiting(nil),
- isScheduled{
- tracking: map[string][]common.Hash{
- "B": {{0x01}},
- },
- fetching: map[string][]common.Hash{
- "B": {{0x01}},
- },
- },
- },
- })
-}
-
-// Tests that announced transactions with the wrong transaction type or size will
-// result in a dropped peer.
-func TestInvalidAnnounceMetadata(t *testing.T) {
- drop := make(chan string, 2)
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- func(txs []*types.Transaction) []error {
- return make([]error, len(txs))
- },
- func(string, []common.Hash) error { return nil },
- func(peer string) { drop <- peer },
- )
- },
- steps: []interface{}{
- // Initial announcement to get something into the waitlist
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0], testTxsHashes[1]}, types: []byte{testTxs[0].Type(), testTxs[1].Type()}, sizes: []uint32{uint32(testTxs[0].Size()), uint32(testTxs[1].Size())}},
- isWaitingWithMeta(map[string][]announce{
- "A": {
- {testTxsHashes[0], typeptr(testTxs[0].Type()), sizeptr(uint32(testTxs[0].Size()))},
- {testTxsHashes[1], typeptr(testTxs[1].Type()), sizeptr(uint32(testTxs[1].Size()))},
- },
- }),
- // Announce from new peers conflicting transactions
- doTxNotify{peer: "B", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{1024 + uint32(testTxs[0].Size())}},
- doTxNotify{peer: "C", hashes: []common.Hash{testTxsHashes[1]}, types: []byte{1 + testTxs[1].Type()}, sizes: []uint32{uint32(testTxs[1].Size())}},
- isWaitingWithMeta(map[string][]announce{
- "A": {
- {testTxsHashes[0], typeptr(testTxs[0].Type()), sizeptr(uint32(testTxs[0].Size()))},
- {testTxsHashes[1], typeptr(testTxs[1].Type()), sizeptr(uint32(testTxs[1].Size()))},
- },
- "B": {
- {testTxsHashes[0], typeptr(testTxs[0].Type()), sizeptr(1024 + uint32(testTxs[0].Size()))},
- },
- "C": {
- {testTxsHashes[1], typeptr(1 + testTxs[1].Type()), sizeptr(uint32(testTxs[1].Size()))},
- },
- }),
- // Schedule all the transactions for retrieval
- doWait{time: txArriveTimeout, step: true},
- isWaitingWithMeta(nil),
- isScheduledWithMeta{
- tracking: map[string][]announce{
- "A": {
- {testTxsHashes[0], typeptr(testTxs[0].Type()), sizeptr(uint32(testTxs[0].Size()))},
- {testTxsHashes[1], typeptr(testTxs[1].Type()), sizeptr(uint32(testTxs[1].Size()))},
- },
- "B": {
- {testTxsHashes[0], typeptr(testTxs[0].Type()), sizeptr(1024 + uint32(testTxs[0].Size()))},
- },
- "C": {
- {testTxsHashes[1], typeptr(1 + testTxs[1].Type()), sizeptr(uint32(testTxs[1].Size()))},
- },
- },
- fetching: map[string][]common.Hash{
- "A": {testTxsHashes[0]},
- "C": {testTxsHashes[1]},
- },
- },
- // Deliver the transactions and wait for B to be dropped
- doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0], testTxs[1]}},
- doFunc(func() { <-drop }),
- doFunc(func() { <-drop }),
- },
- })
-}
-
-// This test reproduces a crash caught by the fuzzer. The root cause was a
-// dangling transaction timing out and clashing on re-add with a concurrently
-// announced one.
-func TestTransactionFetcherFuzzCrash01(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- func(txs []*types.Transaction) []error {
- return make([]error, len(txs))
- },
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Get a transaction into fetching mode and make it dangling with a broadcast
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
- doWait{time: txArriveTimeout, step: true},
- doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}},
-
- // Notify the dangling transaction once more and crash via a timeout
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
- doWait{time: txFetchTimeout, step: true},
- },
- })
-}
-
-// This test reproduces a crash caught by the fuzzer. The root cause was a
-// dangling transaction getting peer-dropped and clashing on re-add with a
-// concurrently announced one.
-func TestTransactionFetcherFuzzCrash02(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- func(txs []*types.Transaction) []error {
- return make([]error, len(txs))
- },
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Get a transaction into fetching mode and make it dangling with a broadcast
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
- doWait{time: txArriveTimeout, step: true},
- doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}},
-
- // Notify the dangling transaction once more, re-fetch, and crash via a drop and timeout
- doTxNotify{peer: "B", hashes: []common.Hash{testTxsHashes[0]}},
- doWait{time: txArriveTimeout, step: true},
- doDrop("A"),
- doWait{time: txFetchTimeout, step: true},
- },
- })
-}
-
-// This test reproduces a crash caught by the fuzzer. The root cause was a
-// dangling transaction getting rescheduled via a partial delivery, clashing
-// with a concurrent notify.
-func TestTransactionFetcherFuzzCrash03(t *testing.T) {
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- func(txs []*types.Transaction) []error {
- return make([]error, len(txs))
- },
- func(string, []common.Hash) error { return nil },
- nil,
- )
- },
- steps: []interface{}{
- // Get a transaction into fetching mode and make it dangling with a broadcast
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0], testTxsHashes[1]}},
- doWait{time: txFetchTimeout, step: true},
- doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0], testTxs[1]}},
-
- // Notify the dangling transaction once more, partially deliver, clash&crash with a timeout
- doTxNotify{peer: "B", hashes: []common.Hash{testTxsHashes[0]}},
- doWait{time: txArriveTimeout, step: true},
-
- doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[1]}, direct: true},
- doWait{time: txFetchTimeout, step: true},
- },
- })
-}
-
-// This test reproduces a crash caught by the fuzzer. The root cause was a
-// dangling transaction getting rescheduled via a disconnect, clashing with
-// a concurrent notify.
-func TestTransactionFetcherFuzzCrash04(t *testing.T) {
- // Create a channel to control when tx requests can fail
- proceed := make(chan struct{})
-
- testTransactionFetcherParallel(t, txFetcherTest{
- init: func() *TxFetcher {
- return NewTxFetcher(
- func(common.Hash) bool { return false },
- func(txs []*types.Transaction) []error {
- return make([]error, len(txs))
- },
- func(string, []common.Hash) error {
- <-proceed
- return errors.New("peer disconnected")
- },
- nil,
- )
- },
- steps: []interface{}{
- // Get a transaction into fetching mode and make it dangling with a broadcast
- doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}},
- doWait{time: txArriveTimeout, step: true},
- doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}},
-
- // Notify the dangling transaction once more, re-fetch, and crash via an in-flight disconnect
- doTxNotify{peer: "B", hashes: []common.Hash{testTxsHashes[0]}},
- doWait{time: txArriveTimeout, step: true},
- doFunc(func() {
- proceed <- struct{}{} // Allow peer A to return the failure
- }),
- doWait{time: 0, step: true},
- doWait{time: txFetchTimeout, step: true},
- },
- })
-}
-
-func testTransactionFetcherParallel(t *testing.T, tt txFetcherTest) {
- t.Parallel()
- testTransactionFetcher(t, tt)
-}
-
-func testTransactionFetcher(t *testing.T, tt txFetcherTest) {
- // Create a fetcher and hook into it's simulated fields
- clock := new(mclock.Simulated)
- wait := make(chan struct{})
-
- fetcher := tt.init()
- fetcher.clock = clock
- fetcher.step = wait
- fetcher.rand = rand.New(rand.NewSource(0x3a29))
-
- fetcher.Start()
- defer fetcher.Stop()
-
- defer func() { // drain the wait chan on exit
- for {
- select {
- case <-wait:
- default:
- return
- }
- }
- }()
-
- // Crunch through all the test steps and execute them
- for i, step := range tt.steps {
- // Auto-expand certain steps to ones with metadata
- switch old := step.(type) {
- case isWaiting:
- new := make(isWaitingWithMeta)
- for peer, hashes := range old {
- for _, hash := range hashes {
- new[peer] = append(new[peer], announce{hash, nil, nil})
- }
- }
- step = new
-
- case isScheduled:
- new := isScheduledWithMeta{
- tracking: make(map[string][]announce),
- fetching: old.fetching,
- dangling: old.dangling,
- }
- for peer, hashes := range old.tracking {
- for _, hash := range hashes {
- new.tracking[peer] = append(new.tracking[peer], announce{hash, nil, nil})
- }
- }
- step = new
- }
- // Process the original or expanded steps
- switch step := step.(type) {
- case doTxNotify:
- if err := fetcher.Notify(step.peer, step.types, step.sizes, step.hashes); err != nil {
- t.Errorf("step %d: %v", i, err)
- }
- <-wait // Fetcher needs to process this, wait until it's done
- select {
- case <-wait:
- panic("wtf")
- case <-time.After(time.Millisecond):
- }
-
- case doTxEnqueue:
- if err := fetcher.Enqueue(step.peer, step.txs, step.direct); err != nil {
- t.Errorf("step %d: %v", i, err)
- }
- <-wait // Fetcher needs to process this, wait until it's done
-
- case doWait:
- clock.Run(step.time)
- if step.step {
- <-wait // Fetcher supposed to do something, wait until it's done
- }
-
- case doDrop:
- if err := fetcher.Drop(string(step)); err != nil {
- t.Errorf("step %d: %v", i, err)
- }
- <-wait // Fetcher needs to process this, wait until it's done
-
- case doFunc:
- step()
-
- case isWaitingWithMeta:
- // We need to check that the waiting list (stage 1) internals
- // match with the expected set. Check the peer->hash mappings
- // first.
- for peer, announces := range step {
- waiting := fetcher.waitslots[peer]
- if waiting == nil {
- t.Errorf("step %d: peer %s missing from waitslots", i, peer)
- continue
- }
- for _, ann := range announces {
- if meta, ok := waiting[ann.hash]; !ok {
- t.Errorf("step %d, peer %s: hash %x missing from waitslots", i, peer, ann.hash)
- } else {
- if (meta == nil && (ann.kind != nil || ann.size != nil)) ||
- (meta != nil && (ann.kind == nil || ann.size == nil)) ||
- (meta != nil && (meta.kind != *ann.kind || meta.size != *ann.size)) {
- t.Errorf("step %d, peer %s, hash %x: waitslot metadata mismatch: want %v, have %v/%v", i, peer, ann.hash, meta, *ann.kind, *ann.size)
- }
- }
- }
- for hash, meta := range waiting {
- ann := announce{hash: hash}
- if meta != nil {
- ann.kind, ann.size = &meta.kind, &meta.size
- }
- if !containsAnnounce(announces, ann) {
- t.Errorf("step %d, peer %s: announce %v extra in waitslots", i, peer, ann)
- }
- }
- }
- for peer := range fetcher.waitslots {
- if _, ok := step[peer]; !ok {
- t.Errorf("step %d: peer %s extra in waitslots", i, peer)
- }
- }
- // Peer->hash sets correct, check the hash->peer and timeout sets
- for peer, announces := range step {
- for _, ann := range announces {
- if _, ok := fetcher.waitlist[ann.hash][peer]; !ok {
- t.Errorf("step %d, hash %x: peer %s missing from waitlist", i, ann.hash, peer)
- }
- if _, ok := fetcher.waittime[ann.hash]; !ok {
- t.Errorf("step %d: hash %x missing from waittime", i, ann.hash)
- }
- }
- }
- for hash, peers := range fetcher.waitlist {
- if len(peers) == 0 {
- t.Errorf("step %d, hash %x: empty peerset in waitlist", i, hash)
- }
- for peer := range peers {
- if !containsHashInAnnounces(step[peer], hash) {
- t.Errorf("step %d, hash %x: peer %s extra in waitlist", i, hash, peer)
- }
- }
- }
- for hash := range fetcher.waittime {
- var found bool
- for _, announces := range step {
- if containsHashInAnnounces(announces, hash) {
- found = true
- break
- }
- }
- if !found {
- t.Errorf("step %d,: hash %x extra in waittime", i, hash)
- }
- }
-
- case isScheduledWithMeta:
- // Check that all scheduled announces are accounted for and no
- // extra ones are present.
- for peer, announces := range step.tracking {
- scheduled := fetcher.announces[peer]
- if scheduled == nil {
- t.Errorf("step %d: peer %s missing from announces", i, peer)
- continue
- }
- for _, ann := range announces {
- if meta, ok := scheduled[ann.hash]; !ok {
- t.Errorf("step %d, peer %s: hash %x missing from announces", i, peer, ann.hash)
- } else {
- if (meta == nil && (ann.kind != nil || ann.size != nil)) ||
- (meta != nil && (ann.kind == nil || ann.size == nil)) ||
- (meta != nil && (meta.kind != *ann.kind || meta.size != *ann.size)) {
- t.Errorf("step %d, peer %s, hash %x: announce metadata mismatch: want %v, have %v/%v", i, peer, ann.hash, meta, *ann.kind, *ann.size)
- }
- }
- }
- for hash, meta := range scheduled {
- ann := announce{hash: hash}
- if meta != nil {
- ann.kind, ann.size = &meta.kind, &meta.size
- }
- if !containsAnnounce(announces, ann) {
- t.Errorf("step %d, peer %s: announce %x extra in announces", i, peer, hash)
- }
- }
- }
- for peer := range fetcher.announces {
- if _, ok := step.tracking[peer]; !ok {
- t.Errorf("step %d: peer %s extra in announces", i, peer)
- }
- }
- // Check that all announces required to be fetching are in the
- // appropriate sets
- for peer, hashes := range step.fetching {
- request := fetcher.requests[peer]
- if request == nil {
- t.Errorf("step %d: peer %s missing from requests", i, peer)
- continue
- }
- for _, hash := range hashes {
- if !containsHash(request.hashes, hash) {
- t.Errorf("step %d, peer %s: hash %x missing from requests", i, peer, hash)
- }
- }
- for _, hash := range request.hashes {
- if !containsHash(hashes, hash) {
- t.Errorf("step %d, peer %s: hash %x extra in requests", i, peer, hash)
- }
- }
- }
- for peer := range fetcher.requests {
- if _, ok := step.fetching[peer]; !ok {
- if _, ok := step.dangling[peer]; !ok {
- t.Errorf("step %d: peer %s extra in requests", i, peer)
- }
- }
- }
- for peer, hashes := range step.fetching {
- for _, hash := range hashes {
- if _, ok := fetcher.fetching[hash]; !ok {
- t.Errorf("step %d, peer %s: hash %x missing from fetching", i, peer, hash)
- }
- }
- }
- for hash := range fetcher.fetching {
- var found bool
- for _, req := range fetcher.requests {
- if containsHash(req.hashes, hash) {
- found = true
- break
- }
- }
- if !found {
- t.Errorf("step %d: hash %x extra in fetching", i, hash)
- }
- }
- for _, hashes := range step.fetching {
- for _, hash := range hashes {
- alternates := fetcher.alternates[hash]
- if alternates == nil {
- t.Errorf("step %d: hash %x missing from alternates", i, hash)
- continue
- }
- for peer := range alternates {
- if _, ok := fetcher.announces[peer]; !ok {
- t.Errorf("step %d: peer %s extra in alternates", i, peer)
- continue
- }
- if _, ok := fetcher.announces[peer][hash]; !ok {
- t.Errorf("step %d, peer %s: hash %x extra in alternates", i, hash, peer)
- continue
- }
- }
- for p := range fetcher.announced[hash] {
- if _, ok := alternates[p]; !ok {
- t.Errorf("step %d, hash %x: peer %s missing from alternates", i, hash, p)
- continue
- }
- }
- }
- }
- for peer, hashes := range step.dangling {
- request := fetcher.requests[peer]
- if request == nil {
- t.Errorf("step %d: peer %s missing from requests", i, peer)
- continue
- }
- for _, hash := range hashes {
- if !containsHash(request.hashes, hash) {
- t.Errorf("step %d, peer %s: hash %x missing from requests", i, peer, hash)
- }
- }
- for _, hash := range request.hashes {
- if !containsHash(hashes, hash) {
- t.Errorf("step %d, peer %s: hash %x extra in requests", i, peer, hash)
- }
- }
- }
- // Check that all transaction announces that are scheduled for
- // retrieval but not actively being downloaded are tracked only
- // in the stage 2 `announced` map.
- var queued []common.Hash
- for _, announces := range step.tracking {
- for _, ann := range announces {
- var found bool
- for _, hs := range step.fetching {
- if containsHash(hs, ann.hash) {
- found = true
- break
- }
- }
- if !found {
- queued = append(queued, ann.hash)
- }
- }
- }
- for _, hash := range queued {
- if _, ok := fetcher.announced[hash]; !ok {
- t.Errorf("step %d: hash %x missing from announced", i, hash)
- }
- }
- for hash := range fetcher.announced {
- if !containsHash(queued, hash) {
- t.Errorf("step %d: hash %x extra in announced", i, hash)
- }
- }
-
- case isUnderpriced:
- if fetcher.underpriced.Len() != int(step) {
- t.Errorf("step %d: underpriced set size mismatch: have %d, want %d", i, fetcher.underpriced.Len(), step)
- }
-
- default:
- t.Fatalf("step %d: unknown step type %T", i, step)
- }
- // After every step, cross validate the internal uniqueness invariants
- // between stage one and stage two.
- for hash := range fetcher.waittime {
- if _, ok := fetcher.announced[hash]; ok {
- t.Errorf("step %d: hash %s present in both stage 1 and 2", i, hash)
- }
- }
- }
-}
-
-// containsAnnounce returns whether an announcement is contained within a slice
-// of announcements.
-func containsAnnounce(slice []announce, ann announce) bool {
- for _, have := range slice {
- if have.hash == ann.hash {
- if have.kind == nil || ann.kind == nil {
- if have.kind != ann.kind {
- return false
- }
- } else if *have.kind != *ann.kind {
- return false
- }
- if have.size == nil || ann.size == nil {
- if have.size != ann.size {
- return false
- }
- } else if *have.size != *ann.size {
- return false
- }
- return true
- }
- }
- return false
-}
-
-// containsHashInAnnounces returns whether a hash is contained within a slice
-// of announcements.
-func containsHashInAnnounces(slice []announce, hash common.Hash) bool {
- for _, have := range slice {
- if have.hash == hash {
- return true
- }
- }
- return false
-}
-
-// containsHash returns whether a hash is contained within a hash slice.
-func containsHash(slice []common.Hash, hash common.Hash) bool {
- for _, have := range slice {
- if have == hash {
- return true
- }
- }
- return false
-}
-
-// Tests that a transaction is forgotten after the timeout.
-func TestTransactionForgotten(t *testing.T) {
- fetcher := NewTxFetcher(
- func(common.Hash) bool { return false },
- func(txs []*types.Transaction) []error {
- errs := make([]error, len(txs))
- for i := 0; i < len(errs); i++ {
- errs[i] = txpool.ErrUnderpriced
- }
- return errs
- },
- func(string, []common.Hash) error { return nil },
- func(string) {},
- )
- fetcher.Start()
- defer fetcher.Stop()
- // Create one TX which is 5 minutes old, and one which is recent
- tx1 := types.NewTx(&types.LegacyTx{Nonce: 0})
- tx1.SetTime(time.Now().Add(-maxTxUnderpricedTimeout - 1*time.Second))
- tx2 := types.NewTx(&types.LegacyTx{Nonce: 1})
-
- // Enqueue both in the fetcher. They will be immediately tagged as underpriced
- if err := fetcher.Enqueue("asdf", []*types.Transaction{tx1, tx2}, false); err != nil {
- t.Fatal(err)
- }
- // isKnownUnderpriced should trigger removal of the first tx (no longer be known underpriced)
- if fetcher.isKnownUnderpriced(tx1.Hash()) {
- t.Fatal("transaction should be forgotten by now")
- }
- // isKnownUnderpriced should not trigger removal of the second
- if !fetcher.isKnownUnderpriced(tx2.Hash()) {
- t.Fatal("transaction should be known underpriced")
- }
-}
diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go
deleted file mode 100644
index a36c670747..0000000000
--- a/eth/gasestimator/gasestimator.go
+++ /dev/null
@@ -1,235 +0,0 @@
-// Copyright 2023 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 .
-
-package gasestimator
-
-import (
- "context"
- "errors"
- "fmt"
- "math"
- "math/big"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/params"
-)
-
-// Options are the contextual parameters to execute the requested call.
-//
-// Whilst it would be possible to pass a blockchain object that aggregates all
-// these together, it would be excessively hard to test. Splitting the parts out
-// allows testing without needing a proper live chain.
-type Options struct {
- Config *params.ChainConfig // Chain configuration for hard fork selection
- Chain core.ChainContext // Chain context to access past block hashes
- Header *types.Header // Header defining the block context to execute in
- State *state.StateDB // Pre-state on top of which to estimate the gas
-
- ErrorRatio float64 // Allowed overestimation ratio for faster estimation termination
-}
-
-// Estimate returns the lowest possible gas limit that allows the transaction to
-// run successfully with the provided context options. It returns an error if the
-// transaction would always revert, or if there are unexpected failures.
-func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uint64) (uint64, []byte, error) {
- // Binary search the gas limit, as it may need to be higher than the amount used
- var (
- lo uint64 // lowest-known gas limit where tx execution fails
- hi uint64 // lowest-known gas limit where tx execution succeeds
- )
- // Determine the highest gas limit can be used during the estimation.
- hi = opts.Header.GasLimit
- if call.GasLimit >= params.TxGas {
- hi = call.GasLimit
- }
- // Normalize the max fee per gas the call is willing to spend.
- var feeCap *big.Int
- if call.GasFeeCap != nil {
- feeCap = call.GasFeeCap
- } else if call.GasPrice != nil {
- feeCap = call.GasPrice
- } else {
- feeCap = common.Big0
- }
- // Recap the highest gas limit with account's available balance.
- if feeCap.BitLen() != 0 {
- balance := opts.State.GetBalance(call.From)
-
- available := new(big.Int).Set(balance)
- if call.Value != nil {
- if call.Value.Cmp(available) >= 0 {
- return 0, nil, core.ErrInsufficientFundsForTransfer
- }
- available.Sub(available, call.Value)
- }
- allowance := new(big.Int).Div(available, feeCap)
-
- // If the allowance is larger than maximum uint64, skip checking
- if allowance.IsUint64() && hi > allowance.Uint64() {
- transfer := call.Value
- if transfer == nil {
- transfer = new(big.Int)
- }
- log.Debug("Gas estimation capped by limited funds", "original", hi, "balance", balance,
- "sent", transfer, "maxFeePerGas", feeCap, "fundable", allowance)
- hi = allowance.Uint64()
- }
- }
- // Recap the highest gas allowance with specified gascap.
- if gasCap != 0 && hi > gasCap {
- log.Debug("Caller gas above allowance, capping", "requested", hi, "cap", gasCap)
- hi = gasCap
- }
- // If the transaction is a plain value transfer, short circuit estimation and
- // directly try 21000. Returning 21000 without any execution is dangerous as
- // some tx field combos might bump the price up even for plain transfers (e.g.
- // unused access list items). Ever so slightly wasteful, but safer overall.
- if len(call.Data) == 0 {
- if call.To != nil && opts.State.GetCodeSize(*call.To) == 0 {
- failed, _, err := execute(ctx, call, opts, params.TxGas)
- if !failed && err == nil {
- return params.TxGas, nil, nil
- }
- }
- }
- // We first execute the transaction at the highest allowable gas limit, since if this fails we
- // can return error immediately.
- failed, result, err := execute(ctx, call, opts, hi)
- if err != nil {
- return 0, nil, err
- }
- if failed {
- if result != nil && !errors.Is(result.Err, vm.ErrOutOfGas) {
- return 0, result.Revert(), result.Err
- }
- return 0, nil, fmt.Errorf("gas required exceeds allowance (%d)", hi)
- }
- // For almost any transaction, the gas consumed by the unconstrained execution
- // above lower-bounds the gas limit required for it to succeed. One exception
- // is those that explicitly check gas remaining in order to execute within a
- // given limit, but we probably don't want to return the lowest possible gas
- // limit for these cases anyway.
- lo = result.UsedGas - 1
-
- // There's a fairly high chance for the transaction to execute successfully
- // with gasLimit set to the first execution's usedGas + gasRefund. Explicitly
- // check that gas amount and use as a limit for the binary search.
- optimisticGasLimit := (result.UsedGas + result.RefundedGas + params.CallStipend) * 64 / 63
- if optimisticGasLimit < hi {
- failed, _, err = execute(ctx, call, opts, optimisticGasLimit)
- if err != nil {
- // This should not happen under normal conditions since if we make it this far the
- // transaction had run without error at least once before.
- log.Error("Execution error in estimate gas", "err", err)
- return 0, nil, err
- }
- if failed {
- lo = optimisticGasLimit
- } else {
- hi = optimisticGasLimit
- }
- }
- // Binary search for the smallest gas limit that allows the tx to execute successfully.
- for lo+1 < hi {
- if opts.ErrorRatio > 0 {
- // It is a bit pointless to return a perfect estimation, as changing
- // network conditions require the caller to bump it up anyway. Since
- // wallets tend to use 20-25% bump, allowing a small approximation
- // error is fine (as long as it's upwards).
- if float64(hi-lo)/float64(hi) < opts.ErrorRatio {
- break
- }
- }
- mid := (hi + lo) / 2
- if mid > lo*2 {
- // Most txs don't need much higher gas limit than their gas used, and most txs don't
- // require near the full block limit of gas, so the selection of where to bisect the
- // range here is skewed to favor the low side.
- mid = lo * 2
- }
- failed, _, err = execute(ctx, call, opts, mid)
- if err != nil {
- // This should not happen under normal conditions since if we make it this far the
- // transaction had run without error at least once before.
- log.Error("Execution error in estimate gas", "err", err)
- return 0, nil, err
- }
- if failed {
- lo = mid
- } else {
- hi = mid
- }
- }
- return hi, nil, nil
-}
-
-// execute is a helper that executes the transaction under a given gas limit and
-// returns true if the transaction fails for a reason that might be related to
-// not enough gas. A non-nil error means execution failed due to reasons unrelated
-// to the gas limit.
-func execute(ctx context.Context, call *core.Message, opts *Options, gasLimit uint64) (bool, *core.ExecutionResult, error) {
- // Configure the call for this specific execution (and revert the change after)
- defer func(gas uint64) { call.GasLimit = gas }(call.GasLimit)
- call.GasLimit = gasLimit
-
- // Execute the call and separate execution faults caused by a lack of gas or
- // other non-fixable conditions
- result, err := run(ctx, call, opts)
- if err != nil {
- if errors.Is(err, core.ErrIntrinsicGas) {
- return true, nil, nil // Special case, raise gas limit
- }
- return true, nil, err // Bail out
- }
- return result.Failed(), result, nil
-}
-
-// run assembles the EVM as defined by the consensus rules and runs the requested
-// call invocation.
-func run(ctx context.Context, call *core.Message, opts *Options) (*core.ExecutionResult, error) {
- // Assemble the call and the call context
- var (
- msgContext = core.NewEVMTxContext(call)
- evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil)
-
- dirtyState = opts.State.Copy()
- evm = vm.NewEVM(evmContext, msgContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true})
- )
- // Monitor the outer context and interrupt the EVM upon cancellation. To avoid
- // a dangling goroutine until the outer estimation finishes, create an internal
- // context for the lifetime of this method call.
- ctx, cancel := context.WithCancel(ctx)
- defer cancel()
-
- go func() {
- <-ctx.Done()
- evm.Cancel()
- }()
- // Execute the call, returning a wrapped error or the result
- result, err := core.ApplyMessage(evm, call, new(core.GasPool).AddGas(math.MaxUint64))
- if vmerr := dirtyState.Error(); vmerr != nil {
- return nil, vmerr
- }
- if err != nil {
- return result, fmt.Errorf("failed with %d gas: %w", call.GasLimit, err)
- }
- return result, nil
-}
diff --git a/eth/handler.go b/eth/handler.go
deleted file mode 100644
index a327af6113..0000000000
--- a/eth/handler.go
+++ /dev/null
@@ -1,692 +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 .
-
-package eth
-
-import (
- "errors"
- "math"
- "math/big"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/consensus"
- "github.com/ethereum/go-ethereum/consensus/beacon"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/forkid"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/eth/downloader"
- "github.com/ethereum/go-ethereum/eth/fetcher"
- "github.com/ethereum/go-ethereum/eth/protocols/eth"
- "github.com/ethereum/go-ethereum/eth/protocols/snap"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/trie/triedb/pathdb"
-)
-
-const (
- // txChanSize is the size of channel listening to NewTxsEvent.
- // The number is referenced from the size of tx pool.
- txChanSize = 4096
-
- // txMaxBroadcastSize is the max size of a transaction that will be broadcasted.
- // All transactions with a higher size will be announced and need to be fetched
- // by the peer.
- txMaxBroadcastSize = 4096
-)
-
-var syncChallengeTimeout = 15 * time.Second // Time allowance for a node to reply to the sync progress challenge
-
-// txPool defines the methods needed from a transaction pool implementation to
-// support all the operations needed by the Ethereum chain protocols.
-type txPool interface {
- // Has returns an indicator whether txpool has a transaction
- // cached with the given hash.
- Has(hash common.Hash) bool
-
- // Get retrieves the transaction from local txpool with given
- // tx hash.
- Get(hash common.Hash) *types.Transaction
-
- // Add should add the given transactions to the pool.
- Add(txs []*types.Transaction, local bool, sync bool) []error
-
- // Pending should return pending transactions.
- // The slice should be modifiable by the caller.
- Pending(enforceTips bool) map[common.Address][]*txpool.LazyTransaction
-
- // SubscribeTransactions subscribes to new transaction events. The subscriber
- // can decide whether to receive notifications only for newly seen transactions
- // or also for reorged out ones.
- SubscribeTransactions(ch chan<- core.NewTxsEvent, reorgs bool) event.Subscription
-}
-
-// handlerConfig is the collection of initialization parameters to create a full
-// node network handler.
-type handlerConfig struct {
- Database ethdb.Database // Database for direct sync insertions
- Chain *core.BlockChain // Blockchain to serve data from
- TxPool txPool // Transaction pool to propagate from
- Merger *consensus.Merger // The manager for eth1/2 transition
- Network uint64 // Network identifier to advertise
- Sync downloader.SyncMode // Whether to snap or full sync
- BloomCache uint64 // Megabytes to alloc for snap sync bloom
- EventMux *event.TypeMux // Legacy event mux, deprecate for `feed`
- RequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges
-}
-
-type handler struct {
- networkID uint64
- forkFilter forkid.Filter // Fork ID filter, constant across the lifetime of the node
-
- snapSync atomic.Bool // Flag whether snap sync is enabled (gets disabled if we already have blocks)
- synced atomic.Bool // Flag whether we're considered synchronised (enables transaction processing)
-
- database ethdb.Database
- txpool txPool
- chain *core.BlockChain
- maxPeers int
-
- downloader *downloader.Downloader
- blockFetcher *fetcher.BlockFetcher
- txFetcher *fetcher.TxFetcher
- peers *peerSet
- merger *consensus.Merger
-
- eventMux *event.TypeMux
- txsCh chan core.NewTxsEvent
- txsSub event.Subscription
- minedBlockSub *event.TypeMuxSubscription
-
- requiredBlocks map[uint64]common.Hash
-
- // channels for fetcher, syncer, txsyncLoop
- quitSync chan struct{}
-
- chainSync *chainSyncer
- wg sync.WaitGroup
-
- handlerStartCh chan struct{}
- handlerDoneCh chan struct{}
-}
-
-// newHandler returns a handler for all Ethereum chain management protocol.
-func newHandler(config *handlerConfig) (*handler, error) {
- // Create the protocol manager with the base fields
- if config.EventMux == nil {
- config.EventMux = new(event.TypeMux) // Nicety initialization for tests
- }
- h := &handler{
- networkID: config.Network,
- forkFilter: forkid.NewFilter(config.Chain),
- eventMux: config.EventMux,
- database: config.Database,
- txpool: config.TxPool,
- chain: config.Chain,
- peers: newPeerSet(),
- merger: config.Merger,
- requiredBlocks: config.RequiredBlocks,
- quitSync: make(chan struct{}),
- handlerDoneCh: make(chan struct{}),
- handlerStartCh: make(chan struct{}),
- }
- if config.Sync == downloader.FullSync {
- // The database seems empty as the current block is the genesis. Yet the snap
- // block is ahead, so snap sync was enabled for this node at a certain point.
- // The scenarios where this can happen is
- // * if the user manually (or via a bad block) rolled back a snap sync node
- // below the sync point.
- // * the last snap sync is not finished while user specifies a full sync this
- // time. But we don't have any recent state for full sync.
- // In these cases however it's safe to reenable snap sync.
- fullBlock, snapBlock := h.chain.CurrentBlock(), h.chain.CurrentSnapBlock()
- if fullBlock.Number.Uint64() == 0 && snapBlock.Number.Uint64() > 0 {
- h.snapSync.Store(true)
- log.Warn("Switch sync mode from full sync to snap sync", "reason", "snap sync incomplete")
- } else if !h.chain.HasState(fullBlock.Root) {
- h.snapSync.Store(true)
- log.Warn("Switch sync mode from full sync to snap sync", "reason", "head state missing")
- }
- } else {
- head := h.chain.CurrentBlock()
- if head.Number.Uint64() > 0 && h.chain.HasState(head.Root) {
- // Print warning log if database is not empty to run snap sync.
- log.Warn("Switch sync mode from snap sync to full sync", "reason", "snap sync complete")
- } else {
- // If snap sync was requested and our database is empty, grant it
- h.snapSync.Store(true)
- log.Info("Enabled snap sync", "head", head.Number, "hash", head.Hash())
- }
- }
- // If snap sync is requested but snapshots are disabled, fail loudly
- if h.snapSync.Load() && config.Chain.Snapshots() == nil {
- return nil, errors.New("snap sync not supported with snapshots disabled")
- }
- // Construct the downloader (long sync)
- h.downloader = downloader.New(config.Database, h.eventMux, h.chain, nil, h.removePeer, h.enableSyncedFeatures)
- if ttd := h.chain.Config().TerminalTotalDifficulty; ttd != nil {
- if h.chain.Config().TerminalTotalDifficultyPassed {
- log.Info("Chain post-merge, sync via beacon client")
- } else {
- head := h.chain.CurrentBlock()
- if td := h.chain.GetTd(head.Hash(), head.Number.Uint64()); td.Cmp(ttd) >= 0 {
- log.Info("Chain post-TTD, sync via beacon client")
- } else {
- log.Warn("Chain pre-merge, sync via PoW (ensure beacon client is ready)")
- }
- }
- } else if h.chain.Config().TerminalTotalDifficultyPassed {
- log.Error("Chain configured post-merge, but without TTD. Are you debugging sync?")
- }
- // Construct the fetcher (short sync)
- validator := func(header *types.Header) error {
- // All the block fetcher activities should be disabled
- // after the transition. Print the warning log.
- if h.merger.PoSFinalized() {
- log.Warn("Unexpected validation activity", "hash", header.Hash(), "number", header.Number)
- return errors.New("unexpected behavior after transition")
- }
- // Reject all the PoS style headers in the first place. No matter
- // the chain has finished the transition or not, the PoS headers
- // should only come from the trusted consensus layer instead of
- // p2p network.
- if beacon, ok := h.chain.Engine().(*beacon.Beacon); ok {
- if beacon.IsPoSHeader(header) {
- return errors.New("unexpected post-merge header")
- }
- }
- return h.chain.Engine().VerifyHeader(h.chain, header)
- }
- heighter := func() uint64 {
- return h.chain.CurrentBlock().Number.Uint64()
- }
- inserter := func(blocks types.Blocks) (int, error) {
- // All the block fetcher activities should be disabled
- // after the transition. Print the warning log.
- if h.merger.PoSFinalized() {
- var ctx []interface{}
- ctx = append(ctx, "blocks", len(blocks))
- if len(blocks) > 0 {
- ctx = append(ctx, "firsthash", blocks[0].Hash())
- ctx = append(ctx, "firstnumber", blocks[0].Number())
- ctx = append(ctx, "lasthash", blocks[len(blocks)-1].Hash())
- ctx = append(ctx, "lastnumber", blocks[len(blocks)-1].Number())
- }
- log.Warn("Unexpected insertion activity", ctx...)
- return 0, errors.New("unexpected behavior after transition")
- }
- // If snap sync is running, deny importing weird blocks. This is a problematic
- // clause when starting up a new network, because snap-syncing miners might not
- // accept each others' blocks until a restart. Unfortunately we haven't figured
- // out a way yet where nodes can decide unilaterally whether the network is new
- // or not. This should be fixed if we figure out a solution.
- if !h.synced.Load() {
- log.Warn("Syncing, discarded propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash())
- return 0, nil
- }
- if h.merger.TDDReached() {
- // The blocks from the p2p network is regarded as untrusted
- // after the transition. In theory block gossip should be disabled
- // entirely whenever the transition is started. But in order to
- // handle the transition boundary reorg in the consensus-layer,
- // the legacy blocks are still accepted, but only for the terminal
- // pow blocks. Spec: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3675.md#halt-the-importing-of-pow-blocks
- for i, block := range blocks {
- ptd := h.chain.GetTd(block.ParentHash(), block.NumberU64()-1)
- if ptd == nil {
- return 0, nil
- }
- td := new(big.Int).Add(ptd, block.Difficulty())
- if !h.chain.Config().IsTerminalPoWBlock(ptd, td) {
- log.Info("Filtered out non-terminal pow block", "number", block.NumberU64(), "hash", block.Hash())
- return 0, nil
- }
- if err := h.chain.InsertBlockWithoutSetHead(block); err != nil {
- return i, err
- }
- }
- return 0, nil
- }
- return h.chain.InsertChain(blocks)
- }
- h.blockFetcher = fetcher.NewBlockFetcher(false, nil, h.chain.GetBlockByHash, validator, h.BroadcastBlock, heighter, nil, inserter, h.removePeer)
-
- fetchTx := func(peer string, hashes []common.Hash) error {
- p := h.peers.peer(peer)
- if p == nil {
- return errors.New("unknown peer")
- }
- return p.RequestTxs(hashes)
- }
- addTxs := func(txs []*types.Transaction) []error {
- return h.txpool.Add(txs, false, false)
- }
- h.txFetcher = fetcher.NewTxFetcher(h.txpool.Has, addTxs, fetchTx, h.removePeer)
- h.chainSync = newChainSyncer(h)
- return h, nil
-}
-
-// protoTracker tracks the number of active protocol handlers.
-func (h *handler) protoTracker() {
- defer h.wg.Done()
- var active int
- for {
- select {
- case <-h.handlerStartCh:
- active++
- case <-h.handlerDoneCh:
- active--
- case <-h.quitSync:
- // Wait for all active handlers to finish.
- for ; active > 0; active-- {
- <-h.handlerDoneCh
- }
- return
- }
- }
-}
-
-// incHandlers signals to increment the number of active handlers if not
-// quitting.
-func (h *handler) incHandlers() bool {
- select {
- case h.handlerStartCh <- struct{}{}:
- return true
- case <-h.quitSync:
- return false
- }
-}
-
-// decHandlers signals to decrement the number of active handlers.
-func (h *handler) decHandlers() {
- h.handlerDoneCh <- struct{}{}
-}
-
-// runEthPeer registers an eth peer into the joint eth/snap peerset, adds it to
-// various subsystems and starts handling messages.
-func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error {
- if !h.incHandlers() {
- return p2p.DiscQuitting
- }
- defer h.decHandlers()
-
- // If the peer has a `snap` extension, wait for it to connect so we can have
- // a uniform initialization/teardown mechanism
- snap, err := h.peers.waitSnapExtension(peer)
- if err != nil {
- peer.Log().Error("Snapshot extension barrier failed", "err", err)
- return err
- }
-
- // Execute the Ethereum handshake
- var (
- genesis = h.chain.Genesis()
- head = h.chain.CurrentHeader()
- hash = head.Hash()
- number = head.Number.Uint64()
- td = h.chain.GetTd(hash, number)
- )
- forkID := forkid.NewID(h.chain.Config(), genesis, number, head.Time)
- if err := peer.Handshake(h.networkID, td, hash, genesis.Hash(), forkID, h.forkFilter); err != nil {
- peer.Log().Debug("Ethereum handshake failed", "err", err)
- return err
- }
- reject := false // reserved peer slots
- if h.snapSync.Load() {
- if snap == nil {
- // If we are running snap-sync, we want to reserve roughly half the peer
- // slots for peers supporting the snap protocol.
- // The logic here is; we only allow up to 5 more non-snap peers than snap-peers.
- if all, snp := h.peers.len(), h.peers.snapLen(); all-snp > snp+5 {
- reject = true
- }
- }
- }
- // Ignore maxPeers if this is a trusted peer
- if !peer.Peer.Info().Network.Trusted {
- if reject || h.peers.len() >= h.maxPeers {
- return p2p.DiscTooManyPeers
- }
- }
- peer.Log().Debug("Ethereum peer connected", "name", peer.Name())
-
- // Register the peer locally
- if err := h.peers.registerPeer(peer, snap); err != nil {
- peer.Log().Error("Ethereum peer registration failed", "err", err)
- return err
- }
- defer h.unregisterPeer(peer.ID())
-
- p := h.peers.peer(peer.ID())
- if p == nil {
- return errors.New("peer dropped during handling")
- }
- // Register the peer in the downloader. If the downloader considers it banned, we disconnect
- if err := h.downloader.RegisterPeer(peer.ID(), peer.Version(), peer); err != nil {
- peer.Log().Error("Failed to register peer in eth syncer", "err", err)
- return err
- }
- if snap != nil {
- if err := h.downloader.SnapSyncer.Register(snap); err != nil {
- peer.Log().Error("Failed to register peer in snap syncer", "err", err)
- return err
- }
- }
- h.chainSync.handlePeerEvent()
-
- // Propagate existing transactions. new transactions appearing
- // after this will be sent via broadcasts.
- h.syncTransactions(peer)
-
- // Create a notification channel for pending requests if the peer goes down
- dead := make(chan struct{})
- defer close(dead)
-
- // If we have any explicit peer required block hashes, request them
- for number, hash := range h.requiredBlocks {
- resCh := make(chan *eth.Response)
-
- req, err := peer.RequestHeadersByNumber(number, 1, 0, false, resCh)
- if err != nil {
- return err
- }
- go func(number uint64, hash common.Hash, req *eth.Request) {
- // Ensure the request gets cancelled in case of error/drop
- defer req.Close()
-
- timeout := time.NewTimer(syncChallengeTimeout)
- defer timeout.Stop()
-
- select {
- case res := <-resCh:
- headers := ([]*types.Header)(*res.Res.(*eth.BlockHeadersRequest))
- if len(headers) == 0 {
- // Required blocks are allowed to be missing if the remote
- // node is not yet synced
- res.Done <- nil
- return
- }
- // Validate the header and either drop the peer or continue
- if len(headers) > 1 {
- res.Done <- errors.New("too many headers in required block response")
- return
- }
- if headers[0].Number.Uint64() != number || headers[0].Hash() != hash {
- peer.Log().Info("Required block mismatch, dropping peer", "number", number, "hash", headers[0].Hash(), "want", hash)
- res.Done <- errors.New("required block mismatch")
- return
- }
- peer.Log().Debug("Peer required block verified", "number", number, "hash", hash)
- res.Done <- nil
- case <-timeout.C:
- peer.Log().Warn("Required block challenge timed out, dropping", "addr", peer.RemoteAddr(), "type", peer.Name())
- h.removePeer(peer.ID())
- }
- }(number, hash, req)
- }
- // Handle incoming messages until the connection is torn down
- return handler(peer)
-}
-
-// runSnapExtension registers a `snap` peer into the joint eth/snap peerset and
-// starts handling inbound messages. As `snap` is only a satellite protocol to
-// `eth`, all subsystem registrations and lifecycle management will be done by
-// the main `eth` handler to prevent strange races.
-func (h *handler) runSnapExtension(peer *snap.Peer, handler snap.Handler) error {
- if !h.incHandlers() {
- return p2p.DiscQuitting
- }
- defer h.decHandlers()
-
- if err := h.peers.registerSnapExtension(peer); err != nil {
- if metrics.Enabled {
- if peer.Inbound() {
- snap.IngressRegistrationErrorMeter.Mark(1)
- } else {
- snap.EgressRegistrationErrorMeter.Mark(1)
- }
- }
- peer.Log().Debug("Snapshot extension registration failed", "err", err)
- return err
- }
- return handler(peer)
-}
-
-// removePeer requests disconnection of a peer.
-func (h *handler) removePeer(id string) {
- peer := h.peers.peer(id)
- if peer != nil {
- peer.Peer.Disconnect(p2p.DiscUselessPeer)
- }
-}
-
-// unregisterPeer removes a peer from the downloader, fetchers and main peer set.
-func (h *handler) unregisterPeer(id string) {
- // Create a custom logger to avoid printing the entire id
- var logger log.Logger
- if len(id) < 16 {
- // Tests use short IDs, don't choke on them
- logger = log.New("peer", id)
- } else {
- logger = log.New("peer", id[:8])
- }
- // Abort if the peer does not exist
- peer := h.peers.peer(id)
- if peer == nil {
- logger.Error("Ethereum peer removal failed", "err", errPeerNotRegistered)
- return
- }
- // Remove the `eth` peer if it exists
- logger.Debug("Removing Ethereum peer", "snap", peer.snapExt != nil)
-
- // Remove the `snap` extension if it exists
- if peer.snapExt != nil {
- h.downloader.SnapSyncer.Unregister(id)
- }
- h.downloader.UnregisterPeer(id)
- h.txFetcher.Drop(id)
-
- if err := h.peers.unregisterPeer(id); err != nil {
- logger.Error("Ethereum peer removal failed", "err", err)
- }
-}
-
-func (h *handler) Start(maxPeers int) {
- h.maxPeers = maxPeers
-
- // broadcast and announce transactions (only new ones, not resurrected ones)
- h.wg.Add(1)
- h.txsCh = make(chan core.NewTxsEvent, txChanSize)
- h.txsSub = h.txpool.SubscribeTransactions(h.txsCh, false)
- go h.txBroadcastLoop()
-
- // broadcast mined blocks
- h.wg.Add(1)
- h.minedBlockSub = h.eventMux.Subscribe(core.NewMinedBlockEvent{})
- go h.minedBroadcastLoop()
-
- // start sync handlers
- h.wg.Add(1)
- go h.chainSync.loop()
-
- // start peer handler tracker
- h.wg.Add(1)
- go h.protoTracker()
-}
-
-func (h *handler) Stop() {
- h.txsSub.Unsubscribe() // quits txBroadcastLoop
- h.minedBlockSub.Unsubscribe() // quits blockBroadcastLoop
-
- // Quit chainSync and txsync64.
- // After this is done, no new peers will be accepted.
- close(h.quitSync)
-
- // Disconnect existing sessions.
- // This also closes the gate for any new registrations on the peer set.
- // sessions which are already established but not added to h.peers yet
- // will exit when they try to register.
- h.peers.close()
- h.wg.Wait()
-
- log.Info("Ethereum protocol stopped")
-}
-
-// BroadcastBlock will either propagate a block to a subset of its peers, or
-// will only announce its availability (depending what's requested).
-func (h *handler) BroadcastBlock(block *types.Block, propagate bool) {
- // Disable the block propagation if the chain has already entered the PoS
- // stage. The block propagation is delegated to the consensus layer.
- if h.merger.PoSFinalized() {
- return
- }
- // Disable the block propagation if it's the post-merge block.
- if beacon, ok := h.chain.Engine().(*beacon.Beacon); ok {
- if beacon.IsPoSHeader(block.Header()) {
- return
- }
- }
- hash := block.Hash()
- peers := h.peers.peersWithoutBlock(hash)
-
- // If propagation is requested, send to a subset of the peer
- if propagate {
- // Calculate the TD of the block (it's not imported yet, so block.Td is not valid)
- var td *big.Int
- if parent := h.chain.GetBlock(block.ParentHash(), block.NumberU64()-1); parent != nil {
- td = new(big.Int).Add(block.Difficulty(), h.chain.GetTd(block.ParentHash(), block.NumberU64()-1))
- } else {
- log.Error("Propagating dangling block", "number", block.Number(), "hash", hash)
- return
- }
- // Send the block to a subset of our peers
- transfer := peers[:int(math.Sqrt(float64(len(peers))))]
- for _, peer := range transfer {
- peer.AsyncSendNewBlock(block, td)
- }
- log.Trace("Propagated block", "hash", hash, "recipients", len(transfer), "duration", common.PrettyDuration(time.Since(block.ReceivedAt)))
- return
- }
- // Otherwise if the block is indeed in out own chain, announce it
- if h.chain.HasBlock(hash, block.NumberU64()) {
- for _, peer := range peers {
- peer.AsyncSendNewBlockHash(block)
- }
- log.Trace("Announced block", "hash", hash, "recipients", len(peers), "duration", common.PrettyDuration(time.Since(block.ReceivedAt)))
- }
-}
-
-// BroadcastTransactions will propagate a batch of transactions
-// - To a square root of all peers for non-blob transactions
-// - And, separately, as announcements to all peers which are not known to
-// already have the given transaction.
-func (h *handler) BroadcastTransactions(txs types.Transactions) {
- var (
- blobTxs int // Number of blob transactions to announce only
- largeTxs int // Number of large transactions to announce only
-
- directCount int // Number of transactions sent directly to peers (duplicates included)
- directPeers int // Number of peers that were sent transactions directly
- annCount int // Number of transactions announced across all peers (duplicates included)
- annPeers int // Number of peers announced about transactions
-
- txset = make(map[*ethPeer][]common.Hash) // Set peer->hash to transfer directly
- annos = make(map[*ethPeer][]common.Hash) // Set peer->hash to announce
- )
- // Broadcast transactions to a batch of peers not knowing about it
- for _, tx := range txs {
- peers := h.peers.peersWithoutTransaction(tx.Hash())
-
- var numDirect int
- switch {
- case tx.Type() == types.BlobTxType:
- blobTxs++
- case tx.Size() > txMaxBroadcastSize:
- largeTxs++
- default:
- numDirect = int(math.Sqrt(float64(len(peers))))
- }
- // Send the tx unconditionally to a subset of our peers
- for _, peer := range peers[:numDirect] {
- txset[peer] = append(txset[peer], tx.Hash())
- }
- // For the remaining peers, send announcement only
- for _, peer := range peers[numDirect:] {
- annos[peer] = append(annos[peer], tx.Hash())
- }
- }
- for peer, hashes := range txset {
- directPeers++
- directCount += len(hashes)
- peer.AsyncSendTransactions(hashes)
- }
- for peer, hashes := range annos {
- annPeers++
- annCount += len(hashes)
- peer.AsyncSendPooledTransactionHashes(hashes)
- }
- log.Debug("Distributed transactions", "plaintxs", len(txs)-blobTxs-largeTxs, "blobtxs", blobTxs, "largetxs", largeTxs,
- "bcastpeers", directPeers, "bcastcount", directCount, "annpeers", annPeers, "anncount", annCount)
-}
-
-// minedBroadcastLoop sends mined blocks to connected peers.
-func (h *handler) minedBroadcastLoop() {
- defer h.wg.Done()
-
- for obj := range h.minedBlockSub.Chan() {
- if ev, ok := obj.Data.(core.NewMinedBlockEvent); ok {
- h.BroadcastBlock(ev.Block, true) // First propagate block to peers
- h.BroadcastBlock(ev.Block, false) // Only then announce to the rest
- }
- }
-}
-
-// txBroadcastLoop announces new transactions to connected peers.
-func (h *handler) txBroadcastLoop() {
- defer h.wg.Done()
- for {
- select {
- case event := <-h.txsCh:
- h.BroadcastTransactions(event.Txs)
- case <-h.txsSub.Err():
- return
- }
- }
-}
-
-// enableSyncedFeatures enables the post-sync functionalities when the initial
-// sync is finished.
-func (h *handler) enableSyncedFeatures() {
- // Mark the local node as synced.
- h.synced.Store(true)
-
- // If we were running snap sync and it finished, disable doing another
- // round on next sync cycle
- if h.snapSync.Load() {
- log.Info("Snap sync complete, auto disabling")
- h.snapSync.Store(false)
- }
- if h.chain.TrieDB().Scheme() == rawdb.PathScheme {
- h.chain.TrieDB().SetBufferSize(pathdb.DefaultBufferSize)
- }
-}
diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go
deleted file mode 100644
index bb342acc18..0000000000
--- a/eth/handler_eth_test.go
+++ /dev/null
@@ -1,608 +0,0 @@
-// Copyright 2020 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 .
-
-package eth
-
-import (
- "fmt"
- "math/big"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/consensus"
- "github.com/ethereum/go-ethereum/consensus/ethash"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/forkid"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/eth/downloader"
- "github.com/ethereum/go-ethereum/eth/protocols/eth"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/params"
-)
-
-// testEthHandler is a mock event handler to listen for inbound network requests
-// on the `eth` protocol and convert them into a more easily testable form.
-type testEthHandler struct {
- blockBroadcasts event.Feed
- txAnnounces event.Feed
- txBroadcasts event.Feed
-}
-
-func (h *testEthHandler) Chain() *core.BlockChain { panic("no backing chain") }
-func (h *testEthHandler) TxPool() eth.TxPool { panic("no backing tx pool") }
-func (h *testEthHandler) AcceptTxs() bool { return true }
-func (h *testEthHandler) RunPeer(*eth.Peer, eth.Handler) error { panic("not used in tests") }
-func (h *testEthHandler) PeerInfo(enode.ID) interface{} { panic("not used in tests") }
-
-func (h *testEthHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
- switch packet := packet.(type) {
- case *eth.NewBlockPacket:
- h.blockBroadcasts.Send(packet.Block)
- return nil
-
- case *eth.NewPooledTransactionHashesPacket67:
- h.txAnnounces.Send(([]common.Hash)(*packet))
- return nil
-
- case *eth.NewPooledTransactionHashesPacket68:
- h.txAnnounces.Send(packet.Hashes)
- return nil
-
- case *eth.TransactionsPacket:
- h.txBroadcasts.Send(([]*types.Transaction)(*packet))
- return nil
-
- case *eth.PooledTransactionsResponse:
- h.txBroadcasts.Send(([]*types.Transaction)(*packet))
- return nil
-
- default:
- panic(fmt.Sprintf("unexpected eth packet type in tests: %T", packet))
- }
-}
-
-// Tests that peers are correctly accepted (or rejected) based on the advertised
-// fork IDs in the protocol handshake.
-func TestForkIDSplit67(t *testing.T) { testForkIDSplit(t, eth.ETH67) }
-func TestForkIDSplit68(t *testing.T) { testForkIDSplit(t, eth.ETH68) }
-
-func testForkIDSplit(t *testing.T, protocol uint) {
- t.Parallel()
-
- var (
- engine = ethash.NewFaker()
-
- configNoFork = ¶ms.ChainConfig{HomesteadBlock: big.NewInt(1)}
- configProFork = ¶ms.ChainConfig{
- HomesteadBlock: big.NewInt(1),
- EIP150Block: big.NewInt(2),
- EIP155Block: big.NewInt(2),
- EIP158Block: big.NewInt(2),
- ByzantiumBlock: big.NewInt(3),
- }
- dbNoFork = rawdb.NewMemoryDatabase()
- dbProFork = rawdb.NewMemoryDatabase()
-
- gspecNoFork = &core.Genesis{Config: configNoFork}
- gspecProFork = &core.Genesis{Config: configProFork}
-
- chainNoFork, _ = core.NewBlockChain(dbNoFork, nil, gspecNoFork, nil, engine, vm.Config{}, nil, nil)
- chainProFork, _ = core.NewBlockChain(dbProFork, nil, gspecProFork, nil, engine, vm.Config{}, nil, nil)
-
- _, blocksNoFork, _ = core.GenerateChainWithGenesis(gspecNoFork, engine, 2, nil)
- _, blocksProFork, _ = core.GenerateChainWithGenesis(gspecProFork, engine, 2, nil)
-
- ethNoFork, _ = newHandler(&handlerConfig{
- Database: dbNoFork,
- Chain: chainNoFork,
- TxPool: newTestTxPool(),
- Merger: consensus.NewMerger(rawdb.NewMemoryDatabase()),
- Network: 1,
- Sync: downloader.FullSync,
- BloomCache: 1,
- })
- ethProFork, _ = newHandler(&handlerConfig{
- Database: dbProFork,
- Chain: chainProFork,
- TxPool: newTestTxPool(),
- Merger: consensus.NewMerger(rawdb.NewMemoryDatabase()),
- Network: 1,
- Sync: downloader.FullSync,
- BloomCache: 1,
- })
- )
- ethNoFork.Start(1000)
- ethProFork.Start(1000)
-
- // Clean up everything after ourselves
- defer chainNoFork.Stop()
- defer chainProFork.Stop()
-
- defer ethNoFork.Stop()
- defer ethProFork.Stop()
-
- // Both nodes should allow the other to connect (same genesis, next fork is the same)
- p2pNoFork, p2pProFork := p2p.MsgPipe()
- defer p2pNoFork.Close()
- defer p2pProFork.Close()
-
- peerNoFork := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pNoFork), p2pNoFork, nil)
- peerProFork := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pProFork), p2pProFork, nil)
- defer peerNoFork.Close()
- defer peerProFork.Close()
-
- errc := make(chan error, 2)
- go func(errc chan error) {
- errc <- ethNoFork.runEthPeer(peerProFork, func(peer *eth.Peer) error { return nil })
- }(errc)
- go func(errc chan error) {
- errc <- ethProFork.runEthPeer(peerNoFork, func(peer *eth.Peer) error { return nil })
- }(errc)
-
- for i := 0; i < 2; i++ {
- select {
- case err := <-errc:
- if err != nil {
- t.Fatalf("frontier nofork <-> profork failed: %v", err)
- }
- case <-time.After(250 * time.Millisecond):
- t.Fatalf("frontier nofork <-> profork handler timeout")
- }
- }
- // Progress into Homestead. Fork's match, so we don't care what the future holds
- chainNoFork.InsertChain(blocksNoFork[:1])
- chainProFork.InsertChain(blocksProFork[:1])
-
- p2pNoFork, p2pProFork = p2p.MsgPipe()
- defer p2pNoFork.Close()
- defer p2pProFork.Close()
-
- peerNoFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
- peerProFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
- defer peerNoFork.Close()
- defer peerProFork.Close()
-
- errc = make(chan error, 2)
- go func(errc chan error) {
- errc <- ethNoFork.runEthPeer(peerProFork, func(peer *eth.Peer) error { return nil })
- }(errc)
- go func(errc chan error) {
- errc <- ethProFork.runEthPeer(peerNoFork, func(peer *eth.Peer) error { return nil })
- }(errc)
-
- for i := 0; i < 2; i++ {
- select {
- case err := <-errc:
- if err != nil {
- t.Fatalf("homestead nofork <-> profork failed: %v", err)
- }
- case <-time.After(250 * time.Millisecond):
- t.Fatalf("homestead nofork <-> profork handler timeout")
- }
- }
- // Progress into Spurious. Forks mismatch, signalling differing chains, reject
- chainNoFork.InsertChain(blocksNoFork[1:2])
- chainProFork.InsertChain(blocksProFork[1:2])
-
- p2pNoFork, p2pProFork = p2p.MsgPipe()
- defer p2pNoFork.Close()
- defer p2pProFork.Close()
-
- peerNoFork = eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pNoFork), p2pNoFork, nil)
- peerProFork = eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pProFork), p2pProFork, nil)
- defer peerNoFork.Close()
- defer peerProFork.Close()
-
- errc = make(chan error, 2)
- go func(errc chan error) {
- errc <- ethNoFork.runEthPeer(peerProFork, func(peer *eth.Peer) error { return nil })
- }(errc)
- go func(errc chan error) {
- errc <- ethProFork.runEthPeer(peerNoFork, func(peer *eth.Peer) error { return nil })
- }(errc)
-
- var successes int
- for i := 0; i < 2; i++ {
- select {
- case err := <-errc:
- if err == nil {
- successes++
- if successes == 2 { // Only one side disconnects
- t.Fatalf("fork ID rejection didn't happen")
- }
- }
- case <-time.After(250 * time.Millisecond):
- t.Fatalf("split peers not rejected")
- }
- }
-}
-
-// Tests that received transactions are added to the local pool.
-func TestRecvTransactions67(t *testing.T) { testRecvTransactions(t, eth.ETH67) }
-func TestRecvTransactions68(t *testing.T) { testRecvTransactions(t, eth.ETH68) }
-
-func testRecvTransactions(t *testing.T, protocol uint) {
- t.Parallel()
-
- // Create a message handler, configure it to accept transactions and watch them
- handler := newTestHandler()
- defer handler.close()
-
- handler.handler.synced.Store(true) // mark synced to accept transactions
-
- txs := make(chan core.NewTxsEvent)
- sub := handler.txpool.SubscribeTransactions(txs, false)
- defer sub.Unsubscribe()
-
- // Create a source peer to send messages through and a sink handler to receive them
- p2pSrc, p2pSink := p2p.MsgPipe()
- defer p2pSrc.Close()
- defer p2pSink.Close()
-
- src := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pSrc), p2pSrc, handler.txpool)
- sink := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pSink), p2pSink, handler.txpool)
- defer src.Close()
- defer sink.Close()
-
- go handler.handler.runEthPeer(sink, func(peer *eth.Peer) error {
- return eth.Handle((*ethHandler)(handler.handler), peer)
- })
- // Run the handshake locally to avoid spinning up a source handler
- var (
- genesis = handler.chain.Genesis()
- head = handler.chain.CurrentBlock()
- td = handler.chain.GetTd(head.Hash(), head.Number.Uint64())
- )
- if err := src.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil {
- t.Fatalf("failed to run protocol handshake")
- }
- // Send the transaction to the sink and verify that it's added to the tx pool
- tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 100000, big.NewInt(0), nil)
- tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
-
- if err := src.SendTransactions([]*types.Transaction{tx}); err != nil {
- t.Fatalf("failed to send transaction: %v", err)
- }
- select {
- case event := <-txs:
- if len(event.Txs) != 1 {
- t.Errorf("wrong number of added transactions: got %d, want 1", len(event.Txs))
- } else if event.Txs[0].Hash() != tx.Hash() {
- t.Errorf("added wrong tx hash: got %v, want %v", event.Txs[0].Hash(), tx.Hash())
- }
- case <-time.After(2 * time.Second):
- t.Errorf("no NewTxsEvent received within 2 seconds")
- }
-}
-
-// This test checks that pending transactions are sent.
-func TestSendTransactions67(t *testing.T) { testSendTransactions(t, eth.ETH67) }
-func TestSendTransactions68(t *testing.T) { testSendTransactions(t, eth.ETH68) }
-
-func testSendTransactions(t *testing.T, protocol uint) {
- t.Parallel()
-
- // Create a message handler and fill the pool with big transactions
- handler := newTestHandler()
- defer handler.close()
-
- insert := make([]*types.Transaction, 100)
- for nonce := range insert {
- tx := types.NewTransaction(uint64(nonce), common.Address{}, big.NewInt(0), 100000, big.NewInt(0), make([]byte, 10240))
- tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
- insert[nonce] = tx
- }
- go handler.txpool.Add(insert, false, false) // Need goroutine to not block on feed
- time.Sleep(250 * time.Millisecond) // Wait until tx events get out of the system (can't use events, tx broadcaster races with peer join)
-
- // Create a source handler to send messages through and a sink peer to receive them
- p2pSrc, p2pSink := p2p.MsgPipe()
- defer p2pSrc.Close()
- defer p2pSink.Close()
-
- src := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pSrc), p2pSrc, handler.txpool)
- sink := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pSink), p2pSink, handler.txpool)
- defer src.Close()
- defer sink.Close()
-
- go handler.handler.runEthPeer(src, func(peer *eth.Peer) error {
- return eth.Handle((*ethHandler)(handler.handler), peer)
- })
- // Run the handshake locally to avoid spinning up a source handler
- var (
- genesis = handler.chain.Genesis()
- head = handler.chain.CurrentBlock()
- td = handler.chain.GetTd(head.Hash(), head.Number.Uint64())
- )
- if err := sink.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil {
- t.Fatalf("failed to run protocol handshake")
- }
- // After the handshake completes, the source handler should stream the sink
- // the transactions, subscribe to all inbound network events
- backend := new(testEthHandler)
-
- anns := make(chan []common.Hash)
- annSub := backend.txAnnounces.Subscribe(anns)
- defer annSub.Unsubscribe()
-
- bcasts := make(chan []*types.Transaction)
- bcastSub := backend.txBroadcasts.Subscribe(bcasts)
- defer bcastSub.Unsubscribe()
-
- go eth.Handle(backend, sink)
-
- // Make sure we get all the transactions on the correct channels
- seen := make(map[common.Hash]struct{})
- for len(seen) < len(insert) {
- switch protocol {
- case 67, 68:
- select {
- case hashes := <-anns:
- for _, hash := range hashes {
- if _, ok := seen[hash]; ok {
- t.Errorf("duplicate transaction announced: %x", hash)
- }
- seen[hash] = struct{}{}
- }
- case <-bcasts:
- t.Errorf("initial tx broadcast received on post eth/66")
- }
-
- default:
- panic("unsupported protocol, please extend test")
- }
- }
- for _, tx := range insert {
- if _, ok := seen[tx.Hash()]; !ok {
- t.Errorf("missing transaction: %x", tx.Hash())
- }
- }
-}
-
-// Tests that transactions get propagated to all attached peers, either via direct
-// broadcasts or via announcements/retrievals.
-func TestTransactionPropagation67(t *testing.T) { testTransactionPropagation(t, eth.ETH67) }
-func TestTransactionPropagation68(t *testing.T) { testTransactionPropagation(t, eth.ETH68) }
-
-func testTransactionPropagation(t *testing.T, protocol uint) {
- t.Parallel()
-
- // Create a source handler to send transactions from and a number of sinks
- // to receive them. We need multiple sinks since a one-to-one peering would
- // broadcast all transactions without announcement.
- source := newTestHandler()
- source.handler.snapSync.Store(false) // Avoid requiring snap, otherwise some will be dropped below
- defer source.close()
-
- sinks := make([]*testHandler, 10)
- for i := 0; i < len(sinks); i++ {
- sinks[i] = newTestHandler()
- defer sinks[i].close()
-
- sinks[i].handler.synced.Store(true) // mark synced to accept transactions
- }
- // Interconnect all the sink handlers with the source handler
- for i, sink := range sinks {
- sink := sink // Closure for gorotuine below
-
- sourcePipe, sinkPipe := p2p.MsgPipe()
- defer sourcePipe.Close()
- defer sinkPipe.Close()
-
- sourcePeer := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{byte(i + 1)}, "", nil, sourcePipe), sourcePipe, source.txpool)
- sinkPeer := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{0}, "", nil, sinkPipe), sinkPipe, sink.txpool)
- defer sourcePeer.Close()
- defer sinkPeer.Close()
-
- go source.handler.runEthPeer(sourcePeer, func(peer *eth.Peer) error {
- return eth.Handle((*ethHandler)(source.handler), peer)
- })
- go sink.handler.runEthPeer(sinkPeer, func(peer *eth.Peer) error {
- return eth.Handle((*ethHandler)(sink.handler), peer)
- })
- }
- // Subscribe to all the transaction pools
- txChs := make([]chan core.NewTxsEvent, len(sinks))
- for i := 0; i < len(sinks); i++ {
- txChs[i] = make(chan core.NewTxsEvent, 1024)
-
- sub := sinks[i].txpool.SubscribeTransactions(txChs[i], false)
- defer sub.Unsubscribe()
- }
- // Fill the source pool with transactions and wait for them at the sinks
- txs := make([]*types.Transaction, 1024)
- for nonce := range txs {
- tx := types.NewTransaction(uint64(nonce), common.Address{}, big.NewInt(0), 100000, big.NewInt(0), nil)
- tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
- txs[nonce] = tx
- }
- source.txpool.Add(txs, false, false)
-
- // Iterate through all the sinks and ensure they all got the transactions
- for i := range sinks {
- for arrived, timeout := 0, false; arrived < len(txs) && !timeout; {
- select {
- case event := <-txChs[i]:
- arrived += len(event.Txs)
- case <-time.After(2 * time.Second):
- t.Errorf("sink %d: transaction propagation timed out: have %d, want %d", i, arrived, len(txs))
- timeout = true
- }
- }
- }
-}
-
-// Tests that blocks are broadcast to a sqrt number of peers only.
-func TestBroadcastBlock1Peer(t *testing.T) { testBroadcastBlock(t, 1, 1) }
-func TestBroadcastBlock2Peers(t *testing.T) { testBroadcastBlock(t, 2, 1) }
-func TestBroadcastBlock3Peers(t *testing.T) { testBroadcastBlock(t, 3, 1) }
-func TestBroadcastBlock4Peers(t *testing.T) { testBroadcastBlock(t, 4, 2) }
-func TestBroadcastBlock5Peers(t *testing.T) { testBroadcastBlock(t, 5, 2) }
-func TestBroadcastBlock8Peers(t *testing.T) { testBroadcastBlock(t, 9, 3) }
-func TestBroadcastBlock12Peers(t *testing.T) { testBroadcastBlock(t, 12, 3) }
-func TestBroadcastBlock16Peers(t *testing.T) { testBroadcastBlock(t, 16, 4) }
-func TestBroadcastBloc26Peers(t *testing.T) { testBroadcastBlock(t, 26, 5) }
-func TestBroadcastBlock100Peers(t *testing.T) { testBroadcastBlock(t, 100, 10) }
-
-func testBroadcastBlock(t *testing.T, peers, bcasts int) {
- t.Parallel()
-
- // Create a source handler to broadcast blocks from and a number of sinks
- // to receive them.
- source := newTestHandlerWithBlocks(1)
- defer source.close()
-
- sinks := make([]*testEthHandler, peers)
- for i := 0; i < len(sinks); i++ {
- sinks[i] = new(testEthHandler)
- }
- // Interconnect all the sink handlers with the source handler
- var (
- genesis = source.chain.Genesis()
- td = source.chain.GetTd(genesis.Hash(), genesis.NumberU64())
- )
- for i, sink := range sinks {
- sink := sink // Closure for gorotuine below
-
- sourcePipe, sinkPipe := p2p.MsgPipe()
- defer sourcePipe.Close()
- defer sinkPipe.Close()
-
- sourcePeer := eth.NewPeer(eth.ETH67, p2p.NewPeerPipe(enode.ID{byte(i)}, "", nil, sourcePipe), sourcePipe, nil)
- sinkPeer := eth.NewPeer(eth.ETH67, p2p.NewPeerPipe(enode.ID{0}, "", nil, sinkPipe), sinkPipe, nil)
- defer sourcePeer.Close()
- defer sinkPeer.Close()
-
- go source.handler.runEthPeer(sourcePeer, func(peer *eth.Peer) error {
- return eth.Handle((*ethHandler)(source.handler), peer)
- })
- if err := sinkPeer.Handshake(1, td, genesis.Hash(), genesis.Hash(), forkid.NewIDWithChain(source.chain), forkid.NewFilter(source.chain)); err != nil {
- t.Fatalf("failed to run protocol handshake")
- }
- go eth.Handle(sink, sinkPeer)
- }
- // Subscribe to all the transaction pools
- blockChs := make([]chan *types.Block, len(sinks))
- for i := 0; i < len(sinks); i++ {
- blockChs[i] = make(chan *types.Block, 1)
- defer close(blockChs[i])
-
- sub := sinks[i].blockBroadcasts.Subscribe(blockChs[i])
- defer sub.Unsubscribe()
- }
- // Initiate a block propagation across the peers
- time.Sleep(100 * time.Millisecond)
- header := source.chain.CurrentBlock()
- source.handler.BroadcastBlock(source.chain.GetBlock(header.Hash(), header.Number.Uint64()), true)
-
- // Iterate through all the sinks and ensure the correct number got the block
- done := make(chan struct{}, peers)
- for _, ch := range blockChs {
- ch := ch
- go func() {
- <-ch
- done <- struct{}{}
- }()
- }
- var received int
- for {
- select {
- case <-done:
- received++
-
- case <-time.After(100 * time.Millisecond):
- if received != bcasts {
- t.Errorf("broadcast count mismatch: have %d, want %d", received, bcasts)
- }
- return
- }
- }
-}
-
-// Tests that a propagated malformed block (uncles or transactions don't match
-// with the hashes in the header) gets discarded and not broadcast forward.
-func TestBroadcastMalformedBlock67(t *testing.T) { testBroadcastMalformedBlock(t, eth.ETH67) }
-func TestBroadcastMalformedBlock68(t *testing.T) { testBroadcastMalformedBlock(t, eth.ETH68) }
-
-func testBroadcastMalformedBlock(t *testing.T, protocol uint) {
- t.Parallel()
-
- // Create a source handler to broadcast blocks from and a number of sinks
- // to receive them.
- source := newTestHandlerWithBlocks(1)
- defer source.close()
-
- // Create a source handler to send messages through and a sink peer to receive them
- p2pSrc, p2pSink := p2p.MsgPipe()
- defer p2pSrc.Close()
- defer p2pSink.Close()
-
- src := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pSrc), p2pSrc, source.txpool)
- sink := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pSink), p2pSink, source.txpool)
- defer src.Close()
- defer sink.Close()
-
- go source.handler.runEthPeer(src, func(peer *eth.Peer) error {
- return eth.Handle((*ethHandler)(source.handler), peer)
- })
- // Run the handshake locally to avoid spinning up a sink handler
- var (
- genesis = source.chain.Genesis()
- td = source.chain.GetTd(genesis.Hash(), genesis.NumberU64())
- )
- if err := sink.Handshake(1, td, genesis.Hash(), genesis.Hash(), forkid.NewIDWithChain(source.chain), forkid.NewFilter(source.chain)); err != nil {
- t.Fatalf("failed to run protocol handshake")
- }
- // After the handshake completes, the source handler should stream the sink
- // the blocks, subscribe to inbound network events
- backend := new(testEthHandler)
-
- blocks := make(chan *types.Block, 1)
- sub := backend.blockBroadcasts.Subscribe(blocks)
- defer sub.Unsubscribe()
-
- go eth.Handle(backend, sink)
-
- // Create various combinations of malformed blocks
- head := source.chain.CurrentBlock()
- block := source.chain.GetBlock(head.Hash(), head.Number.Uint64())
-
- malformedUncles := head
- malformedUncles.UncleHash[0]++
- malformedTransactions := head
- malformedTransactions.TxHash[0]++
- malformedEverything := head
- malformedEverything.UncleHash[0]++
- malformedEverything.TxHash[0]++
-
- // Try to broadcast all malformations and ensure they all get discarded
- for _, header := range []*types.Header{malformedUncles, malformedTransactions, malformedEverything} {
- block := types.NewBlockWithHeader(header).WithBody(block.Transactions(), block.Uncles())
- if err := src.SendNewBlock(block, big.NewInt(131136)); err != nil {
- t.Fatalf("failed to broadcast block: %v", err)
- }
- select {
- case <-blocks:
- t.Fatalf("malformed block forwarded")
- case <-time.After(100 * time.Millisecond):
- }
- }
-}
diff --git a/eth/handler_snap.go b/eth/handler_snap.go
deleted file mode 100644
index 767416ffd6..0000000000
--- a/eth/handler_snap.go
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2020 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 .
-
-package eth
-
-import (
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/eth/protocols/snap"
- "github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-// snapHandler implements the snap.Backend interface to handle the various network
-// packets that are sent as replies or broadcasts.
-type snapHandler handler
-
-func (h *snapHandler) Chain() *core.BlockChain { return h.chain }
-
-// RunPeer is invoked when a peer joins on the `snap` protocol.
-func (h *snapHandler) RunPeer(peer *snap.Peer, hand snap.Handler) error {
- return (*handler)(h).runSnapExtension(peer, hand)
-}
-
-// PeerInfo retrieves all known `snap` information about a peer.
-func (h *snapHandler) PeerInfo(id enode.ID) interface{} {
- if p := h.peers.peer(id.String()); p != nil {
- if p.snapExt != nil {
- return p.snapExt.info()
- }
- }
- return nil
-}
-
-// Handle is invoked from a peer's message handler when it receives a new remote
-// message that the handler couldn't consume and serve itself.
-func (h *snapHandler) Handle(peer *snap.Peer, packet snap.Packet) error {
- return h.downloader.DeliverSnapPacket(peer, packet)
-}
diff --git a/eth/handler_test.go b/eth/handler_test.go
deleted file mode 100644
index 6d6132ee4c..0000000000
--- a/eth/handler_test.go
+++ /dev/null
@@ -1,185 +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 .
-
-package eth
-
-import (
- "math/big"
- "sort"
- "sync"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/consensus"
- "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/types"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/eth/downloader"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/params"
-)
-
-var (
- // testKey is a private key to use for funding a tester account.
- testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
-
- // testAddr is the Ethereum address of the tester account.
- testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
-)
-
-// testTxPool is a mock transaction pool that blindly accepts all transactions.
-// Its goal is to get around setting up a valid statedb for the balance and nonce
-// checks.
-type testTxPool struct {
- pool map[common.Hash]*types.Transaction // Hash map of collected transactions
-
- txFeed event.Feed // Notification feed to allow waiting for inclusion
- lock sync.RWMutex // Protects the transaction pool
-}
-
-// newTestTxPool creates a mock transaction pool.
-func newTestTxPool() *testTxPool {
- return &testTxPool{
- pool: make(map[common.Hash]*types.Transaction),
- }
-}
-
-// Has returns an indicator whether txpool has a transaction
-// cached with the given hash.
-func (p *testTxPool) Has(hash common.Hash) bool {
- p.lock.Lock()
- defer p.lock.Unlock()
-
- return p.pool[hash] != nil
-}
-
-// Get retrieves the transaction from local txpool with given
-// tx hash.
-func (p *testTxPool) Get(hash common.Hash) *types.Transaction {
- p.lock.Lock()
- defer p.lock.Unlock()
- return p.pool[hash]
-}
-
-// Add appends a batch of transactions to the pool, and notifies any
-// listeners if the addition channel is non nil
-func (p *testTxPool) Add(txs []*types.Transaction, local bool, sync bool) []error {
- p.lock.Lock()
- defer p.lock.Unlock()
-
- for _, tx := range txs {
- p.pool[tx.Hash()] = tx
- }
- p.txFeed.Send(core.NewTxsEvent{Txs: txs})
- return make([]error, len(txs))
-}
-
-// Pending returns all the transactions known to the pool
-func (p *testTxPool) Pending(enforceTips bool) map[common.Address][]*txpool.LazyTransaction {
- p.lock.RLock()
- defer p.lock.RUnlock()
-
- batches := make(map[common.Address][]*types.Transaction)
- for _, tx := range p.pool {
- from, _ := types.Sender(types.HomesteadSigner{}, tx)
- batches[from] = append(batches[from], tx)
- }
- for _, batch := range batches {
- sort.Sort(types.TxByNonce(batch))
- }
- pending := make(map[common.Address][]*txpool.LazyTransaction)
- for addr, batch := range batches {
- for _, tx := range batch {
- pending[addr] = append(pending[addr], &txpool.LazyTransaction{
- Hash: tx.Hash(),
- Tx: tx,
- Time: tx.Time(),
- GasFeeCap: tx.GasFeeCap(),
- GasTipCap: tx.GasTipCap(),
- Gas: tx.Gas(),
- BlobGas: tx.BlobGas(),
- })
- }
- }
- return pending
-}
-
-// SubscribeTransactions should return an event subscription of NewTxsEvent and
-// send events to the given channel.
-func (p *testTxPool) SubscribeTransactions(ch chan<- core.NewTxsEvent, reorgs bool) event.Subscription {
- return p.txFeed.Subscribe(ch)
-}
-
-// testHandler is a live implementation of the Ethereum protocol handler, just
-// preinitialized with some sane testing defaults and the transaction pool mocked
-// out.
-type testHandler struct {
- db ethdb.Database
- chain *core.BlockChain
- txpool *testTxPool
- handler *handler
-}
-
-// newTestHandler creates a new handler for testing purposes with no blocks.
-func newTestHandler() *testHandler {
- return newTestHandlerWithBlocks(0)
-}
-
-// newTestHandlerWithBlocks creates a new handler for testing purposes, with a
-// given number of initial blocks.
-func newTestHandlerWithBlocks(blocks int) *testHandler {
- // Create a database pre-initialize with a genesis block
- db := rawdb.NewMemoryDatabase()
- gspec := &core.Genesis{
- Config: params.TestChainConfig,
- Alloc: core.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}},
- }
- chain, _ := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
-
- _, bs, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), blocks, nil)
- if _, err := chain.InsertChain(bs); err != nil {
- panic(err)
- }
- txpool := newTestTxPool()
-
- handler, _ := newHandler(&handlerConfig{
- Database: db,
- Chain: chain,
- TxPool: txpool,
- Merger: consensus.NewMerger(rawdb.NewMemoryDatabase()),
- Network: 1,
- Sync: downloader.SnapSync,
- BloomCache: 1,
- })
- handler.Start(1000)
-
- return &testHandler{
- db: db,
- chain: chain,
- txpool: txpool,
- handler: handler,
- }
-}
-
-// close tears down the handler and all its internal constructs.
-func (b *testHandler) close() {
- b.handler.Stop()
- b.chain.Stop()
-}
diff --git a/eth/peer.go b/eth/peer.go
deleted file mode 100644
index 7618777716..0000000000
--- a/eth/peer.go
+++ /dev/null
@@ -1,59 +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 .
-
-package eth
-
-import (
- "github.com/ethereum/go-ethereum/eth/protocols/eth"
- "github.com/ethereum/go-ethereum/eth/protocols/snap"
-)
-
-// ethPeerInfo represents a short summary of the `eth` sub-protocol metadata known
-// about a connected peer.
-type ethPeerInfo struct {
- Version uint `json:"version"` // Ethereum protocol version negotiated
-}
-
-// ethPeer is a wrapper around eth.Peer to maintain a few extra metadata.
-type ethPeer struct {
- *eth.Peer
- snapExt *snapPeer // Satellite `snap` connection
-}
-
-// info gathers and returns some `eth` protocol metadata known about a peer.
-func (p *ethPeer) info() *ethPeerInfo {
- return ðPeerInfo{
- Version: p.Version(),
- }
-}
-
-// snapPeerInfo represents a short summary of the `snap` sub-protocol metadata known
-// about a connected peer.
-type snapPeerInfo struct {
- Version uint `json:"version"` // Snapshot protocol version negotiated
-}
-
-// snapPeer is a wrapper around snap.Peer to maintain a few extra metadata.
-type snapPeer struct {
- *snap.Peer
-}
-
-// info gathers and returns some `snap` protocol metadata known about a peer.
-func (p *snapPeer) info() *snapPeerInfo {
- return &snapPeerInfo{
- Version: p.Version(),
- }
-}
diff --git a/eth/peerset.go b/eth/peerset.go
deleted file mode 100644
index b27d3964a1..0000000000
--- a/eth/peerset.go
+++ /dev/null
@@ -1,260 +0,0 @@
-// Copyright 2020 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 .
-
-package eth
-
-import (
- "errors"
- "fmt"
- "math/big"
- "sync"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/eth/protocols/eth"
- "github.com/ethereum/go-ethereum/eth/protocols/snap"
- "github.com/ethereum/go-ethereum/p2p"
-)
-
-var (
- // errPeerSetClosed is returned if a peer is attempted to be added or removed
- // from the peer set after it has been terminated.
- errPeerSetClosed = errors.New("peerset closed")
-
- // errPeerAlreadyRegistered is returned if a peer is attempted to be added
- // to the peer set, but one with the same id already exists.
- errPeerAlreadyRegistered = errors.New("peer already registered")
-
- // errPeerNotRegistered is returned if a peer is attempted to be removed from
- // a peer set, but no peer with the given id exists.
- errPeerNotRegistered = errors.New("peer not registered")
-
- // errSnapWithoutEth is returned if a peer attempts to connect only on the
- // snap protocol without advertising the eth main protocol.
- errSnapWithoutEth = errors.New("peer connected on snap without compatible eth support")
-)
-
-// peerSet represents the collection of active peers currently participating in
-// the `eth` protocol, with or without the `snap` extension.
-type peerSet struct {
- peers map[string]*ethPeer // Peers connected on the `eth` protocol
- snapPeers int // Number of `snap` compatible peers for connection prioritization
-
- snapWait map[string]chan *snap.Peer // Peers connected on `eth` waiting for their snap extension
- snapPend map[string]*snap.Peer // Peers connected on the `snap` protocol, but not yet on `eth`
-
- lock sync.RWMutex
- closed bool
-}
-
-// newPeerSet creates a new peer set to track the active participants.
-func newPeerSet() *peerSet {
- return &peerSet{
- peers: make(map[string]*ethPeer),
- snapWait: make(map[string]chan *snap.Peer),
- snapPend: make(map[string]*snap.Peer),
- }
-}
-
-// registerSnapExtension unblocks an already connected `eth` peer waiting for its
-// `snap` extension, or if no such peer exists, tracks the extension for the time
-// being until the `eth` main protocol starts looking for it.
-func (ps *peerSet) registerSnapExtension(peer *snap.Peer) error {
- // Reject the peer if it advertises `snap` without `eth` as `snap` is only a
- // satellite protocol meaningful with the chain selection of `eth`
- if !peer.RunningCap(eth.ProtocolName, eth.ProtocolVersions) {
- return fmt.Errorf("%w: have %v", errSnapWithoutEth, peer.Caps())
- }
- // Ensure nobody can double connect
- ps.lock.Lock()
- defer ps.lock.Unlock()
-
- id := peer.ID()
- if _, ok := ps.peers[id]; ok {
- return errPeerAlreadyRegistered // avoid connections with the same id as existing ones
- }
- if _, ok := ps.snapPend[id]; ok {
- return errPeerAlreadyRegistered // avoid connections with the same id as pending ones
- }
- // Inject the peer into an `eth` counterpart is available, otherwise save for later
- if wait, ok := ps.snapWait[id]; ok {
- delete(ps.snapWait, id)
- wait <- peer
- return nil
- }
- ps.snapPend[id] = peer
- return nil
-}
-
-// waitExtensions blocks until all satellite protocols are connected and tracked
-// by the peerset.
-func (ps *peerSet) waitSnapExtension(peer *eth.Peer) (*snap.Peer, error) {
- // If the peer does not support a compatible `snap`, don't wait
- if !peer.RunningCap(snap.ProtocolName, snap.ProtocolVersions) {
- return nil, nil
- }
- // Ensure nobody can double connect
- ps.lock.Lock()
-
- id := peer.ID()
- if _, ok := ps.peers[id]; ok {
- ps.lock.Unlock()
- return nil, errPeerAlreadyRegistered // avoid connections with the same id as existing ones
- }
- if _, ok := ps.snapWait[id]; ok {
- ps.lock.Unlock()
- return nil, errPeerAlreadyRegistered // avoid connections with the same id as pending ones
- }
- // If `snap` already connected, retrieve the peer from the pending set
- if snap, ok := ps.snapPend[id]; ok {
- delete(ps.snapPend, id)
-
- ps.lock.Unlock()
- return snap, nil
- }
- // Otherwise wait for `snap` to connect concurrently
- wait := make(chan *snap.Peer)
- ps.snapWait[id] = wait
- ps.lock.Unlock()
-
- return <-wait, nil
-}
-
-// registerPeer injects a new `eth` peer into the working set, or returns an error
-// if the peer is already known.
-func (ps *peerSet) registerPeer(peer *eth.Peer, ext *snap.Peer) error {
- // Start tracking the new peer
- ps.lock.Lock()
- defer ps.lock.Unlock()
-
- if ps.closed {
- return errPeerSetClosed
- }
- id := peer.ID()
- if _, ok := ps.peers[id]; ok {
- return errPeerAlreadyRegistered
- }
- eth := ðPeer{
- Peer: peer,
- }
- if ext != nil {
- eth.snapExt = &snapPeer{ext}
- ps.snapPeers++
- }
- ps.peers[id] = eth
- return nil
-}
-
-// unregisterPeer removes a remote peer from the active set, disabling any further
-// actions to/from that particular entity.
-func (ps *peerSet) unregisterPeer(id string) error {
- ps.lock.Lock()
- defer ps.lock.Unlock()
-
- peer, ok := ps.peers[id]
- if !ok {
- return errPeerNotRegistered
- }
- delete(ps.peers, id)
- if peer.snapExt != nil {
- ps.snapPeers--
- }
- return nil
-}
-
-// peer retrieves the registered peer with the given id.
-func (ps *peerSet) peer(id string) *ethPeer {
- ps.lock.RLock()
- defer ps.lock.RUnlock()
-
- return ps.peers[id]
-}
-
-// peersWithoutBlock retrieves a list of peers that do not have a given block in
-// their set of known hashes so it might be propagated to them.
-func (ps *peerSet) peersWithoutBlock(hash common.Hash) []*ethPeer {
- ps.lock.RLock()
- defer ps.lock.RUnlock()
-
- list := make([]*ethPeer, 0, len(ps.peers))
- for _, p := range ps.peers {
- if !p.KnownBlock(hash) {
- list = append(list, p)
- }
- }
- return list
-}
-
-// peersWithoutTransaction retrieves a list of peers that do not have a given
-// transaction in their set of known hashes.
-func (ps *peerSet) peersWithoutTransaction(hash common.Hash) []*ethPeer {
- ps.lock.RLock()
- defer ps.lock.RUnlock()
-
- list := make([]*ethPeer, 0, len(ps.peers))
- for _, p := range ps.peers {
- if !p.KnownTransaction(hash) {
- list = append(list, p)
- }
- }
- return list
-}
-
-// len returns if the current number of `eth` peers in the set. Since the `snap`
-// peers are tied to the existence of an `eth` connection, that will always be a
-// subset of `eth`.
-func (ps *peerSet) len() int {
- ps.lock.RLock()
- defer ps.lock.RUnlock()
-
- return len(ps.peers)
-}
-
-// snapLen returns if the current number of `snap` peers in the set.
-func (ps *peerSet) snapLen() int {
- ps.lock.RLock()
- defer ps.lock.RUnlock()
-
- return ps.snapPeers
-}
-
-// peerWithHighestTD retrieves the known peer with the currently highest total
-// difficulty, but below the given PoS switchover threshold.
-func (ps *peerSet) peerWithHighestTD() *eth.Peer {
- ps.lock.RLock()
- defer ps.lock.RUnlock()
-
- var (
- bestPeer *eth.Peer
- bestTd *big.Int
- )
- for _, p := range ps.peers {
- if _, td := p.Head(); bestPeer == nil || td.Cmp(bestTd) > 0 {
- bestPeer, bestTd = p.Peer, td
- }
- }
- return bestPeer
-}
-
-// close disconnects all peers.
-func (ps *peerSet) close() {
- ps.lock.Lock()
- defer ps.lock.Unlock()
-
- for _, p := range ps.peers {
- p.Disconnect(p2p.DiscQuitting)
- }
- ps.closed = true
-}
diff --git a/eth/state_accessor.go b/eth/state_accessor.go
deleted file mode 100644
index 24694df66c..0000000000
--- a/eth/state_accessor.go
+++ /dev/null
@@ -1,259 +0,0 @@
-// Copyright 2021 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 .
-
-package eth
-
-import (
- "context"
- "errors"
- "fmt"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/eth/tracers"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/trie"
-)
-
-// noopReleaser is returned in case there is no operation expected
-// for releasing state.
-var noopReleaser = tracers.StateReleaseFunc(func() {})
-
-func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (statedb *state.StateDB, release tracers.StateReleaseFunc, err error) {
- var (
- current *types.Block
- database state.Database
- triedb *trie.Database
- report = true
- origin = block.NumberU64()
- )
- // The state is only for reading purposes, check the state presence in
- // live database.
- if readOnly {
- // The state is available in live database, create a reference
- // on top to prevent garbage collection and return a release
- // function to deref it.
- if statedb, err = eth.blockchain.StateAt(block.Root()); err == nil {
- eth.blockchain.TrieDB().Reference(block.Root(), common.Hash{})
- return statedb, func() {
- eth.blockchain.TrieDB().Dereference(block.Root())
- }, nil
- }
- }
- // The state is both for reading and writing, or it's unavailable in disk,
- // try to construct/recover the state over an ephemeral trie.Database for
- // isolating the live one.
- if base != nil {
- if preferDisk {
- // Create an ephemeral trie.Database for isolating the live one. Otherwise
- // the internal junks created by tracing will be persisted into the disk.
- // TODO(rjl493456442), clean cache is disabled to prevent memory leak,
- // please re-enable it for better performance.
- database = state.NewDatabaseWithConfig(eth.chainDb, trie.HashDefaults)
- if statedb, err = state.New(block.Root(), database, nil); err == nil {
- log.Info("Found disk backend for state trie", "root", block.Root(), "number", block.Number())
- return statedb, noopReleaser, nil
- }
- }
- // The optional base statedb is given, mark the start point as parent block
- statedb, database, triedb, report = base, base.Database(), base.Database().TrieDB(), false
- current = eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
- } else {
- // Otherwise, try to reexec blocks until we find a state or reach our limit
- current = block
-
- // Create an ephemeral trie.Database for isolating the live one. Otherwise
- // the internal junks created by tracing will be persisted into the disk.
- // TODO(rjl493456442), clean cache is disabled to prevent memory leak,
- // please re-enable it for better performance.
- triedb = trie.NewDatabase(eth.chainDb, trie.HashDefaults)
- database = state.NewDatabaseWithNodeDB(eth.chainDb, triedb)
-
- // If we didn't check the live database, do check state over ephemeral database,
- // otherwise we would rewind past a persisted block (specific corner case is
- // chain tracing from the genesis).
- if !readOnly {
- statedb, err = state.New(current.Root(), database, nil)
- if err == nil {
- return statedb, noopReleaser, nil
- }
- }
- // Database does not have the state for the given block, try to regenerate
- for i := uint64(0); i < reexec; i++ {
- if err := ctx.Err(); err != nil {
- return nil, nil, err
- }
- if current.NumberU64() == 0 {
- return nil, nil, errors.New("genesis state is missing")
- }
- parent := eth.blockchain.GetBlock(current.ParentHash(), current.NumberU64()-1)
- if parent == nil {
- return nil, nil, fmt.Errorf("missing block %v %d", current.ParentHash(), current.NumberU64()-1)
- }
- current = parent
-
- statedb, err = state.New(current.Root(), database, nil)
- if err == nil {
- break
- }
- }
- if err != nil {
- switch err.(type) {
- case *trie.MissingNodeError:
- return nil, nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec)
- default:
- return nil, nil, err
- }
- }
- }
- // State is available at historical point, re-execute the blocks on top for
- // the desired state.
- var (
- start = time.Now()
- logged time.Time
- parent common.Hash
- )
- for current.NumberU64() < origin {
- if err := ctx.Err(); err != nil {
- return nil, nil, err
- }
- // Print progress logs if long enough time elapsed
- if time.Since(logged) > 8*time.Second && report {
- log.Info("Regenerating historical state", "block", current.NumberU64()+1, "target", origin, "remaining", origin-current.NumberU64()-1, "elapsed", time.Since(start))
- logged = time.Now()
- }
- // Retrieve the next block to regenerate and process it
- next := current.NumberU64() + 1
- if current = eth.blockchain.GetBlockByNumber(next); current == nil {
- return nil, nil, fmt.Errorf("block #%d not found", next)
- }
- _, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{})
- if err != nil {
- return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err)
- }
- // Finalize the state so any modifications are written to the trie
- root, err := statedb.Commit(current.NumberU64(), eth.blockchain.Config().IsEIP158(current.Number()))
- if err != nil {
- return nil, nil, fmt.Errorf("stateAtBlock commit failed, number %d root %v: %w",
- current.NumberU64(), current.Root().Hex(), err)
- }
- statedb, err = state.New(root, database, nil)
- if err != nil {
- return nil, nil, fmt.Errorf("state reset after block %d failed: %v", current.NumberU64(), err)
- }
- // Hold the state reference and also drop the parent state
- // to prevent accumulating too many nodes in memory.
- triedb.Reference(root, common.Hash{})
- if parent != (common.Hash{}) {
- triedb.Dereference(parent)
- }
- parent = root
- }
- if report {
- _, nodes, imgs := triedb.Size() // all memory is contained within the nodes return in hashdb
- log.Info("Historical state regenerated", "block", current.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs)
- }
- return statedb, func() { triedb.Dereference(block.Root()) }, nil
-}
-
-func (eth *Ethereum) pathState(block *types.Block) (*state.StateDB, func(), error) {
- // Check if the requested state is available in the live chain.
- statedb, err := eth.blockchain.StateAt(block.Root())
- if err == nil {
- return statedb, noopReleaser, nil
- }
- // TODO historic state is not supported in path-based scheme.
- // Fully archive node in pbss will be implemented by relying
- // on state history, but needs more work on top.
- return nil, nil, errors.New("historical state not available in path scheme yet")
-}
-
-// stateAtBlock retrieves the state database associated with a certain block.
-// If no state is locally available for the given block, a number of blocks
-// are attempted to be reexecuted to generate the desired state. The optional
-// base layer statedb can be provided which is regarded as the statedb of the
-// parent block.
-//
-// An additional release function will be returned if the requested state is
-// available. Release is expected to be invoked when the returned state is no
-// longer needed. Its purpose is to prevent resource leaking. Though it can be
-// noop in some cases.
-//
-// Parameters:
-// - block: The block for which we want the state(state = block.Root)
-// - reexec: The maximum number of blocks to reprocess trying to obtain the desired state
-// - base: If the caller is tracing multiple blocks, the caller can provide the parent
-// state continuously from the callsite.
-// - readOnly: If true, then the live 'blockchain' state database is used. No mutation should
-// be made from caller, e.g. perform Commit or other 'save-to-disk' changes.
-// Otherwise, the trash generated by caller may be persisted permanently.
-// - preferDisk: This arg can be used by the caller to signal that even though the 'base' is
-// provided, it would be preferable to start from a fresh state, if we have it
-// on disk.
-func (eth *Ethereum) stateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (statedb *state.StateDB, release tracers.StateReleaseFunc, err error) {
- if eth.blockchain.TrieDB().Scheme() == rawdb.HashScheme {
- return eth.hashState(ctx, block, reexec, base, readOnly, preferDisk)
- }
- return eth.pathState(block)
-}
-
-// stateAtTransaction returns the execution environment of a certain transaction.
-func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
- // Short circuit if it's genesis block.
- if block.NumberU64() == 0 {
- return nil, vm.BlockContext{}, nil, nil, errors.New("no transaction in genesis")
- }
- // Create the parent state database
- parent := eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
- if parent == nil {
- return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("parent %#x not found", block.ParentHash())
- }
- // Lookup the statedb of parent block from the live database,
- // otherwise regenerate it on the flight.
- statedb, release, err := eth.stateAtBlock(ctx, parent, reexec, nil, true, false)
- if err != nil {
- return nil, vm.BlockContext{}, nil, nil, err
- }
- if txIndex == 0 && len(block.Transactions()) == 0 {
- return nil, vm.BlockContext{}, statedb, release, nil
- }
- // Recompute transactions up to the target index.
- signer := types.MakeSigner(eth.blockchain.Config(), block.Number(), block.Time())
- for idx, tx := range block.Transactions() {
- // Assemble the transaction call message and return if the requested offset
- msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
- txContext := core.NewEVMTxContext(msg)
- context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
- if idx == txIndex {
- return msg, context, statedb, release, nil
- }
- // Not yet the searched for transaction, execute on top of the current state
- vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{})
- statedb.SetTxContext(tx.Hash(), idx)
- if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
- return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
- }
- // Ensure any modifications are committed to the state
- // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
- statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
- }
- return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash())
-}
diff --git a/eth/sync.go b/eth/sync.go
deleted file mode 100644
index c7ba7c93d6..0000000000
--- a/eth/sync.go
+++ /dev/null
@@ -1,269 +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 .
-
-package eth
-
-import (
- "errors"
- "math/big"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/eth/downloader"
- "github.com/ethereum/go-ethereum/eth/protocols/eth"
- "github.com/ethereum/go-ethereum/log"
-)
-
-const (
- forceSyncCycle = 10 * time.Second // Time interval to force syncs, even if few peers are available
- defaultMinSyncPeers = 5 // Amount of peers desired to start syncing
-)
-
-// syncTransactions starts sending all currently pending transactions to the given peer.
-func (h *handler) syncTransactions(p *eth.Peer) {
- var hashes []common.Hash
- for _, batch := range h.txpool.Pending(false) {
- for _, tx := range batch {
- hashes = append(hashes, tx.Hash)
- }
- }
- if len(hashes) == 0 {
- return
- }
- p.AsyncSendPooledTransactionHashes(hashes)
-}
-
-// chainSyncer coordinates blockchain sync components.
-type chainSyncer struct {
- handler *handler
- force *time.Timer
- forced bool // true when force timer fired
- warned time.Time
- peerEventCh chan struct{}
- doneCh chan error // non-nil when sync is running
-}
-
-// chainSyncOp is a scheduled sync operation.
-type chainSyncOp struct {
- mode downloader.SyncMode
- peer *eth.Peer
- td *big.Int
- head common.Hash
-}
-
-// newChainSyncer creates a chainSyncer.
-func newChainSyncer(handler *handler) *chainSyncer {
- return &chainSyncer{
- handler: handler,
- peerEventCh: make(chan struct{}),
- }
-}
-
-// handlePeerEvent notifies the syncer about a change in the peer set.
-// This is called for new peers and every time a peer announces a new
-// chain head.
-func (cs *chainSyncer) handlePeerEvent() bool {
- select {
- case cs.peerEventCh <- struct{}{}:
- return true
- case <-cs.handler.quitSync:
- return false
- }
-}
-
-// loop runs in its own goroutine and launches the sync when necessary.
-func (cs *chainSyncer) loop() {
- defer cs.handler.wg.Done()
-
- cs.handler.blockFetcher.Start()
- cs.handler.txFetcher.Start()
- defer cs.handler.blockFetcher.Stop()
- defer cs.handler.txFetcher.Stop()
- defer cs.handler.downloader.Terminate()
-
- // The force timer lowers the peer count threshold down to one when it fires.
- // This ensures we'll always start sync even if there aren't enough peers.
- cs.force = time.NewTimer(forceSyncCycle)
- defer cs.force.Stop()
-
- for {
- if op := cs.nextSyncOp(); op != nil {
- cs.startSync(op)
- }
- select {
- case <-cs.peerEventCh:
- // Peer information changed, recheck.
- case err := <-cs.doneCh:
- cs.doneCh = nil
- cs.force.Reset(forceSyncCycle)
- cs.forced = false
-
- // If we've reached the merge transition but no beacon client is available, or
- // it has not yet switched us over, keep warning the user that their infra is
- // potentially flaky.
- if errors.Is(err, downloader.ErrMergeTransition) && time.Since(cs.warned) > 10*time.Second {
- log.Warn("Local chain is post-merge, waiting for beacon client sync switch-over...")
- cs.warned = time.Now()
- }
- case <-cs.force.C:
- cs.forced = true
-
- case <-cs.handler.quitSync:
- // Disable all insertion on the blockchain. This needs to happen before
- // terminating the downloader because the downloader waits for blockchain
- // inserts, and these can take a long time to finish.
- cs.handler.chain.StopInsert()
- cs.handler.downloader.Terminate()
- if cs.doneCh != nil {
- <-cs.doneCh
- }
- return
- }
- }
-}
-
-// nextSyncOp determines whether sync is required at this time.
-func (cs *chainSyncer) nextSyncOp() *chainSyncOp {
- if cs.doneCh != nil {
- return nil // Sync already running
- }
- // If a beacon client once took over control, disable the entire legacy sync
- // path from here on end. Note, there is a slight "race" between reaching TTD
- // and the beacon client taking over. The downloader will enforce that nothing
- // above the first TTD will be delivered to the chain for import.
- //
- // An alternative would be to check the local chain for exceeding the TTD and
- // avoid triggering a sync in that case, but that could also miss sibling or
- // other family TTD block being accepted.
- if cs.handler.chain.Config().TerminalTotalDifficultyPassed || cs.handler.merger.TDDReached() {
- return nil
- }
- // Ensure we're at minimum peer count.
- minPeers := defaultMinSyncPeers
- if cs.forced {
- minPeers = 1
- } else if minPeers > cs.handler.maxPeers {
- minPeers = cs.handler.maxPeers
- }
- if cs.handler.peers.len() < minPeers {
- return nil
- }
- // We have enough peers, pick the one with the highest TD, but avoid going
- // over the terminal total difficulty. Above that we expect the consensus
- // clients to direct the chain head to sync to.
- peer := cs.handler.peers.peerWithHighestTD()
- if peer == nil {
- return nil
- }
- mode, ourTD := cs.modeAndLocalHead()
- op := peerToSyncOp(mode, peer)
- if op.td.Cmp(ourTD) <= 0 {
- // We seem to be in sync according to the legacy rules. In the merge
- // world, it can also mean we're stuck on the merge block, waiting for
- // a beacon client. In the latter case, notify the user.
- if ttd := cs.handler.chain.Config().TerminalTotalDifficulty; ttd != nil && ourTD.Cmp(ttd) >= 0 && time.Since(cs.warned) > 10*time.Second {
- log.Warn("Local chain is post-merge, waiting for beacon client sync switch-over...")
- cs.warned = time.Now()
- }
- return nil // We're in sync
- }
- return op
-}
-
-func peerToSyncOp(mode downloader.SyncMode, p *eth.Peer) *chainSyncOp {
- peerHead, peerTD := p.Head()
- return &chainSyncOp{mode: mode, peer: p, td: peerTD, head: peerHead}
-}
-
-func (cs *chainSyncer) modeAndLocalHead() (downloader.SyncMode, *big.Int) {
- // If we're in snap sync mode, return that directly
- if cs.handler.snapSync.Load() {
- block := cs.handler.chain.CurrentSnapBlock()
- td := cs.handler.chain.GetTd(block.Hash(), block.Number.Uint64())
- return downloader.SnapSync, td
- }
- // We are probably in full sync, but we might have rewound to before the
- // snap sync pivot, check if we should re-enable snap sync.
- head := cs.handler.chain.CurrentBlock()
- if pivot := rawdb.ReadLastPivotNumber(cs.handler.database); pivot != nil {
- if head.Number.Uint64() < *pivot {
- block := cs.handler.chain.CurrentSnapBlock()
- td := cs.handler.chain.GetTd(block.Hash(), block.Number.Uint64())
- return downloader.SnapSync, td
- }
- }
- // We are in a full sync, but the associated head state is missing. To complete
- // the head state, forcefully rerun the snap sync. Note it doesn't mean the
- // persistent state is corrupted, just mismatch with the head block.
- if !cs.handler.chain.HasState(head.Root) {
- block := cs.handler.chain.CurrentSnapBlock()
- td := cs.handler.chain.GetTd(block.Hash(), block.Number.Uint64())
- log.Info("Reenabled snap sync as chain is stateless")
- return downloader.SnapSync, td
- }
- // Nope, we're really full syncing
- td := cs.handler.chain.GetTd(head.Hash(), head.Number.Uint64())
- return downloader.FullSync, td
-}
-
-// startSync launches doSync in a new goroutine.
-func (cs *chainSyncer) startSync(op *chainSyncOp) {
- cs.doneCh = make(chan error, 1)
- go func() { cs.doneCh <- cs.handler.doSync(op) }()
-}
-
-// doSync synchronizes the local blockchain with a remote peer.
-func (h *handler) doSync(op *chainSyncOp) error {
- if op.mode == downloader.SnapSync {
- // Before launch the snap sync, we have to ensure user uses the same
- // txlookup limit.
- // The main concern here is: during the snap sync Geth won't index the
- // block(generate tx indices) before the HEAD-limit. But if user changes
- // the limit in the next snap sync(e.g. user kill Geth manually and
- // restart) then it will be hard for Geth to figure out the oldest block
- // has been indexed. So here for the user-experience wise, it's non-optimal
- // that user can't change limit during the snap sync. If changed, Geth
- // will just blindly use the original one.
- limit := h.chain.TxLookupLimit()
- if stored := rawdb.ReadFastTxLookupLimit(h.database); stored == nil {
- rawdb.WriteFastTxLookupLimit(h.database, limit)
- } else if *stored != limit {
- h.chain.SetTxLookupLimit(*stored)
- log.Warn("Update txLookup limit", "provided", limit, "updated", *stored)
- }
- }
- // Run the sync cycle, and disable snap sync if we're past the pivot block
- err := h.downloader.LegacySync(op.peer.ID(), op.head, op.td, h.chain.Config().TerminalTotalDifficulty, op.mode)
- if err != nil {
- return err
- }
- h.enableSyncedFeatures()
-
- head := h.chain.CurrentBlock()
- if head.Number.Uint64() > 0 {
- // We've completed a sync cycle, notify all peers of new state. This path is
- // essential in star-topology networks where a gateway node needs to notify
- // all its out-of-date peers of the availability of a new block. This failure
- // scenario will most often crop up in private and hackathon networks with
- // degenerate connectivity, but it should be healthy for the mainnet too to
- // more reliably update peers or the local TD state.
- if block := h.chain.GetBlock(head.Hash(), head.Number.Uint64()); block != nil {
- h.BroadcastBlock(block, false)
- }
- }
- return nil
-}