diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 2c9598173..76fe79e53 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 } @@ -37,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 """ @@ -256,6 +275,46 @@ 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), + {:schema, :ok} <- {:schema, ExJsonSchema.Validator.validate(@aeweb_schema, json)} do + :ok + else + {:schema, _} -> + {:error, "Invalid AEWeb transaction - Does not match JSON schema"} + + {:error, _} -> + {:error, "Invalid AEWeb transaction - Not a JSON format"} + end + end + defp do_accept_transaction( tx = %Transaction{ type: :node_rewards, @@ -513,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 -> @@ -674,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, 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/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index e8a5367ac..aa664853c 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,79 @@ 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", + "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 end