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

Add fn in SC lib to get genesis address in smart contract #268

Merged
11 commits merged into from
Apr 29, 2022
17 changes: 17 additions & 0 deletions lib/archethic/contracts/interpreter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,15 @@ defmodule ArchEthic.Contracts.Interpreter do
when scope != :root,
do: {node, acc}

# Whitelist the get_genesis_address/1 function
defp prewalk(
This conversation was marked as resolved.
Show resolved Hide resolved
node = {{:atom, "get_genesis_address"}, _, [_address]},
acc = {:ok, %{scope: scope}}
)
when scope != :root do
{node, acc}
end

# Whitelist the regex_match?/1 function in the condition
defp prewalk(
node = {{:atom, "regex_match?"}, _, [_search]},
Expand Down Expand Up @@ -631,6 +640,14 @@ defmodule ArchEthic.Contracts.Interpreter do
defp prewalk(node = {{:atom, "size"}, _, []}, acc = {:ok, %{scope: :condition}}),
do: {node, acc}

# Whitelist the get_genesis_address/0 function in condition
defp prewalk(
node = {{:atom, "get_genesis_address"}, _, []},
acc = {:ok, %{scope: :condition}}
) do
{node, acc}
end

# Whitelist the used of functions in the actions
defp prewalk(node = {{:atom, fun_name}, _, _}, {:ok, acc = %{scope: :actions}})
when fun_name in @transaction_statements_functions_names,
Expand Down
29 changes: 27 additions & 2 deletions lib/archethic/contracts/interpreter/library.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ defmodule ArchEthic.Contracts.Interpreter.Library do
@moduledoc false

alias ArchEthic.Crypto
alias ArchEthic.P2P
alias ArchEthic.P2P.Message.GetFirstAddress
alias ArchEthic.P2P.Message.FirstAddress
alias ArchEthic.Election

@doc """
Match a regex expression
Expand Down Expand Up @@ -79,12 +83,12 @@ defmodule ArchEthic.Contracts.Interpreter.Library do
end

@doc ~S"""
Match a json path expression
Match a json path expression

## Examples

iex> Library.json_path_match?("{\"1622541930\":{\"uco\":{\"eur\":0.176922,\"usd\":0.21642}}}", "$.*.uco.usd")
true
true
"""
@spec json_path_match?(binary(), binary()) :: boolean()
def json_path_match?(text, path) when is_binary(text) and is_binary(path) do
Expand Down Expand Up @@ -149,4 +153,25 @@ defmodule ArchEthic.Contracts.Interpreter.Library do
def size(binary) when is_binary(binary), do: byte_size(binary)
def size(list) when is_list(list), do: length(list)
def size(map) when is_map(map), do: map_size(map)

@doc """
Get the genesis address of the chain

"""
@spec get_genesis_address(binary()) ::
binary()
def get_genesis_address(address) do
nodes = Election.chain_storage_nodes(address, P2P.available_nodes())
{:ok, address} = download_first_address(nodes, address)
address
end

defp download_first_address([node | rest], address) do
case P2P.send_message(node, %GetFirstAddress{address: address}) do
{:ok, %FirstAddress{address: address}} -> {:ok, address}
{:error, _} -> download_first_address(rest, address)
end
end

defp download_first_address([], _address), do: {:error, :network_issue}
end
32 changes: 32 additions & 0 deletions lib/archethic/p2p/message.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ defmodule ArchEthic.P2P.Message do
alias __MODULE__.EncryptedStorageNonce
alias __MODULE__.Error
alias __MODULE__.FirstPublicKey
alias __MODULE__.FirstAddress
alias __MODULE__.GetFirstAddress
alias __MODULE__.GetBalance
alias __MODULE__.GetBeaconSummaries
alias __MODULE__.GetBeaconSummary
Expand Down Expand Up @@ -114,6 +116,7 @@ defmodule ArchEthic.P2P.Message do
| BeaconUpdate.t()
| TransactionSummary.t()
| ReplicationAttestation.t()
| GetFirstAddress.t()

@type response ::
Ok.t()
Expand All @@ -135,6 +138,7 @@ defmodule ArchEthic.P2P.Message do
| Error.t()
| Summary.t()
| BeaconSummaryList.t()
| FirstAddress.t()

@doc """
Extract the Message Struct name
Expand Down Expand Up @@ -332,6 +336,14 @@ defmodule ArchEthic.P2P.Message do
<<30::8, ReplicationAttestation.serialize(attestation)::binary>>
end

def encode(%GetFirstAddress{address: address}) do
<<31::8, address::binary>>
end

def encode(%FirstAddress{address: address}) do
<<235::8, address::binary>>
end

def encode(%BeaconUpdate{transaction_attestations: transaction_attestations}) do
transaction_attestations_bin =
transaction_attestations
Expand Down Expand Up @@ -740,6 +752,16 @@ defmodule ArchEthic.P2P.Message do
ReplicationAttestation.deserialize(rest)
end

def decode(<<31::8, rest::bitstring>>) do
{address, rest} = Utils.deserialize_address(rest)
{%GetFirstAddress{address: address}, rest}
end

def decode(<<235::8, rest::bitstring>>) do
{address, rest} = Utils.deserialize_address(rest)
{%FirstAddress{address: address}, rest}
end

def decode(<<236::8, nb_transaction_attestations::16, rest::bitstring>>) do
{transaction_attestations, rest} =
Utils.deserialize_transaction_attestations(rest, nb_transaction_attestations, [])
Expand Down Expand Up @@ -1176,6 +1198,16 @@ defmodule ArchEthic.P2P.Message do
end
end

def process(%GetFirstAddress{address: address}) do
case TransactionChain.get_first_transaction(address, [:address]) do
{:ok, %Transaction{address: address}} ->
%FirstAddress{address: address}

{:error, :transaction_not_exists} ->
%NotFound{}
end
end

def process(%GetLastTransactionAddress{address: address, timestamp: timestamp}) do
address = TransactionChain.get_last_address(address, timestamp)
%LastTransactionAddress{address: address}
Expand Down
11 changes: 11 additions & 0 deletions lib/archethic/p2p/message/first_address.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule ArchEthic.P2P.Message.FirstAddress do
@moduledoc """
Represents a message to first address from the transaction chain
"""
@enforce_keys [:address]
defstruct [:address]

@type t :: %__MODULE__{
address: binary()
}
end
12 changes: 12 additions & 0 deletions lib/archethic/p2p/message/get_first_address.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
defmodule ArchEthic.P2P.Message.GetFirstAddress do
@moduledoc """
Represents a message to request the first address from a transaction chain
"""

@enforce_keys [:address]
defstruct [:address]

@type t() :: %__MODULE__{
address: binary()
}
end
2 changes: 1 addition & 1 deletion test/archethic/contracts/interpreter/library_test.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule ArchEthic.Contracts.Interpreter.LibraryTest do
use ExUnit.Case
use ArchEthicCase

alias ArchEthic.Contracts.Interpreter.Library

Expand Down
83 changes: 78 additions & 5 deletions test/archethic/contracts/interpreter_test.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule ArchEthic.Contracts.InterpreterTest do
use ExUnit.Case
use ArchEthicCase

alias ArchEthic.Contracts.Contract
alias ArchEthic.Contracts.Contract.Conditions
Expand All @@ -9,14 +9,16 @@ defmodule ArchEthic.Contracts.InterpreterTest do
alias ArchEthic.Contracts.Interpreter

alias ArchEthic.Crypto

alias ArchEthic.P2P
alias ArchEthic.P2P.Node
alias ArchEthic.P2P.Message.FirstAddress
alias ArchEthic.TransactionChain.Transaction
alias ArchEthic.TransactionChain.TransactionData

alias ArchEthic.TransactionChain.TransactionData.Ledger
alias ArchEthic.TransactionChain.TransactionData.UCOLedger
alias ArchEthic.TransactionChain.TransactionData.UCOLedger.Transfer, as: UCOTransfer

import Mox
doctest Interpreter

describe "parse/1" do
Expand Down Expand Up @@ -273,7 +275,7 @@ defmodule ArchEthic.Contracts.InterpreterTest do
add_uco_transfer to: \"7F6661ACE282F947ACA2EF947D01BDDC90C65F09EE828BDADE2E3ED4258470B3\", amount: 1040000000
add_nft_transfer to: \"30670455713E2CBECF94591226A903651ED8625635181DDA236FECC221D1E7E4\", amount: 20000000000, nft: \"AEB4A6F5AB6D82BE223C5867EBA5FE616F52F410DCF83B45AFF158DD40AE8AC3\"
set_content \"Receipt\"
add_ownership secret: \"MyEncryptedSecret\", secret_key: \"MySecretKey\", authorized_public_keys: ["70C245E5D970B59DF65638BDD5D963EE22E6D892EA224D8809D0FB75D0B1907A"]
add_ownership secret: \"MyEncryptedSecret\", secret_key: \"MySecretKey\", authorized_public_keys: ["70C245E5D970B59DF65638BDD5D963EE22E6D892EA224D8809D0FB75D0B1907A"]
add_recipient \"78273C5CBCEB8617F54380CC2F173DF2404DB676C9F10D546B6F395E6F3BDDEE\"
end
"""
Expand Down Expand Up @@ -522,7 +524,7 @@ defmodule ArchEthic.Contracts.InterpreterTest do
condition inherit: [
type: transfer,
uco_transfers: size() == 1
# TODO: to provide more security, we should check the destination address is within the previous transaction inputs
# TODO: to provide more security, we should check the destination address is within the previous transaction inputs
]


Expand All @@ -542,4 +544,75 @@ defmodule ArchEthic.Contracts.InterpreterTest do
"""
|> Interpreter.parse()
end

describe "get_genesis_address/1" do
setup do
key = <<0::16, :crypto.strong_rand_bytes(32)::binary>>

P2P.add_and_connect_node(%Node{
ip: {127, 0, 0, 1},
port: 3000,
first_public_key: key,
last_public_key: key,
available?: true,
geo_patch: "AAA",
network_patch: "AAA",
authorized?: true,
authorization_date: DateTime.utc_now()
})

{:ok, [key: key]}
end

test "shall get the first address of the chain in the conditions" do
address = "64F05F5236088FC64D1BB19BD13BC548F1C49A42432AF02AD9024D8A2990B2B4"
b_address = Base.decode16!(address)

MockClient
|> expect(:send_message, fn _, _, _ ->
{:ok, %FirstAddress{address: b_address}}
end)

{:ok, %Contract{conditions: %{transaction: conditions}}} =
~s"""
condition transaction: [
address: get_genesis_address() == "64F05F5236088FC64D1BB19BD13BC548F1C49A42432AF02AD9024D8A2990B2B4"
]
"""
|> Interpreter.parse()

assert true =
Interpreter.valid_conditions?(
conditions,
%{"transaction" => %{"address" => :crypto.strong_rand_bytes(32)}}
)
end

@tag :genesis
test "shall parse get_genesis_address/1 in actions" do
address = "64F05F5236088FC64D1BB19BD13BC548F1C49A42432AF02AD9024D8A2990B2B4"
b_address = Base.decode16!(address)

MockClient
|> expect(:send_message, fn _, _, _ ->
{:ok, %FirstAddress{address: b_address}}
end)

{:ok, contract} =
~s"""
actions triggered_by: transaction do
address = get_genesis_address "64F05F5236088FC64D1BB19BD13BC548F1C49A42432AF02AD9024D8A2990B2B4"
if address == "64F05F5236088FC64D1BB19BD13BC548F1C49A42432AF02AD9024D8A2990B2B4" do
set_content "yes"
else
set_content "no"
end
end
"""
|> Interpreter.parse()

assert %Transaction{data: %TransactionData{content: "yes"}} =
Interpreter.execute_actions(contract, :transaction)
end
end
end