Fix `broadcast_validation` handling in `publishBlockV2` (fixes #5531) (#5547)

* Address issue #5531.

* Add more tests.

* Add to resttest ability to check values.
Fix tests.
This commit is contained in:
Eugene Kabanov 2023-11-01 09:31:18 +02:00 committed by GitHub
parent 70aaeee704
commit 1a5bcb479e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 183 additions and 18 deletions

View File

@ -906,19 +906,32 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
# https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlockV2
router.api(MethodPost, "/eth/v2/beacon/blocks") do (
broadcast_validation: Option[BroadcastValidationType],
contentBody: Option[ContentBody]) -> RestApiResponse:
let res =
block:
let
version = request.headers.getString("eth-consensus-version")
validation =
block:
let res =
if broadcast_validation.isNone():
BroadcastValidationType.Gossip
else:
broadcast_validation.get().valueOr:
return RestApiResponse.jsonError(Http400,
InvalidBroadcastValidationType)
# TODO (henridf): support 'consensus' and
# 'consensus_and_equivocation' broadcast_validation types.
if res != BroadcastValidationType.Gossip:
return RestApiResponse.jsonError(Http500,
"Only `gossip` broadcast_validation option supported")
res
body =
block:
if contentBody.isNone():
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
if request.headers.getString("broadcast_validation") != "gossip":
# TODO (henridf): support 'consensus' and 'consensus_and_equivocation'
# broadcast_validation
return RestApiResponse.jsonError(
Http500, "gossip broadcast_validation only supported")
let
body = contentBody.get()
version = request.headers.getString("eth-consensus-version")
contentBody.get()
var
restBlock = decodeBodyJsonOrSsz(RestPublishedSignedBlockContents,
body, version).valueOr:

View File

@ -243,3 +243,5 @@ const
"Invalid or missing timestamp value"
InvalidSidecarIndexValueError* =
"Invalid blob index"
InvalidBroadcastValidationType* =
"Invalid broadcast_validation type value"

View File

@ -3606,6 +3606,15 @@ proc encodeString*(value: StateIdent): RestResult[string] =
of StateIdentType.Justified:
ok("justified")
proc encodeString*(value: BroadcastValidationType): RestResult[string] =
case value
of BroadcastValidationType.Gossip:
ok("gossip")
of BroadcastValidationType.Consensus:
ok("consensus")
of BroadcastValidationType.ConsensusAndEquivocation:
ok("consensus_and_equivocation")
proc encodeString*(value: BlockIdent): RestResult[string] =
case value.kind
of BlockQueryKind.Slot:
@ -3821,6 +3830,18 @@ proc decodeString*(t: typedesc[BlockIdent],
let res = ? Base10.decode(uint64, value)
ok(BlockIdent(kind: BlockQueryKind.Slot, slot: Slot(res)))
proc decodeString*(t: typedesc[BroadcastValidationType],
value: string): Result[BroadcastValidationType, cstring] =
case value
of "gossip":
ok(BroadcastValidationType.Gossip)
of "consensus":
ok(BroadcastValidationType.Consensus)
of "consensus_and_equivocation":
ok(BroadcastValidationType.ConsensusAndEquivocation)
else:
err("Incorrect broadcast validation type value")
proc decodeString*(t: typedesc[ValidatorIdent],
value: string): Result[ValidatorIdent, cstring] =
if len(value) > 2:

View File

@ -84,6 +84,9 @@ type
StateIdentType* {.pure.} = enum
Head, Genesis, Finalized, Justified
BroadcastValidationType* {.pure.} = enum
Gossip, Consensus, ConsensusAndEquivocation
StateIdent* = object
case kind*: StateQueryKind
of StateQueryKind.Slot:

View File

@ -3611,5 +3611,109 @@
}}
]
}
},
{
"topics": ["beacon", "beacon_blocks_publish_v2"],
"request": {
"method": "POST",
"headers": {
"Accept": "application/json",
"Content-Type": "application/json",
"Eth-Consensus-Version": "capella"
},
"url": "/eth/v2/beacon/blocks",
},
"response": {
"status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
"body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": "Empty request's body"}}]
}
},
{
"topics": ["beacon", "beacon_blocks_publish_v2"],
"request": {
"method": "POST",
"headers": {
"Accept": "application/json",
"Content-Type": "application/json",
"Eth-Consensus-Version": "capella"
},
"url": "/eth/v2/beacon/blocks?broadcast_validation=gossip",
},
"response": {
"status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
"body": [{"operator": "jstructcmpnsav", "value": {"code": 400, "message": "Empty request's body"}}]
}
},
{
"topics": ["beacon", "beacon_blocks_publish_v2"],
"request": {
"method": "POST",
"headers": {
"Accept": "application/json",
"Content-Type": "application/json",
"Eth-Consensus-Version": "capella"
},
"url": "/eth/v2/beacon/blocks?broadcast_validation=test",
},
"response": {
"status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
"body": [{"operator": "jstructcmpnsav", "value": {"code": 400, "message": "Invalid broadcast_validation type value"}}]
}
},
{
"topics": ["beacon", "beacon_blocks_publish_v2"],
"request": {
"method": "POST",
"headers": {
"Accept": "application/json",
"Content-Type": "application/json",
"Eth-Consensus-Version": "capella"
},
"url": "/eth/v2/beacon/blocks?broadcast_validation=",
},
"response": {
"status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
"body": [{"operator": "jstructcmpnsav", "value": {"code": 400, "message": "Invalid broadcast_validation type value"}}]
}
},
{
"topics": ["beacon", "beacon_blocks_publish_v2"],
"request": {
"method": "POST",
"headers": {
"Accept": "application/json",
"Content-Type": "application/json",
"Eth-Consensus-Version": "capella"
},
"url": "/eth/v2/beacon/blocks?broadcast_validation=consensus",
},
"comment": "TODO: This should be replaced when `consensus` become supported",
"response": {
"status": {"operator": "equals", "value": "500"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
"body": [{"operator": "jstructcmpnsav", "value": {"code": 500, "message": "Only `gossip` broadcast_validation option supported"}}]
}
},
{
"topics": ["beacon", "beacon_blocks_publish_v2"],
"request": {
"method": "POST",
"headers": {
"Accept": "application/json",
"Content-Type": "application/json",
"Eth-Consensus-Version": "capella"
},
"url": "/eth/v2/beacon/blocks?broadcast_validation=consensus_and_equivocation",
},
"comment": "TODO: This should be replaced when `consensus_and_equivocation` become supported",
"response": {
"status": {"operator": "equals", "value": "500"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
"body": [{"operator": "jstructcmpnsav", "value": {"code": 500, "message": "Only `gossip` broadcast_validation option supported"}}]
}
}
]

View File

@ -34,7 +34,8 @@ type
Exists, NotExists, Equals, OneOf, Substr
BodyOperatorKind {.pure.} = enum
Exists, JsonStructCmpS, JsonStructCmpNS
Exists, JsonStructCmpS, JsonStructCmpNS,
JsonStructCmpSAV, JsonStructCmpNSAV
StatusExpect = object
kind: StatusOperatorKind
@ -649,13 +650,18 @@ proc getResponseBodyExpect(rule: JsonNode): Result[BodyExpect, cstring] =
BodyOperatorKind.JsonStructCmpS
of "jstructcmpns":
BodyOperatorKind.JsonStructCmpNS
of "jstructcmpsav":
BodyOperatorKind.JsonStructCmpSAV
of "jstructcmpnsav":
BodyOperatorKind.JsonStructCmpNSAV
else:
return err("`response.body` element has incorrect operator")
case operator
of BodyOperatorKind.Exists:
res.add(BodyItemExpect(kind: operator))
of BodyOperatorKind.JsonStructCmpS, BodyOperatorKind.JsonStructCmpNS:
of BodyOperatorKind.JsonStructCmpS, BodyOperatorKind.JsonStructCmpNS,
BodyOperatorKind.JsonStructCmpSAV, BodyOperatorKind.JsonStructCmpNSAV:
let start =
block:
var default: seq[string]
@ -762,7 +768,7 @@ proc getPath(jobj: JsonNode, path: seq[string]): Result[JsonNode, cstring] =
jnode = jitem
ok(jnode)
proc structCmp(j1, j2: JsonNode, strict: bool): bool =
proc structCmp(j1, j2: JsonNode, strict: bool, checkvalue: bool): bool =
if j1.kind != j2.kind:
return false
case j1.kind
@ -776,7 +782,7 @@ proc structCmp(j1, j2: JsonNode, strict: bool): bool =
false
else:
for item in j1.elems:
if not(structCmp(item, j2.elems[0], strict)):
if not(structCmp(item, j2.elems[0], strict, checkvalue)):
return false
true
of JObject:
@ -787,7 +793,7 @@ proc structCmp(j1, j2: JsonNode, strict: bool): bool =
let j2node = j2.getOrDefault(key)
if isNil(j2node):
return false
if not(structCmp(value, j2node, strict)):
if not(structCmp(value, j2node, strict, checkvalue)):
return false
true
else:
@ -795,10 +801,18 @@ proc structCmp(j1, j2: JsonNode, strict: bool): bool =
let j1node = j1.getOrDefault(key)
if isNil(j1node):
return false
if not(structCmp(j1node, value, strict)):
if not(structCmp(j1node, value, strict, checkvalue)):
return false
true
else:
of JString:
if checkvalue: j1.str == j2.str else: true
of JInt:
if checkvalue: j1.num == j2.num else: true
of JFloat:
if checkvalue: j1.fnum == j2.fnum else: true
of JBool:
if checkvalue: j1.bval == j2.bval else: true
of JNull:
true
proc validateBody(body: openArray[byte], expect: BodyExpect): bool =
@ -810,7 +824,8 @@ proc validateBody(body: openArray[byte], expect: BodyExpect): bool =
of BodyOperatorKind.Exists:
if len(body) == 0:
return false
of BodyOperatorKind.JsonStructCmpS, BodyOperatorKind.JsonStructCmpNS:
of BodyOperatorKind.JsonStructCmpS, BodyOperatorKind.JsonStructCmpNS,
BodyOperatorKind.JsonStructCmpSAV, BodyOperatorKind.JsonStructCmpNSAV:
let jbody =
block:
let jres = jsonBody(body)
@ -822,11 +837,18 @@ proc validateBody(body: openArray[byte], expect: BodyExpect): bool =
return false
jpathres.get()
let strict =
if item.kind == BodyOperatorKind.JsonStructCmpS:
if item.kind in {BodyOperatorKind.JsonStructCmpS,
BodyOperatorKind.JsonStructCmpSAV}:
true
else:
false
if not(structCmp(jbody, item.value, strict)):
let checkvalue =
if item.kind in {BodyOperatorKind.JsonStructCmpSAV,
BodyOperatorKind.JsonStructCmpNSAV}:
true
else:
false
if not(structCmp(jbody, item.value, strict, checkvalue)):
return false
true