From feeb991d5b89c75b035d66e6aeb4a212cab523bf Mon Sep 17 00:00:00 2001 From: bchamagne <74045243+bchamagne@users.noreply.github.com> Date: Mon, 25 Nov 2024 17:45:15 +0100 Subject: [PATCH] Add the genesis address in the validation_stamp (#1588) * add stamp.genesis_address * migration: add genesis to io * code_change * add genesis to graphql * add genesis to explorer * add genesis column to transactions list * set the genesis before decoding columns * code_change fix * remove opts from deserialize * add missing aliases * update graphql desc * Add genesis address only if requested * Hide genesis address in mobile view --------- Co-authored-by: Neylix --- lib/archethic/bootstrap/network_init.ex | 1 + .../db/embedded_impl/chain_reader.ex | 41 ++++--- .../db/embedded_impl/chain_writer.ex | 2 +- lib/archethic/db/embedded_impl/encoding.ex | 83 ++++++++------ lib/archethic/mining.ex | 3 +- lib/archethic/mining/distributed_workflow.ex | 23 +++- lib/archethic/mining/validation_context.ex | 10 +- .../transaction/cross_validation_stamp.ex | 12 ++- .../transaction/validation_stamp.ex | 101 ++++++++++-------- .../api/graphql/schema/transaction_type.ex | 2 + .../explorer/components/transactions_list.ex | 27 ++++- .../chains/node_shared_secrets_chain_live.ex | 22 +++- .../explorer/live/chains/oracle_chain_live.ex | 11 +- .../explorer/live/chains/origin_chain_live.ex | 10 +- .../explorer/live/chains/reward_chain_live.ex | 15 +-- .../live/transaction_details_live.html.heex | 19 +++- priv/migration_tasks/prod/1.6.0@genesis.exs | 33 ++++++ priv/migration_tasks/prod/1.7.0@genesis.exs | 40 +++++++ test/archethic/db/embedded_impl_test.exs | 18 ++-- .../mining/distributed_workflow_test.exs | 3 + .../mining/validation_context_test.exs | 17 +++ test/archethic/p2p/messages_test.exs | 3 + .../cross_validation_stamp_test.exs | 7 +- .../transaction/validation_stamp_test.exs | 54 +++++++++- .../transaction_chain/transaction_test.exs | 1 + test/support/transaction_factory.ex | 7 ++ 26 files changed, 434 insertions(+), 131 deletions(-) create mode 100644 priv/migration_tasks/prod/1.6.0@genesis.exs create mode 100644 priv/migration_tasks/prod/1.7.0@genesis.exs diff --git a/lib/archethic/bootstrap/network_init.ex b/lib/archethic/bootstrap/network_init.ex index f760c322ad..e88e586b49 100644 --- a/lib/archethic/bootstrap/network_init.ex +++ b/lib/archethic/bootstrap/network_init.ex @@ -200,6 +200,7 @@ defmodule Archethic.Bootstrap.NetworkInit do validation_stamp = %ValidationStamp{ + genesis_address: Transaction.previous_address(tx), protocol_version: 1, timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), diff --git a/lib/archethic/db/embedded_impl/chain_reader.ex b/lib/archethic/db/embedded_impl/chain_reader.ex index 566c39ab51..69a0d54444 100644 --- a/lib/archethic/db/embedded_impl/chain_reader.ex +++ b/lib/archethic/db/embedded_impl/chain_reader.ex @@ -50,6 +50,7 @@ defmodule Archethic.DB.EmbeddedImpl.ChainReader do fd |> read_transaction(column_names, size, 0) |> Enum.into(%{}) + |> set_genesis_address(column_names, genesis_address) |> decode_transaction_columns(version) File.close(fd) @@ -163,12 +164,13 @@ defmodule Archethic.DB.EmbeddedImpl.ChainReader do case File.open(filepath, [:binary, :read]) do {:ok, fd} -> Stream.resource( - fn -> process_get_chain(fd, fields, [], db_path) end, + fn -> process_get_chain(fd, genesis_address, fields, [], db_path) end, fn {transactions, true, paging_address} -> next_transactions = process_get_chain( fd, + genesis_address, fields, [paging_address: paging_address], db_path @@ -273,14 +275,17 @@ defmodule Archethic.DB.EmbeddedImpl.ChainReader do _ -> opts end - process_get_chain(fd, fields, opts, db_path) + process_get_chain(fd, genesis_address, fields, opts, db_path) :desc -> # if the paging_address=genesis_address, # we return empty case Keyword.get(opts, :paging_address) do - ^genesis_address -> {[], false, nil} - _ -> process_get_chain_desc(fd, genesis_address, fields, opts, db_path) + ^genesis_address -> + {[], false, nil} + + _ -> + process_get_chain_desc(fd, genesis_address, fields, opts, db_path) end end @@ -288,18 +293,18 @@ defmodule Archethic.DB.EmbeddedImpl.ChainReader do {transactions, more?, paging_address} end - defp process_get_chain(fd, fields, opts, db_path) do + defp process_get_chain(fd, genesis_address, fields, opts, db_path) do # Set the file cursor position to the paging state case Keyword.get(opts, :paging_address) do nil -> :file.position(fd, 0) - do_process_get_chain(fd, fields) + do_process_get_chain(fd, genesis_address, fields) paging_address -> case ChainIndex.get_tx_entry(paging_address, db_path) do {:ok, %{offset: offset, size: size}} -> :file.position(fd, offset + size) - do_process_get_chain(fd, fields) + do_process_get_chain(fd, genesis_address, fields) {:error, :not_exists} -> {[], false, nil} @@ -307,7 +312,7 @@ defmodule Archethic.DB.EmbeddedImpl.ChainReader do end end - defp do_process_get_chain(fd, fields) do + defp do_process_get_chain(fd, genesis_address, fields) do # Always return transaction address fields = if Enum.empty?(fields), do: fields, else: Enum.uniq([:address | fields]) @@ -328,7 +333,7 @@ defmodule Archethic.DB.EmbeddedImpl.ChainReader do end # Read the transactions until the nb of transactions to fullfil the page (ie. 10 transactions) - {transactions, more?, paging_address} = get_paginated_chain(fd, column_names) + {transactions, more?, paging_address} = get_paginated_chain(fd, genesis_address, column_names) {transactions, more?, paging_address} end @@ -376,14 +381,14 @@ defmodule Archethic.DB.EmbeddedImpl.ChainReader do # call the ASC function and ignore the more? and paging_address {transactions, _more?, _paging_address} = - process_get_chain(fd, fields, [paging_address: paging_address], db_path) + process_get_chain(fd, genesis_address, fields, [paging_address: paging_address], db_path) transactions = Enum.take(transactions, nb_to_take) {Enum.reverse(transactions), more?, new_paging_address} end - defp get_paginated_chain(fd, fields, acc \\ []) do + defp get_paginated_chain(fd, genesis_address, fields, acc \\ []) do case :file.read(fd, 8) do {:ok, <>} -> if length(acc) == @page_size do @@ -393,9 +398,10 @@ defmodule Archethic.DB.EmbeddedImpl.ChainReader do tx = fd |> read_transaction(fields, size, 0) + |> set_genesis_address(fields, genesis_address) |> decode_transaction_columns(version) - get_paginated_chain(fd, fields, [tx | acc]) + get_paginated_chain(fd, genesis_address, fields, [tx | acc]) end :eof -> @@ -553,4 +559,15 @@ defmodule Archethic.DB.EmbeddedImpl.ChainReader do |> Utils.atomize_keys() |> Transaction.cast() end + + defp set_genesis_address(column_values, [], genesis_address), + do: Map.put(column_values, "validation_stamp.genesis_address", genesis_address) + + defp set_genesis_address(column_values, fields, genesis_address) do + genesis_field_name = "validation_stamp.genesis_address" + + if Enum.member?(fields, genesis_field_name), + do: Map.put(column_values, genesis_field_name, genesis_address), + else: column_values + end end diff --git a/lib/archethic/db/embedded_impl/chain_writer.ex b/lib/archethic/db/embedded_impl/chain_writer.ex index b50d37bc48..e011758020 100644 --- a/lib/archethic/db/embedded_impl/chain_writer.ex +++ b/lib/archethic/db/embedded_impl/chain_writer.ex @@ -40,7 +40,7 @@ defmodule Archethic.DB.EmbeddedImpl.ChainWriter do filename = io_path(db_path, address) - data = Encoding.encode(tx) + data = Encoding.encode(tx, storage_type: :io) File.write!( filename, diff --git a/lib/archethic/db/embedded_impl/encoding.ex b/lib/archethic/db/embedded_impl/encoding.ex index c6d7ea98f7..ef049237ee 100644 --- a/lib/archethic/db/embedded_impl/encoding.ex +++ b/lib/archethic/db/embedded_impl/encoding.ex @@ -27,40 +27,48 @@ defmodule Archethic.DB.EmbeddedImpl.Encoding do @doc """ Encode a transaction + + Opts: + storage_type: :io | :chain (default: :chain) + """ - @spec encode(Transaction.t()) :: binary() - def encode(%Transaction{ - version: tx_version, - address: address, - type: type, - data: %TransactionData{ - content: content, - code: code, - contract: contract, - ownerships: ownerships, - ledger: %Ledger{uco: uco_ledger, token: token_ledger}, - recipients: recipients - }, - previous_public_key: previous_public_key, - previous_signature: previous_signature, - origin_signature: origin_signature, - validation_stamp: %ValidationStamp{ - timestamp: timestamp, - proof_of_work: proof_of_work, - proof_of_integrity: proof_of_integrity, - proof_of_election: proof_of_election, - ledger_operations: %LedgerOperations{ - fee: fee, - transaction_movements: transaction_movements, - unspent_outputs: unspent_outputs, - consumed_inputs: consumed_inputs + @spec encode(transaction :: Transaction.t(), opts :: Keyword.t()) :: binary() + def encode( + %Transaction{ + version: tx_version, + address: address, + type: type, + data: %TransactionData{ + content: content, + code: code, + contract: contract, + ownerships: ownerships, + ledger: %Ledger{uco: uco_ledger, token: token_ledger}, + recipients: recipients }, - recipients: resolved_recipients, - signature: validation_stamp_sig, - protocol_version: protocol_version + previous_public_key: previous_public_key, + previous_signature: previous_signature, + origin_signature: origin_signature, + validation_stamp: %ValidationStamp{ + genesis_address: genesis_address, + timestamp: timestamp, + proof_of_work: proof_of_work, + proof_of_integrity: proof_of_integrity, + proof_of_election: proof_of_election, + ledger_operations: %LedgerOperations{ + fee: fee, + transaction_movements: transaction_movements, + unspent_outputs: unspent_outputs, + consumed_inputs: consumed_inputs + }, + recipients: resolved_recipients, + signature: validation_stamp_sig, + protocol_version: protocol_version + }, + cross_validation_stamps: cross_validation_stamps }, - cross_validation_stamps: cross_validation_stamps - }) do + opts \\ [] + ) do ownerships_encoding = ownerships |> Enum.map(&Ownership.serialize(&1, tx_version)) @@ -157,6 +165,7 @@ defmodule Archethic.DB.EmbeddedImpl.Encoding do {"cross_validation_stamps", <>} ] + |> maybe_add_genesis_address(genesis_address, Keyword.get(opts, :storage_type, :chain)) |> Enum.map(fn {column, value} -> wrapped_value = Utils.wrap_binary(value) @@ -246,6 +255,10 @@ defmodule Archethic.DB.EmbeddedImpl.Encoding do put_in(acc, [Access.key(:validation_stamp, %{}), :proof_of_election], poe) end + def decode(_version, "validation_stamp.genesis_address", genesis_address, acc) do + put_in(acc, [Access.key(:validation_stamp, %{}), :genesis_address], genesis_address) + end + def decode(_version, "validation_stamp.ledger_operations.fee", <>, acc) do put_in( acc, @@ -397,4 +410,12 @@ defmodule Archethic.DB.EmbeddedImpl.Encoding do {stamp, rest} = CrossValidationStamp.deserialize(rest) deserialize_cross_validation_stamps(rest, nb, [stamp | acc]) end + + defp maybe_add_genesis_address(encodings, _genesis_address, :chain) do + encodings + end + + defp maybe_add_genesis_address(encodings, genesis_address, :io) do + [{"validation_stamp.genesis_address", genesis_address} | encodings] + end end diff --git a/lib/archethic/mining.ex b/lib/archethic/mining.ex index 4918cba9fa..47ae964a12 100644 --- a/lib/archethic/mining.ex +++ b/lib/archethic/mining.ex @@ -34,7 +34,8 @@ defmodule Archethic.Mining do # version 5->6 the POI changed and is now done with tx.data.recipients.args serialized with :extended mode # version 6->7 add Add consumed inputs in tx.validation_stamp.ledger_operations # version 7->8 movement resolved address are now the genesis address of the destination - @protocol_version 8 + # version 8->9 genesis in the validation stamp + @protocol_version 9 @lock_threshold 0.75 diff --git a/lib/archethic/mining/distributed_workflow.ex b/lib/archethic/mining/distributed_workflow.ex index 0dce7fd31d..763838ded6 100644 --- a/lib/archethic/mining/distributed_workflow.ex +++ b/lib/archethic/mining/distributed_workflow.ex @@ -56,7 +56,7 @@ defmodule Archethic.Mining.DistributedWorkflow do require Logger use GenStateMachine, callback_mode: [:handle_event_function, :state_enter], restart: :temporary - @vsn 2 + @vsn 3 @mining_timeout Application.compile_env!(:archethic, [__MODULE__, :global_timeout]) @coordinator_timeout_supplement Application.compile_env!(:archethic, [ @@ -1022,6 +1022,27 @@ defmodule Archethic.Mining.DistributedWorkflow do {:keep_state_and_data, :postpone} end + def code_change(2, state, data, _extra) do + {:ok, state, + case Map.get(data, :context) do + ctx = %Archethic.Mining.ValidationContext{ + genesis_address: genesis_address, + validation_stamp: stamp + } + when not is_nil(genesis_address) and not is_nil(stamp) -> + %{ + data + | context: %Archethic.Mining.ValidationContext{ + ctx + | validation_stamp: Map.put(stamp, :genesis_address, genesis_address) + } + } + + _ -> + data + end} + end + def code_change(_old_vsn, state, data, _extra), do: {:ok, state, data} defp notify_transaction_context( diff --git a/lib/archethic/mining/validation_context.ex b/lib/archethic/mining/validation_context.ex index 49b8d66de4..3d99482510 100644 --- a/lib/archethic/mining/validation_context.ex +++ b/lib/archethic/mining/validation_context.ex @@ -704,6 +704,7 @@ defmodule Archethic.Mining.ValidationContext do @spec create_validation_stamp(t()) :: t() def create_validation_stamp( context = %__MODULE__{ + genesis_address: genesis_address, transaction: tx = %Transaction{data: %TransactionData{recipients: recipients}}, previous_transaction: prev_tx, validation_time: validation_time, @@ -729,6 +730,7 @@ defmodule Archethic.Mining.ValidationContext do get_ledger_operations(context, fee, validation_time, encoded_state) validation_stamp = %ValidationStamp{ + genesis_address: genesis_address, protocol_version: Mining.protocol_version(), timestamp: validation_time, proof_of_work: do_proof_of_work(tx), @@ -1147,7 +1149,8 @@ defmodule Archethic.Mining.ValidationContext do consumed_inputs: fn -> valid_consumed_inputs?(stamp, ledger_operations) end, unspent_outputs: fn -> valid_stamp_unspent_outputs?(stamp, ledger_operations) end, error: fn -> valid_stamp_error?(stamp, context) end, - protocol_version: fn -> valid_protocol_version?(stamp) end + protocol_version: fn -> valid_protocol_version?(stamp) end, + genesis_address: fn -> valid_genesis_address?(stamp, context) end ] subsets_verifications @@ -1262,6 +1265,11 @@ defmodule Archethic.Mining.ValidationContext do defp valid_protocol_version?(%ValidationStamp{protocol_version: version}), do: Mining.protocol_version() == version + defp valid_genesis_address?(%ValidationStamp{genesis_address: genesis_address}, %__MODULE__{ + genesis_address: ctx_genesis_address + }), + do: genesis_address == ctx_genesis_address + @doc """ Get the chain storage node position """ diff --git a/lib/archethic/transaction_chain/transaction/cross_validation_stamp.ex b/lib/archethic/transaction_chain/transaction/cross_validation_stamp.ex index 370a854a0a..eeaa43f7a1 100644 --- a/lib/archethic/transaction_chain/transaction/cross_validation_stamp.ex +++ b/lib/archethic/transaction_chain/transaction/cross_validation_stamp.ex @@ -23,6 +23,7 @@ defmodule Archethic.TransactionChain.Transaction.CrossValidationStamp do | :protocol_version | :consumed_inputs | :aggregated_utxos + | :genesis_address @typedoc """ A cross validation stamp is composed from: @@ -42,9 +43,10 @@ defmodule Archethic.TransactionChain.Transaction.CrossValidationStamp do @spec sign(t(), ValidationStamp.t()) :: t() def sign( cross_stamp = %__MODULE__{inconsistencies: inconsistencies}, - validation_stamp = %ValidationStamp{} + validation_stamp = %ValidationStamp{protocol_version: protocol_version} ) do - raw_stamp = ValidationStamp.serialize(validation_stamp) + raw_stamp = + ValidationStamp.serialize(validation_stamp, serialize_genesis?: protocol_version >= 9) signature = [raw_stamp, marshal_inconsistencies(inconsistencies)] @@ -66,9 +68,9 @@ defmodule Archethic.TransactionChain.Transaction.CrossValidationStamp do inconsistencies: inconsistencies, node_public_key: node_public_key }, - stamp = %ValidationStamp{} + stamp = %ValidationStamp{protocol_version: protocol_version} ) do - raw_stamp = ValidationStamp.serialize(stamp) + raw_stamp = ValidationStamp.serialize(stamp, serialize_genesis?: protocol_version >= 9) data = [raw_stamp, marshal_inconsistencies(inconsistencies)] Crypto.verify?(signature, data, node_public_key) @@ -132,6 +134,7 @@ defmodule Archethic.TransactionChain.Transaction.CrossValidationStamp do defp serialize_inconsistency(:consumed_inputs), do: 10 defp serialize_inconsistency(:aggregated_utxos), do: 11 defp serialize_inconsistency(:recipients), do: 12 + defp serialize_inconsistency(:genesis_address), do: 13 @doc """ Deserialize an encoded cross validation stamp @@ -201,6 +204,7 @@ defmodule Archethic.TransactionChain.Transaction.CrossValidationStamp do defp do_reduce_inconsistencies(<<10::8, rest::bitstring>>), do: {:consumed_inputs, rest} defp do_reduce_inconsistencies(<<11::8, rest::bitstring>>), do: {:aggregated_utxos, rest} defp do_reduce_inconsistencies(<<12::8, rest::bitstring>>), do: {:recipients, rest} + defp do_reduce_inconsistencies(<<13::8, rest::bitstring>>), do: {:genesis_address, rest} @spec cast(map()) :: t() def cast(stamp = %{}) do diff --git a/lib/archethic/transaction_chain/transaction/validation_stamp.ex b/lib/archethic/transaction_chain/transaction/validation_stamp.ex index 17cfb4262e..460fb66582 100755 --- a/lib/archethic/transaction_chain/transaction/validation_stamp.ex +++ b/lib/archethic/transaction_chain/transaction/validation_stamp.ex @@ -12,6 +12,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do defstruct [ :protocol_version, + :genesis_address, :timestamp, :signature, :proof_of_work, @@ -43,6 +44,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do - Signature: generated from the coordinator private key to avoid non-repudiation of the stamp - Error: Error returned by the pending transaction validation or after mining context - Protocol version: Version of the protocol + - Genesis address: Genesis of the chain. Added in protocol_version=9 """ @type t :: %__MODULE__{ timestamp: DateTime.t(), @@ -51,17 +53,18 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do proof_of_integrity: Crypto.versioned_hash(), proof_of_election: binary(), ledger_operations: LedgerOperations.t(), - recipients: list(Crypto.versioned_hash()), + recipients: list(Crypto.prepended_hash()), + genesis_address: Crypto.prepended_hash(), error: error() | nil, protocol_version: non_neg_integer() } @spec sign(__MODULE__.t()) :: __MODULE__.t() - def sign(stamp = %__MODULE__{}) do + def sign(stamp = %__MODULE__{protocol_version: protocol_version}) do raw_stamp = stamp |> extract_for_signature() - |> serialize() + |> serialize(serialize_genesis?: protocol_version >= 9) sig = Crypto.sign_with_last_node_key(raw_stamp) @@ -80,7 +83,8 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do ledger_operations: ops, recipients: recipients, error: error, - protocol_version: version + protocol_version: version, + genesis_address: genesis_address }) do %__MODULE__{ timestamp: timestamp, @@ -90,25 +94,35 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do ledger_operations: ops, recipients: recipients, error: error, - protocol_version: version + protocol_version: version, + genesis_address: genesis_address } end @doc """ Serialize a validation stamp info binary format + + Opts: + serialize_genesis?: true | false """ @spec serialize(t()) :: bitstring() - def serialize(%__MODULE__{ - timestamp: timestamp, - proof_of_work: pow, - proof_of_integrity: poi, - proof_of_election: poe, - ledger_operations: ledger_operations, - recipients: recipients, - error: error, - signature: nil, - protocol_version: version - }) do + def serialize(stamp, opts \\ []) + + def serialize( + %__MODULE__{ + timestamp: timestamp, + proof_of_work: pow, + proof_of_integrity: poi, + proof_of_election: poe, + ledger_operations: ledger_operations, + recipients: recipients, + error: error, + signature: signature, + protocol_version: version, + genesis_address: genesis_address + }, + opts + ) do pow = if pow == "" do # Empty public key if the no public key matching the origin signature @@ -123,39 +137,26 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do poe::binary, LedgerOperations.serialize(ledger_operations, version)::bitstring, encoded_recipients_len::binary, :erlang.list_to_binary(recipients)::binary, serialize_error(error)::8>> + |> maybe_add_signature(signature) + |> maybe_add_genesis(genesis_address, Keyword.get(opts, :serialize_genesis?, true)) end - def serialize(%__MODULE__{ - timestamp: timestamp, - proof_of_work: pow, - proof_of_integrity: poi, - proof_of_election: poe, - ledger_operations: ledger_operations, - recipients: recipients, - error: error, - signature: signature, - protocol_version: version - }) do - pow = - if pow == "" do - # Empty public key if the no public key matching the origin signature - <<0::8, 0::8, 0::256>> - else - pow - end + defp maybe_add_signature(bin, nil), do: bin - encoded_recipients_len = length(recipients) |> VarInt.from_value() + defp maybe_add_signature(bin, signature), + do: <> - <> - end + defp maybe_add_genesis(bin, _genesis_address, false), do: bin + + defp maybe_add_genesis(bin, genesis_address, true), + do: <> @doc """ Deserialize an encoded validation stamp + + Never used after a serialize(serialize_genesis?: false) """ - @spec deserialize(bitstring()) :: {t(), bitstring()} + @spec deserialize(bin :: bitstring()) :: {t(), bitstring()} def deserialize(<>) do <> = rest pow_key_size = Crypto.key_size(pow_curve_id) @@ -177,8 +178,11 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do <> = rest + {genesis_address, rest} = Utils.deserialize_address(rest) + { %__MODULE__{ + genesis_address: genesis_address, timestamp: DateTime.from_unix!(timestamp, :millisecond), proof_of_work: pow, proof_of_integrity: <>, @@ -205,6 +209,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do recipients: Map.get(stamp, :recipients, []), signature: Map.get(stamp, :signature), error: Map.get(stamp, :error), + genesis_address: Map.get(stamp, :genesis_address), protocol_version: Map.get(stamp, :protocol_version) } end @@ -220,7 +225,8 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do ledger_operations: ledger_operations, recipients: recipients, signature: signature, - error: error + error: error, + genesis_address: genesis_address }) do %{ timestamp: timestamp, @@ -230,7 +236,8 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do ledger_operations: LedgerOperations.to_map(ledger_operations), recipients: recipients, signature: signature, - error: error + error: error, + genesis_address: genesis_address } end @@ -243,14 +250,14 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do def valid_signature?(%__MODULE__{signature: nil}, _public_key), do: false def valid_signature?( - stamp = %__MODULE__{signature: signature}, + stamp = %__MODULE__{signature: signature, protocol_version: protocol_version}, public_key ) when is_binary(signature) do raw_stamp = stamp - |> extract_for_signature - |> serialize + |> extract_for_signature() + |> serialize(serialize_genesis?: protocol_version >= 9) Crypto.verify?(signature, raw_stamp, public_key) end @@ -263,6 +270,8 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do def generate_dummy(opts \\ []) do %__MODULE__{ timestamp: Keyword.get(opts, :timestamp, DateTime.utc_now()), + genesis_address: + Keyword.get(opts, :genesis_address, <<0::16, :crypto.strong_rand_bytes(32)::binary>>), protocol_version: 1, proof_of_work: :crypto.strong_rand_bytes(32), proof_of_election: :crypto.strong_rand_bytes(32), diff --git a/lib/archethic_web/api/graphql/schema/transaction_type.ex b/lib/archethic_web/api/graphql/schema/transaction_type.ex index 1ac6a0409e..6f394458fc 100644 --- a/lib/archethic_web/api/graphql/schema/transaction_type.ex +++ b/lib/archethic_web/api/graphql/schema/transaction_type.ex @@ -162,6 +162,7 @@ defmodule ArchethicWeb.API.GraphQL.Schema.TransactionType do - Ledger operations: All the operations performed by the transaction - Signature: Coordinator signature of the stamp - Protocol version: Version of the transaction validation protocol + - Genesis address: Genesis address of the chain """ object :validation_stamp do field(:timestamp, :timestamp) @@ -170,6 +171,7 @@ defmodule ArchethicWeb.API.GraphQL.Schema.TransactionType do field(:ledger_operations, :ledger_operations) field(:signature, :hex) field(:protocol_version, :integer) + field(:genesis_address, :hex) end @desc """ diff --git a/lib/archethic_web/explorer/components/transactions_list.ex b/lib/archethic_web/explorer/components/transactions_list.ex index 99f7bffc68..99cab4eaf2 100644 --- a/lib/archethic_web/explorer/components/transactions_list.ex +++ b/lib/archethic_web/explorer/components/transactions_list.ex @@ -52,6 +52,7 @@ defmodule ArchethicWeb.Explorer.Components.TransactionsList do
  • Address
    +
    Genesis
    Type
    Date (UTC)
    Fee
    @@ -69,6 +70,16 @@ defmodule ArchethicWeb.Explorer.Components.TransactionsList do ) ) %> +
    + <%= link(short_address(get_genesis(tx)), + to: + Routes.live_path( + @socket, + ArchethicWeb.Explorer.TransactionChainLive, + address: Base.encode16(get_genesis(tx)) + ) + ) %> +
    <%= format_transaction_type(tx.type) %>
    <%= format_date(get_timestamp(tx), display_utc: false) %> @@ -87,18 +98,30 @@ defmodule ArchethicWeb.Explorer.Components.TransactionsList do """ end - # reward/nss/home... + # beacon defp get_timestamp(%TransactionSummary{timestamp: timestamp}), do: timestamp # chain page defp get_timestamp(%Transaction{validation_stamp: %ValidationStamp{timestamp: timestamp}}), do: timestamp - # oracle/origin page + # nss/reward/oracle/origin page defp get_timestamp(map) do Map.get(map, :timestamp) end + defp get_genesis(%Transaction{ + validation_stamp: %ValidationStamp{genesis_address: genesis_address} + }), + do: genesis_address + + defp get_genesis(%TransactionSummary{genesis_address: genesis_address}), + do: genesis_address + + defp get_genesis(map) do + Map.get(map, :genesis_address) + end + # reward/nss/home... defp get_fee(%TransactionSummary{fee: fee}), do: fee diff --git a/lib/archethic_web/explorer/live/chains/node_shared_secrets_chain_live.ex b/lib/archethic_web/explorer/live/chains/node_shared_secrets_chain_live.ex index d899e5e2bb..873fb031f8 100644 --- a/lib/archethic_web/explorer/live/chains/node_shared_secrets_chain_live.ex +++ b/lib/archethic_web/explorer/live/chains/node_shared_secrets_chain_live.ex @@ -3,6 +3,7 @@ defmodule ArchethicWeb.Explorer.NodeSharedSecretsChainLive do use ArchethicWeb.Explorer, :live_view + alias Archethic.Crypto alias Archethic.OracleChain alias Archethic.TransactionChain alias Archethic.TransactionChain.Transaction @@ -110,7 +111,15 @@ defmodule ArchethicWeb.Explorer.NodeSharedSecretsChainLive do |> update( :transactions, fn tx_list -> - [display_data(address, nb_auth_nodes, timestamp) | tx_list] + [ + display_data( + SharedSecrets.genesis_address(@txn_type), + address, + nb_auth_nodes, + timestamp + ) + | tx_list + ] |> Enum.take(@display_limit) end ) @@ -135,8 +144,8 @@ defmodule ArchethicWeb.Explorer.NodeSharedSecretsChainLive do if nb_drops < 0, do: {0, @display_limit + nb_drops}, else: {nb_drops, @display_limit} case SharedSecrets.genesis_address(@txn_type) do - address when is_binary(address) -> - address + genesis_address when is_binary(genesis_address) -> + genesis_address |> TransactionChain.list_chain_addresses() |> Stream.drop(nb_drops) |> Stream.take(display_limit) @@ -144,6 +153,7 @@ defmodule ArchethicWeb.Explorer.NodeSharedSecretsChainLive do nb_authorized_nodes = nb_of_authorized_keys(addr) display_data( + genesis_address, addr, nb_authorized_nodes, timestamp @@ -168,13 +178,15 @@ defmodule ArchethicWeb.Explorer.NodeSharedSecretsChainLive do end @spec display_data( - address :: binary(), + genesis_address :: Crypto.prepended_hash(), + address :: Crypto.prepended_hash(), nb_authorized_nodes :: non_neg_integer(), timestamp :: DateTime.t() ) :: map() - defp display_data(address, nb_authorized_nodes, timestamp) do + defp display_data(genesis_address, address, nb_authorized_nodes, timestamp) do %{ + genesis_address: genesis_address, address: address, type: @txn_type, timestamp: timestamp, diff --git a/lib/archethic_web/explorer/live/chains/oracle_chain_live.ex b/lib/archethic_web/explorer/live/chains/oracle_chain_live.ex index 86100a12da..5e02f569e2 100644 --- a/lib/archethic_web/explorer/live/chains/oracle_chain_live.ex +++ b/lib/archethic_web/explorer/live/chains/oracle_chain_live.ex @@ -153,17 +153,24 @@ defmodule ArchethicWeb.Explorer.OracleChainLive do |> TransactionChain.get([ :address, :type, - validation_stamp: [:timestamp, ledger_operations: [:fee]] + validation_stamp: [:genesis_address, :timestamp, ledger_operations: [:fee]] ]) |> Enum.map(fn %Transaction{ address: address, type: type, validation_stamp: %ValidationStamp{ + genesis_address: genesis_address, timestamp: timestamp, ledger_operations: %LedgerOperations{fee: fee} } } -> - %{address: address, type: type, timestamp: timestamp, fee: fee} + %{ + address: address, + type: type, + timestamp: timestamp, + fee: fee, + genesis_address: genesis_address + } end) |> Enum.reverse() end diff --git a/lib/archethic_web/explorer/live/chains/origin_chain_live.ex b/lib/archethic_web/explorer/live/chains/origin_chain_live.ex index 238fb18219..209092e21c 100644 --- a/lib/archethic_web/explorer/live/chains/origin_chain_live.ex +++ b/lib/archethic_web/explorer/live/chains/origin_chain_live.ex @@ -128,11 +128,14 @@ defmodule ArchethicWeb.Explorer.OriginChainLive do with {:ok, %Transaction{ data: %TransactionData{content: content}, - validation_stamp: %ValidationStamp{timestamp: timestamp} + validation_stamp: %ValidationStamp{ + timestamp: timestamp, + genesis_address: genesis_address + } }} <- TransactionChain.get_transaction(address, data: [:content], - validation_stamp: [:timestamp] + validation_stamp: [:timestamp, :genesis_address] ), {pb_key, _} <- Utils.deserialize_public_key(content), family_id <- SharedSecrets.origin_family_from_public_key(pb_key) do @@ -140,7 +143,8 @@ defmodule ArchethicWeb.Explorer.OriginChainLive do address: address, type: @txn_type, timestamp: timestamp, - family_of_origin: family_id + family_of_origin: family_id, + genesis_address: genesis_address } else _ -> [] diff --git a/lib/archethic_web/explorer/live/chains/reward_chain_live.ex b/lib/archethic_web/explorer/live/chains/reward_chain_live.ex index 9fb6aa2074..8b8d7abb98 100644 --- a/lib/archethic_web/explorer/live/chains/reward_chain_live.ex +++ b/lib/archethic_web/explorer/live/chains/reward_chain_live.ex @@ -3,6 +3,7 @@ defmodule ArchethicWeb.Explorer.RewardChainLive do use ArchethicWeb.Explorer, :live_view + alias Archethic.Crypto alias Archethic.OracleChain alias Archethic.TransactionChain alias Archethic.PubSub @@ -95,7 +96,7 @@ defmodule ArchethicWeb.Explorer.RewardChainLive do 1 -> socket |> update(:transactions, fn tx_list -> - [display_data(address, type, timestamp) | tx_list] + [display_data(Reward.genesis_address(), address, type, timestamp) | tx_list] |> Enum.take(@display_limit) end) |> assign(:tx_count, tx_count + 1) @@ -115,13 +116,14 @@ defmodule ArchethicWeb.Explorer.RewardChainLive do if nb_drops < 0, do: {0, @display_limit + nb_drops}, else: {nb_drops, @display_limit} case Reward.genesis_address() do - address when is_binary(address) -> - address + genesis_address when is_binary(genesis_address) -> + genesis_address |> TransactionChain.list_chain_addresses() |> Stream.drop(nb_drops) |> Stream.take(display_limit) |> Stream.map(fn {addr, timestamp} -> display_data( + genesis_address, addr, (TransactionChain.get_transaction(addr, [:type]) |> elem(1)).type, timestamp @@ -135,12 +137,13 @@ defmodule ArchethicWeb.Explorer.RewardChainLive do end @spec display_data( - address :: binary(), + genesis_address :: Crypto.prepended_hash(), + address :: Crypto.prepended_hash(), type :: :node_rewards | :mint_rewards, timestamp :: DateTime.t() ) :: map() - defp display_data(address, type, timestamp) do - %{address: address, type: type, timestamp: timestamp} + defp display_data(genesis_address, address, type, timestamp) do + %{genesis_address: genesis_address, address: address, type: type, timestamp: timestamp} end end diff --git a/lib/archethic_web/explorer/live/transaction_details_live.html.heex b/lib/archethic_web/explorer/live/transaction_details_live.html.heex index 73688944df..45fe19cfac 100644 --- a/lib/archethic_web/explorer/live/transaction_details_live.html.heex +++ b/lib/archethic_web/explorer/live/transaction_details_live.html.heex @@ -19,9 +19,9 @@ <% end %>
    <% end %> - <%= if @address != burning_address() do %> + <%= if @address != burning_address() && not is_nil(@transaction) do %>
    - <%= link class: "simple-button", to: Routes.live_path(@socket, ArchethicWeb.Explorer.TransactionChainLive, address: Base.encode16(@address)) do %> + <%= link class: "simple-button", to: Routes.live_path(@socket, ArchethicWeb.Explorer.TransactionChainLive, address: Base.encode16(@transaction.validation_stamp.genesis_address)) do %> Explore chain <% end %>
    @@ -64,6 +64,21 @@ <% true -> %>
    + <%!-------------------------------- GENESIS --------------------------------%> +
    +
    Genesis
    +
    + <%= link(short_address(@transaction.validation_stamp.genesis_address), + to: + Routes.live_path( + @socket, + ArchethicWeb.Explorer.TransactionChainLive, + address: Base.encode16(@transaction.validation_stamp.genesis_address) + ) + ) %> +
    +
    + <%!-------------------------------- TYPE --------------------------------%>
    Type
    diff --git a/priv/migration_tasks/prod/1.6.0@genesis.exs b/priv/migration_tasks/prod/1.6.0@genesis.exs new file mode 100644 index 0000000000..2c4e9e0a5b --- /dev/null +++ b/priv/migration_tasks/prod/1.6.0@genesis.exs @@ -0,0 +1,33 @@ +defmodule Migration_1_6_0 do + @moduledoc false + + alias Archethic.DB + alias Archethic.DB.EmbeddedImpl.ChainWriter + alias Archethic.Election + alias Archethic.P2P + alias Archethic.TransactionChain + + require Logger + + def run() do + nodes = P2P.authorized_and_available_nodes() + db_path = :persistent_term.get(:archethic_db_path) + + DB.list_io_transactions([]) + |> Stream.each(fn transaction -> + if is_nil(transaction.validation_stamp.genesis_address) do + # query for genesis + storage_nodes = Election.storage_nodes(transaction.address, nodes) + {:ok, genesis_address} = TransactionChain.fetch_genesis_address(transaction.address, storage_nodes) + + # update in memory + transaction = put_in(transaction, [Access.key!(:validation_stamp), Access.key!(:genesis_address)], genesis_address) + + # update on disk + File.rm!(ChainWriter.io_path(db_path, transaction.address)) + ChainWriter.write_io_transaction(transaction, db_path) + end + end) + |> Stream.run() + end +end diff --git a/priv/migration_tasks/prod/1.7.0@genesis.exs b/priv/migration_tasks/prod/1.7.0@genesis.exs new file mode 100644 index 0000000000..7eec8d4cf3 --- /dev/null +++ b/priv/migration_tasks/prod/1.7.0@genesis.exs @@ -0,0 +1,40 @@ +defmodule Migration_1_7_0 do + @moduledoc false + + alias Archethic.DB + alias Archethic.DB.EmbeddedImpl.ChainWriter + alias Archethic.Election + alias Archethic.P2P + alias Archethic.TransactionChain + + require Logger + + def run() do + nodes = P2P.authorized_and_available_nodes() + db_path = :persistent_term.get(:archethic_db_path) + + DB.list_io_transactions([]) + |> Stream.each(fn transaction -> + if is_nil(transaction.validation_stamp.genesis_address) do + # query for genesis + storage_nodes = Election.storage_nodes(transaction.address, nodes) + + {:ok, genesis_address} = + TransactionChain.fetch_genesis_address(transaction.address, storage_nodes) + + # update in memory + transaction = + put_in( + transaction, + [Access.key!(:validation_stamp), Access.key!(:genesis_address)], + genesis_address + ) + + # update on disk + File.rm!(ChainWriter.io_path(db_path, transaction.address)) + ChainWriter.write_io_transaction(transaction, db_path) + end + end) + |> Stream.run() + end +end diff --git a/test/archethic/db/embedded_impl_test.exs b/test/archethic/db/embedded_impl_test.exs index e46cf2eb73..b40c371a12 100644 --- a/test/archethic/db/embedded_impl_test.exs +++ b/test/archethic/db/embedded_impl_test.exs @@ -86,7 +86,7 @@ defmodule Archethic.DB.EmbeddedTest do contents = File.read!(filename) - assert contents == Encoding.encode(tx1) + assert contents == Encoding.encode(tx1, storage_type: :io) end test "should delete transaction in io storage after writing it in chain storage", %{ @@ -295,7 +295,7 @@ defmodule Archethic.DB.EmbeddedTest do test "should return a page and its paging state" do transactions = - Enum.map(1..20, fn i -> + Enum.map(0..19, fn i -> tx = TransactionFactory.create_valid_transaction([], index: i, @@ -325,7 +325,7 @@ defmodule Archethic.DB.EmbeddedTest do test "should return an empty list when the Paging Address is not found" do transactions = - Enum.map(1..15, fn i -> + Enum.map(0..14, fn i -> tx = TransactionFactory.create_valid_transaction([], index: i, @@ -352,7 +352,7 @@ defmodule Archethic.DB.EmbeddedTest do test "should return entire chain if paging_address is the genesis (asc)" do transactions = - Enum.map(1..5, fn i -> + Enum.map(0..4, fn i -> tx = TransactionFactory.create_valid_transaction([], index: i, @@ -379,7 +379,7 @@ defmodule Archethic.DB.EmbeddedTest do test "should return empty if paging_address is the genesis (desc)" do transactions = - Enum.map(1..5, fn i -> + Enum.map(0..4, fn i -> tx = TransactionFactory.create_valid_transaction([], index: i, @@ -414,7 +414,7 @@ defmodule Archethic.DB.EmbeddedTest do test "should return all transactions if there are less than one page (10)" do transactions = - Enum.map(1..9, fn i -> + Enum.map(0..8, fn i -> tx = TransactionFactory.create_valid_transaction([], index: i, @@ -435,7 +435,7 @@ defmodule Archethic.DB.EmbeddedTest do test "should return transactions paginated if there are more than one page (10)" do transactions = - Enum.map(1..28, fn i -> + Enum.map(0..27, fn i -> tx = TransactionFactory.create_valid_transaction([], index: i, @@ -474,7 +474,7 @@ defmodule Archethic.DB.EmbeddedTest do test "should be able to load the last page if there are 10 transactions (for a page_size=10)" do transactions = - Enum.map(1..30, fn i -> + Enum.map(0..29, fn i -> tx = TransactionFactory.create_valid_transaction([], index: i, @@ -519,7 +519,7 @@ defmodule Archethic.DB.EmbeddedTest do test "should return the number of transaction in a chain" do transactions = - Enum.map(1..20, fn i -> + Enum.map(0..19, fn i -> tx = TransactionFactory.create_valid_transaction([], index: i, diff --git a/test/archethic/mining/distributed_workflow_test.exs b/test/archethic/mining/distributed_workflow_test.exs index 4fde39277e..3e2d6e76b1 100644 --- a/test/archethic/mining/distributed_workflow_test.exs +++ b/test/archethic/mining/distributed_workflow_test.exs @@ -1322,6 +1322,7 @@ defmodule Archethic.Mining.DistributedWorkflowTest do Enum.each(previous_storage_nodes, &P2P.add_and_connect_node(&1)) %ValidationContext{ + genesis_address: Transaction.previous_address(tx), transaction: tx, previous_storage_nodes: previous_storage_nodes, unspent_outputs: [ @@ -1344,6 +1345,7 @@ defmodule Archethic.Mining.DistributedWorkflowTest do end defp create_validation_stamp(%ValidationContext{ + genesis_address: genesis_address, transaction: tx, unspent_outputs: unspent_outputs, validation_time: timestamp @@ -1366,6 +1368,7 @@ defmodule Archethic.Mining.DistributedWorkflowTest do |> LedgerValidation.to_ledger_operations() %ValidationStamp{ + genesis_address: genesis_address, timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), diff --git a/test/archethic/mining/validation_context_test.exs b/test/archethic/mining/validation_context_test.exs index ec5f2714ad..63d80de14a 100644 --- a/test/archethic/mining/validation_context_test.exs +++ b/test/archethic/mining/validation_context_test.exs @@ -606,6 +606,7 @@ defmodule Archethic.Mining.ValidationContextTest do tx |> Transaction.get_movements() |> Enum.map(&{&1.to, &1.to}) |> Map.new() %ValidationContext{ + genesis_address: Transaction.previous_address(tx), transaction: tx, previous_storage_nodes: previous_storage_nodes, unspent_outputs: unspent_outputs, @@ -619,6 +620,7 @@ defmodule Archethic.Mining.ValidationContextTest do end defp create_validation_stamp_with_invalid_signature(%ValidationContext{ + genesis_address: genesis_address, transaction: tx, unspent_outputs: unspent_outputs, validation_time: timestamp @@ -640,6 +642,7 @@ defmodule Archethic.Mining.ValidationContextTest do |> LedgerValidation.to_ledger_operations() %ValidationStamp{ + genesis_address: genesis_address, timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), @@ -651,6 +654,7 @@ defmodule Archethic.Mining.ValidationContextTest do end defp create_validation_stamp_with_invalid_proof_of_work(%ValidationContext{ + genesis_address: genesis_address, transaction: tx, unspent_outputs: unspent_outputs, validation_time: timestamp @@ -672,6 +676,7 @@ defmodule Archethic.Mining.ValidationContextTest do |> LedgerValidation.to_ledger_operations() %ValidationStamp{ + genesis_address: genesis_address, timestamp: timestamp, proof_of_work: <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>, proof_of_integrity: TransactionChain.proof_of_integrity([tx]), @@ -683,6 +688,7 @@ defmodule Archethic.Mining.ValidationContextTest do end defp create_validation_stamp(%ValidationContext{ + genesis_address: genesis_address, transaction: tx, unspent_outputs: unspent_outputs, validation_time: timestamp @@ -704,6 +710,7 @@ defmodule Archethic.Mining.ValidationContextTest do |> LedgerValidation.to_ledger_operations() %ValidationStamp{ + genesis_address: genesis_address, timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), @@ -716,6 +723,7 @@ defmodule Archethic.Mining.ValidationContextTest do defp create_validation_stamp_with_invalid_transaction_fee( %ValidationContext{ + genesis_address: genesis_address, transaction: tx, unspent_outputs: unspent_outputs, validation_time: timestamp @@ -737,6 +745,7 @@ defmodule Archethic.Mining.ValidationContextTest do |> LedgerValidation.to_ledger_operations() %ValidationStamp{ + genesis_address: genesis_address, timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), @@ -748,6 +757,7 @@ defmodule Archethic.Mining.ValidationContextTest do end defp create_validation_stamp_with_invalid_transaction_movements(%ValidationContext{ + genesis_address: genesis_address, transaction: tx, validation_time: timestamp, unspent_outputs: unspent_outputs @@ -771,6 +781,7 @@ defmodule Archethic.Mining.ValidationContextTest do } %ValidationStamp{ + genesis_address: genesis_address, timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), @@ -782,11 +793,13 @@ defmodule Archethic.Mining.ValidationContextTest do end defp create_validation_stamp_with_invalid_unspent_outputs(%ValidationContext{ + genesis_address: genesis_address, transaction: tx, unspent_outputs: unspent_outputs, validation_time: timestamp }) do %ValidationStamp{ + genesis_address: genesis_address, timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), @@ -810,6 +823,7 @@ defmodule Archethic.Mining.ValidationContextTest do end defp create_validation_stamp_with_invalid_errors(%ValidationContext{ + genesis_address: genesis_address, transaction: tx, unspent_outputs: unspent_outputs, validation_time: timestamp @@ -830,6 +844,7 @@ defmodule Archethic.Mining.ValidationContextTest do |> LedgerValidation.to_ledger_operations() %ValidationStamp{ + genesis_address: genesis_address, timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), @@ -842,6 +857,7 @@ defmodule Archethic.Mining.ValidationContextTest do end defp create_validation_stamp_with_invalid_consumed_inputs(%ValidationContext{ + genesis_address: genesis_address, transaction: tx, validation_time: timestamp, unspent_outputs: unspent_outputs @@ -874,6 +890,7 @@ defmodule Archethic.Mining.ValidationContextTest do ) %ValidationStamp{ + genesis_address: genesis_address, timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), diff --git a/test/archethic/p2p/messages_test.exs b/test/archethic/p2p/messages_test.exs index e333b3b5a6..cc8cdcb2eb 100644 --- a/test/archethic/p2p/messages_test.exs +++ b/test/archethic/p2p/messages_test.exs @@ -240,6 +240,7 @@ defmodule Archethic.P2P.MessageTest do <<0, 0, 227, 129, 244, 35, 48, 113, 14, 75, 1, 127, 107, 32, 29, 93, 232, 119, 254, 1, 65, 32, 47, 129, 164, 142, 240, 43, 22, 81, 188, 212, 56, 238>>, validation_stamp: %ValidationStamp{ + genesis_address: random_address(), timestamp: ~U[2020-06-26 06:37:04.000Z], proof_of_work: <<0, 0, 206, 159, 122, 114, 106, 65, 116, 18, 224, 214, 2, 26, 213, 36, 82, 175, 176, @@ -296,6 +297,7 @@ defmodule Archethic.P2P.MessageTest do <<0, 0, 227, 129, 244, 35, 48, 113, 14, 75, 1, 127, 107, 32, 29, 93, 232, 119, 254, 1, 65, 32, 47, 129, 164, 142, 240, 43, 22, 81, 188, 212, 56, 238>>, validation_stamp: %ValidationStamp{ + genesis_address: random_address(), timestamp: ~U[2020-06-26 06:37:04.000Z], proof_of_work: <<0, 0, 206, 159, 122, 114, 106, 65, 116, 18, 224, 214, 2, 26, 213, 36, 82, 175, 176, @@ -484,6 +486,7 @@ defmodule Archethic.P2P.MessageTest do 175, 135, 180, 179, 28, 57, 84, 35, 156, 173, 212, 235, 155, 226, 41, 148, 171, 132, 196, 120, 51, 136, 4, 78, 123, 70, 44, 76, 162>>, validation_stamp: %ValidationStamp{ + genesis_address: random_address(), timestamp: ~U[2020-06-26 06:37:04.000Z], proof_of_work: <<0, 0, 206, 159, 122, 114, 106, 65, 116, 18, 224, 214, 2, 26, 213, 36, 82, 175, diff --git a/test/archethic/transaction_chain/transaction/cross_validation_stamp_test.exs b/test/archethic/transaction_chain/transaction/cross_validation_stamp_test.exs index 0df354b1e7..61b552b2f9 100644 --- a/test/archethic/transaction_chain/transaction/cross_validation_stamp_test.exs +++ b/test/archethic/transaction_chain/transaction/cross_validation_stamp_test.exs @@ -1,6 +1,7 @@ defmodule Archethic.TransactionChain.Transaction.CrossValidationStampTest do use ArchethicCase use ExUnitProperties + import ArchethicCase alias Archethic.Crypto alias Archethic.TransactionChain.Transaction.CrossValidationStamp @@ -15,18 +16,20 @@ defmodule Archethic.TransactionChain.Transaction.CrossValidationStampTest do pow <- StreamData.binary(length: 32), poi <- StreamData.binary(length: 33), poe <- StreamData.binary(length: 64), - signature <- StreamData.binary(length: 64) + signature <- StreamData.binary(length: 64), + protocol_version <- StreamData.integer(1..Archethic.Mining.protocol_version()) ) do pub = Crypto.last_node_public_key() validation_stamp = %ValidationStamp{ + genesis_address: random_address(), timestamp: DateTime.utc_now(), proof_of_work: <<0::8, 0::8, pow::binary>>, proof_of_integrity: poi, proof_of_election: poe, ledger_operations: %LedgerOperations{}, signature: signature, - protocol_version: ArchethicCase.current_protocol_version() + protocol_version: protocol_version } cross_stamp = diff --git a/test/archethic/transaction_chain/transaction/validation_stamp_test.exs b/test/archethic/transaction_chain/transaction/validation_stamp_test.exs index ece0788ade..64798bbfaf 100644 --- a/test/archethic/transaction_chain/transaction/validation_stamp_test.exs +++ b/test/archethic/transaction_chain/transaction/validation_stamp_test.exs @@ -1,7 +1,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStampTest do use ArchethicCase - import ArchethicCase, only: [current_protocol_version: 0] + import ArchethicCase use ExUnitProperties alias Archethic.Crypto @@ -22,17 +22,21 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStampTest do proof_of_work <- StreamData.binary(length: 33), proof_of_integrity <- StreamData.binary(length: 33), proof_of_election <- StreamData.binary(length: 32), - ledger_operations <- gen_ledger_operations() + genesis_address <- + StreamData.binary(length: 32) |> StreamData.map(&<<0::16, &1::binary>>), + ledger_operations <- gen_ledger_operations(), + protocol_version <- StreamData.integer(1..Archethic.Mining.protocol_version()) ) do pub = Crypto.last_node_public_key() assert %ValidationStamp{ + genesis_address: genesis_address, timestamp: DateTime.utc_now(), proof_of_work: proof_of_work, proof_of_integrity: proof_of_integrity, proof_of_election: proof_of_election, ledger_operations: ledger_operations, - protocol_version: current_protocol_version() + protocol_version: protocol_version } |> ValidationStamp.sign() |> ValidationStamp.valid_signature?(pub) @@ -91,6 +95,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStampTest do describe "symmetric serialization" do test "should support latest version" do stamp = %ValidationStamp{ + genesis_address: random_address(), timestamp: ~U[2021-05-07 13:11:19.000Z], proof_of_work: <<0, 0, 34, 248, 200, 166, 69, 102, 246, 46, 84, 7, 6, 84, 66, 27, 8, 78, 103, 37, 155, @@ -134,4 +139,47 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStampTest do |> elem(0) end end + + test "should support serialize_genesis? flag" do + stamp = %ValidationStamp{ + genesis_address: random_address(), + timestamp: ~U[2021-05-07 13:11:19.000Z], + proof_of_work: + <<0, 0, 34, 248, 200, 166, 69, 102, 246, 46, 84, 7, 6, 84, 66, 27, 8, 78, 103, 37, 155, + 114, 208, 205, 40, 44, 6, 159, 178, 5, 186, 168, 237, 206>>, + proof_of_integrity: + <<0, 49, 174, 251, 208, 41, 135, 147, 199, 114, 232, 140, 254, 103, 186, 138, 175, 28, + 156, 201, 30, 100, 75, 172, 95, 135, 167, 180, 242, 16, 74, 87, 170>>, + proof_of_election: + <<195, 51, 61, 55, 140, 12, 138, 246, 249, 106, 198, 175, 145, 9, 255, 133, 67, 240, 175, + 53, 236, 65, 151, 191, 128, 11, 58, 103, 82, 6, 218, 31, 220, 114, 65, 3, 151, 209, 9, + 84, 209, 105, 191, 180, 156, 157, 95, 25, 202, 2, 169, 112, 109, 54, 99, 40, 47, 96, 93, + 33, 82, 40, 100, 13>>, + ledger_operations: %LedgerOperations{ + fee: 10_000_000, + transaction_movements: [], + unspent_outputs: [], + consumed_inputs: [ + %UnspentOutput{ + from: + <<0, 0, 173, 169, 83, 136, 99, 24, 144, 188, 36, 180, 147, 166, 126, 118, 48, 185, + 248, 65, 34, 85, 12, 87, 197, 69, 121, 0, 21, 5, 152, 20, 7, 197>>, + amount: 100_000_000, + type: :UCO, + timestamp: ~U[2021-05-05 13:11:19.000Z] + } + |> VersionedUnspentOutput.wrap_unspent_output(current_protocol_version()) + ] + }, + signature: + <<67, 12, 4, 246, 155, 34, 32, 108, 195, 54, 139, 8, 77, 152, 5, 55, 233, 217, 126, 181, + 204, 195, 215, 239, 124, 186, 99, 187, 251, 243, 201, 6, 122, 65, 238, 221, 14, 89, 120, + 225, 39, 33, 95, 95, 225, 113, 143, 200, 47, 96, 239, 66, 182, 168, 35, 129, 240, 35, + 183, 47, 69, 154, 37, 172>>, + protocol_version: current_protocol_version() + } + + refute ValidationStamp.serialize(stamp) == + ValidationStamp.serialize(stamp, serialize_genesis?: false) + end end diff --git a/test/archethic/transaction_chain/transaction_test.exs b/test/archethic/transaction_chain/transaction_test.exs index 05097210f9..d715058283 100644 --- a/test/archethic/transaction_chain/transaction_test.exs +++ b/test/archethic/transaction_chain/transaction_test.exs @@ -534,6 +534,7 @@ defmodule Archethic.TransactionChain.TransactionTest do 13, 122, 125, 219, 122, 131, 73, 6>>, type: :oracle, validation_stamp: %Archethic.TransactionChain.Transaction.ValidationStamp{ + genesis_address: random_address(), ledger_operations: %Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations{ fee: 0, diff --git a/test/support/transaction_factory.ex b/test/support/transaction_factory.ex index 84d93b51ad..e2ba380b00 100644 --- a/test/support/transaction_factory.ex +++ b/test/support/transaction_factory.ex @@ -118,6 +118,7 @@ defmodule Archethic.TransactionFactory do validation_stamp = %ValidationStamp{ + genesis_address: seed |> Crypto.derive_keypair(0) |> elem(0) |> Crypto.derive_address(), timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_election: Election.validation_nodes_election_seed_sorting(tx, timestamp), @@ -170,6 +171,7 @@ defmodule Archethic.TransactionFactory do validation_stamp = %ValidationStamp{ + genesis_address: "seed" |> Crypto.derive_keypair(0) |> elem(0) |> Crypto.derive_address(), timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), @@ -214,6 +216,7 @@ defmodule Archethic.TransactionFactory do |> LedgerValidation.to_ledger_operations() validation_stamp = %ValidationStamp{ + genesis_address: "seed" |> Crypto.derive_keypair(0) |> elem(0) |> Crypto.derive_address(), timestamp: timestamp, proof_of_work: <<0, 0, :crypto.strong_rand_bytes(32)::binary>>, proof_of_integrity: TransactionChain.proof_of_integrity([tx]), @@ -262,6 +265,7 @@ defmodule Archethic.TransactionFactory do |> LedgerValidation.to_ledger_operations() validation_stamp = %ValidationStamp{ + genesis_address: seed |> Crypto.derive_keypair(0) |> elem(0) |> Crypto.derive_address(), timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), @@ -304,6 +308,7 @@ defmodule Archethic.TransactionFactory do validation_stamp = %ValidationStamp{ + genesis_address: "seed" |> Crypto.derive_keypair(0) |> elem(0) |> Crypto.derive_address(), timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_election: Election.validation_nodes_election_seed_sorting(tx, timestamp), @@ -347,6 +352,7 @@ defmodule Archethic.TransactionFactory do validation_stamp = %ValidationStamp{ + genesis_address: "seed" |> Crypto.derive_keypair(0) |> elem(0) |> Crypto.derive_address(), timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_integrity: TransactionChain.proof_of_integrity([tx]), @@ -407,6 +413,7 @@ defmodule Archethic.TransactionFactory do validation_stamp = %ValidationStamp{ + genesis_address: seed |> Crypto.derive_keypair(0) |> elem(0) |> Crypto.derive_address(), timestamp: timestamp, proof_of_work: Crypto.origin_node_public_key(), proof_of_election: Election.validation_nodes_election_seed_sorting(tx, timestamp),