Skip to content

Commit

Permalink
Add get genesis address function smart contract library (#268)
Browse files Browse the repository at this point in the history
Co-authored-by: Samuel <samuel@uniris.io>
  • Loading branch information
imnik11 and Samuel committed May 6, 2022
1 parent 64ddc62 commit c0aa6f5
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 8 deletions.
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(
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 @@ -349,6 +353,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 @@ -788,6 +800,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 @@ -1233,6 +1255,16 @@ defmodule ArchEthic.P2P.Message do
}
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

0 comments on commit c0aa6f5

Please sign in to comment.