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

Fix issue when resolving addresses and there were multiple movements with the same address #1335

Merged
merged 3 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
48 changes: 12 additions & 36 deletions lib/archethic/mining/validation_context.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ defmodule Archethic.Mining.ValidationContext do
:validation_stamp,
:validation_time,
:contract_context,
resolved_addresses: [],
resolved_addresses: %{},
unspent_outputs: [],
cross_validation_stamps: [],
cross_validation_nodes_confirmation: <<>>,
Expand Down Expand Up @@ -64,6 +64,8 @@ defmodule Archethic.Mining.ValidationContext do
alias Archethic.TransactionChain.Transaction.ValidationStamp
alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations

alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.TransactionMovement

alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.UnspentOutput
alias Archethic.TransactionChain.TransactionData
alias Archethic.TransactionChain.TransactionData.Recipient
Expand All @@ -74,7 +76,7 @@ defmodule Archethic.Mining.ValidationContext do
transaction: Transaction.t(),
previous_transaction: nil | Transaction.t(),
unspent_outputs: list(UnspentOutput.t()),
resolved_addresses: list({original_address :: binary(), resolved_address :: binary()}),
resolved_addresses: %{Crypto.prepended_hash() => Crypto.prepended_hash()},
welcome_node: Node.t(),
coordinator_node: Node.t(),
cross_validation_nodes: list(Node.t()),
Expand Down Expand Up @@ -806,26 +808,10 @@ defmodule Archethic.Mining.ValidationContext do
validation_time,
encoded_state
) do
initial_movements =
resolved_movements =
tx
|> Transaction.get_movements()
|> Enum.map(&{{&1.to, &1.type}, &1})
|> Enum.into(%{})

resolved_movements =
Enum.reduce(resolved_addresses, [], fn
{to, resolved}, acc ->
case Map.get(initial_movements, to) do
nil ->
acc

movement ->
[%{movement | to: resolved} | acc]
end

_, acc ->
acc
end)
|> TransactionMovement.resolve_movements(resolved_addresses)

%LedgerOperations{
fee: fee,
Expand Down Expand Up @@ -1244,22 +1230,10 @@ defmodule Archethic.Mining.ValidationContext do
},
%__MODULE__{transaction: tx, resolved_addresses: resolved_addresses}
) do
initial_movements =
resolved_movements =
tx
|> Transaction.get_movements()
|> Enum.map(&{{&1.to, &1.type}, &1})
|> Enum.into(%{})

resolved_movements =
Enum.reduce(resolved_addresses, [], fn {to, resolved}, acc ->
case Map.get(initial_movements, to) do
nil ->
acc

movement ->
[%{movement | to: resolved} | acc]
end
end)
|> TransactionMovement.resolve_movements(resolved_addresses)

length(resolved_movements) == length(transaction_movements) and
Enum.all?(resolved_movements, &(&1 in transaction_movements))
Expand Down Expand Up @@ -1459,7 +1433,9 @@ defmodule Archethic.Mining.ValidationContext do
end

defp get_resolved_address_for_address(resolved_addresses, address) do
bchamagne marked this conversation as resolved.
Show resolved Hide resolved
{_to, resolved} = Enum.find(resolved_addresses, fn {to, _resolved} -> to == address end)
resolved
Enum.find_value(resolved_addresses, fn
{^address, resolved} -> resolved
_ -> false
end)
end
end
19 changes: 5 additions & 14 deletions lib/archethic/replication/transaction_validator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ defmodule Archethic.Replication.TransactionValidator do
alias Archethic.TransactionChain.Transaction
alias Archethic.TransactionChain.Transaction.ValidationStamp
alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations

alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.TransactionMovement

alias Archethic.TransactionChain.TransactionData
alias Archethic.TransactionChain.TransactionData.Recipient
alias Archethic.TransactionChain.TransactionInput
Expand Down Expand Up @@ -314,22 +317,10 @@ defmodule Archethic.Replication.TransactionValidator do
) do
resolved_addresses = TransactionChain.resolve_transaction_addresses(tx, timestamp)

initial_movements =
resolved_movements =
tx
|> Transaction.get_movements()
|> Enum.map(&{{&1.to, &1.type}, &1})
|> Enum.into(%{})

resolved_movements =
Enum.reduce(resolved_addresses, [], fn {to, resolved}, acc ->
case Map.get(initial_movements, to) do
nil ->
acc

movement ->
[%{movement | to: resolved} | acc]
end
end)
|> TransactionMovement.resolve_movements(resolved_addresses)

with true <- length(resolved_movements) == length(transaction_movements),
true <- Enum.all?(resolved_movements, &(&1 in transaction_movements)) do
Expand Down
26 changes: 3 additions & 23 deletions lib/archethic/transaction_chain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@ defmodule Archethic.TransactionChain do
alias __MODULE__.Transaction.ValidationStamp

alias __MODULE__.Transaction.ValidationStamp.LedgerOperations

alias __MODULE__.Transaction.ValidationStamp.LedgerOperations.TransactionMovement.Type,
as: TransactionMovementType

alias __MODULE__.Transaction.ValidationStamp.LedgerOperations.UnspentOutput
alias __MODULE__.TransactionSummary
alias __MODULE__.TransactionInput
Expand Down Expand Up @@ -781,10 +777,7 @@ defmodule Archethic.TransactionChain do
Resolve all the last addresses from the transaction data
"""
@spec resolve_transaction_addresses(Transaction.t(), DateTime.t()) ::
list(
{{origin_address :: binary(), type :: TransactionMovementType.t()},
resolved_address :: binary()}
)
%{Crypto.prepended_hash() => Crypto.prepended_hash()}
def resolve_transaction_addresses(
tx = %Transaction{data: %TransactionData{recipients: recipients}},
time = %DateTime{}
Expand All @@ -796,7 +789,7 @@ defmodule Archethic.TransactionChain do
addresses =
tx
|> Transaction.get_movements()
|> Enum.map(&{&1.to, &1.type})
|> Enum.map(& &1.to)
|> Enum.concat(recipient_addresses)

authorized_nodes = P2P.authorized_and_available_nodes()
Expand All @@ -805,20 +798,6 @@ defmodule Archethic.TransactionChain do
TaskSupervisor,
addresses,
fn
{^burning_address, type} ->
{{burning_address, type}, burning_address}

{to, type} ->
nodes = Election.chain_storage_nodes(to, authorized_nodes)

case fetch_last_address(to, nodes, timestamp: time) do
{:ok, resolved} ->
{{to, type}, resolved}

_ ->
{{to, type}, to}
end

^burning_address ->
{burning_address, burning_address}

Expand All @@ -837,6 +816,7 @@ defmodule Archethic.TransactionChain do
)
|> Stream.filter(&match?({:ok, _}, &1))
|> Enum.map(fn {:ok, res} -> res end)
|> Enum.into(%{})
end

@doc """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,4 +219,21 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation
token_id: token_id
}
end

@doc """
Resolve the addresses of given movements
If a movement does not have a resolved address, it is dropped
"""
@spec resolve_movements(list(t()), %{Crypto.prepended_hash() => Crypto.prepended_hash()}) ::
list(t())
def resolve_movements(movements, resolved_addresses) do
movements
|> Enum.reduce([], fn mvt = %__MODULE__{to: to}, acc ->
case Map.get(resolved_addresses, to) do
nil -> acc
resolved_address -> [%__MODULE__{mvt | to: resolved_address} | acc]
end
end)
|> Enum.reverse()
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ defmodule ArchethicWeb.API.JsonRPC.Method.EstimateTransactionFee do
end

defp get_resolved_address_for_address(resolved_addresses, address) do
bchamagne marked this conversation as resolved.
Show resolved Hide resolved
{_to, resolved} = Enum.find(resolved_addresses, fn {to, _resolved} -> to == address end)
resolved
Enum.find_value(resolved_addresses, fn
{^address, resolved} -> resolved
_ -> false
end)
end
end
64 changes: 60 additions & 4 deletions test/archethic/mining/validation_context_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,66 @@ defmodule Archethic.Mining.ValidationContextTest do

alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.UnspentOutput
alias Archethic.TransactionChain.TransactionData
alias Archethic.TransactionChain.TransactionData.Ledger
alias Archethic.TransactionChain.TransactionData.UCOLedger
alias Archethic.TransactionChain.TransactionData.Recipient

doctest ValidationContext

describe "create_validation_stamp/1" do
test "should return the correct movements even if there are multiple to the same address" do
timestamp = DateTime.utc_now() |> DateTime.truncate(:millisecond)
transfer_address = random_address()
resolved_address = random_address()

validation_context = %ValidationContext{
create_context(timestamp)
| transaction:
Transaction.new(
:transfer,
%TransactionData{
ledger: %Ledger{
uco: %UCOLedger{
transfers: [
%UCOLedger.Transfer{to: transfer_address, amount: 10},
%UCOLedger.Transfer{to: transfer_address, amount: 20},
%UCOLedger.Transfer{to: transfer_address, amount: 30}
]
}
}
},
"seed",
0
),
resolved_addresses: %{transfer_address => resolved_address}
}

expected_movements = [
%TransactionMovement{
to: resolved_address,
amount: 10,
type: :UCO
},
%TransactionMovement{
to: resolved_address,
amount: 20,
type: :UCO
},
%TransactionMovement{
to: resolved_address,
amount: 30,
type: :UCO
}
]

assert %ValidationContext{
validation_stamp: %ValidationStamp{
ledger_operations: %LedgerOperations{transaction_movements: ^expected_movements}
}
} = ValidationContext.create_validation_stamp(validation_context)
end
end

describe "cross_validate/1" do
test "should validate with a valid validation stamp" do
timestamp = DateTime.utc_now() |> DateTime.truncate(:millisecond)
Expand Down Expand Up @@ -59,10 +115,10 @@ defmodule Archethic.Mining.ValidationContextTest do
validation_context =
%ValidationContext{
create_context()
| resolved_addresses: [
{contract_address1, latest_contract_address},
{contract_address2, latest_contract_address}
],
| resolved_addresses: %{
contract_address1 => latest_contract_address,
contract_address2 => latest_contract_address
},
transaction:
Transaction.new(
:transfer,
Expand Down
Loading
Loading