Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/hex/rustler-0.36.1
Browse files Browse the repository at this point in the history
  • Loading branch information
rodrigo-o authored Feb 3, 2025
2 parents 60fadd9 + 53c4585 commit 31a0f39
Show file tree
Hide file tree
Showing 13 changed files with 284 additions and 34 deletions.
13 changes: 7 additions & 6 deletions .github/config/assertoor/network-params.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@ participants:
keymanager_enabled: true

additional_services:
# - assertoor
- assertoor
- tx_spammer
- blob_spammer
- dora

#assertoor_params:
# run_stability_check: false
# run_block_proposal_check: false
# tests: []
# - https://raw.githubusercontent.com/lambdaclass/lambda_ethereum_consensus/refs/heads/main/.github/config/assertoor/cl-stability-check.yml
assertoor_params:
run_stability_check: false
run_block_proposal_check: false
tests:
- https://raw.githubusercontent.com/lambdaclass/lambda_ethereum_consensus/refs/heads/main/.github/config/assertoor/cl-stability-check.yml

tx_spammer_params:
tx_spammer_extra_args: ["--txcount=3", "--accounts=80"]
7 changes: 7 additions & 0 deletions lib/beacon_api/controllers/error_controller.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
defmodule BeaconApi.ErrorController do
require Logger
use BeaconApi, :controller

@spec bad_request(Plug.Conn.t(), binary()) :: Plug.Conn.t()
def bad_request(conn, message) do
Logger.error("Bad request: #{message}, path: #{conn.request_path}")

conn
|> put_status(400)
|> json(%{
Expand All @@ -13,6 +16,8 @@ defmodule BeaconApi.ErrorController do

@spec not_found(Plug.Conn.t(), any) :: Plug.Conn.t()
def not_found(conn, _params) do
Logger.error("Resource not found, path: #{conn.request_path}")

conn
|> put_status(404)
|> json(%{
Expand All @@ -23,6 +28,8 @@ defmodule BeaconApi.ErrorController do

@spec internal_error(Plug.Conn.t(), any) :: Plug.Conn.t()
def internal_error(conn, _params) do
Logger.error("Internal server error, path: #{conn.request_path}")

conn
|> put_status(500)
|> json(%{
Expand Down
45 changes: 41 additions & 4 deletions lib/beacon_api/controllers/v1/beacon_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ defmodule BeaconApi.V1.BeaconController do
def open_api_operation(:get_finality_checkpoints),
do: ApiSpec.spec().paths["/eth/v1/beacon/states/{state_id}/finality_checkpoints"].get

def open_api_operation(:get_headers_by_block),
do: ApiSpec.spec().paths["/eth/v1/beacon/headers/{block_id}"].get

@spec get_genesis(Plug.Conn.t(), any) :: Plug.Conn.t()
def get_genesis(conn, _params) do
conn
|> json(%{
"data" => %{
"genesis_time" => StoreDb.fetch_genesis_time!(),
"genesis_time" => StoreDb.fetch_genesis_time!() |> Integer.to_string(),
"genesis_validators_root" =>
ChainSpec.get_genesis_validators_root() |> Utils.hex_encode(),
"genesis_fork_version" => ChainSpec.get("GENESIS_FORK_VERSION") |> Utils.hex_encode()
Expand Down Expand Up @@ -168,18 +171,52 @@ defmodule BeaconApi.V1.BeaconController do
finalized: finalized,
data: %{
previous_justified: %{
epoch: previous_justified_checkpoint.epoch,
epoch: previous_justified_checkpoint.epoch |> Integer.to_string(),
root: Utils.hex_encode(previous_justified_checkpoint.root)
},
current_justified: %{
epoch: current_justified_checkpoint.epoch,
epoch: current_justified_checkpoint.epoch |> Integer.to_string(),
root: Utils.hex_encode(current_justified_checkpoint.root)
},
finalized: %{
epoch: finalized_checkpoint.epoch,
epoch: finalized_checkpoint.epoch |> Integer.to_string(),
root: Utils.hex_encode(finalized_checkpoint.root)
}
}
})
end

@spec get_headers_by_block(Plug.Conn.t(), any) :: Plug.Conn.t()
def get_headers_by_block(conn, %{block_id: "head"}) do
{:ok, store} = StoreDb.fetch_store()
head_root = store.head_root
%{signed_block: %{message: message, signature: signature}} = Blocks.get_block_info(head_root)

conn
# TODO: This is a placeholder, a minimum implementation to make assertoor run
|> json(%{
execution_optimistic: false,

# This is obviously false for the head, but should be derived
finalized: false,
data: %{
root: head_root |> Utils.hex_encode(),

# This needs to be derived
canonical: true,
header: %{
message: %{
slot: message.slot |> Integer.to_string(),
proposer_index: message.proposer_index |> Integer.to_string(),
parent_root: message.parent_root |> Utils.hex_encode(),
state_root: message.state_root |> Utils.hex_encode(),
body_root: SszEx.hash_tree_root!(message.body) |> Utils.hex_encode()
},
signature: signature |> Utils.hex_encode()
}
}
})
end

def get_headers_by_block(conn, _params), do: conn |> ErrorController.not_found(nil)
end
61 changes: 61 additions & 0 deletions lib/beacon_api/controllers/v1/config_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
defmodule BeaconApi.V1.ConfigController do
use BeaconApi, :controller
require Logger

alias BeaconApi.ApiSpec
alias BeaconApi.Utils

plug(OpenApiSpex.Plug.CastAndValidate, json_render_error_v2: true)

@chain_spec_removed_keys [
"ATTESTATION_SUBNET_COUNT",
"KZG_COMMITMENT_INCLUSION_PROOF_DEPTH",
"UPDATE_TIMEOUT"
]
@chain_spec_renamed_keys [
{"MAXIMUM_GOSSIP_CLOCK_DISPARITY", "MAXIMUM_GOSSIP_CLOCK_DISPARITY_MILLIS"}
]
@chain_spec_hex_fields [
"TERMINAL_BLOCK_HASH",
"GENESIS_FORK_VERSION",
"ALTAIR_FORK_VERSION",
"BELLATRIX_FORK_VERSION",
"CAPELLA_FORK_VERSION",
"DENEB_FORK_VERSION",
"ELECTRA_FORK_VERSION",
"DEPOSIT_CONTRACT_ADDRESS",
"MESSAGE_DOMAIN_INVALID_SNAPPY",
"MESSAGE_DOMAIN_VALID_SNAPPY"
]

# NOTE: this function is required by OpenApiSpex, and should return the information
# of each specific endpoint. We just return the specific entry from the parsed spec.
def open_api_operation(:get_spec),
do: ApiSpec.spec().paths["/eth/v1/config/spec"].get

# TODO: This is still an incomplete implementation, it should return some constants
# along with the chain spec. It's enough for assertoor.
@spec get_spec(Plug.Conn.t(), any) :: Plug.Conn.t()
def get_spec(conn, _params), do: json(conn, %{"data" => chain_spec()})

defp chain_spec() do
ChainSpec.get_all()
|> Map.drop(@chain_spec_removed_keys)
|> rename_keys(@chain_spec_renamed_keys)
|> Map.new(fn
{k, v} when is_integer(v) -> {k, Integer.to_string(v)}
{k, v} when k in @chain_spec_hex_fields -> {k, Utils.hex_encode(v)}
{k, v} -> {k, v}
end)
end

defp rename_keys(config, renamed_keys) do
renamed_keys
|> Enum.reduce(config, fn {old_key, new_key}, config ->
case Map.get(config, old_key) do
nil -> config
value -> Map.put_new(config, new_key, value) |> Map.delete(old_key)
end
end)
end
end
36 changes: 33 additions & 3 deletions lib/beacon_api/controllers/v1/node_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,21 @@ defmodule BeaconApi.V1.NodeController do
def open_api_operation(:version),
do: ApiSpec.spec().paths["/eth/v1/node/version"].get

def open_api_operation(:syncing),
do: ApiSpec.spec().paths["/eth/v1/node/syncing"].get

def open_api_operation(:peers),
do: ApiSpec.spec().paths["/eth/v1/node/peers"].get

@spec health(Plug.Conn.t(), any) :: Plug.Conn.t()
def health(conn, params) do
# TODO: respond with syncing status if we're still syncing
_syncing_status = Map.get(params, :syncing_status, 206)
%{syncing?: syncing?} = Libp2pPort.sync_status()

syncing_status = if syncing?, do: Map.get(params, :syncing_status, 206), else: 200

send_resp(conn, 200, "")
send_resp(conn, syncing_status, "")
rescue
_ -> send_resp(conn, 503, "")
end

@spec identity(Plug.Conn.t(), any) :: Plug.Conn.t()
Expand Down Expand Up @@ -62,4 +71,25 @@ defmodule BeaconApi.V1.NodeController do
}
})
end

@spec syncing(Plug.Conn.t(), any) :: Plug.Conn.t()
def syncing(conn, _params) do
%{
syncing?: is_syncing,
optimistic?: is_optimistic,
el_offline?: el_offline,
head_slot: head_slot,
sync_distance: sync_distance
} = Libp2pPort.sync_status()

json(conn, %{
"data" => %{
"is_syncing" => is_syncing,
"is_optimistic" => is_optimistic,
"el_offline" => el_offline,
"head_slot" => head_slot |> Integer.to_string(),
"sync_distance" => sync_distance |> Integer.to_string()
}
})
end
end
10 changes: 7 additions & 3 deletions lib/beacon_api/helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,14 @@ defmodule BeaconApi.Helpers do
@spec finality_checkpoint_by_id(state_id()) ::
{:ok, finality_info()} | {:error, String.t()} | :not_found | :empty_slot | :invalid_id
def finality_checkpoint_by_id(id) do
empty_checkpoint = %Types.Checkpoint{epoch: 0, root: <<0::256>>}

with {:ok, {state, optimistic, finalized}} <- state_by_state_id(id) do
{:ok,
{state.previous_justified_checkpoint, state.current_justified_checkpoint,
state.finalized_checkpoint, optimistic, finalized}}
previous_justified_ck = Map.get(state, :previous_justified_checkpoint, empty_checkpoint)
current_justified_ck = Map.get(state, :current_justified_checkpoint, empty_checkpoint)
finalized_ck = Map.get(state, :finalized_checkpoint, empty_checkpoint)

{:ok, {previous_justified_ck, current_justified_ck, finalized_ck, optimistic, finalized}}
end
end

Expand Down
21 changes: 21 additions & 0 deletions lib/beacon_api/router.ex
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
defmodule BeaconApi.Router do
use BeaconApi, :router
require Logger

pipeline :api do
plug(:accepts, ["json", "sse"])
plug(OpenApiSpex.Plug.PutApiSpec, module: BeaconApi.ApiSpec)
plug(:log_requests)
end

# Ethereum API Version 1
Expand All @@ -15,12 +17,19 @@ defmodule BeaconApi.Router do
get("/states/:state_id/root", BeaconController, :get_state_root)
get("/blocks/:block_id/root", BeaconController, :get_block_root)
get("/states/:state_id/finality_checkpoints", BeaconController, :get_finality_checkpoints)
get("/headers/:block_id", BeaconController, :get_headers_by_block)
end

scope "/config" do
get("/spec", ConfigController, :get_spec)
end

scope "/node" do
get("/health", NodeController, :health)
get("/identity", NodeController, :identity)
get("/version", NodeController, :version)
get("/syncing", NodeController, :syncing)
get("/peers", NodeController, :peers)
end

scope "/events" do
Expand All @@ -44,4 +53,16 @@ defmodule BeaconApi.Router do

# Catch-all route outside of any scope
match(:*, "/*path", BeaconApi.ErrorController, :not_found)

defp log_requests(conn, _opts) do
base_message = "[BeaconAPI Router] Processing request: #{conn.method} - #{conn.request_path}"
query = if conn.query_params != %{}, do: "Query: #{inspect(conn.query_params)}", else: ""
body = if conn.body_params != %{}, do: "Body: #{inspect(conn.body_params)}", else: ""

[base_message, query, body]
|> Enum.join("\n\t")
|> Logger.info()

conn
end
end
2 changes: 2 additions & 0 deletions lib/chain_spec/chain_spec.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ defmodule ChainSpec do
# NOTE: this only works correctly for Capella
def get(name), do: get_config().get(name)

def get_all(), do: get_config().get_all()

def get_genesis_validators_root() do
Application.fetch_env!(:lambda_ethereum_consensus, __MODULE__)
|> Keyword.fetch!(:genesis_validators_root)
Expand Down
7 changes: 3 additions & 4 deletions lib/lambda_ethereum_consensus/beacon/sync_blocks.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,10 @@ defmodule LambdaEthereumConsensus.Beacon.SyncBlocks do
finish, each block of those responses will be sent to libp2p port module individually using
Libp2pPort.add_block/1.
"""
@spec run() :: non_neg_integer()
def run() do
%{head_slot: head_slot} = ForkChoice.get_current_status_message()
@spec run(Types.Store.t()) :: non_neg_integer()
def run(%{head_slot: head_slot} = store) do
initial_slot = head_slot + 1
last_slot = ForkChoice.get_current_chain_slot()
last_slot = ForkChoice.get_current_slot(store)

# If we're around genesis, we consider ourselves synced
if last_slot <= 0 do
Expand Down
3 changes: 1 addition & 2 deletions lib/lambda_ethereum_consensus/fork_choice/fork_choice.ex
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,7 @@ defmodule LambdaEthereumConsensus.ForkChoice do
@doc """
Get the current chain slot based on the system time.
There are just 2 uses of this function outside this module:
- At the begining of SyncBlocks.run/1 function, to get the head slot
There is just 1 use of this function outside this module:
- In the Helpers.block_root_by_block_id/1 function
"""
@spec get_current_chain_slot() :: Types.slot()
Expand Down
Loading

0 comments on commit 31a0f39

Please sign in to comment.