Skip to content

Commit

Permalink
feat: add some fork choice values to BeaconChain. (#609)
Browse files Browse the repository at this point in the history
  • Loading branch information
mpaulucci authored Jan 19, 2024
1 parent 82f2e00 commit e395a07
Show file tree
Hide file tree
Showing 11 changed files with 212 additions and 86 deletions.
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
2 changes: 1 addition & 1 deletion config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ network = Keyword.get(args, :network, "mainnet")
checkpoint_sync = Keyword.get(args, :checkpoint_sync)
execution_endpoint = Keyword.get(args, :execution_endpoint, "http://localhost:8551")
jwt_path = Keyword.get(args, :execution_jwt)
mock_execution = Keyword.get(args, :mock_execution, false)
mock_execution = Keyword.get(args, :mock_execution, config_env() == :test)

config :lambda_ethereum_consensus, LambdaEthereumConsensus.ForkChoice,
checkpoint_sync: checkpoint_sync
Expand Down
99 changes: 74 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(),
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,38 @@ 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
@spec handle_call(:get_current_status_message, any, BeaconChainState.t()) ::
{:reply, Types.StatusMessage.t(), BeaconChainState.t()}
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.cached_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 +142,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 +166,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
19 changes: 10 additions & 9 deletions lib/lambda_ethereum_consensus/execution/engine_api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,20 @@ 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),
do: impl().forkchoice_updated(forkchoice_state, payload_attributes)

defp impl,
do:
Application.get_env(
__MODULE__,
:implementation,
LambdaEthereumConsensus.Execution.EngineApi.Api
)
defp impl do
Application.fetch_env!(
:lambda_ethereum_consensus,
__MODULE__
)[
:implementation
]
end
end
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

0 comments on commit e395a07

Please sign in to comment.