diff --git a/beacon_chain/block_pools/chain_dag.nim b/beacon_chain/block_pools/chain_dag.nim index 624e68dd0..429570f01 100644 --- a/beacon_chain/block_pools/chain_dag.nim +++ b/beacon_chain/block_pools/chain_dag.nim @@ -58,7 +58,7 @@ template withState*( ## TODO async transformations will lead to a race where stateData gets updated ## while waiting for future to complete - catch this here somehow? - var cache {.inject.} = blockSlot.blck.getStateCache(blockSlot.slot.epoch()) + var cache {.inject.} = StateCache() updateStateData(dag, stateData, blockSlot, false, cache) template hashedState(): HashedBeaconState {.inject, used.} = stateData.data @@ -97,9 +97,9 @@ func get_effective_balances*(state: BeaconState): seq[Gwei] = for i in 0 ..< result.len: # All non-active validators have a 0 balance - template validator: Validator = state.validators[i] - if validator.is_active_validator(epoch): - result[i] = validator.effective_balance + let validator = unsafeAddr state.validators[i] + if validator[].is_active_validator(epoch): + result[i] = validator[].effective_balance proc init*( T: type EpochRef, state: BeaconState, cache: var StateCache, @@ -146,13 +146,13 @@ proc init*( ret if prevEpoch != nil and ( - prevEpoch.validator_key_store[0] == hash_tree_root(state.validators) or + prevEpoch.validator_key_store[0] == validators_root or sameKeys(prevEpoch.validator_key_store[1][], state.validators.asSeq)): epochRef.validator_key_store = (validators_root, prevEpoch.validator_key_store[1]) else: epochRef.validator_key_store = ( - hash_tree_root(state.validators), + validators_root, newClone(mapIt(state.validators.toSeq, it.pubkey))) # When fork choice runs, it will need the effective balance of the justified @@ -273,34 +273,36 @@ func epochAncestor*(blck: BlockRef, epoch: Epoch): BlockSlot = blck.atEpochStart(epoch) -proc getStateCache*(blck: BlockRef, epoch: Epoch): StateCache = +func findEpochRef*(blck: BlockRef, epoch: Epoch): EpochRef = # may return nil! + let ancestor = blck.epochAncestor(epoch) + doAssert ancestor.blck != nil + for epochRef in ancestor.blck.epochRefs: + if epochRef.epoch == epoch: + return epochRef + +proc loadStateCache*(cache: var StateCache, blck: BlockRef, epoch: Epoch) = # When creating a state cache, we want the current and the previous epoch # information to be preloaded as both of these are used in state transition # functions - var res = StateCache() template load(e: Epoch) = - let ancestor = blck.epochAncestor(epoch) - for epochRef in ancestor.blck.epochRefs: - if epochRef.epoch == e: - res.shuffled_active_validator_indices[epochRef.epoch] = + if epoch notin cache.shuffled_active_validator_indices: + let epochRef = blck.findEpochRef(epoch) + if epochRef != nil: + cache.shuffled_active_validator_indices[epochRef.epoch] = epochRef.shuffled_active_validator_indices if epochRef.epoch == epoch: for i, idx in epochRef.beacon_proposers: - res.beacon_proposer_indices[ + cache.beacon_proposer_indices[ epoch.compute_start_slot_at_epoch + i] = if idx.isSome: some(idx.get()[0]) else: none(ValidatorIndex) - break - load(epoch) if epoch > 0: load(epoch - 1) - res - func init(T: type BlockRef, root: Eth2Digest, slot: Slot): BlockRef = BlockRef( root: root, @@ -443,13 +445,6 @@ proc init*(T: type ChainDAGRef, res -proc findEpochRef*(blck: BlockRef, epoch: Epoch): EpochRef = # may return nil! - let ancestor = blck.epochAncestor(epoch) - doAssert ancestor.blck != nil - for epochRef in ancestor.blck.epochRefs: - if epochRef.epoch == epoch: - return epochRef - proc getEpochRef*(dag: ChainDAGRef, blck: BlockRef, epoch: Epoch): EpochRef = let epochRef = blck.findEpochRef(epoch) if epochRef != nil: @@ -662,6 +657,8 @@ proc applyBlock( doAssert (addr(statePtr.data) == addr v) statePtr[] = dag.headState + loadStateCache(cache, blck.refs, blck.data.message.slot.epoch) + let ok = state_transition( dag.runtimePreset, state.data, blck.data, cache, flags + dag.updateFlags + {slotProcessed}, restore) @@ -783,6 +780,8 @@ proc updateStateData*( dag.applyBlock(state, dag.get(ancestors[i]), {}, cache) doAssert ok, "Blocks in database should never fail to apply.." + loadStateCache(cache, bs.blck, bs.slot.epoch) + # ...and make sure to process empty slots as requested dag.advanceSlots(state, bs.slot, save, cache) diff --git a/beacon_chain/block_pools/clearance.nim b/beacon_chain/block_pools/clearance.nim index 06c50db41..58776da27 100644 --- a/beacon_chain/block_pools/clearance.nim +++ b/beacon_chain/block_pools/clearance.nim @@ -196,7 +196,7 @@ proc addRawBlock*( # TODO if the block is from the future, we should not be resolving it (yet), # but maybe we should use it as a hint that our clock is wrong? - var cache = getStateCache(parent, blck.slot.epoch) + var cache = StateCache() updateStateData( dag, dag.clearanceState, parent.atSlot(blck.slot), true, cache) diff --git a/beacon_chain/eth2_json_rpc_serialization.nim b/beacon_chain/eth2_json_rpc_serialization.nim index fe406e153..1da12905e 100644 --- a/beacon_chain/eth2_json_rpc_serialization.nim +++ b/beacon_chain/eth2_json_rpc_serialization.nim @@ -15,7 +15,10 @@ proc toJsonHex(data: openArray[byte]): string = proc fromJson*(n: JsonNode, argName: string, result: var ValidatorPubKey) = n.kind.expect(JString, argName) - result = initPubKey(ValidatorPubKey.fromHex(n.getStr()).tryGet().initPubKey()) + var tmp = ValidatorPubKey.fromHex(n.getStr()).tryGet() + if not tmp.loadWithCache().isSome(): + raise (ref ValueError)(msg: "Invalid public BLS key") + result = tmp proc `%`*(pubkey: ValidatorPubKey): JsonNode = newJString(toJsonHex(toRaw(pubkey))) diff --git a/beacon_chain/inspector.nim b/beacon_chain/inspector.nim index 0486034d2..9c6b00a43 100644 --- a/beacon_chain/inspector.nim +++ b/beacon_chain/inspector.nim @@ -437,7 +437,7 @@ proc logEnrAddress(address: string) = var forkid = SSZ.decode(eth2Data.get(), ENRForkID) eth2fork_digest = $forkid.fork_digest eth2next_fork_version = $forkid.next_fork_version - eth2next_fork_epoch = strutils.toHex(cast[uint64](forkid.next_fork_epoch)) + eth2next_fork_epoch = strutils.toHex(uint64(forkid.next_fork_epoch)) except CatchableError: eth2fork_digest = "Error" eth2next_fork_version = "Error" diff --git a/beacon_chain/nimbus_signing_process.nim b/beacon_chain/nimbus_signing_process.nim index 422dd8ef6..32ab35bb9 100644 --- a/beacon_chain/nimbus_signing_process.nim +++ b/beacon_chain/nimbus_signing_process.nim @@ -18,7 +18,7 @@ programMain: # load and send all public keys so the BN knows for which ones to ping us doAssert paramCount() == 2 for curr in validatorKeysFromDirs(paramStr(1), paramStr(2)): - validators[curr.toPubKey.initPubKey] = curr + validators[curr.toPubKey] = curr echo curr.toPubKey echo "end" @@ -27,6 +27,6 @@ programMain: let args = stdin.readLine.split(" ") doAssert args.len == 2 - let privKey = validators[ValidatorPubKey.fromHex(args[0]).get().initPubKey()] + let privKey = validators[ValidatorPubKey.fromHex(args[0]).get()] echo blsSign(privKey, Eth2Digest.fromHex(args[1]).data) diff --git a/beacon_chain/nimbus_validator_client.nim b/beacon_chain/nimbus_validator_client.nim index ea0d7b739..1dee6ea0b 100644 --- a/beacon_chain/nimbus_validator_client.nim +++ b/beacon_chain/nimbus_validator_client.nim @@ -302,7 +302,7 @@ programMain: # load all the validators from the data dir into memory for curr in vc.config.validatorKeys: vc.attachedValidators.addLocalValidator( - curr.toPubKey.initPubKey, curr, none(ValidatorIndex)) + curr.toPubKey, curr, none(ValidatorIndex)) waitFor vc.client.connect($vc.config.rpcAddress, vc.config.rpcPort) info "Connected to BN", diff --git a/beacon_chain/rpc/validator_api.nim b/beacon_chain/rpc/validator_api.nim index bfeb04ed8..0467504d2 100644 --- a/beacon_chain/rpc/validator_api.nim +++ b/beacon_chain/rpc/validator_api.nim @@ -92,7 +92,7 @@ proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) = epochRef, slot, committee_index.CommitteeIndex) for index_in_committee, validatorIdx in committee: if validatorIdx < epochRef.validator_keys.len.ValidatorIndex: - let curr_val_pubkey = epochRef.validator_keys[validatorIdx].initPubKey + let curr_val_pubkey = epochRef.validator_keys[validatorIdx] if public_keys.findIt(it == curr_val_pubkey) != -1: result.add((public_key: curr_val_pubkey, validator_index: validatorIdx, @@ -109,7 +109,7 @@ proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) = epochRef = node.chainDag.getEpochRef(head, epoch) for i in 0 ..< SLOTS_PER_EPOCH: if epochRef.beacon_proposers[i].isSome(): - result.add((public_key: epochRef.beacon_proposers[i].get()[1].initPubKey(), + result.add((public_key: epochRef.beacon_proposers[i].get()[1], slot: compute_start_slot_at_epoch(epoch) + i)) rpcServer.rpc("post_v1_validator_beacon_committee_subscriptions") do ( diff --git a/beacon_chain/spec/beaconstate.nim b/beacon_chain/spec/beaconstate.nim index 41851855e..f71214dc6 100644 --- a/beacon_chain/spec/beaconstate.nim +++ b/beacon_chain/spec/beaconstate.nim @@ -9,6 +9,7 @@ import std/[tables, algorithm, math, sequtils, options], + stew/assign2, json_serialization/std/sets, chronicles, ../extras, ../ssz/merkleization, @@ -36,24 +37,32 @@ func is_valid_merkle_branch*(leaf: Eth2Digest, branch: openArray[Eth2Digest], value == root # https://github.com/ethereum/eth2.0-specs/blob/v1.0.0/specs/phase0/beacon-chain.md#increase_balance +func increase_balance*(balance: var Gwei, delta: Gwei) = + balance += delta + func increase_balance*( state: var BeaconState, index: ValidatorIndex, delta: Gwei) = ## Increase the validator balance at index ``index`` by ``delta``. - state.balances[index] += delta + if delta != 0: # avoid dirtying the balance cache if not needed + increase_balance(state.balances[index], delta) # https://github.com/ethereum/eth2.0-specs/blob/v1.0.0/specs/phase0/beacon-chain.md#decrease_balance +func decrease_balance*(balance: var Gwei, delta: Gwei) = + balance = + if delta > balance: + 0'u64 + else: + balance - delta + func decrease_balance*( state: var BeaconState, index: ValidatorIndex, delta: Gwei) = ## Decrease the validator balance at index ``index`` by ``delta``, with ## underflow protection. - state.balances[index] = - if delta > state.balances[index]: - 0'u64 - else: - state.balances[index] - delta + if delta != 0: # avoid dirtying the balance cache if not needed + decrease_balance(state.balances[index], delta) # https://github.com/ethereum/eth2.0-specs/blob/v1.0.0/specs/phase0/beacon-chain.md#deposits -func get_validator_from_deposit(state: BeaconState, deposit: DepositData): +func get_validator_from_deposit(deposit: DepositData): Validator = let amount = deposit.amount @@ -91,31 +100,36 @@ proc process_deposit*(preset: RuntimePreset, let pubkey = deposit.data.pubkey - pubkey_inited = pubkey.initPubKey amount = deposit.data.amount + var index = -1 - for i, validator in state.validators: - if pubkey_inited == validator.pubkey.initPubKey: + # This linear scan is unfortunate, but should be fairly fast as we do a simple + # byte comparison of the key. The alternative would be to build a Table, but + # given that each block can hold no more than 16 deposits, it's slower to + # build the table and use it for lookups than to scan it like this. + # Once we have a reusable, long-lived cache, this should be revisited + for i in 0.. exit_queue_epoch: - exit_queue_epoch = v.exit_epoch + for idx in 0.. exit_queue_epoch: + exit_queue_epoch = exit_epoch - let - exit_queue_churn = countIt( - state.validators, it.exit_epoch == exit_queue_epoch) + var + exit_queue_churn: int + for idx in 0..= get_validator_churn_limit(state, cache): exit_queue_epoch += 1 @@ -250,14 +267,7 @@ proc initialize_beacon_state_from_eth1*( Eth1Data(block_hash: eth1_block_hash, deposit_count: uint64(len(deposits))), latest_block_header: BeaconBlockHeader( - body_root: hash_tree_root(BeaconBlockBody( - # This differs from the spec intentionally. - # We must specify the default value for `ValidatorSig` - # in order to get a correct `hash_tree_root`. - randao_reveal: ValidatorSig(kind: OpaqueBlob) - )) - ) - ) + body_root: hash_tree_root(BeaconBlockBody()))) # Seed RANDAO with Eth1 entropy state.randao_mixes.fill(eth1_block_hash) @@ -286,7 +296,7 @@ proc initialize_beacon_state_from_eth1*( if skipBlsValidation in flags or verify_deposit_signature(preset, deposit): pubkeyToIndex[pubkey] = state.validators.len - state.validators.add(get_validator_from_deposit(state[], deposit)) + state.validators.add(get_validator_from_deposit(deposit)) state.balances.add(amount) else: # Invalid deposits are perfectly possible @@ -296,7 +306,7 @@ proc initialize_beacon_state_from_eth1*( # Process activations for validator_index in 0 ..< state.validators.len: let - balance = state.balances[validator_index] + balance = state.balances.asSeq()[validator_index] validator = addr state.validators[validator_index] validator.effective_balance = min( @@ -321,9 +331,8 @@ proc initialize_hashed_beacon_state_from_eth1*( preset, eth1_block_hash, eth1_timestamp, deposits, flags) HashedBeaconState(data: genesisState[], root: hash_tree_root(genesisState[])) -func emptyBeaconBlockBody(): BeaconBlockBody = - # TODO: This shouldn't be necessary if OpaqueBlob is the default - BeaconBlockBody(randao_reveal: ValidatorSig(kind: OpaqueBlob)) +template emptyBeaconBlockBody(): BeaconBlockBody = + BeaconBlockBody() # https://github.com/ethereum/eth2.0-specs/blob/v1.0.0/specs/phase0/beacon-chain.md#genesis-block func get_initial_beacon_block*(state: BeaconState): SignedBeaconBlock = @@ -398,21 +407,22 @@ proc process_registry_updates*(state: var BeaconState, # the current epoch, 1 + MAX_SEED_LOOKAHEAD epochs ahead. Thus caches # remain valid for this epoch through though this function along with # the rest of the epoch transition. - for index, validator in state.validators: - if is_eligible_for_activation_queue(validator): + for index in 0..= state.validators.lenu64: return err("Exit: invalid validator index") - let validator = state.validators[voluntary_exit.validator_index] + let validator = unsafeAddr state.validators.asSeq()[voluntary_exit.validator_index] # Verify the validator is active - if not is_active_validator(validator, get_current_epoch(state)): + if not is_active_validator(validator[], get_current_epoch(state)): return err("Exit: validator not active") # Verify exit has not been initiated - if validator.exit_epoch != FAR_FUTURE_EPOCH: + if validator[].exit_epoch != FAR_FUTURE_EPOCH: return err("Exit: validator has exited") # Exits must specify an epoch when they become valid; they are not valid @@ -267,7 +267,7 @@ proc check_voluntary_exit*( return err("Exit: exit epoch not passed") # Verify the validator has been active long enough - if not (get_current_epoch(state) >= validator.activation_epoch + + if not (get_current_epoch(state) >= validator[].activation_epoch + SHARD_COMMITTEE_PERIOD): return err("Exit: not in validator set long enough") @@ -275,7 +275,7 @@ proc check_voluntary_exit*( if skipBlsValidation notin flags: if not verify_voluntary_exit_signature( state.fork, state.genesis_validators_root, voluntary_exit, - validator.pubkey, signed_voluntary_exit.signature): + validator[].pubkey, signed_voluntary_exit.signature): return err("Exit: invalid signature") # Initiate exit @@ -284,10 +284,10 @@ proc check_voluntary_exit*( num_validators = state.validators.len, epoch = voluntary_exit.epoch, current_epoch = get_current_epoch(state), - validator_slashed = validator.slashed, - validator_withdrawable_epoch = validator.withdrawable_epoch, - validator_exit_epoch = validator.exit_epoch, - validator_effective_balance = validator.effective_balance + validator_slashed = validator[].slashed, + validator_withdrawable_epoch = validator[].withdrawable_epoch, + validator_exit_epoch = validator[].exit_epoch, + validator_effective_balance = validator[].effective_balance ok() diff --git a/beacon_chain/spec/state_transition_epoch.nim b/beacon_chain/spec/state_transition_epoch.nim index c47268dc7..a06061cb7 100644 --- a/beacon_chain/spec/state_transition_epoch.nim +++ b/beacon_chain/spec/state_transition_epoch.nim @@ -131,19 +131,20 @@ template previous_epoch_head_attesters*(v: TotalBalances): Gwei = func init*(T: type ValidatorStatuses, state: BeaconState): T = result.statuses = newSeq[ValidatorStatus](state.validators.len) - for i, v in state.validators: - result.statuses[i].is_slashed = v.slashed + for i in 0..= v.withdrawable_epoch - result.statuses[i].current_epoch_effective_balance = v.effective_balance + state.get_current_epoch() >= v[].withdrawable_epoch + result.statuses[i].current_epoch_effective_balance = v[].effective_balance - if v.is_active_validator(state.get_current_epoch()): + if v[].is_active_validator(state.get_current_epoch()): result.statuses[i].is_active_in_current_epoch = true - result.total_balances.current_epoch_raw += v.effective_balance + result.total_balances.current_epoch_raw += v[].effective_balance - if v.is_active_validator(state.get_previous_epoch()): + if v[].is_active_validator(state.get_previous_epoch()): result.statuses[i].is_active_in_previous_epoch = true - result.total_balances.previous_epoch_raw += v.effective_balance + result.total_balances.previous_epoch_raw += v[].effective_balance func add(a: var Delta, b: Delta) = a.rewards += b.rewards @@ -515,9 +516,14 @@ func process_rewards_and_penalties( get_attestation_deltas(state, validator_statuses) + # Here almost all balances are updated (assuming most validators are active) - + # clearing the cache becomes a bottleneck if done item by item because of the + # recursive nature of cache clearing - instead, we clear the whole cache then + # update the raw list directly + state.balances.clearCache() for idx, v in validator_statuses.statuses: - increase_balance(state, idx.ValidatorIndex, v.delta.rewards) - decrease_balance(state, idx.ValidatorIndex, v.delta.penalties) + increase_balance(state.balances.asSeq()[idx], v.delta.rewards) + decrease_balance(state.balances.asSeq()[idx], v.delta.penalties) # https://github.com/ethereum/eth2.0-specs/blob/v1.0.0/specs/phase0/beacon-chain.md#slashings func process_slashings*(state: var BeaconState, total_balance: Gwei) {.nbench.}= @@ -526,13 +532,14 @@ func process_slashings*(state: var BeaconState, total_balance: Gwei) {.nbench.}= adjusted_total_slashing_balance = min(sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER, total_balance) - for index, validator in state.validators: - if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR div 2 == - validator.withdrawable_epoch: + for index in 0..