From 1afdcda62d40d8c6440e6aee9e30aa942e3b7c99 Mon Sep 17 00:00:00 2001 From: Dustin Brody Date: Fri, 8 Mar 2019 17:44:31 +0000 Subject: [PATCH] spec version 0.4.0 update: shard_block_root -> crosslink_data_root; switch from validator.slashed_epoch -> validator.slashed approach; a couple assert -> doAssert; note fix for underflow-prone spec changes in checkAttestation; GENESIS_SLOT change from 2^63 to 2^32; refactor Proposer and ProposalSlashing structure; add signed_root; remove duplicate slashValidator (#159) * spec version 0.4.0 update: shard_block_root -> crosslink_data_root; switch from validator.slashed_epoch -> validator.slashed approach; a couple assert -> doAssert; note fix for underflow-prone spec changes in checkAttestation; GENESIS_SLOT change from 2^63 to 2^32; refactor Proposer and ProposalSlashing structure; add signed_root; remove duplicate slashValidator * re-apply shard_block_root -> crosslink_data_root * remove incorrect humaneSlotNum * add (run-time only, alas) sanity check on signed_root --- beacon_chain/beacon_node.nim | 2 +- beacon_chain/spec/beaconstate.nim | 143 +++++++++++--------------- beacon_chain/spec/datatypes.nim | 86 +++++++++------- beacon_chain/spec/helpers.nim | 2 +- beacon_chain/ssz.nim | 15 +++ beacon_chain/state_transition.nim | 161 ++++++++++++------------------ tests/testutil.nim | 2 +- 7 files changed, 189 insertions(+), 222 deletions(-) diff --git a/beacon_chain/beacon_node.nim b/beacon_chain/beacon_node.nim index 82d78ae40..0186c3f55 100644 --- a/beacon_chain/beacon_node.nim +++ b/beacon_chain/beacon_node.nim @@ -274,7 +274,7 @@ proc makeAttestation(node: BeaconNode, shard: shard, beacon_block_root: node.state.blck.root, epoch_boundary_root: Eth2Digest(), # TODO - shard_block_root: Eth2Digest(), # TODO + crosslink_data_root: Eth2Digest(), # TODO latest_crosslink: state.latest_crosslinks[shard], justified_epoch: state.justified_epoch, justified_block_root: justifiedBlockRoot) diff --git a/beacon_chain/spec/beaconstate.nim b/beacon_chain/spec/beaconstate.nim index 9139bb6f5..5971b3c2e 100644 --- a/beacon_chain/spec/beaconstate.nim +++ b/beacon_chain/spec/beaconstate.nim @@ -16,43 +16,25 @@ func get_effective_balance*(state: BeaconState, index: ValidatorIndex): uint64 = ## validator with the given ``index``. min(state.validator_balances[index], MAX_DEPOSIT_AMOUNT) -# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#validate_proof_of_possession -func validate_proof_of_possession(state: BeaconState, - pubkey: ValidatorPubKey, - proof_of_possession: ValidatorSig, - withdrawal_credentials: Eth2Digest): bool = - let proof_of_possession_data = DepositInput( - pubkey: pubkey, - withdrawal_credentials: withdrawal_credentials, - proof_of_possession: ValidatorSig(), - ) - - bls_verify( - pubkey, - hash_tree_root_final(proof_of_possession_data).data, - proof_of_possession, - get_domain( - state.fork, - get_current_epoch(state), - DOMAIN_DEPOSIT, - ) - ) - -# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#process_deposit -func process_deposit(state: var BeaconState, - pubkey: ValidatorPubKey, - amount: Gwei, - proof_of_possession: ValidatorSig, - withdrawal_credentials: Eth2Digest) = +# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#process_deposit +func process_deposit(state: var BeaconState, deposit: Deposit) = ## Process a deposit from Ethereum 1.0. + ## Note that this function mutates ``state``. - if false: - # TODO return error; currently, just fails if ever called - # but hadn't been set up to run at all - doAssert validate_proof_of_possession( - state, pubkey, proof_of_possession, withdrawal_credentials) + let deposit_input = deposit.deposit_data.deposit_input - let validator_pubkeys = state.validator_registry.mapIt(it.pubkey) + ## if not validate_proof_of_possession( + ## state, pubkey, proof_of_possession, withdrawal_credentials): + ## return + ## TODO re-enable (but it wasn't running to begin with, and + ## PoP isn't really a phase 0 concern, so this isn't meaningful + ## regardless. + + let + validator_pubkeys = state.validator_registry.mapIt(it.pubkey) + pubkey = deposit_input.pubkey + amount = deposit.deposit_data.amount + withdrawal_credentials = deposit_input.withdrawal_credentials if pubkey notin validator_pubkeys: # Add new validator @@ -62,8 +44,8 @@ func process_deposit(state: var BeaconState, activation_epoch: FAR_FUTURE_EPOCH, exit_epoch: FAR_FUTURE_EPOCH, withdrawable_epoch: FAR_FUTURE_EPOCH, - slashed_epoch: FAR_FUTURE_EPOCH, - status_flags: 0, + initiated_exit: false, + slashed: false, ) ## Note: In phase 2 registry indices that have been withdrawn for a long @@ -74,7 +56,7 @@ func process_deposit(state: var BeaconState, # Increase balance by deposit amount let index = validator_pubkeys.find(pubkey) let validator = addr state.validator_registry[index] - assert state.validator_registry[index].withdrawal_credentials == + doAssert state.validator_registry[index].withdrawal_credentials == withdrawal_credentials state.validator_balances[index] += amount @@ -99,13 +81,13 @@ func activate_validator(state: var BeaconState, else: get_entry_exit_effect_epoch(get_current_epoch(state)) -# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#initiate_validator_exit +# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#initiate_validator_exit func initiate_validator_exit*(state: var BeaconState, index: ValidatorIndex) = ## Initiate exit for the validator with the given ``index``. ## Note that this function mutates ``state``. var validator = addr state.validator_registry[index] - validator.status_flags = validator.status_flags or INITIATED_EXIT + validator.initiated_exit = true # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#exit_validator func exit_validator*(state: var BeaconState, @@ -125,6 +107,7 @@ func reduce_balance*(balance: var uint64, amount: uint64) = # Not in spec, but useful to avoid underflow. balance -= min(amount, balance) +# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#slash_validator func slash_validator*(state: var BeaconState, index: ValidatorIndex) = ## Slash the validator with index ``index``. ## Note that this function mutates ``state``. @@ -143,13 +126,14 @@ func slash_validator*(state: var BeaconState, index: ValidatorIndex) = whistleblower_reward = get_effective_balance(state, index) div WHISTLEBLOWER_REWARD_QUOTIENT + ## TODO here and elsewhere, if reduce_balance can't reduce balance by full + ## whistleblower_reward (to prevent underflow) should increase be full? It + ## seems wrong for the amounts to differ. state.validator_balances[whistleblower_index] += whistleblower_reward reduce_balance(state.validator_balances[index], whistleblower_reward) - validator.slashed_epoch = get_current_epoch(state) - - # Spec bug in v0.3.0, fixed since: it has LATEST_PENALIZED_EXIT_LENGTH - validator.withdrawable_epoch = get_current_epoch(state) + - LATEST_SLASHED_EXIT_LENGTH + validator.slashed = true + validator.withdrawable_epoch = + get_current_epoch(state) + LATEST_SLASHED_EXIT_LENGTH func update_shuffling_cache*(state: var BeaconState) = let @@ -171,9 +155,9 @@ func update_shuffling_cache*(state: var BeaconState) = state.shuffling_cache.shuffling_1 = shuffling_seq state.shuffling_cache.index = 1 - state.shuffling_cache.index -# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#on-genesis +# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#on-genesis func get_genesis_beacon_state*( - initial_validator_deposits: openArray[Deposit], + genesis_validator_deposits: openArray[Deposit], genesis_time: uint64, latest_eth1_data: Eth1Data, flags: UpdateFlags = {}): BeaconState = @@ -191,7 +175,7 @@ func get_genesis_beacon_state*( # validators - there needs to be at least one member in each committee - # good to know for testing, though arguably the system is not that useful at # at that point :) - assert initial_validator_deposits.len >= SLOTS_PER_EPOCH + doAssert genesis_validator_deposits.len >= SLOTS_PER_EPOCH var state = BeaconState( # Misc @@ -234,19 +218,13 @@ func get_genesis_beacon_state*( for i in 0 ..< SHARD_COUNT: state.latest_crosslinks[i] = Crosslink( - epoch: GENESIS_EPOCH, shard_block_root: ZERO_HASH) + epoch: GENESIS_EPOCH, crosslink_data_root: ZERO_HASH) - # Process initial deposits - for deposit in initial_validator_deposits: - process_deposit( - state, - deposit.deposit_data.deposit_input.pubkey, - deposit.deposit_data.amount, - deposit.deposit_data.deposit_input.proof_of_possession, - deposit.deposit_data.deposit_input.withdrawal_credentials, - ) + # Process genesis deposits + for deposit in genesis_validator_deposits: + process_deposit(state, deposit) - # Process initial activations + # Process genesis activations for validator_index in 0 ..< state.validator_registry.len: let vi = validator_index.ValidatorIndex if get_effective_balance(state, vi) >= MAX_DEPOSIT_AMOUNT: @@ -258,6 +236,7 @@ func get_genesis_beacon_state*( state.latest_active_index_roots[index] = genesis_active_index_root state.current_shuffling_seed = generate_seed(state, GENESIS_EPOCH) + # Not in spec. update_shuffling_cache(state) state @@ -317,15 +296,13 @@ func get_attestation_participants*(state: BeaconState, if aggregation_bit == 1: result.add(validator_index) -# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#ejections -func process_ejections*(state: var BeaconState, active_validator_indices: auto) = +# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#ejections +func process_ejections*(state: var BeaconState) = ## Iterate through the validator registry and eject active validators with ## balance below ``EJECTION_BALANCE`` - ## - ## `active_validator_indices` was already computed in `processEpoch`. Reuse. - ## Spec recomputes. This is called before validator reshuffling, so use that - ## cached version from beginning of `processEpoch`. - for index in active_validator_indices: + for index in get_active_validator_indices( + # Spec bug in 0.4.0: is just current_epoch(state) + state.validator_registry, get_current_epoch(state)): if state.validator_balances[index] < EJECTION_BALANCE: exit_validator(state, index) @@ -334,7 +311,7 @@ func get_total_balance*(state: BeaconState, validators: auto): Gwei = # Return the combined effective balance of an array of validators. foldl(validators, a + get_effective_balance(state, b), 0'u64) -# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#validator-registry-and-shuffling-seed-data +# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#validator-registry-and-shuffling-seed-data func update_validator_registry*(state: var BeaconState) = ## Update validator registry. ## Note that this function mutates ``state``. @@ -355,7 +332,7 @@ func update_validator_registry*(state: var BeaconState) = # Activate validators within the allowable balance churn var balance_churn = 0'u64 for index, validator in state.validator_registry: - if validator.activation_epoch > get_entry_exit_effect_epoch(current_epoch) and + if validator.activation_epoch == FAR_FUTURE_EPOCH and state.validator_balances[index] >= MAX_DEPOSIT_AMOUNT: # Check the balance churn would be within the allowance balance_churn += get_effective_balance(state, index.ValidatorIndex) @@ -368,8 +345,8 @@ func update_validator_registry*(state: var BeaconState) = # Exit validators within the allowable balance churn balance_churn = 0 for index, validator in state.validator_registry: - if validator.exit_epoch > get_entry_exit_effect_epoch(current_epoch) and - ((validator.status_flags and INITIATED_EXIT) == INITIATED_EXIT): + if validator.activation_epoch == FAR_FUTURE_EPOCH and + validator.initiated_exit: # Check the balance churn would be within the allowance balance_churn += get_effective_balance(state, index.ValidatorIndex) if balance_churn > max_balance_churn: @@ -380,25 +357,26 @@ func update_validator_registry*(state: var BeaconState) = state.validator_registry_update_epoch = current_epoch -# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#attestations-1 +# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#attestations-1 proc checkAttestation*( state: BeaconState, attestation: Attestation, flags: UpdateFlags): bool = ## 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! - # Can't underflow, because GENESIS_SLOT > MIN_ATTESTATION_INCLUSION_DELAY - doAssert GENESIS_SLOT > MIN_ATTESTATION_INCLUSION_DELAY + if not (attestation.data.slot >= GENESIS_SLOT): + warn("Attestation predates genesis slot", + attestation_slot = attestation.data.slot, + state_slot = humaneSlotNum(state.slot)) + return - if not (attestation.data.slot <= state.slot - MIN_ATTESTATION_INCLUSION_DELAY): + if not (attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot): warn("Attestation too new", attestation_slot = humaneSlotNum(attestation.data.slot), state_slot = humaneSlotNum(state.slot)) return - # Can't underflow, because GENESIS_SLOT > MIN_ATTESTATION_INCLUSION_DELAY - if not (state.slot - MIN_ATTESTATION_INCLUSION_DELAY < - attestation.data.slot + SLOTS_PER_EPOCH): + if not (state.slot < attestation.data.slot + SLOTS_PER_EPOCH): warn("Attestation too old", attestation_slot = humaneSlotNum(attestation.data.slot), state_slot = humaneSlotNum(state.slot)) @@ -428,14 +406,14 @@ proc checkAttestation*( if not (state.latest_crosslinks[attestation.data.shard] in [ attestation.data.latest_crosslink, Crosslink( - shard_block_root: attestation.data.shard_block_root, + crosslink_data_root: attestation.data.crosslink_data_root, epoch: slot_to_epoch(attestation.data.slot))]): warn("Unexpected crosslink shard", state_latest_crosslinks_attestation_data_shard = state.latest_crosslinks[attestation.data.shard], attestation_data_latest_crosslink = attestation.data.latest_crosslink, epoch = humaneEpochNum(slot_to_epoch(attestation.data.slot)), - shard_block_root = attestation.data.shard_block_root) + crosslink_data_root = attestation.data.crosslink_data_root) return assert allIt(attestation.custody_bitfield, it == 0) #TO BE REMOVED IN PHASE 1 @@ -478,9 +456,6 @@ proc checkAttestation*( custody_bit_1_participants: seq[ValidatorIndex] = @[] custody_bit_0_participants = participants - group_public_key = bls_aggregate_pubkeys( - participants.mapIt(state.validator_registry[it].pubkey)) - if skipValidation notin flags: # Verify that aggregate_signature verifies using the group pubkey. assert bls_verify_multiple( @@ -502,8 +477,8 @@ proc checkAttestation*( ) # To be removed in Phase1: - if attestation.data.shard_block_root != ZERO_HASH: - warn("Invalid shard block root") + if attestation.data.crosslink_data_root != ZERO_HASH: + warn("Invalid crosslink data root") return true @@ -517,4 +492,4 @@ func prepare_validator_for_withdrawal*(state: var BeaconState, index: ValidatorI # Bug in 0.3.0 spec; constant got renamed. Use 0.3.0 name. validator.withdrawable_epoch = get_current_epoch(state) + - MIN_VALIDATOR_WITHDRAWAL_DELAY + MIN_VALIDATOR_WITHDRAWABILITY_DELAY diff --git a/beacon_chain/spec/datatypes.nim b/beacon_chain/spec/datatypes.nim index 8a3078c8a..6c5e70b76 100644 --- a/beacon_chain/spec/datatypes.nim +++ b/beacon_chain/spec/datatypes.nim @@ -40,12 +40,12 @@ import # TODO Many of these constants should go into a config object that can be used # to run.. well.. a chain with different constants! const - SPEC_VERSION* = "0.3.0" ## \ + SPEC_VERSION* = "0.4.0" ## \ ## Spec version we're aiming to be compatible with, right now ## TODO: improve this scheme once we can negotiate versions in protocol # Misc - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#misc + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#misc SHARD_COUNT* {.intdefine.} = 1024 ##\ ## Number of shards supported by the network - validators will jump around ## between these shards and provide attestations to their state. @@ -71,18 +71,16 @@ const MAX_INDICES_PER_SLASHABLE_VOTE* = 2^12 ##\ ## votes - MAX_WITHDRAWALS_PER_EPOCH* = 4 # withdrawals - MAX_EXIT_DEQUEUES_PER_EPOCH* = 4 SHUFFLE_ROUND_COUNT* = 90 # Deposit contract - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#deposit-contract + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#deposit-contract DEPOSIT_CONTRACT_TREE_DEPTH* = 2^5 # Gwei values - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#gwei-values + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#gwei-values MIN_DEPOSIT_AMOUNT* = 2'u64^0 * 10'u64^9 ##\ ## Minimum amounth of ETH that can be deposited in one call - deposits can ## be used either to top up an existing validator or commit to a new one @@ -103,9 +101,9 @@ const ## Compile with -d:SLOTS_PER_EPOCH=4 for shorter epochs # Initial values - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#initial-values + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#initial-values GENESIS_FORK_VERSION* = 0'u64 - GENESIS_SLOT* = 2'u64^63 + GENESIS_SLOT* = 2'u64^32 GENESIS_EPOCH* = GENESIS_SLOT div SLOTS_PER_EPOCH # slot_to_epoch(GENESIS_SLOT) GENESIS_START_SHARD* = 0'u64 FAR_FUTURE_EPOCH* = not 0'u64 # 2^64 - 1 in spec @@ -114,7 +112,7 @@ const BLS_WITHDRAWAL_PREFIX_BYTE* = 0'u8 # Time parameters - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#time-parameters + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.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? @@ -130,6 +128,8 @@ const ## wait towards the end of the slot and still have time to publish the ## attestation. + # SLOTS_PER_EPOCH is defined above. + MIN_SEED_LOOKAHEAD* = 1 ##\ ## epochs (~6.4 minutes) @@ -139,18 +139,18 @@ const EPOCHS_PER_ETH1_VOTING_PERIOD* = 2'u64^4 ##\ ## epochs (~1.7 hours) - MIN_VALIDATOR_WITHDRAWAL_DELAY* = 2'u64^8 ##\ + MIN_VALIDATOR_WITHDRAWABILITY_DELAY* = 2'u64^8 ##\ ## epochs (~27 hours) # State list lengths - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#state-list-lengths + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#state-list-lengths LATEST_BLOCK_ROOTS_LENGTH* = 2'u64^13 LATEST_RANDAO_MIXES_LENGTH* = 2'u64^13 LATEST_ACTIVE_INDEX_ROOTS_LENGTH* = 8192 # 2'u64^13, epochs LATEST_SLASHED_EXIT_LENGTH* = 8192 # epochs # Reward and penalty quotients - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#reward-and-penalty-quotients + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#reward-and-penalty-quotients BASE_REWARD_QUOTIENT* = 2'u64^5 ##\ ## The `BASE_REWARD_QUOTIENT` parameter dictates the per-epoch reward. It ## corresponds to ~2.54% annual interest assuming 10 million participating @@ -160,12 +160,8 @@ const INACTIVITY_PENALTY_QUOTIENT* = 2'u64^24 MIN_PENALTY_QUOTIENT* = 32 # 2^5 - # Status flags - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#status-flags - INITIATED_EXIT* = 1'u64 - # Max transactions per block - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#max-transactions-per-block + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#max-transactions-per-block MAX_PROPOSER_SLASHINGS* = 2^4 MAX_ATTESTER_SLASHINGS* = 2^0 MAX_ATTESTATIONS* = 2^7 @@ -184,15 +180,18 @@ type Epoch* = uint64 Gwei* = uint64 - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#proposerslashing + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#proposerslashing ProposerSlashing* = object - proposer_index*: uint64 - proposal_data_1*: ProposalSignedData - proposal_signature_1*: ValidatorSig - proposal_data_2*: ProposalSignedData - proposal_signature_2*: ValidatorSig + proposer_index*: uint64 ##\ + ## Proposer index - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#attesterslashing + proposal_1*: Proposal ##\ + # First proposal + + proposal_2*: Proposal ##\ + # Second proposal + + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#attesterslashing AttesterSlashing* = object slashable_attestation_1*: SlashableAttestation ## \ ## First slashable attestation @@ -227,7 +226,7 @@ type aggregate_signature*: ValidatorSig ##\ ## BLS aggregate signature - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#attestationdata + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#attestationdata AttestationData* = object slot*: uint64 ##\ ## Slot number @@ -241,8 +240,8 @@ type epoch_boundary_root*: Eth2Digest ##\ ## Hash of root of the ancestor at the epoch boundary - shard_block_root*: Eth2Digest ##\ - ## Shard block's hash of root + crosslink_data_root*: Eth2Digest ##\ + ## Data from the shard since the last attestation latest_crosslink*: Crosslink ##\ ## Last crosslink @@ -253,7 +252,7 @@ type justified_block_root*: Eth2Digest ##\ ## Hash of the last justified beacon block - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#attestationdataandcustodybit + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#attestationdataandcustodybit AttestationDataAndCustodyBit* = object data*: AttestationData custody_bit*: bool @@ -360,6 +359,20 @@ type voluntary_exits*: seq[VoluntaryExit] transfers*: seq[Transfer] + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#proposal + Proposal* = object + slot*: uint64 ##\ + ## Slot number + + shard*: uint64 ##\ + ## Shard number (`BEACON_CHAIN_SHARD_NUMBER` for beacon chain) + + block_root*: Eth2Digest ##\ + ## Block root + + signature*: ValidatorSig ##\ + ## Signature + # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#proposalsigneddata ProposalSignedData* = object slot*: uint64 @@ -420,7 +433,7 @@ type # Not in spec. TODO: don't serialize or deserialize this. shuffling_cache*: ShufflingCache - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#validator + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#validator Validator* = object pubkey*: ValidatorPubKey ##\ ## BLS public key @@ -437,18 +450,19 @@ type withdrawable_epoch*: uint64 ##\ ## Epoch when validator is eligible to withdraw - slashed_epoch*: uint64 ##\ - ## Epoch when validator slashed + initiated_exit*: bool ##\ + ## Did the validator initiate an exit - status_flags*: uint64 + slashed*: bool ##\ + ## Was the validator slashed - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#crosslink + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#crosslink Crosslink* = object epoch*: uint64 ##\ ## Epoch number - shard_block_root*: Eth2Digest ##\ - ## Shard block root + crosslink_data_root*: Eth2Digest ##\ + ## Shard data since the previous crosslink # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#pendingattestation PendingAttestation* = object @@ -485,7 +499,7 @@ type Activation = 0 Exit = 1 - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#signature-domains + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#signature-domains SignatureDomain* {.pure.} = enum DOMAIN_DEPOSIT = 0 DOMAIN_ATTESTATION = 1 diff --git a/beacon_chain/spec/helpers.nim b/beacon_chain/spec/helpers.nim index 6b8b576ad..0034442d6 100644 --- a/beacon_chain/spec/helpers.nim +++ b/beacon_chain/spec/helpers.nim @@ -138,7 +138,7 @@ func is_double_vote*(attestation_data_1: AttestationData, target_epoch_2 = slot_to_epoch(attestation_data_2.slot) target_epoch_1 == target_epoch_2 -# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#is_surround_vote +# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#is_surround_vote func is_surround_vote*(attestation_data_1: AttestationData, attestation_data_2: AttestationData): bool = ## Check if ``attestation_data_1`` surrounds ``attestation_data_2``. diff --git a/beacon_chain/ssz.nim b/beacon_chain/ssz.nim index 1f0d01527..78a1b5fc0 100644 --- a/beacon_chain/ssz.nim +++ b/beacon_chain/ssz.nim @@ -290,6 +290,21 @@ func hash_tree_root*[T: object|tuple](x: T): array[32, byte] = for field in x.fields: h.update hash_tree_root(field) +# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/simple-serialize.md#signed-roots +func signed_root*[T: object](x: T, field_name: string): array[32, byte] = + # TODO write tests for this (check vs hash_tree_root) + + var found_field_name = false + + withHash: + for name, field in x.fieldPairs: + if name == field_name: + found_field_name = true + break + h.update hash_tree_root(field) + + doAssert found_field_name + # ################################# # hash_tree_root not part of official spec func hash_tree_root*(x: enum): array[8, byte] = diff --git a/beacon_chain/state_transition.nim b/beacon_chain/state_transition.nim index d608ff7dc..446e1c525 100644 --- a/beacon_chain/state_transition.nim +++ b/beacon_chain/state_transition.nim @@ -106,32 +106,7 @@ func processDepositRoot(state: var BeaconState, blck: BeaconBlock) = vote_count: 1 ) -# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#slashValidator -func slashValidator(state: var BeaconState, index: ValidatorIndex) = - ## Slash the validator of the given ``index``. - ## Note that this function mutates ``state``. - var validator = addr state.validator_registry[index] - - doAssert state.slot < get_epoch_start_slot(validator.withdrawable_epoch) ##\ - ## [TO BE REMOVED IN PHASE 2] - - exit_validator(state, index) - state.latest_slashed_balances[(get_current_epoch(state) mod - LATEST_SLASHED_EXIT_LENGTH).int] += get_effective_balance(state, - index.ValidatorIndex) - - let - whistleblower_index = get_beacon_proposer_index(state, state.slot) - whistleblower_reward = get_effective_balance(state, index) div - WHISTLEBLOWER_REWARD_QUOTIENT - state.validator_balances[whistleblower_index] += whistleblower_reward - reduce_balance(state.validator_balances[index], whistleblower_reward) - validator.slashed_epoch = get_current_epoch(state) - - # v0.3.0 spec bug, fixed later, involving renamed constants. Use v0.3.0 name. - validator.withdrawable_epoch = get_current_epoch(state) + LATEST_SLASHED_EXIT_LENGTH - -# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#proposer-slashings-1 +# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#proposer-slashings-1 proc processProposerSlashings( state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool = if len(blck.body.proposer_slashings) > MAX_PROPOSER_SLASHINGS: @@ -141,48 +116,49 @@ proc processProposerSlashings( for proposer_slashing in blck.body.proposer_slashings: let proposer = state.validator_registry[proposer_slashing.proposer_index.int] + + if not (proposer_slashing.proposal_1.slot == + proposer_slashing.proposal_2.slot): + notice "PropSlash: slot mismatch" + return false + + if not (proposer_slashing.proposal_1.shard == + proposer_slashing.proposal_2.shard): + notice "PropSlash: shard mismatch" + return false + + if not (proposer_slashing.proposal_1.block_root != + proposer_slashing.proposal_2.block_root): + notice "PropSlash: block root mismatch" + return false + + if not (proposer.slashed == false): + notice "PropSlash: slashed proposer" + return false + if skipValidation notin flags: if not bls_verify( proposer.pubkey, - hash_tree_root_final(proposer_slashing.proposal_data_1).data, - proposer_slashing.proposal_signature_1, + signed_root(proposer_slashing.proposal_1, "signature"), + proposer_slashing.proposal_1.signature, get_domain( - state.fork, slot_to_epoch(proposer_slashing.proposal_data_1.slot), + state.fork, slot_to_epoch(proposer_slashing.proposal_1.slot), DOMAIN_PROPOSAL)): notice "PropSlash: invalid signature 1" return false if not bls_verify( proposer.pubkey, - hash_tree_root_final(proposer_slashing.proposal_data_2).data, - proposer_slashing.proposal_signature_2, + signed_root(proposer_slashing.proposal_2, "signature"), + proposer_slashing.proposal_2.signature, get_domain( - state.fork, slot_to_epoch(proposer_slashing.proposal_data_2.slot), + state.fork, slot_to_epoch(proposer_slashing.proposal_2.slot), DOMAIN_PROPOSAL)): notice "PropSlash: invalid signature 2" return false - if not (proposer_slashing.proposal_data_1.slot == - proposer_slashing.proposal_data_2.slot): - notice "PropSlash: slot mismatch" - return false - - if not (proposer_slashing.proposal_data_1.shard == - proposer_slashing.proposal_data_2.shard): - notice "PropSlash: shard mismatch" - return false - - if not (proposer_slashing.proposal_data_1.block_root == - proposer_slashing.proposal_data_2.block_root): - notice "PropSlash: block root mismatch" - return false - - if not (proposer.slashed_epoch > get_current_epoch(state)): - notice "PropSlash: penalized slot" - return false - slashValidator(state, proposer_slashing.proposer_index.ValidatorIndex) - return true + true # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#verify_slashable_attestation func verify_slashable_attestation(state: BeaconState, slashable_attestation: SlashableAttestation): bool = @@ -233,7 +209,7 @@ func verify_slashable_attestation(state: BeaconState, slashable_attestation: Sla ), ) -# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#attester-slashings-1 +# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#attester-slashings-1 proc processAttesterSlashings(state: var BeaconState, blck: BeaconBlock): bool = if len(blck.body.attester_slashings) > MAX_ATTESTER_SLASHINGS: notice "CaspSlash: too many!" @@ -263,7 +239,7 @@ proc processAttesterSlashings(state: var BeaconState, blck: BeaconBlock): bool = return false let - indices2 = slashable_attestation_2.validator_indices + indices2 = toSet(slashable_attestation_2.validator_indices) slashable_indices = slashable_attestation_1.validator_indices.filterIt(it in indices2) @@ -272,7 +248,7 @@ proc processAttesterSlashings(state: var BeaconState, blck: BeaconBlock): bool = return false for index in slashable_indices: - if state.validator_registry[index.int].slashed_epoch > get_current_epoch(state): + if state.validator_registry[index.int].slashed: slash_validator(state, index.ValidatorIndex) true @@ -413,7 +389,7 @@ func processSlot(state: var BeaconState, previous_block_root: Eth2Digest) = ## chain at that time. In case the proposer is missing, it may happen that ## the no block is produced during the slot. - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#slot + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#slot state.slot += 1 # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#block-roots @@ -503,13 +479,13 @@ func boundary_attestations( func lowerThan(candidate, current: Eth2Digest): bool = # return true iff candidate is "lower" than current, per spec rule: - # "ties broken by favoring lower `shard_block_root` values" + # "ties broken by favoring lower `crosslink_data_root` values" # TODO spec - clarify hash ordering.. for i, v in current.data: if v > candidate.data[i]: return true false -# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#validator-registry-and-shuffling-seed-data +# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#validator-registry-and-shuffling-seed-data func process_slashings(state: var BeaconState) = ## Process the slashings. ## Note that this function mutates ``state``. @@ -517,12 +493,12 @@ func process_slashings(state: var BeaconState) = current_epoch = get_current_epoch(state) active_validator_indices = get_active_validator_indices( state.validator_registry, current_epoch) - # TODO 0.3.0 spec doesn't use this helper function? + # 0.4.0 spec doesn't use this helper function? total_balance = get_total_balance(state, active_validator_indices) for index, validator in state.validator_registry: - if current_epoch == - validator.slashed_epoch + LATEST_SLASHED_EXIT_LENGTH div 2: + if validator.slashed and current_epoch == validator.withdrawable_epoch - + LATEST_SLASHED_EXIT_LENGTH div 2: let epoch_index = current_epoch mod LATEST_SLASHED_EXIT_LENGTH total_at_start = state.latest_slashed_balances[ @@ -543,14 +519,12 @@ func process_exit_queue(state: var BeaconState) = func eligible(index: ValidatorIndex): bool = let validator = state.validator_registry[index] # Filter out dequeued validators - if validator.withdrawable_epoch < FAR_FUTURE_EPOCH: + if validator.withdrawable_epoch != FAR_FUTURE_EPOCH: return false # Dequeue if the minimum amount of time has passed else: return get_current_epoch(state) >= validator.exit_epoch + - # TODO in future versions, remove workaround for 0.3.0 spec bug - # but for 0.3.0 use MIN_VALIDATOR_WITHDRAWAL_DELAY constant - MIN_VALIDATOR_WITHDRAWAL_DELAY + MIN_VALIDATOR_WITHDRAWABILITY_DELAY # TODO try again with filterIt var eligible_indices: seq[ValidatorIndex] @@ -661,22 +635,22 @@ func processEpoch(state: var BeaconState) = # these closures outside this scope, but still.. let statePtr = state.addr func attesting_validator_indices( - crosslink_committee: CrosslinkCommittee, shard_block_root: Eth2Digest): seq[ValidatorIndex] = + crosslink_committee: CrosslinkCommittee, crosslink_data_root: Eth2Digest): seq[ValidatorIndex] = let shard_block_attestations = concat(current_epoch_attestations, previous_epoch_attestations). filterIt(it.data.shard == crosslink_committee.shard and - it.data.shard_block_root == shard_block_root) + it.data.crosslink_data_root == crosslink_data_root) get_attester_indices(statePtr[], shard_block_attestations) func winning_root(crosslink_committee: CrosslinkCommittee): Eth2Digest = # * Let `winning_root(crosslink_committee)` be equal to the value of - # `shard_block_root` such that - # `sum([get_effective_balance(state, i) for i in attesting_validator_indices(crosslink_committee, shard_block_root)])` - # is maximized (ties broken by favoring lower `shard_block_root` values). + # `crosslink_data_root` such that + # `sum([get_effective_balance(state, i) for i in attesting_validator_indices(crosslink_committee, crosslink_data_root)])` + # is maximized (ties broken by favoring lower `crosslink_data_root` values). let candidates = concat(current_epoch_attestations, previous_epoch_attestations). filterIt(it.data.shard == crosslink_committee.shard). - mapIt(it.data.shard_block_root) + mapIt(it.data.crosslink_data_root) # TODO not covered by spec! if candidates.len == 0: @@ -750,7 +724,7 @@ func processEpoch(state: var BeaconState) = 2'u64 * get_total_balance(state, crosslink_committee.committee): state.latest_crosslinks[crosslink_committee.shard] = Crosslink( epoch: slot_to_epoch(slot), - shard_block_root: winning_root(crosslink_committee)) + crosslink_data_root: winning_root(crosslink_committee)) # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#rewards-and-penalties ## First, we define some additional helpers @@ -768,22 +742,7 @@ func processEpoch(state: var BeaconState) = get_effective_balance(state, index) * epochs_since_finality div INACTIVITY_PENALTY_QUOTIENT div 2 - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#justification-and-finalization - ## TODO remove inclusion_{slot,distance} when fully replaced, but note - ## intentional absence, for spec sync purposes. Both positively invite - ## quadratic behavior. - func inclusion_slot(state: BeaconState, v: ValidatorIndex): uint64 = - for a in previous_epoch_attestations: - if v in get_attestation_participants(state, a.data, a.aggregation_bitfield): - return a.inclusion_slot - doAssert false - - func inclusion_distance(state: BeaconState, v: ValidatorIndex): uint64 = - for a in previous_epoch_attestations: - if v in get_attestation_participants(state, a.data, a.aggregation_bitfield): - return a.inclusion_slot - a.data.slot - doAssert false - + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#justification-and-finalization func inclusion_distances(state: BeaconState): auto = result = initTable[ValidatorIndex, uint64]() @@ -797,6 +756,12 @@ func processEpoch(state: var BeaconState) = let epochs_since_finality = next_epoch - state.finalized_epoch + ## Note: Rewards and penalties are for participation in the previous + ## epoch, so the "active validator" set is drawn from + ## get_active_validator_indices(state.validator_registry, previous_epoch). + active_validator_indices = + get_active_validator_indices(state.validator_registry, previous_epoch) + proc update_balance(attesters: HashSet[ValidatorIndex], attesting_balance: uint64) = # TODO Spec - add helper? for v in attesters: @@ -827,18 +792,17 @@ func processEpoch(state: var BeaconState) = previous_epoch_head_attesting_balance) # Inclusion distance - let distances = inclusion_distances(state) + # Strange plural (non)convention, but match spec name. + let inclusion_distance = inclusion_distances(state) for v in previous_epoch_attester_indices: statePtr.validator_balances[v] += base_reward(state, v) * - MIN_ATTESTATION_INCLUSION_DELAY div distances[v] - when false: - doAssert inclusion_distance(state, v) == distances[v] + MIN_ATTESTATION_INCLUSION_DELAY div inclusion_distance[v] else: # Case 2: epochs_since_finality > 4 - let distances = + let inclusion_distance = if previous_epoch_attester_indices.len > 0: inclusion_distances(state) else: @@ -858,7 +822,7 @@ func processEpoch(state: var BeaconState) = if index notin previous_epoch_head_attester_indices: reduce_balance( state.validator_balances[index], base_reward(state, index)) - if state.validator_registry[index].slashed_epoch <= current_epoch: + if state.validator_registry[index].slashed: reduce_balance( state.validator_balances[index], 2'u64 * inactivity_penalty( @@ -866,11 +830,10 @@ func processEpoch(state: var BeaconState) = if index in previous_epoch_attester_indices: reduce_balance( state.validator_balances[index], + # TODO spec issue? depends on left/right associativity of * and / base_reward(state, index) - base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY div - distances[index]) - when false: - doAssert inclusion_distance(state, index) == distances[index] + inclusion_distance[index]) # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#attestation-inclusion block: @@ -935,8 +898,8 @@ func processEpoch(state: var BeaconState) = reduce_balance( state.validator_balances[index], base_reward(state, index)) - # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#ejections - process_ejections(state, active_validator_indices) + # https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#ejections + process_ejections(state) # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#validator-registry-and-shuffling-seed-data block: diff --git a/tests/testutil.nim b/tests/testutil.nim index 00aefe2ba..fc84c3714 100644 --- a/tests/testutil.nim +++ b/tests/testutil.nim @@ -175,7 +175,7 @@ proc makeAttestation*( beacon_block_root: beacon_block_root, epoch_boundary_root: Eth2Digest(), # TODO latest_crosslink: state.latest_crosslinks[sac.shard], - shard_block_root: Eth2Digest(), # TODO + crosslink_data_root: Eth2Digest(), # TODO justified_epoch: state.justified_epoch, justified_block_root: get_block_root(state, get_epoch_start_slot(state.justified_epoch)), )