mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-02-20 18:28:12 +00:00
REST: getBlockRewards() and getSyncCommitteeRewards() implementation (#6556)
* Initial commit. * Use temporary state instead of clearance. * Attempt to fix `finalized`. * Fix `genesis` response. * Pre-calculate genesis block rewards response. * Add implementation for sync committee rewards. * Add total active balance calculation. * Add genesis special case. * Fix negative reward values. * Address review comments. * Fix isGenesis implementation and add REST test rules for both calls.
This commit is contained in:
parent
1cc3c59334
commit
e3fcd8b031
@ -1774,6 +1774,7 @@ proc installRestHandlers(restServer: RestServerRef, node: BeaconNode) =
|
||||
restServer.router.installNimbusApiHandlers(node)
|
||||
restServer.router.installNodeApiHandlers(node)
|
||||
restServer.router.installValidatorApiHandlers(node)
|
||||
restServer.router.installRewardsApiHandlers(node)
|
||||
if node.dag.lcDataStore.serve:
|
||||
restServer.router.installLightClientApiHandlers(node)
|
||||
|
||||
|
@ -19,10 +19,10 @@ import
|
||||
rest_utils,
|
||||
rest_beacon_api, rest_builder_api, rest_config_api, rest_debug_api,
|
||||
rest_event_api, rest_key_management_api, rest_light_client_api,
|
||||
rest_nimbus_api, rest_node_api, rest_validator_api]
|
||||
rest_nimbus_api, rest_node_api, rest_validator_api, rest_rewards_api]
|
||||
|
||||
export
|
||||
rest_utils,
|
||||
rest_beacon_api, rest_builder_api, rest_config_api, rest_debug_api,
|
||||
rest_event_api, rest_key_management_api, rest_light_client_api,
|
||||
rest_nimbus_api, rest_node_api, rest_validator_api
|
||||
rest_nimbus_api, rest_node_api, rest_validator_api, rest_rewards_api
|
||||
|
@ -24,6 +24,12 @@ const
|
||||
"Beacon node is currently syncing and not serving request on that endpoint"
|
||||
BlockNotFoundError* =
|
||||
"Block header/data has not been found"
|
||||
BlockParentUnknownError* =
|
||||
"Block parent unknown"
|
||||
BlockOlderThanParentError* =
|
||||
"Block older than parent block"
|
||||
BlockInvalidError* =
|
||||
"Invalid block"
|
||||
EmptyRequestBodyError* =
|
||||
"Empty request body"
|
||||
InvalidRequestBodyError* =
|
||||
@ -259,3 +265,7 @@ const
|
||||
"Path not found"
|
||||
FileReadError* =
|
||||
"Error reading file"
|
||||
ParentBlockMissingStateError* =
|
||||
"Unable to load state for parent block, database corrupt?"
|
||||
RewardOverflowError* =
|
||||
"Reward value overflow"
|
||||
|
246
beacon_chain/rpc/rest_rewards_api.nim
Normal file
246
beacon_chain/rpc/rest_rewards_api.nim
Normal file
@ -0,0 +1,246 @@
|
||||
# beacon_chain
|
||||
# Copyright (c) 2018-2024 Status Research & Development GmbH
|
||||
# Licensed and distributed under either of
|
||||
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[typetraits, sequtils, sets],
|
||||
stew/base10,
|
||||
chronicles, metrics,
|
||||
./rest_utils,
|
||||
./state_ttl_cache,
|
||||
../beacon_node,
|
||||
../consensus_object_pools/[blockchain_dag, spec_cache, validator_change_pool],
|
||||
../spec/[forks, state_transition]
|
||||
|
||||
export rest_utils
|
||||
|
||||
logScope: topics = "rest_rewardsapi"
|
||||
|
||||
func isGenesis(node: BeaconNode,
|
||||
blockId: BlockIdent,
|
||||
genesisBsid: BlockSlotId): bool =
|
||||
case blockId.kind
|
||||
of BlockQueryKind.Named:
|
||||
case blockId.value
|
||||
of BlockIdentType.Genesis:
|
||||
true
|
||||
of BlockIdentType.Head:
|
||||
node.dag.head.bid.slot == GENESIS_SLOT
|
||||
of BlockIdentType.Finalized:
|
||||
node.dag.finalizedHead.slot == GENESIS_SLOT
|
||||
of BlockQueryKind.Slot:
|
||||
blockId.slot == GENESIS_SLOT
|
||||
of BlockQueryKind.Root:
|
||||
blockId.root == genesisBsid.bid.root
|
||||
|
||||
proc installRewardsApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||
let
|
||||
genesisBlockRewardsResponse =
|
||||
RestApiResponse.prepareJsonResponseFinalized(
|
||||
(
|
||||
proposer_index: "0", total: "0", attestations: "0",
|
||||
sync_aggregate: "0", proposer_slashings: "0", attester_slashings: "0"
|
||||
),
|
||||
Opt.some(false),
|
||||
true,
|
||||
)
|
||||
genesisBsid = node.dag.getBlockIdAtSlot(GENESIS_SLOT).get()
|
||||
|
||||
# https://ethereum.github.io/beacon-APIs/#/Rewards/getBlockRewards
|
||||
router.api2(MethodGet, "/eth/v1/beacon/rewards/blocks/{block_id}") do (
|
||||
block_id: BlockIdent) -> RestApiResponse:
|
||||
let
|
||||
bident = block_id.valueOr:
|
||||
return RestApiResponse.jsonError(Http400, InvalidBlockIdValueError,
|
||||
$error)
|
||||
|
||||
if node.isGenesis(bident, genesisBsid):
|
||||
return RestApiResponse.response(
|
||||
genesisBlockRewardsResponse, Http200, "application/json")
|
||||
|
||||
let
|
||||
bdata = node.getForkedBlock(bident).valueOr:
|
||||
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
|
||||
|
||||
bid = BlockId(slot: bdata.slot, root: bdata.root)
|
||||
|
||||
targetBlock =
|
||||
withBlck(bdata):
|
||||
let parentBid =
|
||||
node.dag.getBlockId(forkyBlck.message.parent_root).valueOr:
|
||||
return RestApiResponse.jsonError(Http404, BlockParentUnknownError)
|
||||
if parentBid.slot >= forkyBlck.message.slot:
|
||||
return RestApiResponse.jsonError(Http404, BlockOlderThanParentError)
|
||||
BlockSlotId.init(parentBid, forkyBlck.message.slot)
|
||||
|
||||
var
|
||||
cache = StateCache()
|
||||
tmpState = assignClone(node.dag.headState)
|
||||
|
||||
if not updateState(
|
||||
node.dag, tmpState[], targetBlock, false, cache):
|
||||
return RestApiResponse.jsonError(Http404, ParentBlockMissingStateError)
|
||||
|
||||
func rollbackProc(state: var ForkedHashedBeaconState) {.
|
||||
gcsafe, noSideEffect, raises: [].} =
|
||||
discard
|
||||
|
||||
let
|
||||
rewards =
|
||||
withBlck(bdata):
|
||||
state_transition_block(
|
||||
node.dag.cfg, tmpState[], forkyBlck,
|
||||
cache, node.dag.updateFlags, rollbackProc).valueOr:
|
||||
return RestApiResponse.jsonError(Http400, BlockInvalidError)
|
||||
total = rewards.attestations + rewards.sync_aggregate +
|
||||
rewards.proposer_slashings + rewards.attester_slashings
|
||||
proposerIndex =
|
||||
withBlck(bdata):
|
||||
forkyBlck.message.proposer_index
|
||||
|
||||
RestApiResponse.jsonResponseFinalized(
|
||||
(
|
||||
proposer_index: Base10.toString(uint64(proposerIndex)),
|
||||
total: Base10.toString(uint64(total)),
|
||||
attestations: Base10.toString(uint64(rewards.attestations)),
|
||||
sync_aggregate: Base10.toString(uint64(rewards.sync_aggregate)),
|
||||
proposer_slashings: Base10.toString(uint64(rewards.proposer_slashings)),
|
||||
attester_slashings: Base10.toString(uint64(rewards.attester_slashings))
|
||||
),
|
||||
node.getBlockOptimistic(bdata),
|
||||
node.dag.isFinalized(bid)
|
||||
)
|
||||
|
||||
# https://ethereum.github.io/beacon-APIs/#/Rewards/getSyncCommitteeRewards
|
||||
router.api2(
|
||||
MethodPost, "/eth/v1/beacon/rewards/sync_committee/{block_id}") do (
|
||||
block_id: BlockIdent,
|
||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||
let
|
||||
idents =
|
||||
block:
|
||||
if contentBody.isNone():
|
||||
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
|
||||
let res = decodeBody(seq[ValidatorIdent], contentBody.get()).valueOr:
|
||||
return RestApiResponse.jsonError(
|
||||
Http400, InvalidRequestBodyError, $error)
|
||||
res
|
||||
|
||||
bident = block_id.valueOr:
|
||||
return RestApiResponse.jsonError(Http400, InvalidBlockIdValueError,
|
||||
$error)
|
||||
bdata = node.getForkedBlock(bident).valueOr:
|
||||
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
|
||||
|
||||
bid = BlockId(slot: bdata.slot, root: bdata.root)
|
||||
|
||||
sync_aggregate =
|
||||
withBlck(bdata):
|
||||
when consensusFork > ConsensusFork.Phase0:
|
||||
forkyBlck.message.body.sync_aggregate
|
||||
else:
|
||||
default(TrustedSyncAggregate)
|
||||
|
||||
targetBlock =
|
||||
withBlck(bdata):
|
||||
if node.isGenesis(bident, genesisBsid):
|
||||
genesisBsid
|
||||
else:
|
||||
let parentBid =
|
||||
node.dag.getBlockId(forkyBlck.message.parent_root).valueOr:
|
||||
return RestApiResponse.jsonError(
|
||||
Http404, BlockParentUnknownError)
|
||||
if parentBid.slot >= forkyBlck.message.slot:
|
||||
return RestApiResponse.jsonError(
|
||||
Http404, BlockOlderThanParentError)
|
||||
BlockSlotId.init(parentBid, forkyBlck.message.slot)
|
||||
|
||||
var
|
||||
cache = StateCache()
|
||||
tmpState = assignClone(node.dag.headState)
|
||||
|
||||
if not updateState(
|
||||
node.dag, tmpState[], targetBlock, false, cache):
|
||||
return RestApiResponse.jsonError(Http404, ParentBlockMissingStateError)
|
||||
|
||||
let response =
|
||||
withState(tmpState[]):
|
||||
let total_active_balance =
|
||||
get_total_active_balance(forkyState.data, cache)
|
||||
var resp: seq[RestSyncCommitteeReward]
|
||||
when consensusFork > ConsensusFork.Phase0:
|
||||
let
|
||||
keys =
|
||||
block:
|
||||
var res: HashSet[ValidatorPubKey]
|
||||
for item in idents:
|
||||
case item.kind
|
||||
of ValidatorQueryKind.Index:
|
||||
let vindex = item.index.toValidatorIndex().valueOr:
|
||||
case error
|
||||
of ValidatorIndexError.TooHighValue:
|
||||
return RestApiResponse.jsonError(
|
||||
Http400, TooHighValidatorIndexValueError)
|
||||
of ValidatorIndexError.UnsupportedValue:
|
||||
return RestApiResponse.jsonError(
|
||||
Http500, UnsupportedValidatorIndexValueError)
|
||||
if uint64(vindex) >= lenu64(forkyState.data.validators):
|
||||
return RestApiResponse.jsonError(
|
||||
Http400, ValidatorNotFoundError)
|
||||
res.incl(forkyState.data.validators.item(vindex).pubkey)
|
||||
of ValidatorQueryKind.Key:
|
||||
res.incl(item.key)
|
||||
res
|
||||
|
||||
committeeKeys =
|
||||
toHashSet(forkyState.data.current_sync_committee.pubkeys.data)
|
||||
|
||||
pubkeyIndices =
|
||||
block:
|
||||
var res: Table[ValidatorPubKey, ValidatorIndex]
|
||||
for vindex in forkyState.data.validators.vindices:
|
||||
let pubkey = forkyState.data.validators.item(vindex).pubkey
|
||||
if pubkey in committeeKeys:
|
||||
res[pubkey] = vindex
|
||||
res
|
||||
reward =
|
||||
block:
|
||||
let res = uint64(get_participant_reward(total_active_balance))
|
||||
if res > uint64(high(int64)):
|
||||
return RestApiResponse.jsonError(
|
||||
Http500, RewardOverflowError)
|
||||
res
|
||||
|
||||
for i in 0 ..< min(
|
||||
len(forkyState.data.current_sync_committee.pubkeys),
|
||||
len(sync_aggregate.sync_committee_bits)):
|
||||
let
|
||||
pubkey = forkyState.data.current_sync_committee.pubkeys.data[i]
|
||||
vindex =
|
||||
try:
|
||||
pubkeyIndices[pubkey]
|
||||
except KeyError:
|
||||
raiseAssert "Unknown sync committee pubkey encountered!"
|
||||
vreward =
|
||||
if sync_aggregate.sync_committee_bits[i]:
|
||||
cast[int64](reward)
|
||||
else:
|
||||
-cast[int64](reward)
|
||||
|
||||
if (len(idents) == 0) or (pubkey in keys):
|
||||
resp.add(RestSyncCommitteeReward(
|
||||
validator_index: RestValidatorIndex(vindex),
|
||||
reward: RestReward(vreward)))
|
||||
|
||||
resp
|
||||
|
||||
RestApiResponse.jsonResponseFinalized(
|
||||
response,
|
||||
node.getBlockOptimistic(bdata),
|
||||
node.dag.isFinalized(bid)
|
||||
)
|
@ -675,24 +675,28 @@ proc jsonResponseWOpt*(t: typedesc[RestApiResponse], data: auto,
|
||||
default
|
||||
RestApiResponse.response(res, Http200, "application/json")
|
||||
|
||||
proc prepareJsonResponseFinalized*(
|
||||
t: typedesc[RestApiResponse], data: auto, exec: Opt[bool],
|
||||
finalized: bool
|
||||
): seq[byte] =
|
||||
try:
|
||||
var
|
||||
stream = memoryOutput()
|
||||
writer = JsonWriter[RestJson].init(stream)
|
||||
writer.beginRecord()
|
||||
if exec.isSome():
|
||||
writer.writeField("execution_optimistic", exec.get())
|
||||
writer.writeField("finalized", finalized)
|
||||
writer.writeField("data", data)
|
||||
writer.endRecord()
|
||||
stream.getOutput(seq[byte])
|
||||
except IOError:
|
||||
default(seq[byte])
|
||||
|
||||
proc jsonResponseFinalized*(t: typedesc[RestApiResponse], data: auto,
|
||||
exec: Opt[bool],
|
||||
finalized: bool): RestApiResponse =
|
||||
let res =
|
||||
block:
|
||||
var default: seq[byte]
|
||||
try:
|
||||
var stream = memoryOutput()
|
||||
var writer = JsonWriter[RestJson].init(stream)
|
||||
writer.beginRecord()
|
||||
if exec.isSome():
|
||||
writer.writeField("execution_optimistic", exec.get())
|
||||
writer.writeField("finalized", finalized)
|
||||
writer.writeField("data", data)
|
||||
writer.endRecord()
|
||||
stream.getOutput(seq[byte])
|
||||
except IOError:
|
||||
default
|
||||
let res = RestApiResponse.prepareJsonResponseFinalized(data, exec, finalized)
|
||||
RestApiResponse.response(res, Http200, "application/json")
|
||||
|
||||
proc jsonResponseWVersion*(t: typedesc[RestApiResponse], data: auto,
|
||||
@ -975,6 +979,29 @@ proc readValue*(reader: var JsonReader[RestJson], value: var uint64) {.
|
||||
else:
|
||||
reader.raiseUnexpectedValue($res.error() & ": " & svalue)
|
||||
|
||||
## RestReward
|
||||
proc writeValue*(
|
||||
w: var JsonWriter[RestJson], value: RestReward) {.raises: [IOError].} =
|
||||
writeValue(w, $int64(value))
|
||||
|
||||
proc readValue*(reader: var JsonReader[RestJson], value: var RestReward) {.
|
||||
raises: [IOError, SerializationError].} =
|
||||
let svalue = reader.readValue(string)
|
||||
if svalue.startsWith("-"):
|
||||
let res =
|
||||
Base10.decode(uint64, svalue.toOpenArray(1, len(svalue) - 1)).valueOr:
|
||||
reader.raiseUnexpectedValue($error & ": " & svalue)
|
||||
if res > uint64(high(int64)):
|
||||
reader.raiseUnexpectedValue("Integer value overflow " & svalue)
|
||||
value = RestReward(-int64(res))
|
||||
else:
|
||||
let res =
|
||||
Base10.decode(uint64, svalue).valueOr:
|
||||
reader.raiseUnexpectedValue($error & ": " & svalue)
|
||||
if res > uint64(high(int64)):
|
||||
reader.raiseUnexpectedValue("Integer value overflow " & svalue)
|
||||
value = RestReward(int64(res))
|
||||
|
||||
## uint8
|
||||
proc writeValue*(
|
||||
w: var JsonWriter[RestJson], value: uint8) {.raises: [IOError].} =
|
||||
@ -4394,3 +4421,11 @@ proc writeValue*(writer: var JsonWriter[RestJson],
|
||||
if len(res) > 0:
|
||||
writer.writeField("statuses", res)
|
||||
writer.endRecord()
|
||||
|
||||
## RestSyncCommitteeReward
|
||||
proc writeValue*(writer: var JsonWriter[RestJson],
|
||||
value: RestSyncCommitteeReward) {.raises: [IOError].} =
|
||||
writer.beginRecord()
|
||||
writer.writeField("validator_index", value.validator_index)
|
||||
writer.writeField("reward", value.reward)
|
||||
writer.endRecord()
|
||||
|
@ -527,6 +527,12 @@ type
|
||||
subcommittee_index*: uint64
|
||||
selection_proof*: ValidatorSig
|
||||
|
||||
RestReward* = distinct int64
|
||||
|
||||
RestSyncCommitteeReward* = object
|
||||
validator_index*: RestValidatorIndex
|
||||
reward*: RestReward
|
||||
|
||||
# Types based on the OAPI yaml file - used in responses to requests
|
||||
GetBeaconHeadResponse* = DataEnclosedObject[Slot]
|
||||
GetAggregatedAttestationResponse* = DataEnclosedObject[phase0.Attestation]
|
||||
|
@ -4968,5 +4968,376 @@
|
||||
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
|
||||
"body": [{"operator": "jstructcmpnsav", "value": {"code": 500, "message": "Only `gossip` broadcast_validation option supported"}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "block_rewards_blockid"],
|
||||
"request": {
|
||||
"url": "/eth/v1/beacon/rewards/blocks/head",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {
|
||||
"status": {"operator": "equals", "value": "200"},
|
||||
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
|
||||
"body": [{"operator": "jstructcmps", "start": ["data"], "value": {"proposer_index": "", "total": "", "attestations": "", "sync_aggregate": "", "proposer_slashings": "", "attester_slashings": ""}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "block_rewards_blockid"],
|
||||
"request": {
|
||||
"url": "/eth/v1/beacon/rewards/blocks/genesis",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {
|
||||
"status": {"operator": "equals", "value": "200"},
|
||||
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
|
||||
"body": [{"operator": "jstructcmps", "start": ["data"], "value": {"proposer_index": "", "total": "", "attestations": "", "sync_aggregate": "", "proposer_slashings": "", "attester_slashings": ""}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "block_rewards_blockid"],
|
||||
"request": {
|
||||
"url": "/eth/v1/beacon/rewards/blocks/finalized",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {
|
||||
"status": {"operator": "equals", "value": "200"},
|
||||
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
|
||||
"body": [{"operator": "jstructcmps", "start": ["data"], "value": {"proposer_index": "", "total": "", "attestations": "", "sync_aggregate": "", "proposer_slashings": "", "attester_slashings": ""}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "block_rewards_blockid"],
|
||||
"request": {
|
||||
"url": "/eth/v1/beacon/rewards/blocks/heat",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {"status": {"operator": "equals", "value": "400"}}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "block_rewards_blockid"],
|
||||
"request": {
|
||||
"url": "/eth/v1/beacon/rewards/blocks/geneziz",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {"status": {"operator": "equals", "value": "400"}}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "block_rewards_blockid"],
|
||||
"request": {
|
||||
"url": "/eth/v1/beacon/rewards/blocks/finalised",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {"status": {"operator": "equals", "value": "400"}}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "block_rewards_blockid"],
|
||||
"request": {
|
||||
"url": "/eth/v1/beacon/rewards/blocks/0",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {
|
||||
"status": {"operator": "equals", "value": "200"},
|
||||
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
|
||||
"body": [{"operator": "jstructcmps", "start": ["data"], "value": {"proposer_index": "", "total": "", "attestations": "", "sync_aggregate": "", "proposer_slashings": "", "attester_slashings": ""}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "block_rewards_blockid"],
|
||||
"request": {
|
||||
"url": "/eth/v1/beacon/rewards/blocks/0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {
|
||||
"status": {"operator": "equals", "value": "404"},
|
||||
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
|
||||
"body": [{"operator": "jstructcmpns", "value": {"code": 404, "message": ""}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "block_rewards_blockid"],
|
||||
"request": {
|
||||
"url": "/eth/v1/beacon/rewards/blocks/18446744073709551615",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {
|
||||
"status": {"operator": "equals", "value": "404"},
|
||||
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
|
||||
"body": [{"operator": "jstructcmpns", "value": {"code": 404, "message": ""}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "block_rewards_blockid"],
|
||||
"request": {
|
||||
"url": "/eth/v1/beacon/rewards/blocks/18446744073709551616",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {
|
||||
"status": {"operator": "equals", "value": "400"},
|
||||
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
|
||||
"body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": ""}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "block_rewards_blockid"],
|
||||
"request": {
|
||||
"url": "/eth/v1/beacon/rewards/blocks/0x",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {"status": {"operator": "equals", "value": "400"}}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "block_rewards_blockid"],
|
||||
"request": {
|
||||
"url": "/eth/v1/beacon/rewards/blocks/0x0",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {"status": {"operator": "equals", "value": "400"}}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "block_rewards_blockid"],
|
||||
"request": {
|
||||
"url": "/eth/v1/beacon/rewards/blocks/0x00",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {"status": {"operator": "equals", "value": "400"}}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "block_rewards_blockid"],
|
||||
"request": {
|
||||
"url": "/eth/v1/beacon/rewards/blocks/0xII",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {"status": {"operator": "equals", "value": "400"}}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "block_rewards_blockid"],
|
||||
"request": {
|
||||
"url": "/eth/v1/beacon/rewards/blocks/foobar",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {"status": {"operator": "equals", "value": "400"}}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "sync_committee_rewards_blockid"],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"body": {
|
||||
"content-type": "application/json",
|
||||
"data": "[]"
|
||||
},
|
||||
"url": "/eth/v1/beacon/rewards/sync_committee/head",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {
|
||||
"status": {"operator": "equals", "value": "200"},
|
||||
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
|
||||
"body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"validator_index": "", "reward": ""}]}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "sync_committee_rewards_blockid"],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"body": {
|
||||
"content-type": "application/json",
|
||||
"data": "[]"
|
||||
},
|
||||
"url": "/eth/v1/beacon/rewards/sync_committee/genesis",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {
|
||||
"status": {"operator": "equals", "value": "200"},
|
||||
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
|
||||
"body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"validator_index": "", "reward": ""}]}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "sync_committee_rewards_blockid"],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"body": {
|
||||
"content-type": "application/json",
|
||||
"data": "[]"
|
||||
},
|
||||
"url": "/eth/v1/beacon/rewards/sync_committee/finalized",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {
|
||||
"status": {"operator": "equals", "value": "200"},
|
||||
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
|
||||
"body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"validator_index": "", "reward": ""}]}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "sync_committee_rewards_blockid"],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"body": {
|
||||
"content-type": "application/json",
|
||||
"data": "[]"
|
||||
},
|
||||
"url": "/eth/v1/beacon/rewards/sync_committee/heat",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {"status": {"operator": "equals", "value": "400"}}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "sync_committee_rewards_blockid"],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"body": {
|
||||
"content-type": "application/json",
|
||||
"data": "[]"
|
||||
},
|
||||
"url": "/eth/v1/beacon/rewards/sync_committee/geneziz",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {"status": {"operator": "equals", "value": "400"}}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "sync_committee_rewards_blockid"],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"body": {
|
||||
"content-type": "application/json",
|
||||
"data": "[]"
|
||||
},
|
||||
"url": "/eth/v1/beacon/rewards/sync_committee/finalised",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {"status": {"operator": "equals", "value": "400"}}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "sync_committee_rewards_blockid"],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"body": {
|
||||
"content-type": "application/json",
|
||||
"data": "[]"
|
||||
},
|
||||
"url": "/eth/v1/beacon/rewards/sync_committee/0",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {
|
||||
"status": {"operator": "equals", "value": "200"},
|
||||
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
|
||||
"body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"validator_index": "", "reward": ""}]}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "sync_committee_rewards_blockid"],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"body": {
|
||||
"content-type": "application/json",
|
||||
"data": "[]"
|
||||
},
|
||||
"url": "/eth/v1/beacon/rewards/sync_committee/0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {
|
||||
"status": {"operator": "equals", "value": "404"},
|
||||
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
|
||||
"body": [{"operator": "jstructcmpns", "value": {"code": 404, "message": ""}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "sync_committee_rewards_blockid"],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"body": {
|
||||
"content-type": "application/json",
|
||||
"data": "[]"
|
||||
},
|
||||
"url": "/eth/v1/beacon/rewards/sync_committee/18446744073709551615",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {
|
||||
"status": {"operator": "equals", "value": "404"},
|
||||
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
|
||||
"body": [{"operator": "jstructcmpns", "value": {"code": 404, "message": ""}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "sync_committee_rewards_blockid"],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"body": {
|
||||
"content-type": "application/json",
|
||||
"data": "[]"
|
||||
},
|
||||
"url": "/eth/v1/beacon/rewards/sync_committee/18446744073709551616",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {
|
||||
"status": {"operator": "equals", "value": "400"},
|
||||
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
|
||||
"body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": ""}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "sync_committee_rewards_blockid"],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"body": {
|
||||
"content-type": "application/json",
|
||||
"data": "[]"
|
||||
},
|
||||
"url": "/eth/v1/beacon/rewards/sync_committee/0x",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {"status": {"operator": "equals", "value": "400"}}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "sync_committee_rewards_blockid"],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"body": {
|
||||
"content-type": "application/json",
|
||||
"data": "[]"
|
||||
},
|
||||
"url": "/eth/v1/beacon/rewards/sync_committee/0x0",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {"status": {"operator": "equals", "value": "400"}}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "sync_committee_rewards_blockid"],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"body": {
|
||||
"content-type": "application/json",
|
||||
"data": "[]"
|
||||
},
|
||||
"url": "/eth/v1/beacon/rewards/sync_committee/0x00",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {"status": {"operator": "equals", "value": "400"}}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "sync_committee_rewards_blockid"],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"body": {
|
||||
"content-type": "application/json",
|
||||
"data": "[]"
|
||||
},
|
||||
"url": "/eth/v1/beacon/rewards/sync_committee/0xII",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {"status": {"operator": "equals", "value": "400"}}
|
||||
},
|
||||
{
|
||||
"topics": ["beacon", "rewards", "sync_committee_rewards_blockid"],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"body": {
|
||||
"content-type": "application/json",
|
||||
"data": "[]"
|
||||
},
|
||||
"url": "/eth/v1/beacon/rewards/sync_committee/foobar",
|
||||
"headers": {"Accept": "application/json"}
|
||||
},
|
||||
"response": {"status": {"operator": "equals", "value": "400"}}
|
||||
}
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user