diff --git a/beacon_chain/fork_choice.nim b/beacon_chain/fork_choice.nim index 781755489..2156eaba3 100644 --- a/beacon_chain/fork_choice.nim +++ b/beacon_chain/fork_choice.nim @@ -173,7 +173,7 @@ func getVoteCount(participation_bitfield: openarray[byte]): int = # https://github.com/status-im/nim-beacon-chain/issues/19 for validatorIdx in 0 ..< participation_bitfield.len * 8: - result += int participation_bitfield.bitIsSet(validatorIdx) + result += int participation_bitfield.get_bitfield_bit(validatorIdx) func getAttestationVoteCount(pool: AttestationPool, current_slot: int): CountTable[Eth2Digest] = ## Returns all blocks more recent that the current slot diff --git a/beacon_chain/spec/datatypes.nim b/beacon_chain/spec/datatypes.nim index cf144fcde..9d65e8cc5 100644 --- a/beacon_chain/spec/datatypes.nim +++ b/beacon_chain/spec/datatypes.nim @@ -62,7 +62,7 @@ const ## with a Verifiable Delay Function (VDF) will improve committee robustness ## and lower the safe minimum committee size.) - EJECTION_BALANCE* = 2'u64^4 ##\ + EJECTION_BALANCE* = 2'u64^4 * 10'u64^9 ##\ ## Once the balance of a validator drops below this, it will be ejected from ## the validator pool @@ -70,20 +70,20 @@ const ## At most `1/MAX_BALANCE_CHURN_QUOTIENT` of the validators can change during ## each validator registry change. - GWEI_PER_ETH* = 10'u64^9 # Gwei/ETH - BEACON_CHAIN_SHARD_NUMBER* = not 0'u64 # 2^64 - 1 in spec - MAX_CASPER_VOTES* = 2^10 + MAX_INDICES_PER_SLASHABLE_VOTE* = 2^12 ##\ + ## votes + MAX_WITHDRAWALS_PER_EPOCH* = 4 # withdrawals # Deposit contract DEPOSIT_CONTRACT_TREE_DEPTH* = 2^5 - MIN_DEPOSIT_AMOUNT* = 2'u64^0 * GWEI_PER_ETH ##\ + 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 - MAX_DEPOSIT_AMOUNT* = 2'u64^5 * GWEI_PER_ETH ##\ + MAX_DEPOSIT_AMOUNT* = 2'u64^5 * 10'u64^9 ##\ ## Maximum amounth of ETH that can be deposited in one call # Time parameter, here so that GENESIS_EPOCH can access it @@ -93,9 +93,8 @@ const ## processing is done # Initial values - GENESIS_FORK_VERSION* = 0'u64 - GENESIS_SLOT* = 2'u64^19 + GENESIS_SLOT* = 2'u64^63 GENESIS_EPOCH* = GENESIS_SLOT div EPOCH_LENGTH # slot_to_epoch(GENESIS_SLOT) GENESIS_START_SHARD* = 0'u64 FAR_FUTURE_EPOCH* = not 0'u64 # 2^64 - 1 in spec @@ -119,17 +118,17 @@ const ## wait towards the end of the slot and still have time to publish the ## attestation. - SEED_LOOKAHEAD* = 64 ##\ - ## slots (~6.4 minutes) + SEED_LOOKAHEAD* = 1 ##\ + ## epochs (~6.4 minutes) - ENTRY_EXIT_DELAY* = 256 ##\ - ## slots (~25.6 minutes) + ENTRY_EXIT_DELAY* = 4 ##\ + ## epochs (~25.6 minutes) - ETH1_DATA_VOTING_PERIOD* = 2'u64^10 ##\ - ## slots (~1.7 hours) + ETH1_DATA_VOTING_PERIOD* = 2'u64^4 ##\ + ## epochs (~1.7 hours) - MIN_VALIDATOR_WITHDRAWAL_TIME* = 2'u64^14 ##\ - ## slots (~27 hours) + MIN_VALIDATOR_WITHDRAWAL_TIME* = 2'u64^8 ##\ + ## epochs (~27 hours) # State list lengths LATEST_BLOCK_ROOTS_LENGTH* = 2'u64^13 @@ -137,7 +136,8 @@ const LATEST_INDEX_ROOTS_LENGTH* = 2'u64^13 LATEST_PENALIZED_EXIT_LENGTH* = 8192 # epochs - # Quotients + # Reward and penalty quotients + # https://github.com/ethereum/eth2.0-specs/blob/dev/specs/core/0_beacon-chain.md#reward-and-penalty-quotients BASE_REWARD_QUOTIENT* = 2'u64^10 ##\ ## The `BASE_REWARD_QUOTIENT` parameter dictates the per-epoch reward. It ## corresponds to ~2.54% annual interest assuming 10 million participating @@ -153,7 +153,7 @@ const # Max operations per block MAX_PROPOSER_SLASHINGS* = 2^4 - MAX_CASPER_SLASHINGS* = 2^4 + MAX_ATTESTER_SLASHINGS* = 2^0 MAX_ATTESTATIONS* = 2^7 MAX_DEPOSITS* = 2^4 MAX_EXITS* = 2^4 @@ -163,7 +163,7 @@ type SlotNumber* = uint64 EpochNumber* = uint64 - # https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#data-structures + # https://github.com/ethereum/eth2.0-specs/blob/dev/specs/core/0_beacon-chain.md#proposerslashing ProposerSlashing* = object proposer_index*: ValidatorIndex proposal_data_1*: ProposalSignedData @@ -171,26 +171,21 @@ type proposal_data_2*: ProposalSignedData proposal_signature_2*: ValidatorSig + # https://github.com/ethereum/eth2.0-specs/blob/dev/specs/core/0_beacon-chain.md#attesterslashing AttesterSlashing* = object - slashable_vote_data_1*: SlashableVote ## \ + slashable_attestation_1*: SlashableAttestation ## \ ## First batch of votes - slashable_vote_data_2*: SlashableVote ## \ + slashable_attestation_2*: SlashableAttestation ## \ ## Second batch of votes - SlashableVote* = object + # https://github.com/ethereum/eth2.0-specs/blob/dev/specs/core/0_beacon-chain.md#slashableattestation + SlashableAttestation* = object validator_indices*: seq[uint64] ##\ ## Validator indices custody_bitfield*: seq[byte] ##\ ## Custody bitfield - # TODO rm aggregate_signature_poc_0_indices, aggregate_signature_poc_1_indices - aggregate_signature_poc_0_indices*: seq[ValidatorIndex] ##\ - ## Proof-of-custody indices (0 bits) - - aggregate_signature_poc_1_indices*: seq[ValidatorIndex] ##\ - ## Proof-of-custody indices (1 bits) - data*: AttestationData ## \ ## Attestation data @@ -429,11 +424,13 @@ type Activation = 0 Exit = 1 + # https://github.com/ethereum/eth2.0-specs/blob/dev/specs/core/0_beacon-chain.md#signature-domains SignatureDomain* {.pure.} = enum DOMAIN_DEPOSIT = 0 DOMAIN_ATTESTATION = 1 DOMAIN_PROPOSAL = 2 DOMAIN_EXIT = 3 + DOMAIN_RANDAO = 4 template epoch*(slot: int|uint64): auto = slot div EPOCH_LENGTH diff --git a/beacon_chain/spec/helpers.nim b/beacon_chain/spec/helpers.nim index e196d0586..24e7c1977 100644 --- a/beacon_chain/spec/helpers.nim +++ b/beacon_chain/spec/helpers.nim @@ -11,12 +11,26 @@ import ./datatypes, ./digest, sequtils, math # TODO spec candidate? there's bits in nim-ranges but that one has some API # issues regarding bit endianess that need resolving.. -func bitIsSet*(bitfield: openArray[byte], index: int): bool = - (bitfield[index div 8] shr byte(7 - (index mod 8))) mod 2 > 0'u8 - func bitSet*(bitfield: var openArray[byte], index: int) = bitfield[index div 8] = bitfield[index div 8] or 1'u8 shl (7 - (index mod 8)) +func get_bitfield_bit*(bitfield: openarray[byte], i: int): byte = + # https://github.com/ethereum/eth2.0-specs/blob/dev/specs/core/0_beacon-chain.md#get_bitfield_bit + # Extract the bit in ``bitfield`` at position ``i``. + (bitfield[i div 8] shr (7 - (i mod 8))) mod 2 + +func verify_bitfield*(bitfield: openarray[byte], committee_size: int): bool = + # https://github.com/ethereum/eth2.0-specs/blob/dev/specs/core/0_beacon-chain.md#verify_bitfield + # Verify ``bitfield`` against the ``committee_size``. + if len(bitfield) != (committee_size + 7) div 8: + return false + + for i in committee_size + 1 ..< committee_size - (committee_size mod 8) + 8: + if get_bitfield_bit(bitfield, i) == 0b1: + return false + + true + func mod_get[T](arr: openarray[T], pos: Natural): T = arr[pos mod arr.len] diff --git a/beacon_chain/state_transition.nim b/beacon_chain/state_transition.nim index c7ebd1b23..45a051723 100644 --- a/beacon_chain/state_transition.nim +++ b/beacon_chain/state_transition.nim @@ -182,65 +182,73 @@ proc processProposerSlashings( return true -func verify_slashable_vote_data(state: BeaconState, vote_data: SlashableVote): bool = - if len(vote_data.aggregate_signature_poc_0_indices) + - len(vote_data.aggregate_signature_poc_1_indices) > MAX_CASPER_VOTES: +func verify_slashable_attestation(state: BeaconState, slashable_attestation: SlashableAttestation): bool = + # https://github.com/ethereum/eth2.0-specs/blob/dev/specs/core/0_beacon-chain.md#verify_slashable_attestation + # Verify validity of ``slashable_attestation`` fields. + + if anyIt(slashable_attestation.custody_bitfield, it != 0): # [TO BE REMOVED IN PHASE 1] return false - let pubs = [ - bls_aggregate_pubkeys(vote_data.aggregate_signature_poc_0_indices. - mapIt(state.validator_registry[it].pubkey)), - bls_aggregate_pubkeys(vote_data.aggregate_signature_poc_1_indices. - mapIt(state.validator_registry[it].pubkey))] + if len(slashable_attestation.validator_indices) == 0: + return false - # TODO - # return bls_verify_multiple(pubs, [hash_tree_root(votes)+bytes1(0), hash_tree_root(votes)+bytes1(1), signature=aggregate_signature) + 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_SLASHABLE_VOTE: + return false + + var + custody_bit_0_indices: seq[uint64] = @[] + custody_bit_1_indices: seq[uint64] = @[] return true -proc indices(vote: SlashableVote): seq[ValidatorIndex] = - vote.aggregate_signature_poc_0_indices & - vote.aggregate_signature_poc_1_indices - proc processAttesterSlashings(state: var BeaconState, blck: BeaconBlock): bool = - ## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#casper-slashings-1 - if len(blck.body.attester_slashings) > MAX_CASPER_SLASHINGS: + ## https://github.com/ethereum/eth2.0-specs/blob/dev/specs/core/0_beacon-chain.md#attester-slashings-1 + if len(blck.body.attester_slashings) > MAX_ATTESTER_SLASHINGS: notice "CaspSlash: too many!" return false - for casper_slashing in blck.body.attester_slashings: + for attester_slashing in blck.body.attester_slashings: let - slashable_vote_data_1 = casper_slashing.slashable_vote_data_1 - slashable_vote_data_2 = casper_slashing.slashable_vote_data_2 - indices2 = indices(slashable_vote_data_2) - intersection = - indices(slashable_vote_data_1).filterIt(it in indices2) + slashable_attestation_1 = attester_slashing.slashable_attestation_1 + slashable_attestation_2 = attester_slashing.slashable_attestation_2 - if not (slashable_vote_data_1.data != slashable_vote_data_2.data): + if not (slashable_attestation_1.data != slashable_attestation_2.data): notice "CaspSlash: invalid data" return false - if not (len(intersection) >= 1): - notice "CaspSlash: no intersection" - return false - if not ( - is_double_vote(slashable_vote_data_1.data, slashable_vote_data_2.data) or - is_surround_vote(slashable_vote_data_1.data, slashable_vote_data_2.data)): + is_double_vote(slashable_attestation_1.data, slashable_attestation_2.data) or + is_surround_vote(slashable_attestation_1.data, slashable_attestation_2.data)): notice "CaspSlash: surround or double vote check failed" return false - if not verify_slashable_vote_data(state, slashable_vote_data_1): + if not verify_slashable_attestation(state, slashable_attestation_1): notice "CaspSlash: invalid votes 1" return false - if not verify_slashable_vote_data(state, slashable_vote_data_2): + if not verify_slashable_attestation(state, slashable_attestation_2): notice "CaspSlash: invalid votes 2" return false - for i in intersection: - if state.validator_registry[i].penalized_epoch > get_current_epoch(state): - penalize_validator(state, i) + let + indices2 = slashable_attestation_2.validator_indices + slashable_indices = + slashable_attestation_1.validator_indices.filterIt(it in indices2) + + if not (len(slashable_indices) >= 1): + notice "CaspSlash: no intersection" + return false + + for index in slashable_indices: + if state.validator_registry[index.int].penalized_epoch > get_current_epoch(state): + penalize_validator(state, index.ValidatorIndex) return true @@ -451,25 +459,11 @@ func processEpoch(state: var BeaconState) = active_validator_indices = get_active_validator_indices(state.validator_registry, state.slot) total_balance = sum_effective_balances(state, active_validator_indices) - total_balance_in_eth = total_balance div GWEI_PER_ETH - - # The per-slot maximum interest rate is `2/reward_quotient`.) - base_reward_quotient = - BASE_REWARD_QUOTIENT * integer_squareroot(total_balance_in_eth) current_epoch = get_current_epoch(state) previous_epoch = if current_epoch > GENESIS_EPOCH: current_epoch - 1 else: current_epoch next_epoch = (current_epoch + 1).EpochNumber - func base_reward(state: BeaconState, index: ValidatorIndex): uint64 = - get_effective_balance(state, index) div base_reward_quotient.uint64 div 4 - - func inactivity_penalty( - state: BeaconState, index: ValidatorIndex, epochs_since_finality: uint64): uint64 = - base_reward(state, index) + - get_effective_balance(state, index) * - epochs_since_finality div INACTIVITY_PENALTY_QUOTIENT div 2 - # TODO doing this with iterators failed: # https://github.com/nim-lang/Nim/issues/9827 let @@ -640,7 +634,21 @@ func processEpoch(state: var BeaconState) = # state.latest_crosslinks[shard] = Crosslink( # slot=state.slot, shard_block_root=winning_root(crosslink_committee)) - # TODO Rewards and penalties helpers + # Rewards and penalties helpers + # https://github.com/ethereum/eth2.0-specs/blob/dev/specs/core/0_beacon-chain.md#rewards-and-penalties + let + base_reward_quotient = + integer_squareroot(previous_total_balance) div BASE_REWARD_QUOTIENT + + func base_reward(state: BeaconState, index: ValidatorIndex): uint64 = + get_effective_balance(state, index) div base_reward_quotient.uint64 div 4 + + func inactivity_penalty( + state: BeaconState, index: ValidatorIndex, epochs_since_finality: uint64): uint64 = + base_reward(state, index) + + get_effective_balance(state, index) * + epochs_since_finality div INACTIVITY_PENALTY_QUOTIENT div 2 + block: # Justification and finalization let epochs_since_finality = next_epoch - state.finalized_epoch