small spec cleanups (#1501)
* clean up logging a bit * return error on indexed attestation check
This commit is contained in:
parent
c765c5ae2d
commit
a1bd44f4b0
|
@ -82,18 +82,18 @@ proc isValidAttestationSlot(
|
||||||
attestationBlck = shortLog(attestationBlck)
|
attestationBlck = shortLog(attestationBlck)
|
||||||
|
|
||||||
if not (attestationBlck.slot > pool.chainDag.finalizedHead.slot):
|
if not (attestationBlck.slot > pool.chainDag.finalizedHead.slot):
|
||||||
debug "voting for already-finalized block"
|
debug "Voting for already-finalized block"
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# we'll also cap it at 4 epochs which is somewhat arbitrary, but puts an
|
# we'll also cap it at 4 epochs which is somewhat arbitrary, but puts an
|
||||||
# upper bound on the processing done to validate the attestation
|
# upper bound on the processing done to validate the attestation
|
||||||
# TODO revisit with less arbitrary approach
|
# TODO revisit with less arbitrary approach
|
||||||
if not (attestationSlot >= attestationBlck.slot):
|
if not (attestationSlot >= attestationBlck.slot):
|
||||||
debug "voting for block that didn't exist at the time"
|
debug "Voting for block that didn't exist at the time"
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if not ((attestationSlot - attestationBlck.slot) <= uint64(4 * SLOTS_PER_EPOCH)):
|
if not ((attestationSlot - attestationBlck.slot) <= uint64(4 * SLOTS_PER_EPOCH)):
|
||||||
debug "voting for very old block"
|
debug "Voting for very old block"
|
||||||
return false
|
return false
|
||||||
|
|
||||||
true
|
true
|
||||||
|
@ -121,7 +121,7 @@ proc isValidAttestation*(
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if not checkPropagationSlotRange(attestation.data, current_slot):
|
if not checkPropagationSlotRange(attestation.data, current_slot):
|
||||||
debug "attestation.data.slot not within ATTESTATION_PROPAGATION_SLOT_RANGE",
|
debug "Attestation slot not in propagation range",
|
||||||
current_slot
|
current_slot
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
@ -137,10 +137,10 @@ proc isValidAttestation*(
|
||||||
continue
|
continue
|
||||||
onesCount += 1
|
onesCount += 1
|
||||||
if onesCount > 1:
|
if onesCount > 1:
|
||||||
debug "attestation has too many aggregation bits"
|
debug "Attestation has too many aggregation bits"
|
||||||
return false
|
return false
|
||||||
if onesCount != 1:
|
if onesCount != 1:
|
||||||
debug "attestation has too few aggregation bits"
|
debug "Attestation has too few aggregation bits"
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# The attestation is the first valid attestation received for the
|
# The attestation is the first valid attestation received for the
|
||||||
|
@ -153,7 +153,7 @@ proc isValidAttestation*(
|
||||||
# Attestations might be aggregated eagerly or lazily; allow for both.
|
# Attestations might be aggregated eagerly or lazily; allow for both.
|
||||||
for validation in attestationEntry.validations:
|
for validation in attestationEntry.validations:
|
||||||
if attestation.aggregation_bits.isSubsetOf(validation.aggregation_bits):
|
if attestation.aggregation_bits.isSubsetOf(validation.aggregation_bits):
|
||||||
debug "attestation already exists at slot",
|
debug "Attestation already exists at slot",
|
||||||
attestation_pool_validation = validation.aggregation_bits
|
attestation_pool_validation = validation.aggregation_bits
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ proc isValidAttestation*(
|
||||||
# of the block in the pool.
|
# of the block in the pool.
|
||||||
let attestationBlck = pool.chainDag.getRef(attestation.data.beacon_block_root)
|
let attestationBlck = pool.chainDag.getRef(attestation.data.beacon_block_root)
|
||||||
if attestationBlck.isNil:
|
if attestationBlck.isNil:
|
||||||
debug "Block not found"
|
debug "Attestation block unknown"
|
||||||
pool.addUnresolved(attestation)
|
pool.addUnresolved(attestation)
|
||||||
pool.quarantine.addMissing(attestation.data.beacon_block_root)
|
pool.quarantine.addMissing(attestation.data.beacon_block_root)
|
||||||
return false
|
return false
|
||||||
|
@ -174,7 +174,7 @@ proc isValidAttestation*(
|
||||||
|
|
||||||
let tgtBlck = pool.chainDag.getRef(attestation.data.target.root)
|
let tgtBlck = pool.chainDag.getRef(attestation.data.target.root)
|
||||||
if tgtBlck.isNil:
|
if tgtBlck.isNil:
|
||||||
debug "Target block not found"
|
debug "Attestation target block unknown"
|
||||||
pool.addUnresolved(attestation)
|
pool.addUnresolved(attestation)
|
||||||
pool.quarantine.addMissing(attestation.data.target.root)
|
pool.quarantine.addMissing(attestation.data.target.root)
|
||||||
return false
|
return false
|
||||||
|
@ -205,7 +205,7 @@ proc isValidAttestation*(
|
||||||
attestation.data.slot, attestation.data.index.CommitteeIndex)
|
attestation.data.slot, attestation.data.index.CommitteeIndex)
|
||||||
|
|
||||||
if requiredSubnetIndex != topicCommitteeIndex:
|
if requiredSubnetIndex != topicCommitteeIndex:
|
||||||
debug "attestation's committee index not for the correct subnet",
|
debug "Attestation's committee index not for the correct subnet",
|
||||||
topicCommitteeIndex = topicCommitteeIndex,
|
topicCommitteeIndex = topicCommitteeIndex,
|
||||||
attestation_data_index = attestation.data.index,
|
attestation_data_index = attestation.data.index,
|
||||||
requiredSubnetIndex = requiredSubnetIndex
|
requiredSubnetIndex = requiredSubnetIndex
|
||||||
|
@ -217,10 +217,10 @@ proc isValidAttestation*(
|
||||||
pool.chainDag.headState.data.data.genesis_validators_root
|
pool.chainDag.headState.data.data.genesis_validators_root
|
||||||
|
|
||||||
# The signature of attestation is valid.
|
# The signature of attestation is valid.
|
||||||
if not is_valid_indexed_attestation(
|
if (let v = is_valid_indexed_attestation(
|
||||||
fork, genesis_validators_root,
|
fork, genesis_validators_root,
|
||||||
epochRef, get_indexed_attestation(epochRef, attestation), {}):
|
epochRef, get_indexed_attestation(epochRef, attestation), {}); v.isErr):
|
||||||
debug "signature verification failed"
|
debug "Attestation verification failed", err = v.error()
|
||||||
return false
|
return false
|
||||||
|
|
||||||
true
|
true
|
||||||
|
@ -251,7 +251,7 @@ proc isValidAggregatedAttestation*(
|
||||||
# MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) -- i.e. aggregate.data.slot +
|
# MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) -- i.e. aggregate.data.slot +
|
||||||
# ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot
|
# ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot
|
||||||
if not checkPropagationSlotRange(aggregate.data, current_slot):
|
if not checkPropagationSlotRange(aggregate.data, current_slot):
|
||||||
debug "aggregation.data.slot not within ATTESTATION_PROPAGATION_SLOT_RANGE"
|
debug "Aggregate slot not in propoagation range"
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# [IGNORE] The valid aggregate attestation defined by
|
# [IGNORE] The valid aggregate attestation defined by
|
||||||
|
@ -273,7 +273,7 @@ proc isValidAggregatedAttestation*(
|
||||||
# passes validation.
|
# passes validation.
|
||||||
let attestationBlck = pool.chainDag.getRef(aggregate.data.beacon_block_root)
|
let attestationBlck = pool.chainDag.getRef(aggregate.data.beacon_block_root)
|
||||||
if attestationBlck.isNil:
|
if attestationBlck.isNil:
|
||||||
debug "Block not found"
|
debug "Aggregate block unknown"
|
||||||
pool.quarantine.addMissing(aggregate.data.beacon_block_root)
|
pool.quarantine.addMissing(aggregate.data.beacon_block_root)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
@ -291,7 +291,7 @@ proc isValidAggregatedAttestation*(
|
||||||
# But (2) would reflect an invalid aggregation in other ways, so reject it
|
# But (2) would reflect an invalid aggregation in other ways, so reject it
|
||||||
# either way.
|
# either way.
|
||||||
if isZeros(aggregate.aggregation_bits):
|
if isZeros(aggregate.aggregation_bits):
|
||||||
debug "Attestation has no or invalid aggregation bits"
|
debug "Aggregate has no or invalid aggregation bits"
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if not isValidAttestationSlot(pool, aggregate.data.slot, attestationBlck):
|
if not isValidAttestationSlot(pool, aggregate.data.slot, attestationBlck):
|
||||||
|
@ -303,7 +303,7 @@ proc isValidAggregatedAttestation*(
|
||||||
# aggregate.data.index, aggregate_and_proof.selection_proof) returns True.
|
# aggregate.data.index, aggregate_and_proof.selection_proof) returns True.
|
||||||
let tgtBlck = pool.chainDag.getRef(aggregate.data.target.root)
|
let tgtBlck = pool.chainDag.getRef(aggregate.data.target.root)
|
||||||
if tgtBlck.isNil:
|
if tgtBlck.isNil:
|
||||||
debug "Target block not found"
|
debug "Aggregate target block unknown"
|
||||||
pool.quarantine.addMissing(aggregate.data.target.root)
|
pool.quarantine.addMissing(aggregate.data.target.root)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -348,14 +348,14 @@ proc isValidAggregatedAttestation*(
|
||||||
fork, genesis_validators_root, aggregate_and_proof,
|
fork, genesis_validators_root, aggregate_and_proof,
|
||||||
epochRef.validator_keys[aggregate_and_proof.aggregator_index],
|
epochRef.validator_keys[aggregate_and_proof.aggregator_index],
|
||||||
signed_aggregate_and_proof.signature):
|
signed_aggregate_and_proof.signature):
|
||||||
debug "Signed_aggregate_and_proof signature verification failed"
|
debug "signed_aggregate_and_proof signature verification failed"
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# [REJECT] The signature of aggregate is valid.
|
# [REJECT] The signature of aggregate is valid.
|
||||||
if not is_valid_indexed_attestation(
|
if (let v = is_valid_indexed_attestation(
|
||||||
fork, genesis_validators_root,
|
fork, genesis_validators_root,
|
||||||
epochRef, get_indexed_attestation(epochRef, aggregate), {}):
|
epochRef, get_indexed_attestation(epochRef, aggregate), {}); v.isErr):
|
||||||
debug "Aggregate signature verification failed"
|
debug "Aggregate verification failed", err = v.error()
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# The following rule follows implicitly from that we clear out any
|
# The following rule follows implicitly from that we clear out any
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
std/[algorithm, sequtils, sets],
|
std/[algorithm, sequtils, sets],
|
||||||
chronicles,
|
|
||||||
../spec/[
|
../spec/[
|
||||||
beaconstate, crypto, datatypes, digest, helpers, presets, signatures,
|
beaconstate, crypto, datatypes, digest, helpers, presets, signatures,
|
||||||
validator],
|
validator],
|
||||||
|
@ -65,7 +64,6 @@ func get_beacon_committee_len*(
|
||||||
epochRef: EpochRef, slot: Slot, index: CommitteeIndex): uint64 =
|
epochRef: EpochRef, slot: Slot, index: CommitteeIndex): uint64 =
|
||||||
# Return the number of members in the beacon committee at ``slot`` for ``index``.
|
# Return the number of members in the beacon committee at ``slot`` for ``index``.
|
||||||
let
|
let
|
||||||
epoch = compute_epoch_at_slot(slot)
|
|
||||||
committees_per_slot = get_committee_count_per_slot(epochRef)
|
committees_per_slot = get_committee_count_per_slot(epochRef)
|
||||||
|
|
||||||
compute_committee_len(
|
compute_committee_len(
|
||||||
|
@ -87,41 +85,41 @@ func is_aggregator*(epochRef: EpochRef, slot: Slot, index: CommitteeIndex,
|
||||||
proc is_valid_indexed_attestation*(
|
proc is_valid_indexed_attestation*(
|
||||||
fork: Fork, genesis_validators_root: Eth2Digest,
|
fork: Fork, genesis_validators_root: Eth2Digest,
|
||||||
epochRef: EpochRef, indexed_attestation: SomeIndexedAttestation,
|
epochRef: EpochRef, indexed_attestation: SomeIndexedAttestation,
|
||||||
flags: UpdateFlags): bool =
|
flags: UpdateFlags): Result[void, cstring] =
|
||||||
# Check if ``indexed_attestation`` is not empty, has sorted and unique
|
# Check if ``indexed_attestation`` is not empty, has sorted and unique
|
||||||
# indices and has a valid aggregate signature.
|
# indices and has a valid aggregate signature.
|
||||||
|
|
||||||
template is_sorted_and_unique(s: untyped): bool =
|
template is_sorted_and_unique(s: untyped): bool =
|
||||||
|
var res = true
|
||||||
for i in 1 ..< s.len:
|
for i in 1 ..< s.len:
|
||||||
if s[i - 1].uint64 >= s[i].uint64:
|
if s[i - 1].uint64 >= s[i].uint64:
|
||||||
return false
|
res = false
|
||||||
|
break
|
||||||
|
res
|
||||||
|
|
||||||
true
|
if len(indexed_attestation.attesting_indices) == 0:
|
||||||
|
return err("indexed_attestation: no attesting indices")
|
||||||
|
|
||||||
# Not from spec, but this function gets used in front-line roles, not just
|
# Not from spec, but this function gets used in front-line roles, not just
|
||||||
# behind firewall.
|
# behind firewall.
|
||||||
let num_validators = epochRef.validator_keys.lenu64
|
let num_validators = epochRef.validator_keys.lenu64
|
||||||
if anyIt(indexed_attestation.attesting_indices, it >= num_validators):
|
if anyIt(indexed_attestation.attesting_indices, it >= num_validators):
|
||||||
trace "indexed attestation: not all indices valid validators"
|
return err("indexed attestation: not all indices valid validators")
|
||||||
return false
|
|
||||||
|
|
||||||
# Verify indices are sorted and unique
|
if not is_sorted_and_unique(indexed_attestation.attesting_indices):
|
||||||
let indices = indexed_attestation.attesting_indices.asSeq
|
return err("indexed attestation: indices not sorted and unique")
|
||||||
if len(indices) == 0 or not is_sorted_and_unique(indices):
|
|
||||||
trace "indexed attestation: indices not sorted and unique"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Verify aggregate signature
|
# Verify aggregate signature
|
||||||
if skipBLSValidation notin flags:
|
if skipBLSValidation notin flags:
|
||||||
# TODO: fuse loops with blsFastAggregateVerify
|
# TODO: fuse loops with blsFastAggregateVerify
|
||||||
let pubkeys = mapIt(indices, epochRef.validator_keys[it])
|
let pubkeys = mapIt(
|
||||||
|
indexed_attestation.attesting_indices, epochRef.validator_keys[it])
|
||||||
if not verify_attestation_signature(
|
if not verify_attestation_signature(
|
||||||
fork, genesis_validators_root, indexed_attestation.data,
|
fork, genesis_validators_root, indexed_attestation.data,
|
||||||
pubkeys, indexed_attestation.signature):
|
pubkeys, indexed_attestation.signature):
|
||||||
trace "indexed attestation: signature verification failure"
|
return err("indexed attestation: signature verification failure")
|
||||||
return false
|
|
||||||
|
|
||||||
true
|
ok()
|
||||||
|
|
||||||
func makeAttestationData*(
|
func makeAttestationData*(
|
||||||
epochRef: EpochRef, bs: BlockSlot,
|
epochRef: EpochRef, bs: BlockSlot,
|
||||||
|
|
|
@ -15,7 +15,9 @@ import
|
||||||
../../nbench/bench_lab
|
../../nbench/bench_lab
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#is_valid_merkle_branch
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#is_valid_merkle_branch
|
||||||
func is_valid_merkle_branch*(leaf: Eth2Digest, branch: openarray[Eth2Digest], depth: int, index: uint64, root: Eth2Digest): bool {.nbench.}=
|
func is_valid_merkle_branch*(leaf: Eth2Digest, branch: openarray[Eth2Digest],
|
||||||
|
depth: int, index: uint64,
|
||||||
|
root: Eth2Digest): bool {.nbench.}=
|
||||||
## Check if ``leaf`` at ``index`` verifies against the Merkle ``root`` and
|
## Check if ``leaf`` at ``index`` verifies against the Merkle ``root`` and
|
||||||
## ``branch``.
|
## ``branch``.
|
||||||
var
|
var
|
||||||
|
@ -416,41 +418,41 @@ proc process_registry_updates*(state: var BeaconState,
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#is_valid_indexed_attestation
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#is_valid_indexed_attestation
|
||||||
proc is_valid_indexed_attestation*(
|
proc is_valid_indexed_attestation*(
|
||||||
state: BeaconState, indexed_attestation: SomeIndexedAttestation,
|
state: BeaconState, indexed_attestation: SomeIndexedAttestation,
|
||||||
flags: UpdateFlags): bool =
|
flags: UpdateFlags): Result[void, cstring] =
|
||||||
# Check if ``indexed_attestation`` is not empty, has sorted and unique
|
# Check if ``indexed_attestation`` is not empty, has sorted and unique
|
||||||
# indices and has a valid aggregate signature.
|
# indices and has a valid aggregate signature.
|
||||||
|
|
||||||
template is_sorted_and_unique(s: untyped): bool =
|
template is_sorted_and_unique(s: untyped): bool =
|
||||||
|
var res = true
|
||||||
for i in 1 ..< s.len:
|
for i in 1 ..< s.len:
|
||||||
if s[i - 1].uint64 >= s[i].uint64:
|
if s[i - 1].uint64 >= s[i].uint64:
|
||||||
return false
|
res = false
|
||||||
|
break
|
||||||
|
res
|
||||||
|
|
||||||
true
|
if len(indexed_attestation.attesting_indices) == 0:
|
||||||
|
return err("indexed_attestation: no attesting indices")
|
||||||
|
|
||||||
# Not from spec, but this function gets used in front-line roles, not just
|
# Not from spec, but this function gets used in front-line roles, not just
|
||||||
# behind firewall.
|
# behind firewall.
|
||||||
let num_validators = state.validators.lenu64
|
let num_validators = state.validators.lenu64
|
||||||
if anyIt(indexed_attestation.attesting_indices, it >= num_validators):
|
if anyIt(indexed_attestation.attesting_indices, it >= num_validators):
|
||||||
trace "indexed attestation: not all indices valid validators"
|
return err("indexed attestation: not all indices valid validators")
|
||||||
return false
|
|
||||||
|
|
||||||
# Verify indices are sorted and unique
|
if not is_sorted_and_unique(indexed_attestation.attesting_indices):
|
||||||
let indices = indexed_attestation.attesting_indices.asSeq
|
return err("indexed attestation: indices not sorted and unique")
|
||||||
if len(indices) == 0 or not is_sorted_and_unique(indices):
|
|
||||||
trace "indexed attestation: indices not sorted and unique"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Verify aggregate signature
|
# Verify aggregate signature
|
||||||
if skipBLSValidation notin flags:
|
if skipBLSValidation notin flags:
|
||||||
# TODO: fuse loops with blsFastAggregateVerify
|
# TODO: fuse loops with blsFastAggregateVerify
|
||||||
let pubkeys = mapIt(indices, state.validators[it].pubkey)
|
let pubkeys = mapIt(
|
||||||
|
indexed_attestation.attesting_indices, state.validators[it].pubkey)
|
||||||
if not verify_attestation_signature(
|
if not verify_attestation_signature(
|
||||||
state.fork, state.genesis_validators_root, indexed_attestation.data,
|
state.fork, state.genesis_validators_root, indexed_attestation.data,
|
||||||
pubkeys, indexed_attestation.signature):
|
pubkeys, indexed_attestation.signature):
|
||||||
trace "indexed attestation: signature verification failure"
|
return err("indexed attestation: signature verification failure")
|
||||||
return false
|
|
||||||
|
|
||||||
true
|
ok()
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#get_attesting_indices
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#get_attesting_indices
|
||||||
func get_attesting_indices*(bits: CommitteeValidatorsBits,
|
func get_attesting_indices*(bits: CommitteeValidatorsBits,
|
||||||
|
@ -582,9 +584,8 @@ proc check_attestation*(
|
||||||
if not (data.source == state.previous_justified_checkpoint):
|
if not (data.source == state.previous_justified_checkpoint):
|
||||||
return err("FFG data not matching previous justified epoch")
|
return err("FFG data not matching previous justified epoch")
|
||||||
|
|
||||||
if not is_valid_indexed_attestation(
|
? is_valid_indexed_attestation(
|
||||||
state, get_indexed_attestation(state, attestation, cache), flags):
|
state, get_indexed_attestation(state, attestation, cache), flags)
|
||||||
return err("signature or bitfields incorrect")
|
|
||||||
|
|
||||||
ok()
|
ok()
|
||||||
|
|
||||||
|
|
|
@ -207,10 +207,10 @@ proc process_attester_slashing*(
|
||||||
attestation_1.data, attestation_2.data):
|
attestation_1.data, attestation_2.data):
|
||||||
return err("Attester slashing: surround or double vote check failed")
|
return err("Attester slashing: surround or double vote check failed")
|
||||||
|
|
||||||
if not is_valid_indexed_attestation(state, attestation_1, flags):
|
if not is_valid_indexed_attestation(state, attestation_1, flags).isOk():
|
||||||
return err("Attester slashing: invalid attestation 1")
|
return err("Attester slashing: invalid attestation 1")
|
||||||
|
|
||||||
if not is_valid_indexed_attestation(state, attestation_2, flags):
|
if not is_valid_indexed_attestation(state, attestation_2, flags).isOk():
|
||||||
return err("Attester slashing: invalid attestation 2")
|
return err("Attester slashing: invalid attestation 2")
|
||||||
|
|
||||||
var slashed_any = false
|
var slashed_any = false
|
||||||
|
|
|
@ -167,10 +167,7 @@ func compute_committee*(shuffled_indices: seq[ValidatorIndex],
|
||||||
|
|
||||||
# In spec, this calls get_shuffled_index() every time, but that's wasteful
|
# In spec, this calls get_shuffled_index() every time, but that's wasteful
|
||||||
# Here, get_beacon_committee() gets the shuffled version.
|
# Here, get_beacon_committee() gets the shuffled version.
|
||||||
try:
|
shuffled_indices[start.int .. (endIdx.int-1)]
|
||||||
shuffled_indices[start.int .. (endIdx.int-1)]
|
|
||||||
except KeyError:
|
|
||||||
raiseAssert("Cached entries are added before use")
|
|
||||||
|
|
||||||
func compute_committee_len*(active_validators: uint64,
|
func compute_committee_len*(active_validators: uint64,
|
||||||
index: uint64, count: uint64): uint64 =
|
index: uint64, count: uint64): uint64 =
|
||||||
|
|
Loading…
Reference in New Issue