Skip to content

Commit

Permalink
SC: add get_calls/0 action (#822)
Browse files Browse the repository at this point in the history
* smart contract new action: get_inputs/1

* get_inputs handle both address formats

* add the tx's content to the inputs

* transform get_inputs/1 to get_calls/1

* move a function to utils

* write the transaction call in DB before executing the worker

* quick refactor get_calls after feedback

* wip

* SC: add get_calls/0

* un-whitelist get_calls/1

* Fix conflict

* refactor: get_io_transaction return a result

Co-authored-by: Neylix <julien.leclerc05@protonmail.com>
  • Loading branch information
bchamagne and Neylix committed Jan 26, 2023
1 parent a6bce72 commit 5441172
Show file tree
Hide file tree
Showing 20 changed files with 231 additions and 59 deletions.
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

0 comments on commit 5441172

Please sign in to comment.