From 89b541d30ca7ccc21219c7ba1aeb4ee4aec444f6 Mon Sep 17 00:00:00 2001 From: bchamagne Date: Thu, 7 Nov 2024 19:03:54 +0100 Subject: [PATCH 1/7] consume the MUCO correctly --- lib/archethic/bootstrap/network_init.ex | 4 +- lib/archethic/mining/ledger_validation.ex | 4 +- lib/archethic/mining/validation_context.ex | 4 +- .../message/validate_smart_contract_call.ex | 6 +- .../mining/validation_context_test.exs | 86 ++++++++++++++++++- 5 files changed, 93 insertions(+), 11 deletions(-) diff --git a/lib/archethic/bootstrap/network_init.ex b/lib/archethic/bootstrap/network_init.ex index d45e5f6f9..7f5bc7c73 100644 --- a/lib/archethic/bootstrap/network_init.ex +++ b/lib/archethic/bootstrap/network_init.ex @@ -190,12 +190,12 @@ defmodule Archethic.Bootstrap.NetworkInit do resolved_addresses = Enum.map(movements, &{&1.to, &1.to}) |> Map.new() operations = - %LedgerValidation{fee: fee} + %LedgerValidation{fee: fee, transaction_movements: movements} |> LedgerValidation.filter_usable_inputs(unspent_outputs, nil) |> LedgerValidation.mint_token_utxos(tx, timestamp, 1) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx_type) |> LedgerValidation.validate_sufficient_funds() |> LedgerValidation.consume_inputs(address, timestamp) + |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx_type) |> LedgerValidation.to_ledger_operations() validation_stamp = diff --git a/lib/archethic/mining/ledger_validation.ex b/lib/archethic/mining/ledger_validation.ex index 766ff43e5..a3c9c067e 100644 --- a/lib/archethic/mining/ledger_validation.ex +++ b/lib/archethic/mining/ledger_validation.ex @@ -58,7 +58,7 @@ defmodule Archethic.Mining.LedgerValidation do def burning_address, do: @burning_address @doc """ - Filter inputs that can be used in this transaction + Filter inputs that can be used in this transaction """ @spec filter_usable_inputs( ops :: t(), @@ -186,6 +186,8 @@ defmodule Archethic.Mining.LedgerValidation do @doc """ Build the resolved view of the movement, with the resolved address and convert MUCO movement to UCO movement + + **MUST** be done after sufficient_funds? and consume_inputs """ @spec build_resolved_movements( ops :: t(), diff --git a/lib/archethic/mining/validation_context.ex b/lib/archethic/mining/validation_context.ex index de5333249..febdafec9 100644 --- a/lib/archethic/mining/validation_context.ex +++ b/lib/archethic/mining/validation_context.ex @@ -800,10 +800,9 @@ defmodule Archethic.Mining.ValidationContext do protocol_version = Mining.protocol_version() ops = - %LedgerValidation{fee: fee} + %LedgerValidation{fee: fee, transaction_movements: movements} |> LedgerValidation.filter_usable_inputs(unspent_outputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, validation_time, protocol_version) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx_type) |> LedgerValidation.validate_sufficient_funds() |> LedgerValidation.consume_inputs( address, @@ -811,6 +810,7 @@ defmodule Archethic.Mining.ValidationContext do encoded_state, contract_context ) + |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx_type) case ops do %LedgerValidation{sufficient_funds?: false} -> diff --git a/lib/archethic/p2p/message/validate_smart_contract_call.ex b/lib/archethic/p2p/message/validate_smart_contract_call.ex index ded9e448a..5023432e8 100644 --- a/lib/archethic/p2p/message/validate_smart_contract_call.ex +++ b/lib/archethic/p2p/message/validate_smart_contract_call.ex @@ -270,19 +270,17 @@ defmodule Archethic.P2P.Message.ValidateSmartContractCall do defp calculate_fee(_, _), do: 0 defp enough_funds_to_send?( - %ActionWithTransaction{next_tx: tx = %Transaction{type: tx_type}}, + %ActionWithTransaction{next_tx: tx}, inputs, timestamp ) do movements = Transaction.get_movements(tx) protocol_version = Mining.protocol_version() - resolved_addresses = Enum.map(movements, &{&1.to, &1.to}) |> Map.new() %LedgerValidation{sufficient_funds?: sufficient_funds?} = - %LedgerValidation{fee: 0} + %LedgerValidation{fee: 0, transaction_movements: movements} |> LedgerValidation.filter_usable_inputs(inputs, nil) |> LedgerValidation.mint_token_utxos(tx, timestamp, protocol_version) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx_type) |> LedgerValidation.validate_sufficient_funds() sufficient_funds? diff --git a/test/archethic/mining/validation_context_test.exs b/test/archethic/mining/validation_context_test.exs index 02d9b936e..0cb64baa1 100644 --- a/test/archethic/mining/validation_context_test.exs +++ b/test/archethic/mining/validation_context_test.exs @@ -12,7 +12,7 @@ defmodule Archethic.Mining.ValidationContextTest do alias Archethic.P2P alias Archethic.P2P.Node - + alias Archethic.Reward.MemTables.RewardTokens alias Archethic.SharedSecrets alias Archethic.TransactionChain @@ -30,6 +30,7 @@ defmodule Archethic.Mining.ValidationContextTest do alias Archethic.TransactionChain.TransactionData alias Archethic.TransactionChain.TransactionData.Ledger alias Archethic.TransactionChain.TransactionData.UCOLedger + alias Archethic.TransactionChain.TransactionData.TokenLedger alias Archethic.TransactionChain.TransactionData.Recipient alias Archethic.TransactionFactory @@ -158,6 +159,83 @@ defmodule Archethic.Mining.ValidationContextTest do } } = ValidationContext.create_validation_stamp(validation_context) end + + test "should handle the MUCO correctly" do + timestamp = DateTime.utc_now() |> DateTime.truncate(:millisecond) + transfer_address = random_address() + resolved_address = random_address() + muco_addr1 = random_address() + muco_addr2 = random_address() + + start_supervised!(RewardTokens) + RewardTokens.add_reward_token_address(muco_addr1) + RewardTokens.add_reward_token_address(muco_addr2) + + validation_context = %ValidationContext{ + create_context(timestamp, + utxos: [ + %UnspentOutput{ + from: random_address(), + amount: 10, + type: {:token, muco_addr1, 0}, + timestamp: timestamp + }, + %UnspentOutput{ + from: random_address(), + amount: 10, + type: {:token, muco_addr2, 0}, + timestamp: timestamp + } + ] + ) + | transaction: + Transaction.new( + :transfer, + %TransactionData{ + ledger: %Ledger{ + token: %TokenLedger{ + transfers: [ + %TokenLedger.Transfer{ + token_address: muco_addr1, + token_id: 0, + to: transfer_address, + amount: 10 + }, + %TokenLedger.Transfer{ + token_address: muco_addr2, + token_id: 0, + to: transfer_address, + amount: 10 + } + ] + } + } + }, + "seed", + 0 + ), + resolved_addresses: %{transfer_address => resolved_address} + } + + assert %ValidationContext{validation_stamp: %ValidationStamp{ledger_operations: ops}} = + ValidationContext.create_validation_stamp(validation_context) + + assert [ + %TransactionMovement{to: ^resolved_address, amount: 20, type: :UCO} + ] = ops.transaction_movements + + assert 111 = + Enum.reduce(ops.consumed_inputs, 0, fn %VersionedUnspentOutput{ + unspent_output: utxo + }, + acc -> + case utxo do + %UnspentOutput{amount: 204_000_000, type: :UCO} -> acc + 1 + %UnspentOutput{amount: 10, type: {:token, ^muco_addr1, 0}} -> acc + 10 + %UnspentOutput{amount: 10, type: {:token, ^muco_addr2, 0}} -> acc + 100 + end + end) + end end describe "cross_validate/1" do @@ -452,7 +530,10 @@ defmodule Archethic.Mining.ValidationContextTest do end end - defp create_context(validation_time \\ DateTime.utc_now() |> DateTime.truncate(:millisecond)) do + defp create_context( + validation_time \\ DateTime.utc_now() |> DateTime.truncate(:millisecond), + opts \\ [] + ) do welcome_node = %Node{ last_public_key: "key1", first_public_key: "key1", @@ -516,6 +597,7 @@ defmodule Archethic.Mining.ValidationContextTest do type: :UCO, timestamp: validation_time } + | Keyword.get(opts, :utxos, []) ] |> VersionedUnspentOutput.wrap_unspent_outputs(current_protocol_version()) From f3377f09969bd42742c54b2fe79399013fc2a043 Mon Sep 17 00:00:00 2001 From: bchamagne Date: Fri, 8 Nov 2024 09:37:53 +0100 Subject: [PATCH 2/7] lint: use factory --- .../mining/validation_context_test.exs | 41 ++++++++----------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/test/archethic/mining/validation_context_test.exs b/test/archethic/mining/validation_context_test.exs index 0cb64baa1..a1c30064b 100644 --- a/test/archethic/mining/validation_context_test.exs +++ b/test/archethic/mining/validation_context_test.exs @@ -189,30 +189,25 @@ defmodule Archethic.Mining.ValidationContextTest do ] ) | transaction: - Transaction.new( - :transfer, - %TransactionData{ - ledger: %Ledger{ - token: %TokenLedger{ - transfers: [ - %TokenLedger.Transfer{ - token_address: muco_addr1, - token_id: 0, - to: transfer_address, - amount: 10 - }, - %TokenLedger.Transfer{ - token_address: muco_addr2, - token_id: 0, - to: transfer_address, - amount: 10 - } - ] - } + TransactionFactory.create_non_valided_transaction( + ledger: %Ledger{ + token: %TokenLedger{ + transfers: [ + %TokenLedger.Transfer{ + token_address: muco_addr1, + token_id: 0, + to: transfer_address, + amount: 10 + }, + %TokenLedger.Transfer{ + token_address: muco_addr2, + token_id: 0, + to: transfer_address, + amount: 10 + } + ] } - }, - "seed", - 0 + } ), resolved_addresses: %{transfer_address => resolved_address} } From 92a2e3d61c9977c24d583e6143a40ef7060af55e Mon Sep 17 00:00:00 2001 From: bchamagne Date: Fri, 8 Nov 2024 09:40:34 +0100 Subject: [PATCH 3/7] update order in the tests --- .../archethic/mining/distributed_workflow_test.exs | 2 +- test/archethic/mining/validation_context_test.exs | 12 ++++++------ test/support/transaction_factory.ex | 14 +++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/test/archethic/mining/distributed_workflow_test.exs b/test/archethic/mining/distributed_workflow_test.exs index 93de7451c..b6c7fa474 100644 --- a/test/archethic/mining/distributed_workflow_test.exs +++ b/test/archethic/mining/distributed_workflow_test.exs @@ -1360,9 +1360,9 @@ defmodule Archethic.Mining.DistributedWorkflowTest do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(unspent_outputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, protocol_version) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.validate_sufficient_funds() |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) + |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() %ValidationStamp{ diff --git a/test/archethic/mining/validation_context_test.exs b/test/archethic/mining/validation_context_test.exs index a1c30064b..efc26d34f 100644 --- a/test/archethic/mining/validation_context_test.exs +++ b/test/archethic/mining/validation_context_test.exs @@ -624,9 +624,9 @@ defmodule Archethic.Mining.ValidationContextTest do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(unspent_outputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.validate_sufficient_funds() |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) + |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() %ValidationStamp{ @@ -656,9 +656,9 @@ defmodule Archethic.Mining.ValidationContextTest do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(unspent_outputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.validate_sufficient_funds() |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) + |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() %ValidationStamp{ @@ -688,9 +688,9 @@ defmodule Archethic.Mining.ValidationContextTest do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(unspent_outputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.validate_sufficient_funds() |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) + |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() %ValidationStamp{ @@ -721,9 +721,9 @@ defmodule Archethic.Mining.ValidationContextTest do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(unspent_outputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.validate_sufficient_funds() |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) + |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() %ValidationStamp{ @@ -814,9 +814,9 @@ defmodule Archethic.Mining.ValidationContextTest do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(unspent_outputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.validate_sufficient_funds() |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) + |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() %ValidationStamp{ @@ -846,9 +846,9 @@ defmodule Archethic.Mining.ValidationContextTest do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(unspent_outputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.validate_sufficient_funds() |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) + |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() |> Map.put( :consumed_inputs, diff --git a/test/support/transaction_factory.ex b/test/support/transaction_factory.ex index daa37e749..32d026b3d 100644 --- a/test/support/transaction_factory.ex +++ b/test/support/transaction_factory.ex @@ -101,9 +101,9 @@ defmodule Archethic.TransactionFactory do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(inputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, protocol_version) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.validate_sufficient_funds() |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) + |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() poi = @@ -160,9 +160,9 @@ defmodule Archethic.TransactionFactory do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(inputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, protocol_version) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.validate_sufficient_funds() |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) + |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() validation_stamp = @@ -205,9 +205,9 @@ defmodule Archethic.TransactionFactory do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(inputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, protocol_version) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.validate_sufficient_funds() |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) + |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() validation_stamp = %ValidationStamp{ @@ -253,9 +253,9 @@ defmodule Archethic.TransactionFactory do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(inputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, protocol_version) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.validate_sufficient_funds() |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) + |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() validation_stamp = %ValidationStamp{ @@ -294,9 +294,9 @@ defmodule Archethic.TransactionFactory do %LedgerValidation{fee: 1_000_000_000} |> LedgerValidation.filter_usable_inputs(inputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, protocol_version) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.validate_sufficient_funds() |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) + |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() validation_stamp = @@ -338,9 +338,9 @@ defmodule Archethic.TransactionFactory do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(inputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, protocol_version) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.validate_sufficient_funds() |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) + |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() validation_stamp = @@ -399,9 +399,9 @@ defmodule Archethic.TransactionFactory do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(inputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, protocol_version) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.validate_sufficient_funds() |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) + |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() validation_stamp = From 477fefc7942c9b029618db10136593cd7d22fdea Mon Sep 17 00:00:00 2001 From: bchamagne Date: Fri, 8 Nov 2024 10:55:37 +0100 Subject: [PATCH 4/7] movements passed by the validate_sufficient_funds func --- lib/archethic/bootstrap/network_init.ex | 6 +- lib/archethic/mining/ledger_validation.ex | 39 ++++++----- lib/archethic/mining/validation_context.ex | 10 +-- .../message/validate_smart_contract_call.ex | 4 +- .../mining/distributed_workflow_test.exs | 4 +- .../mining/ledger_validation_test.exs | 66 ++++++++----------- .../mining/validation_context_test.exs | 24 +++---- test/support/transaction_factory.ex | 28 ++++---- 8 files changed, 89 insertions(+), 92 deletions(-) diff --git a/lib/archethic/bootstrap/network_init.ex b/lib/archethic/bootstrap/network_init.ex index 7f5bc7c73..f760c322a 100644 --- a/lib/archethic/bootstrap/network_init.ex +++ b/lib/archethic/bootstrap/network_init.ex @@ -190,12 +190,12 @@ defmodule Archethic.Bootstrap.NetworkInit do resolved_addresses = Enum.map(movements, &{&1.to, &1.to}) |> Map.new() operations = - %LedgerValidation{fee: fee, transaction_movements: movements} + %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(unspent_outputs, nil) |> LedgerValidation.mint_token_utxos(tx, timestamp, 1) - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(address, timestamp) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx_type) + |> LedgerValidation.build_resolved_movements(resolved_addresses, tx_type) |> LedgerValidation.to_ledger_operations() validation_stamp = diff --git a/lib/archethic/mining/ledger_validation.ex b/lib/archethic/mining/ledger_validation.ex index a3c9c067e..b9a536176 100644 --- a/lib/archethic/mining/ledger_validation.ex +++ b/lib/archethic/mining/ledger_validation.ex @@ -5,7 +5,7 @@ defmodule Archethic.Mining.LedgerValidation do @unit_uco 100_000_000 - defstruct transaction_movements: [], + defstruct transaction_movements: nil, unspent_outputs: [], fee: 0, consumed_inputs: [], @@ -38,7 +38,7 @@ defmodule Archethic.Mining.LedgerValidation do - Consumed inputs: represents the list of inputs consumed to produce the unspent outputs """ @type t() :: %__MODULE__{ - transaction_movements: list(TransactionMovement.t()), + transaction_movements: nil | list(TransactionMovement.t()), unspent_outputs: list(UnspentOutput.t()), fee: non_neg_integer(), consumed_inputs: list(VersionedUnspentOutput.t()), @@ -191,31 +191,39 @@ defmodule Archethic.Mining.LedgerValidation do """ @spec build_resolved_movements( ops :: t(), - movements :: list(TransactionMovement.t()), resolved_addresses :: %{Crypto.prepended_hash() => Crypto.prepended_hash()}, tx_type :: Transaction.transaction_type() ) :: t() - def build_resolved_movements(ops, movements, resolved_addresses, tx_type) do - resolved_movements = - movements - |> TransactionMovement.resolve_addresses(resolved_addresses) - |> Enum.map(&TransactionMovement.maybe_convert_reward(&1, tx_type)) - |> TransactionMovement.aggregate() - - %__MODULE__{ops | transaction_movements: resolved_movements} + def build_resolved_movements( + ops = %__MODULE__{ + transaction_movements: unresolved_movements + }, + resolved_addresses, + tx_type + ) + when is_list(unresolved_movements) do + %__MODULE__{ + ops + | transaction_movements: + unresolved_movements + |> TransactionMovement.resolve_addresses(resolved_addresses) + |> Enum.map(&TransactionMovement.maybe_convert_reward(&1, tx_type)) + |> TransactionMovement.aggregate() + } end @doc """ Determine if the transaction has enough funds for it's movements """ - @spec validate_sufficient_funds(ops :: t()) :: t() + @spec validate_sufficient_funds(ops :: t(), list(TransactionMovement.t())) :: t() def validate_sufficient_funds( ops = %__MODULE__{ fee: fee, inputs: inputs, minted_utxos: minted_utxos, - transaction_movements: movements - } + transaction_movements: nil + }, + movements ) do balances = %{uco: uco_balance, token: tokens_balance} = ledger_balances(inputs ++ minted_utxos) @@ -228,7 +236,8 @@ defmodule Archethic.Mining.LedgerValidation do | sufficient_funds?: sufficient_funds?(uco_balance, uco_to_spend, tokens_balance, tokens_to_spend), balances: balances, - amount_to_spend: amount_to_spend + amount_to_spend: amount_to_spend, + transaction_movements: movements } end diff --git a/lib/archethic/mining/validation_context.ex b/lib/archethic/mining/validation_context.ex index febdafec9..64a129189 100644 --- a/lib/archethic/mining/validation_context.ex +++ b/lib/archethic/mining/validation_context.ex @@ -800,17 +800,17 @@ defmodule Archethic.Mining.ValidationContext do protocol_version = Mining.protocol_version() ops = - %LedgerValidation{fee: fee, transaction_movements: movements} + %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(unspent_outputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, validation_time, protocol_version) - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs( address, validation_time, encoded_state, contract_context ) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx_type) + |> LedgerValidation.build_resolved_movements(resolved_addresses, tx_type) case ops do %LedgerValidation{sufficient_funds?: false} -> @@ -1245,8 +1245,8 @@ defmodule Archethic.Mining.ValidationContext do movements = Transaction.get_movements(tx) %LedgerOperations{transaction_movements: resolved_movements} = - %LedgerValidation{} - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx_type) + %LedgerValidation{transaction_movements: movements} + |> LedgerValidation.build_resolved_movements(resolved_addresses, tx_type) |> LedgerValidation.to_ledger_operations() length(resolved_movements) == length(transaction_movements) and diff --git a/lib/archethic/p2p/message/validate_smart_contract_call.ex b/lib/archethic/p2p/message/validate_smart_contract_call.ex index 5023432e8..6adb3b6bd 100644 --- a/lib/archethic/p2p/message/validate_smart_contract_call.ex +++ b/lib/archethic/p2p/message/validate_smart_contract_call.ex @@ -278,10 +278,10 @@ defmodule Archethic.P2P.Message.ValidateSmartContractCall do protocol_version = Mining.protocol_version() %LedgerValidation{sufficient_funds?: sufficient_funds?} = - %LedgerValidation{fee: 0, transaction_movements: movements} + %LedgerValidation{} |> LedgerValidation.filter_usable_inputs(inputs, nil) |> LedgerValidation.mint_token_utxos(tx, timestamp, protocol_version) - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) sufficient_funds? end diff --git a/test/archethic/mining/distributed_workflow_test.exs b/test/archethic/mining/distributed_workflow_test.exs index b6c7fa474..4fde39277 100644 --- a/test/archethic/mining/distributed_workflow_test.exs +++ b/test/archethic/mining/distributed_workflow_test.exs @@ -1360,9 +1360,9 @@ defmodule Archethic.Mining.DistributedWorkflowTest do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(unspent_outputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, protocol_version) - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) + |> LedgerValidation.build_resolved_movements(resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() %ValidationStamp{ diff --git a/test/archethic/mining/ledger_validation_test.exs b/test/archethic/mining/ledger_validation_test.exs index 3250c1ad7..80a8236bc 100644 --- a/test/archethic/mining/ledger_validation_test.exs +++ b/test/archethic/mining/ledger_validation_test.exs @@ -348,10 +348,10 @@ defmodule Archethic.Mining.LedgerValidationTest do end end - describe "validate_sufficient_funds/1" do + describe "validate_sufficient_funds/2" do test "should return insufficient funds when not enough uco" do assert %LedgerValidation{sufficient_funds?: false} = - %LedgerValidation{fee: 1_000} |> LedgerValidation.validate_sufficient_funds() + %LedgerValidation{fee: 1_000} |> LedgerValidation.validate_sufficient_funds([]) end test "should return insufficient funds when not enough tokens" do @@ -374,8 +374,8 @@ defmodule Archethic.Mining.LedgerValidationTest do ] assert %LedgerValidation{sufficient_funds?: false} = - %LedgerValidation{fee: 1_000, transaction_movements: movements, inputs: inputs} - |> LedgerValidation.validate_sufficient_funds() + %LedgerValidation{fee: 1_000, inputs: inputs} + |> LedgerValidation.validate_sufficient_funds(movements) end test "should not be able to pay with the same non-fungible token twice" do @@ -415,11 +415,10 @@ defmodule Archethic.Mining.LedgerValidationTest do assert %LedgerValidation{sufficient_funds?: false} = %LedgerValidation{ fee: 1_000, - transaction_movements: movements, inputs: inputs, minted_utxos: minted_utxos } - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) end test "should return available balance and amount to spend and return sufficient_funds to true" do @@ -491,11 +490,10 @@ defmodule Archethic.Mining.LedgerValidationTest do } = %LedgerValidation{ fee: 1_000, - transaction_movements: movements, inputs: inputs, minted_utxos: minted_utxos } - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) end end @@ -542,10 +540,9 @@ defmodule Archethic.Mining.LedgerValidationTest do } = %LedgerValidation{ fee: 40_000_000, - transaction_movements: movements, inputs: inputs } - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx_address, timestamp) end @@ -630,10 +627,9 @@ defmodule Archethic.Mining.LedgerValidationTest do } = %LedgerValidation{ fee: 40_000_000, - transaction_movements: movements, inputs: inputs } - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx_address, timestamp) end @@ -703,10 +699,9 @@ defmodule Archethic.Mining.LedgerValidationTest do } = %LedgerValidation{ fee: 40_000_000, - transaction_movements: movements, inputs: inputs } - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx_address, timestamp) end @@ -800,10 +795,9 @@ defmodule Archethic.Mining.LedgerValidationTest do } = %LedgerValidation{ fee: 40_000_000, - transaction_movements: movements, inputs: inputs } - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx_address, timestamp) end @@ -879,10 +873,9 @@ defmodule Archethic.Mining.LedgerValidationTest do } = %LedgerValidation{ fee: 40_000_000, - transaction_movements: movements, inputs: inputs } - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx_address, timestamp) end @@ -921,11 +914,10 @@ defmodule Archethic.Mining.LedgerValidationTest do assert ops_result = %LedgerValidation{ fee: 1_000, - transaction_movements: movements, inputs: inputs, minted_utxos: minted_utxos } - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx_address, now) assert [ @@ -990,11 +982,10 @@ defmodule Archethic.Mining.LedgerValidationTest do assert ops_result = %LedgerValidation{ fee: 1_000, - transaction_movements: movements, inputs: inputs, minted_utxos: minted_utxos } - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx_address, now) assert [] = ops_result.unspent_outputs @@ -1059,11 +1050,10 @@ defmodule Archethic.Mining.LedgerValidationTest do assert ops_result = %LedgerValidation{ fee: 1_000, - transaction_movements: movements, inputs: inputs, minted_utxos: minted_utxos } - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx_address, now) assert [ @@ -1166,7 +1156,7 @@ defmodule Archethic.Mining.LedgerValidationTest do fee: 40_000_000 } = %LedgerValidation{fee: 40_000_000, inputs: inputs} - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds([]) |> LedgerValidation.consume_inputs(transaction_address, transaction_timestamp) tx_address = "@Alice2" @@ -1210,8 +1200,8 @@ defmodule Archethic.Mining.LedgerValidationTest do %VersionedUnspentOutput{unspent_output: %UnspentOutput{from: "@Tom5"}} ] } = - %LedgerValidation{inputs: inputs, transaction_movements: movements} - |> LedgerValidation.validate_sufficient_funds() + %LedgerValidation{inputs: inputs} + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx_address, now) end @@ -1234,7 +1224,7 @@ defmodule Archethic.Mining.LedgerValidationTest do unspent_outputs: [%UnspentOutput{type: :state, encoded_payload: ^new_state}] } = %LedgerValidation{fee: 0, inputs: inputs} - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds([]) |> LedgerValidation.consume_inputs("@Alice2", DateTime.utc_now(), new_state, nil) end @@ -1284,7 +1274,7 @@ defmodule Archethic.Mining.LedgerValidationTest do assert %LedgerValidation{fee: 0, unspent_outputs: [], consumed_inputs: []} = %LedgerValidation{fee: 0, inputs: inputs} - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds([]) |> LedgerValidation.consume_inputs("@Alice2", ~U[2022-10-10 10:44:38.983Z]) end @@ -1336,8 +1326,8 @@ defmodule Archethic.Mining.LedgerValidationTest do ] assert %LedgerValidation{fee: 0, unspent_outputs: [], consumed_inputs: consumed_inputs} = - %LedgerValidation{fee: 0, inputs: all_utxos, transaction_movements: movements} - |> LedgerValidation.validate_sufficient_funds() + %LedgerValidation{fee: 0, inputs: all_utxos} + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(random_address(), ~U[2022-10-10 10:44:38.983Z]) # order does not matter @@ -1385,8 +1375,8 @@ defmodule Archethic.Mining.LedgerValidationTest do movements = [%TransactionMovement{to: random_address(), amount: 200_000_000, type: :UCO}] assert %LedgerValidation{fee: 0, unspent_outputs: [], consumed_inputs: consumed_inputs} = - %LedgerValidation{fee: 0, inputs: all_utxos, transaction_movements: movements} - |> LedgerValidation.validate_sufficient_funds() + %LedgerValidation{fee: 0, inputs: all_utxos} + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(random_address(), ~U[2022-10-10 10:44:38.983Z]) # order does not matter @@ -1441,10 +1431,9 @@ defmodule Archethic.Mining.LedgerValidationTest do assert %LedgerValidation{fee: 0, unspent_outputs: [], consumed_inputs: consumed_inputs} = %LedgerValidation{ fee: 0, - inputs: randomized_utxo, - transaction_movements: movements + inputs: randomized_utxo } - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs( random_address(), ~U[2022-10-10 10:44:38.983Z] @@ -1472,7 +1461,7 @@ defmodule Archethic.Mining.LedgerValidationTest do RewardTokens.add_reward_token_address(reward_token_address) - movement = [ + movements = [ %TransactionMovement{to: address1, amount: 10, type: :UCO}, %TransactionMovement{to: address1, amount: 10, type: {:token, token_address, 0}}, %TransactionMovement{to: address1, amount: 40, type: {:token, token_address, 0}}, @@ -1488,8 +1477,7 @@ defmodule Archethic.Mining.LedgerValidationTest do assert %LedgerValidation{transaction_movements: resolved_movements} = LedgerValidation.build_resolved_movements( - %LedgerValidation{}, - movement, + %LedgerValidation{transaction_movements: movements}, resolved_addresses, :transfer ) diff --git a/test/archethic/mining/validation_context_test.exs b/test/archethic/mining/validation_context_test.exs index efc26d34f..58f509547 100644 --- a/test/archethic/mining/validation_context_test.exs +++ b/test/archethic/mining/validation_context_test.exs @@ -624,9 +624,9 @@ defmodule Archethic.Mining.ValidationContextTest do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(unspent_outputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) + |> LedgerValidation.build_resolved_movements(resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() %ValidationStamp{ @@ -656,9 +656,9 @@ defmodule Archethic.Mining.ValidationContextTest do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(unspent_outputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) + |> LedgerValidation.build_resolved_movements(resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() %ValidationStamp{ @@ -688,9 +688,9 @@ defmodule Archethic.Mining.ValidationContextTest do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(unspent_outputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) + |> LedgerValidation.build_resolved_movements(resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() %ValidationStamp{ @@ -721,9 +721,9 @@ defmodule Archethic.Mining.ValidationContextTest do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(unspent_outputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) + |> LedgerValidation.build_resolved_movements(resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() %ValidationStamp{ @@ -814,9 +814,9 @@ defmodule Archethic.Mining.ValidationContextTest do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(unspent_outputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) + |> LedgerValidation.build_resolved_movements(resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() %ValidationStamp{ @@ -846,9 +846,9 @@ defmodule Archethic.Mining.ValidationContextTest do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(unspent_outputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) + |> LedgerValidation.build_resolved_movements(resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() |> Map.put( :consumed_inputs, diff --git a/test/support/transaction_factory.ex b/test/support/transaction_factory.ex index 32d026b3d..2d95d0fd7 100644 --- a/test/support/transaction_factory.ex +++ b/test/support/transaction_factory.ex @@ -101,9 +101,9 @@ defmodule Archethic.TransactionFactory do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(inputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, protocol_version) - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) + |> LedgerValidation.build_resolved_movements(resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() poi = @@ -160,9 +160,9 @@ defmodule Archethic.TransactionFactory do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(inputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, protocol_version) - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) + |> LedgerValidation.build_resolved_movements(resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() validation_stamp = @@ -205,9 +205,9 @@ defmodule Archethic.TransactionFactory do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(inputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, protocol_version) - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) + |> LedgerValidation.build_resolved_movements(resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() validation_stamp = %ValidationStamp{ @@ -253,9 +253,9 @@ defmodule Archethic.TransactionFactory do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(inputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, protocol_version) - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) + |> LedgerValidation.build_resolved_movements(resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() validation_stamp = %ValidationStamp{ @@ -294,9 +294,9 @@ defmodule Archethic.TransactionFactory do %LedgerValidation{fee: 1_000_000_000} |> LedgerValidation.filter_usable_inputs(inputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, protocol_version) - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) + |> LedgerValidation.build_resolved_movements(resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() validation_stamp = @@ -338,9 +338,9 @@ defmodule Archethic.TransactionFactory do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(inputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, protocol_version) - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) + |> LedgerValidation.build_resolved_movements(resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() validation_stamp = @@ -399,9 +399,9 @@ defmodule Archethic.TransactionFactory do %LedgerValidation{fee: fee} |> LedgerValidation.filter_usable_inputs(inputs, contract_context) |> LedgerValidation.mint_token_utxos(tx, timestamp, protocol_version) - |> LedgerValidation.validate_sufficient_funds() + |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx.address, timestamp, encoded_state, contract_context) - |> LedgerValidation.build_resolved_movements(movements, resolved_addresses, tx.type) + |> LedgerValidation.build_resolved_movements(resolved_addresses, tx.type) |> LedgerValidation.to_ledger_operations() validation_stamp = From 15ff998dc15df112e9d6a148d7e8f696662d5330 Mon Sep 17 00:00:00 2001 From: Neylix Date: Tue, 12 Nov 2024 11:22:15 +0100 Subject: [PATCH 5/7] Add test to ledger_validation_test Ensure validate_sufficient_funds add movements to the struct --- test/archethic/mining/ledger_validation_test.exs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/archethic/mining/ledger_validation_test.exs b/test/archethic/mining/ledger_validation_test.exs index 80a8236bc..89dae244b 100644 --- a/test/archethic/mining/ledger_validation_test.exs +++ b/test/archethic/mining/ledger_validation_test.exs @@ -349,6 +349,20 @@ defmodule Archethic.Mining.LedgerValidationTest do end describe "validate_sufficient_funds/2" do + test "should set the movement in the struct" do + movements = [ + %TransactionMovement{ + to: "@JeanClaude", + amount: 100_000_000, + type: {:token, "@CharlieToken", 0} + } + ] + + assert %LedgerValidation{transaction_movements: ^movements} = + %LedgerValidation{fee: 1_000} + |> LedgerValidation.validate_sufficient_funds(movements) + end + test "should return insufficient funds when not enough uco" do assert %LedgerValidation{sufficient_funds?: false} = %LedgerValidation{fee: 1_000} |> LedgerValidation.validate_sufficient_funds([]) From 415df587697a77fbaf0b252e0105ea7b8327dcfb Mon Sep 17 00:00:00 2001 From: Neylix Date: Tue, 12 Nov 2024 13:50:22 +0100 Subject: [PATCH 6/7] Add state field in LedgerValidation struct It allow to ensure the order of called functions --- lib/archethic/mining/ledger_validation.ex | 129 ++-- lib/archethic/mining/validation_context.ex | 24 +- .../mining/ledger_validation_test.exs | 564 ++++++++++++------ 3 files changed, 493 insertions(+), 224 deletions(-) diff --git a/lib/archethic/mining/ledger_validation.ex b/lib/archethic/mining/ledger_validation.ex index b9a536176..ee7052d52 100644 --- a/lib/archethic/mining/ledger_validation.ex +++ b/lib/archethic/mining/ledger_validation.ex @@ -5,7 +5,8 @@ defmodule Archethic.Mining.LedgerValidation do @unit_uco 100_000_000 - defstruct transaction_movements: nil, + defstruct state: :init, + transaction_movements: [], unspent_outputs: [], fee: 0, consumed_inputs: [], @@ -31,6 +32,25 @@ defmodule Archethic.Mining.LedgerValidation do alias Archethic.TransactionChain.TransactionData + @typedoc """ + LedgerValidation should execute functions in a specific order. + To avoid miss order, state is updated to ensure order is respected + State is updated following + - :init + - :filtered_inputs + - :utxos_minted + - :sufficient_funds_validated + - :inputs_consumed + - :movements_resolved + """ + @type state() :: + :init + | :filtered_inputs + | :utxos_minted + | :sufficient_funds_validated + | :inputs_consumed + | :movements_resolved + @typedoc """ - Transaction movements: represents the pending transaction ledger movements - Unspent outputs: represents the new unspent outputs @@ -38,7 +58,8 @@ defmodule Archethic.Mining.LedgerValidation do - Consumed inputs: represents the list of inputs consumed to produce the unspent outputs """ @type t() :: %__MODULE__{ - transaction_movements: nil | list(TransactionMovement.t()), + state: state(), + transaction_movements: list(TransactionMovement.t()), unspent_outputs: list(UnspentOutput.t()), fee: non_neg_integer(), consumed_inputs: list(VersionedUnspentOutput.t()), @@ -65,10 +86,13 @@ defmodule Archethic.Mining.LedgerValidation do inputs :: list(VersionedUnspentOutput.t()), contract_context :: ContractContext.t() | nil ) :: t() - def filter_usable_inputs(ops, inputs, nil), do: %__MODULE__{ops | inputs: inputs} + def filter_usable_inputs(ops = %__MODULE__{state: :init}, inputs, nil), + do: %__MODULE__{ops | inputs: inputs} |> next_state() - def filter_usable_inputs(ops, inputs, contract_context), - do: %__MODULE__{ops | inputs: ContractContext.ledger_inputs(contract_context, inputs)} + def filter_usable_inputs(ops = %__MODULE__{state: :init}, inputs, contract_context) do + %__MODULE__{ops | inputs: ContractContext.ledger_inputs(contract_context, inputs)} + |> next_state() + end @doc """ Build some ledger operations from a specific transaction @@ -80,27 +104,30 @@ defmodule Archethic.Mining.LedgerValidation do protocol_version :: non_neg_integer() ) :: t() def mint_token_utxos( - ops, + ops = %__MODULE__{state: :filtered_inputs}, %Transaction{address: address, type: type, data: %TransactionData{content: content}}, timestamp, protocol_version ) when type in [:token, :mint_rewards] and not is_nil(timestamp) do - case Jason.decode(content) do - {:ok, json} -> - minted_utxos = - json - |> create_token_utxos(address, timestamp) - |> VersionedUnspentOutput.wrap_unspent_outputs(protocol_version) - - %__MODULE__{ops | minted_utxos: minted_utxos} + new_ops = + case Jason.decode(content) do + {:ok, json} -> + minted_utxos = + json + |> create_token_utxos(address, timestamp) + |> VersionedUnspentOutput.wrap_unspent_outputs(protocol_version) + + %__MODULE__{ops | minted_utxos: minted_utxos} + + _ -> + ops + end - _ -> - ops - end + next_state(new_ops) end - def mint_token_utxos(ops, _, _, _), do: ops + def mint_token_utxos(ops = %__MODULE__{state: :filtered_inputs}, _, _, _), do: next_state(ops) defp create_token_utxos( %{"token_reference" => token_ref, "supply" => supply}, @@ -196,20 +223,19 @@ defmodule Archethic.Mining.LedgerValidation do ) :: t() def build_resolved_movements( ops = %__MODULE__{ - transaction_movements: unresolved_movements + state: :inputs_consumed, + transaction_movements: movements }, resolved_addresses, tx_type - ) - when is_list(unresolved_movements) do - %__MODULE__{ - ops - | transaction_movements: - unresolved_movements - |> TransactionMovement.resolve_addresses(resolved_addresses) - |> Enum.map(&TransactionMovement.maybe_convert_reward(&1, tx_type)) - |> TransactionMovement.aggregate() - } + ) do + resolved_movements = + movements + |> TransactionMovement.resolve_addresses(resolved_addresses) + |> Enum.map(&TransactionMovement.maybe_convert_reward(&1, tx_type)) + |> TransactionMovement.aggregate() + + %__MODULE__{ops | transaction_movements: resolved_movements} |> next_state() end @doc """ @@ -218,10 +244,10 @@ defmodule Archethic.Mining.LedgerValidation do @spec validate_sufficient_funds(ops :: t(), list(TransactionMovement.t())) :: t() def validate_sufficient_funds( ops = %__MODULE__{ + state: :utxos_minted, fee: fee, inputs: inputs, - minted_utxos: minted_utxos, - transaction_movements: nil + minted_utxos: minted_utxos }, movements ) do @@ -239,6 +265,7 @@ defmodule Archethic.Mining.LedgerValidation do amount_to_spend: amount_to_spend, transaction_movements: movements } + |> next_state() end defp total_to_spend(fee, movements) do @@ -298,6 +325,7 @@ defmodule Archethic.Mining.LedgerValidation do """ @spec to_ledger_operations(ops :: t()) :: LedgerOperations.t() def to_ledger_operations(%__MODULE__{ + state: :movements_resolved, transaction_movements: movements, unspent_outputs: utxos, fee: fee, @@ -323,10 +351,26 @@ defmodule Archethic.Mining.LedgerValidation do encoded_state :: State.encoded() | nil, contract_context :: ContractContext.t() | nil ) :: t() - def consumed_inputs(ops = %__MODULE__{sufficient_funds?: false}), do: ops + def consume_inputs( + ops, + change_address, + timestamp, + encoded_state \\ nil, + contract_context \\ nil + ) + + def consume_inputs( + ops = %__MODULE__{state: :sufficient_funds_validated, sufficient_funds?: false}, + _, + _, + _, + _ + ), + do: next_state(ops) def consume_inputs( ops = %__MODULE__{ + state: :sufficient_funds_validated, inputs: inputs, minted_utxos: minted_utxos, balances: %{uco: uco_balance, token: tokens_balance}, @@ -334,8 +378,8 @@ defmodule Archethic.Mining.LedgerValidation do }, change_address, timestamp = %DateTime{}, - encoded_state \\ nil, - contract_context \\ nil + encoded_state, + contract_context ) do # Since AEIP-19 we can consume from minted tokens # Sort inputs, to have consistent results across all nodes @@ -380,6 +424,7 @@ defmodule Archethic.Mining.LedgerValidation do | unspent_outputs: new_unspent_outputs, consumed_inputs: versioned_consumed_utxos } + |> next_state() end defp tokens_utxos( @@ -522,4 +567,20 @@ defmodule Archethic.Mining.LedgerValidation do [new_utxo | utxos] end + + defp next_state(ops = %__MODULE__{state: :init}), do: %__MODULE__{ops | state: :filtered_inputs} + + defp next_state(ops = %__MODULE__{state: :filtered_inputs}), + do: %__MODULE__{ops | state: :utxos_minted} + + defp next_state(ops = %__MODULE__{state: :utxos_minted}), + do: %__MODULE__{ops | state: :sufficient_funds_validated} + + defp next_state(ops = %__MODULE__{state: :sufficient_funds_validated}), + do: %__MODULE__{ops | state: :inputs_consumed} + + defp next_state(ops = %__MODULE__{state: :inputs_consumed}), + do: %__MODULE__{ops | state: :movements_resolved} + + defp next_state(ops = %__MODULE__{state: :movements_resolved}), do: ops end diff --git a/lib/archethic/mining/validation_context.ex b/lib/archethic/mining/validation_context.ex index 64a129189..d2c07df94 100644 --- a/lib/archethic/mining/validation_context.ex +++ b/lib/archethic/mining/validation_context.ex @@ -1139,7 +1139,9 @@ defmodule Archethic.Mining.ValidationContext do proof_of_integrity: fn -> valid_stamp_proof_of_integrity?(stamp, context) end, proof_of_election: fn -> valid_stamp_proof_of_election?(stamp, context) end, transaction_fee: fn -> valid_stamp_fee?(stamp, fee) end, - transaction_movements: fn -> valid_stamp_transaction_movements?(stamp, context) end, + transaction_movements: fn -> + valid_stamp_transaction_movements?(stamp, ledger_operations) + end, recipients: fn -> valid_stamp_recipients?(stamp, context) end, consumed_inputs: fn -> valid_consumed_inputs?(stamp, ledger_operations) end, unspent_outputs: fn -> valid_stamp_unspent_outputs?(stamp, ledger_operations) end, @@ -1232,25 +1234,11 @@ defmodule Archethic.Mining.ValidationContext do defp valid_stamp_transaction_movements?( %ValidationStamp{ - ledger_operations: - _ops = %LedgerOperations{ - transaction_movements: transaction_movements - } + ledger_operations: %LedgerOperations{transaction_movements: stamp_movements} }, - %__MODULE__{ - transaction: tx = %Transaction{type: tx_type}, - resolved_addresses: resolved_addresses - } + %LedgerOperations{transaction_movements: expected_movements} ) do - movements = Transaction.get_movements(tx) - - %LedgerOperations{transaction_movements: resolved_movements} = - %LedgerValidation{transaction_movements: movements} - |> LedgerValidation.build_resolved_movements(resolved_addresses, tx_type) - |> LedgerValidation.to_ledger_operations() - - length(resolved_movements) == length(transaction_movements) and - Enum.all?(resolved_movements, &(&1 in transaction_movements)) + expected_movements |> MapSet.new() |> MapSet.equal?(MapSet.new(stamp_movements)) end defp valid_consumed_inputs?( diff --git a/test/archethic/mining/ledger_validation_test.exs b/test/archethic/mining/ledger_validation_test.exs index 89dae244b..937d2f3d8 100644 --- a/test/archethic/mining/ledger_validation_test.exs +++ b/test/archethic/mining/ledger_validation_test.exs @@ -5,6 +5,8 @@ defmodule Archethic.Mining.LedgerValidationTest do alias Archethic.TransactionFactory + alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations + alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.TransactionMovement alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.UnspentOutput @@ -22,13 +24,36 @@ defmodule Archethic.Mining.LedgerValidationTest do end describe "mint_token_utxos/4" do + test "should raise if not in filtered_inputs state" do + tx = TransactionFactory.create_valid_transaction([]) + + assert_raise FunctionClauseError, fn -> + %LedgerValidation{} + |> LedgerValidation.mint_token_utxos(tx, DateTime.utc_now(), current_protocol_version()) + end + end + + test "should update state to utxos_minted" do + tx = TransactionFactory.create_valid_transaction([]) + + assert %LedgerValidation{state: :utxos_minted} = + %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) + |> LedgerValidation.mint_token_utxos( + tx, + DateTime.utc_now(), + current_protocol_version() + ) + end + test "should return empty list for non token/mint_reward transaction" do types = Archethic.TransactionChain.Transaction.types() -- [:node, :mint_reward] Enum.each(types, fn t -> assert %LedgerValidation{minted_utxos: []} = - LedgerValidation.mint_token_utxos( - %LedgerValidation{}, + %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) + |> LedgerValidation.mint_token_utxos( TransactionFactory.create_valid_transaction([], type: t), DateTime.utc_now(), current_protocol_version() @@ -38,8 +63,9 @@ defmodule Archethic.Mining.LedgerValidationTest do test "should return empty list if content is invalid" do assert %LedgerValidation{minted_utxos: []} = - LedgerValidation.mint_token_utxos( - %LedgerValidation{}, + %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) + |> LedgerValidation.mint_token_utxos( TransactionFactory.create_valid_transaction([], type: :token, content: "not a json" @@ -49,8 +75,9 @@ defmodule Archethic.Mining.LedgerValidationTest do ) assert %LedgerValidation{minted_utxos: []} = - LedgerValidation.mint_token_utxos( - %LedgerValidation{}, + %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) + |> LedgerValidation.mint_token_utxos( TransactionFactory.create_valid_transaction([], type: :token, content: "{}"), DateTime.utc_now(), current_protocol_version() @@ -86,6 +113,7 @@ defmodule Archethic.Mining.LedgerValidationTest do } ] = %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) |> LedgerValidation.mint_token_utxos(tx, now, current_protocol_version()) |> Map.fetch!(:minted_utxos) |> VersionedUnspentOutput.unwrap_unspent_outputs() @@ -107,6 +135,7 @@ defmodule Archethic.Mining.LedgerValidationTest do assert %LedgerValidation{minted_utxos: []} = %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) |> LedgerValidation.mint_token_utxos(tx, now, current_protocol_version()) tx = @@ -122,6 +151,7 @@ defmodule Archethic.Mining.LedgerValidationTest do assert %LedgerValidation{minted_utxos: []} = %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) |> LedgerValidation.mint_token_utxos(tx, now, current_protocol_version()) token_address = random_address() @@ -140,6 +170,7 @@ defmodule Archethic.Mining.LedgerValidationTest do assert %LedgerValidation{minted_utxos: []} = %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) |> LedgerValidation.mint_token_utxos(tx, now, current_protocol_version()) end end @@ -173,6 +204,7 @@ defmodule Archethic.Mining.LedgerValidationTest do } ] = %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) |> LedgerValidation.mint_token_utxos(tx, now, current_protocol_version()) |> Map.fetch!(:minted_utxos) |> VersionedUnspentOutput.unwrap_unspent_outputs() @@ -216,6 +248,7 @@ defmodule Archethic.Mining.LedgerValidationTest do ] } = %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) |> LedgerValidation.mint_token_utxos(tx, now, current_protocol_version()) end @@ -273,6 +306,7 @@ defmodule Archethic.Mining.LedgerValidationTest do assert %LedgerValidation{minted_utxos: ^expected_utxos} = %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) |> LedgerValidation.mint_token_utxos(tx, now, current_protocol_version()) end @@ -298,6 +332,7 @@ defmodule Archethic.Mining.LedgerValidationTest do assert %LedgerValidation{minted_utxos: []} = %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) |> LedgerValidation.mint_token_utxos(tx, now, current_protocol_version()) end @@ -316,6 +351,7 @@ defmodule Archethic.Mining.LedgerValidationTest do assert %LedgerValidation{minted_utxos: []} = %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) |> LedgerValidation.mint_token_utxos(tx, now, current_protocol_version()) tx = @@ -330,6 +366,7 @@ defmodule Archethic.Mining.LedgerValidationTest do assert %LedgerValidation{minted_utxos: []} = %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) |> LedgerValidation.mint_token_utxos(tx, now, current_protocol_version()) tx = @@ -344,12 +381,35 @@ defmodule Archethic.Mining.LedgerValidationTest do assert %LedgerValidation{minted_utxos: []} = %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) |> LedgerValidation.mint_token_utxos(tx, now, current_protocol_version()) end end describe "validate_sufficient_funds/2" do - test "should set the movement in the struct" do + setup do + %{tx: TransactionFactory.create_valid_transaction()} + end + + test "should raise if not in minted_utxos state" do + assert_raise FunctionClauseError, fn -> + %LedgerValidation{} |> LedgerValidation.validate_sufficient_funds([]) + end + end + + test "should update state to sufficient_funds_validated", %{tx: tx} do + assert %LedgerValidation{state: :sufficient_funds_validated} = + %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) + |> LedgerValidation.mint_token_utxos( + tx, + DateTime.utc_now(), + current_protocol_version() + ) + |> LedgerValidation.validate_sufficient_funds([]) + end + + test "should set the movement in the struct", %{tx: tx} do movements = [ %TransactionMovement{ to: "@JeanClaude", @@ -360,15 +420,28 @@ defmodule Archethic.Mining.LedgerValidationTest do assert %LedgerValidation{transaction_movements: ^movements} = %LedgerValidation{fee: 1_000} + |> LedgerValidation.filter_usable_inputs([], nil) + |> LedgerValidation.mint_token_utxos( + tx, + DateTime.utc_now(), + current_protocol_version() + ) |> LedgerValidation.validate_sufficient_funds(movements) end - test "should return insufficient funds when not enough uco" do + test "should return insufficient funds when not enough uco", %{tx: tx} do assert %LedgerValidation{sufficient_funds?: false} = - %LedgerValidation{fee: 1_000} |> LedgerValidation.validate_sufficient_funds([]) + %LedgerValidation{fee: 1_000} + |> LedgerValidation.filter_usable_inputs([], nil) + |> LedgerValidation.mint_token_utxos( + tx, + DateTime.utc_now(), + current_protocol_version() + ) + |> LedgerValidation.validate_sufficient_funds([]) end - test "should return insufficient funds when not enough tokens" do + test "should return insufficient funds when not enough tokens", %{tx: tx} do inputs = [ %UnspentOutput{ from: "@Charlie1", @@ -388,11 +461,30 @@ defmodule Archethic.Mining.LedgerValidationTest do ] assert %LedgerValidation{sufficient_funds?: false} = - %LedgerValidation{fee: 1_000, inputs: inputs} + %LedgerValidation{fee: 1_000} + |> LedgerValidation.filter_usable_inputs(inputs, nil) + |> LedgerValidation.mint_token_utxos( + tx, + DateTime.utc_now(), + current_protocol_version() + ) |> LedgerValidation.validate_sufficient_funds(movements) end test "should not be able to pay with the same non-fungible token twice" do + tx = + TransactionFactory.create_valid_transaction([], + type: :token, + content: """ + { + "supply": 100000000, + "type": "non-fungible", + "name": "My NFT", + "symbol": "MNFT" + } + """ + ) + inputs = [ %UnspentOutput{ from: "@Charlie1", @@ -407,35 +499,40 @@ defmodule Archethic.Mining.LedgerValidationTest do %TransactionMovement{ to: "@JeanClaude", amount: 100_000_000, - type: {:token, "@Token", 1} + type: {:token, tx.address, 1} }, %TransactionMovement{ to: "@JeanBob", amount: 100_000_000, - type: {:token, "@Token", 1} - } - ] - - minted_utxos = [ - %UnspentOutput{ - from: "@Alice", - amount: 100_000_000, - type: {:token, "@Token", 1}, - timestamp: ~U[2022-10-09 08:39:10.463Z] + type: {:token, tx.address, 1} } - |> VersionedUnspentOutput.wrap_unspent_output(current_protocol_version()) ] assert %LedgerValidation{sufficient_funds?: false} = - %LedgerValidation{ - fee: 1_000, - inputs: inputs, - minted_utxos: minted_utxos - } + %LedgerValidation{fee: 1_000} + |> LedgerValidation.filter_usable_inputs(inputs, nil) + |> LedgerValidation.mint_token_utxos( + tx, + DateTime.utc_now(), + current_protocol_version() + ) |> LedgerValidation.validate_sufficient_funds(movements) end test "should return available balance and amount to spend and return sufficient_funds to true" do + tx = + TransactionFactory.create_valid_transaction([], + type: :token, + content: """ + { + "supply": 100000000, + "type": "non-fungible", + "name": "My NFT", + "symbol": "MNFT" + } + """ + ) + inputs = [ %UnspentOutput{ @@ -463,7 +560,7 @@ defmodule Archethic.Mining.LedgerValidationTest do %TransactionMovement{ to: "@JeanClaude", amount: 100_000_000, - type: {:token, "@Token", 1} + type: {:token, tx.address, 1} }, %TransactionMovement{ to: "@Michel", @@ -477,24 +574,14 @@ defmodule Archethic.Mining.LedgerValidationTest do } ] - minted_utxos = [ - %UnspentOutput{ - from: "@Alice", - amount: 100_000_000, - type: {:token, "@Token", 1}, - timestamp: ~U[2022-10-09 08:39:10.463Z] - } - |> VersionedUnspentOutput.wrap_unspent_output(current_protocol_version()) - ] - expected_balance = %{ uco: 10_000, - token: %{{"@Token1", 0} => 200_100_000, {"@Token", 1} => 100_000_000} + token: %{{"@Token1", 0} => 200_100_000, {tx.address, 1} => 100_000_000} } expected_amount_to_spend = %{ uco: 1456, - token: %{{"@Token1", 0} => 120_000_000, {"@Token", 1} => 100_000_000} + token: %{{"@Token1", 0} => 120_000_000, {tx.address, 1} => 100_000_000} } assert %LedgerValidation{ @@ -502,17 +589,45 @@ defmodule Archethic.Mining.LedgerValidationTest do balances: ^expected_balance, amount_to_spend: ^expected_amount_to_spend } = - %LedgerValidation{ - fee: 1_000, - inputs: inputs, - minted_utxos: minted_utxos - } + %LedgerValidation{fee: 1_000} + |> LedgerValidation.filter_usable_inputs(inputs, nil) + |> LedgerValidation.mint_token_utxos( + tx, + DateTime.utc_now(), + current_protocol_version() + ) |> LedgerValidation.validate_sufficient_funds(movements) end end describe "consume_inputs/4" do - test "When a single unspent output is sufficient to satisfy the transaction movements" do + setup do + %{tx: TransactionFactory.create_valid_transaction()} + end + + test "should raise if not in sufficient_funds_validated state" do + assert_raise FunctionClauseError, fn -> + %LedgerValidation{} + |> LedgerValidation.consume_inputs(random_address(), DateTime.utc_now()) + end + end + + test "should update state to inputs_consumed", %{tx: tx} do + assert %LedgerValidation{state: :inputs_consumed} = + %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) + |> LedgerValidation.mint_token_utxos( + tx, + DateTime.utc_now(), + current_protocol_version() + ) + |> LedgerValidation.validate_sufficient_funds([]) + |> LedgerValidation.consume_inputs(random_address(), DateTime.utc_now()) + end + + test "When a single unspent output is sufficient to satisfy the transaction movements", %{ + tx: tx + } do timestamp = ~U[2022-10-10 10:44:38.983Z] tx_address = "@Alice2" @@ -552,15 +667,15 @@ defmodule Archethic.Mining.LedgerValidationTest do } ] } = - %LedgerValidation{ - fee: 40_000_000, - inputs: inputs - } + %LedgerValidation{fee: 40_000_000} + |> LedgerValidation.filter_usable_inputs(inputs, nil) + |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx_address, timestamp) end - test "When multiple little unspent output are sufficient to satisfy the transaction movements" do + test "When multiple little unspent output are sufficient to satisfy the transaction movements", + %{tx: tx} do tx_address = "@Alice2" timestamp = ~U[2022-10-10 10:44:38.983Z] @@ -639,15 +754,15 @@ defmodule Archethic.Mining.LedgerValidationTest do ], consumed_inputs: ^expected_consumed_inputs } = - %LedgerValidation{ - fee: 40_000_000, - inputs: inputs - } + %LedgerValidation{fee: 40_000_000} + |> LedgerValidation.filter_usable_inputs(inputs, nil) + |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx_address, timestamp) end - test "When using Token unspent outputs are sufficient to satisfy the transaction movements" do + test "When using Token unspent outputs are sufficient to satisfy the transaction movements", + %{tx: tx} do tx_address = "@Alice2" timestamp = ~U[2022-10-10 10:44:38.983Z] @@ -711,15 +826,15 @@ defmodule Archethic.Mining.LedgerValidationTest do ], consumed_inputs: ^expected_consumed_inputs } = - %LedgerValidation{ - fee: 40_000_000, - inputs: inputs - } + %LedgerValidation{fee: 40_000_000} + |> LedgerValidation.filter_usable_inputs(inputs, nil) + |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx_address, timestamp) end - test "When multiple Token unspent outputs are sufficient to satisfy the transaction movements" do + test "When multiple Token unspent outputs are sufficient to satisfy the transaction movements", + %{tx: tx} do tx_address = "@Alice2" timestamp = ~U[2022-10-10 10:44:38.983Z] @@ -807,15 +922,16 @@ defmodule Archethic.Mining.LedgerValidationTest do ], consumed_inputs: ^expected_consumed_inputs } = - %LedgerValidation{ - fee: 40_000_000, - inputs: inputs - } + %LedgerValidation{fee: 40_000_000} + |> LedgerValidation.filter_usable_inputs(inputs, nil) + |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx_address, timestamp) end - test "When non-fungible tokens are used as input but want to consume only a single input" do + test "When non-fungible tokens are used as input but want to consume only a single input", %{ + tx: tx + } do tx_address = "@Alice2" timestamp = ~U[2022-10-10 10:44:38.983Z] @@ -885,16 +1001,28 @@ defmodule Archethic.Mining.LedgerValidationTest do ], consumed_inputs: ^expected_consumed_inputs } = - %LedgerValidation{ - fee: 40_000_000, - inputs: inputs - } + %LedgerValidation{fee: 40_000_000} + |> LedgerValidation.filter_usable_inputs(inputs, nil) + |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx_address, timestamp) end test "should be able to pay with the minted fungible tokens" do - tx_address = "@Alice" + tx = + TransactionFactory.create_valid_transaction([], + type: :token, + content: """ + { + "supply": 100000000, + "type": "fungible", + "name": "My NFT", + "symbol": "MNFT" + } + """ + ) + + tx_address = tx.address now = DateTime.utc_now() inputs = [ @@ -911,34 +1039,22 @@ defmodule Archethic.Mining.LedgerValidationTest do %TransactionMovement{ to: "@JeanClaude", amount: 50_000_000, - type: {:token, "@Token", 0} - } - ] - - minted_utxos = [ - %UnspentOutput{ - from: "@Alice", - amount: 100_000_000, - type: {:token, "@Token", 0}, - timestamp: ~U[2022-10-09 08:39:10.463Z] + type: {:token, tx_address, 0} } - |> VersionedUnspentOutput.wrap_unspent_output(current_protocol_version()) ] assert ops_result = - %LedgerValidation{ - fee: 1_000, - inputs: inputs, - minted_utxos: minted_utxos - } + %LedgerValidation{fee: 1_000} + |> LedgerValidation.filter_usable_inputs(inputs, nil) + |> LedgerValidation.mint_token_utxos(tx, now, current_protocol_version()) |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx_address, now) assert [ %UnspentOutput{ - from: "@Alice", + from: ^tx_address, amount: 50_000_000, - type: {:token, "@Token", 0}, + type: {:token, ^tx_address, 0}, timestamp: ^now } ] = ops_result.unspent_outputs @@ -955,14 +1071,27 @@ defmodule Archethic.Mining.LedgerValidationTest do %UnspentOutput{ from: ^burn_address, amount: 100_000_000, - type: {:token, "@Token", 0}, - timestamp: ~U[2022-10-09 08:39:10.463Z] + type: {:token, ^tx_address, 0}, + timestamp: ^now } ] = ops_result.consumed_inputs |> VersionedUnspentOutput.unwrap_unspent_outputs() end test "should be able to pay with the minted non-fungible tokens" do - tx_address = "@Alice" + tx = + TransactionFactory.create_valid_transaction([], + type: :token, + content: """ + { + "supply": 100000000, + "type": "non-fungible", + "name": "My NFT", + "symbol": "MNFT" + } + """ + ) + + tx_address = tx.address now = DateTime.utc_now() inputs = [ @@ -979,26 +1108,14 @@ defmodule Archethic.Mining.LedgerValidationTest do %TransactionMovement{ to: "@JeanClaude", amount: 100_000_000, - type: {:token, "@Token", 1} + type: {:token, tx_address, 1} } ] - minted_utxos = [ - %UnspentOutput{ - from: "@Alice", - amount: 100_000_000, - type: {:token, "@Token", 1}, - timestamp: ~U[2022-10-09 08:39:10.463Z] - } - |> VersionedUnspentOutput.wrap_unspent_output(current_protocol_version()) - ] - assert ops_result = - %LedgerValidation{ - fee: 1_000, - inputs: inputs, - minted_utxos: minted_utxos - } + %LedgerValidation{fee: 1_000} + |> LedgerValidation.filter_usable_inputs(inputs, nil) + |> LedgerValidation.mint_token_utxos(tx, now, current_protocol_version()) |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx_address, now) @@ -1016,14 +1133,34 @@ defmodule Archethic.Mining.LedgerValidationTest do %UnspentOutput{ from: ^burn_address, amount: 100_000_000, - type: {:token, "@Token", 1}, - timestamp: ~U[2022-10-09 08:39:10.463Z] + type: {:token, ^tx_address, 1}, + timestamp: ^now } ] = ops_result.consumed_inputs |> VersionedUnspentOutput.unwrap_unspent_outputs() end test "should be able to pay with the minted non-fungible tokens (collection)" do - tx_address = "@Alice" + tx = + TransactionFactory.create_valid_transaction([], + type: :token, + content: """ + { + "supply": 200000000, + "name": "My NFT", + "type": "non-fungible", + "symbol": "MNFT", + "properties": { + "description": "this property is for all NFT" + }, + "collection": [ + { "image": "link of the 1st NFT image" }, + { "image": "link of the 2nd NFT image" } + ] + } + """ + ) + + tx_address = tx.address now = DateTime.utc_now() inputs = [ @@ -1040,42 +1177,23 @@ defmodule Archethic.Mining.LedgerValidationTest do %TransactionMovement{ to: "@JeanClaude", amount: 100_000_000, - type: {:token, "@Token", 2} + type: {:token, tx_address, 2} } ] - minted_utxos = - [ - %UnspentOutput{ - from: "@Alice", - amount: 100_000_000, - type: {:token, "@Token", 1}, - timestamp: ~U[2022-10-09 08:39:10.463Z] - }, - %UnspentOutput{ - from: "@Alice", - amount: 100_000_000, - type: {:token, "@Token", 2}, - timestamp: ~U[2022-10-09 08:39:10.463Z] - } - ] - |> VersionedUnspentOutput.wrap_unspent_outputs(current_protocol_version()) - assert ops_result = - %LedgerValidation{ - fee: 1_000, - inputs: inputs, - minted_utxos: minted_utxos - } + %LedgerValidation{fee: 1_000} + |> LedgerValidation.filter_usable_inputs(inputs, nil) + |> LedgerValidation.mint_token_utxos(tx, now, current_protocol_version()) |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx_address, now) assert [ %UnspentOutput{ - from: "@Alice", + from: ^tx_address, amount: 100_000_000, - type: {:token, "@Token", 1}, - timestamp: ~U[2022-10-09 08:39:10.463Z] + type: {:token, ^tx_address, 1}, + timestamp: ^now } ] = ops_result.unspent_outputs @@ -1091,13 +1209,13 @@ defmodule Archethic.Mining.LedgerValidationTest do %UnspentOutput{ from: ^burn_address, amount: 100_000_000, - type: {:token, "@Token", 2}, - timestamp: ~U[2022-10-09 08:39:10.463Z] + type: {:token, ^tx_address, 2}, + timestamp: ^now } ] = ops_result.consumed_inputs |> VersionedUnspentOutput.unwrap_unspent_outputs() end - test "should merge two similar tokens and update the from & timestamp" do + test "should merge two similar tokens and update the from & timestamp", %{tx: tx} do transaction_address = random_address() transaction_timestamp = DateTime.utc_now() @@ -1169,7 +1287,13 @@ defmodule Archethic.Mining.LedgerValidationTest do consumed_inputs: ^expected_consumed_inputs, fee: 40_000_000 } = - %LedgerValidation{fee: 40_000_000, inputs: inputs} + %LedgerValidation{fee: 40_000_000} + |> LedgerValidation.filter_usable_inputs(inputs, nil) + |> LedgerValidation.mint_token_utxos( + tx, + transaction_timestamp, + current_protocol_version() + ) |> LedgerValidation.validate_sufficient_funds([]) |> LedgerValidation.consume_inputs(transaction_address, transaction_timestamp) @@ -1214,19 +1338,23 @@ defmodule Archethic.Mining.LedgerValidationTest do %VersionedUnspentOutput{unspent_output: %UnspentOutput{from: "@Tom5"}} ] } = - %LedgerValidation{inputs: inputs} + %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs(inputs, nil) + |> LedgerValidation.mint_token_utxos(tx, now, current_protocol_version()) |> LedgerValidation.validate_sufficient_funds(movements) |> LedgerValidation.consume_inputs(tx_address, now) end - test "should consume state if it's not the same" do + test "should consume state if it's not the same", %{tx: tx} do + now = DateTime.utc_now() + inputs = [ %UnspentOutput{ type: :state, from: random_address(), encoded_payload: :crypto.strong_rand_bytes(32), - timestamp: DateTime.utc_now() + timestamp: now } ] |> VersionedUnspentOutput.wrap_unspent_outputs(current_protocol_version()) @@ -1237,9 +1365,11 @@ defmodule Archethic.Mining.LedgerValidationTest do consumed_inputs: ^inputs, unspent_outputs: [%UnspentOutput{type: :state, encoded_payload: ^new_state}] } = - %LedgerValidation{fee: 0, inputs: inputs} + %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs(inputs, nil) + |> LedgerValidation.mint_token_utxos(tx, now, current_protocol_version()) |> LedgerValidation.validate_sufficient_funds([]) - |> LedgerValidation.consume_inputs("@Alice2", DateTime.utc_now(), new_state, nil) + |> LedgerValidation.consume_inputs("@Alice2", now, new_state, nil) end # test "should not consume state if it's the same" do @@ -1275,24 +1405,28 @@ defmodule Archethic.Mining.LedgerValidationTest do # ) # end - test "should not return any utxo if nothing is spent" do + test "should not return any utxo if nothing is spent", %{tx: tx} do + timestamp = ~U[2022-10-10 10:44:38.983Z] + inputs = [ %UnspentOutput{ from: "@Bob3", amount: 2_000_000_000, type: :UCO, - timestamp: ~U[2022-10-09 08:39:10.463Z] + timestamp: timestamp } |> VersionedUnspentOutput.wrap_unspent_output(current_protocol_version()) ] assert %LedgerValidation{fee: 0, unspent_outputs: [], consumed_inputs: []} = - %LedgerValidation{fee: 0, inputs: inputs} + %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs(inputs, nil) + |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) |> LedgerValidation.validate_sufficient_funds([]) - |> LedgerValidation.consume_inputs("@Alice2", ~U[2022-10-10 10:44:38.983Z]) + |> LedgerValidation.consume_inputs("@Alice2", timestamp) end - test "should not update utxo if not consumed" do + test "should not update utxo if not consumed", %{tx: tx} do token_address = random_address() utxo_not_used = [ @@ -1339,17 +1473,21 @@ defmodule Archethic.Mining.LedgerValidationTest do } ] + timestamp = ~U[2022-10-10 10:44:38.983Z] + assert %LedgerValidation{fee: 0, unspent_outputs: [], consumed_inputs: consumed_inputs} = - %LedgerValidation{fee: 0, inputs: all_utxos} + %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs(all_utxos, nil) + |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) |> LedgerValidation.validate_sufficient_funds(movements) - |> LedgerValidation.consume_inputs(random_address(), ~U[2022-10-10 10:44:38.983Z]) + |> LedgerValidation.consume_inputs(random_address(), timestamp) # order does not matter assert Enum.all?(consumed_inputs, &(&1 in consumed_utxo)) and length(consumed_inputs) == length(consumed_utxo) end - test "should optimize consumed utxo to avoid consolidation" do + test "should optimize consumed utxo to avoid consolidation", %{tx: tx} do optimized_utxo = [ %UnspentOutput{ from: random_address(), @@ -1388,17 +1526,21 @@ defmodule Archethic.Mining.LedgerValidationTest do movements = [%TransactionMovement{to: random_address(), amount: 200_000_000, type: :UCO}] + timestamp = ~U[2022-10-10 10:44:38.983Z] + assert %LedgerValidation{fee: 0, unspent_outputs: [], consumed_inputs: consumed_inputs} = - %LedgerValidation{fee: 0, inputs: all_utxos} + %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs(all_utxos, nil) + |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) |> LedgerValidation.validate_sufficient_funds(movements) - |> LedgerValidation.consume_inputs(random_address(), ~U[2022-10-10 10:44:38.983Z]) + |> LedgerValidation.consume_inputs(random_address(), timestamp) # order does not matter assert Enum.all?(consumed_inputs, &(&1 in consumed_utxo)) and length(consumed_inputs) == length(consumed_utxo) end - test "should sort utxo to be consistent across nodes" do + test "should sort utxo to be consistent across nodes", %{tx: tx} do [lower_address, higher_address] = [random_address(), random_address()] |> Enum.sort() optimized_utxo = [ @@ -1439,19 +1581,17 @@ defmodule Archethic.Mining.LedgerValidationTest do movements = [%TransactionMovement{to: random_address(), amount: 310_000_000, type: :UCO}] + timestamp = ~U[2022-10-10 10:44:38.983Z] + Enum.each(1..5, fn _ -> randomized_utxo = Enum.shuffle(all_utxo) assert %LedgerValidation{fee: 0, unspent_outputs: [], consumed_inputs: consumed_inputs} = - %LedgerValidation{ - fee: 0, - inputs: randomized_utxo - } + %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs(randomized_utxo, nil) + |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) |> LedgerValidation.validate_sufficient_funds(movements) - |> LedgerValidation.consume_inputs( - random_address(), - ~U[2022-10-10 10:44:38.983Z] - ) + |> LedgerValidation.consume_inputs(random_address(), timestamp) # order does not matter assert Enum.all?(consumed_inputs, &(&1 in consumed_utxo)) and @@ -1461,7 +1601,31 @@ defmodule Archethic.Mining.LedgerValidationTest do end describe "build_resoved_movements/3" do - test "should resolve, convert reward and aggregate movements" do + setup do + %{tx: TransactionFactory.create_valid_transaction()} + end + + test "should raise if not in inputs_consumed state" do + assert_raise FunctionClauseError, fn -> + %LedgerValidation{} |> LedgerValidation.build_resolved_movements(%{}, :transfer) + end + end + + test "should update state to movements_resolved", %{tx: tx} do + now = DateTime.utc_now() + + assert %LedgerValidation{state: :movements_resolved} = + %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) + |> LedgerValidation.mint_token_utxos(tx, now, current_protocol_version()) + |> LedgerValidation.validate_sufficient_funds([]) + |> LedgerValidation.consume_inputs(random_address(), now) + |> LedgerValidation.build_resolved_movements(%{}, :transfer) + end + + test "should resolve, convert reward and aggregate movements", %{tx: tx} do + now = DateTime.utc_now() + address1 = random_address() address2 = random_address() @@ -1490,15 +1654,71 @@ defmodule Archethic.Mining.LedgerValidationTest do ] assert %LedgerValidation{transaction_movements: resolved_movements} = - LedgerValidation.build_resolved_movements( - %LedgerValidation{transaction_movements: movements}, - resolved_addresses, - :transfer - ) + %LedgerValidation{} + |> LedgerValidation.filter_usable_inputs([], nil) + |> LedgerValidation.mint_token_utxos(tx, now, current_protocol_version()) + |> LedgerValidation.validate_sufficient_funds(movements) + |> LedgerValidation.consume_inputs(random_address(), now) + |> LedgerValidation.build_resolved_movements(resolved_addresses, :transfer) # Order does not matters assert length(expected_resolved_movement) == length(resolved_movements) assert Enum.all?(expected_resolved_movement, &Enum.member?(resolved_movements, &1)) end end + + describe "to_ledger_operations/1" do + setup do + %{tx: TransactionFactory.create_valid_transaction()} + end + + test "should raise if not in inputs_consumed state" do + assert_raise FunctionClauseError, fn -> + %LedgerValidation{} |> LedgerValidation.to_ledger_operations() + end + end + + test "should return LegderOperations struct", %{tx: tx} do + timestamp = ~U[2022-10-10 10:44:38.983Z] + tx_address = "@Alice2" + + inputs = [ + %UnspentOutput{ + from: "@Bob3", + amount: 2_000_000_000, + type: :UCO, + timestamp: ~U[2022-10-09 08:39:10.463Z] + } + |> VersionedUnspentOutput.wrap_unspent_output(current_protocol_version()) + ] + + movements = [ + %TransactionMovement{to: "@Bob4", amount: 1_040_000_000, type: :UCO}, + %TransactionMovement{to: "@Charlie2", amount: 217_000_000, type: :UCO} + ] + + resolved_addresses = Enum.map(movements, &{&1.to, &1.to}) |> Map.new() + + assert %LedgerOperations{ + fee: 40_000_000, + unspent_outputs: [ + %UnspentOutput{ + from: "@Alice2", + amount: 703_000_000, + type: :UCO, + timestamp: ~U[2022-10-10 10:44:38.983Z] + } + ], + consumed_inputs: ^inputs, + transaction_movements: ^movements + } = + %LedgerValidation{fee: 40_000_000} + |> LedgerValidation.filter_usable_inputs(inputs, nil) + |> LedgerValidation.mint_token_utxos(tx, timestamp, current_protocol_version()) + |> LedgerValidation.validate_sufficient_funds(movements) + |> LedgerValidation.consume_inputs(tx_address, timestamp) + |> LedgerValidation.build_resolved_movements(resolved_addresses, :transfer) + |> LedgerValidation.to_ledger_operations() + end + end end From aaefafcac19ba963ac4968344119d13e9f83adb1 Mon Sep 17 00:00:00 2001 From: Neylix Date: Tue, 12 Nov 2024 15:14:26 +0100 Subject: [PATCH 7/7] Simplify MUCO test --- .../mining/validation_context_test.exs | 142 ++++++++++-------- 1 file changed, 76 insertions(+), 66 deletions(-) diff --git a/test/archethic/mining/validation_context_test.exs b/test/archethic/mining/validation_context_test.exs index 58f509547..eac461590 100644 --- a/test/archethic/mining/validation_context_test.exs +++ b/test/archethic/mining/validation_context_test.exs @@ -163,7 +163,6 @@ defmodule Archethic.Mining.ValidationContextTest do test "should handle the MUCO correctly" do timestamp = DateTime.utc_now() |> DateTime.truncate(:millisecond) transfer_address = random_address() - resolved_address = random_address() muco_addr1 = random_address() muco_addr2 = random_address() @@ -171,65 +170,67 @@ defmodule Archethic.Mining.ValidationContextTest do RewardTokens.add_reward_token_address(muco_addr1) RewardTokens.add_reward_token_address(muco_addr2) - validation_context = %ValidationContext{ - create_context(timestamp, - utxos: [ - %UnspentOutput{ - from: random_address(), - amount: 10, - type: {:token, muco_addr1, 0}, - timestamp: timestamp - }, - %UnspentOutput{ - from: random_address(), - amount: 10, - type: {:token, muco_addr2, 0}, - timestamp: timestamp - } - ] - ) - | transaction: - TransactionFactory.create_non_valided_transaction( - ledger: %Ledger{ - token: %TokenLedger{ - transfers: [ - %TokenLedger.Transfer{ - token_address: muco_addr1, - token_id: 0, - to: transfer_address, - amount: 10 - }, - %TokenLedger.Transfer{ - token_address: muco_addr2, - token_id: 0, - to: transfer_address, - amount: 10 - } - ] - } + utxos = [ + %UnspentOutput{ + from: random_address(), + amount: 204_000_000, + type: :UCO, + timestamp: timestamp + }, + %UnspentOutput{ + from: random_address(), + amount: 10, + type: {:token, muco_addr1, 0}, + timestamp: timestamp + }, + %UnspentOutput{ + from: random_address(), + amount: 10, + type: {:token, muco_addr2, 0}, + timestamp: timestamp + } + ] + + transaction_opts = [ + ledger: %Ledger{ + token: %TokenLedger{ + transfers: [ + %TokenLedger.Transfer{ + token_address: muco_addr1, + token_id: 0, + to: transfer_address, + amount: 10 + }, + %TokenLedger.Transfer{ + token_address: muco_addr2, + token_id: 0, + to: transfer_address, + amount: 10 } - ), - resolved_addresses: %{transfer_address => resolved_address} - } + ] + } + } + ] + + validation_context = + create_context(timestamp, unspent_outputs: utxos, transaction_opts: transaction_opts) assert %ValidationContext{validation_stamp: %ValidationStamp{ledger_operations: ops}} = ValidationContext.create_validation_stamp(validation_context) - assert [ - %TransactionMovement{to: ^resolved_address, amount: 20, type: :UCO} - ] = ops.transaction_movements - - assert 111 = - Enum.reduce(ops.consumed_inputs, 0, fn %VersionedUnspentOutput{ - unspent_output: utxo - }, - acc -> - case utxo do - %UnspentOutput{amount: 204_000_000, type: :UCO} -> acc + 1 - %UnspentOutput{amount: 10, type: {:token, ^muco_addr1, 0}} -> acc + 10 - %UnspentOutput{amount: 10, type: {:token, ^muco_addr2, 0}} -> acc + 100 - end - end) + assert [%TransactionMovement{to: ^transfer_address, amount: 20, type: :UCO}] = + ops.transaction_movements + + assert utxos + |> VersionedUnspentOutput.wrap_unspent_outputs(current_protocol_version()) + |> MapSet.new() + |> MapSet.equal?(MapSet.new(ops.consumed_inputs)) + + remaining_uco = 204_000_000 - ops.fee + tx_address = validation_context.transaction.address + + assert [%UnspentOutput{from: ^tx_address, amount: ^remaining_uco, type: :UCO}] = + ops.unspent_outputs end end @@ -584,27 +585,36 @@ defmodule Archethic.Mining.ValidationContextTest do Enum.each(cross_validation_nodes, &P2P.add_and_connect_node(&1)) Enum.each(previous_storage_nodes, &P2P.add_and_connect_node(&1)) + default_utxo = %UnspentOutput{ + from: "@Alice2", + amount: 204_000_000, + type: :UCO, + timestamp: validation_time + } + unspent_outputs = - [ - %UnspentOutput{ - from: "@Alice2", - amount: 204_000_000, - type: :UCO, - timestamp: validation_time - } - | Keyword.get(opts, :utxos, []) - ] + opts + |> Keyword.get(:unspent_outputs, [default_utxo]) |> VersionedUnspentOutput.wrap_unspent_outputs(current_protocol_version()) + tx = + opts + |> Keyword.get(:transaction_opts, []) + |> TransactionFactory.create_non_valided_transaction() + + resolved_addresses = + tx |> Transaction.get_movements() |> Enum.map(&{&1.to, &1.to}) |> Map.new() + %ValidationContext{ - transaction: TransactionFactory.create_non_valided_transaction(), + transaction: tx, previous_storage_nodes: previous_storage_nodes, unspent_outputs: unspent_outputs, aggregated_utxos: unspent_outputs, welcome_node: welcome_node, coordinator_node: coordinator_node, cross_validation_nodes: cross_validation_nodes, - validation_time: validation_time + validation_time: validation_time, + resolved_addresses: resolved_addresses } end