Skip to content

Commit

Permalink
Add geo patch update date in node tx
Browse files Browse the repository at this point in the history
  • Loading branch information
Neylix committed Feb 11, 2025
1 parent d48fe19 commit 0efff5a
Show file tree
Hide file tree
Showing 16 changed files with 125 additions and 37 deletions.
3 changes: 3 additions & 0 deletions config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions config/prod.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 8 additions & 1 deletion lib/archethic/bootstrap/transaction_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
"""
Expand Down Expand Up @@ -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: [
Expand Down
31 changes: 27 additions & 4 deletions lib/archethic/mining/pending_transaction_validation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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 """
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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 """
Expand Down
3 changes: 3 additions & 0 deletions lib/archethic/networking/scheduler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion lib/archethic/p2p/geo_patch.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 20 additions & 14 deletions lib/archethic/p2p/node/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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__{
Expand All @@ -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 """
Expand Down Expand Up @@ -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__{
Expand All @@ -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
<<ip1, ip2, ip3, ip4, port::16, http_port::16, serialize_transport(transport)::8,
reward_address::binary, origin_public_key::binary, byte_size(origin_certificate)::16,
origin_certificate::binary, mining_public_key::binary, geo_patch::binary-size(3)>>
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
Expand All @@ -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(<<ip::binary-size(4), port::16, http_port::16, transport::8, rest::binary>>) do
with <<ip1, ip2, ip3, ip4>> <- ip,
Expand All @@ -112,7 +115,7 @@ defmodule Archethic.P2P.NodeConfig do
<<origin_certificate_size::16, origin_certificate::binary-size(origin_certificate_size),
rest::binary>> <- 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,
Expand All @@ -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}
Expand All @@ -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(<<geo_patch::binary-size(3), rest::binary>>), do: {geo_patch, rest}
defp extract_geo_patch(rest), do: {nil, rest}
defp extract_geo_patch(<<geo_patch::binary-size(3), geo_patch_update::64, rest::binary>>),
do: {geo_patch, DateTime.from_unix!(geo_patch_update), rest}

defp extract_geo_patch(rest), do: {nil, nil, rest}
end
7 changes: 5 additions & 2 deletions lib/archethic_web/explorer/live/settings_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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}}} =
Expand Down
3 changes: 2 additions & 1 deletion test/archethic/bootstrap/sync_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
10 changes: 8 additions & 2 deletions test/archethic/bootstrap/transaction_handler_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
7 changes: 6 additions & 1 deletion test/archethic/mining/distributed_workflow_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 =
Expand Down
20 changes: 17 additions & 3 deletions test/archethic/mining/pending_transaction_validation_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 0efff5a

Please sign in to comment.