diff --git a/core/beacon/errors.go b/core/beacon/errors.go index 6c1bc3547..4d039f936 100644 --- a/core/beacon/errors.go +++ b/core/beacon/errors.go @@ -21,6 +21,39 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) +// EngineAPIError is a standardized error message between consensus and execution +// clients, also containing any custom error message Geth might include. +type EngineAPIError struct { + code int + msg string + err error +} + +func (e *EngineAPIError) ErrorCode() int { return e.code } +func (e *EngineAPIError) Error() string { return e.msg } +func (e *EngineAPIError) ErrorData() interface{} { + if e.err == nil { + return nil + } + return struct { + Error string `json:"err"` + }{e.err.Error()} +} + +// With returns a copy of the error with a new embedded custom data field. +func (e *EngineAPIError) With(err error) *EngineAPIError { + return &EngineAPIError{ + code: e.code, + msg: e.msg, + err: err, + } +} + +var ( + _ rpc.Error = new(EngineAPIError) + _ rpc.DataError = new(EngineAPIError) +) + var ( // VALID is returned by the engine API in the following calls: // - newPayloadV1: if the payload was already known or was just validated and executed @@ -43,10 +76,10 @@ var ( INVALIDBLOCKHASH = "INVALID_BLOCK_HASH" - 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"} + GenericServerError = &EngineAPIError{code: -32000, msg: "Server error"} + UnknownPayload = &EngineAPIError{code: -38001, msg: "Unknown payload"} + InvalidForkChoiceState = &EngineAPIError{code: -38002, msg: "Invalid forkchoice state"} + InvalidPayloadAttributes = &EngineAPIError{code: -38003, msg: "Invalid payload attributes"} STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil} STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil} diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 556e4aec2..54090257e 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -166,10 +166,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, &beacon.InvalidForkChoiceState + return beacon.STATUS_INVALID, beacon.InvalidForkChoiceState.With(errors.New("final block not available in database")) } 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, &beacon.InvalidForkChoiceState + return beacon.STATUS_INVALID, beacon.InvalidForkChoiceState.With(errors.New("final block not in canonical chain")) } // Set the finalized block api.eth.BlockChain().SetFinalized(finalBlock) @@ -179,21 +179,19 @@ 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, &beacon.InvalidForkChoiceState + return beacon.STATUS_INVALID, beacon.InvalidForkChoiceState.With(errors.New("safe block not available in database")) } if rawdb.ReadCanonicalHash(api.eth.ChainDb(), safeBlock.NumberU64()) != update.SafeBlockHash { log.Warn("Safe block not in canonical chain") - return beacon.STATUS_INVALID, &beacon.InvalidForkChoiceState + return beacon.STATUS_INVALID, beacon.InvalidForkChoiceState.With(errors.New("safe block not in canonical chain")) } } - valid := func(id *beacon.PayloadID) beacon.ForkChoiceResponse { return beacon.ForkChoiceResponse{ PayloadStatus: beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &update.HeadBlockHash}, PayloadID: id, } } - // If payload generation was requested, create a new block to be potentially // sealed by the beacon client. The payload will be requested later, and we // might replace it arbitrarily many times in between. @@ -202,14 +200,14 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa empty, err := api.eth.Miner().GetSealingBlockSync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.Random, true) if err != nil { log.Error("Failed to create empty sealing payload", "err", err) - return valid(nil), &beacon.InvalidPayloadAttributes + return valid(nil), beacon.InvalidPayloadAttributes.With(err) } // 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 { log.Error("Failed to create async sealing payload", "err", err) - return valid(nil), &beacon.InvalidPayloadAttributes + return valid(nil), beacon.InvalidPayloadAttributes.With(err) } id := computePayloadId(update.HeadBlockHash, payloadAttributes) api.localBlocks.put(id, &payload{empty: empty, result: resCh}) @@ -248,7 +246,7 @@ func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.Execu log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID) data := api.localBlocks.get(payloadID) if data == nil { - return nil, &beacon.UnknownPayload + return nil, beacon.UnknownPayload } return data, nil } diff --git a/les/catalyst/api.go b/les/catalyst/api.go index 2877dae4c..de09acdb0 100644 --- a/les/catalyst/api.go +++ b/les/catalyst/api.go @@ -98,7 +98,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, pay // GetPayloadV1 returns a cached payload by id. It's not supported in les mode. func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableDataV1, error) { - return nil, &beacon.GenericServerError + return nil, beacon.GenericServerError.With(errors.New("not supported in light client mode")) } // ExecutePayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. @@ -157,7 +157,7 @@ func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error { // make sure the parent has enough terminal total difficulty header := api.les.BlockChain().GetHeaderByHash(head) if header == nil { - return &beacon.GenericServerError + return errors.New("unknown header") } td := api.les.BlockChain().GetTd(header.Hash(), header.Number.Uint64()) if td != nil && td.Cmp(api.les.BlockChain().Config().TerminalTotalDifficulty) < 0 { @@ -176,7 +176,7 @@ func (api *ConsensusAPI) setCanonical(newHead common.Hash) error { } newHeadHeader := api.les.BlockChain().GetHeaderByHash(newHead) if newHeadHeader == nil { - return &beacon.GenericServerError + return errors.New("unknown header") } if err := api.les.BlockChain().SetCanonical(newHeadHeader); err != nil { return err diff --git a/rpc/errors.go b/rpc/errors.go index 75425b925..4c06a745f 100644 --- a/rpc/errors.go +++ b/rpc/errors.go @@ -54,7 +54,6 @@ var ( _ Error = new(invalidRequestError) _ Error = new(invalidMessageError) _ Error = new(invalidParamsError) - _ Error = new(CustomError) ) const defaultErrorCode = -32000 @@ -102,12 +101,3 @@ type invalidParamsError struct{ message string } func (e *invalidParamsError) ErrorCode() int { return -32602 } func (e *invalidParamsError) Error() string { return e.message } - -type CustomError struct { - Code int - ValidationError string -} - -func (e *CustomError) ErrorCode() int { return e.Code } - -func (e *CustomError) Error() string { return e.ValidationError }