Skip to content

Commit

Permalink
eth/catalyst: update implementation to spec (ethereum#24802)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
MariusVanDerWijden authored and sadoci committed Jan 27, 2023
1 parent 75f3bb8 commit 3e1ebb5
Show file tree
Hide file tree
Showing 4 changed files with 283 additions and 85 deletions.
7 changes: 4 additions & 3 deletions core/beacon/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@ var (

INVALIDBLOCKHASH = "INVALID_BLOCK_HASH"

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

STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}
Expand Down
29 changes: 18 additions & 11 deletions eth/catalyst/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
Expand Down Expand Up @@ -172,10 +173,10 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
finalBlock := api.eth.BlockChain().GetBlockByHash(update.FinalizedBlockHash)
if finalBlock == nil {
log.Warn("Final block not available in database", "hash", update.FinalizedBlockHash)
return beacon.STATUS_INVALID, errors.New("final block not available")
return beacon.STATUS_INVALID, &beacon.InvalidForkChoiceState
} else if rawdb.ReadCanonicalHash(api.eth.ChainDb(), finalBlock.NumberU64()) != update.FinalizedBlockHash {
log.Warn("Final block not in canonical chain", "number", block.NumberU64(), "hash", update.HeadBlockHash)
return beacon.STATUS_INVALID, errors.New("final block not canonical")
return beacon.STATUS_INVALID, &beacon.InvalidForkChoiceState
}
// Set the finalized block
api.eth.BlockChain().SetFinalized(finalBlock)
Expand All @@ -185,11 +186,11 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
safeBlock := api.eth.BlockChain().GetBlockByHash(update.SafeBlockHash)
if safeBlock == nil {
log.Warn("Safe block not available in database")
return beacon.STATUS_INVALID, errors.New("safe head not available")
return beacon.STATUS_INVALID, &beacon.InvalidForkChoiceState
}
if rawdb.ReadCanonicalHash(api.eth.ChainDb(), safeBlock.NumberU64()) != update.SafeBlockHash {
log.Warn("Safe block not in canonical chain")
return beacon.STATUS_INVALID, errors.New("safe head not canonical")
return beacon.STATUS_INVALID, &beacon.InvalidForkChoiceState
}
}

Expand All @@ -207,13 +208,15 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
// Create an empty block first which can be used as a fallback
empty, err := api.eth.Miner().GetSealingBlockSync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.Random, true)
if err != nil {
return valid(nil), err
log.Error("Failed to create empty sealing payload", "err", err)
return valid(nil), &beacon.InvalidPayloadAttributes
}
// Send a request to generate a full block in the background.
// The result can be obtained via the returned channel.
resCh, err := api.eth.Miner().GetSealingBlockAsync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.Random, false)
if err != nil {
return valid(nil), err
log.Error("Failed to create async sealing payload", "err", err)
return valid(nil), &beacon.InvalidPayloadAttributes
}
id := computePayloadId(update.HeadBlockHash, payloadAttributes)
api.localBlocks.put(id, &payload{empty: empty, result: resCh})
Expand Down Expand Up @@ -310,7 +313,7 @@ func (api *ConsensusAPI) NewPayloadV1(params beacon.ExecutableDataV1) (beacon.Pa
}
if block.Time() <= parent.Time() {
log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time())
return api.invalid(errors.New("invalid timestamp")), nil
return api.invalid(errors.New("invalid timestamp"), parent), nil
}
if !api.eth.BlockChain().HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
api.remoteBlocks.put(block.Hash(), block.Header())
Expand All @@ -320,7 +323,7 @@ func (api *ConsensusAPI) NewPayloadV1(params beacon.ExecutableDataV1) (beacon.Pa
log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number)
if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil {
log.Warn("NewPayloadV1: inserting block failed", "error", err)
return api.invalid(err), nil
return api.invalid(err, parent), nil
}
// We've accepted a valid payload from the beacon client. Mark the local
// chain transitions to notify other subsystems (e.g. downloader) of the
Expand All @@ -346,9 +349,13 @@ func computePayloadId(headBlockHash common.Hash, params *beacon.PayloadAttribute
return out
}

// invalid returns a response "INVALID" with the latest valid hash set to the current head.
func (api *ConsensusAPI) invalid(err error) beacon.PayloadStatusV1 {
currentHash := api.eth.BlockChain().CurrentHeader().Hash()
// invalid returns a response "INVALID" with the latest valid hash supplied by latest or to the current head
// if no latestValid block was provided.
func (api *ConsensusAPI) invalid(err error, latestValid *types.Block) beacon.PayloadStatusV1 {
currentHash := api.eth.BlockChain().CurrentBlock().Hash()
if latestValid != nil {
currentHash = latestValid.Hash()
}
errorMsg := err.Error()
return beacon.PayloadStatusV1{Status: beacon.INVALID, LatestValidHash: &currentHash, ValidationError: &errorMsg}
}
Loading

0 comments on commit 3e1ebb5

Please sign in to comment.