Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: indexer for cross level messages on Arbitrum #9312

Merged
merged 84 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from 70 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
42f39e9
Initial version of x-level messages indexer
akolotov Feb 2, 2024
f9e9a81
fixes for cspell and credo
akolotov Feb 2, 2024
95417b2
new state of x-level messages
akolotov Feb 2, 2024
7437e82
Monitoring of new L1-to-L2 messages on L1
akolotov Feb 8, 2024
73de8b8
new batches discovery
akolotov Feb 14, 2024
1ce0957
fetcher workers in separate modules
akolotov Feb 15, 2024
aabfcc1
proper name
akolotov Feb 15, 2024
63d9e2b
Fix for responses without "id", e.g. "Too Many Requests"
akolotov Feb 18, 2024
9f40882
update DB with new batches and corresponding data
akolotov Feb 19, 2024
6f316b5
update DB with confirmed blocks
akolotov Feb 28, 2024
1298fa0
fixes for cspell and credo
akolotov Feb 28, 2024
e3d4ee5
tracking commitments confirmations for L1 to L2 messages
akolotov Mar 1, 2024
3329742
Proper usign of max function
akolotov Mar 1, 2024
cf477f1
tracking completion of L2 to L1 messages
akolotov Mar 1, 2024
6b4bbb0
catchup historical messages to L2
akolotov Mar 6, 2024
b75e7dc
incorrect version of committed file
akolotov Mar 6, 2024
a38bd02
catchup historical messages from L2 and completion of L1-to-L2 messages
akolotov Mar 6, 2024
3f92409
historical batches catchup
akolotov Mar 7, 2024
b540efa
status for historical l2-to-l1 messages
akolotov Mar 7, 2024
695ba8b
address matching issue
akolotov Mar 7, 2024
c7cd0d8
catchup historical executions of L2-to-L1 messages
akolotov Mar 8, 2024
3dc4e2e
db query to find unconfirmed blocks gaps
akolotov Mar 12, 2024
3988b87
first changes to catchup historical confirmations
akolotov Mar 13, 2024
8368931
finalized catchup of historical confirmations
akolotov Mar 15, 2024
36bab5e
Merge branch 'master' into feature/arbitrum/bridge
akolotov Mar 15, 2024
f51493b
4844 blobs support
akolotov Mar 18, 2024
fbc2f03
Merge branch 'master' into feature/arbitrum/bridge
akolotov Mar 18, 2024
3965e3a
fix for the issue with multiple confirmations
akolotov Mar 18, 2024
56db5b1
limit amount of batches to handle at once
akolotov Mar 18, 2024
49bf214
Use latest L1 block by fetchers if start block is not configured
akolotov Mar 20, 2024
0d60e2f
merge issue fix
akolotov Mar 20, 2024
53df4f2
missed file
akolotov Mar 20, 2024
2734987
historical messages discovery
akolotov Mar 26, 2024
dc101ad
Merge branch 'master' into feature/arbitrum/bridge
akolotov Mar 26, 2024
28668e2
reduce logs severity
akolotov Mar 28, 2024
cbd02b8
first iteration to improve documentation for new functionality
akolotov Mar 28, 2024
d90f5a9
Merge branch 'master' into feature/arbitrum/bridge
akolotov Mar 28, 2024
75d3a3c
second iteration to improve documentation for new functionality
akolotov Mar 29, 2024
ec50967
Merge branch 'master' into feature/arbitrum/bridge
akolotov Mar 29, 2024
14e9a46
third iteration to improve documentation for new functionality
akolotov Apr 1, 2024
4ab9034
fourth iteration to improve documentation for new functionality
akolotov Apr 4, 2024
1879c60
Merge branch 'master' into feature/arbitrum/bridge
akolotov Apr 4, 2024
a268c90
fifth iteration to improve documentation for new functionality
akolotov Apr 4, 2024
6ccf897
final iteration to improve documentation for new functionality
akolotov Apr 10, 2024
6eac50d
Merge branch 'master' into feature/arbitrum/bridge
akolotov Apr 10, 2024
e84a9e4
Merge branch 'master' into feature/arbitrum/bridge
akolotov Apr 17, 2024
c280488
Merge branch 'master' into feature/arbitrum/bridge
akolotov Apr 26, 2024
3fbb9f6
merge issues addressed
akolotov Apr 26, 2024
414fd2b
Merge branch 'master' into feature/arbitrum/bridge
akolotov Apr 29, 2024
4cc6f1d
code review issues addressed
akolotov Apr 30, 2024
f411222
Merge branch 'master' into feature/arbitrum/bridge
akolotov Apr 30, 2024
8156d20
code review issues addressed
akolotov May 1, 2024
f36495d
fix merge issue
akolotov May 1, 2024
79e5936
Merge branch 'master' into feature/arbitrum/bridge
akolotov May 1, 2024
d57aad4
raising exception in the case of DB inconsistency
akolotov May 1, 2024
685fdff
fix formatting issue
akolotov May 1, 2024
77383ab
termination case for RollupMessagesCatchup
akolotov May 1, 2024
2219173
code review comments addressed
akolotov May 2, 2024
1b93945
Merge branch 'master' into feature/arbitrum/bridge
akolotov May 2, 2024
8912b3d
code review comments addressed
akolotov May 6, 2024
cda998c
consistency in primary keys
akolotov May 6, 2024
62352bd
Merge branch 'master' into feature/arbitrum/bridge
akolotov May 7, 2024
4745c53
dialyzer fix
akolotov May 7, 2024
9f26bec
code review comments addressed
akolotov May 7, 2024
e3a50d1
missed doc comment
akolotov May 7, 2024
d98e0c7
code review comments addressed
akolotov May 7, 2024
dcff53b
Merge branch 'master' into feature/arbitrum/bridge
akolotov May 8, 2024
3a2110c
Merge branch 'master' into feature/arbitrum/bridge
akolotov May 14, 2024
d104114
updated indices creation as per code review comments
akolotov May 14, 2024
f6c9d8a
Merge branch 'master' into feature/arbitrum/bridge
akolotov May 15, 2024
550fe5e
Merge branch 'master' into feature/arbitrum/bridge
akolotov May 17, 2024
343efe5
fix merge issue
akolotov May 17, 2024
4e1dc66
configuration of intervals as time variables
akolotov May 20, 2024
deceef2
TODO added to reflect improvement ability
akolotov May 20, 2024
66544a2
database fields refactoring
akolotov May 20, 2024
e7670d3
Merge branch 'master' into feature/arbitrum/bridge
akolotov May 20, 2024
3d81e76
association renaming
akolotov May 21, 2024
944a4b7
feat: APIv2 endpoints for Arbitrum messages and batches (#9963)
akolotov May 22, 2024
2effba4
feat: Arbitrum-specific fields in the block and transaction API endpo…
akolotov May 22, 2024
d0225b0
fix credo issue
akolotov May 22, 2024
0cff89b
fix tests issues
akolotov May 22, 2024
2afb86f
ethereumjsonrpc test fail investigation
akolotov May 22, 2024
717c495
test issues fixes
akolotov May 23, 2024
b87be60
Merge branch 'master' into feature/arbitrum/bridge
akolotov May 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 42 additions & 6 deletions apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,34 @@ defmodule EthereumJSONRPC.HTTP do
{:error, resp}
end

# restrict response to only those fields supported by the JSON-RPC 2.0 standard, which means that level of keys is
# validated, so we can indicate that with switch to atom keys.
def standardize_response(%{"jsonrpc" => "2.0" = jsonrpc, "id" => id} = unstandardized) do
@doc """
Standardizes responses to adhere to the JSON-RPC 2.0 standard.

This function adjusts responses to conform to JSON-RPC 2.0, ensuring the keys are atom-based
and that 'id', 'jsonrpc', 'result', and 'error' fields meet the protocol's requirements.
It also validates the mutual exclusivity of 'result' and 'error' fields within a response.

## Parameters
- `unstandardized`: A map representing the response with string keys.

## Returns
- A standardized map with atom keys and fields aligned with the JSON-RPC 2.0 standard, including
handling of possible mutual exclusivity errors between 'result' and 'error' fields.
"""
@spec standardize_response(map()) :: %{
:id => nil | non_neg_integer(),
:jsonrpc => binary(),
optional(:error) => %{:code => integer(), :message => binary(), optional(:data) => any()},
optional(:result) => any()
}
def standardize_response(%{"jsonrpc" => "2.0" = jsonrpc} = unstandardized) do
# Avoid extracting `id` directly in the function declaration. Some endpoints
# do not adhere to standards and may omit the `id` in responses related to
# error scenarios. Consequently, the function call would fail during input
# argument matching.

# Nethermind return string ids
id = quantity_to_integer(id)
id = quantity_to_integer(unstandardized["id"])
varasev marked this conversation as resolved.
Show resolved Hide resolved

standardized = %{jsonrpc: jsonrpc, id: id}

Expand All @@ -187,8 +210,21 @@ defmodule EthereumJSONRPC.HTTP do
end
end

# restrict error to only those fields supported by the JSON-RPC 2.0 standard, which means that level of keys is
# validated, so we can indicate that with switch to atom keys.
@doc """
Standardizes error responses to adhere to the JSON-RPC 2.0 standard.

This function converts a map containing error information into a format compliant
with the JSON-RPC 2.0 specification. It ensures the keys are atom-based and checks
for the presence of optional 'data' field, incorporating it if available.

## Parameters
- `unstandardized`: A map representing the error with string keys: "code", "message"
and "data" (optional).

## Returns
- A standardized map with keys as atoms and fields aligned with the JSON-RPC 2.0 standard.
"""
@spec standardize_error(map()) :: %{:code => integer(), :message => binary(), optional(:data) => any()}
def standardize_error(%{"code" => code, "message" => message} = unstandardized)
when is_integer(code) and is_binary(message) do
standardized = %{code: code, message: message}
Expand Down
14 changes: 13 additions & 1 deletion apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ defmodule EthereumJSONRPC.Transaction do
]
)

:arbitrum ->
@chain_type_fields quote(
do: [
request_id: non_neg_integer()
]
)

_ ->
@chain_type_fields quote(do: [])
end
Expand Down Expand Up @@ -509,6 +516,11 @@ defmodule EthereumJSONRPC.Transaction do
})
end

:arbitrum ->
put_if_present(elixir, params, [
{"requestId", :request_id}
])

_ ->
params
end
Expand Down Expand Up @@ -631,7 +643,7 @@ defmodule EthereumJSONRPC.Transaction do
do: {"input", value}

defp entry_to_elixir({key, quantity})
when key in ~w(gas gasPrice nonce r s standardV v value type maxPriorityFeePerGas maxFeePerGas maxFeePerBlobGas) and
when key in ~w(gas gasPrice nonce r s standardV v value type maxPriorityFeePerGas maxFeePerGas maxFeePerBlobGas requestId) and
quantity != nil do
{key, quantity_to_integer(quantity)}
end
Expand Down
3 changes: 3 additions & 0 deletions apps/explorer/config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ config :explorer, Explorer.Repo.Suave, timeout: :timer.seconds(80)

config :explorer, Explorer.Repo.Beacon, timeout: :timer.seconds(80)

# Configure Arbitrum database
config :explorer, Explorer.Repo.Arbitrum, timeout: :timer.seconds(80)

config :explorer, Explorer.Repo.BridgedTokens, timeout: :timer.seconds(80)

config :explorer, Explorer.Repo.Filecoin, timeout: :timer.seconds(80)
Expand Down
4 changes: 4 additions & 0 deletions apps/explorer/config/prod.exs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ config :explorer, Explorer.Repo.Beacon,
prepare: :unnamed,
timeout: :timer.seconds(60)

config :explorer, Explorer.Repo.Arbitrum,
prepare: :unnamed,
timeout: :timer.seconds(60)

config :explorer, Explorer.Repo.BridgedTokens,
prepare: :unnamed,
timeout: :timer.seconds(60)
Expand Down
1 change: 1 addition & 0 deletions apps/explorer/config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ for repo <- [
Explorer.Repo.RSK,
Explorer.Repo.Shibarium,
Explorer.Repo.Suave,
Explorer.Repo.Arbitrum,
Explorer.Repo.BridgedTokens,
Explorer.Repo.Filecoin,
Explorer.Repo.Stability,
Expand Down
1 change: 1 addition & 0 deletions apps/explorer/lib/explorer/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ defmodule Explorer.Application do
Explorer.Repo.RSK,
Explorer.Repo.Shibarium,
Explorer.Repo.Suave,
Explorer.Repo.Arbitrum,
Explorer.Repo.BridgedTokens,
Explorer.Repo.Filecoin,
Explorer.Repo.Stability
Expand Down
49 changes: 47 additions & 2 deletions apps/explorer/lib/explorer/chain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2467,6 +2467,21 @@ defmodule Explorer.Chain do
end
end

@doc """
Finds the block number closest to a given timestamp, with a one-minute buffer, optionally
adjusting based on whether the block should be before or after the timestamp.

## Parameters
- `given_timestamp`: The timestamp for which the closest block number is being sought.
- `closest`: A direction indicator (`:before` or `:after`) specifying whether the block number
returned should be before or after the given timestamp.
- `from_api`: A boolean flag indicating whether to use the replica database or the primary one
for the query.

## Returns
- `{:ok, block_number}` where `block_number` is the block number closest to the specified timestamp.
- `{:error, :not_found}` if no block is found within the specified criteria.
"""
@spec timestamp_to_block_number(DateTime.t(), :before | :after, boolean()) ::
{:ok, Block.block_number()} | {:error, :not_found}
def timestamp_to_block_number(given_timestamp, closest, from_api) do
Expand Down Expand Up @@ -3299,6 +3314,22 @@ defmodule Explorer.Chain do

def limit_showing_transactions, do: @limit_showing_transactions

@doc """
Dynamically joins and preloads associations in a query based on necessity.

This function adjusts the provided Ecto query to include joins for associations. It supports
both optional and required joins. Optional joins use the `preload` function to fetch associations
without enforcing their presence. Required joins ensure the association exists.

## Parameters
- `query`: The initial Ecto query.
- `associations`: A single association or a tuple with nested association preloads.
- `necessity`: Specifies if the association is `:optional` or `:required`.

## Returns
- The modified query with the specified associations joined according to the defined necessity.
"""
@spec join_association(atom() | Ecto.Query.t(), [{atom(), atom()}], :optional | :required) :: Ecto.Query.t()
def join_association(query, [{association, nested_preload}], necessity)
when is_atom(association) and is_atom(nested_preload) do
case necessity do
Expand All @@ -3316,6 +3347,7 @@ defmodule Explorer.Chain do
end
end

@spec join_association(atom() | Ecto.Query.t(), atom(), :optional | :required) :: Ecto.Query.t()
def join_association(query, association, necessity) do
case necessity do
:optional ->
Expand All @@ -3326,10 +3358,23 @@ defmodule Explorer.Chain do
end
end

@spec join_associations(atom() | Ecto.Query.t(), map) :: Ecto.Query.t()
@doc """
Function to preload entities associated with selected in provided query items
Applies dynamic joins to a query based on provided association necessities.

This function iterates over a map of associations with their required join types, either
`:optional` or `:required`, and applies the corresponding joins to the given query.

More info is available on https://hexdocs.pm/ecto/Ecto.Query.html#preload/3

## Parameters
- `query`: The base query to which associations will be joined.
- `necessity_by_association`: A map specifying each association and its necessity
(`:optional` or `:required`).

## Returns
- The query with all specified associations joined according to their necessity.
"""
@spec join_associations(atom() | Ecto.Query.t(), %{any() => :optional | :required}) :: Ecto.Query.t()
def join_associations(query, necessity_by_association) when is_map(necessity_by_association) do
Enum.reduce(necessity_by_association, query, fn {association, join}, acc_query ->
join_association(acc_query, association, join)
Expand Down
53 changes: 53 additions & 0 deletions apps/explorer/lib/explorer/chain/arbitrum/batch_block.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
defmodule Explorer.Chain.Arbitrum.BatchBlock do
@moduledoc """
Models a list of blocks related to a batch for Arbitrum.

Changes in the schema should be reflected in the bulk import module:
- Explorer.Chain.Import.Runner.Arbitrum.BatchBlocks

Migrations:
- Explorer.Repo.Arbitrum.Migrations.CreateArbitrumTables
"""

use Explorer.Schema

alias Explorer.Chain.Arbitrum.{L1Batch, LifecycleTransaction}

@optional_attrs ~w(confirm_id)a

@required_attrs ~w(batch_number block_number)a

@type t :: %__MODULE__{
batch_number: non_neg_integer(),
batch: %Ecto.Association.NotLoaded{} | L1Batch.t() | nil,
block_number: non_neg_integer(),
confirm_id: non_neg_integer() | nil,
confirm_transaction: %Ecto.Association.NotLoaded{} | LifecycleTransaction.t() | nil
}

@primary_key {:block_number, :integer, autogenerate: false}
schema "arbitrum_batch_l2_blocks" do
belongs_to(:batch, L1Batch, foreign_key: :batch_number, references: :number, type: :integer)

belongs_to(:confirm_transaction, LifecycleTransaction,
foreign_key: :confirm_id,
references: :id,
type: :integer
)

timestamps()
end

@doc """
Validates that the `attrs` are valid.
"""
@spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t()
def changeset(%__MODULE__{} = items, attrs \\ %{}) do
items
|> cast(attrs, @required_attrs ++ @optional_attrs)
|> validate_required(@required_attrs)
|> foreign_key_constraint(:batch_number)
|> foreign_key_constraint(:confirm_id)
|> unique_constraint(:block_number)
end
end
52 changes: 52 additions & 0 deletions apps/explorer/lib/explorer/chain/arbitrum/batch_transaction.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
defmodule Explorer.Chain.Arbitrum.BatchTransaction do
@moduledoc """
Models a list of transactions related to a batch for Arbitrum.

Changes in the schema should be reflected in the bulk import module:
- Explorer.Chain.Import.Runner.Arbitrum.BatchTransactions

Migrations:
- Explorer.Repo.Arbitrum.Migrations.CreateArbitrumTables
"""

use Explorer.Schema

alias Explorer.Chain.Arbitrum.L1Batch
alias Explorer.Chain.{Hash, Transaction}

@required_attrs ~w(batch_number tx_hash)a

@type t :: %__MODULE__{
batch_number: non_neg_integer(),
batch: %Ecto.Association.NotLoaded{} | L1Batch.t() | nil,
tx_hash: Hash.t(),
l2_transaction: %Ecto.Association.NotLoaded{} | Transaction.t() | nil
}

@primary_key false
schema "arbitrum_batch_l2_transactions" do
belongs_to(:batch, L1Batch, foreign_key: :batch_number, references: :number, type: :integer)
varasev marked this conversation as resolved.
Show resolved Hide resolved

belongs_to(:l2_transaction, Transaction,
foreign_key: :tx_hash,
primary_key: true,
references: :hash,
type: Hash.Full
)

timestamps()
end

@doc """
Validates that the `attrs` are valid.
"""
@spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t()
def changeset(%__MODULE__{} = transactions, attrs \\ %{}) do
transactions
|> cast(attrs, @required_attrs)
|> validate_required(@required_attrs)
|> foreign_key_constraint(:batch_number)
|> foreign_key_constraint(:block_hash)
|> unique_constraint(:tx_hash)
end
end
62 changes: 62 additions & 0 deletions apps/explorer/lib/explorer/chain/arbitrum/l1_batch.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
defmodule Explorer.Chain.Arbitrum.L1Batch do
@moduledoc """
Models an L1 batch for Arbitrum.

Changes in the schema should be reflected in the bulk import module:
- Explorer.Chain.Import.Runner.Arbitrum.L1Batches

Migrations:
- Explorer.Repo.Arbitrum.Migrations.CreateArbitrumTables
"""

use Explorer.Schema

alias Explorer.Chain.{
Block,
Hash
}

alias Explorer.Chain.Arbitrum.LifecycleTransaction

@required_attrs ~w(number tx_count start_block end_block before_acc after_acc commit_id)a

@type t :: %__MODULE__{
number: non_neg_integer(),
tx_count: non_neg_integer(),
start_block: Block.block_number(),
end_block: Block.block_number(),
before_acc: Hash.t(),
after_acc: Hash.t(),
commit_id: non_neg_integer(),
commit_transaction: %Ecto.Association.NotLoaded{} | LifecycleTransaction.t() | nil
}

@primary_key {:number, :integer, autogenerate: false}
schema "arbitrum_l1_batches" do
field(:tx_count, :integer)
field(:start_block, :integer)
field(:end_block, :integer)
field(:before_acc, Hash.Full)
field(:after_acc, Hash.Full)

belongs_to(:commit_transaction, LifecycleTransaction,
foreign_key: :commit_id,
references: :id,
type: :integer
)

timestamps()
end

@doc """
Validates that the `attrs` are valid.
"""
@spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t()
def changeset(%__MODULE__{} = batches, attrs \\ %{}) do
batches
|> cast(attrs, @required_attrs)
|> validate_required(@required_attrs)
|> foreign_key_constraint(:commit_id)
|> unique_constraint(:number)
end
end