diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index cdacf354a5..b485833a02 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -83,19 +83,35 @@ 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 { - 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 + // During the live merge transision, the consensus engine used the terminal + // total difficulty to detect when PoW (PoA) switched to PoS. Maintainig 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 distinguis 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 transisioned 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 parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) if parent == nil { return consensus.ErrUnknownAncestor } - // Sanity checks passed, do a proper verification + 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) + } return beacon.verifyHeader(chain, header, parent) } diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 91b6511f71..c9948c5557 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -361,21 +361,12 @@ 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 { - 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 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 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 { + if ph.Difficulty.Sign() == 0 && block.Difficulty().Sign() > 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 } @@ -901,21 +892,6 @@ 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 e0a155f12b..b1348d07f9 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -164,24 +164,6 @@ 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 @@ -902,70 +884,6 @@ 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/ethstats/ethstats.go b/ethstats/ethstats.go index afed5332df..0090a7d4c1 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -65,7 +65,6 @@ 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 } @@ -628,7 +627,6 @@ 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 ) @@ -644,8 +642,6 @@ 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() @@ -656,7 +652,6 @@ 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 @@ -671,7 +666,7 @@ func (s *Service) assembleBlockStats(header *types.Header) *blockStats { GasUsed: header.GasUsed, GasLimit: header.GasLimit, Diff: header.Difficulty.String(), - TotalDiff: td.String(), + TotalDiff: "0", // unknown post-merge with pruned chain tail Txs: txs, TxHash: header.TxHash, Root: header.Root,