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:
parent
c24c737866
commit
eb6b7affee
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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] =
|
||||
|
|
|
@ -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](),
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -210,6 +210,7 @@ type
|
|||
head_slot*: Slot
|
||||
sync_distance*: uint64
|
||||
is_syncing*: bool
|
||||
is_optimistic*: Option[bool]
|
||||
|
||||
RestPeerCount* = object
|
||||
disconnected*: uint64
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 61fbbc551208aca182ff810661bdf37b08a377cd
|
||||
Subproject commit 2a5095505f771610f9559d2e774b2a9561f01101
|
|
@ -1 +1 @@
|
|||
Subproject commit 1dba6dd6f466cd4e7b793b0e473c237ce453d82a
|
||||
Subproject commit 6e36cbc474e075ea9a64e7e1657082c0ec80f4e6
|
Loading…
Reference in New Issue