Add the `execution_optimistic` flag to REST API responses. (#3780)

* Initial commit

* Make `events` API spec compliant.

* Add `Eth-Consensus-Version` in responses.

* Bump chronos to get redirect with headers working.

* Add `is_optimistic` field and handling to syncing RestSyncInfo.
This commit is contained in:
Eugene Kabanov 2022-06-20 08:53:39 +03:00 committed by GitHub
parent c24c737866
commit eb6b7affee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 417 additions and 90 deletions

View File

@ -38,7 +38,7 @@ type
RpcServer* = RpcHttpServer
EventBus* = object
blocksQueue*: AsyncEventQueue[ForkedTrustedSignedBeaconBlock]
blocksQueue*: AsyncEventQueue[EventBeaconBlockObject]
headQueue*: AsyncEventQueue[HeadChangeInfoObject]
reorgQueue*: AsyncEventQueue[ReorgInfoObject]
finUpdateQueue*: AsyncEventQueue[altair.LightClientFinalityUpdate]

View File

@ -294,6 +294,7 @@ type
epoch_transition*: bool
previous_duty_dependent_root*: Eth2Digest
current_duty_dependent_root*: Eth2Digest
optimistic* {.serializedFieldName: "execution_optimistic".}: Option[bool]
ReorgInfoObject* = object
slot*: Slot
@ -302,11 +303,18 @@ type
new_head_block*: Eth2Digest
old_head_state*: Eth2Digest
new_head_state*: Eth2Digest
optimistic* {.serializedFieldName: "execution_optimistic".}: Option[bool]
FinalizationInfoObject* = object
block_root* {.serializedFieldName: "block".}: Eth2Digest
state_root* {.serializedFieldName: "state".}: Eth2Digest
epoch*: Epoch
optimistic* {.serializedFieldName: "execution_optimistic".}: Option[bool]
EventBeaconBlockObject* = object
slot*: Slot
block_root* {.serializedFieldName: "block".}: Eth2Digest
optimistic* {.serializedFieldName: "execution_optimistic".}: Option[bool]
template head*(dag: ChainDAGRef): BlockRef = dag.headState.blck
@ -355,34 +363,48 @@ func blockRef*(key: KeyedBlockRef): BlockRef =
func init*(t: typedesc[HeadChangeInfoObject], slot: Slot, blockRoot: Eth2Digest,
stateRoot: Eth2Digest, epochTransition: bool,
previousDutyDepRoot: Eth2Digest,
currentDutyDepRoot: Eth2Digest): HeadChangeInfoObject =
previousDutyDepRoot: Eth2Digest, currentDutyDepRoot: Eth2Digest,
optimistic: Option[bool]): HeadChangeInfoObject =
HeadChangeInfoObject(
slot: slot,
block_root: blockRoot,
state_root: stateRoot,
epoch_transition: epochTransition,
previous_duty_dependent_root: previousDutyDepRoot,
current_duty_dependent_root: currentDutyDepRoot
current_duty_dependent_root: currentDutyDepRoot,
optimistic: optimistic
)
func init*(t: typedesc[ReorgInfoObject], slot: Slot, depth: uint64,
oldHeadBlockRoot: Eth2Digest, newHeadBlockRoot: Eth2Digest,
oldHeadStateRoot: Eth2Digest,
newHeadStateRoot: Eth2Digest): ReorgInfoObject =
oldHeadStateRoot: Eth2Digest, newHeadStateRoot: Eth2Digest,
optimistic: Option[bool]): ReorgInfoObject =
ReorgInfoObject(
slot: slot,
depth: depth,
old_head_block: oldHeadBlockRoot,
new_head_block: newHeadBlockRoot,
old_head_state: oldHeadStateRoot,
new_head_state: newHeadStateRoot
new_head_state: newHeadStateRoot,
optimistic: optimistic
)
func init*(t: typedesc[FinalizationInfoObject], blockRoot: Eth2Digest,
stateRoot: Eth2Digest, epoch: Epoch): FinalizationInfoObject =
stateRoot: Eth2Digest, epoch: Epoch,
optimistic: Option[bool]): FinalizationInfoObject =
FinalizationInfoObject(
block_root: blockRoot,
state_root: stateRoot,
epoch: epoch
epoch: epoch,
optimistic: optimistic
)
func init*(t: typedesc[EventBeaconBlockObject],
v: ForkedTrustedSignedBeaconBlock,
optimistic: Option[bool]): EventBeaconBlockObject =
withBlck(v):
EventBeaconBlockObject(
slot: blck.message.slot,
block_root: blck.root,
optimistic: optimistic
)

View File

@ -1531,6 +1531,13 @@ proc pruneStateCachesDAG*(dag: ChainDAGRef) =
statePruneDur = statePruneTick - startTick,
epochRefPruneDur = epochRefPruneTick - statePruneTick
template getHeadStateMergeComplete*(dag: ChainDAGRef): bool =
withState(dag.headState):
when stateFork >= BeaconStateFork.Bellatrix:
is_merge_transition_complete(state.data)
else:
false
proc updateHead*(
dag: ChainDAGRef,
newHead: BlockRef,
@ -1563,16 +1570,9 @@ proc updateHead*(
error "Cannot update head to block without parent"
return
template getHeadStateMergeComplete(): bool =
withState(dag.headState):
when stateFork >= BeaconStateFork.Bellatrix:
is_merge_transition_complete(state.data)
else:
false
let
lastHeadStateRoot = getStateRoot(dag.headState)
lastHeadMergeComplete = getHeadStateMergeComplete()
lastHeadMergeComplete = dag.getHeadStateMergeComplete()
# Start off by making sure we have the right state - updateState will try
# to use existing in-memory states to make this smooth
@ -1589,7 +1589,7 @@ proc updateHead*(
dag.head = newHead
if getHeadStateMergeComplete() and not lastHeadMergeComplete:
if dag.getHeadStateMergeComplete() and not lastHeadMergeComplete:
dag.vanityLogs.onMergeTransitionBlock()
dag.db.putHeadBlock(newHead.root)
@ -1622,10 +1622,15 @@ proc updateHead*(
finalized = shortLog(getStateField(dag.headState, finalized_checkpoint))
if not(isNil(dag.onReorgHappened)):
let data = ReorgInfoObject.init(dag.head.slot, uint64(ancestorDepth),
lastHead.root, newHead.root,
lastHeadStateRoot,
getStateRoot(dag.headState))
let
# TODO (cheatfate): Proper implementation required
optimistic =
if dag.getHeadStateMergeComplete(): some(false) else: none[bool]()
data = ReorgInfoObject.init(dag.head.slot, uint64(ancestorDepth),
lastHead.root, newHead.root,
lastHeadStateRoot,
getStateRoot(dag.headState),
optimistic)
dag.onReorgHappened(data)
# A reasonable criterion for "reorganizations of the chain"
@ -1646,10 +1651,13 @@ proc updateHead*(
depRoot = withState(dag.headState): state.proposer_dependent_root
prevDepRoot = withState(dag.headState): state.attester_dependent_root
epochTransition = (finalizedHead != dag.finalizedHead)
let data = HeadChangeInfoObject.init(dag.head.slot, dag.head.root,
getStateRoot(dag.headState),
epochTransition, depRoot,
prevDepRoot)
# TODO (cheatfate): Proper implementation required
optimistic =
if dag.getHeadStateMergeComplete(): some(false) else: none[bool]()
data = HeadChangeInfoObject.init(dag.head.slot, dag.head.root,
getStateRoot(dag.headState),
epochTransition, depRoot,
prevDepRoot, optimistic)
dag.onHeadChanged(data)
withState(dag.headState):
@ -1708,11 +1716,13 @@ proc updateHead*(
int(dag.finalizedHead.slot mod SLOTS_PER_HISTORICAL_ROOT)]
else:
Eth2Digest() # The thing that finalized was >8192 blocks old?
# TODO (cheatfate): Proper implementation required
let optimistic =
if dag.getHeadStateMergeComplete(): some(false) else: none[bool]()
let data = FinalizationInfoObject.init(
dag.finalizedHead.blck.root,
stateRoot,
dag.finalizedHead.slot.epoch)
dag.finalizedHead.blck.root, stateRoot, dag.finalizedHead.slot.epoch,
optimistic)
dag.onFinHappened(dag, data)
proc isInitialized*(T: type ChainDAGRef, db: BeaconChainDB): Result[void, cstring] =

View File

@ -152,10 +152,17 @@ proc loadChainDag(
eventBus: EventBus,
validatorMonitor: ref ValidatorMonitor,
networkGenesisValidatorsRoot: Option[Eth2Digest]): ChainDAGRef =
var dag: ChainDAGRef
info "Loading block DAG from database", path = config.databaseDir
proc onBlockAdded(data: ForkedTrustedSignedBeaconBlock) =
eventBus.blocksQueue.emit(data)
# TODO (cheatfate): Proper implementation required
let optimistic =
if isNil(dag):
none[bool]()
else:
if dag.getHeadStateMergeComplete(): some(false) else: none[bool]()
eventBus.blocksQueue.emit(EventBeaconBlockObject.init(data, optimistic))
proc onHeadChanged(data: HeadChangeInfoObject) =
eventBus.headQueue.emit(data)
proc onChainReorg(data: ReorgInfoObject) =
@ -175,14 +182,17 @@ proc loadChainDag(
onLightClientOptimisticUpdateCb =
if config.lightClientDataServe.get: onLightClientOptimisticUpdate
else: nil
dag = ChainDAGRef.init(
cfg, db, validatorMonitor, chainDagFlags, config.eraDir,
onBlockAdded, onHeadChanged, onChainReorg,
onLCFinalityUpdateCb = onLightClientFinalityUpdateCb,
onLCOptimisticUpdateCb = onLightClientOptimisticUpdateCb,
lightClientDataServe = config.lightClientDataServe.get,
lightClientDataImportMode = config.lightClientDataImportMode.get,
vanityLogs = getPandas(detectTTY(config.logStdout)))
dag = ChainDAGRef.init(
cfg, db, validatorMonitor, chainDagFlags, config.eraDir,
onBlockAdded, onHeadChanged, onChainReorg,
onLCFinalityUpdateCb = onLightClientFinalityUpdateCb,
onLCOptimisticUpdateCb = onLightClientOptimisticUpdateCb,
lightClientDataServe = config.lightClientDataServe.get,
lightClientDataImportMode = config.lightClientDataImportMode.get,
vanityLogs = getPandas(detectTTY(config.logStdout)))
let
databaseGenesisValidatorsRoot =
getStateField(dag.headState, genesis_validators_root)
@ -371,7 +381,7 @@ proc init*(T: type BeaconNode,
let
eventBus = EventBus(
blocksQueue: newAsyncEventQueue[ForkedTrustedSignedBeaconBlock](),
blocksQueue: newAsyncEventQueue[EventBeaconBlockObject](),
headQueue: newAsyncEventQueue[HeadChangeInfoObject](),
reorgQueue: newAsyncEventQueue[ReorgInfoObject](),
finUpdateQueue: newAsyncEventQueue[altair.LightClientFinalityUpdate](),

View File

@ -128,7 +128,10 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
$error)
node.withStateForBlockSlotId(bslot):
return RestApiResponse.jsonResponse((root: stateRoot))
return RestApiResponse.jsonResponseWOpt(
(root: stateRoot),
node.getStateOptimistic(state)
)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
@ -148,12 +151,16 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
$error)
node.withStateForBlockSlotId(bslot):
return RestApiResponse.jsonResponse(
return RestApiResponse.jsonResponseWOpt(
(
previous_version: getStateField(state, fork).previous_version,
current_version: getStateField(state, fork).current_version,
epoch: getStateField(state, fork).epoch
)
previous_version:
getStateField(state, fork).previous_version,
current_version:
getStateField(state, fork).current_version,
epoch:
getStateField(state, fork).epoch
),
node.getStateOptimistic(state)
)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
@ -171,15 +178,19 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
# in current version of database.
return RestApiResponse.jsonError(Http500, NoImplementationError)
return RestApiResponse.jsonError(Http404, StateNotFoundError,
$error)
$error)
node.withStateForBlockSlotId(bslot):
return RestApiResponse.jsonResponse(
return RestApiResponse.jsonResponseWOpt(
(
previous_justified: getStateField(state, previous_justified_checkpoint),
current_justified: getStateField(state, current_justified_checkpoint),
finalized: getStateField(state, finalized_checkpoint)
)
previous_justified:
getStateField(state, previous_justified_checkpoint),
current_justified:
getStateField(state, current_justified_checkpoint),
finalized:
getStateField(state, finalized_checkpoint)
),
node.getStateOptimistic(state)
)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
@ -303,7 +314,10 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
res.add(RestValidator.init(index, balance, toString(status),
validator))
res
return RestApiResponse.jsonResponse(response)
return RestApiResponse.jsonResponseWOpt(
response,
node.getStateOptimistic(state)
)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidator
@ -365,8 +379,9 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
ValidatorStatusNotFoundError,
$sres.get())
toString(sres.get())
return RestApiResponse.jsonResponse(
RestValidator.init(vindex, balance, status, validator)
return RestApiResponse.jsonResponseWOpt(
RestValidator.init(vindex, balance, status, validator),
node.getStateOptimistic(state)
)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
@ -454,7 +469,10 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
let balance = getStateField(state, balances).item(index)
res.add(RestValidatorBalance.init(index, balance))
res
return RestApiResponse.jsonResponse(response)
return RestApiResponse.jsonResponseWOpt(
response,
node.getStateOptimistic(state)
)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
@ -567,7 +585,10 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
else:
forSlot(vslot.get(), vindex, res)
return RestApiResponse.jsonResponse(res)
return RestApiResponse.jsonResponseWOpt(
res,
node.getStateOptimistic(state)
)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
@ -644,8 +665,10 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
offset.inc(length)
res
return RestApiResponse.jsonResponse(RestEpochSyncCommittee(
validators: indices, validator_aggregates: aggregates)
return RestApiResponse.jsonResponseWOpt(
RestEpochSyncCommittee(validators: indices,
validator_aggregates: aggregates),
node.getStateOptimistic(state)
)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
@ -677,7 +700,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return
withBlck(bdata):
RestApiResponse.jsonResponse(
RestApiResponse.jsonResponseWOpt(
[
(
root: blck.root,
@ -688,7 +711,8 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
signature: blck.signature
)
)
]
],
node.getBlockOptimistic(bdata)
)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockHeader
@ -704,7 +728,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return
withBlck(bdata):
RestApiResponse.jsonResponse(
RestApiResponse.jsonResponseWOpt(
(
root: blck.root,
canonical: node.dag.isCanonical(
@ -713,7 +737,8 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
message: blck.toBeaconBlockHeader,
signature: blck.signature
)
)
),
node.getBlockOptimistic(bdata)
)
# https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlock
@ -812,12 +837,25 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
if not node.dag.getBlockSSZ(bid, data):
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
RestApiResponse.response(data, Http200, $sszMediaType)
let
fork = node.dag.cfg.blockForkAtEpoch(bid.slot.epoch)
headers = [("eth-consensus-version", fork.toString())]
RestApiResponse.response(data, Http200, $sszMediaType,
headers = headers)
elif contentType == jsonMediaType:
let bdata = node.dag.getForkedBlock(bid).valueOr:
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
RestApiResponse.jsonResponsePlain(bdata.asSigned())
let
fork = node.dag.cfg.blockForkAtEpoch(bid.slot.epoch)
headers = [("eth-consensus-version", fork.toString())]
RestApiResponse.jsonResponseBlock(
bdata.asSigned(),
node.getBlockOptimistic(bdata),
headers
)
else:
RestApiResponse.jsonError(Http500, InvalidAcceptError)
@ -832,7 +870,13 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
bid = node.getBlockId(blockIdent).valueOr:
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
return RestApiResponse.jsonResponse((root: bid.root))
bdata = node.dag.getForkedBlock(bid).valueOr:
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
return RestApiResponse.jsonResponseWOpt(
(root: bid.root),
node.getBlockOptimistic(bdata)
)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockAttestations
router.api(MethodGet,
@ -848,7 +892,10 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return
withBlck(bdata):
RestApiResponse.jsonResponse(blck.message.body.attestations.asSeq())
RestApiResponse.jsonResponseWOpt(
blck.message.body.attestations.asSeq(),
node.getBlockOptimistic(bdata)
)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getPoolAttestations
router.api(MethodGet, "/eth/v1/beacon/pool/attestations") do (

View File

@ -42,7 +42,7 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
case state.kind
of BeaconStateFork.Phase0:
if contentType == sszMediaType:
RestApiResponse.sszResponse(state.phase0Data.data)
RestApiResponse.sszResponse(state.phase0Data.data, [])
elif contentType == jsonMediaType:
RestApiResponse.jsonResponse(state.phase0Data.data)
else:
@ -75,10 +75,14 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
node.withStateForBlockSlotId(bslot):
return
if contentType == jsonMediaType:
RestApiResponse.jsonResponsePlain(state)
RestApiResponse.jsonResponseState(
state,
node.getStateOptimistic(state)
)
elif contentType == sszMediaType:
let headers = [("eth-consensus-version", state.kind.toString())]
withState(state):
RestApiResponse.sszResponse(state.data)
RestApiResponse.sszResponse(state.data, headers)
else:
RestApiResponse.jsonError(Http500, InvalidAcceptError)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
@ -90,6 +94,19 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
node.dag.heads.mapIt((root: it.root, slot: it.slot))
)
# https://ethereum.github.io/beacon-APIs/#/Debug/getDebugChainHeadsV2
router.api(MethodGet,
"/eth/v2/debug/beacon/heads") do () -> RestApiResponse:
return RestApiResponse.jsonResponse(
node.dag.heads.mapIt(
(
root: it.root,
slot: it.slot,
execution_optimistic: node.getBlockRefOptimistic(it)
)
)
)
# Legacy URLS - Nimbus <= 1.5.5 used to expose the REST API with an additional
# `/api` path component
router.redirect(
@ -107,3 +124,8 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
"/api/eth/v1/debug/beacon/heads",
"/eth/v1/debug/beacon/heads"
)
router.redirect(
MethodGet,
"/api/eth/v2/debug/beacon/heads",
"/eth/v2/debug/beacon/heads"
)

View File

@ -53,12 +53,7 @@ proc eventHandler*[T](response: HttpResponseRef,
empty
for event in events:
let jsonRes =
when T is ForkedTrustedSignedBeaconBlock:
let blockInfo = RestBlockInfo.init(event)
RestApiResponse.prepareJsonStringResponse(blockInfo)
else:
RestApiResponse.prepareJsonStringResponse(event)
let jsonRes = RestApiResponse.prepareJsonStringResponse(event)
exitLoop =
if response.state != HttpResponseState.Sending:

View File

@ -257,13 +257,21 @@ proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) =
wallSlot = node.beaconClock.now().slotOrZero()
headSlot = node.dag.head.slot
distance = wallSlot - headSlot
isSyncing =
if isNil(node.syncManager):
false
else:
node.syncManager.inProgress
isOptimistic =
if node.dag.getHeadStateMergeComplete():
# TODO (cheatfate): Proper implementation required
some(false)
else:
none[bool]()
info = RestSyncInfo(
head_slot: headSlot, sync_distance: distance,
is_syncing:
if isNil(node.syncManager):
false
else:
node.syncManager.inProgress
is_syncing: isSyncing, is_optimistic: isOptimistic
)
return RestApiResponse.jsonResponse(info)

View File

@ -272,6 +272,40 @@ func keysToIndices*(cacheTable: var Table[ValidatorPubKey, ValidatorIndex],
proc getRouter*(allowedOrigin: Option[string]): RestRouter =
RestRouter.init(validate, allowedOrigin = allowedOrigin)
proc getStateOptimistic*(node: BeaconNode,
state: ForkedHashedBeaconState): Option[bool] =
if node.dag.getHeadStateMergeComplete():
case state.kind
of BeaconStateFork.Phase0, BeaconStateFork.Altair:
some[bool](false)
of BeaconStateFork.Bellatrix:
# TODO (cheatfate): Proper implementation required.
some[bool](false)
else:
none[bool]()
proc getBlockOptimistic*(node: BeaconNode,
blck: ForkedTrustedSignedBeaconBlock |
ForkedSignedBeaconBlock): Option[bool] =
if node.dag.getHeadStateMergeComplete():
case blck.kind
of BeaconBlockFork.Phase0, BeaconBlockFork.Altair:
some[bool](false)
of BeaconBlockFork.Bellatrix:
# TODO (cheatfate): Proper implementation required.
some[bool](false)
else:
none[bool]()
proc getBlockRefOptimistic*(node: BeaconNode, blck: BlockRef): bool =
let blck = node.dag.getForkedBlock(blck.bid).get()
case blck.kind
of BeaconBlockFork.Phase0, BeaconBlockFork.Altair:
false
of BeaconBlockFork.Bellatrix:
# TODO (cheatfate): Proper implementation required.
false
const
jsonMediaType* = MediaType.init("application/json")
sszMediaType* = MediaType.init("application/octet-stream")

View File

@ -96,8 +96,13 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
)
)
res
# TODO (cheatfate): Proper implementation required
let optimistic =
if node.dag.getHeadStateMergeComplete(): some(false) else: none[bool]()
return RestApiResponse.jsonResponseWRoot(
duties, epochRef.attester_dependent_root)
duties, epochRef.attester_dependent_root, optimistic)
# https://ethereum.github.io/beacon-APIs/#/Validator/getProposerDuties
router.api(MethodGet, "/eth/v1/validator/duties/proposer/{epoch}") do (
@ -142,8 +147,13 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
)
)
res
# TODO (cheatfate): Proper implementation required
let optimistic =
if node.dag.getHeadStateMergeComplete(): some(false) else: none[bool]()
return RestApiResponse.jsonResponseWRoot(
duties, epochRef.proposer_dependent_root)
duties, epochRef.proposer_dependent_root, optimistic)
router.api(MethodPost, "/eth/v1/validator/duties/sync/{epoch}") do (
epoch: Epoch, contentBody: Option[ContentBody]) -> RestApiResponse:
@ -226,6 +236,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
headSyncPeriod = sync_committee_period(headEpoch)
if qSyncPeriod == headSyncPeriod:
let optimistic = node.getStateOptimistic(node.dag.headState)
let res = withState(node.dag.headState):
when stateFork >= BeaconStateFork.Altair:
produceResponse(indexList,
@ -233,8 +244,9 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
state.data.validators.asSeq)
else:
emptyResponse()
return RestApiResponse.jsonResponse(res)
return RestApiResponse.jsonResponseWOpt(res, optimistic)
elif qSyncPeriod == (headSyncPeriod + 1):
let optimistic = node.getStateOptimistic(node.dag.headState)
let res = withState(node.dag.headState):
when stateFork >= BeaconStateFork.Altair:
produceResponse(indexList,
@ -242,7 +254,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
state.data.validators.asSeq)
else:
emptyResponse()
return RestApiResponse.jsonResponse(res)
return RestApiResponse.jsonResponseWOpt(res, optimistic)
elif qSyncPeriod > headSyncPeriod:
# The requested epoch may still be too far in the future.
if not(node.isSynced(node.dag.head)):
@ -265,6 +277,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonError(Http404, StateNotFoundError)
node.withStateForBlockSlotId(bsi):
let optimistic = node.getStateOptimistic(state)
let res = withState(state):
when stateFork >= BeaconStateFork.Altair:
produceResponse(indexList,
@ -272,7 +285,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
state.data.validators.asSeq)
else:
emptyResponse()
return RestApiResponse.jsonResponse(res)
return RestApiResponse.jsonResponseWOpt(res, optimistic)
return RestApiResponse.jsonError(Http404, StateNotFoundError)

View File

@ -11,6 +11,7 @@ import stew/[assign2, results, base10, byteutils], presto/common,
json_serialization/std/[options, net, sets],
chronicles
import ".."/[eth2_ssz_serialization, forks, keystore],
".."/../consensus_object_pools/block_pools_types,
".."/datatypes/[phase0, altair, bellatrix],
".."/mev/bellatrix_mev,
".."/../validators/slashing_protection_common,
@ -187,7 +188,8 @@ proc prepareJsonStringResponse*(t: typedesc[RestApiResponse], d: auto): string =
res
proc jsonResponseWRoot*(t: typedesc[RestApiResponse], data: auto,
dependent_root: Eth2Digest): RestApiResponse =
dependent_root: Eth2Digest,
execOpt: Option[bool]): RestApiResponse =
let res =
block:
var default: seq[byte]
@ -196,6 +198,8 @@ proc jsonResponseWRoot*(t: typedesc[RestApiResponse], data: auto,
var writer = JsonWriter[RestJson].init(stream)
writer.beginRecord()
writer.writeField("dependent_root", dependent_root)
if execOpt.isSome():
writer.writeField("execution_optimistic", execOpt.get())
writer.writeField("data", data)
writer.endRecord()
stream.getOutput(seq[byte])
@ -222,6 +226,84 @@ proc jsonResponse*(t: typedesc[RestApiResponse], data: auto): RestApiResponse =
default
RestApiResponse.response(res, Http200, "application/json")
proc jsonResponseBlock*(t: typedesc[RestApiResponse],
data: ForkedSignedBeaconBlock,
execOpt: Option[bool],
headers: openArray[tuple[key: string, value: string]]
): RestApiResponse =
let res =
block:
var default: seq[byte]
try:
var stream = memoryOutput()
var writer = JsonWriter[RestJson].init(stream)
writer.beginRecord()
writer.writeField("version", data.kind.toString())
if execOpt.isSome():
writer.writeField("execution_optimistic", execOpt.get())
withBlck(data):
writer.writeField("data", blck)
writer.endRecord()
stream.getOutput(seq[byte])
except SerializationError:
default
except IOError:
default
RestApiResponse.response(res, Http200, "application/json",
headers = headers)
proc jsonResponseState*(t: typedesc[RestApiResponse],
forkedState: ForkedHashedBeaconState,
execOpt: Option[bool]): RestApiResponse =
let
headers = [("eth-consensus-version", forkedState.kind.toString())]
res =
block:
var default: seq[byte]
try:
var stream = memoryOutput()
var writer = JsonWriter[RestJson].init(stream)
writer.beginRecord()
writer.writeField("version", forkedState.kind.toString())
if execOpt.isSome():
writer.writeField("execution_optimistic", execOpt.get())
# TODO (cheatfate): Unable to use `forks.withState()` template here
# because of compiler issues and some kind of generic sandwich.
case forkedState.kind
of BeaconStateFork.Bellatrix:
writer.writeField("data", forkedState.bellatrixData.data)
of BeaconStateFork.Altair:
writer.writeField("data", forkedState.altairData.data)
of BeaconStateFork.Phase0:
writer.writeField("data", forkedState.phase0Data.data)
writer.endRecord()
stream.getOutput(seq[byte])
except SerializationError:
default
except IOError:
default
RestApiResponse.response(res, Http200, "application/json", headers = headers)
proc jsonResponseWOpt*(t: typedesc[RestApiResponse], data: auto,
execOpt: Option[bool]): RestApiResponse =
let res =
block:
var default: seq[byte]
try:
var stream = memoryOutput()
var writer = JsonWriter[RestJson].init(stream)
writer.beginRecord()
if execOpt.isSome():
writer.writeField("execution_optimistic", execOpt.get())
writer.writeField("data", data)
writer.endRecord()
stream.getOutput(seq[byte])
except SerializationError:
default
except IOError:
default
RestApiResponse.response(res, Http200, "application/json")
proc jsonResponsePlain*(t: typedesc[RestApiResponse],
data: auto): RestApiResponse =
let res =
@ -365,7 +447,9 @@ proc jsonErrorList*(t: typedesc[RestApiResponse],
default
RestApiResponse.error(status, data, "application/json")
proc sszResponse*(t: typedesc[RestApiResponse], data: auto): RestApiResponse =
proc sszResponse*(t: typedesc[RestApiResponse], data: auto,
headers: openArray[tuple[key: string, value: string]]
): RestApiResponse =
let res =
block:
var default: seq[byte]
@ -378,7 +462,8 @@ proc sszResponse*(t: typedesc[RestApiResponse], data: auto): RestApiResponse =
default
except IOError:
default
RestApiResponse.response(res, Http200, "application/octet-stream")
RestApiResponse.response(res, Http200, "application/octet-stream",
headers = headers)
template hexOriginal(data: openArray[byte]): string =
to0xHex(data)
@ -2012,6 +2097,68 @@ proc dump*(value: KeystoresAndSlashingProtection): string {.
writer.writeValue(value)
stream.getOutput(string)
proc writeValue*(writer: var JsonWriter[RestJson],
value: HeadChangeInfoObject) {.
raises: [IOError, Defect].} =
writer.beginRecord()
writer.writeField("slot", value.slot)
writer.writeField("block", value.block_root)
writer.writeField("state", value.state_root)
writer.writeField("epoch_transition", value.epoch_transition)
writer.writeField("previous_duty_dependent_root",
value.previous_duty_dependent_root)
writer.writeField("current_duty_dependent_root",
value.current_duty_dependent_root)
if value.optimistic.isSome():
writer.writeField("execution_optimistic", value.optimistic.get())
writer.endRecord()
proc writeValue*(writer: var JsonWriter[RestJson],
value: ReorgInfoObject) {.
raises: [IOError, Defect].} =
writer.beginRecord()
writer.writeField("slot", value.slot)
writer.writeField("depth", value.depth)
writer.writeField("old_head_block", value.old_head_block)
writer.writeField("new_head_block", value.new_head_block)
writer.writeField("old_head_state", value.old_head_state)
writer.writeField("new_head_state", value.new_head_state)
if value.optimistic.isSome():
writer.writeField("execution_optimistic", value.optimistic.get())
writer.endRecord()
proc writeValue*(writer: var JsonWriter[RestJson],
value: FinalizationInfoObject) {.
raises: [IOError, Defect].} =
writer.beginRecord()
writer.writeField("block", value.block_root)
writer.writeField("state", value.state_root)
writer.writeField("epoch", value.epoch)
if value.optimistic.isSome():
writer.writeField("execution_optimistic", value.optimistic.get())
writer.endRecord()
proc writeValue*(writer: var JsonWriter[RestJson],
value: EventBeaconBlockObject) {.
raises: [IOError, Defect].} =
writer.beginRecord()
writer.writeField("slot", value.slot)
writer.writeField("block", value.block_root)
if value.optimistic.isSome():
writer.writeField("execution_optimistic", value.optimistic.get())
writer.endRecord()
proc writeValue*(writer: var JsonWriter[RestJson],
value: RestSyncInfo) {.
raises: [IOError, Defect].} =
writer.beginRecord()
writer.writeField("head_slot", value.head_slot)
writer.writeField("sync_distance", value.sync_distance)
writer.writeField("is_syncing", value.is_syncing)
if value.is_optimistic.isSome():
writer.writeField("is_optimistic", value.is_optimistic.get())
writer.endRecord()
proc parseRoot(value: string): Result[Eth2Digest, cstring] =
try:
ok(Eth2Digest(data: hexToByteArray[32](value)))

View File

@ -210,6 +210,7 @@ type
head_slot*: Slot
sync_distance*: uint64
is_syncing*: bool
is_optimistic*: Option[bool]
RestPeerCount* = object
disconnected*: uint64

View File

@ -249,6 +249,24 @@ template init*(T: type ForkedTrustedSignedBeaconBlock, blck: altair.TrustedSigne
template init*(T: type ForkedTrustedSignedBeaconBlock, blck: bellatrix.TrustedSignedBeaconBlock): T =
T(kind: BeaconBlockFork.Bellatrix, bellatrixData: blck)
template toString*(kind: BeaconBlockFork): string =
case kind
of BeaconBlockFork.Phase0:
"phase0"
of BeaconBlockFork.Altair:
"altair"
of BeaconBlockFork.Bellatrix:
"bellatrix"
template toString*(kind: BeaconStateFork): string =
case kind
of BeaconStateFork.Phase0:
"phase0"
of BeaconStateFork.Altair:
"altair"
of BeaconStateFork.Bellatrix:
"bellatrix"
template toFork*[T:
phase0.SignedBeaconBlock |
phase0.SigVerifiedSignedBeaconBlock |

2
vendor/nim-chronos vendored

@ -1 +1 @@
Subproject commit 61fbbc551208aca182ff810661bdf37b08a377cd
Subproject commit 2a5095505f771610f9559d2e774b2a9561f01101

2
vendor/nim-presto vendored

@ -1 +1 @@
Subproject commit 1dba6dd6f466cd4e7b793b0e473c237ce453d82a
Subproject commit 6e36cbc474e075ea9a64e7e1657082c0ec80f4e6