From 068f23dde3eb2ae3136069048b365b811a3fc554 Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Fri, 12 May 2023 12:37:07 +0200 Subject: [PATCH 1/3] EVM/Kernel: store transaction object --- src/kernel_evm/ethereum/src/transaction.rs | 33 +++++++ .../evm_execution/src/account_storage.rs | 2 +- src/kernel_evm/kernel/src/block.rs | 40 +++++++- src/kernel_evm/kernel/src/storage.rs | 93 ++++++++++++++++++- 4 files changed, 162 insertions(+), 6 deletions(-) diff --git a/src/kernel_evm/ethereum/src/transaction.rs b/src/kernel_evm/ethereum/src/transaction.rs index da62cad4c89c..4ebfe5741ffa 100644 --- a/src/kernel_evm/ethereum/src/transaction.rs +++ b/src/kernel_evm/ethereum/src/transaction.rs @@ -80,6 +80,39 @@ pub struct TransactionReceipt { pub status: TransactionStatus, } +/// Transaction receipt, https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionbyhash +/// There a lot of redundancy between a transaction object and a transaction +/// receipt. In fact, transaction objects should not be stored in the kernel +/// but rather in the EVM node. Duplicating the code instead of sharing fields +/// is intentional to facilitate the associated code to the EVM node. +/// TODO: https://gitlab.com/tezos/tezos/-/issues/5695 +pub struct TransactionObject { + /// Address of the sender. + pub from: H160, + /// The amount of gas used by this specific transaction alone. + pub gas_used: U256, + /// The amount of gas price provided by the sender in Wei. + pub gas_price: U256, + /// Hash of the transaction. + pub hash: TransactionHash, + /// The data send along with the transaction. + pub input: Vec, + /// The number of transactions made by the sender prior to this one. + pub nonce: U256, + /// Address of the receiver. null when its a contract creation transaction. + pub to: Option, + /// Integer of the transactions index position in the block. + pub index: u32, + /// Value transferred in Wei. + pub value: U256, + /// ECDSA recovery id + pub v: U256, + /// ECDSA signature r + pub r: H256, + /// ECDSA signature s + pub s: H256, +} + #[allow(clippy::from_over_into)] impl Into<&'static [u8]> for &TransactionType { fn into(self) -> &'static [u8] { diff --git a/src/kernel_evm/evm_execution/src/account_storage.rs b/src/kernel_evm/evm_execution/src/account_storage.rs index 00bc34895841..2a0e68caa9d7 100644 --- a/src/kernel_evm/evm_execution/src/account_storage.rs +++ b/src/kernel_evm/evm_execution/src/account_storage.rs @@ -199,7 +199,7 @@ pub fn account_path(address: &H160) -> Result { } /// Store larger than MAX_FILE_CHUNK_SIZE amount of data. Placeholder for future SDK feature. -fn store_write_all( +pub fn store_write_all( host: &mut impl Runtime, path: &T, code: &[u8], diff --git a/src/kernel_evm/kernel/src/block.rs b/src/kernel_evm/kernel/src/block.rs index c67dc9f29089..c086c105f127 100644 --- a/src/kernel_evm/kernel/src/block.rs +++ b/src/kernel_evm/kernel/src/block.rs @@ -9,13 +9,13 @@ use crate::error::StorageError::AccountInitialisation; use crate::error::TransferError::{ CumulativeGasUsedOverflow, InvalidCallerAddress, InvalidNonce, }; +use crate::inbox::Transaction; use crate::storage; use evm_execution::account_storage::init_account_storage; use evm_execution::account_storage::EthereumAccountStorage; use evm_execution::handler::ExecutionOutcome; use evm_execution::{precompiles, run_transaction}; - -use tezos_ethereum::transaction::TransactionHash; +use tezos_ethereum::transaction::{TransactionHash, TransactionObject}; use tezos_smart_rollup_host::runtime::Runtime; use primitive_types::{H160, U256}; @@ -101,6 +101,29 @@ fn make_receipt( Ok(tx_receipt) } +#[inline(always)] +fn make_object( + transaction: Transaction, + from: H160, + index: u32, + gas_used: U256, +) -> TransactionObject { + TransactionObject { + from, + gas_used, + gas_price: transaction.tx.gas_price, + hash: transaction.tx_hash, + input: transaction.tx.data, + nonce: transaction.tx.nonce, + to: transaction.tx.to, + index, + value: transaction.tx.value, + v: transaction.tx.v, + r: transaction.tx.r, + s: transaction.tx.s, + } +} + fn make_receipts( block: &L2Block, receipt_infos: Vec, @@ -148,6 +171,7 @@ pub fn produce(host: &mut Host, queue: Queue) -> Result<(), Error for proposal in queue.proposals { let mut valid_txs = Vec::new(); let mut receipts_infos = Vec::new(); + let mut objects = Vec::new(); let transactions = proposal.transactions; for (transaction, index) in transactions.into_iter().zip(0u32..) { @@ -163,7 +187,7 @@ pub fn produce(host: &mut Host, queue: Queue) -> Result<(), Error &precompiles, transaction.tx.to, caller, - transaction.tx.data, + transaction.tx.data.clone(), Some(transaction.tx.gas_limit), Some(transaction.tx.value), ) { @@ -185,6 +209,12 @@ pub fn produce(host: &mut Host, queue: Queue) -> Result<(), Error transaction.tx.to, ), }; + let gas_used = match &receipt_info.execution_outcome { + Some(execution_outcome) => execution_outcome.gas_used.into(), + None => U256::zero(), + }; + let object = make_object(transaction, caller, index, gas_used); + objects.push(object); receipts_infos.push(receipt_info) } @@ -194,6 +224,10 @@ pub fn produce(host: &mut Host, queue: Queue) -> Result<(), Error host, &make_receipts(&new_block, receipts_infos)?, )?; + // Note that this is not efficient nor "properly" implemented. This + // is a temporary hack to answer to third-party tools that asks + // for transaction objects. + storage::store_transaction_objects(host, &new_block, &objects)?; current_block = new_block; } Ok(()) diff --git a/src/kernel_evm/kernel/src/storage.rs b/src/kernel_evm/kernel/src/storage.rs index e7cc4ab50026..ddd19bfb2953 100644 --- a/src/kernel_evm/kernel/src/storage.rs +++ b/src/kernel_evm/kernel/src/storage.rs @@ -10,10 +10,11 @@ use tezos_smart_rollup_host::path::*; use tezos_smart_rollup_host::runtime::{Runtime, ValueType}; use crate::error::{Error, StorageError}; +use evm_execution::account_storage::store_write_all; use tezos_ethereum::block::L2Block; use tezos_ethereum::transaction::{ - TransactionHash, TransactionReceipt, TransactionStatus, TransactionType, - TRANSACTION_HASH_SIZE, + TransactionHash, TransactionObject, TransactionReceipt, TransactionStatus, + TransactionType, TRANSACTION_HASH_SIZE, }; use tezos_ethereum::wei::Wei; @@ -42,6 +43,22 @@ const TRANSACTION_CUMULATIVE_GAS_USED: RefPath = const TRANSACTION_RECEIPT_TYPE: RefPath = RefPath::assert_from(b"/type"); const TRANSACTION_RECEIPT_STATUS: RefPath = RefPath::assert_from(b"/status"); +const TRANSACTIONS_OBJECTS: RefPath = RefPath::assert_from(b"/transactions_objects"); +const TRANSACTION_OBJECT_BLOCK_HASH: RefPath = RefPath::assert_from(b"/block_hash"); +const TRANSACTION_OBJECT_BLOCK_NUMBER: RefPath = RefPath::assert_from(b"/block_number"); +const TRANSACTION_OBJECT_FROM: RefPath = RefPath::assert_from(b"/from"); +const TRANSACTION_OBJECT_GAS_USED: RefPath = RefPath::assert_from(b"/gas_used"); +const TRANSACTION_OBJECT_GAS_PRICE: RefPath = RefPath::assert_from(b"/gas_price"); +const TRANSACTION_OBJECT_HASH: RefPath = RefPath::assert_from(b"/hash"); +const TRANSACTION_OBJECT_INPUT: RefPath = RefPath::assert_from(b"/input"); +const TRANSACTION_OBJECT_NONCE: RefPath = RefPath::assert_from(b"/nonce"); +const TRANSACTION_OBJECT_TO: RefPath = RefPath::assert_from(b"/to"); +const TRANSACTION_OBJECT_INDEX: RefPath = RefPath::assert_from(b"/index"); +const TRANSACTION_OBJECT_VALUE: RefPath = RefPath::assert_from(b"/value"); +const TRANSACTION_OBJECT_V: RefPath = RefPath::assert_from(b"/v"); +const TRANSACTION_OBJECT_R: RefPath = RefPath::assert_from(b"/r"); +const TRANSACTION_OBJECT_S: RefPath = RefPath::assert_from(b"/s"); + /// The size of an address. Size in bytes. const ADDRESS_SIZE: usize = 20; /// The size of a 256 bit hash. Size in bytes. @@ -136,6 +153,7 @@ pub fn block_path(number: U256) -> Result { let number_path = OwnedPath::try_from(raw_number_path)?; concat(&EVM_BLOCKS, &number_path).map_err(Error::from) } + pub fn receipt_path(receipt_hash: &TransactionHash) -> Result { let hash = hex::encode(receipt_hash); let raw_receipt_path: Vec = format!("/{}", &hash).into(); @@ -143,6 +161,13 @@ pub fn receipt_path(receipt_hash: &TransactionHash) -> Result concat(&TRANSACTIONS_RECEIPTS, &receipt_path).map_err(Error::from) } +pub fn object_path(object_hash: &TransactionHash) -> Result { + let hash = hex::encode(object_hash); + let raw_object_path: Vec = format!("/{}", &hash).into(); + let object_path = OwnedPath::try_from(raw_object_path)?; + concat(&TRANSACTIONS_OBJECTS, &object_path).map_err(Error::from) +} + pub fn read_current_block_number(host: &mut Host) -> Result { let path = concat(&EVM_CURRENT_BLOCK, &EVM_BLOCKS_NUMBER)?; let mut buffer = [0_u8; 8]; @@ -340,6 +365,70 @@ pub fn store_transaction_receipt( Ok(()) } +pub fn store_transaction_object( + object_path: &OwnedPath, + host: &mut Host, + block_hash: H256, + block_number: U256, + object: &TransactionObject, +) -> Result<(), Error> { + // Block hash + let block_hash_path = concat(object_path, &TRANSACTION_OBJECT_BLOCK_HASH)?; + host.store_write(&block_hash_path, block_hash.as_bytes(), 0)?; + // Block number + let block_number_path = concat(object_path, &TRANSACTION_OBJECT_BLOCK_NUMBER)?; + write_u256(host, &block_number_path, block_number)?; + // From + let from_path = concat(object_path, &TRANSACTION_OBJECT_FROM)?; + host.store_write(&from_path, object.from.as_bytes(), 0)?; + // Gas used + let gas_used_path = concat(object_path, &TRANSACTION_OBJECT_GAS_USED)?; + write_u256(host, &gas_used_path, object.gas_used)?; + // Gas price + let gas_price_path = concat(object_path, &TRANSACTION_OBJECT_GAS_PRICE)?; + write_u256(host, &gas_price_path, object.gas_price)?; + // Input + let input_path = concat(object_path, &TRANSACTION_OBJECT_INPUT)?; + store_write_all(host, &input_path, &object.input)?; + // Nonce + let nonce_path = concat(object_path, &TRANSACTION_OBJECT_NONCE)?; + write_u256(host, &nonce_path, object.nonce)?; + // To + if let Some(to) = object.to { + let to_path = concat(object_path, &TRANSACTION_OBJECT_TO)?; + host.store_write(&to_path, to.as_bytes(), 0)?; + }; + // Index + let index_path = concat(object_path, &TRANSACTION_OBJECT_INDEX)?; + host.store_write(&index_path, &object.index.to_le_bytes(), 0)?; + // Value + let value_path = concat(object_path, &TRANSACTION_OBJECT_VALUE)?; + write_u256(host, &value_path, object.value)?; + // V + let v_path = concat(object_path, &TRANSACTION_OBJECT_V)?; + write_u256(host, &v_path, object.v)?; + // R + let r_path = concat(object_path, &TRANSACTION_OBJECT_R)?; + host.store_write(&r_path, object.r.as_bytes(), 0)?; + // S + let s_path = concat(object_path, &TRANSACTION_OBJECT_S)?; + host.store_write(&s_path, object.s.as_bytes(), 0)?; + + Ok(()) +} + +pub fn store_transaction_objects( + host: &mut Host, + block: &L2Block, + objects: &[TransactionObject], +) -> Result<(), Error> { + for object in objects { + let object_path = object_path(&object.hash)?; + store_transaction_object(&object_path, host, block.hash, block.number, object)?; + } + Ok(()) +} + pub fn store_transaction_receipts( host: &mut Host, receipts: &[TransactionReceipt], -- GitLab From f516f3d035f7ffe03609e0ea33aaafcd46b0ad34 Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Fri, 12 May 2023 14:07:23 +0200 Subject: [PATCH 2/3] EVM/Proxy: asks kernel the transaction object --- src/bin_evm_proxy/ethereum_types.ml | 12 +- src/bin_evm_proxy/mockup.ml | 8 +- src/bin_evm_proxy/rollup_node.ml | 193 +++++++++++++++++++++++++--- src/bin_evm_proxy/rollup_node.mli | 5 + src/bin_evm_proxy/rpc_encodings.ml | 4 +- src/bin_evm_proxy/rpc_encodings.mli | 2 +- src/bin_evm_proxy/services.ml | 5 +- 7 files changed, 196 insertions(+), 33 deletions(-) diff --git a/src/bin_evm_proxy/ethereum_types.ml b/src/bin_evm_proxy/ethereum_types.ml index 148bba44a33b..0a080e415da6 100644 --- a/src/bin_evm_proxy/ethereum_types.ml +++ b/src/bin_evm_proxy/ethereum_types.ml @@ -432,13 +432,13 @@ type transaction_object = { hash : hash; input : hash option; nonce : quantity; - to_ : address; + to_ : address option; transactionIndex : quantity; (* It can be null if it's in a pending block, but we don't have a notion of pending. *) value : quantity; v : quantity; - r : quantity; - s : quantity; + r : hash; + s : hash; } let transaction_object_encoding = @@ -508,13 +508,13 @@ let transaction_object_encoding = (req "hash" hash_encoding) (req "input" (option hash_encoding)) (req "nonce" quantity_encoding) - (req "to" address_encoding) + (req "to" (option address_encoding)) (req "transactionIndex" quantity_encoding)) (obj4 (req "value" quantity_encoding) (req "v" quantity_encoding) - (req "r" quantity_encoding) - (req "s" quantity_encoding))) + (req "r" hash_encoding) + (req "s" hash_encoding))) type transaction = { from : address; diff --git a/src/bin_evm_proxy/mockup.ml b/src/bin_evm_proxy/mockup.ml index 65bf0a1cde6c..8dcb5c75ce89 100644 --- a/src/bin_evm_proxy/mockup.ml +++ b/src/bin_evm_proxy/mockup.ml @@ -124,12 +124,12 @@ let transaction_object = hash = transaction_hash; input = None; nonce = qty_f Z.zero; - to_ = address_of_string "0xA5A5bf58c7Dc91cBE5005A7E5c6314998Eda479E"; + to_ = Some (address_of_string "0xA5A5bf58c7Dc91cBE5005A7E5c6314998Eda479E"); transactionIndex = qty_f Z.zero; value = qty_f Z.zero; v = qty_f Z.zero; - r = qty_f Z.zero; - s = qty_f Z.zero; + r = hash_f @@ "00"; + s = hash_f @@ "00"; } let call = hash_f "0x" @@ -154,3 +154,5 @@ let current_block_number () = return (block_height ()) let nth_block ~full_transaction_object:_ _n = return (block ()) let transaction_receipt _tx_hash = return (transaction_receipt ()) + +let transaction_object _tx_hash = return (Some transaction_object) diff --git a/src/bin_evm_proxy/rollup_node.ml b/src/bin_evm_proxy/rollup_node.ml index e8e98230ff5a..a7bdb839464f 100644 --- a/src/bin_evm_proxy/rollup_node.ml +++ b/src/bin_evm_proxy/rollup_node.ml @@ -102,6 +102,39 @@ module Durable_storage_path = struct let contract_address tx_hash = receipt_field tx_hash "contract_address" end + + module Transaction_object = struct + let objects = "/transactions_objects" + + let object_field tx_hash field = + Format.sprintf "%s/%s/%s" objects tx_hash field + + let block_hash tx_hash = object_field tx_hash "block_hash" + + let block_number tx_hash = object_field tx_hash "block_number" + + let from tx_hash = object_field tx_hash "from" + + let gas_used tx_hash = object_field tx_hash "gas_used" + + let gas_price tx_hash = object_field tx_hash "gas_price" + + let input tx_hash = object_field tx_hash "input" + + let nonce tx_hash = object_field tx_hash "nonce" + + let to_ tx_hash = object_field tx_hash "to" + + let index tx_hash = object_field tx_hash "index" + + let value tx_hash = object_field tx_hash "value" + + let v tx_hash = object_field tx_hash "v" + + let r tx_hash = object_field tx_hash "r" + + let s tx_hash = object_field tx_hash "r" + end end module RPC = struct @@ -371,65 +404,78 @@ module RPC = struct ~number:Durable_storage_path.Block.(Nth n) base + let inspect_durable_and_decode_opt base key decode = + let open Lwt_result_syntax in + let* bytes = call_service ~base durable_state_value () {key} () in + match bytes with + | Some bytes -> return_some (decode bytes) + | None -> return_none + + let inspect_durable_and_decode base key decode = + let open Lwt_result_syntax in + let* res_opt = inspect_durable_and_decode_opt base key decode in + match res_opt with Some res -> return res | None -> failwith "null" + + let decode_block_hash bytes = + Block_hash (Bytes.to_string bytes |> Hex.of_string |> Hex.show) + + let decode_address bytes = + Address (Bytes.to_string bytes |> Hex.of_string |> Hex.show) + + let decode_number bytes = + Bytes.to_string bytes |> Z.of_bits |> Ethereum_types.quantity_of_z + + let decode_hash bytes = + Hash (Bytes.to_string bytes |> Hex.of_string |> Hex.show) + let transaction_receipt base (Hash tx_hash) = let open Lwt_result_syntax in - let inspect_durable_and_decode_opt key decode = - let* bytes = call_service ~base durable_state_value () {key} () in - match bytes with - | Some bytes -> return_some (decode bytes) - | None -> return_none - in - let inspect_durable_and_decode key decode = - let* res_opt = inspect_durable_and_decode_opt key decode in - match res_opt with Some res -> return res | None -> failwith "null" - in - let decode_block_hash bytes = - Block_hash (Bytes.to_string bytes |> Hex.of_string |> Hex.show) - in - let decode_address bytes = - Address (Bytes.to_string bytes |> Hex.of_string |> Hex.show) - in - let decode_number bytes = - Bytes.to_string bytes |> Z.of_bits |> Ethereum_types.quantity_of_z - in let* block_hash = inspect_durable_and_decode + base (Durable_storage_path.Transaction_receipt.block_hash tx_hash) decode_block_hash in let* block_number = inspect_durable_and_decode + base (Durable_storage_path.Transaction_receipt.block_number tx_hash) decode_number in let* from = inspect_durable_and_decode + base (Durable_storage_path.Transaction_receipt.from tx_hash) decode_address in (* This can be none *) let* to_ = inspect_durable_and_decode_opt + base (Durable_storage_path.Transaction_receipt.to_ tx_hash) decode_address in let* index = inspect_durable_and_decode + base (Durable_storage_path.Transaction_receipt.index tx_hash) decode_number in let* status = inspect_durable_and_decode + base (Durable_storage_path.Transaction_receipt.status tx_hash) decode_number in let* contract_address = inspect_durable_and_decode_opt + base (Durable_storage_path.Transaction_receipt.contract_address tx_hash) decode_address in let+ type_ = inspect_durable_and_decode + base (Durable_storage_path.Transaction_receipt.type_ tx_hash) decode_number in @@ -449,6 +495,109 @@ module RPC = struct status; contractAddress = contract_address; } + + let transaction_object base (Hash tx_hash as hash) = + let open Lwt_result_syntax in + let* block_hash_opt = + inspect_durable_and_decode_opt + base + (Durable_storage_path.Transaction_object.block_hash tx_hash) + decode_block_hash + in + match block_hash_opt with + | None -> + (* If the transaction has no block hash, it was not mined. *) + return_none + | Some block_hash -> + let* block_number = + inspect_durable_and_decode + base + (Durable_storage_path.Transaction_object.block_number tx_hash) + decode_number + in + let* from = + inspect_durable_and_decode + base + (Durable_storage_path.Transaction_object.from tx_hash) + decode_address + in + let* gas_used = + inspect_durable_and_decode + base + (Durable_storage_path.Transaction_object.gas_used tx_hash) + decode_number + in + let* gas_price = + inspect_durable_and_decode + base + (Durable_storage_path.Transaction_object.gas_price tx_hash) + decode_number + in + let* input = + inspect_durable_and_decode_opt + base + (Durable_storage_path.Transaction_object.input tx_hash) + decode_hash + in + let* nonce = + inspect_durable_and_decode + base + (Durable_storage_path.Transaction_object.nonce tx_hash) + decode_number + in + let* to_ = + inspect_durable_and_decode_opt + base + (Durable_storage_path.Transaction_object.to_ tx_hash) + decode_address + in + let* index = + inspect_durable_and_decode + base + (Durable_storage_path.Transaction_object.index tx_hash) + decode_number + in + let* value = + inspect_durable_and_decode + base + (Durable_storage_path.Transaction_object.value tx_hash) + decode_number + in + let* v = + inspect_durable_and_decode + base + (Durable_storage_path.Transaction_object.v tx_hash) + decode_number + in + let* r = + inspect_durable_and_decode + base + (Durable_storage_path.Transaction_object.r tx_hash) + decode_hash + in + let* s = + inspect_durable_and_decode + base + (Durable_storage_path.Transaction_object.s tx_hash) + decode_hash + in + return_some + { + blockHash = block_hash; + blockNumber = block_number; + from; + gas = gas_used; + gasPrice = gas_price; + hash; + input; + nonce; + to_; + transactionIndex = index; + value; + v; + r; + s; + } end module type S = sig @@ -473,6 +622,10 @@ module type S = sig val transaction_receipt : Ethereum_types.hash -> Ethereum_types.transaction_receipt tzresult Lwt.t + + val transaction_object : + Ethereum_types.hash -> + Ethereum_types.transaction_object option tzresult Lwt.t end module Make (Base : sig @@ -495,4 +648,6 @@ end) : S = struct let nth_block = RPC.nth_block Base.base let transaction_receipt = RPC.transaction_receipt Base.base + + let transaction_object = RPC.transaction_object Base.base end diff --git a/src/bin_evm_proxy/rollup_node.mli b/src/bin_evm_proxy/rollup_node.mli index b52e856f92ca..8129c5fb20d9 100644 --- a/src/bin_evm_proxy/rollup_node.mli +++ b/src/bin_evm_proxy/rollup_node.mli @@ -73,6 +73,11 @@ module type S = sig (** [transaction_receipt tx_hash] returns the receipt of [tx_hash]. *) val transaction_receipt : Ethereum_types.hash -> Ethereum_types.transaction_receipt tzresult Lwt.t + + (** [transaction_object tx_hash] returns the informations of [tx_hash]. *) + val transaction_object : + Ethereum_types.hash -> + Ethereum_types.transaction_object option tzresult Lwt.t end (** Instantiate a module of type {!S} that communicates with a rollup diff --git a/src/bin_evm_proxy/rpc_encodings.ml b/src/bin_evm_proxy/rpc_encodings.ml index c9e07db8db81..8cc2952775c2 100644 --- a/src/bin_evm_proxy/rpc_encodings.ml +++ b/src/bin_evm_proxy/rpc_encodings.ml @@ -341,11 +341,11 @@ module Get_transaction_by_hash = MethodMaker (struct type input = hash - type output = transaction_object + type output = transaction_object option let input_encoding = Data_encoding.tup1 hash_encoding - let output_encoding = transaction_object_encoding + let output_encoding = Data_encoding.option transaction_object_encoding let method_ = "eth_getTransactionByHash" end) diff --git a/src/bin_evm_proxy/rpc_encodings.mli b/src/bin_evm_proxy/rpc_encodings.mli index a4cfa454bc62..80501dfc1c95 100644 --- a/src/bin_evm_proxy/rpc_encodings.mli +++ b/src/bin_evm_proxy/rpc_encodings.mli @@ -228,7 +228,7 @@ module Get_transaction_receipt : module Get_transaction_by_hash : METHOD with type m_input = Ethereum_types.hash - and type m_output = Ethereum_types.transaction_object + and type m_output = Ethereum_types.transaction_object option module Send_raw_transaction : METHOD diff --git a/src/bin_evm_proxy/services.ml b/src/bin_evm_proxy/services.ml index af2982f2c506..184de541a5e0 100644 --- a/src/bin_evm_proxy/services.ml +++ b/src/bin_evm_proxy/services.ml @@ -114,8 +114,9 @@ let dispatch_input match res with Ok x -> return_some x | Error _ -> return_none in return (Get_transaction_receipt.Output (Ok receipt)) - | Get_transaction_by_hash.Input _ -> - return (Get_transaction_by_hash.Output (Ok Mockup.transaction_object)) + | Get_transaction_by_hash.Input (Some tx_hash) -> + let* transaction_object = Rollup_node_rpc.transaction_object tx_hash in + return (Get_transaction_by_hash.Output (Ok transaction_object)) | Send_raw_transaction.Input (Some tx_raw) -> let* tx_hash = Rollup_node_rpc.inject_raw_transaction ~smart_rollup_address tx_raw -- GitLab From 3d42047fed7543e81c6b831fcb7cde763e2d7472 Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Mon, 22 May 2023 16:43:59 +0200 Subject: [PATCH 3/3] EVM/Tezt: test rpc getTransactionByHash --- tezt/lib_ethereum/eth_cli.ml | 42 ++++++++++++++++------ tezt/lib_ethereum/eth_cli.mli | 12 +++++-- tezt/lib_ethereum/transaction.ml | 60 ++++++++++++++++++++++++++++++++ tezt/tests/evm_rollup.ml | 35 +++++++++++++++++-- 4 files changed, 133 insertions(+), 16 deletions(-) create mode 100644 tezt/lib_ethereum/transaction.ml diff --git a/tezt/lib_ethereum/eth_cli.ml b/tezt/lib_ethereum/eth_cli.ml index 0766c882096e..6a4ce3422619 100644 --- a/tezt/lib_ethereum/eth_cli.ml +++ b/tezt/lib_ethereum/eth_cli.ml @@ -25,19 +25,22 @@ let path = "eth" -let spawn_command_and_read command decode = +let spawn_command_and_read_json command decode = let process = Process.spawn path command in let* output = Process.check_and_read_stdout process in return (JSON.parse ~origin:"eth_spawn_command" output |> decode) +let spawn_command_and_read_string command = + let process = Process.spawn path command in + Process.check_and_read_stdout process + let spawn_command command = let process = Process.spawn path command in - let* output = Process.check process in - return output + Process.check process let balance ~account ~endpoint = let* balance = - spawn_command_and_read + spawn_command_and_read_json ["address:balance"; account; "--network"; endpoint] JSON.as_int in @@ -45,7 +48,7 @@ let balance ~account ~endpoint = let transaction_send ~source_private_key ~to_public_key ~value ~endpoint ?data () = - spawn_command_and_read + spawn_command_and_read_json ([ "transaction:send"; "--pk"; @@ -60,22 +63,41 @@ let transaction_send ~source_private_key ~to_public_key ~value ~endpoint ?data @ match data with Some data -> ["--data"; data] | None -> []) JSON.as_string +let transaction_get ~endpoint ~tx_hash = + let* output = + spawn_command_and_read_string + ["transaction:get"; tx_hash; "--network"; endpoint] + in + let output = String.trim output in + if output = "null" then return None + else + return + (Some + (JSON.parse ~origin:"transaction_get" output + |> Transaction.transaction_object_of_json)) + let get_block ~block_id ~endpoint = - spawn_command_and_read + spawn_command_and_read_json ["block:get"; block_id; "--network"; endpoint] Block.of_json let block_number ~endpoint = - spawn_command_and_read ["block:number"; "--network"; endpoint] JSON.as_int + spawn_command_and_read_json + ["block:number"; "--network"; endpoint] + JSON.as_int let add_abi ~label ~abi () = spawn_command ["abi:add"; label; abi] -let deploy ~source_private_key ~endpoint ~abi ~bin () = +let deploy ~source_private_key ~endpoint ~abi ~bin = let decode json = let open JSON in - json |-> "receipt" |-> "contractAddress" |> as_string + let contract_address = + json |-> "receipt" |-> "contractAddress" |> as_string + in + let tx_hash = json |-> "receipt" |-> "transactionHash" |> as_string in + (contract_address, tx_hash) in - spawn_command_and_read + spawn_command_and_read_json [ "contract:deploy"; "--pk"; diff --git a/tezt/lib_ethereum/eth_cli.mli b/tezt/lib_ethereum/eth_cli.mli index 644cac559dc3..fd3c9c3cd6a3 100644 --- a/tezt/lib_ethereum/eth_cli.mli +++ b/tezt/lib_ethereum/eth_cli.mli @@ -40,6 +40,13 @@ val transaction_send : unit -> string Lwt.t +(** [transaction_get ~endpoint ~tx_hash] returns the transaction object + of [tx_hash] if it exists. *) +val transaction_get : + endpoint:string -> + tx_hash:string -> + Transaction.transaction_object option Lwt.t + (** [add_abi ~label ~abi ()] register an ABI (Application Binary Interface) in the client. [abi] should be a path to the json file. [label] is a string used by the client to register the ABI in memory so it can be refered to @@ -51,14 +58,13 @@ val add_abi : label:string -> abi:string -> unit -> unit Lwt.t client, and sends the raw transaction to the JSON-API server listening at [endpoint]. [bin] is a path to the binary file, and [abi] is the label used while registering the ABI in the client. Returns the deployed contract - address. *) + address and the transaction hash *) val deploy : source_private_key:string -> endpoint:string -> abi:string -> bin:string -> - unit -> - string Lwt.t + (string * string) Lwt.t (** [get_block ~block_id ~endpoint] asks the block [block_id] (it can be a hash or a number) to the JSON-RPC API server listening at [endpoint]. *) diff --git a/tezt/lib_ethereum/transaction.ml b/tezt/lib_ethereum/transaction.ml new file mode 100644 index 000000000000..6d2252ecbc14 --- /dev/null +++ b/tezt/lib_ethereum/transaction.ml @@ -0,0 +1,60 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +type transaction_object = { + blockHash : string; + blockNumber : int32; + from : string; + gas : int32; + gasPrice : int32; + hash : string; + input : string option; + nonce : int32; + to_ : string option; + transactionIndex : int32; + value : Wei.t; + v : int32; + r : string; + s : string; +} + +let transaction_object_of_json json = + let open JSON in + { + blockHash = json |-> "blockHash" |> as_string; + blockNumber = json |-> "blockNumber" |> as_int32; + from = json |-> "from" |> as_string; + gas = json |-> "gas" |> as_int32; + gasPrice = json |-> "gasPrice" |> as_int32; + hash = json |-> "hash" |> as_string; + input = json |-> "input" |> as_string_opt; + nonce = json |-> "nonce" |> as_int32; + to_ = json |-> "to" |> as_string_opt; + transactionIndex = json |-> "transactionIndex" |> as_int32; + value = json |-> "value" |> as_string |> Wei.of_string; + v = json |-> "v" |> as_int32; + r = json |-> "r" |> as_string; + s = json |-> "s" |> as_string; + } diff --git a/tezt/tests/evm_rollup.ml b/tezt/tests/evm_rollup.ml index 2cdf479126fd..393b500d7af0 100644 --- a/tezt/tests/evm_rollup.ml +++ b/tezt/tests/evm_rollup.ml @@ -410,14 +410,14 @@ let test_l2_deploy = () in let contract_compiled_file = kernel_inputs_path ^ "/storage.bin" in - let send_deploy = + let send_deploy () = Eth_cli.deploy ~source_private_key:sender.private_key ~endpoint:evm_proxy_server_endpoint ~abi:"simpleStorage" ~bin:contract_compiled_file in - let* contract_address = + let* contract_address, tx_hash = wait_for_application ~sc_rollup_node ~node ~client send_deploy () in let* code_in_kernel = @@ -430,6 +430,18 @@ let test_l2_deploy = in Check.((code_in_kernel = expected_code) string) ~error_msg:"Unexpected code %L, it should be %R" ; + (* The transaction was a contract creation, the transaction object + must not contain the [to] field. *) + let* tx_object = + Eth_cli.transaction_get ~endpoint:evm_proxy_server_endpoint ~tx_hash + in + (match tx_object with + | Some tx_object -> + Check.((tx_object.to_ = None) (option string)) + ~error_msg: + "The transaction object of a contract creation should not have the \ + [to] field present" + | None -> Test.fail "The transaction object of %s should be available" tx_hash) ; unit let transfer ?data protocol = @@ -448,7 +460,7 @@ let transfer ?data protocol = let* sender_nonce = get_transaction_count evm_proxy_server sender.address in (* We always send less than the balance, to ensure it always works. *) let value = Wei.(sender_balance - one) in - let* _tx_hash = + let* tx_hash = send_and_wait_until_tx_mined ~sc_rollup_node ~node @@ -474,6 +486,23 @@ let transfer ?data protocol = Check.((new_sender_nonce = Int64.succ sender_nonce) int64) ~error_msg: "Unexpected sender nonce after transfer, should be %R, but got %L" ; + (* Perform some sanity checks on the transaction object produced by the + kernel. *) + let* tx_object = + Eth_cli.transaction_get ~endpoint:evm_proxy_server_endpoint ~tx_hash + in + let tx_object = + match tx_object with + | Some tx_object -> tx_object + | None -> + Test.fail "The transaction object of %s should be available" tx_hash + in + Check.((tx_object.from = sender.address) string) + ~error_msg:"Unexpected transaction's sender" ; + Check.((tx_object.to_ = Some receiver.address) (option string)) + ~error_msg:"Unexpected transaction's receiver" ; + Check.((tx_object.value = value) Wei.typ) + ~error_msg:"Unexpected transaction's value" ; unit let test_l2_transfer = -- GitLab