Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mining: check for unknown origin as part of the valid pending transaction #1557

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions lib/archethic/mining/error.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand All @@ -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.
Expand All @@ -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 """
Expand Down
24 changes: 22 additions & 2 deletions lib/archethic/mining/validation_context.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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),
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1137,7 +1157,7 @@ defmodule Archethic.Mining.ValidationContext do
transaction: tx
}) do
case pow do
"" ->
<<0::16, 0::256>> ->
do_proof_of_work(tx) == ""

_ ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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
3 changes: 1 addition & 2 deletions test/archethic/mining/distributed_workflow_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 25 additions & 20 deletions test/archethic/mining/pending_transaction_validation_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
8 changes: 8 additions & 0 deletions test/archethic/mining/standalone_workflow_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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,
Expand Down
41 changes: 39 additions & 2 deletions test/archethic/mining/validation_context_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -272,15 +272,18 @@
end

test "should get inconsistency when the proof of work is not in authorized keys" do
timestamp = DateTime.utc_now() |> DateTime.truncate(:millisecond)

Check failure on line 275 in test/archethic/mining/validation_context_test.exs

View workflow job for this annotation

GitHub Actions / Build and test

test cross_validate/1 should get inconsistency when the proof of work is not in authorized keys (Archethic.Mining.ValidationContextTest)
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
Expand Down Expand Up @@ -596,6 +599,40 @@
|> 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}

Check warning on line 613 in test/archethic/mining/validation_context_test.exs

View workflow job for this annotation

GitHub Actions / Build and test

Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.consume_inputs/6 is undefined or private
|> LedgerOperations.consume_inputs(
tx.address,
timestamp,
unspent_outputs,
movements,

Check warning on line 618 in test/archethic/mining/validation_context_test.exs

View workflow job for this annotation

GitHub Actions / Build and test

Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.get_utxos_from_transaction/3 is undefined or private
LedgerOperations.get_utxos_from_transaction(tx, timestamp, current_protocol_version())
)
|> elem(1)

Check warning on line 621 in test/archethic/mining/validation_context_test.exs

View workflow job for this annotation

GitHub Actions / Build and test

Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.build_resolved_movements/4 is undefined or private
|> 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,
Expand Down
Loading