From 44672a822cb5a8a1839a292036f686ead503bbe2 Mon Sep 17 00:00:00 2001 From: "bl@ckode" Date: Wed, 16 Feb 2022 17:31:34 +0530 Subject: [PATCH 1/5] Add validation for content at schema level --- config/config.exs | 3 +++ .../controllers/api/schema/transaction_data.ex | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/config/config.exs b/config/config.exs index 9db2b8bc2..139bda8ad 100644 --- a/config/config.exs +++ b/config/config.exs @@ -45,6 +45,9 @@ config :archethic, :mut_dir, "data" config :archethic, :marker, "-=%=-=%=-=%=-" +# size represents in mb i.e 1024 * 1024 * 3 bytes +config :archethic, :transaction_data_content_max_size, 3 + config :archethic, ArchEthic.Crypto, supported_curves: [ :ed25519, diff --git a/lib/archethic_web/controllers/api/schema/transaction_data.ex b/lib/archethic_web/controllers/api/schema/transaction_data.ex index 1b14e0944..cc76eb6b4 100644 --- a/lib/archethic_web/controllers/api/schema/transaction_data.ex +++ b/lib/archethic_web/controllers/api/schema/transaction_data.ex @@ -1,5 +1,6 @@ defmodule ArchEthicWeb.API.Schema.TransactionData do @moduledoc false + @content_max_size Application.get_env(:archethic, :transaction_data_content_max_size) use Ecto.Schema import Ecto.Changeset @@ -22,5 +23,21 @@ defmodule ArchEthicWeb.API.Schema.TransactionData do |> cast(params, [:code, :content, :recipients]) |> cast_embed(:ledger) |> cast_embed(:ownerships) + |> validate_content_size() + end + + defp validate_content_size(%Ecto.Changeset{} = changeset) do + content = Map.get(changeset.changes, :content) + content_size = byte_size(content) / (1024 * 1024) + + if content_size >= @content_max_size do + add_error( + changeset, + :content, + "Content size cannot be greater than #{@content_max_size} MB" + ) + else + changeset + end end end From f5303f2a5ee727661bd358b5278e97a853c787c6 Mon Sep 17 00:00:00 2001 From: "bl@ckode" Date: Thu, 17 Feb 2022 12:14:52 +0530 Subject: [PATCH 2/5] Add test case for validating content size --- .../api/schema/transaction_data.ex | 19 +++++++--------- .../controllers/api/transaction_payload.ex | 10 +++++++++ .../api/transaction_payload_test.exs | 22 +++++++++++++++++++ 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/lib/archethic_web/controllers/api/schema/transaction_data.ex b/lib/archethic_web/controllers/api/schema/transaction_data.ex index cc76eb6b4..0cbefbef2 100644 --- a/lib/archethic_web/controllers/api/schema/transaction_data.ex +++ b/lib/archethic_web/controllers/api/schema/transaction_data.ex @@ -27,17 +27,14 @@ defmodule ArchEthicWeb.API.Schema.TransactionData do end defp validate_content_size(%Ecto.Changeset{} = changeset) do - content = Map.get(changeset.changes, :content) - content_size = byte_size(content) / (1024 * 1024) + validate_change(changeset, :content, fn field, content -> + content_size = byte_size(content) / (1024 * 1024) - if content_size >= @content_max_size do - add_error( - changeset, - :content, - "Content size cannot be greater than #{@content_max_size} MB" - ) - else - changeset - end + if content_size >= @content_max_size do + [{field, "content size must be lessthan content_max_size"}] + else + [] + end + end) end end diff --git a/lib/archethic_web/controllers/api/transaction_payload.ex b/lib/archethic_web/controllers/api/transaction_payload.ex index 248e8a7a7..d8b3c8259 100644 --- a/lib/archethic_web/controllers/api/transaction_payload.ex +++ b/lib/archethic_web/controllers/api/transaction_payload.ex @@ -42,6 +42,7 @@ defmodule ArchEthicWeb.API.TransactionPayload do :originSignature ]) |> cast_embed(:data, required: true) + |> validate_data() end def to_map(changes, acc \\ %{}) @@ -65,4 +66,13 @@ defmodule ArchEthicWeb.API.TransactionPayload do end def to_map(value, _), do: value + + defp validate_data(%Ecto.Changeset{} = changeset) do + validate_change(changeset, :data, fn _, data_changeset -> + case data_changeset.valid? do + true -> [] + false -> data_changeset.errors + end + end) + end end diff --git a/test/archethic_web/controllers/api/transaction_payload_test.exs b/test/archethic_web/controllers/api/transaction_payload_test.exs index 935ecedb3..ed2480a07 100644 --- a/test/archethic_web/controllers/api/transaction_payload_test.exs +++ b/test/archethic_web/controllers/api/transaction_payload_test.exs @@ -77,6 +77,28 @@ defmodule ArchEthicWeb.API.TransactionPayloadTest do assert {"must be hexadecimal", _} = Keyword.get(errors, :content) end + test "should return an error if the content size is greater than content max size" do + content = Base.encode16(:crypto.strong_rand_bytes(4 * 1024 * 1024)) + + %Ecto.Changeset{ + valid?: false, + changes: %{data: %{errors: errors}} + } = + TransactionPayload.changeset(%{ + "version" => 1, + "address" => Base.encode16(<<0::8, :crypto.strong_rand_bytes(32)::binary>>), + "type" => "transfer", + "timestamp" => DateTime.utc_now() |> DateTime.to_unix(:millisecond), + "previousPublicKey" => + Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>), + "previousSignature" => Base.encode16(:crypto.strong_rand_bytes(64)), + "originSignature" => Base.encode16(:crypto.strong_rand_bytes(64)), + "data" => %{"content" => content} + }) + + assert {"content size must be lessthan content_max_size", _} = Keyword.get(errors, :content) + end + test "should return an error if the code is not a string" do %Ecto.Changeset{ valid?: false, From 3441e58c003e96ff761c2244518ebcf8c28d76d4 Mon Sep 17 00:00:00 2001 From: "bl@ckode" Date: Thu, 17 Feb 2022 14:35:39 +0530 Subject: [PATCH 3/5] Config updated from MB to bytes for content max size --- config/config.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/config.exs b/config/config.exs index 139bda8ad..c47b95f28 100644 --- a/config/config.exs +++ b/config/config.exs @@ -45,8 +45,8 @@ config :archethic, :mut_dir, "data" config :archethic, :marker, "-=%=-=%=-=%=-" -# size represents in mb i.e 1024 * 1024 * 3 bytes -config :archethic, :transaction_data_content_max_size, 3 +# size represents in bytes binary +config :archethic, :transaction_data_content_max_size, 3145728 config :archethic, ArchEthic.Crypto, supported_curves: [ From 2838712908f79dac8c2b49a000b4bffb6dbbd847 Mon Sep 17 00:00:00 2001 From: "bl@ckode" Date: Thu, 17 Feb 2022 14:38:14 +0530 Subject: [PATCH 4/5] Update validate_content_size calculation of size --- lib/archethic_web/controllers/api/schema/transaction_data.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/archethic_web/controllers/api/schema/transaction_data.ex b/lib/archethic_web/controllers/api/schema/transaction_data.ex index 0cbefbef2..02e6485ea 100644 --- a/lib/archethic_web/controllers/api/schema/transaction_data.ex +++ b/lib/archethic_web/controllers/api/schema/transaction_data.ex @@ -28,7 +28,7 @@ defmodule ArchEthicWeb.API.Schema.TransactionData do defp validate_content_size(%Ecto.Changeset{} = changeset) do validate_change(changeset, :content, fn field, content -> - content_size = byte_size(content) / (1024 * 1024) + content_size = byte_size(content) if content_size >= @content_max_size do [{field, "content size must be lessthan content_max_size"}] From e72fda0411bb7a468c0b1a370ade62dd0af73bea Mon Sep 17 00:00:00 2001 From: "bl@ckode" Date: Thu, 17 Feb 2022 15:36:57 +0530 Subject: [PATCH 5/5] Add validation for transaction content over mining and p2p --- config/config.exs | 2 +- .../mining/pending_transaction_validation.ex | 11 +++++++ .../api/schema/transaction_data.ex | 2 +- .../pending_transaction_validation_test.exs | 29 +++++++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/config/config.exs b/config/config.exs index c47b95f28..72b613219 100644 --- a/config/config.exs +++ b/config/config.exs @@ -46,7 +46,7 @@ config :archethic, :mut_dir, "data" config :archethic, :marker, "-=%=-=%=-=%=-" # size represents in bytes binary -config :archethic, :transaction_data_content_max_size, 3145728 +config :archethic, :transaction_data_content_max_size, 3_145_728 config :archethic, ArchEthic.Crypto, supported_curves: [ diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 219259070..4388230e6 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -42,6 +42,7 @@ defmodule ArchEthic.Mining.PendingTransactionValidation do with true <- Transaction.verify_previous_signature?(tx), :ok <- validate_contract(tx), + :ok <- validate_content_size(tx), :ok <- do_accept_transaction(tx) do :telemetry.execute( [:archethic, :mining, :pending_transaction_validation], @@ -65,6 +66,16 @@ defmodule ArchEthic.Mining.PendingTransactionValidation do end end + defp validate_content_size(%Transaction{data: %TransactionData{content: content}}) do + content_max_size = Application.get_env(:archethic, :transaction_data_content_max_size) + + if byte_size(content) >= content_max_size do + {:error, "Invalid node transaction with content size greaterthan content_max_size"} + else + :ok + end + end + defp validate_contract(%Transaction{data: %TransactionData{code: ""}}), do: :ok defp validate_contract(%Transaction{ diff --git a/lib/archethic_web/controllers/api/schema/transaction_data.ex b/lib/archethic_web/controllers/api/schema/transaction_data.ex index 02e6485ea..78c3a69d3 100644 --- a/lib/archethic_web/controllers/api/schema/transaction_data.ex +++ b/lib/archethic_web/controllers/api/schema/transaction_data.ex @@ -1,6 +1,6 @@ defmodule ArchEthicWeb.API.Schema.TransactionData do @moduledoc false - @content_max_size Application.get_env(:archethic, :transaction_data_content_max_size) + @content_max_size Application.compile_env!(:archethic, :transaction_data_content_max_size) use Ecto.Schema import Ecto.Changeset diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index a40e4a6ac..b003df51e 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -81,6 +81,35 @@ defmodule ArchEthic.Mining.PendingTransactionValidationTest do PendingTransactionValidation.validate(tx) end + test "should return an error when a node transaction content is greater than content_max_size " do + {public_key, private_key} = Crypto.derive_keypair("seed", 0) + {next_public_key, _} = Crypto.derive_keypair("seed", 1) + certificate = Crypto.get_key_certificate(public_key) + + content_pretext = + <<80, 20, 10, 200, 3000::16, 1, 0, 4, 221, 19, 74, 75, 69, 16, 50, 149, 253, 24, 115, 128, + 241, 110, 118, 139, 7, 48, 217, 58, 43, 145, 233, 77, 125, 190, 207, 31, 64, 157, 137>> + + random_content = :crypto.strong_rand_bytes(4 * 1024 * 1024) + + content = + content_pretext <> random_content <> <> + + tx = + Transaction.new_with_keys( + :node, + %TransactionData{ + content: content + }, + private_key, + public_key, + next_public_key + ) + + assert {:error, "Invalid node transaction with content size greaterthan content_max_size"} = + PendingTransactionValidation.validate(tx) + end + 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},