Skip to content

Commit

Permalink
add force_acceptance flag
Browse files Browse the repository at this point in the history
use it in the replication when we are sure the previous transaction exists
  • Loading branch information
bchamagne committed Oct 22, 2024
1 parent 3c9e19a commit fc4e76f
Show file tree
Hide file tree
Showing 10 changed files with 195 additions and 77 deletions.
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
26 changes: 6 additions & 20 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,13 @@ 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,
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

0 comments on commit fc4e76f

Please sign in to comment.