Skip to content

Commit

Permalink
feat: add /v2/micro-blocks/:hash endpoint (#898)
Browse files Browse the repository at this point in the history
* feat: add /v2//micro-blocks/:hash endpoint

* docs: add /micro-blocks/:hash endpoint to README

* test: add micro_block_index match on /micro-blocks/:hash

* chore: avoid iterating micro block lists unnecessarily

* test: fix get_reverse_micro_blocks test
  • Loading branch information
sborrazas committed Sep 13, 2022
1 parent 5f5583a commit 2c16e47
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 12 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,26 @@ $ curl https://mainnet.aeternity.io/key-blocks/kh_2HvzkfTvRjfwbim8YZ2q2ETKLhuYK1
}
```

### `/v2/micro-blocks/:hash`

```
$ curl https://mainnet.aeternity.io/mdw/v2/micro-blocks/mh_HqJKqWdJ1vaPcr82zYNue99GXcKfjpYbmrEcZ7kmUHAzQoeZv
{
"micro_block_index": 39,
"transactions_count": 0,
"hash": "mh_HqJKqWdJ1vaPcr82zYNue99GXcKfjpYbmrEcZ7kmUHAzQoeZv",
"height": 654915,
"pof_hash": "no_fraud",
"prev_hash": "mh_G2gtKvDAkoi3HZDe5TmWYzGX2pD2AL2DrFXACTch57QEWi1Bo",
"prev_key_hash": "kh_2HvzkfTvRjfwbim8YZ2q2ETKLhuYK125JGpisr1Cc9m2VSa5iC",
"signature": "sg_Eyv2nWKwMbxga4XDHH2oCtnSCWhtD87qUjvFLqKvzt9kq2yVPMkcHSv51kr9fmHQk6TGxBHjRjm74pVZtNuHpZkvybsXX",
"state_hash": "bs_2WNN8aZ15a7pd68wWDZkTqpGUTPezUV6KTN2ra5m3v1x5vVJGC",
"time": 1662950429203,
"txs_hash": "bx_AK5hwnJdG3KAEHEvzs4gwjkRDZP5sw5sbtqgHsgJT2fp1PJka",
"version": 5
}
```

---

## Naming System
Expand Down
40 changes: 31 additions & 9 deletions lib/ae_mdw/blocks.ex
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ defmodule AeMdw.Blocks do
end
end

@spec fetch_micro_block(State.t(), binary()) :: {:ok, block()} | {:error, Error.t()}
def fetch_micro_block(state, hash) do
with {:ok, hash, height} <- extract_height(state, :micro, hash) do
mbi =
hash
|> Db.get_reverse_micro_blocks()
|> Enum.count()

if State.exists?(state, Model.Block, {height, mbi}) do
{:ok, render_micro_block(state, height, mbi)}
else
{:error, ErrInput.NotFound.exception(value: hash)}
end
end
end

@spec fetch_key_block_micro_blocks(
State.t(),
binary(),
Expand Down Expand Up @@ -286,20 +302,26 @@ defmodule AeMdw.Blocks do
{:error, ErrInput.NotFound.exception(value: hash_or_kbi)}

:error ->
with {:ok, encoded_hash} <- Validate.id(hash_or_kbi),
{:ok, block} <- :aec_chain.get_block(encoded_hash),
header <- :aec_blocks.to_header(block),
:key <- :aec_headers.type(header),
last_gen <- DbUtil.last_gen(state),
height when height <= last_gen <- :aec_headers.height(header) do
with {:ok, _hash, height} <- extract_height(state, :key, hash_or_kbi) do
{:ok, height}
else
{:error, reason} -> {:error, reason}
_error_or_invalid_height -> {:error, ErrInput.NotFound.exception(value: hash_or_kbi)}
end
end
end

defp extract_height(state, type, hash) do
with {:ok, encoded_hash} <- Validate.id(hash),
{:ok, block} <- :aec_chain.get_block(encoded_hash),
header <- :aec_blocks.to_header(block),
^type <- :aec_headers.type(header),
last_gen <- DbUtil.last_gen(state),
height when height <= last_gen <- :aec_headers.height(header) do
{:ok, encoded_hash, height}
else
{:error, reason} -> {:error, reason}
_error_or_invalid_height -> {:error, ErrInput.NotFound.exception(value: hash)}
end
end

defp serialize_cursor(nil), do: nil

defp serialize_cursor({gen, is_reversed?}), do: {Integer.to_string(gen), is_reversed?}
Expand Down
9 changes: 6 additions & 3 deletions lib/ae_mdw/node/db.ex
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,15 @@ defmodule AeMdw.Node.Db do
end

@spec get_micro_blocks(Blocks.block_hash()) :: list()
def get_micro_blocks(next_kb_hash) do
next_kb_hash
def get_micro_blocks(next_block_hash),
do: next_block_hash |> get_reverse_micro_blocks() |> Enum.reverse()

@spec get_reverse_micro_blocks(Blocks.block_hash()) :: Enumerable.t()
def get_reverse_micro_blocks(next_block_hash) do
next_block_hash
|> :aec_db.get_header()
|> :aec_headers.prev_hash()
|> Stream.unfold(&micro_block_walker/1)
|> Enum.reverse()
end

@spec get_key_block_hash(Blocks.height()) :: Blocks.block_hash() | nil
Expand Down
7 changes: 7 additions & 0 deletions lib/ae_mdw_web/controllers/block_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ defmodule AeMdwWeb.BlockController do
end
end

@spec micro_block(Conn.t(), map()) :: Conn.t()
def micro_block(%Conn{assigns: %{state: state}} = conn, %{"hash" => hash}) do
with {:ok, block} <- Blocks.fetch_micro_block(state, hash) do
json(conn, block)
end
end

@doc """
Endpoint for blocks info based on pagination.
"""
Expand Down
1 change: 1 addition & 0 deletions lib/ae_mdw_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ defmodule AeMdwWeb.Router do
get "/key-blocks", BlockController, :key_blocks
get "/key-blocks/:hash_or_kbi", BlockController, :key_block
get "/key-blocks/:hash_or_kbi/micro-blocks", BlockController, :key_block_micro_blocks
get "/micro-blocks/:hash", BlockController, :micro_block

get "/txs", TxController, :txs
get "/txs/:hash_or_index", TxController, :tx
Expand Down
65 changes: 65 additions & 0 deletions test/ae_mdw_web/controllers/block_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,71 @@ defmodule AeMdwWeb.BlockControllerTest do
end
end

describe "micro-block" do
test "it returns a micro block with txs counts", %{conn: conn, store: store} do
kbi = 1
decoded_hash = TS.micro_block_hash(0)
encoded_hash = :aeser_api_encoder.encode(:micro_block_hash, decoded_hash)

store =
store
|> Store.put(
Model.Block,
Model.block(index: {kbi, -1}, hash: TS.key_block_hash(0), tx_index: 4)
)
|> Store.put(
Model.Block,
Model.block(index: {kbi, 0}, hash: decoded_hash, tx_index: 4)
)
|> Store.put(
Model.Block,
Model.block(index: {kbi, 1}, hash: TS.micro_block_hash(1), tx_index: 10)
)

with_mocks [
{:aec_chain, [], [get_block: fn ^decoded_hash -> {:ok, :block} end]},
{:aec_db, [], [get_header: fn ^decoded_hash -> :header end]},
{:aec_blocks, [], [to_header: fn :block -> :header end]},
{:aec_headers, [],
[
height: fn :header -> kbi end,
serialize_for_client: fn :header, :key -> %{height: kbi} end,
type: fn :header -> :micro end
]},
{AeMdw.Node.Db, [],
[
prev_block_type: fn :header -> :key end,
get_reverse_micro_blocks: fn ^decoded_hash -> [] end
]}
] do
assert %{
"height" => ^kbi,
"micro_block_index" => 0,
"transactions_count" => 6
} =
conn
|> with_store(store)
|> get("/v2/micro-blocks/#{encoded_hash}")
|> json_response(200)
end
end

test "it returns 404 when not found", %{conn: conn, store: store} do
decoded_hash = TS.micro_block_hash(0)
encoded_hash = :aeser_api_encoder.encode(:micro_block_hash, decoded_hash)
error_msg = "not found: #{encoded_hash}"

store =
Store.put(store, Model.Block, Model.block(index: {0, -1}, hash: TS.key_block_hash(0)))

assert %{"error" => ^error_msg} =
conn
|> with_store(store)
|> get("/v2/micro-blocks/#{encoded_hash}")
|> json_response(404)
end
end

describe "blocks" do
test "get key block by hash", %{conn: conn, store: store} do
with_blockchain %{}, kb1: [] do
Expand Down

0 comments on commit 2c16e47

Please sign in to comment.