From 723ec6ac9569c459ba77c90b8bf32f66e49ec5e3 Mon Sep 17 00:00:00 2001 From: bchamagne Date: Mon, 26 Aug 2024 16:52:20 +0200 Subject: [PATCH 1/2] add inconsistency of type :invalid_origin_signature --- lib/archethic/mining/error.ex | 14 ++++-- lib/archethic/mining/validation_context.ex | 29 +++++++++++- .../transaction/validation_stamp.ex | 3 ++ .../mining/distributed_workflow_test.exs | 3 +- .../pending_transaction_validation_test.exs | 45 ++++++++++--------- .../mining/standalone_workflow_test.exs | 8 ++++ .../mining/validation_context_test.exs | 41 ++++++++++++++++- 7 files changed, 114 insertions(+), 29 deletions(-) diff --git a/lib/archethic/mining/error.ex b/lib/archethic/mining/error.ex index 426ff0654..213e8d387 100644 --- a/lib/archethic/mining/error.ex +++ b/lib/archethic/mining/error.ex @@ -39,7 +39,8 @@ defmodule Archethic.Mining.Error do defp get_error_code_message(:invalid_pending_transaction), do: {-30100, "Invalid transaction data"} - defp get_error_code_message(:insufficient_funds), do: {-31000, "Insufficient funds"} + defp get_error_code_message(:insufficient_funds), + do: {-31000, "Insufficient funds"} defp get_error_code_message(:invalid_inherit_constraints), do: {-31001, "Invalid contract inherit condition"} @@ -56,8 +57,14 @@ defmodule Archethic.Mining.Error do defp get_error_code_message(:invalid_contract_context_inputs), do: {-31500, "Invalid contract context inputs"} - defp get_error_code_message(:consensus_not_reached), do: {-31501, "Consensus not reached"} - defp get_error_code_message(:timeout), do: {-31502, "Transaction validation timeout"} + defp get_error_code_message(:consensus_not_reached), + do: {-31501, "Consensus not reached"} + + defp get_error_code_message(:timeout), + do: {-31502, "Transaction validation timeout"} + + defp get_error_code_message(:invalid_origin_signature), + do: {-31503, "Invalid origin signature"} @doc """ Return the context of the error. @@ -79,6 +86,7 @@ defmodule Archethic.Mining.Error do def to_stamp_error(%__MODULE__{code: -31003}), do: :invalid_recipients_execution def to_stamp_error(%__MODULE__{code: -31004}), do: :recipients_not_distinct def to_stamp_error(%__MODULE__{code: -31500}), do: :invalid_contract_context_inputs + def to_stamp_error(%__MODULE__{code: -31503}), do: :invalid_origin_signature def to_stamp_error(_), do: nil @doc """ diff --git a/lib/archethic/mining/validation_context.ex b/lib/archethic/mining/validation_context.ex index 1b7d4727b..ddab529fb 100644 --- a/lib/archethic/mining/validation_context.ex +++ b/lib/archethic/mining/validation_context.ex @@ -688,10 +688,12 @@ defmodule Archethic.Mining.ValidationContext do {context, ledger_operations} = get_ledger_operations(context, fee, validation_time, encoded_state) + {context, pow} = validate_origin_signature(context) + validation_stamp = %ValidationStamp{ protocol_version: Mining.protocol_version(), timestamp: validation_time, - proof_of_work: do_proof_of_work(tx), + proof_of_work: pow, proof_of_integrity: TransactionChain.proof_of_integrity([tx, prev_tx]), proof_of_election: Election.validation_nodes_election_seed_sorting(tx, validation_time), ledger_operations: ledger_operations, @@ -705,6 +707,22 @@ defmodule Archethic.Mining.ValidationContext do %__MODULE__{context | validation_stamp: validation_stamp} end + defp validate_origin_signature(context = %__MODULE__{transaction: tx}) do + pow = do_proof_of_work(tx) + + { + case pow do + "" -> + context + |> set_mining_error(Error.new(:invalid_origin_signature, "Invalid origin signature")) + + _ -> + context + end, + pow + } + end + defp validate_smart_contract(context, resolved_recipients) do with :ok <- validate_contract_context_inputs(context), :ok <- validate_distinct_contract_recipients(resolved_recipients), @@ -1076,6 +1094,8 @@ defmodule Archethic.Mining.ValidationContext do context = validate_inherit_condition(context, stamp) + {context, _} = validate_origin_signature(context) + inconsistencies = validation_stamp_inconsistencies( context, @@ -1100,6 +1120,7 @@ defmodule Archethic.Mining.ValidationContext do timestamp: fn -> valid_timestamp(stamp, context) end, signature: fn -> valid_stamp_signature(stamp, context) end, proof_of_work: fn -> valid_stamp_proof_of_work?(stamp, context) end, + # origin_signature: fn -> valid_stamp_origin_signature?(stamp, context) end, proof_of_integrity: fn -> valid_stamp_proof_of_integrity?(stamp, context) end, proof_of_election: fn -> valid_stamp_proof_of_election?(stamp, context) end, transaction_fee: fn -> valid_stamp_fee?(stamp, fee) end, @@ -1137,7 +1158,7 @@ defmodule Archethic.Mining.ValidationContext do transaction: tx }) do case pow do - "" -> + <<0::16, 0::256>> -> do_proof_of_work(tx) == "" _ -> @@ -1146,6 +1167,10 @@ defmodule Archethic.Mining.ValidationContext do end end + def valid_stamp_origin_signature?(%ValidationStamp{proof_of_work: pow}, %__MODULE__{}) do + pow != <<0::16, 0::256>> + end + defp valid_stamp_proof_of_integrity?(%ValidationStamp{proof_of_integrity: poi}, %__MODULE__{ transaction: tx, previous_transaction: prev_tx diff --git a/lib/archethic/transaction_chain/transaction/validation_stamp.ex b/lib/archethic/transaction_chain/transaction/validation_stamp.ex index 17cfb4262..b92b1e91d 100755 --- a/lib/archethic/transaction_chain/transaction/validation_stamp.ex +++ b/lib/archethic/transaction_chain/transaction/validation_stamp.ex @@ -30,6 +30,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do | :invalid_recipients_execution | :recipients_not_distinct | :invalid_contract_context_inputs + | :invalid_origin_signature @typedoc """ Validation performed by a coordinator: @@ -278,6 +279,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do defp serialize_error(:invalid_recipients_execution), do: 5 defp serialize_error(:recipients_not_distinct), do: 6 defp serialize_error(:invalid_contract_context_inputs), do: 7 + defp serialize_error(:invalid_origin_signature), do: 8 defp deserialize_error(0), do: nil defp deserialize_error(1), do: :invalid_pending_transaction @@ -287,4 +289,5 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do defp deserialize_error(5), do: :invalid_recipients_execution defp deserialize_error(6), do: :recipients_not_distinct defp deserialize_error(7), do: :invalid_contract_context_inputs + defp deserialize_error(8), do: :invalid_origin_signature end diff --git a/test/archethic/mining/distributed_workflow_test.exs b/test/archethic/mining/distributed_workflow_test.exs index 8ab1628d1..2949e261c 100644 --- a/test/archethic/mining/distributed_workflow_test.exs +++ b/test/archethic/mining/distributed_workflow_test.exs @@ -94,8 +94,7 @@ defmodule Archethic.Mining.DistributedWorkflowTest do reward_address: <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> }) - {origin_public_key, _} = - Crypto.generate_deterministic_keypair(:crypto.strong_rand_bytes(32), :secp256r1) + {origin_public_key, _} = Crypto.derive_keypair("seed", 0, :secp256r1) {_, ca_pv} = :crypto.generate_key(:ecdh, :secp256r1, "ca_root_key") <<_::8, _::8, origin_key::binary>> = origin_public_key diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index a4ce6c619..36a6553dc 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -42,6 +42,30 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do import ArchethicCase setup do + OriginKeyLookup.start_link([]) + + P2P.add_and_connect_node(%Node{ + ip: {127, 0, 0, 1}, + port: 3000, + http_port: 4000, + first_public_key: Crypto.derive_keypair("node_key1", 0) |> elem(0), + last_public_key: Crypto.derive_keypair("node_key1", 1) |> elem(0), + available?: true + }) + + P2P.add_and_connect_node(%Node{ + ip: {127, 0, 0, 1}, + port: 3000, + http_port: 4000, + first_public_key: Crypto.derive_keypair("node_key2", 0) |> elem(0), + last_public_key: Crypto.derive_keypair("node_key2", 1) |> elem(0), + available?: true + }) + + # this is the seed used by MockCrypto.NodeKeystore.Origin + {public_key, _} = Crypto.derive_keypair("seed", 0, :secp256r1) + OriginKeyLookup.add_public_key(:software, public_key) + P2P.add_and_connect_node(%Node{ first_public_key: Crypto.last_node_public_key(), network_patch: "AAA", @@ -532,8 +556,7 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do describe "Node" do test "should return :ok when a node transaction data content contains node endpoint information" do - {origin_public_key, _} = - Crypto.generate_deterministic_keypair(:crypto.strong_rand_bytes(32), :secp256r1) + {origin_public_key, _} = Crypto.derive_keypair("seed", 0, :secp256r1) {_, ca_pv} = :crypto.generate_key(:ecdh, :secp256r1, "ca_root_key") <<_::8, _::8, origin_key::binary>> = origin_public_key @@ -620,24 +643,6 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do describe "Node Shared Secrets" do test "should return :ok when a node shared secrets transaction data keys contains existing node public keys with first tx" do - P2P.add_and_connect_node(%Node{ - ip: {127, 0, 0, 1}, - port: 3000, - http_port: 4000, - first_public_key: Crypto.derive_keypair("node_key1", 0) |> elem(0), - last_public_key: Crypto.derive_keypair("node_key1", 1) |> elem(0), - available?: true - }) - - P2P.add_and_connect_node(%Node{ - ip: {127, 0, 0, 1}, - port: 3000, - http_port: 4000, - first_public_key: Crypto.derive_keypair("node_key2", 0) |> elem(0), - last_public_key: Crypto.derive_keypair("node_key2", 1) |> elem(0), - available?: true - }) - MockDB |> expect(:get_latest_tps, 2, fn -> 1000.0 end) diff --git a/test/archethic/mining/standalone_workflow_test.exs b/test/archethic/mining/standalone_workflow_test.exs index 4f732933b..4c6d09ad5 100644 --- a/test/archethic/mining/standalone_workflow_test.exs +++ b/test/archethic/mining/standalone_workflow_test.exs @@ -23,6 +23,8 @@ defmodule Archethic.Mining.StandaloneWorkflowTest do alias Archethic.P2P.Message.GenesisAddress alias Archethic.P2P.Node + alias Archethic.SharedSecrets.MemTables.OriginKeyLookup + alias Archethic.TransactionChain.Transaction alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.UnspentOutput @@ -37,6 +39,12 @@ defmodule Archethic.Mining.StandaloneWorkflowTest do start_supervised!({BeaconSlotTimer, interval: "0 * * * * * *"}) start_supervised!({BeaconSummaryTimer, interval: "0 * * * * *"}) + OriginKeyLookup.start_link([]) + + # this is the seed used by MockCrypto.NodeKeystore.Origin + {public_key, _} = Crypto.derive_keypair("seed", 0, :secp256r1) + OriginKeyLookup.add_public_key(:software, public_key) + P2P.add_and_connect_node(%Node{ ip: {127, 0, 0, 1}, port: 3000, diff --git a/test/archethic/mining/validation_context_test.exs b/test/archethic/mining/validation_context_test.exs index 3b605a743..5160d42d9 100644 --- a/test/archethic/mining/validation_context_test.exs +++ b/test/archethic/mining/validation_context_test.exs @@ -276,11 +276,14 @@ defmodule Archethic.Mining.ValidationContextTest do validation_context = create_context(timestamp) assert %ValidationContext{ - cross_validation_stamps: [%CrossValidationStamp{inconsistencies: [:proof_of_work]}] + mining_error: %Archethic.Mining.Error{code: -31503}, + cross_validation_stamps: [ + %CrossValidationStamp{inconsistencies: []} + ] } = validation_context |> ValidationContext.add_validation_stamp( - create_validation_stamp(validation_context) + create_validation_stamp_with_empty_proof_of_work(validation_context) ) |> ValidationContext.cross_validate() end @@ -596,6 +599,40 @@ defmodule Archethic.Mining.ValidationContextTest do |> ValidationStamp.sign() end + defp create_validation_stamp_with_empty_proof_of_work(%ValidationContext{ + transaction: tx, + unspent_outputs: unspent_outputs, + validation_time: timestamp + }) do + fee = Fee.calculate(tx, nil, 0.07, timestamp, nil, 0, current_protocol_version()) + + movements = Transaction.get_movements(tx) + resolved_addresses = Enum.map(movements, &{&1.to, &1.to}) |> Map.new() + + ledger_operations = + %LedgerOperations{fee: fee} + |> LedgerOperations.consume_inputs( + tx.address, + timestamp, + unspent_outputs, + movements, + LedgerOperations.get_utxos_from_transaction(tx, timestamp, current_protocol_version()) + ) + |> elem(1) + |> LedgerOperations.build_resolved_movements(movements, resolved_addresses, tx.type) + + %ValidationStamp{ + timestamp: timestamp, + proof_of_work: <<0::8, 0::8, 0::256>>, + proof_of_integrity: TransactionChain.proof_of_integrity([tx]), + proof_of_election: Election.validation_nodes_election_seed_sorting(tx, DateTime.utc_now()), + ledger_operations: ledger_operations, + protocol_version: current_protocol_version(), + error: :invalid_origin_signature + } + |> ValidationStamp.sign() + end + defp create_validation_stamp(%ValidationContext{ transaction: tx, unspent_outputs: unspent_outputs, From b1f2b55a2ec2638bc2771ec2f8c4c32134d37107 Mon Sep 17 00:00:00 2001 From: bchamagne Date: Fri, 4 Oct 2024 17:22:09 +0200 Subject: [PATCH 2/2] cleanup --- lib/archethic/mining/validation_context.ex | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/archethic/mining/validation_context.ex b/lib/archethic/mining/validation_context.ex index ddab529fb..3292070b1 100644 --- a/lib/archethic/mining/validation_context.ex +++ b/lib/archethic/mining/validation_context.ex @@ -1120,7 +1120,6 @@ defmodule Archethic.Mining.ValidationContext do timestamp: fn -> valid_timestamp(stamp, context) end, signature: fn -> valid_stamp_signature(stamp, context) end, proof_of_work: fn -> valid_stamp_proof_of_work?(stamp, context) end, - # origin_signature: fn -> valid_stamp_origin_signature?(stamp, context) end, proof_of_integrity: fn -> valid_stamp_proof_of_integrity?(stamp, context) end, proof_of_election: fn -> valid_stamp_proof_of_election?(stamp, context) end, transaction_fee: fn -> valid_stamp_fee?(stamp, fee) end, @@ -1167,10 +1166,6 @@ defmodule Archethic.Mining.ValidationContext do end end - def valid_stamp_origin_signature?(%ValidationStamp{proof_of_work: pow}, %__MODULE__{}) do - pow != <<0::16, 0::256>> - end - defp valid_stamp_proof_of_integrity?(%ValidationStamp{proof_of_integrity: poi}, %__MODULE__{ transaction: tx, previous_transaction: prev_tx