diff --git a/lib/archethic/db.ex b/lib/archethic/db.ex index 599bb4bc8..856e4f762 100644 --- a/lib/archethic/db.ex +++ b/lib/archethic/db.ex @@ -31,8 +31,8 @@ defmodule Archethic.DB do @callback list_addresses_by_type(Transaction.transaction_type()) :: Enumerable.t() | list(binary()) - @callback get_last_chain_address(binary()) :: binary() - @callback get_last_chain_address(binary(), DateTime.t()) :: binary() + @callback get_last_chain_address(binary()) :: {binary(), DateTime.t()} + @callback get_last_chain_address(binary(), DateTime.t()) :: {binary(), DateTime.t()} @callback get_first_chain_address(binary()) :: binary() @callback get_first_public_key(Crypto.key()) :: binary() diff --git a/lib/archethic/db/embedded_impl.ex b/lib/archethic/db/embedded_impl.ex index 71f1adb18..3e5ab47e9 100644 --- a/lib/archethic/db/embedded_impl.ex +++ b/lib/archethic/db/embedded_impl.ex @@ -191,9 +191,10 @@ defmodule Archethic.DB.EmbeddedImpl do end @doc """ - Return the last address from the given transaction's address until the given date + Return the last address from the given transaction's address until the given date along with its timestamp """ - @spec get_last_chain_address(address :: binary(), until :: DateTime.t()) :: binary() + @spec get_last_chain_address(address :: binary(), until :: DateTime.t()) :: + {address :: binary(), last_address_timestamp :: DateTime.t()} def get_last_chain_address(address, date = %DateTime{} \\ DateTime.utc_now()) when is_binary(address) do ChainIndex.get_last_chain_address(address, date, db_path()) @@ -248,6 +249,7 @@ defmodule Archethic.DB.EmbeddedImpl do def list_last_transaction_addresses do ChainIndex.list_genesis_addresses() |> Stream.map(&get_last_chain_address/1) + |> Stream.map(fn {address, _time} -> address end) end @doc """ diff --git a/lib/archethic/db/embedded_impl/chain_index.ex b/lib/archethic/db/embedded_impl/chain_index.ex index 34dd0e324..f729cd6ae 100644 --- a/lib/archethic/db/embedded_impl/chain_index.ex +++ b/lib/archethic/db/embedded_impl/chain_index.ex @@ -32,6 +32,7 @@ defmodule Archethic.DB.EmbeddedImpl.ChainIndex do scan_summary_table(subset_summary_filename) end) + fill_last_addresses(db_path) fill_type_stats(db_path) end @@ -56,9 +57,6 @@ defmodule Archethic.DB.EmbeddedImpl.ChainIndex do current_address = <> genesis_address = <> - # Register last addresses of genesis address - true = :ets.insert(:archethic_db_last_index, {genesis_address, current_address}) - true = :ets.insert( :archethic_db_tx_index, @@ -83,6 +81,19 @@ defmodule Archethic.DB.EmbeddedImpl.ChainIndex do end end + defp fill_last_addresses(db_path) do + Enum.each(list_genesis_addresses(), fn genesis_address -> + # Register last addresses of genesis address + {last_address, timestamp} = get_last_chain_address(genesis_address, db_path) + + true = + :ets.insert( + :archethic_db_last_index, + {genesis_address, last_address, DateTime.to_unix(timestamp, :millisecond)} + ) + end) + end + defp fill_type_stats(db_path) do Enum.each(Transaction.types(), fn type -> case File.open(type_path(db_path, type), [:read, :binary]) do @@ -325,14 +336,15 @@ defmodule Archethic.DB.EmbeddedImpl.ChainIndex do filename = chain_addresses_path(db_path, genesis_address) :ok = File.write!(filename, encoded_data, [:binary, :append]) - true = :ets.insert(:archethic_db_last_index, {genesis_address, new_address}) + true = :ets.insert(:archethic_db_last_index, {genesis_address, new_address, unix_time}) :ok end @doc """ Return the last address of the chain """ - @spec get_last_chain_address(address :: binary(), db_path :: String.t()) :: binary() + @spec get_last_chain_address(address :: binary(), db_path :: String.t()) :: + {binary(), DateTime.t()} def get_last_chain_address(address, db_path) do # We try with a transaction on a chain, to identity the genesis address case get_tx_entry(address, db_path) do @@ -343,20 +355,25 @@ defmodule Archethic.DB.EmbeddedImpl.ChainIndex do # If not present, the we search in the index file unix_time = DateTime.utc_now() |> DateTime.to_unix(:millisecond) - search_last_address_until(genesis_address, unix_time, db_path) || address + search_last_address_until(genesis_address, unix_time, db_path) || + {address, DateTime.utc_now()} - [{_, last_address}] -> - last_address + [{_, last_address, last_time}] -> + {last_address, DateTime.from_unix!(last_time, :millisecond)} end {:error, :not_exists} -> # We try if the request address is the genesis address to fetch the in memory index case :ets.lookup(:archethic_db_last_index, address) do [] -> - address + # If not present, the we search in the index file + unix_time = DateTime.utc_now() |> DateTime.to_unix(:millisecond) - [{_, last_address}] -> - last_address + search_last_address_until(address, unix_time, db_path) || + {address, DateTime.utc_now()} + + [{_, last_address, last_time}] -> + {last_address, DateTime.from_unix!(last_time, :millisecond)} end end end @@ -365,28 +382,31 @@ defmodule Archethic.DB.EmbeddedImpl.ChainIndex do Return the last address of the chain before or equal to the given date """ @spec get_last_chain_address(address :: binary(), until :: DateTime.t(), db_path :: String.t()) :: - binary() + {last_addresss :: binary(), last_time :: DateTime.t()} def get_last_chain_address(address, datetime = %DateTime{}, db_path) do unix_time = DateTime.to_unix(datetime, :millisecond) # We get the genesis address of this given transaction address case get_tx_entry(address, db_path) do {:ok, %{genesis_address: genesis_address}} -> - search_last_address_until(genesis_address, unix_time, db_path) || address + search_last_address_until(genesis_address, unix_time, db_path) || {address, datetime} {:error, :not_exists} -> # We try to search with given address as genesis address # Then `address` acts the genesis address - search_last_address_until(address, unix_time, db_path) || address + search_last_address_until(address, unix_time, db_path) || {address, datetime} end end defp search_last_address_until(genesis_address, until, db_path) do filepath = chain_addresses_path(db_path, genesis_address) - case File.open(filepath, [:binary, :read]) do - {:ok, fd} -> - do_search_last_address_until(fd, until) + with {:ok, fd} <- File.open(filepath, [:binary, :read]), + {address, timestamp} <- do_search_last_address_until(fd, until) do + {address, DateTime.from_unix!(timestamp, :millisecond)} + else + nil -> + nil {:error, _} -> nil @@ -400,21 +420,17 @@ defmodule Archethic.DB.EmbeddedImpl.ChainIndex do {:ok, hash} <- :file.read(fd, hash_size) do address = <> - if timestamp < until do - do_search_last_address_until(fd, until, address) - else - cond do - timestamp == until -> - :file.close(fd) - address + cond do + timestamp < until -> + do_search_last_address_until(fd, until, {address, timestamp}) - timestamp < until -> - do_search_last_address_until(fd, until, address) + timestamp == until -> + :file.close(fd) + {address, timestamp} - true -> - :file.close(fd) - acc - end + true -> + :file.close(fd) + acc end else :eof -> diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index e55156b7c..320d8dbc7 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -321,19 +321,19 @@ defmodule Archethic.Mining.PendingTransactionValidation do type: :mint_rewards, data: %TransactionData{content: content} }) do + total_fee = DB.get_latest_burned_fees() + with :ok <- verify_token_creation(content), - {:ok, %{"supply" => supply}} <- Jason.decode(content), - true <- supply == DB.get_latest_burned_fees(), + {:ok, %{"supply" => ^total_fee}} <- Jason.decode(content), network_pool_address <- SharedSecrets.get_network_pool_address(), - false <- - DB.get_last_chain_address(network_pool_address, Reward.last_scheduling_date()) != - network_pool_address do + {^network_pool_address, _} <- + DB.get_last_chain_address(network_pool_address, Reward.last_scheduling_date()) do :ok else - false -> + {:ok, %{"supply" => _}} -> {:error, "The supply do not match burned fees from last summary"} - true -> + {_, _} -> {:error, "There is already a mint rewards transaction since last schedule"} e -> diff --git a/lib/archethic/oracle_chain/scheduler.ex b/lib/archethic/oracle_chain/scheduler.ex index 143af328e..ac2b5834a 100644 --- a/lib/archethic/oracle_chain/scheduler.ex +++ b/lib/archethic/oracle_chain/scheduler.ex @@ -573,10 +573,9 @@ defmodule Archethic.OracleChain.Scheduler do end defp chain_size(summary_date = %DateTime{}) do - summary_date - |> Crypto.derive_oracle_address(0) - |> TransactionChain.get_last_address() - |> TransactionChain.size() + oracle_genesis_address = Crypto.derive_oracle_address(summary_date, 0) + {last_address, _} = TransactionChain.get_last_address(oracle_genesis_address) + TransactionChain.size(last_address) end defp get_oracle_data(address) do diff --git a/lib/archethic/p2p/message.ex b/lib/archethic/p2p/message.ex index 49d3f5dd6..b07341d9d 100644 --- a/lib/archethic/p2p/message.ex +++ b/lib/archethic/p2p/message.ex @@ -459,8 +459,8 @@ defmodule Archethic.P2P.Message do <<240::8, Summary.serialize(summary)::bitstring>> end - def encode(%LastTransactionAddress{address: address}) do - <<241::8, address::binary>> + def encode(%LastTransactionAddress{address: address, timestamp: timestamp}) do + <<241::8, address::binary, DateTime.to_unix(timestamp, :millisecond)::64>> end def encode(%FirstPublicKey{public_key: public_key}) do @@ -971,8 +971,12 @@ defmodule Archethic.P2P.Message do end def decode(<<241::8, rest::bitstring>>) do - {address, rest} = Utils.deserialize_address(rest) - {%LastTransactionAddress{address: address}, rest} + {address, <>} = Utils.deserialize_address(rest) + + {%LastTransactionAddress{ + address: address, + timestamp: DateTime.from_unix!(timestamp, :millisecond) + }, rest} end def decode(<<242::8, rest::bitstring>>) do @@ -1433,8 +1437,8 @@ defmodule Archethic.P2P.Message do end def process(%GetLastTransactionAddress{address: address, timestamp: timestamp}) do - address = TransactionChain.get_last_address(address, timestamp) - %LastTransactionAddress{address: address} + {address, time} = TransactionChain.get_last_address(address, timestamp) + %LastTransactionAddress{address: address, timestamp: time} end def process(%NotifyLastTransactionAddress{ diff --git a/lib/archethic/p2p/message/last_transaction_address.ex b/lib/archethic/p2p/message/last_transaction_address.ex index 588f75e3d..6c87271b5 100644 --- a/lib/archethic/p2p/message/last_transaction_address.ex +++ b/lib/archethic/p2p/message/last_transaction_address.ex @@ -3,11 +3,12 @@ defmodule Archethic.P2P.Message.LastTransactionAddress do Represents a message with the last address key from a transaction chain """ @enforce_keys [:address] - defstruct [:address] + defstruct [:address, :timestamp] alias Archethic.Crypto @type t :: %__MODULE__{ - address: Crypto.versioned_hash() + address: Crypto.versioned_hash(), + timestamp: DateTime.t() } end diff --git a/lib/archethic/transaction_chain.ex b/lib/archethic/transaction_chain.ex index c3003ec7f..dc9eb218b 100644 --- a/lib/archethic/transaction_chain.ex +++ b/lib/archethic/transaction_chain.ex @@ -75,17 +75,17 @@ defmodule Archethic.TransactionChain do defdelegate list_addresses_by_type(type), to: DB @doc """ - Get the last transaction address from a transaction chain + Get the last transaction address from a transaction chain with the latest time """ - @spec get_last_address(binary()) :: binary() + @spec get_last_address(binary()) :: {binary(), DateTime.t()} defdelegate get_last_address(address), to: DB, as: :get_last_chain_address @doc """ - Get the last transaction address from a transaction chain before a given date + Get the last transaction address from a transaction chain before a given date along its last time """ - @spec get_last_address(binary(), DateTime.t()) :: binary() + @spec get_last_address(binary(), DateTime.t()) :: {binary(), DateTime.t()} defdelegate get_last_address(address, timestamp), to: DB, as: :get_last_chain_address @@ -238,9 +238,8 @@ defmodule Archethic.TransactionChain do | {:error, :transaction_not_exists} | {:error, :invalid_transaction} def get_last_transaction(address, fields \\ []) when is_binary(address) and is_list(fields) do - address - |> get_last_address() - |> get_transaction(fields) + {address, _} = get_last_address(address) + get_transaction(address, fields) end @doc """ @@ -563,10 +562,14 @@ defmodule Archethic.TransactionChain do {:ok, binary()} | {:error, :network_issue} def fetch_last_address_remotely(address, nodes, timestamp = %DateTime{} \\ DateTime.utc_now()) when is_binary(address) and is_list(nodes) do - # TODO: implement conflict resolver to get the latest address + conflict_resolver = fn results -> + Enum.max_by(results, &DateTime.to_unix(&1.timestamp, :millisecond)) + end + case P2P.quorum_read( nodes, - %GetLastTransactionAddress{address: address, timestamp: timestamp} + %GetLastTransactionAddress{address: address, timestamp: timestamp}, + conflict_resolver ) do {:ok, %LastTransactionAddress{address: last_address}} -> {:ok, last_address} @@ -844,8 +847,8 @@ defmodule Archethic.TransactionChain do case fetch_genesis_address_remotely(address) do {:ok, genesis_address} -> case get_last_address(genesis_address) do - ^genesis_address -> nil - last_address -> last_address + {^genesis_address, _} -> nil + {last_address, _} -> last_address end _ -> diff --git a/lib/archethic_web/live/chains/oracle_live.ex b/lib/archethic_web/live/chains/oracle_live.ex index d51e3466b..41cdaa054 100644 --- a/lib/archethic_web/live/chains/oracle_live.ex +++ b/lib/archethic_web/live/chains/oracle_live.ex @@ -168,9 +168,10 @@ defmodule ArchethicWeb.OracleChainLive do end defp list_transactions_by_date(date = %DateTime{}) do - date - |> Crypto.derive_oracle_address(0) - |> TransactionChain.get_last_address() + oracle_genesis_address = Crypto.derive_oracle_address(date, 0) + {last_address, _} = TransactionChain.get_last_address(oracle_genesis_address) + + last_address |> get_transaction_chain() |> Stream.map(fn %Transaction{ address: address, diff --git a/test/archethic/bootstrap/network_init_test.exs b/test/archethic/bootstrap/network_init_test.exs index b1a9bae78..f31cfdac1 100644 --- a/test/archethic/bootstrap/network_init_test.exs +++ b/test/archethic/bootstrap/network_init_test.exs @@ -80,7 +80,7 @@ defmodule Archethic.Bootstrap.NetworkInitTest do MockClient |> stub(:send_message, fn _, %GetLastTransactionAddress{address: address}, _ -> - {:ok, %LastTransactionAddress{address: address}} + {:ok, %LastTransactionAddress{address: address, timestamp: DateTime.utc_now()}} end) MockDB @@ -306,7 +306,7 @@ defmodule Archethic.Bootstrap.NetworkInitTest do {:ok, %TransactionInputList{inputs: []}} _, %GetLastTransactionAddress{address: address}, _ -> - {:ok, %LastTransactionAddress{address: address}} + {:ok, %LastTransactionAddress{address: address, timestamp: DateTime.utc_now()}} _, %GetTransactionChainLength{}, _ -> %TransactionChainLength{length: 1} @@ -354,7 +354,7 @@ defmodule Archethic.Bootstrap.NetworkInitTest do {:ok, %TransactionInputList{inputs: []}} _, %GetLastTransactionAddress{address: address}, _ -> - {:ok, %LastTransactionAddress{address: address}} + {:ok, %LastTransactionAddress{address: address, timestamp: DateTime.utc_now()}} _, %GetTransactionChainLength{}, _ -> %TransactionChainLength{length: 1} @@ -401,7 +401,7 @@ defmodule Archethic.Bootstrap.NetworkInitTest do {:ok, %TransactionInputList{inputs: []}} _, %GetLastTransactionAddress{address: address}, _ -> - {:ok, %LastTransactionAddress{address: address}} + {:ok, %LastTransactionAddress{address: address, timestamp: DateTime.utc_now()}} _, %GetTransactionChainLength{}, _ -> %TransactionChainLength{length: 1} diff --git a/test/archethic/bootstrap/sync_test.exs b/test/archethic/bootstrap/sync_test.exs index 832e24784..4d4ed2e9a 100644 --- a/test/archethic/bootstrap/sync_test.exs +++ b/test/archethic/bootstrap/sync_test.exs @@ -51,7 +51,7 @@ defmodule Archethic.Bootstrap.SyncTest do MockClient |> stub(:send_message, fn _, %GetLastTransactionAddress{address: address}, _ -> - {:ok, %LastTransactionAddress{address: address}} + {:ok, %LastTransactionAddress{address: address, timestamp: DateTime.utc_now()}} _, %GetTransaction{}, _ -> {:ok, %NotFound{}} diff --git a/test/archethic/bootstrap_test.exs b/test/archethic/bootstrap_test.exs index 9af40aeeb..6eff8e8c9 100644 --- a/test/archethic/bootstrap_test.exs +++ b/test/archethic/bootstrap_test.exs @@ -104,7 +104,7 @@ defmodule Archethic.BootstrapTest do MockClient |> stub(:send_message, fn _, %GetLastTransactionAddress{address: address}, _ -> - {:ok, %LastTransactionAddress{address: address}} + {:ok, %LastTransactionAddress{address: address, timestamp: DateTime.utc_now()}} _, %GetTransactionInputs{}, _ -> {:ok, %TransactionInputList{inputs: []}} diff --git a/test/archethic/db/embedded_impl/chain_index_test.exs b/test/archethic/db/embedded_impl/chain_index_test.exs index e09ead066..d5b8ec314 100644 --- a/test/archethic/db/embedded_impl/chain_index_test.exs +++ b/test/archethic/db/embedded_impl/chain_index_test.exs @@ -36,7 +36,7 @@ defmodule Archethic.DB.EmbeddedImpl.ChainIndexTest do assert 1 == ChainIndex.count_transactions_by_type(:oracle) end - test "should load transactions tables and bloom filters", %{db_path: db_path} do + test "should load transactions tables", %{db_path: db_path} do {:ok, pid} = ChainIndex.start_link(path: db_path) tx_address = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> genesis_address = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> @@ -53,10 +53,9 @@ defmodule Archethic.DB.EmbeddedImpl.ChainIndexTest do assert {100, 1} = ChainIndex.get_file_stats(genesis_address) - :ets.tab2list(:archethic_db_last_index) - assert ^tx_address = ChainIndex.get_last_chain_address(genesis_address, db_path) + assert {^tx_address, _} = ChainIndex.get_last_chain_address(genesis_address, db_path) - # Remove the transaction from the cache and try the bloom filter + # Remove the transaction from the cache and try to fetch from the file instead :ets.delete(:archethic_db_tx_index, tx_address) assert true == ChainIndex.transaction_exists?(tx_address, db_path) assert false == ChainIndex.transaction_exists?(:crypto.strong_rand_bytes(32), db_path) diff --git a/test/archethic/db/embedded_impl_test.exs b/test/archethic/db/embedded_impl_test.exs index 249404269..01d6a95e6 100644 --- a/test/archethic/db/embedded_impl_test.exs +++ b/test/archethic/db/embedded_impl_test.exs @@ -335,17 +335,30 @@ defmodule Archethic.DB.EmbeddedTest do EmbeddedImpl.write_transaction_chain([tx1, tx2, tx3]) - assert tx3.address == EmbeddedImpl.get_last_chain_address(tx1.address) - assert tx3.address == EmbeddedImpl.get_last_chain_address(tx2.address) - assert tx3.address == EmbeddedImpl.get_last_chain_address(tx3.address) - assert tx3.address == EmbeddedImpl.get_last_chain_address(Transaction.previous_address(tx1)) + assert {tx3.address, ~U[2020-04-10 10:13:00.000Z]} == + EmbeddedImpl.get_last_chain_address(tx1.address) + + assert {tx3.address, ~U[2020-04-10 10:13:00.000Z]} == + EmbeddedImpl.get_last_chain_address(tx2.address) + + assert {tx3.address, ~U[2020-04-10 10:13:00.000Z]} == + EmbeddedImpl.get_last_chain_address(tx3.address) + + assert {tx3.address, ~U[2020-04-10 10:13:00.000Z]} == + EmbeddedImpl.get_last_chain_address(Transaction.previous_address(tx1)) end end describe "get_last_chain_address/2" do test "should return the same given address if not previous chain" do + now = DateTime.utc_now() address = :crypto.strong_rand_bytes(32) - assert ^address = EmbeddedImpl.get_last_chain_address(address) + assert {^address, last_time} = EmbeddedImpl.get_last_chain_address(address) + + assert DateTime.compare( + now |> DateTime.truncate(:millisecond), + last_time |> DateTime.truncate(:millisecond) + ) == :eq end test "should get the last address of chain for the exact time of last address timestamp" do @@ -369,16 +382,16 @@ defmodule Archethic.DB.EmbeddedTest do EmbeddedImpl.write_transaction_chain([tx1, tx2, tx3]) - assert tx3.address == + assert {tx3.address, ~U[2020-04-10 10:13:00.000Z]} == EmbeddedImpl.get_last_chain_address(tx3.address, tx3.validation_stamp.timestamp) - assert tx2.address == + assert {tx2.address, ~U[2020-04-02 10:13:00.000Z]} == EmbeddedImpl.get_last_chain_address(tx2.address, tx2.validation_stamp.timestamp) - assert tx1.address == + assert {tx1.address, ~U[2020-03-30 10:13:00.000Z]} == EmbeddedImpl.get_last_chain_address(tx1.address, tx1.validation_stamp.timestamp) - assert tx1.address == + assert {tx1.address, ~U[2020-03-30 10:13:00.000Z]} == EmbeddedImpl.get_last_chain_address( Transaction.previous_address(tx1), tx1.validation_stamp.timestamp @@ -406,22 +419,22 @@ defmodule Archethic.DB.EmbeddedTest do EmbeddedImpl.write_transaction_chain([tx1, tx2, tx3]) - assert tx3.address == + assert {tx3.address, ~U[2020-04-10 10:13:00.000Z]} == EmbeddedImpl.get_last_chain_address(tx2.address, tx3.validation_stamp.timestamp) - assert tx3.address == + assert {tx3.address, ~U[2020-04-10 10:13:00.000Z]} == EmbeddedImpl.get_last_chain_address(tx1.address, tx3.validation_stamp.timestamp) - assert tx2.address == + assert {tx2.address, ~U[2020-04-02 10:13:00.000Z]} == EmbeddedImpl.get_last_chain_address( tx1.address, DateTime.add(tx2.validation_stamp.timestamp, 100) ) - assert tx2.address == + assert {tx2.address, ~U[2020-04-02 10:13:00.000Z]} == EmbeddedImpl.get_last_chain_address(tx1.address, tx2.validation_stamp.timestamp) - assert tx1.address == + assert {tx1.address, ~U[2020-03-30 10:13:00.000Z]} == EmbeddedImpl.get_last_chain_address( tx1.address, DateTime.add(tx1.validation_stamp.timestamp, 100) @@ -673,9 +686,10 @@ defmodule Archethic.DB.EmbeddedTest do genesis_address = Transaction.previous_address(tx1) EmbeddedImpl.write_transaction(tx1) - EmbeddedImpl.add_last_transaction_address(genesis_address, tx2.address, DateTime.utc_now()) + now = DateTime.utc_now() |> DateTime.truncate(:millisecond) + EmbeddedImpl.add_last_transaction_address(genesis_address, tx2.address, now) - assert tx2.address == EmbeddedImpl.get_last_chain_address(tx1.address) + assert {tx2.address, now} == EmbeddedImpl.get_last_chain_address(tx1.address) end end diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index 82317330b..140a6bf7e 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -363,7 +363,7 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do MockDB |> stub(:get_latest_burned_fees, fn -> 300_000_000 end) - |> stub(:get_last_chain_address, fn _, _ -> address end) + |> stub(:get_last_chain_address, fn _, _ -> {address, DateTime.utc_now()} end) tx = Transaction.new( @@ -428,7 +428,7 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do MockDB |> stub(:get_latest_burned_fees, fn -> 300_000_000 end) - |> stub(:get_last_chain_address, fn _, _ -> address end) + |> stub(:get_last_chain_address, fn _, _ -> {address, DateTime.utc_now()} end) tx = Transaction.new( diff --git a/test/archethic/p2p/messages_test.exs b/test/archethic/p2p/messages_test.exs index d6b4d3d0d..5620d3b4b 100644 --- a/test/archethic/p2p/messages_test.exs +++ b/test/archethic/p2p/messages_test.exs @@ -829,7 +829,8 @@ defmodule Archethic.P2P.MessageTest do test "LastTransactionAddress message" do msg = %LastTransactionAddress{ - address: <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> + address: <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>, + timestamp: DateTime.utc_now() |> DateTime.truncate(:millisecond) } assert msg == diff --git a/test/archethic/replication_test.exs b/test/archethic/replication_test.exs index f73487662..29c9841ff 100644 --- a/test/archethic/replication_test.exs +++ b/test/archethic/replication_test.exs @@ -215,7 +215,7 @@ defmodule Archethic.ReplicationTest do |> stub(:add_last_transaction_address, fn _address, _last_address, _ -> :ok end) - |> expect(:get_last_chain_address, fn _ -> "@Alice2" end) + |> expect(:get_last_chain_address, fn _ -> {"@Alice2", DateTime.utc_now()} end) assert :ok = Replication.acknowledge_previous_storage_nodes( @@ -224,7 +224,7 @@ defmodule Archethic.ReplicationTest do DateTime.utc_now() ) - assert "@Alice2" == TransactionChain.get_last_address("@Alice1") + assert {"@Alice2", _} = TransactionChain.get_last_address("@Alice1") end test "should notify previous storage pool if transaction exists" do @@ -232,7 +232,7 @@ defmodule Archethic.ReplicationTest do |> stub(:add_last_transaction_address, fn _address, _last_address, _ -> :ok end) - |> expect(:get_last_chain_address, fn _ -> "@Alice2" end) + |> expect(:get_last_chain_address, fn _ -> {"@Alice2", DateTime.utc_now()} end) |> stub(:get_transaction, fn _, _ -> {:ok, %Transaction{previous_public_key: "Alice1"}} end) @@ -264,7 +264,7 @@ defmodule Archethic.ReplicationTest do DateTime.utc_now() ) - assert "@Alice2" == TransactionChain.get_last_address("@Alice1") + assert {"@Alice2", _} = TransactionChain.get_last_address("@Alice1") assert_receive :notification_sent, 500 end diff --git a/test/archethic/transaction_chain_test.exs b/test/archethic/transaction_chain_test.exs index 38ba64683..ab37d82b0 100644 --- a/test/archethic/transaction_chain_test.exs +++ b/test/archethic/transaction_chain_test.exs @@ -29,33 +29,81 @@ defmodule Archethic.TransactionChainTest do import Mox - test "resolve_last_address/1 should retrieve the last address for a chain" do - MockClient - |> stub(:send_message, fn - _, %GetLastTransactionAddress{timestamp: ~U[2021-03-25 15:11:29Z]}, _ -> - {:ok, %LastTransactionAddress{address: "@Alice1"}} - - _, %GetLastTransactionAddress{timestamp: ~U[2021-03-25 15:12:29Z]}, _ -> - {:ok, %LastTransactionAddress{address: "@Alice2"}} - end) - - 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(), - available?: true, - geo_patch: "AAA", - network_patch: "AAA", - authorized?: true, - authorization_date: DateTime.utc_now() - }) - - assert {:ok, "@Alice1"} = - TransactionChain.resolve_last_address("@Alice1", ~U[2021-03-25 15:11:29Z]) - - assert {:ok, "@Alice2"} = - TransactionChain.resolve_last_address("@Alice1", ~U[2021-03-25 15:12:29Z]) + describe "resolve_last_address/1 should retrieve the last address for a chain" do + test "when not conflicts" do + MockClient + |> stub(:send_message, fn + _, %GetLastTransactionAddress{timestamp: ~U[2021-03-25 15:11:29Z]}, _ -> + {:ok, %LastTransactionAddress{address: "@Alice1", timestamp: DateTime.utc_now()}} + + _, %GetLastTransactionAddress{timestamp: ~U[2021-03-25 15:12:29Z]}, _ -> + {:ok, + %LastTransactionAddress{ + address: "@Alice2", + timestamp: DateTime.utc_now() |> DateTime.add(2) + }} + end) + + 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(), + available?: true, + geo_patch: "AAA", + network_patch: "AAA", + authorized?: true, + authorization_date: DateTime.utc_now() + }) + + assert {:ok, "@Alice1"} = + TransactionChain.resolve_last_address("@Alice1", ~U[2021-03-25 15:11:29Z]) + + assert {:ok, "@Alice2"} = + TransactionChain.resolve_last_address("@Alice1", ~U[2021-03-25 15:12:29Z]) + end + + test "with conflicts" do + MockClient + |> stub(:send_message, fn + %Node{port: 3000}, %GetLastTransactionAddress{}, _ -> + {:ok, %LastTransactionAddress{address: "@Alice1", timestamp: DateTime.utc_now()}} + + %Node{port: 3001}, %GetLastTransactionAddress{}, _ -> + {:ok, + %LastTransactionAddress{ + address: "@Alice2", + timestamp: DateTime.utc_now() |> DateTime.add(2) + }} + end) + + 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(), + available?: true, + geo_patch: "AAA", + network_patch: "AAA", + authorized?: true, + authorization_date: DateTime.utc_now() + }) + + P2P.add_and_connect_node(%Node{ + ip: {127, 0, 0, 1}, + port: 3001, + first_public_key: :crypto.strong_rand_bytes(34), + last_public_key: :crypto.strong_rand_bytes(34), + available?: true, + geo_patch: "AAA", + network_patch: "AAA", + authorized?: true, + authorization_date: DateTime.utc_now() + }) + + assert {:ok, "@Alice2"} = + TransactionChain.resolve_last_address("@Alice1", DateTime.utc_now()) + end end describe "fetch_transaction_remotely/2" do diff --git a/test/archethic_test.exs b/test/archethic_test.exs index ba4cd4f5e..40f6e0671 100644 --- a/test/archethic_test.exs +++ b/test/archethic_test.exs @@ -150,7 +150,7 @@ defmodule ArchethicTest do MockClient |> stub(:send_message, fn _, %GetLastTransactionAddress{address: address}, _ -> - {:ok, %LastTransactionAddress{address: address}} + {:ok, %LastTransactionAddress{address: address, timestamp: DateTime.utc_now()}} _, %GetTransaction{}, _ -> {:ok, %Transaction{previous_public_key: "Alice1"}} @@ -187,7 +187,7 @@ defmodule ArchethicTest do MockClient |> stub(:send_message, fn _, %GetLastTransactionAddress{address: address}, _ -> - {:ok, %LastTransactionAddress{address: address}} + {:ok, %LastTransactionAddress{address: address, timestamp: DateTime.utc_now()}} _, %GetTransaction{}, _ -> {:ok, %NotFound{}} @@ -349,7 +349,7 @@ defmodule ArchethicTest do MockDB |> stub(:get_last_chain_address, fn _address -> - "@Alice5" + {"@Alice5", DateTime.utc_now()} end) MockDB diff --git a/test/support/template.ex b/test/support/template.ex index 1a09ac94e..a91cfd3c0 100644 --- a/test/support/template.ex +++ b/test/support/template.ex @@ -40,8 +40,8 @@ defmodule ArchethicCase do |> stub(:get_transaction_chain, fn _, _, _ -> {[], false, nil} end) |> stub(:list_last_transaction_addresses, fn -> [] end) |> stub(:add_last_transaction_address, fn _, _, _ -> :ok end) - |> stub(:get_last_chain_address, fn addr -> addr end) - |> stub(:get_last_chain_address, fn addr, _ -> addr end) + |> stub(:get_last_chain_address, fn addr -> {addr, DateTime.utc_now()} end) + |> stub(:get_last_chain_address, fn addr, _ -> {addr, DateTime.utc_now()} end) |> stub(:get_first_public_key, fn pub -> pub end) |> stub(:get_first_chain_address, fn addr -> addr end) |> stub(:chain_size, fn _ -> 0 end)