Skip to content
This repository has been archived by the owner on Oct 4, 2024. It is now read-only.

Store tx receipts #29

Merged
merged 33 commits into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
09b3ed5
Add postgres yml file
fkrause98 Jul 4, 2023
2e2cc32
Add draft GenServer and block migration
fkrause98 Jul 4, 2023
9765ea0
Add tx migration, schema and block schema
fkrause98 Jul 5, 2023
167f1ce
Working commit
fkrause98 Jul 5, 2023
61acd85
Update blocks and transactions schemas
fkrause98 Jul 5, 2023
ea80955
Use BlockFetcher only in Prod
fkrause98 Jul 5, 2023
3d5313a
Move block schema, update some fetcher code, mix format
fkrause98 Jul 5, 2023
6590ea9
Update tx fields
fkrause98 Jul 6, 2023
0637600
Lower fetch interval
fkrause98 Jul 6, 2023
42eef27
Mix format + update tx fields
fkrause98 Jul 6, 2023
87eb1d2
Remove unnecesary comment
fkrause98 Jul 6, 2023
f05f03f
Add block fetcher tests
fkrause98 Jul 6, 2023
f509beb
Add postgres to CI job
fkrause98 Jul 6, 2023
acc219a
Format
fkrause98 Jul 6, 2023
a7be459
Add infura api key secret read
fkrause98 Jul 6, 2023
9fdc79e
Undo wrong template change
fkrause98 Jul 6, 2023
05d056a
Format
fkrause98 Jul 6, 2023
39eaa51
Update makefile and readme
fkrause98 Jul 6, 2023
5568206
Fix CI
fkrause98 Jul 6, 2023
a4ec4a9
Add seed
fkrause98 Jul 6, 2023
b4b8593
Update block index to show DB data
fkrause98 Jul 6, 2023
5f2d956
Make block fetching take transaction receipts
fkrause98 Jul 10, 2023
cd52b81
Update function comment
fkrause98 Jul 10, 2023
0426e62
Merge branch 'main' into show-db-data
fkrause98 Jul 10, 2023
cc28961
Add create-seed make target
fkrause98 Jul 10, 2023
a26d3c7
Delete seed.sql
fkrause98 Jul 10, 2023
6295772
Format
fkrause98 Jul 10, 2023
b596e94
Fix block and home pages
fkrause98 Jul 11, 2023
d77b57f
Update transaction and receipts validations
fkrause98 Jul 11, 2023
6a08eae
Remove unused key
fkrause98 Jul 11, 2023
7aa0315
Update block fetcher tests
fkrause98 Jul 11, 2023
83dfa0c
Modify some receipt fields
fkrause98 Jul 11, 2023
c25c513
Remove minor warning
fkrause98 Jul 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ stop-db:

deps-get:
mix deps.get

db_container := $(shell docker ps -aqf name=starknet_explorer_dev_db)
seed: db
cat ./priv/repo/seed.sql | docker exec -i $(db_container) psql -U postgres -d starknet_explorer_dev
create-seed: db
docker exec -i $(db_container) pg_dump --column-inserts --data-only -d starknet_explorer_dev -U postgres > ./priv/repo/seed.sql
66 changes: 50 additions & 16 deletions lib/starknet_explorer/block.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ defmodule StarknetExplorer.Block do
use Ecto.Schema
import Ecto.Query
import Ecto.Changeset
alias StarknetExplorer.{Repo, Transaction}
alias StarknetExplorer.{Repo, Transaction, Block}
alias StarknetExplorer.TransactionReceipt, as: Receipt
require Logger
@primary_key {:number, :integer, []}
schema "blocks" do
Expand All @@ -12,7 +13,7 @@ defmodule StarknetExplorer.Block do
field :new_root, :string
field :timestamp, :integer
field :sequencer_address, :string
field :original_json, :binary
field :original_json, :binary, load_in_query: false
has_many :transactions, StarknetExplorer.Transaction, foreign_key: :block_number
timestamps()
end
Expand Down Expand Up @@ -45,9 +46,10 @@ defmodule StarknetExplorer.Block do
end

@doc """
Given a block from the RPC response, insert it into the database.
Given a block from the RPC response, and transactions receipts
insert them into the DB.
"""
def insert_from_rpc_response(block = %{"transactions" => txs}) when is_map(block) do
def insert_from_rpc_response(block = %{"transactions" => txs}, receipts) when is_map(block) do
# Store the original response, in case we need it
# in the future, as a binary blob.
original_json = :erlang.term_to_binary(block)
Expand All @@ -65,23 +67,31 @@ defmodule StarknetExplorer.Block do
end)
|> Map.put("original_json", original_json)

# Validate each transaction before inserting
# the block into postgres. I think
# this can be done with cast_assoc.
transactions =
txs
|> Enum.map(fn tx ->
Transaction.changeset(%Transaction{}, Map.put(tx, "block_number", block["number"]))
end)
transaction_result =
StarknetExplorer.Repo.transaction(fn ->
block_changeset = Block.changeset(%Block{original_json: original_json}, block)

{:ok, block} = Repo.insert(block_changeset)

changeset = changeset(%StarknetExplorer.Block{transactions: transactions}, block)
_txs_changeset =
Enum.map(txs, fn tx ->
tx =
Ecto.build_assoc(block, :transactions, tx)
|> Transaction.changeset(tx)
|> Repo.insert!()

case Repo.insert(changeset) do
Ecto.build_assoc(tx, :receipt, receipts[tx.hash])
|> Receipt.changeset(receipts[tx.hash])
|> Repo.insert!()
end)
end)

case transaction_result do
{:ok, _} ->
:ok

{:error, error} ->
Logger.error("Failed to insert block: #{inspect(error)}")
{:error, err} ->
Logger.error("Error inserting block: #{inspect(err)}")
:error
end
end
Expand All @@ -101,4 +111,28 @@ defmodule StarknetExplorer.Block do
[%{number: number}] -> number
end
end

@doc """
Returns the n latests blocks
"""
def latest_n_blocks(n \\ 20) do
query =
from b in Block,
order_by: [desc: b.number],
limit: ^n

Repo.all(query)
end

@doc """
Returns the n latests blocks
"""
def latest_n_blocks_with_txs(n \\ 20) do
query =
from b in Block,
order_by: [desc: b.number],
limit: ^n

Repo.all(query) |> Repo.preload(:transactions)
end
end
11 changes: 9 additions & 2 deletions lib/starknet_explorer/block_fetcher.ex
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,15 @@ defmodule StarknetExplorer.BlockFetcher do
# fetch a new block, else do nothing.
if curr_height + 10 >= state.latest_block_fetched do
case fetch_block(state.latest_block_fetched + 1) do
{:ok, block = %{"block_number" => new_block_number}} ->
:ok = Block.insert_from_rpc_response(block)
{:ok, block = %{"block_number" => new_block_number, "transactions" => transactions}} ->
receipts =
transactions
|> Map.new(fn %{"transaction_hash" => tx_hash} ->
{:ok, receipt} = Rpc.get_transaction_receipt(tx_hash)
{tx_hash, receipt}
end)

:ok = Block.insert_from_rpc_response(block, receipts)
Logger.info("Inserted new block: #{new_block_number}")
Process.send_after(self(), :fetch_and_store, @fetch_interval)
{:noreply, %{state | block_height: curr_height, latest_block_fetched: new_block_number}}
Expand Down
31 changes: 23 additions & 8 deletions lib/starknet_explorer/transaction.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
defmodule StarknetExplorer.Transaction do

Check warning on line 1 in lib/starknet_explorer/transaction.ex

View workflow job for this annotation

GitHub Actions / Build and test (1.15, 25)

invalid association `block` in schema StarknetExplorer.Transaction: associated schema StarknetExplorer.Block does not have field `id`

Check warning on line 1 in lib/starknet_explorer/transaction.ex

View workflow job for this annotation

GitHub Actions / Build and test (1.15, 25)

invalid association `block` in schema StarknetExplorer.Transaction: associated schema StarknetExplorer.Block does not have field `id`
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
alias StarknetExplorer.{Transaction, Repo, TransactionReceipt}

@l1_handler_tx_fields [
:hash,
Expand All @@ -27,7 +29,6 @@
@invoke_v1_tx_fields [
:calldata,
:sender_address,
:entry_point_selector,
:hash,
:max_fee,
:nonce,
Expand Down Expand Up @@ -65,16 +66,15 @@
:nonce,
:signature,
:type,
:type,
:version
]

@fields @l1_handler_tx_fields ++
@invoke_tx_fields ++
@declare_tx_fields ++ @deploy_contract_tx_fields ++ @deploy_account_tx_fields

@primary_key {:hash, :string, []}
schema "transactions" do
field :hash, :string
field :constructor_calldata, {:array, :string}
field :class_hash, :string
field :type, :string
Expand All @@ -91,23 +91,33 @@
field :sender_address, :string
field :calldata, {:array, :string}
belongs_to :block, StarknetExplorer.Block, foreign_key: :block_number
has_one :receipt, TransactionReceipt
timestamps()
end

def changeset(tx, attrs) do
def changeset(tx, attrs = %{"transaction_hash" => hash}) do
attrs = rename_rpc_fields(attrs)

tx
|> cast(
attrs,
@fields
)
|> Ecto.Changeset.change(%{hash: hash})
|> unique_constraint([:hash])
|> validate_according_to_tx_type(attrs)
|> unique_constraint(:hash)
end

def get_by_hash(hash) do
query =
from tx in Transaction,
where: tx.hash == ^hash

Repo.one!(query)
end

defp rename_rpc_fields(rpc_tx = %{"transaction_hash" => th}) do
rpc_tx |> Map.put("hash", th)
rpc_tx |> Map.delete("transaction_hash") |> Map.put("hash", th)
end

defp validate_according_to_tx_type(changeset, _tx = %{"type" => "INVOKE", "version" => "0x1"}) do
Expand All @@ -120,12 +130,17 @@
|> validate_required(@invoke_tx_fields)
end

defp validate_according_to_tx_type(changeset, tx = %{"type" => "DEPLOY", "max_fee" => _}) do
defp validate_according_to_tx_type(changeset, _tx = %{"type" => "DEPLOY", "max_fee" => _}) do
changeset
|> validate_required(@deploy_account_tx_fields)
end

defp validate_according_to_tx_type(changeset, _tx = %{"type" => "DEPLOY_ACCOUNT"}) do
changeset
|> validate_required(@deploy_account_tx_fields)
end

defp validate_according_to_tx_type(changeset, tx = %{"type" => "DEPLOY"}) do
defp validate_according_to_tx_type(changeset, _tx = %{"type" => "DEPLOY"}) do
changeset
|> validate_required(@deploy_contract_tx_fields)
end
Expand Down
147 changes: 147 additions & 0 deletions lib/starknet_explorer/transaction_receipt.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
defmodule StarknetExplorer.TransactionReceipt do
use Ecto.Schema
import Ecto.Changeset
alias StarknetExplorer.{Transaction, TransactionReceipt}

@invoke_tx_receipt_fields [
:type,
:transaction_hash,
:actual_fee,
:status,
:messages_sent,
:events,
:block_hash,
:block_number,
:messages_sent,
:events
]

@l1_receipt_handler [
:type,
:transaction_hash,
:actual_fee,
:status,
:block_hash,
:block_number,
:messages_sent,
:events
]

@declare_tx_receipt [
:type,
:transaction_hash,
:actual_fee,
:status,
:block_hash,
:block_number,
:messages_sent,
:events
]

@deploy_tx_receipt [
:transaction_hash,
:actual_fee,
:status,
:block_hash,
:messages_sent,
:events,
:type,
:contract_address
]

@deploy_account_tx_receipt [
:transaction_hash,
:actual_fee,
:status,
:block_hash,
:block_number,
:messages_sent,
:events,
:type,
:contract_address
]

# @pending_receipt [
# :transaction_hash,
# :actual_fee,
# :type,
# :messages_sent,
# :events,
# :contract_address
# ]

# @pending_deploy_receipt [
# :transaction_hash,
# :type,
# :actual_fee,
# :messages_sent,
# :events,
# :contract_address
# ]

@fields @invoke_tx_receipt_fields ++
@l1_receipt_handler ++ @declare_tx_receipt ++ @deploy_account_tx_receipt
schema "transaction_receipts" do
belongs_to :transaction, Transaction
field :transaction_hash
field :type, :string
field :actual_fee, :string
field :status, :string
field :block_hash, :string
field :block_number, :integer
field :messages_sent, {:array, :map}
field :events, {:array, :map}
field :contract_address
timestamps()
end

def changeset(receipt, attrs) do
receipt
|> cast(
attrs,
@fields
)
|> validate_according_to_type(attrs)

# |> foreign_key_constraint(:transaction_id)
# |> unique_constraint([:id])
# |> dbg
end

# TODO: Use the proper changeset function
def changeset_for_assoc(attrs = %{"transaction_hash" => hash}) do

Check warning on line 112 in lib/starknet_explorer/transaction_receipt.ex

View workflow job for this annotation

GitHub Actions / Build and test (1.15, 25)

variable "hash" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 112 in lib/starknet_explorer/transaction_receipt.ex

View workflow job for this annotation

GitHub Actions / Build and test (1.15, 25)

variable "hash" is unused (if the variable is not meant to be used, prefix it with an underscore)
%TransactionReceipt{}
|> Ecto.Changeset.change()
|> cast(
attrs,
@fields
)
|> validate_according_to_type(attrs)
|> unique_constraint(:receipt)
end

defp validate_according_to_type(changeset, _tx = %{"type" => "INVOKE"}) do
changeset
|> validate_required(@invoke_tx_receipt_fields)
end

defp validate_according_to_type(changeset, _tx = %{"type" => "L1_HANDLER"}) do
changeset
|> validate_required(@l1_receipt_handler)
end

defp validate_according_to_type(changeset, _tx = %{"type" => "DECLARE"}) do
changeset
|> validate_required(@declare_tx_receipt)
end

defp validate_according_to_type(changeset, _tx = %{"type" => "DEPLOY"}) do
changeset
|> validate_required(@deploy_tx_receipt)
end

defp validate_according_to_type(changeset, _tx = %{"type" => "DEPLOY_ACCOUNT"}) do
changeset
|> validate_required(@deploy_account_tx_receipt)
end
end
2 changes: 1 addition & 1 deletion lib/starknet_explorer_web/live/block_index_live.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule StarknetExplorerWeb.BlockIndexLive do
use StarknetExplorerWeb, :live_view
alias StarknetExplorerWeb.Utils

alias StarknetExplorer.Block
@impl true
def render(assigns) do
~H"""
Expand Down
4 changes: 2 additions & 2 deletions priv/repo/migrations/20230704215332_transactions.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule StarknetExplorer.Repo.Migrations.Transactions do
use Ecto.Migration

def change do
create table("transactions", primary_key: false) do
create table("transactions") do
add :block_number,
references("blocks",
on_delete: :delete_all,
Expand All @@ -22,7 +22,7 @@ defmodule StarknetExplorer.Repo.Migrations.Transactions do
add :compiled_class_hash, :string
add :sender_address, :string
add :entry_point_selector, :string
add :hash, :string, primary_key: true
add :hash, :string
add :max_fee, :string
add :nonce, :string
add :signature, {:array, :string}
Expand Down
Loading
Loading