Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: errors in compute_proposer_index #503

Merged
merged 7 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 75 additions & 36 deletions lib/constants.ex
Original file line number Diff line number Diff line change
@@ -1,81 +1,120 @@
defmodule Constants do
@moduledoc """
Constants module with 0-arity functions.
The following values are (non-configurable) constants used throughout the specification.
"""

@spec genesis_epoch() :: integer
### Misc

@spec genesis_epoch() :: SszTypes.slot()
def genesis_epoch, do: 0

@spec genesis_slot() :: integer
@spec genesis_slot() :: SszTypes.slot()
def genesis_slot, do: 0

@spec bls_withdrawal_prefix() :: <<_::8>>
@far_future_epoch 2 ** 64 - 1

@spec far_future_epoch() :: non_neg_integer()
def far_future_epoch, do: @far_future_epoch

@spec base_rewards_per_epoch() :: non_neg_integer()
def base_rewards_per_epoch, do: 4

@spec deposit_contract_tree_depth() :: non_neg_integer()
def deposit_contract_tree_depth, do: 32

@spec justification_bits_length() :: non_neg_integer()
def justification_bits_length, do: 4

@spec endianness() :: atom()
def endianness, do: :little

@spec participation_flag_weights() :: list(non_neg_integer())
def participation_flag_weights,
do: [timely_source_weight(), timely_target_weight(), timely_head_weight()]

### Withdrawal prefixes

@spec bls_withdrawal_prefix() :: SszTypes.bytes1()
def bls_withdrawal_prefix, do: <<0>>

@spec eth1_address_withdrawal_prefix() :: <<_::8>>
@spec eth1_address_withdrawal_prefix() :: SszTypes.bytes1()
def eth1_address_withdrawal_prefix, do: <<1>>

@spec domain_beacon_attester() :: SszTypes.domain_type()
def domain_beacon_attester, do: <<1, 0, 0, 0>>
### Domain types

@spec domain_beacon_proposer() :: SszTypes.domain_type()
def domain_beacon_proposer, do: <<0, 0, 0, 0>>

@spec domain_deposit() :: SszTypes.domain_type()
def domain_deposit, do: <<3, 0, 0, 0>>
@spec domain_beacon_attester() :: SszTypes.domain_type()
def domain_beacon_attester, do: <<1, 0, 0, 0>>

@spec domain_randao() :: SszTypes.domain_type()
def domain_randao, do: <<2, 0, 0, 0>>

@spec domain_sync_committee() :: SszTypes.domain_type()
def domain_sync_committee, do: <<7, 0, 0, 0>>
@spec domain_deposit() :: SszTypes.domain_type()
def domain_deposit, do: <<3, 0, 0, 0>>

@spec domain_voluntary_exit() :: SszTypes.domain_type()
def domain_voluntary_exit, do: <<4, 0, 0, 0>>

@spec domain_selection_proof() :: SszTypes.domain_type()
def domain_selection_proof, do: <<5, 0, 0, 0>>

@spec domain_aggregate_and_proof() :: SszTypes.domain_type()
def domain_aggregate_and_proof, do: <<6, 0, 0, 0>>

@spec domain_application_mask() :: SszTypes.domain_type()
def domain_application_mask, do: <<0, 0, 0, 1>>

@spec domain_sync_committee() :: SszTypes.domain_type()
def domain_sync_committee, do: <<7, 0, 0, 0>>

@spec domain_sync_committee_selection_proof() :: SszTypes.domain_type()
def domain_sync_committee_selection_proof, do: <<8, 0, 0, 0>>

@spec domain_contribution_and_proof() :: SszTypes.domain_type()
def domain_contribution_and_proof, do: <<9, 0, 0, 0>>

@spec domain_bls_to_execution_change() :: SszTypes.domain_type()
def domain_bls_to_execution_change, do: <<10, 0, 0, 0>>

@spec timely_source_flag_index() :: integer
### Participation flag indices

@spec timely_source_flag_index() :: non_neg_integer()
def timely_source_flag_index, do: 0

@spec timely_target_flag_index() :: integer
@spec timely_target_flag_index() :: non_neg_integer()
def timely_target_flag_index, do: 1

@spec timely_head_flag_index() :: integer
@spec timely_head_flag_index() :: non_neg_integer()
def timely_head_flag_index, do: 2

@spec proposer_weight() :: integer
def proposer_weight, do: 8

@spec sync_reward_weight() :: integer
def sync_reward_weight, do: 2

@spec weight_denominator() :: integer
def weight_denominator, do: 64
### Incentivization weights

@spec participation_flag_weights() :: list(integer)
def participation_flag_weights,
do: [timely_source_weight(), timely_target_weight(), timely_head_weight()]

@spec base_reward_factor() :: integer
def base_reward_factor, do: 64

@spec timely_source_weight() :: integer
@spec timely_source_weight() :: non_neg_integer()
def timely_source_weight, do: 14

@spec timely_target_weight() :: integer
@spec timely_target_weight() :: non_neg_integer()
def timely_target_weight, do: 26

@spec timely_head_weight() :: integer
@spec timely_head_weight() :: non_neg_integer()
def timely_head_weight, do: 14

@spec far_future_epoch() :: integer
def far_future_epoch, do: 2 ** 64 - 1
@spec sync_reward_weight() :: non_neg_integer()
def sync_reward_weight, do: 2

@spec deposit_contract_tree_depth() :: integer
def deposit_contract_tree_depth, do: 32
@spec proposer_weight() :: non_neg_integer()
def proposer_weight, do: 8

@spec weight_denominator() :: non_neg_integer()
def weight_denominator, do: 64

@spec intervals_per_slot() :: integer
## Fork choice

@spec intervals_per_slot() :: non_neg_integer()
def intervals_per_slot, do: 3

@spec proposer_score_boost() :: non_neg_integer()
def proposer_score_boost, do: 3
end
21 changes: 10 additions & 11 deletions lib/lambda_ethereum_consensus/state_transition/accessors.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do
alias LambdaEthereumConsensus.Utils
alias SszTypes.{Attestation, BeaconState, IndexedAttestation, SyncCommittee, Validator}

@max_random_byte 2 ** 8 - 1

@doc """
Return the next sync committee, with possible pubkey duplicates.
"""
Expand Down Expand Up @@ -80,8 +82,6 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do
validators,
sync_committee_indices
) do
max_random_byte = 2 ** 8 - 1

with {:ok, shuffled_index} <-
rem(index, active_validator_count)
|> Misc.compute_shuffled_index(active_validator_count, seed) do
Expand All @@ -90,10 +90,10 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do
<<_::binary-size(rem(index, 32)), random_byte, _::binary>> =
SszEx.hash(seed <> Misc.uint64_to_bytes(div(index, 32)))

max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE")
effective_balance = Enum.fetch!(validators, candidate_index).effective_balance

if effective_balance * max_random_byte >=
ChainSpec.get("MAX_EFFECTIVE_BALANCE") * random_byte do
if effective_balance * @max_random_byte >= max_effective_balance * random_byte do
{:ok, sync_committee_indices |> List.insert_at(0, candidate_index)}
else
{:ok, sync_committee_indices}
Expand Down Expand Up @@ -278,7 +278,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do

@spec get_base_reward_per_increment(BeaconState.t()) :: SszTypes.gwei()
def get_base_reward_per_increment(state) do
numerator = ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT") * Constants.base_reward_factor()
numerator = ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT") * ChainSpec.get("BASE_REWARD_FACTOR")
denominator = Math.integer_squareroot(get_total_active_balance(state))
div(numerator, denominator)
end
Expand Down Expand Up @@ -397,12 +397,11 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do
"""
@spec get_seed(BeaconState.t(), SszTypes.epoch(), SszTypes.domain_type()) :: SszTypes.bytes32()
def get_seed(state, epoch, domain_type) do
mix =
get_randao_mix(
state,
epoch + ChainSpec.get("EPOCHS_PER_HISTORICAL_VECTOR") -
ChainSpec.get("MIN_SEED_LOOKAHEAD") - 1
)
future_epoch =
epoch + ChainSpec.get("EPOCHS_PER_HISTORICAL_VECTOR") -
ChainSpec.get("MIN_SEED_LOOKAHEAD") - 1

mix = get_randao_mix(state, future_epoch)

SszEx.hash(domain_type <> Misc.uint64_to_bytes(epoch) <> mix)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do
current_epoch = Accessors.get_current_epoch(state)
next_epoch = current_epoch + 1
epochs_per_historical_vector = ChainSpec.get("EPOCHS_PER_HISTORICAL_VECTOR")
random_mix = Accessors.get_randao_mix(state, current_epoch)
randao_mix = Accessors.get_randao_mix(state, current_epoch)
index = rem(next_epoch, epochs_per_historical_vector)
new_randao_mixes = List.replace_at(randao_mixes, index, random_mix)
new_randao_mixes = List.replace_at(randao_mixes, index, randao_mix)
new_state = %BeaconState{state | randao_mixes: new_randao_mixes}
{:ok, new_state}
end
Expand Down
91 changes: 37 additions & 54 deletions lib/lambda_ethereum_consensus/state_transition/misc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ defmodule LambdaEthereumConsensus.StateTransition.Misc do
Misc functions
"""

alias LambdaEthereumConsensus.SszEx
alias SszTypes.BeaconState
import Bitwise
alias SszTypes.BeaconState

alias LambdaEthereumConsensus.SszEx
alias SszTypes.BeaconState

@max_random_byte 2 ** 8 - 1

@doc """
Returns the Unix timestamp at the start of the given slot
"""
Expand Down Expand Up @@ -52,38 +52,23 @@ defmodule LambdaEthereumConsensus.StateTransition.Misc do
def compute_shuffled_index(index, index_count, seed) do
shuffle_round_count = ChainSpec.get("SHUFFLE_ROUND_COUNT")

new_index =
Enum.reduce(0..(shuffle_round_count - 1), index, fn round, current_index ->
round_as_bytes = <<round>>

hash_of_seed_round = SszEx.hash(seed <> round_as_bytes)
0..(shuffle_round_count - 1)
|> Enum.reduce(index, fn round, current_index ->
pivot = SszEx.hash(seed <> <<round>>) |> bytes_to_uint64() |> rem(index_count)

pivot = rem(bytes_to_uint64(hash_of_seed_round), index_count)
flip = rem(pivot + index_count - current_index, index_count)
position = max(current_index, flip)

flip = rem(pivot + index_count - current_index, index_count)
position = max(current_index, flip)
position_div_256 = position |> div(256) |> uint_to_bytes(32)

position_div_256 = uint_to_bytes4(div(position, 256))
source = SszEx.hash(seed <> <<round>> <> position_div_256)

source =
SszEx.hash(seed <> round_as_bytes <> position_div_256)
bit_index = rem(position, 256) + 7 - 2 * rem(position, 8)
<<_::size(bit_index), bit::1, _::bits>> = source

byte_index = div(rem(position, 256), 8)
<<_::binary-size(byte_index), byte, _::binary>> = source
right_shift = byte >>> rem(position, 8)
bit = rem(right_shift, 2)

current_index =
if bit == 1 do
flip
else
current_index
end

current_index
end)

{:ok, new_index}
if bit == 1, do: flip, else: current_index
end)
|> then(&{:ok, &1})
end

@spec increase_inactivity_score(SszTypes.uint64(), integer, MapSet.t(), SszTypes.uint64()) ::
Expand Down Expand Up @@ -132,31 +117,29 @@ defmodule LambdaEthereumConsensus.StateTransition.Misc do
"""
@spec compute_proposer_index(BeaconState.t(), [SszTypes.validator_index()], SszTypes.bytes32()) ::
{:ok, SszTypes.validator_index()} | {:error, binary()}
def compute_proposer_index(state, indices, seed) do
if length(indices) <= 0 do
{:error, "Empty indices"}
else
{:ok, compute_proposer_index(state, indices, seed, 0)}
end
end
def compute_proposer_index(_state, [], _seed), do: {:error, "Empty indices"}

defp compute_proposer_index(state, indices, seed, i) when i < length(indices) do
max_random_byte = 2 ** 8 - 1
def compute_proposer_index(state, indices, seed) do
max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE")

total = length(indices)
{:ok, i} = compute_shuffled_index(rem(i, total), total, seed)
candidate_index = Enum.at(indices, i)
random_byte = SszEx.hash(seed <> uint_to_bytes4(div(i, 32)))
<<_::binary-size(rem(i, 32)), byte, _::binary>> = random_byte

effective_balance = Enum.at(state.validators, candidate_index).effective_balance
Stream.iterate(0, &(&1 + 1))
|> Stream.map(fn i ->
{:ok, index} = compute_shuffled_index(rem(i, total), total, seed)
candidate_index = Enum.at(indices, index)

if effective_balance * max_random_byte >= max_effective_balance * byte do
candidate_index
else
compute_proposer_index(state, indices, seed, i + 1)
end
<<_::binary-size(rem(i, 32)), random_byte, _::binary>> =
SszEx.hash(seed <> uint_to_bytes(div(i, 32), 64))

effective_balance = Enum.at(state.validators, candidate_index).effective_balance

{effective_balance, random_byte, candidate_index}
end)
|> Stream.filter(fn {effective_balance, random_byte, _} ->
effective_balance * @max_random_byte >= max_effective_balance * random_byte
end)
|> Enum.take(1)
|> then(fn [{_, _, candidate_index}] -> {:ok, candidate_index} end)
end

@doc """
Expand All @@ -180,10 +163,10 @@ defmodule LambdaEthereumConsensus.StateTransition.Misc do
first_8_bytes
end

@spec uint_to_bytes4(integer()) :: SszTypes.bytes4()
def uint_to_bytes4(value) do
# Converts an unsigned integer value to a bytes 4 value
<<value::unsigned-integer-little-size(32)>>
@spec uint_to_bytes(non_neg_integer(), 8 | 32 | 64) :: binary()
def uint_to_bytes(value, size) do
# Converts an unsigned integer value to a bytes value
<<value::unsigned-integer-little-size(size)>>
end

@spec uint64_to_bytes(SszTypes.uint64()) :: <<_::64>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
effective_balance_increment = ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT")
total_active_increments = total_active_balance |> div(effective_balance_increment)

numerator = effective_balance_increment * Constants.base_reward_factor()
numerator = effective_balance_increment * ChainSpec.get("BASE_REWARD_FACTOR")
denominator = Math.integer_squareroot(total_active_balance)
base_reward_per_increment = div(numerator, denominator)
total_base_rewards = base_reward_per_increment * total_active_increments
Expand Down
Loading
Loading