Skip to content

Commit

Permalink
make Gwei distinct (#6090)
Browse files Browse the repository at this point in the history
#6087 introduced a subtle change to `nim-web3` resulting in `Gwei` to be
serialized differently than before. Using a `distinct` type for `Gwei`
improves type safety and avoids such problems in the future.
  • Loading branch information
etan-status authored Mar 19, 2024
1 parent 1dd2c93 commit 5d42859
Show file tree
Hide file tree
Showing 39 changed files with 246 additions and 203 deletions.
12 changes: 6 additions & 6 deletions beacon_chain/beacon_chain_db_immutable.nim
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ type

# Registry
validators*: HashList[ValidatorStatus, Limit VALIDATOR_REGISTRY_LIMIT]
balances*: HashList[uint64, Limit VALIDATOR_REGISTRY_LIMIT]
balances*: HashList[Gwei, Limit VALIDATOR_REGISTRY_LIMIT]

# Randomness
randao_mixes*: HashArray[Limit EPOCHS_PER_HISTORICAL_VECTOR, Eth2Digest]

# Slashings
slashings*: HashArray[Limit EPOCHS_PER_SLASHINGS_VECTOR, uint64]
slashings*: HashArray[Limit EPOCHS_PER_SLASHINGS_VECTOR, Gwei]
## Per-epoch sums of slashed effective balances

# Attestations
Expand Down Expand Up @@ -97,13 +97,13 @@ type

# Registry
validators*: HashList[ValidatorStatus, Limit VALIDATOR_REGISTRY_LIMIT]
balances*: HashList[uint64, Limit VALIDATOR_REGISTRY_LIMIT]
balances*: HashList[Gwei, Limit VALIDATOR_REGISTRY_LIMIT]

# Randomness
randao_mixes*: HashArray[Limit EPOCHS_PER_HISTORICAL_VECTOR, Eth2Digest]

# Slashings
slashings*: HashArray[Limit EPOCHS_PER_SLASHINGS_VECTOR, uint64]
slashings*: HashArray[Limit EPOCHS_PER_SLASHINGS_VECTOR, Gwei]
## Per-epoch sums of slashed effective balances

# Participation
Expand Down Expand Up @@ -155,13 +155,13 @@ type

# Registry
validators*: HashList[ValidatorStatus, Limit VALIDATOR_REGISTRY_LIMIT]
balances*: HashList[uint64, Limit VALIDATOR_REGISTRY_LIMIT]
balances*: HashList[Gwei, Limit VALIDATOR_REGISTRY_LIMIT]

# Randomness
randao_mixes*: HashArray[Limit EPOCHS_PER_HISTORICAL_VECTOR, Eth2Digest]

# Slashings
slashings*: HashArray[Limit EPOCHS_PER_SLASHINGS_VECTOR, uint64]
slashings*: HashArray[Limit EPOCHS_PER_SLASHINGS_VECTOR, Gwei]
## Per-epoch sums of slashed effective balances

# Participation
Expand Down
2 changes: 1 addition & 1 deletion beacon_chain/beacon_node.nim
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ type
processor*: ref Eth2Processor
blockProcessor*: ref BlockProcessor
consensusManager*: ref ConsensusManager
attachedValidatorBalanceTotal*: uint64
attachedValidatorBalanceTotal*: Gwei
gossipState*: GossipState
blocksGossipState*: GossipState
beaconClock*: BeaconClock
Expand Down
2 changes: 1 addition & 1 deletion beacon_chain/el/el_manager.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1368,7 +1368,7 @@ func depositEventsToBlocks(depositsList: openArray[JsonString]): seq[Eth1Block]
lastEth1Block.deposits.add DepositData(
pubkey: ValidatorPubKey.init(pubkey.toArray),
withdrawal_credentials: Eth2Digest(data: withdrawalCredentials.toArray),
amount: bytes_to_uint64(amount.toArray),
amount: bytes_to_uint64(amount.toArray).Gwei,
signature: ValidatorSig.init(signature.toArray))

type
Expand Down
4 changes: 2 additions & 2 deletions beacon_chain/fork_choice/fork_choice.nim
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ func compute_deltas(
# If the validator was not included in `old_balances` (i.e. did not exist)
# its balance is zero
let old_balance = if val_index < old_balances.len: old_balances[val_index]
else: 0
else: 0.Gwei

# If the validator is not known in the `new_balances` then use balance of zero
#
Expand All @@ -444,7 +444,7 @@ func compute_deltas(
#
# Note that attesters are not different as they are activated only under finality
let new_balance = if val_index < new_balances.len: new_balances[val_index]
else: 0
else: 0.Gwei

if vote.current_root != vote.next_root or old_balance != new_balance:
# Ignore the current or next vote if it is not known in `indices`.
Expand Down
2 changes: 1 addition & 1 deletion beacon_chain/fork_choice/fork_choice_types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ type
indices*: Table[Eth2Digest, Index]
currentEpochTips*: Table[Index, FinalityCheckpoints]
previousProposerBoostRoot*: Eth2Digest
previousProposerBoostScore*: uint64
previousProposerBoostScore*: Gwei

ProtoNode* = object
bid*: BlockId
Expand Down
19 changes: 10 additions & 9 deletions beacon_chain/nimbus_beacon_node.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1204,16 +1204,17 @@ proc maybeUpdateActionTrackerNextEpoch(
# functions get_flag_index_deltas() and get_inactivity_penalty_deltas().
#
# There are no penalties associated with TIMELY_HEAD_FLAG_INDEX, but a
# reward exists. effective_balance == MAX_EFFECTIVE_BALANCE ensures if
# even so, then the effective balance cannot change as a result.
# reward exists. effective_balance == MAX_EFFECTIVE_BALANCE.Gwei ensures
# if even so, then the effective balance cannot change as a result.
#
# It's not truly necessary to avoid all rewards and penalties, but only
# to bound them to ensure they won't unexpected alter effective balance
# during the upcoming epoch transition.
#
# During genesis epoch, the check for epoch participation is against current,
# not previous, epoch, and therefore there's a possibility of checking for if
# a validator has participated in an epoch before it will happen.
# During genesis epoch, the check for epoch participation is against
# current, not previous, epoch, and therefore there's a possibility of
# checking for if a validator has participated in an epoch before it will
# happen.
#
# Because process_rewards_and_penalties() in epoch processing happens
# before the current/previous participation swap, previous is correct
Expand All @@ -1234,7 +1235,7 @@ proc maybeUpdateActionTrackerNextEpoch(

if participation_flags.has_flag(TIMELY_SOURCE_FLAG_INDEX) and
participation_flags.has_flag(TIMELY_TARGET_FLAG_INDEX) and
effective_balance == MAX_EFFECTIVE_BALANCE and
effective_balance == MAX_EFFECTIVE_BALANCE.Gwei and
forkyState.data.slot.epoch != GENESIS_EPOCH and
forkyState.data.inactivity_scores.item(
nextEpochFirstProposer) == 0 and
Expand Down Expand Up @@ -1956,13 +1957,13 @@ proc start*(node: BeaconNode) {.raises: [CatchableError].} =
node.elManager.start()
node.run()

func formatGwei(amount: uint64): string =
func formatGwei(amount: Gwei): string =
# TODO This is implemented in a quite a silly way.
# Better routines for formatting decimal numbers
# should exists somewhere else.
let
eth = amount div 1000000000
remainder = amount mod 1000000000
eth = distinctBase(amount) div 1000000000
remainder = distinctBase(amount) mod 1000000000

result = $eth
if remainder != 0:
Expand Down
4 changes: 2 additions & 2 deletions beacon_chain/rpc/rest_beacon_api.nim
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,10 @@ proc getStatus(validator: Validator,
ok(ValidatorFilterKind.ExitedSlashed)
elif validator.withdrawable_epoch <= current_epoch:
# withdrawal
if validator.effective_balance != 0:
if validator.effective_balance != 0.Gwei:
ok(ValidatorFilterKind.WithdrawalPossible)
else:
# validator.effective_balance == 0
# validator.effective_balance == 0.Gwei
ok(ValidatorFilterKind.WithdrawalDone)
else:
err("Invalid validator status")
Expand Down
56 changes: 32 additions & 24 deletions beacon_chain/spec/beaconstate.nim
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,22 @@ func increase_balance*(balance: var Gwei, delta: Gwei) =
func increase_balance*(
state: var ForkyBeaconState, index: ValidatorIndex, delta: Gwei) =
## Increase the validator balance at index ``index`` by ``delta``.
if delta != 0: # avoid dirtying the balance cache if not needed
if delta != 0.Gwei: # avoid dirtying the balance cache if not needed
increase_balance(state.balances.mitem(index), delta)

# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#decrease_balance
func decrease_balance*(balance: var Gwei, delta: Gwei) =
balance =
if delta > balance:
0'u64
0.Gwei
else:
balance - delta

func decrease_balance*(
state: var ForkyBeaconState, index: ValidatorIndex, delta: Gwei) =
## Decrease the validator balance at index ``index`` by ``delta``, with
## underflow protection.
if delta != 0: # avoid dirtying the balance cache if not needed
if delta != 0.Gwei: # avoid dirtying the balance cache if not needed
decrease_balance(state.balances.mitem(index), delta)

# https://github.com/ethereum/consensus-specs/blob/v1.4.0-alpha.3/specs/phase0/beacon-chain.md#deposits
Expand All @@ -54,7 +54,8 @@ func get_validator_from_deposit*(deposit: DepositData):
let
amount = deposit.amount
effective_balance = min(
amount - amount mod EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
amount - amount mod EFFECTIVE_BALANCE_INCREMENT.Gwei,
MAX_EFFECTIVE_BALANCE.Gwei)

Validator(
pubkeyData: HashedValidatorPubKey.init(deposit.pubkey),
Expand Down Expand Up @@ -349,13 +350,13 @@ template get_total_balance(
var res = 0.Gwei
for validator_index in validator_indices:
res += state.validators[validator_index].effective_balance
max(EFFECTIVE_BALANCE_INCREMENT, res)
max(EFFECTIVE_BALANCE_INCREMENT.Gwei, res)

# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#is_eligible_for_activation_queue
func is_eligible_for_activation_queue*(validator: Validator): bool =
## Check if ``validator`` is eligible to be placed into the activation queue.
validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and
validator.effective_balance == MAX_EFFECTIVE_BALANCE
validator.effective_balance == MAX_EFFECTIVE_BALANCE.Gwei

# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#is_eligible_for_activation
func is_eligible_for_activation*(
Expand Down Expand Up @@ -619,11 +620,13 @@ func get_total_active_balance*(state: ForkyBeaconState, cache: var StateCache):
# https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/altair/beacon-chain.md#get_base_reward_per_increment
func get_base_reward_per_increment_sqrt(
total_active_balance_sqrt: uint64): Gwei =
EFFECTIVE_BALANCE_INCREMENT * BASE_REWARD_FACTOR div total_active_balance_sqrt
EFFECTIVE_BALANCE_INCREMENT.Gwei * BASE_REWARD_FACTOR div
total_active_balance_sqrt

func get_base_reward_per_increment*(
total_active_balance: Gwei): Gwei =
get_base_reward_per_increment_sqrt(integer_squareroot(total_active_balance))
get_base_reward_per_increment_sqrt(
integer_squareroot(distinctBase(total_active_balance)))

# https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/altair/beacon-chain.md#get_base_reward
func get_base_reward(
Expand All @@ -633,7 +636,8 @@ func get_base_reward(
## Return the base reward for the validator defined by ``index`` with respect
## to the current ``state``.
let increments =
state.validators[index].effective_balance div EFFECTIVE_BALANCE_INCREMENT
state.validators[index].effective_balance div
EFFECTIVE_BALANCE_INCREMENT.Gwei
increments * base_reward_per_increment

# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#attestations
Expand Down Expand Up @@ -701,11 +705,12 @@ proc check_bls_to_execution_change*(

ok()

func get_proposer_reward*(state: ForkyBeaconState,
attestation: SomeAttestation,
base_reward_per_increment: Gwei,
cache: var StateCache,
epoch_participation: var EpochParticipationFlags): uint64 =
func get_proposer_reward*(
state: ForkyBeaconState,
attestation: SomeAttestation,
base_reward_per_increment: Gwei,
cache: var StateCache,
epoch_participation: var EpochParticipationFlags): Gwei =
let participation_flag_indices = get_attestation_participation_flag_indices(
state, attestation.data, state.slot - attestation.data.slot)
for index in get_attesting_indices_iter(
Expand All @@ -724,7 +729,7 @@ func get_proposer_reward*(state: ForkyBeaconState,
(WEIGHT_DENOMINATOR.uint64 - PROPOSER_WEIGHT.uint64) *
WEIGHT_DENOMINATOR.uint64 div PROPOSER_WEIGHT.uint64

return result div proposer_reward_denominator
result div proposer_reward_denominator

proc process_attestation*(
state: var ForkyBeaconState, attestation: SomeAttestation, flags: UpdateFlags,
Expand Down Expand Up @@ -811,7 +816,8 @@ func get_next_sync_committee_keys(
candidate_index = active_validator_indices[shuffled_index]
random_byte = eth2digest(hash_buffer).data[i mod 32]
effective_balance = state.validators[candidate_index].effective_balance
if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte:
if effective_balance * MAX_RANDOM_BYTE >=
MAX_EFFECTIVE_BALANCE.Gwei * random_byte:
res[index] = state.validators[candidate_index].pubkey
inc index
i += 1'u64
Expand All @@ -827,16 +833,16 @@ func is_fully_withdrawable_validator(
validator: Validator, balance: Gwei, epoch: Epoch): bool =
## Check if ``validator`` is fully withdrawable.
has_eth1_withdrawal_credential(validator) and
validator.withdrawable_epoch <= epoch and balance > 0
validator.withdrawable_epoch <= epoch and balance > 0.Gwei

# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.5/specs/capella/beacon-chain.md#is_partially_withdrawable_validator
func is_partially_withdrawable_validator(
validator: Validator, balance: Gwei): bool =
## Check if ``validator`` is partially withdrawable.
let
has_max_effective_balance =
validator.effective_balance == MAX_EFFECTIVE_BALANCE
has_excess_balance = balance > MAX_EFFECTIVE_BALANCE
validator.effective_balance == MAX_EFFECTIVE_BALANCE.Gwei
has_excess_balance = balance > MAX_EFFECTIVE_BALANCE.Gwei
has_eth1_withdrawal_credential(validator) and
has_max_effective_balance and has_excess_balance

Expand Down Expand Up @@ -868,7 +874,7 @@ func get_expected_withdrawals*(
var w = Withdrawal(
index: withdrawal_index,
validator_index: validator_index,
amount: balance - MAX_EFFECTIVE_BALANCE)
amount: balance - MAX_EFFECTIVE_BALANCE.Gwei)
w.address.data[0..19] = validator.withdrawal_credentials.data[12..^1]
withdrawals.add w
withdrawal_index = WithdrawalIndex(withdrawal_index + 1)
Expand Down Expand Up @@ -974,9 +980,10 @@ proc initialize_beacon_state_from_eth1(
validator = addr state.validators.mitem(vidx)

validator.effective_balance = min(
balance - balance mod EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
balance - balance mod EFFECTIVE_BALANCE_INCREMENT.Gwei,
MAX_EFFECTIVE_BALANCE.Gwei)

if validator.effective_balance == MAX_EFFECTIVE_BALANCE:
if validator.effective_balance == MAX_EFFECTIVE_BALANCE.Gwei:
validator.activation_eligibility_epoch = GENESIS_EPOCH
validator.activation_epoch = GENESIS_EPOCH

Expand Down Expand Up @@ -1091,9 +1098,10 @@ proc initialize_beacon_state_from_eth1*(
validator = addr state.validators.mitem(vidx)

validator.effective_balance = min(
balance - balance mod EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
balance - balance mod EFFECTIVE_BALANCE_INCREMENT.Gwei,
MAX_EFFECTIVE_BALANCE.Gwei)

if validator.effective_balance == MAX_EFFECTIVE_BALANCE:
if validator.effective_balance == MAX_EFFECTIVE_BALANCE.Gwei:
validator.activation_eligibility_epoch = GENESIS_EPOCH
validator.activation_epoch = GENESIS_EPOCH

Expand Down
33 changes: 25 additions & 8 deletions beacon_chain/spec/datatypes/base.nim
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
import
std/[macros, hashes, sets, strutils, tables, typetraits],
results,
stew/[assign2, byteutils, endians2],
stew/[assign2, base10, byteutils, endians2],
chronicles,
json_serialization,
ssz_serialization/types as sszTypes,
Expand Down Expand Up @@ -122,22 +122,39 @@ template maxSize*(n: int) {.pragma.}

type
Wei* = UInt256
Gwei* = uint64
Gwei* = distinct uint64
Ether* = distinct uint64

template ethAmountUnit*(typ: type) {.dirty.} =
# Arithmetic
func `+`*(x, y: typ): typ {.borrow.}
func `-`*(x, y: typ): typ {.borrow.}
func `*`*(x: typ, y: distinctBase(typ)): typ {.borrow.}
func `*`*(x: distinctBase(typ), y: typ): typ {.borrow.}

# Arithmetic, changing type
func `div`*(x, y: typ): distinctBase(typ) {.borrow.}
func `div`*(x: typ, y: distinctBase(typ)): typ {.borrow.}
func `mod`*(x, y: typ): typ {.borrow.}

func `+=`*(x: var typ, y: typ) {.borrow.}

# Comparison
func `<`*(x, y: typ): bool {.borrow.}
func `<=`*(x, y: typ): bool {.borrow.}
func `==`*(x, y: typ): bool {.borrow.}

func `$`*(x: typ): string {.borrow.}

func u256*(n: typ): UInt256 {.borrow.}

proc toString*(B: typedesc[Base10], value: typ): string {.borrow.}

proc writeValue*(writer: var JsonWriter, value: typ) {.raises: [IOError].} =
writer.writeValue(distinctBase(value))

proc readValue*(
reader: var JsonReader,
value: var typ) {.raises: [IOError, SerializationError].} =
reader.readValue(distinctBase(value))

ethAmountUnit Gwei
ethAmountUnit Ether

type
Expand Down Expand Up @@ -758,8 +775,8 @@ template lenu64*(x: untyped): untyped =
func `$`*(v: ForkDigest | Version | DomainType): string =
toHex(distinctBase(v))

func toGaugeValue*(x: uint64 | Epoch | Slot): int64 =
if x > uint64(int64.high):
func toGaugeValue*[T: uint64 | Gwei | Slot | Epoch](x: T): int64 =
if x > uint64(int64.high).T:
int64.high
else:
int64(x)
Expand Down
2 changes: 1 addition & 1 deletion beacon_chain/spec/datatypes/capella.nim
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ type

# Mod-increment
randao_mix*: Eth2Digest
slashing*: uint64
slashing*: Gwei

# Represent in full; for the next epoch, current_epoch_participation in
# epoch n is previous_epoch_participation in epoch n+1 but this doesn't
Expand Down
Loading

0 comments on commit 5d42859

Please sign in to comment.