diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 9f864b797..6241f4216 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -1,40 +1,18 @@ defmodule Archethic.Mining.PendingTransactionValidation do @moduledoc false - alias Archethic.{ - Contracts, - Contracts.Contract, - Crypto, - DB, - SharedSecrets, - Election, - Governance, - Networking, - OracleChain, - P2P, - P2P.Message.FirstPublicKey, - P2P.Message.GetFirstPublicKey, - P2P.Message.GetTransactionSummary, - P2P.Message.TransactionSummaryMessage, - P2P.Message.NotFound, - P2P.Node, - Reward, - SharedSecrets.NodeRenewal, - Utils, - TransactionChain - } - - alias Archethic.TransactionChain.{ - Transaction, - TransactionData, - TransactionData.Ledger, - TransactionData.Ownership, - TransactionData.UCOLedger, - TransactionData.TokenLedger, - TransactionSummary - } + alias Archethic.{Contracts, Contracts.Contract, Crypto, DB, SharedSecrets, Election} + alias Archethic.{Governance, Networking, OracleChain, P2P, Reward, P2P.Message, P2P.Node} + alias Archethic.{SharedSecrets.NodeRenewal, Utils, TransactionChain} + + alias Message.{FirstPublicKey, GetFirstPublicKey, GetTransactionSummary} + alias Message.{TransactionSummaryMessage, NotFound} + + alias TransactionChain.{Transaction, TransactionData, TransactionSummary} + alias TransactionData.{Ledger, Ownership, UCOLedger, TokenLedger} alias Archethic.Governance.Code.Proposal, as: CodeProposal + require Logger @unit_uco 100_000_000 @@ -514,20 +492,20 @@ defmodule Archethic.Mining.PendingTransactionValidation do defp do_accept_transaction( %Transaction{ type: :keychain, - data: %TransactionData{content: "", ownerships: []} - }, - _ - ) do - {:error, "Invalid Keychain transaction"} - end - - defp do_accept_transaction( - %Transaction{ - type: :keychain, - data: %TransactionData{content: content, ownerships: _ownerships} + data: %TransactionData{ + ownerships: ownerships, + content: content, + ledger: %Ledger{ + uco: %UCOLedger{transfers: []}, + token: %TokenLedger{transfers: []} + }, + recipients: [] + } }, _ - ) do + ) + when content != "" and ownerships != [] do + # ownerships validate in :ok <- validate_ownerships(tx), with {:ok, json_did} <- Jason.decode(content), :ok <- ExJsonSchema.Validator.validate(@did_schema, json_did) do :ok @@ -541,31 +519,37 @@ defmodule Archethic.Mining.PendingTransactionValidation do end end - defp do_accept_transaction( - %Transaction{ - type: :keychain_access, - data: %TransactionData{ownerships: []} - }, - _ - ) do - {:error, "Invalid Keychain access transaction"} - end + defp do_accept_transaction(%Transaction{type: :keychain, data: _}, _), + do: {:error, "Invalid Keychain transaction"} defp do_accept_transaction( %Transaction{ type: :keychain_access, - data: %TransactionData{ownerships: ownerships}, - previous_public_key: previous_public_key + previous_public_key: previous_public_key, + data: %TransactionData{ + content: "", + ownerships: [ownership = %Ownership{secret: _, authorized_keys: _}], + ledger: %Ledger{ + uco: %UCOLedger{transfers: []}, + token: %TokenLedger{transfers: []} + }, + recipients: [] + } }, _ ) do - if Enum.any?(ownerships, &Ownership.authorized_public_key?(&1, previous_public_key)) do + # ownerships validate in :ok <- validate_ownerships(tx), + # forbid empty ownership or more than one secret, content, uco & token transfers + if Ownership.authorized_public_key?(ownership, previous_public_key) do :ok else {:error, "Invalid Keychain access transaction - Previous public key must be authorized"} end end + defp do_accept_transaction(%Transaction{type: :keychain_access}, _), + do: {:error, "Invalid Keychain Access transaction"} + defp do_accept_transaction( %Transaction{ type: :token, diff --git a/src/c/nat/miniupnp b/src/c/nat/miniupnp index 99fc9941a..e439318cf 160000 --- a/src/c/nat/miniupnp +++ b/src/c/nat/miniupnp @@ -1 +1 @@ -Subproject commit 99fc9941aa301323307a865f3798f64d189cc544 +Subproject commit e439318cf782e30066d430f27a1365e013a5ab94 diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index 8c58937f8..197fbbb3c 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -1,34 +1,17 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do use ArchethicCase, async: false - alias Archethic.{ - Crypto, - Mining.PendingTransactionValidation, - P2P, - P2P.Node, - Reward.Scheduler, - SharedSecrets, - TransactionChain - } + alias Archethic.{Crypto, P2P, P2P.Node, P2P.Message, SharedSecrets, TransactionChain} + alias Archethic.{Mining.PendingTransactionValidation, Reward.Scheduler} + + alias SharedSecrets.{MemTables.NetworkLookup, MemTables.OriginKeyLookup} + alias Message.{FirstPublicKey, GetFirstPublicKey, GetTransactionSummary, NotFound} + alias TransactionChain.{Transaction, TransactionData} + alias TransactionData.{Ledger, Ownership, TokenLedger, UCOLedger} alias Archethic.Governance.Pools.MemTable, as: PoolsMemTable - alias Archethic.SharedSecrets.{MemTables.NetworkLookup, MemTables.OriginKeyLookup} - - alias Archethic.P2P.Message.{ - FirstPublicKey, - GetFirstPublicKey, - GetTransactionSummary, - NotFound - } - - alias TransactionChain.{ - Transaction, - TransactionData, - TransactionData.Ledger, - TransactionData.UCOLedger, - TransactionData.UCOLedger.Transfer, - TransactionData.Ownership - } + alias TokenLedger.Transfer, as: TokenTransfer + alias UCOLedger.Transfer, as: UCOTransfer import Mox @@ -914,7 +897,7 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do ledger: %Ledger{ uco: %UCOLedger{ transfers: [ - %Transfer{to: :crypto.strong_rand_bytes(32), amount: 100_000} + %UCOTransfer{to: :crypto.strong_rand_bytes(32), amount: 100_000} ] } }, @@ -944,4 +927,223 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do assert :ok = PendingTransactionValidation.validate(tx) end end + + describe "Keychain Transaction" do + test "should reject empty content in keychain transaction" do + tx_seed = :crypto.strong_rand_bytes(32) + + tx = + Transaction.new( + :keychain, + %TransactionData{ + content: "" + }, + tx_seed, + 0 + ) + + assert {:error, "Invalid Keychain transaction"} = PendingTransactionValidation.validate(tx) + end + + test "Should Reject keychain tx with empty Ownerships list in keychain transaction" do + tx_seed = :crypto.strong_rand_bytes(32) + + tx = + Transaction.new( + :keychain, + %TransactionData{ + content: "content", + ownerships: [] + }, + tx_seed, + 0 + ) + + assert {:error, "Invalid Keychain transaction"} = PendingTransactionValidation.validate(tx) + end + + test "Should Reject keychain tx with UCO tranfers" do + tx_seed = :crypto.strong_rand_bytes(32) + + tx = + Transaction.new( + :keychain, + %TransactionData{ + content: "content", + ownerships: [ + Ownership.new(tx_seed, :crypto.strong_rand_bytes(32), [ + Crypto.storage_nonce_public_key() + ]) + ], + ledger: %Ledger{ + uco: %UCOLedger{ + transfers: [ + %UCOTransfer{to: :crypto.strong_rand_bytes(32), amount: 100_000} + ] + } + } + }, + tx_seed, + 0 + ) + + assert {:error, "Invalid Keychain transaction"} = PendingTransactionValidation.validate(tx) + + tx = + Transaction.new( + :keychain, + %TransactionData{ + content: "content", + ownerships: [ + Ownership.new(tx_seed, :crypto.strong_rand_bytes(32), [ + Crypto.storage_nonce_public_key() + ]) + ], + ledger: %Ledger{ + token: %TokenLedger{ + transfers: [ + %TokenTransfer{ + to: :crypto.strong_rand_bytes(32), + amount: 100_000_000, + token_address: "0123" + } + ] + } + } + }, + tx_seed, + 0 + ) + + assert {:error, "Invalid Keychain transaction"} = PendingTransactionValidation.validate(tx) + + tx = + Transaction.new( + :keychain, + %TransactionData{ + content: "content", + ownerships: [ + Ownership.new(tx_seed, :crypto.strong_rand_bytes(32), [ + Crypto.storage_nonce_public_key() + ]) + ], + ledger: %Ledger{ + uco: %UCOLedger{ + transfers: [ + %UCOTransfer{to: :crypto.strong_rand_bytes(32), amount: 100_000} + ] + }, + token: %TokenLedger{ + transfers: [ + %TokenTransfer{ + to: :crypto.strong_rand_bytes(32), + amount: 100_000_000, + token_address: "0123" + } + ] + } + } + }, + tx_seed, + 0 + ) + + assert {:error, "Invalid Keychain transaction"} = PendingTransactionValidation.validate(tx) + end + end + + describe "Keychain Acesss Transaction" do + test "should reject tx with more than one ownership" do + tx_seed = :crypto.strong_rand_bytes(32) + + tx = + Transaction.new( + :keychain_access, + %TransactionData{ + ownerships: [ + Ownership.new(tx_seed, :crypto.strong_rand_bytes(32), [ + Crypto.storage_nonce_public_key() + ]), + Ownership.new(tx_seed, :crypto.strong_rand_bytes(32), [ + Crypto.storage_nonce_public_key() + ]) + ] + }, + tx_seed, + 0 + ) + + assert {:error, "Invalid Keychain Access transaction"} = + PendingTransactionValidation.validate(tx) + + tx = + Transaction.new( + :keychain_access, + %TransactionData{ + ownerships: [] + }, + tx_seed, + 0 + ) + + assert {:error, "Invalid Keychain Access transaction"} = + PendingTransactionValidation.validate(tx) + + tx = + Transaction.new( + :keychain_access, + %TransactionData{ + content: "content", + ownerships: [ + Ownership.new(tx_seed, :crypto.strong_rand_bytes(32), [ + Crypto.storage_nonce_public_key() + ]) + ] + }, + tx_seed, + 0 + ) + + assert {:error, "Invalid Keychain Access transaction"} = + PendingTransactionValidation.validate(tx) + + tx = + Transaction.new( + :keychain_access, + %TransactionData{ + content: "", + ownerships: [ + Ownership.new(tx_seed, :crypto.strong_rand_bytes(32), [ + Crypto.storage_nonce_public_key() + ]) + ] + }, + tx_seed, + 0 + ) + + assert {:error, + "Invalid Keychain access transaction - Previous public key must be authorized"} = + PendingTransactionValidation.validate(tx) + + tx = + Transaction.new( + :keychain_access, + %TransactionData{ + recipients: ["sendtoSAM"], + content: "", + ownerships: [ + Ownership.new(tx_seed, :crypto.strong_rand_bytes(32), [ + Crypto.storage_nonce_public_key() + ]) + ] + }, + tx_seed, + 0 + ) + + assert {:error, "Invalid Keychain Access transaction"} = + PendingTransactionValidation.validate(tx) + end + end end