diff --git a/beacon_chain/attestation_pool.nim b/beacon_chain/attestation_pool.nim index bf55724ec..7a426989a 100644 --- a/beacon_chain/attestation_pool.nim +++ b/beacon_chain/attestation_pool.nim @@ -27,7 +27,7 @@ proc combine*(tgt: var Attestation, src: Attestation, flags: UpdateFlags) = tgt.aggregation_bitfield.combine(src.aggregation_bitfield) if skipValidation notin flags: - tgt.aggregate_signature.combine(src.aggregate_signature) + tgt.signature.combine(src.signature) proc validate( state: BeaconState, attestation: Attestation, flags: UpdateFlags): bool = @@ -38,7 +38,7 @@ proc validate( # 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(): debug "Old attestation", @@ -71,7 +71,7 @@ proc validate( ## the rest; turns into expensive NOP until then. if skipValidation notin flags: let - participants = get_attestation_participants( + participants = get_attesting_indices_seq( state, attestation.data, attestation.aggregation_bitfield) ## TODO when the custody_bitfield assertion-to-emptiness disappears do this @@ -97,9 +97,9 @@ proc validate( hash_tree_root(AttestationDataAndCustodyBit( data: attestation.data, custody_bit: true)), ], - attestation.aggregate_signature, + attestation.signature, get_domain(state, DOMAIN_ATTESTATION, - slot_to_epoch(attestation.data.slot)), + slot_to_epoch(get_attestation_slot(state, attestation))), ): notice "Invalid signature", participants return false @@ -174,14 +174,14 @@ proc add*(pool: var AttestationPool, # TODO inefficient data structures.. let - attestationSlot = attestation.data.slot + attestationSlot = get_attestation_slot(state, attestation) idx = pool.slotIndex(state, attestationSlot) slotData = addr pool.slots[idx] validation = Validation( aggregation_bitfield: attestation.aggregation_bitfield, custody_bitfield: attestation.custody_bitfield, - aggregate_signature: attestation.aggregate_signature) - participants = get_attestation_participants( + aggregate_signature: attestation.signature) + participants = get_attesting_indices_seq( state, attestation.data, validation.aggregation_bitfield) var found = false @@ -196,7 +196,7 @@ proc add*(pool: var AttestationPool, # sets by virtue of not overlapping with some other attestation # and therefore being useful after all? debug "Ignoring subset attestation", - existingParticipants = get_attestation_participants( + existingParticipants = get_attesting_indices_seq( state, a.data, v.aggregation_bitfield), newParticipants = participants found = true @@ -209,7 +209,7 @@ proc add*(pool: var AttestationPool, if it.aggregation_bitfield.isSubsetOf( validation.aggregation_bitfield): debug "Removing subset attestation", - existingParticipants = get_attestation_participants( + existingParticipants = get_attesting_indices_seq( state, a.data, it.aggregation_bitfield), newParticipants = participants false @@ -287,7 +287,7 @@ proc getAttestationsForBlock*( aggregation_bitfield: a.validations[0].aggregation_bitfield, data: a.data, 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 @@ -312,7 +312,7 @@ proc getAttestationsForBlock*( attestation.aggregation_bitfield.combine( v.aggregation_bitfield) attestation.custody_bitfield.combine(v.custody_bitfield) - attestation.aggregate_signature.combine(v.aggregate_signature) + attestation.signature.combine(v.aggregate_signature) result.add(attestation) @@ -324,7 +324,8 @@ proc resolve*(pool: var AttestationPool, state: BeaconState) = var resolved: seq[Attestation] 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) else: if pool.blockPool.get(k).isSome(): diff --git a/beacon_chain/beacon_node.nim b/beacon_chain/beacon_node.nim index dc6fbd0c8..dcf8778ad 100644 --- a/beacon_chain/beacon_node.nim +++ b/beacon_chain/beacon_node.nim @@ -314,7 +314,7 @@ proc sendAttestation(node: BeaconNode, var attestation = Attestation( data: attestationData, - aggregate_signature: validatorSignature, + signature: validatorSignature, aggregation_bitfield: aggregationBitfield, # Stub in phase0 custody_bitfield: BitField.init(committeeLen) @@ -371,7 +371,9 @@ proc proposeBlock(node: BeaconNode, var tmpState = hashedState 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 @@ -405,7 +407,7 @@ proc onAttestation(node: BeaconNode, attestation: Attestation) = # we're on, or that it follows the rules of the protocol debug "Attestation received", 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 # though - maybe we should use the state from the block pointed to by diff --git a/beacon_chain/spec/beaconstate.nim b/beacon_chain/spec/beaconstate.nim index c73105fab..1796114b8 100644 --- a/beacon_chain/spec/beaconstate.nim +++ b/beacon_chain/spec/beaconstate.nim @@ -6,12 +6,12 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - algorithm, chronicles, math, options, sequtils, + algorithm, chronicles, collections/sets, math, options, sequtils, ../extras, ../ssz, ../beacon_node_types, ./bitfield, ./crypto, ./datatypes, ./digest, ./helpers, ./validator, 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 = ## Verify that the given ``leaf`` is on the merkle branch ``proof`` ## starting with the given ``root``. @@ -153,7 +153,7 @@ func initiate_validator_exit*(state: var BeaconState, validator.withdrawable_epoch = 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) = # Slash the validator with index ``index``. let current_epoch = get_current_epoch(state) @@ -168,6 +168,7 @@ func slash_validator*(state: var BeaconState, slashed_index: ValidatorIndex) = let proposer_index = get_beacon_proposer_index(state) + # Spec has whistleblower_index as optional param, but it's never used. whistleblower_index = proposer_index whistleblowing_reward = slashed_balance div WHISTLEBLOWING_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) 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 -func get_temporary_block_header*(blck: BeaconBlock): BeaconBlockHeader = +# 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 = ## Return the block header corresponding to a block with ``state_root`` set ## to ``ZERO_HASH``. BeaconBlockHeader( @@ -186,10 +187,9 @@ func get_temporary_block_header*(blck: BeaconBlock): BeaconBlockHeader = state_root: ZERO_HASH, block_body_root: hash_tree_root(blck.body), # 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 = # Nim default values fill this in mostly correctly. BeaconBlock(slot: GENESIS_SLOT) @@ -283,7 +283,7 @@ func get_initial_beacon_block*(state: BeaconState): BeaconBlock = # 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, attestation: Attestation|PendingAttestation, committee_count: uint64): Slot = @@ -291,7 +291,10 @@ func get_attestation_slot*(state: BeaconState, epoch = attestation.data.target_epoch offset = (attestation.data.shard + 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. 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``. 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 func get_total_balance*(state: BeaconState, validators: auto): Gwei = # Return the combined effective balance of an array of ``validators``. @@ -432,9 +360,133 @@ func process_registry_updates*(state: var BeaconState) = validator.activation_epoch = 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*( state: BeaconState, attestation: Attestation, flags: UpdateFlags): bool = + ## Process ``Attestation`` operation. ## 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 ## be followed! @@ -443,142 +495,45 @@ proc checkAttestation*( if nextSlot in flags: state.slot + 1 else: state.slot - # Can't submit attestations that are too far in history (or in prehistory) - if not (attestation.data.slot >= GENESIS_SLOT): - 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): + let attestation_slot = get_attestation_slot(state, attestation) + if not (attestation_slot + MIN_ATTESTATION_INCLUSION_DELAY <= stateSlot): warn("Attestation too new", - attestation_slot = humaneSlotNum(attestation.data.slot), + attestation_slot = humaneSlotNum(attestation_slot), state_slot = humaneSlotNum(stateSlot)) return - # # Verify that the justified epoch and root is correct - if slot_to_epoch(attestation.data.slot) >= stateSlot.slot_to_epoch(): - # Case 1: current epoch attestations - if not (attestation.data.source_epoch == state.current_justified_epoch): - 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) + if not (stateSlot <= attestation_slot + SLOTS_PER_EPOCH): + warn("Attestation too old", + attestation_slot = humaneSlotNum(attestation_slot), + state_slot = humaneSlotNum(stateSlot)) return - # Attestation must be nonempty! - if not anyIt(attestation.aggregation_bitfield.bits, it != 0): - warn("No signature bits") + # Check target epoch, source epoch, source root, and source crosslink + let data = attestation.data + 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 - # Custody must be empty (to be removed in phase 1) - if not allIt(attestation.custody_bitfield.bits, it == 0): - 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) + ## Check crosslink data root + ## [to be removed in phase 1] if attestation.data.crosslink_data_root != ZERO_HASH: warn("Invalid crosslink data root") return + # Check signature and bitfields + if not verify_indexed_attestation( + state, convert_to_indexed(state, attestation)): + warn("checkAttestation: signature or bitfields incorrect") + return + true proc makeAttestationData*( @@ -600,8 +555,8 @@ proc makeAttestationData*( beacon_block_root: beacon_block_root, target_root: target_root, 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_root: state.current_justified_root, - target_epoch: slot_to_epoch(epoch_start_slot) + target_epoch: slot_to_epoch(state.slot) ) diff --git a/beacon_chain/spec/datatypes.nim b/beacon_chain/spec/datatypes.nim index 2fffc9440..1e3da1e46 100644 --- a/beacon_chain/spec/datatypes.nim +++ b/beacon_chain/spec/datatypes.nim @@ -57,21 +57,15 @@ const ## Spec version we're aiming to be compatible with, right now ## 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 FORK_CHOICE_BALANCE_INCREMENT* = 2'u64^0 * 10'u64^9 # 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 ##\ ## slot_to_epoch(GENESIS_SLOT) - GENESIS_START_SHARD* = 0'u64 ZERO_HASH* = Eth2Digest() - EMPTY_SIGNATURE* = ValidatorSig() type ValidatorIndex* = range[0'u32 .. 0xFFFFFF'u32] # TODO: wrap-around @@ -97,18 +91,16 @@ type attestation_2*: IndexedAttestation ## \ ## 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 - validator_indices*: seq[uint64] ##\ - ## Validator indices + # These probably should be seq[ValidatorIndex], but that throws RLP errors + custody_bit_0_indices*: seq[uint64] + custody_bit_1_indices*: seq[uint64] data*: AttestationData ## \ ## Attestation data - custody_bitfield*: BitField ##\ - ## Custody bitfield - - aggregate_signature*: ValidatorSig ## \ + signature*: ValidatorSig ## \ ## Aggregate signature # 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 - aggregate_signature*: ValidatorSig ##\ + signature*: ValidatorSig ##\ ## 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 + slot*: Slot # TODO remove this, once figure out attestation pool issues # LMD GHOST vote - slot*: Slot beacon_block_root*: Eth2Digest # FFG vote @@ -139,7 +131,7 @@ type # Crosslink vote shard*: uint64 - previous_crosslink*: Crosslink + previous_crosslink_root*: Eth2Digest crosslink_data_root*: Eth2Digest # 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) ) -func shortLog*(v: AttestationData): tuple[ - slot: uint64, beacon_block_root: string, source_epoch: uint64, - target_root: string, source_root: string, shard: uint64, - previous_crosslink_epoch: uint64, previous_crosslink_data_root: string, - crosslink_data_root: string - ] = ( - humaneSlotNum(v.slot), shortLog(v.beacon_block_root), +func shortLog*(v: AttestationData): auto = + ( + shortLog(v.beacon_block_root), humaneEpochNum(v.source_epoch), shortLog(v.target_root), shortLog(v.source_root), - v.shard, humaneEpochNum(v.previous_crosslink.epoch), - shortLog(v.previous_crosslink.crosslink_data_root), + v.shard, v.previous_crosslink_root, shortLog(v.crosslink_data_root) ) diff --git a/beacon_chain/spec/helpers.nim b/beacon_chain/spec/helpers.nim index 1e5f25742..a2b36110e 100644 --- a/beacon_chain/spec/helpers.nim +++ b/beacon_chain/spec/helpers.nim @@ -81,7 +81,7 @@ func get_active_validator_indices*(state: BeaconState, epoch: Epoch): if is_active_validator(val, epoch): 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 = # Return the number of committees at ``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 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, epoch: Epoch): Eth2Digest = ## Returns the randao mix at a recent ``epoch``. - ## ``epoch`` expected to be between - ## (current_epoch - LATEST_ACTIVE_INDEX_ROOTS_LENGTH + ACTIVATION_EXIT_DELAY, - ## current_epoch + ACTIVATION_EXIT_DELAY]. + ## ``epoch`` expected to be between (current_epoch - + ## LATEST_RANDAO_MIXES_LENGTH, current_epoch]. 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 @@ -152,7 +151,7 @@ func int_to_bytes4*(x: uint64): array[4, byte] = result[2] = ((x shr 16) 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*( state: BeaconState, domain_type: SignatureDomain, message_epoch: Epoch): uint64 = ## Return the signature domain (fork version concatenated with domain type) diff --git a/beacon_chain/spec/presets/mainnet.nim b/beacon_chain/spec/presets/mainnet.nim index 89225b0e6..25abeb8d3 100644 --- a/beacon_chain/spec/presets/mainnet.nim +++ b/beacon_chain/spec/presets/mainnet.nim @@ -22,7 +22,7 @@ type {.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 # Misc # --------------------------------------------------------------- @@ -83,16 +83,16 @@ const # 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_SLOT* = 64.Slot + GENESIS_SLOT* = 0.Slot FAR_FUTURE_EPOCH* = (not 0'u64).Epoch # 2^64 - 1 in spec BLS_WITHDRAWAL_PREFIX_BYTE* = 0'u8 # 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 ## TODO consistent time unit across projects, similar to C++ chrono? diff --git a/beacon_chain/spec/presets/minimal.nim b/beacon_chain/spec/presets/minimal.nim index 4d9d818ca..72355c037 100644 --- a/beacon_chain/spec/presets/minimal.nim +++ b/beacon_chain/spec/presets/minimal.nim @@ -26,7 +26,7 @@ type const # 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 SHARD_COUNT* {.intdefine.} = 8 @@ -61,11 +61,11 @@ const # 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 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 BLS_WITHDRAWAL_PREFIX_BYTE* = 0'u8 @@ -89,7 +89,7 @@ const # Changed 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 MIN_VALIDATOR_WITHDRAWABILITY_DELAY* = 2'u64^8 diff --git a/beacon_chain/spec/validator.nim b/beacon_chain/spec/validator.nim index 7dc68fea7..0ab58eb55 100644 --- a/beacon_chain/spec/validator.nim +++ b/beacon_chain/spec/validator.nim @@ -13,8 +13,8 @@ import ./crypto, ./datatypes, ./digest, ./helpers # 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.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#get_shuffled_index +# 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, list_size: uint64, ): seq[ValidatorIndex] = @@ -118,7 +118,7 @@ func get_shuffled_index(index: ValidatorIndex, index_count: uint64, seed: Eth2Di if bit != 0: 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 = ## Return the previous epoch of the given ``state``. ## 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 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, index: uint64, count: uint64): seq[ValidatorIndex] = let @@ -212,7 +212,7 @@ iterator get_crosslink_committees_at_slot_cached*( cache.crosslink_committee_cache[key] = result 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 = # Return the current beacon proposer index. const diff --git a/beacon_chain/state_transition.nim b/beacon_chain/state_transition.nim index 89f712f93..78def2f01 100644 --- a/beacon_chain/state_transition.nim +++ b/beacon_chain/state_transition.nim @@ -171,56 +171,6 @@ proc processProposerSlashings( 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 func is_slashable_attestation_data( 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_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 = # Process ``AttesterSlashing`` operation. if len(blck.body.attester_slashings) > MAX_ATTESTER_SLASHINGS: notice "CaspSlash: too many!" return false + result = true for attester_slashing in blck.body.attester_slashings: let 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" return false - if not verify_slashable_attestation(state, attestation_1): + if not verify_indexed_attestation(state, attestation_1): notice "CaspSlash: invalid votes 1" return false - if not verify_slashable_attestation(state, attestation_2): + if not verify_indexed_attestation(state, attestation_2): notice "CaspSlash: invalid votes 2" return false var slashed_any = false - let - indices2 = toSet(attestation_2.validator_indices) - slashable_indices = - attestation_1.validator_indices.filterIt( - it in indices2 and not state.validator_registry[it.int].slashed) + ## TODO there's a lot of sorting/set construction here and + ## verify_indexed_attestation, but go by spec unless there + ## is compelling perf evidence otherwise. + let attesting_indices_1 = + 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): - 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 +# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#attestations proc processAttestations( state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool = ## 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]() for attestation in blck.body.attestations: + # Caching let epoch = attestation.data.target_epoch committee_count = if epoch in committee_count_cache: @@ -303,6 +255,8 @@ proc processAttestations( else: get_epoch_committee_count(state, epoch) committee_count_cache[epoch] = committee_count + + # Spec content let attestation_slot = get_attestation_slot(state, attestation, committee_count) let pending_attestation = PendingAttestation( @@ -312,7 +266,7 @@ proc processAttestations( 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) else: state.previous_epoch_attestations.add(pending_attestation) @@ -520,6 +474,20 @@ proc processBlock( 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 func get_total_active_balance(state: BeaconState): Gwei = 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)) ) -# 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( state: BeaconState, attestations: seq[PendingAttestation]): HashSet[ValidatorIndex] = @@ -593,8 +529,8 @@ func get_unslashed_attesting_indices( if state.validator_registry[index].slashed: result.excl index -func get_attesting_balance(state: BeaconState, - attestations: seq[PendingAttestation]): Gwei = +func get_attesting_balance( + state: BeaconState, attestations: seq[PendingAttestation]): Gwei = get_total_balance(state, get_unslashed_attesting_indices(state, attestations)) func get_attesting_balance_cached( @@ -603,7 +539,16 @@ func get_attesting_balance_cached( get_total_balance(state, get_attesting_indices_cached( 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 = # return true iff candidate is "lower" than current, per spec rule: # "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 false -func get_winning_root_and_participants( - state: BeaconState, shard: Shard, cache: var StateCache): - tuple[a: Eth2Digest, b: HashSet[ValidatorIndex]] = +# TODO check/profile if should add cache: var StateCache param +func get_winning_crosslink_and_attesting_indices( + state: BeaconState, epoch: Epoch, shard: Shard): tuple[a: Crosslink, b: HashSet[ValidatorIndex]] = let - all_attestations = - concat(state.current_epoch_attestations, - state.previous_epoch_attestations) - valid_attestations = + ## TODO Z-F could help here + ## TODO get_winning_crosslink_and_attesting_indices was profiling hotspot + shard_attestations = filterIt( - all_attestations, - it.data.previous_crosslink == state.current_crosslinks[shard]) - all_roots = mapIt(valid_attestations, it.data.crosslink_data_root) + get_matching_source_attestations(state, epoch), it.data.shard == shard) + shard_crosslinks = + 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(all_roots) == 0: - return (ZERO_HASH, initSet[ValidatorIndex]()) + if len(candidate_crosslinks) == 0: + return (Crosslink(), initSet[ValidatorIndex]()) - # 0.5.1 spec has less-than-ideal get_attestations_for nested function. - var attestations_for = initTable[Eth2Digest, seq[PendingAttestation]]() - for valid_attestation in valid_attestations: - if valid_attestation.data.crosslink_data_root in attestations_for: - attestations_for[valid_attestation.data.crosslink_data_root].add( - valid_attestation) - else: - attestations_for[valid_attestation.data.crosslink_data_root] = - @[valid_attestation] + ## TODO check if should cache this again, as with 0.5 + ## var attestations_for = initTable[Eth2Digest, seq[PendingAttestation]]() + ## for valid_attestation in valid_attestations: + ## if valid_attestation.data.crosslink_data_root in attestations_for: + ## attestations_for[valid_attestation.data.crosslink_data_root].add( + ## valid_attestation) + ## else: + ## attestations_for[valid_attestation.data.crosslink_data_root] = + ## @[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 - ## in favor of lexicographically higher hash + ## Winning crosslink has the crosslink data root with the most balance voting + ## for it (ties broken lexicographically) var - winning_root: Eth2Digest - winning_root_balance = 0'u64 + winning_crosslink: Crosslink + winning_crosslink_balance = 0.Gwei - for r in all_roots: - let root_balance = get_attesting_balance_cached( - state, attestations_for.getOrDefault(r), cache) - if (root_balance > winning_root_balance or - (root_balance == winning_root_balance and - lowerThan(winning_root, r))): - winning_root = r - winning_root_balance = root_balance + for candidate_crosslink in candidate_crosslinks: + ## TODO check if should cache this again + ## let root_balance = get_attesting_balance_cached( + ## state, attestations_for.getOrDefault(r), cache) + let crosslink_balance = + get_attesting_balance(state, get_attestations_for(candidate_crosslink)) + if (crosslink_balance > winning_crosslink_balance or + (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, - get_attesting_indices_cached( - state, - attestations_for.getOrDefault(winning_root), cache)) + (winning_crosslink, + get_unslashed_attesting_indices(state, + get_attestations_for(winning_crosslink))) # 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) = @@ -723,48 +681,32 @@ func process_justification_and_finalization(state: var BeaconState) = state.finalized_epoch = old_current_justified_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 -func process_crosslinks( - state: var BeaconState, per_epoch_cache: var StateCache) = - let - current_epoch = get_current_epoch(state) - previous_epoch = current_epoch - 1 - next_epoch = current_epoch + 1 +# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#crosslinks +func process_crosslinks(state: var BeaconState, per_epoch_cache: var StateCache) = + ## TODO is there a semantic reason for this, or is this just a way to force + ## copying? If so, why not just `list(foo)` or similar? This is strange. In + ## this case, for type reasons, don't do weird + ## [c for c in state.current_crosslinks] from spec. + state.previous_crosslinks = state.current_crosslinks - ## TODO is it actually correct to be setting state.current_crosslinks[shard] - ## to something pre-GENESIS_EPOCH, ever? I guess the intent is if there are - ## a quorum of participants for get_epoch_start_slot(previous_epoch), when - ## state.slot == GENESIS_SLOT, then there will be participants for a quorum - ## in the current-epoch (i.e. genesis epoch) version of that shard? - #for slot in get_epoch_start_slot(previous_epoch).uint64 ..< - 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): + for epoch_int in get_previous_epoch(state).uint64 .. + get_current_epoch(state).uint64: + # This issue comes up regularly -- iterating means an int type, + # which then needs re-conversion back to specialized type. + let epoch = epoch_int.Epoch + for offset in 0'u64 ..< get_epoch_committee_count(state, epoch): 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 # get_winning_root_and_participants is defined to return # the same results from the previous epoch as current. - (winning_root, participants) = - if shard notin per_epoch_cache.winning_root_participants_cache: - get_winning_root_and_participants(state, shard, per_epoch_cache) - else: - (ZERO_HASH, per_epoch_cache.winning_root_participants_cache[shard]) - participating_balance = get_total_balance(state, participants) - 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 - ) + # TODO cache like before, in 0.5 version of this function + (winning_crosslink, attesting_indices) = + get_winning_crosslink_and_attesting_indices(state, epoch, shard) + if 3'u64 * get_total_balance(state, attesting_indices) >= + 2'u64 * get_total_balance(state, crosslink_committee): + state.current_crosslinks[shard] = winning_crosslink # 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 = @@ -775,7 +717,7 @@ func get_base_reward(state: BeaconState, index: ValidatorIndex): Gwei = state.validator_registry[index].effective_balance div adjusted_quotient div 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): tuple[a: seq[Gwei], b: seq[Gwei]] = let @@ -846,40 +788,34 @@ func get_attestation_deltas(state: BeaconState): (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): tuple[a: seq[Gwei], b: seq[Gwei]] = + var rewards = repeat(0'u64, len(state.validator_registry)) penalties = repeat(0'u64, len(state.validator_registry)) - let - previous_epoch_start_slot = - get_epoch_start_slot(get_previous_epoch(state)) - current_epoch_start_slot = - get_epoch_start_slot(get_current_epoch(state)) - for slot in previous_epoch_start_slot.uint64 ..< - current_epoch_start_slot.uint64: - for cas in get_crosslink_committees_at_slot_cached(state, slot, cache): - let - (crosslink_committee, shard) = cas - (winning_root, participants) = - if shard notin cache.winning_root_participants_cache: - get_winning_root_and_participants(state, shard, cache) - else: - (ZERO_HASH, cache.winning_root_participants_cache[shard]) - participating_balance = get_total_balance(state, participants) - total_balance = get_total_balance(state, crosslink_committee) - 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) + let epoch = get_previous_epoch(state) + for offset in 0'u64 ..< get_epoch_committee_count(state, epoch): + let + shard = (get_epoch_start_shard(state, epoch) + offset) mod SHARD_COUNT + crosslink_committee = get_crosslink_committee(state, epoch, shard) + (winning_crosslink, attesting_indices) = + get_winning_crosslink_and_attesting_indices(state, epoch, shard) + attesting_balance = get_total_balance(state, attesting_indices) + committee_balance = get_total_balance(state, crosslink_committee) + for index in crosslink_committee: + let base_reward = get_base_reward(state, index) + if index in attesting_indices: + rewards[index] += + get_base_reward(state, index) * attesting_balance div + committee_balance + else: + penalties[index] += get_base_reward(state, index) (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( state: var BeaconState, cache: var StateCache) = let diff --git a/beacon_chain/time.nim b/beacon_chain/time.nim index 5d9dc366f..147305a7c 100644 --- a/beacon_chain/time.nim +++ b/beacon_chain/time.nim @@ -42,10 +42,7 @@ func toSlot*(t: BeaconTime): Slot = Slot(uint64(t) div SECONDS_PER_SLOT) func toBeaconTime*(c: BeaconClock, t: Time): BeaconTime = - doAssert t > c.genesis, - "Cannot represent time before genesis, fix BeaconClock" - - BeaconTime(times.seconds(t - c.genesis).uint64) + BeaconTime(times.seconds(t - c.genesis).int64) func toSlot*(c: BeaconClock, t: Time): Slot = c.toBeaconTime(t).toSlot() diff --git a/tests/testutil.nim b/tests/testutil.nim index bc107efee..a431f901c 100644 --- a/tests/testutil.nim +++ b/tests/testutil.nim @@ -80,11 +80,6 @@ proc addBlock*( let proposer_index = get_beacon_proposer_index(state) 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 # Index from the new state, but registry from the old state.. hmm... proposer = state.validator_registry[proposer_index] @@ -182,7 +177,7 @@ proc makeAttestation*( Attestation( data: data, aggregation_bitfield: aggregation_bitfield, - aggregate_signature: sig, + signature: sig, custody_bitfield: BitField.init(sac.committee.len) )