Skip to content

Commit

Permalink
Merge branch 'main' into beacon-api-to-readme
Browse files Browse the repository at this point in the history
  • Loading branch information
avilagaston9 authored Apr 4, 2024
2 parents 0e35c90 + 39a9412 commit b50e41d
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 64 deletions.
3 changes: 2 additions & 1 deletion lib/beacon_api/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ defmodule BeaconApi.Utils do
|> Map.new()
end

defp to_json(binary, {x, :bytes, _}) when x in [:list, :vector], do: to_json(binary)
defp to_json(binary, {:byte_list, _}), do: to_json(binary)
defp to_json(binary, {:byte_vector, _}), do: to_json(binary)

defp to_json(list, {x, schema, _}) when x in [:list, :vector],
do: Enum.map(list, fn elem -> to_json(elem, schema) end)
Expand Down
3 changes: 3 additions & 0 deletions lib/lambda_ethereum_consensus/execution/engine_api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,8 @@ defmodule LambdaEthereumConsensus.Execution.EngineApi do
@spec get_block_header(nil | Types.uint64() | Types.root()) :: {:ok, any} | {:error, any}
def get_block_header(block_id), do: impl().get_block_header(block_id)

@spec get_deposit_logs(Range.t()) :: {:ok, list(any)} | {:error, any}
def get_deposit_logs(block_number_range), do: impl().get_deposit_logs(block_number_range)

defp impl, do: Application.fetch_env!(:lambda_ethereum_consensus, __MODULE__)[:implementation]
end
17 changes: 17 additions & 0 deletions lib/lambda_ethereum_consensus/execution/engine_api/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,23 @@ defmodule LambdaEthereumConsensus.Execution.EngineApi.Api do
def get_block_header(block_id) when is_binary(block_id),
do: call("eth_getBlockByHash", [RPC.normalize(block_id), false])

@spec get_deposit_logs(Range.t()) :: {:ok, list(any)} | {:error, any}
def get_deposit_logs(from_block..to_block) do
deposit_contract = ChainSpec.get("DEPOSIT_CONTRACT_ADDRESS")

# `keccak("DepositEvent(bytes,bytes,bytes,bytes,bytes)")`
deposit_event_topic = "0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"

filter = %{
"address" => RPC.normalize(deposit_contract),
"fromBlock" => RPC.normalize(from_block),
"toBlock" => RPC.normalize(to_block),
"topics" => [deposit_event_topic]
}

call("eth_getLogs", [filter])
end

defp call(method, params) do
config = Application.fetch_env!(:lambda_ethereum_consensus, EngineApi)

Expand Down
4 changes: 4 additions & 0 deletions lib/lambda_ethereum_consensus/execution/engine_api/mocked.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@ defmodule LambdaEthereumConsensus.Execution.EngineApi.Mocked do
# TODO: should we mock this too?
@spec get_block_header(nil | Types.uint64() | Types.root()) :: {:ok, any} | {:error, any}
def get_block_header(_block_id), do: raise("not supported")

# TODO: should we mock this too?
@spec get_deposit_logs(Range.t()) :: {:ok, list(any)} | {:error, any}
def get_deposit_logs(_range), do: raise("not supported")
end
47 changes: 46 additions & 1 deletion lib/lambda_ethereum_consensus/execution/execution_client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ defmodule LambdaEthereumConsensus.Execution.ExecutionClient do
Execution Layer Engine API methods
"""
alias LambdaEthereumConsensus.Execution.EngineApi
alias LambdaEthereumConsensus.SszEx
alias Types.DepositData
alias Types.ExecutionPayload
alias Types.NewPayloadRequest
require Logger
Expand Down Expand Up @@ -91,13 +93,26 @@ defmodule LambdaEthereumConsensus.Execution.ExecutionClient do
}

@spec get_block_metadata(nil | Types.uint64() | Types.root()) ::
{:ok, block_metadata()} | {:error, any}
{:ok, block_metadata() | nil} | {:error, any}
def get_block_metadata(block_id) do
with {:ok, block} <- EngineApi.get_block_header(block_id) do
parse_block_metadata(block)
end
end

@type deposit_log :: %{
data: DepositData.t(),
block_number: Types.uint64(),
index: Types.uint64()
}

@spec get_deposit_logs(Range.t()) :: {:ok, [deposit_log()]} | {:error, any}
def get_deposit_logs(block_range) do
with {:ok, raw_logs} <- EngineApi.get_deposit_logs(block_range) do
parse_raw_logs(raw_logs)
end
end

defp parse_block_metadata(nil), do: {:ok, nil}

defp parse_block_metadata(%{
Expand All @@ -119,4 +134,34 @@ defmodule LambdaEthereumConsensus.Execution.ExecutionClient do
end

defp parse_block_metadata(_), do: {:error, "invalid block format"}

defp parse_raw_logs(raw_logs) do
{:ok, Enum.map(raw_logs, &parse_raw_log/1)}
end

@min_hex_data_byte_size 1104

defp parse_raw_log(%{"data" => "0x" <> hex_data, "blockNumber" => "0x" <> hex_block_number})
when byte_size(hex_data) >= @min_hex_data_byte_size do
# TODO: we might want to move this parsing behind the EngineApi module (and maybe rename it).
data = Base.decode16!(hex_data, case: :mixed)

# These magic numbers correspond to the start and length of each field in the deposit log data.
pubkey = binary_part(data, 192, 48)
withdrawal_credentials = binary_part(data, 288, 32)
{:ok, amount} = binary_part(data, 352, 8) |> SszEx.decode(TypeAliases.uint64())
signature = binary_part(data, 416, 96)
{:ok, index} = binary_part(data, 544, 8) |> SszEx.decode(TypeAliases.uint64())

block_number = String.to_integer(hex_block_number, 16)

deposit_data = %DepositData{
pubkey: pubkey,
withdrawal_credentials: withdrawal_credentials,
amount: amount,
signature: signature
}

%{data: deposit_data, block_number: block_number, index: index}
end
end
4 changes: 4 additions & 0 deletions lib/lambda_ethereum_consensus/execution/rpc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ defmodule LambdaEthereumConsensus.Execution.RPC do
end)
end

def normalize_response(response) when is_list(response) do
Enum.map(response, &normalize_response/1)
end

def normalize_response(response) do
response
end
Expand Down
66 changes: 24 additions & 42 deletions lib/ssz_ex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ defmodule LambdaEthereumConsensus.SszEx do
Composite type of ordered homogeneous elements with an exact number of elements (`length`).
`inner_type` is the schema of the inner elements. Depending on it, the vector could be
variable-size or fixed-size.
- `{:byte_list, max_length}` or `{:list, :bytes, max_length}`
- `{:byte_list, max_length}`
Same as `{:list, {:int, 8}, length}` (i.e. a list of bytes), but
encodes-from/decodes-into an Elixir binary.
- `{:bytes, length}` or `{:vector, :bytes, length}`
- `{:byte_vector, length}`
Same as `{:vector, {:int, 8}, length}` (i.e. a vector of bytes),
but encodes-from/decodes-into an Elixir binary.
- `{:bitlist, max_length}`
Expand All @@ -45,17 +45,17 @@ defmodule LambdaEthereumConsensus.SszEx do
:bool
| uint_schema()
| byte_list_schema()
| byte_vector_schema()
| list_schema()
| bytes_schema()
| vector_schema()
| bitlist_schema()
| bitvector_schema()
| container_schema()

@type uint_schema() :: {:int, 8 | 16 | 32 | 64 | 128 | 256}
@type byte_list_schema() :: {:byte_list, max_size :: non_neg_integer}
@type byte_vector_schema() :: {:byte_vector, size :: non_neg_integer}
@type list_schema() :: {:list, schema(), max_size :: non_neg_integer}
@type bytes_schema() :: {:bytes, size :: non_neg_integer}
@type vector_schema() :: {:vector, schema(), size :: non_neg_integer}
@type bitlist_schema() :: {:bitlist, max_size :: non_neg_integer}
@type bitvector_schema() :: {:bitvector, size :: non_neg_integer}
Expand Down Expand Up @@ -87,10 +87,8 @@ defmodule LambdaEthereumConsensus.SszEx do
@spec validate_schema!(schema()) :: :ok
def validate_schema!(:bool), do: :ok
def validate_schema!({:int, n}) when n in @allowed_uints, do: :ok
def validate_schema!({:bytes, size}) when size > 0, do: :ok
def validate_schema!({:byte_list, size}) when size > 0, do: :ok
def validate_schema!({:list, :bytes, size}) when size > 0, do: :ok
def validate_schema!({:vector, :bytes, size}) when size > 0, do: :ok
def validate_schema!({:byte_vector, size}) when size > 0, do: :ok
def validate_schema!({:list, sub, size}) when size > 0, do: validate_schema!(sub)
def validate_schema!({:vector, sub, size}) when size > 0, do: validate_schema!(sub)
def validate_schema!({:bitlist, size}) when size > 0, do: :ok
Expand Down Expand Up @@ -122,8 +120,8 @@ defmodule LambdaEthereumConsensus.SszEx do
@spec encode(any(), schema()) :: {:ok, binary()} | {:error, String.t()}
def encode(value, {:int, size}), do: encode_int(value, size)
def encode(value, :bool), do: encode_bool(value)
def encode(value, {:bytes, _}), do: {:ok, value}
def encode(value, {:byte_list, _}), do: {:ok, value}
def encode(value, {:byte_vector, _}), do: {:ok, value}

def encode(list, {:list, inner_type, size}) do
if variable_size?(inner_type),
Expand Down Expand Up @@ -152,8 +150,8 @@ defmodule LambdaEthereumConsensus.SszEx do
@spec decode(binary(), schema()) :: {:ok, any()} | {:error, String.t()}
def decode(binary, :bool), do: decode_bool(binary)
def decode(binary, {:int, size}), do: decode_uint(binary, size)
def decode(value, {:bytes, _}), do: {:ok, value}
def decode(value, {:byte_list, _}), do: {:ok, value}
def decode(value, {:byte_vector, _}), do: {:ok, value}

def decode(binary, {:list, inner_type, size}) do
if variable_size?(inner_type),
Expand Down Expand Up @@ -205,11 +203,21 @@ defmodule LambdaEthereumConsensus.SszEx do
@spec hash_tree_root(non_neg_integer, {:int, non_neg_integer}) :: {:ok, Types.root()}
def hash_tree_root(value, {:int, size}), do: {:ok, pack(value, {:int, size})}

@spec hash_tree_root(binary, {:bytes, non_neg_integer}) :: {:ok, Types.root()}
def hash_tree_root(value, {:bytes, size}) do
packed_chunks = pack(value, {:bytes, size})
@spec hash_tree_root(binary, {:byte_list | :byte_vector, non_neg_integer}) ::
{:ok, Types.root()}
def hash_tree_root(value, {type, _size}) when type in [:byte_list, :byte_vector] do
packed_chunks = pack_bytes(value)
leaf_count = packed_chunks |> get_chunks_len() |> next_pow_of_two()
root = merkleize_chunks_with_virtual_padding(packed_chunks, leaf_count)

root =
if type == :byte_list do
len = value |> bit_size()
root |> mix_in_length(len)
else
root
end

{:ok, root}
end

Expand Down Expand Up @@ -392,11 +400,6 @@ defmodule LambdaEthereumConsensus.SszEx do
<<value::size(size)-little>> |> pack_bytes()
end

@spec pack(binary, {:bytes, non_neg_integer}) :: binary()
def pack(value, {:bytes, _size}) do
value |> pack_bytes()
end

@spec pack(list(), {:list | :vector, any, non_neg_integer}) :: binary() | :error
def pack(list, {type, schema, _}) when type in [:vector, :list] do
list
Expand Down Expand Up @@ -433,10 +436,9 @@ defmodule LambdaEthereumConsensus.SszEx do
"""
def default({:int, _}), do: 0
def default(:bool), do: false
def default({:bytes, size}), do: <<0::size(size * 8)>>
def default({:list, :bytes, _size}), do: <<>>
def default({:byte_list, _size}), do: <<>>
def default({:byte_vector, size}), do: <<0::size(size * 8)>>
def default({:list, _, _}), do: []
def default({:vector, :bytes, size}), do: <<0::size(size * 8)>>
def default({:vector, inner_type, size}), do: default(inner_type) |> List.duplicate(size)
def default({:bitlist, _}), do: BitList.default()
def default({:bitvector, size}), do: BitVector.new(size)
Expand Down Expand Up @@ -476,10 +478,6 @@ defmodule LambdaEthereumConsensus.SszEx do
defp encode_fixed_size_list(list, _inner_type, max_size) when length(list) > max_size,
do: {:error, "invalid max_size of list"}

defp encode_fixed_size_list(binary, :bytes, _size) when is_binary(binary) do
{:ok, binary}
end

defp encode_fixed_size_list(list, inner_type, _size) when is_list(list) do
list
|> Enum.map(&encode(&1, inner_type))
Expand Down Expand Up @@ -592,12 +590,6 @@ defmodule LambdaEthereumConsensus.SszEx do
end
end

defp decode_fixed_list(binary, :bytes, size) do
with :ok <- check_valid_list_size_after_decode(size, byte_size(binary)) do
{:ok, binary}
end
end

defp decode_fixed_list(binary, inner_type, size) do
fixed_size = get_fixed_size(inner_type)

Expand All @@ -607,12 +599,6 @@ defmodule LambdaEthereumConsensus.SszEx do
end
end

defp decode_fixed_vector(binary, :bytes, size) do
with :ok <- check_valid_list_size_after_decode(size, byte_size(binary)) do
{:ok, binary}
end
end

defp decode_fixed_vector(binary, inner_type, size) do
fixed_size = get_fixed_size(inner_type)

Expand Down Expand Up @@ -951,8 +937,7 @@ defmodule LambdaEthereumConsensus.SszEx do

defp get_fixed_size(:bool), do: 1
defp get_fixed_size({:int, size}), do: div(size, @bits_per_byte)
defp get_fixed_size({:bytes, size}), do: size
defp get_fixed_size({:vector, :bytes, size}), do: size
defp get_fixed_size({:byte_vector, size}), do: size
defp get_fixed_size({:vector, inner_type, size}), do: size * get_fixed_size(inner_type)
defp get_fixed_size({:bitvector, size}), do: div(size + 7, 8)

Expand All @@ -967,12 +952,10 @@ defmodule LambdaEthereumConsensus.SszEx do
defp variable_size?({:list, _, _}), do: true
defp variable_size?(:bool), do: false
defp variable_size?({:byte_list, _}), do: true
defp variable_size?(:bytes), do: false
defp variable_size?({:byte_vector, _}), do: false
defp variable_size?({:int, _}), do: false
defp variable_size?({:bytes, _}), do: false
defp variable_size?({:bitlist, _}), do: true
defp variable_size?({:bitvector, _}), do: false
defp variable_size?({:vector, :bytes, _}), do: false
defp variable_size?({:vector, inner_type, _}), do: variable_size?(inner_type)

defp variable_size?(module) when is_atom(module) do
Expand All @@ -983,7 +966,6 @@ defmodule LambdaEthereumConsensus.SszEx do

defp basic_type?({:int, _}), do: true
defp basic_type?(:bool), do: true
defp basic_type?({:bytes, _}), do: false
defp basic_type?({:list, _, _}), do: false
defp basic_type?({:vector, _, _}), do: false
defp basic_type?({:bitlist, _}), do: false
Expand Down
4 changes: 2 additions & 2 deletions lib/types/beacon_chain/execution_payload.ex
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ defmodule Types.ExecutionPayload do
{:fee_recipient, TypeAliases.execution_address()},
{:state_root, TypeAliases.root()},
{:receipts_root, TypeAliases.root()},
{:logs_bloom, {:vector, :bytes, ChainSpec.get("BYTES_PER_LOGS_BLOOM")}},
{:logs_bloom, {:byte_vector, ChainSpec.get("BYTES_PER_LOGS_BLOOM")}},
{:prev_randao, TypeAliases.bytes32()},
{:block_number, TypeAliases.uint64()},
{:gas_limit, TypeAliases.uint64()},
{:gas_used, TypeAliases.uint64()},
{:timestamp, TypeAliases.uint64()},
{:extra_data, {:list, :bytes, ChainSpec.get("MAX_EXTRA_DATA_BYTES")}},
{:extra_data, {:byte_list, ChainSpec.get("MAX_EXTRA_DATA_BYTES")}},
{:base_fee_per_gas, TypeAliases.uint256()},
{:block_hash, TypeAliases.hash32()},
{:transactions, TypeAliases.transactions()},
Expand Down
4 changes: 2 additions & 2 deletions lib/types/beacon_chain/execution_payload_header.ex
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@ defmodule Types.ExecutionPayloadHeader do
{:fee_recipient, TypeAliases.execution_address()},
{:state_root, TypeAliases.root()},
{:receipts_root, TypeAliases.root()},
{:logs_bloom, {:vector, :bytes, ChainSpec.get("BYTES_PER_LOGS_BLOOM")}},
{:logs_bloom, {:byte_vector, ChainSpec.get("BYTES_PER_LOGS_BLOOM")}},
{:prev_randao, TypeAliases.bytes32()},
{:block_number, TypeAliases.uint64()},
{:gas_limit, TypeAliases.uint64()},
{:gas_used, TypeAliases.uint64()},
{:timestamp, TypeAliases.uint64()},
{:extra_data, {:list, :bytes, ChainSpec.get("MAX_EXTRA_DATA_BYTES")}},
{:extra_data, {:byte_list, ChainSpec.get("MAX_EXTRA_DATA_BYTES")}},
{:base_fee_per_gas, TypeAliases.uint256()},
{:block_hash, TypeAliases.hash32()},
{:transactions_root, TypeAliases.root()},
Expand Down
Loading

0 comments on commit b50e41d

Please sign in to comment.