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

feat: add some fork choice values to BeaconChain. #609

Merged
merged 14 commits into from
Jan 19, 2024
Merged
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,7 @@ fmt:
cd native/snappy_nif; cargo fmt
cd native/ssz_nif; cargo fmt
cd native/bls_nif; cargo fmt

#✅ dialyzer: @ Run dialyzer (static analysis tool).
dialyzer:
mix dialyzer
Comment on lines +210 to +212
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

97 changes: 72 additions & 25 deletions lib/lambda_ethereum_consensus/beacon/beacon_chain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@ defmodule LambdaEthereumConsensus.Beacon.BeaconChain do
defstruct [
:genesis_time,
:genesis_validators_root,
:time
:time,
:cached_fork_choice
]

@type t :: %__MODULE__{
genesis_time: Types.uint64(),
genesis_validators_root: Types.bytes32(),
time: Types.uint64()
time: Types.uint64(),
cached_fork_choice: %{
head_root: Types.root(),
mpaulucci marked this conversation as resolved.
Show resolved Hide resolved
head_slot: Types.slot(),
finalized_root: Types.root(),
finalized_epoch: Types.epoch()
}
}
end

Expand All @@ -33,6 +40,14 @@ defmodule LambdaEthereumConsensus.Beacon.BeaconChain do
GenServer.call(__MODULE__, :get_current_slot)
end

@spec update_fork_choice_cache(Types.root(), Types.slot(), Types.root(), Types.epoch()) :: :ok
def update_fork_choice_cache(head_root, head_slot, finalized_root, finalized_epoch) do
GenServer.cast(
__MODULE__,
{:update_fork_choice_cache, head_root, head_slot, finalized_root, finalized_epoch}
)
end

@spec get_current_epoch() :: integer()
def get_current_epoch do
Misc.compute_epoch_at_slot(get_current_slot())
Expand All @@ -50,20 +65,8 @@ defmodule LambdaEthereumConsensus.Beacon.BeaconChain do

@spec get_current_status_message() :: {:ok, Types.StatusMessage.t()} | {:error, any}
def get_current_status_message do
# TODO: un-hardcode when get_head is optimized and/or cached
# GenServer.call(__MODULE__, :get_current_status_message, @default_timeout)

# hardcoded response from random peer
{:ok,
%Types.StatusMessage{
fork_digest: get_fork_digest(),
finalized_root:
Base.decode16!("7715794499C07D9954DD223EC2C6B846D3BAB27956D093000FADC1B8219F74D4"),
finalized_epoch: 228_168,
head_root:
Base.decode16!("D62A74AE0F933224133C5E6E1827A2835A1E705F0CDFEE3AD25808DDEA5572DB"),
head_slot: 7_301_450
}}
status_message = GenServer.call(__MODULE__, :get_current_status_message)
{:ok, status_message}
end

##########################
Expand All @@ -79,6 +82,12 @@ defmodule LambdaEthereumConsensus.Beacon.BeaconChain do
%BeaconChainState{
genesis_time: anchor_state.genesis_time,
genesis_validators_root: anchor_state.genesis_validators_root,
cached_fork_choice: %{
head_root: <<0::256>>,
head_slot: anchor_state.slot,
finalized_root: anchor_state.finalized_checkpoint.root,
finalized_epoch: anchor_state.finalized_checkpoint.epoch
},
time: time
}}
end
Expand All @@ -90,23 +99,36 @@ defmodule LambdaEthereumConsensus.Beacon.BeaconChain do

@impl true
def handle_call({:get_fork_digest, slot}, _from, state) do
current_fork_version =
fork_digest =
case slot do
nil -> compute_current_slot(state)
_ -> slot
end
|> Misc.compute_epoch_at_slot()
|> ChainSpec.get_fork_version_for_epoch()

fork_digest =
Misc.compute_fork_digest(
current_fork_version,
state.genesis_validators_root
)
|> compute_fork_digest(state.genesis_validators_root)

{:reply, fork_digest, state}
end

@impl true
def handle_call(:get_current_status_message, _from, state) do
%{
head_root: head_root,
head_slot: head_slot,
finalized_root: finalized_root,
finalized_epoch: finalized_epoch
} = state.fork_choice

status_message = %Types.StatusMessage{
fork_digest: compute_fork_digest(head_slot, state.genesis_validators_root),
finalized_root: finalized_root,
finalized_epoch: finalized_epoch,
head_root: head_root,
head_slot: head_slot
}

{:reply, status_message, state}
end

@impl true
def handle_info(:on_tick, state) do
schedule_next_tick()
Expand All @@ -118,6 +140,21 @@ defmodule LambdaEthereumConsensus.Beacon.BeaconChain do
{:noreply, %BeaconChainState{state | time: time}}
end

@impl true
def handle_cast(
{:update_fork_choice_cache, head_root, head_slot, finalized_root, finalized_epoch},
state
) do
{:noreply,
state
|> Map.put(:cached_fork_choice, %{
head_root: head_root,
head_slot: head_slot,
finalized_root: finalized_root,
finalized_epoch: finalized_epoch
})}
end

def schedule_next_tick do
# For millisecond precision
time_to_next_tick = 1000 - rem(:os.system_time(:millisecond), 1000)
Expand All @@ -127,4 +164,14 @@ defmodule LambdaEthereumConsensus.Beacon.BeaconChain do
defp compute_current_slot(state) do
div(state.time - state.genesis_time, ChainSpec.get("SECONDS_PER_SLOT"))
end

defp compute_fork_digest(slot, genesis_validators_root) do
current_fork_version =
slot |> Misc.compute_epoch_at_slot() |> ChainSpec.get_fork_version_for_epoch()

Misc.compute_fork_digest(
current_fork_version,
genesis_validators_root
)
end
end
4 changes: 2 additions & 2 deletions lib/lambda_ethereum_consensus/execution/engine_api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ defmodule LambdaEthereumConsensus.Execution.EngineApi do
@spec exchange_capabilities() :: {:ok, any} | {:error, any}
def exchange_capabilities, do: impl().exchange_capabilities()

@spec new_payload_v1(Types.ExecutionPayload.t()) ::
@spec new_payload(Types.ExecutionPayload.t()) ::
{:ok, any} | {:error, any}
def new_payload_v1(execution_payload), do: impl().new_payload_v1(execution_payload)
def new_payload(execution_payload), do: impl().new_payload(execution_payload)

@spec forkchoice_updated(map, map | any) :: {:ok, any} | {:error, any}
def forkchoice_updated(forkchoice_state, payload_attributes),
Expand Down
17 changes: 7 additions & 10 deletions lib/lambda_ethereum_consensus/execution/engine_api/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,18 @@ defmodule LambdaEthereumConsensus.Execution.EngineApi.Api do
call("engine_exchangeCapabilities", [@supported_methods])
end

@spec new_payload_v1(Types.ExecutionPayload.t()) ::
@spec new_payload(Types.ExecutionPayload.t()) ::
{:ok, any} | {:error, any}
def new_payload_v1(execution_payload) do
call("engine_newPayloadV2", [execution_payload])
def new_payload(execution_payload) do
call("engine_newPayloadV2", [RPC.normalize(execution_payload)])
end

@spec forkchoice_updated(map, map | any) :: {:ok, any} | {:error, any}
def forkchoice_updated(forkchoice_state, payload_attributes) do
forkchoice_state =
forkchoice_state
|> Map.update!("finalizedBlockHash", &RPC.encode_binary/1)
|> Map.update!("headBlockHash", &RPC.encode_binary/1)
|> Map.update!("safeBlockHash", &RPC.encode_binary/1)

call("engine_forkchoiceUpdatedV2", [forkchoice_state, payload_attributes])
call("engine_forkchoiceUpdatedV2", [
RPC.normalize(forkchoice_state),
RPC.normalize(payload_attributes)
])
end

defp call(method, params) do
Expand Down
36 changes: 4 additions & 32 deletions lib/lambda_ethereum_consensus/execution/engine_api/mocked.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,42 +12,14 @@ defmodule LambdaEthereumConsensus.Execution.EngineApi.Mocked do
{:ok, ["engine_newPayloadV2"]}
end

@spec new_payload_v1(Types.ExecutionPayload.t()) ::
@spec new_payload(Types.ExecutionPayload.t()) ::
{:ok, any} | {:error, any}
def new_payload_v1(_execution_payload) do
{:ok, generic_response()}
def new_payload(_execution_payload) do
{:ok, %{"status" => "SYNCING"}}
end

@spec forkchoice_updated(map, map | any) :: {:ok, any} | {:error, any}
def forkchoice_updated(_forkchoice_state, _payload_attributes) do
{:ok, generic_response()}
{:ok, %{"payload_id" => nil, payload_status: %{"status" => "SYNCING"}}}
end

defp generic_response do
%{
id: 1,
jsonrpc: "2.0",
result: %{
payloadId: nil,
payloadStatus: %{
status: "VALID",
latestValidHash: nil,
validationError: nil
}
},
error: ""
}
end

# # This will be used for logging
# defp mock_call(method, params) do
# config =
# Application.fetch_env!(
# :lambda_ethereum_consensus,
# LambdaEthereumConsensus.Execution.EngineApi
# )

# endpoint = Keyword.fetch!(config, :endpoint)
# version = Keyword.fetch!(config, :version)
# end
end
30 changes: 23 additions & 7 deletions lib/lambda_ethereum_consensus/execution/execution_client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,26 @@ defmodule LambdaEthereumConsensus.Execution.ExecutionClient do
@doc """
Verifies the validity of the data contained in the new payload and notifies the Execution client of a new payload
"""
@spec verify_and_notify_new_payload(Types.ExecutionPayload.t()) :: {:ok, any} | {:error, any}
def verify_and_notify_new_payload(_execution_payload) do
# TODO: call engine api
{:ok, true}
@spec verify_and_notify_new_payload(Types.ExecutionPayload.t()) ::
{:ok, boolean()} | {:error, any}
def verify_and_notify_new_payload(execution_payload) do
result = EngineApi.new_payload(execution_payload)

case result do
{:ok, %{"status" => "SYNCING"}} ->
{:ok, true}

{:ok, %{"status" => "VALID"}} ->
Logger.info("Block execution payload is valid")
{:ok, true}

{:ok, %{"status" => "INVALID"}} ->
Logger.error("Block execution payload is invalid")
{:ok, false}

{:error, error} ->
{:error, error}
end
end

@doc """
Expand All @@ -29,9 +45,9 @@ defmodule LambdaEthereumConsensus.Execution.ExecutionClient do
{:ok, any} | {:error, any}
def notify_forkchoice_updated(head_block_hash, safe_block_hash, finalized_block_hash) do
fork_choice_state = %{
finalizedBlockHash: finalized_block_hash,
headBlockHash: head_block_hash,
safeBlockHash: safe_block_hash
finalized_block_hash: finalized_block_hash,
head_block_hash: head_block_hash,
safe_block_hash: safe_block_hash
}

EngineApi.forkchoice_updated(fork_choice_state, nil)
Expand Down
49 changes: 48 additions & 1 deletion lib/lambda_ethereum_consensus/execution/rpc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,59 @@ defmodule LambdaEthereumConsensus.Execution.RPC do
if Map.has_key?(result.body, "error") do
{:error, result.body["error"]["message"]}
else
{:ok, result.body["result"]}
{:ok, result.body["result"] |> normalize_response()}
end
end

def normalize(nil), do: nil

def normalize(payload) when is_struct(payload) do
normalize(Map.from_struct(payload))
end

def normalize(payload) when is_map(payload) do
Enum.reduce(payload, %{}, fn {k, v}, acc ->
Map.put(acc, to_camel_case(k), normalize(v))
end)
end

def normalize(payload) when is_list(payload) do
Enum.map(payload, &normalize/1)
end

def normalize(payload) when is_binary(payload) do
encode_binary(payload)
end

def normalize(payload) when is_integer(payload) do
payload |> encode_integer()
end

def normalize_response(response) when is_map(response) do
Enum.reduce(response, %{}, fn {k, v}, acc ->
Map.put(acc, Recase.to_snake(k), v)
end)
end

def normalize_response(response) do
response
end

@spec encode_binary(binary) :: binary
def encode_binary(binary) do
"0x" <> Base.encode16(binary, case: :lower)
end

def encode_integer(integer) do
"0x" <> Integer.to_string(integer, 16)
end

defp to_camel_case(key) when is_atom(key) do
Atom.to_string(key) |> to_camel_case()
end

defp to_camel_case(key) when is_binary(key) do
key
|> Recase.to_camel()
end
end
Loading
Loading