capella gossip support (#4386)
This commit is contained in:
parent
2f228e3fbd
commit
38827d776c
|
@ -291,7 +291,8 @@ proc installMessageValidators*(
|
||||||
ValidationResult.Ignore
|
ValidationResult.Ignore
|
||||||
|
|
||||||
let forkDigests = lightClient.forkDigests
|
let forkDigests = lightClient.forkDigests
|
||||||
for digest in [forkDigests.altair, forkDigests.bellatrix]:
|
for digest in [
|
||||||
|
forkDigests.altair, forkDigests.bellatrix, forkDigests.capella]:
|
||||||
lightClient.network.addValidator(
|
lightClient.network.addValidator(
|
||||||
getLightClientFinalityUpdateTopic(digest),
|
getLightClientFinalityUpdateTopic(digest),
|
||||||
proc(msg: altair.LightClientFinalityUpdate): ValidationResult =
|
proc(msg: altair.LightClientFinalityUpdate): ValidationResult =
|
||||||
|
@ -321,7 +322,8 @@ proc updateGossipStatus*(
|
||||||
isBehind = lcBehind and dagBehind
|
isBehind = lcBehind and dagBehind
|
||||||
|
|
||||||
currentEpochTargetGossipState = getTargetGossipState(
|
currentEpochTargetGossipState = getTargetGossipState(
|
||||||
epoch, cfg.ALTAIR_FORK_EPOCH, cfg.BELLATRIX_FORK_EPOCH, isBehind)
|
epoch, cfg.ALTAIR_FORK_EPOCH, cfg.BELLATRIX_FORK_EPOCH,
|
||||||
|
cfg.CAPELLA_FORK_EPOCH, isBehind)
|
||||||
targetGossipState =
|
targetGossipState =
|
||||||
if lcBehind or epoch < 1:
|
if lcBehind or epoch < 1:
|
||||||
currentEpochTargetGossipState
|
currentEpochTargetGossipState
|
||||||
|
@ -330,7 +332,8 @@ proc updateGossipStatus*(
|
||||||
# which is in the past relative to the signature slot (current slot).
|
# which is in the past relative to the signature slot (current slot).
|
||||||
# Therefore, LC topic subscriptions are kept for 1 extra epoch.
|
# Therefore, LC topic subscriptions are kept for 1 extra epoch.
|
||||||
let previousEpochTargetGossipState = getTargetGossipState(
|
let previousEpochTargetGossipState = getTargetGossipState(
|
||||||
epoch - 1, cfg.ALTAIR_FORK_EPOCH, cfg.BELLATRIX_FORK_EPOCH, isBehind)
|
epoch - 1, cfg.ALTAIR_FORK_EPOCH, cfg.BELLATRIX_FORK_EPOCH,
|
||||||
|
cfg.CAPELLA_FORK_EPOCH, isBehind)
|
||||||
currentEpochTargetGossipState + previousEpochTargetGossipState
|
currentEpochTargetGossipState + previousEpochTargetGossipState
|
||||||
|
|
||||||
template currentGossipState(): auto = lightClient.gossipState
|
template currentGossipState(): auto = lightClient.gossipState
|
||||||
|
|
|
@ -816,11 +816,13 @@ func chunkMaxSize[T](): uint32 =
|
||||||
func maxGossipMaxSize(): auto {.compileTime.} =
|
func maxGossipMaxSize(): auto {.compileTime.} =
|
||||||
max(GOSSIP_MAX_SIZE, GOSSIP_MAX_SIZE_BELLATRIX)
|
max(GOSSIP_MAX_SIZE, GOSSIP_MAX_SIZE_BELLATRIX)
|
||||||
|
|
||||||
|
from ../spec/datatypes/capella import SignedBeaconBlock
|
||||||
|
|
||||||
template gossipMaxSize(T: untyped): uint32 =
|
template gossipMaxSize(T: untyped): uint32 =
|
||||||
const maxSize = static:
|
const maxSize = static:
|
||||||
when isFixedSize(T):
|
when isFixedSize(T):
|
||||||
fixedPortionSize(T)
|
fixedPortionSize(T)
|
||||||
elif T is bellatrix.SignedBeaconBlock:
|
elif T is bellatrix.SignedBeaconBlock or T is capella.SignedBeaconBlock:
|
||||||
GOSSIP_MAX_SIZE_BELLATRIX
|
GOSSIP_MAX_SIZE_BELLATRIX
|
||||||
# TODO https://github.com/status-im/nim-ssz-serialization/issues/20 for
|
# TODO https://github.com/status-im/nim-ssz-serialization/issues/20 for
|
||||||
# Attestation, AttesterSlashing, and SignedAggregateAndProof, which all
|
# Attestation, AttesterSlashing, and SignedAggregateAndProof, which all
|
||||||
|
@ -2625,9 +2627,6 @@ proc broadcastBeaconBlock*(
|
||||||
let topic = getBeaconBlocksTopic(node.forkDigests.bellatrix)
|
let topic = getBeaconBlocksTopic(node.forkDigests.bellatrix)
|
||||||
node.broadcast(topic, blck)
|
node.broadcast(topic, blck)
|
||||||
|
|
||||||
# TODO when forks re-exports this, use that instead and rm this
|
|
||||||
from ../spec/datatypes/capella import SignedBeaconBlock
|
|
||||||
|
|
||||||
proc broadcastBeaconBlock*(
|
proc broadcastBeaconBlock*(
|
||||||
node: Eth2Node, blck: capella.SignedBeaconBlock): Future[SendResult] =
|
node: Eth2Node, blck: capella.SignedBeaconBlock): Future[SendResult] =
|
||||||
let topic = getBeaconBlocksTopic(node.forkDigests.capella)
|
let topic = getBeaconBlocksTopic(node.forkDigests.capella)
|
||||||
|
|
|
@ -813,7 +813,8 @@ proc updateBlocksGossipStatus*(
|
||||||
dagIsBehind
|
dagIsBehind
|
||||||
|
|
||||||
targetGossipState = getTargetGossipState(
|
targetGossipState = getTargetGossipState(
|
||||||
slot.epoch, cfg.ALTAIR_FORK_EPOCH, cfg.BELLATRIX_FORK_EPOCH, isBehind)
|
slot.epoch, cfg.ALTAIR_FORK_EPOCH, cfg.BELLATRIX_FORK_EPOCH,
|
||||||
|
cfg.CAPELLA_FORK_EPOCH, isBehind)
|
||||||
|
|
||||||
template currentGossipState(): auto = node.blocksGossipState
|
template currentGossipState(): auto = node.blocksGossipState
|
||||||
if currentGossipState == targetGossipState:
|
if currentGossipState == targetGossipState:
|
||||||
|
@ -1024,6 +1025,7 @@ proc updateGossipStatus(node: BeaconNode, slot: Slot) {.async.} =
|
||||||
slot.epoch,
|
slot.epoch,
|
||||||
node.dag.cfg.ALTAIR_FORK_EPOCH,
|
node.dag.cfg.ALTAIR_FORK_EPOCH,
|
||||||
node.dag.cfg.BELLATRIX_FORK_EPOCH,
|
node.dag.cfg.BELLATRIX_FORK_EPOCH,
|
||||||
|
node.dag.cfg.CAPELLA_FORK_EPOCH,
|
||||||
isBehind)
|
isBehind)
|
||||||
|
|
||||||
doAssert targetGossipState.card <= 2
|
doAssert targetGossipState.card <= 2
|
||||||
|
@ -1374,6 +1376,8 @@ proc installRestHandlers(restServer: RestServerRef, node: BeaconNode) =
|
||||||
if node.dag.lcDataStore.serve:
|
if node.dag.lcDataStore.serve:
|
||||||
restServer.router.installLightClientApiHandlers(node)
|
restServer.router.installLightClientApiHandlers(node)
|
||||||
|
|
||||||
|
from ./spec/datatypes/capella import SignedBeaconBlock
|
||||||
|
|
||||||
proc installMessageValidators(node: BeaconNode) =
|
proc installMessageValidators(node: BeaconNode) =
|
||||||
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.1/specs/phase0/p2p-interface.md#attestations-and-aggregation
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.1/specs/phase0/p2p-interface.md#attestations-and-aggregation
|
||||||
# These validators stay around the whole time, regardless of which specific
|
# These validators stay around the whole time, regardless of which specific
|
||||||
|
@ -1433,10 +1437,11 @@ proc installMessageValidators(node: BeaconNode) =
|
||||||
|
|
||||||
installPhase0Validators(forkDigests.phase0)
|
installPhase0Validators(forkDigests.phase0)
|
||||||
|
|
||||||
# Validators introduced in phase0 are also used in altair and merge, but with
|
# Validators introduced in phase0 are also used in Altair and Bellatrix, but
|
||||||
# different fork digest
|
# with different fork digests
|
||||||
installPhase0Validators(forkDigests.altair)
|
installPhase0Validators(forkDigests.altair)
|
||||||
installPhase0Validators(forkDigests.bellatrix)
|
installPhase0Validators(forkDigests.bellatrix)
|
||||||
|
installPhase0Validators(forkDigests.capella)
|
||||||
|
|
||||||
node.network.addValidator(
|
node.network.addValidator(
|
||||||
getBeaconBlocksTopic(forkDigests.altair),
|
getBeaconBlocksTopic(forkDigests.altair),
|
||||||
|
@ -1458,6 +1463,16 @@ proc installMessageValidators(node: BeaconNode) =
|
||||||
toValidationResult(node.processor[].processSignedBeaconBlock(
|
toValidationResult(node.processor[].processSignedBeaconBlock(
|
||||||
MsgSource.gossip, signedBlock)))
|
MsgSource.gossip, signedBlock)))
|
||||||
|
|
||||||
|
node.network.addValidator(
|
||||||
|
getBeaconBlocksTopic(forkDigests.capella),
|
||||||
|
proc (signedBlock: capella.SignedBeaconBlock): ValidationResult =
|
||||||
|
if node.shouldSyncOptimistically(node.currentSlot):
|
||||||
|
toValidationResult(
|
||||||
|
node.optimisticProcessor.processSignedBeaconBlock(signedBlock))
|
||||||
|
else:
|
||||||
|
toValidationResult(node.processor[].processSignedBeaconBlock(
|
||||||
|
MsgSource.gossip, signedBlock)))
|
||||||
|
|
||||||
template installSyncCommitteeeValidators(digest: auto) =
|
template installSyncCommitteeeValidators(digest: auto) =
|
||||||
for subcommitteeIdx in SyncSubcommitteeIndex:
|
for subcommitteeIdx in SyncSubcommitteeIndex:
|
||||||
closureScope:
|
closureScope:
|
||||||
|
@ -1479,6 +1494,7 @@ proc installMessageValidators(node: BeaconNode) =
|
||||||
|
|
||||||
installSyncCommitteeeValidators(forkDigests.altair)
|
installSyncCommitteeeValidators(forkDigests.altair)
|
||||||
installSyncCommitteeeValidators(forkDigests.bellatrix)
|
installSyncCommitteeeValidators(forkDigests.bellatrix)
|
||||||
|
installSyncCommitteeeValidators(forkDigests.capella)
|
||||||
|
|
||||||
node.installLightClientMessageValidators()
|
node.installLightClientMessageValidators()
|
||||||
|
|
||||||
|
|
|
@ -219,7 +219,8 @@ programMain:
|
||||||
isBehind = not shouldSyncOptimistically(slot)
|
isBehind = not shouldSyncOptimistically(slot)
|
||||||
|
|
||||||
targetGossipState = getTargetGossipState(
|
targetGossipState = getTargetGossipState(
|
||||||
slot.epoch, cfg.ALTAIR_FORK_EPOCH, cfg.BELLATRIX_FORK_EPOCH, isBehind)
|
slot.epoch, cfg.ALTAIR_FORK_EPOCH, cfg.BELLATRIX_FORK_EPOCH,
|
||||||
|
cfg.CAPELLA_FORK_EPOCH, isBehind)
|
||||||
|
|
||||||
template currentGossipState(): auto = blocksGossipState
|
template currentGossipState(): auto = blocksGossipState
|
||||||
if currentGossipState == targetGossipState:
|
if currentGossipState == targetGossipState:
|
||||||
|
|
|
@ -966,7 +966,7 @@ func checkForkConsistency*(cfg: RuntimeConfig) =
|
||||||
firstForkEpoch: Epoch, secondForkEpoch: Epoch) =
|
firstForkEpoch: Epoch, secondForkEpoch: Epoch) =
|
||||||
doAssert distinctBase(firstForkEpoch) <= distinctBase(secondForkEpoch)
|
doAssert distinctBase(firstForkEpoch) <= distinctBase(secondForkEpoch)
|
||||||
|
|
||||||
# TODO https://github.com/ethereum/consensus-specs/issues/2902 multiple
|
# https://github.com/ethereum/consensus-specs/issues/2902 multiple
|
||||||
# fork transitions per epoch don't work in a well-defined way.
|
# fork transitions per epoch don't work in a well-defined way.
|
||||||
doAssert distinctBase(firstForkEpoch) < distinctBase(secondForkEpoch) or
|
doAssert distinctBase(firstForkEpoch) < distinctBase(secondForkEpoch) or
|
||||||
firstForkEpoch in [GENESIS_EPOCH, FAR_FUTURE_EPOCH]
|
firstForkEpoch in [GENESIS_EPOCH, FAR_FUTURE_EPOCH]
|
||||||
|
|
|
@ -26,20 +26,20 @@ const
|
||||||
topicAggregateAndProofsSuffix* = "beacon_aggregate_and_proof/ssz_snappy"
|
topicAggregateAndProofsSuffix* = "beacon_aggregate_and_proof/ssz_snappy"
|
||||||
topicBlsToExecutionChangeSuffix* = "bls_to_execution_change/ssz_snappy"
|
topicBlsToExecutionChangeSuffix* = "bls_to_execution_change/ssz_snappy"
|
||||||
|
|
||||||
# https://github.com/ethereum/consensus-specs/blob/v1.2.0-rc.2/specs/phase0/p2p-interface.md#configuration
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.1/specs/phase0/p2p-interface.md#configuration
|
||||||
MAX_CHUNK_SIZE* = 1 * 1024 * 1024 # bytes
|
MAX_CHUNK_SIZE* = 1 * 1024 * 1024 # bytes
|
||||||
GOSSIP_MAX_SIZE* = 1 * 1024 * 1024 # bytes
|
GOSSIP_MAX_SIZE* = 1 * 1024 * 1024 # bytes
|
||||||
TTFB_TIMEOUT* = 5.seconds
|
TTFB_TIMEOUT* = 5.seconds
|
||||||
RESP_TIMEOUT* = 10.seconds
|
RESP_TIMEOUT* = 10.seconds
|
||||||
|
|
||||||
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.0/specs/bellatrix/p2p-interface.md#configuration
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.1/specs/bellatrix/p2p-interface.md#configuration
|
||||||
GOSSIP_MAX_SIZE_BELLATRIX* = 10 * 1024 * 1024 # bytes
|
GOSSIP_MAX_SIZE_BELLATRIX* = 10 * 1024 * 1024 # bytes
|
||||||
MAX_CHUNK_SIZE_BELLATRIX* = 10 * 1024 * 1024 # bytes
|
MAX_CHUNK_SIZE_BELLATRIX* = 10 * 1024 * 1024 # bytes
|
||||||
|
|
||||||
defaultEth2TcpPort* = 9000
|
defaultEth2TcpPort* = 9000
|
||||||
defaultEth2TcpPortDesc* = $defaultEth2TcpPort
|
defaultEth2TcpPortDesc* = $defaultEth2TcpPort
|
||||||
|
|
||||||
# This is not part of the spec! But its port which uses Lighthouse
|
# This is not part of the spec! But it's port which Lighthouse uses
|
||||||
defaultEth2RestPort* = 5052
|
defaultEth2RestPort* = 5052
|
||||||
defaultEth2RestPortDesc* = $defaultEth2RestPort
|
defaultEth2RestPortDesc* = $defaultEth2RestPort
|
||||||
|
|
||||||
|
@ -147,44 +147,43 @@ func getDiscoveryForkID*(cfg: RuntimeConfig,
|
||||||
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.1/specs/altair/p2p-interface.md#transitioning-the-gossip
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.1/specs/altair/p2p-interface.md#transitioning-the-gossip
|
||||||
type GossipState* = set[BeaconStateFork]
|
type GossipState* = set[BeaconStateFork]
|
||||||
func getTargetGossipState*(
|
func getTargetGossipState*(
|
||||||
epoch, ALTAIR_FORK_EPOCH, BELLATRIX_FORK_EPOCH: Epoch, isBehind: bool):
|
epoch, ALTAIR_FORK_EPOCH, BELLATRIX_FORK_EPOCH, CAPELLA_FORK_EPOCH: Epoch,
|
||||||
GossipState =
|
isBehind: bool): GossipState =
|
||||||
if isBehind:
|
if isBehind:
|
||||||
{}
|
return {}
|
||||||
|
|
||||||
# The order of these checks doesn't matter.
|
# When EIP4844 comes in, can be transformed to discard of that symbol
|
||||||
elif epoch >= BELLATRIX_FORK_EPOCH:
|
static: doAssert high(BeaconStateFork) == BeaconStateFork.Capella
|
||||||
{BeaconStateFork.Bellatrix}
|
|
||||||
elif epoch + 1 < ALTAIR_FORK_EPOCH:
|
|
||||||
{BeaconStateFork.Phase0}
|
|
||||||
|
|
||||||
# Order remaining checks so ALTAIR_FORK_EPOCH == BELLATRIX_FORK_EPOCH works
|
doAssert BELLATRIX_FORK_EPOCH >= ALTAIR_FORK_EPOCH
|
||||||
# and when the transition zones align contiguously, or are separated by
|
doAssert CAPELLA_FORK_EPOCH >= BELLATRIX_FORK_EPOCH
|
||||||
# intermediate pure-Altair epochs.
|
|
||||||
#
|
|
||||||
# In the first case, should never enable Altair, and there's also never
|
|
||||||
# a Phase -> Altair, or Altair -> Bellatrix gossip transition epoch. In
|
|
||||||
# contiguous Phase0 -> Altair and Altair -> Bellatrix transitions, that
|
|
||||||
# pure Altair state gossip state never occurs, but it works without any
|
|
||||||
# special cases so long as one checks for transition-to-fork+1 before a
|
|
||||||
# pure fork gossip state.
|
|
||||||
#
|
|
||||||
# Therefore, check for transition-to-merge before pure-Altair.
|
|
||||||
elif epoch + 1 >= BELLATRIX_FORK_EPOCH:
|
|
||||||
# As there are only two fork epochs and there's no transition to phase0
|
|
||||||
{if ALTAIR_FORK_EPOCH == BELLATRIX_FORK_EPOCH:
|
|
||||||
BeaconStateFork.Phase0
|
|
||||||
else:
|
|
||||||
BeaconStateFork.Altair,
|
|
||||||
BeaconStateFork.Bellatrix}
|
|
||||||
elif epoch >= ALTAIR_FORK_EPOCH:
|
|
||||||
{BeaconStateFork.Altair}
|
|
||||||
|
|
||||||
# Must be after the case which catches phase0 => merge
|
# https://github.com/ethereum/consensus-specs/issues/2902
|
||||||
elif epoch + 1 >= ALTAIR_FORK_EPOCH:
|
# Don't care whether ALTAIR_FORK_EPOCH == BELLATRIX_FORK_EPOCH or
|
||||||
{BeaconStateFork.Phase0, BeaconStateFork.Altair}
|
# BELLATRIX_FORK_EPOCH == CAPELLA_FORK_EPOCH works, because those
|
||||||
else:
|
# theoretically possible networks are ill-defined regardless, and
|
||||||
raiseAssert "Unknown target gossip state"
|
# consequently prohibited by checkForkConsistency(). Therefore, a
|
||||||
|
# transitional epoch always exists, for every fork.
|
||||||
|
var targetForks: GossipState
|
||||||
|
|
||||||
|
template maybeIncludeFork(
|
||||||
|
targetFork: BeaconStateFork, targetForkEpoch: Epoch,
|
||||||
|
successiveForkEpoch: Epoch) =
|
||||||
|
# Subscribe one epoch ahead
|
||||||
|
if epoch + 1 >= targetForkEpoch and epoch < successiveForkEpoch:
|
||||||
|
targetForks.incl targetFork
|
||||||
|
|
||||||
|
maybeIncludeFork(
|
||||||
|
BeaconStateFork.Phase0, GENESIS_EPOCH, ALTAIR_FORK_EPOCH)
|
||||||
|
maybeIncludeFork(
|
||||||
|
BeaconStateFork.Altair, ALTAIR_FORK_EPOCH, BELLATRIX_FORK_EPOCH)
|
||||||
|
maybeIncludeFork(
|
||||||
|
BeaconStateFork.Bellatrix, BELLATRIX_FORK_EPOCH, CAPELLA_FORK_EPOCH)
|
||||||
|
maybeIncludeFork(
|
||||||
|
BeaconStateFork.Capella, CAPELLA_FORK_EPOCH, FAR_FUTURE_EPOCH)
|
||||||
|
|
||||||
|
doAssert len(targetForks) <= 2
|
||||||
|
targetForks
|
||||||
|
|
||||||
func nearSyncCommitteePeriod*(epoch: Epoch): Option[uint64] =
|
func nearSyncCommitteePeriod*(epoch: Epoch): Option[uint64] =
|
||||||
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.1/specs/altair/validator.md#sync-committee-subnet-stability
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.1/specs/altair/validator.md#sync-committee-subnet-stability
|
||||||
|
|
|
@ -334,6 +334,7 @@ proc forkchoice_updated(
|
||||||
head_block_hash
|
head_block_hash
|
||||||
finalized_block_hash
|
finalized_block_hash
|
||||||
|
|
||||||
|
discard $capellaImplementationMissing & ": ensure fcU usage updated for capella"
|
||||||
let
|
let
|
||||||
forkchoiceResponse =
|
forkchoiceResponse =
|
||||||
try:
|
try:
|
||||||
|
@ -355,15 +356,18 @@ proc forkchoice_updated(
|
||||||
else:
|
else:
|
||||||
none(bellatrix.PayloadID)
|
none(bellatrix.PayloadID)
|
||||||
|
|
||||||
proc get_execution_payload(
|
proc get_execution_payload[EP](
|
||||||
payload_id: Option[bellatrix.PayloadID], execution_engine: Eth1Monitor):
|
payload_id: Option[bellatrix.PayloadID], execution_engine: Eth1Monitor):
|
||||||
Future[bellatrix.ExecutionPayload] {.async.} =
|
Future[EP] {.async.} =
|
||||||
return if payload_id.isNone():
|
return if payload_id.isNone():
|
||||||
# Pre-merge, empty payload
|
# Pre-merge, empty payload
|
||||||
default(bellatrix.ExecutionPayload)
|
default(EP)
|
||||||
else:
|
else:
|
||||||
asConsensusExecutionPayload(
|
when EP is bellatrix.ExecutionPayload:
|
||||||
await execution_engine.getPayload(payload_id.get))
|
asConsensusExecutionPayload(
|
||||||
|
await execution_engine.getPayload(payload_id.get))
|
||||||
|
else:
|
||||||
|
raiseAssert $capellaImplementationMissing & ": implement getPayload V2"
|
||||||
|
|
||||||
proc getFeeRecipient(node: BeaconNode,
|
proc getFeeRecipient(node: BeaconNode,
|
||||||
pubkey: ValidatorPubKey,
|
pubkey: ValidatorPubKey,
|
||||||
|
@ -372,6 +376,7 @@ proc getFeeRecipient(node: BeaconNode,
|
||||||
node.consensusManager[].getFeeRecipient(pubkey, Opt.some(validatorIdx), epoch)
|
node.consensusManager[].getFeeRecipient(pubkey, Opt.some(validatorIdx), epoch)
|
||||||
|
|
||||||
from web3/engine_api_types import PayloadExecutionStatus
|
from web3/engine_api_types import PayloadExecutionStatus
|
||||||
|
from ../spec/datatypes/capella import BeaconBlock, ExecutionPayload
|
||||||
|
|
||||||
proc getExecutionPayload[T](
|
proc getExecutionPayload[T](
|
||||||
node: BeaconNode, proposalState: ref ForkedHashedBeaconState,
|
node: BeaconNode, proposalState: ref ForkedHashedBeaconState,
|
||||||
|
@ -387,11 +392,17 @@ proc getExecutionPayload[T](
|
||||||
node.getFeeRecipient(pubkey.get().toPubKey(), validator_index, epoch)
|
node.getFeeRecipient(pubkey.get().toPubKey(), validator_index, epoch)
|
||||||
|
|
||||||
template empty_execution_payload(): auto =
|
template empty_execution_payload(): auto =
|
||||||
|
# Callers should already ensure these match, but type system doesn't
|
||||||
|
# transmit this information through the Forked types, so this has to
|
||||||
|
# be re-proven here.
|
||||||
withState(proposalState[]):
|
withState(proposalState[]):
|
||||||
when stateFork >= BeaconStateFork.Capella:
|
when (stateFork == BeaconStateFork.Capella and
|
||||||
raiseAssert $capellaImplementationMissing
|
T is capella.ExecutionPayload) or
|
||||||
elif stateFork >= BeaconStateFork.Bellatrix:
|
(stateFork == BeaconStateFork.Bellatrix and
|
||||||
|
T is bellatrix.ExecutionPayload):
|
||||||
build_empty_execution_payload(forkyState.data, feeRecipient)
|
build_empty_execution_payload(forkyState.data, feeRecipient)
|
||||||
|
elif stateFork >= BeaconStateFork.Bellatrix:
|
||||||
|
raiseAssert "getExecutionPayload: mismatched proposalState and ExecutionPayload fork"
|
||||||
else:
|
else:
|
||||||
default(T)
|
default(T)
|
||||||
|
|
||||||
|
@ -444,7 +455,7 @@ proc getExecutionPayload[T](
|
||||||
feeRecipient, node.consensusManager.eth1Monitor))
|
feeRecipient, node.consensusManager.eth1Monitor))
|
||||||
payload = try:
|
payload = try:
|
||||||
awaitWithTimeout(
|
awaitWithTimeout(
|
||||||
get_execution_payload(payload_id, node.consensusManager.eth1Monitor),
|
get_execution_payload[T](payload_id, node.consensusManager.eth1Monitor),
|
||||||
GETPAYLOAD_TIMEOUT):
|
GETPAYLOAD_TIMEOUT):
|
||||||
beacon_block_payload_errors.inc()
|
beacon_block_payload_errors.inc()
|
||||||
warn "Getting execution payload from Engine API timed out", payload_id
|
warn "Getting execution payload from Engine API timed out", payload_id
|
||||||
|
@ -828,9 +839,6 @@ proc makeBlindedBeaconBlockForHeadAndSlot*(
|
||||||
return ok constructPlainBlindedBlock[BlindedBeaconBlock](
|
return ok constructPlainBlindedBlock[BlindedBeaconBlock](
|
||||||
forkedBlck, executionPayloadHeader)
|
forkedBlck, executionPayloadHeader)
|
||||||
|
|
||||||
# TODO once forks re-exports these, use that instead
|
|
||||||
from ../spec/datatypes/capella import BeaconBlock
|
|
||||||
|
|
||||||
proc proposeBlock(node: BeaconNode,
|
proc proposeBlock(node: BeaconNode,
|
||||||
validator: AttachedValidator,
|
validator: AttachedValidator,
|
||||||
validator_index: ValidatorIndex,
|
validator_index: ValidatorIndex,
|
||||||
|
@ -876,9 +884,13 @@ proc proposeBlock(node: BeaconNode,
|
||||||
beacon_block_builder_missed_without_fallback.inc()
|
beacon_block_builder_missed_without_fallback.inc()
|
||||||
return newBlockMEV.get
|
return newBlockMEV.get
|
||||||
|
|
||||||
discard $capellaImplementationMissing & ": use capella.ExecutionPayload here as appropriate"
|
let newBlock =
|
||||||
let newBlock = await makeBeaconBlockForHeadAndSlot[bellatrix.ExecutionPayload](
|
if slot.epoch >= node.dag.cfg.CAPELLA_FORK_EPOCH:
|
||||||
node, randao, validator_index, node.graffitiBytes, head, slot)
|
await makeBeaconBlockForHeadAndSlot[capella.ExecutionPayload](
|
||||||
|
node, randao, validator_index, node.graffitiBytes, head, slot)
|
||||||
|
else:
|
||||||
|
await makeBeaconBlockForHeadAndSlot[bellatrix.ExecutionPayload](
|
||||||
|
node, randao, validator_index, node.graffitiBytes, head, slot)
|
||||||
|
|
||||||
if newBlock.isErr():
|
if newBlock.isErr():
|
||||||
return head # already logged elsewhere!
|
return head # already logged elsewhere!
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
# beacon_chain
|
||||||
|
# Copyright (c) 2021-2022 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.
|
||||||
|
|
||||||
{.used.}
|
{.used.}
|
||||||
|
|
||||||
import
|
import
|
||||||
|
@ -5,60 +12,109 @@ import
|
||||||
./testutil,
|
./testutil,
|
||||||
../beacon_chain/spec/[forks, network]
|
../beacon_chain/spec/[forks, network]
|
||||||
|
|
||||||
template getTargetGossipState(a, b, c: int, isBehind: bool): auto =
|
template getTargetGossipState(a, b, c, d: int, isBehind: bool): auto =
|
||||||
getTargetGossipState(a.Epoch, b.Epoch, c.Epoch, isBehind)
|
getTargetGossipState(a.Epoch, b.Epoch, c.Epoch, d.Epoch, isBehind)
|
||||||
|
|
||||||
suite "Gossip fork transition":
|
suite "Gossip fork transition":
|
||||||
test "Gossip fork transition":
|
test "Gossip fork transition":
|
||||||
check:
|
check:
|
||||||
getTargetGossipState(0, 0, 0, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(0, 1, 6, 7, false) == {BeaconStateFork.Phase0, BeaconStateFork.Altair}
|
||||||
getTargetGossipState(0, 0, 2, false) == {BeaconStateFork.Altair}
|
getTargetGossipState(7, 2, 5, 6, false) == {BeaconStateFork.Capella}
|
||||||
getTargetGossipState(0, 1, 2, false) == {BeaconStateFork.Phase0, BeaconStateFork.Altair}
|
getTargetGossipState(5, 0, 2, 5, false) == {BeaconStateFork.Capella}
|
||||||
getTargetGossipState(0, 2, 3, false) == {BeaconStateFork.Phase0}
|
getTargetGossipState(4, 2, 4, 5, false) == {BeaconStateFork.Bellatrix, BeaconStateFork.Capella}
|
||||||
getTargetGossipState(0, 2, 5, false) == {BeaconStateFork.Phase0}
|
getTargetGossipState(1, 2, 3, 7, false) == {BeaconStateFork.Phase0, BeaconStateFork.Altair}
|
||||||
getTargetGossipState(0, 3, 4, false) == {BeaconStateFork.Phase0}
|
getTargetGossipState(9, 2, 4, 5, false) == {BeaconStateFork.Capella}
|
||||||
getTargetGossipState(0, 3, 5, false) == {BeaconStateFork.Phase0}
|
getTargetGossipState(5, 2, 3, 7, false) == {BeaconStateFork.Bellatrix}
|
||||||
getTargetGossipState(0, 4, 4, false) == {BeaconStateFork.Phase0}
|
getTargetGossipState(5, 0, 1, 7, false) == {BeaconStateFork.Bellatrix}
|
||||||
getTargetGossipState(0, 4, 5, false) == {BeaconStateFork.Phase0}
|
getTargetGossipState(7, 2, 6, 7, false) == {BeaconStateFork.Capella}
|
||||||
getTargetGossipState(1, 0, 1, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(5, 0, 5, 7, true) == {}
|
||||||
getTargetGossipState(1, 0, 5, false) == {BeaconStateFork.Altair}
|
getTargetGossipState(8, 1, 5, 6, false) == {BeaconStateFork.Capella}
|
||||||
getTargetGossipState(1, 1, 4, false) == {BeaconStateFork.Altair}
|
getTargetGossipState(8, 3, 4, 7, false) == {BeaconStateFork.Capella}
|
||||||
getTargetGossipState(2, 0, 2, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(4, 1, 2, 7, false) == {BeaconStateFork.Bellatrix}
|
||||||
getTargetGossipState(2, 2, 3, false) == {BeaconStateFork.Altair, BeaconStateFork.Bellatrix}
|
getTargetGossipState(0, 1, 4, 7, false) == {BeaconStateFork.Phase0, BeaconStateFork.Altair}
|
||||||
getTargetGossipState(2, 2, 4, false) == {BeaconStateFork.Altair}
|
getTargetGossipState(9, 1, 4, 7, true) == {}
|
||||||
getTargetGossipState(2, 3, 4, false) == {BeaconStateFork.Phase0, BeaconStateFork.Altair}
|
getTargetGossipState(9, 2, 4, 6, false) == {BeaconStateFork.Capella}
|
||||||
getTargetGossipState(2, 3, 5, true) == {}
|
getTargetGossipState(4, 0, 1, 5, true) == {}
|
||||||
getTargetGossipState(2, 5, 5, false) == {BeaconStateFork.Phase0}
|
getTargetGossipState(1, 1, 5, 6, true) == {}
|
||||||
getTargetGossipState(3, 0, 2, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(2, 0, 0, 7, true) == {}
|
||||||
getTargetGossipState(3, 0, 3, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(7, 1, 5, 7, false) == {BeaconStateFork.Capella}
|
||||||
getTargetGossipState(3, 0, 5, false) == {BeaconStateFork.Altair}
|
getTargetGossipState(5, 1, 3, 6, true) == {}
|
||||||
getTargetGossipState(3, 1, 2, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(8, 4, 5, 6, false) == {BeaconStateFork.Capella}
|
||||||
getTargetGossipState(3, 1, 1, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(3, 0, 4, 6, false) == {BeaconStateFork.Altair, BeaconStateFork.Bellatrix}
|
||||||
getTargetGossipState(3, 1, 3, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(1, 2, 6, 7, false) == {BeaconStateFork.Phase0, BeaconStateFork.Altair}
|
||||||
getTargetGossipState(3, 1, 5, true) == {}
|
getTargetGossipState(1, 0, 1, 6, false) == {BeaconStateFork.Bellatrix}
|
||||||
getTargetGossipState(3, 1, 4, false) == {BeaconStateFork.Altair, BeaconStateFork.Bellatrix}
|
getTargetGossipState(6, 0, 3, 5, false) == {BeaconStateFork.Capella}
|
||||||
getTargetGossipState(3, 2, 3, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(0, 4, 5, 6, true) == {}
|
||||||
getTargetGossipState(3, 3, 4, false) == {BeaconStateFork.Altair, BeaconStateFork.Bellatrix}
|
getTargetGossipState(3, 0, 3, 5, false) == {BeaconStateFork.Bellatrix}
|
||||||
getTargetGossipState(3, 3, 4, true) == {}
|
getTargetGossipState(5, 1, 3, 5, true) == {}
|
||||||
getTargetGossipState(3, 4, 4, false) == {BeaconStateFork.Phase0, BeaconStateFork.Bellatrix}
|
getTargetGossipState(4, 3, 4, 5, false) == {BeaconStateFork.Bellatrix, BeaconStateFork.Capella}
|
||||||
getTargetGossipState(4, 0, 0, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(6, 1, 2, 5, true) == {}
|
||||||
getTargetGossipState(4, 0, 1, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(8, 3, 5, 6, false) == {BeaconStateFork.Capella}
|
||||||
getTargetGossipState(4, 1, 1, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(1, 4, 6, 7, false) == {BeaconStateFork.Phase0}
|
||||||
getTargetGossipState(4, 1, 3, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(2, 5, 6, 7, false) == {BeaconStateFork.Phase0}
|
||||||
getTargetGossipState(4, 2, 4, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(5, 3, 4, 5, false) == {BeaconStateFork.Capella}
|
||||||
getTargetGossipState(4, 3, 4, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(5, 1, 4, 5, false) == {BeaconStateFork.Capella}
|
||||||
getTargetGossipState(4, 4, 4, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(6, 2, 4, 7, true) == {}
|
||||||
getTargetGossipState(4, 5, 5, false) == {BeaconStateFork.Phase0, BeaconStateFork.Bellatrix}
|
getTargetGossipState(7, 3, 5, 7, false) == {BeaconStateFork.Capella}
|
||||||
getTargetGossipState(5, 0, 0, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(6, 0, 1, 7, false) == {BeaconStateFork.Bellatrix, BeaconStateFork.Capella}
|
||||||
getTargetGossipState(5, 0, 2, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(0, 1, 4, 5, false) == {BeaconStateFork.Phase0, BeaconStateFork.Altair}
|
||||||
getTargetGossipState(5, 0, 4, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(7, 0, 1, 4, true) == {}
|
||||||
getTargetGossipState(5, 0, 5, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(6, 0, 4, 5, true) == {}
|
||||||
getTargetGossipState(5, 0, 5, true) == {}
|
getTargetGossipState(3, 0, 0, 1, true) == {}
|
||||||
getTargetGossipState(5, 1, 5, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(2, 1, 3, 5, false) == {BeaconStateFork.Altair, BeaconStateFork.Bellatrix}
|
||||||
getTargetGossipState(5, 2, 2, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(8, 0, 2, 6, false) == {BeaconStateFork.Capella}
|
||||||
getTargetGossipState(5, 2, 4, true) == {}
|
getTargetGossipState(7, 0, 1, 4, false) == {BeaconStateFork.Capella}
|
||||||
getTargetGossipState(5, 3, 4, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(8, 1, 2, 7, true) == {}
|
||||||
getTargetGossipState(5, 3, 5, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(3, 0, 2, 7, false) == {BeaconStateFork.Bellatrix}
|
||||||
getTargetGossipState(5, 5, 5, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(9, 1, 2, 5, false) == {BeaconStateFork.Capella}
|
||||||
getTargetGossipState(2, 0, 3, false) == {BeaconStateFork.Altair, BeaconStateFork.Bellatrix}
|
getTargetGossipState(6, 1, 4, 5, false) == {BeaconStateFork.Capella}
|
||||||
getTargetGossipState(4, 1, 2, false) == {BeaconStateFork.Bellatrix}
|
getTargetGossipState(0, 3, 4, 5, true) == {}
|
||||||
|
getTargetGossipState(9, 1, 3, 4, false) == {BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(1, 1, 4, 7, false) == {BeaconStateFork.Altair}
|
||||||
|
getTargetGossipState(5, 1, 4, 6, false) == {BeaconStateFork.Bellatrix, BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(7, 0, 5, 7, false) == {BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(9, 0, 0, 5, false) == {BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(5, 0, 0, 0, false) == {BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(9, 2, 3, 4, false) == {BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(3, 0, 0, 5, false) == {BeaconStateFork.Bellatrix}
|
||||||
|
getTargetGossipState(0, 0, 1, 6, false) == {BeaconStateFork.Altair, BeaconStateFork.Bellatrix}
|
||||||
|
getTargetGossipState(4, 1, 4, 6, true) == {}
|
||||||
|
getTargetGossipState(4, 1, 2, 3, false) == {BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(6, 1, 3, 4, false) == {BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(4, 0, 0, 5, false) == {BeaconStateFork.Bellatrix, BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(8, 0, 3, 7, false) == {BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(2, 2, 3, 4, false) == {BeaconStateFork.Altair, BeaconStateFork.Bellatrix}
|
||||||
|
getTargetGossipState(6, 2, 5, 6, false) == {BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(3, 0, 6, 7, true) == {}
|
||||||
|
getTargetGossipState(1, 1, 2, 6, false) == {BeaconStateFork.Altair, BeaconStateFork.Bellatrix}
|
||||||
|
getTargetGossipState(2, 2, 4, 5, true) == {}
|
||||||
|
getTargetGossipState(9, 0, 3, 7, true) == {}
|
||||||
|
getTargetGossipState(4, 1, 3, 7, true) == {}
|
||||||
|
getTargetGossipState(7, 0, 0, 3, false) == {BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(0, 2, 5, 6, false) == {BeaconStateFork.Phase0}
|
||||||
|
getTargetGossipState(2, 0, 1, 7, false) == {BeaconStateFork.Bellatrix}
|
||||||
|
getTargetGossipState(9, 1, 6, 7, false) == {BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(6, 3, 5, 6, false) == {BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(2, 0, 0, 3, false) == {BeaconStateFork.Bellatrix, BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(3, 1, 3, 4, true) == {}
|
||||||
|
getTargetGossipState(7, 0, 1, 5, false) == {BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(2, 0, 3, 6, false) == {BeaconStateFork.Altair, BeaconStateFork.Bellatrix}
|
||||||
|
getTargetGossipState(2, 0, 2, 5, false) == {BeaconStateFork.Bellatrix}
|
||||||
|
getTargetGossipState(1, 2, 4, 5, false) == {BeaconStateFork.Phase0, BeaconStateFork.Altair}
|
||||||
|
getTargetGossipState(8, 0, 2, 5, false) == {BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(6, 1, 5, 6, false) == {BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(6, 4, 5, 7, false) == {BeaconStateFork.Bellatrix, BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(3, 0, 5, 6, true) == {}
|
||||||
|
getTargetGossipState(4, 0, 2, 7, false) == {BeaconStateFork.Bellatrix}
|
||||||
|
getTargetGossipState(4, 4, 5, 6, true) == {}
|
||||||
|
getTargetGossipState(3, 0, 4, 5, true) == {}
|
||||||
|
getTargetGossipState(6, 0, 2, 6, false) == {BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(2, 1, 2, 3, false) == {BeaconStateFork.Bellatrix, BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(1, 0, 5, 6, true) == {}
|
||||||
|
getTargetGossipState(5, 2, 5, 6, false) == {BeaconStateFork.Bellatrix, BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(8, 0, 1, 6, false) == {BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(4, 2, 5, 6, false) == {BeaconStateFork.Altair, BeaconStateFork.Bellatrix}
|
||||||
|
getTargetGossipState(1, 1, 2, 5, false) == {BeaconStateFork.Altair, BeaconStateFork.Bellatrix}
|
||||||
|
getTargetGossipState(9, 1, 4, 6, false) == {BeaconStateFork.Capella}
|
||||||
|
getTargetGossipState(1, 0, 0, 5, false) == {BeaconStateFork.Bellatrix}
|
||||||
|
getTargetGossipState(0, 0, 5, 7, false) == {BeaconStateFork.Altair}
|
||||||
|
|
|
@ -208,7 +208,8 @@ suite "Gossip validation - Extra": # Not based on preset config
|
||||||
const nilCallback = OnBellatrixBlockAdded(nil)
|
const nilCallback = OnBellatrixBlockAdded(nil)
|
||||||
dag.addHeadBlock(verifier, blck.bellatrixData, nilCallback)
|
dag.addHeadBlock(verifier, blck.bellatrixData, nilCallback)
|
||||||
of BeaconBlockFork.Capella:
|
of BeaconBlockFork.Capella:
|
||||||
raiseAssert $capellaImplementationMissing
|
const nilCallback = OnCapellaBlockAdded(nil)
|
||||||
|
dag.addHeadBlock(verifier, blck.capellaData, nilCallback)
|
||||||
check: added.isOk()
|
check: added.isOk()
|
||||||
dag.updateHead(added[], quarantine[])
|
dag.updateHead(added[], quarantine[])
|
||||||
dag
|
dag
|
||||||
|
|
|
@ -68,7 +68,8 @@ suite "Light client" & preset():
|
||||||
const nilCallback = OnBellatrixBlockAdded(nil)
|
const nilCallback = OnBellatrixBlockAdded(nil)
|
||||||
dag.addHeadBlock(verifier, blck.bellatrixData, nilCallback)
|
dag.addHeadBlock(verifier, blck.bellatrixData, nilCallback)
|
||||||
of BeaconBlockFork.Capella:
|
of BeaconBlockFork.Capella:
|
||||||
raiseAssert $capellaImplementationMissing
|
const nilCallback = OnCapellaBlockAdded(nil)
|
||||||
|
dag.addHeadBlock(verifier, blck.capellaData, nilCallback)
|
||||||
check: added.isOk()
|
check: added.isOk()
|
||||||
dag.updateHead(added[], quarantine)
|
dag.updateHead(added[], quarantine)
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,8 @@ suite "Light client processor" & preset():
|
||||||
const nilCallback = OnBellatrixBlockAdded(nil)
|
const nilCallback = OnBellatrixBlockAdded(nil)
|
||||||
dag.addHeadBlock(verifier, blck.bellatrixData, nilCallback)
|
dag.addHeadBlock(verifier, blck.bellatrixData, nilCallback)
|
||||||
of BeaconBlockFork.Capella:
|
of BeaconBlockFork.Capella:
|
||||||
raiseAssert $capellaImplementationMissing
|
const nilCallback = OnCapellaBlockAdded(nil)
|
||||||
|
dag.addHeadBlock(verifier, blck.capellaData, nilCallback)
|
||||||
doAssert added.isOk()
|
doAssert added.isOk()
|
||||||
dag.updateHead(added[], quarantine[])
|
dag.updateHead(added[], quarantine[])
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue