Skip to content

Commit 645f1ed

Browse files
authored
Add SSZ encoding support to REST API endpoint /eth/v1/validator/register_validator (#6943)
* Add SSZ version of /eth/v1/validator/register_validator * Add ability to test `application/octet-stream` bodies to `restclient`. Add tests for SSZ version `/eth/v1/validator/register_validator`. * Fix test value to be correct hexadecimal string. * Update copyright years.
1 parent 4b864cc commit 645f1ed

5 files changed

Lines changed: 86 additions & 20 deletions

File tree

beacon_chain/rpc/rest_constants.nim

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# beacon_chain
2-
# Copyright (c) 2021-2024 Status Research & Development GmbH
2+
# Copyright (c) 2021-2025 Status Research & Development GmbH
33
# Licensed and distributed under either of
44
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
55
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@@ -271,3 +271,4 @@ const
271271
"Unable to load state for parent block, database corrupt?"
272272
RewardOverflowError* =
273273
"Reward value overflow"
274+
InvalidContentTypeError* = "Invalid content type"

beacon_chain/rpc/rest_validator_api.nim

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2018-2024 Status Research & Development GmbH
1+
# Copyright (c) 2018-2025 Status Research & Development GmbH
22
# Licensed and distributed under either of
33
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
44
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@@ -1215,25 +1215,21 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
12151215
router.api2(MethodPost,
12161216
"/eth/v1/validator/register_validator") do (
12171217
contentBody: Option[ContentBody]) -> RestApiResponse:
1218+
if contentBody.isNone():
1219+
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
12181220
let
1219-
body =
1220-
block:
1221-
if contentBody.isNone():
1222-
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
1223-
let dres = decodeBody(seq[SignedValidatorRegistrationV1], contentBody.get())
1224-
if dres.isErr():
1225-
return RestApiResponse.jsonError(Http400,
1226-
InvalidPrepareBeaconProposerError)
1227-
dres.get()
1221+
body = decodeBodyJsonOrSsz(seq[SignedValidatorRegistrationV1],
1222+
contentBody.get()).valueOr:
1223+
return RestApiResponse.jsonError(error)
12281224

1229-
for signedValidatorRegistration in body:
1225+
for registration in body:
12301226
# Don't validate beyond syntactically, because
12311227
# "requests containing currently inactive or unknown validator pubkeys
12321228
# will be accepted, as they may become active at a later epoch". Along
12331229
# these lines, even if it's adding a validator the BN already has as a
12341230
# local validator, the keymanager API might remove that from the BN.
1235-
node.externalBuilderRegistrations[signedValidatorRegistration.message.pubkey] =
1236-
signedValidatorRegistration
1231+
node.externalBuilderRegistrations[registration.message.pubkey] =
1232+
registration
12371233

12381234
RestApiResponse.response(Http200)
12391235

beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3036,7 +3036,7 @@ proc decodeBody*(
30363036
[version, $exc.msg]))
30373037
ok(RestPublishedSignedBeaconBlock(ForkedSignedBeaconBlock.init(blck)))
30383038
else:
3039-
err(RestErrorMessage.init(Http415, "Invalid content type",
3039+
err(RestErrorMessage.init(Http415, InvalidContentTypeError,
30403040
[version, $body.contentType]))
30413041

30423042
proc decodeBody*(
@@ -3232,9 +3232,45 @@ proc decodeBody*(
32323232
ok(RestPublishedSignedBlockContents(
32333233
kind: ConsensusFork.Fulu, fuluData: blckContents))
32343234
else:
3235-
err(RestErrorMessage.init(Http415, "Invalid content type",
3235+
err(RestErrorMessage.init(Http415, InvalidContentTypeError,
32363236
[version, $body.contentType]))
32373237

3238+
proc decodeBodyJsonOrSsz*(
3239+
t: typedesc[seq[SignedValidatorRegistrationV1]],
3240+
body: ContentBody
3241+
): Result[seq[SignedValidatorRegistrationV1], RestErrorMessage] =
3242+
if body.contentType == ApplicationJsonMediaType:
3243+
let data =
3244+
try:
3245+
RestJson.decode(
3246+
body.data,
3247+
seq[SignedValidatorRegistrationV1],
3248+
requireAllFields = true,
3249+
allowUnknownFields = true)
3250+
except SerializationError as exc:
3251+
debug "Failed to deserialize REST JSON data",
3252+
err = exc.formatMsg("<data>")
3253+
return err(
3254+
RestErrorMessage.init(Http400, UnableDecodeError,
3255+
[exc.formatMsg("<data>")]))
3256+
ok(data)
3257+
elif body.contentType == OctetStreamMediaType:
3258+
let data =
3259+
try:
3260+
SSZ.decode(
3261+
body.data,
3262+
List[SignedValidatorRegistrationV1, Limit VALIDATOR_REGISTRY_LIMIT])
3263+
except SerializationError as exc:
3264+
debug "Failed to deserialize REST SSZ data",
3265+
err = exc.formatMsg("<data>")
3266+
return err(
3267+
RestErrorMessage.init(Http400, UnableDecodeError,
3268+
[exc.formatMsg("<data>")]))
3269+
ok(data.toSeq)
3270+
else:
3271+
err(RestErrorMessage.init(Http415, InvalidContentTypeError,
3272+
[$body.contentType]))
3273+
32383274
proc decodeBody*[T](t: typedesc[T],
32393275
body: ContentBody): Result[T, cstring] =
32403276
if body.contentType != ApplicationJsonMediaType:
@@ -3285,7 +3321,7 @@ proc decodeBodyJsonOrSsz*[T](t: typedesc[T],
32853321
RestErrorMessage.init(Http400, UnexpectedDecodeError, [$exc.msg]))
32863322
ok(blck)
32873323
else:
3288-
err(RestErrorMessage.init(Http415, "Invalid content type",
3324+
err(RestErrorMessage.init(Http415, InvalidContentTypeError,
32893325
[$body.contentType]))
32903326

32913327
proc encodeBytes*[T: EncodeTypes](value: T,

ncli/resttest-rules.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5000,6 +5000,31 @@
50005000
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}]
50015001
}
50025002
},
5003+
{
5004+
"topics": ["validator", "register_validators"],
5005+
"request": {
5006+
"url": "/eth/v1/validator/register_validator",
5007+
"method": "POST",
5008+
"headers": {"Accept": "application/json"},
5009+
"body": {"content-type": "application/octet-stream", "data": "64000000000000000000000000000000000000000000000000000000b5b5b76700000000a81da27d35ce91ddd3129042e47d0bf91a0ff60ed415ed25cda46b42c8d9cb91eb727966e5054828417bda9a1e5e8ba79043ce8b1542c118846537dc33ec2bfe8fa63858d8104b94b7673dd1390f05ac8ff949ba709cfde534857ddfbf757faa0af4a9e1e3743add7c51ca36223a580d2fa25b4dc46623e57f8d75fc647e6966c4828ee90c8eaf4f1babaa4b2f40d82bc8000000000000000000000000000000000000000000000000000000b5b5b76700000000886979da71af92933ebb6055a8c4a43a0c5e4ab645f9272ab6d441a5c2645c3278893439890eeda72f5580f05ae0898b8ba37e74091eb05ab1f66bf02f03ab7bfe0ee8f471ff8de8e10ef71164cef57a2ea694cde397d678a2ca18994cb2e154170e2a71a40fca2505c1e375c2ec05f9bc0a4f987968822177fe8d6326890362e0e55d18d0cf30b171ae44f969dbdf33"}
5010+
},
5011+
"response": {
5012+
"status": {"operator": "equals", "value": "200"}
5013+
}
5014+
},
5015+
{
5016+
"topics": ["validator", "register_validators"],
5017+
"request": {
5018+
"url": "/eth/v1/validator/register_validator",
5019+
"method": "POST",
5020+
"headers": {"Accept": "application/json"},
5021+
"body": {"content-type": "application/octet-stream", "data": "64000000000000000000000000000000000000000000000000000000b5b5b76700000000a81da27d35ce91ddd3129042e47d0bf91a0ff60ed415ed25cda46b42c8d9cb91eb727966e5054828417bda9a1e5e8ba79043ce8b1542c118846537dc33ec2bfe8fa63858d8104b94b7673dd1390f05ac8ff949ba709cfde534857ddfbf757faa0af4a9e1e3743add7c51ca36223a580d2fa25b4dc46623e57f8d75fc647e6966c4828ee90c8eaf4f1babaa4b2f40d82bc8000000000000000000000000000000000000000000000000000000b5b5b76700000000886979da71af92933ebb6055a8c4a43a0c5e4ab645f9272ab6d441a5c2645c3278893439890eeda72f5580f05ae0898b8ba37e74091eb05ab1f66bf02f03ab7bfe0ee8f471ff8de8e10ef71164cef57a2ea694cde397d678a2ca18994cb2e154170e2a71a40fca2505c1e375c2ec05f9bc0a4f987968822177fe8d6326890362e0e55d18d0cf30b171ae44f969dbdf"}
5022+
},
5023+
"response": {
5024+
"status": {"operator": "equals", "value": "400"},
5025+
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}]
5026+
}
5027+
},
50035028
{
50045029
"topics": ["key_management", "list_keys"],
50055030
"request": {

ncli/resttest.nim

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# beacon_chain
2-
# Copyright (c) 2021-2024 Status Research & Development GmbH
2+
# Copyright (c) 2021-2025 Status Research & Development GmbH
33
# Licensed and distributed under either of
44
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
55
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@@ -10,7 +10,7 @@
1010
import
1111
std/[strutils, os, options, uri, json, tables],
1212
results,
13-
stew/[io2, base10],
13+
stew/[io2, base10, byteutils],
1414
confutils, chronicles, httputils,
1515
chronos, chronos/streams/[asyncstream, tlsstream]
1616

@@ -455,7 +455,15 @@ proc prepareRequest(uri: Uri,
455455
return err("Field `body.data` must be present")
456456
if bdata.kind != JString:
457457
return err("Field `body.data` should be string")
458-
(btype.str, bdata.str)
458+
if toLowerAscii(btype.str) == "application/octet-stream":
459+
let data =
460+
try:
461+
string.fromBytes(hexToSeqByte(bdata.str))
462+
except ValueError:
463+
return err("Field `body.data` should be valid hexadecimal string")
464+
(btype.str, data)
465+
else:
466+
(btype.str, bdata.str)
459467

460468
var res = meth & " " & uri.path & requestUri & " HTTP/1.1\r\n"
461469
res.add("Content-Length: " & Base10.toString(uint64(len(requestBodyData))) &

0 commit comments

Comments
 (0)