From 22aedab81d126e43f7562e50799eac0b4adbb86f Mon Sep 17 00:00:00 2001 From: Wassim Mansouri Date: Tue, 5 Nov 2024 15:59:44 +0100 Subject: [PATCH] Add timestamp in node validation election --- lib/archethic.ex | 3 +- lib/archethic/election.ex | 5 +- lib/archethic/mining.ex | 17 +- lib/archethic/mining/distributed_workflow.ex | 4 +- lib/archethic/p2p/message/start_mining.ex | 6 +- test/archethic/election_test.exs | 313 ++++++++++++------ .../mining/validation_context_test.exs | 16 +- test/support/transaction_factory.ex | 11 +- 8 files changed, 248 insertions(+), 127 deletions(-) diff --git a/lib/archethic.ex b/lib/archethic.ex index 99ad0b23f..b92df3542 100644 --- a/lib/archethic.ex +++ b/lib/archethic.ex @@ -74,10 +74,11 @@ defmodule Archethic do welcome_node_key = Keyword.get(opts, :welcome_node_key, Crypto.first_node_public_key()) contract_context = Keyword.get(opts, :contract_context, nil) forward? = Keyword.get(opts, :forward?, false) + ref_timestamp = DateTime.utc_now() cond do P2P.authorized_and_available_node?() and shared_secret_synced?() -> - validation_nodes = Mining.get_validation_nodes(tx) + validation_nodes = Mining.get_validation_nodes(tx, ref_timestamp) responses = %{already_locked?: already_locked?} = diff --git a/lib/archethic/election.ex b/lib/archethic/election.ex index a7fd9c678..5555bbc1c 100755 --- a/lib/archethic/election.ex +++ b/lib/archethic/election.ex @@ -23,10 +23,13 @@ defmodule Archethic.Election do """ @spec validation_nodes_election_seed_sorting(Transaction.t(), DateTime.t()) :: binary() def validation_nodes_election_seed_sorting(tx = %Transaction{}, timestamp = %DateTime{}) do - tx_hash = + serialized_tx = tx |> Transaction.to_pending() |> Transaction.serialize() + + tx_hash = + <> |> Crypto.hash() Crypto.sign_with_daily_nonce_key(tx_hash, timestamp) diff --git a/lib/archethic/mining.ex b/lib/archethic/mining.ex index b5e61c72e..43090261b 100644 --- a/lib/archethic/mining.ex +++ b/lib/archethic/mining.ex @@ -80,11 +80,13 @@ defmodule Archethic.Mining do @doc """ Elect validation nodes for a transaction """ - def get_validation_nodes(tx = %Transaction{address: tx_address, validation_stamp: nil}) do - current_date = DateTime.utc_now() - sorting_seed = Election.validation_nodes_election_seed_sorting(tx, current_date) + def get_validation_nodes( + tx = %Transaction{address: tx_address, validation_stamp: nil}, + ref_timestamp + ) do + sorting_seed = Election.validation_nodes_election_seed_sorting(tx, ref_timestamp) - node_list = P2P.authorized_and_available_nodes(current_date) + node_list = P2P.authorized_and_available_nodes() storage_nodes = Election.chain_storage_nodes(tx_address, node_list) @@ -100,10 +102,11 @@ defmodule Archethic.Mining do @doc """ Determines if the election of validation nodes performed by the welcome node is valid """ - @spec valid_election?(Transaction.t(), list(Crypto.key())) :: boolean() - def valid_election?(tx, validation_node_public_keys) + @spec valid_election?(Transaction.t(), list(Crypto.key()), ref_timestamp :: DateTime.t()) :: + boolean() + def valid_election?(tx, validation_node_public_keys, ref_timestamp) when is_list(validation_node_public_keys) do - validation_nodes = get_validation_nodes(tx) + validation_nodes = get_validation_nodes(tx, ref_timestamp) validation_node_public_keys == Enum.map(validation_nodes, & &1.last_public_key) end diff --git a/lib/archethic/mining/distributed_workflow.ex b/lib/archethic/mining/distributed_workflow.ex index b27c437a3..0dce7fd31 100644 --- a/lib/archethic/mining/distributed_workflow.ex +++ b/lib/archethic/mining/distributed_workflow.ex @@ -218,9 +218,9 @@ defmodule Archethic.Mining.DistributedWorkflow do :internal, {:start_mining, tx, welcome_node, validation_nodes, contract_context}, :idle, - data = %{node_public_key: node_public_key} + data = %{ref_timestamp: ref_timestamp, node_public_key: node_public_key} ) do - validation_time = DateTime.utc_now() |> DateTime.truncate(:millisecond) + validation_time = ref_timestamp |> DateTime.truncate(:millisecond) authorized_nodes = P2P.authorized_and_available_nodes(validation_time) diff --git a/lib/archethic/p2p/message/start_mining.ex b/lib/archethic/p2p/message/start_mining.ex index 359cc2a62..615b01f3f 100644 --- a/lib/archethic/p2p/message/start_mining.ex +++ b/lib/archethic/p2p/message/start_mining.ex @@ -64,7 +64,7 @@ defmodule Archethic.P2P.Message.StartMining do ) do with :ok <- check_ref_timestamp(ref_timestamp), :ok <- check_synchronization(network_chains_view_hash, p2p_view_hash), - :ok <- check_valid_election(tx, validation_nodes), + :ok <- check_valid_election(tx, validation_nodes, ref_timestamp), :ok <- check_current_node_is_elected(validation_nodes), :ok <- check_not_already_mining(tx.address), :ok <- Mining.request_chain_lock(tx) do @@ -212,8 +212,8 @@ defmodule Archethic.P2P.Message.StartMining do end end - defp check_valid_election(tx, validation_nodes) do - if Mining.valid_election?(tx, validation_nodes) do + defp check_valid_election(tx, validation_nodes, ref_timestamp) do + if Mining.valid_election?(tx, validation_nodes, ref_timestamp) do :ok else {:error, :invalid_validation_nodes_election} diff --git a/test/archethic/election_test.exs b/test/archethic/election_test.exs index 794e2eab5..4599ebb4d 100644 --- a/test/archethic/election_test.exs +++ b/test/archethic/election_test.exs @@ -16,117 +16,201 @@ defmodule Archethic.ElectionTest do doctest Election - describe "validation_nodes/4" do - test "should change for new transaction" do - authorized_nodes = [ - %Node{ - first_public_key: "Node0", - last_public_key: "Node0", - available?: true, - geo_patch: "AAA" - }, - %Node{ - first_public_key: "Node1", - last_public_key: "Node1", - available?: true, - geo_patch: "CCC" - }, - %Node{ - first_public_key: "Node2", - last_public_key: "Node2", - available?: true, - geo_patch: "CCC" - }, - %Node{ - first_public_key: "Node3", - last_public_key: "Node3", - available?: true, - geo_patch: "F24" - } - ] - - storage_nodes = [ - %Node{ - first_public_key: "Node10", - last_public_key: "Node10", - available?: true, - geo_patch: "AAA" - }, - %Node{ - first_public_key: "Node11", - last_public_key: "Node11", - available?: true, - geo_patch: "CCC" - }, - %Node{ - first_public_key: "Node12", - last_public_key: "Node12", - available?: true, - geo_patch: "CCC" - }, - %Node{ - first_public_key: "Node13", - last_public_key: "Node13", - available?: true, - geo_patch: "F24" - } - ] + setup do + authorized_nodes = [ + %Node{ + first_public_key: "Node0", + last_public_key: "Node0", + available?: true, + geo_patch: "AAA" + }, + %Node{ + first_public_key: "Node1", + last_public_key: "Node1", + available?: true, + geo_patch: "CCC" + }, + %Node{ + first_public_key: "Node2", + last_public_key: "Node2", + available?: true, + geo_patch: "CCC" + }, + %Node{ + first_public_key: "Node3", + last_public_key: "Node3", + available?: true, + geo_patch: "F24" + } + ] - tx1 = %Transaction{ - address: - <<0, 120, 195, 32, 77, 84, 215, 196, 116, 215, 56, 141, 40, 54, 226, 48, 66, 254, 119, - 11, 73, 77, 243, 125, 62, 94, 133, 67, 9, 253, 45, 134, 89>>, - type: :transfer, - data: %TransactionData{}, - previous_public_key: - <<0, 239, 240, 90, 182, 66, 190, 68, 20, 250, 131, 83, 190, 29, 184, 177, 52, 166, 207, - 80, 193, 110, 57, 6, 199, 152, 184, 24, 178, 179, 11, 164, 150>>, - previous_signature: - <<200, 70, 0, 25, 105, 111, 15, 161, 146, 188, 100, 234, 147, 62, 127, 8, 152, 60, 66, - 169, 113, 255, 51, 112, 59, 200, 61, 63, 128, 228, 111, 104, 47, 15, 81, 185, 179, 36, - 59, 86, 171, 7, 138, 199, 203, 252, 50, 87, 160, 107, 119, 131, 121, 11, 239, 169, 99, - 203, 76, 159, 158, 243, 133, 133>>, - origin_signature: - <<162, 223, 100, 72, 17, 56, 99, 212, 78, 132, 166, 81, 127, 91, 214, 143, 221, 32, 106, - 189, 247, 64, 183, 27, 55, 142, 254, 72, 47, 215, 34, 108, 233, 55, 35, 94, 49, 165, - 180, 248, 229, 160, 229, 220, 191, 35, 80, 127, 213, 240, 195, 185, 165, 89, 172, 97, - 170, 217, 57, 254, 125, 127, 62, 169>> + storage_nodes = [ + %Node{ + first_public_key: "Node10", + last_public_key: "Node10", + available?: true, + geo_patch: "AAA" + }, + %Node{ + first_public_key: "Node11", + last_public_key: "Node11", + available?: true, + geo_patch: "CCC" + }, + %Node{ + first_public_key: "Node12", + last_public_key: "Node12", + available?: true, + geo_patch: "CCC" + }, + %Node{ + first_public_key: "Node13", + last_public_key: "Node13", + available?: true, + geo_patch: "F24" } + ] + + tx1 = %Transaction{ + address: + <<0, 120, 195, 32, 77, 84, 215, 196, 116, 215, 56, 141, 40, 54, 226, 48, 66, 254, 119, 11, + 73, 77, 243, 125, 62, 94, 133, 67, 9, 253, 45, 134, 89>>, + type: :transfer, + data: %TransactionData{}, + previous_public_key: + <<0, 239, 240, 90, 182, 66, 190, 68, 20, 250, 131, 83, 190, 29, 184, 177, 52, 166, 207, + 80, 193, 110, 57, 6, 199, 152, 184, 24, 178, 179, 11, 164, 150>>, + previous_signature: + <<200, 70, 0, 25, 105, 111, 15, 161, 146, 188, 100, 234, 147, 62, 127, 8, 152, 60, 66, + 169, 113, 255, 51, 112, 59, 200, 61, 63, 128, 228, 111, 104, 47, 15, 81, 185, 179, 36, + 59, 86, 171, 7, 138, 199, 203, 252, 50, 87, 160, 107, 119, 131, 121, 11, 239, 169, 99, + 203, 76, 159, 158, 243, 133, 133>>, + origin_signature: + <<162, 223, 100, 72, 17, 56, 99, 212, 78, 132, 166, 81, 127, 91, 214, 143, 221, 32, 106, + 189, 247, 64, 183, 27, 55, 142, 254, 72, 47, 215, 34, 108, 233, 55, 35, 94, 49, 165, + 180, 248, 229, 160, 229, 220, 191, 35, 80, 127, 213, 240, 195, 185, 165, 89, 172, 97, + 170, 217, 57, 254, 125, 127, 62, 169>> + } + + tx2 = %Transaction{ + address: + <<0, 121, 194, 31, 76, 85, 216, 195, 115, 214, 57, 140, 41, 55, 225, 49, 67, 255, 118, 12, + 72, 76, 242, 124, 63, 95, 132, 66, 8, 252, 46, 135, 88>>, + type: :transfer, + data: %TransactionData{}, + previous_public_key: + <<0, 238, 241, 91, 183, 67, 191, 69, 21, 251, 130, 82, 191, 28, 185, 176, 53, 167, 206, + 81, 192, 111, 56, 7, 198, 153, 185, 19, 179, 178, 10, 165, 151>>, + previous_signature: + <<199, 71, 1, 24, 104, 110, 14, 160, 147, 189, 101, 235, 146, 61, 126, 9, 153, 61, 67, + 168, 112, 254, 52, 113, 58, 199, 60, 62, 129, 229, 110, 105, 46, 14, 80, 184, 178, 35, + 58, 85, 172, 6, 139, 198, 202, 253, 51, 86, 161, 106, 118, 132, 120, 10, 238, 168, 98, + 202, 75, 158, 159, 242, 132, 134>>, + origin_signature: + <<163, 222, 101, 73, 16, 57, 98, 213, 79, 133, 167, 82, 126, 90, 215, 142, 220, 31, 107, + 188, 246, 65, 182, 26, 54, 143, 255, 71, 46, 214, 33, 109, 232, 56, 34, 93, 50, 164, + 181, 249, 228, 161, 228, 221, 190, 34, 81, 126, 212, 241, 194, 184, 164, 88, 173, 96, + 171, 216, 56, 253, 126, 126, 63, 168>> + } + + %{ + authorized_nodes: authorized_nodes, + storage_nodes: storage_nodes, + tx1: tx1, + tx2: tx2 + } + end + + describe "validation_nodes_election_seed_sorting/2" do + test "should change for different transactions", %{ + tx1: tx1, + tx2: tx2 + } do + timestamp = ~U[2024-11-04 13:41:18.280699Z] + + assert Election.validation_nodes_election_seed_sorting(tx1, timestamp) != + Election.validation_nodes_election_seed_sorting(tx2, timestamp) + end + + test "should change for same transaction with different timestamps", %{ + tx1: tx1 + } do + # simulate two diffrenet transaction mining timestamps + ref_timestamp_first_attempt = ~U[2024-11-04 13:41:18.280699Z] + + ref_timestamp_second_attempt = ~U[2024-11-04 13:41:50.280699Z] + + assert Election.validation_nodes_election_seed_sorting(tx1, ref_timestamp_first_attempt) != + Election.validation_nodes_election_seed_sorting(tx1, ref_timestamp_second_attempt) + end + + test "should not change for same transaction with same timestamp", %{ + tx1: tx1 + } do + timestamp = ~U[2024-11-04 13:41:18.280699Z] + + assert Election.validation_nodes_election_seed_sorting(tx1, timestamp) == + Election.validation_nodes_election_seed_sorting(tx1, timestamp) + end + end + + describe "validation_nodes/4" do + test "should change for new transaction", + %{ + authorized_nodes: authorized_nodes, + storage_nodes: storage_nodes, + tx1: tx1, + tx2: tx2 + } do + ref_timestamp = ~U[2024-11-04 13:41:18.280699Z] first_election = Election.validation_nodes( tx1, - "sorting_seed", + Election.validation_nodes_election_seed_sorting(tx1, ref_timestamp), authorized_nodes, storage_nodes, ValidationConstraints.new() ) - tx2 = %Transaction{ - address: - <<0, 120, 195, 32, 77, 84, 215, 196, 116, 215, 56, 141, 40, 54, 226, 48, 66, 254, 119, - 11, 73, 77, 243, 125, 62, 94, 133, 67, 9, 253, 45, 134, 89>>, - type: :transfer, - data: %TransactionData{}, - previous_public_key: - <<0, 239, 240, 90, 182, 66, 190, 68, 20, 250, 131, 83, 190, 29, 184, 177, 52, 166, 207, - 80, 193, 110, 57, 6, 199, 152, 184, 24, 178, 179, 11, 164, 150>>, - previous_signature: - <<200, 70, 0, 25, 105, 111, 15, 161, 146, 188, 100, 234, 147, 62, 127, 8, 152, 60, 66, - 169, 113, 255, 51, 112, 59, 200, 61, 63, 128, 228, 111, 104, 47, 15, 81, 185, 179, 36, - 59, 86, 171, 7, 138, 199, 203, 252, 50, 87, 160, 107, 119, 131, 121, 11, 239, 169, 99, - 203, 76, 159, 158, 243, 133, 133>>, - origin_signature: - <<162, 223, 100, 72, 17, 56, 99, 212, 78, 132, 166, 81, 127, 91, 214, 143, 221, 32, 106, - 189, 247, 64, 183, 27, 55, 142, 254, 72, 47, 215, 34, 108, 233, 55, 35, 94, 49, 165, - 180, 248, 229, 160, 229, 220, 191, 35, 80, 127, 213, 240, 195, 185, 165, 89, 172, 97, - 170, 217, 57, 254, 125, 127, 62, 169>> - } - second_election = Election.validation_nodes( tx2, - "daily_nonce_proof", + Election.validation_nodes_election_seed_sorting(tx2, ref_timestamp), + authorized_nodes, + storage_nodes, + ValidationConstraints.new() + ) + + assert Enum.map(first_election, & &1.last_public_key) != + Enum.map(second_election, & &1.last_public_key) + end + + test "should change for same transaction with different timestamp", %{ + authorized_nodes: authorized_nodes, + storage_nodes: storage_nodes, + tx1: tx1 + } do + tx = tx1 + + # simulate two diffrenet transaction mining timestamps + ref_timestamp_first_attempt = ~U[2024-11-04 13:41:18.280699Z] + + ref_timestamp_second_attempt = ~U[2024-11-04 13:41:50.280699Z] + + first_election = + Election.validation_nodes( + tx, + Election.validation_nodes_election_seed_sorting(tx, ref_timestamp_first_attempt), + authorized_nodes, + storage_nodes, + ValidationConstraints.new() + ) + + second_election = + Election.validation_nodes( + tx, + Election.validation_nodes_election_seed_sorting(tx, ref_timestamp_second_attempt), authorized_nodes, storage_nodes, ValidationConstraints.new() @@ -136,6 +220,37 @@ defmodule Archethic.ElectionTest do Enum.map(second_election, & &1.last_public_key) end + test "should not change for same transaction with same timestamp", %{ + authorized_nodes: authorized_nodes, + storage_nodes: storage_nodes, + tx1: tx1 + } do + tx = tx1 + + ref_timestamp = ~U[2024-11-04 13:41:18.280699Z] + + first_election = + Election.validation_nodes( + tx, + Election.validation_nodes_election_seed_sorting(tx, ref_timestamp), + authorized_nodes, + storage_nodes, + ValidationConstraints.new() + ) + + second_election = + Election.validation_nodes( + tx, + Election.validation_nodes_election_seed_sorting(tx, ref_timestamp), + authorized_nodes, + storage_nodes, + ValidationConstraints.new() + ) + + assert Enum.map(first_election, & &1.last_public_key) == + Enum.map(second_election, & &1.last_public_key) + end + test "should never return more validation nodes than storages nodes" do authorized_nodes = [ %Node{ @@ -211,11 +326,13 @@ defmodule Archethic.ElectionTest do 170, 217, 57, 254, 125, 127, 62, 169>> } + ref_timestamp = ~U[2024-11-04 13:41:18.280699Z] + assert 3 == length( Election.validation_nodes( tx1, - "sorting_seed", + Election.validation_nodes_election_seed_sorting(tx1, ref_timestamp), authorized_nodes, storage_nodes, ValidationConstraints.new() diff --git a/test/archethic/mining/validation_context_test.exs b/test/archethic/mining/validation_context_test.exs index 02d9b936e..ee7df14f1 100644 --- a/test/archethic/mining/validation_context_test.exs +++ b/test/archethic/mining/validation_context_test.exs @@ -556,7 +556,7 @@ defmodule Archethic.Mining.ValidationContextTest do timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), - proof_of_election: Election.validation_nodes_election_seed_sorting(tx, DateTime.utc_now()), + proof_of_election: Election.validation_nodes_election_seed_sorting(tx, timestamp), ledger_operations: ledger_operations, signature: :crypto.strong_rand_bytes(32), protocol_version: current_protocol_version() @@ -588,7 +588,7 @@ defmodule Archethic.Mining.ValidationContextTest do timestamp: timestamp, proof_of_work: <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>, proof_of_integrity: TransactionChain.proof_of_integrity([tx]), - proof_of_election: Election.validation_nodes_election_seed_sorting(tx, DateTime.utc_now()), + proof_of_election: Election.validation_nodes_election_seed_sorting(tx, timestamp), ledger_operations: ledger_operations, protocol_version: current_protocol_version() } @@ -620,7 +620,7 @@ defmodule Archethic.Mining.ValidationContextTest do timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), - proof_of_election: Election.validation_nodes_election_seed_sorting(tx, DateTime.utc_now()), + proof_of_election: Election.validation_nodes_election_seed_sorting(tx, timestamp), ledger_operations: ledger_operations, protocol_version: current_protocol_version() } @@ -653,7 +653,7 @@ defmodule Archethic.Mining.ValidationContextTest do timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), - proof_of_election: Election.validation_nodes_election_seed_sorting(tx, DateTime.utc_now()), + proof_of_election: Election.validation_nodes_election_seed_sorting(tx, timestamp), ledger_operations: ledger_operations, protocol_version: current_protocol_version() } @@ -687,7 +687,7 @@ defmodule Archethic.Mining.ValidationContextTest do timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), - proof_of_election: Election.validation_nodes_election_seed_sorting(tx, DateTime.utc_now()), + proof_of_election: Election.validation_nodes_election_seed_sorting(tx, timestamp), ledger_operations: ledger_operations, protocol_version: current_protocol_version() } @@ -703,7 +703,7 @@ defmodule Archethic.Mining.ValidationContextTest do timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), - proof_of_election: Election.validation_nodes_election_seed_sorting(tx, DateTime.utc_now()), + proof_of_election: Election.validation_nodes_election_seed_sorting(tx, timestamp), ledger_operations: %LedgerOperations{ fee: Fee.calculate(tx, nil, 0.07, timestamp, nil, 0, current_protocol_version()), transaction_movements: Transaction.get_movements(tx), @@ -746,7 +746,7 @@ defmodule Archethic.Mining.ValidationContextTest do timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), - proof_of_election: Election.validation_nodes_election_seed_sorting(tx, DateTime.utc_now()), + proof_of_election: Election.validation_nodes_election_seed_sorting(tx, timestamp), ledger_operations: ledger_operations, error: :invalid_pending_transaction, protocol_version: current_protocol_version() @@ -790,7 +790,7 @@ defmodule Archethic.Mining.ValidationContextTest do timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), - proof_of_election: Election.validation_nodes_election_seed_sorting(tx, DateTime.utc_now()), + proof_of_election: Election.validation_nodes_election_seed_sorting(tx, timestamp), ledger_operations: ledger_operations, protocol_version: current_protocol_version() } diff --git a/test/support/transaction_factory.ex b/test/support/transaction_factory.ex index daa37e749..5ce213d04 100644 --- a/test/support/transaction_factory.ex +++ b/test/support/transaction_factory.ex @@ -116,8 +116,7 @@ defmodule Archethic.TransactionFactory do %ValidationStamp{ timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), - proof_of_election: - Election.validation_nodes_election_seed_sorting(tx, DateTime.utc_now()), + proof_of_election: Election.validation_nodes_election_seed_sorting(tx, timestamp), proof_of_integrity: poi, ledger_operations: ledger_operations, protocol_version: protocol_version, @@ -262,7 +261,7 @@ defmodule Archethic.TransactionFactory do timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), - proof_of_election: Election.validation_nodes_election_seed_sorting(tx, DateTime.utc_now()), + proof_of_election: Election.validation_nodes_election_seed_sorting(tx, timestamp), ledger_operations: ledger_operations, signature: :crypto.strong_rand_bytes(32), protocol_version: protocol_version @@ -303,8 +302,7 @@ defmodule Archethic.TransactionFactory do %ValidationStamp{ timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), - proof_of_election: - Election.validation_nodes_election_seed_sorting(tx, DateTime.utc_now()), + proof_of_election: Election.validation_nodes_election_seed_sorting(tx, timestamp), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), ledger_operations: ledger_operations, protocol_version: protocol_version @@ -348,8 +346,7 @@ defmodule Archethic.TransactionFactory do timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), - proof_of_election: - Election.validation_nodes_election_seed_sorting(tx, DateTime.utc_now()), + proof_of_election: Election.validation_nodes_election_seed_sorting(tx, timestamp), ledger_operations: ledger_operations, protocol_version: protocol_version }