Skip to content

Commit

Permalink
feat: add open api spex beacon endpoints (#635)
Browse files Browse the repository at this point in the history
  • Loading branch information
f3r10 authored Jan 22, 2024
1 parent dfa0f48 commit a7814b4
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 20 deletions.
48 changes: 44 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,25 @@ jobs:
if: steps.output-cache.outputs.cache-hit != 'true'
run: make compile-port compile-native

download-beacon-node-oapi:
name: Download Beacon Node OAPI
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Cache Beacon Node OAPI
id: output-cache
uses: actions/cache@v3
with:
path: ./beacon-node-oapi.json
key: ${{ runner.os }}-beacon-node-oapi-${{ hashFiles('.oapi_version') }}
lookup-only: true
- name: Download Beacon Node OAPI
if: steps.output-cache.outputs.cache-hit != 'true'
run: make download-beacon-node-oapi

build:
name: Build project
needs: compile-native
needs: [compile-native, download-beacon-node-oapi]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
Expand All @@ -75,6 +91,12 @@ jobs:
path: deps
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: ${{ runner.os }}-mix-
- name: Fetch beacon node oapi file
uses: actions/cache/restore@v3
with:
path: ./beacon-node-oapi.json
key: ${{ runner.os }}-beacon-node-oapi-${{ hashFiles('.oapi_version') }}
fail-on-cache-miss: true
- name: Install dependencies
run: |
sudo apt-get install -y protobuf-compiler
Expand Down Expand Up @@ -104,7 +126,7 @@ jobs:

smoke:
name: Start and stop the node
needs: compile-native
needs: [compile-native, download-beacon-node-oapi]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
Expand All @@ -126,6 +148,12 @@ jobs:
path: deps
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: ${{ runner.os }}-mix-
- name: Fetch beacon node oapi file
uses: actions/cache/restore@v3
with:
path: ./beacon-node-oapi.json
key: ${{ runner.os }}-beacon-node-oapi-${{ hashFiles('.oapi_version') }}
fail-on-cache-miss: true
- name: Install dependencies
run: |
sudo apt-get install -y protobuf-compiler
Expand All @@ -145,7 +173,7 @@ jobs:

test:
name: Test
needs: compile-native
needs: [compile-native, download-beacon-node-oapi]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
Expand All @@ -167,6 +195,12 @@ jobs:
path: deps
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: ${{ runner.os }}-mix-
- name: Fetch beacon node oapi file
uses: actions/cache/restore@v3
with:
path: ./beacon-node-oapi.json
key: ${{ runner.os }}-beacon-node-oapi-${{ hashFiles('.oapi_version') }}
fail-on-cache-miss: true
- name: Set up cargo cache
uses: Swatinem/rust-cache@v2
with:
Expand Down Expand Up @@ -223,7 +257,7 @@ jobs:

spectests:
name: Run spec-tests
needs: [compile-native, download-spectests]
needs: [compile-native, download-spectests, download-beacon-node-oapi]
strategy:
matrix:
config: ["minimal", "general", "mainnet"]
Expand Down Expand Up @@ -253,6 +287,12 @@ jobs:
path: deps
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: ${{ runner.os }}-mix-
- name: Fetch beacon node oapi file
uses: actions/cache/restore@v3
with:
path: ./beacon-node-oapi.json
key: ${{ runner.os }}-beacon-node-oapi-${{ hashFiles('.oapi_version') }}
fail-on-cache-miss: true
- name: Set up cargo cache
uses: Swatinem/rust-cache@v2
with:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,6 @@ priv
# profiling artifacts
callgrind.out.*
*-eflambe-output.bggg

# beacon node oapi json file
beacon-node-oapi.json
1 change: 1 addition & 0 deletions .oapi_version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v2.4.2
15 changes: 13 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
clean-vectors download-vectors uncompress-vectors proto \
spec-test-% spec-test spec-test-config-% spec-test-runner-% \
spec-test-mainnet-% spec-test-minimal-% spec-test-general-% \
clean-tests gen-spec compile-all
clean-tests gen-spec compile-all download-beacon-node-oapi

# Delete current file when command fails
.DELETE_ON_ERROR:
Expand Down Expand Up @@ -86,7 +86,7 @@ proto: $(PROTOBUF_EX_FILES) $(PROTOBUF_GO_FILES)
compile-native: $(OUTPUT_DIR)/libp2p_nif.so $(OUTPUT_DIR)/libp2p_port

#🔨 compile-all: @ Compile the elixir project and its dependencies.
compile-all: compile-native $(PROTOBUF_EX_FILES)
compile-all: compile-native $(PROTOBUF_EX_FILES) download-beacon-node-oapi
mix compile

#🗑️ clean: @ Remove the build files.
Expand Down Expand Up @@ -126,6 +126,17 @@ sepolia: compile-all
test: compile-all
mix test --no-start --exclude spectest

#### BEACON NODE OAPI ####
OAPI_NAME = beacon-node-oapi
OAPI_VERSION := $(shell cat .oapi_version)
$(OAPI_NAME).json: .oapi_version
curl -L -o "$@" \
"https://ethereum.github.io/beacon-APIs/releases/${OAPI_VERSION}/beacon-node-oapi.json"

OPENAPI_JSON := $(OAPI_NAME).json

download-beacon-node-oapi: ${OPENAPI_JSON}

##### SPEC TEST VECTORS #####

SPECTEST_VERSION := $(shell cat .spectest_version)
Expand Down
15 changes: 15 additions & 0 deletions lib/beacon_api/api_spec.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defmodule BeaconApi.ApiSpec do
@moduledoc false
alias OpenApiSpex.OpenApi
@behaviour OpenApi

file = "beacon-node-oapi.json"
@external_resource file
@ethspec file
|> File.read!()
|> Jason.decode!()
|> OpenApiSpex.OpenApi.Decode.decode()

@impl OpenApi
def spec, do: @ethspec
end
31 changes: 24 additions & 7 deletions lib/beacon_api/controllers/v1/beacon_controller.ex
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
defmodule BeaconApi.V1.BeaconController do
alias BeaconApi.ApiSpec
alias BeaconApi.ErrorController

alias LambdaEthereumConsensus.ForkChoice
alias LambdaEthereumConsensus.Store.BlockStore
use BeaconApi, :controller

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

@doc """
action is an atom that correspond to the controller action's function atoms declared on `BeaconApi.Router`
"""
def open_api_operation(action) when is_atom(action) do
apply(__MODULE__, :"#{action}_operation", [])
end

def get_state_root_operation,
do: ApiSpec.spec().paths["/eth/v1/beacon/states/{state_id}/root"].get

@spec get_state_root(Plug.Conn.t(), any) :: Plug.Conn.t()
def get_state_root(conn, %{"state_id" => state_id}) do
def get_state_root(conn, %{state_id: state_id}) do
with {:ok, {root, execution_optimistic, finalized}} <-
BeaconApi.Utils.parse_id(state_id) |> ForkChoice.Helpers.root_by_id(),
{:ok, state_root} <- ForkChoice.Helpers.get_state_root(root) do
Expand All @@ -25,28 +39,31 @@ defmodule BeaconApi.V1.BeaconController do
end
end

def get_block_root_operation,
do: ApiSpec.spec().paths["/eth/v1/beacon/blocks/{block_id}/root"].get

@spec get_block_root(Plug.Conn.t(), any) :: Plug.Conn.t()
def get_block_root(conn, %{"block_id" => "head"}) do
def get_block_root(conn, %{block_id: "head"}) do
# TODO: determine head and return it
conn |> block_not_found()
end

def get_block_root(conn, %{"block_id" => "finalized"}) do
def get_block_root(conn, %{block_id: "finalized"}) do
# TODO
conn |> block_not_found()
end

def get_block_root(conn, %{"block_id" => "justified"}) do
def get_block_root(conn, %{block_id: "justified"}) do
# TODO
conn |> block_not_found()
end

def get_block_root(conn, %{"block_id" => "genesis"}) do
def get_block_root(conn, %{block_id: "genesis"}) do
# TODO
conn |> block_not_found()
end

def get_block_root(conn, %{"block_id" => "0x" <> hex_block_id}) do
def get_block_root(conn, %{block_id: "0x" <> hex_block_id}) do
with {:ok, block_root} <- Base.decode16(hex_block_id, case: :mixed),
{:ok, _signed_block} <- BlockStore.get_block(block_root) do
conn |> root_response(block_root, true, false)
Expand All @@ -56,7 +73,7 @@ defmodule BeaconApi.V1.BeaconController do
end
end

def get_block_root(conn, %{"block_id" => block_id}) do
def get_block_root(conn, %{block_id: block_id}) do
with {slot, ""} when slot >= 0 <- Integer.parse(block_id),
{:ok, block_root} <- BlockStore.get_block_root_by_slot(slot) do
conn |> root_response(block_root, true, false)
Expand Down
25 changes: 19 additions & 6 deletions lib/beacon_api/controllers/v2/beacon_controller.ex
Original file line number Diff line number Diff line change
@@ -1,30 +1,43 @@
defmodule BeaconApi.V2.BeaconController do
alias BeaconApi.ApiSpec
alias BeaconApi.ErrorController
alias LambdaEthereumConsensus.Store.BlockStore
use BeaconApi, :controller

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

@doc """
action is an atom that correspond to the controller action's function atoms declared on `BeaconApi.Router`
"""
def open_api_operation(action) when is_atom(action) do
apply(__MODULE__, :"#{action}_operation", [])
end

def get_block_operation,
do: ApiSpec.spec().paths["/eth/v2/beacon/blocks/{block_id}"].get

@spec get_block(Plug.Conn.t(), any) :: Plug.Conn.t()
def get_block(conn, %{"block_id" => "head"}) do
def get_block(conn, %{block_id: "head"}) do
# TODO: determine head and return it
conn |> block_not_found()
end

def get_block(conn, %{"block_id" => "finalized"}) do
def get_block(conn, %{block_id: "finalized"}) do
# TODO
conn |> block_not_found()
end

def get_block(conn, %{"block_id" => "justified"}) do
def get_block(conn, %{block_id: "justified"}) do
# TODO
conn |> block_not_found()
end

def get_block(conn, %{"block_id" => "genesis"}) do
def get_block(conn, %{block_id: "genesis"}) do
# TODO
conn |> block_not_found()
end

def get_block(conn, %{"block_id" => "0x" <> hex_block_id}) do
def get_block(conn, %{block_id: "0x" <> hex_block_id}) do
with {:ok, block_root} <- Base.decode16(hex_block_id, case: :mixed),
{:ok, block} <- BlockStore.get_block(block_root) do
conn |> block_response(block)
Expand All @@ -34,7 +47,7 @@ defmodule BeaconApi.V2.BeaconController do
end
end

def get_block(conn, %{"block_id" => block_id}) do
def get_block(conn, %{block_id: block_id}) do
with {slot, ""} when slot >= 0 <- Integer.parse(block_id),
{:ok, block} <- BlockStore.get_block_by_slot(slot) do
conn |> block_response(block)
Expand Down
6 changes: 6 additions & 0 deletions lib/beacon_api/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule BeaconApi.Router do

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

# Ethereum API Version 1
Expand All @@ -24,6 +25,11 @@ defmodule BeaconApi.Router do
end
end

scope "/api" do
pipe_through(:api)
get("/openapi", OpenApiSpex.Plug.RenderSpec, [])
end

# Catch-all route outside of any scope
match(:*, "/*path", BeaconApi.ErrorController, :not_found)
end
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ defmodule LambdaEthereumConsensus.MixProject do
{:stream_data, "~> 0.5", only: [:test]},
{:benchee, "~> 1.2", only: [:dev]},
{:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false}
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:open_api_spex, "~> 3.18"}
]
end

Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"},
"open_api_spex": {:hex, :open_api_spex, "3.18.1", "0a73cd5dbcba7d32952dd9738c6819892933d9bae1642f04c9f200281524dd31", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "f52933cddecca675e42ead660379ae2d3853f57f5a35d201eaed85e2e81517d1"},
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
"patch": {:hex, :patch, "0.12.0", "2da8967d382bade20344a3e89d618bfba563b12d4ac93955468e830777f816b0", [:mix], [], "hexpm", "ffd0e9a7f2ad5054f37af84067ee88b1ad337308a1cb227e181e3967127b0235"},
"phoenix": {:hex, :phoenix, "1.7.10", "02189140a61b2ce85bb633a9b6fd02dff705a5f1596869547aeb2b2b95edd729", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "cf784932e010fd736d656d7fead6a584a4498efefe5b8227e9f383bf15bb79d0"},
Expand Down

0 comments on commit a7814b4

Please sign in to comment.