Skip to content

Commit

Permalink
Add the genesis address in the validation_stamp (#1588)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
bchamagne and Neylix committed Jan 8, 2025
1 parent e2d39e0 commit feeb991
Show file tree
Hide file tree
Showing 26 changed files with 434 additions and 131 deletions.
1 change: 1 addition & 0 deletions lib/archethic/bootstrap/network_init.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
41 changes: 29 additions & 12 deletions lib/archethic/db/embedded_impl/chain_reader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -273,41 +275,44 @@ 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

File.close(fd)
{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}
end
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])

Expand All @@ -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
Expand Down Expand Up @@ -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, <<size::32, version::32>>} ->
if length(acc) == @page_size do
Expand All @@ -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 ->
Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion lib/archethic/db/embedded_impl/chain_writer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
83 changes: 52 additions & 31 deletions lib/archethic/db/embedded_impl/encoding.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -157,6 +165,7 @@ defmodule Archethic.DB.EmbeddedImpl.Encoding do
{"cross_validation_stamps",
<<length(cross_validation_stamps)::8, cross_validation_stamps_encoding::binary>>}
]
|> maybe_add_genesis_address(genesis_address, Keyword.get(opts, :storage_type, :chain))
|> Enum.map(fn {column, value} ->
wrapped_value = Utils.wrap_binary(value)

Expand Down Expand Up @@ -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", <<fee::64>>, acc) do
put_in(
acc,
Expand Down Expand Up @@ -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
3 changes: 2 additions & 1 deletion lib/archethic/mining.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
23 changes: 22 additions & 1 deletion lib/archethic/mining/distributed_workflow.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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, [
Expand Down Expand Up @@ -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(
Expand Down
10 changes: 9 additions & 1 deletion lib/archethic/mining/validation_context.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)]
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit feeb991

Please sign in to comment.