diff --git a/config/config.exs b/config/config.exs index 0b361833d..f7cb9e7c3 100644 --- a/config/config.exs +++ b/config/config.exs @@ -91,7 +91,8 @@ config :archethic, Archethic.Bootstrap.NetworkInit, genesis_origin_public_keys: [ "010104AB41291F847A601055AEDD1AF24FF76FA970D6441E2DCA3818A8319B004C96B27B8FEB1DA31A044BA0A4800B4353359735719EBB3A05F98393A9CC599C3FAFD6" |> Base.decode16!(case: :mixed) - ] + ], + genesis_network_pool_amount: 3_400_000_000_000_000 config :archethic, Archethic.P2P.BootstrappingSeeds, backup_file: "p2p/seeds", diff --git a/config/test.exs b/config/test.exs index 39cbf0468..61eb1c90d 100755 --- a/config/test.exs +++ b/config/test.exs @@ -6,7 +6,7 @@ config :logger, level: :error config :archethic, :mut_dir, "data_test" config :archethic, Archethic.Account.MemTablesLoader, enabled: false -config :archethic, Archethic.Account.MemTables.NFTLedger, enabled: false +config :archethic, Archethic.Account.MemTables.TokenLedger, enabled: false config :archethic, Archethic.Account.MemTables.UCOLedger, enabled: false config :archethic, Archethic.BeaconChain.Subset, enabled: false diff --git a/lib/archethic/account/supervisor.ex b/lib/archethic/account/supervisor.ex index 05fb395de..2168a49aa 100644 --- a/lib/archethic/account/supervisor.ex +++ b/lib/archethic/account/supervisor.ex @@ -3,7 +3,7 @@ defmodule Archethic.Account.Supervisor do use Supervisor - alias Archethic.Account.MemTables.NFTLedger + alias Archethic.Account.MemTables.TokenLedger alias Archethic.Account.MemTables.UCOLedger alias Archethic.Account.MemTablesLoader @@ -15,7 +15,7 @@ defmodule Archethic.Account.Supervisor do def init(_args) do children = [ - NFTLedger, + TokenLedger, UCOLedger, MemTablesLoader ] diff --git a/lib/archethic/bootstrap/network_init.ex b/lib/archethic/bootstrap/network_init.ex index ffd85c03f..b061e05d1 100644 --- a/lib/archethic/bootstrap/network_init.ex +++ b/lib/archethic/bootstrap/network_init.ex @@ -21,6 +21,8 @@ defmodule Archethic.Bootstrap.NetworkInit do alias Archethic.SharedSecrets + alias Archethic.Reward + alias Archethic.TransactionChain.Transaction alias Archethic.TransactionChain.Transaction.CrossValidationStamp alias Archethic.TransactionChain.Transaction.ValidationStamp @@ -43,6 +45,11 @@ defmodule Archethic.Bootstrap.NetworkInit do [__MODULE__, :genesis_origin_public_keys] ) + @genesis_network_pool_amount Application.compile_env!( + :archethic, + [__MODULE__, :genesis_network_pool_amount] + ) + defp get_genesis_pools do Application.get_env(:archethic, __MODULE__) |> Keyword.get(:genesis_pools, []) end @@ -115,12 +122,11 @@ defmodule Archethic.Bootstrap.NetworkInit do """ @spec init_genesis_wallets() :: :ok def init_genesis_wallets do - network_pool_address = SharedSecrets.get_network_pool_address() Logger.info("Create UCO distribution genesis transaction") tx = - network_pool_address - |> genesis_transfers() + get_genesis_pools() + |> Enum.map(&%Transfer{to: &1.address, amount: &1.amount}) |> create_genesis_transaction() genesis_transfers_amount = @@ -139,6 +145,15 @@ defmodule Archethic.Bootstrap.NetworkInit do |> self_replication() end + @spec init_network_reward_pool() :: :ok + def init_network_reward_pool() do + Logger.info("Create mining reward pool") + + Reward.new_rewards_mint(@genesis_network_pool_amount) + |> self_validation() + |> self_replication() + end + defp create_genesis_transaction(genesis_transfers) do Transaction.new( :transfer, @@ -154,12 +169,6 @@ defmodule Archethic.Bootstrap.NetworkInit do ) end - defp genesis_transfers(network_pool_address) do - get_genesis_pools() - |> Enum.map(&%Transfer{to: &1.address, amount: &1.amount}) - |> Enum.concat([%Transfer{to: network_pool_address, amount: 146_000_000_000_000_000}]) - end - @spec self_validation(Transaction.t(), list(UnspentOutput.t())) :: Transaction.t() def self_validation(tx = %Transaction{}, unspent_outputs \\ []) do operations = diff --git a/lib/archethic/bootstrap/sync.ex b/lib/archethic/bootstrap/sync.ex index 8b5113b58..86adfdca4 100644 --- a/lib/archethic/bootstrap/sync.ex +++ b/lib/archethic/bootstrap/sync.ex @@ -122,6 +122,7 @@ defmodule Archethic.Bootstrap.Sync do NetworkInit.init_software_origin_chain() NetworkInit.init_node_shared_secrets_chain() NetworkInit.init_genesis_wallets() + NetworkInit.init_network_reward_pool() end @doc """ diff --git a/lib/archethic/crypto.ex b/lib/archethic/crypto.ex index 22c2158a0..b542fc5f5 100755 --- a/lib/archethic/crypto.ex +++ b/lib/archethic/crypto.ex @@ -1047,13 +1047,14 @@ defmodule Archethic.Crypto do end end - def load_transaction(%Transaction{type: :node_rewards, address: address}) do + def load_transaction(%Transaction{type: type, address: address}) + when type in [:node_rewards, :mint_rewards] do nb_transactions = TransactionChain.size(address) SharedSecretsKeystore.set_network_pool_key_index(nb_transactions) Logger.info("Network pool chain positioned at#{nb_transactions}", transaction_address: Base.encode16(address), - transaction_type: :node_rewards + transaction_type: type ) end diff --git a/lib/archethic/crypto/keystore/shared_secrets/software_impl.ex b/lib/archethic/crypto/keystore/shared_secrets/software_impl.ex index a889ccbf5..8cc65a49b 100644 --- a/lib/archethic/crypto/keystore/shared_secrets/software_impl.ex +++ b/lib/archethic/crypto/keystore/shared_secrets/software_impl.ex @@ -39,7 +39,10 @@ defmodule Archethic.Crypto.SharedSecretsKeystore.SoftwareImpl do Logger.info("Node shared secrets keys positioned at #{nb_node_shared_secrets_keys}") - nb_network_pool_keys = TransactionChain.count_transactions_by_type(:node_rewards) + nb_network_pool_keys = + TransactionChain.count_transactions_by_type(:node_rewards) + + TransactionChain.count_transactions_by_type(:mint_rewards) + Logger.info("Network pool keys positioned at #{nb_network_pool_keys}") :ets.insert(@keystore_table, {:shared_secrets_index, nb_node_shared_secrets_keys}) diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index daaeeddd5..cf89dcd0e 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -27,7 +27,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do alias Archethic.TransactionChain.TransactionData alias Archethic.TransactionChain.TransactionData.Ledger alias Archethic.TransactionChain.TransactionData.Ownership - alias Archethic.TransactionChain.TransactionData.UCOLedger + alias Archethic.TransactionChain.TransactionData.TokenLedger alias Archethic.Utils @@ -104,12 +104,12 @@ defmodule Archethic.Mining.PendingTransactionValidation do type: :node_rewards, data: %TransactionData{ ledger: %Ledger{ - uco: %UCOLedger{transfers: uco_transfers} + token: %TokenLedger{transfers: token_transfers} } } }) do - case Reward.get_transfers_for_in_need_validation_nodes(Reward.last_scheduling_date()) do - ^uco_transfers -> + case Reward.get_transfers(Reward.last_scheduling_date()) do + ^token_transfers -> :ok _ -> @@ -304,9 +304,10 @@ defmodule Archethic.Mining.PendingTransactionValidation do end defp do_accept_transaction(%Transaction{ - type: :token, + type: type, data: %TransactionData{content: content} - }) do + }) + when type in [:token, :mint_rewards] do schema = :archethic |> Application.app_dir("priv/json-schemas/token-core.json") diff --git a/lib/archethic/p2p/message/replicate_transaction_chain.ex b/lib/archethic/p2p/message/replicate_transaction_chain.ex index b573d9f42..79ddc1951 100644 --- a/lib/archethic/p2p/message/replicate_transaction_chain.ex +++ b/lib/archethic/p2p/message/replicate_transaction_chain.ex @@ -3,12 +3,11 @@ defmodule Archethic.P2P.Message.ReplicateTransactionChain do Represents a message to initiate the replication of the transaction chain related to the given transaction """ @enforce_keys [:transaction] - defstruct [:transaction, ack_storage?: false] + defstruct [:transaction] alias Archethic.TransactionChain.Transaction @type t :: %__MODULE__{ - transaction: Transaction.t(), - ack_storage?: boolean() + transaction: Transaction.t() } end diff --git a/lib/archethic/replication.ex b/lib/archethic/replication.ex index 9b5c2b48d..e57006704 100644 --- a/lib/archethic/replication.ex +++ b/lib/archethic/replication.ex @@ -28,8 +28,6 @@ defmodule Archethic.Replication do alias Archethic.OracleChain - alias Archethic.Reward - alias Archethic.SharedSecrets alias __MODULE__.TransactionContext @@ -553,7 +551,6 @@ defmodule Archethic.Replication do Contracts.load_transaction(tx) BeaconChain.load_transaction(tx) OracleChain.load_transaction(tx) - Reward.load_transaction(tx) :ok end end diff --git a/lib/archethic/reward.ex b/lib/archethic/reward.ex index b73f3f40f..8b65d050d 100644 --- a/lib/archethic/reward.ex +++ b/lib/archethic/reward.ex @@ -3,21 +3,12 @@ defmodule Archethic.Reward do Module which handles the rewards and transfer scheduling """ - alias Archethic.Election - alias Archethic.OracleChain - alias Archethic.P2P - alias Archethic.P2P.Message.GetTransactionChain - alias Archethic.P2P.Message.GetUnspentOutputs - alias Archethic.P2P.Message.TransactionList - alias Archethic.P2P.Message.UnspentOutputList - alias Archethic.P2P.Node - alias __MODULE__.NetworkPoolScheduler - alias Archethic.TransactionChain alias Archethic.TransactionChain.Transaction + alias Archethic.TransactionChain.TransactionData alias Archethic.TransactionChain.TransactionData.UCOLedger.Transfer @unit_uco 100_000_000 @@ -36,76 +27,33 @@ defmodule Archethic.Reward do end @doc """ - Return the list of transfers to rewards the validation nodes which receive less than the minimum validation node reward - - This will get and check all the unspent outputs after the last reward date and determine which were mining reward - and compare it with the minimum of rewards for a validation node + Create a transaction for minting new rewards """ - @spec get_transfers_for_in_need_validation_nodes(last_reward_date :: DateTime.t()) :: - reward_transfers :: list(Transfer.t()) - def get_transfers_for_in_need_validation_nodes(last_date = %DateTime{}) do - min_validation_nodes_reward = min_validation_nodes_reward() - - Task.async_stream( - P2P.authorized_and_available_nodes(), - fn node = %Node{reward_address: reward_address} -> - mining_rewards = - reward_address - |> get_transactions_after(last_date) - |> Task.async_stream(&get_reward_unspent_outputs/1, timeout: 500, on_exit: :kill_task) - |> Stream.filter(&match?({:ok, _}, &1)) - |> Enum.flat_map(& &1) - - {node, mining_rewards} - end - ) - |> Enum.filter(fn {_, balance} -> balance < min_validation_nodes_reward end) - |> Enum.map(fn {%Node{reward_address: address}, amount} -> - %Transfer{to: address, amount: min_validation_nodes_reward - amount} - end) - end - - defp get_transactions_after(address, date) do - {:ok, last_address} = TransactionChain.resolve_last_address(address, DateTime.utc_now()) - - last_address - |> Election.chain_storage_nodes(P2P.available_nodes()) - |> P2P.nearest_nodes() - |> get_transaction_chain_after(address, date) - end - - defp get_transaction_chain_after([node | rest], address, date) do - case P2P.send_message(node, %GetTransactionChain{address: address}) do - {:ok, %TransactionList{transactions: transactions, more?: false}} -> - transactions - - {:error, _} -> - get_transaction_chain_after(rest, address, date) - end + @spec new_rewards_mint(amount :: non_neg_integer()) :: Transaction.t() + def new_rewards_mint(amount) do + data = %TransactionData{ + content: """ + { + "supply":#{amount}, + "type":"fungible", + "name":"Mining UCO rewards", + "symbol":"MUCO" + } + """ + } + + Transaction.new(:mint_rewards, data) end - defp get_reward_unspent_outputs(%Transaction{address: address}) do - address - |> Election.chain_storage_nodes(P2P.available_nodes()) - |> P2P.nearest_nodes() - |> get_unspent_outputs(address) - |> Enum.filter(&(&1.type == :reward)) - end - - defp get_unspent_outputs([node | rest], address) do - case P2P.send_message(node, %GetUnspentOutputs{address: address}) do - {:ok, %UnspentOutputList{unspent_outputs: unspent_outputs}} -> - unspent_outputs - - {:error, _} -> - get_unspent_outputs(rest, address) - end + @doc """ + Return the list of transfers to rewards the validation nodes for a specific date + """ + @spec get_transfers(last_reward_date :: DateTime.t()) :: reward_transfers :: list(Transfer.t()) + def get_transfers(_last_date = %DateTime{}) do + # TODO + [] end - defp get_unspent_outputs([], _), do: {:error, :network_issue} - - def load_transaction(_), do: :ok - @doc """ Returns the last date of the rewards scheduling from the network pool """ diff --git a/lib/archethic/reward/network_pool_scheduler.ex b/lib/archethic/reward/network_pool_scheduler.ex index bd725c955..08d2d9e49 100644 --- a/lib/archethic/reward/network_pool_scheduler.ex +++ b/lib/archethic/reward/network_pool_scheduler.ex @@ -15,10 +15,10 @@ defmodule Archethic.Reward.NetworkPoolScheduler do alias Archethic.Reward - alias Archethic.TransactionChain.Transaction - alias Archethic.TransactionChain.TransactionData - alias Archethic.TransactionChain.TransactionData.Ledger - alias Archethic.TransactionChain.TransactionData.UCOLedger + # alias Archethic.TransactionChain.Transaction + # alias Archethic.TransactionChain.TransactionData + # alias Archethic.TransactionChain.TransactionData.Ledger + # alias Archethic.TransactionChain.TransactionData.UCOLedger alias Archethic.Utils @@ -84,7 +84,7 @@ defmodule Archethic.Reward.NetworkPoolScheduler do if sender?() do interval |> get_last_date - |> Reward.get_transfers_for_in_need_validation_nodes() + |> Reward.get_transfers() |> send_rewards() end @@ -140,25 +140,25 @@ defmodule Archethic.Reward.NetworkPoolScheduler do defp send_rewards([]), do: :ok - defp send_rewards(transfers) do - Logger.debug("Sending node reward transaction") - - Transaction.new(:node_rewards, %TransactionData{ - code: """ - condition inherit: [ - # We need to ensure the transaction type keep consistent - # So we can apply specific rules during the transaction verification - type: node_rewards - ] - """, - ledger: %Ledger{ - uco: %UCOLedger{ - transfers: transfers - } - } - }) - |> Archethic.send_new_transaction() - end + # defp send_rewards(transfers) do + # Logger.debug("Sending node reward transaction") + + # Transaction.new(:node_rewards, %TransactionData{ + # code: """ + # condition inherit: [ + # # We need to ensure the transaction type keep consistent + # # So we can apply specific rules during the transaction verification + # type: node_rewards + # ] + # """, + # ledger: %Ledger{ + # uco: %UCOLedger{ + # transfers: transfers + # } + # } + # }) + # |> Archethic.send_new_transaction() + # end defp schedule(interval) do Process.send_after(self(), :send_rewards, Utils.time_offset(interval) * 1000) diff --git a/lib/archethic/shared_secrets/mem_tables_loader.ex b/lib/archethic/shared_secrets/mem_tables_loader.ex index aa23501ab..6f7da6c0c 100644 --- a/lib/archethic/shared_secrets/mem_tables_loader.ex +++ b/lib/archethic/shared_secrets/mem_tables_loader.ex @@ -138,7 +138,8 @@ defmodule Archethic.SharedSecrets.MemTablesLoader do ) end - def load_transaction(%Transaction{type: :node_rewards, address: address}) do + def load_transaction(%Transaction{type: type, address: address}) + when type in [:node_rewards, :mint_rewards] do NetworkLookup.set_network_pool_address(address) end diff --git a/lib/archethic/transaction_chain/mem_tables_loader.ex b/lib/archethic/transaction_chain/mem_tables_loader.ex index efddab0de..9fe85a689 100644 --- a/lib/archethic/transaction_chain/mem_tables_loader.ex +++ b/lib/archethic/transaction_chain/mem_tables_loader.ex @@ -29,6 +29,7 @@ defmodule Archethic.TransactionChain.MemTablesLoader do :beacon, :beacon_summary, :node_rewards, + :mint_rewards, :origin ] diff --git a/lib/archethic/transaction_chain/transaction.ex b/lib/archethic/transaction_chain/transaction.ex index 2444779ab..86215dcd8 100755 --- a/lib/archethic/transaction_chain/transaction.ex +++ b/lib/archethic/transaction_chain/transaction.ex @@ -63,6 +63,7 @@ defmodule Archethic.TransactionChain.Transaction do :node | :node_shared_secrets | :node_rewards + | :mint_rewards | :beacon | :beacon_summary | :oracle @@ -84,6 +85,7 @@ defmodule Archethic.TransactionChain.Transaction do :oracle, :oracle_summary, :node_rewards, + :mint_rewards, :code_proposal, :code_approval, :keychain, @@ -181,7 +183,7 @@ defmodule Archethic.TransactionChain.Transaction do {previous_public_key, next_public_key} end - defp get_transaction_public_keys(:node_rewards) do + defp get_transaction_public_keys(type) when type in [:node_rewards, :mint_rewards] do key_index = Crypto.number_of_network_pool_keys() previous_public_key = Crypto.network_pool_public_key(key_index) next_public_key = Crypto.network_pool_public_key(key_index + 1) @@ -206,7 +208,8 @@ defmodule Archethic.TransactionChain.Transaction do %{tx | previous_signature: previous_signature} end - defp previous_sign_transaction(tx = %__MODULE__{type: :node_rewards}) do + defp previous_sign_transaction(tx = %__MODULE__{type: type}) + when type in [:node_rewards, :mint_rewards] do key_index = Crypto.number_of_network_pool_keys() previous_signature = @@ -308,6 +311,7 @@ defmodule Archethic.TransactionChain.Transaction do def serialize_type(:code_proposal), do: 7 def serialize_type(:code_approval), do: 8 def serialize_type(:node_rewards), do: 9 + def serialize_type(:mint_rewards), do: 10 # User transaction's type def serialize_type(:keychain), do: 255 @@ -331,6 +335,7 @@ defmodule Archethic.TransactionChain.Transaction do def parse_type(7), do: :code_proposal def parse_type(8), do: :code_approval def parse_type(9), do: :node_rewards + def parse_type(10), do: :mint_rewards # User transaction's type def parse_type(255), do: :keychain @@ -351,6 +356,7 @@ defmodule Archethic.TransactionChain.Transaction do def network_type?(:oracle), do: true def network_type?(:oracle_summary), do: true def network_type?(:node_rewards), do: true + def network_type?(:mint_rewards), do: true def network_type?(_), do: false @doc """ diff --git a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex index 6daa10376..a6d1255af 100644 --- a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex +++ b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex @@ -80,9 +80,10 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation @spec from_transaction(t(), Transaction.t()) :: t() def from_transaction(ops = %__MODULE__{}, %Transaction{ address: address, - type: :token, + type: type, data: %TransactionData{content: content} - }) do + }) + when type in [:token, :mint_rewards] do case Jason.decode(content) do {:ok, json} -> utxos = get_token_utxos(json, address) diff --git a/test/archethic/bootstrap/network_init_test.exs b/test/archethic/bootstrap/network_init_test.exs index e8d98633f..657e677fb 100644 --- a/test/archethic/bootstrap/network_init_test.exs +++ b/test/archethic/bootstrap/network_init_test.exs @@ -271,8 +271,6 @@ defmodule Archethic.Bootstrap.NetworkInitTest do |> elem(0) |> NetworkLookup.set_daily_nonce_public_key(DateTime.utc_now() |> DateTime.add(-10)) - NetworkLookup.set_network_pool_address(:crypto.strong_rand_bytes(32)) - assert :ok = NetworkInit.init_genesis_wallets() genesis_pools = Application.get_env(:archethic, NetworkInit)[:genesis_pools] @@ -280,9 +278,47 @@ defmodule Archethic.Bootstrap.NetworkInitTest do assert Enum.all?(genesis_pools, fn %{address: address, amount: amount} -> match?(%{uco: ^amount}, Account.get_balance(address)) end) + end + + test "init_network_reward_pool/1 should initialize genesis wallets" do + MockClient + |> stub(:send_message, fn + _, %GetTransaction{}, _ -> + {:ok, %NotFound{}} + + _, %GetTransactionChain{}, _ -> + {:ok, %TransactionList{transactions: [], more?: false, paging_state: nil}} + + _, %GetTransactionInputs{}, _ -> + {:ok, %TransactionInputList{inputs: []}} + + _, %GetLastTransactionAddress{address: address}, _ -> + {:ok, %LastTransactionAddress{address: address}} + end) + + network_pool_seed = :crypto.strong_rand_bytes(32) + + {_, pv} = Crypto.generate_deterministic_keypair(network_pool_seed) + + {pub, _} = Crypto.derive_keypair(network_pool_seed, 1) + + NetworkLookup.set_network_pool_address(Crypto.derive_address(pub)) + + MockCrypto + |> expect(:sign_with_network_pool_key, fn data, _ -> + Crypto.sign(data, pv) + end) + |> stub(:network_pool_public_key, fn index -> + {pub, _} = Crypto.derive_keypair(network_pool_seed, index) + pub + end) + + assert :ok = NetworkInit.init_network_reward_pool() + + network_address = SharedSecrets.get_network_pool_address() + key = {network_address, 0} - assert %{uco: 146_000_000_000_000_000} = - Account.get_balance(SharedSecrets.get_network_pool_address()) + assert %{token: %{^key => 3_400_000_000_000_000}} = Account.get_balance(network_address) end test "init_software_origin_shared_secrets_chain/1 should create first origin shared secret transaction" do diff --git a/test/archethic/crypto/keystore/shared_secrets/software_impl_test.exs b/test/archethic/crypto/keystore/shared_secrets/software_impl_test.exs index 6242d7e77..fbed8e761 100644 --- a/test/archethic/crypto/keystore/shared_secrets/software_impl_test.exs +++ b/test/archethic/crypto/keystore/shared_secrets/software_impl_test.exs @@ -29,6 +29,7 @@ defmodule Archethic.Crypto.SharedSecrets.SoftwareImplTest do |> stub(:count_transactions_by_type, fn :node_rewards -> 1 :node_shared_secrets -> 1 + :mint_rewards -> 1 end) |> expect(:list_addresses_by_type, fn :node_shared_secrets -> [:crypto.strong_rand_bytes(32)] @@ -52,7 +53,7 @@ defmodule Archethic.Crypto.SharedSecrets.SoftwareImplTest do unix_timestamp = DateTime.to_unix(timestamp) assert [{_, 1}] = :ets.lookup(:archethic_shared_secrets_keystore, :shared_secrets_index) - assert [{_, 1}] = :ets.lookup(:archethic_shared_secrets_keystore, :network_pool_index) + assert [{_, 2}] = :ets.lookup(:archethic_shared_secrets_keystore, :network_pool_index) assert [{^unix_timestamp, ^daily_nonce_keypair}] = :ets.tab2list(:archethic_shared_secrets_daily_keys)