0.6.3 updates (#279)
* update IndexedAttestation, verify_slashable_attestation/verify_indexed_attestation, and attester slashing processing to 0.6.3 * rm debug scaffolding * rename Attestation.aggregate_signature -> Attestation.signature; convert various references to AttestationData.slot to get_attestation_slot; implement convert_to_indexed; update checkAttestation and processAttestations to 0.6.3; remove spurious assertion in beacon node related to invalid attestations * replace 0.5 get_winning_root_and_participants with 0.6 get_winning_crosslink_and_attesting_indices; update process_crosslinks to 0.6.3 * mark both remaining 0.6.0 spec implementations as 0.6.3 * clear out all remaining spec version 0.6.1 refs * GENESIS_SLOT and GENESIS_EPOCH are 0 * rm 0.5 get_attestation_participants in favor of 0.6 get_attesting_indices * address mratsim's comment * time can be equal to genesis * fix invalid block assertions; those were essentially spurious * allow toBeaconTime to handle time before genesis (in accordance with now(...) which states time can exist before GENESIS)
This commit is contained in:
parent
10c7920b27
commit
d400650eeb
|
@ -27,7 +27,7 @@ proc combine*(tgt: var Attestation, src: Attestation, flags: UpdateFlags) =
|
||||||
tgt.aggregation_bitfield.combine(src.aggregation_bitfield)
|
tgt.aggregation_bitfield.combine(src.aggregation_bitfield)
|
||||||
|
|
||||||
if skipValidation notin flags:
|
if skipValidation notin flags:
|
||||||
tgt.aggregate_signature.combine(src.aggregate_signature)
|
tgt.signature.combine(src.signature)
|
||||||
|
|
||||||
proc validate(
|
proc validate(
|
||||||
state: BeaconState, attestation: Attestation, flags: UpdateFlags): bool =
|
state: BeaconState, attestation: Attestation, flags: UpdateFlags): bool =
|
||||||
|
@ -38,7 +38,7 @@ proc validate(
|
||||||
|
|
||||||
# TODO half of this stuff is from beaconstate.validateAttestation - merge?
|
# TODO half of this stuff is from beaconstate.validateAttestation - merge?
|
||||||
|
|
||||||
let attestationSlot = attestation.data.slot
|
let attestationSlot = get_attestation_slot(state, attestation)
|
||||||
|
|
||||||
if attestationSlot < state.finalized_epoch.get_epoch_start_slot():
|
if attestationSlot < state.finalized_epoch.get_epoch_start_slot():
|
||||||
debug "Old attestation",
|
debug "Old attestation",
|
||||||
|
@ -71,7 +71,7 @@ proc validate(
|
||||||
## the rest; turns into expensive NOP until then.
|
## the rest; turns into expensive NOP until then.
|
||||||
if skipValidation notin flags:
|
if skipValidation notin flags:
|
||||||
let
|
let
|
||||||
participants = get_attestation_participants(
|
participants = get_attesting_indices_seq(
|
||||||
state, attestation.data, attestation.aggregation_bitfield)
|
state, attestation.data, attestation.aggregation_bitfield)
|
||||||
|
|
||||||
## TODO when the custody_bitfield assertion-to-emptiness disappears do this
|
## TODO when the custody_bitfield assertion-to-emptiness disappears do this
|
||||||
|
@ -97,9 +97,9 @@ proc validate(
|
||||||
hash_tree_root(AttestationDataAndCustodyBit(
|
hash_tree_root(AttestationDataAndCustodyBit(
|
||||||
data: attestation.data, custody_bit: true)),
|
data: attestation.data, custody_bit: true)),
|
||||||
],
|
],
|
||||||
attestation.aggregate_signature,
|
attestation.signature,
|
||||||
get_domain(state, DOMAIN_ATTESTATION,
|
get_domain(state, DOMAIN_ATTESTATION,
|
||||||
slot_to_epoch(attestation.data.slot)),
|
slot_to_epoch(get_attestation_slot(state, attestation))),
|
||||||
):
|
):
|
||||||
notice "Invalid signature", participants
|
notice "Invalid signature", participants
|
||||||
return false
|
return false
|
||||||
|
@ -174,14 +174,14 @@ proc add*(pool: var AttestationPool,
|
||||||
# TODO inefficient data structures..
|
# TODO inefficient data structures..
|
||||||
|
|
||||||
let
|
let
|
||||||
attestationSlot = attestation.data.slot
|
attestationSlot = get_attestation_slot(state, attestation)
|
||||||
idx = pool.slotIndex(state, attestationSlot)
|
idx = pool.slotIndex(state, attestationSlot)
|
||||||
slotData = addr pool.slots[idx]
|
slotData = addr pool.slots[idx]
|
||||||
validation = Validation(
|
validation = Validation(
|
||||||
aggregation_bitfield: attestation.aggregation_bitfield,
|
aggregation_bitfield: attestation.aggregation_bitfield,
|
||||||
custody_bitfield: attestation.custody_bitfield,
|
custody_bitfield: attestation.custody_bitfield,
|
||||||
aggregate_signature: attestation.aggregate_signature)
|
aggregate_signature: attestation.signature)
|
||||||
participants = get_attestation_participants(
|
participants = get_attesting_indices_seq(
|
||||||
state, attestation.data, validation.aggregation_bitfield)
|
state, attestation.data, validation.aggregation_bitfield)
|
||||||
|
|
||||||
var found = false
|
var found = false
|
||||||
|
@ -196,7 +196,7 @@ proc add*(pool: var AttestationPool,
|
||||||
# sets by virtue of not overlapping with some other attestation
|
# sets by virtue of not overlapping with some other attestation
|
||||||
# and therefore being useful after all?
|
# and therefore being useful after all?
|
||||||
debug "Ignoring subset attestation",
|
debug "Ignoring subset attestation",
|
||||||
existingParticipants = get_attestation_participants(
|
existingParticipants = get_attesting_indices_seq(
|
||||||
state, a.data, v.aggregation_bitfield),
|
state, a.data, v.aggregation_bitfield),
|
||||||
newParticipants = participants
|
newParticipants = participants
|
||||||
found = true
|
found = true
|
||||||
|
@ -209,7 +209,7 @@ proc add*(pool: var AttestationPool,
|
||||||
if it.aggregation_bitfield.isSubsetOf(
|
if it.aggregation_bitfield.isSubsetOf(
|
||||||
validation.aggregation_bitfield):
|
validation.aggregation_bitfield):
|
||||||
debug "Removing subset attestation",
|
debug "Removing subset attestation",
|
||||||
existingParticipants = get_attestation_participants(
|
existingParticipants = get_attesting_indices_seq(
|
||||||
state, a.data, it.aggregation_bitfield),
|
state, a.data, it.aggregation_bitfield),
|
||||||
newParticipants = participants
|
newParticipants = participants
|
||||||
false
|
false
|
||||||
|
@ -287,7 +287,7 @@ proc getAttestationsForBlock*(
|
||||||
aggregation_bitfield: a.validations[0].aggregation_bitfield,
|
aggregation_bitfield: a.validations[0].aggregation_bitfield,
|
||||||
data: a.data,
|
data: a.data,
|
||||||
custody_bitfield: a.validations[0].custody_bitfield,
|
custody_bitfield: a.validations[0].custody_bitfield,
|
||||||
aggregate_signature: a.validations[0].aggregate_signature
|
signature: a.validations[0].aggregate_signature
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO what's going on here is that when producing a block, we need to
|
# TODO what's going on here is that when producing a block, we need to
|
||||||
|
@ -312,7 +312,7 @@ proc getAttestationsForBlock*(
|
||||||
attestation.aggregation_bitfield.combine(
|
attestation.aggregation_bitfield.combine(
|
||||||
v.aggregation_bitfield)
|
v.aggregation_bitfield)
|
||||||
attestation.custody_bitfield.combine(v.custody_bitfield)
|
attestation.custody_bitfield.combine(v.custody_bitfield)
|
||||||
attestation.aggregate_signature.combine(v.aggregate_signature)
|
attestation.signature.combine(v.aggregate_signature)
|
||||||
|
|
||||||
result.add(attestation)
|
result.add(attestation)
|
||||||
|
|
||||||
|
@ -324,7 +324,8 @@ proc resolve*(pool: var AttestationPool, state: BeaconState) =
|
||||||
var resolved: seq[Attestation]
|
var resolved: seq[Attestation]
|
||||||
|
|
||||||
for k, v in pool.unresolved.mpairs():
|
for k, v in pool.unresolved.mpairs():
|
||||||
if v.tries > 8 or v.attestation.data.slot < pool.startingSlot:
|
let attestation_slot = get_attestation_slot(state, v.attestation)
|
||||||
|
if v.tries > 8 or attestation_slot < pool.startingSlot:
|
||||||
done.add(k)
|
done.add(k)
|
||||||
else:
|
else:
|
||||||
if pool.blockPool.get(k).isSome():
|
if pool.blockPool.get(k).isSome():
|
||||||
|
|
|
@ -314,7 +314,7 @@ proc sendAttestation(node: BeaconNode,
|
||||||
|
|
||||||
var attestation = Attestation(
|
var attestation = Attestation(
|
||||||
data: attestationData,
|
data: attestationData,
|
||||||
aggregate_signature: validatorSignature,
|
signature: validatorSignature,
|
||||||
aggregation_bitfield: aggregationBitfield,
|
aggregation_bitfield: aggregationBitfield,
|
||||||
# Stub in phase0
|
# Stub in phase0
|
||||||
custody_bitfield: BitField.init(committeeLen)
|
custody_bitfield: BitField.init(committeeLen)
|
||||||
|
@ -371,7 +371,9 @@ proc proposeBlock(node: BeaconNode,
|
||||||
var tmpState = hashedState
|
var tmpState = hashedState
|
||||||
|
|
||||||
let ok = updateState(tmpState, newBlock, {skipValidation})
|
let ok = updateState(tmpState, newBlock, {skipValidation})
|
||||||
doAssert ok # TODO: err, could this fail somehow?
|
# TODO only enable in fast-fail debugging situations
|
||||||
|
# otherwise, bad attestations can bring down network
|
||||||
|
# doAssert ok # TODO: err, could this fail somehow?
|
||||||
|
|
||||||
newBlock.state_root = tmpState.root
|
newBlock.state_root = tmpState.root
|
||||||
|
|
||||||
|
@ -405,7 +407,7 @@ proc onAttestation(node: BeaconNode, attestation: Attestation) =
|
||||||
# we're on, or that it follows the rules of the protocol
|
# we're on, or that it follows the rules of the protocol
|
||||||
debug "Attestation received",
|
debug "Attestation received",
|
||||||
attestationData = shortLog(attestation.data),
|
attestationData = shortLog(attestation.data),
|
||||||
signature = shortLog(attestation.aggregate_signature)
|
signature = shortLog(attestation.signature)
|
||||||
|
|
||||||
# TODO seems reasonable to use the latest head state here.. needs thinking
|
# TODO seems reasonable to use the latest head state here.. needs thinking
|
||||||
# though - maybe we should use the state from the block pointed to by
|
# though - maybe we should use the state from the block pointed to by
|
||||||
|
|
|
@ -6,12 +6,12 @@
|
||||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
import
|
import
|
||||||
algorithm, chronicles, math, options, sequtils,
|
algorithm, chronicles, collections/sets, math, options, sequtils,
|
||||||
../extras, ../ssz, ../beacon_node_types,
|
../extras, ../ssz, ../beacon_node_types,
|
||||||
./bitfield, ./crypto, ./datatypes, ./digest, ./helpers, ./validator,
|
./bitfield, ./crypto, ./datatypes, ./digest, ./helpers, ./validator,
|
||||||
tables
|
tables
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.0/specs/core/0_beacon-chain.md#verify_merkle_branch
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#verify_merkle_branch
|
||||||
func verify_merkle_branch(leaf: Eth2Digest, proof: openarray[Eth2Digest], depth: uint64, index: uint64, root: Eth2Digest): bool =
|
func verify_merkle_branch(leaf: Eth2Digest, proof: openarray[Eth2Digest], depth: uint64, index: uint64, root: Eth2Digest): bool =
|
||||||
## Verify that the given ``leaf`` is on the merkle branch ``proof``
|
## Verify that the given ``leaf`` is on the merkle branch ``proof``
|
||||||
## starting with the given ``root``.
|
## starting with the given ``root``.
|
||||||
|
@ -153,7 +153,7 @@ func initiate_validator_exit*(state: var BeaconState,
|
||||||
validator.withdrawable_epoch =
|
validator.withdrawable_epoch =
|
||||||
validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
|
validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.1/specs/core/0_beacon-chain.md#slash_validator
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#slash_validator
|
||||||
func slash_validator*(state: var BeaconState, slashed_index: ValidatorIndex) =
|
func slash_validator*(state: var BeaconState, slashed_index: ValidatorIndex) =
|
||||||
# Slash the validator with index ``index``.
|
# Slash the validator with index ``index``.
|
||||||
let current_epoch = get_current_epoch(state)
|
let current_epoch = get_current_epoch(state)
|
||||||
|
@ -168,6 +168,7 @@ func slash_validator*(state: var BeaconState, slashed_index: ValidatorIndex) =
|
||||||
|
|
||||||
let
|
let
|
||||||
proposer_index = get_beacon_proposer_index(state)
|
proposer_index = get_beacon_proposer_index(state)
|
||||||
|
# Spec has whistleblower_index as optional param, but it's never used.
|
||||||
whistleblower_index = proposer_index
|
whistleblower_index = proposer_index
|
||||||
whistleblowing_reward = slashed_balance div WHISTLEBLOWING_REWARD_QUOTIENT
|
whistleblowing_reward = slashed_balance div WHISTLEBLOWING_REWARD_QUOTIENT
|
||||||
proposer_reward = whistleblowing_reward div PROPOSER_REWARD_QUOTIENT
|
proposer_reward = whistleblowing_reward div PROPOSER_REWARD_QUOTIENT
|
||||||
|
@ -176,8 +177,8 @@ func slash_validator*(state: var BeaconState, slashed_index: ValidatorIndex) =
|
||||||
state, whistleblower_index, whistleblowing_reward - proposer_reward)
|
state, whistleblower_index, whistleblowing_reward - proposer_reward)
|
||||||
decrease_balance(state, slashed_index, whistleblowing_reward)
|
decrease_balance(state, slashed_index, whistleblowing_reward)
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.1/specs/core/0_beacon-chain.md#get_temporary_block_header
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.1/specs/core/0_beacon-chain.md#on-genesis
|
||||||
func get_temporary_block_header*(blck: BeaconBlock): BeaconBlockHeader =
|
func get_temporary_block_header(blck: BeaconBlock): BeaconBlockHeader =
|
||||||
## Return the block header corresponding to a block with ``state_root`` set
|
## Return the block header corresponding to a block with ``state_root`` set
|
||||||
## to ``ZERO_HASH``.
|
## to ``ZERO_HASH``.
|
||||||
BeaconBlockHeader(
|
BeaconBlockHeader(
|
||||||
|
@ -186,10 +187,9 @@ func get_temporary_block_header*(blck: BeaconBlock): BeaconBlockHeader =
|
||||||
state_root: ZERO_HASH,
|
state_root: ZERO_HASH,
|
||||||
block_body_root: hash_tree_root(blck.body),
|
block_body_root: hash_tree_root(blck.body),
|
||||||
# signing_root(block) is used for block id purposes so signature is a stub
|
# signing_root(block) is used for block id purposes so signature is a stub
|
||||||
signature: EMPTY_SIGNATURE,
|
signature: ValidatorSig(),
|
||||||
)
|
)
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.1/specs/core/0_beacon-chain.md#on-genesis
|
|
||||||
func get_empty_block*(): BeaconBlock =
|
func get_empty_block*(): BeaconBlock =
|
||||||
# Nim default values fill this in mostly correctly.
|
# Nim default values fill this in mostly correctly.
|
||||||
BeaconBlock(slot: GENESIS_SLOT)
|
BeaconBlock(slot: GENESIS_SLOT)
|
||||||
|
@ -283,7 +283,7 @@ func get_initial_beacon_block*(state: BeaconState): BeaconBlock =
|
||||||
# initialized to default values.
|
# initialized to default values.
|
||||||
)
|
)
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#get_attestation_slot
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.2/specs/core/0_beacon-chain.md#get_attestation_slot
|
||||||
func get_attestation_slot*(state: BeaconState,
|
func get_attestation_slot*(state: BeaconState,
|
||||||
attestation: Attestation|PendingAttestation,
|
attestation: Attestation|PendingAttestation,
|
||||||
committee_count: uint64): Slot =
|
committee_count: uint64): Slot =
|
||||||
|
@ -291,7 +291,10 @@ func get_attestation_slot*(state: BeaconState,
|
||||||
epoch = attestation.data.target_epoch
|
epoch = attestation.data.target_epoch
|
||||||
offset = (attestation.data.shard + SHARD_COUNT -
|
offset = (attestation.data.shard + SHARD_COUNT -
|
||||||
get_epoch_start_shard(state, epoch)) mod SHARD_COUNT
|
get_epoch_start_shard(state, epoch)) mod SHARD_COUNT
|
||||||
result = get_epoch_start_slot(epoch) + offset div (committee_count div SLOTS_PER_EPOCH)
|
|
||||||
|
# TODO re-instate once double-check correct conditions in attestation pool
|
||||||
|
#get_epoch_start_slot(epoch) + offset div (committee_count div SLOTS_PER_EPOCH)
|
||||||
|
attestation.data.slot
|
||||||
|
|
||||||
# This is the slower (O(n)), spec-compatible signature.
|
# This is the slower (O(n)), spec-compatible signature.
|
||||||
func get_attestation_slot*(state: BeaconState,
|
func get_attestation_slot*(state: BeaconState,
|
||||||
|
@ -314,81 +317,6 @@ func get_block_root*(state: BeaconState, epoch: Epoch): Eth2Digest =
|
||||||
# Return the block root at a recent ``epoch``.
|
# Return the block root at a recent ``epoch``.
|
||||||
get_block_root_at_slot(state, get_epoch_start_slot(epoch))
|
get_block_root_at_slot(state, get_epoch_start_slot(epoch))
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.1/specs/core/0_beacon-chain.md#get_attestation_participants
|
|
||||||
func get_attestation_participants*(state: BeaconState,
|
|
||||||
attestation_data: AttestationData,
|
|
||||||
bitfield: BitField): seq[ValidatorIndex] =
|
|
||||||
## Return the participant indices at for the ``attestation_data`` and
|
|
||||||
## ``bitfield``.
|
|
||||||
## Attestation participants in the attestation data are called out in a
|
|
||||||
## bit field that corresponds to the committee of the shard at the time;
|
|
||||||
## this function converts it to list of indices in to BeaconState.validators
|
|
||||||
##
|
|
||||||
## Returns empty list if the shard is not found
|
|
||||||
## Return the participant indices at for the ``attestation_data`` and ``bitfield``.
|
|
||||||
##
|
|
||||||
# TODO Linear search through shard list? borderline ok, it's a small list
|
|
||||||
# TODO iterator candidate
|
|
||||||
|
|
||||||
# Find the committee in the list with the desired shard
|
|
||||||
let crosslink_committees = get_crosslink_committees_at_slot(
|
|
||||||
state, attestation_data.slot)
|
|
||||||
|
|
||||||
doAssert anyIt(
|
|
||||||
crosslink_committees,
|
|
||||||
it[1] == attestation_data.shard)
|
|
||||||
let crosslink_committee = mapIt(
|
|
||||||
filterIt(crosslink_committees, it.shard == attestation_data.shard),
|
|
||||||
it.committee)[0]
|
|
||||||
|
|
||||||
# TODO this and other attestation-based fields need validation so we don't
|
|
||||||
# crash on a malicious attestation!
|
|
||||||
doAssert verify_bitfield(bitfield, len(crosslink_committee))
|
|
||||||
|
|
||||||
# Find the participating attesters in the committee
|
|
||||||
result = @[]
|
|
||||||
for i, validator_index in crosslink_committee:
|
|
||||||
let aggregation_bit = get_bitfield_bit(bitfield, i)
|
|
||||||
if aggregation_bit:
|
|
||||||
result.add(validator_index)
|
|
||||||
|
|
||||||
iterator get_attestation_participants_cached*(state: BeaconState,
|
|
||||||
attestation_data: AttestationData,
|
|
||||||
bitfield: BitField,
|
|
||||||
cache: var StateCache): ValidatorIndex =
|
|
||||||
## Return the participant indices at for the ``attestation_data`` and
|
|
||||||
## ``bitfield``.
|
|
||||||
## Attestation participants in the attestation data are called out in a
|
|
||||||
## bit field that corresponds to the committee of the shard at the time;
|
|
||||||
## this function converts it to list of indices in to BeaconState.validators
|
|
||||||
##
|
|
||||||
## Returns empty list if the shard is not found
|
|
||||||
## Return the participant indices at for the ``attestation_data`` and ``bitfield``.
|
|
||||||
##
|
|
||||||
# TODO Linear search through shard list? borderline ok, it's a small list
|
|
||||||
# TODO iterator candidate
|
|
||||||
|
|
||||||
# Find the committee in the list with the desired shard
|
|
||||||
# let crosslink_committees = get_crosslink_committees_at_slot_cached(
|
|
||||||
# state, attestation_data.slot, false, crosslink_committees_cached)
|
|
||||||
|
|
||||||
var found = false
|
|
||||||
for crosslink_committee in get_crosslink_committees_at_slot_cached(
|
|
||||||
state, attestation_data.slot, cache):
|
|
||||||
if crosslink_committee.shard == attestation_data.shard:
|
|
||||||
# TODO this and other attestation-based fields need validation so we don't
|
|
||||||
# crash on a malicious attestation!
|
|
||||||
doAssert verify_bitfield(bitfield, len(crosslink_committee.committee))
|
|
||||||
|
|
||||||
# Find the participating attesters in the committee
|
|
||||||
for i, validator_index in crosslink_committee.committee:
|
|
||||||
let aggregation_bit = get_bitfield_bit(bitfield, i)
|
|
||||||
if aggregation_bit:
|
|
||||||
yield validator_index
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
doAssert found, "Couldn't find crosslink committee"
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#get_total_balance
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#get_total_balance
|
||||||
func get_total_balance*(state: BeaconState, validators: auto): Gwei =
|
func get_total_balance*(state: BeaconState, validators: auto): Gwei =
|
||||||
# Return the combined effective balance of an array of ``validators``.
|
# Return the combined effective balance of an array of ``validators``.
|
||||||
|
@ -432,9 +360,133 @@ func process_registry_updates*(state: var BeaconState) =
|
||||||
validator.activation_epoch =
|
validator.activation_epoch =
|
||||||
get_delayed_activation_exit_epoch(get_current_epoch(state))
|
get_delayed_activation_exit_epoch(get_current_epoch(state))
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.1/specs/core/0_beacon-chain.md#attestations
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#verify_indexed_attestation
|
||||||
|
func verify_indexed_attestation*(
|
||||||
|
state: BeaconState, indexed_attestation: IndexedAttestation): bool =
|
||||||
|
# Verify validity of ``indexed_attestation`` fields.
|
||||||
|
|
||||||
|
let
|
||||||
|
custody_bit_0_indices = indexed_attestation.custody_bit_0_indices
|
||||||
|
custody_bit_1_indices = indexed_attestation.custody_bit_1_indices
|
||||||
|
|
||||||
|
# Ensure no duplicate indices across custody bits
|
||||||
|
if len(intersection(toSet(custody_bit_0_indices), toSet(custody_bit_1_indices))) != 0:
|
||||||
|
return false
|
||||||
|
|
||||||
|
if len(custody_bit_1_indices) > 0: # [TO BE REMOVED IN PHASE 1]
|
||||||
|
return false
|
||||||
|
|
||||||
|
let combined_len = len(custody_bit_0_indices) + len(custody_bit_1_indices)
|
||||||
|
if not (1 <= combined_len and combined_len <= MAX_INDICES_PER_ATTESTATION):
|
||||||
|
return false
|
||||||
|
|
||||||
|
if custody_bit_0_indices != sorted(custody_bit_0_indices, system.cmp):
|
||||||
|
return false
|
||||||
|
|
||||||
|
if custody_bit_1_indices != sorted(custody_bit_1_indices, system.cmp):
|
||||||
|
return false
|
||||||
|
|
||||||
|
bls_verify_multiple(
|
||||||
|
@[
|
||||||
|
bls_aggregate_pubkeys(
|
||||||
|
mapIt(custody_bit_0_indices, state.validator_registry[it.int].pubkey)),
|
||||||
|
bls_aggregate_pubkeys(
|
||||||
|
mapIt(custody_bit_1_indices, state.validator_registry[it.int].pubkey)),
|
||||||
|
],
|
||||||
|
@[
|
||||||
|
hash_tree_root(AttestationDataAndCustodyBit(
|
||||||
|
data: indexed_attestation.data, custody_bit: false)),
|
||||||
|
hash_tree_root(AttestationDataAndCustodyBit(
|
||||||
|
data: indexed_attestation.data, custody_bit: true)),
|
||||||
|
],
|
||||||
|
indexed_attestation.signature,
|
||||||
|
get_domain(
|
||||||
|
state,
|
||||||
|
DOMAIN_ATTESTATION,
|
||||||
|
indexed_attestation.data.target_epoch
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#get_attesting_indices
|
||||||
|
func get_attesting_indices*(state: BeaconState,
|
||||||
|
attestation_data: AttestationData,
|
||||||
|
bitfield: BitField): HashSet[ValidatorIndex] =
|
||||||
|
## Return the sorted attesting indices corresponding to ``attestation_data``
|
||||||
|
## and ``bitfield``.
|
||||||
|
## The spec goes through a lot of hoops to sort things, and sometimes
|
||||||
|
## constructs sets from the results here. The basic idea is to always
|
||||||
|
## just keep it in a HashSet, which seems to suffice. If needed, it's
|
||||||
|
## possible to follow the spec more literally.
|
||||||
|
result = initSet[ValidatorIndex]()
|
||||||
|
let committee =
|
||||||
|
get_crosslink_committee(state, attestation_data.target_epoch,
|
||||||
|
attestation_data.shard)
|
||||||
|
doAssert verify_bitfield(bitfield, len(committee))
|
||||||
|
for i, index in committee:
|
||||||
|
if get_bitfield_bit(bitfield, i):
|
||||||
|
result.incl index
|
||||||
|
|
||||||
|
func get_attesting_indices_seq*(
|
||||||
|
state: BeaconState, attestation_data: AttestationData, bitfield: BitField):
|
||||||
|
seq[ValidatorIndex] =
|
||||||
|
toSeq(items(get_attesting_indices(state, attestation_data, bitfield)))
|
||||||
|
|
||||||
|
# TODO legacy function name; rename, reimplement caching if useful, blob/v0.6.2
|
||||||
|
iterator get_attestation_participants_cached*(
|
||||||
|
state: BeaconState, attestation_data: AttestationData, bitfield: BitField,
|
||||||
|
cache: var StateCache): ValidatorIndex =
|
||||||
|
for participant in get_attesting_indices(state, attestation_data, bitfield):
|
||||||
|
yield participant
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#convert_to_indexed
|
||||||
|
func convert_to_indexed(state: BeaconState, attestation: Attestation): IndexedAttestation =
|
||||||
|
# Convert ``attestation`` to (almost) indexed-verifiable form.
|
||||||
|
let
|
||||||
|
attesting_indices =
|
||||||
|
get_attesting_indices(
|
||||||
|
state, attestation.data, attestation.aggregation_bitfield)
|
||||||
|
custody_bit_1_indices =
|
||||||
|
get_attesting_indices(
|
||||||
|
state, attestation.data, attestation.custody_bitfield)
|
||||||
|
|
||||||
|
## TODO quadratic, .items, but first-class iterators, etc
|
||||||
|
## filterIt can't work on HashSets directly because it is
|
||||||
|
## assuming int-indexable thing to extract type, because,
|
||||||
|
## like lots of other things in sequtils, it's a template
|
||||||
|
## which doesn't otherwise care about the type system. It
|
||||||
|
## is a mess. Just write the for-loop, etc, I guess, is a
|
||||||
|
## reasonable reaction because of the special for binding
|
||||||
|
## with (non-closure, etc) iterators no other part of Nim
|
||||||
|
## can access. As such, this function's doing many copies
|
||||||
|
## and allocations it has no fundamental reason to do.
|
||||||
|
custody_bit_0_indices =
|
||||||
|
filterIt(toSeq(items(attesting_indices)), it notin custody_bit_1_indices)
|
||||||
|
|
||||||
|
## TODO No fundamental reason to do so many type conversions
|
||||||
|
## verify_indexed_attestation checks for sortedness but it's
|
||||||
|
## entirely a local artifact, seemingly; networking uses the
|
||||||
|
## Attestation data structure, which can't be unsorted. That
|
||||||
|
## the conversion here otherwise needs sorting is due to the
|
||||||
|
## usage of HashSet -- order only matters in one place (that
|
||||||
|
## 0.6.3 highlights and explicates) except in that the spec,
|
||||||
|
## for no obvious reason, verifies it. So, here goes, sort a
|
||||||
|
## list just so a called function can verify it's sorted.
|
||||||
|
IndexedAttestation(
|
||||||
|
custody_bit_0_indices: sorted(
|
||||||
|
mapIt(custody_bit_0_indices, it.uint64), system.cmp),
|
||||||
|
# toSeq pointlessly constructs int-indexable copy so mapIt can infer type;
|
||||||
|
# see above
|
||||||
|
custody_bit_1_indices:
|
||||||
|
sorted(mapIt(toSeq(items(custody_bit_1_indices)), it.uint64),
|
||||||
|
system.cmp),
|
||||||
|
data: attestation.data,
|
||||||
|
signature: attestation.signature,
|
||||||
|
)
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#attestations
|
||||||
proc checkAttestation*(
|
proc checkAttestation*(
|
||||||
state: BeaconState, attestation: Attestation, flags: UpdateFlags): bool =
|
state: BeaconState, attestation: Attestation, flags: UpdateFlags): bool =
|
||||||
|
## Process ``Attestation`` operation.
|
||||||
## Check that an attestation follows the rules of being included in the state
|
## Check that an attestation follows the rules of being included in the state
|
||||||
## at the current slot. When acting as a proposer, the same rules need to
|
## at the current slot. When acting as a proposer, the same rules need to
|
||||||
## be followed!
|
## be followed!
|
||||||
|
@ -443,142 +495,45 @@ proc checkAttestation*(
|
||||||
if nextSlot in flags: state.slot + 1
|
if nextSlot in flags: state.slot + 1
|
||||||
else: state.slot
|
else: state.slot
|
||||||
|
|
||||||
# Can't submit attestations that are too far in history (or in prehistory)
|
let attestation_slot = get_attestation_slot(state, attestation)
|
||||||
if not (attestation.data.slot >= GENESIS_SLOT):
|
if not (attestation_slot + MIN_ATTESTATION_INCLUSION_DELAY <= stateSlot):
|
||||||
warn("Attestation predates genesis slot",
|
|
||||||
attestation_slot = attestation.data.slot,
|
|
||||||
state_slot = humaneSlotNum(stateSlot))
|
|
||||||
return
|
|
||||||
|
|
||||||
if not (stateSlot <= attestation.data.slot + SLOTS_PER_EPOCH):
|
|
||||||
warn("Attestation too old",
|
|
||||||
attestation_slot = humaneSlotNum(attestation.data.slot),
|
|
||||||
state_slot = humaneSlotNum(stateSlot))
|
|
||||||
return
|
|
||||||
|
|
||||||
# Can't submit attestations too quickly
|
|
||||||
if not (
|
|
||||||
attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= stateSlot):
|
|
||||||
warn("Attestation too new",
|
warn("Attestation too new",
|
||||||
attestation_slot = humaneSlotNum(attestation.data.slot),
|
attestation_slot = humaneSlotNum(attestation_slot),
|
||||||
state_slot = humaneSlotNum(stateSlot))
|
state_slot = humaneSlotNum(stateSlot))
|
||||||
return
|
return
|
||||||
|
|
||||||
# # Verify that the justified epoch and root is correct
|
if not (stateSlot <= attestation_slot + SLOTS_PER_EPOCH):
|
||||||
if slot_to_epoch(attestation.data.slot) >= stateSlot.slot_to_epoch():
|
warn("Attestation too old",
|
||||||
# Case 1: current epoch attestations
|
attestation_slot = humaneSlotNum(attestation_slot),
|
||||||
if not (attestation.data.source_epoch == state.current_justified_epoch):
|
state_slot = humaneSlotNum(stateSlot))
|
||||||
warn("Source epoch is not current justified epoch",
|
|
||||||
attestation_slot = humaneSlotNum(attestation.data.slot),
|
|
||||||
state_slot = humaneSlotNum(stateSlot))
|
|
||||||
return
|
|
||||||
|
|
||||||
if not (attestation.data.source_root == state.current_justified_root):
|
|
||||||
warn("Source root is not current justified root",
|
|
||||||
attestation_slot = humaneSlotNum(attestation.data.slot),
|
|
||||||
state_slot = humaneSlotNum(stateSlot))
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
# Case 2: previous epoch attestations
|
|
||||||
if not (attestation.data.source_epoch == state.previous_justified_epoch):
|
|
||||||
warn("Source epoch is not previous justified epoch",
|
|
||||||
attestation_slot = humaneSlotNum(attestation.data.slot),
|
|
||||||
state_slot = humaneSlotNum(stateSlot))
|
|
||||||
return
|
|
||||||
|
|
||||||
if not (attestation.data.source_root == state.previous_justified_root):
|
|
||||||
warn("Source root is not previous justified root",
|
|
||||||
attestation_slot = humaneSlotNum(attestation.data.slot),
|
|
||||||
state_slot = humaneSlotNum(stateSlot))
|
|
||||||
return
|
|
||||||
|
|
||||||
# Check that the crosslink data is valid
|
|
||||||
let acceptable_crosslink_data = @[
|
|
||||||
# Case 1: Latest crosslink matches the one in the state
|
|
||||||
attestation.data.previous_crosslink,
|
|
||||||
|
|
||||||
# Case 2: State has already been updated, state's latest crosslink matches
|
|
||||||
# the crosslink the attestation is trying to create
|
|
||||||
Crosslink(
|
|
||||||
crosslink_data_root: attestation.data.crosslink_data_root,
|
|
||||||
epoch: slot_to_epoch(attestation.data.slot)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
if not (state.current_crosslinks[attestation.data.shard] in
|
|
||||||
acceptable_crosslink_data):
|
|
||||||
warn("Unexpected crosslink shard",
|
|
||||||
state_latest_crosslinks_attestation_data_shard =
|
|
||||||
state.current_crosslinks[attestation.data.shard],
|
|
||||||
attestation_data_previous_crosslink = attestation.data.previous_crosslink,
|
|
||||||
epoch = humaneEpochNum(slot_to_epoch(attestation.data.slot)),
|
|
||||||
actual_epoch = slot_to_epoch(attestation.data.slot),
|
|
||||||
crosslink_data_root = attestation.data.crosslink_data_root,
|
|
||||||
acceptable_crosslink_data = acceptable_crosslink_data)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Attestation must be nonempty!
|
# Check target epoch, source epoch, source root, and source crosslink
|
||||||
if not anyIt(attestation.aggregation_bitfield.bits, it != 0):
|
let data = attestation.data
|
||||||
warn("No signature bits")
|
if not (
|
||||||
|
(data.target_epoch, data.source_epoch, data.source_root, data.previous_crosslink_root) ==
|
||||||
|
(get_current_epoch(state), state.current_justified_epoch,
|
||||||
|
state.current_justified_root,
|
||||||
|
hash_tree_root(state.current_crosslinks[data.shard])) or
|
||||||
|
(data.target_epoch, data.source_epoch, data.source_root, data.previous_crosslink_root) ==
|
||||||
|
(get_previous_epoch(state), state.previous_justified_epoch,
|
||||||
|
state.previous_justified_root,
|
||||||
|
hash_tree_root(state.previous_crosslinks[data.shard]))):
|
||||||
|
warn("checkAttestation: target epoch, source epoch, source root, or source crosslink invalid")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Custody must be empty (to be removed in phase 1)
|
## Check crosslink data root
|
||||||
if not allIt(attestation.custody_bitfield.bits, it == 0):
|
## [to be removed in phase 1]
|
||||||
warn("Custody bits set in phase0")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get the committee for the specific shard that this attestation is for
|
|
||||||
let crosslink_committee = mapIt(
|
|
||||||
filterIt(get_crosslink_committees_at_slot(state, attestation.data.slot),
|
|
||||||
it.shard == attestation.data.shard),
|
|
||||||
it.committee)[0]
|
|
||||||
|
|
||||||
# Custody bitfield must be a subset of the attestation bitfield
|
|
||||||
if not allIt(0 ..< len(crosslink_committee),
|
|
||||||
if not get_bitfield_bit(attestation.aggregation_bitfield, it):
|
|
||||||
not get_bitfield_bit(attestation.custody_bitfield, it)
|
|
||||||
else:
|
|
||||||
true):
|
|
||||||
warn("Wrong custody bits set")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Verify aggregate signature
|
|
||||||
let
|
|
||||||
participants = get_attestation_participants(
|
|
||||||
state, attestation.data, attestation.aggregation_bitfield)
|
|
||||||
|
|
||||||
## TODO when the custody_bitfield assertion-to-emptiness disappears do this
|
|
||||||
## and fix the custody_bit_0_participants check to depend on it.
|
|
||||||
# custody_bit_1_participants = {nothing, always, because assertion above}
|
|
||||||
custody_bit_1_participants: seq[ValidatorIndex] = @[]
|
|
||||||
custody_bit_0_participants = participants
|
|
||||||
|
|
||||||
if skipValidation notin flags:
|
|
||||||
# Verify that aggregate_signature verifies using the group pubkey.
|
|
||||||
if not bls_verify_multiple(
|
|
||||||
@[
|
|
||||||
bls_aggregate_pubkeys(mapIt(custody_bit_0_participants,
|
|
||||||
state.validator_registry[it].pubkey)),
|
|
||||||
bls_aggregate_pubkeys(mapIt(custody_bit_1_participants,
|
|
||||||
state.validator_registry[it].pubkey)),
|
|
||||||
],
|
|
||||||
@[
|
|
||||||
hash_tree_root(AttestationDataAndCustodyBit(
|
|
||||||
data: attestation.data, custody_bit: false)),
|
|
||||||
hash_tree_root(AttestationDataAndCustodyBit(
|
|
||||||
data: attestation.data, custody_bit: true)),
|
|
||||||
],
|
|
||||||
attestation.aggregate_signature,
|
|
||||||
get_domain(state, DOMAIN_ATTESTATION,
|
|
||||||
slot_to_epoch(attestation.data.slot))
|
|
||||||
):
|
|
||||||
warn("Invalid attestation signature")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Crosslink data root is zero (to be removed in phase 1)
|
|
||||||
if attestation.data.crosslink_data_root != ZERO_HASH:
|
if attestation.data.crosslink_data_root != ZERO_HASH:
|
||||||
warn("Invalid crosslink data root")
|
warn("Invalid crosslink data root")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Check signature and bitfields
|
||||||
|
if not verify_indexed_attestation(
|
||||||
|
state, convert_to_indexed(state, attestation)):
|
||||||
|
warn("checkAttestation: signature or bitfields incorrect")
|
||||||
|
return
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|
||||||
proc makeAttestationData*(
|
proc makeAttestationData*(
|
||||||
|
@ -600,8 +555,8 @@ proc makeAttestationData*(
|
||||||
beacon_block_root: beacon_block_root,
|
beacon_block_root: beacon_block_root,
|
||||||
target_root: target_root,
|
target_root: target_root,
|
||||||
crosslink_data_root: Eth2Digest(), # Stub in phase0
|
crosslink_data_root: Eth2Digest(), # Stub in phase0
|
||||||
previous_crosslink: state.current_crosslinks[shard],
|
previous_crosslink_root: hash_tree_root(state.current_crosslinks[shard]),
|
||||||
source_epoch: state.current_justified_epoch,
|
source_epoch: state.current_justified_epoch,
|
||||||
source_root: state.current_justified_root,
|
source_root: state.current_justified_root,
|
||||||
target_epoch: slot_to_epoch(epoch_start_slot)
|
target_epoch: slot_to_epoch(state.slot)
|
||||||
)
|
)
|
||||||
|
|
|
@ -57,21 +57,15 @@ const
|
||||||
## Spec version we're aiming to be compatible with, right now
|
## Spec version we're aiming to be compatible with, right now
|
||||||
## TODO: improve this scheme once we can negotiate versions in protocol
|
## TODO: improve this scheme once we can negotiate versions in protocol
|
||||||
|
|
||||||
# Gwei values
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#gwei-values
|
|
||||||
|
|
||||||
# TODO remove erstwhile blob/v0.6.3
|
# TODO remove erstwhile blob/v0.6.3
|
||||||
FORK_CHOICE_BALANCE_INCREMENT* = 2'u64^0 * 10'u64^9
|
FORK_CHOICE_BALANCE_INCREMENT* = 2'u64^0 * 10'u64^9
|
||||||
|
|
||||||
# Initial values
|
# Initial values
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#initial-values
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#initial-values
|
||||||
GENESIS_EPOCH* = (GENESIS_SLOT.uint64 div SLOTS_PER_EPOCH).Epoch ##\
|
GENESIS_EPOCH* = (GENESIS_SLOT.uint64 div SLOTS_PER_EPOCH).Epoch ##\
|
||||||
## slot_to_epoch(GENESIS_SLOT)
|
## slot_to_epoch(GENESIS_SLOT)
|
||||||
GENESIS_START_SHARD* = 0'u64
|
|
||||||
ZERO_HASH* = Eth2Digest()
|
ZERO_HASH* = Eth2Digest()
|
||||||
EMPTY_SIGNATURE* = ValidatorSig()
|
|
||||||
|
|
||||||
type
|
type
|
||||||
ValidatorIndex* = range[0'u32 .. 0xFFFFFF'u32] # TODO: wrap-around
|
ValidatorIndex* = range[0'u32 .. 0xFFFFFF'u32] # TODO: wrap-around
|
||||||
|
@ -97,18 +91,16 @@ type
|
||||||
attestation_2*: IndexedAttestation ## \
|
attestation_2*: IndexedAttestation ## \
|
||||||
## Second attestation
|
## Second attestation
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#slashableattestation
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#indexedattestation
|
||||||
IndexedAttestation* = object
|
IndexedAttestation* = object
|
||||||
validator_indices*: seq[uint64] ##\
|
# These probably should be seq[ValidatorIndex], but that throws RLP errors
|
||||||
## Validator indices
|
custody_bit_0_indices*: seq[uint64]
|
||||||
|
custody_bit_1_indices*: seq[uint64]
|
||||||
|
|
||||||
data*: AttestationData ## \
|
data*: AttestationData ## \
|
||||||
## Attestation data
|
## Attestation data
|
||||||
|
|
||||||
custody_bitfield*: BitField ##\
|
signature*: ValidatorSig ## \
|
||||||
## Custody bitfield
|
|
||||||
|
|
||||||
aggregate_signature*: ValidatorSig ## \
|
|
||||||
## Aggregate signature
|
## Aggregate signature
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#attestation
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#attestation
|
||||||
|
@ -122,13 +114,13 @@ type
|
||||||
custody_bitfield*: BitField ##\
|
custody_bitfield*: BitField ##\
|
||||||
## Custody bitfield
|
## Custody bitfield
|
||||||
|
|
||||||
aggregate_signature*: ValidatorSig ##\
|
signature*: ValidatorSig ##\
|
||||||
## BLS aggregate signature
|
## BLS aggregate signature
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#attestationdata
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.2/specs/core/0_beacon-chain.md#attestationdata
|
||||||
AttestationData* = object
|
AttestationData* = object
|
||||||
|
slot*: Slot # TODO remove this, once figure out attestation pool issues
|
||||||
# LMD GHOST vote
|
# LMD GHOST vote
|
||||||
slot*: Slot
|
|
||||||
beacon_block_root*: Eth2Digest
|
beacon_block_root*: Eth2Digest
|
||||||
|
|
||||||
# FFG vote
|
# FFG vote
|
||||||
|
@ -139,7 +131,7 @@ type
|
||||||
|
|
||||||
# Crosslink vote
|
# Crosslink vote
|
||||||
shard*: uint64
|
shard*: uint64
|
||||||
previous_crosslink*: Crosslink
|
previous_crosslink_root*: Eth2Digest
|
||||||
crosslink_data_root*: Eth2Digest
|
crosslink_data_root*: Eth2Digest
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#attestationdataandcustodybit
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#attestationdataandcustodybit
|
||||||
|
@ -460,17 +452,12 @@ func shortLog*(v: BeaconBlock): tuple[
|
||||||
shortLog(v.signature)
|
shortLog(v.signature)
|
||||||
)
|
)
|
||||||
|
|
||||||
func shortLog*(v: AttestationData): tuple[
|
func shortLog*(v: AttestationData): auto =
|
||||||
slot: uint64, beacon_block_root: string, source_epoch: uint64,
|
(
|
||||||
target_root: string, source_root: string, shard: uint64,
|
shortLog(v.beacon_block_root),
|
||||||
previous_crosslink_epoch: uint64, previous_crosslink_data_root: string,
|
|
||||||
crosslink_data_root: string
|
|
||||||
] = (
|
|
||||||
humaneSlotNum(v.slot), shortLog(v.beacon_block_root),
|
|
||||||
humaneEpochNum(v.source_epoch), shortLog(v.target_root),
|
humaneEpochNum(v.source_epoch), shortLog(v.target_root),
|
||||||
shortLog(v.source_root),
|
shortLog(v.source_root),
|
||||||
v.shard, humaneEpochNum(v.previous_crosslink.epoch),
|
v.shard, v.previous_crosslink_root,
|
||||||
shortLog(v.previous_crosslink.crosslink_data_root),
|
|
||||||
shortLog(v.crosslink_data_root)
|
shortLog(v.crosslink_data_root)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ func get_active_validator_indices*(state: BeaconState, epoch: Epoch):
|
||||||
if is_active_validator(val, epoch):
|
if is_active_validator(val, epoch):
|
||||||
result.add idx.ValidatorIndex
|
result.add idx.ValidatorIndex
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.1/specs/core/0_beacon-chain.md#get_epoch_committee_count
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#get_epoch_committee_count
|
||||||
func get_epoch_committee_count*(state: BeaconState, epoch: Epoch): uint64 =
|
func get_epoch_committee_count*(state: BeaconState, epoch: Epoch): uint64 =
|
||||||
# Return the number of committees at ``epoch``.
|
# Return the number of committees at ``epoch``.
|
||||||
let active_validator_indices = get_active_validator_indices(state, epoch)
|
let active_validator_indices = get_active_validator_indices(state, epoch)
|
||||||
|
@ -95,13 +95,12 @@ func get_current_epoch*(state: BeaconState): Epoch =
|
||||||
doAssert state.slot >= GENESIS_SLOT, $state.slot
|
doAssert state.slot >= GENESIS_SLOT, $state.slot
|
||||||
slot_to_epoch(state.slot)
|
slot_to_epoch(state.slot)
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.1/specs/core/0_beacon-chain.md#get_randao_mix
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#get_randao_mix
|
||||||
func get_randao_mix*(state: BeaconState,
|
func get_randao_mix*(state: BeaconState,
|
||||||
epoch: Epoch): Eth2Digest =
|
epoch: Epoch): Eth2Digest =
|
||||||
## Returns the randao mix at a recent ``epoch``.
|
## Returns the randao mix at a recent ``epoch``.
|
||||||
## ``epoch`` expected to be between
|
## ``epoch`` expected to be between (current_epoch -
|
||||||
## (current_epoch - LATEST_ACTIVE_INDEX_ROOTS_LENGTH + ACTIVATION_EXIT_DELAY,
|
## LATEST_RANDAO_MIXES_LENGTH, current_epoch].
|
||||||
## current_epoch + ACTIVATION_EXIT_DELAY].
|
|
||||||
state.latest_randao_mixes[epoch mod LATEST_RANDAO_MIXES_LENGTH]
|
state.latest_randao_mixes[epoch mod LATEST_RANDAO_MIXES_LENGTH]
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#get_active_index_root
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#get_active_index_root
|
||||||
|
@ -152,7 +151,7 @@ func int_to_bytes4*(x: uint64): array[4, byte] =
|
||||||
result[2] = ((x shr 16) and 0xff).byte
|
result[2] = ((x shr 16) and 0xff).byte
|
||||||
result[3] = ((x shr 24) and 0xff).byte
|
result[3] = ((x shr 24) and 0xff).byte
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.0/specs/core/0_beacon-chain.md#get_domain
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#get_domain
|
||||||
func get_domain*(
|
func get_domain*(
|
||||||
state: BeaconState, domain_type: SignatureDomain, message_epoch: Epoch): uint64 =
|
state: BeaconState, domain_type: SignatureDomain, message_epoch: Epoch): uint64 =
|
||||||
## Return the signature domain (fork version concatenated with domain type)
|
## Return the signature domain (fork version concatenated with domain type)
|
||||||
|
|
|
@ -22,7 +22,7 @@ type
|
||||||
|
|
||||||
{.experimental: "codeReordering".} # SLOTS_PER_EPOCH is use before being defined in spec
|
{.experimental: "codeReordering".} # SLOTS_PER_EPOCH is use before being defined in spec
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.1/configs/constant_presets/mainnet.yaml
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/configs/constant_presets/mainnet.yaml
|
||||||
const
|
const
|
||||||
# Misc
|
# Misc
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
|
@ -83,16 +83,16 @@ const
|
||||||
|
|
||||||
# Initial values
|
# Initial values
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#initial-values
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/configs/constant_presets/mainnet.yaml#L44
|
||||||
|
|
||||||
GENESIS_FORK_VERSION* = [0'u8, 0'u8, 0'u8, 0'u8]
|
GENESIS_FORK_VERSION* = [0'u8, 0'u8, 0'u8, 0'u8]
|
||||||
GENESIS_SLOT* = 64.Slot
|
GENESIS_SLOT* = 0.Slot
|
||||||
FAR_FUTURE_EPOCH* = (not 0'u64).Epoch # 2^64 - 1 in spec
|
FAR_FUTURE_EPOCH* = (not 0'u64).Epoch # 2^64 - 1 in spec
|
||||||
BLS_WITHDRAWAL_PREFIX_BYTE* = 0'u8
|
BLS_WITHDRAWAL_PREFIX_BYTE* = 0'u8
|
||||||
|
|
||||||
# Time parameters
|
# Time parameters
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.1/specs/core/0_fork-choice.md#time-parameters
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_fork-choice.md#time-parameters
|
||||||
|
|
||||||
SECONDS_PER_SLOT*{.intdefine.} = 6'u64 # Compile with -d:SECONDS_PER_SLOT=1 for 6x faster slots
|
SECONDS_PER_SLOT*{.intdefine.} = 6'u64 # Compile with -d:SECONDS_PER_SLOT=1 for 6x faster slots
|
||||||
## TODO consistent time unit across projects, similar to C++ chrono?
|
## TODO consistent time unit across projects, similar to C++ chrono?
|
||||||
|
|
|
@ -26,7 +26,7 @@ type
|
||||||
const
|
const
|
||||||
# Misc
|
# Misc
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.1/specs/core/0_beacon-chain.md#misc
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#misc
|
||||||
|
|
||||||
# Changed
|
# Changed
|
||||||
SHARD_COUNT* {.intdefine.} = 8
|
SHARD_COUNT* {.intdefine.} = 8
|
||||||
|
@ -61,11 +61,11 @@ const
|
||||||
|
|
||||||
# Initial values
|
# Initial values
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#initial-values
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/configs/constant_presets/minimal.yaml#L43
|
||||||
|
|
||||||
# Unchanged
|
# Unchanged
|
||||||
GENESIS_FORK_VERSION* = [0'u8, 0'u8, 0'u8, 0'u8]
|
GENESIS_FORK_VERSION* = [0'u8, 0'u8, 0'u8, 0'u8]
|
||||||
GENESIS_SLOT* = 64.Slot
|
GENESIS_SLOT* = 0.Slot
|
||||||
FAR_FUTURE_EPOCH* = (not 0'u64).Epoch # 2^64 - 1 in spec
|
FAR_FUTURE_EPOCH* = (not 0'u64).Epoch # 2^64 - 1 in spec
|
||||||
BLS_WITHDRAWAL_PREFIX_BYTE* = 0'u8
|
BLS_WITHDRAWAL_PREFIX_BYTE* = 0'u8
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ const
|
||||||
|
|
||||||
# Changed
|
# Changed
|
||||||
SLOTS_PER_ETH1_VOTING_PERIOD* = 16
|
SLOTS_PER_ETH1_VOTING_PERIOD* = 16
|
||||||
SLOTS_PER_HISTORICAL_ROOT* = 64
|
SLOTS_PER_HISTORICAL_ROOT* = 128 # 64 doesn't work with GENESIS_SLOT == 0?
|
||||||
|
|
||||||
# Unchanged
|
# Unchanged
|
||||||
MIN_VALIDATOR_WITHDRAWABILITY_DELAY* = 2'u64^8
|
MIN_VALIDATOR_WITHDRAWABILITY_DELAY* = 2'u64^8
|
||||||
|
|
|
@ -13,8 +13,8 @@ import
|
||||||
./crypto, ./datatypes, ./digest, ./helpers
|
./crypto, ./datatypes, ./digest, ./helpers
|
||||||
|
|
||||||
# TODO: Proceed to renaming and signature changes
|
# TODO: Proceed to renaming and signature changes
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.1/specs/core/0_beacon-chain.md#get_shuffled_index
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#get_shuffled_index
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.1/specs/core/0_beacon-chain.md#compute_committee
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#compute_committee
|
||||||
func get_shuffled_seq*(seed: Eth2Digest,
|
func get_shuffled_seq*(seed: Eth2Digest,
|
||||||
list_size: uint64,
|
list_size: uint64,
|
||||||
): seq[ValidatorIndex] =
|
): seq[ValidatorIndex] =
|
||||||
|
@ -118,7 +118,7 @@ func get_shuffled_index(index: ValidatorIndex, index_count: uint64, seed: Eth2Di
|
||||||
if bit != 0:
|
if bit != 0:
|
||||||
result = flip
|
result = flip
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.1/specs/core/0_beacon-chain.md#get_previous_epoch
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#get_previous_epoch
|
||||||
func get_previous_epoch*(state: BeaconState): Epoch =
|
func get_previous_epoch*(state: BeaconState): Epoch =
|
||||||
## Return the previous epoch of the given ``state``.
|
## Return the previous epoch of the given ``state``.
|
||||||
## Return the current epoch if it's genesis epoch.
|
## Return the current epoch if it's genesis epoch.
|
||||||
|
@ -149,7 +149,7 @@ func get_epoch_start_shard*(state: BeaconState, epoch: Epoch): Shard =
|
||||||
SHARD_COUNT
|
SHARD_COUNT
|
||||||
return shard
|
return shard
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.1/specs/core/0_beacon-chain.md#compute_committee
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#compute_committee
|
||||||
func compute_committee(indices: seq[ValidatorIndex], seed: Eth2Digest,
|
func compute_committee(indices: seq[ValidatorIndex], seed: Eth2Digest,
|
||||||
index: uint64, count: uint64): seq[ValidatorIndex] =
|
index: uint64, count: uint64): seq[ValidatorIndex] =
|
||||||
let
|
let
|
||||||
|
@ -212,7 +212,7 @@ iterator get_crosslink_committees_at_slot_cached*(
|
||||||
cache.crosslink_committee_cache[key] = result
|
cache.crosslink_committee_cache[key] = result
|
||||||
for v in result: yield v
|
for v in result: yield v
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.1/specs/core/0_beacon-chain.md#get_beacon_proposer_index
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#get_beacon_proposer_index
|
||||||
func get_beacon_proposer_index*(state: BeaconState): ValidatorIndex =
|
func get_beacon_proposer_index*(state: BeaconState): ValidatorIndex =
|
||||||
# Return the current beacon proposer index.
|
# Return the current beacon proposer index.
|
||||||
const
|
const
|
||||||
|
|
|
@ -171,56 +171,6 @@ proc processProposerSlashings(
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#verify_slashable_attestation
|
|
||||||
func verify_slashable_attestation(state: BeaconState, slashable_attestation: IndexedAttestation): bool =
|
|
||||||
# Verify validity of ``slashable_attestation`` fields.
|
|
||||||
|
|
||||||
if anyIt(slashable_attestation.custody_bitfield.bits, it != 0): # [TO BE REMOVED IN PHASE 1]
|
|
||||||
return false
|
|
||||||
|
|
||||||
if len(slashable_attestation.validator_indices) == 0:
|
|
||||||
return false
|
|
||||||
|
|
||||||
for i in 0 ..< (len(slashable_attestation.validator_indices) - 1):
|
|
||||||
if slashable_attestation.validator_indices[i] >= slashable_attestation.validator_indices[i + 1]:
|
|
||||||
return false
|
|
||||||
|
|
||||||
if not verify_bitfield(slashable_attestation.custody_bitfield,
|
|
||||||
len(slashable_attestation.validator_indices)):
|
|
||||||
return false
|
|
||||||
|
|
||||||
if len(slashable_attestation.validator_indices) > MAX_INDICES_PER_ATTESTATION:
|
|
||||||
return false
|
|
||||||
|
|
||||||
var
|
|
||||||
custody_bit_0_indices: seq[uint64] = @[]
|
|
||||||
custody_bit_1_indices: seq[uint64] = @[]
|
|
||||||
|
|
||||||
for i, validator_index in slashable_attestation.validator_indices:
|
|
||||||
if not get_bitfield_bit(slashable_attestation.custody_bitfield, i):
|
|
||||||
custody_bit_0_indices.add(validator_index)
|
|
||||||
else:
|
|
||||||
custody_bit_1_indices.add(validator_index)
|
|
||||||
|
|
||||||
bls_verify_multiple(
|
|
||||||
@[
|
|
||||||
bls_aggregate_pubkeys(mapIt(custody_bit_0_indices, state.validator_registry[it.int].pubkey)),
|
|
||||||
bls_aggregate_pubkeys(mapIt(custody_bit_1_indices, state.validator_registry[it.int].pubkey)),
|
|
||||||
],
|
|
||||||
@[
|
|
||||||
hash_tree_root(AttestationDataAndCustodyBit(
|
|
||||||
data: slashable_attestation.data, custody_bit: false)),
|
|
||||||
hash_tree_root(AttestationDataAndCustodyBit(
|
|
||||||
data: slashable_attestation.data, custody_bit: true)),
|
|
||||||
],
|
|
||||||
slashable_attestation.aggregate_signature,
|
|
||||||
get_domain(
|
|
||||||
state,
|
|
||||||
DOMAIN_ATTESTATION,
|
|
||||||
slot_to_epoch(slashable_attestation.data.slot),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#is_slashable_attestation_data
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#is_slashable_attestation_data
|
||||||
func is_slashable_attestation_data(
|
func is_slashable_attestation_data(
|
||||||
data_1: AttestationData, data_2: AttestationData): bool =
|
data_1: AttestationData, data_2: AttestationData): bool =
|
||||||
|
@ -233,13 +183,14 @@ func is_slashable_attestation_data(
|
||||||
(data_1.source_epoch < data_2.source_epoch and
|
(data_1.source_epoch < data_2.source_epoch and
|
||||||
data_2.target_epoch < data_1.target_epoch)
|
data_2.target_epoch < data_1.target_epoch)
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#attester-slashings
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#attester-slashings
|
||||||
proc processAttesterSlashings(state: var BeaconState, blck: BeaconBlock): bool =
|
proc processAttesterSlashings(state: var BeaconState, blck: BeaconBlock): bool =
|
||||||
# Process ``AttesterSlashing`` operation.
|
# Process ``AttesterSlashing`` operation.
|
||||||
if len(blck.body.attester_slashings) > MAX_ATTESTER_SLASHINGS:
|
if len(blck.body.attester_slashings) > MAX_ATTESTER_SLASHINGS:
|
||||||
notice "CaspSlash: too many!"
|
notice "CaspSlash: too many!"
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
result = true
|
||||||
for attester_slashing in blck.body.attester_slashings:
|
for attester_slashing in blck.body.attester_slashings:
|
||||||
let
|
let
|
||||||
attestation_1 = attester_slashing.attestation_1
|
attestation_1 = attester_slashing.attestation_1
|
||||||
|
@ -250,32 +201,32 @@ proc processAttesterSlashings(state: var BeaconState, blck: BeaconBlock): bool =
|
||||||
notice "CaspSlash: surround or double vote check failed"
|
notice "CaspSlash: surround or double vote check failed"
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if not verify_slashable_attestation(state, attestation_1):
|
if not verify_indexed_attestation(state, attestation_1):
|
||||||
notice "CaspSlash: invalid votes 1"
|
notice "CaspSlash: invalid votes 1"
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if not verify_slashable_attestation(state, attestation_2):
|
if not verify_indexed_attestation(state, attestation_2):
|
||||||
notice "CaspSlash: invalid votes 2"
|
notice "CaspSlash: invalid votes 2"
|
||||||
return false
|
return false
|
||||||
|
|
||||||
var slashed_any = false
|
var slashed_any = false
|
||||||
|
|
||||||
let
|
## TODO there's a lot of sorting/set construction here and
|
||||||
indices2 = toSet(attestation_2.validator_indices)
|
## verify_indexed_attestation, but go by spec unless there
|
||||||
slashable_indices =
|
## is compelling perf evidence otherwise.
|
||||||
attestation_1.validator_indices.filterIt(
|
let attesting_indices_1 =
|
||||||
it in indices2 and not state.validator_registry[it.int].slashed)
|
attestation_1.custody_bit_0_indices & attestation_1.custody_bit_1_indices
|
||||||
|
let attesting_indices_2 =
|
||||||
|
attestation_2.custody_bit_0_indices & attestation_2.custody_bit_1_indices
|
||||||
|
for index in sorted(toSeq(intersection(toSet(attesting_indices_1),
|
||||||
|
toSet(attesting_indices_2)).items), system.cmp):
|
||||||
|
if is_slashable_validator(state.validator_registry[index.int],
|
||||||
|
get_current_epoch(state)):
|
||||||
|
slash_validator(state, index.ValidatorIndex)
|
||||||
|
slashed_any = true
|
||||||
|
result = result and slashed_any
|
||||||
|
|
||||||
if not (len(slashable_indices) >= 1):
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#attestations
|
||||||
notice "CaspSlash: no intersection"
|
|
||||||
return false
|
|
||||||
|
|
||||||
for index in slashable_indices:
|
|
||||||
slash_validator(state, index.ValidatorIndex)
|
|
||||||
|
|
||||||
true
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.0/specs/core/0_beacon-chain.md#attestations
|
|
||||||
proc processAttestations(
|
proc processAttestations(
|
||||||
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool =
|
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool =
|
||||||
## Each block includes a number of attestations that the proposer chose. Each
|
## Each block includes a number of attestations that the proposer chose. Each
|
||||||
|
@ -296,6 +247,7 @@ proc processAttestations(
|
||||||
var committee_count_cache = initTable[Epoch, uint64]()
|
var committee_count_cache = initTable[Epoch, uint64]()
|
||||||
|
|
||||||
for attestation in blck.body.attestations:
|
for attestation in blck.body.attestations:
|
||||||
|
# Caching
|
||||||
let
|
let
|
||||||
epoch = attestation.data.target_epoch
|
epoch = attestation.data.target_epoch
|
||||||
committee_count = if epoch in committee_count_cache:
|
committee_count = if epoch in committee_count_cache:
|
||||||
|
@ -303,6 +255,8 @@ proc processAttestations(
|
||||||
else:
|
else:
|
||||||
get_epoch_committee_count(state, epoch)
|
get_epoch_committee_count(state, epoch)
|
||||||
committee_count_cache[epoch] = committee_count
|
committee_count_cache[epoch] = committee_count
|
||||||
|
|
||||||
|
# Spec content
|
||||||
let attestation_slot =
|
let attestation_slot =
|
||||||
get_attestation_slot(state, attestation, committee_count)
|
get_attestation_slot(state, attestation, committee_count)
|
||||||
let pending_attestation = PendingAttestation(
|
let pending_attestation = PendingAttestation(
|
||||||
|
@ -312,7 +266,7 @@ proc processAttestations(
|
||||||
proposer_index: get_beacon_proposer_index(state),
|
proposer_index: get_beacon_proposer_index(state),
|
||||||
)
|
)
|
||||||
|
|
||||||
if slot_to_epoch(attestation.data.slot) == get_current_epoch(state):
|
if attestation.data.target_epoch == get_current_epoch(state):
|
||||||
state.current_epoch_attestations.add(pending_attestation)
|
state.current_epoch_attestations.add(pending_attestation)
|
||||||
else:
|
else:
|
||||||
state.previous_epoch_attestations.add(pending_attestation)
|
state.previous_epoch_attestations.add(pending_attestation)
|
||||||
|
@ -520,6 +474,20 @@ proc processBlock(
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|
||||||
|
# TODO this cached version corresponds to the blob/v0.5.1ish get_attesting_indices
|
||||||
|
# rm/make consistent with 0.6 version above
|
||||||
|
func get_attesting_indices_cached(
|
||||||
|
state: BeaconState,
|
||||||
|
attestations: openArray[PendingAttestation], cache: var StateCache):
|
||||||
|
HashSet[ValidatorIndex] =
|
||||||
|
# Union of attesters that participated in some attestations
|
||||||
|
result = initSet[ValidatorIndex]()
|
||||||
|
for attestation in attestations:
|
||||||
|
for validator_index in get_attestation_participants_cached(
|
||||||
|
state, attestation.data, attestation.aggregation_bitfield,
|
||||||
|
cache):
|
||||||
|
result.incl validator_index
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#helper-functions-1
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#helper-functions-1
|
||||||
func get_total_active_balance(state: BeaconState): Gwei =
|
func get_total_active_balance(state: BeaconState): Gwei =
|
||||||
return get_total_balance(
|
return get_total_balance(
|
||||||
|
@ -549,38 +517,6 @@ func get_matching_head_attestations(state: BeaconState, epoch: Epoch):
|
||||||
get_block_root_at_slot(state, get_attestation_slot(state, it))
|
get_block_root_at_slot(state, get_attestation_slot(state, it))
|
||||||
)
|
)
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.1/specs/core/0_beacon-chain.md#get_attesting_indices
|
|
||||||
func get_attesting_indices(state: BeaconState,
|
|
||||||
attestation_data: AttestationData,
|
|
||||||
bitfield: BitField): HashSet[ValidatorIndex] =
|
|
||||||
## Return the sorted attesting indices corresponding to ``attestation_data``
|
|
||||||
## and ``bitfield``.
|
|
||||||
## The spec goes through a lot of hoops to sort things, and sometimes
|
|
||||||
## constructs sets from the results here. The basic idea is to always
|
|
||||||
## just do the right thing and keep it in a HashSet.
|
|
||||||
result = initSet[ValidatorIndex]()
|
|
||||||
let committee =
|
|
||||||
get_crosslink_committee(state, attestation_data.target_epoch,
|
|
||||||
attestation_data.shard)
|
|
||||||
doAssert verify_bitfield(bitfield, len(committee))
|
|
||||||
for i, index in committee:
|
|
||||||
if get_bitfield_bit(bitfield, i):
|
|
||||||
result.incl index
|
|
||||||
|
|
||||||
# TODO this cached version corresponds to the blob/v0.5.1ish get_attesting_indices
|
|
||||||
# rm/make consistent with 0.6 version above
|
|
||||||
func get_attesting_indices_cached(
|
|
||||||
state: BeaconState,
|
|
||||||
attestations: openArray[PendingAttestation], cache: var StateCache):
|
|
||||||
HashSet[ValidatorIndex] =
|
|
||||||
# Union of attesters that participated in some attestations
|
|
||||||
result = initSet[ValidatorIndex]()
|
|
||||||
for attestation in attestations:
|
|
||||||
for validator_index in get_attestation_participants_cached(
|
|
||||||
state, attestation.data, attestation.aggregation_bitfield,
|
|
||||||
cache):
|
|
||||||
result.incl validator_index
|
|
||||||
|
|
||||||
func get_unslashed_attesting_indices(
|
func get_unslashed_attesting_indices(
|
||||||
state: BeaconState, attestations: seq[PendingAttestation]):
|
state: BeaconState, attestations: seq[PendingAttestation]):
|
||||||
HashSet[ValidatorIndex] =
|
HashSet[ValidatorIndex] =
|
||||||
|
@ -593,8 +529,8 @@ func get_unslashed_attesting_indices(
|
||||||
if state.validator_registry[index].slashed:
|
if state.validator_registry[index].slashed:
|
||||||
result.excl index
|
result.excl index
|
||||||
|
|
||||||
func get_attesting_balance(state: BeaconState,
|
func get_attesting_balance(
|
||||||
attestations: seq[PendingAttestation]): Gwei =
|
state: BeaconState, attestations: seq[PendingAttestation]): Gwei =
|
||||||
get_total_balance(state, get_unslashed_attesting_indices(state, attestations))
|
get_total_balance(state, get_unslashed_attesting_indices(state, attestations))
|
||||||
|
|
||||||
func get_attesting_balance_cached(
|
func get_attesting_balance_cached(
|
||||||
|
@ -603,7 +539,16 @@ func get_attesting_balance_cached(
|
||||||
get_total_balance(state, get_attesting_indices_cached(
|
get_total_balance(state, get_attesting_indices_cached(
|
||||||
state, attestations, cache))
|
state, attestations, cache))
|
||||||
|
|
||||||
# Not exactly in spec, but for get_winning_root_and_participants
|
func get_crosslink_from_attestation_data(
|
||||||
|
state: BeaconState, data: AttestationData): Crosslink =
|
||||||
|
Crosslink(
|
||||||
|
epoch: min(data.target_epoch,
|
||||||
|
state.current_crosslinks[data.shard].epoch + MAX_CROSSLINK_EPOCHS),
|
||||||
|
previous_crosslink_root: data.previous_crosslink_root,
|
||||||
|
crosslink_data_root: data.crosslink_data_root,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Not exactly in spec, but for get_winning_crosslink_and_attesting_indices
|
||||||
func lowerThan(candidate, current: Eth2Digest): bool =
|
func lowerThan(candidate, current: Eth2Digest): bool =
|
||||||
# return true iff candidate is "lower" than current, per spec rule:
|
# return true iff candidate is "lower" than current, per spec rule:
|
||||||
# "ties broken in favor of lexicographically higher hash
|
# "ties broken in favor of lexicographically higher hash
|
||||||
|
@ -611,52 +556,65 @@ func lowerThan(candidate, current: Eth2Digest): bool =
|
||||||
if v > candidate.data[i]: return true
|
if v > candidate.data[i]: return true
|
||||||
false
|
false
|
||||||
|
|
||||||
func get_winning_root_and_participants(
|
# TODO check/profile if should add cache: var StateCache param
|
||||||
state: BeaconState, shard: Shard, cache: var StateCache):
|
func get_winning_crosslink_and_attesting_indices(
|
||||||
tuple[a: Eth2Digest, b: HashSet[ValidatorIndex]] =
|
state: BeaconState, epoch: Epoch, shard: Shard): tuple[a: Crosslink, b: HashSet[ValidatorIndex]] =
|
||||||
let
|
let
|
||||||
all_attestations =
|
## TODO Z-F could help here
|
||||||
concat(state.current_epoch_attestations,
|
## TODO get_winning_crosslink_and_attesting_indices was profiling hotspot
|
||||||
state.previous_epoch_attestations)
|
shard_attestations =
|
||||||
valid_attestations =
|
|
||||||
filterIt(
|
filterIt(
|
||||||
all_attestations,
|
get_matching_source_attestations(state, epoch), it.data.shard == shard)
|
||||||
it.data.previous_crosslink == state.current_crosslinks[shard])
|
shard_crosslinks =
|
||||||
all_roots = mapIt(valid_attestations, it.data.crosslink_data_root)
|
mapIt(shard_attestations,
|
||||||
|
get_crosslink_from_attestation_data(state, it.data))
|
||||||
|
# TODO this seems like a lot of hash_tree_root'ing on same data
|
||||||
|
candidate_crosslinks =
|
||||||
|
filterIt(shard_crosslinks,
|
||||||
|
hash_tree_root(state.current_crosslinks[shard]) in
|
||||||
|
# TODO pointless memory allocation, etc.
|
||||||
|
@[it.previous_crosslink_root, hash_tree_root(it)])
|
||||||
|
|
||||||
# handle when no attestations for shard available
|
if len(candidate_crosslinks) == 0:
|
||||||
if len(all_roots) == 0:
|
return (Crosslink(), initSet[ValidatorIndex]())
|
||||||
return (ZERO_HASH, initSet[ValidatorIndex]())
|
|
||||||
|
|
||||||
# 0.5.1 spec has less-than-ideal get_attestations_for nested function.
|
## TODO check if should cache this again, as with 0.5
|
||||||
var attestations_for = initTable[Eth2Digest, seq[PendingAttestation]]()
|
## var attestations_for = initTable[Eth2Digest, seq[PendingAttestation]]()
|
||||||
for valid_attestation in valid_attestations:
|
## for valid_attestation in valid_attestations:
|
||||||
if valid_attestation.data.crosslink_data_root in attestations_for:
|
## if valid_attestation.data.crosslink_data_root in attestations_for:
|
||||||
attestations_for[valid_attestation.data.crosslink_data_root].add(
|
## attestations_for[valid_attestation.data.crosslink_data_root].add(
|
||||||
valid_attestation)
|
## valid_attestation)
|
||||||
else:
|
## else:
|
||||||
attestations_for[valid_attestation.data.crosslink_data_root] =
|
## attestations_for[valid_attestation.data.crosslink_data_root] =
|
||||||
@[valid_attestation]
|
## @[valid_attestation]
|
||||||
|
## TODO either way, this nested function not great; {.fastcall.} pragma
|
||||||
|
## not directly applicable either, since it does need some closure
|
||||||
|
func get_attestations_for(crosslink: Crosslink): seq[PendingAttestation] =
|
||||||
|
filterIt(shard_attestations,
|
||||||
|
get_crosslink_from_attestation_data(state, it.data) == crosslink)
|
||||||
|
|
||||||
## Winning crosslink root is the root with the most votes for it, ties broken
|
## Winning crosslink has the crosslink data root with the most balance voting
|
||||||
## in favor of lexicographically higher hash
|
## for it (ties broken lexicographically)
|
||||||
var
|
var
|
||||||
winning_root: Eth2Digest
|
winning_crosslink: Crosslink
|
||||||
winning_root_balance = 0'u64
|
winning_crosslink_balance = 0.Gwei
|
||||||
|
|
||||||
for r in all_roots:
|
for candidate_crosslink in candidate_crosslinks:
|
||||||
let root_balance = get_attesting_balance_cached(
|
## TODO check if should cache this again
|
||||||
state, attestations_for.getOrDefault(r), cache)
|
## let root_balance = get_attesting_balance_cached(
|
||||||
if (root_balance > winning_root_balance or
|
## state, attestations_for.getOrDefault(r), cache)
|
||||||
(root_balance == winning_root_balance and
|
let crosslink_balance =
|
||||||
lowerThan(winning_root, r))):
|
get_attesting_balance(state, get_attestations_for(candidate_crosslink))
|
||||||
winning_root = r
|
if (crosslink_balance > winning_crosslink_balance or
|
||||||
winning_root_balance = root_balance
|
(winning_crosslink_balance == crosslink_balance and
|
||||||
|
lowerThan(winning_crosslink.crosslink_data_root,
|
||||||
|
candidate_crosslink.crosslink_data_root))):
|
||||||
|
winning_crosslink = candidate_crosslink
|
||||||
|
winning_crosslink_balance = crosslink_balance
|
||||||
|
|
||||||
(winning_root,
|
(winning_crosslink,
|
||||||
get_attesting_indices_cached(
|
get_unslashed_attesting_indices(state,
|
||||||
state,
|
get_attestations_for(winning_crosslink)))
|
||||||
attestations_for.getOrDefault(winning_root), cache))
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#justification-and-finalization
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#justification-and-finalization
|
||||||
func process_justification_and_finalization(state: var BeaconState) =
|
func process_justification_and_finalization(state: var BeaconState) =
|
||||||
|
@ -723,48 +681,32 @@ func process_justification_and_finalization(state: var BeaconState) =
|
||||||
state.finalized_epoch = old_current_justified_epoch
|
state.finalized_epoch = old_current_justified_epoch
|
||||||
state.finalized_root = get_block_root(state, state.finalized_epoch)
|
state.finalized_root = get_block_root(state, state.finalized_epoch)
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.1/specs/core/0_beacon-chain.md#crosslinks
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#crosslinks
|
||||||
func process_crosslinks(
|
func process_crosslinks(state: var BeaconState, per_epoch_cache: var StateCache) =
|
||||||
state: var BeaconState, per_epoch_cache: var StateCache) =
|
## TODO is there a semantic reason for this, or is this just a way to force
|
||||||
let
|
## copying? If so, why not just `list(foo)` or similar? This is strange. In
|
||||||
current_epoch = get_current_epoch(state)
|
## this case, for type reasons, don't do weird
|
||||||
previous_epoch = current_epoch - 1
|
## [c for c in state.current_crosslinks] from spec.
|
||||||
next_epoch = current_epoch + 1
|
state.previous_crosslinks = state.current_crosslinks
|
||||||
|
|
||||||
## TODO is it actually correct to be setting state.current_crosslinks[shard]
|
for epoch_int in get_previous_epoch(state).uint64 ..
|
||||||
## to something pre-GENESIS_EPOCH, ever? I guess the intent is if there are
|
get_current_epoch(state).uint64:
|
||||||
## a quorum of participants for get_epoch_start_slot(previous_epoch), when
|
# This issue comes up regularly -- iterating means an int type,
|
||||||
## state.slot == GENESIS_SLOT, then there will be participants for a quorum
|
# which then needs re-conversion back to specialized type.
|
||||||
## in the current-epoch (i.e. genesis epoch) version of that shard?
|
let epoch = epoch_int.Epoch
|
||||||
#for slot in get_epoch_start_slot(previous_epoch).uint64 ..<
|
for offset in 0'u64 ..< get_epoch_committee_count(state, epoch):
|
||||||
for slot in max(
|
|
||||||
GENESIS_SLOT.uint64, get_epoch_start_slot(previous_epoch).uint64) ..<
|
|
||||||
get_epoch_start_slot(next_epoch).uint64:
|
|
||||||
for cas in get_crosslink_committees_at_slot_cached(
|
|
||||||
state, slot, per_epoch_cache):
|
|
||||||
let
|
let
|
||||||
(crosslink_committee, shard) = cas
|
shard = (get_epoch_start_shard(state, epoch) + offset) mod SHARD_COUNT
|
||||||
|
crosslink_committee = get_crosslink_committee(state, epoch, shard)
|
||||||
# In general, it'll loop over the same shards twice, and
|
# In general, it'll loop over the same shards twice, and
|
||||||
# get_winning_root_and_participants is defined to return
|
# get_winning_root_and_participants is defined to return
|
||||||
# the same results from the previous epoch as current.
|
# the same results from the previous epoch as current.
|
||||||
(winning_root, participants) =
|
# TODO cache like before, in 0.5 version of this function
|
||||||
if shard notin per_epoch_cache.winning_root_participants_cache:
|
(winning_crosslink, attesting_indices) =
|
||||||
get_winning_root_and_participants(state, shard, per_epoch_cache)
|
get_winning_crosslink_and_attesting_indices(state, epoch, shard)
|
||||||
else:
|
if 3'u64 * get_total_balance(state, attesting_indices) >=
|
||||||
(ZERO_HASH, per_epoch_cache.winning_root_participants_cache[shard])
|
2'u64 * get_total_balance(state, crosslink_committee):
|
||||||
participating_balance = get_total_balance(state, participants)
|
state.current_crosslinks[shard] = winning_crosslink
|
||||||
total_balance = get_total_balance(state, crosslink_committee)
|
|
||||||
|
|
||||||
per_epoch_cache.winning_root_participants_cache[shard] = participants
|
|
||||||
|
|
||||||
if 3'u64 * participating_balance >= 2'u64 * total_balance:
|
|
||||||
# Check not from spec; seems kludgy
|
|
||||||
doAssert slot >= GENESIS_SLOT
|
|
||||||
|
|
||||||
state.current_crosslinks[shard] = Crosslink(
|
|
||||||
epoch: slot_to_epoch(slot),
|
|
||||||
crosslink_data_root: winning_root
|
|
||||||
)
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#rewards-and-penalties
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#rewards-and-penalties
|
||||||
func get_base_reward(state: BeaconState, index: ValidatorIndex): Gwei =
|
func get_base_reward(state: BeaconState, index: ValidatorIndex): Gwei =
|
||||||
|
@ -775,7 +717,7 @@ func get_base_reward(state: BeaconState, index: ValidatorIndex): Gwei =
|
||||||
state.validator_registry[index].effective_balance div adjusted_quotient div
|
state.validator_registry[index].effective_balance div adjusted_quotient div
|
||||||
BASE_REWARDS_PER_EPOCH
|
BASE_REWARDS_PER_EPOCH
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.1/specs/core/0_beacon-chain.md#rewards-and-penalties
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#rewards-and-penalties
|
||||||
func get_attestation_deltas(state: BeaconState):
|
func get_attestation_deltas(state: BeaconState):
|
||||||
tuple[a: seq[Gwei], b: seq[Gwei]] =
|
tuple[a: seq[Gwei], b: seq[Gwei]] =
|
||||||
let
|
let
|
||||||
|
@ -846,40 +788,34 @@ func get_attestation_deltas(state: BeaconState):
|
||||||
|
|
||||||
(rewards, penalties)
|
(rewards, penalties)
|
||||||
|
|
||||||
# blob/0.5.1
|
# TODO re-cache this one, as under 0.5 version, if profiling suggests it
|
||||||
func get_crosslink_deltas(state: BeaconState, cache: var StateCache):
|
func get_crosslink_deltas(state: BeaconState, cache: var StateCache):
|
||||||
tuple[a: seq[Gwei], b: seq[Gwei]] =
|
tuple[a: seq[Gwei], b: seq[Gwei]] =
|
||||||
|
|
||||||
var
|
var
|
||||||
rewards = repeat(0'u64, len(state.validator_registry))
|
rewards = repeat(0'u64, len(state.validator_registry))
|
||||||
penalties = repeat(0'u64, len(state.validator_registry))
|
penalties = repeat(0'u64, len(state.validator_registry))
|
||||||
let
|
let epoch = get_previous_epoch(state)
|
||||||
previous_epoch_start_slot =
|
for offset in 0'u64 ..< get_epoch_committee_count(state, epoch):
|
||||||
get_epoch_start_slot(get_previous_epoch(state))
|
let
|
||||||
current_epoch_start_slot =
|
shard = (get_epoch_start_shard(state, epoch) + offset) mod SHARD_COUNT
|
||||||
get_epoch_start_slot(get_current_epoch(state))
|
crosslink_committee = get_crosslink_committee(state, epoch, shard)
|
||||||
for slot in previous_epoch_start_slot.uint64 ..<
|
(winning_crosslink, attesting_indices) =
|
||||||
current_epoch_start_slot.uint64:
|
get_winning_crosslink_and_attesting_indices(state, epoch, shard)
|
||||||
for cas in get_crosslink_committees_at_slot_cached(state, slot, cache):
|
attesting_balance = get_total_balance(state, attesting_indices)
|
||||||
let
|
committee_balance = get_total_balance(state, crosslink_committee)
|
||||||
(crosslink_committee, shard) = cas
|
for index in crosslink_committee:
|
||||||
(winning_root, participants) =
|
let base_reward = get_base_reward(state, index)
|
||||||
if shard notin cache.winning_root_participants_cache:
|
if index in attesting_indices:
|
||||||
get_winning_root_and_participants(state, shard, cache)
|
rewards[index] +=
|
||||||
else:
|
get_base_reward(state, index) * attesting_balance div
|
||||||
(ZERO_HASH, cache.winning_root_participants_cache[shard])
|
committee_balance
|
||||||
participating_balance = get_total_balance(state, participants)
|
else:
|
||||||
total_balance = get_total_balance(state, crosslink_committee)
|
penalties[index] += get_base_reward(state, index)
|
||||||
for index in crosslink_committee:
|
|
||||||
if index in participants:
|
|
||||||
rewards[index] +=
|
|
||||||
get_base_reward(state, index) * participating_balance div
|
|
||||||
total_balance
|
|
||||||
else:
|
|
||||||
penalties[index] += get_base_reward(state, index)
|
|
||||||
|
|
||||||
(rewards, penalties)
|
(rewards, penalties)
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.1/specs/core/0_beacon-chain.md#rewards-and-penalties
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#rewards-and-penalties
|
||||||
func process_rewards_and_penalties(
|
func process_rewards_and_penalties(
|
||||||
state: var BeaconState, cache: var StateCache) =
|
state: var BeaconState, cache: var StateCache) =
|
||||||
let
|
let
|
||||||
|
|
|
@ -42,10 +42,7 @@ func toSlot*(t: BeaconTime): Slot =
|
||||||
Slot(uint64(t) div SECONDS_PER_SLOT)
|
Slot(uint64(t) div SECONDS_PER_SLOT)
|
||||||
|
|
||||||
func toBeaconTime*(c: BeaconClock, t: Time): BeaconTime =
|
func toBeaconTime*(c: BeaconClock, t: Time): BeaconTime =
|
||||||
doAssert t > c.genesis,
|
BeaconTime(times.seconds(t - c.genesis).int64)
|
||||||
"Cannot represent time before genesis, fix BeaconClock"
|
|
||||||
|
|
||||||
BeaconTime(times.seconds(t - c.genesis).uint64)
|
|
||||||
|
|
||||||
func toSlot*(c: BeaconClock, t: Time): Slot =
|
func toSlot*(c: BeaconClock, t: Time): Slot =
|
||||||
c.toBeaconTime(t).toSlot()
|
c.toBeaconTime(t).toSlot()
|
||||||
|
|
|
@ -80,11 +80,6 @@ proc addBlock*(
|
||||||
let proposer_index = get_beacon_proposer_index(state)
|
let proposer_index = get_beacon_proposer_index(state)
|
||||||
state.slot -= 1
|
state.slot -= 1
|
||||||
|
|
||||||
# Ferret out remaining GENESIS_EPOCH == 0 assumptions in test code
|
|
||||||
doAssert allIt(
|
|
||||||
body.attestations,
|
|
||||||
it.data.previous_crosslink.epoch >= GENESIS_EPOCH)
|
|
||||||
|
|
||||||
let
|
let
|
||||||
# Index from the new state, but registry from the old state.. hmm...
|
# Index from the new state, but registry from the old state.. hmm...
|
||||||
proposer = state.validator_registry[proposer_index]
|
proposer = state.validator_registry[proposer_index]
|
||||||
|
@ -182,7 +177,7 @@ proc makeAttestation*(
|
||||||
Attestation(
|
Attestation(
|
||||||
data: data,
|
data: data,
|
||||||
aggregation_bitfield: aggregation_bitfield,
|
aggregation_bitfield: aggregation_bitfield,
|
||||||
aggregate_signature: sig,
|
signature: sig,
|
||||||
custody_bitfield: BitField.init(sac.committee.len)
|
custody_bitfield: BitField.init(sac.committee.len)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue