From 066925e32bc49f132508c00c5f86af8b14bd859f Mon Sep 17 00:00:00 2001 From: Mihail Dobrev Date: Tue, 2 Apr 2024 16:03:01 +0300 Subject: [PATCH] fix: resolve name to contract address up to a given block hash --- lib/ae_mdw/contract.ex | 26 ++++--- lib/ae_mdw/db/format.ex | 14 ++-- .../db/mutations/contract_call_mutation.ex | 3 +- .../db/mutations/write_fields_mutation.ex | 16 +++- lib/ae_mdw/db/name.ex | 26 +++++-- lib/ae_mdw/db/sync/transaction.ex | 10 ++- test/ae_mdw/contract_test.exs | 77 +++++++++++++------ .../ae_mdw/db/contract_call_mutation_test.exs | 53 +++++-------- .../mutations/write_fields_mutation_test.exs | 4 +- test/ae_mdw/db/sync/transaction_test.exs | 2 +- .../controllers/tx_controller_test.exs | 12 +-- 11 files changed, 144 insertions(+), 99 deletions(-) diff --git a/lib/ae_mdw/contract.ex b/lib/ae_mdw/contract.ex index d25b23ae9..c51a07d72 100644 --- a/lib/ae_mdw/contract.ex +++ b/lib/ae_mdw/contract.ex @@ -3,6 +3,8 @@ defmodule AeMdw.Contract do AE smart contracts type (signatures) and previous calls information based on direct chain info. """ + alias AeMdw.Blocks + alias AeMdw.Db.Name alias AeMdw.EtsCache alias AeMdw.Log alias AeMdw.Node @@ -215,13 +217,14 @@ defmodule AeMdw.Contract do |> call_rec_from_id(contract_pk, block_hash) end - @spec call_tx_info(tx(), DBN.pubkey(), block_hash()) :: {fun_arg_res_or_error(), call()} - def call_tx_info(tx_rec, contract_pk, block_hash) do + @spec call_tx_info(tx(), DBN.pubkey(), DBN.pubkey(), block_hash()) :: + {fun_arg_res_or_error(), call()} + def call_tx_info(tx_rec, contract_pk, contract_or_name_pk, block_hash) do {:ok, {type_info, _compiler_vsn, _source_hash}} = get_info(contract_pk) call_id = :aect_call_tx.call_id(tx_rec) call_data = :aect_call_tx.call_data(tx_rec) - case :aec_chain.get_contract_call(contract_pk, call_id, block_hash) do + case :aec_chain.get_contract_call(contract_or_name_pk, call_id, block_hash) do {:ok, call} -> case :aect_call.return_type(call) do :ok -> @@ -244,7 +247,7 @@ defmodule AeMdw.Contract do {:error, reason} -> Log.error( - "call_tx_info error reason=#{inspect(reason)} params=#{inspect([contract_pk, call_id, block_hash])}" + "call_tx_info error reason=#{inspect(reason)} params=#{inspect([contract_or_name_pk, call_id, block_hash])}" ) {{:error, reason}, nil} @@ -348,19 +351,20 @@ defmodule AeMdw.Contract do Enum.group_by(get_events(micro_block), fn {_event_name, %{tx_hash: tx_hash}} -> tx_hash end) end - @spec maybe_resolve_contract_pk(name_pubkey() | contract_pubkey()) :: contract_pubkey() - def maybe_resolve_contract_pk(contract_or_name_pk) do + @spec maybe_resolve_contract_pk(name_pubkey() | contract_pubkey(), Blocks.block_hash()) :: + contract_pubkey() + def maybe_resolve_contract_pk(contract_or_name_pk, block_hash) do contract_or_name_pk |> case do <<217, 52, 92, _rest::binary>> = name_pk -> - "contract_pubkey" - |> :aec_chain.resolve_namehash(name_pk) + block_hash + |> Name.ptr_resolve(name_pk, "contract_pubkey") |> case do - {:ok, {:id, :contract, pubkey}} -> - pubkey + {:ok, contract_pk} -> + contract_pk {:error, reason} -> - raise "#{__MODULE__}.maybe_resolve_contract_pk: failed to resolve contract pubkey with reason: #{inspect(reason)}" + raise "Contract not resolved: #{inspect(contract_or_name_pk)} with reason #{inspect(reason)}" end contract_pk -> diff --git a/lib/ae_mdw/db/format.ex b/lib/ae_mdw/db/format.ex index f3aa71ab5..b936896c8 100644 --- a/lib/ae_mdw/db/format.ex +++ b/lib/ae_mdw/db/format.ex @@ -125,15 +125,16 @@ defmodule AeMdw.Db.Format do end defp custom_raw_data(state, :contract_call_tx, tx, tx_rec, signed_tx, block_hash) do - contract_pk = + contract_or_name_pk = tx_rec |> :aect_call_tx.contract_id() |> Db.id_pubkey() - |> Contract.maybe_resolve_contract_pk() + + contract_pk = Contract.maybe_resolve_contract_pk(contract_or_name_pk, block_hash) txi = tx.tx_index fun_arg_res = AeMdw.Db.Contract.call_fun_arg_res(state, contract_pk, txi) - call_info = format_call_info(signed_tx, contract_pk, block_hash, txi) + call_info = format_call_info(signed_tx, contract_or_name_pk, block_hash, txi) call_details = Map.merge(call_info, fun_arg_res) update_in(tx, [:tx], &Map.merge(&1, call_details)) end @@ -411,11 +412,12 @@ defmodule AeMdw.Db.Format do end defp custom_encode(state, :contract_call_tx, tx, tx_rec, signed_tx, txi, block_hash) do - contract_pk = + contract_or_name_pk = tx_rec |> :aect_call_tx.contract_id() |> Db.id_pubkey() - |> Contract.maybe_resolve_contract_pk() + + contract_pk = Contract.maybe_resolve_contract_pk(contract_or_name_pk, block_hash) fun_arg_res = state @@ -424,7 +426,7 @@ defmodule AeMdw.Db.Format do call_details = signed_tx - |> format_call_info(contract_pk, block_hash, txi) + |> format_call_info(contract_or_name_pk, block_hash, txi) |> Map.merge(fun_arg_res) aexn_type = DbContract.get_aexn_type(state, contract_pk) diff --git a/lib/ae_mdw/db/mutations/contract_call_mutation.ex b/lib/ae_mdw/db/mutations/contract_call_mutation.ex index 7048c1735..efe07d154 100644 --- a/lib/ae_mdw/db/mutations/contract_call_mutation.ex +++ b/lib/ae_mdw/db/mutations/contract_call_mutation.ex @@ -44,14 +44,13 @@ defmodule AeMdw.Db.ContractCallMutation do @spec execute(t(), State.t()) :: State.t() def execute( %__MODULE__{ - contract_pk: contract_or_name_pk, + contract_pk: contract_pk, txi: txi, fun_arg_res: fun_arg_res, call_rec: call_rec }, state ) do - contract_pk = Contract.maybe_resolve_contract_pk(contract_or_name_pk) create_txi = Origin.tx_index!(state, {:contract, contract_pk}) state = DBContract.call_write(state, create_txi, txi, fun_arg_res) diff --git a/lib/ae_mdw/db/mutations/write_fields_mutation.ex b/lib/ae_mdw/db/mutations/write_fields_mutation.ex index f59c293f0..6692c2ee1 100644 --- a/lib/ae_mdw/db/mutations/write_fields_mutation.ex +++ b/lib/ae_mdw/db/mutations/write_fields_mutation.ex @@ -70,14 +70,22 @@ defmodule AeMdw.Db.WriteFieldsMutation do end) end - defp resolve_pubkey(state, id, :spend_tx, :recipient_id, block_index) do + defp resolve_pubkey(state, id, type, field, block_index) + when type == :spend_tx and field == :recipient_id + when type == :contract_call_tx and field == :contract_id do + pointee = + case type do + :spend_tx -> "account_pubkey" + :contract_call_tx -> "contract_pubkey" + end + with {:name, name_hash} <- :aeser_id.specialize(id), - {:ok, account_pk} <- Name.ptr_resolve(state, block_index, name_hash) do - account_pk + {:ok, contract_pk} <- Name.ptr_resolve(state, block_index, name_hash, pointee) do + contract_pk else {:error, :name_revoked} -> Log.warn( - "Revoked name used on spend! id: #{inspect(id)}, block_index: #{inspect(block_index)}" + "Revoked name used on contract call! id: #{inspect(id)}, block_index: #{inspect(block_index)}" ) Name.last_update_pointee_pubkey(state, id) diff --git a/lib/ae_mdw/db/name.ex b/lib/ae_mdw/db/name.ex index 262885d0e..1809c3224 100644 --- a/lib/ae_mdw/db/name.ex +++ b/lib/ae_mdw/db/name.ex @@ -55,12 +55,23 @@ defmodule AeMdw.Db.Name do plain_name end - @spec ptr_resolve(state(), Blocks.block_index(), binary()) :: + @spec ptr_resolve(state(), Blocks.block_index(), binary(), binary()) :: {:ok, binary()} | {:error, :name_revoked} - def ptr_resolve(state, block_index, name_hash) do - with {:ok, account_id} <- - :aens.resolve_hash("account_pubkey", name_hash, ns_tree!(state, block_index)) do - {:ok, Validate.id(account_id)} + def ptr_resolve(state, block_index, name_hash, pointer_key) do + with {:ok, id} <- + :aens.resolve_hash(pointer_key, name_hash, ns_tree!(state, block_index)), + {:ok, valid_id} <- Validate.id(id) do + {:ok, valid_id} + end + end + + @spec ptr_resolve(Blocks.block_hash(), binary(), binary()) :: + {:ok, binary()} | {:error, :name_revoked} + def ptr_resolve(block_hash, name_hash, pointer_key) do + with {:ok, id} <- + :aens.resolve_hash(pointer_key, name_hash, ns_tree!(block_hash)), + {:ok, valid_id} <- Validate.id(id) do + {:ok, valid_id} end end @@ -310,6 +321,11 @@ defmodule AeMdw.Db.Name do state |> State.fetch!(Model.Block, block_index) |> Model.block(:hash) + |> ns_tree!() + end + + defp ns_tree!(block_hash) do + block_hash |> :aec_db.get_block_state() |> :aec_trees.ns() end diff --git a/lib/ae_mdw/db/sync/transaction.ex b/lib/ae_mdw/db/sync/transaction.ex index e3b1eb92f..f62122a56 100644 --- a/lib/ae_mdw/db/sync/transaction.ex +++ b/lib/ae_mdw/db/sync/transaction.ex @@ -120,7 +120,6 @@ defmodule AeMdw.Db.Sync.Transaction do contract_pk = tx |> :aect_create_tx.contract_pubkey() - |> Contract.maybe_resolve_contract_pk() mutations = Origin.origin_mutations(:contract_create_tx, nil, contract_pk, txi, tx_hash) @@ -175,9 +174,14 @@ defmodule AeMdw.Db.Sync.Transaction do |> :aect_call_tx.contract_id() |> Db.id_pubkey() - contract_pk = Contract.maybe_resolve_contract_pk(contract_or_name_pk) + contract_pk = + Contract.maybe_resolve_contract_pk( + contract_or_name_pk, + block_hash + ) - {fun_arg_res, call_rec} = Contract.call_tx_info(tx, contract_pk, block_hash) + {fun_arg_res, call_rec} = + Contract.call_tx_info(tx, contract_pk, contract_or_name_pk, block_hash) events_mutations = SyncContract.events_mutations( diff --git a/test/ae_mdw/contract_test.exs b/test/ae_mdw/contract_test.exs index daedb8276..601014350 100644 --- a/test/ae_mdw/contract_test.exs +++ b/test/ae_mdw/contract_test.exs @@ -88,12 +88,13 @@ defmodule AeMdw.ContractTest do get_contract_call: fn ^contract_pk, ^call_id, ^block_hash -> {:ok, call} end ]} ] do - assert {:error, ^call} = Contract.call_tx_info(call_tx, contract_pk, block_hash) + assert {:error, ^call} = + Contract.call_tx_info(call_tx, contract_pk, contract_pk, block_hash) end end end - describe "maybe_resolve_contract_pk/1" do + describe "maybe_resolve_contract_pk/2" do setup do contract_pk = <<1::256>> @@ -101,43 +102,71 @@ defmodule AeMdw.ContractTest do <<217, 52, 92, 99, 90, 59, 250, 112, 123, 114, 18, 135, 95, 95, 205, 71, 74, 160, 14, 22, 47, 119, 229, 124, 220, 96, 81, 40, 117, 5, 23, 138>> - %{contract_pk: contract_pk, name_pk: name_pk} - end + fake_mb = "mh_2XGZtE8jxUs2NymKHsytkaLLrk6KY2t2w1FjJxtAUqYZn8Wsdd" - test "it returns contract_pk when name contains contract_pubkey pointer", %{ - contract_pk: contract_pk, - name_pk: name_pk - } do - with_mocks [ - {:aec_chain, [], + success_mock = + {:aens, [], [ - resolve_namehash: fn "contract_pubkey", ^name_pk -> + resolve_hash: fn "contract_pubkey", ^name_pk, _block -> {:ok, {:id, :contract, contract_pk}} end ]} - ] do - assert ^contract_pk = Contract.maybe_resolve_contract_pk(name_pk) + + fail_mock = + {:aens, [], + [ + resolve_hash: fn "contract_pubkey", ^name_pk, _block -> + {:error, :name_not_resolved} + end + ]} + + common_mocks = [ + {:aec_db, [], + [ + get_block_state: fn _block_hash -> {:ok, %{}} end + ]}, + {:aec_trees, [], + [ + ns: fn _ -> {:ok, %{}} end + ]} + ] + + %{ + contract_pk: contract_pk, + name_pk: name_pk, + fake_mb: fake_mb, + success_mocks: [success_mock | common_mocks], + fail_mocks: [fail_mock | common_mocks] + } + end + + test "it returns contract_pk when name contains contract_pubkey pointer", %{ + contract_pk: contract_pk, + name_pk: name_pk, + fake_mb: fake_mb, + success_mocks: success_mocks + } do + with_mocks(success_mocks) do + assert ^contract_pk = Contract.maybe_resolve_contract_pk(name_pk, fake_mb) end end test "it returns contract_pk when contract_pk is passed", %{ - contract_pk: contract_pk + contract_pk: contract_pk, + fake_mb: fake_mb } do - assert ^contract_pk = Contract.maybe_resolve_contract_pk(contract_pk) + assert ^contract_pk = Contract.maybe_resolve_contract_pk(contract_pk, fake_mb) end test "it returns :error when contract isn't resolved", %{ - name_pk: name_pk + name_pk: name_pk, + fake_mb: fake_mb, + fail_mocks: fail_mocks } do - with_mocks [ - {:aec_chain, [], - [ - resolve_namehash: fn "contract_pubkey", ^name_pk -> {:error, :state_trees} end - ]} - ] do + with_mocks fail_mocks do assert_raise RuntimeError, - "Elixir.AeMdw.Contract.maybe_resolve_contract_pk: failed to resolve contract pubkey", - fn -> Contract.maybe_resolve_contract_pk(name_pk) end + "Contract not resolved: #{inspect(name_pk)} with reason :name_not_resolved", + fn -> Contract.maybe_resolve_contract_pk(name_pk, fake_mb) end end end end diff --git a/test/ae_mdw/db/contract_call_mutation_test.exs b/test/ae_mdw/db/contract_call_mutation_test.exs index 8befe2566..bd7b3539d 100644 --- a/test/ae_mdw/db/contract_call_mutation_test.exs +++ b/test/ae_mdw/db/contract_call_mutation_test.exs @@ -15,26 +15,21 @@ defmodule AeMdw.Db.ContractCallMutationTest do import AeMdw.Node.ContractCallFixtures import AeMdw.Node.AexnEventFixtures, only: [aexn_event_hash: 1] import AeMdw.Util.Encoding, only: [encode_account: 1] - import Mock require Model @burn_caller_pk <<234, 90, 164, 101, 3, 211, 169, 40, 246, 51, 6, 203, 132, 12, 34, 114, 203, 201, 104, 124, 76, 144, 134, 158, 55, 106, 213, 160, 170, 64, 59, 72>> - describe "calling a contract by name" do - test "it call contract by name_pk", %{store: store} do - name_pk = - <<217, 52, 92, 99, 90, 59, 250, 112, 123, 114, 18, 135, 95, 95, 205, 71, 74, 160, 14, 22, - 47, 119, 229, 124, 220, 96, 81, 40, 117, 5, 35, 138>> - + describe "calling a contract" do + test "it create call contract mutation", %{store: store} do contract_pk = <<108, 159, 218, 252, 142, 182, 31, 215, 107, 90, 189, 201, 108, 136, 21, 96, 45, 160, 108, 218, 130, 229, 90, 80, 44, 238, 94, 180, 157, 190, 40, 100>> call_txi = 1_000_002 - assert {account_pk, mutation} = contract_call_mutation("mint", call_txi, name_pk) - assert %ContractCallMutation{txi: ^call_txi, contract_pk: ^name_pk} = mutation + assert {account_pk, mutation} = contract_call_mutation("mint", call_txi, contract_pk) + assert %ContractCallMutation{txi: ^call_txi, contract_pk: ^contract_pk} = mutation account_pk = :aeser_api_encoder.encode(:account_pubkey, account_pk) @@ -45,35 +40,23 @@ defmodule AeMdw.Db.ContractCallMutationTest do EtsCache.put(Contract, contract_pk, ct_info) - with_mocks [ - {:aec_chain, [], - [ - resolve_namehash: fn "contract_pubkey", ^name_pk -> - {:ok, {:id, :contract, contract_pk}} - end - ]} - ] do - store = - store - |> Store.put( - Model.Field, - Model.field(index: {:contract_create_tx, nil, contract_pk, call_txi - 1}) - ) - |> change_store([mutation]) + store = + store + |> Store.put( + Model.Field, + Model.field(index: {:contract_create_tx, nil, contract_pk, call_txi - 1}) + ) + |> change_store([mutation]) - # name_pubkey = Db.id_pubkey(name_pk) - create_txi = call_txi - 1 + create_txi = call_txi - 1 - args = [ - %{type: :address, value: account_pk}, - %{type: :int, value: 70_000_000_000_000_000_000} - ] + args = [ + %{type: :address, value: account_pk}, + %{type: :int, value: 70_000_000_000_000_000_000} + ] - assert resp = - {:ok, - Model.contract_call(index: {^create_txi, ^call_txi}, fun: "mint", args: ^args)} = - Store.get(store, Model.ContractCall, {call_txi - 1, call_txi}) - end + assert {:ok, Model.contract_call(index: {^create_txi, ^call_txi}, fun: "mint", args: ^args)} = + Store.get(store, Model.ContractCall, {call_txi - 1, call_txi}) end end diff --git a/test/ae_mdw/db/mutations/write_fields_mutation_test.exs b/test/ae_mdw/db/mutations/write_fields_mutation_test.exs index 129fba6bc..33df5e92a 100644 --- a/test/ae_mdw/db/mutations/write_fields_mutation_test.exs +++ b/test/ae_mdw/db/mutations/write_fields_mutation_test.exs @@ -54,9 +54,9 @@ defmodule AeMdw.Db.WriteFieldsMuationTest do mutation = WriteFieldsMutation.new(:spend_tx, spend_tx, block_index, txi) with_mocks [ - {Name, [], + {Name, [:passthrough], [ - ptr_resolve: fn _store, _block_index, _name_hash -> + ptr_resolve: fn _store, _block_index, _name_hash, "account_pubkey" -> {:ok, Validate.id!(@fake_account2)} end ]} diff --git a/test/ae_mdw/db/sync/transaction_test.exs b/test/ae_mdw/db/sync/transaction_test.exs index 866b1c920..9b0034394 100644 --- a/test/ae_mdw/db/sync/transaction_test.exs +++ b/test/ae_mdw/db/sync/transaction_test.exs @@ -145,7 +145,7 @@ defmodule AeMdw.Db.Sync.TransactionTest do with_mocks [ {Contract, [:passthrough], [ - call_tx_info: fn _tx, ^contract_pk, _block_hash -> + call_tx_info: fn _tx, ^contract_pk, ^contract_pk, _block_hash -> { fun_args_res("create_pair"), call_rec("create_pair") diff --git a/test/ae_mdw_web/controllers/tx_controller_test.exs b/test/ae_mdw_web/controllers/tx_controller_test.exs index dae6813d2..48078c911 100644 --- a/test/ae_mdw_web/controllers/tx_controller_test.exs +++ b/test/ae_mdw_web/controllers/tx_controller_test.exs @@ -33,7 +33,6 @@ defmodule AeMdwWeb.TxControllerTest do 108, 218, 130, 229, 90, 80, 44, 238, 94, 180, 157, 190, 40, 100>> contract_pk = :aeser_api_encoder.encode(:contract_pubkey, contract_pk_raw) - contract_id = :aeser_id.create(:contract, contract_pk) name_pk_raw = <<217, 52, 92, 99, 90, 59, 250, 112, 123, 114, 18, 135, 95, 95, 205, 71, 74, 160, 14, 22, @@ -56,11 +55,8 @@ defmodule AeMdwWeb.TxControllerTest do [ {:aec_chain, [:passthrough], [ - get_contract_call: fn ^contract_pk, _call_id, _block_hash -> + get_contract_call: fn ^name_pk_raw, _call_id, _block_hash -> {:ok, call_rec("mint")} - end, - resolve_namehash: fn "contract_pubkey", ^name_pk_raw -> - {:ok, contract_id} end ]}, {AeMdwContract, [:passthrough], @@ -76,6 +72,10 @@ defmodule AeMdwWeb.TxControllerTest do "compiler_version" => 8, "source_hash" => Base.encode64("some_hash") } + end, + maybe_resolve_contract_pk: fn + ^name_pk_raw, _block -> + contract_pk_raw end ]} ] do @@ -110,7 +110,7 @@ defmodule AeMdwWeb.TxControllerTest do ) |> Store.put( Model.Field, - Model.field(index: {:contract_call_tx, nil, name_pk, 1}) + Model.field(index: {:contract_call_tx, nil, contract_pk_raw, 1}) ) |> Store.put( Model.ContractCall,