// 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 ( "crypto/ecdsa" "math/big" "sort" "sync" "testing" "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/trie" "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" "github.com/holiman/uint256" ) 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 reannoTxFeed event.Feed // Notification feed to trigger reannouce 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)) } // ReannouceTransactions announce the transactions to some peers. func (p *testTxPool) ReannouceTransactions(txs []*types.Transaction) []error { p.lock.Lock() defer p.lock.Unlock() for _, tx := range txs { p.pool[tx.Hash()] = tx } p.reannoTxFeed.Send(core.ReannoTxsEvent{Txs: txs}) return make([]error, len(txs)) } // Pending returns all the transactions known to the pool func (p *testTxPool) Pending(filter txpool.PendingFilter) 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: uint256.MustFromBig(tx.GasFeeCap()), GasTipCap: uint256.MustFromBig(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) } // SubscribeReannoTxsEvent should return an event subscription of ReannoTxsEvent and // send events to the given channel. func (p *testTxPool) SubscribeReannoTxsEvent(ch chan<- core.ReannoTxsEvent) event.Subscription { return p.reannoTxFeed.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 votepool *testVotePool 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: types.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() votepool := newTestVotePool() handler, _ := newHandler(&handlerConfig{ Database: db, Chain: chain, TxPool: txpool, Merger: consensus.NewMerger(rawdb.NewMemoryDatabase()), VotePool: votepool, Network: 1, Sync: downloader.SnapSync, BloomCache: 1, }) handler.Start(1000, 3) return &testHandler{ db: db, chain: chain, txpool: txpool, votepool: votepool, handler: handler, } } type mockParlia struct { consensus.Engine } func (c *mockParlia) Author(header *types.Header) (common.Address, error) { return header.Coinbase, nil } func (c *mockParlia) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { return nil } func (c *mockParlia) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header) error { return nil } func (c *mockParlia) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header) (chan<- struct{}, <-chan error) { abort := make(chan<- struct{}) results := make(chan error, len(headers)) for i := 0; i < len(headers); i++ { results <- nil } return abort, results } func (c *mockParlia) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, _ *[]*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal, _ *[]*types.Receipt, _ *[]*types.Transaction, _ *uint64) (err error) { return } func (c *mockParlia) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt, withdrawals []*types.Withdrawal) (*types.Block, []*types.Receipt, error) { // Finalize block c.Finalize(chain, header, state, &txs, uncles, nil, nil, nil, nil) // Assign the final state root to header. header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) // Header seems complete, assemble into a block and return return types.NewBlock(header, txs, uncles, receipts, trie.NewStackTrie(nil)), receipts, nil } func (c *mockParlia) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { return big.NewInt(1) } func newTestParliaHandlerAfterCancun(t *testing.T, config *params.ChainConfig, mode downloader.SyncMode, preCancunBlks, postCancunBlks uint64) *testHandler { // Have N headers in the freezer frdir := t.TempDir() db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false, false, false, false) if err != nil { t.Fatalf("failed to create database with ancient backend") } gspec := &core.Genesis{ Config: config, Alloc: types.GenesisAlloc{testAddr: {Balance: new(big.Int).SetUint64(10 * params.Ether)}}, } engine := &mockParlia{} chain, _ := core.NewBlockChain(db, nil, gspec, nil, engine, vm.Config{}, nil, nil) signer := types.LatestSigner(config) _, bs, _ := core.GenerateChainWithGenesis(gspec, engine, int(preCancunBlks+postCancunBlks), func(i int, gen *core.BlockGen) { if !config.IsCancun(gen.Number(), gen.Timestamp()) { tx, _ := makeMockTx(config, signer, testKey, gen.TxNonce(testAddr), gen.BaseFee().Uint64(), eip4844.CalcBlobFee(gen.ExcessBlobGas()).Uint64(), false) gen.AddTxWithChain(chain, tx) return } tx, sidecar := makeMockTx(config, signer, testKey, gen.TxNonce(testAddr), gen.BaseFee().Uint64(), eip4844.CalcBlobFee(gen.ExcessBlobGas()).Uint64(), true) gen.AddTxWithChain(chain, tx) gen.AddBlobSidecar(&types.BlobSidecar{ BlobTxSidecar: *sidecar, TxIndex: 0, TxHash: tx.Hash(), }) }) if _, err := chain.InsertChain(bs); err != nil { panic(err) } txpool := newTestTxPool() votepool := newTestVotePool() handler, _ := newHandler(&handlerConfig{ Database: db, Chain: chain, TxPool: txpool, Merger: consensus.NewMerger(rawdb.NewMemoryDatabase()), VotePool: votepool, Network: 1, Sync: mode, BloomCache: 1, }) handler.Start(1000, 3) return &testHandler{ db: db, chain: chain, txpool: txpool, votepool: votepool, handler: handler, } } // close tears down the handler and all its internal constructs. func (b *testHandler) close() { b.handler.Stop() b.chain.Stop() } // newTestVotePool creates a mock vote pool. type testVotePool struct { pool map[common.Hash]*types.VoteEnvelope // Hash map of collected votes voteFeed event.Feed // Notification feed to allow waiting for inclusion lock sync.RWMutex // Protects the vote pool } // newTestVotePool creates a mock vote pool. func newTestVotePool() *testVotePool { return &testVotePool{ pool: make(map[common.Hash]*types.VoteEnvelope), } } func (t *testVotePool) PutVote(vote *types.VoteEnvelope) { t.lock.Lock() defer t.lock.Unlock() t.pool[vote.Hash()] = vote t.voteFeed.Send(core.NewVoteEvent{Vote: vote}) } func (t *testVotePool) FetchVoteByBlockHash(blockHash common.Hash) []*types.VoteEnvelope { panic("implement me") } func (t *testVotePool) GetVotes() []*types.VoteEnvelope { t.lock.RLock() defer t.lock.RUnlock() votes := make([]*types.VoteEnvelope, 0, len(t.pool)) for _, vote := range t.pool { votes = append(votes, vote) } return votes } func (t *testVotePool) SubscribeNewVoteEvent(ch chan<- core.NewVoteEvent) event.Subscription { return t.voteFeed.Subscribe(ch) } var ( emptyBlob = kzg4844.Blob{} emptyBlobCommit, _ = kzg4844.BlobToCommitment(emptyBlob) emptyBlobProof, _ = kzg4844.ComputeBlobProof(emptyBlob, emptyBlobCommit) ) func makeMockTx(config *params.ChainConfig, signer types.Signer, key *ecdsa.PrivateKey, nonce uint64, baseFee uint64, blobBaseFee uint64, isBlobTx bool) (*types.Transaction, *types.BlobTxSidecar) { if !isBlobTx { raw := &types.DynamicFeeTx{ ChainID: config.ChainID, Nonce: nonce, GasTipCap: big.NewInt(10), GasFeeCap: new(big.Int).SetUint64(baseFee + 10), Gas: params.TxGas, To: &common.Address{0x00}, Value: big.NewInt(0), } tx, _ := types.SignTx(types.NewTx(raw), signer, key) return tx, nil } sidecar := &types.BlobTxSidecar{ Blobs: []kzg4844.Blob{emptyBlob, emptyBlob}, Commitments: []kzg4844.Commitment{emptyBlobCommit, emptyBlobCommit}, Proofs: []kzg4844.Proof{emptyBlobProof, emptyBlobProof}, } raw := &types.BlobTx{ ChainID: uint256.MustFromBig(config.ChainID), Nonce: nonce, GasTipCap: uint256.NewInt(10), GasFeeCap: uint256.NewInt(baseFee + 10), Gas: params.TxGas, To: common.Address{0x00}, Value: uint256.NewInt(0), BlobFeeCap: uint256.NewInt(blobBaseFee), BlobHashes: sidecar.BlobHashes(), } tx, _ := types.SignTx(types.NewTx(raw), signer, key) return tx, sidecar }