Skip to content

Commit

Permalink
raises for beacon validators & router (#5826)
Browse files Browse the repository at this point in the history
Changes here are more significant because of some good old tech debt in
block production which has grown quite hairy - the reduction in
exception handling at least provides some steps in the right direction.
  • Loading branch information
arnetheduck committed Feb 7, 2024
1 parent 94a65c2 commit 47704bd
Show file tree
Hide file tree
Showing 8 changed files with 845 additions and 910 deletions.
19 changes: 10 additions & 9 deletions beacon_chain/el/el_manager.nim
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@ proc getPayload*(m: ELManager,
randomData: Eth2Digest,
suggestedFeeRecipient: Eth1Address,
withdrawals: seq[capella.Withdrawal]):
Future[Opt[PayloadType]] {.async.} =
Future[Opt[PayloadType]] {.async: (raises: [CancelledError]).} =
if m.elConnections.len == 0:
return err()

Expand All @@ -790,6 +790,7 @@ proc getPayload*(m: ELManager,
))
requestsCompleted = allFutures(requests)

# TODO cancel requests on cancellation
await requestsCompleted or deadline

var bestPayloadIdx = none int
Expand All @@ -807,40 +808,40 @@ proc getPayload*(m: ELManager,
# TODO: The engine_api module may offer an alternative API where it is guaranteed
# to return the correct response type (i.e. the rule below will be enforced
# during deserialization).
if req.read.executionPayload.withdrawals.isNone:
if req.value().executionPayload.withdrawals.isNone:
warn "Execution client returned a block without a 'withdrawals' field for a post-Shanghai block",
url = m.elConnections[idx].engineUrl.url
continue

if engineApiWithdrawals != req.read.executionPayload.withdrawals.maybeDeref:
if engineApiWithdrawals != req.value().executionPayload.withdrawals.maybeDeref:
# otherwise it formats as "@[(index: ..., validatorIndex: ...,
# address: ..., amount: ...), (index: ..., validatorIndex: ...,
# address: ..., amount: ...)]"
warn "Execution client did not return correct withdrawals",
withdrawals_from_cl_len = engineApiWithdrawals.len,
withdrawals_from_el_len =
req.read.executionPayload.withdrawals.maybeDeref.len,
req.value().executionPayload.withdrawals.maybeDeref.len,
withdrawals_from_cl =
mapIt(engineApiWithdrawals, it.asConsensusWithdrawal),
withdrawals_from_el =
mapIt(
req.read.executionPayload.withdrawals.maybeDeref,
req.value().executionPayload.withdrawals.maybeDeref,
it.asConsensusWithdrawal)

if req.read.executionPayload.extraData.len > MAX_EXTRA_DATA_BYTES:
if req.value().executionPayload.extraData.len > MAX_EXTRA_DATA_BYTES:
warn "Execution client provided a block with invalid extraData (size exceeds limit)",
size = req.read.executionPayload.extraData.len,
size = req.value().executionPayload.extraData.len,
limit = MAX_EXTRA_DATA_BYTES
continue

if bestPayloadIdx.isNone:
bestPayloadIdx = some idx
else:
if cmpGetPayloadResponses(req.read, requests[bestPayloadIdx.get].read) > 0:
if cmpGetPayloadResponses(req.value(), requests[bestPayloadIdx.get].value()) > 0:
bestPayloadIdx = some idx

if bestPayloadIdx.isSome:
return ok requests[bestPayloadIdx.get].read.asConsensusType
return ok requests[bestPayloadIdx.get].value().asConsensusType
else:
return err()

Expand Down
231 changes: 114 additions & 117 deletions beacon_chain/spec/eth2_apis/rest_remote_signer_calls.nim
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ proc signDataPlain*(identifier: ValidatorPubKey,
proc init(t: typedesc[Web3SignerError], kind: Web3SignerErrorKind,
message: string): Web3SignerError =
Web3SignerError(kind: kind, message: message)

proc signData*(client: RestClientRef, identifier: ValidatorPubKey,
body: Web3SignerRequest
): Future[Web3SignerDataResponse] {.async.} =
): Future[Web3SignerDataResponse]
{.async: (raises: [CancelledError]).} =
inc(nbc_remote_signer_requests)

let
Expand All @@ -111,132 +111,126 @@ proc signData*(client: RestClientRef, identifier: ValidatorPubKey,
except RestError as exc:
return Web3SignerDataResponse.err(
Web3SignerError.init(Web3SignerErrorKind.CommError, $exc.msg))
except CancelledError as exc:
raise exc
except CatchableError as exc:
return Web3SignerDataResponse.err(
Web3SignerError.init(Web3SignerErrorKind.UnexpectedError, $exc.msg))

return
case response.status
of 200:
inc(nbc_remote_signer_200_responses)
let sig =
if response.contentType.isNone() or
isWildCard(response.contentType.get().mediaType):
inc(nbc_remote_signer_failures)
return Web3SignerDataResponse.err(
Web3SignerError.init(
Web3SignerErrorKind.InvalidContentType,
"Unable to decode signature from missing or incorrect content"
)
)
else:
let mediaType = response.contentType.get().mediaType
if mediaType == TextPlainMediaType:
let
asStr = fromBytes(string, response.data)
sigFromText = fromHex(ValidatorSig, asStr).valueOr:
inc(nbc_remote_signer_failures)
return Web3SignerDataResponse.err(
Web3SignerError.init(
Web3SignerErrorKind.InvalidPlain,
"Unable to decode signature from plain text"
)
)
sigFromText.load()
else:
let res = decodeBytes(Web3SignerSignatureResponse, response.data,
response.contentType).valueOr:
inc(nbc_remote_signer_failures)
return Web3SignerDataResponse.err(
Web3SignerError.init(
Web3SignerErrorKind.InvalidContent,
"Unable to decode remote signer response [" & $error & "]"
)
)
res.signature.load()

if sig.isNone():
case response.status
of 200:
inc(nbc_remote_signer_200_responses)
let sig = block:
if response.contentType.isNone() or
isWildCard(response.contentType.get().mediaType):
inc(nbc_remote_signer_failures)
return Web3SignerDataResponse.err(
Web3SignerError.init(
Web3SignerErrorKind.InvalidSignature,
"Remote signer returns invalid signature"
Web3SignerErrorKind.InvalidContentType,
"Unable to decode signature from missing or incorrect content"
)
)

inc(nbc_remote_signer_signatures)
Web3SignerDataResponse.ok(sig.get())
of 400:
inc(nbc_remote_signer_400_responses)
let message =
block:
let res = decodeBytes(Web3SignerErrorResponse, response.data,
response.contentType)
if res.isErr():
"Remote signer returns 400 Bad Request Format Error"
else:
res.get().error
Web3SignerDataResponse.err(
Web3SignerError.init(Web3SignerErrorKind.Error400, message))
of 404:
inc(nbc_remote_signer_404_responses)
let message =
block:
let res = decodeBytes(Web3SignerErrorResponse, response.data,
response.contentType)
if res.isErr():
"Remote signer returns 404 Validator's Key Not Found Error"
else:
res.get().error
Web3SignerDataResponse.err(
Web3SignerError.init(Web3SignerErrorKind.Error404, message))
of 412:
inc(nbc_remote_signer_412_responses)
let message =
block:
let res = decodeBytes(Web3SignerErrorResponse, response.data,
response.contentType)
if res.isErr():
"Remote signer returns 412 Slashing Protection Error"
else:
res.get().error
Web3SignerDataResponse.err(
Web3SignerError.init(Web3SignerErrorKind.Error412, message))
of 500:
inc(nbc_remote_signer_500_responses)
let message =
block:
let res = decodeBytes(Web3SignerErrorResponse, response.data,
response.contentType)
if res.isErr():
"Remote signer returns 500 Internal Server Error"
else:
res.get().error
Web3SignerDataResponse.err(
Web3SignerError.init(Web3SignerErrorKind.Error500, message))
else:
inc(nbc_remote_signer_unknown_responses)
let message =
block:
let res = decodeBytes(Web3SignerErrorResponse, response.data,
response.contentType)
if res.isErr():
"Remote signer returns unexpected status code " &
Base10.toString(uint64(response.status))
else:
res.get().error
Web3SignerDataResponse.err(
Web3SignerError.init(Web3SignerErrorKind.UknownStatus, message))
let mediaType = response.contentType.get().mediaType
if mediaType == TextPlainMediaType:
let
asStr = fromBytes(string, response.data)
sigFromText = fromHex(ValidatorSig, asStr).valueOr:
inc(nbc_remote_signer_failures)
return Web3SignerDataResponse.err(
Web3SignerError.init(
Web3SignerErrorKind.InvalidPlain,
"Unable to decode signature from plain text"
)
)
sigFromText.load()
else:
let res = decodeBytes(Web3SignerSignatureResponse, response.data,
response.contentType).valueOr:
inc(nbc_remote_signer_failures)
return Web3SignerDataResponse.err(
Web3SignerError.init(
Web3SignerErrorKind.InvalidContent,
"Unable to decode remote signer response [" & $error & "]"
)
)
res.signature.load()

if sig.isNone():
inc(nbc_remote_signer_failures)
return Web3SignerDataResponse.err(
Web3SignerError.init(
Web3SignerErrorKind.InvalidSignature,
"Remote signer returns invalid signature"
)
)

inc(nbc_remote_signer_signatures)
Web3SignerDataResponse.ok(sig.get())
of 400:
inc(nbc_remote_signer_400_responses)
let message =
block:
let res = decodeBytes(Web3SignerErrorResponse, response.data,
response.contentType)
if res.isErr():
"Remote signer returns 400 Bad Request Format Error"
else:
res.get().error
Web3SignerDataResponse.err(
Web3SignerError.init(Web3SignerErrorKind.Error400, message))
of 404:
inc(nbc_remote_signer_404_responses)
let message =
block:
let res = decodeBytes(Web3SignerErrorResponse, response.data,
response.contentType)
if res.isErr():
"Remote signer returns 404 Validator's Key Not Found Error"
else:
res.get().error
Web3SignerDataResponse.err(
Web3SignerError.init(Web3SignerErrorKind.Error404, message))
of 412:
inc(nbc_remote_signer_412_responses)
let message =
block:
let res = decodeBytes(Web3SignerErrorResponse, response.data,
response.contentType)
if res.isErr():
"Remote signer returns 412 Slashing Protection Error"
else:
res.get().error
Web3SignerDataResponse.err(
Web3SignerError.init(Web3SignerErrorKind.Error412, message))
of 500:
inc(nbc_remote_signer_500_responses)
let message =
block:
let res = decodeBytes(Web3SignerErrorResponse, response.data,
response.contentType)
if res.isErr():
"Remote signer returns 500 Internal Server Error"
else:
res.get().error
Web3SignerDataResponse.err(
Web3SignerError.init(Web3SignerErrorKind.Error500, message))
else:
inc(nbc_remote_signer_unknown_responses)
let message =
block:
let res = decodeBytes(Web3SignerErrorResponse, response.data,
response.contentType)
if res.isErr():
"Remote signer returns unexpected status code " &
Base10.toString(uint64(response.status))
else:
res.get().error
Web3SignerDataResponse.err(
Web3SignerError.init(Web3SignerErrorKind.UknownStatus, message))

proc signData*(
client: RestClientRef,
identifier: ValidatorPubKey,
timerFut: Future[void],
attemptsCount: int,
body: Web3SignerRequest
): Future[Web3SignerDataResponse] {.async.} =
): Future[Web3SignerDataResponse] {.async: (raises: [CancelledError]).} =
doAssert(attemptsCount >= 1)

const BackoffTimeouts = [
Expand All @@ -249,14 +243,17 @@ proc signData*(

while true:
var
operationFut: Future[Web3SignerDataResponse]
operationFut: Future[Web3SignerDataResponse].Raising([CancelledError])
lastError: Opt[Web3SignerError]
try:
operationFut = signData(client, identifier, body)
if isNil(timerFut):
await allFutures(operationFut)
else:
discard await race(timerFut, operationFut)
try:
discard await race(timerFut, operationFut)
except ValueError:
raiseAssert "race precondition satisfied"
except CancelledError as exc:
if not(operationFut.finished()):
await operationFut.cancelAndWait()
Expand All @@ -275,7 +272,7 @@ proc signData*(
)
)
else:
let resp = operationFut.read()
let resp = await operationFut
if resp.isOk():
return resp

Expand Down

0 comments on commit 47704bd

Please sign in to comment.