Skip to content

Commit

Permalink
Merge branch 'develop' into 1217-stateful-utxo
Browse files Browse the repository at this point in the history
  • Loading branch information
bchamagne committed Oct 10, 2023
2 parents 0864d93 + 5802014 commit 926e68b
Show file tree
Hide file tree
Showing 26 changed files with 866 additions and 772 deletions.
2 changes: 1 addition & 1 deletion lib/archethic/bootstrap/network_init.ex
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ defmodule Archethic.Bootstrap.NetworkInit do

operations =
%LedgerOperations{
fee: Mining.get_transaction_fee(tx, 0.07, timestamp),
fee: Mining.get_transaction_fee(tx, nil, 0.07, timestamp),
transaction_movements: Transaction.get_movements(tx),
tokens_to_mint: LedgerOperations.get_utxos_from_transaction(tx, timestamp)
}
Expand Down
70 changes: 39 additions & 31 deletions lib/archethic/contracts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -84,37 +84,13 @@ defmodule Archethic.Contracts do
opts
) do
{:ok, nil, next_state, logs} ->
case State.to_utxo(next_state) do
{:ok, nil} ->
# empty state
%Contract.Result.Noop{
next_state_utxo: nil,
logs: logs
}

{:ok, ^maybe_state_utxo} ->
# output state == input state
%Contract.Result.Noop{
next_state_utxo: maybe_state_utxo,
logs: logs
}

{:ok, state_utxo} ->
# state changed, we "forward" the same transaction
%Contract.Result.Success{
logs: logs,
next_tx: generate_next_tx(contract_tx),
next_state_utxo: state_utxo
}

{:error, :state_too_big} ->
%Contract.Result.Error{
logs: [],
error: "Execution was successful but the state exceed the threshold",
stacktrace: [],
user_friendly_error: "Execution was successful but the state exceed the threshold"
}
end
# I hate you credo
execute_trigger_noop_response(
State.to_utxo(next_state),
logs,
maybe_state_utxo,
contract_tx
)

{:ok, next_tx, next_state, logs} ->
case State.to_utxo(next_state) do
Expand Down Expand Up @@ -363,4 +339,36 @@ defmodule Archethic.Contracts do
Exception.message(err)
end
end

defp execute_trigger_noop_response({:ok, nil}, logs, _input_utxo, _contract_tx) do
%Contract.Result.Noop{
next_state_utxo: nil,
logs: logs
}
end

defp execute_trigger_noop_response({:ok, output_utxo}, logs, input_utxo, _contract_tx)
when input_utxo == output_utxo do
%Contract.Result.Noop{
next_state_utxo: output_utxo,
logs: logs
}
end

defp execute_trigger_noop_response({:ok, state_utxo}, logs, _input_utxo, contract_tx) do
%Contract.Result.Success{
logs: logs,
next_tx: generate_next_tx(contract_tx),
next_state_utxo: state_utxo
}
end

defp execute_trigger_noop_response({:error, :state_too_big}, logs, _input_utxo, _contract_tx) do
%Contract.Result.Error{
logs: logs,
error: "Execution was successful but the state exceed the threshold",
stacktrace: [],
user_friendly_error: "Execution was successful but the state exceed the threshold"
}
end
end
12 changes: 11 additions & 1 deletion lib/archethic/contracts/interpreter/library/common/chain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.Chain do

@callback get_genesis_address(binary()) :: binary()
@callback get_first_transaction_address(binary()) :: binary() | nil
@callback get_last_address(binary()) :: binary()
@callback get_genesis_public_key(binary()) :: binary() | nil
@callback get_transaction(binary()) :: map()
@callback get_transaction(binary()) :: map() | nil
@callback get_last_transaction(binary()) :: map() | nil
@callback get_burn_address() :: binary()
@callback get_previous_address(binary() | map()) :: binary()
@callback get_balance(binary()) :: map()
Expand All @@ -29,6 +31,10 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.Chain do
binary_or_variable_or_function?(first)
end

def check_types(:get_last_address, [first]) do
binary_or_variable_or_function?(first)
end

def check_types(:get_genesis_public_key, [first]) do
binary_or_variable_or_function?(first)
end
Expand All @@ -37,6 +43,10 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.Chain do
binary_or_variable_or_function?(first)
end

def check_types(:get_last_transaction, [first]) do
binary_or_variable_or_function?(first)
end

def check_types(:get_burn_address, []), do: true

def check_types(:get_previous_address, [first]) do
Expand Down
30 changes: 29 additions & 1 deletion lib/archethic/contracts/interpreter/library/common/chain_impl.ex
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,34 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainImpl do
end
end

@tag [:io]
@impl Chain
def get_last_address(address) do
function = "Chain.get_last_address"

address
|> get_binary_address(function)
|> Archethic.get_last_transaction_address()
|> then(fn
{:ok, last_address} -> Base.encode16(last_address)
{:error, _} -> raise Library.Error, message: "Network issue in #{function}"
end)
end

@tag [:io]
@impl Chain
def get_last_transaction(address) do
function = "Chain.get_last_transaction"

address
|> get_binary_address(function)
|> Archethic.get_last_transaction()
|> then(fn
{:ok, tx} -> Constants.from_transaction(tx)
{:error, _} -> nil
end)
end

@tag [:io]
@impl Chain
def get_genesis_public_key(public_key) do
Expand Down Expand Up @@ -155,7 +183,7 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainImpl do
end

defp get_binary_address(address_hex, function) do
with {:ok, address} <- Base.decode16(address_hex),
with {:ok, address} <- Base.decode16(address_hex, case: :mixed),
true <- Crypto.valid_address?(address) do
address
else
Expand Down
2 changes: 1 addition & 1 deletion lib/archethic/contracts/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ defmodule Archethic.Contracts.Worker do
DateTime.utc_now()
|> OracleChain.get_uco_price()
|> Keyword.get(:usd)
|> Fee.minimum_fee()
|> Fee.base_fee()

case Account.get_balance(contract_address) do
%{uco: uco_balance} when uco_balance >= minimum_fees ->
Expand Down
11 changes: 10 additions & 1 deletion lib/archethic/mining.ex
Original file line number Diff line number Diff line change
Expand Up @@ -248,18 +248,27 @@ defmodule Archethic.Mining do
"""
@spec get_transaction_fee(
transaction :: Transaction.t(),
contract_context :: Contract.Context.t() | nil,
uco_price_in_usd :: float(),
timestamp :: DateTime.t(),
protocol_version :: pos_integer(),
maybe_state_utxo :: nil | UnspentOutput.t()
) :: non_neg_integer()
def get_transaction_fee(
tx,
contract_context,
uco_price_in_usd,
timestamp,
proto_version \\ protocol_version(),
maybe_state_utxo \\ nil
) do
Fee.calculate(tx, uco_price_in_usd, timestamp, proto_version, maybe_state_utxo)
Fee.calculate(
tx,
contract_context,
uco_price_in_usd,
timestamp,
proto_version,
maybe_state_utxo
)
end
end
34 changes: 24 additions & 10 deletions lib/archethic/mining/fee.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ defmodule Archethic.Mining.Fee do
"""
alias Archethic.Bootstrap

alias Archethic.Contracts.Contract

alias Archethic.Election

alias Archethic.P2P
Expand Down Expand Up @@ -31,9 +33,9 @@ defmodule Archethic.Mining.Fee do
@doc """
Return the minimum UCO amount to pay the minimum fee (equivalent of 1 cts)
"""
@spec minimum_fee(uco_price_in_usd :: float()) :: float()
def minimum_fee(uco_price_in_usd) do
0.01 / uco_price_in_usd
@spec base_fee(uco_price_in_usd :: float()) :: non_neg_integer()
def base_fee(uco_price_in_usd) do
trunc(minimum_fee(uco_price_in_usd) * @unit_uco)
end

@doc """
Expand All @@ -45,20 +47,28 @@ defmodule Archethic.Mining.Fee do
"""
@spec calculate(
transaction :: Transaction.t(),
contract_context :: Contract.Context.t() | nil,
uco_usd_price :: float(),
timestamp :: DateTime.t(),
protocol_version :: pos_integer(),
maybe_state_utxo :: nil | UnspentOutput.t()
) :: non_neg_integer()
def calculate(transaction, uco_usd_price, timestamp, protocol_version, maybe_state_utxo \\ nil)
def calculate(%Transaction{type: :keychain}, _, _, _, _), do: 0
def calculate(%Transaction{type: :keychain_access}, _, _, _, _), do: 0
def calculate(
transaction,
contract_context,
uco_usd_price,
timestamp,
protocol_version,
maybe_state_utxo \\ nil
)

def calculate(%Transaction{type: :keychain}, _, _, _, _, _), do: 0
def calculate(%Transaction{type: :keychain_access}, _, _, _, _, _), do: 0
def calculate(_, %Contract.Context{trigger: {:transaction, _, _}}, _, _, _, _), do: 0

def calculate(
tx = %Transaction{
address: address,
type: type
},
tx = %Transaction{address: address, type: type},
_contract_context,
uco_price_in_usd,
timestamp,
protocol_version,
Expand Down Expand Up @@ -95,6 +105,10 @@ defmodule Archethic.Mining.Fee do
end
end

defp minimum_fee(uco_price_in_usd) do
0.01 / uco_price_in_usd
end

defp get_additional_fee(
%Transaction{type: :token, data: %TransactionData{content: content}},
uco_price_in_usd
Expand Down
27 changes: 12 additions & 15 deletions lib/archethic/mining/smart_contract_validation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,27 @@ defmodule Archethic.Mining.SmartContractValidation do
This function requests storage nodes of the contract address to execute the transaction validation and return assertion about the execution
"""
@spec valid_contract_calls?(
@spec validate_contract_calls(
recipients :: list(Recipient.t()),
transaction :: Transaction.t(),
validation_time :: DateTime.t()
) :: boolean()
def valid_contract_calls?(
) :: {true, fee :: non_neg_integer()} | {false, 0}
def validate_contract_calls(
recipients,
transaction = %Transaction{},
validation_time = %DateTime{}
) do
TaskSupervisor
|> Task.Supervisor.async_stream_nolink(
recipients,
&request_contract_validation?(&1, transaction, validation_time),
&request_contract_validation(&1, transaction, validation_time),
timeout: 3_000,
ordered: false
)
|> Stream.filter(&match?({:ok, true}, &1))
|> Enum.count() == length(recipients)
|> Enum.reduce_while({true, 0}, fn
{:ok, {_valid? = true, fee}}, {true, total_fee} -> {:cont, {true, total_fee + fee}}
_, _ -> {:halt, {false, 0}}
end)
end

@doc """
Expand Down Expand Up @@ -185,7 +187,7 @@ defmodule Archethic.Mining.SmartContractValidation do
DateTime.diff(validation_datetime, datetime) < 10
end

defp request_contract_validation?(
defp request_contract_validation(
recipient = %Recipient{address: address},
transaction = %Transaction{},
validation_time
Expand All @@ -209,15 +211,10 @@ defmodule Archethic.Mining.SmartContractValidation do
inputs_before: validation_time
},
conflicts_resolver,
0,
# Only accept valid
& &1.valid?
0
) do
{:ok, _} ->
true

_ ->
false
{:ok, %SmartContractCallValidation{valid?: valid?, fee: fee}} -> {valid?, fee}
_ -> {false, 0}
end
end
end
Loading

0 comments on commit 926e68b

Please sign in to comment.