Skip to content

Commit 381c66c

Browse files
eth/catalyst: set the correct LatestValidHash (#24855)
* eth/catalyst: set the correct LatestValidHash * eth/catalyst: core: return LVH during reorg, rework invalid teminal block * eth/catalyst: nitpicks
1 parent 29a6b6b commit 381c66c

File tree

4 files changed

+39
-36
lines changed

4 files changed

+39
-36
lines changed

core/beacon/errors.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616

1717
package beacon
1818

19-
import "github.com/ethereum/go-ethereum/rpc"
19+
import (
20+
"github.com/ethereum/go-ethereum/common"
21+
"github.com/ethereum/go-ethereum/rpc"
22+
)
2023

2124
var (
2225
// VALID is returned by the engine API in the following calls:
@@ -38,13 +41,13 @@ var (
3841
// - newPayloadV1: if the payload was accepted, but not processed (side chain)
3942
ACCEPTED = "ACCEPTED"
4043

41-
INVALIDBLOCKHASH = "INVALID_BLOCK_HASH"
42-
INVALIDTERMINALBLOCK = "INVALID_TERMINAL_BLOCK"
44+
INVALIDBLOCKHASH = "INVALID_BLOCK_HASH"
4345

4446
GenericServerError = rpc.CustomError{Code: -32000, ValidationError: "Server error"}
4547
UnknownPayload = rpc.CustomError{Code: -32001, ValidationError: "Unknown payload"}
4648
InvalidTB = rpc.CustomError{Code: -32002, ValidationError: "Invalid terminal block"}
4749

48-
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
49-
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}
50+
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
51+
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}
52+
INVALID_TERMINAL_BLOCK = PayloadStatusV1{Status: INVALID, LatestValidHash: &common.Hash{}}
5053
)

core/blockchain.go

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,7 +1480,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool)
14801480
} else {
14811481
// We're post-merge and the parent is pruned, try to recover the parent state
14821482
log.Debug("Pruned ancestor", "number", block.Number(), "hash", block.Hash())
1483-
return it.index, bc.recoverAncestors(block)
1483+
_, err := bc.recoverAncestors(block)
1484+
return it.index, err
14841485
}
14851486
// First block is future, shove it (and all children) to the future queue (unknown ancestor)
14861487
case errors.Is(err, consensus.ErrFutureBlock) || (errors.Is(err, consensus.ErrUnknownAncestor) && bc.futureBlocks.Contains(it.first().ParentHash())):
@@ -1849,7 +1850,8 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
18491850
// recoverAncestors finds the closest ancestor with available state and re-execute
18501851
// all the ancestor blocks since that.
18511852
// recoverAncestors is only used post-merge.
1852-
func (bc *BlockChain) recoverAncestors(block *types.Block) error {
1853+
// We return the hash of the latest block that we could correctly validate.
1854+
func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error) {
18531855
// Gather all the sidechain hashes (full blocks may be memory heavy)
18541856
var (
18551857
hashes []common.Hash
@@ -1864,18 +1866,18 @@ func (bc *BlockChain) recoverAncestors(block *types.Block) error {
18641866
// If the chain is terminating, stop iteration
18651867
if bc.insertStopped() {
18661868
log.Debug("Abort during blocks iteration")
1867-
return errInsertionInterrupted
1869+
return common.Hash{}, errInsertionInterrupted
18681870
}
18691871
}
18701872
if parent == nil {
1871-
return errors.New("missing parent")
1873+
return common.Hash{}, errors.New("missing parent")
18721874
}
18731875
// Import all the pruned blocks to make the state available
18741876
for i := len(hashes) - 1; i >= 0; i-- {
18751877
// If the chain is terminating, stop processing blocks
18761878
if bc.insertStopped() {
18771879
log.Debug("Abort during blocks processing")
1878-
return errInsertionInterrupted
1880+
return common.Hash{}, errInsertionInterrupted
18791881
}
18801882
var b *types.Block
18811883
if i == 0 {
@@ -1884,10 +1886,10 @@ func (bc *BlockChain) recoverAncestors(block *types.Block) error {
18841886
b = bc.GetBlock(hashes[i], numbers[i])
18851887
}
18861888
if _, err := bc.insertChain(types.Blocks{b}, false, false); err != nil {
1887-
return err
1889+
return b.ParentHash(), err
18881890
}
18891891
}
1890-
return nil
1892+
return block.Hash(), nil
18911893
}
18921894

18931895
// collectLogs collects the logs that were generated or removed during
@@ -2090,24 +2092,24 @@ func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block) error {
20902092
// SetCanonical rewinds the chain to set the new head block as the specified
20912093
// block. It's possible that the state of the new head is missing, and it will
20922094
// be recovered in this function as well.
2093-
func (bc *BlockChain) SetCanonical(head *types.Block) error {
2095+
func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) {
20942096
if !bc.chainmu.TryLock() {
2095-
return errChainStopped
2097+
return common.Hash{}, errChainStopped
20962098
}
20972099
defer bc.chainmu.Unlock()
20982100

20992101
// Re-execute the reorged chain in case the head state is missing.
21002102
if !bc.HasState(head.Root()) {
2101-
if err := bc.recoverAncestors(head); err != nil {
2102-
return err
2103+
if latestValidHash, err := bc.recoverAncestors(head); err != nil {
2104+
return latestValidHash, err
21032105
}
21042106
log.Info("Recovered head state", "number", head.Number(), "hash", head.Hash())
21052107
}
21062108
// Run the reorg if necessary and set the given block as new head.
21072109
start := time.Now()
21082110
if head.ParentHash() != bc.CurrentBlock().Hash() {
21092111
if err := bc.reorg(bc.CurrentBlock(), head); err != nil {
2110-
return err
2112+
return common.Hash{}, err
21112113
}
21122114
}
21132115
bc.writeHeadBlock(head)
@@ -2130,7 +2132,7 @@ func (bc *BlockChain) SetCanonical(head *types.Block) error {
21302132
context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...)
21312133
}
21322134
log.Info("Chain head was updated", context...)
2133-
return nil
2135+
return head.Hash(), nil
21342136
}
21352137

21362138
func (bc *BlockChain) updateFutureBlocks() {

eth/catalyst/api.go

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -132,14 +132,14 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
132132
}
133133
if td.Cmp(ttd) < 0 || (block.NumberU64() > 0 && ptd.Cmp(ttd) > 0) {
134134
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)))
135-
return beacon.ForkChoiceResponse{PayloadStatus: beacon.PayloadStatusV1{Status: beacon.INVALIDTERMINALBLOCK}, PayloadID: nil}, nil
135+
return beacon.ForkChoiceResponse{PayloadStatus: beacon.INVALID_TERMINAL_BLOCK, PayloadID: nil}, nil
136136
}
137137
}
138138

139139
if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) != update.HeadBlockHash {
140140
// Block is not canonical, set head.
141-
if err := api.eth.BlockChain().SetCanonical(block); err != nil {
142-
return beacon.STATUS_INVALID, err
141+
if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil {
142+
return beacon.ForkChoiceResponse{PayloadStatus: beacon.PayloadStatusV1{Status: beacon.INVALID, LatestValidHash: &latestValid}}, err
143143
}
144144
} else {
145145
// If the head block is already in our canonical chain, the beacon client is
@@ -176,6 +176,14 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
176176
return beacon.STATUS_INVALID, errors.New("safe head not canonical")
177177
}
178178
}
179+
180+
valid := func(id *beacon.PayloadID) beacon.ForkChoiceResponse {
181+
return beacon.ForkChoiceResponse{
182+
PayloadStatus: beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &update.HeadBlockHash},
183+
PayloadID: id,
184+
}
185+
}
186+
179187
// If payload generation was requested, create a new block to be potentially
180188
// sealed by the beacon client. The payload will be requested later, and we
181189
// might replace it arbitrarily many times in between.
@@ -186,25 +194,15 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
186194
data, err := api.assembleBlock(update.HeadBlockHash, payloadAttributes)
187195
if err != nil {
188196
log.Error("Failed to create sealing payload", "err", err)
189-
return api.validForkChoiceResponse(nil), err // valid setHead, invalid payload
197+
return valid(nil), err // valid setHead, invalid payload
190198
}
191199
id := computePayloadId(update.HeadBlockHash, payloadAttributes)
192200
api.localBlocks.put(id, data)
193201

194202
log.Info("Created payload for sealing", "id", id, "elapsed", time.Since(start))
195-
return api.validForkChoiceResponse(&id), nil
196-
}
197-
return api.validForkChoiceResponse(nil), nil
198-
}
199-
200-
// validForkChoiceResponse returns the ForkChoiceResponse{VALID}
201-
// with the latest valid hash and an optional payloadID.
202-
func (api *ConsensusAPI) validForkChoiceResponse(id *beacon.PayloadID) beacon.ForkChoiceResponse {
203-
currentHash := api.eth.BlockChain().CurrentBlock().Hash()
204-
return beacon.ForkChoiceResponse{
205-
PayloadStatus: beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &currentHash},
206-
PayloadID: id,
203+
return valid(&id), nil
207204
}
205+
return valid(nil), nil
208206
}
209207

210208
// ExchangeTransitionConfigurationV1 checks the given configuration against
@@ -291,7 +289,7 @@ func (api *ConsensusAPI) NewPayloadV1(params beacon.ExecutableDataV1) (beacon.Pa
291289
)
292290
if td.Cmp(ttd) < 0 {
293291
log.Warn("Ignoring pre-merge payload", "number", params.Number, "hash", params.BlockHash, "td", td, "ttd", ttd)
294-
return beacon.PayloadStatusV1{Status: beacon.INVALIDTERMINALBLOCK}, nil
292+
return beacon.INVALID_TERMINAL_BLOCK, nil
295293
}
296294
if block.Time() <= parent.Time() {
297295
log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time())

eth/catalyst/api_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func TestSetHeadBeforeTotalDifficulty(t *testing.T) {
136136
}
137137
if resp, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
138138
t.Errorf("fork choice updated should not error: %v", err)
139-
} else if resp.PayloadStatus.Status != beacon.INVALIDTERMINALBLOCK {
139+
} else if resp.PayloadStatus.Status != beacon.INVALID_TERMINAL_BLOCK.Status {
140140
t.Errorf("fork choice updated before total terminal difficulty should be INVALID")
141141
}
142142
}

0 commit comments

Comments
 (0)