diff --git a/lib/lambda_ethereum_consensus/p2p/gossip_handler.ex b/lib/lambda_ethereum_consensus/p2p/gossip_handler.ex index dbd79265f..9656bf0c4 100644 --- a/lib/lambda_ethereum_consensus/p2p/gossip_handler.ex +++ b/lib/lambda_ethereum_consensus/p2p/gossip_handler.ex @@ -7,6 +7,7 @@ defmodule LambdaEthereumConsensus.P2P.GossipHandler do alias LambdaEthereumConsensus.Beacon.PendingBlocks alias LambdaEthereumConsensus.ForkChoice.Store + alias LambdaEthereumConsensus.Utils.BitVector alias SszTypes.{AggregateAndProof, SignedAggregateAndProof, SignedBeaconBlock} @spec handle_message(String.t(), struct) :: :ok @@ -31,7 +32,7 @@ defmodule LambdaEthereumConsensus.P2P.GossipHandler do "/eth2/bba4da96/beacon_aggregate_and_proof/ssz_snappy", %SignedAggregateAndProof{message: %AggregateAndProof{aggregate: aggregate}} ) do - votes = count_bits(aggregate.aggregation_bits) + votes = BitVector.count(aggregate.aggregation_bits) slot = aggregate.data.slot root = aggregate.data.beacon_block_root |> Base.encode16() @@ -49,7 +50,4 @@ defmodule LambdaEthereumConsensus.P2P.GossipHandler do |> then(&"[#{topic_name}] decoded: '#{&1}'") |> Logger.debug() end - - defp count_bits(bitstring), - do: for(<>, do: bit) |> Enum.sum() end diff --git a/lib/lambda_ethereum_consensus/state_transition/accessors.ex b/lib/lambda_ethereum_consensus/state_transition/accessors.ex index abf4403ea..b2f2d04f5 100644 --- a/lib/lambda_ethereum_consensus/state_transition/accessors.ex +++ b/lib/lambda_ethereum_consensus/state_transition/accessors.ex @@ -3,8 +3,9 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do Functions accessing the current `BeaconState` """ + alias LambdaEthereumConsensus.SszEx alias LambdaEthereumConsensus.StateTransition.{Math, Misc, Predicates} - alias SszTypes.{Attestation, BeaconState, IndexedAttestation, SyncCommittee} + alias SszTypes.{Attestation, BeaconState, IndexedAttestation, SyncCommittee, Validator} @doc """ Return the next sync committee, with possible pubkey duplicates. @@ -86,7 +87,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do candidate_index = active_validator_indices |> Enum.fetch!(shuffled_index) <<_::binary-size(rem(index, 32)), random_byte, _::binary>> = - :crypto.hash(:sha256, seed <> Misc.uint64_to_bytes(div(index, 32))) + SszEx.hash(seed <> Misc.uint64_to_bytes(div(index, 32))) effective_balance = Enum.fetch!(validators, candidate_index).effective_balance @@ -104,7 +105,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do """ @spec get_active_validator_indices(BeaconState.t(), SszTypes.epoch()) :: list(SszTypes.validator_index()) - def get_active_validator_indices(%BeaconState{validators: validators} = _state, epoch) do + def get_active_validator_indices(%BeaconState{validators: validators}, epoch) do validators |> Stream.with_index() |> Stream.filter(fn {v, _} -> @@ -184,8 +185,13 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do """ @spec get_total_active_balance(BeaconState.t()) :: SszTypes.gwei() def get_total_active_balance(state) do - active_validator_indices = get_active_validator_indices(state, get_current_epoch(state)) - get_total_balance(state, active_validator_indices) + epoch = get_current_epoch(state) + + state.validators + |> Stream.filter(&Predicates.is_active_validator(&1, epoch)) + |> Stream.map(fn %Validator{effective_balance: effective_balance} -> effective_balance end) + |> Enum.sum() + |> max(ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT")) end @doc """ @@ -227,23 +233,16 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do Return the beacon proposer index at the current slot. """ @spec get_beacon_proposer_index(BeaconState.t()) :: - {:ok, SszTypes.validator_index()} | {:error, binary()} + {:ok, SszTypes.validator_index()} | {:error, String.t()} def get_beacon_proposer_index(state) do epoch = get_current_epoch(state) - seed = - :crypto.hash( - :sha256, - get_seed(state, epoch, Constants.domain_beacon_proposer()) <> - Misc.uint64_to_bytes(state.slot) - ) - indices = get_active_validator_indices(state, epoch) - case Misc.compute_proposer_index(state, indices, seed) do - {:error, msg} -> {:error, msg} - {:ok, i} -> {:ok, i} - end + state + |> get_seed(epoch, Constants.domain_beacon_proposer()) + |> then(&SszEx.hash(&1 <> Misc.uint64_to_bytes(state.slot))) + |> then(&Misc.compute_proposer_index(state, indices, &1)) end @doc """ @@ -412,7 +411,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do ChainSpec.get("MIN_SEED_LOOKAHEAD") - 1 ) - :crypto.hash(:sha256, domain_type <> Misc.uint64_to_bytes(epoch) <> mix) + SszEx.hash(domain_type <> Misc.uint64_to_bytes(epoch) <> mix) end @doc """ diff --git a/lib/lambda_ethereum_consensus/state_transition/misc.ex b/lib/lambda_ethereum_consensus/state_transition/misc.ex index 0cade1233..dd029d20e 100644 --- a/lib/lambda_ethereum_consensus/state_transition/misc.ex +++ b/lib/lambda_ethereum_consensus/state_transition/misc.ex @@ -3,6 +3,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Misc do Misc functions """ + alias LambdaEthereumConsensus.SszEx alias SszTypes.BeaconState import Bitwise alias SszTypes.BeaconState @@ -55,7 +56,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Misc do Enum.reduce(0..(shuffle_round_count - 1), index, fn round, current_index -> round_as_bytes = <> - hash_of_seed_round = :crypto.hash(:sha256, seed <> round_as_bytes) + hash_of_seed_round = SszEx.hash(seed <> round_as_bytes) pivot = rem(bytes_to_uint64(hash_of_seed_round), index_count) @@ -65,7 +66,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Misc do position_div_256 = uint_to_bytes4(div(position, 256)) source = - :crypto.hash(:sha256, seed <> round_as_bytes <> position_div_256) + SszEx.hash(seed <> round_as_bytes <> position_div_256) byte_index = div(rem(position, 256), 8) <<_::binary-size(byte_index), byte, _::binary>> = source @@ -146,7 +147,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Misc do total = length(indices) {:ok, i} = compute_shuffled_index(rem(i, total), total, seed) candidate_index = Enum.at(indices, i) - random_byte = :crypto.hash(:sha256, seed <> uint_to_bytes4(div(i, 32))) + 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 diff --git a/lib/lambda_ethereum_consensus/state_transition/operations.ex b/lib/lambda_ethereum_consensus/state_transition/operations.ex index ea07f6e8d..3d0a0766d 100644 --- a/lib/lambda_ethereum_consensus/state_transition/operations.ex +++ b/lib/lambda_ethereum_consensus/state_transition/operations.ex @@ -3,7 +3,8 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do This module contains functions for handling state transition """ - alias LambdaEthereumConsensus.StateTransition.{Accessors, Misc, Mutators, Predicates} + alias LambdaEthereumConsensus.SszEx + alias LambdaEthereumConsensus.StateTransition.{Accessors, Math, Misc, Mutators, Predicates} alias LambdaEthereumConsensus.Utils.BitVector alias SszTypes.{ @@ -109,80 +110,91 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do @spec process_sync_aggregate(BeaconState.t(), SyncAggregate.t()) :: {:ok, BeaconState.t()} | {:error, String.t()} - def process_sync_aggregate( - %BeaconState{ - slot: slot, - current_sync_committee: current_sync_committee, - validators: validators - } = state, - %SyncAggregate{ - sync_committee_bits: sync_committee_bits, - sync_committee_signature: sync_committee_signature - } - ) do + def process_sync_aggregate(%BeaconState{} = state, %SyncAggregate{} = aggregate) do # Verify sync committee aggregate signature signing over the previous slot block root - committee_pubkeys = current_sync_committee.pubkeys - - # TODO: Change bitvectors to be in little-endian instead of converting manually - sync_committee_bits_as_num = sync_committee_bits |> :binary.decode_unsigned() - - sync_committee_bits = - <> + committee_pubkeys = state.current_sync_committee.pubkeys + sync_committee_bits = parse_sync_committee_bits(aggregate.sync_committee_bits) participant_pubkeys = - Enum.with_index(committee_pubkeys) + committee_pubkeys + |> Enum.with_index() |> Enum.filter(fn {_, index} -> BitVector.set?(sync_committee_bits, index) end) |> Enum.map(fn {public_key, _} -> public_key end) - previous_slot = max(slot, 1) - 1 + previous_slot = max(state.slot, 1) - 1 epoch = Misc.compute_epoch_at_slot(previous_slot) domain = Accessors.get_domain(state, Constants.domain_sync_committee(), epoch) with {:ok, block_root} <- Accessors.get_block_root_at_slot(state, previous_slot), signing_root <- Misc.compute_signing_root(block_root, domain), - {:ok, true} <- - Bls.eth_fast_aggregate_verify( - participant_pubkeys, - signing_root, - sync_committee_signature - ) do + :ok <- + verify_signature(participant_pubkeys, signing_root, aggregate.sync_committee_signature), + {:ok, proposer_index} <- Accessors.get_beacon_proposer_index(state) do # Compute participant and proposer rewards {participant_reward, proposer_reward} = compute_sync_aggregate_rewards(state) + total_proposer_reward = BitVector.count(sync_committee_bits) * proposer_reward + + # PERF: make Map with committee_index by pubkey, then + # Enum.map validators -> new balance all in place, without map_reduce + committee_deltas = + state.validators + |> get_sync_committee_indices(committee_pubkeys) + |> Stream.with_index() + |> Stream.map(fn {validator_index, committee_index} -> + if BitVector.set?(sync_committee_bits, committee_index), + do: {validator_index, participant_reward}, + else: {validator_index, -participant_reward} + end) + |> Enum.sort(fn {vi1, _}, {vi2, _} -> vi1 <= vi2 end) + # Apply participant and proposer rewards - committee_indices = get_sync_committee_indices(validators, committee_pubkeys) + {new_balances, []} = + state.balances + |> Stream.with_index() + |> Stream.map(&add_proposer_reward(&1, proposer_index, total_proposer_reward)) + |> Enum.map_reduce(committee_deltas, &update_balance/2) - Stream.with_index(committee_indices) - |> Enum.reduce_while({:ok, state}, fn {participant_index, index}, {_, state} -> - if BitVector.set?(sync_committee_bits, index) do - state - |> increase_balance_or_return_error( - participant_index, - participant_reward, - proposer_reward - ) - else - {:cont, - {:ok, state |> Mutators.decrease_balance(participant_index, participant_reward)}} - end - end) - else - {:ok, false} -> {:error, "Signature verification failed"} - {:error, message} -> {:error, message} + {:ok, %BeaconState{state | balances: new_balances}} end end + defp add_proposer_reward({balance, proposer}, proposer, proposer_reward), + do: {balance + proposer_reward, proposer} + + defp add_proposer_reward(v, _, _), do: v + + defp update_balance({balance, i}, [{i, delta} | acc]), + do: update_balance({max(balance + delta, 0), i}, acc) + + defp update_balance({balance, _}, acc), do: {balance, acc} + + defp verify_signature(pubkeys, message, signature) do + case Bls.eth_fast_aggregate_verify(pubkeys, message, signature) do + {:ok, true} -> :ok + _ -> {:error, "Signature verification failed"} + end + end + + defp parse_sync_committee_bits(bits) do + # TODO: Change bitvectors to be in little-endian instead of converting manually + bitsize = bit_size(bits) + <> = bits + <> + end + @spec compute_sync_aggregate_rewards(BeaconState.t()) :: {SszTypes.gwei(), SszTypes.gwei()} defp compute_sync_aggregate_rewards(state) do # Compute participant and proposer rewards - total_active_increments = - div( - Accessors.get_total_active_balance(state), - ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT") - ) + total_active_balance = Accessors.get_total_active_balance(state) - total_base_rewards = - Accessors.get_base_reward_per_increment(state) * total_active_increments + 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() + denominator = Math.integer_squareroot(total_active_balance) + base_reward_per_increment = div(numerator, denominator) + total_base_rewards = base_reward_per_increment * total_active_increments max_participant_rewards = (total_base_rewards * Constants.sync_reward_weight()) @@ -201,41 +213,14 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do @spec get_sync_committee_indices(list(Validator.t()), list(SszTypes.bls_pubkey())) :: list(integer) defp get_sync_committee_indices(validators, committee_pubkeys) do - # Apply participant and proposer rewards all_pubkeys = validators - |> Enum.map(fn %Validator{pubkey: pubkey} -> pubkey end) + |> Stream.map(fn %Validator{pubkey: pubkey} -> pubkey end) + |> Stream.with_index() + |> Map.new() committee_pubkeys - |> Enum.with_index() - |> Enum.map(fn {public_key, _} -> - Enum.find_index(all_pubkeys, fn x -> x == public_key end) - end) - end - - @spec increase_balance_or_return_error( - BeaconState.t(), - SszTypes.validator_index(), - SszTypes.gwei(), - SszTypes.gwei() - ) :: {:cont, {:ok, BeaconState.t()}} | {:halt, {:error, String.t()}} - defp increase_balance_or_return_error( - %BeaconState{} = state, - participant_index, - participant_reward, - proposer_reward - ) do - case Accessors.get_beacon_proposer_index(state) do - {:ok, proposer_index} -> - {:cont, - {:ok, - state - |> Mutators.increase_balance(participant_index, participant_reward) - |> Mutators.increase_balance(proposer_index, proposer_reward)}} - - {:error, _} -> - {:halt, {:error, "Error getting beacon proposer index"}} - end + |> Enum.map(&Map.fetch!(all_pubkeys, &1)) end @doc """ @@ -317,151 +302,117 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do ) do expected_withdrawals = get_expected_withdrawals(state) - length_of_validators = length(validators) - - with {:ok, state} <- decrease_balances(state, withdrawals, expected_withdrawals) do - {:ok, - state - |> update_next_withdrawal_index(expected_withdrawals) - |> update_next_withdrawal_validator_index(expected_withdrawals, length_of_validators)} + with :ok <- check_withdrawals(withdrawals, expected_withdrawals) do + state + |> decrease_balances(withdrawals) + |> update_next_withdrawal_index(withdrawals) + |> update_next_withdrawal_validator_index(withdrawals, length(validators)) + |> then(&{:ok, &1}) end end + # Update the next withdrawal index if this block contained withdrawals @spec update_next_withdrawal_index(BeaconState.t(), list(Withdrawal.t())) :: BeaconState.t() - defp update_next_withdrawal_index(state, expected_withdrawals) do - # Update the next withdrawal index if this block contained withdrawals - length_of_expected_withdrawals = length(expected_withdrawals) + defp update_next_withdrawal_index(state, []), do: state - case length_of_expected_withdrawals != 0 do - true -> - latest_withdrawal = List.last(expected_withdrawals) - %BeaconState{state | next_withdrawal_index: latest_withdrawal.index + 1} - - false -> - state - end + defp update_next_withdrawal_index(state, withdrawals) do + latest_withdrawal = List.last(withdrawals) + %BeaconState{state | next_withdrawal_index: latest_withdrawal.index + 1} end - @spec update_next_withdrawal_validator_index(BeaconState.t(), list(Withdrawal.t()), integer) :: + @spec update_next_withdrawal_validator_index(BeaconState.t(), list(Withdrawal.t()), integer()) :: BeaconState.t() - defp update_next_withdrawal_validator_index(state, expected_withdrawals, length_of_validators) do - length_of_expected_withdrawals = length(expected_withdrawals) + defp update_next_withdrawal_validator_index(state, withdrawals, validator_len) do + next_index = + if length(withdrawals) == ChainSpec.get("MAX_WITHDRAWALS_PER_PAYLOAD") do + # Update the next validator index to start the next withdrawal sweep + latest_withdrawal = List.last(withdrawals) + latest_withdrawal.validator_index + 1 + else + # Advance sweep by the max length of the sweep if there was not a full set of withdrawals + state.next_withdrawal_validator_index + + ChainSpec.get("MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP") + end - case length_of_expected_withdrawals == ChainSpec.get("MAX_WITHDRAWALS_PER_PAYLOAD") do - # Update the next validator index to start the next withdrawal sweep - true -> - latest_withdrawal = List.last(expected_withdrawals) - next_validator_index = rem(latest_withdrawal.validator_index + 1, length_of_validators) - %BeaconState{state | next_withdrawal_validator_index: next_validator_index} - - # Advance sweep by the max length of the sweep if there was not a full set of withdrawals - false -> - next_index = - state.next_withdrawal_validator_index + - ChainSpec.get("MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP") - - next_validator_index = rem(next_index, length_of_validators) - %BeaconState{state | next_withdrawal_validator_index: next_validator_index} - end + next_validator_index = rem(next_index, validator_len) + %BeaconState{state | next_withdrawal_validator_index: next_validator_index} end - @spec decrease_balances(BeaconState.t(), list(Withdrawal.t()), list(Withdrawal.t())) :: - {:ok, BeaconState.t()} | {:error, String.t()} - defp decrease_balances(_state, withdrawals, expected_withdrawals) + @spec check_withdrawals(list(Withdrawal.t()), list(Withdrawal.t())) :: + :ok | {:error, String.t()} + defp check_withdrawals(withdrawals, expected_withdrawals) when length(withdrawals) !== length(expected_withdrawals) do {:error, "expected withdrawals don't match the state withdrawals in length"} end - @spec decrease_balances(BeaconState.t(), list(Withdrawal.t()), list(Withdrawal.t())) :: - {:ok, BeaconState.t()} | {:error, String.t()} - defp decrease_balances(state, withdrawals, expected_withdrawals) do - Enum.zip(expected_withdrawals, withdrawals) - |> Enum.reduce_while({:ok, state}, &decrease_or_halt/2) + defp check_withdrawals(withdrawals, expected_withdrawals) do + Stream.zip(expected_withdrawals, withdrawals) + |> Enum.all?(fn {expected_withdrawal, withdrawal} -> + expected_withdrawal == withdrawal + end) + |> then(&if &1, do: :ok, else: {:error, "withdrawal doesn't match expected withdrawal"}) end - defp decrease_or_halt({expected_withdrawal, withdrawal}, _) - when expected_withdrawal !== withdrawal do - {:halt, {:error, "withdrawal != expected_withdrawal"}} - end + @spec decrease_balances(BeaconState.t(), list(Withdrawal.t())) :: BeaconState.t() + defp decrease_balances(state, withdrawals) do + withdrawals = Enum.sort(withdrawals, &(&1.validator_index <= &2.validator_index)) - defp decrease_or_halt({_, withdrawal}, {:ok, state}) do - {:cont, - {:ok, BeaconState.decrease_balance(state, withdrawal.validator_index, withdrawal.amount)}} + state.balances + |> Stream.with_index() + |> Enum.map_reduce(withdrawals, &maybe_decrease_balance/2) + |> then(fn {balances, []} -> %BeaconState{state | balances: balances} end) end + defp maybe_decrease_balance({balance, index}, [ + %Withdrawal{validator_index: index, amount: amount} | remaining + ]), + do: {max(balance - amount, 0), remaining} + + defp maybe_decrease_balance({balance, _index}, acc), do: {balance, acc} + @spec get_expected_withdrawals(BeaconState.t()) :: list(Withdrawal.t()) - defp get_expected_withdrawals( - %BeaconState{ - next_withdrawal_index: next_withdrawal_index, - next_withdrawal_validator_index: next_withdrawal_validator_index, - validators: validators, - balances: balances - } = state - ) do + defp get_expected_withdrawals(%BeaconState{} = state) do # Compute the next batch of withdrawals which should be included in a block. epoch = Accessors.get_current_epoch(state) - withdrawal_index = next_withdrawal_index - validator_index = next_withdrawal_validator_index + max_validators_per_withdrawals_sweep = ChainSpec.get("MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP") - bound = min(length(validators), max_validators_per_withdrawals_sweep) - - {withdrawals, _, _} = - Enum.reduce_while(0..(bound - 1), {[], validator_index, withdrawal_index}, fn _, - {withdrawals, - validator_index, - withdrawal_index} -> - validator = Enum.fetch!(validators, validator_index) - balance = Enum.fetch!(balances, validator_index) - %Validator{withdrawal_credentials: withdrawal_credentials} = validator - - {withdrawals, withdrawal_index} = - cond do - Validator.is_fully_withdrawable_validator(validator, balance, epoch) -> - <<_::binary-size(12), execution_address::binary>> = withdrawal_credentials - - withdrawal = %Withdrawal{ - index: withdrawal_index, - validator_index: validator_index, - address: execution_address, - amount: balance - } - - withdrawals = [withdrawal | withdrawals] - withdrawal_index = withdrawal_index + 1 - - {withdrawals, withdrawal_index} - - Validator.is_partially_withdrawable_validator(validator, balance) -> - <<_::binary-size(12), execution_address::binary>> = withdrawal_credentials - max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE") - - withdrawal = %Withdrawal{ - index: withdrawal_index, - validator_index: validator_index, - address: execution_address, - amount: balance - max_effective_balance - } - - withdrawals = [withdrawal | withdrawals] - withdrawal_index = withdrawal_index + 1 - - {withdrawals, withdrawal_index} - - true -> - {withdrawals, withdrawal_index} - end - - max_withdrawals_per_payload = ChainSpec.get("MAX_WITHDRAWALS_PER_PAYLOAD") - - if length(withdrawals) == max_withdrawals_per_payload do - {:halt, {withdrawals, validator_index, withdrawal_index}} - else - validator_index = rem(validator_index + 1, length(validators)) - {:cont, {withdrawals, validator_index, withdrawal_index}} - end - end) + max_withdrawals_per_payload = ChainSpec.get("MAX_WITHDRAWALS_PER_PAYLOAD") + max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE") + + bound = min(length(state.validators), max_validators_per_withdrawals_sweep) - Enum.reverse(withdrawals) + Stream.zip([state.validators, state.balances]) + |> Stream.with_index() + |> Stream.cycle() + |> Stream.drop(state.next_withdrawal_validator_index) + |> Stream.take(bound) + |> Stream.map(fn {{validator, balance}, index} -> + cond do + Validator.is_fully_withdrawable_validator(validator, balance, epoch) -> + {validator, balance, index} + + Validator.is_partially_withdrawable_validator(validator, balance) -> + {validator, balance - max_effective_balance, index} + + true -> + nil + end + end) + |> Stream.reject(&is_nil/1) + |> Stream.with_index() + |> Stream.map(fn {{validator, balance, validator_index}, index} -> + %Validator{withdrawal_credentials: withdrawal_credentials} = validator + + <<_::binary-size(12), execution_address::binary>> = withdrawal_credentials + + %Withdrawal{ + index: index + state.next_withdrawal_index, + validator_index: validator_index, + address: execution_address, + amount: balance + } + end) + |> Enum.take(max_withdrawals_per_payload) end @spec process_proposer_slashing(BeaconState.t(), SszTypes.ProposerSlashing.t()) :: @@ -832,7 +783,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do if Bls.valid?(proposer.pubkey, signing_root, randao_reveal) do randao_mix = Accessors.get_randao_mix(state, epoch) - hash = :crypto.hash(:sha256, randao_reveal) + hash = SszEx.hash(randao_reveal) # Mix in RANDAO reveal mix = :crypto.exor(randao_mix, hash) @@ -1013,7 +964,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do defp validate_withdrawal_credentials(validator, address_change) do <> = validator.withdrawal_credentials - <<_, hash::binary-size(31)>> = :crypto.hash(:sha256, address_change.from_bls_pubkey) + <<_, hash::binary-size(31)>> = SszEx.hash(address_change.from_bls_pubkey) if prefix == Constants.bls_withdrawal_prefix() and address == hash do {:ok} diff --git a/lib/lambda_ethereum_consensus/state_transition/predicates.ex b/lib/lambda_ethereum_consensus/state_transition/predicates.ex index 3a38ff0db..73ffe288e 100644 --- a/lib/lambda_ethereum_consensus/state_transition/predicates.ex +++ b/lib/lambda_ethereum_consensus/state_transition/predicates.ex @@ -3,6 +3,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Predicates do Range of predicates enabling verification of state """ + alias LambdaEthereumConsensus.SszEx alias LambdaEthereumConsensus.StateTransition.{Accessors, Misc} alias SszTypes.BeaconState alias SszTypes.Validator @@ -131,9 +132,9 @@ defmodule LambdaEthereumConsensus.StateTransition.Predicates do defp hash_merkle_node(value_1, value_2, index, i) do if rem(div(index, 2 ** i), 2) == 1 do - :crypto.hash(:sha256, value_1 <> value_2) + SszEx.hash(value_1 <> value_2) else - :crypto.hash(:sha256, value_2 <> value_1) + SszEx.hash(value_2 <> value_1) end end diff --git a/lib/ssz_ex.ex b/lib/ssz_ex.ex index 85acb55eb..e46db263c 100644 --- a/lib/ssz_ex.ex +++ b/lib/ssz_ex.ex @@ -2,9 +2,14 @@ defmodule LambdaEthereumConsensus.SszEx do @moduledoc """ SSZ library in Elixir """ + ################# ### Public API ################# + + @spec hash(iodata()) :: binary() + def hash(data), do: :crypto.hash(:sha256, data) + def encode(value, {:int, size}), do: encode_int(value, size) def encode(value, :bool), do: encode_bool(value) diff --git a/lib/utils/bit_vector.ex b/lib/utils/bit_vector.ex index 3af1e1d44..5225e1f27 100644 --- a/lib/utils/bit_vector.ex +++ b/lib/utils/bit_vector.ex @@ -92,4 +92,10 @@ defmodule LambdaEthereumConsensus.Utils.BitVector do <> = bit_vector <<0::size(steps), remaining::bitstring>> end + + @doc """ + Returns the amount of bits set. + """ + @spec count(t) :: non_neg_integer() + def count(bit_vector), do: for(<>, do: bit) |> Enum.sum() end