diff --git a/lib/archethic/bootstrap.ex b/lib/archethic/bootstrap.ex index 9387392dd..1af3fe670 100644 --- a/lib/archethic/bootstrap.ex +++ b/lib/archethic/bootstrap.ex @@ -5,6 +5,8 @@ defmodule Archethic.Bootstrap do alias Archethic.Crypto + alias Archethic.P2P.GeoPatch + alias Archethic.Networking alias Archethic.P2P @@ -175,6 +177,7 @@ defmodule Archethic.Bootstrap do configured_reward_address ) do Logger.info("Bootstrapping starting") + geo_patch = GeoPatch.from_ip(ip) cond do Sync.should_initialize_network?(closest_bootstrapping_nodes) -> @@ -187,7 +190,8 @@ defmodule Archethic.Bootstrap do port, http_port, transport, - configured_reward_address + configured_reward_address, + geo_patch ) Sync.initialize_network(tx) @@ -203,7 +207,8 @@ defmodule Archethic.Bootstrap do http_port, transport, closest_bootstrapping_nodes, - configured_reward_address + configured_reward_address, + geo_patch ) true -> @@ -215,7 +220,8 @@ defmodule Archethic.Bootstrap do ) {:ok, _ip, _p2p_port, _http_port, _transport, last_reward_address, _origin_public_key, - _key_certificate, _mining_public_key} = Node.decode_transaction_content(content) + _key_certificate, _mining_public_key, + _geo_patch} = Node.decode_transaction_content(content) update_node( ip, @@ -223,7 +229,8 @@ defmodule Archethic.Bootstrap do http_port, transport, closest_bootstrapping_nodes, - last_reward_address + last_reward_address, + geo_patch ) end end @@ -265,7 +272,8 @@ defmodule Archethic.Bootstrap do http_port, transport, closest_bootstrapping_nodes, - configured_reward_address + configured_reward_address, + geo_patch ) do # In case node had lose it's DB, we ask the network if the node chain already exists {:ok, length} = @@ -286,7 +294,8 @@ defmodule Archethic.Bootstrap do TransactionChain.fetch_transaction(last_address, closest_bootstrapping_nodes) {:ok, _ip, _p2p_port, _http_port, _transport, last_reward_address, _origin_public_key, - _key_certificate, _mining_public_key} = Node.decode_transaction_content(content) + _key_certificate, _mining_public_key, + _geo_patch} = Node.decode_transaction_content(content) last_reward_address else @@ -294,7 +303,14 @@ defmodule Archethic.Bootstrap do end tx = - TransactionHandler.create_node_transaction(ip, port, http_port, transport, reward_address) + TransactionHandler.create_node_transaction( + ip, + port, + http_port, + transport, + reward_address, + geo_patch + ) {:ok, validated_tx} = TransactionHandler.send_transaction(tx, closest_bootstrapping_nodes) @@ -307,18 +323,27 @@ defmodule Archethic.Bootstrap do ) end - defp update_node(_ip, _port, _http_port, _transport, [], _reward_address) do + defp update_node(_ip, _port, _http_port, _transport, [], _reward_address, _geo_patch) do Logger.warning("Not enough nodes in the network. No node update") end - defp update_node(ip, port, http_port, transport, closest_bootstrapping_nodes, reward_address) do + defp update_node( + ip, + port, + http_port, + transport, + closest_bootstrapping_nodes, + reward_address, + geo_patch + ) do tx = TransactionHandler.create_node_transaction( ip, port, http_port, transport, - reward_address + reward_address, + geo_patch ) {:ok, validated_tx} = TransactionHandler.send_transaction(tx, closest_bootstrapping_nodes) diff --git a/lib/archethic/bootstrap/transaction_handler.ex b/lib/archethic/bootstrap/transaction_handler.ex index 18d22645d..2d4e2564e 100644 --- a/lib/archethic/bootstrap/transaction_handler.ex +++ b/lib/archethic/bootstrap/transaction_handler.ex @@ -73,13 +73,21 @@ defmodule Archethic.Bootstrap.TransactionHandler do p2p_port :: :inet.port_number(), http_port :: :inet.port_number(), transport :: P2P.supported_transport(), - reward_address :: Crypto.versioned_hash() + reward_address :: Crypto.versioned_hash(), + geo_patch :: binary() ) :: Transaction.t() - def create_node_transaction(ip = {_, _, _, _}, port, http_port, transport, reward_address) + def create_node_transaction( + ip = {_, _, _, _}, + port, + http_port, + transport, + reward_address, + geo_patch + ) when is_number(port) and port >= 0 and is_binary(reward_address) do origin_public_key = Crypto.origin_node_public_key() - origin_public_key_certificate = Crypto.get_key_certificate(origin_public_key) + origin_public_certificate = Crypto.get_key_certificate(origin_public_key) mining_public_key = Crypto.mining_node_public_key() Transaction.new(:node, %TransactionData{ @@ -94,16 +102,17 @@ defmodule Archethic.Bootstrap.TransactionHandler do ] """, content: - Node.encode_transaction_content( - ip, - port, - http_port, - transport, - reward_address, - origin_public_key, - origin_public_key_certificate, - mining_public_key - ) + Node.encode_transaction_content(%{ + ip: ip, + port: port, + http_port: http_port, + transport: transport, + reward_address: reward_address, + origin_public_key: origin_public_key, + key_certificate: origin_public_certificate, + mining_public_key: mining_public_key, + geo_patch: geo_patch + }) }) end end diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 20ae21921..f4a100575 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -17,6 +17,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do alias Archethic.OracleChain alias Archethic.P2P + alias Archethic.P2P.GeoPatch alias Archethic.P2P.Message.FirstPublicKey alias Archethic.P2P.Message.GetFirstPublicKey alias Archethic.P2P.Node @@ -317,7 +318,8 @@ defmodule Archethic.Mining.PendingTransactionValidation do }, _ ) do - with {:ok, ip, port, _http_port, _, _, origin_public_key, key_certificate, mining_public_key} <- + with {:ok, ip, port, _http_port, _, _, origin_public_key, key_certificate, mining_public_key, + geo_patch} <- Node.decode_transaction_content(content), {:auth_origin, true} <- {:auth_origin, @@ -338,7 +340,9 @@ defmodule Archethic.Mining.PendingTransactionValidation do {:mining_public_key, true} <- {:mining_public_key, Crypto.valid_public_key?(mining_public_key) and - Crypto.get_public_key_curve(mining_public_key) == :bls} do + Crypto.get_public_key_curve(mining_public_key) == :bls}, + {:geo_patch, true} <- + {:geo_patch, valid_geopatch?(ip, geo_patch)} do :ok else :error -> @@ -362,6 +366,9 @@ defmodule Archethic.Mining.PendingTransactionValidation do {:mining_public_key, false} -> {:error, "Invalid mining public key"} + + {:geo_patch, false} -> + {:error, "Invalid geo patch from IP"} end end @@ -966,6 +973,10 @@ defmodule Archethic.Mining.PendingTransactionValidation do end end + defp valid_geopatch?(ip, calculated_geopatch) do + calculated_geopatch == GeoPatch.from_ip(ip) + end + defp get_allowed_node_key_origins do :archethic |> Application.get_env(__MODULE__, []) diff --git a/lib/archethic/mining/proof_of_work.ex b/lib/archethic/mining/proof_of_work.ex index a3d39e7ec..d9ef39db2 100644 --- a/lib/archethic/mining/proof_of_work.ex +++ b/lib/archethic/mining/proof_of_work.ex @@ -139,7 +139,8 @@ defmodule Archethic.Mining.ProofOfWork do } }) do {:ok, _ip, _p2p_port, _http_port, _transport, _reward_address, origin_public_key, - _origin_certificate, _mining_public_key} = Node.decode_transaction_content(content) + _origin_certificate, _mining_public_key, + _geo_patch} = Node.decode_transaction_content(content) [origin_public_key] end diff --git a/lib/archethic/networking/scheduler.ex b/lib/archethic/networking/scheduler.ex index a9eb66974..4e18f2894 100644 --- a/lib/archethic/networking/scheduler.ex +++ b/lib/archethic/networking/scheduler.ex @@ -10,6 +10,7 @@ defmodule Archethic.Networking.Scheduler do alias Archethic.Networking.PortForwarding alias Archethic.P2P + alias(Archethic.P2P.GeoPatch) alias Archethic.P2P.Listener, as: P2PListener alias Archethic.P2P.Node @@ -103,21 +104,23 @@ defmodule Archethic.Networking.Scheduler do origin_public_key = Crypto.origin_node_public_key() mining_public_key = Crypto.mining_node_public_key() key_certificate = Crypto.get_key_certificate(origin_public_key) + new_geo_patch = GeoPatch.from_ip(ip) tx = Transaction.new(:node, %TransactionData{ code: code, content: - Node.encode_transaction_content( - ip, - p2p_port, - web_port, - transport, - reward_address, - origin_public_key, - key_certificate, - mining_public_key - ) + Node.encode_transaction_content(%{ + ip: ip, + port: p2p_port, + http_port: web_port, + transport: transport, + reward_address: reward_address, + origin_public_key: origin_public_key, + orogin_key_certificate: key_certificate, + mining_public_key: mining_public_key, + geo_patch: new_geo_patch + }) }) Archethic.send_new_transaction(tx, forward?: true) diff --git a/lib/archethic/p2p/mem_table_loader.ex b/lib/archethic/p2p/mem_table_loader.ex index 8b1e3fc20..013ad4d8f 100644 --- a/lib/archethic/p2p/mem_table_loader.ex +++ b/lib/archethic/p2p/mem_table_loader.ex @@ -105,7 +105,9 @@ defmodule Archethic.P2P.MemTableLoader do first_public_key = TransactionChain.get_first_public_key(previous_public_key) {:ok, ip, port, http_port, transport, reward_address, origin_public_key, _certificate, - mining_public_key} = Node.decode_transaction_content(content) + mining_public_key, geo_patch} = Node.decode_transaction_content(content) + + geo_patch = if geo_patch == nil, do: GeoPatch.from_ip(ip), else: geo_patch if first_node_change?(first_public_key, previous_public_key) do node = %Node{ @@ -114,7 +116,7 @@ defmodule Archethic.P2P.MemTableLoader do http_port: http_port, first_public_key: first_public_key, last_public_key: previous_public_key, - geo_patch: GeoPatch.from_ip(ip), + geo_patch: geo_patch, transport: transport, last_address: address, reward_address: reward_address, @@ -135,7 +137,7 @@ defmodule Archethic.P2P.MemTableLoader do port: port, http_port: http_port, last_public_key: previous_public_key, - geo_patch: GeoPatch.from_ip(ip), + geo_patch: geo_patch, transport: transport, last_address: address, reward_address: reward_address, diff --git a/lib/archethic/p2p/node.ex b/lib/archethic/p2p/node.ex index 45b9175e1..0a4928d9d 100755 --- a/lib/archethic/p2p/node.ex +++ b/lib/archethic/p2p/node.ex @@ -47,7 +47,8 @@ defmodule Archethic.P2P.Node do {:ok, ip_address :: :inet.ip_address(), p2p_port :: :inet.port_number(), http_port :: :inet.port_number(), P2P.supported_transport(), reward_address :: binary(), origin_public_key :: Crypto.key(), - key_certificate :: binary(), mining_public_key :: binary() | nil} + key_certificate :: binary(), mining_public_key :: binary() | nil, + geo_patch :: binary() | nil} | :error def decode_transaction_content( <> @@ -56,18 +57,11 @@ defmodule Archethic.P2P.Node do {reward_address, rest} <- Utils.deserialize_address(rest), {origin_public_key, rest} <- Utils.deserialize_public_key(rest), <> <- rest do - mining_public_key = - case rest do - "" -> - nil - - mining_public_key -> - mining_public_key |> Utils.deserialize_public_key() |> elem(0) - end - + rest::binary>> <- rest, + {mining_public_key, rest} <- extract_mining_public_key(rest), + {geo_patch, _rest} <- extract_geo_patch(rest) do {:ok, {ip0, ip1, ip2, ip3}, port, http_port, deserialize_transport(transport), - reward_address, origin_public_key, key_certificate, mining_public_key} + reward_address, origin_public_key, key_certificate, mining_public_key, geo_patch} else _ -> :error @@ -76,32 +70,47 @@ defmodule Archethic.P2P.Node do def decode_transaction_content(<<>>), do: :error + @spec extract_mining_public_key(binary()) :: {Crypto.key() | nil, binary()} + defp extract_mining_public_key(<<>>), do: {nil, <<>>} + + defp extract_mining_public_key(rest) do + {mining_public_key, remaining} = Utils.deserialize_public_key(rest) + {mining_public_key, remaining} + end + + @spec extract_geo_patch(binary()) :: {binary() | nil, binary()} + defp extract_geo_patch(<>), do: {geo_patch, rest} + + defp extract_geo_patch(rest), do: {nil, rest} + @doc """ Encode node's transaction content """ - @spec encode_transaction_content( - :inet.ip_address(), - :inet.port_number(), - :inet.port_number(), - P2P.supported_transport(), - reward_address :: binary(), - origin_public_key :: Crypto.key(), - origin_key_certificate :: binary(), - mining_public_key :: Crypto.key() - ) :: binary() - def encode_transaction_content( - {ip1, ip2, ip3, ip4}, - port, - http_port, - transport, - reward_address, - origin_public_key, - key_certificate, - mining_public_key - ) do + @spec encode_transaction_content(%{ + ip: :inet.ip_address(), + port: :inet.port_number(), + http_port: :inet.port_number(), + transport: P2P.supported_transport(), + reward_address: reward_address :: binary(), + origin_public_key: origin_public_key :: Crypto.key(), + origin_key_certificate: origin_key_certificate :: binary(), + mining_public_key: mining_public_key :: Crypto.key(), + geo_patch: geo_patch :: binary() + }) :: binary() + def encode_transaction_content(%{ + ip: {ip1, ip2, ip3, ip4}, + port: port, + http_port: http_port, + transport: transport, + reward_address: reward_address, + origin_public_key: origin_public_key, + key_certificate: key_certificate, + mining_public_key: mining_public_key, + geo_patch: geo_patch + }) do <> + key_certificate::binary, mining_public_key::binary, geo_patch::binary-size(3)>> end @type t() :: %__MODULE__{ diff --git a/lib/archethic/shared_secrets/mem_tables_loader.ex b/lib/archethic/shared_secrets/mem_tables_loader.ex index b4f7aedcc..ed7afc4b9 100644 --- a/lib/archethic/shared_secrets/mem_tables_loader.ex +++ b/lib/archethic/shared_secrets/mem_tables_loader.ex @@ -61,7 +61,7 @@ defmodule Archethic.SharedSecrets.MemTablesLoader do } }) do {:ok, _ip, _p2p_port, _http_port, _transport, _reward_address, origin_public_key, _cert, - _mining_public_key} = Node.decode_transaction_content(content) + _mining_public_key, _geo_patch} = Node.decode_transaction_content(content) <<_::8, origin_id::8, _::binary>> = origin_public_key diff --git a/lib/archethic_web/explorer/live/settings_live.ex b/lib/archethic_web/explorer/live/settings_live.ex index 6f926c090..73504c4ba 100644 --- a/lib/archethic_web/explorer/live/settings_live.ex +++ b/lib/archethic_web/explorer/live/settings_live.ex @@ -120,6 +120,7 @@ defmodule ArchethicWeb.Explorer.SettingsLive do defp send_new_transaction(next_reward_address) do %Node{ ip: ip, + geo_patch: geo_patch, port: port, http_port: http_port, transport: transport, @@ -149,16 +150,17 @@ defmodule ArchethicWeb.Explorer.SettingsLive do }, code: code, content: - Node.encode_transaction_content( - ip, - port, - http_port, - transport, - next_reward_address, - Crypto.origin_node_public_key(), - Crypto.get_key_certificate(Crypto.origin_node_public_key()), - Crypto.mining_node_public_key() - ) + Node.encode_transaction_content(%{ + ip: ip, + port: port, + http_port: http_port, + transport: transport, + reward_address: next_reward_address, + origin_public_key: Crypto.origin_node_public_key(), + key_certificate: Crypto.get_key_certificate(Crypto.origin_node_public_key()), + mining_public_key: Crypto.mining_node_public_key(), + geo_patch: geo_patch + }) }) TransactionSubscriber.register(tx.address, System.monotonic_time()) @@ -169,6 +171,7 @@ defmodule ArchethicWeb.Explorer.SettingsLive do defp send_noop_transaction() do %Node{ ip: ip, + geo_patch: geo_patch, port: port, http_port: http_port, transport: transport, @@ -184,16 +187,17 @@ defmodule ArchethicWeb.Explorer.SettingsLive do Transaction.new(:node, %TransactionData{ code: code, content: - Node.encode_transaction_content( - ip, - port, - http_port, - transport, - reward_address, - Crypto.origin_node_public_key(), - Crypto.get_key_certificate(Crypto.origin_node_public_key()), - Crypto.mining_node_public_key() - ) + Node.encode_transaction_content(%{ + ip: ip, + port: port, + http_port: http_port, + transport: transport, + reward_address: reward_address, + origin_public_key: Crypto.origin_node_public_key(), + key_certificate: Crypto.get_key_certificate(Crypto.origin_node_public_key()), + mining_public_key: Crypto.mining_node_public_key(), + geo_patch: geo_patch + }) }) TransactionSubscriber.register(tx.address, System.monotonic_time()) diff --git a/lib/archethic_web/explorer/views/explorer_view.ex b/lib/archethic_web/explorer/views/explorer_view.ex index 6d7df2fab..77f1775d9 100644 --- a/lib/archethic_web/explorer/views/explorer_view.ex +++ b/lib/archethic_web/explorer/views/explorer_view.ex @@ -52,10 +52,11 @@ defmodule ArchethicWeb.Explorer.ExplorerView do def format_transaction_content(:node, content) do {:ok, ip, port, http_port, transport, reward_address, origin_public_key, key_certificate, - mining_public_key} = Node.decode_transaction_content(content) + mining_public_key, geo_patch} = Node.decode_transaction_content(content) content = """ IP: #{:inet.ntoa(ip)} + GeoPatch: #{geo_patch} P2P Port: #{port} HTTP Port: #{http_port} Transport: #{transport} diff --git a/mix.exs b/mix.exs index d1efefcad..41c118601 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Archethic.MixProject do def project do [ app: :archethic, - version: "1.5.13", + version: "1.5.14", build_path: "_build", config_path: "config/config.exs", deps_path: "deps", diff --git a/priv/migration_tasks/prod/1.5.14@add_geopatch_node_transactiosn.ex b/priv/migration_tasks/prod/1.5.14@add_geopatch_node_transactiosn.ex new file mode 100644 index 000000000..93bf4f706 --- /dev/null +++ b/priv/migration_tasks/prod/1.5.14@add_geopatch_node_transactiosn.ex @@ -0,0 +1,159 @@ +defmodule Migration_1_5_14 do + @moduledoc """ + Migration script to add geopatch to a node's transaction. + """ + + alias Archethic.Crypto + alias Archethic.P2P + alias Archethic.P2P.Node + alias Archethic.TransactionChain + alias Archethic.TransactionChain.Transaction + alias Archethic.TransactionChain.TransactionData + alias Archethic.Utils + alias Archethic.PubSub + + require Logger + + def run() do + nodes = P2P.list_nodes() |> Enum.sort_by(& &1.first_public_key) + + execute_migration(nodes) + end + + defp execute_migration([]), do: :ok + + defp execute_migration(nodes) do + current_node_pk = Crypto.first_node_public_key() + + Enum.reduce_while(nodes, :ok, fn node, _acc -> + node_pk = node.first_public_key + + if node_pk == current_node_pk do + Logger.info("Starting migration for: #{Base.encode16(node_pk)}") + + case send_node_transaction() do + :ok -> + Logger.info("Migration complete for: #{Base.encode16(node_pk)}") + {:halt, :ok} + + {:error, reason} -> + Logger.error("Migration failed for: #{Base.encode16(node_pk)}") + {:halt, {:error, reason}} + end + else + PubSub.register_to_new_transaction_by_type(:node) + + receive do + {:new_transaction, address, :node, _timestamp} -> + with {:ok, %Transaction{previous_public_key: previous_pk} = transaction} <- + TransactionChain.get_transaction(address) do + first_pk = TransactionChain.get_first_public_key(previous_pk) + + if first_pk == node_pk do + process_transaction(transaction) + else + {:cont, :ok} + end + else + {:error, reason} -> + Logger.error( + "Failed to fetch transaction for address #{Base.encode16(address)}: #{inspect(reason)}" + ) + + {:cont, :ok} + end + after + 60_000 -> + Logger.error("Timeout waiting for updates from: #{Base.encode16(node_pk)}") + + PubSub.unregister_to_new_transaction_by_type(:node) + {:cont, :ok} + end + end + end) + end + + defp process_transaction(transaction) do + case check_geopatch_status(transaction) do + :geopatch_set -> + PubSub.unregister_to_new_transaction_by_type(:node) + {:cont, :ok} + + :waiting_for_update -> + PubSub.unregister_to_new_transaction_by_type(:node) + {:cont, :ok} + end + end + + defp check_geopatch_status(%Transaction{data: %TransactionData{content: content}}) do + with {:ok, _ip, _p2p_port, _http_port, _transport, _last_reward_address, _origin_public_key, + _key_certificate, _mining_public_key, + geo_patch} <- Node.decode_transaction_content(content) do + if geo_patch do + :geopatch_set + else + :waiting_for_update + end + else + _ -> + Logger.error("Could not find a set geopatch in the last transaction for this node.") + + :waiting_for_update + end + end + + defp send_node_transaction() do + %Node{ + ip: ip, + port: port, + http_port: http_port, + transport: transport, + reward_address: reward_address, + origin_public_key: origin_public_key + } = P2P.get_node_info() + + geopatch = Archethic.P2P.GeoPatch.from_ip(ip) + + mining_public_key = Crypto.mining_node_public_key() + key_certificate = Crypto.get_key_certificate(origin_public_key) + + genesis_address = Crypto.first_node_public_key() |> Crypto.derive_address() + + {:ok, %Transaction{data: %TransactionData{code: code}}} = + TransactionChain.get_last_transaction(genesis_address, data: [:code]) + + tx = + Transaction.new(:node, %TransactionData{ + code: code, + content: + Node.encode_transaction_content(%{ + ip, + port, + http_port, + transport, + reward_address, + origin_public_key, + key_certificate, + mining_public_key, + geopatch + }) + }) + + :ok = Archethic.send_new_transaction(tx, forward?: true) + + nodes = + P2P.authorized_and_available_nodes() + |> Enum.filter(&P2P.node_connected?/1) + |> P2P.sort_by_nearest_nodes() + + case Utils.await_confirmation(tx.address, nodes) do + {:ok, tx} -> + Logger.info("Mining node transaction successful") + :ok + + {:error, reason} -> + Logger.error("Cannot update node transaction - #{inspect(reason)}") + {:error, reason} + end + end +end diff --git a/test/archethic/bootstrap/sync_test.exs b/test/archethic/bootstrap/sync_test.exs index 94dea3858..9731e3f3b 100644 --- a/test/archethic/bootstrap/sync_test.exs +++ b/test/archethic/bootstrap/sync_test.exs @@ -308,16 +308,17 @@ defmodule Archethic.Bootstrap.SyncTest do node_tx = Transaction.new(:node, %TransactionData{ content: - Node.encode_transaction_content( - {127, 0, 0, 1}, - 3000, - 4000, - :tcp, - ArchethicCase.random_public_key(), - ArchethicCase.random_public_key(), - :crypto.strong_rand_bytes(64), - Crypto.generate_random_keypair(:bls) |> elem(0) - ) + Node.encode_transaction_content(%{ + ip: {127, 0, 0, 1}, + port: 3000, + http_port: 4000, + transport: :tcp, + reward_address: ArchethicCase.random_public_key(), + origin_public_key: ArchethicCase.random_public_key(), + key_certificate: :crypto.strong_rand_bytes(64), + mining_public_key: Crypto.generate_random_keypair(:bls) |> elem(0), + geo_patch: "000" + }) }) :ok = Sync.initialize_network(node_tx) diff --git a/test/archethic/bootstrap/transaction_handler_test.exs b/test/archethic/bootstrap/transaction_handler_test.exs index f0053f2c1..a82cad19c 100644 --- a/test/archethic/bootstrap/transaction_handler_test.exs +++ b/test/archethic/bootstrap/transaction_handler_test.exs @@ -18,7 +18,7 @@ defmodule Archethic.Bootstrap.TransactionHandlerTest do import Mox - test "create_node_transaction/4 should create transaction with ip and port encoded in the content" do + test "create_node_transaction/4 should create transaction with ip, geopatch and port encoded in the content" do assert %Transaction{ data: %TransactionData{ content: content @@ -29,11 +29,12 @@ defmodule Archethic.Bootstrap.TransactionHandlerTest do 3000, 4000, :tcp, - <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> + <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>, + "000" ) assert {:ok, {127, 0, 0, 1}, 3000, 4000, :tcp, _reward_address, _origin_public_key, _cert, - mining_public_key} = Node.decode_transaction_content(content) + mining_public_key, "000"} = Node.decode_transaction_content(content) assert Archethic.Crypto.mining_node_public_key() == mining_public_key end @@ -59,7 +60,8 @@ defmodule Archethic.Bootstrap.TransactionHandlerTest do 3000, 4000, :tcp, - "00610F69B6C5C3449659C99F22956E5F37AA6B90B473585216CF4931DAF7A0AB45" + "00610F69B6C5C3449659C99F22956E5F37AA6B90B473585216CF4931DAF7A0AB45", + "000" ) validated_transaction = %Transaction{ diff --git a/test/archethic/mining/distributed_workflow_test.exs b/test/archethic/mining/distributed_workflow_test.exs index 4fde39277..37745eb9e 100644 --- a/test/archethic/mining/distributed_workflow_test.exs +++ b/test/archethic/mining/distributed_workflow_test.exs @@ -104,17 +104,19 @@ defmodule Archethic.Mining.DistributedWorkflowTest do tx = Transaction.new(:node, %TransactionData{ content: - Node.encode_transaction_content( - {80, 10, 20, 102}, - 3000, - 4000, - MockTransport, - <<0, 0, 16, 233, 156, 172, 143, 228, 236, 12, 227, 76, 1, 80, 12, 236, 69, 10, 209, 6, - 234, 172, 97, 188, 240, 207, 70, 115, 64, 117, 44, 82, 132, 186>>, - origin_public_key, - certificate, - Crypto.generate_random_keypair(:bls) |> elem(0) - ) + Node.encode_transaction_content(%{ + ip: {80, 10, 20, 102}, + port: 3000, + http_port: 4000, + transport: MockTransport, + reward_address: + <<0, 0, 16, 233, 156, 172, 143, 228, 236, 12, 227, 76, 1, 80, 12, 236, 69, 10, 209, + 6, 234, 172, 97, 188, 240, 207, 70, 115, 64, 117, 44, 82, 132, 186>>, + origin_public_key: origin_public_key, + key_certificate: certificate, + mining_public_key: Crypto.generate_random_keypair(:bls) |> elem(0), + geo_patch: "F1B" + }) }) {:ok, @@ -139,6 +141,10 @@ defmodule Archethic.Mining.DistributedWorkflowTest do P2P.authorized_and_available_nodes() ) + stub(MockGeoIP, :get_coordinates, fn + {80, 10, 20, 102} -> {38.345170, -0.481490} + end) + MockClient |> stub(:send_message, fn _, %Ping{}, _ -> @@ -222,6 +228,10 @@ defmodule Archethic.Mining.DistributedWorkflowTest do Election.chain_storage_nodes(tx.address, P2P.authorized_and_available_nodes()) ) + stub(MockGeoIP, :get_coordinates, fn + {80, 10, 20, 102} -> {38.345170, -0.481490} + end) + MockClient |> stub(:send_message, fn _, %Ping{}, _ -> @@ -353,6 +363,13 @@ defmodule Archethic.Mining.DistributedWorkflowTest do {:ok, %Ok{}} end) + stub(MockGeoIP, :get_coordinates, fn ip -> + case ip do + {80, 10, 20, 102} -> + {38.345170, -0.481490} + end + end) + welcome_node = %Node{ ip: {80, 10, 20, 102}, port: 3005, @@ -456,6 +473,10 @@ defmodule Archethic.Mining.DistributedWorkflowTest do Election.chain_storage_nodes(tx.address, P2P.authorized_and_available_nodes()) ) + stub(MockGeoIP, :get_coordinates, fn + {80, 10, 20, 102} -> {38.345170, -0.481490} + end) + MockClient |> stub(:send_message, fn _, %Ping{}, _ -> @@ -589,6 +610,10 @@ defmodule Archethic.Mining.DistributedWorkflowTest do me = self() + stub(MockGeoIP, :get_coordinates, fn + {80, 10, 20, 102} -> {38.345170, -0.481490} + end) + MockClient |> stub(:send_message, fn _, %Ping{}, _ -> @@ -832,6 +857,10 @@ defmodule Archethic.Mining.DistributedWorkflowTest do {:ok, agent_pid} = Agent.start_link(fn -> nil end) + stub(MockGeoIP, :get_coordinates, fn + {80, 10, 20, 102} -> {38.345170, -0.481490} + end) + MockClient |> stub(:send_message, fn _, %ValidationError{}, _ -> @@ -1072,6 +1101,10 @@ defmodule Archethic.Mining.DistributedWorkflowTest do me = self() + stub(MockGeoIP, :get_coordinates, fn + {80, 10, 20, 102} -> {38.345170, -0.481490} + end) + MockClient |> stub(:send_message, fn _, %Ping{}, _ -> @@ -1153,6 +1186,10 @@ defmodule Archethic.Mining.DistributedWorkflowTest do me = self() + stub(MockGeoIP, :get_coordinates, fn + {80, 10, 20, 102} -> {38.345170, -0.481490} + end) + MockClient |> stub(:send_message, fn _, %Ping{}, _ -> @@ -1218,6 +1255,13 @@ defmodule Archethic.Mining.DistributedWorkflowTest do } ) + stub(MockGeoIP, :get_coordinates, fn ip -> + case ip do + {80, 10, 20, 102} -> + {38.345170, -0.481490} + end + end) + assert_receive :validation_error assert_receive :unlock_chain refute_receive :ack_replication diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index 84ca849a0..2f6d30608 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -542,17 +542,19 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do certificate = Crypto.ECDSA.sign(:secp256r1, ca_pv, origin_key) content = - Node.encode_transaction_content( - {80, 20, 10, 200}, - 3000, - 4000, - :tcp, - <<0, 0, 4, 221, 19, 74, 75, 69, 16, 50, 149, 253, 24, 115, 128, 241, 110, 118, 139, 7, - 48, 217, 58, 43, 145, 233, 77, 125, 190, 207, 31, 64, 157, 137>>, - origin_public_key, - certificate, - Crypto.generate_random_keypair(:bls) |> elem(0) - ) + Node.encode_transaction_content(%{ + ip: {88, 22, 30, 229}, + port: 3000, + http_port: 4000, + transport: :tcp, + reward_address: + <<0, 0, 4, 221, 19, 74, 75, 69, 16, 50, 149, 253, 24, 115, 128, 241, 110, 118, 139, 7, + 48, 217, 58, 43, 145, 233, 77, 125, 190, 207, 31, 64, 157, 137>>, + origin_public_key: origin_public_key, + key_certificate: certificate, + mining_public_key: Crypto.generate_random_keypair(:bls) |> elem(0), + geo_patch: "F1B" + }) tx = TransactionFactory.create_non_valided_transaction(type: :node, content: content) @@ -561,6 +563,13 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do address end) + stub(MockGeoIP, :get_coordinates, fn ip -> + case ip do + {88, 22, 30, 229} -> + {38.345170, -0.481490} + end + end) + assert :ok = PendingTransactionValidation.validate_type_rules(tx, DateTime.utc_now()) end @@ -573,17 +582,19 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do certificate = Crypto.get_key_certificate(public_key) content = - Node.encode_transaction_content( - {80, 20, 10, 200}, - 3000, - 4000, - :tcp, - <<0, 0, 4, 221, 19, 74, 75, 69, 16, 50, 149, 253, 24, 115, 128, 241, 110, 118, 139, 7, - 48, 217, 58, 43, 145, 233, 77, 125, 190, 207, 31, 64, 157, 137>>, - <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>, - certificate, - Crypto.generate_random_keypair(:bls) |> elem(0) - ) + Node.encode_transaction_content(%{ + ip: {80, 20, 10, 200}, + port: 3000, + http_port: 4000, + transport: :tcp, + reward_address: + <<0, 0, 4, 221, 19, 74, 75, 69, 16, 50, 149, 253, 24, 115, 128, 241, 110, 118, 139, 7, + 48, 217, 58, 43, 145, 233, 77, 125, 190, 207, 31, 64, 157, 137>>, + origin_public_key: <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>, + key_certificate: certificate, + mining_public_key: Crypto.generate_random_keypair(:bls) |> elem(0), + geo_patch: "BBB" + }) tx = TransactionFactory.create_non_valided_transaction( diff --git a/test/archethic/p2p/geo_patch_test.exs b/test/archethic/p2p/geo_patch_test.exs index b5a064f1d..5efde2535 100644 --- a/test/archethic/p2p/geo_patch_test.exs +++ b/test/archethic/p2p/geo_patch_test.exs @@ -7,6 +7,7 @@ defmodule Archethic.P2P.GeoPatchTest do use ExUnit.Case alias Archethic.P2P.GeoPatch + alias Archethic.P2P.Node import Mox @@ -73,4 +74,82 @@ defmodule Archethic.P2P.GeoPatchTest do assert {{33.75, 39.375}, {-118.125, -112.5}} == GeoPatch.to_coordinates("A1B") assert {{28.125, 33.75}, {-112.5, -106.875}} == GeoPatch.to_coordinates("B14") end + + test "includes and validates geopatch in a node transaction" do + ip = {127, 0, 0, 1} + expected_geopatch = GeoPatch.from_ip(ip) + + assert byte_size(expected_geopatch) == 3 + + port = 3000 + http_port = 4000 + transport = :tcp + reward_address = ArchethicCase.random_address() + origin_public_key = ArchethicCase.random_public_key() + key_certificate = "" + mining_public_key = ArchethicCase.random_public_key() + + content = + Node.encode_transaction_content(%{ + ip: ip, + port: port, + http_port: http_port, + transport: transport, + reward_address: reward_address, + origin_public_key: origin_public_key, + key_certificate: key_certificate, + mining_public_key: mining_public_key, + geo_patch: expected_geopatch + }) + + assert {:ok, decoded_ip, decoded_port, decoded_http_port, decoded_transport, + decoded_reward_address, decoded_origin_public_key, decoded_key_certificate, + decoded_mining_public_key, + decoded_geopatch} = Node.decode_transaction_content(content) + + assert decoded_ip == ip + assert decoded_port == port + assert decoded_http_port == http_port + assert decoded_transport == transport + assert decoded_reward_address == reward_address + assert decoded_origin_public_key == origin_public_key + assert decoded_key_certificate == key_certificate + assert decoded_mining_public_key == mining_public_key + assert decoded_geopatch == expected_geopatch + + assert GeoPatch.from_ip(decoded_ip) == decoded_geopatch + end + + test "rejects invalid geopatch in node transaction" do + ip = {127, 0, 0, 1} + invalid_geopatch = "BAD" + assert byte_size(invalid_geopatch) == 3 + + port = 3000 + http_port = 4000 + transport = :tcp + reward_address = ArchethicCase.random_address() + origin_public_key = ArchethicCase.random_public_key() + key_certificate = "" + mining_public_key = ArchethicCase.random_public_key() + + content = + Node.encode_transaction_content(%{ + ip: ip, + port: port, + http_port: http_port, + transport: transport, + reward_address: reward_address, + origin_public_key: origin_public_key, + key_certificate: key_certificate, + mining_public_key: mining_public_key, + geo_patch: invalid_geopatch + }) + + assert {:ok, decoded_ip, _, _, _, _, _, _, _, decoded_geopatch} = + Node.decode_transaction_content(content) + + assert decoded_ip == ip + refute GeoPatch.from_ip(decoded_ip) == decoded_geopatch + end end diff --git a/test/archethic/p2p/node_test.exs b/test/archethic/p2p/node_test.exs index 74be43266..1a40ed190 100644 --- a/test/archethic/p2p/node_test.exs +++ b/test/archethic/p2p/node_test.exs @@ -41,18 +41,19 @@ defmodule Archethic.P2P.NodeTest do mining_public_key = ArchethicCase.random_public_key() assert {:ok, {127, 0, 0, 1}, 3000, 4000, :tcp, ^reward_address, ^origin_public_key, - ^certificate, - ^mining_public_key} = - Node.encode_transaction_content( - {127, 0, 0, 1}, - 3000, - 4000, - :tcp, - reward_address, - origin_public_key, - certificate, - mining_public_key - ) + ^certificate, ^mining_public_key, + "000"} = + Node.encode_transaction_content(%{ + ip: {127, 0, 0, 1}, + port: 3000, + http_port: 4000, + transport: :tcp, + reward_address: reward_address, + origin_public_key: origin_public_key, + key_certificate: certificate, + mining_public_key: mining_public_key, + geo_patch: "000" + }) |> Node.decode_transaction_content() end end diff --git a/test/archethic/shared_secrets/mem_tables_loader_test.exs b/test/archethic/shared_secrets/mem_tables_loader_test.exs index 21d820c0f..0e333c01e 100644 --- a/test/archethic/shared_secrets/mem_tables_loader_test.exs +++ b/test/archethic/shared_secrets/mem_tables_loader_test.exs @@ -40,16 +40,17 @@ defmodule Archethic.SharedSecrets.MemTablesLoaderTest do type: :node, data: %TransactionData{ content: - Node.encode_transaction_content( - {127, 0, 0, 1}, - 3000, - 4000, - :tcp, - ArchethicCase.random_address(), - origin_public_key, - :crypto.strong_rand_bytes(32), - Crypto.generate_random_keypair(:bls) |> elem(0) - ) + Node.encode_transaction_content(%{ + ip: {127, 0, 0, 1}, + port: 3000, + http_port: 4000, + transport: :tcp, + reward_address: ArchethicCase.random_address(), + origin_public_key: origin_public_key, + key_certificate: :crypto.strong_rand_bytes(32), + mining_public_key: Crypto.generate_random_keypair(:bls) |> elem(0), + geo_patch: "000" + }) } } @@ -152,16 +153,17 @@ defmodule Archethic.SharedSecrets.MemTablesLoaderTest do type: :node, data: %TransactionData{ content: - Node.encode_transaction_content( - {127, 0, 0, 1}, - 3000, - 4000, - :tcp, - ArchethicCase.random_address(), - node_origin_public_key, - :crypto.strong_rand_bytes(32), - Crypto.generate_random_keypair(:bls) |> elem(0) - ) + Node.encode_transaction_content(%{ + ip: {127, 0, 0, 1}, + port: 3000, + http_port: 4000, + transport: :tcp, + reward_address: ArchethicCase.random_address(), + origin_public_key: node_origin_public_key, + key_certificate: :crypto.strong_rand_bytes(32), + mining_public_key: Crypto.generate_random_keypair(:bls) |> elem(0), + geo_patch: "000" + }) } }