From 33f2f9482bf76add83ca007446f79ad90e24ab88 Mon Sep 17 00:00:00 2001 From: apoorv-2204 Date: Thu, 22 Dec 2022 18:35:06 +0530 Subject: [PATCH 01/15] basic rules for new type txns --- config/config.exs | 2 + .../mining/pending_transaction_validation.ex | 66 +++++++++++++++++++ .../transaction_chain/transaction.ex | 6 +- .../controllers/api/schema/ownership.ex | 3 +- 4 files changed, 75 insertions(+), 2 deletions(-) diff --git a/config/config.exs b/config/config.exs index d14e69a2d..e7c0cad2d 100644 --- a/config/config.exs +++ b/config/config.exs @@ -57,6 +57,8 @@ config :archethic, :mut_dir, "data" config :archethic, :marker, "-=%=-=%=-=%=-" +config :archethic, :ownership_max_authorized_keys, 256 + # size represents in bytes binary config :archethic, :transaction_data_content_max_size, 3_145_728 diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 2c9598173..0dd654055 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -671,8 +671,74 @@ defmodule Archethic.Mining.PendingTransactionValidation do end end + @code_max_size Application.compile_env!(:archethic, :transaction_data_code_max_size) + defp do_accept_transaction(%Transaction{type: :contract, data: %TransactionData{code: code}}, _) do + cond do + byte_size(code) == 0 -> + {:error, "invalid contract type transaction - code is empty "} + + byte_size(code) >= @code_max_size -> + {:error, "invalid contract type transaction , code exceed max size "} + + true -> + :ok + end + end + + @content_max_size Application.compile_env!(:archethic, :transaction_data_content_max_size) + @ownership_max_keys Application.compile_env!(:archethic, :ownership_max_authorized_keys) + @max_ownerships @ownership_max_keys + defp do_accept_transaction( + %Transaction{ + type: :data, + data: %TransactionData{content: content, ownerships: ownerships} + }, + _ + ) do + # content_size = byte_size(content) already handled + nb_ownerships = length(ownerships) + + cond do + content == "" && nb_ownerships == 0 -> + {:error, "invalid data type transaction - Both content & ownership are empty"} + + nb_ownerships > @max_ownerships -> + {:error, "invalid data type transaction - ownerships exceeds limit"} + + nb_ownerships != 0 -> + validate_ownerships(ownerships) + end + end + defp do_accept_transaction(_, _), do: :ok + defp validate_ownerships(ownerships) do + Enum.reduce_while(ownerships, :ok, fn + %Ownership{secret: "", authorized_keys: _}, :ok -> + {:halt, {:error, "invalid data type transaction - secret is empty"}} + + %Ownership{secret: secret, authorized_keys: %{}}, :ok when is_binary(secret) -> + {:halt, {:error, "invalid data type transaction - authorized keys is empty"}} + + %Ownership{secret: secret, authorized_keys: authorized_keys}, :ok when is_binary(secret) -> + Enum.reduce_while(authorized_keys, {:cont, :ok}, fn + {"", _}, _ -> + {:halt, {:error, "invalid data type transaction - public key is empty"}} + + {_, ""}, _ -> + {:halt, {:error, "invalid data type transaction - encrypted key is empty"}} + + {public_key, _}, acc -> + if Crypto.verify_public_key?(public_key), + do: {:cont, acc}, + else: {:halt, {:error, "invalid data type transaction - invalid public key"}} + + _, acc -> + {:cont, acc} + end) + end) + end + defp verify_token_creation(content) do schema = :archethic diff --git a/lib/archethic/transaction_chain/transaction.ex b/lib/archethic/transaction_chain/transaction.ex index 4afae41d3..a56650440 100755 --- a/lib/archethic/transaction_chain/transaction.ex +++ b/lib/archethic/transaction_chain/transaction.ex @@ -76,6 +76,8 @@ defmodule Archethic.TransactionChain.Transaction do | :token | :hosting | :origin + | :data + | :contract @transaction_types [ :node, @@ -91,7 +93,9 @@ defmodule Archethic.TransactionChain.Transaction do :transfer, :hosting, :token, - :origin + :origin, + :data, + :contract ] @doc """ diff --git a/lib/archethic_web/controllers/api/schema/ownership.ex b/lib/archethic_web/controllers/api/schema/ownership.ex index bc2fe606c..594eec4b9 100644 --- a/lib/archethic_web/controllers/api/schema/ownership.ex +++ b/lib/archethic_web/controllers/api/schema/ownership.ex @@ -1,5 +1,6 @@ defmodule ArchethicWeb.API.Schema.Ownership do @moduledoc false + @ownership_max_keys Application.compile_env!(:archethic, :ownership_max_authorized_keys) use Ecto.Schema import Ecto.Changeset @@ -18,7 +19,7 @@ defmodule ArchethicWeb.API.Schema.Ownership do |> cast_embed(:authorizedKeys, required: [:publicKey, :encryptedSecretKey]) |> validate_required([:secret]) |> validate_length(:authorizedKeys, - max: 256, + max: @ownership_max_keys, message: "maximum number of authorized keys can be 256" ) |> format_authorized_keys() From dbf5f5f540cc02ba82f3203ececbc9926f851cd1 Mon Sep 17 00:00:00 2001 From: apoorv-2204 Date: Thu, 22 Dec 2022 18:51:07 +0530 Subject: [PATCH 02/15] remove unneccesary data --- lib/archethic/mining/pending_transaction_validation.ex | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 0dd654055..01a46ba7e 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -717,10 +717,10 @@ defmodule Archethic.Mining.PendingTransactionValidation do %Ownership{secret: "", authorized_keys: _}, :ok -> {:halt, {:error, "invalid data type transaction - secret is empty"}} - %Ownership{secret: secret, authorized_keys: %{}}, :ok when is_binary(secret) -> + %Ownership{secret: _, authorized_keys: %{}}, :ok -> {:halt, {:error, "invalid data type transaction - authorized keys is empty"}} - %Ownership{secret: secret, authorized_keys: authorized_keys}, :ok when is_binary(secret) -> + %Ownership{secret: secret, authorized_keys: authorized_keys}, :ok -> Enum.reduce_while(authorized_keys, {:cont, :ok}, fn {"", _}, _ -> {:halt, {:error, "invalid data type transaction - public key is empty"}} @@ -729,12 +729,9 @@ defmodule Archethic.Mining.PendingTransactionValidation do {:halt, {:error, "invalid data type transaction - encrypted key is empty"}} {public_key, _}, acc -> - if Crypto.verify_public_key?(public_key), + if Crypto.valid_public_key?(public_key), do: {:cont, acc}, else: {:halt, {:error, "invalid data type transaction - invalid public key"}} - - _, acc -> - {:cont, acc} end) end) end From 1bd900f2f8ed942235cbbfd953248f7725860bd5 Mon Sep 17 00:00:00 2001 From: apoorv-2204 Date: Tue, 27 Dec 2022 14:06:13 +0530 Subject: [PATCH 03/15] minor changes --- lib/archethic/mining/pending_transaction_validation.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 01a46ba7e..b63470659 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -720,7 +720,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do %Ownership{secret: _, authorized_keys: %{}}, :ok -> {:halt, {:error, "invalid data type transaction - authorized keys is empty"}} - %Ownership{secret: secret, authorized_keys: authorized_keys}, :ok -> + %Ownership{secret: _, authorized_keys: authorized_keys}, :ok -> Enum.reduce_while(authorized_keys, {:cont, :ok}, fn {"", _}, _ -> {:halt, {:error, "invalid data type transaction - public key is empty"}} From 1340d867fec29c24368aaabc280019293cd0f649 Mon Sep 17 00:00:00 2001 From: apoorv-2204 Date: Tue, 10 Jan 2023 13:45:15 +0530 Subject: [PATCH 04/15] update to upnpc --- src/c/nat/miniupnp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/c/nat/miniupnp b/src/c/nat/miniupnp index 207cf440a..014c9df8e 160000 --- a/src/c/nat/miniupnp +++ b/src/c/nat/miniupnp @@ -1 +1 @@ -Subproject commit 207cf440a22c075cb55fb067a850be4f9c204e6e +Subproject commit 014c9df8ee7a36e5bf85aa619062a2d4b95ec8f6 From fc6e578fd5f8f9ddc692806c907f2aef449fea01 Mon Sep 17 00:00:00 2001 From: apoorv-2204 Date: Tue, 10 Jan 2023 16:59:21 +0530 Subject: [PATCH 05/15] - add checks for ownerships in all txns, - check for byte size data should not exceed txn content size --- .../mining/pending_transaction_validation.ex | 93 ++++++++++++++----- 1 file changed, 68 insertions(+), 25 deletions(-) diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 2ce02b5ca..b863590ed 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -56,6 +56,12 @@ defmodule Archethic.Mining.PendingTransactionValidation do |> Jason.decode!() |> ExJsonSchema.Schema.resolve() + @code_max_size Application.compile_env!(:archethic, :transaction_data_code_max_size) + + @tx_data_max_size Application.compile_env!(:archethic, :transaction_data_content_max_size) + + @ownership_max_keys Application.compile_env!(:archethic, :ownership_max_authorized_keys) + @doc """ Determines if the transaction is accepted into the network """ @@ -70,6 +76,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do :ok <- valid_previous_signature(tx), :ok <- validate_contract(tx), :ok <- validate_content_size(tx), + :ok <- validate_ownerships(tx), :ok <- do_accept_transaction(tx, validation_time), :ok <- validate_previous_transaction_type?(tx) do :telemetry.execute( @@ -723,7 +730,6 @@ defmodule Archethic.Mining.PendingTransactionValidation do end end - @code_max_size Application.compile_env!(:archethic, :transaction_data_code_max_size) defp do_accept_transaction(%Transaction{type: :contract, data: %TransactionData{code: code}}, _) do cond do byte_size(code) == 0 -> @@ -737,54 +743,91 @@ defmodule Archethic.Mining.PendingTransactionValidation do end end - @content_max_size Application.compile_env!(:archethic, :transaction_data_content_max_size) - @ownership_max_keys Application.compile_env!(:archethic, :ownership_max_authorized_keys) - @max_ownerships @ownership_max_keys defp do_accept_transaction( %Transaction{ type: :data, - data: %TransactionData{content: content, ownerships: ownerships} + data: data = %TransactionData{content: content, ownerships: ownerships}, + version: tx_version }, _ ) do - # content_size = byte_size(content) already handled nb_ownerships = length(ownerships) + tx_data_size = + data + |> TransactionData.serialize(tx_version) + |> :erlang.byte_size() + cond do + tx_data_size > @tx_data_max_size -> + # only for data type txns + # content_size already handled + {:error, "invalid data type transaction - transaction size exceeds limit"} + content == "" && nb_ownerships == 0 -> + # content or ownership either must be present or error + # defstruct recipients: [], ledger: %Ledger{}, code: "", ownerships: [], content: "" {:error, "invalid data type transaction - Both content & ownership are empty"} - nb_ownerships > @max_ownerships -> - {:error, "invalid data type transaction - ownerships exceeds limit"} + content == "" && nb_ownerships != 0 -> + :ok - nb_ownerships != 0 -> - validate_ownerships(ownerships) + nb_ownerships == 0 && content != "" -> + :ok end end defp do_accept_transaction(_, _), do: :ok - defp validate_ownerships(ownerships) do + def validate_ownerships(%Transaction{data: %TransactionData{ownerships: ownerships}}) do + nb_ownerships = length(ownerships) + + cond do + # defstruct recipients: [], ledger: %Ledger{}`, code: "", ownerships: [], content: "" + # we might not have any ownerships at all + nb_ownerships == 0 -> + :ok + + nb_ownerships > @ownership_max_keys -> + {:error, "invalid transaction - ownerships exceeds limit"} + + nb_ownerships > 0 and nb_ownerships <= @ownership_max_keys -> + do_validate_ownerships(ownerships) + end + end + + @max_authorized_keys @ownership_max_keys + def do_validate_ownerships(ownerships) do + # handles irregulrarites in ownerships Enum.reduce_while(ownerships, :ok, fn %Ownership{secret: "", authorized_keys: _}, :ok -> - {:halt, {:error, "invalid data type transaction - secret is empty"}} + {:halt, {:error, "invalid transaction - Ownership: secret is empty"}} %Ownership{secret: _, authorized_keys: %{}}, :ok -> - {:halt, {:error, "invalid data type transaction - authorized keys is empty"}} + # defstruct authorized_keys: %{}, secret: "" + {:halt, {:error, "invalid transaction - Ownership: authorized keys are empty"}} + + %Ownership{secret: _, authorized_keys: authorized_keys}, :ok + when length(authorized_keys) > @max_authorized_keys -> + {:halt, {:error, "invalid transaction - Ownership: authorized keys excceds limit"}} %Ownership{secret: _, authorized_keys: authorized_keys}, :ok -> - Enum.reduce_while(authorized_keys, {:cont, :ok}, fn - {"", _}, _ -> - {:halt, {:error, "invalid data type transaction - public key is empty"}} - - {_, ""}, _ -> - {:halt, {:error, "invalid data type transaction - encrypted key is empty"}} - - {public_key, _}, acc -> - if Crypto.valid_public_key?(public_key), - do: {:cont, acc}, - else: {:halt, {:error, "invalid data type transaction - invalid public key"}} - end) + verify_authorized_keys(authorized_keys) + end) + end + + def verify_authorized_keys(authorized_keys) do + Enum.reduce_while(authorized_keys, {:cont, :ok}, fn + {"", _}, _ -> + {:halt, {:error, "invalid transaction - Ownership: public key is empty "}} + + {_, ""}, _ -> + {:halt, {:error, "invalid transaction - Ownership: encrypted key is empty"}} + + {public_key, _}, acc -> + if Crypto.valid_public_key?(public_key), + do: {:cont, acc}, + else: {:halt, {:error, "invalid transaction - Ownership: invalid public key"}} end) end From 6eab80f814dd6c5f32837492a748835611ff6bff Mon Sep 17 00:00:00 2001 From: apoorv-2204 Date: Tue, 10 Jan 2023 18:43:05 +0530 Subject: [PATCH 06/15] update tests --- .../mining/pending_transaction_validation.ex | 35 ++++++++++++------- .../pending_transaction_validation_test.exs | 21 ++++++----- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index b863590ed..52adf90a4 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -800,34 +800,43 @@ defmodule Archethic.Mining.PendingTransactionValidation do def do_validate_ownerships(ownerships) do # handles irregulrarites in ownerships Enum.reduce_while(ownerships, :ok, fn - %Ownership{secret: "", authorized_keys: _}, :ok -> - {:halt, {:error, "invalid transaction - Ownership: secret is empty"}} + ownership, :ok -> + %Ownership{secret: secret, authorized_keys: authorized_keys} = ownership + nb_authorized_keys = map_size(authorized_keys) - %Ownership{secret: _, authorized_keys: %{}}, :ok -> - # defstruct authorized_keys: %{}, secret: "" - {:halt, {:error, "invalid transaction - Ownership: authorized keys are empty"}} + cond do + secret == "" -> + {:halt, {:error, "invalid transaction - Ownership: secret is empty"}} - %Ownership{secret: _, authorized_keys: authorized_keys}, :ok - when length(authorized_keys) > @max_authorized_keys -> - {:halt, {:error, "invalid transaction - Ownership: authorized keys excceds limit"}} + nb_authorized_keys == 0 -> + {:halt, {:error, "invalid transaction - Ownership: authorized keys are empty"}} - %Ownership{secret: _, authorized_keys: authorized_keys}, :ok -> - verify_authorized_keys(authorized_keys) + nb_authorized_keys > @max_authorized_keys -> + {:halt, {:error, "invalid transaction - Ownership: authorized keys excceds limit"}} + + true -> + verify_authorized_keys(authorized_keys) + end end) end + @spec verify_authorized_keys(any) :: any def verify_authorized_keys(authorized_keys) do + # authorized_keys: %{(public_key :: Crypto.key()) => encrypted_key } + Enum.reduce_while(authorized_keys, {:cont, :ok}, fn {"", _}, _ -> - {:halt, {:error, "invalid transaction - Ownership: public key is empty "}} + e = {:halt, {:error, "invalid transaction - Ownership: public key is empty "}} + {:halt, e} {_, ""}, _ -> - {:halt, {:error, "invalid transaction - Ownership: encrypted key is empty"}} + e = {:halt, {:error, "invalid transaction - Ownership: encrypted key is empty"}} + {:halt, e} {public_key, _}, acc -> if Crypto.valid_public_key?(public_key), do: {:cont, acc}, - else: {:halt, {:error, "invalid transaction - Ownership: invalid public key"}} + else: {:halt, {:halt, {:error, "invalid transaction - Ownership: invalid public key"}}} end) end diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index aa664853c..b46856e4b 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -179,8 +179,8 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do ip: {127, 0, 0, 1}, port: 3000, http_port: 4000, - first_public_key: "node_key1", - last_public_key: "node_key1", + first_public_key: Crypto.derive_keypair("node_key1", 0) |> elem(1), + last_public_key: Crypto.derive_keypair("node_key1", 1) |> elem(1), available?: true }) @@ -188,8 +188,8 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do ip: {127, 0, 0, 1}, port: 3000, http_port: 4000, - first_public_key: "node_key2", - last_public_key: "node_key2", + first_public_key: Crypto.derive_keypair("node_key2", 0) |> elem(1), + last_public_key: Crypto.derive_keypair("node_key2", 1) |> elem(1), available?: true }) @@ -214,10 +214,10 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do %Ownership{ secret: :crypto.strong_rand_bytes(32), authorized_keys: %{ - "node_key1" => "", - "node_key2" => "", + (Crypto.derive_keypair("node_key1", 0) |> elem(1)) => "a_encrypted_key", + (Crypto.derive_keypair("node_key2", 0) |> elem(1)) => "a_encrypted_key", # we started and connected this node in setup - Crypto.last_node_public_key() => "" + Crypto.last_node_public_key() => "a_encrypted_key" } } ] @@ -262,7 +262,7 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do secret: :crypto.strong_rand_bytes(32), authorized_keys: %{ # we started and connected this node in setup - Crypto.last_node_public_key() => "" + Crypto.last_node_public_key() => :crypto.strong_rand_bytes(32) } } ] @@ -706,7 +706,10 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do ownerships: [ %Ownership{ secret: :crypto.strong_rand_bytes(32), - authorized_keys: %{"node_key" => :crypto.strong_rand_bytes(32)} + authorized_keys: %{ + <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> => + :crypto.strong_rand_bytes(32) + } } ] }, From 318b176221b3df4ba6a47f98de662e9a1c94887b Mon Sep 17 00:00:00 2001 From: apoorv-2204 Date: Wed, 11 Jan 2023 19:16:37 +0530 Subject: [PATCH 07/15] review changes --- config/config.exs | 2 - .../mining/pending_transaction_validation.ex | 41 +++++-------------- .../controllers/api/schema/ownership.ex | 4 +- 3 files changed, 12 insertions(+), 35 deletions(-) diff --git a/config/config.exs b/config/config.exs index 40a19d56b..39626b59b 100644 --- a/config/config.exs +++ b/config/config.exs @@ -57,8 +57,6 @@ config :archethic, :mut_dir, "data" config :archethic, :marker, "-=%=-=%=-=%=-" -config :archethic, :ownership_max_authorized_keys, 256 - # size represents in bytes binary config :archethic, :transaction_data_content_max_size, 3_145_728 diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 52adf90a4..141fea5d6 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -58,9 +58,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do @code_max_size Application.compile_env!(:archethic, :transaction_data_code_max_size) - @tx_data_max_size Application.compile_env!(:archethic, :transaction_data_content_max_size) - - @ownership_max_keys Application.compile_env!(:archethic, :ownership_max_authorized_keys) + @tx_max_size Application.compile_env!(:archethic, :transaction_data_content_max_size) @doc """ Determines if the transaction is accepted into the network @@ -75,7 +73,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do with :ok <- valid_not_exists(tx), :ok <- valid_previous_signature(tx), :ok <- validate_contract(tx), - :ok <- validate_content_size(tx), + :ok <- validate_size(tx), :ok <- validate_ownerships(tx), :ok <- do_accept_transaction(tx, validation_time), :ok <- validate_previous_transaction_type?(tx) do @@ -248,13 +246,14 @@ defmodule Archethic.Mining.PendingTransactionValidation do def validate_network_chain?(_type, _tx), do: true - defp validate_content_size(%Transaction{data: %TransactionData{content: content}}) do - content_max_size = Application.get_env(:archethic, :transaction_data_content_max_size) + defp validate_size(%Transaction{data: data}) do + tx_size = byte_size(data) - if byte_size(content) >= content_max_size do - {:error, "Invalid node transaction with content size greaterthan content_max_size"} - else - :ok + cond tx_size >= @tx_max_size do + {:error, "invalid transaction : transaction data exceeds limit"} + + true -> + :ok end end @@ -746,24 +745,13 @@ defmodule Archethic.Mining.PendingTransactionValidation do defp do_accept_transaction( %Transaction{ type: :data, - data: data = %TransactionData{content: content, ownerships: ownerships}, - version: tx_version + data: %TransactionData{content: content, ownerships: ownerships} }, _ ) do nb_ownerships = length(ownerships) - tx_data_size = - data - |> TransactionData.serialize(tx_version) - |> :erlang.byte_size() - cond do - tx_data_size > @tx_data_max_size -> - # only for data type txns - # content_size already handled - {:error, "invalid data type transaction - transaction size exceeds limit"} - content == "" && nb_ownerships == 0 -> # content or ownership either must be present or error # defstruct recipients: [], ledger: %Ledger{}, code: "", ownerships: [], content: "" @@ -788,15 +776,11 @@ defmodule Archethic.Mining.PendingTransactionValidation do nb_ownerships == 0 -> :ok - nb_ownerships > @ownership_max_keys -> - {:error, "invalid transaction - ownerships exceeds limit"} - - nb_ownerships > 0 and nb_ownerships <= @ownership_max_keys -> + true -> do_validate_ownerships(ownerships) end end - @max_authorized_keys @ownership_max_keys def do_validate_ownerships(ownerships) do # handles irregulrarites in ownerships Enum.reduce_while(ownerships, :ok, fn @@ -811,9 +795,6 @@ defmodule Archethic.Mining.PendingTransactionValidation do nb_authorized_keys == 0 -> {:halt, {:error, "invalid transaction - Ownership: authorized keys are empty"}} - nb_authorized_keys > @max_authorized_keys -> - {:halt, {:error, "invalid transaction - Ownership: authorized keys excceds limit"}} - true -> verify_authorized_keys(authorized_keys) end diff --git a/lib/archethic_web/controllers/api/schema/ownership.ex b/lib/archethic_web/controllers/api/schema/ownership.ex index bf17f027f..e614821ce 100644 --- a/lib/archethic_web/controllers/api/schema/ownership.ex +++ b/lib/archethic_web/controllers/api/schema/ownership.ex @@ -1,7 +1,5 @@ defmodule ArchethicWeb.API.Schema.Ownership do @moduledoc false - @ownership_max_keys Application.compile_env!(:archethic, :ownership_max_authorized_keys) - use Ecto.Schema import Ecto.Changeset @@ -19,7 +17,7 @@ defmodule ArchethicWeb.API.Schema.Ownership do |> cast_embed(:authorizedKeys, required: [:publicKey, :encryptedSecretKey]) |> validate_required([:secret]) |> validate_length(:authorizedKeys, - max: @ownership_max_keys, + max: 256, message: "maximum number of authorized keys can be 256" ) end From 4fe25efd18f514eba50ac8fdec08183849ed8269 Mon Sep 17 00:00:00 2001 From: apoorv-2204 Date: Wed, 11 Jan 2023 19:41:17 +0530 Subject: [PATCH 08/15] refactor for r,c,m --- .../mining/pending_transaction_validation.ex | 320 +++++++++--------- .../transaction_chain/transaction.ex | 2 +- .../pending_transaction_validation_test.exs | 6 +- 3 files changed, 160 insertions(+), 168 deletions(-) diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 141fea5d6..3407f46eb 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -70,10 +70,10 @@ defmodule Archethic.Mining.PendingTransactionValidation do ) do start = System.monotonic_time() - with :ok <- valid_not_exists(tx), + with :ok <- validate_size(tx), + :ok <- valid_not_exists(tx), :ok <- valid_previous_signature(tx), :ok <- validate_contract(tx), - :ok <- validate_size(tx), :ok <- validate_ownerships(tx), :ok <- do_accept_transaction(tx, validation_time), :ok <- validate_previous_transaction_type?(tx) do @@ -85,25 +85,26 @@ defmodule Archethic.Mining.PendingTransactionValidation do :ok else - {:error, :invalid_tx_type} -> - Logger.error("Invalid Transaction Type", + {:error, reason} = e -> + Logger.info("Invalid Transaction: #{inspect(reason)}", transaction_address: Base.encode16(address), transaction_type: type ) - {:error, "Invalid Transaction Type"} - - false -> - Logger.error("Invalid previous signature", - transaction_address: Base.encode16(address), - transaction_type: type - ) + e + end + end - {:error, "Invalid previous signature"} + defp validate_size(%Transaction{data: data, version: tx_version}) do + tx_size = + data + |> TransactionData.serialize(tx_version) + |> byte_size() - {:error, reason} = e -> - Logger.info(reason, transaction_address: Base.encode16(address), transaction_type: type) - e + if tx_size >= @tx_max_size do + {:error, "invalid transaction : transaction data exceeds limit"} + else + :ok end end @@ -138,6 +139,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do end end + @spec valid_previous_signature(tx :: Transaction.t()) :: :ok | {:error, any()} defp valid_previous_signature(tx = %Transaction{}) do if Transaction.verify_previous_signature?(tx) do :ok @@ -146,117 +148,6 @@ defmodule Archethic.Mining.PendingTransactionValidation do end end - def validate_previous_transaction_type?(tx) do - case Transaction.network_type?(tx.type) do - false -> - # not a network tx, no need to validate with last tx - :ok - - true -> - # when network tx, check with previous transaction - if validate_network_chain?(tx.type, tx), do: :ok, else: {:error, :invalid_tx_type} - end - end - - @spec validate_network_chain?( - :code_approval - | :code_proposal - | :mint_rewards - | :node - | :node_rewards - | :node_shared_secrets - | :oracle - | :oracle_summary - | :origin, - Archethic.TransactionChain.Transaction.t() - ) :: boolean - def validate_network_chain?(type, tx = %Transaction{}) - when type in [:oracle, :oracle_summary] do - # mulitpe txn chain based on summary date - - case OracleChain.genesis_address() do - nil -> - false - - gen_addr -> - # first adddress found by looking up in the db - first_addr = - tx - |> Transaction.previous_address() - |> TransactionChain.get_genesis_address() - - # 1: tx gen & validated in current summary cycle, gen_addr.current must match - # 2: tx gen in prev summary cycle & validated in current summary cycle, gen_addr.prev must match - # situation: the shift b/w summary A to summary B - gen_addr.current |> elem(0) == first_addr || - gen_addr.prev |> elem(0) == first_addr - end - end - - def validate_network_chain?(type, tx = %Transaction{}) - when type in [:mint_rewards, :node_rewards] do - # singleton tx chain in network lifespan - case Reward.genesis_address() do - nil -> - false - - gen_addr -> - # first addr located in db by prev_address from tx - first_addr = - tx - |> Transaction.previous_address() - |> TransactionChain.get_genesis_address() - - gen_addr == first_addr - end - end - - def validate_network_chain?(:node_shared_secrets, tx = %Transaction{}) do - # singleton tx chain in network lifespan - case SharedSecrets.genesis_address(:node_shared_secrets) do - nil -> - false - - gen_addr -> - first_addr = - tx - |> Transaction.previous_address() - |> TransactionChain.get_genesis_address() - - first_addr == gen_addr - end - end - - def validate_network_chain?(:origin, tx = %Transaction{}) do - # singleton tx chain in network lifespan - # not parsing orgin pub key for origin family - case SharedSecrets.genesis_address(:origin) do - nil -> - false - - origin_gen_addr_list -> - first_addr_from_prev_addr = - tx - |> Transaction.previous_address() - |> TransactionChain.get_genesis_address() - - first_addr_from_prev_addr in origin_gen_addr_list - end - end - - def validate_network_chain?(_type, _tx), do: true - - defp validate_size(%Transaction{data: data}) do - tx_size = byte_size(data) - - cond tx_size >= @tx_max_size do - {:error, "invalid transaction : transaction data exceeds limit"} - - true -> - :ok - end - end - defp validate_contract(%Transaction{data: %TransactionData{code: ""}}), do: :ok defp validate_contract(%Transaction{ @@ -281,6 +172,59 @@ defmodule Archethic.Mining.PendingTransactionValidation do end end + @spec validate_ownerships(Transaction.t()) :: :ok | {:error, any()} + def validate_ownerships(%Transaction{data: %TransactionData{ownerships: ownerships}}) do + nb_ownerships = length(ownerships) + + # defstruct recipients: [], ledger: %Ledger{}`, code: "", ownerships: [], content: "" + if nb_ownerships == 0 do + # we might not have any ownerships at all + :ok + else + do_validate_ownerships(ownerships) + end + end + + def do_validate_ownerships(ownerships) do + # handles irregulrarites in ownerships + Enum.reduce_while(ownerships, :ok, fn + ownership, :ok -> + %Ownership{secret: secret, authorized_keys: authorized_keys} = ownership + nb_authorized_keys = map_size(authorized_keys) + + cond do + secret == "" -> + {:halt, {:error, "invalid transaction - Ownership: secret is empty"}} + + nb_authorized_keys == 0 -> + {:halt, {:error, "invalid transaction - Ownership: authorized keys are empty"}} + + true -> + verify_authorized_keys(authorized_keys) + end + end) + end + + @spec verify_authorized_keys(any) :: any + def verify_authorized_keys(authorized_keys) do + # authorized_keys: %{(public_key :: Crypto.key()) => encrypted_key } + + Enum.reduce_while(authorized_keys, {:cont, :ok}, fn + {"", _}, _ -> + e = {:halt, {:error, "invalid transaction - Ownership: public key is empty "}} + {:halt, e} + + {_, ""}, _ -> + e = {:halt, {:error, "invalid transaction - Ownership: encrypted key is empty"}} + {:halt, e} + + {public_key, _}, acc -> + if Crypto.valid_public_key?(public_key), + do: {:cont, acc}, + else: {:halt, {:halt, {:error, "invalid transaction - Ownership: invalid public key"}}} + end) + end + defp do_accept_transaction( %Transaction{ type: :transfer, @@ -767,60 +711,108 @@ defmodule Archethic.Mining.PendingTransactionValidation do defp do_accept_transaction(_, _), do: :ok - def validate_ownerships(%Transaction{data: %TransactionData{ownerships: ownerships}}) do - nb_ownerships = length(ownerships) - - cond do - # defstruct recipients: [], ledger: %Ledger{}`, code: "", ownerships: [], content: "" - # we might not have any ownerships at all - nb_ownerships == 0 -> + def validate_previous_transaction_type?(tx) do + case Transaction.network_type?(tx.type) do + false -> + # not a network tx, no need to validate with last tx :ok true -> - do_validate_ownerships(ownerships) + # when network tx, check with previous transaction + if validate_network_chain?(tx.type, tx), + do: :ok, + else: {:error, "Invalid Transaction Type"} end end - def do_validate_ownerships(ownerships) do - # handles irregulrarites in ownerships - Enum.reduce_while(ownerships, :ok, fn - ownership, :ok -> - %Ownership{secret: secret, authorized_keys: authorized_keys} = ownership - nb_authorized_keys = map_size(authorized_keys) + @spec validate_network_chain?( + :code_approval + | :code_proposal + | :mint_rewards + | :node + | :node_rewards + | :node_shared_secrets + | :oracle + | :oracle_summary + | :origin, + Archethic.TransactionChain.Transaction.t() + ) :: boolean + def validate_network_chain?(type, tx = %Transaction{}) + when type in [:oracle, :oracle_summary] do + # mulitpe txn chain based on summary date - cond do - secret == "" -> - {:halt, {:error, "invalid transaction - Ownership: secret is empty"}} + case OracleChain.genesis_address() do + nil -> + false - nb_authorized_keys == 0 -> - {:halt, {:error, "invalid transaction - Ownership: authorized keys are empty"}} + gen_addr -> + # first adddress found by looking up in the db + first_addr = + tx + |> Transaction.previous_address() + |> TransactionChain.get_genesis_address() - true -> - verify_authorized_keys(authorized_keys) - end - end) + # 1: tx gen & validated in current summary cycle, gen_addr.current must match + # 2: tx gen in prev summary cycle & validated in current summary cycle, gen_addr.prev must match + # situation: the shift b/w summary A to summary B + gen_addr.current |> elem(0) == first_addr || + gen_addr.prev |> elem(0) == first_addr + end end - @spec verify_authorized_keys(any) :: any - def verify_authorized_keys(authorized_keys) do - # authorized_keys: %{(public_key :: Crypto.key()) => encrypted_key } + def validate_network_chain?(type, tx = %Transaction{}) + when type in [:mint_rewards, :node_rewards] do + # singleton tx chain in network lifespan + case Reward.genesis_address() do + nil -> + false - Enum.reduce_while(authorized_keys, {:cont, :ok}, fn - {"", _}, _ -> - e = {:halt, {:error, "invalid transaction - Ownership: public key is empty "}} - {:halt, e} + gen_addr -> + # first addr located in db by prev_address from tx + first_addr = + tx + |> Transaction.previous_address() + |> TransactionChain.get_genesis_address() - {_, ""}, _ -> - e = {:halt, {:error, "invalid transaction - Ownership: encrypted key is empty"}} - {:halt, e} + gen_addr == first_addr + end + end - {public_key, _}, acc -> - if Crypto.valid_public_key?(public_key), - do: {:cont, acc}, - else: {:halt, {:halt, {:error, "invalid transaction - Ownership: invalid public key"}}} - end) + def validate_network_chain?(:node_shared_secrets, tx = %Transaction{}) do + # singleton tx chain in network lifespan + case SharedSecrets.genesis_address(:node_shared_secrets) do + nil -> + false + + gen_addr -> + first_addr = + tx + |> Transaction.previous_address() + |> TransactionChain.get_genesis_address() + + first_addr == gen_addr + end + end + + def validate_network_chain?(:origin, tx = %Transaction{}) do + # singleton tx chain in network lifespan + # not parsing orgin pub key for origin family + case SharedSecrets.genesis_address(:origin) do + nil -> + false + + origin_gen_addr_list -> + first_addr_from_prev_addr = + tx + |> Transaction.previous_address() + |> TransactionChain.get_genesis_address() + + first_addr_from_prev_addr in origin_gen_addr_list + end end + def validate_network_chain?(_type, _tx), do: true + defp verify_token_creation(content) do with {:ok, json_token} <- Jason.decode(content), :ok <- ExJsonSchema.Validator.validate(@token_schema, json_token), diff --git a/lib/archethic/transaction_chain/transaction.ex b/lib/archethic/transaction_chain/transaction.ex index 419db5f6e..e707ee9e9 100755 --- a/lib/archethic/transaction_chain/transaction.ex +++ b/lib/archethic/transaction_chain/transaction.ex @@ -526,7 +526,7 @@ defmodule Archethic.TransactionChain.Transaction do @doc """ Determines if the previous signature is valid """ - @spec verify_previous_signature?(t()) :: boolean() + @spec verify_previous_signature?(tx :: t()) :: boolean() def verify_previous_signature?( tx = %__MODULE__{previous_public_key: prev_key, previous_signature: prev_sig} ) do diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index b46856e4b..d5c486cb2 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -170,7 +170,7 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do {:error, :transaction_not_exists} end) - assert {:error, "Invalid node transaction with content size greaterthan content_max_size"} = + assert {:error, "invalid transaction : transaction data exceeds limit"} = PendingTransactionValidation.validate(tx) end @@ -788,7 +788,7 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do }) }) - assert {:error, error} = PendingTransactionValidation.validate(tx, DateTime.utc_now()) + assert {:error, _error} = PendingTransactionValidation.validate(tx, DateTime.utc_now()) end test "should return :error when we deploy a wrong aeweb ref transaction" do @@ -813,7 +813,7 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do }) }) - assert {:error, reason} = PendingTransactionValidation.validate(tx, DateTime.utc_now()) + assert {:error, _reason} = PendingTransactionValidation.validate(tx, DateTime.utc_now()) end end end From 773bcb712e8c65a120f7a35596efa0975a12fe55 Mon Sep 17 00:00:00 2001 From: apoorv-2204 Date: Thu, 12 Jan 2023 00:12:52 +0530 Subject: [PATCH 09/15] add serialization --- lib/archethic/transaction_chain/transaction.ex | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/archethic/transaction_chain/transaction.ex b/lib/archethic/transaction_chain/transaction.ex index e707ee9e9..c4110a6a7 100755 --- a/lib/archethic/transaction_chain/transaction.ex +++ b/lib/archethic/transaction_chain/transaction.ex @@ -361,7 +361,9 @@ defmodule Archethic.TransactionChain.Transaction do def serialize_type(:code_proposal), do: 5 def serialize_type(:code_approval), do: 6 def serialize_type(:node_rewards), do: 7 - def serialize_type(:mint_rewards), do: 8 + + def serialize_type(:mint_rewards), + do: 8 # User transaction's type def serialize_type(:keychain), do: 255 @@ -369,6 +371,8 @@ defmodule Archethic.TransactionChain.Transaction do def serialize_type(:transfer), do: 253 def serialize_type(:hosting), do: 252 def serialize_type(:token), do: 251 + def serialize_type(:data), do: 250 + def serialize_type(:contract), do: 249 @doc """ Parse a serialize transaction type @@ -391,6 +395,8 @@ defmodule Archethic.TransactionChain.Transaction do def parse_type(253), do: :transfer def parse_type(252), do: :hosting def parse_type(251), do: :token + def parse_type(250), do: :data + def parse_type(249), do: :contract @doc """ Determines if a transaction type is a network one From 436f92a999332543bdcd1996ffe48169a8c88a91 Mon Sep 17 00:00:00 2001 From: apoorv-2204 Date: Thu, 12 Jan 2023 00:13:48 +0530 Subject: [PATCH 10/15] refactor validation tests --- .../pending_transaction_validation_test.exs | 518 +++++++++--------- 1 file changed, 270 insertions(+), 248 deletions(-) diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index d5c486cb2..8ee5686ff 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -54,7 +54,164 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do :ok end - describe "validate_pending_transaction/1" do + describe "Data" do + end + + describe "Code Approval" do + test "should return :ok when a code approval transaction contains a proposal target and the sender is member of the technical council and not previously signed" do + tx = + Transaction.new( + :code_approval, + %TransactionData{ + recipients: ["@CodeProposal1"] + }, + "approval_seed", + 0 + ) + + P2P.add_and_connect_node(%Node{ + ip: {127, 0, 0, 1}, + port: 3000, + http_port: 4000, + first_public_key: "node1", + last_public_key: "node1", + geo_patch: "AAA", + network_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() + }) + + assert :ok = PoolsMemTable.put_pool_member(:technical_council, tx.previous_public_key) + + MockDB + |> expect(:get_transaction, fn _, _ -> + {:ok, + %Transaction{ + data: %TransactionData{ + content: """ + Description: My Super Description + Changes: + diff --git a/mix.exs b/mix.exs + index d9d9a06..5e34b89 100644 + --- a/mix.exs + +++ b/mix.exs + @@ -4,7 +4,7 @@ defmodule Archethic.MixProject do + def project do + [ + app: :archethic, + - version: \"0.7.1\", + + version: \"0.7.2\", + build_path: \"_build\", + config_path: \"config/config.exs\", + deps_path: \"deps\", + @@ -53,7 +53,7 @@ defmodule Archethic.MixProject do + {:git_hooks, \"~> 0.4.0\", only: [:test, :dev], runtime: false}, + {:mox, \"~> 0.5.2\", only: [:test]}, + {:stream_data, \"~> 0.4.3\", only: [:test]}, + - {:elixir_make, \"~> 0.6.0\", only: [:dev, :test], runtime: false}, + + {:elixir_make, \"~> 0.6.0\", only: [:dev, :test]}, + {:logger_file_backend, \"~> 0.0.11\", only: [:dev]} + ] + end + """ + } + }} + end) + + MockClient + |> stub(:send_message, fn + _, %GetFirstPublicKey{}, _ -> + {:ok, %FirstPublicKey{public_key: tx.previous_public_key}} + + _, %GetTransactionSummary{}, _ -> + {:ok, %NotFound{}} + end) + + assert :ok = PendingTransactionValidation.validate(tx) + end + end + + describe "Contract" do + end + + describe "Hosting" do + test "should return :ok when we deploy a aeweb ref transaction" do + tx = + Transaction.new(:hosting, %TransactionData{ + content: + Jason.encode!(%{ + "aewebVersion" => 1, + "metaData" => %{ + "index.html" => %{ + "encoding" => "gzip", + "hash" => "abcd123", + "size" => 144, + "addresses" => [ + Crypto.derive_keypair("seed", 0) + |> elem(0) + |> Crypto.derive_address() + |> Base.encode16() + ] + } + } + }) + }) + + assert :ok = PendingTransactionValidation.validate(tx, DateTime.utc_now()) + end + + test "should return :ok when we deploy a aeweb file transaction" do + tx = + Transaction.new(:hosting, %TransactionData{ + content: + Jason.encode!(%{ + "index.html" => Base.url_encode64(:crypto.strong_rand_bytes(1000)) + }) + }) + + assert :ok = PendingTransactionValidation.validate(tx, DateTime.utc_now()) + end + + test "should return :error when we deploy a wrong aeweb file transaction" do + tx = + Transaction.new(:hosting, %TransactionData{ + content: + Jason.encode!(%{ + "index.html" => 32 + }) + }) + + assert {:error, _error} = PendingTransactionValidation.validate(tx, DateTime.utc_now()) + end + + test "should return :error when we deploy a wrong aeweb ref transaction" do + tx = + Transaction.new(:hosting, %TransactionData{ + content: + Jason.encode!(%{ + "wrongKey" => 1, + "metaData" => %{ + "index.html" => %{ + "encoding" => "gzip", + "hash" => "abcd123", + "size" => 144, + "addresses" => [ + Crypto.derive_keypair("seed", 0) + |> elem(0) + |> Crypto.derive_address() + |> Base.encode16() + ] + } + } + }) + }) + + assert {:error, _reason} = PendingTransactionValidation.validate(tx, DateTime.utc_now()) + end + end + + 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) @@ -173,7 +330,9 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do assert {:error, "invalid transaction : transaction data exceeds limit"} = PendingTransactionValidation.validate(tx) end + end + 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}, @@ -277,6 +436,51 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do :persistent_term.put(:node_shared_secrets_gen_addr, nil) end + test "should return error when there is already a node shared secrets transaction since the last schedule" do + MockDB + |> expect(:get_last_chain_address, fn _, _ -> + {"OtherAddress", DateTime.utc_now()} + end) + + tx = + Transaction.new( + :node_shared_secrets, + %TransactionData{ + content: :crypto.strong_rand_bytes(32), + ownerships: [ + %Ownership{ + secret: :crypto.strong_rand_bytes(32), + authorized_keys: %{ + <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> => + :crypto.strong_rand_bytes(32) + } + } + ] + }, + "seed", + 0 + ) + + assert {:error, "Invalid node shared secrets trigger time"} = + PendingTransactionValidation.validate(tx, ~U[2022-01-01 00:00:03Z]) + end + end + + describe "Oracle" do + test "should return error when there is already a oracle transaction since the last schedule" do + MockDB + |> expect(:get_last_chain_address, fn _, _ -> + {"OtherAddress", DateTime.utc_now()} + end) + + tx = Transaction.new(:oracle, %TransactionData{}, "seed", 0) + + assert {:error, "Invalid oracle trigger time"} = + PendingTransactionValidation.validate(tx, ~U[2022-01-01 00:10:03Z]) + end + end + + describe "Origin" do test "should return :ok when a origin transaction is made" do P2P.add_and_connect_node(%Node{ ip: {127, 0, 0, 1}, @@ -416,150 +620,9 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do :persistent_term.put(:origin_gen_addr, nil) end + end - test "should return :ok when a code approval transaction contains a proposal target and the sender is member of the technical council and not previously signed" do - tx = - Transaction.new( - :code_approval, - %TransactionData{ - recipients: ["@CodeProposal1"] - }, - "approval_seed", - 0 - ) - - P2P.add_and_connect_node(%Node{ - ip: {127, 0, 0, 1}, - port: 3000, - http_port: 4000, - first_public_key: "node1", - last_public_key: "node1", - geo_patch: "AAA", - network_patch: "AAA", - available?: true, - authorized?: true, - authorization_date: DateTime.utc_now() - }) - - assert :ok = PoolsMemTable.put_pool_member(:technical_council, tx.previous_public_key) - - MockDB - |> expect(:get_transaction, fn _, _ -> - {:ok, - %Transaction{ - data: %TransactionData{ - content: """ - Description: My Super Description - Changes: - diff --git a/mix.exs b/mix.exs - index d9d9a06..5e34b89 100644 - --- a/mix.exs - +++ b/mix.exs - @@ -4,7 +4,7 @@ defmodule Archethic.MixProject do - def project do - [ - app: :archethic, - - version: \"0.7.1\", - + version: \"0.7.2\", - build_path: \"_build\", - config_path: \"config/config.exs\", - deps_path: \"deps\", - @@ -53,7 +53,7 @@ defmodule Archethic.MixProject do - {:git_hooks, \"~> 0.4.0\", only: [:test, :dev], runtime: false}, - {:mox, \"~> 0.5.2\", only: [:test]}, - {:stream_data, \"~> 0.4.3\", only: [:test]}, - - {:elixir_make, \"~> 0.6.0\", only: [:dev, :test], runtime: false}, - + {:elixir_make, \"~> 0.6.0\", only: [:dev, :test]}, - {:logger_file_backend, \"~> 0.0.11\", only: [:dev]} - ] - end - """ - } - }} - end) - - MockClient - |> stub(:send_message, fn - _, %GetFirstPublicKey{}, _ -> - {:ok, %FirstPublicKey{public_key: tx.previous_public_key}} - - _, %GetTransactionSummary{}, _ -> - {:ok, %NotFound{}} - end) - - assert :ok = PendingTransactionValidation.validate(tx) - end - - test "should return :ok when a transaction contains a valid smart contract code" do - tx_seed = :crypto.strong_rand_bytes(32) - - tx = - Transaction.new( - :transfer, - %TransactionData{ - ledger: %Ledger{ - uco: %UCOLedger{ - transfers: [ - %Transfer{to: :crypto.strong_rand_bytes(32), amount: 100_000} - ] - } - }, - code: """ - condition inherit: [ - content: "hello" - ] - - condition transaction: [ - content: "" - ] - - actions triggered_by: transaction do - set_content "hello" - end - """, - ownerships: [ - Ownership.new(tx_seed, :crypto.strong_rand_bytes(32), [ - Crypto.storage_nonce_public_key() - ]) - ] - }, - tx_seed, - 0 - ) - - assert :ok = PendingTransactionValidation.validate(tx) - end - - test "should return :ok when a transaction contains valid fields for token creation" do - tx_seed = :crypto.strong_rand_bytes(32) - - tx = - Transaction.new( - :token, - %TransactionData{ - content: - Jason.encode!(%{ - supply: 300_000_000, - name: "MyToken", - type: "non-fungible", - symbol: "MTK", - properties: %{ - global: "property" - }, - collection: [ - %{image: "link", value: "link"}, - %{image: "link", value: "link"}, - %{image: "link", value: "link"} - ] - }) - }, - tx_seed, - 0 - ) - - assert :ok = PendingTransactionValidation.validate(tx) - end - + describe "Reward" do test "should return :ok when a mint reward transaction passes all tests" do tx_seed = :crypto.strong_rand_bytes(32) {pub, _} = Crypto.derive_keypair(tx_seed, 1) @@ -680,47 +743,6 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do PendingTransactionValidation.validate(tx) end - test "should return error when there is already a oracle transaction since the last schedule" do - MockDB - |> expect(:get_last_chain_address, fn _, _ -> - {"OtherAddress", DateTime.utc_now()} - end) - - tx = Transaction.new(:oracle, %TransactionData{}, "seed", 0) - - assert {:error, "Invalid oracle trigger time"} = - PendingTransactionValidation.validate(tx, ~U[2022-01-01 00:10:03Z]) - end - - test "should return error when there is already a node shared secrets transaction since the last schedule" do - MockDB - |> expect(:get_last_chain_address, fn _, _ -> - {"OtherAddress", DateTime.utc_now()} - end) - - tx = - Transaction.new( - :node_shared_secrets, - %TransactionData{ - content: :crypto.strong_rand_bytes(32), - ownerships: [ - %Ownership{ - secret: :crypto.strong_rand_bytes(32), - authorized_keys: %{ - <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> => - :crypto.strong_rand_bytes(32) - } - } - ] - }, - "seed", - 0 - ) - - assert {:error, "Invalid node shared secrets trigger time"} = - PendingTransactionValidation.validate(tx, ~U[2022-01-01 00:00:03Z]) - end - test "should return error when there is already a node rewards transaction since the last schedule" do MockDB |> expect(:get_last_chain_address, fn _, _ -> @@ -741,79 +763,79 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do assert {:error, "Invalid node rewards trigger time"} = PendingTransactionValidation.validate(tx, ~U[2022-01-01 00:00:03Z]) end + end - test "should return :ok when we deploy a aeweb ref transaction" do - tx = - Transaction.new(:hosting, %TransactionData{ - content: - Jason.encode!(%{ - "aewebVersion" => 1, - "metaData" => %{ - "index.html" => %{ - "encoding" => "gzip", - "hash" => "abcd123", - "size" => 144, - "addresses" => [ - Crypto.derive_keypair("seed", 0) - |> elem(0) - |> Crypto.derive_address() - |> Base.encode16() - ] - } - } - }) - }) - - assert :ok = PendingTransactionValidation.validate(tx, DateTime.utc_now()) - end + describe "token" do + test "should return :ok when a transaction contains valid fields for token creation" do + tx_seed = :crypto.strong_rand_bytes(32) - test "should return :ok when we deploy a aeweb file transaction" do tx = - Transaction.new(:hosting, %TransactionData{ - content: - Jason.encode!(%{ - "index.html" => Base.url_encode64(:crypto.strong_rand_bytes(1000)) - }) - }) + Transaction.new( + :token, + %TransactionData{ + content: + Jason.encode!(%{ + supply: 300_000_000, + name: "MyToken", + type: "non-fungible", + symbol: "MTK", + properties: %{ + global: "property" + }, + collection: [ + %{image: "link", value: "link"}, + %{image: "link", value: "link"}, + %{image: "link", value: "link"} + ] + }) + }, + tx_seed, + 0 + ) - assert :ok = PendingTransactionValidation.validate(tx, DateTime.utc_now()) + assert :ok = PendingTransactionValidation.validate(tx) end + end - test "should return :error when we deploy a wrong aeweb file transaction" do - tx = - Transaction.new(:hosting, %TransactionData{ - content: - Jason.encode!(%{ - "index.html" => 32 - }) - }) - - assert {:error, _error} = PendingTransactionValidation.validate(tx, DateTime.utc_now()) - end + describe "transfer" do + test "should return :ok when a transaction contains a valid smart contract code" do + tx_seed = :crypto.strong_rand_bytes(32) - test "should return :error when we deploy a wrong aeweb ref transaction" do tx = - Transaction.new(:hosting, %TransactionData{ - content: - Jason.encode!(%{ - "wrongKey" => 1, - "metaData" => %{ - "index.html" => %{ - "encoding" => "gzip", - "hash" => "abcd123", - "size" => 144, - "addresses" => [ - Crypto.derive_keypair("seed", 0) - |> elem(0) - |> Crypto.derive_address() - |> Base.encode16() - ] - } + Transaction.new( + :transfer, + %TransactionData{ + ledger: %Ledger{ + uco: %UCOLedger{ + transfers: [ + %Transfer{to: :crypto.strong_rand_bytes(32), amount: 100_000} + ] } - }) - }) + }, + code: """ + condition inherit: [ + content: "hello" + ] - assert {:error, _reason} = PendingTransactionValidation.validate(tx, DateTime.utc_now()) + condition transaction: [ + content: "" + ] + + actions triggered_by: transaction do + set_content "hello" + end + """, + ownerships: [ + Ownership.new(tx_seed, :crypto.strong_rand_bytes(32), [ + Crypto.storage_nonce_public_key() + ]) + ] + }, + tx_seed, + 0 + ) + + assert :ok = PendingTransactionValidation.validate(tx) end end end From 0bba40ea1d987fcfdae87f9f9ffcb5db970ecbdb Mon Sep 17 00:00:00 2001 From: apoorv-2204 Date: Thu, 12 Jan 2023 01:40:27 +0530 Subject: [PATCH 11/15] temp --- .../mining/pending_transaction_validation.ex | 12 +-- .../pending_transaction_validation_test.exs | 77 ++++++++++++++++++- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 3407f46eb..417a48bd8 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -86,7 +86,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do :ok else {:error, reason} = e -> - Logger.info("Invalid Transaction: #{inspect(reason)}", + Logger.info(reason, transaction_address: Base.encode16(address), transaction_type: type ) @@ -102,10 +102,12 @@ defmodule Archethic.Mining.PendingTransactionValidation do |> byte_size() if tx_size >= @tx_max_size do - {:error, "invalid transaction : transaction data exceeds limit"} + {:error, "invalid transaction: transaction data exceeds limit"} else :ok end + rescue + _ -> {:error, "invalid transaction: serialization error"} end defp valid_not_exists(%Transaction{address: address}) do @@ -173,7 +175,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do end @spec validate_ownerships(Transaction.t()) :: :ok | {:error, any()} - def validate_ownerships(%Transaction{data: %TransactionData{ownerships: ownerships}}) do + defp validate_ownerships(%Transaction{data: %TransactionData{ownerships: ownerships}}) do nb_ownerships = length(ownerships) # defstruct recipients: [], ledger: %Ledger{}`, code: "", ownerships: [], content: "" @@ -211,7 +213,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do Enum.reduce_while(authorized_keys, {:cont, :ok}, fn {"", _}, _ -> - e = {:halt, {:error, "invalid transaction - Ownership: public key is empty "}} + e = {:halt, {:error, "invalid transaction - Ownership: public key is empty"}} {:halt, e} {_, ""}, _ -> @@ -676,7 +678,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do defp do_accept_transaction(%Transaction{type: :contract, data: %TransactionData{code: code}}, _) do cond do byte_size(code) == 0 -> - {:error, "invalid contract type transaction - code is empty "} + {:error, "invalid contract type transaction - code is empty"} byte_size(code) >= @code_max_size -> {:error, "invalid contract type transaction , code exceed max size "} diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index 8ee5686ff..0f4a3a2f0 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -54,7 +54,77 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do :ok end + describe "validate_size/1" do + test "should return :ok when the transaction size is less than 3.1MB" do + tx = Transaction.new(:data, %TransactionData{content: :crypto.strong_rand_bytes(3_145_711)}) + + assert :ok = PendingTransactionValidation.validate(tx) + end + + test "should return transaction data exceeds limit when the transaction size is greater than 3.1MB" do + tx = Transaction.new(:data, %TransactionData{content: :crypto.strong_rand_bytes(3_145_728)}) + + assert {:error, "invalid transaction: transaction data exceeds limit"} = + PendingTransactionValidation.validate(tx) + end + end + + describe "validate_validate_ownerships" do + defp get_tx(ownership) do + Transaction.new(:data, %TransactionData{ownerships: ownership}) + end + + test "validate conditions for ownerships" do + assert {:error, "invalid data type transaction - Both content & ownership are empty"} = + PendingTransactionValidation.validate(get_tx([])) + + assert {:error, "invalid transaction - Ownership: secret is empty"} = + [%Ownership{secret: "", authorized_keys: %{}}] + |> get_tx() + |> PendingTransactionValidation.validate() + + assert {:error, "invalid transaction - Ownership: authorized keys are empty"} = + [%Ownership{secret: "secret", authorized_keys: %{}}] + |> get_tx() + |> PendingTransactionValidation.validate() + + assert {:error, "invalid transaction - Ownership: public key is empty"} = + [%Ownership{secret: "secret", authorized_keys: %{"" => "ecnrypted_key"}}] + |> get_tx() + |> PendingTransactionValidation.validate() + + assert {:error, "invalid transaction - Ownership: encrypted key is empty"} = + [%Ownership{secret: "secret", authorized_keys: %{"abc" => ""}}] + |> get_tx() + |> PendingTransactionValidation.validate() + + assert {:error, "invalid transaction - Ownership: invalid public key"} = + [%Ownership{secret: "secret", authorized_keys: %{"abc" => "cba"}}] + |> get_tx() + |> PendingTransactionValidation.validate() + + assert :ok = + [%Ownership{secret: "secret", authorized_keys: %{<<0::272>> => "cba"}}] + |> get_tx() + |> PendingTransactionValidation.validate() + end + end + describe "Data" do + test "Should return error when both content and ownerships are empty" do + assert {:error, "invalid data type transaction - Both content & ownership are empty"} = + Transaction.new(:data, %TransactionData{}) + |> PendingTransactionValidation.validate() + + assert :ok == + [%Ownership{secret: "secret", authorized_keys: %{<<0::272>> => "cba"}}] + |> get_tx() + |> PendingTransactionValidation.validate() + + assert :ok == + Transaction.new(:data, %TransactionData{content: "content"}) + |> PendingTransactionValidation.validate() + end end describe "Code Approval" do @@ -133,6 +203,11 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do end describe "Contract" do + test "should return error when code is empty" do + assert {:error, "invalid contract type transaction - code is empty"} = + Transaction.new(:contract, %TransactionData{code: ""}) + |> PendingTransactionValidation.validate() + end end describe "Hosting" do @@ -327,7 +402,7 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do {:error, :transaction_not_exists} end) - assert {:error, "invalid transaction : transaction data exceeds limit"} = + assert {:error, "invalid transaction: transaction data exceeds limit"} = PendingTransactionValidation.validate(tx) end end From e82fe60af2338dce88ff6111eca566baa9a9d88a Mon Sep 17 00:00:00 2001 From: apoorv-2204 Date: Thu, 12 Jan 2023 11:32:23 +0530 Subject: [PATCH 12/15] tests --- .../mining/pending_transaction_validation.ex | 21 ++--- mix.lock | 2 +- .../pending_transaction_validation_test.exs | 81 +++++++++++++------ 3 files changed, 68 insertions(+), 36 deletions(-) diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 417a48bd8..6b1359f94 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -154,7 +154,8 @@ defmodule Archethic.Mining.PendingTransactionValidation do defp validate_contract(%Transaction{ data: %TransactionData{code: code, ownerships: ownerships} - }) do + }) + when byte_size(code) <= @code_max_size do case Contracts.parse(code) do {:ok, %Contract{triggers: triggers}} when map_size(triggers) > 0 -> if Enum.any?( @@ -174,6 +175,11 @@ defmodule Archethic.Mining.PendingTransactionValidation do end end + defp validate_contract(%Transaction{data: %TransactionData{code: code}}) + when byte_size(code) > @code_max_size do + {:error, "invalid contract type transaction , code exceed max size"} + end + @spec validate_ownerships(Transaction.t()) :: :ok | {:error, any()} defp validate_ownerships(%Transaction{data: %TransactionData{ownerships: ownerships}}) do nb_ownerships = length(ownerships) @@ -676,15 +682,10 @@ defmodule Archethic.Mining.PendingTransactionValidation do end defp do_accept_transaction(%Transaction{type: :contract, data: %TransactionData{code: code}}, _) do - cond do - byte_size(code) == 0 -> - {:error, "invalid contract type transaction - code is empty"} - - byte_size(code) >= @code_max_size -> - {:error, "invalid contract type transaction , code exceed max size "} - - true -> - :ok + if byte_size(code) == 0 do + {:error, "invalid contract type transaction - code is empty"} + else + :ok end end diff --git a/mix.lock b/mix.lock index fc063fd0d..33b604f0e 100644 --- a/mix.lock +++ b/mix.lock @@ -8,7 +8,7 @@ "benchee_json": {:hex, :benchee_json, "1.0.0", "cc661f4454d5995c08fe10dd1f2f72f229c8f0fb1c96f6b327a8c8fc96a91fe5", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "da05d813f9123505f870344d68fb7c86a4f0f9074df7d7b7e2bb011a63ec231c"}, "blankable": {:hex, :blankable, "1.0.0", "89ab564a63c55af117e115144e3b3b57eb53ad43ba0f15553357eb283e0ed425", [:mix], [], "hexpm", "7cf11aac0e44f4eedbee0c15c1d37d94c090cb72a8d9fddf9f7aec30f9278899"}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, - "castore": {:hex, :castore, "0.1.20", "62a0126cbb7cb3e259257827b9190f88316eb7aa3fdac01fd6f2dfd64e7f46e9", [:mix], [], "hexpm", "a020b7650529c986c454a4035b6b13a328e288466986307bea3aadb4c95ac98a"}, + "castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"}, "cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"}, "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index 0f4a3a2f0..63fa62dfb 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -1,32 +1,34 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do use ArchethicCase, async: false - alias Archethic.Crypto + alias Archethic.{ + Crypto, + Mining.PendingTransactionValidation, + P2P, + P2P.Node, + Reward.Scheduler, + SharedSecrets, + TransactionChain + } alias Archethic.Governance.Pools.MemTable, as: PoolsMemTable - - alias Archethic.Mining.PendingTransactionValidation - - alias Archethic.SharedSecrets.MemTables.NetworkLookup - - alias Archethic.Reward.Scheduler - - alias Archethic.P2P - alias Archethic.P2P.Message.FirstPublicKey - alias Archethic.P2P.Message.GetFirstPublicKey - alias Archethic.P2P.Message.GetTransactionSummary - alias Archethic.P2P.Message.NotFound - alias Archethic.P2P.Node - - alias Archethic.TransactionChain.Transaction - alias Archethic.TransactionChain.TransactionData - alias Archethic.TransactionChain.TransactionData.Ledger - alias Archethic.TransactionChain.TransactionData.UCOLedger - alias Archethic.TransactionChain.TransactionData.UCOLedger.Transfer - alias Archethic.TransactionChain.TransactionData.Ownership - - alias Archethic.SharedSecrets - alias Archethic.SharedSecrets.MemTables.OriginKeyLookup + alias Archethic.SharedSecrets.{MemTables.NetworkLookup, MemTables.OriginKeyLookup} + + alias Archethic.P2P.Message.{ + FirstPublicKey, + GetFirstPublicKey, + GetTransactionSummary, + NotFound + } + + alias Archethic.TransactionChain.{ + Transaction, + TransactionData, + TransactionData.Ledger, + TransactionData.UCOLedger, + TransactionData.UCOLedger.Transfer, + TransactionData.Ownership + } import Mox @@ -69,7 +71,7 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do end end - describe "validate_validate_ownerships" do + describe "validate_ownerships" do defp get_tx(ownership) do Transaction.new(:data, %TransactionData{ownerships: ownership}) end @@ -110,6 +112,35 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do end end + describe "validate_contract" do + test "parse" do + code = ~s""" + condition inherit: [ + uco_transfers: %{ "7F6661ACE282F947ACA2EF947D01BDDC90C65F09EE828BDADE2E3ED4258470B3" => 1040000000 } + ] + """ + + assert :ok = + Transaction.new(:contract, %TransactionData{code: code}) + |> PendingTransactionValidation.validate() + end + + test "exceeds max code size" do + size = Application.get_env(:archethic, :transaction_data_code_max_size) + data = :crypto.strong_rand_bytes(size + 1) + + code = ~s""" + condition transaction: [ + content: hash(#{data}}) + ] + """ + + assert {:error, "invalid contract type transaction , code exceed max size"} = + Transaction.new(:contract, %TransactionData{code: code}) + |> PendingTransactionValidation.validate() + end + end + describe "Data" do test "Should return error when both content and ownerships are empty" do assert {:error, "invalid data type transaction - Both content & ownership are empty"} = From d0212bb62800b8bce1f80993c6f6e8d7fde2960b Mon Sep 17 00:00:00 2001 From: apoorv-2204 Date: Thu, 12 Jan 2023 13:09:06 +0530 Subject: [PATCH 13/15] review changes --- .../mining/pending_transaction_validation.ex | 57 ++++++------------- .../pending_transaction_validation_test.exs | 2 +- 2 files changed, 18 insertions(+), 41 deletions(-) diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 6b1359f94..30d7a5f12 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -106,8 +106,6 @@ defmodule Archethic.Mining.PendingTransactionValidation do else :ok end - rescue - _ -> {:error, "invalid transaction: serialization error"} end defp valid_not_exists(%Transaction{address: address}) do @@ -181,19 +179,13 @@ defmodule Archethic.Mining.PendingTransactionValidation do end @spec validate_ownerships(Transaction.t()) :: :ok | {:error, any()} - defp validate_ownerships(%Transaction{data: %TransactionData{ownerships: ownerships}}) do - nb_ownerships = length(ownerships) + defp validate_ownerships(%Transaction{data: %TransactionData{ownerships: []}}), do: :ok - # defstruct recipients: [], ledger: %Ledger{}`, code: "", ownerships: [], content: "" - if nb_ownerships == 0 do - # we might not have any ownerships at all - :ok - else - do_validate_ownerships(ownerships) - end + defp validate_ownerships(%Transaction{data: %TransactionData{ownerships: ownerships}}) do + do_validate_ownerships(ownerships) end - def do_validate_ownerships(ownerships) do + defp do_validate_ownerships(ownerships) do # handles irregulrarites in ownerships Enum.reduce_while(ownerships, :ok, fn ownership, :ok -> @@ -214,7 +206,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do end @spec verify_authorized_keys(any) :: any - def verify_authorized_keys(authorized_keys) do + defp verify_authorized_keys(authorized_keys) do # authorized_keys: %{(public_key :: Crypto.key()) => encrypted_key } Enum.reduce_while(authorized_keys, {:cont, :ok}, fn @@ -690,31 +682,16 @@ defmodule Archethic.Mining.PendingTransactionValidation do end defp do_accept_transaction( - %Transaction{ - type: :data, - data: %TransactionData{content: content, ownerships: ownerships} - }, + %Transaction{type: :data, data: %TransactionData{content: "", ownerships: []}}, _ - ) do - nb_ownerships = length(ownerships) - - cond do - content == "" && nb_ownerships == 0 -> - # content or ownership either must be present or error - # defstruct recipients: [], ledger: %Ledger{}, code: "", ownerships: [], content: "" - {:error, "invalid data type transaction - Both content & ownership are empty"} - - content == "" && nb_ownerships != 0 -> - :ok + ), + do: {:error, "invalid data type transaction - Both content & ownership are empty"} - nb_ownerships == 0 && content != "" -> - :ok - end - end + defp do_accept_transaction(%Transaction{type: :data}, _), do: :ok defp do_accept_transaction(_, _), do: :ok - def validate_previous_transaction_type?(tx) do + defp validate_previous_transaction_type?(tx) do case Transaction.network_type?(tx.type) do false -> # not a network tx, no need to validate with last tx @@ -740,8 +717,8 @@ defmodule Archethic.Mining.PendingTransactionValidation do | :origin, Archethic.TransactionChain.Transaction.t() ) :: boolean - def validate_network_chain?(type, tx = %Transaction{}) - when type in [:oracle, :oracle_summary] do + defp validate_network_chain?(type, tx = %Transaction{}) + when type in [:oracle, :oracle_summary] do # mulitpe txn chain based on summary date case OracleChain.genesis_address() do @@ -763,8 +740,8 @@ defmodule Archethic.Mining.PendingTransactionValidation do end end - def validate_network_chain?(type, tx = %Transaction{}) - when type in [:mint_rewards, :node_rewards] do + defp validate_network_chain?(type, tx = %Transaction{}) + when type in [:mint_rewards, :node_rewards] do # singleton tx chain in network lifespan case Reward.genesis_address() do nil -> @@ -781,7 +758,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do end end - def validate_network_chain?(:node_shared_secrets, tx = %Transaction{}) do + defp validate_network_chain?(:node_shared_secrets, tx = %Transaction{}) do # singleton tx chain in network lifespan case SharedSecrets.genesis_address(:node_shared_secrets) do nil -> @@ -797,7 +774,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do end end - def validate_network_chain?(:origin, tx = %Transaction{}) do + defp validate_network_chain?(:origin, tx = %Transaction{}) do # singleton tx chain in network lifespan # not parsing orgin pub key for origin family case SharedSecrets.genesis_address(:origin) do @@ -814,7 +791,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do end end - def validate_network_chain?(_type, _tx), do: true + defp validate_network_chain?(_type, _tx), do: true defp verify_token_creation(content) do with {:ok, json_token} <- Jason.decode(content), diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index 63fa62dfb..885820486 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -21,7 +21,7 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do NotFound } - alias Archethic.TransactionChain.{ + alias TransactionChain.{ Transaction, TransactionData, TransactionData.Ledger, From abe5228ca7bae226af9543db981056448b14c4bf Mon Sep 17 00:00:00 2001 From: apoorv-2204 Date: Thu, 12 Jan 2023 15:20:54 +0530 Subject: [PATCH 14/15] review Changes --- lib/archethic/mining/pending_transaction_validation.ex | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 30d7a5f12..028859ea7 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -182,11 +182,6 @@ defmodule Archethic.Mining.PendingTransactionValidation do defp validate_ownerships(%Transaction{data: %TransactionData{ownerships: []}}), do: :ok defp validate_ownerships(%Transaction{data: %TransactionData{ownerships: ownerships}}) do - do_validate_ownerships(ownerships) - end - - defp do_validate_ownerships(ownerships) do - # handles irregulrarites in ownerships Enum.reduce_while(ownerships, :ok, fn ownership, :ok -> %Ownership{secret: secret, authorized_keys: authorized_keys} = ownership @@ -205,7 +200,8 @@ defmodule Archethic.Mining.PendingTransactionValidation do end) end - @spec verify_authorized_keys(any) :: any + @spec verify_authorized_keys(authorized_keys :: map()) :: + {:cont, :ok} | {:halt, {:error, any()}} defp verify_authorized_keys(authorized_keys) do # authorized_keys: %{(public_key :: Crypto.key()) => encrypted_key } From 0ae3bd33956234695994a1b8965f2d53d663d91f Mon Sep 17 00:00:00 2001 From: Neylix Date: Fri, 13 Jan 2023 12:35:42 +0100 Subject: [PATCH 15/15] Few improvements for logging and pattern matching --- .../mining/pending_transaction_validation.ex | 38 +++++++------------ .../controllers/api/types/transaction_type.ex | 2 + .../pending_transaction_validation_test.exs | 22 +++++------ 3 files changed, 27 insertions(+), 35 deletions(-) diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 028859ea7..cfa9fbe10 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -86,7 +86,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do :ok else {:error, reason} = e -> - Logger.info(reason, + Logger.info("Invalid Transaction: #{reason}", transaction_address: Base.encode16(address), transaction_type: type ) @@ -102,7 +102,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do |> byte_size() if tx_size >= @tx_max_size do - {:error, "invalid transaction: transaction data exceeds limit"} + {:error, "Transaction data exceeds limit"} else :ok end @@ -129,7 +129,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do conflict_resolver ) do {:ok, %TransactionSummary{address: ^address}} -> - {:error, "transaction already exists"} + {:error, "Transaction already exists"} {:ok, %NotFound{}} -> :ok @@ -175,7 +175,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do defp validate_contract(%Transaction{data: %TransactionData{code: code}}) when byte_size(code) > @code_max_size do - {:error, "invalid contract type transaction , code exceed max size"} + {:error, "Invalid contract type transaction , code exceed max size"} end @spec validate_ownerships(Transaction.t()) :: :ok | {:error, any()} @@ -183,16 +183,13 @@ defmodule Archethic.Mining.PendingTransactionValidation do defp validate_ownerships(%Transaction{data: %TransactionData{ownerships: ownerships}}) do Enum.reduce_while(ownerships, :ok, fn - ownership, :ok -> - %Ownership{secret: secret, authorized_keys: authorized_keys} = ownership - nb_authorized_keys = map_size(authorized_keys) - + %Ownership{secret: secret, authorized_keys: authorized_keys}, :ok -> cond do secret == "" -> - {:halt, {:error, "invalid transaction - Ownership: secret is empty"}} + {:halt, {:error, "Ownership: secret is empty"}} - nb_authorized_keys == 0 -> - {:halt, {:error, "invalid transaction - Ownership: authorized keys are empty"}} + authorized_keys == %{} -> + {:halt, {:error, "Ownership: authorized keys are empty"}} true -> verify_authorized_keys(authorized_keys) @@ -207,17 +204,17 @@ defmodule Archethic.Mining.PendingTransactionValidation do Enum.reduce_while(authorized_keys, {:cont, :ok}, fn {"", _}, _ -> - e = {:halt, {:error, "invalid transaction - Ownership: public key is empty"}} + e = {:halt, {:error, "Ownership: public key is empty"}} {:halt, e} {_, ""}, _ -> - e = {:halt, {:error, "invalid transaction - Ownership: encrypted key is empty"}} + e = {:halt, {:error, "Ownership: encrypted key is empty"}} {:halt, e} {public_key, _}, acc -> if Crypto.valid_public_key?(public_key), do: {:cont, acc}, - else: {:halt, {:halt, {:error, "invalid transaction - Ownership: invalid public key"}}} + else: {:halt, {:halt, {:error, "Ownership: invalid public key"}}} end) end @@ -669,21 +666,14 @@ defmodule Archethic.Mining.PendingTransactionValidation do end end - defp do_accept_transaction(%Transaction{type: :contract, data: %TransactionData{code: code}}, _) do - if byte_size(code) == 0 do - {:error, "invalid contract type transaction - code is empty"} - else - :ok - end - end + defp do_accept_transaction(%Transaction{type: :contract, data: %TransactionData{code: ""}}, _), + do: {:error, "Invalid contract type transaction - code is empty"} defp do_accept_transaction( %Transaction{type: :data, data: %TransactionData{content: "", ownerships: []}}, _ ), - do: {:error, "invalid data type transaction - Both content & ownership are empty"} - - defp do_accept_transaction(%Transaction{type: :data}, _), do: :ok + do: {:error, "Invalid data type transaction - Both content & ownership are empty"} defp do_accept_transaction(_, _), do: :ok diff --git a/lib/archethic_web/controllers/api/types/transaction_type.ex b/lib/archethic_web/controllers/api/types/transaction_type.ex index 2e7796c49..c3126e0ae 100644 --- a/lib/archethic_web/controllers/api/types/transaction_type.ex +++ b/lib/archethic_web/controllers/api/types/transaction_type.ex @@ -11,6 +11,8 @@ defmodule ArchethicWeb.API.Types.TransactionType do "transfer", "hosting", "token", + "data", + "contract", "code_proposal", "code_approval" ] diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index 885820486..f00c19acf 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -66,7 +66,7 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do test "should return transaction data exceeds limit when the transaction size is greater than 3.1MB" do tx = Transaction.new(:data, %TransactionData{content: :crypto.strong_rand_bytes(3_145_728)}) - assert {:error, "invalid transaction: transaction data exceeds limit"} = + assert {:error, "Transaction data exceeds limit"} = PendingTransactionValidation.validate(tx) end end @@ -77,30 +77,30 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do end test "validate conditions for ownerships" do - assert {:error, "invalid data type transaction - Both content & ownership are empty"} = + assert {:error, "Invalid data type transaction - Both content & ownership are empty"} = PendingTransactionValidation.validate(get_tx([])) - assert {:error, "invalid transaction - Ownership: secret is empty"} = + assert {:error, "Ownership: secret is empty"} = [%Ownership{secret: "", authorized_keys: %{}}] |> get_tx() |> PendingTransactionValidation.validate() - assert {:error, "invalid transaction - Ownership: authorized keys are empty"} = + assert {:error, "Ownership: authorized keys are empty"} = [%Ownership{secret: "secret", authorized_keys: %{}}] |> get_tx() |> PendingTransactionValidation.validate() - assert {:error, "invalid transaction - Ownership: public key is empty"} = + assert {:error, "Ownership: public key is empty"} = [%Ownership{secret: "secret", authorized_keys: %{"" => "ecnrypted_key"}}] |> get_tx() |> PendingTransactionValidation.validate() - assert {:error, "invalid transaction - Ownership: encrypted key is empty"} = + assert {:error, "Ownership: encrypted key is empty"} = [%Ownership{secret: "secret", authorized_keys: %{"abc" => ""}}] |> get_tx() |> PendingTransactionValidation.validate() - assert {:error, "invalid transaction - Ownership: invalid public key"} = + assert {:error, "Ownership: invalid public key"} = [%Ownership{secret: "secret", authorized_keys: %{"abc" => "cba"}}] |> get_tx() |> PendingTransactionValidation.validate() @@ -135,7 +135,7 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do ] """ - assert {:error, "invalid contract type transaction , code exceed max size"} = + assert {:error, "Invalid contract type transaction , code exceed max size"} = Transaction.new(:contract, %TransactionData{code: code}) |> PendingTransactionValidation.validate() end @@ -143,7 +143,7 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do describe "Data" do test "Should return error when both content and ownerships are empty" do - assert {:error, "invalid data type transaction - Both content & ownership are empty"} = + assert {:error, "Invalid data type transaction - Both content & ownership are empty"} = Transaction.new(:data, %TransactionData{}) |> PendingTransactionValidation.validate() @@ -235,7 +235,7 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do describe "Contract" do test "should return error when code is empty" do - assert {:error, "invalid contract type transaction - code is empty"} = + assert {:error, "Invalid contract type transaction - code is empty"} = Transaction.new(:contract, %TransactionData{code: ""}) |> PendingTransactionValidation.validate() end @@ -433,7 +433,7 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do {:error, :transaction_not_exists} end) - assert {:error, "invalid transaction: transaction data exceeds limit"} = + assert {:error, "Transaction data exceeds limit"} = PendingTransactionValidation.validate(tx) end end