From 0efff5aa15dacfbee94d2151dfc7c01f966c1aa0 Mon Sep 17 00:00:00 2001 From: Neylix Date: Tue, 11 Feb 2025 11:13:29 +0100 Subject: [PATCH] Add geo patch update date in node tx --- config/dev.exs | 3 ++ config/prod.exs | 3 ++ config/test.exs | 3 ++ .../bootstrap/transaction_handler.ex | 9 ++++- .../mining/pending_transaction_validation.ex | 31 ++++++++++++++--- lib/archethic/networking/scheduler.ex | 3 ++ lib/archethic/p2p/geo_patch.ex | 2 +- lib/archethic/p2p/node/config.ex | 34 +++++++++++-------- .../explorer/live/settings_live.ex | 7 ++-- test/archethic/bootstrap/sync_test.exs | 3 +- .../bootstrap/transaction_handler_test.exs | 10 ++++-- .../mining/distributed_workflow_test.exs | 7 +++- .../pending_transaction_validation_test.exs | 20 +++++++++-- test/archethic/p2p/node/config_test.exs | 18 +++++++--- test/archethic/p2p/node_test.exs | 3 +- .../shared_secrets/mem_tables_loader_test.exs | 6 ++-- 16 files changed, 125 insertions(+), 37 deletions(-) diff --git a/config/dev.exs b/config/dev.exs index 68f736225a..3637bb5ab9 100755 --- a/config/dev.exs +++ b/config/dev.exs @@ -183,3 +183,6 @@ config :archethic, :throttle, period: 1000, limit: System.get_env("ARCHETHIC_THROTTLE_IP_AND_PATH", "5000") |> String.to_integer() ] + +# Apply geopatch in 1min 10 sec (needs to be over global timeout) +config :archethic, :geopatch_update_time, 70000 diff --git a/config/prod.exs b/config/prod.exs index c1454ded32..90e418e104 100755 --- a/config/prod.exs +++ b/config/prod.exs @@ -314,3 +314,6 @@ config :archethic, :throttle, period: 1000, limit: System.get_env("ARCHETHIC_THROTTLE_IP_AND_PATH", "20") |> String.to_integer() ] + +# Apply geopatch in 10 min (needs to be over global timeout) +config :archethic, :geopatch_update_time, 600_000 diff --git a/config/test.exs b/config/test.exs index 0a3e5262b9..1f1a3b72bf 100755 --- a/config/test.exs +++ b/config/test.exs @@ -225,3 +225,6 @@ config :archethic, Archethic.P2P.Message.GetUnspentOutputs, threshold: 1_000 config :archethic, Archethic.P2P.Message.ValidateSmartContractCall, timeout: 50 config :archethic, Archethic.Contracts.Wasm.IO, MockWasmIO + +# Apply geopatch in 1min 10 sec (needs to be over global timeout) +config :archethic, :geopatch_update_time, 70000 diff --git a/lib/archethic/bootstrap/transaction_handler.ex b/lib/archethic/bootstrap/transaction_handler.ex index 44e9978542..2852d56d72 100644 --- a/lib/archethic/bootstrap/transaction_handler.ex +++ b/lib/archethic/bootstrap/transaction_handler.ex @@ -14,6 +14,8 @@ defmodule Archethic.Bootstrap.TransactionHandler do require Logger + @geopatch_update_time Application.compile_env!(:archethic, :geopatch_update_time) + @doc """ Send a transaction to the network towards a welcome node """ @@ -68,7 +70,12 @@ defmodule Archethic.Bootstrap.TransactionHandler do Create a new node transaction """ @spec create_node_transaction(node_config :: NodeConfig.t()) :: Transaction.t() - def create_node_transaction(node_config) do + def create_node_transaction(node_config, date \\ DateTime.utc_now()) do + node_config = %NodeConfig{ + node_config + | geo_patch_update: DateTime.add(date, @geopatch_update_time, :millisecond) + } + Transaction.new(:node, %TransactionData{ code: """ condition inherit: [ diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index cb6ab005d5..c92acce772 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -71,6 +71,13 @@ defmodule Archethic.Mining.PendingTransactionValidation do @tx_max_size Application.compile_env!(:archethic, :transaction_data_content_max_size) + @mining_timeout Application.compile_env!(:archethic, [ + Archethic.Mining.DistributedWorkflow, + :global_timeout + ]) + + @geo_patch_max_update_time Application.compile_env!(:archethic, :geopatch_update_time) + @prod? Mix.env() == :prod @doc """ @@ -346,14 +353,14 @@ defmodule Archethic.Mining.PendingTransactionValidation do }, previous_public_key: previous_public_key }, - _ + validation_time ) do with {:ok, node_config} <- validate_node_tx_content(content), :ok <- validate_node_tx_origin(node_config), :ok <- validate_node_tx_connection(node_config, previous_public_key), :ok <- validate_node_tx_transfers(token_transfers), :ok <- vallidate_node_tx_mining_key(node_config) do - validate_node_tx_geopatch(node_config) + validate_node_tx_geopatch(node_config, validation_time) end end @@ -740,8 +747,24 @@ defmodule Archethic.Mining.PendingTransactionValidation do end end - defp validate_node_tx_geopatch(%NodeConfig{ip: ip, geo_patch: geo_patch}) do - if geo_patch == GeoPatch.from_ip(ip), do: :ok, else: {:error, "Invalid geo patch from IP"} + defp validate_node_tx_geopatch( + %NodeConfig{ip: ip, geo_patch: geo_patch, geo_patch_update: geo_patch_update}, + validation_time + ) do + diff = DateTime.diff(geo_patch_update, validation_time, :millisecond) + + # TODO: Ensure there is no other P2P view which update the geopatch after the + # update time in this transaction + cond do + geo_patch != GeoPatch.from_ip(ip) -> + {:error, "Invalid geo patch from IP"} + + diff < @mining_timeout or diff > @geo_patch_max_update_time -> + {:error, "Invalid geo patch update time"} + + true -> + :ok + end end @doc """ diff --git a/lib/archethic/networking/scheduler.ex b/lib/archethic/networking/scheduler.ex index 7ac4c2f2a0..3fc3c579ed 100644 --- a/lib/archethic/networking/scheduler.ex +++ b/lib/archethic/networking/scheduler.ex @@ -31,6 +31,8 @@ defmodule Archethic.Networking.Scheduler do require Logger + @geopatch_update_time Application.compile_env!(:archethic, :geopatch_update_time) + def start_link(arg \\ []) do GenServer.start_link(__MODULE__, arg) end @@ -108,6 +110,7 @@ defmodule Archethic.Networking.Scheduler do NodeConfig.from_node(node) | origin_certificate: Crypto.get_key_certificate(origin_public_key), geo_patch: GeoPatch.from_ip(ip), + geo_patch_update: DateTime.add(DateTime.utc_now(), @geopatch_update_time, :millisecond), port: p2p_port, http_port: web_port } diff --git a/lib/archethic/p2p/geo_patch.ex b/lib/archethic/p2p/geo_patch.ex index 202aa30936..b27370872d 100755 --- a/lib/archethic/p2p/geo_patch.ex +++ b/lib/archethic/p2p/geo_patch.ex @@ -12,7 +12,7 @@ defmodule Archethic.P2P.GeoPatch do Null island, patch for local host. """ @spec from_ip(:inet.ip_address()) :: binary() - def from_ip({127, 0, 0, 1}), do: "000" + def from_ip({127, 0, 0, 1}), do: "AAA" def from_ip(ip) when is_tuple(ip) do case GeoIP.get_coordinates(ip) do diff --git a/lib/archethic/p2p/node/config.ex b/lib/archethic/p2p/node/config.ex index 92250ab984..30deeaee20 100644 --- a/lib/archethic/p2p/node/config.ex +++ b/lib/archethic/p2p/node/config.ex @@ -19,7 +19,8 @@ defmodule Archethic.P2P.NodeConfig do :origin_public_key, :origin_certificate, :mining_public_key, - :geo_patch + :geo_patch, + :geo_patch_update ] @type t :: %__MODULE__{ @@ -32,7 +33,8 @@ defmodule Archethic.P2P.NodeConfig do origin_public_key: Crypto.key(), origin_certificate: nil | binary(), mining_public_key: nil | Crypto.key(), - geo_patch: nil | binary() + geo_patch: nil | binary(), + geo_patch_update: nil | DateTime.t() } @doc """ @@ -65,19 +67,19 @@ defmodule Archethic.P2P.NodeConfig do @doc """ Returns true if the config are different - do not compare origin certificate + do not compare origin certificate and geo patch update """ @spec different?(config1 :: t(), config2 :: t()) :: boolean() def different?(config1, config2) do - config1 = %__MODULE__{config1 | origin_certificate: nil} - config2 = %__MODULE__{config2 | origin_certificate: nil} + config1 = %__MODULE__{config1 | origin_certificate: nil, geo_patch_update: nil} + config2 = %__MODULE__{config2 | origin_certificate: nil, geo_patch_update: nil} config1 != config2 end @doc """ Serialize a config in binary. - Origin certificate should not be nil + Origin certificate and geo_patch_update should not be nil """ @spec serialize(node_config :: t()) :: binary() def serialize(%__MODULE__{ @@ -89,12 +91,14 @@ defmodule Archethic.P2P.NodeConfig do origin_public_key: origin_public_key, origin_certificate: origin_certificate, mining_public_key: mining_public_key, - geo_patch: geo_patch + geo_patch: geo_patch, + geo_patch_update: geo_patch_update = %DateTime{} }) - when origin_certificate != nil do + when is_binary(origin_certificate) do <> + origin_certificate::binary, mining_public_key::binary, geo_patch::binary-size(3), + DateTime.to_unix(geo_patch_update)::64>> end defp serialize_transport(MockTransport), do: 0 @@ -103,7 +107,6 @@ defmodule Archethic.P2P.NodeConfig do @doc """ Deserialize a binary and return a NodeConfig """ - @spec deserialize(binary()) :: {t(), binary()} | :error def deserialize(<>) do with <> <- ip, @@ -112,7 +115,7 @@ defmodule Archethic.P2P.NodeConfig do <> <- rest, {mining_public_key, rest} <- extract_mining_public_key(rest), - {geo_patch, rest} <- extract_geo_patch(rest) do + {geo_patch, geo_patch_update, rest} <- extract_geo_patch(rest) do node_config = %__MODULE__{ ip: {ip1, ip2, ip3, ip4}, port: port, @@ -122,7 +125,8 @@ defmodule Archethic.P2P.NodeConfig do origin_public_key: origin_public_key, origin_certificate: origin_certificate, mining_public_key: mining_public_key, - geo_patch: geo_patch + geo_patch: geo_patch, + geo_patch_update: geo_patch_update } {node_config, rest} @@ -139,6 +143,8 @@ defmodule Archethic.P2P.NodeConfig do defp extract_mining_public_key(<<>>), do: {nil, <<>>} defp extract_mining_public_key(rest), do: Utils.deserialize_public_key(rest) - defp extract_geo_patch(<>), do: {geo_patch, rest} - defp extract_geo_patch(rest), do: {nil, rest} + defp extract_geo_patch(<>), + do: {geo_patch, DateTime.from_unix!(geo_patch_update), rest} + + defp extract_geo_patch(rest), do: {nil, nil, rest} end diff --git a/lib/archethic_web/explorer/live/settings_live.ex b/lib/archethic_web/explorer/live/settings_live.ex index 2a3f4a516f..62837d85e4 100644 --- a/lib/archethic_web/explorer/live/settings_live.ex +++ b/lib/archethic_web/explorer/live/settings_live.ex @@ -23,6 +23,7 @@ defmodule ArchethicWeb.Explorer.SettingsLive do alias ArchethicWeb.TransactionSubscriber @ip_validate_regex ~r/(^127\.)|(^192\.168\.)/ + @geopatch_update_time Application.compile_env!(:archethic, :geopatch_update_time) def mount(_params, %{"remote_ip" => remote_ip}, socket) do # Only authorized the page in the node's private network @@ -130,7 +131,8 @@ defmodule ArchethicWeb.Explorer.SettingsLive do node_config = %NodeConfig{ NodeConfig.from_node(node) | origin_certificate: Crypto.get_key_certificate(origin_public_key), - reward_address: next_reward_address + reward_address: next_reward_address, + geo_patch_update: DateTime.add(DateTime.utc_now(), @geopatch_update_time, :millisecond) } genesis_address = Crypto.derive_address(first_public_key) @@ -162,7 +164,8 @@ defmodule ArchethicWeb.Explorer.SettingsLive do node_config = %NodeConfig{ NodeConfig.from_node(node) - | origin_certificate: Crypto.get_key_certificate(origin_public_key) + | origin_certificate: Crypto.get_key_certificate(origin_public_key), + geo_patch_update: DateTime.add(DateTime.utc_now(), @geopatch_update_time, :millisecond) } {:ok, %Transaction{data: %TransactionData{code: code}}} = diff --git a/test/archethic/bootstrap/sync_test.exs b/test/archethic/bootstrap/sync_test.exs index 49a12952bf..f271fa792f 100644 --- a/test/archethic/bootstrap/sync_test.exs +++ b/test/archethic/bootstrap/sync_test.exs @@ -419,7 +419,8 @@ defmodule Archethic.Bootstrap.SyncTest do origin_public_key: random_public_key(), origin_certificate: :crypto.strong_rand_bytes(64), mining_public_key: <<3::8, 2::8, :crypto.strong_rand_bytes(48)::binary>>, - geo_patch: "000" + geo_patch: "000", + geo_patch_update: DateTime.utc_now() |> DateTime.truncate(:second) } node_tx = diff --git a/test/archethic/bootstrap/transaction_handler_test.exs b/test/archethic/bootstrap/transaction_handler_test.exs index 3a34c720a9..879967be85 100644 --- a/test/archethic/bootstrap/transaction_handler_test.exs +++ b/test/archethic/bootstrap/transaction_handler_test.exs @@ -20,7 +20,11 @@ defmodule Archethic.Bootstrap.TransactionHandlerTest do import ArchethicCase import Mox + @geo_patch_max_update_time Application.compile_env!(:archethic, :geopatch_update_time) + test "create_node_transaction/4 should create transaction with ip, geopatch and port encoded in the content" do + now = DateTime.utc_now() + node_config = %NodeConfig{ ip: {127, 0, 0, 1}, port: 3000, @@ -30,11 +34,13 @@ defmodule Archethic.Bootstrap.TransactionHandlerTest do origin_public_key: random_public_key(), origin_certificate: :crypto.strong_rand_bytes(64), mining_public_key: <<3::8, 2::8, :crypto.strong_rand_bytes(48)::binary>>, - geo_patch: "AAA" + geo_patch: "AAA", + geo_patch_update: + DateTime.add(now, @geo_patch_max_update_time, :millisecond) |> DateTime.truncate(:second) } assert %Transaction{data: %TransactionData{content: content}} = - TransactionHandler.create_node_transaction(node_config) + TransactionHandler.create_node_transaction(node_config, now) assert {:ok, node_config} == Node.decode_transaction_content(content) end diff --git a/test/archethic/mining/distributed_workflow_test.exs b/test/archethic/mining/distributed_workflow_test.exs index d3143e8426..a949e0b0a9 100644 --- a/test/archethic/mining/distributed_workflow_test.exs +++ b/test/archethic/mining/distributed_workflow_test.exs @@ -7,6 +7,7 @@ defmodule Archethic.Mining.DistributedWorkflowTest do @publickey1 Crypto.generate_deterministic_keypair("seed2") @publickey2 Crypto.generate_deterministic_keypair("seed3") + @geo_patch_max_update_time Application.compile_env!(:archethic, :geopatch_update_time) alias Archethic.BeaconChain alias Archethic.BeaconChain.SummaryTimer, as: BeaconSummaryTimer @@ -114,7 +115,11 @@ defmodule Archethic.Mining.DistributedWorkflowTest do origin_public_key: origin_public_key, origin_certificate: certificate, mining_public_key: Crypto.generate_random_keypair(:bls) |> elem(0), - geo_patch: "F1B" + geo_patch: "F1B", + geo_patch_update: + DateTime.utc_now() + |> DateTime.add(@geo_patch_max_update_time, :millisecond) + |> DateTime.truncate(:second) } tx = diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index d91850c8e2..d9e63042f6 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -41,6 +41,8 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do import Mock import ArchethicCase + @geo_patch_max_update_time Application.compile_env!(:archethic, :geopatch_update_time) + setup do P2P.add_and_connect_node(%Node{ first_public_key: Crypto.last_node_public_key(), @@ -612,7 +614,11 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do origin_public_key: origin_public_key, origin_certificate: certificate, mining_public_key: Crypto.generate_random_keypair(:bls) |> elem(0), - geo_patch: "F1B" + geo_patch: "F1B", + geo_patch_update: + DateTime.utc_now() + |> DateTime.add(@geo_patch_max_update_time, :millisecond) + |> DateTime.truncate(:second) } content = Node.encode_transaction_content(node_config) @@ -652,7 +658,11 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do origin_public_key: origin_public_key, origin_certificate: certificate, mining_public_key: Crypto.generate_random_keypair(:bls) |> elem(0), - geo_patch: "FFF" + geo_patch: "FFF", + geo_patch_update: + DateTime.utc_now() + |> DateTime.add(@geo_patch_max_update_time, :millisecond) + |> DateTime.truncate(:second) } content = Node.encode_transaction_content(node_config) @@ -693,7 +703,11 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do origin_public_key: public_key, origin_certificate: certificate, mining_public_key: Crypto.generate_random_keypair(:bls) |> elem(0), - geo_patch: "BBB" + geo_patch: "BBB", + geo_patch_update: + DateTime.utc_now() + |> DateTime.add(@geo_patch_max_update_time, :millisecond) + |> DateTime.truncate(:second) } content = Node.encode_transaction_content(node_config) diff --git a/test/archethic/p2p/node/config_test.exs b/test/archethic/p2p/node/config_test.exs index 64829352bb..607f7ccc80 100644 --- a/test/archethic/p2p/node/config_test.exs +++ b/test/archethic/p2p/node/config_test.exs @@ -50,7 +50,8 @@ defmodule Archethic.P2P.NodeConfigTest do origin_public_key: random_public_key(), origin_certificate: :crypto.strong_rand_bytes(32), mining_public_key: random_public_key(), - geo_patch: "AAA" + geo_patch: "AAA", + geo_patch_update: DateTime.utc_now() |> DateTime.truncate(:second) } config2 = %NodeConfig{ @@ -63,7 +64,8 @@ defmodule Archethic.P2P.NodeConfigTest do origin_public_key: random_public_key(), origin_certificate: :crypto.strong_rand_bytes(32), mining_public_key: random_public_key(), - geo_patch: "BBB" + geo_patch: "BBB", + geo_patch_update: DateTime.utc_now() |> DateTime.truncate(:second) } assert NodeConfig.different?(config1, config2) @@ -80,10 +82,15 @@ defmodule Archethic.P2P.NodeConfigTest do origin_public_key: random_public_key(), origin_certificate: :crypto.strong_rand_bytes(32), mining_public_key: Crypto.generate_random_keypair(:bls) |> elem(0), - geo_patch: "AAA" + geo_patch: "AAA", + geo_patch_update: DateTime.utc_now() |> DateTime.truncate(:second) } - same_config = %NodeConfig{config | origin_certificate: :crypto.strong_rand_bytes(32)} + same_config = %NodeConfig{ + config + | origin_certificate: :crypto.strong_rand_bytes(32), + geo_patch_update: DateTime.utc_now() |> DateTime.add(-2) |> DateTime.truncate(:second) + } refute NodeConfig.different?(config, same_config) end @@ -101,7 +108,8 @@ defmodule Archethic.P2P.NodeConfigTest do origin_public_key: random_public_key(), origin_certificate: :crypto.strong_rand_bytes(32), mining_public_key: random_public_key(), - geo_patch: "AAA" + geo_patch: "AAA", + geo_patch_update: DateTime.utc_now() |> DateTime.truncate(:second) } assert {config, <<>>} == config |> NodeConfig.serialize() |> NodeConfig.deserialize() diff --git a/test/archethic/p2p/node_test.exs b/test/archethic/p2p/node_test.exs index ef53b30f7a..407b463a2f 100644 --- a/test/archethic/p2p/node_test.exs +++ b/test/archethic/p2p/node_test.exs @@ -46,7 +46,8 @@ defmodule Archethic.P2P.NodeTest do origin_public_key: random_public_key(), origin_certificate: :crypto.strong_rand_bytes(64), mining_public_key: <<3::8, 2::8, :crypto.strong_rand_bytes(48)::binary>>, - geo_patch: "AAA" + geo_patch: "AAA", + geo_patch_update: DateTime.utc_now() |> DateTime.truncate(:second) } assert {:ok, node_config} == diff --git a/test/archethic/shared_secrets/mem_tables_loader_test.exs b/test/archethic/shared_secrets/mem_tables_loader_test.exs index 4c193e2305..5f0b378fd8 100644 --- a/test/archethic/shared_secrets/mem_tables_loader_test.exs +++ b/test/archethic/shared_secrets/mem_tables_loader_test.exs @@ -44,7 +44,8 @@ defmodule Archethic.SharedSecrets.MemTablesLoaderTest do origin_public_key: origin_public_key, origin_certificate: :crypto.strong_rand_bytes(64), mining_public_key: <<3::8, 2::8, :crypto.strong_rand_bytes(48)::binary>>, - geo_patch: "AAA" + geo_patch: "AAA", + geo_patch_update: DateTime.utc_now() |> DateTime.truncate(:second) } tx = %Transaction{ @@ -156,7 +157,8 @@ defmodule Archethic.SharedSecrets.MemTablesLoaderTest do origin_public_key: node_origin_public_key, origin_certificate: :crypto.strong_rand_bytes(64), mining_public_key: <<3::8, 2::8, :crypto.strong_rand_bytes(48)::binary>>, - geo_patch: "AAA" + geo_patch: "AAA", + geo_patch_update: DateTime.utc_now() |> DateTime.truncate(:second) } node_tx = %Transaction{