From 0c2dd0014cd1700e81d1b4528cdeb3c53d422ffa Mon Sep 17 00:00:00 2001 From: Bastien CHAMAGNE Date: Wed, 16 Nov 2022 09:03:23 +0100 Subject: [PATCH] Query P2P in order to determine if an input is spent later on we query the last transaction and check if the utxo is still there if it is, it means the utxo has not been spent --- lib/archethic.ex | 21 +++++ test/archethic_test.exs | 174 +++++++++++++++++++++++++++++++++++----- 2 files changed, 175 insertions(+), 20 deletions(-) diff --git a/lib/archethic.ex b/lib/archethic.ex index 1827ae722..1855a229c 100644 --- a/lib/archethic.ex +++ b/lib/archethic.ex @@ -168,6 +168,27 @@ defmodule Archethic do """ @spec get_transaction_inputs(binary()) :: list(TransactionInput.t()) def get_transaction_inputs(address) when is_binary(address) do + # check the last transaction inputs to determine if a utxo is spent or not + {:ok, latest_address} = get_last_transaction_address(address) + + if latest_address == address do + do_get_transaction_inputs(address) + else + latest_tx_inputs = do_get_transaction_inputs(latest_address) + current_tx_inputs = do_get_transaction_inputs(address) + + Enum.map(current_tx_inputs, fn input -> + spent? = + not Enum.any?(latest_tx_inputs, fn input2 -> + input.from == input2.from and input.type == input2.type + end) + + %TransactionInput{input | spent?: spent?} + end) + end + end + + defp do_get_transaction_inputs(address) do nodes = address |> Election.chain_storage_nodes(P2P.authorized_and_available_nodes()) diff --git a/test/archethic_test.exs b/test/archethic_test.exs index 7040d04bd..18f37ee17 100644 --- a/test/archethic_test.exs +++ b/test/archethic_test.exs @@ -232,12 +232,125 @@ defmodule ArchethicTest do end describe "get_transaction_inputs/1" do - test "should request the storages nodes to fetch the inputs remotely" do + @tag :toto1 + test "should request the storages nodes to fetch the inputs remotely, this is latest tx" do + address1 = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> + P2P.add_and_connect_node(%Node{ ip: {127, 0, 0, 1}, port: 3000, - first_public_key: Crypto.last_node_public_key(), - last_public_key: Crypto.last_node_public_key(), + first_public_key: Crypto.first_node_public_key(), + last_public_key: Crypto.first_node_public_key(), + network_patch: "AAA", + geo_patch: "AAA" + }) + + P2P.add_and_connect_node(%Node{ + ip: {127, 0, 0, 1}, + port: 3000, + first_public_key: "key1", + last_public_key: "key1", + network_patch: "AAA", + geo_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() + }) + + MockClient + |> stub(:send_message, fn + _, %GetTransactionInputs{address: ^address1}, _ -> + {:ok, + %TransactionInputList{ + inputs: [ + %VersionedTransactionInput{ + input: %TransactionInput{ + from: "@Bob3", + amount: 1_000_000_000, + spent?: false, + type: :UCO, + timestamp: DateTime.utc_now() + }, + protocol_version: 1 + } + ] + }} + + _, %GetLastTransactionAddress{address: ^address1}, _ -> + {:ok, %LastTransactionAddress{address: address1, timestamp: DateTime.utc_now()}} + end) + + assert [%TransactionInput{from: "@Bob3", amount: 1_000_000_000, spent?: false, type: :UCO}] = + Archethic.get_transaction_inputs(address1) + end + + test "should request the storages nodes to fetch the inputs remotely, inputs are spent in a later tx" do + address1 = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> + address1bis = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> + + P2P.add_and_connect_node(%Node{ + ip: {127, 0, 0, 1}, + port: 3000, + first_public_key: Crypto.first_node_public_key(), + last_public_key: Crypto.first_node_public_key(), + network_patch: "AAA", + geo_patch: "AAA" + }) + + P2P.add_and_connect_node(%Node{ + ip: {127, 0, 0, 1}, + port: 3000, + first_public_key: "key1", + last_public_key: "key1", + network_patch: "AAA", + geo_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() + }) + + MockClient + |> stub(:send_message, fn + _, %GetTransactionInputs{address: ^address1}, _ -> + {:ok, + %TransactionInputList{ + inputs: [ + %VersionedTransactionInput{ + input: %TransactionInput{ + from: "@Bob3", + amount: 1_000_000_000, + spent?: false, + type: :UCO, + timestamp: DateTime.utc_now() + }, + protocol_version: 1 + } + ] + }} + + _, %GetTransactionInputs{address: ^address1bis}, _ -> + {:ok, + %TransactionInputList{ + inputs: [] + }} + + _, %GetLastTransactionAddress{address: ^address1}, _ -> + {:ok, %LastTransactionAddress{address: address1bis, timestamp: DateTime.utc_now()}} + end) + + assert [%TransactionInput{from: "@Bob3", amount: 1_000_000_000, spent?: true, type: :UCO}] = + Archethic.get_transaction_inputs(address1) + end + + test "should request the storages nodes to fetch the inputs remotely, inputs are not spent in a later tx" do + address1 = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> + address1bis = Crypto.derive_address(address1) + + P2P.add_and_connect_node(%Node{ + ip: {127, 0, 0, 1}, + port: 3000, + first_public_key: Crypto.first_node_public_key(), + last_public_key: Crypto.first_node_public_key(), network_patch: "AAA", geo_patch: "AAA" }) @@ -255,26 +368,47 @@ defmodule ArchethicTest do }) MockClient - |> expect(:send_message, fn _, %GetTransactionInputs{}, _ -> - {:ok, - %TransactionInputList{ - inputs: [ - %VersionedTransactionInput{ - input: %TransactionInput{ - from: "@Bob3", - amount: 1_000_000_000, - spent?: false, - type: :UCO, - timestamp: DateTime.utc_now() - }, - protocol_version: 1 - } - ] - }} + |> stub(:send_message, fn + _, %GetTransactionInputs{address: ^address1}, _ -> + {:ok, + %TransactionInputList{ + inputs: [ + %VersionedTransactionInput{ + input: %TransactionInput{ + from: "@Bob3", + amount: 1_000_000_000, + spent?: false, + type: :UCO, + timestamp: DateTime.utc_now() + }, + protocol_version: 1 + } + ] + }} + + _, %GetTransactionInputs{address: ^address1bis}, _ -> + {:ok, + %TransactionInputList{ + inputs: [ + %VersionedTransactionInput{ + input: %TransactionInput{ + from: "@Bob3", + amount: 1_000_000_000, + spent?: false, + type: :UCO, + timestamp: DateTime.utc_now() + }, + protocol_version: 1 + } + ] + }} + + _, %GetLastTransactionAddress{address: ^address1}, _ -> + {:ok, %LastTransactionAddress{address: address1bis, timestamp: DateTime.utc_now()}} end) assert [%TransactionInput{from: "@Bob3", amount: 1_000_000_000, spent?: false, type: :UCO}] = - Archethic.get_transaction_inputs("@Alice2") + Archethic.get_transaction_inputs(address1) end end