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

Use the acceptance_resolver more often #1582

Merged
merged 2 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 9 additions & 2 deletions lib/archethic/mining/smart_contract_validation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ defmodule Archethic.Mining.SmartContractValidation do
end
end

# TODO: instead of address we could have a transaction_summary with proof of validation/replication
# TODO: to avoid downloading the tx
defp validate_trigger({:transaction, address, recipient}, _, contract_genesis_address, inputs) do
storage_nodes = Election.storage_nodes(address, P2P.authorized_and_available_nodes())

Expand All @@ -288,7 +290,10 @@ defmodule Archethic.Mining.SmartContractValidation do
inputs,
&(&1.type == :call and &1.from == address)
),
{:ok, tx} <- TransactionChain.fetch_transaction(address, storage_nodes),
{:ok, tx} <-
TransactionChain.fetch_transaction(address, storage_nodes,
acceptance_resolver: :accept_transaction
),
true <- Enum.member?(tx.data.recipients, recipient) do
{:ok, tx}
else
Expand All @@ -311,7 +316,9 @@ defmodule Archethic.Mining.SmartContractValidation do
defp validate_trigger({:oracle, address}, _, _, _) do
storage_nodes = Election.chain_storage_nodes(address, P2P.authorized_and_available_nodes())

case TransactionChain.fetch_transaction(address, storage_nodes) do
case TransactionChain.fetch_transaction(address, storage_nodes,
acceptance_resolver: :accept_transaction
) do
{:ok, tx} ->
{:ok, tx}

Expand Down
10 changes: 8 additions & 2 deletions lib/archethic/mining/transaction_context.ex
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,13 @@ defmodule Archethic.Mining.TransactionContext do
|> List.flatten()
|> Enum.uniq()

prev_tx_task = request_previous_tx(previous_address, authorized_nodes)
prev_tx_task =
if previous_address == genesis_address do
Task.completed(nil)
else
request_previous_tx(previous_address, authorized_nodes)
end

utxos_task = request_utxos(genesis_address, authorized_nodes)
nodes_view_task = request_nodes_view(node_public_keys)

Expand Down Expand Up @@ -88,7 +94,7 @@ defmodule Archethic.Mining.TransactionContext do
# Timeout of 4 sec because the coordinator node wait 5 sec to get the context
# from the cross validation nodes
case TransactionChain.fetch_transaction(previous_address, previous_storage_nodes,
search_mode: :remote,
acceptance_resolver: :accept_transaction,
timeout: 4000
) do
{:ok, tx} ->
Expand Down
14 changes: 12 additions & 2 deletions lib/archethic/replication.ex
Original file line number Diff line number Diff line change
Expand Up @@ -307,12 +307,22 @@ defmodule Archethic.Replication do

genesis_task =
Task.Supervisor.async(TaskSupervisor, fn ->
TransactionContext.fetch_genesis_address(previous_address)
fetch_opts =
if TransactionChain.first_transaction?(tx),
do: [],
else: [acceptance_resolver: :accept_different_genesis]

TransactionContext.fetch_genesis_address(previous_address, fetch_opts)
end)

previous_transaction_task =
Task.Supervisor.async(TaskSupervisor, fn ->
TransactionContext.fetch_transaction(previous_address)
fetch_opts =
if TransactionChain.first_transaction?(tx),
do: [],
else: [acceptance_resolver: :accept_transaction]

TransactionContext.fetch_transaction(previous_address, fetch_opts)
end)

resolved_addresses_task =
Expand Down
20 changes: 8 additions & 12 deletions lib/archethic/replication/transaction_context.ex
Original file line number Diff line number Diff line change
@@ -1,30 +1,26 @@
defmodule Archethic.Replication.TransactionContext do
@moduledoc false

alias Archethic.Crypto

alias Archethic.BeaconChain

alias Archethic.Crypto
alias Archethic.Election

alias Archethic.P2P
alias Archethic.TransactionChain
alias Archethic.TransactionChain.Transaction

alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.VersionedUnspentOutput

alias Archethic.P2P

require Logger

@doc """
Fetch transaction
"""
@spec fetch_transaction(address :: Crypto.versioned_hash()) ::
@spec fetch_transaction(address :: Crypto.versioned_hash(), opts :: Keyword.t()) ::
Transaction.t() | nil
def fetch_transaction(address) when is_binary(address) do
def fetch_transaction(address, opts \\ []) when is_binary(address) do
storage_nodes = Election.chain_storage_nodes(address, P2P.authorized_and_available_nodes())

case TransactionChain.fetch_transaction(address, storage_nodes) do
case TransactionChain.fetch_transaction(address, storage_nodes, opts) do
{:ok, tx} ->
tx

Expand All @@ -36,12 +32,12 @@ defmodule Archethic.Replication.TransactionContext do
@doc """
Fetch genesis address
"""
@spec fetch_genesis_address(address :: Crypto.prepended_hash()) ::
@spec fetch_genesis_address(address :: Crypto.prepended_hash(), opts :: Keyword.t()) ::
genesis_address :: Crypto.prepended_hash()
def fetch_genesis_address(address) do
def fetch_genesis_address(address, opts \\ []) do
storage_nodes = Election.chain_storage_nodes(address, P2P.authorized_and_available_nodes())

case TransactionChain.fetch_genesis_address(address, storage_nodes) do
case TransactionChain.fetch_genesis_address(address, storage_nodes, opts) do
{:ok, genesis_address} -> genesis_address
{:error, _} -> address
end
Expand Down
25 changes: 6 additions & 19 deletions lib/archethic/self_repair.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,21 @@ defmodule Archethic.SelfRepair do
Synchronization for all the Archethic nodes relies on the self-repair mechanism started during
the bootstrapping phase and stores last synchronization date after each cycle.
"""
alias __MODULE__.Notifier
alias __MODULE__.NotifierSupervisor
alias __MODULE__.RepairWorker
alias __MODULE__.Scheduler
alias __MODULE__.Sync
alias Archethic.BeaconChain

alias Archethic.Crypto

alias Archethic.Election

alias Archethic.P2P
alias Archethic.P2P.Message
alias Archethic.P2P.Node

alias Archethic.Replication

alias Archethic.TransactionChain
alias Archethic.TransactionChain.Transaction

alias Archethic.Utils

alias __MODULE__.Notifier
alias __MODULE__.NotifierSupervisor
alias __MODULE__.Scheduler
alias __MODULE__.Sync
alias __MODULE__.RepairWorker

require Logger

defmodule Error do
Expand Down Expand Up @@ -287,19 +279,14 @@ defmodule Archethic.SelfRepair do
defp fetch_transaction_data(address, authorized_nodes) do
timeout = Message.get_max_timeout()

acceptance_resolver = fn
%Transaction{address: ^address} -> true
_ -> false
end

storage_nodes = Election.chain_storage_nodes(address, authorized_nodes)

[
Task.async(fn ->
TransactionChain.fetch_transaction(address, storage_nodes,
search_mode: :remote,
bchamagne marked this conversation as resolved.
Show resolved Hide resolved
timeout: timeout,
acceptance_resolver: acceptance_resolver
acceptance_resolver: :accept_transaction
)
end),
Task.async(fn -> TransactionChain.fetch_inputs(address, storage_nodes) end)
Expand Down
8 changes: 6 additions & 2 deletions lib/archethic/self_repair/sync.ex
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,9 @@ defmodule Archethic.SelfRepair.Sync do
Task.async(fn ->
storage_nodes = Election.chain_storage_nodes(tx_address, download_nodes)

case TransactionChain.fetch_genesis_address(tx_address, storage_nodes) do
case TransactionChain.fetch_genesis_address(tx_address, storage_nodes,
acceptance_resolver: :accept_different_genesis
) do
{:ok, genesis_address} ->
genesis_address

Expand Down Expand Up @@ -516,7 +518,9 @@ defmodule Archethic.SelfRepair.Sync do
fn recipient ->
genesis_nodes = Election.chain_storage_nodes(recipient, authorized_nodes)

case TransactionChain.fetch_genesis_address(recipient, genesis_nodes) do
case TransactionChain.fetch_genesis_address(recipient, genesis_nodes,
acceptance_resolver: :accept_different_genesis
) do
{:ok, genesis_address} ->
[recipient, genesis_address]

Expand Down
55 changes: 49 additions & 6 deletions lib/archethic/transaction_chain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ defmodule Archethic.TransactionChain do

@type search_options :: [
timeout: non_neg_integer(),
acceptance_resolver: (any() -> boolean()),
acceptance_resolver: (any() -> boolean()) | atom(),
consistency_level: pos_integer(),
search_mode: search_mode()
]
Expand Down Expand Up @@ -366,7 +366,7 @@ defmodule Archethic.TransactionChain do
@spec fetch_transaction(
address :: Crypto.prepended_hash(),
storage_nodes :: list(Node.t()),
search_options()
opts :: search_options()
) ::
{:ok, Transaction.t()}
| {:error, :transaction_not_exists}
Expand All @@ -379,7 +379,18 @@ defmodule Archethic.TransactionChain do
else
_ ->
timeout = Keyword.get(opts, :timeout, Message.get_max_timeout())
acceptance_resolver = Keyword.get(opts, :acceptance_resolver, fn _ -> true end)

acceptance_resolver =
case Keyword.get(opts, :acceptance_resolver, fn _ -> true end) do
fun when is_function(fun, 1) ->
fun

:accept_transaction ->
fn
%Transaction{address: ^address} -> true
_ -> false
end
end

conflict_resolver = fn results ->
Enum.reduce(results, fn
Expand Down Expand Up @@ -811,16 +822,37 @@ defmodule Archethic.TransactionChain do
Retrieve the genesis address for a chain from P2P Quorom
It queries the the network for genesis address.
"""
@spec fetch_genesis_address(address :: binary(), list(Node.t())) ::
@spec fetch_genesis_address(address :: binary(), nodes :: list(Node.t()), opts :: Keyword.t()) ::
{:ok, binary()} | {:error, :network_issue}
def fetch_genesis_address(address, nodes) when is_binary(address) do
def fetch_genesis_address(address, nodes, opts \\ []) when is_binary(address) do
case find_genesis_address(address) do
{:error, :not_found} ->
conflict_resolver = fn results ->
Enum.min_by(results, & &1.timestamp, DateTime)
end

case P2P.quorum_read(nodes, %GetGenesisAddress{address: address}, conflict_resolver) do
timeout = Keyword.get(opts, :timeout, 0)

acceptance_resolver =
case Keyword.get(opts, :acceptance_resolver, fn _ -> true end) do
fun when is_function(fun, 1) ->
fun

:accept_different_genesis ->
# credo:disable-for-next-line
fn
%GenesisAddress{address: ^address} -> false
%GenesisAddress{address: _} -> true
end
end

case P2P.quorum_read(
nodes,
%GetGenesisAddress{address: address},
conflict_resolver,
timeout,
acceptance_resolver
) do
{:ok, %GenesisAddress{address: genesis_address}} ->
{:ok, genesis_address}

Expand Down Expand Up @@ -1225,6 +1257,17 @@ defmodule Archethic.TransactionChain do
|> Crypto.hash()
end

@doc """
By checking at the proof of integrity (determined by the coordinator) we can ensure a transaction is not the first
(because the poi contains the hash of the previous if any)
"""
@spec first_transaction?(Transaction.t()) :: boolean()
def first_transaction?(
tx = %Transaction{validation_stamp: %ValidationStamp{proof_of_integrity: poi}}
) do
poi == proof_of_integrity([tx])
end

@doc """
Load the transaction into the TransactionChain context filling the memory tables
"""
Expand Down
Loading
Loading