From 1b00b5542c8d7e62426b8d9186d1f7f907599e70 Mon Sep 17 00:00:00 2001 From: Neylix Date: Fri, 7 Jul 2023 13:45:35 +0200 Subject: [PATCH 1/3] Add p2p view verification in SlotValidation --- lib/archethic/beacon_chain.ex | 3 + lib/archethic/beacon_chain/slot/validation.ex | 15 ++ .../beacon_chain/slot/validation_test.exs | 152 ++++++++++++++++++ test/archethic/beacon_chain_test.exs | 2 +- 4 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 test/archethic/beacon_chain/slot/validation_test.exs diff --git a/lib/archethic/beacon_chain.ex b/lib/archethic/beacon_chain.ex index d3b250233..3d95a2680 100644 --- a/lib/archethic/beacon_chain.ex +++ b/lib/archethic/beacon_chain.ex @@ -144,6 +144,9 @@ defmodule Archethic.BeaconChain do !SlotValidation.valid_end_of_node_sync?(slot) -> {:error, :invalid_end_of_node_sync} + !SlotValidation.valid_p2p_view?(slot) -> + {:error, :invalid_p2p_view} + true -> :ok end diff --git a/lib/archethic/beacon_chain/slot/validation.ex b/lib/archethic/beacon_chain/slot/validation.ex index 78f9f6c26..62dea6f38 100644 --- a/lib/archethic/beacon_chain/slot/validation.ex +++ b/lib/archethic/beacon_chain/slot/validation.ex @@ -4,6 +4,7 @@ defmodule Archethic.BeaconChain.Slot.Validation do alias Archethic.BeaconChain.ReplicationAttestation alias Archethic.BeaconChain.Slot alias Archethic.BeaconChain.Slot.EndOfNodeSync + alias Archethic.BeaconChain.Subset.P2PSampling alias Archethic.P2P alias Archethic.P2P.Node @@ -60,4 +61,18 @@ defmodule Archethic.BeaconChain.Slot.Validation do match?({:ok, %Node{first_public_key: ^key}}, P2P.get_node_info(key)) end) end + + @doc """ + Validate the p2p view to ensure it correspond to the node list of the subset + """ + @spec valid_p2p_view?(slot :: Slot.t()) :: boolean + def valid_p2p_view?(%Slot{ + subset: subset, + p2p_view: %{availabilities: availabilities_bin, network_stats: network_stats} + }) do + subset_nodes_length = P2PSampling.list_nodes_to_sample(subset) |> length() + availabilities = for <>, do: availability_time + + length(availabilities) == subset_nodes_length and length(network_stats) == subset_nodes_length + end end diff --git a/test/archethic/beacon_chain/slot/validation_test.exs b/test/archethic/beacon_chain/slot/validation_test.exs new file mode 100644 index 000000000..36e687fe3 --- /dev/null +++ b/test/archethic/beacon_chain/slot/validation_test.exs @@ -0,0 +1,152 @@ +defmodule Archethic.BeaconChain.Slot.ValidationTest do + use ArchethicCase + + alias Archethic.BeaconChain.ReplicationAttestation + alias Archethic.BeaconChain.Slot + alias Archethic.BeaconChain.Slot.EndOfNodeSync + alias Archethic.BeaconChain.Slot.Validation, as: SlotValidation + + alias Archethic.P2P + alias Archethic.P2P.Node + + alias Archethic.TransactionChain.TransactionSummary + + alias Archethic.TransactionFactory + + import Mock + + describe "valid_transaction_attestations?/1" do + setup_with_mocks([ + {ReplicationAttestation, [], + validate: fn %ReplicationAttestation{transaction_summary: %TransactionSummary{type: type}} -> + if type == :transfer, do: :ok, else: {:error, :invalid_confirmations_signatures} + end} + ]) do + :ok + end + + test "should return true if all attestation are valid" do + tx1 = + TransactionFactory.create_valid_transaction([], seed: "abc") + |> TransactionSummary.from_transaction() + + tx2 = + TransactionFactory.create_valid_transaction([], seed: "123") + |> TransactionSummary.from_transaction() + + attestation1 = %ReplicationAttestation{transaction_summary: tx1, confirmations: []} + attestation2 = %ReplicationAttestation{transaction_summary: tx2, confirmations: []} + + slot = %Slot{transaction_attestations: [attestation1, attestation2]} + + assert SlotValidation.valid_transaction_attestations?(slot) + end + + test "should return false if at least one attestation is invalid" do + tx1 = + TransactionFactory.create_valid_transaction([], seed: "abc") + |> TransactionSummary.from_transaction() + + tx2 = + TransactionFactory.create_valid_transaction([], seed: "123", type: :node) + |> TransactionSummary.from_transaction() + + attestation1 = %ReplicationAttestation{transaction_summary: tx1, confirmations: []} + attestation2 = %ReplicationAttestation{transaction_summary: tx2, confirmations: []} + + slot = %Slot{transaction_attestations: [attestation1, attestation2]} + + refute SlotValidation.valid_transaction_attestations?(slot) + end + end + + describe "valid_end_of_node_sync?/1" do + setup do + P2P.add_and_connect_node(%Node{ + ip: {127, 0, 0, 1}, + port: 3000, + first_public_key: "key1", + last_public_key: "key1", + geo_patch: "AAA", + network_patch: "AAA" + }) + + P2P.add_and_connect_node(%Node{ + ip: {127, 0, 0, 1}, + port: 3000, + first_public_key: "key2", + last_public_key: "key2", + geo_patch: "AAA", + network_patch: "AAA" + }) + end + + test "should return true if node exists" do + slot = %Slot{ + end_of_node_synchronizations: [ + %EndOfNodeSync{public_key: "key1", timestamp: DateTime.utc_now()}, + %EndOfNodeSync{public_key: "key2", timestamp: DateTime.utc_now()} + ] + } + + assert SlotValidation.valid_end_of_node_sync?(slot) + end + + test "should return false if at least one node doesn't exist" do + slot = %Slot{ + end_of_node_synchronizations: [ + %EndOfNodeSync{public_key: "key1", timestamp: DateTime.utc_now()}, + %EndOfNodeSync{public_key: "key3", timestamp: DateTime.utc_now()} + ] + } + + refute SlotValidation.valid_end_of_node_sync?(slot) + end + end + + describe "valid_p2p_view?/1" do + setup do + P2P.add_and_connect_node(%Node{ + ip: {127, 0, 0, 1}, + port: 3000, + first_public_key: <<0::24, :crypto.strong_rand_bytes(31)::binary>>, + last_public_key: <<0::24, :crypto.strong_rand_bytes(31)::binary>>, + geo_patch: "AAA", + network_patch: "AAA" + }) + + P2P.add_and_connect_node(%Node{ + ip: {127, 0, 0, 1}, + port: 3000, + first_public_key: <<0::24, :crypto.strong_rand_bytes(31)::binary>>, + last_public_key: <<0::24, :crypto.strong_rand_bytes(31)::binary>>, + geo_patch: "AAA", + network_patch: "AAA" + }) + end + + test "should return true if p2p view length correspond to node subset" do + slot = %Slot{ + subset: <<0>>, + p2p_view: %{ + availabilities: <<600::16, 600::16>>, + network_stats: [%{latency: 10}, %{latency: 10}] + } + } + + assert SlotValidation.valid_p2p_view?(slot) + end + + test "should return false if p2p view length does not correspond to node subset" do + slot = %Slot{ + subset: <<0>>, + p2p_view: %{ + availabilities: <<600::16>>, + network_stats: [%{latency: 10}] + } + } + + refute SlotValidation.valid_p2p_view?(slot) + end + end +end diff --git a/test/archethic/beacon_chain_test.exs b/test/archethic/beacon_chain_test.exs index f4e2574c7..34e5226d4 100644 --- a/test/archethic/beacon_chain_test.exs +++ b/test/archethic/beacon_chain_test.exs @@ -71,7 +71,7 @@ defmodule Archethic.BeaconChainTest do end describe "load_slot/1" do - test "should fetch the transaction chain from the beacon involved nodes" do + test "should add slot in summary cache" do SummaryCache.start_link() File.mkdir_p!(Utils.mut_dir()) From 08c9c725cfe6058ab5983f20c7631142c6c9e9ca Mon Sep 17 00:00:00 2001 From: Neylix Date: Fri, 7 Jul 2023 15:03:36 +0200 Subject: [PATCH 2/3] Filter invalid stats view in network patch calcul --- .../beacon_chain/network_coordinates.ex | 11 +++- .../beacon_chain/network_coordinates_test.exs | 63 ++++++++++++++++++- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/lib/archethic/beacon_chain/network_coordinates.ex b/lib/archethic/beacon_chain/network_coordinates.ex index 42df76327..cafd5f624 100644 --- a/lib/archethic/beacon_chain/network_coordinates.ex +++ b/lib/archethic/beacon_chain/network_coordinates.ex @@ -267,7 +267,7 @@ defmodule Archethic.BeaconChain.NetworkCoordinates do ) |> Stream.filter(fn {:ok, {:ok, %NetworkStats{stats: stats}}} when map_size(stats) > 0 -> - true + valid_stats?(stats) _ -> false @@ -275,6 +275,15 @@ defmodule Archethic.BeaconChain.NetworkCoordinates do |> Stream.map(fn {:ok, {:ok, %NetworkStats{stats: stats}}} -> stats end) end + defp valid_stats?(stats) do + Enum.all?(stats, fn {subset, nodes_stats} -> + expected_stats_length = P2PSampling.list_nodes_to_sample(subset) |> length() + + Enum.map(nodes_stats, fn {_node, stats} -> length(stats) end) + |> Enum.all?(&(&1 == expected_stats_length)) + end) + end + defp aggregate_stats_per_subset(stats) do stats |> Enum.flat_map(& &1) diff --git a/test/archethic/beacon_chain/network_coordinates_test.exs b/test/archethic/beacon_chain/network_coordinates_test.exs index f9dca62b1..16c17278e 100644 --- a/test/archethic/beacon_chain/network_coordinates_test.exs +++ b/test/archethic/beacon_chain/network_coordinates_test.exs @@ -13,7 +13,7 @@ defmodule Archethic.BeaconChain.NetworkCoordinatesTest do import Mox describe "fetch_network_stats/1" do - test "should retrieve the stats for a given summary time" do + setup do beacon_nodes = Enum.map(0..2, fn i -> %Node{ @@ -40,9 +40,11 @@ defmodule Archethic.BeaconChain.NetworkCoordinatesTest do Enum.each(beacon_nodes, &P2P.add_and_connect_node/1) Enum.each(sampled_nodes, &P2P.add_and_connect_node/1) + end + test "should retrieve the stats for a given summary time" do MockClient - |> stub(:send_message, fn + |> expect(:send_message, 3, fn _, %GetNetworkStats{subsets: _}, _ -> {:ok, %NetworkStats{ @@ -77,5 +79,62 @@ defmodule Archethic.BeaconChain.NetworkCoordinatesTest do [90, 105, 90, 0, 0, 0] ]) == NetworkCoordinates.fetch_network_stats(DateTime.utc_now()) end + + test "should filter stats that are different from expected nodes for a subset" do + ok_stats_1 = %NetworkStats{ + stats: %{ + <<0>> => %{ + <<0::8, 0::8, 1::8, "key_b0">> => [%{latency: 100}, %{latency: 100}, %{latency: 100}], + <<0::8, 0::8, 1::8, "key_b1">> => [%{latency: 100}, %{latency: 100}, %{latency: 100}], + <<0::8, 0::8, 1::8, "key_b2">> => [%{latency: 100}, %{latency: 100}, %{latency: 100}] + } + } + } + + ok_stats_2 = %NetworkStats{ + stats: %{ + <<0>> => %{ + <<0::8, 0::8, 1::8, "key_b0">> => [%{latency: 200}, %{latency: 200}, %{latency: 200}], + <<0::8, 0::8, 1::8, "key_b1">> => [%{latency: 200}, %{latency: 200}, %{latency: 200}], + <<0::8, 0::8, 1::8, "key_b2">> => [%{latency: 200}, %{latency: 200}, %{latency: 200}] + } + } + } + + wrong_stats = %NetworkStats{ + stats: %{ + <<0>> => %{ + <<0::8, 0::8, 1::8, "key_b0">> => [%{latency: 100}, %{latency: 200}], + <<0::8, 0::8, 1::8, "key_b1">> => [%{latency: 100}, %{latency: 105}, %{latency: 90}], + <<0::8, 0::8, 1::8, "key_b2">> => [%{latency: 90}, %{latency: 105}, %{latency: 90}] + } + } + } + + wrong_node = P2P.get_node_info!(<<0::8, 0::8, 1::8, "key_b0">>) + ok_node_1 = P2P.get_node_info!(<<0::8, 0::8, 1::8, "key_b1">>) + ok_node_2 = P2P.get_node_info!(<<0::8, 0::8, 1::8, "key_b2">>) + + MockClient + |> expect(:send_message, 3, fn + ^wrong_node, %GetNetworkStats{subsets: _}, _ -> + {:ok, wrong_stats} + + ^ok_node_1, %GetNetworkStats{subsets: _}, _ -> + {:ok, ok_stats_1} + + ^ok_node_2, %GetNetworkStats{subsets: _}, _ -> + {:ok, ok_stats_2} + end) + + assert [ + [0, 0, 0, 150, 150, 150], + [0, 0, 0, 150, 150, 150], + [0, 0, 0, 150, 150, 150], + [150, 150, 150, 0, 0, 0], + [150, 150, 150, 0, 0, 0], + [150, 150, 150, 0, 0, 0] + ] == NetworkCoordinates.fetch_network_stats(DateTime.utc_now()) |> Nx.to_list() + end end end From 5d9e91f85871c4cf03c287ed2b0b4fde6de8a69f Mon Sep 17 00:00:00 2001 From: Neylix Date: Fri, 7 Jul 2023 17:22:07 +0200 Subject: [PATCH 3/3] Verify p2p view when adding summary in aggregate --- .../beacon_chain/summary_aggregate.ex | 70 ++++++-- .../beacon_chain/summary_aggregate_test.exs | 127 ++++++++++--- test/archethic/beacon_chain_test.exs | 168 ++++++------------ 3 files changed, 210 insertions(+), 155 deletions(-) diff --git a/lib/archethic/beacon_chain/summary_aggregate.ex b/lib/archethic/beacon_chain/summary_aggregate.ex index 537dcfe34..31444f7b5 100644 --- a/lib/archethic/beacon_chain/summary_aggregate.ex +++ b/lib/archethic/beacon_chain/summary_aggregate.ex @@ -71,8 +71,8 @@ defmodule Archethic.BeaconChain.SummaryAggregate do ) |> Map.update!(:availability_adding_time, &[availability_adding_time | &1]) - if bit_size(node_availabilities) > 0 or length(node_average_availabilities) > 0 or - length(end_of_node_synchronizations) > 0 do + if node_availabilities != <<>> or not Enum.empty?(node_average_availabilities) or + not Enum.empty?(end_of_node_synchronizations) or not Enum.empty?(network_patches) do update_in( agg, [ @@ -85,22 +85,13 @@ defmodule Archethic.BeaconChain.SummaryAggregate do }) ], fn prev -> - prev - |> Map.update!( - :node_availabilities, - &Enum.concat(&1, [Utils.bitstring_to_integer_list(node_availabilities)]) - ) - |> Map.update!( - :node_average_availabilities, - &Enum.concat(&1, [node_average_availabilities]) - ) - |> Map.update!( - :end_of_node_synchronizations, - &Enum.concat(&1, end_of_node_synchronizations) - ) - |> Map.update!( - :network_patches, - &Enum.concat(&1, [network_patches]) + add_p2p_availabilities( + subset, + prev, + node_availabilities, + node_average_availabilities, + end_of_node_synchronizations, + network_patches ) end ) @@ -109,6 +100,49 @@ defmodule Archethic.BeaconChain.SummaryAggregate do end end + defp add_p2p_availabilities( + subset, + map, + node_availabilities, + node_average_availabilities, + end_of_node_synchronizations, + network_patches + ) do + map = + map + |> Map.update!( + :end_of_node_synchronizations, + &Enum.concat(&1, end_of_node_synchronizations) + ) + |> Map.update!( + :network_patches, + &Enum.concat(&1, [network_patches]) + ) + + expected_subset_length = P2PSampling.list_nodes_to_sample(subset) |> Enum.count() + + map = + if bit_size(node_availabilities) == expected_subset_length do + map + |> Map.update!( + :node_availabilities, + &Enum.concat(&1, [Utils.bitstring_to_integer_list(node_availabilities)]) + ) + else + map + end + + if Enum.count(node_average_availabilities) == expected_subset_length do + map + |> Map.update!( + :node_average_availabilities, + &Enum.concat(&1, [node_average_availabilities]) + ) + else + map + end + end + @doc """ Aggregate summaries batch diff --git a/test/archethic/beacon_chain/summary_aggregate_test.exs b/test/archethic/beacon_chain/summary_aggregate_test.exs index 01919f049..016c4473e 100644 --- a/test/archethic/beacon_chain/summary_aggregate_test.exs +++ b/test/archethic/beacon_chain/summary_aggregate_test.exs @@ -1,30 +1,37 @@ defmodule Archethic.BeaconChain.SummaryAggregateTest do use ArchethicCase + alias Archethic.BeaconChain.Summary alias Archethic.BeaconChain.SummaryAggregate alias Archethic.P2P alias Archethic.P2P.Node alias Archethic.BeaconChain.ReplicationAttestation alias Archethic.TransactionChain.TransactionSummary + import Mock + doctest SummaryAggregate + setup do + P2P.add_and_connect_node(%Node{ + first_public_key: <<0::8, 0::8, 0::8, :crypto.strong_rand_bytes(31)::binary>>, + last_public_key: <<0::8, 0::8, 0::8, :crypto.strong_rand_bytes(31)::binary>>, + ip: {127, 0, 0, 1}, + port: 3000 + }) + + P2P.add_and_connect_node(%Node{ + first_public_key: <<0::8, 0::8, 0::8, :crypto.strong_rand_bytes(31)::binary>>, + last_public_key: <<0::8, 0::8, 0::8, :crypto.strong_rand_bytes(31)::binary>>, + ip: {127, 0, 0, 1}, + port: 3001 + }) + + :ok + end + describe "aggregate/1" do test "should aggregate multiple network patches into a single one" do - P2P.add_and_connect_node(%Node{ - first_public_key: <<0::8, 0::8, 0::8, :crypto.strong_rand_bytes(31)::binary>>, - last_public_key: <<0::8, 0::8, 0::8, :crypto.strong_rand_bytes(31)::binary>>, - ip: {127, 0, 0, 1}, - port: 3000 - }) - - P2P.add_and_connect_node(%Node{ - first_public_key: <<0::8, 0::8, 0::8, :crypto.strong_rand_bytes(31)::binary>>, - last_public_key: <<0::8, 0::8, 0::8, :crypto.strong_rand_bytes(31)::binary>>, - ip: {127, 0, 0, 1}, - port: 3001 - }) - assert %SummaryAggregate{ p2p_availabilities: %{ <<0>> => %{ @@ -49,20 +56,6 @@ defmodule Archethic.BeaconChain.SummaryAggregateTest do end test "should aggregate multiple different network patches into a single one" do - P2P.add_and_connect_node(%Node{ - first_public_key: <<0::8, 0::8, 0::8, :crypto.strong_rand_bytes(31)::binary>>, - last_public_key: <<0::8, 0::8, 0::8, :crypto.strong_rand_bytes(31)::binary>>, - ip: {127, 0, 0, 1}, - port: 3000 - }) - - P2P.add_and_connect_node(%Node{ - first_public_key: <<0::8, 0::8, 0::8, :crypto.strong_rand_bytes(31)::binary>>, - last_public_key: <<0::8, 0::8, 0::8, :crypto.strong_rand_bytes(31)::binary>>, - ip: {127, 0, 0, 1}, - port: 3001 - }) - assert %SummaryAggregate{ p2p_availabilities: %{ <<0>> => %{ @@ -91,4 +84,82 @@ defmodule Archethic.BeaconChain.SummaryAggregateTest do |> SummaryAggregate.aggregate() end end + + describe "add_summary/2" do + setup_with_mocks [ + {ReplicationAttestation, [], validate: fn _ -> :ok end} + ] do + attestation = %ReplicationAttestation{ + transaction_summary: %TransactionSummary{address: "addr1"} + } + + attestation2 = %ReplicationAttestation{ + transaction_summary: %TransactionSummary{address: "addr2"} + } + + aggregate = %SummaryAggregate{ + replication_attestations: [attestation], + p2p_availabilities: %{ + <<0>> => %{ + node_availabilities: [[1, 0]], + node_average_availabilities: [[0.95, 0.7]], + end_of_node_synchronizations: [], + network_patches: [["ABC", "DEF"]] + } + } + } + + {:ok, %{aggregate: aggregate, attestation2: attestation2}} + end + + test "should add summary into aggregate", %{ + aggregate: aggregate = %SummaryAggregate{replication_attestations: previous_attestations}, + attestation2: attestation2 + } do + summary = %Summary{ + subset: <<0>>, + node_availabilities: <<1::1, 1::1>>, + node_average_availabilities: [1, 0.8], + network_patches: ["DEF", "ABC"], + transaction_attestations: [attestation2] + } + + assert %SummaryAggregate{ + replication_attestations: [^attestation2 | ^previous_attestations], + p2p_availabilities: %{ + <<0>> => %{ + node_availabilities: [[1, 0], [1, 1]], + node_average_availabilities: [[0.95, 0.7], [1, 0.8]], + end_of_node_synchronizations: [], + network_patches: [["ABC", "DEF"], ["DEF", "ABC"]] + } + } + } = SummaryAggregate.add_summary(aggregate, summary) + end + + test "should not add p2p view when summary one is invalid", %{ + aggregate: aggregate = %SummaryAggregate{replication_attestations: previous_attestations}, + attestation2: attestation2 + } do + summary = %Summary{ + subset: <<0>>, + node_availabilities: <<1::1>>, + node_average_availabilities: [0.8], + network_patches: ["DEF", "ABC"], + transaction_attestations: [attestation2] + } + + assert %SummaryAggregate{ + replication_attestations: [^attestation2 | ^previous_attestations], + p2p_availabilities: %{ + <<0>> => %{ + node_availabilities: [[1, 0]], + node_average_availabilities: [[0.95, 0.7]], + end_of_node_synchronizations: [], + network_patches: [["ABC", "DEF"], ["DEF", "ABC"]] + } + } + } = SummaryAggregate.add_summary(aggregate, summary) + end + end end diff --git a/test/archethic/beacon_chain_test.exs b/test/archethic/beacon_chain_test.exs index 34e5226d4..abd15de72 100644 --- a/test/archethic/beacon_chain_test.exs +++ b/test/archethic/beacon_chain_test.exs @@ -16,8 +16,6 @@ defmodule Archethic.BeaconChainTest do alias Archethic.Crypto - alias Archethic.Election - alias Archethic.P2P alias Archethic.P2P.Message.GetBeaconSummaries @@ -34,6 +32,7 @@ defmodule Archethic.BeaconChainTest do doctest Archethic.BeaconChain import Mox + import Mock setup do start_supervised!({SlotTimer, interval: "0 0 * * * *"}) @@ -103,19 +102,16 @@ defmodule Archethic.BeaconChainTest do end describe "fetch_and_aggregate_summaries/1" do - setup do + setup_with_mocks [ + {ReplicationAttestation, [:passthrough], validate: fn _ -> :ok end} + ] do summary_time = ~U[2021-01-22 16:12:58Z] - node_keypair1 = Crypto.derive_keypair("node_seed", 1) - node_keypair2 = Crypto.derive_keypair("node_seed", 2) - node_keypair3 = Crypto.derive_keypair("node_seed", 3) - node_keypair4 = Crypto.derive_keypair("node_seed", 4) - node1 = %Node{ ip: {127, 0, 0, 1}, port: 3000, - first_public_key: elem(node_keypair1, 0), - last_public_key: elem(node_keypair1, 0), + first_public_key: <<0::24, :crypto.strong_rand_bytes(31)::binary>>, + last_public_key: <<0::24, :crypto.strong_rand_bytes(31)::binary>>, network_patch: "AAA", geo_patch: "AAA", available?: true, @@ -127,8 +123,8 @@ defmodule Archethic.BeaconChainTest do node2 = %Node{ ip: {127, 0, 0, 1}, port: 3000, - first_public_key: elem(node_keypair2, 0), - last_public_key: elem(node_keypair2, 0), + first_public_key: <<0::24, :crypto.strong_rand_bytes(31)::binary>>, + last_public_key: <<0::24, :crypto.strong_rand_bytes(31)::binary>>, network_patch: "AAA", geo_patch: "AAA", available?: true, @@ -140,8 +136,8 @@ defmodule Archethic.BeaconChainTest do node3 = %Node{ ip: {127, 0, 0, 1}, port: 3000, - first_public_key: elem(node_keypair3, 0), - last_public_key: elem(node_keypair3, 0), + first_public_key: <<0::24, :crypto.strong_rand_bytes(31)::binary>>, + last_public_key: <<0::24, :crypto.strong_rand_bytes(31)::binary>>, network_patch: "AAA", geo_patch: "AAA", available?: true, @@ -153,8 +149,8 @@ defmodule Archethic.BeaconChainTest do node4 = %Node{ ip: {127, 0, 0, 1}, port: 3000, - first_public_key: elem(node_keypair4, 0), - last_public_key: elem(node_keypair4, 0), + first_public_key: <<0::24, :crypto.strong_rand_bytes(31)::binary>>, + last_public_key: <<0::24, :crypto.strong_rand_bytes(31)::binary>>, network_patch: "AAA", geo_patch: "AAA", available?: true, @@ -177,10 +173,7 @@ defmodule Archethic.BeaconChainTest do {:ok, %{summary_time: summary_time, nodes: [node1, node2, node3, node4]}} end - test "should download the beacon summary", %{ - summary_time: summary_time, - nodes: [node1, node2, node3, node4] - } do + test "should download the beacon summary", %{summary_time: summary_time} do addr1 = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> tx_summary = %TransactionSummary{ @@ -191,24 +184,12 @@ defmodule Archethic.BeaconChainTest do validation_stamp_checksum: :crypto.strong_rand_bytes(32) } - nodes = P2P.authorized_and_available_nodes() |> Enum.sort_by(& &1.first_public_key) + attestation = %ReplicationAttestation{transaction_summary: tx_summary, confirmations: []} beacon_summary = %Summary{ - subset: "A", + subset: <<0>>, summary_time: summary_time, - transaction_attestations: [ - %ReplicationAttestation{ - transaction_summary: tx_summary, - confirmations: - [node1, node2, node3, node4] - |> Enum.with_index(1) - |> Enum.map(fn {node, index} -> - node_index = Enum.find_index(nodes, &(&1 == node)) - {_, pv} = Crypto.derive_keypair("node_seed", index) - {node_index, Crypto.sign(TransactionSummary.serialize(tx_summary), pv)} - end) - } - ] + transaction_attestations: [attestation] } MockClient @@ -227,6 +208,7 @@ defmodule Archethic.BeaconChainTest do ) |> SummaryAggregate.aggregate() + assert_called(ReplicationAttestation.validate(attestation)) assert [addr1] == Enum.map(attestations, & &1.transaction_summary.address) end @@ -237,9 +219,7 @@ defmodule Archethic.BeaconChainTest do addr1 = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> addr2 = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> - storage_nodes = Election.chain_storage_nodes(addr1, P2P.authorized_and_available_nodes()) - - tx_summary = %TransactionSummary{ + tx_summary1 = %TransactionSummary{ address: addr1, timestamp: DateTime.utc_now(), type: :transfer, @@ -247,57 +227,27 @@ defmodule Archethic.BeaconChainTest do validation_stamp_checksum: :crypto.strong_rand_bytes(32) } + tx_summary2 = %TransactionSummary{ + address: addr2, + timestamp: DateTime.utc_now(), + type: :transfer, + fee: 100_000_000, + validation_stamp_checksum: :crypto.strong_rand_bytes(32) + } + + attestation1 = %ReplicationAttestation{transaction_summary: tx_summary1, confirmations: []} + attestation2 = %ReplicationAttestation{transaction_summary: tx_summary2, confirmations: []} + summary_v1 = %Summary{ - subset: "A", + subset: <<0>>, summary_time: summary_time, - transaction_attestations: [ - %ReplicationAttestation{ - transaction_summary: tx_summary, - confirmations: - [node1, node2, node3, node4] - |> Enum.with_index(1) - |> Enum.map(fn {node, index} -> - node_index = Enum.find_index(storage_nodes, &(&1 == node)) - {_, pv} = Crypto.derive_keypair("node_seed", index) - {node_index, Crypto.sign(TransactionSummary.serialize(tx_summary), pv)} - end) - } - ] + transaction_attestations: [attestation1] } summary_v2 = %Summary{ - subset: "A", + subset: <<0>>, summary_time: summary_time, - transaction_attestations: [ - %ReplicationAttestation{ - transaction_summary: tx_summary, - confirmations: - [node1, node2, node3, node4] - |> Enum.with_index(1) - |> Enum.map(fn {node, index} -> - node_index = Enum.find_index(storage_nodes, &(&1 == node)) - {_, pv} = Crypto.derive_keypair("node_seed", index) - {node_index, Crypto.sign(TransactionSummary.serialize(tx_summary), pv)} - end) - }, - %ReplicationAttestation{ - transaction_summary: %TransactionSummary{ - address: addr2, - timestamp: DateTime.utc_now(), - type: :transfer, - fee: 100_000_000, - validation_stamp_checksum: :crypto.strong_rand_bytes(32) - }, - confirmations: - [node1, node2, node3, node4] - |> Enum.with_index(1) - |> Enum.map(fn {node, index} -> - node_index = Enum.find_index(storage_nodes, &(&1 == node)) - {_, pv} = Crypto.derive_keypair("node_seed", index) - {node_index, Crypto.sign(TransactionSummary.serialize(tx_summary), pv)} - end) - } - ] + transaction_attestations: [attestation1, attestation2] } MockClient @@ -313,9 +263,6 @@ defmodule Archethic.BeaconChainTest do ^node4, %GetBeaconSummaries{}, _ -> {:ok, %BeaconSummaryList{summaries: [summary_v2]}} - - _, %GetTransactionSummary{}, _ -> - {:ok, tx_summary} end) %SummaryAggregate{replication_attestations: attestations} = @@ -327,6 +274,9 @@ defmodule Archethic.BeaconChainTest do transaction_addresses = Enum.map(attestations, & &1.transaction_summary.address) + assert_called(ReplicationAttestation.validate(attestation1)) + assert_called(ReplicationAttestation.validate(attestation2)) + assert Enum.all?(transaction_addresses, &(&1 in [addr1, addr2])) end @@ -335,34 +285,34 @@ defmodule Archethic.BeaconChainTest do nodes: [node1, node2, node3, node4] } do summary_v1 = %Summary{ - subset: "A", + subset: <<0>>, summary_time: summary_time, - node_availabilities: <<1::1, 1::1, 0::1>>, + node_availabilities: <<1::1, 1::1, 0::1, 0::1>>, end_of_node_synchronizations: [] } summary_v2 = %Summary{ - subset: "A", + subset: <<0>>, summary_time: summary_time, - node_availabilities: <<1::1, 1::1, 1::1>>, + node_availabilities: <<1::1, 1::1, 1::1, 0::1>>, end_of_node_synchronizations: [] } summary_v3 = %Summary{ - subset: "A", + subset: <<0>>, summary_time: summary_time, - node_availabilities: <<1::1, 0::1, 1::1>>, + node_availabilities: <<1::1, 0::1, 1::1, 0::1>>, end_of_node_synchronizations: [] } summary_v4 = %Summary{ - subset: "A", + subset: <<0>>, summary_time: summary_time, - node_availabilities: <<1::1, 1::1, 0::1>>, + node_availabilities: <<1::1, 1::1, 0::1, 1::1>>, end_of_node_synchronizations: [] } - subset_address = Crypto.derive_beacon_chain_address("A", summary_time, true) + subset_address = Crypto.derive_beacon_chain_address(<<0>>, summary_time, true) MockClient |> stub(:send_message, fn @@ -408,7 +358,7 @@ defmodule Archethic.BeaconChainTest do end) assert %SummaryAggregate{ - p2p_availabilities: %{"A" => %{node_availabilities: node_availabilities}} + p2p_availabilities: %{<<0>> => %{node_availabilities: node_availabilities}} } = BeaconChain.fetch_and_aggregate_summaries( summary_time, @@ -416,7 +366,7 @@ defmodule Archethic.BeaconChainTest do ) |> SummaryAggregate.aggregate() - assert <<1::1, 1::1, 1::1>> == node_availabilities + assert <<1::1, 1::1, 1::1, 0::1>> == node_availabilities end test "should find other beacon summaries and accumulate node P2P avg availabilities", %{ @@ -424,31 +374,31 @@ defmodule Archethic.BeaconChainTest do nodes: [node1, node2, node3, node4] } do summary_v1 = %Summary{ - subset: "A", + subset: <<0>>, summary_time: summary_time, node_average_availabilities: [1.0, 0.9, 1.0, 1.0] } summary_v2 = %Summary{ - subset: "A", + subset: <<0>>, summary_time: summary_time, node_average_availabilities: [0.90, 0.9, 1.0, 1.0] } summary_v3 = %Summary{ - subset: "A", + subset: <<0>>, summary_time: summary_time, node_average_availabilities: [0.8, 0.9, 0.7, 1.0] } summary_v4 = %Summary{ - subset: "A", + subset: <<0>>, summary_time: summary_time, node_availabilities: <<1::1, 1::1, 0::1>>, node_average_availabilities: [1.0, 0.5, 1.0, 0.4] } - subset_address = Crypto.derive_beacon_chain_address("A", summary_time, true) + subset_address = Crypto.derive_beacon_chain_address(<<0>>, summary_time, true) MockClient |> stub(:send_message, fn @@ -495,7 +445,7 @@ defmodule Archethic.BeaconChainTest do assert %SummaryAggregate{ p2p_availabilities: %{ - "A" => %{node_average_availabilities: node_average_availabilities} + <<0>> => %{node_average_availabilities: node_average_availabilities} } } = BeaconChain.fetch_and_aggregate_summaries( @@ -512,34 +462,34 @@ defmodule Archethic.BeaconChainTest do nodes: [node1, node2, node3, node4] } do summary_v1 = %Summary{ - subset: "A", + subset: <<0>>, summary_time: summary_time, node_availabilities: <<1::1, 1::1>>, network_patches: ["ABC", "DEF"] } summary_v2 = %Summary{ - subset: "A", + subset: <<0>>, summary_time: summary_time, node_availabilities: <<1::1, 1::1>>, network_patches: ["ABC", "DEF"] } summary_v3 = %Summary{ - subset: "A", + subset: <<0>>, summary_time: summary_time, node_availabilities: <<1::1, 1::1>>, network_patches: ["ABC", "DEF"] } summary_v4 = %Summary{ - subset: "A", + subset: <<0>>, summary_time: summary_time, node_availabilities: <<1::1, 1::1>>, network_patches: ["ABC", "DEF"] } - subset_address = Crypto.derive_beacon_chain_address("A", summary_time, true) + subset_address = Crypto.derive_beacon_chain_address(<<0>>, summary_time, true) MockClient |> stub(:send_message, fn @@ -586,7 +536,7 @@ defmodule Archethic.BeaconChainTest do assert %SummaryAggregate{ p2p_availabilities: %{ - "A" => %{ + <<0>> => %{ network_patches: [ ["ABC", "DEF"], ["ABC", "DEF"],