diff --git a/cmd/devp2p/internal/ethtest/chain.go b/cmd/devp2p/internal/ethtest/chain.go index 8c6d5cb836..6f813879ea 100644 --- a/cmd/devp2p/internal/ethtest/chain.go +++ b/cmd/devp2p/internal/ethtest/chain.go @@ -143,7 +143,11 @@ func (c *Chain) ForkID() forkid.ID { // TD calculates the total difficulty of the chain at the // chain head. func (c *Chain) TD() *big.Int { - return new(big.Int) + sum := new(big.Int) + for _, block := range c.blocks[:c.Len()] { + sum.Add(sum, block.Difficulty()) + } + return sum } // GetBlock returns the block at the specified number. diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 3f5458cb85..320ffeeee9 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -20,6 +20,7 @@ import ( "context" "crypto/rand" "fmt" + "math/big" "reflect" "sync" "time" @@ -77,6 +78,7 @@ func (s *Suite) EthTests() []utesting.Test { {Name: "GetBlockBodies", Fn: s.TestGetBlockBodies}, // // malicious handshakes + status {Name: "MaliciousHandshake", Fn: s.TestMaliciousHandshake}, + {Name: "MaliciousStatus", Fn: s.TestMaliciousStatus}, // test transactions {Name: "LargeTxRequest", Fn: s.TestLargeTxRequest, Slow: true}, {Name: "Transaction", Fn: s.TestTransaction}, @@ -469,6 +471,42 @@ func (s *Suite) TestMaliciousHandshake(t *utesting.T) { } } +func (s *Suite) TestMaliciousStatus(t *utesting.T) { + t.Log(`This test sends a malicious eth Status message to the node and expects a disconnect.`) + + conn, err := s.dial() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err := conn.handshake(); err != nil { + t.Fatalf("handshake failed: %v", err) + } + // Create status with large total difficulty. + status := ð.StatusPacket{ + ProtocolVersion: uint32(conn.negotiatedProtoVersion), + NetworkID: s.chain.config.ChainID.Uint64(), + TD: new(big.Int).SetBytes(randBuf(2048)), + Head: s.chain.Head().Hash(), + Genesis: s.chain.GetBlock(0).Hash(), + ForkID: s.chain.ForkID(), + } + if err := conn.statusExchange(s.chain, status); err != nil { + t.Fatalf("status exchange failed: %v", err) + } + // Wait for disconnect. + code, _, err := conn.Read() + if err != nil { + t.Fatalf("error reading from connection: %v", err) + } + switch code { + case discMsg: + break + default: + t.Fatalf("expected disconnect, got: %d", code) + } +} + func (s *Suite) TestTransaction(t *utesting.T) { t.Log(`This test sends a valid transaction to the node and checks if the transaction gets propagated.`) diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index 457f8ad575..5a3c675636 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -25,7 +25,6 @@ import ( "errors" "fmt" "io" - "math/big" "os" "os/signal" "path/filepath" @@ -455,10 +454,6 @@ func ExportHistory(bc *core.BlockChain, dir string, first, last, step uint64) er buf = bytes.NewBuffer(nil) checksums []string ) - td := new(big.Int) - for i := uint64(0); i < first; i++ { - td.Add(td, bc.GetHeaderByNumber(i).Difficulty) - } for i := first; i <= last; i += step { err := func() error { filename := filepath.Join(dir, era.Filename(network, int(i/step), common.Hash{})) @@ -481,8 +476,11 @@ func ExportHistory(bc *core.BlockChain, dir string, first, last, step uint64) er if receipts == nil { return fmt.Errorf("export failed on #%d: receipts not found", n) } - td.Add(td, block.Difficulty()) - if err := w.Add(block, receipts, new(big.Int).Set(td)); err != nil { + td := bc.GetTd(block.Hash(), block.NumberU64()) + if td == nil { + return fmt.Errorf("export failed on #%d: total difficulty not found", n) + } + if err := w.Add(block, receipts, td); err != nil { return err } } diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index de28a6a36b..31d562b149 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -93,55 +93,67 @@ func (beacon *Beacon) Author(header *types.Header) (common.Address, error) { // VerifyHeader checks whether a header conforms to the consensus rules of the // stock Ethereum consensus engine. func (beacon *Beacon) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header) error { - // During the live merge transition, the consensus engine used the terminal - // total difficulty to detect when PoW (PoA) switched to PoS. Maintaining the - // total difficulty values however require applying all the blocks from the - // genesis to build up the TD. This stops being a possibility if the tail of - // the chain is pruned already during sync. - // - // One heuristic that can be used to distinguish pre-merge and post-merge - // blocks is whether their *difficulty* is >0 or ==0 respectively. This of - // course would mean that we cannot prove anymore for a past chain that it - // truly transitioned at the correct TTD, but if we consider that ancient - // point in time finalized a long time ago, there should be no attempt from - // the consensus client to rewrite very old history. - // - // One thing that's probably not needed but which we can add to make this - // verification even stricter is to enforce that the chain can switch from - // >0 to ==0 TD only once by forbidding an ==0 to be followed by a >0. - - // Verify that we're not reverting to pre-merge from post-merge + reached, err := IsTTDReached(chain, header.ParentHash, header.Number.Uint64()-1) + if err != nil { + return err + } + if !reached { + return beacon.ethone.VerifyHeader(chain, header) + } + // Short circuit if the parent is not known parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) if parent == nil { return consensus.ErrUnknownAncestor } - if parent.Difficulty.Sign() == 0 && header.Difficulty.Sign() > 0 { - return consensus.ErrInvalidTerminalBlock - } - // Check >0 TDs with pre-merge, --0 TDs with post-merge rules - if header.Difficulty.Sign() > 0 { - return beacon.ethone.VerifyHeader(chain, header) - } + // Sanity checks passed, do a proper verification return beacon.verifyHeader(chain, header, parent) } +// errOut constructs an error channel with prefilled errors inside. +func errOut(n int, err error) chan error { + errs := make(chan error, n) + for i := 0; i < n; i++ { + errs <- err + } + return errs +} + // splitHeaders splits the provided header batch into two parts according to -// the difficulty field. -// +// the configured ttd. It requires the parent of header batch along with its +// td are stored correctly in chain. If ttd is not configured yet, all headers +// will be treated legacy PoW headers. // Note, this function will not verify the header validity but just split them. -func (beacon *Beacon) splitHeaders(headers []*types.Header) ([]*types.Header, []*types.Header) { +func (beacon *Beacon) splitHeaders(chain consensus.ChainHeaderReader, headers []*types.Header) ([]*types.Header, []*types.Header, error) { + // TTD is not defined yet, all headers should be in legacy format. + ttd := chain.Config().TerminalTotalDifficulty + ptd := chain.GetTd(headers[0].ParentHash, headers[0].Number.Uint64()-1) + if ptd == nil { + return nil, nil, consensus.ErrUnknownAncestor + } + // The entire header batch already crosses the transition. + if ptd.Cmp(ttd) >= 0 { + return nil, headers, nil + } var ( preHeaders = headers postHeaders []*types.Header + td = new(big.Int).Set(ptd) + tdPassed bool ) for i, header := range headers { - if header.Difficulty.Sign() == 0 { + if tdPassed { preHeaders = headers[:i] postHeaders = headers[i:] break } + td = td.Add(td, header.Difficulty) + if td.Cmp(ttd) >= 0 { + // This is the last PoW header, it still belongs to + // the preHeaders, so we cannot split+break yet. + tdPassed = true + } } - return preHeaders, postHeaders + return preHeaders, postHeaders, nil } // VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers @@ -149,7 +161,10 @@ func (beacon *Beacon) splitHeaders(headers []*types.Header) ([]*types.Header, [] // a results channel to retrieve the async verifications. // VerifyHeaders expect the headers to be ordered and continuous. func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header) (chan<- struct{}, <-chan error) { - preHeaders, postHeaders := beacon.splitHeaders(headers) + preHeaders, postHeaders, err := beacon.splitHeaders(chain, headers) + if err != nil { + return make(chan struct{}), errOut(len(headers), err) + } if len(postHeaders) == 0 { return beacon.ethone.VerifyHeaders(chain, headers) } @@ -329,7 +344,12 @@ func (beacon *Beacon) verifyHeaders(chain consensus.ChainHeaderReader, headers [ // Prepare implements consensus.Engine, initializing the difficulty field of a // header to conform to the beacon protocol. The changes are done inline. func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { - if !isPostMerge(chain.Config(), header.Number.Uint64(), header.Time) { + // Transition isn't triggered yet, use the legacy rules for preparation. + reached, err := IsTTDReached(chain, header.ParentHash, header.Number.Uint64()-1) + if err != nil { + return err + } + if !reached { return beacon.ethone.Prepare(chain, header) } header.Difficulty = beaconDifficulty @@ -440,7 +460,8 @@ func (beacon *Beacon) SealHash(header *types.Header) common.Hash { // the difficulty that a new block should have when created at time // given the parent block's time and difficulty. func (beacon *Beacon) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { - if !isPostMerge(chain.Config(), parent.Number.Uint64()+1, time) { + // Transition isn't triggered yet, use the legacy rules for calculation + if reached, _ := IsTTDReached(chain, parent.Hash(), parent.Number.Uint64()); !reached { return beacon.ethone.CalcDifficulty(chain, time, parent) } return beaconDifficulty @@ -481,3 +502,14 @@ func (beacon *Beacon) SetThreads(threads int) { th.SetThreads(threads) } } + +// IsTTDReached checks if the TotalTerminalDifficulty has been surpassed on the `parentHash` block. +// It depends on the parentHash already being stored in the database. +// If the parentHash is not stored in the database a UnknownAncestor error is returned. +func IsTTDReached(chain consensus.ChainHeaderReader, parentHash common.Hash, parentNumber uint64) (bool, error) { + td := chain.GetTd(parentHash, parentNumber) + if td == nil { + return false, consensus.ErrUnknownAncestor + } + return td.Cmp(chain.Config().TerminalTotalDifficulty) >= 0, nil +} diff --git a/consensus/beacon/faker.go b/consensus/beacon/faker.go new file mode 100644 index 0000000000..981e345e3e --- /dev/null +++ b/consensus/beacon/faker.go @@ -0,0 +1,41 @@ +// 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 beacon + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/types" +) + +// NewFaker creates a fake consensus engine for testing. +// The fake engine simulates a merged network. +// It can not be used to test the merge transition. +// This type is needed since the fakeChainReader can not be used with +// a normal beacon consensus engine. +func NewFaker() consensus.Engine { + return new(faker) +} + +type faker struct { + Beacon +} + +func (f *faker) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { + return beaconDifficulty +} diff --git a/consensus/consensus.go b/consensus/consensus.go index 6e17cfd540..3e41cb52d7 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -45,6 +45,9 @@ type ChainHeaderReader interface { // GetHeaderByHash retrieves a block header from the database by its hash. GetHeaderByHash(hash common.Hash) *types.Header + + // GetTd retrieves the total difficulty from the database by hash and number. + GetTd(hash common.Hash, number uint64) *big.Int } // ChainReader defines a small collection of methods needed to access the local diff --git a/core/bench_test.go b/core/bench_test.go index 36eb3b07da..64714e098c 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -295,6 +295,7 @@ func makeChainForBench(db ethdb.Database, genesis *Genesis, full bool, count uin rawdb.WriteHeader(db, header) rawdb.WriteCanonicalHash(db, hash, n) + rawdb.WriteTd(db, hash, n, big.NewInt(int64(n+1))) if n == 0 { rawdb.WriteChainConfig(db, hash, genesis.Config) diff --git a/core/blockchain.go b/core/blockchain.go index b657004630..a450245e94 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -152,7 +152,7 @@ const ( // // - Version 9 // The following incompatible database changes were added: - // * Total difficulty has been removed from both the key-value store and the ancient store. + // * (not applicable for bor) Total difficulty has been removed from both the key-value store and the ancient store. // * The metadata structure of freezer is changed by adding 'flushOffset' BlockChainVersion uint64 = 9 ) @@ -787,18 +787,23 @@ func (bc *BlockChain) loadLastState() error { var ( currentSnapBlock = bc.CurrentSnapBlock() currentFinalBlock = bc.CurrentFinalBlock() + + headerTd = bc.GetTd(headHeader.Hash(), headHeader.Number.Uint64()) + blockTd = bc.GetTd(headBlock.Hash(), headBlock.NumberU64()) ) if headHeader.Hash() != headBlock.Hash() { - log.Info("Loaded most recent local header", "number", headHeader.Number, "hash", headHeader.Hash(), "age", common.PrettyAge(time.Unix(int64(headHeader.Time), 0))) + log.Info("Loaded most recent local header", "number", headHeader.Number, "hash", headHeader.Hash(), "td", headerTd, "age", common.PrettyAge(time.Unix(int64(headHeader.Time), 0))) } - log.Info("Loaded most recent local block", "number", headBlock.Number(), "hash", headBlock.Hash(), "age", common.PrettyAge(time.Unix(int64(headBlock.Time()), 0))) + log.Info("Loaded most recent local block", "number", headBlock.Number(), "hash", headBlock.Hash(), "td", blockTd, "age", common.PrettyAge(time.Unix(int64(headBlock.Time()), 0))) if headBlock.Hash() != currentSnapBlock.Hash() { - log.Info("Loaded most recent local snap block", "number", currentSnapBlock.Number, "hash", currentSnapBlock.Hash(), "age", common.PrettyAge(time.Unix(int64(currentSnapBlock.Time), 0))) + snapTd := bc.GetTd(currentSnapBlock.Hash(), currentSnapBlock.Number.Uint64()) + log.Info("Loaded most recent local snap block", "number", currentSnapBlock.Number, "hash", currentSnapBlock.Hash(), "td", snapTd, "age", common.PrettyAge(time.Unix(int64(currentSnapBlock.Time), 0))) } if currentFinalBlock != nil { - log.Info("Loaded most recent local finalized block", "number", currentFinalBlock.Number, "hash", currentFinalBlock.Hash(), "age", common.PrettyAge(time.Unix(int64(currentFinalBlock.Time), 0))) + finalTd := bc.GetTd(currentFinalBlock.Hash(), currentFinalBlock.Number.Uint64()) + log.Info("Loaded most recent local finalized block", "number", currentFinalBlock.Number, "hash", currentFinalBlock.Hash(), "td", finalTd, "age", common.PrettyAge(time.Unix(int64(currentFinalBlock.Time), 0))) } if pivot := rawdb.ReadLastPivotNumber(bc.db); pivot != nil { @@ -1248,6 +1253,7 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error { // Prepare the genesis block and reinitialise the chain batch := bc.db.NewBatch() + rawdb.WriteTd(batch, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty()) rawdb.WriteBlock(batch, genesis) if err := batch.Write(); err != nil { @@ -1550,7 +1556,8 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ // Ensure genesis is in ancients. if first.NumberU64() == 1 { if frozen, _ := bc.db.Ancients(); frozen == 0 { - writeSize, err := rawdb.WriteAncientBlocks(bc.db, []*types.Block{bc.genesisBlock}, []types.Receipts{nil}) + td := bc.genesisBlock.Difficulty() + writeSize, err := rawdb.WriteAncientBlocks(bc.db, []*types.Block{bc.genesisBlock}, []types.Receipts{nil}, td) if err != nil { log.Error("Error writing genesis to ancients", "err", err) return 0, err @@ -1579,7 +1586,9 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ headers = append(headers, block.Header()) } - writeSize, err := rawdb.WriteAncientBlocks(bc.db, blockChain, receiptChain) + // Write all chain data to ancients. + td := bc.GetTd(first.Hash(), first.NumberU64()) + writeSize, err := rawdb.WriteAncientBlocks(bc.db, blockChain, receiptChain, td) if err != nil { log.Error("Error importing chain data to ancients", "err", err) return 0, err @@ -1776,11 +1785,12 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ // writeBlockWithoutState writes only the block and its metadata to the database, // but does not write any state. This is used to construct competing side forks // up to the point where they exceed the canonical total difficulty. -func (bc *BlockChain) writeBlockWithoutState(block *types.Block) (err error) { +func (bc *BlockChain) writeBlockWithoutState(block *types.Block, td *big.Int) (err error) { if bc.insertStopped() { return errInsertionInterrupted } batch := bc.db.NewBatch() + rawdb.WriteTd(batch, block.Hash(), block.NumberU64(), td) rawdb.WriteBlock(batch, block) if err := batch.Write(); err != nil { @@ -1808,14 +1818,20 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error { // writeBlockWithState writes block, metadata and corresponding state data to the // database. func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, statedb *state.StateDB) ([]*types.Log, error) { - if !bc.HasHeader(block.ParentHash(), block.NumberU64()-1) { + // Calculate the total difficulty of the block + ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1) + if ptd == nil { return consensus.ErrUnknownAncestor } + // Make sure no inconsistent state is leaked during insertion + externTd := new(big.Int).Add(block.Difficulty(), ptd) + // Irrelevant of the canonical status, write the block itself to the database. // - // Note all the components of block(hash->number map, header, body, receipts) + // Note all the components of block(td, hash->number map, header, body, receipts) // should be written atomically. BlockBatch is used for containing all components. blockBatch := bc.db.NewBatch() + rawdb.WriteTd(blockBatch, block.Hash(), block.NumberU64(), externTd) rawdb.WriteBlock(blockBatch, block) rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts) @@ -2312,6 +2328,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness if bc.logger != nil && bc.logger.OnSkippedBlock != nil { bc.logger.OnSkippedBlock(tracing.BlockEvent{ Block: block, + TD: bc.GetTd(block.ParentHash(), block.NumberU64()-1), Finalized: bc.CurrentFinalBlock(), Safe: bc.CurrentSafeBlock(), }) @@ -2547,8 +2564,10 @@ type blockProcessingResult struct { // nolint : unused func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, start time.Time, setHead bool) (_ *blockProcessingResult, blockEndErr error) { if bc.logger != nil && bc.logger.OnBlockStart != nil { + td := bc.GetTd(block.ParentHash(), block.NumberU64()-1) bc.logger.OnBlockStart(tracing.BlockEvent{ Block: block, + TD: td, Finalized: bc.CurrentFinalBlock(), Safe: bc.CurrentSafeBlock(), }) @@ -2662,6 +2681,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator, ma lastBlock = block current = bc.CurrentBlock() headers []*types.Header + externTd *big.Int ) // The first sidechain block error is already verified to be ErrPrunedAncestor. @@ -2676,6 +2696,11 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator, ma canonical := bc.GetBlockByNumber(number) if canonical != nil && canonical.Hash() == block.Hash() { // Not a sidechain block, this is a re-import of a canon block which has it's state pruned + + // Collect the TD of the block. Since we know it's a canon one, + // we can get it directly, and not (like further below) use + // the parent and then add the block on top + externTd = bc.GetTd(block.Hash(), block.NumberU64()) continue } @@ -2695,9 +2720,14 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator, ma return nil, it.index, errors.New("sidechain ghost-state attack") } } + if externTd == nil { + externTd = bc.GetTd(block.ParentHash(), block.NumberU64()-1) + } + externTd = new(big.Int).Add(externTd, block.Difficulty()) + if !bc.HasBlock(block.Hash(), block.NumberU64()) { start := time.Now() - if err := bc.writeBlockWithoutState(block); err != nil { + if err := bc.writeBlockWithoutState(block, externTd); err != nil { return nil, it.index, err } diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index 85f45d7958..692c306c47 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -18,6 +18,7 @@ package core import ( "errors" + "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" @@ -341,6 +342,12 @@ func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLoo return lookup, tx, nil } +// GetTd retrieves a block's total difficulty in the canonical chain from the +// database by hash and number, caching it if found. +func (bc *BlockChain) GetTd(hash common.Hash, number uint64) *big.Int { + return bc.hc.GetTd(hash, number) +} + // HasState checks if state trie is fully present in the database or not. func (bc *BlockChain) HasState(hash common.Hash) bool { _, err := bc.statedb.OpenTrie(hash) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 7e3b3e105a..51efbd6258 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -91,7 +91,7 @@ func newGwei(n int64) *big.Int { } // Test fork of length N starting from block i -func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, scheme string) { +func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int), scheme string) { // Copy old chain up to #i into a new db genDb, _, blockchain2, err := newCanonical(ethash.NewFaker(), i, full, scheme) if err != nil { @@ -130,15 +130,27 @@ func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, scheme } } // Sanity check that the forked chain can be imported into the original + var tdPre, tdPost *big.Int + if full { + cur := blockchain.CurrentBlock() + tdPre = blockchain.GetTd(cur.Hash(), cur.Number.Uint64()) if err := testBlockChainImport(blockChainB, blockchain); err != nil { t.Fatalf("failed to import forked block chain: %v", err) } + last := blockChainB[len(blockChainB)-1] + tdPost = blockchain.GetTd(last.Hash(), last.NumberU64()) } else { + cur := blockchain.CurrentHeader() + tdPre = blockchain.GetTd(cur.Hash(), cur.Number.Uint64()) if err := testHeaderChainImport(headerChainB, blockchain); err != nil { t.Fatalf("failed to import forked header chain: %v", err) } + last := headerChainB[len(headerChainB)-1] + tdPost = blockchain.GetTd(last.Hash(), last.Number.Uint64()) } + // Compare the total difficulties of the chains + comparator(tdPre, tdPost) } // testBlockChainImport tries to process a chain of blocks, writing them into @@ -179,6 +191,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { } blockchain.chainmu.MustLock() + rawdb.WriteTd(blockchain.db, block.Hash(), block.NumberU64(), new(big.Int).Add(block.Difficulty(), blockchain.GetTd(block.ParentHash(), block.NumberU64()-1))) rawdb.WriteBlock(blockchain.db, block) statedb.Commit(block.NumberU64(), false, false) blockchain.chainmu.Unlock() @@ -296,6 +309,7 @@ func testHeaderChainImport(chain []*types.Header, blockchain *BlockChain) error } // Manually insert the header into the database, but don't reorganise (allows subsequent testing) blockchain.chainmu.MustLock() + rawdb.WriteTd(blockchain.db, header.Hash(), header.Number.Uint64(), new(big.Int).Add(header.Difficulty, blockchain.GetTd(header.ParentHash, header.Number.Uint64()-1))) rawdb.WriteHeader(blockchain.db, header) blockchain.chainmu.Unlock() } @@ -399,11 +413,17 @@ func testExtendCanonical(t *testing.T, full bool, scheme string) { } defer processor.Stop() + // Define the difficulty comparator + better := func(td1, td2 *big.Int) { + if td2.Cmp(td1) <= 0 { + t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1) + } + } // Start fork from current height - testFork(t, processor, length, 1, full, scheme) - testFork(t, processor, length, 2, full, scheme) - testFork(t, processor, length, 5, full, scheme) - testFork(t, processor, length, 10, full, scheme) + testFork(t, processor, length, 1, full, better, scheme) + testFork(t, processor, length, 2, full, better, scheme) + testFork(t, processor, length, 5, full, better, scheme) + testFork(t, processor, length, 10, full, better, scheme) } // Tests that given a starting canonical chain of a given size, it can be extended @@ -452,13 +472,19 @@ func testShorterFork(t *testing.T, full bool, scheme string) { } defer processor.Stop() + // Define the difficulty comparator + worse := func(td1, td2 *big.Int) { + if td2.Cmp(td1) >= 0 { + t.Errorf("total difficulty mismatch: have %v, expected less than %v", td2, td1) + } + } // Sum of numbers must be less than `length` for this to be a shorter fork - testFork(t, processor, 0, 3, full, scheme) - testFork(t, processor, 0, 7, full, scheme) - testFork(t, processor, 1, 1, full, scheme) - testFork(t, processor, 1, 7, full, scheme) - testFork(t, processor, 5, 3, full, scheme) - testFork(t, processor, 5, 4, full, scheme) + testFork(t, processor, 0, 3, full, worse, scheme) + testFork(t, processor, 0, 7, full, worse, scheme) + testFork(t, processor, 1, 1, full, worse, scheme) + testFork(t, processor, 1, 7, full, worse, scheme) + testFork(t, processor, 5, 3, full, worse, scheme) + testFork(t, processor, 5, 4, full, worse, scheme) } // Tests that given a starting canonical chain of a given size, creating shorter @@ -569,13 +595,19 @@ func testEqualFork(t *testing.T, full bool, scheme string) { } defer processor.Stop() + // Define the difficulty comparator + equal := func(td1, td2 *big.Int) { + if td2.Cmp(td1) != 0 { + t.Errorf("total difficulty mismatch: have %v, want %v", td2, td1) + } + } // Sum of numbers must be equal to `length` for this to be an equal fork - testFork(t, processor, 0, 10, full, scheme) - testFork(t, processor, 1, 9, full, scheme) - testFork(t, processor, 2, 8, full, scheme) - testFork(t, processor, 5, 5, full, scheme) - testFork(t, processor, 6, 4, full, scheme) - testFork(t, processor, 9, 1, full, scheme) + testFork(t, processor, 0, 10, full, equal, scheme) + testFork(t, processor, 1, 9, full, equal, scheme) + testFork(t, processor, 2, 8, full, equal, scheme) + testFork(t, processor, 5, 5, full, equal, scheme) + testFork(t, processor, 6, 4, full, equal, scheme) + testFork(t, processor, 9, 1, full, equal, scheme) } // Tests that given a starting canonical chain of a given size, creating equal @@ -738,6 +770,19 @@ func testReorg(t *testing.T, first, second []int64, td int64, full bool, scheme } } } + // Make sure the chain total difficulty is the correct one + want := new(big.Int).Add(blockchain.genesisBlock.Difficulty(), big.NewInt(td)) + if full { + cur := blockchain.CurrentBlock() + if have := blockchain.GetTd(cur.Hash(), cur.Number.Uint64()); have.Cmp(want) != 0 { + t.Errorf("total difficulty mismatch: have %v, want %v", have, want) + } + } else { + cur := blockchain.CurrentHeader() + if have := blockchain.GetTd(cur.Hash(), cur.Number.Uint64()); have.Cmp(want) != 0 { + t.Errorf("total difficulty mismatch: have %v, want %v", have, want) + } + } } // Tests chain insertions in the face of one entity containing an invalid nonce. @@ -893,6 +938,12 @@ func testFastVsFullChains(t *testing.T, scheme string) { for i := 0; i < len(blocks); i++ { num, hash, time := blocks[i].NumberU64(), blocks[i].Hash(), blocks[i].Time() + if ftd, atd := fast.GetTd(hash, num), archive.GetTd(hash, num); ftd.Cmp(atd) != 0 { + t.Errorf("block #%d [%x]: td mismatch: fastdb %v, archivedb %v", num, hash, ftd, atd) + } + if antd, artd := ancient.GetTd(hash, num), archive.GetTd(hash, num); antd.Cmp(artd) != 0 { + t.Errorf("block #%d [%x]: td mismatch: ancientdb %v, archivedb %v", num, hash, antd, artd) + } if fheader, aheader := fast.GetHeaderByHash(hash), archive.GetHeaderByHash(hash); fheader.Hash() != aheader.Hash() { t.Errorf("block #%d [%x]: header mismatch: fastdb %v, archivedb %v", num, hash, fheader, aheader) } @@ -4465,7 +4516,7 @@ func TestEIP3651(t *testing.T) { var ( aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") - engine = beacon.New(ethash.NewFaker()) + engine = beacon.NewFaker() // A sender who makes transactions, has some funds key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") @@ -4585,7 +4636,7 @@ func TestPragueRequests(t *testing.T) { addr1 = crypto.PubkeyToAddress(key1.PublicKey) config = *params.MergedTestChainConfig signer = types.LatestSigner(&config) - engine = beacon.New(ethash.NewFaker()) + engine = beacon.NewFaker() ) gspec := &Genesis{ Config: &config, @@ -4663,7 +4714,7 @@ func TestEIP7702(t *testing.T) { var ( config = *params.MergedTestChainConfig signer = types.LatestSigner(&config) - engine = beacon.New(ethash.NewFaker()) + engine = beacon.NewFaker() key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) diff --git a/core/chain_makers.go b/core/chain_makers.go index 06ae05b0b0..0fe2467fb7 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -764,3 +764,7 @@ func (cm *chainMaker) GetHeader(hash common.Hash, number uint64) *types.Header { func (cm *chainMaker) GetBlock(hash common.Hash, number uint64) *types.Block { return cm.blockByNumber(number) } + +func (cm *chainMaker) GetTd(hash common.Hash, number uint64) *big.Int { + return nil // not supported +} diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index 16d84e42cd..16a7348818 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -76,9 +76,8 @@ func TestGeneratePOSChain(t *testing.T) { Code: common.Hex2Bytes("600154600354"), } genesis := gspec.MustCommit(gendb, triedb.NewDatabase(gendb, triedb.HashDefaults)) - engine := beacon.New(ethash.NewFaker()) - genchain, genreceipts := GenerateChain(gspec.Config, genesis, engine, gendb, 4, func(i int, gen *BlockGen) { + genchain, genreceipts := GenerateChain(gspec.Config, genesis, beacon.NewFaker(), gendb, 4, func(i int, gen *BlockGen) { gen.SetParentBeaconRoot(common.Hash{byte(i + 1)}) // Add value transfer tx. @@ -121,7 +120,7 @@ func TestGeneratePOSChain(t *testing.T) { }) // Import the chain. This runs all block validation rules. - blockchain, _ := NewBlockChain(db, nil, gspec, nil, engine, vm.Config{}, nil, nil, nil) + blockchain, _ := NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer blockchain.Stop() if i, err := blockchain.InsertChain(genchain); err != nil { diff --git a/core/genesis.go b/core/genesis.go index 2cacb80f5b..209c6a5c70 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -576,6 +576,7 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Blo } batch := db.NewBatch() rawdb.WriteGenesisStateSpec(batch, block.Hash(), blob) + rawdb.WriteTd(batch, block.Hash(), block.NumberU64(), block.Difficulty()) rawdb.WriteBlock(batch, block) rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), nil) rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64()) diff --git a/core/genesis_test.go b/core/genesis_test.go index 94ba39485f..fb9c032bee 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -203,7 +203,7 @@ func TestGenesisHashes(t *testing.T) { } } -func TestGenesisCommit(t *testing.T) { +func TestGenesis_Commit(t *testing.T) { genesis := &Genesis{ BaseFee: big.NewInt(params.InitialBaseFee), Config: params.TestChainConfig, @@ -221,6 +221,13 @@ func TestGenesisCommit(t *testing.T) { if genesisBlock.Difficulty().Cmp(params.GenesisDifficulty) != 0 { t.Errorf("assumption wrong: want: %d, got: %v", params.GenesisDifficulty, genesisBlock.Difficulty()) } + + // Expect the stored total difficulty to be the difficulty of the genesis block. + stored := rawdb.ReadTd(db, genesisBlock.Hash(), genesisBlock.NumberU64()) + + if stored.Cmp(genesisBlock.Difficulty()) != 0 { + t.Errorf("inequal difficulty; stored: %v, genesisBlock: %v", stored, genesisBlock.Difficulty()) + } } func TestReadWriteGenesisAlloc(t *testing.T) { diff --git a/core/headerchain.go b/core/headerchain.go index 66038e4dc9..eeacc13a5c 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -19,6 +19,7 @@ package core import ( "errors" "fmt" + "math/big" "sync/atomic" "time" @@ -35,6 +36,7 @@ import ( const ( headerCacheLimit = 512 + tdCacheLimit = 1024 numberCacheLimit = 2048 ) @@ -63,7 +65,8 @@ type HeaderChain struct { currentHeaderHash common.Hash // Hash of the current head of the header chain (prevent recomputing all the time) headerCache *lru.Cache[common.Hash, *types.Header] - numberCache *lru.Cache[common.Hash, uint64] // most recent block numbers + tdCache *lru.Cache[common.Hash, *big.Int] // most recent total difficulties + numberCache *lru.Cache[common.Hash, uint64] // most recent block numbers procInterrupt func() bool engine consensus.Engine @@ -76,6 +79,7 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c config: config, chainDb: chainDb, headerCache: lru.NewCache[common.Hash, *types.Header](headerCacheLimit), + tdCache: lru.NewCache[common.Hash, *big.Int](tdCacheLimit), numberCache: lru.NewCache[common.Hash, uint64](numberCacheLimit), procInterrupt: procInterrupt, engine: engine, @@ -210,13 +214,15 @@ func (hc *HeaderChain) WriteHeaders(headers []*types.Header) (int, error) { if len(headers) == 0 { return 0, nil } - if !hc.HasHeader(headers[0].ParentHash, headers[0].Number.Uint64()-1) { + ptd := hc.GetTd(headers[0].ParentHash, headers[0].Number.Uint64()-1) + if ptd == nil { return 0, consensus.ErrUnknownAncestor } var ( - inserted []rawdb.NumberHash // Ephemeral lookup of number/hash for the chain - parentKnown = true // Set to true to force hc.HasHeader check the first iteration + newTD = new(big.Int).Set(ptd) // Total difficulty of inserted chain + inserted []rawdb.NumberHash // Ephemeral lookup of number/hash for the chain + parentKnown = true // Set to true to force hc.HasHeader check the first iteration batch = hc.chainDb.NewBatch() ) @@ -232,11 +238,16 @@ func (hc *HeaderChain) WriteHeaders(headers []*types.Header) (int, error) { } number := header.Number.Uint64() + newTD.Add(newTD, header.Difficulty) // If the parent was not present, store it // If the header is already known, skip it, otherwise store alreadyKnown := parentKnown && hc.HasHeader(hash, number) if !alreadyKnown { + // Irrelevant of the canonical status, write the TD and header to the database. + rawdb.WriteTd(batch, hash, number, newTD) + hc.tdCache.Add(hash, new(big.Int).Set(newTD)) + rawdb.WriteHeader(batch, header) inserted = append(inserted, rawdb.NumberHash{Number: number, Hash: hash}) @@ -442,6 +453,22 @@ func (hc *HeaderChain) GetAncestor(hash common.Hash, number, ancestor uint64, ma return hash, number } +// GetTd retrieves a block's total difficulty in the canonical chain from the +// database by hash and number, caching it if found. +func (hc *HeaderChain) GetTd(hash common.Hash, number uint64) *big.Int { + // Short circuit if the td's already in the cache, retrieve otherwise + if cached, ok := hc.tdCache.Get(hash); ok { + return cached + } + td := rawdb.ReadTd(hc.chainDb, hash, number) + if td == nil { + return nil + } + // Cache the found body for next time and return + hc.tdCache.Add(hash, td) + return td +} + // GetHeader retrieves a block header from the database by hash and number, // caching it if found. func (hc *HeaderChain) GetHeader(hash common.Hash, number uint64) *types.Header { @@ -677,6 +704,7 @@ func (hc *HeaderChain) setHead(headBlock uint64, headTime uint64, updateFn Updat } rawdb.DeleteHeader(batch, hash, num) + rawdb.DeleteTd(batch, hash, num) } rawdb.DeleteCanonicalHash(batch, num) @@ -688,6 +716,7 @@ func (hc *HeaderChain) setHead(headBlock uint64, headTime uint64, updateFn Updat } // Clear out any stale content from the caches hc.headerCache.Purge() + hc.tdCache.Purge() hc.numberCache.Purge() } diff --git a/core/headerchain_test.go b/core/headerchain_test.go index 962225be96..aabe5123e8 100644 --- a/core/headerchain_test.go +++ b/core/headerchain_test.go @@ -40,6 +40,10 @@ func verifyUnbrokenCanonchain(hc *HeaderChain) error { return fmt.Errorf("Canon hash chain broken, block %d got %x, expected %x", h.Number, canonHash[:8], exp[:8]) } + // Verify that we have the TD + if td := rawdb.ReadTd(hc.chainDb, canonHash, h.Number.Uint64()); td == nil { + return fmt.Errorf("Canon TD missing at block %d", h.Number) + } if h.Number.Uint64() == 0 { break } diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 950ae6ccb1..76131cb1e7 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -601,6 +601,54 @@ func DeleteBody(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { } } +// ReadTdRLP retrieves a block's total difficulty corresponding to the hash in RLP encoding. +func ReadTdRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue { + var data []byte + db.ReadAncients(func(reader ethdb.AncientReaderOp) error { + // Check if the data is in ancients + if isCanon(reader, number, hash) { + data, _ = reader.Ancient(ChainFreezerDifficultyTable, number) + return nil + } + // If not, try reading from leveldb + data, _ = db.Get(headerTDKey(number, hash)) + return nil + }) + return data +} + +// ReadTd retrieves a block's total difficulty corresponding to the hash. +func ReadTd(db ethdb.Reader, hash common.Hash, number uint64) *big.Int { + data := ReadTdRLP(db, hash, number) + if len(data) == 0 { + return nil + } + td := new(big.Int) + if err := rlp.DecodeBytes(data, td); err != nil { + log.Error("Invalid block total difficulty RLP", "hash", hash, "err", err) + return nil + } + return td +} + +// WriteTd stores the total difficulty of a block into the database. +func WriteTd(db ethdb.KeyValueWriter, hash common.Hash, number uint64, td *big.Int) { + data, err := rlp.EncodeToBytes(td) + if err != nil { + log.Crit("Failed to RLP encode block total difficulty", "err", err) + } + if err := db.Put(headerTDKey(number, hash), data); err != nil { + log.Crit("Failed to store block total difficulty", "err", err) + } +} + +// DeleteTd removes all block total difficulty data associated with a hash. +func DeleteTd(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { + if err := db.Delete(headerTDKey(number, hash)); err != nil { + log.Crit("Failed to delete block total difficulty", "err", err) + } +} + // HasReceipts verifies the existence of all the transaction receipts belonging // to a block. func HasReceipts(db ethdb.Reader, hash common.Hash, number uint64) bool { @@ -801,8 +849,9 @@ func WriteBlock(db ethdb.KeyValueWriter, block *types.Block) { } // WriteAncientBlocks writes entire block data into ancient store and returns the total written size. -func WriteAncientBlocks(db ethdb.AncientWriter, blocks []*types.Block, receipts []types.Receipts, borReceipts []types.Receipts) (int64, error) { +func WriteAncientBlocks(db ethdb.AncientWriter, blocks []*types.Block, receipts []types.Receipts, borReceipts []types.Receipts, td *big.Int) (int64, error) { var ( + tdSum = new(big.Int).Set(td) stReceipts []*types.ReceiptForStorage borStReceipts []*types.ReceiptForStorage ) @@ -823,7 +872,10 @@ func WriteAncientBlocks(db ethdb.AncientWriter, blocks []*types.Block, receipts header := block.Header() - if err := writeAncientBlock(op, block, header, stReceipts, borStReceipts); err != nil { + if i > 0 { + tdSum.Add(tdSum, header.Difficulty) + } + if err := writeAncientBlock(op, block, header, stReceipts, borStReceipts, tdSum); err != nil { return err } } @@ -832,7 +884,7 @@ func WriteAncientBlocks(db ethdb.AncientWriter, blocks []*types.Block, receipts }) } -func writeAncientBlock(op ethdb.AncientWriteOp, block *types.Block, header *types.Header, receipts []*types.ReceiptForStorage, borReceipts []*types.ReceiptForStorage) error { +func writeAncientBlock(op ethdb.AncientWriteOp, block *types.Block, header *types.Header, receipts []*types.ReceiptForStorage, borReceipts []*types.ReceiptForStorage, td *big.Int) error { num := block.NumberU64() if err := op.AppendRaw(ChainFreezerHashTable, num, block.Hash().Bytes()); err != nil { return fmt.Errorf("can't add block %d hash: %v", num, err) @@ -854,6 +906,9 @@ func writeAncientBlock(op ethdb.AncientWriteOp, block *types.Block, header *type return fmt.Errorf("can't append block %d borReceipts: %v", num, err) } + if err := op.Append(ChainFreezerDifficultyTable, num, td); err != nil { + return fmt.Errorf("can't append block %d total difficulty: %v", num, err) + } return nil } @@ -862,6 +917,7 @@ func DeleteBlock(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { DeleteReceipts(db, hash, number) DeleteHeader(db, hash, number) DeleteBody(db, hash, number) + DeleteTd(db, hash, number) // delete bor receipt DeleteBorReceipt(db, hash, number) @@ -873,6 +929,7 @@ func DeleteBlockWithoutNumber(db ethdb.KeyValueWriter, hash common.Hash, number DeleteReceipts(db, hash, number) deleteHeaderWithoutNumber(db, hash, number) DeleteBody(db, hash, number) + DeleteTd(db, hash, number) // delete bor receipt DeleteBorReceipt(db, hash, number) diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index 02de8e997c..efa3eec2ab 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -281,6 +281,29 @@ func TestBadBlockStorage(t *testing.T) { } } +// Tests block total difficulty storage and retrieval operations. +func TestTdStorage(t *testing.T) { + db := NewMemoryDatabase() + + // Create a test TD to move around the database and make sure it's really new + hash, td := common.Hash{}, big.NewInt(314) + if entry := ReadTd(db, hash, 0); entry != nil { + t.Fatalf("Non existent TD returned: %v", entry) + } + // Write and verify the TD in the database + WriteTd(db, hash, 0, td) + if entry := ReadTd(db, hash, 0); entry == nil { + t.Fatalf("Stored TD not found") + } else if entry.Cmp(td) != 0 { + t.Fatalf("Retrieved TD mismatch: have %v, want %v", entry, td) + } + // Delete the TD and verify the execution + DeleteTd(db, hash, 0) + if entry := ReadTd(db, hash, 0); entry != nil { + t.Fatalf("Deleted TD returned: %v", entry) + } +} + // Tests that canonical numbers can be mapped to hashes and retrieved. func TestCanonicalMappingStorage(t *testing.T) { db := NewMemoryDatabase() @@ -472,9 +495,12 @@ func TestAncientStorage(t *testing.T) { if blob := ReadReceiptsRLP(db, hash, number); len(blob) > 0 { t.Fatalf("non existent receipts returned") } + if blob := ReadTdRLP(db, hash, number); len(blob) > 0 { + t.Fatalf("non existent td returned") + } // Write and verify the header in the database - WriteAncientBlocks(db, []*types.Block{block}, []types.Receipts{nil}, []types.Receipts{nil}) + WriteAncientBlocks(db, []*types.Block{block}, []types.Receipts{nil}, []types.Receipts{nil}, big.NewInt(100)) if blob := ReadHeaderRLP(db, hash, number); len(blob) == 0 { t.Fatalf("no header returned") @@ -487,6 +513,9 @@ func TestAncientStorage(t *testing.T) { if blob := ReadReceiptsRLP(db, hash, number); len(blob) == 0 { t.Fatalf("no receipts returned") } + if blob := ReadTdRLP(db, hash, number); len(blob) == 0 { + t.Fatalf("no td returned") + } // Use a fake hash for data retrieval, nothing should be returned. fakeHash := common.BytesToHash([]byte{0x01, 0x02, 0x03}) @@ -501,6 +530,9 @@ func TestAncientStorage(t *testing.T) { if blob := ReadReceiptsRLP(db, fakeHash, number); len(blob) != 0 { t.Fatalf("invalid receipts returned") } + if blob := ReadTdRLP(db, fakeHash, number); len(blob) != 0 { + t.Fatalf("invalid td returned") + } } func TestCanonicalHashIteration(t *testing.T) { @@ -526,6 +558,7 @@ func TestCanonicalHashIteration(t *testing.T) { // Fill database with testing data. for i := uint64(1); i <= 8; i++ { WriteCanonicalHash(db, common.Hash{}, i) + WriteTd(db, common.Hash{}, i, big.NewInt(10)) // Write some interferential data } for i, c := range cases { @@ -613,6 +646,7 @@ func BenchmarkWriteAncientBlocks(b *testing.B) { // The benchmark loop writes batches of blocks, but note that the total block count is // b.N. This means the resulting ns/op measurement is the time it takes to write a // single block and its associated data. + var td = big.NewInt(55) var totalSize int64 for i := 0; i < b.N; i += batchSize { @@ -624,7 +658,7 @@ func BenchmarkWriteAncientBlocks(b *testing.B) { blocks := allBlocks[i : i+length] receipts := batchReceipts[:length] - writeSize, err := WriteAncientBlocks(db, blocks, receipts, []types.Receipts{nil}) + writeSize, err := WriteAncientBlocks(db, blocks, receipts, []types.Receipts{nil}, td) if err != nil { b.Fatal(err) } @@ -950,7 +984,7 @@ func TestHeadersRLPStorage(t *testing.T) { var borReceipts []types.Receipts = make([]types.Receipts, 100) // Write first half to ancients - WriteAncientBlocks(db, chain[:50], receipts[:50], borReceipts[:50]) + WriteAncientBlocks(db, chain[:50], receipts[:50], borReceipts[:50], big.NewInt(100)) // Write second half to db for i := 50; i < 100; i++ { WriteCanonicalHash(db, chain[i].Hash(), chain[i].NumberU64()) diff --git a/core/rawdb/ancient_scheme.go b/core/rawdb/ancient_scheme.go index 8d009c8ed2..628dde3f46 100644 --- a/core/rawdb/ancient_scheme.go +++ b/core/rawdb/ancient_scheme.go @@ -35,6 +35,9 @@ const ( // ChainFreezerReceiptTable indicates the name of the freezer receipts table. ChainFreezerReceiptTable = "receipts" + + // ChainFreezerDifficultyTable indicates the name of the freezer total difficulty table. + ChainFreezerDifficultyTable = "diffs" ) // chainFreezerTableConfigs configures the settings for tables in the chain freezer. @@ -42,11 +45,12 @@ const ( // tail truncation is disabled for the header and hash tables, as these are intended // to be retained long-term. var chainFreezerTableConfigs = map[string]freezerTableConfig{ - ChainFreezerHeaderTable: {noSnappy: false, prunable: false}, - ChainFreezerHashTable: {noSnappy: true, prunable: false}, - ChainFreezerBodiesTable: {noSnappy: false, prunable: true}, - ChainFreezerReceiptTable: {noSnappy: false, prunable: true}, - freezerBorReceiptTable: {noSnappy: false, prunable: true}, + ChainFreezerHeaderTable: {noSnappy: false, prunable: false}, + ChainFreezerHashTable: {noSnappy: true, prunable: false}, + ChainFreezerBodiesTable: {noSnappy: false, prunable: true}, + ChainFreezerReceiptTable: {noSnappy: false, prunable: true}, + ChainFreezerDifficultyTable: {noSnappy: false, prunable: true}, + freezerBorReceiptTable: {noSnappy: false, prunable: true}, } // freezerTableConfig contains the settings for a freezer table. diff --git a/core/rawdb/chain_freezer.go b/core/rawdb/chain_freezer.go index f2eb77fad9..4384d26dcd 100644 --- a/core/rawdb/chain_freezer.go +++ b/core/rawdb/chain_freezer.go @@ -348,6 +348,11 @@ func (f *chainFreezer) freezeRange(nfdb *nofreezedb, number, limit uint64) (hash if len(receipts) == 0 { return fmt.Errorf("block receipts missing, can't freeze block %d", number) } + td := ReadTdRLP(nfdb, hash, number) + if len(td) == 0 { + return fmt.Errorf("total difficulty missing, can't freeze block %d", number) + } + // Write to the batch. if err := op.AppendRaw(ChainFreezerHashTable, number, hash[:]); err != nil { return fmt.Errorf("can't write hash to Freezer: %v", err) @@ -371,6 +376,9 @@ func (f *chainFreezer) freezeRange(nfdb *nofreezedb, number, limit uint64) (hash return fmt.Errorf("can't write bor-receipt to freezer: %v", err) } + if err := op.AppendRaw(ChainFreezerDifficultyTable, number, td); err != nil { + return fmt.Errorf("can't write td to Freezer: %v", err) + } hashes = append(hashes, hash) } diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 3977cdac47..f2e02bdd08 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -669,7 +669,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { {"Key-Value store", "Headers", headers.Size(), headers.Count()}, {"Key-Value store", "Bodies", bodies.Size(), bodies.Count()}, {"Key-Value store", "Receipt lists", receipts.Size(), receipts.Count()}, - {"Key-Value store", "Difficulties (deprecated)", tds.Size(), tds.Count()}, + {"Key-Value store", "Difficulties", tds.Size(), tds.Count()}, {"Key-Value store", "Block number->hash", numHashPairings.Size(), numHashPairings.Count()}, {"Key-Value store", "Block hash->number", hashNumPairings.Size(), hashNumPairings.Count()}, {"Key-Value store", "Transaction index", txLookups.Size(), txLookups.Count()}, diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 9a48c4471b..ecca1de152 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -104,7 +104,7 @@ var ( // Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes). headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header - headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td (deprecated) + headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td headerHashSuffix = []byte("n") // headerPrefix + num (uint64 big endian) + headerHashSuffix -> hash headerNumberPrefix = []byte("H") // headerNumberPrefix + hash -> num (uint64 big endian) @@ -181,6 +181,11 @@ func headerKey(number uint64, hash common.Hash) []byte { return append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...) } +// headerTDKey = headerPrefix + num (uint64 big endian) + hash + headerTDSuffix +func headerTDKey(number uint64, hash common.Hash) []byte { + return append(headerKey(number, hash), headerTDSuffix...) +} + // headerHashKey = headerPrefix + num (uint64 big endian) + headerHashSuffix func headerHashKey(number uint64) []byte { return append(append(headerPrefix, encodeBlockNumber(number)...), headerHashSuffix...) diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 0485f7a3eb..41e39c583d 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -71,6 +71,7 @@ type VMContext struct { // It contains the block as well as consensus related information. type BlockEvent struct { Block *types.Block + TD *big.Int Finalized *types.Header Safe *types.Header } diff --git a/core/txindexer_test.go b/core/txindexer_test.go index 74559e6c7d..7a41db3cc4 100644 --- a/core/txindexer_test.go +++ b/core/txindexer_test.go @@ -242,7 +242,7 @@ func TestTxIndexerRepair(t *testing.T) { } for _, c := range cases { db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) - rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...)) + rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...), big.NewInt(0)) // Index the initial blocks from ancient store indexer := &txIndexer{ diff --git a/eth/api_backend.go b/eth/api_backend.go index 8ae2d9f147..abf8157782 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -277,6 +277,13 @@ func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash, number ui 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, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM { if vmConfig == nil { vmConfig = b.eth.blockchain.GetVMConfig() diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 3b7ba7c44c..ef5d9f0e02 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -364,12 +364,21 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl } // 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 { - ph := api.eth.BlockChain().GetHeader(block.ParentHash(), block.NumberU64()-1) - if ph == nil { - return engine.STATUS_INVALID, errors.New("parent unavailable for difficulty check") + 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 ph.Difficulty.Sign() == 0 && block.Difficulty().Sign() > 0 { + 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 } @@ -893,6 +902,21 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe if parent == nil { return api.delayPayloadImport(block), nil } + // 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 diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index ff6e853bf9..8c9707e08c 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -32,7 +32,8 @@ import ( "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/beacon" + "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" @@ -61,13 +62,10 @@ var ( func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) { config := *params.AllEthashProtocolChanges - engine := beacon.New(ethash.NewFaker()) + engine := consensus.Engine(beaconConsensus.New(ethash.NewFaker())) if merged { config.TerminalTotalDifficulty = common.Big0 - config.MergeNetsplitBlock = common.Big0 - } else { - // When !merged, the tests expect the next block after the generated chain to be in PoS. - config.MergeNetsplitBlock = big.NewInt(int64(n + 1)) + engine = beaconConsensus.NewFaker() } genesis := &core.Genesis{ @@ -104,6 +102,7 @@ func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) { } config.TerminalTotalDifficulty = totalDifficulty } + return genesis, blocks } @@ -166,6 +165,24 @@ func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) { } } +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 @@ -887,6 +904,70 @@ func TestInvalidBloom(t *testing.T) { } } +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, false) + 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(types.Body{Transactions: txs}) + 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'. diff --git a/eth/downloader/bor_downloader.go b/eth/downloader/bor_downloader.go index 522cd19387..09a82605c1 100644 --- a/eth/downloader/bor_downloader.go +++ b/eth/downloader/bor_downloader.go @@ -186,6 +186,9 @@ type LightChain interface { // CurrentHeader retrieves the head header from the local chain. CurrentHeader() *types.Header + // GetTd returns the total difficulty of a local block. + GetTd(common.Hash, uint64) *big.Int + // InsertHeaderChain inserts a batch of headers into the local chain. InsertHeaderChain([]*types.Header) (int, error) diff --git a/eth/downloader/peer.go b/eth/downloader/peer.go index 5e59b3080d..0a11bca05d 100644 --- a/eth/downloader/peer.go +++ b/eth/downloader/peer.go @@ -21,6 +21,7 @@ package downloader import ( "errors" + "math/big" "sync" "time" @@ -56,6 +57,7 @@ type peerConnection struct { // Peer encapsulates the methods required to synchronise with a remote full peer. type Peer interface { + Head() (common.Hash, *big.Int) RequestHeadersByHash(common.Hash, int, int, bool, chan *eth.Response) (*eth.Request, error) RequestHeadersByNumber(uint64, int, int, bool, chan *eth.Response) (*eth.Request, error) diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index bc3a0dfeff..c7d0e48587 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -157,6 +157,7 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, cancunBlock *big.Int, pe // config.MergeNetsplitBlock = cancunBlock // } // engine := beacon.New(ethash.NewFaker()) + config.TerminalTotalDifficulty = common.Big0 engine := ethash.NewFaker() // if cancunBlock != nil { diff --git a/eth/handler.go b/eth/handler.go index 379cebd87f..e793844328 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -321,9 +321,10 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { 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, hash, genesis.Hash(), forkID, h.forkFilter); err != nil { + 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 } diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go index 690bf26921..2c641c9d55 100644 --- a/eth/handler_eth_test.go +++ b/eth/handler_eth_test.go @@ -272,8 +272,9 @@ func testRecvTransactions(t *testing.T, protocol uint) { var ( genesis = handler.chain.Genesis() head = handler.chain.CurrentBlock() + td = handler.chain.GetTd(head.Hash(), head.Number.Uint64()) ) - if err := src.Handshake(1, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil { + 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 @@ -332,8 +333,9 @@ func testSendTransactions(t *testing.T, protocol uint) { var ( genesis = handler.chain.Genesis() head = handler.chain.CurrentBlock() + td = handler.chain.GetTd(head.Hash(), head.Number.Uint64()) ) - if err := sink.Handshake(1, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil { + 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 diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index b2fc0461e8..ad56f89c82 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -18,6 +18,7 @@ package eth import ( "fmt" + "math/big" "time" "github.com/ethereum/go-ethereum/common" @@ -124,10 +125,11 @@ func MakeProtocols(backend Backend, network uint64, disc enode.Iterator) []p2p.P // NodeInfo represents a short summary of the `eth` sub-protocol metadata // known about the host peer. type NodeInfo struct { - Network uint64 `json:"network"` // Ethereum network ID (1=Mainnet, Holesky=17000) - Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block - Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules - Head common.Hash `json:"head"` // Hex hash of the host's best owned block + Network uint64 `json:"network"` // Ethereum network ID (1=Mainnet, Holesky=17000) + Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain + Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block + Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules + Head common.Hash `json:"head"` // Hex hash of the host's best owned block } // nodeInfo retrieves some `eth` protocol metadata about the running host node. @@ -136,10 +138,11 @@ func nodeInfo(chain *core.BlockChain, network uint64) *NodeInfo { hash := head.Hash() return &NodeInfo{ - Network: network, - Genesis: chain.Genesis().Hash(), - Config: chain.Config(), - Head: hash, + Network: network, + Difficulty: chain.GetTd(hash, head.Number.Uint64()), + Genesis: chain.Genesis().Hash(), + Config: chain.Config(), + Head: hash, } } diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index 9ac4d8d2fc..3a9797946c 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -27,6 +27,7 @@ import ( "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/ethash" "github.com/ethereum/go-ethereum/core" @@ -74,10 +75,11 @@ func newTestBackend(blocks int) *testBackend { func newTestBackendWithGenerator(blocks int, shanghai bool, cancun bool, generator func(int, *core.BlockGen)) *testBackend { var ( // Create a database pre-initialize with a genesis block - db = rawdb.NewMemoryDatabase() - config = params.TestChainConfig - engine = beacon.New(ethash.NewFaker()) + db = rawdb.NewMemoryDatabase() + config = params.TestChainConfig + engine consensus.Engine = ethash.NewFaker() ) + if shanghai { config = ¶ms.ChainConfig{ ChainID: big.NewInt(1), @@ -102,6 +104,7 @@ func newTestBackendWithGenerator(blocks int, shanghai bool, cancun bool, generat Ethash: new(params.EthashConfig), Bor: params.TestChainConfig.Bor, } + engine = beacon.NewFaker() } gspec := &core.Genesis{ diff --git a/eth/protocols/eth/handshake.go b/eth/protocols/eth/handshake.go index 63b05bdb27..5ed587b0df 100644 --- a/eth/protocols/eth/handshake.go +++ b/eth/protocols/eth/handshake.go @@ -36,7 +36,7 @@ const ( // Handshake executes the eth protocol handshake, negotiating version number, // network IDs, difficulties, head and genesis blocks. -func (p *Peer) Handshake(network uint64, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) error { +func (p *Peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) error { // Send out own handshake in a new thread errc := make(chan error, 2) @@ -46,7 +46,7 @@ func (p *Peer) Handshake(network uint64, head common.Hash, genesis common.Hash, errc <- p2p.Send(p.rw, StatusMsg, &StatusPacket{ ProtocolVersion: uint32(p.version), NetworkID: network, - TD: new(big.Int), // unknown for post-merge tail=pruned networks + TD: td, Head: head, Genesis: genesis, ForkID: forkID, @@ -71,6 +71,13 @@ func (p *Peer) Handshake(network uint64, head common.Hash, genesis common.Hash, return p2p.DiscReadTimeout } } + p.td, p.head = status.TD, status.Head + + // TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times + // larger, it will still fit within 100 bits + if tdlen := p.td.BitLen(); tdlen > 100 { + return fmt.Errorf("too large total difficulty: bitlen %d", tdlen) + } return nil } diff --git a/eth/protocols/eth/handshake_test.go b/eth/protocols/eth/handshake_test.go index a9575aae9c..6089723e57 100644 --- a/eth/protocols/eth/handshake_test.go +++ b/eth/protocols/eth/handshake_test.go @@ -18,7 +18,6 @@ package eth import ( "errors" - "math/big" "testing" "github.com/ethereum/go-ethereum/common" @@ -41,6 +40,7 @@ func testHandshake(t *testing.T, protocol uint) { var ( genesis = backend.chain.Genesis() head = backend.chain.CurrentBlock() + td = backend.chain.GetTd(head.Hash(), head.Number.Uint64()) forkID = forkid.NewID(backend.chain.Config(), backend.chain.Genesis(), backend.chain.CurrentHeader().Number.Uint64(), backend.chain.CurrentHeader().Time) ) @@ -54,19 +54,19 @@ func testHandshake(t *testing.T, protocol uint) { want: errNoStatusMsg, }, { - code: StatusMsg, data: StatusPacket{10, 1, new(big.Int), head.Hash(), genesis.Hash(), forkID}, + code: StatusMsg, data: StatusPacket{10, 1, td, head.Hash(), genesis.Hash(), forkID}, want: errProtocolVersionMismatch, }, { - code: StatusMsg, data: StatusPacket{uint32(protocol), 999, new(big.Int), head.Hash(), genesis.Hash(), forkID}, + code: StatusMsg, data: StatusPacket{uint32(protocol), 999, td, head.Hash(), genesis.Hash(), forkID}, want: errNetworkIDMismatch, }, { - code: StatusMsg, data: StatusPacket{uint32(protocol), 1, new(big.Int), head.Hash(), common.Hash{3}, forkID}, + code: StatusMsg, data: StatusPacket{uint32(protocol), 1, td, head.Hash(), common.Hash{3}, forkID}, want: errGenesisMismatch, }, { - code: StatusMsg, data: StatusPacket{uint32(protocol), 1, new(big.Int), head.Hash(), genesis.Hash(), forkid.ID{Hash: [4]byte{0x00, 0x01, 0x02, 0x03}}}, + code: StatusMsg, data: StatusPacket{uint32(protocol), 1, td, head.Hash(), genesis.Hash(), forkid.ID{Hash: [4]byte{0x00, 0x01, 0x02, 0x03}}}, want: errForkIDRejected, }, } @@ -82,7 +82,7 @@ func testHandshake(t *testing.T, protocol uint) { // Send the junk test with one peer, check the handshake failure go p2p.Send(app, test.code, test.data) - err := peer.Handshake(1, head.Hash(), genesis.Hash(), forkID, forkid.NewFilter(backend.chain)) + err := peer.Handshake(1, td, head.Hash(), genesis.Hash(), forkID, forkid.NewFilter(backend.chain)) if err == nil { t.Errorf("test %d: protocol returned nil error, want %q", i, test.want) } else if !errors.Is(err, test.want) { diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index c63c6aecfc..6390a014da 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -1289,7 +1289,7 @@ func TestTraceChain(t *testing.T) { func newTestMergedBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *testBackend { backend := &testBackend{ chainConfig: gspec.Config, - engine: beacon.New(ethash.NewFaker()), + engine: beacon.NewFaker(), chaindb: rawdb.NewMemoryDatabase(), } // Generate blocks for testing diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index f412552af7..a68ffdbad7 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -68,6 +68,7 @@ type backend interface { SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription CurrentHeader() *types.Header HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) + GetTd(ctx context.Context, hash common.Hash) *big.Int Stats() (pending int, queued int) SyncProgress() ethereum.SyncProgress @@ -707,6 +708,7 @@ func (s *Service) reportBlock(conn *connWrapper, header *types.Header) error { func (s *Service) assembleBlockStats(header *types.Header) *blockStats { // Gather the block infos from the local blockchain var ( + td *big.Int txs []txStats uncles []*types.Header ) @@ -722,6 +724,8 @@ func (s *Service) assembleBlockStats(header *types.Header) *blockStats { if block == nil { return nil } + td = fullBackend.GetTd(context.Background(), header.Hash()) + txs = make([]txStats, len(block.Transactions())) for i, tx := range block.Transactions() { txs[i].Hash = tx.Hash() @@ -733,6 +737,7 @@ func (s *Service) assembleBlockStats(header *types.Header) *blockStats { if header == nil { header = s.backend.CurrentHeader() } + td = s.backend.GetTd(context.Background(), header.Hash()) txs = []txStats{} } // Assemble and return the block stats @@ -747,7 +752,7 @@ func (s *Service) assembleBlockStats(header *types.Header) *blockStats { GasUsed: header.GasUsed, GasLimit: header.GasLimit, Diff: header.Difficulty.String(), - TotalDiff: "0", // unknown post-merge with pruned chain tail + TotalDiff: td.String(), Txs: txs, TxHash: header.TxHash, Root: header.Root, diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index a0f0b1eda8..51c4bf2c1b 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -29,6 +29,7 @@ import ( "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/ethash" "github.com/ethereum/go-ethereum/core" @@ -466,7 +467,7 @@ func newGQLService(t *testing.T, stack *node.Node, shanghai bool, gspec *core.Ge RPCGasCap: 1000000, StateScheme: rawdb.HashScheme, } - var engine = beacon.New(ethash.NewFaker()) + var engine consensus.Engine = ethash.NewFaker() if shanghai { engine = beacon.NewFaker() gspec.Config.TerminalTotalDifficulty = common.Big0 diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 45383ee02d..b7e559cac6 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -564,6 +564,12 @@ func (b testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.R receipts := rawdb.ReadReceipts(b.db, hash, header.Number.Uint64(), header.Time, b.chain.Config()) return receipts, nil } +func (b testBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { + if b.pending != nil && hash == b.pending.Hash() { + return nil + } + return big.NewInt(1) +} func (b testBackend) GetEVM(ctx context.Context, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockContext *vm.BlockContext) *vm.EVM { if vmConfig == nil { vmConfig = b.chain.GetVMConfig() diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index 667d34e494..a66f03ac40 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -380,6 +380,7 @@ func (b *backendMock) GetLogs(ctx context.Context, blockHash common.Hash, number //nolint:nilnil return nil, nil } +func (b *backendMock) GetTd(ctx context.Context, hash common.Hash) *big.Int { return nil } func (b *backendMock) GetEVM(ctx context.Context, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM { return nil } diff --git a/tests/block_test.go b/tests/block_test.go index 0d754bf902..7b37f234bc 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -57,8 +57,8 @@ func TestBlockchain(t *testing.T) { bt.skipLoad(`.*InvalidBlocks*`) bt.skipLoad(`.*TransitionTests*`) - // With chain history removal, TDs become unavailable, this transition tests based on TTD are unrunnable - bt.skipLoad(`.*bcArrowGlacierToParis/powToPosBlockRejection.json`) + // // With chain history removal, TDs become unavailable, this transition tests based on TTD are unrunnable + // bt.skipLoad(`.*bcArrowGlacierToParis/powToPosBlockRejection.json`) // This directory contains no test. bt.skipLoad(`.*\.meta/.*`)