consensus, eth/catalyst: remove TD checks and enforcement

This commit is contained in:
Péter Szilágyi 2024-11-11 01:46:06 +07:00 committed by Gary Rong
parent a840e9b59f
commit 776d8ec1c1
4 changed files with 31 additions and 126 deletions

View file

@ -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 // VerifyHeader checks whether a header conforms to the consensus rules of the
// stock Ethereum consensus engine. // stock Ethereum consensus engine.
func (beacon *Beacon) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header) error { func (beacon *Beacon) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header) error {
reached, err := IsTTDReached(chain, header.ParentHash, header.Number.Uint64()-1) // During the live merge transision, the consensus engine used the terminal
if err != nil { // total difficulty to detect when PoW (PoA) switched to PoS. Maintainig the
return err // 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
if !reached { // the chain is pruned already during sync.
return beacon.ethone.VerifyHeader(chain, header) //
} // One heuristic that can be used to distinguis pre-merge and post-merge
// Short circuit if the parent is not known // 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) parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
if parent == nil { if parent == nil {
return consensus.ErrUnknownAncestor 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) return beacon.verifyHeader(chain, header, parent)
} }

View file

@ -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 // Block is known locally, just sanity check that the beacon client does not
// attempt to push us back to before the merge. // attempt to push us back to before the merge.
if block.Difficulty().BitLen() > 0 || block.NumberU64() == 0 { if block.Difficulty().BitLen() > 0 && block.NumberU64() > 0 {
var ( ph := api.eth.BlockChain().GetHeader(block.ParentHash(), block.NumberU64()-1)
td = api.eth.BlockChain().GetTd(update.HeadBlockHash, block.NumberU64()) if ph == nil {
ptd = api.eth.BlockChain().GetTd(block.ParentHash(), block.NumberU64()-1) return engine.STATUS_INVALID, errors.New("parent unavailable for difficulty check")
ttd = api.eth.BlockChain().Config().TerminalTotalDifficulty
)
if td == nil || (block.NumberU64() > 0 && ptd == nil) {
log.Error("TDs unavailable for TTD check", "number", block.NumberU64(), "hash", update.HeadBlockHash, "td", td, "parent", block.ParentHash(), "ptd", ptd)
return engine.STATUS_INVALID, errors.New("TDs unavailable for TDD check")
} }
if td.Cmp(ttd) < 0 { if ph.Difficulty.Sign() == 0 && block.Difficulty().Sign() > 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))) 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 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 { if parent == nil {
return api.delayPayloadImport(block), 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() { if block.Time() <= parent.Time() {
log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time()) log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time())
return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil

View file

@ -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) { func TestEth2PrepareAndGetPayload(t *testing.T) {
genesis, blocks := generateMergeChain(10, false) genesis, blocks := generateMergeChain(10, false)
// We need to properly set the terminal total difficulty // 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 // TestSimultaneousNewBlock does several parallel inserts, both as
// newPayLoad and forkchoiceUpdate. This is to test that the api behaves // newPayLoad and forkchoiceUpdate. This is to test that the api behaves
// well even of the caller is not being 'serial'. // well even of the caller is not being 'serial'.

View file

@ -65,7 +65,6 @@ type backend interface {
SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription
CurrentHeader() *types.Header CurrentHeader() *types.Header
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
GetTd(ctx context.Context, hash common.Hash) *big.Int
Stats() (pending int, queued int) Stats() (pending int, queued int)
SyncProgress() ethereum.SyncProgress 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 { func (s *Service) assembleBlockStats(header *types.Header) *blockStats {
// Gather the block infos from the local blockchain // Gather the block infos from the local blockchain
var ( var (
td *big.Int
txs []txStats txs []txStats
uncles []*types.Header uncles []*types.Header
) )
@ -644,8 +642,6 @@ func (s *Service) assembleBlockStats(header *types.Header) *blockStats {
if block == nil { if block == nil {
return nil return nil
} }
td = fullBackend.GetTd(context.Background(), header.Hash())
txs = make([]txStats, len(block.Transactions())) txs = make([]txStats, len(block.Transactions()))
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
txs[i].Hash = tx.Hash() txs[i].Hash = tx.Hash()
@ -656,7 +652,6 @@ func (s *Service) assembleBlockStats(header *types.Header) *blockStats {
if header == nil { if header == nil {
header = s.backend.CurrentHeader() header = s.backend.CurrentHeader()
} }
td = s.backend.GetTd(context.Background(), header.Hash())
txs = []txStats{} txs = []txStats{}
} }
// Assemble and return the block stats // Assemble and return the block stats
@ -671,7 +666,7 @@ func (s *Service) assembleBlockStats(header *types.Header) *blockStats {
GasUsed: header.GasUsed, GasUsed: header.GasUsed,
GasLimit: header.GasLimit, GasLimit: header.GasLimit,
Diff: header.Difficulty.String(), Diff: header.Difficulty.String(),
TotalDiff: td.String(), TotalDiff: "0", // unknown post-merge with pruned chain tail
Txs: txs, Txs: txs,
TxHash: header.TxHash, TxHash: header.TxHash,
Root: header.Root, Root: header.Root,