Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapt the fee for multi recipients transactions #1234

Merged
merged 1 commit into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions lib/archethic/mining.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ defmodule Archethic.Mining do

use Retry

@protocol_version 1
@protocol_version 2

def protocol_version, do: @protocol_version

Expand Down Expand Up @@ -245,6 +245,13 @@ defmodule Archethic.Mining do
@doc """
Get the transaction fee
"""
@spec get_transaction_fee(Transaction.t(), float(), DateTime.t()) :: non_neg_integer()
defdelegate get_transaction_fee(tx, uco_price_in_usd, timestamp), to: Fee, as: :calculate
@spec get_transaction_fee(
transaction :: Transaction.t(),
uco_price_in_usd :: float(),
timestamp :: DateTime.t(),
protocol_version :: pos_integer()
) :: non_neg_integer()
def get_transaction_fee(tx, uco_price_in_usd, timestamp, proto_version \\ protocol_version()) do
Fee.calculate(tx, uco_price_in_usd, timestamp, proto_version)
end
end
41 changes: 27 additions & 14 deletions lib/archethic/mining/fee.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,20 @@ defmodule Archethic.Mining.Fee do
@spec calculate(
transaction :: Transaction.t(),
uco_usd_price :: float(),
timestamp :: DateTime.t()
timestamp :: DateTime.t(),
protocol_version :: pos_integer()
) :: non_neg_integer()
def calculate(%Transaction{type: :keychain}, _, _), do: 0
def calculate(%Transaction{type: :keychain_access}, _, _), do: 0
def calculate(%Transaction{type: :keychain}, _, _, _), do: 0
def calculate(%Transaction{type: :keychain_access}, _, _, _), do: 0

def calculate(
tx = %Transaction{
address: address,
type: type
},
uco_price_in_usd,
timestamp
timestamp,
protocol_version
) do
cond do
address == Bootstrap.genesis_address() ->
Expand All @@ -71,7 +73,7 @@ defmodule Archethic.Mining.Fee do
nb_storage_nodes
)

replication_cost = cost_per_recipients(nb_recipients, uco_price_in_usd)
replication_cost = cost_per_recipients(nb_recipients, uco_price_in_usd, protocol_version)

fee =
minimum_fee(uco_price_in_usd) + storage_cost + replication_cost +
Expand Down Expand Up @@ -143,15 +145,6 @@ defmodule Archethic.Mining.Fee do
price_per_storage_node * nb_storage_nodes
end

# Send transaction to a single recipient does not include an additional cost
defp cost_per_recipients(1, _), do: 0

# Send transaction to multiple recipients (for bulk transfers) will generate an additional cost
# As more storage pools are required to send the transaction
defp cost_per_recipients(nb_recipients, uco_price_in_usd) do
nb_recipients * (0.1 / uco_price_in_usd)
end

defp get_token_recipients(%Transaction{
type: :token,
data: %TransactionData{content: content}
Expand All @@ -178,4 +171,24 @@ defmodule Archethic.Mining.Fee do

defp get_token_recipients_from_json(%{"recipients" => recipients}), do: recipients
defp get_token_recipients_from_json(_json), do: []

# Send transaction to a single recipient does not include an additional cost
defp cost_per_recipients(1, _, 1), do: 0

# Send transaction to multiple recipients (for bulk transfers) will generate an additional cost
# As more storage pools are required to send the transaction
defp cost_per_recipients(nb_recipients, uco_price_in_usd, 1) do
nb_recipients * (0.1 / uco_price_in_usd)
end

defp cost_per_recipients(nb_recipients, uco_price_in_usd, _protocol_version)
when nb_recipients > 0 do
base_fee = minimum_fee(uco_price_in_usd)
# To ensure with a simple tx, the price doesn't beyond $0.01
# We can assume the recipient cost to replicate transaction to be something about 1/3 of the load for a given transaction
# And we apply a logarithmic progression, as the cost of replication might be reduced by the overlap of storage node election
(:math.log10(nb_recipients) + 0.3) * base_fee
end

defp cost_per_recipients(_, _, _protocol_version), do: 0
end
36 changes: 23 additions & 13 deletions lib/archethic/mining/validation_context.ex
Original file line number Diff line number Diff line change
Expand Up @@ -726,13 +726,14 @@ defmodule Archethic.Mining.ValidationContext do
contract_context: contract_context
}
) do
{sufficient_funds?, ledger_operations} = get_ledger_operations(context)
protocol_version = Mining.protocol_version()
{sufficient_funds?, ledger_operations} = get_ledger_operations(context, protocol_version)

resolved_recipients = resolved_recipients(recipients, resolved_addresses)

validation_stamp =
%ValidationStamp{
protocol_version: Mining.protocol_version(),
protocol_version: protocol_version,
timestamp: validation_time,
proof_of_work: do_proof_of_work(tx),
proof_of_integrity: TransactionChain.proof_of_integrity([tx, prev_tx]),
Expand All @@ -755,12 +756,15 @@ defmodule Archethic.Mining.ValidationContext do
%{context | validation_stamp: validation_stamp}
end

defp get_ledger_operations(%__MODULE__{
transaction: tx,
unspent_outputs: unspent_outputs,
validation_time: validation_time,
resolved_addresses: resolved_addresses
}) do
defp get_ledger_operations(
%__MODULE__{
transaction: tx,
unspent_outputs: unspent_outputs,
validation_time: validation_time,
resolved_addresses: resolved_addresses
},
protocol_version
) do
previous_usd_price =
validation_time
|> OracleChain.get_last_scheduling_date()
Expand All @@ -777,7 +781,8 @@ defmodule Archethic.Mining.ValidationContext do
Fee.calculate(
tx,
previous_usd_price,
validation_time
validation_time,
protocol_version
)

resolved_movements =
Expand Down Expand Up @@ -1124,7 +1129,11 @@ defmodule Archethic.Mining.ValidationContext do
do: poe == Election.validation_nodes_election_seed_sorting(tx, timestamp)

defp valid_stamp_fee?(
%ValidationStamp{timestamp: timestamp, ledger_operations: %LedgerOperations{fee: fee}},
%ValidationStamp{
protocol_version: protocol_version,
timestamp: timestamp,
ledger_operations: %LedgerOperations{fee: fee}
},
%__MODULE__{transaction: tx}
) do
previous_usd_price =
Expand All @@ -1136,12 +1145,13 @@ defmodule Archethic.Mining.ValidationContext do
Fee.calculate(
tx,
previous_usd_price,
timestamp
timestamp,
protocol_version
) == fee
end

defp valid_stamp_error?(
stamp = %ValidationStamp{error: error},
stamp = %ValidationStamp{error: error, protocol_version: protocol_version},
context = %__MODULE__{
transaction: tx = %Transaction{data: %TransactionData{recipients: recipients}},
previous_transaction: prev_tx,
Expand All @@ -1154,7 +1164,7 @@ defmodule Archethic.Mining.ValidationContext do
validated_context = %{context | transaction: %{tx | validation_stamp: stamp}}

resolved_recipients = resolved_recipients(recipients, resolved_addresses)
{sufficient_funds?, _} = get_ledger_operations(validated_context)
{sufficient_funds?, _} = get_ledger_operations(validated_context, protocol_version)

expected_error =
get_validation_error(
Expand Down
2 changes: 1 addition & 1 deletion test/archethic/mining/distributed_workflow_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1274,7 +1274,7 @@ defmodule Archethic.Mining.DistributedWorkflowTest do
proof_of_election: Election.validation_nodes_election_seed_sorting(tx, DateTime.utc_now()),
ledger_operations:
%LedgerOperations{
fee: Fee.calculate(tx, 0.07, timestamp),
fee: Fee.calculate(tx, 0.07, timestamp, ArchethicCase.current_protocol_version()),
transaction_movements: Transaction.get_movements(tx),
tokens_to_mint: LedgerOperations.get_utxos_from_transaction(tx, timestamp)
}
Expand Down
Loading