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

SC: add get_calls/0 action #822

Merged
merged 12 commits into from
Jan 26, 2023
28 changes: 28 additions & 0 deletions lib/archethic/contracts/interpreter/action.ex
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ defmodule Archethic.Contracts.ActionInterpreter do
{node, {:ok, %{context | scope: {:function, function, parent_scope}}}}
end

# Whitelist the get_calls/0 (this will be expanded to a get_calls(contract.address) in postwalk)
defp prewalk(
node = {{:atom, "get_calls"}, _, []},
acc = {:ok, %{scope: {:actions, _}}}
) do
{node, acc}
end

# Whitelist the add_uco_transfer function parameters
defp prewalk(
node = {{:atom, "to"}, address},
Expand Down Expand Up @@ -316,6 +324,26 @@ defmodule Archethic.Contracts.ActionInterpreter do
end
end

# expand get_calls() to get_calls(contract.address)
defp postwalk(
{{:atom, "get_calls"}, meta, []},
acc
) do
node = {
{:atom, "get_calls"},
meta,
[
{:get_in, meta,
[
{:scope, meta, nil},
["contract", "address"]
]}
]
}

{node, acc}
end

defp postwalk(node, acc) do
InterpreterUtils.postwalk(node, acc)
end
Expand Down
46 changes: 26 additions & 20 deletions lib/archethic/contracts/interpreter/library.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ defmodule Archethic.Contracts.Interpreter.Library do
P2P,
P2P.Message.GetFirstPublicKey,
P2P.Message.FirstPublicKey,
TransactionChain
TransactionChain,
Contracts.ContractConstants,
Contracts.TransactionLookup,
Contracts.Interpreter.Utils
}

@doc """
Expand Down Expand Up @@ -138,7 +141,7 @@ defmodule Archethic.Contracts.Interpreter.Library do
:blake2b
end

:crypto.hash(algo, decode_binary(content))
:crypto.hash(algo, Utils.maybe_decode_hex(content))
|> Base.encode16()
end

Expand Down Expand Up @@ -177,16 +180,33 @@ defmodule Archethic.Contracts.Interpreter.Library do
2
"""
@spec size(binary() | list()) :: non_neg_integer()
def size(binary) when is_binary(binary), do: binary |> decode_binary() |> byte_size()
def size(binary) when is_binary(binary), do: binary |> Utils.maybe_decode_hex() |> byte_size()
def size(list) when is_list(list), do: length(list)
def size(map) when is_map(map), do: map_size(map)

@doc """
Get the inputs(type= :call) of the given transaction

This is useful for contracts that want to throttle their calls
"""
@spec get_calls(binary()) :: list(map())
def get_calls(contract_address) do
contract_address
|> Utils.maybe_decode_hex()
|> TransactionLookup.list_contract_transactions()
|> Enum.map(fn {address, _, _} ->
# TODO: parallelize
{:ok, tx} = TransactionChain.get_transaction(address, [], :io)
ContractConstants.from_transaction(tx)
end)
end

@doc """
Get the genesis public key
"""
@spec get_genesis_public_key(binary()) :: binary()
def get_genesis_public_key(address) do
bin_address = decode_binary(address)
bin_address = Utils.maybe_decode_hex(address)
nodes = Election.chain_storage_nodes(bin_address, P2P.authorized_and_available_nodes())
{:ok, key} = download_first_public_key(nodes, bin_address)
Base.encode16(key)
Expand All @@ -208,27 +228,13 @@ defmodule Archethic.Contracts.Interpreter.Library do
@spec timestamp() :: non_neg_integer()
def timestamp, do: DateTime.utc_now() |> DateTime.to_unix()

def decode_binary(bin) do
if String.printable?(bin) do
case Base.decode16(bin, case: :mixed) do
{:ok, hex} ->
hex

_ ->
bin
end
else
bin
end
end

@doc """
Get the genesis address of the chain
"""
@spec get_genesis_address(binary()) ::
binary()
def get_genesis_address(address) do
addr_bin = decode_binary(address)
addr_bin = Utils.maybe_decode_hex(address)
nodes = Election.chain_storage_nodes(address, P2P.authorized_and_available_nodes())

case TransactionChain.fetch_genesis_address_remotely(addr_bin, nodes) do
Expand All @@ -243,7 +249,7 @@ defmodule Archethic.Contracts.Interpreter.Library do
@spec get_first_transaction_address(address :: binary()) ::
binary()
def get_first_transaction_address(address) do
addr_bin = decode_binary(address)
addr_bin = Utils.maybe_decode_hex(address)
nodes = Election.chain_storage_nodes(address, P2P.authorized_and_available_nodes())

case TransactionChain.fetch_first_transaction_address_remotely(addr_bin, nodes) do
Expand Down
32 changes: 17 additions & 15 deletions lib/archethic/contracts/interpreter/transaction_statements.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ defmodule Archethic.Contracts.Interpreter.TransactionStatements do
alias Archethic.TransactionChain.TransactionData.Ownership
alias Archethic.TransactionChain.TransactionData.UCOLedger.Transfer, as: UCOTransfer

alias Archethic.Contracts.Interpreter.Utils

@doc """
Set the transaction type

Expand Down Expand Up @@ -47,7 +49,7 @@ defmodule Archethic.Contracts.Interpreter.TransactionStatements do
update_in(
tx,
[Access.key(:data), Access.key(:ledger), Access.key(:uco), Access.key(:transfers)],
&[%UCOTransfer{to: decode_binary(to), amount: amount} | &1]
&[%UCOTransfer{to: Utils.maybe_decode_hex(to), amount: amount} | &1]
)
end

Expand Down Expand Up @@ -92,9 +94,9 @@ defmodule Archethic.Contracts.Interpreter.TransactionStatements do
&[
%TokenTransfer{
token_id: Map.get(map_args, "token_id", 0),
to: decode_binary(to),
to: Utils.maybe_decode_hex(to),
amount: amount,
token_address: decode_binary(token_address)
token_address: Utils.maybe_decode_hex(token_address)
}
| &1
]
Expand All @@ -118,6 +120,14 @@ defmodule Archethic.Contracts.Interpreter.TransactionStatements do
put_in(tx, [Access.key(:data), Access.key(:content)], content)
end

def set_content(tx = %Transaction{}, content) when is_integer(content) do
put_in(tx, [Access.key(:data), Access.key(:content)], Integer.to_string(content))
end

def set_content(tx = %Transaction{}, content) when is_float(content) do
put_in(tx, [Access.key(:data), Access.key(:content)], Float.to_string(content))
end

@doc """
Set transaction smart contract code

Expand Down Expand Up @@ -165,9 +175,9 @@ defmodule Archethic.Contracts.Interpreter.TransactionStatements do

ownership =
Ownership.new(
decode_binary(secret),
decode_binary(secret_key),
Enum.map(authorized_public_keys, &decode_binary(&1))
Utils.maybe_decode_hex(secret),
Utils.maybe_decode_hex(secret_key),
Enum.map(authorized_public_keys, &Utils.maybe_decode_hex(&1))
)

update_in(
Expand Down Expand Up @@ -196,15 +206,7 @@ defmodule Archethic.Contracts.Interpreter.TransactionStatements do
update_in(
tx,
[Access.key(:data), Access.key(:recipients)],
&[decode_binary(recipient_address) | &1]
&[Utils.maybe_decode_hex(recipient_address) | &1]
)
end

defp decode_binary(bin) do
if String.match?(bin, ~r/^[[:xdigit:]]+$/) do
Base.decode16!(bin, case: :mixed)
else
bin
end
end
end
16 changes: 16 additions & 0 deletions lib/archethic/contracts/interpreter/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ defmodule Archethic.Contracts.Interpreter.Utils do

def postwalk(node, acc) when is_binary(node) do
if String.printable?(node) do
# uniform hexadecimal to all uppercase
case Base.decode16(node, case: :mixed) do
{:ok, hex} ->
{Base.encode16(hex), acc}
Expand Down Expand Up @@ -377,6 +378,21 @@ defmodule Archethic.Contracts.Interpreter.Utils do
ast
end

@doc """
Decode an hexadecimal binary or no-op
"""
@spec maybe_decode_hex(binary()) :: binary()
def maybe_decode_hex(bin) do
if String.match?(bin, ~r/^[[:xdigit:]]+$/) do
case Base.decode16(bin, case: :mixed) do
{:ok, bin} -> bin
:error -> bin
end
else
bin
end
end

defp do_postwalk_execution({:=, metadata, [var_name, content]}, acc) do
put_ast =
{{:., metadata, [{:__aliases__, metadata, [:Map]}, :put]}, metadata,
Expand Down
5 changes: 2 additions & 3 deletions lib/archethic/contracts/loader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,15 @@ defmodule Archethic.Contracts.Loader do
transaction_type: tx_type
)

# execute asynchronously the contract
Worker.execute(contract_address, tx)

TransactionLookup.add_contract_transaction(
contract_address,
tx_address,
tx_timestamp,
protocol_version
)

Worker.execute(contract_address, tx)

Logger.info("Transaction towards contract ingested",
transaction_address: Base.encode16(tx_address),
transaction_type: tx_type
Expand Down
4 changes: 4 additions & 0 deletions lib/archethic/db.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ defmodule Archethic.DB do

@type storage_type() :: :chain | :io

@callback get_transaction(address :: binary()) ::
{:ok, Transaction.t()} | {:error, :transaction_not_exists}
@callback get_transaction(address :: binary(), fields :: list()) ::
{:ok, Transaction.t()} | {:error, :transaction_not_exists}
@callback get_transaction(address :: binary(), fields :: list(), storage_type :: storage_type()) ::
{:ok, Transaction.t()} | {:error, :transaction_not_exists}
@callback get_beacon_summary(summary_address :: binary()) ::
{:ok, Summary.t()} | {:error, :summary_not_exists}
@callback get_beacon_summaries_aggregate(DateTime.t()) ::
Expand Down
18 changes: 15 additions & 3 deletions lib/archethic/db/embedded_impl.ex
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,23 @@ defmodule Archethic.DB.EmbeddedImpl do

@doc """
Get a transaction at the given address
Read from storage first and maybe read from IO storage if flag is passed
"""
@spec get_transaction(address :: binary(), fields :: list()) ::
@spec get_transaction(address :: binary(), fields :: list(), storage_type :: DB.storage_type()) ::
{:ok, Transaction.t()} | {:error, :transaction_not_exists}
def get_transaction(address, fields \\ []) when is_binary(address) and is_list(fields) do
ChainReader.get_transaction(address, fields, db_path())
def get_transaction(address, fields \\ [], storage_type \\ :chain)
when is_binary(address) and is_list(fields) do
case ChainReader.get_transaction(address, fields, db_path()) do
{:ok, transaction} ->
{:ok, transaction}

{:error, :transaction_not_exists} ->
if storage_type == :io do
ChainReader.get_io_transaction(address, fields, db_path())
else
{:error, :transaction_not_exists}
end
end
end

@doc """
Expand Down
15 changes: 15 additions & 0 deletions lib/archethic/db/embedded_impl/chain_reader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,21 @@ defmodule Archethic.DB.EmbeddedImpl.ChainReader do
end
end

@doc """
Read a transaction from io storage
"""
@spec get_io_transaction(binary(), fields :: list(), db_path :: String.t()) ::
{:ok, Transaction.t()} | {:error, :transaction_not_exists}
def get_io_transaction(address, fields, db_path) do
filepath = ChainWriter.io_path(db_path, address)

if File.exists?(filepath) do
{:ok, read_io_transaction(filepath, fields)}
else
{:error, :transaction_not_exists}
end
end

@doc """
List all the transactions in io storage
"""
Expand Down
4 changes: 2 additions & 2 deletions lib/archethic/transaction_chain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,11 @@ defmodule Archethic.TransactionChain do
{:ok, Transaction.t()}
| {:error, :transaction_not_exists}
| {:error, :invalid_transaction}
def get_transaction(address, fields \\ []) when is_list(fields) do
def get_transaction(address, fields \\ [], storage_type \\ :chain) when is_list(fields) do
if KOLedger.has_transaction?(address) do
{:error, :invalid_transaction}
else
DB.get_transaction(address, fields)
DB.get_transaction(address, fields, storage_type)
end
end

Expand Down
2 changes: 1 addition & 1 deletion test/archethic/bootstrap_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ defmodule Archethic.BootstrapTest do
^addr2, _ -> true
^addr1, _ -> true
end)
|> expect(:get_transaction, fn ^addr3, _ ->
|> expect(:get_transaction, fn ^addr3, _, _ ->
{:error, :transaction_not_exists}
end)
|> stub(:write_transaction, fn tx, _ ->
Expand Down
Loading