diff --git a/lib/lambda_ethereum_consensus/state_transition/accessors.ex b/lib/lambda_ethereum_consensus/state_transition/accessors.ex index 8ebe73010..d4eb1dc61 100644 --- a/lib/lambda_ethereum_consensus/state_transition/accessors.ex +++ b/lib/lambda_ethereum_consensus/state_transition/accessors.ex @@ -556,7 +556,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do {:ok, IndexedAttestation.t()} | {:error, String.t()} def get_indexed_attestation(%BeaconState{} = state, attestation) do with {:ok, indices} <- - get_attesting_indices(state, attestation.data, attestation.aggregation_bits) do + get_attesting_indices(state, attestation) do %IndexedAttestation{ attesting_indices: Enum.sort(indices), data: attestation.data, @@ -585,16 +585,34 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do that slot) and then filters the ones that actually participated. It returns an unordered MapSet, which is useful for checking inclusion, but should be ordered if used to validate an attestation. """ - @spec get_attesting_indices(BeaconState.t(), Types.AttestationData.t(), Types.bitlist()) :: + @spec get_attesting_indices(BeaconState.t(), Types.Attestation.t()) :: {:ok, MapSet.t()} | {:error, String.t()} - def get_attesting_indices(%BeaconState{} = state, data, bits) do - with {:ok, committee} <- get_beacon_committee(state, data.slot, data.index) do - committee - |> Stream.with_index() - |> Stream.filter(fn {_value, index} -> participated?(bits, index) end) - |> Stream.map(fn {value, _index} -> value end) - |> MapSet.new() - |> then(&{:ok, &1}) + def get_attesting_indices(%BeaconState{} = state, %Attestation{ + data: data, + aggregation_bits: aggregation_bits, + committee_bits: committee_bits + }) do + committee_bits + |> get_committee_indices() + |> Enum.reduce_while({MapSet.new(), 0}, fn committee_index, {attesters, offset} -> + case get_beacon_committee(state, data.slot, committee_index) do + {:ok, committee} -> + committee_attesters = + committee + |> Stream.with_index(offset) + |> Stream.filter(fn {_validator, pos} -> participated?(aggregation_bits, pos) end) + |> Stream.map(fn {validator, _} -> validator end) + |> MapSet.new() + + {:cont, {MapSet.union(attesters, committee_attesters), offset + length(committee)}} + + error -> + {:halt, error} + end + end) + |> case do + {:error, error} -> {:error, error} + {attesters, _offset} -> {:ok, attesters} end end @@ -629,4 +647,11 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do max(ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT"), total_balance) end + + @spec get_committee_indices(Types.bitvector()) :: Enumerable.t(Types.commitee_index()) + def get_committee_indices(committee_bits) do + bitlist = committee_bits |> :binary.bin_to_list() |> Enum.reverse() + + for {bit, index} <- Enum.with_index(bitlist), bit == 1, do: index + end end diff --git a/lib/lambda_ethereum_consensus/state_transition/predicates.ex b/lib/lambda_ethereum_consensus/state_transition/predicates.ex index eb7ce9850..6246237ab 100644 --- a/lib/lambda_ethereum_consensus/state_transition/predicates.ex +++ b/lib/lambda_ethereum_consensus/state_transition/predicates.ex @@ -37,10 +37,10 @@ defmodule LambdaEthereumConsensus.StateTransition.Predicates do @spec eligible_for_activation_queue?(Validator.t()) :: boolean def eligible_for_activation_queue?(%Validator{} = validator) do far_future_epoch = Constants.far_future_epoch() - max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE") + min_effective_balance = ChainSpec.get("MIN_ACTIVATION_BALANCE") validator.activation_eligibility_epoch == far_future_epoch && - validator.effective_balance == max_effective_balance + validator.effective_balance >= min_effective_balance end @doc """ diff --git a/lib/types/beacon_chain/validator.ex b/lib/types/beacon_chain/validator.ex index 96e536020..f477c572e 100644 --- a/lib/types/beacon_chain/validator.ex +++ b/lib/types/beacon_chain/validator.ex @@ -5,8 +5,6 @@ defmodule Types.Validator do """ use LambdaEthereumConsensus.Container - @eth1_address_withdrawal_prefix <<0x01>> - fields = [ :pubkey, :withdrawal_credentials, @@ -38,7 +36,7 @@ defmodule Types.Validator do @spec has_eth1_withdrawal_credential(t()) :: boolean def has_eth1_withdrawal_credential(%{withdrawal_credentials: withdrawal_credentials}) do <> = withdrawal_credentials - first_byte_of_withdrawal_credentials == @eth1_address_withdrawal_prefix + first_byte_of_withdrawal_credentials == Constants.eth1_address_withdrawal_prefix() end @doc """ @@ -51,7 +49,7 @@ defmodule Types.Validator do balance, epoch ) do - has_eth1_withdrawal_credential(validator) && withdrawable_epoch <= epoch && balance > 0 + has_execution_withdrawal_credential(validator) && withdrawable_epoch <= epoch && balance > 0 end @doc """ @@ -62,10 +60,12 @@ defmodule Types.Validator do %{effective_balance: effective_balance} = validator, balance ) do - max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE") + max_effective_balance = get_max_effective_balance(validator) has_max_effective_balance = effective_balance == max_effective_balance has_excess_balance = balance > max_effective_balance - has_eth1_withdrawal_credential(validator) && has_max_effective_balance && has_excess_balance + + has_execution_withdrawal_credential(validator) && has_max_effective_balance && + has_excess_balance end @impl LambdaEthereumConsensus.Container @@ -81,4 +81,38 @@ defmodule Types.Validator do {:withdrawable_epoch, TypeAliases.epoch()} ] end + + @spec compounding_withdrawal_credential?(Types.bytes32()) :: boolean() + def compounding_withdrawal_credential?(withdrawal_credentials) do + <> = withdrawal_credentials + first_byte == Constants.compounding_withdrawal_prefix() + end + + @doc """ + Check if ``validator`` has an 0x02 prefixed "compounding" withdrawal credential. + """ + @spec has_compounding_withdrawal_credential(t()) :: boolean() + def has_compounding_withdrawal_credential(validator) do + compounding_withdrawal_credential?(validator.withdrawal_credentials) + end + + @doc """ + Check if ``validator`` has a 0x01 or 0x02 prefixed withdrawal credential. + """ + @spec has_execution_withdrawal_credential(t()) :: boolean() + def has_execution_withdrawal_credential(validator) do + has_compounding_withdrawal_credential(validator) || has_eth1_withdrawal_credential(validator) + end + + @doc """ + Get max effective balance for ``validator``. + """ + @spec get_max_effective_balance(t()) :: Types.gwei() + def get_max_effective_balance(validator) do + if has_compounding_withdrawal_credential(validator) do + ChainSpec.get("MAX_EFFECTIVE_BALANCE_ELECTRA") + else + ChainSpec.get("MIN_ACTIVATION_BALANCE") + end + end end