Skip to content

Commit 68fb396

Browse files
authored
More spec-compliant handling of unknown fields in REST json (#3647)
* More spec-compliant handling of unknown fields in REST json * Address review comments
1 parent 50e8415 commit 68fb396

File tree

1 file changed

+92
-48
lines changed

1 file changed

+92
-48
lines changed

beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim

Lines changed: 92 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
import std/typetraits
88
import stew/[assign2, results, base10, byteutils], presto/common,
99
libp2p/peerid, serialization, json_serialization,
10-
json_serialization/std/[options, net, sets]
10+
json_serialization/std/[options, net, sets],
11+
chronicles
1112
import ".."/[eth2_ssz_serialization, forks, keystore],
1213
".."/datatypes/[phase0, altair, bellatrix],
1314
".."/mev/bellatrix_mev,
@@ -16,14 +17,41 @@ import ".."/[eth2_ssz_serialization, forks, keystore],
1617
import nimcrypto/utils as ncrutils
1718

1819
export
19-
eth2_ssz_serialization, results, peerid, common, serialization,
20+
eth2_ssz_serialization, results, peerid, common, serialization, chronicles,
2021
json_serialization, options, net, sets, rest_types, slashing_protection_common
2122

2223
from web3/ethtypes import BlockHash
2324
export ethtypes.BlockHash
2425

2526
Json.createFlavor RestJson
2627

28+
## The RestJson format implements JSON serialization in the way specified
29+
## by the Beacon API:
30+
##
31+
## https://ethereum.github.io/beacon-APIs/
32+
##
33+
## In this format, we must always set `allowUnknownFields = true` in the
34+
## decode calls in order to conform the following spec:
35+
##
36+
## All JSON responses return the requested data under a data key in the top
37+
## level of their response. Additional metadata may or may not be present
38+
## in other keys at the top level of the response, dependent on the endpoint.
39+
## The rules that require an increase in version number are as follows:
40+
##
41+
## - no field that is listed in an endpoint shall be removed without an increase
42+
## in the version number
43+
##
44+
## - no field that is listed in an endpoint shall be altered in terms of format
45+
## (e.g. from a string to an array) without an increase in the version number
46+
##
47+
## Note that it is possible for a field to be added to an endpoint's data or
48+
## metadata without an increase in the version number.
49+
##
50+
## TODO nim-json-serializations should allow setting up this policy per format
51+
##
52+
## This also means that when new fields are introduced to the object definitions
53+
## below, one must use the `Option[T]` type.
54+
2755
const
2856
DecimalSet = {'0' .. '9'}
2957
# Base10 (decimal) set of chars
@@ -93,12 +121,6 @@ type
93121
Web3SignerSignatureResponse |
94122
Web3SignerStatusResponse
95123

96-
# These types may be extended with additional fields in the future.
97-
# Locally unknown fields are silently ignored when decoding them.
98-
ExtensibleDecodeTypes* =
99-
GetSpecResponse |
100-
GetSpecVCResponse
101-
102124
SszDecodeTypes* =
103125
GetPhase0StateSszResponse |
104126
GetPhase0BlockSszResponse
@@ -338,7 +360,9 @@ proc decodeJsonString*[T](t: typedesc[T],
338360
data: JsonString,
339361
requireAllFields = true): Result[T, cstring] =
340362
try:
341-
ok(RestJson.decode(string(data), T, requireAllFields = requireAllFields))
363+
ok(RestJson.decode(string(data), T,
364+
requireAllFields = requireAllFields,
365+
allowUnknownFields = true))
342366
except SerializationError:
343367
err("Unable to deserialize data")
344368

@@ -664,6 +688,13 @@ proc readValue*(
664688
raiseUnexpectedValue(
665689
reader, "Expected a valid hex string with " & $value.len() & " bytes")
666690

691+
template unrecognizedFieldWarning =
692+
# TODO: There should be a different notification mechanism for informing the
693+
# caller of a deserialization routine for unexpected fields.
694+
# The chonicles import in this module should be removed.
695+
debug "JSON field not recognized by the current version of Nimbus. Consider upgrading",
696+
fieldName, typeName = typetraits.name(typeof value)
697+
667698
## ForkedBeaconBlock
668699
proc readValue*[BlockType: Web3SignerForkedBeaconBlock|ForkedBeaconBlock](
669700
reader: var JsonReader[RestJson],
@@ -694,7 +725,7 @@ proc readValue*[BlockType: Web3SignerForkedBeaconBlock|ForkedBeaconBlock](
694725
"ForkedBeaconBlock")
695726
data = some(reader.readValue(JsonString))
696727
else:
697-
reader.raiseUnexpectedField(fieldName, "ForkedBeaconBlock")
728+
unrecognizedFieldWarning()
698729

699730
if version.isNone():
700731
reader.raiseUnexpectedValue("Field version is missing")
@@ -705,8 +736,10 @@ proc readValue*[BlockType: Web3SignerForkedBeaconBlock|ForkedBeaconBlock](
705736
of BeaconBlockFork.Phase0:
706737
let res =
707738
try:
708-
some(RestJson.decode(string(data.get()), phase0.BeaconBlock,
709-
requireAllFields = true))
739+
some(RestJson.decode(string(data.get()),
740+
phase0.BeaconBlock,
741+
requireAllFields = true,
742+
allowUnknownFields = true))
710743
except SerializationError:
711744
none[phase0.BeaconBlock]()
712745
if res.isNone():
@@ -715,8 +748,10 @@ proc readValue*[BlockType: Web3SignerForkedBeaconBlock|ForkedBeaconBlock](
715748
of BeaconBlockFork.Altair:
716749
let res =
717750
try:
718-
some(RestJson.decode(string(data.get()), altair.BeaconBlock,
719-
requireAllFields = true))
751+
some(RestJson.decode(string(data.get()),
752+
altair.BeaconBlock,
753+
requireAllFields = true,
754+
allowUnknownFields = true))
720755
except SerializationError:
721756
none[altair.BeaconBlock]()
722757
if res.isNone():
@@ -725,8 +760,10 @@ proc readValue*[BlockType: Web3SignerForkedBeaconBlock|ForkedBeaconBlock](
725760
of BeaconBlockFork.Bellatrix:
726761
let res =
727762
try:
728-
some(RestJson.decode(string(data.get()), bellatrix.BeaconBlock,
729-
requireAllFields = true))
763+
some(RestJson.decode(string(data.get()),
764+
bellatrix.BeaconBlock,
765+
requireAllFields = true,
766+
allowUnknownFields = true))
730767
except SerializationError:
731768
none[bellatrix.BeaconBlock]()
732769
if res.isNone():
@@ -835,8 +872,7 @@ proc readValue*(reader: var JsonReader[RestJson],
835872
"RestPublishedBeaconBlockBody")
836873
execution_payload = some(reader.readValue(ExecutionPayload))
837874
else:
838-
# Ignore unknown fields
839-
discard
875+
unrecognizedFieldWarning()
840876

841877
if randao_reveal.isNone():
842878
reader.raiseUnexpectedValue("Field `randao_reveal` is missing")
@@ -949,8 +985,7 @@ proc readValue*(reader: var JsonReader[RestJson],
949985
"RestPublishedBeaconBlock")
950986
blockBody = some(reader.readValue(RestPublishedBeaconBlockBody))
951987
else:
952-
# Ignore unknown fields
953-
discard
988+
unrecognizedFieldWarning()
954989

955990
if slot.isNone():
956991
reader.raiseUnexpectedValue("Field `slot` is missing")
@@ -1017,8 +1052,7 @@ proc readValue*(reader: var JsonReader[RestJson],
10171052
"RestPublishedSignedBeaconBlock")
10181053
signature = some(reader.readValue(ValidatorSig))
10191054
else:
1020-
# Ignore unknown fields
1021-
discard
1055+
unrecognizedFieldWarning()
10221056

10231057
if signature.isNone():
10241058
reader.raiseUnexpectedValue("Field `signature` is missing")
@@ -1081,7 +1115,7 @@ proc readValue*(reader: var JsonReader[RestJson],
10811115
"ForkedSignedBeaconBlock")
10821116
data = some(reader.readValue(JsonString))
10831117
else:
1084-
reader.raiseUnexpectedField(fieldName, "ForkedSignedBeaconBlock")
1118+
unrecognizedFieldWarning()
10851119

10861120
if version.isNone():
10871121
reader.raiseUnexpectedValue("Field version is missing")
@@ -1092,8 +1126,10 @@ proc readValue*(reader: var JsonReader[RestJson],
10921126
of BeaconBlockFork.Phase0:
10931127
let res =
10941128
try:
1095-
some(RestJson.decode(string(data.get()), phase0.SignedBeaconBlock,
1096-
requireAllFields = true))
1129+
some(RestJson.decode(string(data.get()),
1130+
phase0.SignedBeaconBlock,
1131+
requireAllFields = true,
1132+
allowUnknownFields = true))
10971133
except SerializationError:
10981134
none[phase0.SignedBeaconBlock]()
10991135
if res.isNone():
@@ -1102,8 +1138,10 @@ proc readValue*(reader: var JsonReader[RestJson],
11021138
of BeaconBlockFork.Altair:
11031139
let res =
11041140
try:
1105-
some(RestJson.decode(string(data.get()), altair.SignedBeaconBlock,
1106-
requireAllFields = true))
1141+
some(RestJson.decode(string(data.get()),
1142+
altair.SignedBeaconBlock,
1143+
requireAllFields = true,
1144+
allowUnknownFields = true))
11071145
except SerializationError:
11081146
none[altair.SignedBeaconBlock]()
11091147
if res.isNone():
@@ -1112,8 +1150,10 @@ proc readValue*(reader: var JsonReader[RestJson],
11121150
of BeaconBlockFork.Bellatrix:
11131151
let res =
11141152
try:
1115-
some(RestJson.decode(string(data.get()), bellatrix.SignedBeaconBlock,
1116-
requireAllFields = true))
1153+
some(RestJson.decode(string(data.get()),
1154+
bellatrix.SignedBeaconBlock,
1155+
requireAllFields = true,
1156+
allowUnknownFields = true))
11171157
except SerializationError:
11181158
none[bellatrix.SignedBeaconBlock]()
11191159
if res.isNone():
@@ -1165,7 +1205,7 @@ proc readValue*(reader: var JsonReader[RestJson],
11651205
"ForkedBeaconState")
11661206
data = some(reader.readValue(JsonString))
11671207
else:
1168-
reader.raiseUnexpectedField(fieldName, "ForkedBeaconState")
1208+
unrecognizedFieldWarning()
11691209

11701210
if version.isNone():
11711211
reader.raiseUnexpectedValue("Field version is missing")
@@ -1188,23 +1228,32 @@ proc readValue*(reader: var JsonReader[RestJson],
11881228
of BeaconStateFork.Phase0:
11891229
try:
11901230
tmp[].phase0Data.data = RestJson.decode(
1191-
string(data.get()), phase0.BeaconState, requireAllFields = true)
1231+
string(data.get()),
1232+
phase0.BeaconState,
1233+
requireAllFields = true,
1234+
allowUnknownFields = true)
11921235
except SerializationError:
11931236
reader.raiseUnexpectedValue("Incorrect phase0 beacon state format")
11941237

11951238
toValue(phase0Data)
11961239
of BeaconStateFork.Altair:
11971240
try:
11981241
tmp[].altairData.data = RestJson.decode(
1199-
string(data.get()), altair.BeaconState, requireAllFields = true)
1242+
string(data.get()),
1243+
altair.BeaconState,
1244+
requireAllFields = true,
1245+
allowUnknownFields = true)
12001246
except SerializationError:
12011247
reader.raiseUnexpectedValue("Incorrect altair beacon state format")
12021248

12031249
toValue(altairData)
12041250
of BeaconStateFork.Bellatrix:
12051251
try:
12061252
tmp[].bellatrixData.data = RestJson.decode(
1207-
string(data.get()), bellatrix.BeaconState, requireAllFields = true)
1253+
string(data.get()),
1254+
bellatrix.BeaconState,
1255+
requireAllFields = true,
1256+
allowUnknownFields = true)
12081257
except SerializationError:
12091258
reader.raiseUnexpectedValue("Incorrect altair beacon state format")
12101259
toValue(bellatrixData)
@@ -1382,8 +1431,7 @@ proc readValue*(reader: var JsonReader[RestJson],
13821431
dataName = fieldName
13831432
data = some(reader.readValue(JsonString))
13841433
else:
1385-
# We ignore all unknown fields.
1386-
discard
1434+
unrecognizedFieldWarning()
13871435

13881436
if requestKind.isNone():
13891437
reader.raiseUnexpectedValue("Field `type` is missing")
@@ -1620,10 +1668,11 @@ proc readValue*(reader: var JsonReader[RestJson],
16201668
reader.raiseUnexpectedValue("Invalid `status` value")
16211669
)
16221670
else:
1623-
# We ignore all unknown fields.
1624-
discard
1671+
unrecognizedFieldWarning()
1672+
16251673
if status.isNone():
16261674
reader.raiseUnexpectedValue("Field `status` is missing")
1675+
16271676
value = RemoteKeystoreStatus(status: status.get(), message: message)
16281677

16291678
## ScryptSalt
@@ -1675,8 +1724,7 @@ proc readValue*(reader: var JsonReader[RestJson], value: var Pbkdf2Params) {.
16751724
"Pbkdf2Params")
16761725
salt = some(reader.readValue(Pbkdf2Salt))
16771726
else:
1678-
# Ignore unknown field names.
1679-
discard
1727+
unrecognizedFieldWarning()
16801728

16811729
if dklen.isNone():
16821730
reader.raiseUnexpectedValue("Field `dklen` is missing")
@@ -1749,8 +1797,7 @@ proc readValue*(reader: var JsonReader[RestJson], value: var ScryptParams) {.
17491797
"ScryptParams")
17501798
salt = some(reader.readValue(ScryptSalt))
17511799
else:
1752-
# Ignore unknown field names.
1753-
discard
1800+
unrecognizedFieldWarning()
17541801

17551802
if dklen.isNone():
17561803
reader.raiseUnexpectedValue("Field `dklen` is missing")
@@ -1833,8 +1880,7 @@ proc readValue*(reader: var JsonReader[RestJson], value: var Keystore) {.
18331880
reader.raiseUnexpectedValue("Unexpected negative `version` value")
18341881
version = some(res)
18351882
else:
1836-
# Ignore unknown field names.
1837-
discard
1883+
unrecognizedFieldWarning()
18381884

18391885
if crypto.isNone():
18401886
reader.raiseUnexpectedValue("Field `crypto` is missing")
@@ -1888,8 +1934,7 @@ proc readValue*(reader: var JsonReader[RestJson],
18881934
"KeystoresAndSlashingProtection")
18891935
slashing = some(reader.readValue(SPDIR))
18901936
else:
1891-
# Ignore unknown field names.
1892-
discard
1937+
unrecognizedFieldWarning()
18931938

18941939
if len(keystores) == 0:
18951940
reader.raiseUnexpectedValue("Missing `keystores` value")
@@ -1912,7 +1957,7 @@ proc decodeBody*[T](t: typedesc[T],
19121957
return err("Unsupported content type")
19131958
let data =
19141959
try:
1915-
RestJson.decode(body.data, T)
1960+
RestJson.decode(body.data, T, allowUnknownFields = true)
19161961
except SerializationError as exc:
19171962
return err("Unable to deserialize data")
19181963
except CatchableError:
@@ -1959,11 +2004,10 @@ proc encodeBytes*[T: EncodeArrays](value: T,
19592004

19602005
proc decodeBytes*[T: DecodeTypes](t: typedesc[T], value: openArray[byte],
19612006
contentType: string): RestResult[T] =
1962-
const isExtensibleType = t is ExtensibleDecodeTypes
19632007
case contentType
19642008
of "application/json":
19652009
try:
1966-
ok RestJson.decode(value, T, allowUnknownFields = isExtensibleType)
2010+
ok RestJson.decode(value, T, allowUnknownFields = true)
19672011
except SerializationError:
19682012
err("Serialization error")
19692013
else:

0 commit comments

Comments
 (0)