diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index e3499233f..756e64054 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -224,15 +224,16 @@ defmodule Archethic.Mining.PendingTransactionValidation do ) do last_scheduling_date = Reward.get_last_scheduling_date(validation_time) - network_pool_address = SharedSecrets.get_network_pool_address() + genesis_address = DB.list_addresses_by_type(:mint_rewards) |> Stream.take(1) |> Enum.at(0) + {last_network_pool_address, _} = DB.get_last_chain_address(genesis_address) previous_address = Transaction.previous_address(tx) time_validation = with {:ok, %Transaction{type: :node_rewards}} <- TransactionChain.get_transaction(previous_address, [:type]), - {^network_pool_address, _} <- - DB.get_last_chain_address(network_pool_address, last_scheduling_date) do + {^last_network_pool_address, _} <- + DB.get_last_chain_address(genesis_address, last_scheduling_date) do :ok else {:ok, %Transaction{type: :mint_rewards}} -> @@ -505,12 +506,13 @@ defmodule Archethic.Mining.PendingTransactionValidation do _ ) do total_fee = DB.get_latest_burned_fees() + genesis_address = DB.list_addresses_by_type(:mint_rewards) |> Stream.take(1) |> Enum.at(0) + {last_address, _} = DB.get_last_chain_address(genesis_address) with :ok <- verify_token_creation(content), {:ok, %{"supply" => ^total_fee}} <- Jason.decode(content), - network_pool_address <- SharedSecrets.get_network_pool_address(), - {^network_pool_address, _} <- - DB.get_last_chain_address(network_pool_address, Reward.last_scheduling_date()) do + {^last_address, _} <- + DB.get_last_chain_address(genesis_address, Reward.last_scheduling_date()) do :ok else {:ok, %{"supply" => _}} -> diff --git a/lib/archethic/shared_secrets/node_renewal.ex b/lib/archethic/shared_secrets/node_renewal.ex index 224071ad7..ce75dbbb7 100644 --- a/lib/archethic/shared_secrets/node_renewal.ex +++ b/lib/archethic/shared_secrets/node_renewal.ex @@ -28,19 +28,18 @@ defmodule Archethic.SharedSecrets.NodeRenewal do @doc """ Determine if the local node is the initiator of the node renewal """ - @spec initiator? :: boolean() - def initiator?(index \\ 0) do + @spec initiator?(binary) :: boolean() + def initiator?(address, index \\ 0) do %Node{first_public_key: initiator_key} = - next_address() + address |> Election.storage_nodes(P2P.authorized_and_available_nodes()) |> Enum.at(index) initiator_key == Crypto.first_node_public_key() end - @spec next_address() :: binary() - def next_address do - key_index = Crypto.number_of_node_shared_secrets_keys() + @spec next_address(non_neg_integer()) :: binary() + def next_address(key_index) do next_public_key = Crypto.node_shared_secrets_public_key(key_index + 1) Crypto.derive_address(next_public_key) end diff --git a/lib/archethic/shared_secrets/node_renewal_scheduler.ex b/lib/archethic/shared_secrets/node_renewal_scheduler.ex index 04dc22583..70f4abafc 100644 --- a/lib/archethic/shared_secrets/node_renewal_scheduler.ex +++ b/lib/archethic/shared_secrets/node_renewal_scheduler.ex @@ -67,7 +67,7 @@ defmodule Archethic.SharedSecrets.NodeRenewalScheduler do end end - def handle_event(:internal, :schedule, _state, data = %{interval: interval}) do + def handle_event(:internal, :schedule, _state, data = %{interval: interval, index: index}) do timer = case Map.get(data, :timer) do nil -> @@ -85,7 +85,7 @@ defmodule Archethic.SharedSecrets.NodeRenewalScheduler do new_data = data |> Map.put(:timer, timer) - |> Map.put(:next_address, NodeRenewal.next_address()) + |> Map.put(:next_address, NodeRenewal.next_address(index)) {:next_state, :scheduled, new_data} end @@ -176,14 +176,14 @@ defmodule Archethic.SharedSecrets.NodeRenewalScheduler do index ) - if NodeRenewal.initiator?() do + if NodeRenewal.initiator?(tx.address) do Logger.info("Node shared secrets renewal creation...") make_renewal(tx) {:keep_state, data} else {:ok, pid} = DetectNodeResponsiveness.start_link(tx.address, fn count -> - if NodeRenewal.initiator?(count) do + if NodeRenewal.initiator?(tx.address, count) do Logger.info("Node shared secret renewal creation...attempt #{count}") make_renewal(tx) end diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index 2bbea70db..11ed19a6e 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -203,6 +203,344 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do assert :ok = PendingTransactionValidation.validate(tx) :persistent_term.put(:node_shared_secrets_gen_addr, nil) end + + test "should return :ok when a origin transaction is made" do + P2P.add_and_connect_node(%Node{ + ip: {127, 0, 0, 1}, + port: 3000, + http_port: 4000, + first_public_key: "node_key1", + last_public_key: "node_key1", + available?: true + }) + + P2P.add_and_connect_node(%Node{ + ip: {127, 0, 0, 1}, + port: 3000, + http_port: 4000, + first_public_key: "node_key2", + last_public_key: "node_key2", + available?: true + }) + + {public_key, _} = Crypto.derive_keypair("random", 0) + certificate = Crypto.get_key_certificate(public_key) + certificate_size = byte_size(certificate) + + tx = + Transaction.new( + :origin, + %TransactionData{ + code: """ + condition inherit: [ + type: origin, + content: true + ] + """, + content: <> + } + ) + + :persistent_term.put(:origin_gen_addr, [Transaction.previous_address(tx)]) + assert :ok = PendingTransactionValidation.validate(tx) + :persistent_term.put(:origin_gen_addr, nil) + end + + test "should return :ok when a code approval transaction contains a proposal target and the sender is member of the technical council and not previously signed" do + tx = + Transaction.new( + :code_approval, + %TransactionData{ + recipients: ["@CodeProposal1"] + }, + "approval_seed", + 0 + ) + + P2P.add_and_connect_node(%Node{ + ip: {127, 0, 0, 1}, + port: 3000, + http_port: 4000, + first_public_key: "node1", + last_public_key: "node1", + geo_patch: "AAA", + network_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() + }) + + assert :ok = PoolsMemTable.put_pool_member(:technical_council, tx.previous_public_key) + + MockDB + |> expect(:get_transaction, fn _, _ -> + {:ok, + %Transaction{ + data: %TransactionData{ + content: """ + Description: My Super Description + Changes: + diff --git a/mix.exs b/mix.exs + index d9d9a06..5e34b89 100644 + --- a/mix.exs + +++ b/mix.exs + @@ -4,7 +4,7 @@ defmodule Archethic.MixProject do + def project do + [ + app: :archethic, + - version: \"0.7.1\", + + version: \"0.7.2\", + build_path: \"_build\", + config_path: \"config/config.exs\", + deps_path: \"deps\", + @@ -53,7 +53,7 @@ defmodule Archethic.MixProject do + {:git_hooks, \"~> 0.4.0\", only: [:test, :dev], runtime: false}, + {:mox, \"~> 0.5.2\", only: [:test]}, + {:stream_data, \"~> 0.4.3\", only: [:test]}, + - {:elixir_make, \"~> 0.6.0\", only: [:dev, :test], runtime: false}, + + {:elixir_make, \"~> 0.6.0\", only: [:dev, :test]}, + {:logger_file_backend, \"~> 0.0.11\", only: [:dev]} + ] + end + """ + } + }} + end) + + MockClient + |> expect(:send_message, fn _, %GetFirstPublicKey{}, _ -> + {:ok, %FirstPublicKey{public_key: tx.previous_public_key}} + end) + + assert :ok = PendingTransactionValidation.validate(tx) + end + + test "should return :ok when a transaction contains a valid smart contract code" do + tx_seed = :crypto.strong_rand_bytes(32) + + tx = + Transaction.new( + :transfer, + %TransactionData{ + code: """ + condition inherit: [ + content: "hello" + ] + + condition transaction: [ + content: "" + ] + + actions triggered_by: transaction do + set_content "hello" + end + """, + ownerships: [ + Ownership.new(tx_seed, :crypto.strong_rand_bytes(32), [ + Crypto.storage_nonce_public_key() + ]) + ] + }, + tx_seed, + 0 + ) + + assert :ok = PendingTransactionValidation.validate(tx) + end + + test "should return :ok when a transaction contains valid fields for token creation" do + tx_seed = :crypto.strong_rand_bytes(32) + + tx = + Transaction.new( + :token, + %TransactionData{ + content: + Jason.encode!(%{ + supply: 300_000_000, + name: "MyToken", + type: "non-fungible", + symbol: "MTK", + properties: [ + [ + %{name: "image", value: "link"} + ], + [ + %{name: "image", value: "link"} + ], + [ + %{name: "image", value: "link"} + ] + ] + }) + }, + tx_seed, + 0 + ) + + assert :ok = PendingTransactionValidation.validate(tx) + end + + test "should return :ok when a mint reward transaction passes all tests" do + tx_seed = :crypto.strong_rand_bytes(32) + {pub, _} = Crypto.derive_keypair(tx_seed, 1) + address = Crypto.derive_address(pub) + + NetworkLookup.set_network_pool_address(address) + + Scheduler.start_link(interval: "0 * * * * *") + + MockDB + |> stub(:get_latest_burned_fees, fn -> 300_000_000 end) + |> stub(:get_last_chain_address, fn _, _ -> {address, DateTime.utc_now()} end) + |> stub(:get_last_chain_address, fn _ -> {address, DateTime.utc_now()} end) + + tx = + Transaction.new( + :mint_rewards, + %TransactionData{ + content: + Jason.encode!(%{ + supply: 300_000_000, + name: "MyToken", + type: "fungible", + symbol: "MTK" + }) + }, + tx_seed, + 0 + ) + + :persistent_term.put(:reward_gen_addr, Transaction.previous_address(tx)) + assert :ok = PendingTransactionValidation.validate(tx) + :persistent_term.put(:reward_gen_addr, nil) + end + + test "should return :error when a mint reward transaction has != burned_fees" do + tx_seed = :crypto.strong_rand_bytes(32) + {pub, _} = Crypto.derive_keypair(tx_seed, 1) + address = Crypto.derive_address(pub) + + NetworkLookup.set_network_pool_address(address) + + Scheduler.start_link(interval: "0 * * * * *") + + MockDB + |> stub(:get_latest_burned_fees, fn -> 200_000_000 end) + |> stub(:get_last_chain_address, fn _, _ -> {address, DateTime.utc_now()} end) + |> stub(:get_last_chain_address, fn _ -> {address, DateTime.utc_now()} end) + + tx = + Transaction.new( + :mint_rewards, + %TransactionData{ + content: + Jason.encode!(%{ + supply: 300_000_000, + name: "MyToken", + type: "fungible", + symbol: "MTK" + }) + }, + tx_seed, + 0 + ) + + assert {:error, "The supply do not match burned fees from last summary"} = + PendingTransactionValidation.validate(tx) + end + + test "should return :error when there is already a mint rewards transaction since last schedule" do + tx_seed = :crypto.strong_rand_bytes(32) + {pub, _} = Crypto.derive_keypair(tx_seed, 1) + address = Crypto.derive_address(pub) + + NetworkLookup.set_network_pool_address(:crypto.strong_rand_bytes(32)) + + Scheduler.start_link(interval: "0 * * * * *") + + MockDB + |> stub(:get_latest_burned_fees, fn -> 300_000_000 end) + |> stub(:get_last_chain_address, fn _, _ -> {address, DateTime.utc_now()} end) + + tx = + Transaction.new( + :mint_rewards, + %TransactionData{ + content: + Jason.encode!(%{ + supply: 300_000_000, + name: "MyToken", + type: "fungible", + symbol: "MTK" + }) + }, + tx_seed, + 0 + ) + + assert {:error, "There is already a mint rewards transaction since last schedule"} = + PendingTransactionValidation.validate(tx) + end + + test "should return error when there is already a oracle transaction since the last schedule" do + MockDB + |> expect(:get_last_chain_address, fn _, _ -> + {"OtherAddress", DateTime.utc_now()} + end) + + tx = Transaction.new(:oracle, %TransactionData{}, "seed", 0) + + assert {:error, "Invalid oracle trigger time"} = + PendingTransactionValidation.validate(tx, ~U[2022-01-01 00:10:03Z]) + end + + test "should return error when there is already a node shared secrets transaction since the last schedule" do + MockDB + |> expect(:get_last_chain_address, fn _, _ -> + {"OtherAddress", DateTime.utc_now()} + end) + + tx = + Transaction.new( + :node_shared_secrets, + %TransactionData{ + content: :crypto.strong_rand_bytes(32), + ownerships: [ + %Ownership{ + secret: :crypto.strong_rand_bytes(32), + authorized_keys: %{"node_key" => :crypto.strong_rand_bytes(32)} + } + ] + }, + "seed", + 0 + ) + + assert {:error, "Invalid node shared secrets trigger time"} = + PendingTransactionValidation.validate(tx, ~U[2022-01-01 00:00:03Z]) + end + + test "should return error when there is already a node rewards transaction since the last schedule" do + MockDB + |> expect(:get_last_chain_address, fn _, _ -> + {"OtherAddress", DateTime.utc_now()} + end) + |> expect(:get_transaction, fn _, _ -> + {:ok, %Transaction{type: :node_rewards}} + end) + + tx = + Transaction.new( + :node_rewards, + %TransactionData{}, + "seed", + 0 + ) + + assert {:error, "Invalid node rewards trigger time"} = + PendingTransactionValidation.validate(tx, ~U[2022-01-01 00:00:03Z]) + end end test "should return :ok when a origin transaction is made" do @@ -394,6 +732,7 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do MockDB |> stub(:get_latest_burned_fees, fn -> 300_000_000 end) |> stub(:get_last_chain_address, fn _, _ -> {address, DateTime.utc_now()} end) + |> stub(:get_last_chain_address, fn _ -> {address, DateTime.utc_now()} end) tx = Transaction.new( diff --git a/test/archethic/shared_secrets/node_renewal_test.exs b/test/archethic/shared_secrets/node_renewal_test.exs index 9d4f0a123..7240dd1bd 100644 --- a/test/archethic/shared_secrets/node_renewal_test.exs +++ b/test/archethic/shared_secrets/node_renewal_test.exs @@ -39,7 +39,7 @@ defmodule Archethic.SharedSecrets.NodeRenewalTest do assert {:ok, _, _} = NodeRenewal.decode_transaction_content(content) end - describe "initiator?/0" do + describe "initiator?/2" do test "should return false when the first elected node is not the current node" do P2P.add_and_connect_node(%Node{ ip: {127, 0, 0, 1}, @@ -76,7 +76,7 @@ defmodule Archethic.SharedSecrets.NodeRenewalTest do authorization_date: DateTime.utc_now() |> DateTime.add(-86_400) }) - assert false == NodeRenewal.initiator?() + assert false == NodeRenewal.initiator?("address") end test "should return true when the first elected node is the current node" do @@ -92,7 +92,7 @@ defmodule Archethic.SharedSecrets.NodeRenewalTest do authorization_date: DateTime.utc_now() |> DateTime.add(-86_400) }) - assert true == NodeRenewal.initiator?() + assert true == NodeRenewal.initiator?("address") end end