Skip to content

Commit 4a4d531

Browse files
eth/catalyst: update implementation to spec (#24802)
* eth/catalyst: return invalid payload attributes error * eth/catalyst: implement LVH as specified, add tests * eth/catalyst: return current block hash not header hash * eth/catalyst: fix test * eth/catalyst: bring error codes in line with spec
1 parent 310f751 commit 4a4d531

File tree

4 files changed

+283
-85
lines changed

4 files changed

+283
-85
lines changed

core/beacon/errors.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,10 @@ var (
4343

4444
INVALIDBLOCKHASH = "INVALID_BLOCK_HASH"
4545

46-
GenericServerError = rpc.CustomError{Code: -32000, ValidationError: "Server error"}
47-
UnknownPayload = rpc.CustomError{Code: -32001, ValidationError: "Unknown payload"}
48-
InvalidTB = rpc.CustomError{Code: -32002, ValidationError: "Invalid terminal block"}
46+
GenericServerError = rpc.CustomError{Code: -32000, ValidationError: "Server error"}
47+
UnknownPayload = rpc.CustomError{Code: -38001, ValidationError: "Unknown payload"}
48+
InvalidForkChoiceState = rpc.CustomError{Code: -38002, ValidationError: "Invalid forkchoice state"}
49+
InvalidPayloadAttributes = rpc.CustomError{Code: -38003, ValidationError: "Invalid payload attributes"}
4950

5051
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
5152
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}

eth/catalyst/api.go

+18-11
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/ethereum/go-ethereum/common/hexutil"
3030
"github.com/ethereum/go-ethereum/core/beacon"
3131
"github.com/ethereum/go-ethereum/core/rawdb"
32+
"github.com/ethereum/go-ethereum/core/types"
3233
"github.com/ethereum/go-ethereum/eth"
3334
"github.com/ethereum/go-ethereum/log"
3435
"github.com/ethereum/go-ethereum/node"
@@ -165,10 +166,10 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
165166
finalBlock := api.eth.BlockChain().GetBlockByHash(update.FinalizedBlockHash)
166167
if finalBlock == nil {
167168
log.Warn("Final block not available in database", "hash", update.FinalizedBlockHash)
168-
return beacon.STATUS_INVALID, errors.New("final block not available")
169+
return beacon.STATUS_INVALID, &beacon.InvalidForkChoiceState
169170
} else if rawdb.ReadCanonicalHash(api.eth.ChainDb(), finalBlock.NumberU64()) != update.FinalizedBlockHash {
170171
log.Warn("Final block not in canonical chain", "number", block.NumberU64(), "hash", update.HeadBlockHash)
171-
return beacon.STATUS_INVALID, errors.New("final block not canonical")
172+
return beacon.STATUS_INVALID, &beacon.InvalidForkChoiceState
172173
}
173174
// Set the finalized block
174175
api.eth.BlockChain().SetFinalized(finalBlock)
@@ -178,11 +179,11 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
178179
safeBlock := api.eth.BlockChain().GetBlockByHash(update.SafeBlockHash)
179180
if safeBlock == nil {
180181
log.Warn("Safe block not available in database")
181-
return beacon.STATUS_INVALID, errors.New("safe head not available")
182+
return beacon.STATUS_INVALID, &beacon.InvalidForkChoiceState
182183
}
183184
if rawdb.ReadCanonicalHash(api.eth.ChainDb(), safeBlock.NumberU64()) != update.SafeBlockHash {
184185
log.Warn("Safe block not in canonical chain")
185-
return beacon.STATUS_INVALID, errors.New("safe head not canonical")
186+
return beacon.STATUS_INVALID, &beacon.InvalidForkChoiceState
186187
}
187188
}
188189

@@ -200,13 +201,15 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
200201
// Create an empty block first which can be used as a fallback
201202
empty, err := api.eth.Miner().GetSealingBlockSync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.Random, true)
202203
if err != nil {
203-
return valid(nil), err
204+
log.Error("Failed to create empty sealing payload", "err", err)
205+
return valid(nil), &beacon.InvalidPayloadAttributes
204206
}
205207
// Send a request to generate a full block in the background.
206208
// The result can be obtained via the returned channel.
207209
resCh, err := api.eth.Miner().GetSealingBlockAsync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.Random, false)
208210
if err != nil {
209-
return valid(nil), err
211+
log.Error("Failed to create async sealing payload", "err", err)
212+
return valid(nil), &beacon.InvalidPayloadAttributes
210213
}
211214
id := computePayloadId(update.HeadBlockHash, payloadAttributes)
212215
api.localBlocks.put(id, &payload{empty: empty, result: resCh})
@@ -303,7 +306,7 @@ func (api *ConsensusAPI) NewPayloadV1(params beacon.ExecutableDataV1) (beacon.Pa
303306
}
304307
if block.Time() <= parent.Time() {
305308
log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time())
306-
return api.invalid(errors.New("invalid timestamp")), nil
309+
return api.invalid(errors.New("invalid timestamp"), parent), nil
307310
}
308311
if !api.eth.BlockChain().HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
309312
api.remoteBlocks.put(block.Hash(), block.Header())
@@ -313,7 +316,7 @@ func (api *ConsensusAPI) NewPayloadV1(params beacon.ExecutableDataV1) (beacon.Pa
313316
log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number)
314317
if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil {
315318
log.Warn("NewPayloadV1: inserting block failed", "error", err)
316-
return api.invalid(err), nil
319+
return api.invalid(err, parent), nil
317320
}
318321
// We've accepted a valid payload from the beacon client. Mark the local
319322
// chain transitions to notify other subsystems (e.g. downloader) of the
@@ -339,9 +342,13 @@ func computePayloadId(headBlockHash common.Hash, params *beacon.PayloadAttribute
339342
return out
340343
}
341344

342-
// invalid returns a response "INVALID" with the latest valid hash set to the current head.
343-
func (api *ConsensusAPI) invalid(err error) beacon.PayloadStatusV1 {
344-
currentHash := api.eth.BlockChain().CurrentHeader().Hash()
345+
// invalid returns a response "INVALID" with the latest valid hash supplied by latest or to the current head
346+
// if no latestValid block was provided.
347+
func (api *ConsensusAPI) invalid(err error, latestValid *types.Block) beacon.PayloadStatusV1 {
348+
currentHash := api.eth.BlockChain().CurrentBlock().Hash()
349+
if latestValid != nil {
350+
currentHash = latestValid.Hash()
351+
}
345352
errorMsg := err.Error()
346353
return beacon.PayloadStatusV1{Status: beacon.INVALID, LatestValidHash: &currentHash, ValidationError: &errorMsg}
347354
}

0 commit comments

Comments
 (0)