From 08082c6861ae8ba6df3f63a39315383e3c5e619a Mon Sep 17 00:00:00 2001 From: apoorv-2204 Date: Thu, 6 Apr 2023 16:01:03 +0530 Subject: [PATCH 1/3] infinite_looping_in_forward_transaction_#931 --- lib/archethic.ex | 55 ++++++---- lib/archethic/p2p.ex | 28 ++--- lib/archethic/p2p/geo_patch.ex | 3 +- test/archethic/reward/scheduler_test.exs | 2 + test/archethic_test.exs | 133 +++++++++++++++++++++-- 5 files changed, 175 insertions(+), 46 deletions(-) diff --git a/lib/archethic.ex b/lib/archethic.ex index 01befa55d..144c4560e 100644 --- a/lib/archethic.ex +++ b/lib/archethic.ex @@ -3,21 +3,15 @@ defmodule Archethic do Provides high level functions serving the API and the Explorer """ - alias Archethic.SharedSecrets alias __MODULE__.{Account, BeaconChain, Crypto, Election, P2P, P2P.Node, P2P.Message} - alias __MODULE__.{SelfRepair, TransactionChain} - alias __MODULE__.Contracts.Interpreter - alias __MODULE__.Contracts.Contract + alias __MODULE__.{SelfRepair, TransactionChain, SharedSecrets} + alias __MODULE__.Contracts.{Interpreter, Contract} alias Message.{NewTransaction, NotFound, StartMining} alias Message.{Balance, GetBalance, GetTransactionSummary} alias Message.{StartMining, Ok, TransactionSummaryMessage} - alias TransactionChain.{ - Transaction, - TransactionInput, - TransactionSummary - } + alias TransactionChain.{Transaction, TransactionInput, TransactionSummary} require Logger @@ -80,25 +74,46 @@ defmodule Archethic do end end - defp forward_transaction( - tx, - welcome_node_key, - nodes \\ P2P.authorized_and_available_nodes() - |> Enum.filter(&Node.locally_available?/1) - |> P2P.nearest_nodes() - ) + @spec forward_transaction(tx :: Transaction.t(), welcome_node_key :: Crypto.key()) :: + :ok | {:error, :network_issue} + defp forward_transaction(tx, welcome_node_key) do + %Node{network_patch: welcome_node_patch} = P2P.get_node_info!(welcome_node_key) + + nodes = + P2P.authorized_and_available_nodes() + |> Enum.reject(&(&1.first_public_key == welcome_node_key)) + |> Enum.sort_by(& &1.first_public_key) + |> P2P.nearest_nodes(welcome_node_patch) + |> Enum.filter(&Node.locally_available?/1) + + this_node = Crypto.first_node_public_key() + + nodes = + if this_node != welcome_node_key do + # if this node is not the welcome node then select + # next node from the this node position in nodes list + index = Enum.find_index(nodes, &(&1.first_public_key == this_node)) + {_l, r} = Enum.split(nodes, index + 1) + r + else + nodes + end + + %NewTransaction{transaction: tx, welcome_node: welcome_node_key} + |> do_forward_transaction(nodes) + end - defp forward_transaction(tx, welcome_node_key, [node | rest]) do - case P2P.send_message(node, %NewTransaction{transaction: tx, welcome_node: welcome_node_key}) do + defp do_forward_transaction(msg, [node | rest]) do + case P2P.send_message(node, msg) do {:ok, %Ok{}} -> :ok {:error, _} -> - forward_transaction(tx, welcome_node_key, rest) + do_forward_transaction(msg, rest) end end - defp forward_transaction(_, _, []), do: {:error, :network_issue} + defp do_forward_transaction(_, []), do: {:error, :network_issue} defp do_send_transaction(tx = %Transaction{type: tx_type}, welcome_node_key) do current_date = DateTime.utc_now() diff --git a/lib/archethic/p2p.ex b/lib/archethic/p2p.ex index 92490507b..d156d462f 100644 --- a/lib/archethic/p2p.ex +++ b/lib/archethic/p2p.ex @@ -319,6 +319,20 @@ defmodule Archethic.P2P do defdelegate do_send_message(node, message, timeout), to: Client, as: :send_message + @doc """ + Return the nearest storages nodes from the local node + """ + @spec nearest_nodes(list(Node.t())) :: list(Node.t()) + def nearest_nodes(storage_nodes) when is_list(storage_nodes) do + case get_node_info(Crypto.first_node_public_key()) do + {:ok, %Node{network_patch: network_patch}} -> + nearest_nodes(storage_nodes, network_patch) + + {:error, :not_found} -> + storage_nodes + end + end + @doc """ Get the nearest nodes from a specified node and a list of nodes to compare with @@ -369,20 +383,6 @@ defmodule Archethic.P2P do int end - @doc """ - Return the nearest storages nodes from the local node - """ - @spec nearest_nodes(list(Node.t())) :: list(Node.t()) - def nearest_nodes(storage_nodes) when is_list(storage_nodes) do - case get_node_info(Crypto.first_node_public_key()) do - {:ok, %Node{network_patch: network_patch}} -> - nearest_nodes(storage_nodes, network_patch) - - {:error, :not_found} -> - storage_nodes - end - end - @doc """ Return a list of nodes information from a list of public keys """ diff --git a/lib/archethic/p2p/geo_patch.ex b/lib/archethic/p2p/geo_patch.ex index f3a5cca20..202aa3093 100755 --- a/lib/archethic/p2p/geo_patch.ex +++ b/lib/archethic/p2p/geo_patch.ex @@ -9,9 +9,10 @@ defmodule Archethic.P2P.GeoPatch do @doc """ Get a patch from an IP address + Null island, patch for local host. """ @spec from_ip(:inet.ip_address()) :: binary() - def from_ip({127, 0, 0, 1}), do: compute_random_patch() + def from_ip({127, 0, 0, 1}), do: "000" def from_ip(ip) when is_tuple(ip) do case GeoIP.get_coordinates(ip) do diff --git a/test/archethic/reward/scheduler_test.exs b/test/archethic/reward/scheduler_test.exs index 40c229fc6..a46f584f4 100644 --- a/test/archethic/reward/scheduler_test.exs +++ b/test/archethic/reward/scheduler_test.exs @@ -165,6 +165,8 @@ defmodule Archethic.Reward.SchedulerTest do end test "should wait for :node_down message to stop the scheduler" do + :persistent_term.put(:archethic_up, nil) + P2P.add_and_connect_node(%Node{ ip: {127, 0, 0, 1}, port: 3002, diff --git a/test/archethic_test.exs b/test/archethic_test.exs index 16ca42d64..4aee142e3 100644 --- a/test/archethic_test.exs +++ b/test/archethic_test.exs @@ -9,20 +9,22 @@ defmodule ArchethicTest do alias Message.{NotFound, StartMining, TransactionChainLength, TransactionInputList} alias Message.{NewTransaction} - alias TransactionChain.{Transaction, TransactionData} + alias TransactionChain.{Transaction, TransactionData, Transaction.ValidationStamp} alias TransactionChain.{TransactionInput, VersionedTransactionInput} import ArchethicCase, only: [setup_before_send_tx: 0] import Mox + setup :set_mox_global setup do setup_before_send_tx() + :ok end - describe "should validate NSS Chain before sending a tx" do - test "When NOT authorized & available should forward the tx " do + describe "should follow sequence of checks when sending Transaction" do + test "should forward Transaction, Current Node Unauthorized and Available" do P2P.add_and_connect_node(%Node{ ip: {127, 0, 0, 1}, port: 3000, @@ -36,7 +38,7 @@ defmodule ArchethicTest do }) P2P.add_and_connect_node(%Node{ - ip: {127, 0, 0, 1}, + ip: {122, 12, 0, 5}, port: 3000, first_public_key: "node2", last_public_key: "node2", @@ -47,18 +49,106 @@ defmodule ArchethicTest do authorization_date: DateTime.utc_now() |> DateTime.add(-1) }) + P2P.add_and_connect_node(%Node{ + ip: {122, 12, 0, 5}, + port: 3000, + first_public_key: "node3", + last_public_key: "node3", + network_patch: "AAA", + geo_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() |> DateTime.add(-1) + }) + + tx = Transaction.new(:transfer, %TransactionData{}, "seed", 0) + MockClient |> expect(:send_message, 1, fn _, %NewTransaction{}, _ -> {:ok, %Ok{}} end) - tx = Transaction.new(:transfer, %TransactionData{}, "seed", 0) + assert :ok = Archethic.send_new_transaction(tx) + Process.sleep(10) + end + + test "should send StartMining Message, Current Node Synchronized and Available" do + nss_genesis_address = "nss_genesis_address" + nss_last_address = "nss_last_address" + :persistent_term.put(:node_shared_secrets_gen_addr, nss_genesis_address) + + 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", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() |> DateTime.add(-20_000) + }) + + P2P.add_and_connect_node(%Node{ + ip: {122, 12, 0, 5}, + port: 3000, + first_public_key: "node2", + last_public_key: "node2", + network_patch: "AAA", + geo_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() |> DateTime.add(-1) + }) + + P2P.add_and_connect_node(%Node{ + ip: {122, 12, 0, 5}, + port: 3000, + first_public_key: "node3", + last_public_key: "node3", + network_patch: "AAA", + geo_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() |> DateTime.add(-1) + }) + start_supervised!({SummaryTimer, Application.get_env(:archethic, SummaryTimer)}) + + now = DateTime.utc_now() + + MockDB + |> stub(:get_last_chain_address, fn ^nss_genesis_address -> + {nss_last_address, now} + end) + |> stub( + :get_transaction, + fn + ^nss_last_address, [validation_stamp: [:timestamp]], :chain -> + {:ok, + %Transaction{ + validation_stamp: %ValidationStamp{ + timestamp: SharedSecrets.get_last_scheduling_date(now) + } + }} + + _, _, _ -> + {:error, :transaction_not_exists} + end + ) + + MockClient + |> expect(:send_message, 3, fn + _, %StartMining{}, _ -> + {:ok, %Ok{}} + end) + + tx = Transaction.new(:transfer, %TransactionData{}, "seed", 0) assert :ok = Archethic.send_new_transaction(tx) end - test "When NOT synced should forward the tx and start repair " do + test "should forward Transaction & Start Repair, Current Node Not Synchronized" do nss_genesis_address = "nss_genesis_address" nss_last_address = "nss_last_address" :persistent_term.put(:node_shared_secrets_gen_addr, nss_genesis_address) @@ -75,6 +165,30 @@ defmodule ArchethicTest do authorization_date: DateTime.utc_now() |> DateTime.add(-20_000) }) + P2P.add_and_connect_node(%Node{ + ip: {122, 12, 0, 5}, + port: 3000, + first_public_key: "node2", + last_public_key: "node2", + network_patch: "AAA", + geo_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() |> DateTime.add(-1) + }) + + P2P.add_and_connect_node(%Node{ + ip: {122, 12, 0, 5}, + port: 3000, + first_public_key: "node3", + last_public_key: "node3", + network_patch: "AAA", + geo_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() |> DateTime.add(-1) + }) + start_supervised!({SummaryTimer, Application.get_env(:archethic, SummaryTimer)}) MockDB @@ -87,10 +201,7 @@ defmodule ArchethicTest do ^nss_last_address, [validation_stamp: [:timestamp]], :chain -> {:ok, %Transaction{ - validation_stamp: %{ - __struct__: :ValidationStamp, - # fail mathematical check with irregular timestamp - # causes validate_scheduling_time() to fail + validation_stamp: %ValidationStamp{ timestamp: DateTime.utc_now() |> DateTime.add(-86_400) } }} @@ -101,7 +212,7 @@ defmodule ArchethicTest do ) MockClient - |> expect(:send_message, 4, fn + |> stub(:send_message, fn # validate nss chain from network # anticippated to be failed _, %GetLastTransactionAddress{}, _ -> From 79e51ccdcd15e693a107a5881176b78d0f79c5eb Mon Sep 17 00:00:00 2001 From: apoorv-2204 Date: Thu, 6 Apr 2023 19:59:11 +0530 Subject: [PATCH 2/3] infinite_looping_in_forward_transaction_#931 --- config/test.exs | 2 + lib/archethic.ex | 56 +++-- lib/archethic/p2p.ex | 28 +-- lib/archethic/p2p/message/new_transaction.ex | 9 +- .../controllers/api/origin_key_controller.ex | 19 +- .../controllers/api/transaction_controller.ex | 25 +- .../controllers/faucet_controller.ex | 23 +- test/archethic_test.exs | 228 +++++++++++++++++- .../controllers/faucet_controller_test.exs | 1 + test/support/conn_case.ex | 3 - 10 files changed, 302 insertions(+), 92 deletions(-) diff --git a/config/test.exs b/config/test.exs index 66a0f6fb6..d157a2f8a 100755 --- a/config/test.exs +++ b/config/test.exs @@ -3,6 +3,8 @@ import Config # Print only errors during test config :logger, level: :error +config :archethic, Archethic.TaskSupervisor, enabled: true + config :archethic, :mut_dir, "data_test" config :archethic, Archethic.Account.MemTablesLoader, enabled: false diff --git a/lib/archethic.ex b/lib/archethic.ex index 01befa55d..9409df989 100644 --- a/lib/archethic.ex +++ b/lib/archethic.ex @@ -3,11 +3,9 @@ defmodule Archethic do Provides high level functions serving the API and the Explorer """ - alias Archethic.SharedSecrets alias __MODULE__.{Account, BeaconChain, Crypto, Election, P2P, P2P.Node, P2P.Message} - alias __MODULE__.{SelfRepair, TransactionChain} - alias __MODULE__.Contracts.Interpreter - alias __MODULE__.Contracts.Contract + alias __MODULE__.{SelfRepair, TransactionChain, SharedSecrets, TaskSupervisor} + alias __MODULE__.Contracts.{Interpreter, Contract} alias Message.{NewTransaction, NotFound, StartMining} alias Message.{Balance, GetBalance, GetTransactionSummary} @@ -57,7 +55,7 @@ defmodule Archethic do @doc """ Send a new transaction in the network to be mined. The current node will act as welcome node """ - @spec send_new_transaction(Transaction.t()) :: :ok | {:error, :network_issue} + @spec send_new_transaction(Transaction.t()) :: :ok def send_new_transaction( tx = %Transaction{}, welcome_node_key \\ Crypto.first_node_public_key() @@ -80,25 +78,51 @@ defmodule Archethic do end end - defp forward_transaction( - tx, - welcome_node_key, - nodes \\ P2P.authorized_and_available_nodes() - |> Enum.filter(&Node.locally_available?/1) - |> P2P.nearest_nodes() - ) + @spec forward_transaction(tx :: Transaction.t(), welcome_node_key :: Crypto.key()) :: :ok + defp forward_transaction(tx, welcome_node_key) do + %Node{network_patch: welcome_node_patch} = P2P.get_node_info!(welcome_node_key) + + nodes = + P2P.authorized_and_available_nodes() + |> Enum.reject(&(&1.first_public_key == welcome_node_key)) + |> Enum.sort_by(& &1.first_public_key) + |> P2P.nearest_nodes(welcome_node_patch) + |> Enum.filter(&Node.locally_available?/1) + + nodes = + if Crypto.first_node_public_key() != welcome_node_key do + # if this node is not the welcome node then select + # next node from the this node position in nodes list + current_node = Crypto.first_node_public_key() + + index = Enum.find_index(nodes, &(&1.first_public_key == current_node)) + {_l, r} = Enum.split(nodes, index + 1) + r + else + nodes + end + + TaskSupervisor + |> Task.Supervisor.start_child(fn -> + :ok = + %NewTransaction{transaction: tx, welcome_node: welcome_node_key} + |> do_forward_transaction(nodes) + end) + + :ok + end - defp forward_transaction(tx, welcome_node_key, [node | rest]) do - case P2P.send_message(node, %NewTransaction{transaction: tx, welcome_node: welcome_node_key}) do + defp do_forward_transaction(msg, [node | rest]) do + case P2P.send_message(node, msg) do {:ok, %Ok{}} -> :ok {:error, _} -> - forward_transaction(tx, welcome_node_key, rest) + do_forward_transaction(msg, rest) end end - defp forward_transaction(_, _, []), do: {:error, :network_issue} + defp do_forward_transaction(_, []), do: {:error, :network_issue} defp do_send_transaction(tx = %Transaction{type: tx_type}, welcome_node_key) do current_date = DateTime.utc_now() diff --git a/lib/archethic/p2p.ex b/lib/archethic/p2p.ex index 92490507b..d156d462f 100644 --- a/lib/archethic/p2p.ex +++ b/lib/archethic/p2p.ex @@ -319,6 +319,20 @@ defmodule Archethic.P2P do defdelegate do_send_message(node, message, timeout), to: Client, as: :send_message + @doc """ + Return the nearest storages nodes from the local node + """ + @spec nearest_nodes(list(Node.t())) :: list(Node.t()) + def nearest_nodes(storage_nodes) when is_list(storage_nodes) do + case get_node_info(Crypto.first_node_public_key()) do + {:ok, %Node{network_patch: network_patch}} -> + nearest_nodes(storage_nodes, network_patch) + + {:error, :not_found} -> + storage_nodes + end + end + @doc """ Get the nearest nodes from a specified node and a list of nodes to compare with @@ -369,20 +383,6 @@ defmodule Archethic.P2P do int end - @doc """ - Return the nearest storages nodes from the local node - """ - @spec nearest_nodes(list(Node.t())) :: list(Node.t()) - def nearest_nodes(storage_nodes) when is_list(storage_nodes) do - case get_node_info(Crypto.first_node_public_key()) do - {:ok, %Node{network_patch: network_patch}} -> - nearest_nodes(storage_nodes, network_patch) - - {:error, :not_found} -> - storage_nodes - end - end - @doc """ Return a list of nodes information from a list of public keys """ diff --git a/lib/archethic/p2p/message/new_transaction.ex b/lib/archethic/p2p/message/new_transaction.ex index 158e53df0..2b92d8a89 100644 --- a/lib/archethic/p2p/message/new_transaction.ex +++ b/lib/archethic/p2p/message/new_transaction.ex @@ -17,13 +17,8 @@ defmodule Archethic.P2P.Message.NewTransaction do @spec process(__MODULE__.t(), Crypto.key()) :: Ok.t() | Error.t() def process(%__MODULE__{transaction: tx, welcome_node: node_pbkey}, _) do - case Archethic.send_new_transaction(tx, node_pbkey) do - :ok -> - %Ok{} - - {:error, :network_issue} -> - %Error{reason: :network_issue} - end + :ok = Archethic.send_new_transaction(tx, node_pbkey) + %Ok{} end @spec serialize(t()) :: bitstring() diff --git a/lib/archethic_web/controllers/api/origin_key_controller.ex b/lib/archethic_web/controllers/api/origin_key_controller.ex index 9b5f2de08..975b235f1 100644 --- a/lib/archethic_web/controllers/api/origin_key_controller.ex +++ b/lib/archethic_web/controllers/api/origin_key_controller.ex @@ -71,18 +71,13 @@ defmodule ArchethicWeb.API.OriginKeyController do end defp send_transaction(tx = %Transaction{}) do - case Archethic.send_new_transaction(tx) do - :ok -> - TransactionSubscriber.register(tx.address, System.monotonic_time()) + :ok = Archethic.send_new_transaction(tx) + TransactionSubscriber.register(tx.address, System.monotonic_time()) - {201, - %{ - transaction_address: Base.encode16(tx.address), - status: "pending" - }} - - {:error, :network_issue} -> - {422, %{status: "error - may be invalid transaction"}} - end + {201, + %{ + transaction_address: Base.encode16(tx.address), + status: "pending" + }} end end diff --git a/lib/archethic_web/controllers/api/transaction_controller.ex b/lib/archethic_web/controllers/api/transaction_controller.ex index 693b1d2de..ac4f552ff 100644 --- a/lib/archethic_web/controllers/api/transaction_controller.ex +++ b/lib/archethic_web/controllers/api/transaction_controller.ex @@ -54,22 +54,15 @@ defmodule ArchethicWeb.API.TransactionController do end defp send_transaction(conn, tx = %Transaction{}) do - case Archethic.send_new_transaction(tx) do - :ok -> - TransactionSubscriber.register(tx.address, System.monotonic_time()) - - conn - |> put_status(201) - |> json(%{ - transaction_address: Base.encode16(tx.address), - status: "pending" - }) - - {:error, :network_issue} -> - conn - |> put_status(422) - |> json(%{status: "error - transaction may be invalid"}) - end + :ok = Archethic.send_new_transaction(tx) + TransactionSubscriber.register(tx.address, System.monotonic_time()) + + conn + |> put_status(201) + |> json(%{ + transaction_address: Base.encode16(tx.address), + status: "pending" + }) end def last_transaction_content(conn, params = %{"address" => address}) do diff --git a/lib/archethic_web/controllers/faucet_controller.ex b/lib/archethic_web/controllers/faucet_controller.ex index 4fa3d16fc..9011c8d07 100644 --- a/lib/archethic_web/controllers/faucet_controller.ex +++ b/lib/archethic_web/controllers/faucet_controller.ex @@ -119,19 +119,16 @@ defmodule ArchethicWeb.FaucetController do tx_address = tx.address TransactionSubscriber.register(tx_address, System.monotonic_time()) - case Archethic.send_new_transaction(tx) do - :ok -> - receive do - {:new_transaction, ^tx_address} -> - FaucetRateLimiter.register(recipient_address, System.monotonic_time()) - {:ok, tx_address} - after - 5000 -> - {:error, :network_issue} - end - - {:error, _} = e -> - e + # case Archethic.send_new_transaction(tx) do + :ok = Archethic.send_new_transaction(tx) + + receive do + {:new_transaction, ^tx_address} -> + FaucetRateLimiter.register(recipient_address, System.monotonic_time()) + {:ok, tx_address} + after + 5000 -> + {:error, :network_issue} end end end diff --git a/test/archethic_test.exs b/test/archethic_test.exs index 16ca42d64..47f22bf73 100644 --- a/test/archethic_test.exs +++ b/test/archethic_test.exs @@ -9,20 +9,22 @@ defmodule ArchethicTest do alias Message.{NotFound, StartMining, TransactionChainLength, TransactionInputList} alias Message.{NewTransaction} - alias TransactionChain.{Transaction, TransactionData} + alias TransactionChain.{Transaction, TransactionData, Transaction.ValidationStamp} alias TransactionChain.{TransactionInput, VersionedTransactionInput} import ArchethicCase, only: [setup_before_send_tx: 0] import Mox + setup :set_mox_global setup do setup_before_send_tx() + :ok end - describe "should validate NSS Chain before sending a tx" do - test "When NOT authorized & available should forward the tx " do + describe "should follow sequence of checks when sending Transaction" do + test "should forward Transaction, Current Node Unauthorized and Available" do P2P.add_and_connect_node(%Node{ ip: {127, 0, 0, 1}, port: 3000, @@ -36,7 +38,7 @@ defmodule ArchethicTest do }) P2P.add_and_connect_node(%Node{ - ip: {127, 0, 0, 1}, + ip: {122, 12, 0, 5}, port: 3000, first_public_key: "node2", last_public_key: "node2", @@ -47,18 +49,106 @@ defmodule ArchethicTest do authorization_date: DateTime.utc_now() |> DateTime.add(-1) }) + P2P.add_and_connect_node(%Node{ + ip: {122, 12, 0, 5}, + port: 3000, + first_public_key: "node3", + last_public_key: "node3", + network_patch: "AAA", + geo_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() |> DateTime.add(-1) + }) + + tx = Transaction.new(:transfer, %TransactionData{}, "seed", 0) + MockClient |> expect(:send_message, 1, fn _, %NewTransaction{}, _ -> {:ok, %Ok{}} end) - tx = Transaction.new(:transfer, %TransactionData{}, "seed", 0) + assert :ok = Archethic.send_new_transaction(tx) + Process.sleep(10) + end + + test "should send StartMining Message, Current Node Synchronized and Available" do + nss_genesis_address = "nss_genesis_address" + nss_last_address = "nss_last_address" + :persistent_term.put(:node_shared_secrets_gen_addr, nss_genesis_address) + + 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", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() |> DateTime.add(-20_000) + }) + + P2P.add_and_connect_node(%Node{ + ip: {122, 12, 0, 5}, + port: 3000, + first_public_key: "node2", + last_public_key: "node2", + network_patch: "AAA", + geo_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() |> DateTime.add(-1) + }) + P2P.add_and_connect_node(%Node{ + ip: {122, 12, 0, 5}, + port: 3000, + first_public_key: "node3", + last_public_key: "node3", + network_patch: "AAA", + geo_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() |> DateTime.add(-1) + }) + + start_supervised!({SummaryTimer, Application.get_env(:archethic, SummaryTimer)}) + + now = DateTime.utc_now() + + MockDB + |> stub(:get_last_chain_address, fn ^nss_genesis_address -> + {nss_last_address, now} + end) + |> stub( + :get_transaction, + fn + ^nss_last_address, [validation_stamp: [:timestamp]], :chain -> + {:ok, + %Transaction{ + validation_stamp: %ValidationStamp{ + timestamp: SharedSecrets.get_last_scheduling_date(now) + } + }} + + _, _, _ -> + {:error, :transaction_not_exists} + end + ) + + MockClient + |> expect(:send_message, 3, fn + _, %StartMining{}, _ -> + {:ok, %Ok{}} + end) + + tx = Transaction.new(:transfer, %TransactionData{}, "seed", 0) assert :ok = Archethic.send_new_transaction(tx) end - test "When NOT synced should forward the tx and start repair " do + test "should forward Transaction & Start Repair, Current Node Not Synchronized" do nss_genesis_address = "nss_genesis_address" nss_last_address = "nss_last_address" :persistent_term.put(:node_shared_secrets_gen_addr, nss_genesis_address) @@ -75,6 +165,30 @@ defmodule ArchethicTest do authorization_date: DateTime.utc_now() |> DateTime.add(-20_000) }) + P2P.add_and_connect_node(%Node{ + ip: {122, 12, 0, 5}, + port: 3000, + first_public_key: "node2", + last_public_key: "node2", + network_patch: "AAA", + geo_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() |> DateTime.add(-1) + }) + + P2P.add_and_connect_node(%Node{ + ip: {122, 12, 0, 5}, + port: 3000, + first_public_key: "node3", + last_public_key: "node3", + network_patch: "AAA", + geo_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() |> DateTime.add(-1) + }) + start_supervised!({SummaryTimer, Application.get_env(:archethic, SummaryTimer)}) MockDB @@ -87,10 +201,7 @@ defmodule ArchethicTest do ^nss_last_address, [validation_stamp: [:timestamp]], :chain -> {:ok, %Transaction{ - validation_stamp: %{ - __struct__: :ValidationStamp, - # fail mathematical check with irregular timestamp - # causes validate_scheduling_time() to fail + validation_stamp: %ValidationStamp{ timestamp: DateTime.utc_now() |> DateTime.add(-86_400) } }} @@ -101,7 +212,7 @@ defmodule ArchethicTest do ) MockClient - |> expect(:send_message, 4, fn + |> stub(:send_message, fn # validate nss chain from network # anticippated to be failed _, %GetLastTransactionAddress{}, _ -> @@ -130,6 +241,101 @@ defmodule ArchethicTest do Process.sleep(150) assert pid != nil end + + test "Should forward Transaction until StartMining message is sent without Node Loop & Message Waiting" do + nss_genesis_address = "nss_genesis_address" + nss_last_address = "nss_last_address" + :persistent_term.put(:node_shared_secrets_gen_addr, nss_genesis_address) + + welcome_node = Crypto.first_node_public_key() + second_node_first_public_key = "node2" + + 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", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() |> DateTime.add(-20_000) + }) + + P2P.add_and_connect_node(%Node{ + ip: {122, 12, 0, 5}, + port: 3000, + first_public_key: second_node_first_public_key, + last_public_key: second_node_first_public_key, + network_patch: "AAA", + geo_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() |> DateTime.add(-1) + }) + + start_supervised!({SummaryTimer, Application.get_env(:archethic, SummaryTimer)}) + + MockDB + |> stub(:get_last_chain_address, fn ^nss_genesis_address -> + {nss_last_address, DateTime.utc_now()} + end) + |> stub( + :get_transaction, + fn + ^nss_last_address, [validation_stamp: [:timestamp]], :chain -> + {:ok, + %Transaction{ + validation_stamp: %ValidationStamp{ + timestamp: DateTime.utc_now() |> DateTime.add(-86_400) + } + }} + + _, _, _ -> + {:error, :transaction_not_exists} + end + ) + + me = self() + + tx = Transaction.new(:transfer, %TransactionData{}, "seed", 0) + + MockClient + |> expect(:send_message, 5, fn + # validate nss chain from network , anticippated to be failed + _, %GetLastTransactionAddress{}, _ -> + {:ok, %LastTransactionAddress{address: "willnotmatchaddress"}} + + %Node{first_public_key: ^second_node_first_public_key}, + %NewTransaction{transaction: ^tx, welcome_node: ^welcome_node}, + _ -> + send(me, {:forwarded_to_node2, tx}) + {:ok, %Ok{}} + + _, %GetTransaction{address: _}, _ -> + {:ok, %NotFound{}} + + _, %StartMining{transaction: ^tx, welcome_node_public_key: ^welcome_node}, _ -> + # we are not handling CONSENSUS DFW order in prelimeinay checks for send transaction + # we rely completely on DFW to handle it. + {:ok, %Ok{}} + end) + + # last address is d/f it returns last address from quorum + assert {:error, "willnotmatchaddress"} = SharedSecrets.verify_synchronization() + + # trying to ssend a tx when NSS chain not synced + assert :ok = Archethic.send_new_transaction(tx) + + # start repair and should bottleneck requests + pid = SelfRepair.repair_in_progress?(nss_genesis_address) + Process.sleep(100) + assert pid != nil + + assert_receive({:forwarded_to_node2, ^tx}) + Process.sleep(100) + assert :ok = Archethic.send_new_transaction(tx) + end end describe "search_transaction/1" do diff --git a/test/archethic_web/controllers/faucet_controller_test.exs b/test/archethic_web/controllers/faucet_controller_test.exs index 745b006d8..9adc3f7a3 100644 --- a/test/archethic_web/controllers/faucet_controller_test.exs +++ b/test/archethic_web/controllers/faucet_controller_test.exs @@ -32,6 +32,7 @@ defmodule ArchethicWeb.FaucetControllerTest do }) setup_before_send_tx() + start_supervised(FaucetRateLimiter) :ok end diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index f1bf435ab..1aa970ce4 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -19,8 +19,6 @@ defmodule ArchethicWeb.ConnCase do alias Phoenix.ConnTest - alias ArchethicWeb.FaucetRateLimiter - using do quote do # Import conveniences for testing with connections @@ -38,7 +36,6 @@ defmodule ArchethicWeb.ConnCase do # mark the node as bootstraped :persistent_term.put(:archethic_up, :up) - start_supervised!(FaucetRateLimiter) {:ok, conn: ConnTest.build_conn()} end end From 1cf2796959ef3d5b82d2b53514bcc97fe97f86a7 Mon Sep 17 00:00:00 2001 From: apoorv-2204 Date: Thu, 6 Apr 2023 20:08:12 +0530 Subject: [PATCH 3/3] remove multiple calls --- lib/archethic.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/archethic.ex b/lib/archethic.ex index d2469562f..2b05b0c10 100644 --- a/lib/archethic.ex +++ b/lib/archethic.ex @@ -85,13 +85,13 @@ defmodule Archethic do |> P2P.nearest_nodes(welcome_node_patch) |> Enum.filter(&Node.locally_available?/1) + this_node = Crypto.first_node_public_key() + nodes = - if Crypto.first_node_public_key() != welcome_node_key do + if this_node != welcome_node_key do # if this node is not the welcome node then select # next node from the this node position in nodes list - current_node = Crypto.first_node_public_key() - - index = Enum.find_index(nodes, &(&1.first_public_key == current_node)) + index = Enum.find_index(nodes, &(&1.first_public_key == this_node)) {_l, r} = Enum.split(nodes, index + 1) r else