From b08703807dfdd2d101aeb5f6bedccef468a1f70a Mon Sep 17 00:00:00 2001 From: Samuel Manzanera Date: Fri, 2 Dec 2022 09:54:16 +0100 Subject: [PATCH 1/5] Add transaction's content verification --- .../mining/pending_transaction_validation.ex | 75 +++++++++++++++++++ priv/json-schemas/aeweb_file.json | 9 +++ priv/json-schemas/aeweb_ref.json | 43 +++++++++++ .../pending_transaction_validation_test.exs | 45 +++++++++++ 4 files changed, 172 insertions(+) create mode 100644 priv/json-schemas/aeweb_file.json create mode 100644 priv/json-schemas/aeweb_ref.json diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 2c9598173..2ba638227 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -28,6 +28,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do TransactionData, TransactionData.Ledger, TransactionData.Ownership, + TransactionData.UCOLedger, TransactionData.TokenLedger, TransactionSummary } @@ -256,6 +257,47 @@ defmodule Archethic.Mining.PendingTransactionValidation do end end + defp do_accept_transaction( + %Transaction{ + type: :transfer, + data: %TransactionData{ + ledger: %Ledger{ + uco: %UCOLedger{transfers: uco_transfers}, + token: %TokenLedger{transfers: token_transfers} + }, + recipients: recipients + } + }, + _ + ) do + if length(uco_transfers) > 0 or length(token_transfers) > 0 or length(recipients) > 0 do + :ok + else + {:error, + "Transfer's transaction requires some recipients for ledger or smart contract calls"} + end + end + + defp do_accept_transaction( + %Transaction{ + type: :hosting, + data: %TransactionData{content: content} + }, + _ + ) do + with {:ok, json} <- Jason.decode(content), + :ok <- check_aeweb_format(json) do + :ok + else + :error -> + {:error, "Invalid AEWeb transaction"} + + {:error, reason} -> + Logger.debug("Invalid AEWeb format #{inspect(reason)}") + {:error, "Invalid AEWeb transaction"} + end + end + defp do_accept_transaction( tx = %Transaction{ type: :node_rewards, @@ -781,4 +823,37 @@ defmodule Archethic.Mining.PendingTransactionValidation do {:error, :invalid_ip} end end + + defp check_aeweb_format(json) do + ref_schema = + :archethic + |> Application.app_dir("priv/json-schemas/aeweb_ref.json") + |> File.read!() + |> Jason.decode!() + |> ExJsonSchema.Schema.resolve() + + file_schema = + :archethic + |> Application.app_dir("priv/json-schemas/aeweb_file.json") + |> File.read!() + |> Jason.decode!() + |> ExJsonSchema.Schema.resolve() + + case ExJsonSchema.Validator.validate(ref_schema, json) do + :ok -> + :ok + + {:error, [{"Required properties aewebVersion, metadata were not present.", _}]} -> + case ExJsonSchema.Validator.validate(file_schema, json) do + :ok -> + :ok + + {:error, error_file} -> + {:error, error_file} + end + + {:error, error_ref} -> + {:error, error_ref} + end + end end diff --git a/priv/json-schemas/aeweb_file.json b/priv/json-schemas/aeweb_file.json new file mode 100644 index 000000000..bf41ceea5 --- /dev/null +++ b/priv/json-schemas/aeweb_file.json @@ -0,0 +1,9 @@ +{ + "$chema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "description": "List of files", + "minProperties": 1, + "additionalProperties": { + "type": "string" + } +} diff --git a/priv/json-schemas/aeweb_ref.json b/priv/json-schemas/aeweb_ref.json new file mode 100644 index 000000000..9ccc7497b --- /dev/null +++ b/priv/json-schemas/aeweb_ref.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "aewebVersion": { + "type": "number", + "exclusiveMinimum": 0, + "description": "AEWeb's version" + }, + "sslCertificate": { + "type": "string", + "description": "SSL certificate of the website" + }, + "metadata": { + "description": "List of files", + "type": "object", + "minProperties": 1, + "additionalProperties": { + "type": "object", + "properties": { + "encoding": { + "description": "The encoding of the file", + "type": "string", + "enum": ["gzip"] + }, + "addresses": { + "description": "List of addresses to storage the file", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "pattern": "^([0-9A-Fa-f])*$", + "minLength": 64, + "maxLength": 68 + } + } + }, + "required": ["addresses"] + } + } + }, + "required": ["aewebVersion", "metadata"] +} diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index e8a5367ac..ccee147d6 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -20,6 +20,9 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do 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 @@ -494,6 +497,13 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do Transaction.new( :transfer, %TransactionData{ + ledger: %Ledger{ + uco: %UCOLedger{ + transfers: [ + %Transfer{to: :crypto.strong_rand_bytes(32), amount: 100_000} + ] + } + }, code: """ condition inherit: [ content: "hello" @@ -728,5 +738,40 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do assert {:error, "Invalid node rewards trigger time"} = PendingTransactionValidation.validate(tx, ~U[2022-01-01 00:00:03Z]) 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", + "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 end end From 1cdc2fe60dfe1c911d925086cc325050cee50913 Mon Sep 17 00:00:00 2001 From: Bastien CHAMAGNE Date: Tue, 3 Jan 2023 17:46:10 +0100 Subject: [PATCH 2/5] aeweb_ref json schema typo (metaData instead of metadata) --- .../mining/pending_transaction_validation.ex | 2 +- priv/json-schemas/aeweb_ref.json | 27 +++++++++++++++---- .../pending_transaction_validation_test.exs | 4 ++- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 2ba638227..5ea502549 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -843,7 +843,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do :ok -> :ok - {:error, [{"Required properties aewebVersion, metadata were not present.", _}]} -> + {:error, [{"Required properties aewebVersion, metaData were not present.", _}]} -> case ExJsonSchema.Validator.validate(file_schema, json) do :ok -> :ok diff --git a/priv/json-schemas/aeweb_ref.json b/priv/json-schemas/aeweb_ref.json index 9ccc7497b..1ea60f8c2 100644 --- a/priv/json-schemas/aeweb_ref.json +++ b/priv/json-schemas/aeweb_ref.json @@ -11,17 +11,26 @@ "type": "string", "description": "SSL certificate of the website" }, - "metadata": { + "metaData": { "description": "List of files", "type": "object", "minProperties": 1, "additionalProperties": { "type": "object", "properties": { + "hash": { + "type": "string" + }, + "size": { + "type": "number", + "exclusiveMinimum": 0 + }, "encoding": { "description": "The encoding of the file", "type": "string", - "enum": ["gzip"] + "enum": [ + "gzip" + ] }, "addresses": { "description": "List of addresses to storage the file", @@ -35,9 +44,17 @@ } } }, - "required": ["addresses"] + "required": [ + "hash", + "size", + "encoding", + "addresses" + ] } } }, - "required": ["aewebVersion", "metadata"] -} + "required": [ + "aewebVersion", + "metaData" + ] +} \ No newline at end of file diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index ccee147d6..469251417 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -745,9 +745,11 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do content: Jason.encode!(%{ "aewebVersion" => 1, - "metadata" => %{ + "metaData" => %{ "index.html" => %{ "encoding" => "gzip", + "hash" => "abcd123", + "size" => 144, "addresses" => [ Crypto.derive_keypair("seed", 0) |> elem(0) From d812238e49a3158b87157f49cfbfc7cc3730756f Mon Sep 17 00:00:00 2001 From: Neylix Date: Wed, 4 Jan 2023 17:17:10 +0100 Subject: [PATCH 3/5] Rework json schema into one schema --- .../mining/pending_transaction_validation.ex | 33 ++------- priv/json-schemas/aeweb.json | 73 +++++++++++++++++++ priv/json-schemas/aeweb_file.json | 9 --- priv/json-schemas/aeweb_ref.json | 60 --------------- .../pending_transaction_validation_test.exs | 37 ++++++++++ 5 files changed, 118 insertions(+), 94 deletions(-) create mode 100644 priv/json-schemas/aeweb.json delete mode 100644 priv/json-schemas/aeweb_file.json delete mode 100644 priv/json-schemas/aeweb_ref.json diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 5ea502549..55eae5877 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -290,11 +290,10 @@ defmodule Archethic.Mining.PendingTransactionValidation do :ok else :error -> - {:error, "Invalid AEWeb transaction"} + {:error, "Invalid AEWeb transaction - Does not match JSON schema"} - {:error, reason} -> - Logger.debug("Invalid AEWeb format #{inspect(reason)}") - {:error, "Invalid AEWeb transaction"} + {:error, _} -> + {:error, "Invalid AEWeb transaction - Not a JSON format"} end end @@ -825,35 +824,19 @@ defmodule Archethic.Mining.PendingTransactionValidation do end defp check_aeweb_format(json) do - ref_schema = + aeweb_schema = :archethic - |> Application.app_dir("priv/json-schemas/aeweb_ref.json") + |> Application.app_dir("priv/json-schemas/aeweb.json") |> File.read!() |> Jason.decode!() |> ExJsonSchema.Schema.resolve() - file_schema = - :archethic - |> Application.app_dir("priv/json-schemas/aeweb_file.json") - |> File.read!() - |> Jason.decode!() - |> ExJsonSchema.Schema.resolve() - - case ExJsonSchema.Validator.validate(ref_schema, json) do + case ExJsonSchema.Validator.validate(aeweb_schema, json) do :ok -> :ok - {:error, [{"Required properties aewebVersion, metaData were not present.", _}]} -> - case ExJsonSchema.Validator.validate(file_schema, json) do - :ok -> - :ok - - {:error, error_file} -> - {:error, error_file} - end - - {:error, error_ref} -> - {:error, error_ref} + _ -> + :error end end end diff --git a/priv/json-schemas/aeweb.json b/priv/json-schemas/aeweb.json new file mode 100644 index 000000000..6bdd298f3 --- /dev/null +++ b/priv/json-schemas/aeweb.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { + "type": "object", + "description": "List of files", + "minProperties": 1, + "additionalProperties": { + "type": "string" + } + }, + { + "type": "object", + "description": "MetaData", + "properties": { + "aewebVersion": { + "type": "number", + "exclusiveMinimum": 0, + "description": "AEWeb's version" + }, + "sslCertificate": { + "type": "string", + "description": "SSL certificate of the website" + }, + "metaData": { + "description": "List of files", + "type": "object", + "minProperties": 1, + "additionalProperties": { + "type": "object", + "properties": { + "hash": { + "type": "string" + }, + "size": { + "type": "number", + "exclusiveMinimum": 0 + }, + "encoding": { + "description": "The encoding of the file", + "type": "string", + "enum": [ + "gzip" + ] + }, + "addresses": { + "description": "List of addresses to storage the file", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "pattern": "^([0-9A-Fa-f])*$", + "minLength": 68, + "maxLength": 132 + } + } + }, + "required": [ + "hash", + "size", + "encoding", + "addresses" + ] + } + } + }, + "required": [ + "aewebVersion", + "metaData" + ] + } + ] +} diff --git a/priv/json-schemas/aeweb_file.json b/priv/json-schemas/aeweb_file.json deleted file mode 100644 index bf41ceea5..000000000 --- a/priv/json-schemas/aeweb_file.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$chema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "description": "List of files", - "minProperties": 1, - "additionalProperties": { - "type": "string" - } -} diff --git a/priv/json-schemas/aeweb_ref.json b/priv/json-schemas/aeweb_ref.json deleted file mode 100644 index 1ea60f8c2..000000000 --- a/priv/json-schemas/aeweb_ref.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "aewebVersion": { - "type": "number", - "exclusiveMinimum": 0, - "description": "AEWeb's version" - }, - "sslCertificate": { - "type": "string", - "description": "SSL certificate of the website" - }, - "metaData": { - "description": "List of files", - "type": "object", - "minProperties": 1, - "additionalProperties": { - "type": "object", - "properties": { - "hash": { - "type": "string" - }, - "size": { - "type": "number", - "exclusiveMinimum": 0 - }, - "encoding": { - "description": "The encoding of the file", - "type": "string", - "enum": [ - "gzip" - ] - }, - "addresses": { - "description": "List of addresses to storage the file", - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "pattern": "^([0-9A-Fa-f])*$", - "minLength": 64, - "maxLength": 68 - } - } - }, - "required": [ - "hash", - "size", - "encoding", - "addresses" - ] - } - } - }, - "required": [ - "aewebVersion", - "metaData" - ] -} \ No newline at end of file diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index 469251417..aa664853c 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -775,5 +775,42 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do 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 end From 2c93be8270e98971268d16351ca9010368509123 Mon Sep 17 00:00:00 2001 From: Neylix Date: Wed, 4 Jan 2023 19:46:23 +0100 Subject: [PATCH 4/5] Use constant to read static file content --- .../mining/pending_transaction_validation.ex | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 55eae5877..8331c518b 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -38,6 +38,24 @@ defmodule Archethic.Mining.PendingTransactionValidation do @unit_uco 100_000_000 + @aeweb_schema :archethic + |> Application.app_dir("priv/json-schemas/aeweb.json") + |> File.read!() + |> Jason.decode!() + |> ExJsonSchema.Schema.resolve() + + @did_schema :archethic + |> Application.app_dir("priv/json-schemas/did-core.json") + |> File.read!() + |> Jason.decode!() + |> ExJsonSchema.Schema.resolve() + + @token_schema :archethic + |> Application.app_dir("priv/json-schemas/token-core.json") + |> File.read!() + |> Jason.decode!() + |> ExJsonSchema.Schema.resolve() + @doc """ Determines if the transaction is accepted into the network """ @@ -554,15 +572,8 @@ defmodule Archethic.Mining.PendingTransactionValidation do }, _ ) do - schema = - :archethic - |> Application.app_dir("priv/json-schemas/did-core.json") - |> File.read!() - |> Jason.decode!() - |> ExJsonSchema.Schema.resolve() - with {:ok, json_did} <- Jason.decode(content), - :ok <- ExJsonSchema.Validator.validate(schema, json_did) do + :ok <- ExJsonSchema.Validator.validate(@did_schema, json_did) do :ok else :error -> @@ -715,15 +726,8 @@ defmodule Archethic.Mining.PendingTransactionValidation do defp do_accept_transaction(_, _), do: :ok defp verify_token_creation(content) do - schema = - :archethic - |> Application.app_dir("priv/json-schemas/token-core.json") - |> File.read!() - |> Jason.decode!() - |> ExJsonSchema.Schema.resolve() - with {:ok, json_token} <- Jason.decode(content), - :ok <- ExJsonSchema.Validator.validate(schema, json_token), + :ok <- ExJsonSchema.Validator.validate(@token_schema, json_token), %{ "type" => "non-fungible", "supply" => supply, @@ -824,14 +828,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do end defp check_aeweb_format(json) do - aeweb_schema = - :archethic - |> Application.app_dir("priv/json-schemas/aeweb.json") - |> File.read!() - |> Jason.decode!() - |> ExJsonSchema.Schema.resolve() - - case ExJsonSchema.Validator.validate(aeweb_schema, json) do + case ExJsonSchema.Validator.validate(@aeweb_schema, json) do :ok -> :ok From 55fcb0bd8ee394f29ab24ea7cad7437c31d8cfac Mon Sep 17 00:00:00 2001 From: Neylix Date: Wed, 4 Jan 2023 22:21:57 +0100 Subject: [PATCH 5/5] Refactor aeweb check format --- .../mining/pending_transaction_validation.ex | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 8331c518b..76fe79e53 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -304,10 +304,10 @@ defmodule Archethic.Mining.PendingTransactionValidation do _ ) do with {:ok, json} <- Jason.decode(content), - :ok <- check_aeweb_format(json) do + {:schema, :ok} <- {:schema, ExJsonSchema.Validator.validate(@aeweb_schema, json)} do :ok else - :error -> + {:schema, _} -> {:error, "Invalid AEWeb transaction - Does not match JSON schema"} {:error, _} -> @@ -826,14 +826,4 @@ defmodule Archethic.Mining.PendingTransactionValidation do {:error, :invalid_ip} end end - - defp check_aeweb_format(json) do - case ExJsonSchema.Validator.validate(@aeweb_schema, json) do - :ok -> - :ok - - _ -> - :error - end - end end