Skip to content

Commit

Permalink
Use elected node view to create proof
Browse files Browse the repository at this point in the history
  • Loading branch information
Neylix committed Oct 28, 2024
1 parent c9d61c2 commit 33656a7
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 216 deletions.
2 changes: 1 addition & 1 deletion lib/archethic/mining/distributed_workflow.ex
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ defmodule Archethic.Mining.DistributedWorkflow do
resolved_addresses: resolved_addresses,
contract_context: contract_context,
genesis_address: genesis_address,
sorted_nodes: ProofOfValidation.sort_nodes(authorized_nodes)
proof_elected_nodes: ProofOfValidation.get_election(authorized_nodes, tx.address)
)

role = if node_public_key == coordinator_key, do: :coordinator, else: :cross_validator
Expand Down
2 changes: 1 addition & 1 deletion lib/archethic/mining/standalone_workflow.ex
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ defmodule Archethic.Mining.StandaloneWorkflow do
resolved_addresses: resolved_addresses,
contract_context: contract_context,
genesis_address: genesis_address,
sorted_nodes: ProofOfValidation.sort_nodes(authorized_nodes)
proof_elected_nodes: ProofOfValidation.get_election(authorized_nodes, tx.address)
)

validation_context = ValidationContext.validate_pending_transaction(validation_context)
Expand Down
34 changes: 15 additions & 19 deletions lib/archethic/mining/validation_context.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ defmodule Archethic.Mining.ValidationContext do
:contract_context,
:genesis_address,
:proof_of_validation,
:sorted_nodes,
:proof_elected_nodes,
resolved_addresses: %{},
unspent_outputs: [],
cross_validation_stamps: [],
Expand Down Expand Up @@ -67,7 +67,7 @@ defmodule Archethic.Mining.ValidationContext do
alias Archethic.TransactionChain.Transaction
alias Archethic.TransactionChain.Transaction.CrossValidationStamp
alias Archethic.TransactionChain.Transaction.ProofOfValidation
alias Archethic.TransactionChain.Transaction.ProofOfValidation.SortedNode
alias Archethic.TransactionChain.Transaction.ProofOfValidation.ElectedNodes
alias Archethic.TransactionChain.Transaction.ValidationStamp
alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations

Expand Down Expand Up @@ -105,7 +105,7 @@ defmodule Archethic.Mining.ValidationContext do
},
cross_validation_stamps: list({Crypto.key(), CrossValidationStamp.t()}),
proof_of_validation: nil | ProofOfValidation.t(),
sorted_nodes: nil | SortedNode.t(),
proof_elected_nodes: nil | ElectedNodes.t(),
chain_storage_nodes_view: bitstring(),
beacon_storage_nodes_view: bitstring(),
storage_nodes_confirmations: list({index :: non_neg_integer(), signature :: binary()}),
Expand Down Expand Up @@ -451,40 +451,36 @@ defmodule Archethic.Mining.ValidationContext do
@spec get_cross_validation_state(t()) :: :reached | :not_reached | :error
def get_cross_validation_state(%__MODULE__{
cross_validation_stamps: stamps,
sorted_nodes: sorted_nodes
proof_elected_nodes: proof_elected_nodes
}),
do: ProofOfValidation.get_state(sorted_nodes, stamps)
do: ProofOfValidation.get_state(proof_elected_nodes, stamps)

@doc """
Aggregate signature of the cross validation stamps
Create a bitmask of the node that signed the cross stamps
"""
@spec create_proof_of_validation(context :: t()) :: t()
def create_proof_of_validation(
context = %__MODULE__{cross_validation_stamps: stamps, sorted_nodes: sorted_nodes}
context = %__MODULE__{
cross_validation_stamps: stamps,
proof_elected_nodes: proof_elected_nodes
}
) do
%__MODULE__{context | proof_of_validation: ProofOfValidation.create(sorted_nodes, stamps)}
%__MODULE__{
context
| proof_of_validation: ProofOfValidation.create(proof_elected_nodes, stamps)
}
end

@doc """
Ensure a proof of validation is valid according to current context
"""
@spec valid_proof_of_validation?(context :: t(), proof :: ProofOfValidation.t()) :: boolean()
def valid_proof_of_validation?(
context = %__MODULE__{
chain_storage_nodes: storage_nodes,
sorted_nodes: sorted_nodes,
validation_stamp: stamp
},
%__MODULE__{proof_elected_nodes: proof_elected_nodes, validation_stamp: stamp},
proof
) do
validation_nodes = ProofOfValidation.get_nodes(sorted_nodes, proof)

expected_validation_nodes =
context |> get_confirmed_validation_nodes() |> Enum.concat(storage_nodes) |> Enum.uniq()

Enum.all?(validation_nodes, &Enum.member?(expected_validation_nodes, &1)) and
ProofOfValidation.valid?(sorted_nodes, proof, stamp)
ProofOfValidation.valid?(proof_elected_nodes, proof, stamp)
end

@doc """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ defmodule Archethic.P2P.Message.ReplicatePendingTransactionChain do
with {:ok, tx, validation_inputs} <- get_transaction_data(address),
authorized_nodes <- P2P.authorized_and_available_nodes(tx.validation_stamp.timestamp),
true <- Election.chain_storage_node?(address, node_public_key, authorized_nodes),
sorted_nodes <- ProofOfValidation.sort_nodes(authorized_nodes),
true <- ProofOfValidation.valid?(sorted_nodes, proof, tx.validation_stamp) do
elected_nodes <- ProofOfValidation.get_election(authorized_nodes, address),
true <- ProofOfValidation.valid?(elected_nodes, proof, tx.validation_stamp) do
replicate_transaction(tx, validation_inputs, genesis_address, sender_public_key)
else
_ -> :skip
Expand Down
8 changes: 5 additions & 3 deletions lib/archethic/p2p/message/replicate_transaction.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@ defmodule Archethic.P2P.Message.ReplicateTransaction do
},
_
) do
sorted_nodes =
validation_time |> P2P.authorized_and_available_nodes() |> ProofOfValidation.sort_nodes()
elected_nodes =
validation_time
|> P2P.authorized_and_available_nodes()
|> ProofOfValidation.get_election(tx.address)

if ProofOfValidation.valid?(sorted_nodes, proof_of_validation, stamp) do
if ProofOfValidation.valid?(elected_nodes, proof_of_validation, stamp) do
Task.Supervisor.start_child(TaskSupervisor, fn ->
replicate_transaction(tx, genesis_address)
end)
Expand Down
126 changes: 87 additions & 39 deletions lib/archethic/transaction_chain/transaction/proof_of_validation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ defmodule Archethic.TransactionChain.Transaction.ProofOfValidation do

alias Archethic.Utils

alias __MODULE__.SortedNode
alias __MODULE__.ElectedNodes

@enforce_keys [:signature, :nodes_bitmask]
defstruct [:signature, :nodes_bitmask, version: 1]
Expand All @@ -34,23 +34,37 @@ defmodule Archethic.TransactionChain.Transaction.ProofOfValidation do

@bls_signature_size 96

defmodule SortedNode do
defmodule ElectedNodes do
@moduledoc """
Struct holding sorted authorized nodes created for ProofOfValidation
Struct holding sorted validation nodes elected for the transaction
and the required number of validations
"""
alias Archethic.P2P.Node

@enforce_keys [:nodes]
defstruct nodes: []
@enforce_keys [:required_validations, :validation_nodes]
defstruct [:required_validations, validation_nodes: []]

@type t :: %__MODULE__{nodes: list(Node.t())}
@type t :: %__MODULE__{
required_validations: non_neg_integer(),
validation_nodes: list(Node.t())
}
end

@doc """
Returns the sorted list of nodes
Returns the sorted list of nodes and the required number of validation nodes
The input nodes list needs to be the authorized and available nodes at the time of the transaction
"""
@spec sort_nodes(nodes :: list(Node.t())) :: SortedNode.t()
def sort_nodes(nodes), do: %SortedNode{nodes: Enum.sort_by(nodes, & &1.first_public_key)}
@spec get_election(nodes :: list(Node.t()), tx_address :: Crypto.prepended_hash()) ::
ElectedNodes.t()
def get_election(nodes, tx_address) do
required_validations = get_nb_required_validations(nodes)
validation_nodes = Election.storage_nodes(tx_address, nodes)

%ElectedNodes{
required_validations: required_validations,
validation_nodes: Enum.sort_by(validation_nodes, & &1.first_public_key)
}
end

@doc """
Determines if enough cross validation stamps have been received to create the aggregated signature
Expand All @@ -59,31 +73,35 @@ defmodule Archethic.TransactionChain.Transaction.ProofOfValidation do
- :not_reached if not enough stamps received yet
- :error if it's not possible to reach the required validations
"""
@spec get_state(nodes :: SortedNode.t(), stamps :: list()) :: :reached | :not_reached | :error
def get_state(sorted_nodes = %SortedNode{}, stamps) do
nb_valid_stamps = stamps |> filter_valid_cross_stamps() |> Enum.count()
nb_required_stamps = get_nb_required_validations(sorted_nodes)
@spec get_state(nodes :: ElectedNodes.t(), stamps :: list()) ::
:reached | :not_reached | :error
def get_state(
%ElectedNodes{required_validations: required_validations, validation_nodes: nodes},
stamps
) do
nb_valid_stamps = stamps |> filter_valid_cross_stamps(nodes) |> Enum.count()

if nb_valid_stamps >= nb_required_stamps do
if nb_valid_stamps >= required_validations do
:reached
else
nb_validation_nodes = get_nb_validation_nodes(sorted_nodes)
nb_remaining_stamps = nb_validation_nodes - Enum.count(stamps)
nb_remaining_stamps = Enum.count(nodes) - Enum.count(stamps)

# If the remaining stamp to receive cannot reach the required validations we return an error
if nb_valid_stamps + nb_remaining_stamps >= nb_required_stamps,
if nb_valid_stamps + nb_remaining_stamps >= required_validations,
do: :not_reached,
else: :error
end
end

@doc """
Construct the proof of validation aggregating valid cross stamps
Construct the proof of validation aggregating valid cross stamps signatures
"""
@spec create(nodes :: SortedNode.t(), stamps :: list({Crypto.key(), CrossValidationStamp.t()})) ::
t()
def create(%SortedNode{nodes: nodes}, stamps) do
valid_cross = filter_valid_cross_stamps(stamps)
@spec create(
nodes :: ElectedNodes.t(),
stamps :: list({Crypto.key(), CrossValidationStamp.t()})
) :: t()
def create(%ElectedNodes{validation_nodes: nodes}, stamps) do
valid_cross = filter_valid_cross_stamps(stamps, nodes)

{public_keys, signatures} =
Enum.reduce(
Expand All @@ -109,8 +127,8 @@ defmodule Archethic.TransactionChain.Transaction.ProofOfValidation do
@doc """
Returns the list of node that signed the proof of validation
"""
@spec get_nodes(nodes :: SortedNode.t(), proof :: t()) :: list(Node.t())
def get_nodes(%SortedNode{nodes: nodes}, %__MODULE__{nodes_bitmask: bitmask}) do
@spec get_nodes(nodes :: ElectedNodes.t(), proof :: t()) :: list(Node.t())
def get_nodes(%ElectedNodes{validation_nodes: nodes}, %__MODULE__{nodes_bitmask: bitmask}) do
bitmask
|> Utils.bitstring_to_integer_list()
|> Enum.with_index()
Expand All @@ -123,44 +141,74 @@ defmodule Archethic.TransactionChain.Transaction.ProofOfValidation do
- Number of validation reach the threshold
- aggregated signature is valid
"""
@spec valid?(nodes :: SortedNode.t(), proof :: t(), validation_stamp :: ValidationStamp.t()) ::
boolean()
@spec valid?(
nodes :: ElectedNodes.t(),
proof :: t(),
validation_stamp :: ValidationStamp.t()
) :: boolean()
def valid?(
sorted_nodes = %SortedNode{},
elected_nodes = %ElectedNodes{
required_validations: required_validations,
validation_nodes: validation_nodes
},
proof = %__MODULE__{signature: signature},
validation_stamp
) do
nb_required_stamps = get_nb_required_validations(sorted_nodes)
validation_nodes = get_nodes(sorted_nodes, proof)
signer_nodes = get_nodes(elected_nodes, proof)

if Enum.count(validation_nodes) >= nb_required_stamps do
with true <- Enum.count(signer_nodes) >= required_validations,
true <- signer_nodes |> MapSet.new() |> MapSet.subset?(MapSet.new(validation_nodes)) do
aggregated_public_key =
validation_nodes
signer_nodes
|> Enum.map(& &1.mining_public_key)
|> Crypto.aggregate_mining_public_keys()

raw_data = CrossValidationStamp.get_raw_data_to_sign(validation_stamp, [])

Crypto.verify?(signature, raw_data, aggregated_public_key)
else
false
_ -> false
end
end

defp filter_valid_cross_stamps(stamps) do
Enum.filter(stamps, fn {_from, %CrossValidationStamp{inconsistencies: inconsistencies}} ->
Enum.empty?(inconsistencies)
def valid_opti?(
elected_nodes = %ElectedNodes{
required_validations: required_validations,
validation_nodes: validation_nodes
},
proof = %__MODULE__{signature: signature},
validation_stamp
) do
signer_nodes = get_nodes(elected_nodes, proof)
validation_nodes = MapSet.new(validation_nodes)

Enum.reduce_while(signer_nodes, {[], 0}, fn node, {keys, nb} ->
if MapSet.member?(validation_nodes, node),
do: {:cont, {[node.mining_public_key | keys], nb + 1}},
else: {:halt, {[], 0}}
end)
|> then(fn
{_keys, nb} when nb < required_validations ->
false

{keys, _} ->
aggregated_public_key = Crypto.aggregate_mining_public_keys(keys)
raw_data = CrossValidationStamp.get_raw_data_to_sign(validation_stamp, [])
Crypto.verify?(signature, raw_data, aggregated_public_key)
end)
end

defp get_nb_required_validations(%SortedNode{nodes: nodes}) do
defp filter_valid_cross_stamps(stamps, validation_nodes) do
Enum.filter(stamps, fn {from, %CrossValidationStamp{inconsistencies: inconsistencies}} ->
Enum.empty?(inconsistencies) and Utils.key_in_node_list?(validation_nodes, from)
end)
end

defp get_nb_required_validations(nodes) do
%StorageConstraints{number_replicas: nb_replicas_fn} = Election.get_storage_constraints()
nb_replicas_fn.(nodes)
end

# Will be usefull once overbooking will be implemented
defp get_nb_validation_nodes(%SortedNode{nodes: nodes}), do: Enum.count(nodes)

@spec serialize(t()) :: bitstring()
def serialize(%__MODULE__{version: version, signature: signature, nodes_bitmask: bitmask}) do
<<version::8, signature::binary, bit_size(bitmask)::8, bitmask::bitstring>>
Expand Down
Loading

0 comments on commit 33656a7

Please sign in to comment.