diff --git a/etherlink/CHANGES_KERNEL.md b/etherlink/CHANGES_KERNEL.md index c3f48bbba3dc25efa37abc60163c152f353feb77..9a63739ad69bf59fc77bd770e003d6f2b335de76 100644 --- a/etherlink/CHANGES_KERNEL.md +++ b/etherlink/CHANGES_KERNEL.md @@ -4,6 +4,16 @@ ### Features +### Bug fixes + +### Breaking changes + +### Internal + +## Version 9978f3a5f8bee0be78686c5c568109d2e6148f13 + +### Features + - Fix contract code storage cost (!10356) - Fix contract creation gas cost and transaction data cost. (!10349) - Implementation of EIP-3541, new code starting with the 0xEF byte cannot be diff --git a/etherlink/CHANGES_NODE.md b/etherlink/CHANGES_NODE.md index ba6543781b257e59e3f3ce4e816741f0da4ce196..7af77956780a65bc7eb7953e39ca784dc7ff0cb9 100644 --- a/etherlink/CHANGES_NODE.md +++ b/etherlink/CHANGES_NODE.md @@ -2,12 +2,22 @@ ## Notes -Currently supported kernel version: 32f957d52ace920916d54b9f02a2d32ee30e16b3 +Currently supported kernel version: 9978f3a5f8bee0be78686c5c568109d2e6148f13 ## Version Next ### Features +### Bug fixes + +### Breaking changes + +### Internal + +## Version for kernel 9978f3a5f8bee0be78686c5c568109d2e6148f13 + +### Features + - Stream the L2 blocks to give a faster and more consistent inclusion of transactions on the L1. (!11102) - Add a keep alive argument that waits until the connection is made with the diff --git a/etherlink/bin_evm_node/evm_node.ml b/etherlink/bin_evm_node/evm_node.ml index 8f295a5d78de1a4b5d860902f03fc15fb29b9077..ee912902a5f47d2dcc8b78dc2de8edecf1b19e64 100644 --- a/etherlink/bin_evm_node/evm_node.ml +++ b/etherlink/bin_evm_node/evm_node.ml @@ -116,7 +116,9 @@ let install_finalizer_prod server = Lwt_exit.register_clean_up_callback ~loc:__LOC__ @@ fun exit_status -> let* () = emit Event.event_shutdown_node exit_status in let* () = Tezos_rpc_http_server.RPC_server.shutdown server in - emit (Event.event_shutdown_rpc_server ~private_:false) () + let* () = emit (Event.event_shutdown_rpc_server ~private_:false) () in + let* () = Evm_node_lib_prod.Tx_pool.shutdown () in + emit Event.event_shutdown_tx_pool () let install_finalizer_dev server = let open Lwt_syntax in @@ -160,10 +162,11 @@ let rollup_node_config_prod ~rollup_node_endpoint ~keep_alive = let* smart_rollup_address = fetch_smart_rollup_address ~keep_alive - (fun _endpoint -> Rollup_node_rpc.smart_rollup_address) + Rollup_node_services.smart_rollup_address rollup_node_endpoint in - return ((module Rollup_node_rpc : Rollup_node.S), smart_rollup_address) + return + ((module Rollup_node_rpc : Services_backend_sig.S), smart_rollup_address) let rollup_node_config_dev ~rollup_node_endpoint ~keep_alive = let open Lwt_result_syntax in @@ -479,10 +482,17 @@ let proxy_command = let* () = Configuration.save_proxy ~force:true ~data_dir config in let* () = if not config.devmode then - let* rollup_config = + let* ((backend_rpc, smart_rollup_address) as rollup_config) = rollup_node_config_prod ~rollup_node_endpoint ~keep_alive in - let* () = Evm_node_lib_prod.Tx_pool.start rollup_config in + let* () = + Evm_node_lib_prod.Tx_pool.start + { + rollup_node = backend_rpc; + smart_rollup_address; + mode = Proxy {rollup_node_endpoint}; + } + in let* directory = prod_directory config rollup_config in let* server = start config ~directory in let (_ : Lwt_exit.clean_up_callback_id) = @@ -631,12 +641,13 @@ let sequencer_command = let make_prod_messages ~smart_rollup_address s = let open Lwt_result_syntax in let open Evm_node_lib_prod in + let s = Ethereum_types.hex_of_string s in let*? _, messages = - Rollup_node.make_encoded_messages + Transaction_format.make_encoded_messages ~smart_rollup_address - (Evm_node_lib_prod_encoding.Ethereum_types.hex_of_string s) + (Evm_node_lib_dev_encoding.Ethereum_types.hex_to_bytes s) in - return messages + return (List.map (fun m -> m |> Hex.of_string |> Hex.show) messages) let make_dev_messages ~smart_rollup_address s = let open Lwt_result_syntax in diff --git a/etherlink/bin_evm_node/lib_dev/durable_storage_path.mli b/etherlink/bin_evm_node/lib_dev/durable_storage_path.mli index f900c0d9e921cd2053c46a90519e06a60baae677..4fa978e08a0bf92de6f060b0ab72274e6d0350e6 100644 --- a/etherlink/bin_evm_node/lib_dev/durable_storage_path.mli +++ b/etherlink/bin_evm_node/lib_dev/durable_storage_path.mli @@ -1,3 +1,13 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* Copyright (c) 2023 Marigold *) +(* Copyright (c) 2023 Functori *) +(* Copyright (c) 2023 Trilitech *) +(* *) +(*****************************************************************************) + open Ethereum_types type path = string diff --git a/etherlink/bin_evm_node/lib_dev/filter_helpers.ml b/etherlink/bin_evm_node/lib_dev/filter_helpers.ml index 3feab686e9bea67d758d456659bb9e56ac686b3f..6da40250cf5578dea1b83a44c97bd7c35c28a14f 100644 --- a/etherlink/bin_evm_node/lib_dev/filter_helpers.ml +++ b/etherlink/bin_evm_node/lib_dev/filter_helpers.ml @@ -53,7 +53,7 @@ module Event = struct let incompatible_block_params = Internal_event.Simple.declare_0 ~section - ~name:"incompatible_block_params" + ~name:"incompatible_block_params_dev" ~msg:"block_hash field cannot be set when from_block and to_block are set" ~level:Error () @@ -61,7 +61,7 @@ module Event = struct let block_range_too_large = Internal_event.Simple.declare_0 ~section - ~name:"block_range_too_large" + ~name:"block_range_too_large_dev" ~msg:"Requested block range is above the maximum" ~level:Error () @@ -69,7 +69,7 @@ module Event = struct let topic_list_too_large = Internal_event.Simple.declare_0 ~section - ~name:"topic_list_too_large" + ~name:"topic_list_too_large_dev" ~msg:"Topic list length should be at most 4" ~level:Error () @@ -77,7 +77,7 @@ module Event = struct let receipt_not_found = Internal_event.Simple.declare_1 ~section - ~name:"receipt_not_found" + ~name:"receipt_not_found_dev" ~msg:"Receipt not found for {tx_hash}" ~level:Error ("tx_hash", hash_encoding) @@ -85,7 +85,7 @@ module Event = struct let too_many_logs = Internal_event.Simple.declare_0 ~section - ~name:"too_many_logs" + ~name:"too_many_logs_dev" ~msg:"Too many logs requested" ~level:Error () diff --git a/etherlink/bin_evm_node/lib_dev/rollup_node_services.ml b/etherlink/bin_evm_node/lib_dev/rollup_node_services.ml index bc671e4381d05cee2de194f451b673c0779f1a55..bce4cd59a3e6f4492580f32f3c3e4b2475168853 100644 --- a/etherlink/bin_evm_node/lib_dev/rollup_node_services.ml +++ b/etherlink/bin_evm_node/lib_dev/rollup_node_services.ml @@ -20,7 +20,7 @@ let () = in register_error_kind `Temporary - ~id:"evm_node_lost_connection" + ~id:"evm_node_dev_lost_connection" ~title:"Lost connection with rollup node" ~description ~pp:(fun ppf () -> Format.fprintf ppf "%s" description) diff --git a/etherlink/bin_evm_node/lib_prod/dune b/etherlink/bin_evm_node/lib_prod/dune index 7566e1284752a9faba1fa6cc6420e7612d3d01b6..c3d714a5683a5d172fe2df06654f66f584004a2f 100644 --- a/etherlink/bin_evm_node/lib_prod/dune +++ b/etherlink/bin_evm_node/lib_prod/dune @@ -14,7 +14,12 @@ octez-libs.stdlib-unix octez-evm-node-libs.evm_node_lib_prod_encoding lwt-exit - octez-evm-node-libs.evm_node_config) + octez-evm-node-libs.evm_node_config + octez-libs.tezos-context.disk + octez-libs.tezos-context.encoding + octez-l2-libs.scoru-wasm + octez-l2-libs.scoru-wasm-helpers + octez-smart-rollup-wasm-debugger-lib) (flags (:standard) -open Tezos_base.TzPervasives @@ -22,4 +27,6 @@ -open Tezos_workers -open Tezos_stdlib_unix -open Evm_node_lib_prod_encoding - -open Evm_node_config)) + -open Evm_node_config + -open Tezos_scoru_wasm_helpers + -open Octez_smart_rollup_wasm_debugger_lib)) diff --git a/etherlink/bin_evm_node/lib_prod/durable_storage.ml b/etherlink/bin_evm_node/lib_prod/durable_storage.ml new file mode 100644 index 0000000000000000000000000000000000000000..31e7dbdc45a1794f42ba1e8a48dff3bf1f6fa6c3 --- /dev/null +++ b/etherlink/bin_evm_node/lib_prod/durable_storage.ml @@ -0,0 +1,222 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(*****************************************************************************) + +open Ethereum_types + +module type READER = sig + val read : Durable_storage_path.path -> bytes option tzresult Lwt.t +end + +module Make (Reader : READER) = struct + let inspect_durable_and_decode_opt path decode = + let open Lwt_result_syntax in + let* bytes = Reader.read path in + match bytes with + | Some bytes -> return_some (decode bytes) + | None -> return_none + + let inspect_durable_and_decode path decode = + let open Lwt_result_syntax in + let* res_opt = inspect_durable_and_decode_opt path decode in + match res_opt with Some res -> return res | None -> failwith "null" + + let balance address = + let open Lwt_result_syntax in + let+ answer = Reader.read (Durable_storage_path.Accounts.balance address) in + match answer with + | Some bytes -> + Bytes.to_string bytes |> Z.of_bits |> Ethereum_types.quantity_of_z + | None -> Ethereum_types.Qty Z.zero + + let nonce address = + let open Lwt_result_syntax in + let+ answer = Reader.read (Durable_storage_path.Accounts.nonce address) in + answer + |> Option.map (fun bytes -> + bytes |> Bytes.to_string |> Z.of_bits |> Ethereum_types.quantity_of_z) + + let code address = + let open Lwt_result_syntax in + let+ answer = Reader.read (Durable_storage_path.Accounts.code address) in + match answer with + | Some bytes -> + bytes |> Hex.of_bytes |> Hex.show |> Ethereum_types.hex_of_string + | None -> Ethereum_types.Hex "" + + exception Invalid_block_structure of string + + exception Invalid_block_index of Z.t + + let block_number n = + let open Lwt_result_syntax in + match n with + (* This avoids an unecessary service call in case we ask a block's number + with an already expected/known block number [n]. *) + | Durable_storage_path.Block.Nth i -> + return @@ Ethereum_types.Block_height i + | Durable_storage_path.Block.Current -> ( + let+ answer = Reader.read Durable_storage_path.Block.current_number in + match answer with + | Some bytes -> + Ethereum_types.Block_height (Bytes.to_string bytes |> Z.of_bits) + | None -> + raise + @@ Invalid_block_structure + "Unexpected [None] value for [current_number]'s [answer]") + + let current_block_number () = block_number Durable_storage_path.Block.Current + + let un_qty (Qty z) = z + + let transaction_receipt tx_hash = + let open Lwt_result_syntax in + (* We use a mock block hash to decode the rest of the receipt, + so that we can get the number to query for the actual block + hash. *) + let mock_block_hash = Block_hash (Hex (String.make 64 'a')) in + let* opt_receipt = + inspect_durable_and_decode_opt + (Durable_storage_path.Transaction_receipt.receipt tx_hash) + (Ethereum_types.transaction_receipt_from_rlp mock_block_hash) + in + match opt_receipt with + | Some temp_receipt -> + let+ blockHash = + inspect_durable_and_decode + (Durable_storage_path.Indexes.block_by_number + (Nth (un_qty temp_receipt.blockNumber))) + decode_block_hash + in + let logs = + List.map + (fun (log : transaction_log) -> + {log with blockHash = Some blockHash}) + temp_receipt.logs + in + Some {temp_receipt with blockHash; logs} + | None -> return_none + + let transaction_object tx_hash = + let open Lwt_result_syntax in + (* We use a mock block hash to decode the rest of the receipt, + so that we can get the number to query for the actual block + hash. *) + let mock_block_hash = Block_hash (Hex (String.make 64 'a')) in + let* opt_object = + inspect_durable_and_decode_opt + (Durable_storage_path.Transaction_object.object_ tx_hash) + (Ethereum_types.transaction_object_from_rlp mock_block_hash) + in + match opt_object with + | Some temp_object -> + let+ blockHash = + inspect_durable_and_decode + (Durable_storage_path.Indexes.block_by_number + (Nth (un_qty temp_object.blockNumber))) + decode_block_hash + in + Some {temp_object with blockHash} + | None -> return_none + + let transaction_object_with_block_hash block_hash tx_hash = + inspect_durable_and_decode_opt + (Durable_storage_path.Transaction_object.object_ tx_hash) + (Ethereum_types.transaction_object_from_rlp block_hash) + + let full_transactions block_hash transactions = + let open Lwt_result_syntax in + match transactions with + | TxHash hashes -> + let+ objects = + List.filter_map_es + (transaction_object_with_block_hash block_hash) + hashes + in + TxFull objects + | TxFull _ -> return transactions + + let populate_tx_objects ~full_transaction_object block = + let open Lwt_result_syntax in + if full_transaction_object then + let* transactions = full_transactions block.hash block.transactions in + return {block with transactions} + else return block + + let blocks_by_number ~full_transaction_object ~number = + let open Lwt_result_syntax in + let* (Ethereum_types.Block_height level) = block_number number in + let* block_hash_opt = + inspect_durable_and_decode_opt + (Durable_storage_path.Indexes.block_by_number (Nth level)) + decode_block_hash + in + match block_hash_opt with + | None -> raise @@ Invalid_block_index level + | Some block_hash -> ( + let* block_opt = + inspect_durable_and_decode_opt + (Durable_storage_path.Block.by_hash block_hash) + Ethereum_types.block_from_rlp + in + match block_opt with + | None -> raise @@ Invalid_block_structure "Couldn't decode bytes" + | Some block -> populate_tx_objects ~full_transaction_object block) + + let current_block ~full_transaction_object = + blocks_by_number + ~full_transaction_object + ~number:Durable_storage_path.Block.Current + + let nth_block ~full_transaction_object n = + blocks_by_number + ~full_transaction_object + ~number:Durable_storage_path.Block.(Nth n) + + let block_by_hash ~full_transaction_object block_hash = + let open Lwt_result_syntax in + let* block_opt = + inspect_durable_and_decode_opt + (Durable_storage_path.Block.by_hash block_hash) + Ethereum_types.block_from_rlp + in + match block_opt with + | None -> raise @@ Invalid_block_structure "Couldn't decode bytes" + | Some block -> populate_tx_objects ~full_transaction_object block + + let chain_id () = + inspect_durable_and_decode Durable_storage_path.chain_id decode_number + + let base_fee_per_gas () = + inspect_durable_and_decode + Durable_storage_path.base_fee_per_gas + decode_number + + let kernel_version () = + inspect_durable_and_decode + Durable_storage_path.kernel_version + Bytes.to_string + + let storage_at address (Qty pos) = + let open Lwt_result_syntax in + let pad32left0 s = + let open Ethereum_types in + (* Strip 0x *) + let (Hex s) = hex_of_string s in + let len = String.length s in + (* This is a Hex string of 32 bytes, therefore the length is 64 *) + String.make (64 - len) '0' ^ s + in + let index = Z.format "#x" pos |> pad32left0 in + let+ answer = + Reader.read (Durable_storage_path.Accounts.storage address index) + in + match answer with + | Some bytes -> + Bytes.to_string bytes |> Hex.of_string |> Hex.show + |> Ethereum_types.hex_of_string + | None -> Ethereum_types.Hex (pad32left0 "0") +end diff --git a/etherlink/bin_evm_node/lib_prod/durable_storage_path.ml b/etherlink/bin_evm_node/lib_prod/durable_storage_path.ml new file mode 100644 index 0000000000000000000000000000000000000000..57ce39dcfe3ff659b7fafad0d9d9feadd019458d --- /dev/null +++ b/etherlink/bin_evm_node/lib_prod/durable_storage_path.ml @@ -0,0 +1,87 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* Copyright (c) 2023 Marigold *) +(* Copyright (c) 2023 Functori *) +(* Copyright (c) 2023 Trilitech *) +(* *) +(*****************************************************************************) + +open Ethereum_types + +type path = string + +module EVM = struct + let root = "/evm" + + let make s = root ^ s +end + +let chain_id = EVM.make "/chain_id" + +let base_fee_per_gas = EVM.make "/base_fee_per_gas" + +let kernel_version = EVM.make "/kernel_version" + +let upgrade_nonce = EVM.make "/upgrade_nonce" + +module Accounts = struct + let accounts = EVM.make "/eth_accounts" + + let balance = "/balance" + + let nonce = "/nonce" + + let code = "/code" + + let storage = "/storage" + + let account (Address (Hex s)) = accounts ^ "/" ^ s + + let balance address = account address ^ balance + + let nonce address = account address ^ nonce + + let code address = account address ^ code + + let storage address index = account address ^ storage ^ "/" ^ index +end + +module Block = struct + type number = Current | Nth of Z.t + + let blocks = EVM.make "/blocks" + + let number = "/number" + + let by_hash (Block_hash (Hex hash)) = blocks ^ "/" ^ hash + + let current_number = blocks ^ "/current" ^ number +end + +module Indexes = struct + let indexes = EVM.make "/indexes" + + let blocks = "/blocks" + + let blocks = indexes ^ blocks + + let number_to_string = function + | Block.Current -> "current" + | Nth i -> Z.to_string i + + let block_by_number number = blocks ^ "/" ^ number_to_string number +end + +module Transaction_receipt = struct + let receipts = EVM.make "/transactions_receipts" + + let receipt (Hash (Hex tx_hash)) = receipts ^ "/" ^ tx_hash +end + +module Transaction_object = struct + let objects = EVM.make "/transactions_objects" + + let object_ (Hash (Hex tx_hash)) = objects ^ "/" ^ tx_hash +end diff --git a/etherlink/bin_evm_node/lib_prod/durable_storage_path.mli b/etherlink/bin_evm_node/lib_prod/durable_storage_path.mli new file mode 100644 index 0000000000000000000000000000000000000000..4fa978e08a0bf92de6f060b0ab72274e6d0350e6 --- /dev/null +++ b/etherlink/bin_evm_node/lib_prod/durable_storage_path.mli @@ -0,0 +1,63 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* Copyright (c) 2023 Marigold *) +(* Copyright (c) 2023 Functori *) +(* Copyright (c) 2023 Trilitech *) +(* *) +(*****************************************************************************) + +open Ethereum_types + +type path = string + +val chain_id : path + +val base_fee_per_gas : path + +val kernel_version : path + +val upgrade_nonce : path + +(** Paths related to accounts. *) +module Accounts : sig + (** Path to the account's balance. *) + val balance : address -> path + + (** Path to the account's nonce. *) + val nonce : address -> path + + (** Path to the account's code. *) + val code : address -> path + + (** Path to the account's storage at a given index. *) + val storage : address -> path -> path +end + +(** Paths related to blocks. *) +module Block : sig + (** Block number is either the current head or a specific height. *) + type number = Current | Nth of Z.t + + (** Path to the given block. *) + val by_hash : block_hash -> path + + (** Path to the current block number. *) + val current_number : path +end + +module Indexes : sig + (** Make the path to the indexed block hash. *) + val block_by_number : Block.number -> path +end + +module Transaction_receipt : sig + (** Path to the given transaction receipt. *) + val receipt : hash -> path +end + +module Transaction_object : sig + (** Path to the given transaction object. *) + val object_ : hash -> path +end diff --git a/etherlink/bin_evm_node/lib_prod/encodings/ethereum_types.ml b/etherlink/bin_evm_node/lib_prod/encodings/ethereum_types.ml index 36455a4cf36ad11085795d7a2288976b857fc29d..ab4b4bfe12c7b2a7f505644bdfbe270185d500b6 100644 --- a/etherlink/bin_evm_node/lib_prod/encodings/ethereum_types.ml +++ b/etherlink/bin_evm_node/lib_prod/encodings/ethereum_types.ml @@ -161,6 +161,22 @@ let decode_number bytes = Bytes.to_string bytes |> Z.of_bits |> quantity_of_z let decode_hash bytes = Hash (decode_hex bytes) +let pad_to_n_bytes_le bytes length = + let current_length = Bytes.length bytes in + if current_length >= length then bytes + else + let padding_length = length - current_length in + let padding = Bytes.make padding_length '\x00' in + Bytes.cat bytes padding + +let encode_u256_le (Qty n) = + let bits = Z.to_bits n |> Bytes.of_string in + pad_to_n_bytes_le bits 32 + +let encode_u16_le (Qty n) = + let bits = Z.to_bits n |> Bytes.of_string in + pad_to_n_bytes_le bits 2 + type transaction_log = { address : address; topics : hash list; @@ -971,10 +987,9 @@ let hash_raw_tx str = str |> Bytes.of_string |> Tezos_crypto.Hacl.Hash.Keccak_256.digest |> Bytes.to_string -(** [transaction_nonce raw_tx] returns the nonce of a given raw transaction. *) -let transaction_nonce raw_tx = +(** [transaction_nonce bytes] returns the nonce of a given raw transaction. *) +let transaction_nonce bytes = let open Result_syntax in - let bytes = hex_to_bytes raw_tx in if String.starts_with ~prefix:"01" bytes then (* eip 2930*) match bytes |> String.to_bytes |> Rlp.decode with @@ -999,11 +1014,10 @@ let transaction_nonce raw_tx = Qty nonce | _ -> tzfail (Rlp.Rlp_decoding_error "Expected a list of 9 elements") -(** [transaction_gas_price base_fee raw_tx] returns the maximum gas price the +(** [transaction_gas_price base_fee bytes] returns the maximum gas price the user can pay for the tx. *) -let transaction_gas_price base_fee raw_tx = +let transaction_gas_price base_fee bytes = let open Result_syntax in - let bytes = hex_to_bytes raw_tx in if String.starts_with ~prefix:"01" bytes then (* eip 2930*) match bytes |> String.to_bytes |> Rlp.decode with diff --git a/etherlink/bin_evm_node/lib_prod/filter_helpers.ml b/etherlink/bin_evm_node/lib_prod/filter_helpers.ml index 3255c4b58a561ccccf977244db4a8cc6a5b9ad37..3feab686e9bea67d758d456659bb9e56ac686b3f 100644 --- a/etherlink/bin_evm_node/lib_prod/filter_helpers.ml +++ b/etherlink/bin_evm_node/lib_prod/filter_helpers.ml @@ -48,7 +48,7 @@ type valid_filter = { } module Event = struct - let section = ["evm_node"; "prod"; "logs_filter"] + let section = ["evm_node"; "dev"; "logs_filter"] let incompatible_block_params = Internal_event.Simple.declare_0 @@ -94,7 +94,8 @@ end (** [height_from_param (module Rollup_node_rpc) from to_] returns the block height for params [from] and [to_] as a tuple. *) -let height_from_param (module Rollup_node_rpc : Rollup_node.S) from to_ = +let height_from_param (module Rollup_node_rpc : Services_backend_sig.S) from to_ + = let open Lwt_result_syntax in match (from, to_) with | Hash_param h1, Hash_param h2 -> return (h1, h2) @@ -116,8 +117,8 @@ let emit_and_return_none event arg = return_none (* Parses the [from_block] and [to_block] fields, as described before. *) -let validate_range log_filter_config (module Rollup_node_rpc : Rollup_node.S) - (filter : filter) = +let validate_range log_filter_config + (module Rollup_node_rpc : Services_backend_sig.S) (filter : filter) = let open Lwt_result_syntax in match filter with | {from_block = Some _; to_block = Some _; block_hash = Some _; _} -> @@ -165,7 +166,8 @@ let ( let*?? ) m f = (* Parsing a filter into a simpler representation, this is the input validation step *) -let validate_filter log_filter_config (module Rollup_node_rpc : Rollup_node.S) : +let validate_filter log_filter_config + (module Rollup_node_rpc : Services_backend_sig.S) : filter -> valid_filter option tzresult Lwt.t = fun filter -> let open Lwt_result_syntax in @@ -221,7 +223,7 @@ let filter_one_log : valid_filter -> transaction_log -> filter_changes option = else None (* Apply a filter on one transaction *) -let filter_one_tx (module Rollup_node_rpc : Rollup_node.S) : +let filter_one_tx (module Rollup_node_rpc : Services_backend_sig.S) : valid_filter -> hash -> filter_changes list option tzresult Lwt.t = fun filter tx_hash -> let open Lwt_result_syntax in @@ -234,7 +236,7 @@ let filter_one_tx (module Rollup_node_rpc : Rollup_node.S) : | None -> emit_and_return_none Event.receipt_not_found tx_hash (* Apply a filter on one block *) -let filter_one_block (module Rollup_node_rpc : Rollup_node.S) : +let filter_one_block (module Rollup_node_rpc : Services_backend_sig.S) : valid_filter -> Z.t -> filter_changes list option tzresult Lwt.t = fun filter block_number -> let open Lwt_result_syntax in @@ -292,7 +294,7 @@ let split_in_chunks ~chunk_size ~base ~length = performace and not exceeding the bound in number of logs. *) let get_logs (log_filter_config : Configuration.log_filter_config) - (module Rollup_node_rpc : Rollup_node.S) filter = + (module Rollup_node_rpc : Services_backend_sig.S) filter = let open Lwt_result_syntax in let+ logs = let*?? filter = diff --git a/etherlink/bin_evm_node/lib_prod/helpers.ml b/etherlink/bin_evm_node/lib_prod/helpers.ml new file mode 100644 index 0000000000000000000000000000000000000000..e8cd7c674efdd6a5581ca751f712ceed5adb493e --- /dev/null +++ b/etherlink/bin_evm_node/lib_prod/helpers.ml @@ -0,0 +1,17 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(*****************************************************************************) + +let now () = + let now = Ptime_clock.now () in + let now = Ptime.to_rfc3339 now in + Time.Protocol.of_notation_exn now + +let timestamp_to_bytes timestamp = + let seconds = Time.Protocol.to_seconds timestamp in + let buffer = Bytes.make 8 '\000' in + Bytes.set_int64_le buffer 0 seconds ; + buffer diff --git a/etherlink/bin_evm_node/lib_prod/helpers.mli b/etherlink/bin_evm_node/lib_prod/helpers.mli new file mode 100644 index 0000000000000000000000000000000000000000..468b2281a100834a6ea7739c47f42729001eadd3 --- /dev/null +++ b/etherlink/bin_evm_node/lib_prod/helpers.mli @@ -0,0 +1,13 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(*****************************************************************************) + +(** [now ()] returns the current time. *) +val now : unit -> Time.Protocol.t + +(** [timestamp_to_bytes timestamp] transforms the timestamp to bytes + compatible with the kernel. *) +val timestamp_to_bytes : Time.Protocol.t -> bytes diff --git a/etherlink/bin_evm_node/lib_prod/publisher.ml b/etherlink/bin_evm_node/lib_prod/publisher.ml new file mode 100644 index 0000000000000000000000000000000000000000..d56b3d894ffda79c7f091a4b66755b877c38a66b --- /dev/null +++ b/etherlink/bin_evm_node/lib_prod/publisher.ml @@ -0,0 +1,47 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(*****************************************************************************) + +open Ethereum_types + +module type TxEncoder = sig + val encode_transaction : + smart_rollup_address:string -> + transaction:string -> + (hash * string list) tzresult +end + +module type Publisher = sig + val publish_messages : + timestamp:Time.Protocol.t -> + smart_rollup_address:string -> + messages:string list -> + unit tzresult Lwt.t +end + +module Make (TxEncoder : TxEncoder) (Publisher : Publisher) = struct + let inject_raw_transactions ~timestamp ~smart_rollup_address ~transactions = + let open Lwt_result_syntax in + let* rev_tx_hashes, to_publish = + List.fold_left_es + (fun (tx_hashes, to_publish) tx_raw -> + let*? tx_hash, messages = + TxEncoder.encode_transaction + ~smart_rollup_address + ~transaction:tx_raw + in + return (tx_hash :: tx_hashes, to_publish @ messages)) + ([], []) + transactions + in + let* () = + Publisher.publish_messages + ~timestamp + ~smart_rollup_address + ~messages:to_publish + in + return (List.rev rev_tx_hashes) +end diff --git a/etherlink/bin_evm_node/lib_prod/rollup_node.ml b/etherlink/bin_evm_node/lib_prod/rollup_node.ml index e8e29771a71e1450a843e20648212957106e9b34..214b90a7de7123181a430c11d68b50147b717265 100644 --- a/etherlink/bin_evm_node/lib_prod/rollup_node.ml +++ b/etherlink/bin_evm_node/lib_prod/rollup_node.ml @@ -26,600 +26,47 @@ (* *) (*****************************************************************************) -open Ethereum_types open Rollup_node_services +open Transaction_format -(* The hard limit is 4096 but it needs to add the external message tag. *) -let max_input_size = 4095 - -let smart_rollup_address_size = 20 - -let transaction_tag_size = 1 - -let framing_protocol_tag_size = 1 - -type transaction = - | Simple of string - | NewChunked of (string * int * string) - | Chunk of string - -let encode_transaction ~smart_rollup_address kind = - let data = - match kind with - | Simple data -> "\000" ^ data - | NewChunked (tx_hash, len, all_chunk_hashes) -> - let number_of_chunks_bytes = Ethereum_types.u16_to_bytes len in - "\001" ^ tx_hash ^ number_of_chunks_bytes ^ all_chunk_hashes - | Chunk data -> "\002" ^ data - in - "\000" ^ smart_rollup_address ^ data - -let chunk_transaction ~tx_hash ~tx_raw = - let open Result_syntax in - let size_per_chunk = - max_input_size - framing_protocol_tag_size - smart_rollup_address_size - - transaction_tag_size - 2 (* Index as u16 *) - - (Ethereum_types.transaction_hash_size * 2) - in - let* chunks = String.chunk_bytes size_per_chunk (Bytes.of_string tx_raw) in - let all_chunk_hashes, chunks = - List.fold_left_i - (fun i (all_chunk_hashes, chunks) chunk -> - let chunk_hash = Ethereum_types.hash_raw_tx chunk in - let all_chunk_hashes = all_chunk_hashes ^ chunk_hash in - let chunk = - Chunk (tx_hash ^ Ethereum_types.u16_to_bytes i ^ chunk_hash ^ chunk) - in - (all_chunk_hashes, chunk :: chunks)) - ("", []) - chunks - in - let new_chunk_transaction = - NewChunked (tx_hash, List.length chunks, all_chunk_hashes) - in - return (tx_hash, new_chunk_transaction :: chunks) - -let make_evm_inbox_transactions tx_raw = - let open Result_syntax in - let tx_raw = Ethereum_types.hex_to_bytes tx_raw in - (* Maximum size describes the maximum size of [tx_raw] to fit - in a simple transaction. *) - let maximum_size = - max_input_size - framing_protocol_tag_size - smart_rollup_address_size - - transaction_tag_size - Ethereum_types.transaction_hash_size - in - let tx_hash = Ethereum_types.hash_raw_tx tx_raw in - if String.length tx_raw <= maximum_size then - (* Simple transaction, fits in a single input. *) - let tx = Simple (tx_hash ^ tx_raw) in - return (tx_hash, [tx]) - else chunk_transaction ~tx_hash ~tx_raw - -let make_encoded_messages ~smart_rollup_address tx_raw = - let open Result_syntax in - let* tx_hash, messages = make_evm_inbox_transactions tx_raw in - let messages = - List.map - (fun x -> - x - |> encode_transaction ~smart_rollup_address - |> Hex.of_string |> Hex.show) - messages - in - return (tx_hash, messages) - -module Durable_storage_path = struct - module EVM = struct - let root = "/evm" - - let make s = root ^ s - end - - let chain_id = EVM.make "/chain_id" - - let base_fee_per_gas = EVM.make "/base_fee_per_gas" - - let kernel_version = EVM.make "/kernel_version" - - module Accounts = struct - let accounts = EVM.make "/eth_accounts" - - let balance = "/balance" - - let nonce = "/nonce" - - let code = "/code" - - let storage = "/storage" - - let account (Address (Hex s)) = accounts ^ "/" ^ s - - let balance address = account address ^ balance - - let nonce address = account address ^ nonce - - let code address = account address ^ code - - let storage address index = account address ^ storage ^ "/" ^ index - end - - module Block = struct - type number = Current | Nth of Z.t - - let blocks = EVM.make "/blocks" - - let hash = "/hash" - - let number = "/number" - - let by_hash (Block_hash (Hex hash)) = blocks ^ "/" ^ hash - - let current_number = blocks ^ "/current" ^ number - - let _current_hash = blocks ^ "/current" ^ hash - end - - module Indexes = struct - let indexes = EVM.make "/indexes" - - let blocks = "/blocks" - - let blocks = indexes ^ blocks - - let number_to_string = function - | Block.Current -> "current" - | Nth i -> Z.to_string i - - let blocks_by_number number = blocks ^ "/" ^ number_to_string number - end - - module Transaction_receipt = struct - let receipts = EVM.make "/transactions_receipts" - - let receipt tx_hash = receipts ^ "/" ^ tx_hash +module MakeBackend (Base : sig + val base : Uri.t +end) : Services_backend_sig.Backend = struct + module READER = struct + let read path = + call_service ~base:Base.base durable_state_value () {key = path} () end - module Transaction_object = struct - let objects = EVM.make "/transactions_objects" - - let object_ tx_hash = objects ^ "/" ^ tx_hash + module TxEncoder = struct + let encode_transaction ~smart_rollup_address ~transaction = + make_encoded_messages ~smart_rollup_address transaction end -end -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 smart_rollup_address base = - let open Lwt_result_syntax in - let*! answer = - call_service - ~base - ~media_types:[Media_type.octet_stream] - smart_rollup_address - () - () - () - in - match answer with - | Ok address -> return (Bytes.to_string address) - | Error tztrace -> - failwith - "Failed to communicate with %a, because %a" - Uri.pp - base - pp_print_trace - tztrace - -let balance base address = - let open Lwt_result_syntax in - let key = Durable_storage_path.Accounts.balance address in - let+ answer = call_service ~base durable_state_value () {key} () in - match answer with - | Some bytes -> - Bytes.to_string bytes |> Z.of_bits |> Ethereum_types.quantity_of_z - | None -> Ethereum_types.Qty Z.zero - -let nonce base address = - let open Lwt_result_syntax in - let key = Durable_storage_path.Accounts.nonce address in - let+ answer = call_service ~base durable_state_value () {key} () in - answer - |> Option.map (fun bytes -> - bytes |> Bytes.to_string |> Z.of_bits |> Ethereum_types.quantity_of_z) - -let code base address = - let open Lwt_result_syntax in - let key = Durable_storage_path.Accounts.code address in - let+ answer = call_service ~base durable_state_value () {key} () in - match answer with - | Some bytes -> - bytes |> Hex.of_bytes |> Hex.show |> Ethereum_types.hex_of_string - | None -> Ethereum_types.Hex "" - -let inject_raw_transaction base tx = - let open Lwt_result_syntax in - (* The injection's service returns a notion of L2 message hash (defined - by the rollup node) used to track the message's injection in the batcher. - We do not wish to follow the message's inclusion, and thus, ignore - the resulted hash. *) - let* _answer = call_service ~base batcher_injection () () [tx] in - return_unit - -let inject_raw_transaction base ~smart_rollup_address tx_raw = - let open Lwt_result_syntax in - let*? tx_hash, messages = - make_encoded_messages ~smart_rollup_address tx_raw - in - let* () = List.iter_es (inject_raw_transaction base) messages in - return (Ethereum_types.Hash Hex.(of_string tx_hash |> show |> hex_of_string)) - -exception Invalid_block_structure of string - -exception Invalid_block_index of Z.t - -let block_number base n = - let open Lwt_result_syntax in - match n with - (* This avoids an unecessary service call in case we ask a block's number - with an already expected/known block number [n]. *) - | Durable_storage_path.Block.Nth i -> return @@ Ethereum_types.Block_height i - | Durable_storage_path.Block.Current -> ( - let key = Durable_storage_path.Block.current_number in - let+ answer = call_service ~base durable_state_value () {key} () in - match answer with - | Some bytes -> - Ethereum_types.Block_height (Bytes.to_string bytes |> Z.of_bits) - | None -> - raise - @@ Invalid_block_structure - "Unexpected [None] value for [current_number]'s [answer]") - -let current_block_number base () = - block_number base Durable_storage_path.Block.Current - -let un_qty (Qty z) = z - -let transaction_receipt base (Hash (Hex tx_hash)) = - let open Lwt_result_syntax in - (* We use a mock block hash to decode the rest of the receipt, - so that we can get the number to query for the actual block - hash. *) - let mock_block_hash = Block_hash (Hex (String.make 64 'a')) in - let* opt_receipt = - inspect_durable_and_decode_opt - base - (Durable_storage_path.Transaction_receipt.receipt tx_hash) - (Ethereum_types.transaction_receipt_from_rlp mock_block_hash) - in - match opt_receipt with - | Some temp_receipt -> - let+ blockHash = - inspect_durable_and_decode - base - (Durable_storage_path.Indexes.blocks_by_number - (Nth (un_qty temp_receipt.blockNumber))) - decode_block_hash - in - let logs = - List.map - (fun (log : transaction_log) -> {log with blockHash = Some blockHash}) - temp_receipt.logs + module Publisher = struct + let publish_messages ~timestamp:_ ~smart_rollup_address:_ ~messages = + let open Lwt_result_syntax in + (* The injection's service returns a notion of L2 message hash (defined + by the rollup node) used to track the message's injection in the batcher. + We do not wish to follow the message's inclusion, and thus, ignore + the resulted hash. *) + let* _answer = + call_service ~base:Base.base batcher_injection () () messages in - Some {temp_receipt with blockHash; logs} - | None -> return_none - -let transaction_object base (Hash (Hex tx_hash)) = - let open Lwt_result_syntax in - (* We use a mock block hash to decode the rest of the receipt, - so that we can get the number to query for the actual block - hash. *) - let mock_block_hash = Block_hash (Hex (String.make 64 'a')) in - let* opt_object = - inspect_durable_and_decode_opt - base - (Durable_storage_path.Transaction_object.object_ tx_hash) - (Ethereum_types.transaction_object_from_rlp mock_block_hash) - in - match opt_object with - | Some temp_object -> - let+ blockHash = - inspect_durable_and_decode - base - (Durable_storage_path.Indexes.blocks_by_number - (Nth (un_qty temp_object.blockNumber))) - decode_block_hash - in - Some {temp_object with blockHash} - | None -> return_none - -let transaction_object_with_block_hash base block_hash (Hash (Hex tx_hash)) = - inspect_durable_and_decode_opt - base - (Durable_storage_path.Transaction_object.object_ tx_hash) - (Ethereum_types.transaction_object_from_rlp block_hash) - -let full_transactions block_hash transactions base = - let open Lwt_result_syntax in - match transactions with - | TxHash hashes -> - let+ objects = - List.filter_map_es - (transaction_object_with_block_hash base block_hash) - hashes - in - TxFull objects - | TxFull _ -> return transactions - -let populate_tx_objects ~full_transaction_object base block = - let open Lwt_result_syntax in - if full_transaction_object then - let* transactions = full_transactions block.hash block.transactions base in - return {block with transactions} - else return block + return_unit + end -let blocks_by_number ~full_transaction_object ~number base = - let open Lwt_result_syntax in - let* (Ethereum_types.Block_height level) = block_number base number in - let* block_hash_opt = - inspect_durable_and_decode_opt - base - (Durable_storage_path.Indexes.blocks_by_number (Nth level)) - decode_block_hash - in - match block_hash_opt with - | None -> raise @@ Invalid_block_index level - | Some block_hash -> ( - let* block_opt = - inspect_durable_and_decode_opt - base - (Durable_storage_path.Block.by_hash block_hash) - Ethereum_types.block_from_rlp + module SimulatorBackend = struct + let simulate_and_read ~input = + let open Lwt_result_syntax in + let* json = call_service ~base:Base.base simulation () () input in + let eval_result = + Data_encoding.Json.destruct Simulation.Encodings.eval_result json in - match block_opt with - | None -> raise @@ Invalid_block_structure "Couldn't decode bytes" - | Some block -> populate_tx_objects ~full_transaction_object base block) - -let current_block base ~full_transaction_object = - blocks_by_number - ~full_transaction_object - ~number:Durable_storage_path.Block.Current - base - -let nth_block base ~full_transaction_object n = - blocks_by_number - ~full_transaction_object - ~number:Durable_storage_path.Block.(Nth n) - base - -let block_by_hash base ~full_transaction_object block_hash = - let open Lwt_result_syntax in - let* block_opt = - inspect_durable_and_decode_opt - base - (Durable_storage_path.Block.by_hash block_hash) - Ethereum_types.block_from_rlp - in - match block_opt with - | None -> raise @@ Invalid_block_structure "Couldn't decode bytes" - | Some block -> populate_tx_objects ~full_transaction_object base block - -let txpool _ () = - Lwt.return_ok {pending = AddressMap.empty; queued = AddressMap.empty} - -let chain_id base () = - inspect_durable_and_decode base Durable_storage_path.chain_id decode_number - -let base_fee_per_gas base () = - inspect_durable_and_decode - base - Durable_storage_path.base_fee_per_gas - decode_number - -let kernel_version base () = - inspect_durable_and_decode - base - Durable_storage_path.kernel_version - Bytes.to_string - -let simulate_call base call = - let open Lwt_result_syntax in - let*? messages = Simulation.encode call in - let insight_requests = - [ - Simulation.Encodings.Durable_storage_key ["evm"; "simulation_result"]; - (* TODO: https://gitlab.com/tezos/tezos/-/issues/5900 - for now the status is not used but it should be for error handling *) - Simulation.Encodings.Durable_storage_key ["evm"; "simulation_status"]; - ] - in - let* r = - call_service - ~base - simulation - () - () - { - messages; - reveal_pages = None; - insight_requests; - log_kernel_debug_file = Some "simulate_call"; - } - in - Simulation.call_result r - -let estimate_gas base call = - let open Lwt_result_syntax in - let*? messages = Simulation.encode call in - let insight_requests = - [ - Simulation.Encodings.Durable_storage_key ["evm"; "simulation_gas"]; - (* TODO: https://gitlab.com/tezos/tezos/-/issues/5900 - for now the status is not used but it should be for error handling *) - Simulation.Encodings.Durable_storage_key ["evm"; "simulation_status"]; - ] - in - let* r = - call_service - ~base - simulation - () - () - { - messages; - reveal_pages = None; - insight_requests; - log_kernel_debug_file = Some "estimate_gas"; - } - in - Simulation.gas_estimation r - -let is_tx_valid base (Hex tx_raw) = - let open Lwt_result_syntax in - let*? messages = Simulation.encode_tx tx_raw in - let insight_requests = - [ - Simulation.Encodings.Durable_storage_key ["evm"; "simulation_result"]; - Simulation.Encodings.Durable_storage_key ["evm"; "simulation_status"]; - ] - in - let* r = - call_service - ~base - simulation - () - () - { - messages; - reveal_pages = None; - insight_requests; - log_kernel_debug_file = Some "tx_validity"; - } - in - Simulation.is_tx_valid r - -let storage_at base address (Qty pos) = - let open Lwt_result_syntax in - let pad32left0 s = - let open Ethereum_types in - (* Strip 0x *) - let (Hex s) = hex_of_string s in - let len = String.length s in - (* This is a Hex string of 32 bytes, therefore the length is 64 *) - String.make (64 - len) '0' ^ s - in - let index = Z.format "#x" pos |> pad32left0 in - let key = Durable_storage_path.Accounts.storage address index in - let+ answer = call_service ~base durable_state_value () {key} () in - match answer with - | Some bytes -> - Bytes.to_string bytes |> Hex.of_string |> Hex.show - |> Ethereum_types.hex_of_string - | None -> Ethereum_types.Hex (pad32left0 "0") - -module type S = sig - val smart_rollup_address : string tzresult Lwt.t - - val balance : Ethereum_types.address -> Ethereum_types.quantity tzresult Lwt.t - - val nonce : - Ethereum_types.address -> Ethereum_types.quantity option tzresult Lwt.t - - val code : Ethereum_types.address -> Ethereum_types.hex tzresult Lwt.t - - val inject_raw_transaction : - smart_rollup_address:string -> hex -> hash tzresult Lwt.t - - val current_block : - full_transaction_object:bool -> Ethereum_types.block tzresult Lwt.t - - val current_block_number : unit -> Ethereum_types.block_height tzresult Lwt.t - - val nth_block : - full_transaction_object:bool -> Z.t -> Ethereum_types.block tzresult Lwt.t - - val block_by_hash : - full_transaction_object:bool -> - Ethereum_types.block_hash -> - Ethereum_types.block tzresult Lwt.t - - val transaction_receipt : - Ethereum_types.hash -> - Ethereum_types.transaction_receipt option tzresult Lwt.t - - val transaction_object : - Ethereum_types.hash -> - Ethereum_types.transaction_object option tzresult Lwt.t - - val txpool : unit -> Ethereum_types.txpool tzresult Lwt.t - - val chain_id : unit -> Ethereum_types.quantity tzresult Lwt.t - - val base_fee_per_gas : unit -> Ethereum_types.quantity tzresult Lwt.t - - val kernel_version : unit -> string tzresult Lwt.t - - val simulate_call : - Ethereum_types.call -> (Ethereum_types.hash, unit) result tzresult Lwt.t - - val estimate_gas : - Ethereum_types.call -> Ethereum_types.quantity tzresult Lwt.t - - val is_tx_valid : - Ethereum_types.hex -> (Ethereum_types.address, string) result tzresult Lwt.t - - val storage_at : - Ethereum_types.address -> - Ethereum_types.quantity -> - Ethereum_types.hex tzresult Lwt.t + return eval_result.insights + end end module Make (Base : sig val base : Uri.t -end) : S = struct - let smart_rollup_address = smart_rollup_address Base.base - - let balance = balance Base.base - - let nonce = nonce Base.base - - let code = code Base.base - - let inject_raw_transaction = inject_raw_transaction Base.base - - let current_block = current_block Base.base - - let current_block_number = current_block_number Base.base - - let nth_block = nth_block Base.base - - let block_by_hash = block_by_hash Base.base - - let transaction_receipt = transaction_receipt Base.base - - let transaction_object = transaction_object Base.base - - let txpool = txpool Base.base - - let chain_id = chain_id Base.base - - let base_fee_per_gas = base_fee_per_gas Base.base - - let kernel_version = kernel_version Base.base - - let simulate_call = simulate_call Base.base - - let estimate_gas = estimate_gas Base.base - - let is_tx_valid = is_tx_valid Base.base - - let storage_at = storage_at Base.base -end +end) = + Services_backend_sig.Make (MakeBackend (Base)) diff --git a/etherlink/bin_evm_node/lib_prod/rollup_node.mli b/etherlink/bin_evm_node/lib_prod/rollup_node.mli index d34102fef523da264700e2fc2d46d6b4f72ca18c..f3a43667aeff6f0721811035f1a3188e99b0a08f 100644 --- a/etherlink/bin_evm_node/lib_prod/rollup_node.mli +++ b/etherlink/bin_evm_node/lib_prod/rollup_node.mli @@ -25,127 +25,10 @@ (* *) (*****************************************************************************) -(** [make_encoded_messages ~smart_rollup_address raw_tx] returns the - hash of the transaction, and a list of transactions to include in the inbox. - - [smart_rollup_address] is encoded on 20 bytes - - [raw_tx] is an ethereum transaction in hex format (without the 0x prefix). - - All messages go through the same encoding, but will only be chunked if - necessary. *) -val make_encoded_messages : - smart_rollup_address:string -> - Ethereum_types.hex -> - (string * string list, 'a) result - -(** List of services supported to communicate with a rollup node. *) -module type S = sig - (** [smart_rollup_address] asks for the smart rollup node's address. *) - val smart_rollup_address : string tzresult Lwt.t - - (** [balance address] returns the [address]'s balance. *) - val balance : Ethereum_types.address -> Ethereum_types.quantity tzresult Lwt.t - - (** [nonce address] returns the [address]'s nonce. *) - val nonce : - Ethereum_types.address -> Ethereum_types.quantity option tzresult Lwt.t - - (** [code address] returns the [address]'s code. *) - val code : Ethereum_types.address -> Ethereum_types.hex tzresult Lwt.t - - (** [inject_raw_transaction ~smart_rollup_address tx_raw] crafts the hash of [tx_raw] and sends to - the injector a message consisting of: - - First 20 bytes: [smart_rollup_address]. - - Following 32 bytes: crafted transaction hash. - - Remaining bytes: [tx_raw] in binary format. - *) - val inject_raw_transaction : - smart_rollup_address:string -> - Ethereum_types.hex -> - Ethereum_types.hash tzresult Lwt.t - - (** [current_block ~full_transaction_object] returns the most recent - processed and stored block. - - If [full_transaction_object] is [true], returns the transaction objects, - the transactions hashes otherwise. - *) - val current_block : - full_transaction_object:bool -> Ethereum_types.block tzresult Lwt.t - - (** [current_block_number ()] returns the most recent processed and stored block - number. *) - val current_block_number : unit -> Ethereum_types.block_height tzresult Lwt.t - - (** [nth_block ~full_transaction_object n] returns the [n]th processed and - stored block. - - If [full_transaction_object] is [true], returns the transaction objects, - the transactions hashes otherwise. - *) - val nth_block : - full_transaction_object:bool -> Z.t -> Ethereum_types.block tzresult Lwt.t - - (** [block_by_hash ~full_transaction_object hash] returns the block with the - given [hash]. - - If [full_transaction_object] is [true], returns the transaction objects, - the transactions hashes otherwise. - *) - val block_by_hash : - full_transaction_object:bool -> - Ethereum_types.block_hash -> - Ethereum_types.block tzresult Lwt.t - - (** [transaction_receipt tx_hash] returns the receipt of [tx_hash]. *) - val transaction_receipt : - Ethereum_types.hash -> - Ethereum_types.transaction_receipt option 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 - - (** [txpool ()] returns the pending and queued transactions. *) - val txpool : unit -> Ethereum_types.txpool tzresult Lwt.t - - (** [chain_id ()] returns chain id defined by the rollup. *) - val chain_id : unit -> Ethereum_types.quantity tzresult Lwt.t - - (** [base_fee_per_gas ()] returns base fee defined by the rollup. *) - val base_fee_per_gas : unit -> Ethereum_types.quantity tzresult Lwt.t - - (** [kernel_version ()] returns the internal kernel version (i.e the commit hash where - the kernel was compiled). *) - val kernel_version : unit -> string tzresult Lwt.t - - (** [simulate_call call_info] asks the rollup to simulate a call, and returns the - result. *) - val simulate_call : - Ethereum_types.call -> (Ethereum_types.hash, unit) result tzresult Lwt.t - - (** [estimate_gas call_info] asks the rollup to simulate a call, and returns the - gas used to execute the call. *) - val estimate_gas : - Ethereum_types.call -> Ethereum_types.quantity tzresult Lwt.t - - (** [is_tx_valid tx_raw] checks if the transaction is valid. Checks if the nonce is correct - and returns the associated public key of transaction. *) - val is_tx_valid : - Ethereum_types.hex -> (Ethereum_types.address, string) result tzresult Lwt.t - - (** [storage_at address pos] returns the value at index [pos] of the - account [address]'s storage. *) - val storage_at : - Ethereum_types.address -> - Ethereum_types.quantity -> - Ethereum_types.hex tzresult Lwt.t -end - -(** Instantiate a module of type {!S} that communicates with a rollup +(** Instantiate a module of type {!Services_backend_sig.S} that communicates with a rollup node endpoint given by [Base.base]. *) module Make : functor (Base : sig val base : Uri.t end) - -> S + -> Services_backend_sig.S diff --git a/etherlink/bin_evm_node/lib_prod/rollup_node_services.ml b/etherlink/bin_evm_node/lib_prod/rollup_node_services.ml index b3892e5e00f8c404daed709df36abe46d1336685..bc671e4381d05cee2de194f451b673c0779f1a55 100644 --- a/etherlink/bin_evm_node/lib_prod/rollup_node_services.ml +++ b/etherlink/bin_evm_node/lib_prod/rollup_node_services.ml @@ -11,6 +11,35 @@ open Tezos_rpc open Path +type error += Lost_connection + +let () = + let description = + "The EVM node is no longer able to communicate with the rollup node, the \ + communication was lost" + in + register_error_kind + `Temporary + ~id:"evm_node_lost_connection" + ~title:"Lost connection with rollup node" + ~description + ~pp:(fun ppf () -> Format.fprintf ppf "%s" description) + Data_encoding.unit + (function Lost_connection -> Some () | _ -> None) + (fun () -> Lost_connection) + +let is_connection_error trace = + TzTrace.fold + (fun yes error -> + yes + || + match error with + | RPC_client_errors.(Request_failed {error = Connection_failed _; _}) -> + true + | _ -> false) + false + trace + let smart_rollup_address : ([`GET], unit, unit, unit, unit, bytes) Service.service = Service.get_service @@ -46,7 +75,7 @@ let batcher_injection : ~query:Tezos_rpc.Query.empty ~input: Data_encoding.( - def "messages" ~description:"Messages to inject" (list string)) + def "messages" ~description:"Messages to inject" (list (string' Hex))) ~output: Data_encoding.( def @@ -72,5 +101,55 @@ let simulation : ~output:Data_encoding.Json.encoding (open_root / "global" / "block" / "head" / "simulate") -let call_service ~base ?(media_types = Media_type.all_media_types) = - Tezos_rpc_http_client_unix.RPC_client_unix.call_service media_types ~base +let global_block_watcher : + ([`GET], unit, unit, unit, unit, Data_encoding.json) Service.service = + Tezos_rpc.Service.get_service + ~description:"Monitor and streaming the L2 blocks" + ~query:Tezos_rpc.Query.empty + ~output:Data_encoding.json + (open_root / "global" / "monitor_blocks") + +let call_service ~base ?(media_types = Media_type.all_media_types) a b c d = + let open Lwt_result_syntax in + let*! res = + Tezos_rpc_http_client_unix.RPC_client_unix.call_service + media_types + ~base + a + b + c + d + in + match res with + | Ok res -> return res + | Error trace when is_connection_error trace -> fail (Lost_connection :: trace) + | Error trace -> fail trace + +let publish : + rollup_node_endpoint:Uri.t -> + [< `External of string] list -> + unit tzresult Lwt.t = + fun ~rollup_node_endpoint inputs -> + let open Lwt_result_syntax in + let inputs = List.map (function `External s -> s) inputs in + let* _answer = + call_service ~base:rollup_node_endpoint batcher_injection () () inputs + in + return_unit + +(** [smart_rollup_address base] asks for the smart rollup node's + address, using the endpoint [base]. *) +let smart_rollup_address base = + let open Lwt_result_syntax in + let*! answer = + call_service + ~base + ~media_types:[Media_type.octet_stream] + smart_rollup_address + () + () + () + in + match answer with + | Ok address -> return (Bytes.to_string address) + | Error trace -> fail trace diff --git a/etherlink/bin_evm_node/lib_prod/rpc_encodings.ml b/etherlink/bin_evm_node/lib_prod/rpc_encodings.ml index f09c90e1fe90799330b615eb17d2c38da57960de..eb473293e71705215c10dea978f45f2d260ad996 100644 --- a/etherlink/bin_evm_node/lib_prod/rpc_encodings.ml +++ b/etherlink/bin_evm_node/lib_prod/rpc_encodings.ml @@ -54,21 +54,21 @@ module JSONRPC = struct type id = id_repr option - type 'params request = { + type request = { method_ : string; - parameters : 'params option; + parameters : Data_encoding.json option; id : id; } - let request_encoding method_ parameters_encoding = + let request_encoding = Data_encoding.( conv - (fun {parameters; id; _} -> ((), (), parameters, id)) - (fun ((), (), parameters, id) -> {method_; parameters; id}) + (fun {parameters; id; method_; _} -> ((), method_, parameters, id)) + (fun ((), method_, parameters, id) -> {method_; parameters; id}) (obj4 (req "jsonrpc" (constant version)) - (req "method" (constant method_)) - (opt "params" parameters_encoding) + (req "method" string) + (opt "params" Data_encoding.json) (opt "id" id_repr_encoding))) type 'data error = {code : int; message : string; data : 'data option} @@ -83,12 +83,11 @@ module JSONRPC = struct (req "message" string) (opt "data" data_encoding))) - type ('result, 'data_error) response = { - value : ('result, 'data_error error) result; - id : id; - } + type value = (Data_encoding.json, Data_encoding.json error) result + + type response = {value : value; id : id} - let response_encoding result_encoding error_data_encoding = + let response_encoding = Data_encoding.( conv (fun {value; id} -> @@ -108,15 +107,11 @@ module JSONRPC = struct {value; id}) (obj4 (req "jsonrpc" (constant version)) - (opt "result" result_encoding) - (opt "error" (error_encoding error_data_encoding)) + (opt "result" Data_encoding.json) + (opt "error" (error_encoding Data_encoding.json)) (req "id" (option id_repr_encoding)))) end -type 'method_ input = .. - -type 'method_ output = .. - module Error = struct type t = unit @@ -125,9 +120,9 @@ end type 'result rpc_result = ('result, Error.t JSONRPC.error) result -module type METHOD_DEF = sig - type method_ +type ('input, 'output) method_ = .. +module type METHOD = sig val method_ : string type input @@ -137,66 +132,11 @@ module type METHOD_DEF = sig val input_encoding : input Data_encoding.t val output_encoding : output Data_encoding.t -end - -module type METHOD = sig - type method_ - - type m_input - - type m_output - - (* The parameters MAY be omitted. See JSONRPC Specification. *) - type 'method_ input += Input : m_input option -> method_ input - - type 'method_ output += Output : m_output rpc_result -> method_ output - - val method_ : string - - val request_encoding : m_input JSONRPC.request Data_encoding.t - - val request : m_input option -> JSONRPC.id -> m_input JSONRPC.request - val response_encoding : (m_output, Error.t) JSONRPC.response Data_encoding.t - - val response : - (m_output, Error.t JSONRPC.error) result -> - JSONRPC.id -> - (m_output, Error.t) JSONRPC.response - - val response_ok : - m_output -> JSONRPC.id -> (m_output, Error.t) JSONRPC.response + type ('input, 'output) method_ += Method : (input, output) method_ end -module MethodMaker (M : METHOD_DEF) : - METHOD with type m_input = M.input and type m_output = M.output = struct - type m_input = M.input - - type m_output = M.output - - type method_ = M.method_ - - type 'a input += Input : m_input option -> method_ input - - type 'a output += Output : m_output rpc_result -> method_ output - - let method_ = M.method_ - - let request_encoding = JSONRPC.request_encoding M.method_ M.input_encoding - - let request parameters id = JSONRPC.{method_; parameters; id} - - let response_encoding = - JSONRPC.response_encoding M.output_encoding Error.encoding - - let response value id = JSONRPC.{value; id} - - let response_ok result id = response (Ok result) id -end - -module Kernel_version = MethodMaker (struct - type method_ - +module Kernel_version = struct type input = unit type output = string @@ -206,11 +146,11 @@ module Kernel_version = MethodMaker (struct let output_encoding = Data_encoding.string let method_ = "tez_kernelVersion" -end) -module Network_id = MethodMaker (struct - type method_ + type ('input, 'output) method_ += Method : (input, output) method_ +end +module Network_id = struct type input = unit type output = string @@ -220,11 +160,11 @@ module Network_id = MethodMaker (struct let output_encoding = Data_encoding.string let method_ = "net_version" -end) -module Chain_id = MethodMaker (struct - type method_ + type ('input, 'output) method_ += Method : (input, output) method_ +end +module Chain_id = struct type input = unit type output = Ethereum_types.quantity @@ -234,11 +174,11 @@ module Chain_id = MethodMaker (struct let output_encoding = Ethereum_types.quantity_encoding let method_ = "eth_chainId" -end) -module Accounts = MethodMaker (struct - type method_ + type ('input, 'output) method_ += Method : (input, output) method_ +end +module Accounts = struct type input = unit type output = Ethereum_types.address list @@ -248,12 +188,12 @@ module Accounts = MethodMaker (struct let output_encoding = Data_encoding.list Ethereum_types.address_encoding let method_ = "eth_accounts" -end) -module Get_balance = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Get_balance = struct + open Ethereum_types type input = address * block_param @@ -264,12 +204,12 @@ module Get_balance = MethodMaker (struct let output_encoding = quantity_encoding let method_ = "eth_getBalance" -end) -module Get_storage_at = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Get_storage_at = struct + open Ethereum_types type input = address * quantity * block_param @@ -281,12 +221,12 @@ module Get_storage_at = MethodMaker (struct let output_encoding = hex_encoding let method_ = "eth_getStorageAt" -end) -module Block_number = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Block_number = struct + open Ethereum_types type input = unit @@ -297,12 +237,12 @@ module Block_number = MethodMaker (struct let output_encoding = block_height_encoding let method_ = "eth_blockNumber" -end) -module Get_block_by_number = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Get_block_by_number = struct + open Ethereum_types type input = block_param * bool @@ -314,12 +254,12 @@ module Get_block_by_number = MethodMaker (struct let output_encoding = block_encoding let method_ = "eth_getBlockByNumber" -end) -module Get_block_by_hash = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Get_block_by_hash = struct + open Ethereum_types type input = block_hash * bool @@ -330,12 +270,12 @@ module Get_block_by_hash = MethodMaker (struct let output_encoding = block_encoding let method_ = "eth_getBlockByHash" -end) -module Get_code = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Get_code = struct + open Ethereum_types type input = address * block_param @@ -346,12 +286,12 @@ module Get_code = MethodMaker (struct let output_encoding = hex_encoding let method_ = "eth_getCode" -end) -module Gas_price = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Gas_price = struct + open Ethereum_types type input = unit @@ -362,12 +302,12 @@ module Gas_price = MethodMaker (struct let output_encoding = quantity_encoding let method_ = "eth_gasPrice" -end) -module Get_transaction_count = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Get_transaction_count = struct + open Ethereum_types type input = address * block_param @@ -378,12 +318,12 @@ module Get_transaction_count = MethodMaker (struct let output_encoding = quantity_encoding let method_ = "eth_getTransactionCount" -end) -module Get_block_transaction_count_by_hash = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Get_block_transaction_count_by_hash = struct + open Ethereum_types type input = block_hash @@ -394,12 +334,12 @@ module Get_block_transaction_count_by_hash = MethodMaker (struct let output_encoding = quantity_encoding let method_ = "eth_getBlockTransactionCountByHash" -end) -module Get_block_transaction_count_by_number = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Get_block_transaction_count_by_number = struct + open Ethereum_types type input = block_param @@ -410,12 +350,12 @@ module Get_block_transaction_count_by_number = MethodMaker (struct let output_encoding = quantity_encoding let method_ = "eth_getBlockTransactionCountByNumber" -end) -module Get_uncle_count_by_block_hash = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Get_uncle_count_by_block_hash = struct + open Ethereum_types type input = block_hash @@ -426,12 +366,12 @@ module Get_uncle_count_by_block_hash = MethodMaker (struct let output_encoding = quantity_encoding let method_ = "eth_getUncleCountByBlockHash" -end) -module Get_uncle_count_by_block_number = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Get_uncle_count_by_block_number = struct + open Ethereum_types type input = block_param @@ -442,12 +382,12 @@ module Get_uncle_count_by_block_number = MethodMaker (struct let output_encoding = quantity_encoding let method_ = "eth_getUncleCountByBlockNumber" -end) -module Get_transaction_receipt = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Get_transaction_receipt = struct + open Ethereum_types type input = hash @@ -458,12 +398,12 @@ module Get_transaction_receipt = MethodMaker (struct let output_encoding = Data_encoding.option transaction_receipt_encoding let method_ = "eth_getTransactionReceipt" -end) -module Get_transaction_by_hash = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Get_transaction_by_hash = struct + open Ethereum_types type input = hash @@ -474,12 +414,12 @@ module Get_transaction_by_hash = MethodMaker (struct let output_encoding = Data_encoding.option transaction_object_encoding let method_ = "eth_getTransactionByHash" -end) -module Get_transaction_by_block_hash_and_index = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Get_transaction_by_block_hash_and_index = struct + open Ethereum_types type input = block_hash * quantity @@ -490,12 +430,12 @@ module Get_transaction_by_block_hash_and_index = MethodMaker (struct let output_encoding = Data_encoding.option transaction_object_encoding let method_ = "eth_getTransactionByBlockHashAndIndex" -end) -module Get_transaction_by_block_number_and_index = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Get_transaction_by_block_number_and_index = struct + open Ethereum_types type input = block_param * quantity @@ -506,12 +446,12 @@ module Get_transaction_by_block_number_and_index = MethodMaker (struct let output_encoding = Data_encoding.option transaction_object_encoding let method_ = "eth_getTransactionByBlockNumberAndIndex" -end) -module Get_uncle_by_block_hash_and_index = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Get_uncle_by_block_hash_and_index = struct + open Ethereum_types type input = block_hash * quantity @@ -522,12 +462,12 @@ module Get_uncle_by_block_hash_and_index = MethodMaker (struct let output_encoding = Data_encoding.option block_encoding let method_ = "eth_getUncleByBlockHashAndIndex" -end) -module Get_uncle_by_block_number_and_index = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Get_uncle_by_block_number_and_index = struct + open Ethereum_types type input = block_param * quantity @@ -538,12 +478,12 @@ module Get_uncle_by_block_number_and_index = MethodMaker (struct let output_encoding = Data_encoding.option block_encoding let method_ = "eth_getUncleByBlockNumberAndIndex" -end) -module Send_raw_transaction = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Send_raw_transaction = struct + open Ethereum_types type input = hex @@ -554,12 +494,12 @@ module Send_raw_transaction = MethodMaker (struct let output_encoding = hash_encoding let method_ = "eth_sendRawTransaction" -end) -module Send_transaction = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Send_transaction = struct + open Ethereum_types type input = transaction @@ -570,12 +510,12 @@ module Send_transaction = MethodMaker (struct let output_encoding = hash_encoding let method_ = "eth_sendTransaction" -end) -module Eth_call = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Eth_call = struct + open Ethereum_types type input = call * block_param @@ -586,12 +526,12 @@ module Eth_call = MethodMaker (struct let output_encoding = hash_encoding let method_ = "eth_call" -end) -module Get_estimate_gas = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Get_estimate_gas = struct + open Ethereum_types type input = call * block_param @@ -619,12 +559,12 @@ module Get_estimate_gas = MethodMaker (struct let output_encoding = quantity_encoding let method_ = "eth_estimateGas" -end) -module Txpool_content = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Txpool_content = struct + open Ethereum_types type input = unit @@ -635,12 +575,12 @@ module Txpool_content = MethodMaker (struct let output_encoding = txpool_encoding let method_ = "txpool_content" -end) -module Web3_clientVersion = MethodMaker (struct - type input = unit + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Web3_clientVersion = struct + type input = unit type output = string @@ -649,12 +589,12 @@ module Web3_clientVersion = MethodMaker (struct let output_encoding = Data_encoding.string let method_ = "web3_clientVersion" -end) -module Web3_sha3 = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Web3_sha3 = struct + open Ethereum_types type input = hex @@ -665,12 +605,12 @@ module Web3_sha3 = MethodMaker (struct let output_encoding = hash_encoding let method_ = "web3_sha3" -end) -module Get_logs = MethodMaker (struct - open Ethereum_types + type ('input, 'output) method_ += Method : (input, output) method_ +end - type method_ +module Get_logs = struct + open Ethereum_types type input = filter @@ -681,9 +621,33 @@ module Get_logs = MethodMaker (struct let output_encoding = Data_encoding.list filter_changes_encoding let method_ = "eth_getLogs" -end) -let methods : (module METHOD) list = + type ('input, 'output) method_ += Method : (input, output) method_ +end + +module Produce_block = struct + type input = unit + + type output = Ethereum_types.quantity + + let input_encoding = Data_encoding.unit + + let output_encoding = Ethereum_types.quantity_encoding + + let method_ = "produceBlock" + + type ('input, 'output) method_ += Method : (input, output) method_ +end + +type map_result = + | Method : + ('input, 'output) method_ + * (module METHOD with type input = 'input and type output = 'output) + -> map_result + | Unsupported + | Unknown + +let supported_methods : (module METHOD) list = [ (module Kernel_version); (module Network_id); @@ -715,43 +679,31 @@ let methods : (module METHOD) list = (module Txpool_content); (module Web3_clientVersion); (module Web3_sha3); + (module Produce_block); ] -module Input = struct - type t = Box : 'a input -> t [@@ocaml.unboxed] - - let case_maker tag_id (module M : METHOD) = - let open Data_encoding in - case - ~title:M.method_ - (Tag tag_id) - M.request_encoding - (function - | Box (M.Input input), id -> Some (M.request input id) | _ -> None) - (fun {parameters; id; _} -> (Box (M.Input parameters), id)) - - let encoding = - let open Data_encoding in - union @@ List.mapi case_maker methods -end - -module Output = struct - type nonrec 'a result = ('a, error JSONRPC.error) result - - type t = Box : 'a output -> t [@@ocaml.unboxed] +let unsupported_methods : string list = + [ + "net_listening"; + "net_peerCount"; + "eth_protocolVersion"; + "eth_syncing"; + "eth_coinbase"; + "eth_mining"; + "eth_hashrate"; + "eth_accounts"; + "eth_sign"; + "eth_signTransaction"; + "eth_sendTransaction"; + ] - let case_maker tag_id (module M : METHOD) = - let open Data_encoding in - case - ~title:M.method_ - (Tag tag_id) - M.response_encoding - (function - | Box (M.Output accounts), id -> Some JSONRPC.{value = accounts; id} - | _ -> None) - (fun {value = req; id} -> (Box (M.Output req), id)) - - let encoding = - let open Data_encoding in - union @@ List.mapi case_maker methods -end +let map_method_name method_name = + match + List.find + (fun (module M : METHOD) -> M.method_ = method_name) + supported_methods + with + | Some (module M) -> Method (M.Method, (module M)) + | None -> + if List.mem ~equal:( = ) method_name unsupported_methods then Unsupported + else Unknown diff --git a/etherlink/bin_evm_node/lib_prod/rpc_encodings.mli b/etherlink/bin_evm_node/lib_prod/rpc_encodings.mli index 6d5b39543fa3cc50a095e9ae19e18849b780e3af..46512f3ddd2546465459c24851976e7dd4f0b273 100644 --- a/etherlink/bin_evm_node/lib_prod/rpc_encodings.mli +++ b/etherlink/bin_evm_node/lib_prod/rpc_encodings.mli @@ -49,14 +49,13 @@ module JSONRPC : sig } ]} *) - type 'params request = { + type request = { method_ : string; - parameters : 'params option; (** `params` is optional. *) + parameters : Data_encoding.json option; (** `params` is optional. *) id : id; (** `id` is optional. *) } - val request_encoding : - string -> 'a Data_encoding.t -> 'a request Data_encoding.t + val request_encoding : request Data_encoding.t (** JSON-RPC Error representation. {@js[ @@ -70,6 +69,8 @@ module JSONRPC : sig val error_encoding : 'a Data_encoding.t -> 'a error Data_encoding.t + type value = (Data_encoding.json, Data_encoding.json error) result + (** JSON-RPC Response object: {@js[ { "jsonrpc": "2.0", @@ -81,15 +82,9 @@ module JSONRPC : sig Note that `result` and `error` cannot appear at the same time, hence the choice of using the result type as representation. *) - type ('result, 'data_error) response = { - value : ('result, 'data_error error) result; - id : id; - } + type response = {value : value; id : id} - val response_encoding : - 'a Data_encoding.t -> - 'b Data_encoding.t -> - ('a, 'b) response Data_encoding.t + val response_encoding : response Data_encoding.t end (* Errors returned by the RPC server, to be embedded as data to the JSON-RPC @@ -100,26 +95,12 @@ module Error : sig val encoding : unit Data_encoding.t end -(** Extensible variant representing the possible input requests extended by the - application of the method generator. - Parameterized by a type indicating which method this input is for. -*) -type 'method_ input = .. - -(** Extensible variant representing the possible outputs extended by the - application of the method generator. - Parameterized by a type indicating which method produced this output. -*) -type 'method_ output = .. - type 'result rpc_result = ('result, Error.t JSONRPC.error) result -(** API of an Ethereum method. *) -module type METHOD_DEF = sig - (** Type-level identifier of the method. - Used as parameter for [input] and [output] types. *) - type method_ +type ('input, 'output) method_ = .. +(** API of an Ethereum method. *) +module type METHOD = sig (** Method name in the specification. *) val method_ : string @@ -132,206 +113,154 @@ module type METHOD_DEF = sig val input_encoding : input Data_encoding.t val output_encoding : output Data_encoding.t -end - -(** Interface of a generated method. *) -module type METHOD = sig - type method_ - - (** Input type of the method, if any. *) - type m_input - - (** Output type of the method. *) - type m_output - - (** Variant representing the method's request. *) - type 'a input += Input : m_input option -> method_ input - - (** Variant representing the method's response. *) - type 'a output += Output : m_output rpc_result -> method_ output - - (** See METHOD_DEF.method_ *) - val method_ : string - val request_encoding : m_input JSONRPC.request Data_encoding.t - - (** [request input] builds a request object of the current method. *) - val request : m_input option -> JSONRPC.id -> m_input JSONRPC.request - - val response_encoding : (m_output, Error.t) JSONRPC.response Data_encoding.t - - (** [response output] returns a response object for the method. *) - val response : - (m_output, Error.t JSONRPC.error) result -> - JSONRPC.id -> - (m_output, Error.t) JSONRPC.response - - (** [response_ok output] is a shortcut for [reponse (Ok output)]. *) - val response_ok : - m_output -> JSONRPC.id -> (m_output, Error.t) JSONRPC.response -end - -(** Builds a full Method module out of a method description. *) -module MethodMaker : functor (M : METHOD_DEF) -> - METHOD with type m_input = M.input and type m_output = M.output - -(** [methods] is the list of currently defined methods. *) -val methods : (module METHOD) list - -(** [Input] defines the input encoding matching the defined methods in - [methods]. *) -module Input : sig - type t = Box : 'a input -> t [@@ocaml.unboxed] - - val encoding : (t * JSONRPC.id) Data_encoding.t + type ('input, 'output) method_ += Method : (input, output) method_ end -(** [Output] defines the output encoding matching the defined methods in - [methods]. *) -module Output : sig - type nonrec 'a result = ('a, error JSONRPC.error) result +module Kernel_version : METHOD with type input = unit and type output = string - type t = Box : 'a output -> t [@@ocaml.unboxed] - - val encoding : (t * JSONRPC.id) Data_encoding.t -end - -module Kernel_version : - METHOD with type m_input = unit and type m_output = string - -module Network_id : METHOD with type m_input = unit and type m_output = string +module Network_id : METHOD with type input = unit and type output = string module Chain_id : - METHOD with type m_input = unit and type m_output = Ethereum_types.quantity + METHOD with type input = unit and type output = Ethereum_types.quantity module Accounts : - METHOD - with type m_input = unit - and type m_output = Ethereum_types.address list + METHOD with type input = unit and type output = Ethereum_types.address list module Get_balance : METHOD - with type m_input = Ethereum_types.address * Ethereum_types.block_param - and type m_output = Ethereum_types.quantity + with type input = Ethereum_types.address * Ethereum_types.block_param + and type output = Ethereum_types.quantity module Get_storage_at : METHOD - with type m_input = + with type input = Ethereum_types.address * Ethereum_types.quantity * Ethereum_types.block_param - and type m_output = Ethereum_types.hex + and type output = Ethereum_types.hex module Block_number : - METHOD - with type m_input = unit - and type m_output = Ethereum_types.block_height + METHOD with type input = unit and type output = Ethereum_types.block_height module Get_block_by_number : METHOD - with type m_input = Ethereum_types.block_param * bool - and type m_output = Ethereum_types.block + with type input = Ethereum_types.block_param * bool + and type output = Ethereum_types.block module Get_block_by_hash : METHOD - with type m_input = Ethereum_types.block_hash * bool - and type m_output = Ethereum_types.block + with type input = Ethereum_types.block_hash * bool + and type output = Ethereum_types.block module Get_code : METHOD - with type m_input = Ethereum_types.address * Ethereum_types.block_param - and type m_output = Ethereum_types.hex + with type input = Ethereum_types.address * Ethereum_types.block_param + and type output = Ethereum_types.hex module Gas_price : - METHOD with type m_input = unit and type m_output = Ethereum_types.quantity + METHOD with type input = unit and type output = Ethereum_types.quantity module Get_transaction_count : METHOD - with type m_input = Ethereum_types.address * Ethereum_types.block_param - and type m_output = Ethereum_types.quantity + with type input = Ethereum_types.address * Ethereum_types.block_param + and type output = Ethereum_types.quantity module Get_block_transaction_count_by_hash : METHOD - with type m_input = Ethereum_types.block_hash - and type m_output = Ethereum_types.quantity + with type input = Ethereum_types.block_hash + and type output = Ethereum_types.quantity module Get_block_transaction_count_by_number : METHOD - with type m_input = Ethereum_types.block_param - and type m_output = Ethereum_types.quantity + with type input = Ethereum_types.block_param + and type output = Ethereum_types.quantity module Get_uncle_count_by_block_hash : METHOD - with type m_input = Ethereum_types.block_hash - and type m_output = Ethereum_types.quantity + with type input = Ethereum_types.block_hash + and type output = Ethereum_types.quantity module Get_uncle_count_by_block_number : METHOD - with type m_input = Ethereum_types.block_param - and type m_output = Ethereum_types.quantity + with type input = Ethereum_types.block_param + and type output = Ethereum_types.quantity module Get_transaction_receipt : METHOD - with type m_input = Ethereum_types.hash - and type m_output = Ethereum_types.transaction_receipt option + with type input = Ethereum_types.hash + and type output = Ethereum_types.transaction_receipt option module Get_transaction_by_hash : METHOD - with type m_input = Ethereum_types.hash - and type m_output = Ethereum_types.transaction_object option + with type input = Ethereum_types.hash + and type output = Ethereum_types.transaction_object option module Get_transaction_by_block_hash_and_index : METHOD - with type m_input = Ethereum_types.block_hash * Ethereum_types.quantity - and type m_output = Ethereum_types.transaction_object option + with type input = Ethereum_types.block_hash * Ethereum_types.quantity + and type output = Ethereum_types.transaction_object option module Get_transaction_by_block_number_and_index : METHOD - with type m_input = Ethereum_types.block_param * Ethereum_types.quantity - and type m_output = Ethereum_types.transaction_object option + with type input = Ethereum_types.block_param * Ethereum_types.quantity + and type output = Ethereum_types.transaction_object option module Get_uncle_by_block_hash_and_index : METHOD - with type m_input = Ethereum_types.block_hash * Ethereum_types.quantity - and type m_output = Ethereum_types.block option + with type input = Ethereum_types.block_hash * Ethereum_types.quantity + and type output = Ethereum_types.block option module Get_uncle_by_block_number_and_index : METHOD - with type m_input = Ethereum_types.block_param * Ethereum_types.quantity - and type m_output = Ethereum_types.block option + with type input = Ethereum_types.block_param * Ethereum_types.quantity + and type output = Ethereum_types.block option module Send_raw_transaction : METHOD - with type m_input = Ethereum_types.hex - and type m_output = Ethereum_types.hash + with type input = Ethereum_types.hex + and type output = Ethereum_types.hash module Send_transaction : METHOD - with type m_input = Ethereum_types.transaction - and type m_output = Ethereum_types.hash + with type input = Ethereum_types.transaction + and type output = Ethereum_types.hash module Eth_call : METHOD - with type m_input = Ethereum_types.call * Ethereum_types.block_param - and type m_output = Ethereum_types.hash + with type input = Ethereum_types.call * Ethereum_types.block_param + and type output = Ethereum_types.hash module Get_estimate_gas : METHOD - with type m_input = Ethereum_types.call * Ethereum_types.block_param - and type m_output = Ethereum_types.quantity + with type input = Ethereum_types.call * Ethereum_types.block_param + and type output = Ethereum_types.quantity module Txpool_content : - METHOD with type m_input = unit and type m_output = Ethereum_types.txpool + METHOD with type input = unit and type output = Ethereum_types.txpool module Web3_clientVersion : - METHOD with type m_input = unit and type m_output = string + METHOD with type input = unit and type output = string module Web3_sha3 : METHOD - with type m_input = Ethereum_types.hex - and type m_output = Ethereum_types.hash + with type input = Ethereum_types.hex + and type output = Ethereum_types.hash module Get_logs : METHOD - with type m_input = Ethereum_types.filter - and type m_output = Ethereum_types.filter_changes list + with type input = Ethereum_types.filter + and type output = Ethereum_types.filter_changes list + +module Produce_block : + METHOD with type input = unit and type output = Ethereum_types.quantity + +type map_result = + | Method : + ('input, 'output) method_ + * (module METHOD with type input = 'input and type output = 'output) + -> map_result + | Unsupported + | Unknown + +val map_method_name : string -> map_result diff --git a/etherlink/bin_evm_node/lib_prod/sequencer.ml b/etherlink/bin_evm_node/lib_prod/sequencer.ml new file mode 100644 index 0000000000000000000000000000000000000000..41bde1bdbda185591e9ab12e73cf2b5213888160 --- /dev/null +++ b/etherlink/bin_evm_node/lib_prod/sequencer.ml @@ -0,0 +1,78 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(*****************************************************************************) + +module MakeBackend (Ctxt : sig + val ctxt : Sequencer_context.t + + val rollup_node_endpoint : Uri.t +end) : Services_backend_sig.Backend = struct + module READER = struct + let read path = + let open Lwt_result_syntax in + let* Sequencer_context.{evm_state; _} = + Sequencer_context.sync Ctxt.ctxt + in + let*! res = Sequencer_state.inspect evm_state path in + return res + end + + module TxEncoder = struct + let encode_transaction ~smart_rollup_address:_ ~transaction = + let tx_hash_str = Ethereum_types.hash_raw_tx transaction in + let tx_hash = + Ethereum_types.( + Hash Hex.(of_string tx_hash_str |> show |> hex_of_string)) + in + Result_syntax.return (tx_hash, [transaction]) + end + + module Publisher = struct + let publish_messages ~timestamp ~smart_rollup_address ~messages = + let open Lwt_result_syntax in + let* ctxt = Sequencer_context.sync Ctxt.ctxt in + (* Create the blueprint with the messages. *) + let (Ethereum_types.(Qty next) as number) = ctxt.next_blueprint_number in + let inputs = + Sequencer_blueprint.create + ~timestamp + ~smart_rollup_address + ~transactions:messages + ~number + in + let* () = + Rollup_node_services.publish + ~rollup_node_endpoint:Ctxt.rollup_node_endpoint + inputs + in + ctxt.next_blueprint_number <- Qty (Z.succ next) ; + (* Execute the blueprint. *) + let inputs = + List.map + (function `External payload -> `Input ("\001" ^ payload)) + inputs + in + let* _ctxt = Sequencer_state.execute ~commit:true ctxt inputs in + return_unit + end + + module SimulatorBackend = struct + let simulate_and_read ~input = + let open Lwt_result_syntax in + let* ctxt = Sequencer_context.sync Ctxt.ctxt in + let* raw_insights = Sequencer_state.execute_and_inspect ctxt ~input in + match Simulation.Encodings.insights_from_list raw_insights with + | Some i -> return i + | None -> Error_monad.failwith "Invalid insights format" + end +end + +module Make (Ctxt : sig + val ctxt : Sequencer_context.t + + val rollup_node_endpoint : Uri.t +end) = + Services_backend_sig.Make (MakeBackend (Ctxt)) diff --git a/etherlink/bin_evm_node/lib_prod/sequencer_blueprint.ml b/etherlink/bin_evm_node/lib_prod/sequencer_blueprint.ml new file mode 100644 index 0000000000000000000000000000000000000000..7088b7fd82d24b2f89fdedcba2b056cbccbba585 --- /dev/null +++ b/etherlink/bin_evm_node/lib_prod/sequencer_blueprint.ml @@ -0,0 +1,90 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(*****************************************************************************) + +open Ethereum_types + +(* U256 *) +let blueprint_number_size = 32 + +(* U16 *) +let nb_chunks_size = 2 + +(* U16 *) +let chunk_index_size = 2 + +let blueprint_tag_size = 1 + +(* Tags added by RLP encoding for the sequencer blueprint. + The sequencer blueprint follows the format: + [ chunk, <- max size around 4kb, requires tag of 3 bytes + number, <- 32 bytes, requires a tag of 1 byte + nb_chunks, <- 2 bytes, requires a tag of 1 byte + chunk_index <- 2 bytes, requires a tag of 1 byte + ] <- outer list requires tag of 3 bytes. + + In total, the tags take 9 bytes. We use 16 to be safe. +*) +let rlp_tags_size = 16 + +let max_chunk_size = + let open Transaction_format in + (* max_input_size already considers the external tag *) + max_input_size - framing_protocol_tag_size - smart_rollup_address_size + - blueprint_tag_size - blueprint_number_size - nb_chunks_size + - chunk_index_size - rlp_tags_size + +let make_blueprint_chunks ~timestamp ~transactions = + let open Rlp in + let messages = + List + (List.map + (fun transaction -> + let tx_hash_str = Ethereum_types.hash_raw_tx transaction in + List + [ + Value (Bytes.of_string tx_hash_str); + List + [ + Value (Bytes.of_string "\001"); + Value (Bytes.of_string transaction); + ]; + ]) + transactions) + in + let timestamp = Value (Helpers.timestamp_to_bytes timestamp) in + let blob = List [messages; timestamp] |> encode in + match String.chunk_bytes max_chunk_size blob with + | Ok chunks -> chunks + | Error _ -> + (* [chunk_bytes] can only return an [Error] if the optional + argument [error_on_partial_chunk] is passed. As this is not + the case in this call, this branch is impossible. *) + assert false + +let encode_u16_le i = + let bytes = Bytes.make 2 '\000' in + Bytes.set_uint16_le bytes 0 i ; + bytes + +let create ~timestamp ~smart_rollup_address ~number ~transactions = + let open Rlp in + let number = Value (encode_u256_le number) in + let chunks = make_blueprint_chunks ~timestamp ~transactions in + let nb_chunks = Rlp.Value (encode_u16_le @@ List.length chunks) in + let message_from_chunk chunk_index chunk = + let chunk_index = Rlp.Value (encode_u16_le chunk_index) in + let rlp_sequencer_blueprint = + List [Value (Bytes.of_string chunk); number; nb_chunks; chunk_index] + |> encode |> Bytes.to_string + in + `External + ("\000" (* Framed protocol *) ^ smart_rollup_address + ^ "\003" + ^ (* Sequencer blueprint *) + rlp_sequencer_blueprint) + in + List.mapi message_from_chunk chunks diff --git a/etherlink/bin_evm_node/lib_prod/sequencer_blueprint.mli b/etherlink/bin_evm_node/lib_prod/sequencer_blueprint.mli new file mode 100644 index 0000000000000000000000000000000000000000..e3f0cf49cc37ea59e4b657d90c286c1c9a8f4f70 --- /dev/null +++ b/etherlink/bin_evm_node/lib_prod/sequencer_blueprint.mli @@ -0,0 +1,18 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(*****************************************************************************) + +(** [create ~timestamp ~smart_rollup_address ~number ~transactions] + creates a sequencer blueprint at [timestamp] with a given [number] + containing [transactions]. Returns valid payload of external + messages inputs to put in the inbox. +*) +val create : + timestamp:Time.Protocol.t -> + smart_rollup_address:string -> + number:Ethereum_types.quantity -> + transactions:string list -> + [> `External of string] list diff --git a/etherlink/bin_evm_node/lib_prod/sequencer_context.ml b/etherlink/bin_evm_node/lib_prod/sequencer_context.ml new file mode 100644 index 0000000000000000000000000000000000000000..c957efc54a9a61fbc310c30110a49c89f02ea0c5 --- /dev/null +++ b/etherlink/bin_evm_node/lib_prod/sequencer_context.ml @@ -0,0 +1,124 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(*****************************************************************************) + +module Context = Tezos_context_disk.Context_binary + +type index = Context.index + +type store = Context.t + +type evm_state = Context.tree + +type t = { + data_dir : string; + index : index; + store : store; + evm_state : evm_state; + kernel : string; + preimages : string; + smart_rollup_address : Tezos_crypto.Hashed.Smart_rollup_address.t; + mutable next_blueprint_number : Ethereum_types.quantity; +} + +(** The EVM/PVM local state used by the sequencer. *) +module EVMState = struct + let key = ["evm_state"] + + let get store = + let open Lwt_result_syntax in + let*! tree_opt = Context.find_tree store key in + match tree_opt with + | Some tree -> return tree + | None -> failwith "The EVM state was not found" + + let set ctxt tree = + let open Lwt_syntax in + let* store = Context.add_tree ctxt.store key tree in + return {ctxt with store} +end + +type metadata = { + checkpoint : Context_hash.t; + next_blueprint_number : Ethereum_types.quantity; +} + +let store_path ~data_dir = Filename.Infix.(data_dir // "store") + +let metadata_path ~data_dir = Filename.Infix.(data_dir // "metadata") + +let metadata_encoding = + let open Data_encoding in + conv + (fun {checkpoint; next_blueprint_number} -> + (checkpoint, next_blueprint_number)) + (fun (checkpoint, next_blueprint_number) -> + {checkpoint; next_blueprint_number}) + (obj2 + (req "checkpoint" Context_hash.encoding) + (req "next_blueprint_number" Ethereum_types.quantity_encoding)) + +let store_metadata ~data_dir metadata = + let json = Data_encoding.Json.construct metadata_encoding metadata in + Lwt_utils_unix.Json.write_file (metadata_path ~data_dir) json + +let load_metadata ~data_dir index = + let open Lwt_result_syntax in + let path = metadata_path ~data_dir in + let*! exists = Lwt_unix.file_exists path in + if exists then + let* content = Lwt_utils_unix.Json.read_file path in + let {checkpoint; next_blueprint_number} = + Data_encoding.Json.destruct metadata_encoding content + in + let*! store = Context.checkout_exn index checkpoint in + let* evm_state = EVMState.get store in + return (store, evm_state, next_blueprint_number, true) + else + let store = Context.empty index in + let evm_state = Context.Tree.empty store in + return (store, evm_state, Ethereum_types.Qty Z.zero, false) + +let init ~data_dir ~kernel ~preimages ~smart_rollup_address = + let open Lwt_result_syntax in + let*! index = Context.init (store_path ~data_dir) in + let* store, evm_state, next_blueprint_number, loaded = + load_metadata ~data_dir index + in + let smart_rollup_address = + Tezos_crypto.Hashed.Smart_rollup_address.of_string_exn smart_rollup_address + in + return + ( { + index; + store; + data_dir; + evm_state; + kernel; + preimages; + smart_rollup_address; + next_blueprint_number; + }, + loaded ) + +let commit ctxt evm_state = + let open Lwt_result_syntax in + let*! ctxt = EVMState.set ctxt evm_state in + let*! checkpoint = Context.commit ~time:Time.Protocol.epoch ctxt.store in + let* () = + store_metadata + ~data_dir:ctxt.data_dir + {checkpoint; next_blueprint_number = ctxt.next_blueprint_number} + in + return {ctxt with evm_state} + +let sync ctxt = + let open Lwt_result_syntax in + let*! () = Context.sync ctxt.index in + let* store, evm_state, next_blueprint_number, _loaded = + load_metadata ~data_dir:ctxt.data_dir ctxt.index + in + return {ctxt with store; evm_state; next_blueprint_number} diff --git a/etherlink/bin_evm_node/lib_prod/sequencer_context.mli b/etherlink/bin_evm_node/lib_prod/sequencer_context.mli new file mode 100644 index 0000000000000000000000000000000000000000..883bc3cd1ee84265a56f4b665a45c5ac4b33a8e5 --- /dev/null +++ b/etherlink/bin_evm_node/lib_prod/sequencer_context.mli @@ -0,0 +1,47 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(*****************************************************************************) + +module Context : Tezos_tree_encoding.Encodings_util.Bare_tezos_context_sig + +type index + +type store + +type evm_state = Context.tree + +type t = { + data_dir : string; (** Data dir of the EVM node. *) + index : index; (** Irmin index. *) + store : store; (** Irmin store. *) + evm_state : evm_state; (** EVM local state of the sequencer. *) + kernel : string; (** Path to the kernel to execute. *) + preimages : string; (** Path to the preimages directory. *) + smart_rollup_address : Tezos_crypto.Hashed.Smart_rollup_address.t; + mutable next_blueprint_number : Ethereum_types.quantity; + (** Number for the next bluerpint to be produced. *) +} + +(** [init ~data_dir ~kernel ~preimages ~smart_rollup_address] creates + a context where it initializes the {!type-index}, and use a + checkpoint mechanism to load the latest {!type-store} if any. + + Also returns a boolean denoting whether the context was initialized or not. +*) +val init : + data_dir:string -> + kernel:string -> + preimages:string -> + smart_rollup_address:string -> + (t * bool) tzresult Lwt.t + +(** [commit ctxt evm_state] updates the [evm_state] in [ctxt], commits + to disk the changes, and update the checkpoint. *) +val commit : t -> evm_state -> t tzresult Lwt.t + +(** [sync ctxt] synchronizes the [ctxt] based on on-disk information, loads the + latest checkpoint. *) +val sync : t -> t tzresult Lwt.t diff --git a/etherlink/bin_evm_node/lib_prod/sequencer_state.ml b/etherlink/bin_evm_node/lib_prod/sequencer_state.ml new file mode 100644 index 0000000000000000000000000000000000000000..8131af386d7c3b650ae6c4fbdacf2be64596c24f --- /dev/null +++ b/etherlink/bin_evm_node/lib_prod/sequencer_state.ml @@ -0,0 +1,80 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(*****************************************************************************) + +module TreeEncoding = + Wasm_utils.Make + (Tezos_tree_encoding.Encodings_util.Make (Sequencer_context.Context)) +module Wasm = Wasm_debugger.Make (TreeEncoding) + +let execute ?(commit = false) ctxt inbox = + let open Lwt_result_syntax in + let inbox = List.map (function `Input s -> s) inbox in + let inbox = List.to_seq [inbox] in + let config = + Config.config + ~preimage_directory:ctxt.Sequencer_context.preimages + ~kernel_debug:true + ~destination:ctxt.Sequencer_context.smart_rollup_address + () + in + let* evm_state, _, _, _ = + Wasm.Commands.eval 0l inbox config Inbox ctxt.Sequencer_context.evm_state + in + let* ctxt = + if commit then Sequencer_context.commit ctxt evm_state else return ctxt + in + return (ctxt, evm_state) + +let init ~smart_rollup_address ~rollup_node_endpoint ctxt = + let open Lwt_result_syntax in + let* evm_state = + Wasm.start + ~tree:ctxt.Sequencer_context.evm_state + Tezos_scoru_wasm.Wasm_pvm_state.V3 + ctxt.kernel + in + let ctxt = + {ctxt with evm_state; next_blueprint_number = Ethereum_types.(Qty Z.one)} + in + (* Create the first empty block. *) + let inputs = + Sequencer_blueprint.create + ~timestamp:(Helpers.now ()) + ~smart_rollup_address + ~transactions:[] + ~number:Ethereum_types.(Qty Z.zero) + in + let* () = Rollup_node_services.publish ~rollup_node_endpoint inputs in + let inputs = + List.map (function `External payload -> `Input ("\001" ^ payload)) inputs + in + let* ctxt, _evm_state = execute ~commit:true ctxt inputs in + return ctxt + +let inspect evm_state key = + let open Lwt_syntax in + let key = Tezos_scoru_wasm.Durable.key_of_string_exn key in + let* value = Wasm.Commands.find_key_in_durable evm_state key in + Option.map_s Tezos_lazy_containers.Chunked_byte_vector.to_bytes value + +let execute_and_inspect ctxt + ~input:Simulation.Encodings.{messages; insight_requests; _} = + let open Lwt_result_syntax in + let keys = + List.map + (function + | Simulation.Encodings.Durable_storage_key l -> + "/" ^ String.concat "/" l + (* We use only `Durable_storage_key` in simulation. *) + | _ -> assert false) + insight_requests + in + (* Messages from simulation requests are already valid inputs. *) + let messages = List.map (fun s -> `Input s) messages in + let* _ctxt, evm_state = execute ctxt messages in + let*! values = List.map_p (fun key -> inspect evm_state key) keys in + return values diff --git a/etherlink/bin_evm_node/lib_prod/sequencer_state.mli b/etherlink/bin_evm_node/lib_prod/sequencer_state.mli new file mode 100644 index 0000000000000000000000000000000000000000..ff90ce82425fecd737538d5808fbab3ffc16735f --- /dev/null +++ b/etherlink/bin_evm_node/lib_prod/sequencer_state.mli @@ -0,0 +1,34 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(*****************************************************************************) + +(** [execute ?commit ctxt messages] executes [messages] on the local + EVM state of [ctxt], commits to disk if [commit] is true. Returns + the modified EVM state even if it's not commited. *) +val execute : + ?commit:bool -> + Sequencer_context.t -> + [< `Input of string] list -> + (Sequencer_context.t * Sequencer_context.evm_state) tzresult Lwt.t + +(** [init ~smart_rollup_address ~rollup_node_endpoint ctxt] + initializes the local state in [ctxt], produces and publishes the + genesis block. *) +val init : + smart_rollup_address:string -> + rollup_node_endpoint:Uri.t -> + Sequencer_context.t -> + Sequencer_context.t tzresult Lwt.t + +(** [inspect evm_state key] inspects [key] in [evm_state]. *) +val inspect : Sequencer_context.evm_state -> string -> bytes option Lwt.t + +(** [execute_and_inspect ctxt ~input] executes [input] in [ctxt] and + returns the [input.insights_requests]. *) +val execute_and_inspect : + Sequencer_context.t -> + input:Simulation.Encodings.simulate_input -> + bytes option list tzresult Lwt.t diff --git a/etherlink/bin_evm_node/lib_prod/services.ml b/etherlink/bin_evm_node/lib_prod/services.ml index 3330c1aef4ca2b7ed96f51854372f61c23ea65f3..72257b4de63bc9b9c0dcfabf7b02d8a12f47f799 100644 --- a/etherlink/bin_evm_node/lib_prod/services.ml +++ b/etherlink/bin_evm_node/lib_prod/services.ml @@ -51,7 +51,7 @@ let version dir = (* The node can either take a single request or multiple requests at once. *) -type 'a request = Singleton of 'a | Batch of 'a list +type 'a batched_request = Singleton of 'a | Batch of 'a list let request_encoding kind = Data_encoding.( @@ -71,15 +71,15 @@ let request_encoding kind = (fun i -> Batch i); ]) -let dispatch_service = +let dispatch_service ~path = Service.post_service ~query:Query.empty - ~input:(request_encoding Input.encoding) - ~output:(request_encoding Output.encoding) - Path.(root) + ~input:(request_encoding JSONRPC.request_encoding) + ~output:(request_encoding JSONRPC.response_encoding) + path let get_block_by_number ~full_transaction_object block_param - (module Rollup_node_rpc : Rollup_node.S) = + (module Rollup_node_rpc : Services_backend_sig.S) = match block_param with | Ethereum_types.(Hash_param (Block_height n)) -> Rollup_node_rpc.nth_block ~full_transaction_object n @@ -87,7 +87,7 @@ let get_block_by_number ~full_transaction_object block_param Rollup_node_rpc.current_block ~full_transaction_object let get_transaction_from_index block index - (module Rollup_node_rpc : Rollup_node.S) = + (module Rollup_node_rpc : Services_backend_sig.S) = let open Lwt_result_syntax in match block.Ethereum_types.transactions with | TxHash l -> ( @@ -103,199 +103,453 @@ let block_transaction_count block = | TxHash l -> List.length l | TxFull l -> List.length l -let dispatch_input (config : 'a Configuration.t) - ((module Rollup_node_rpc : Rollup_node.S), _) (input, id) = - let open Lwt_result_syntax in - let dispatch_input_aux : type w. w input -> w output tzresult Lwt.t = function - (* INTERNAL RPCs *) - | Kernel_version.Input _ -> - let* kernel_version = Rollup_node_rpc.kernel_version () in - return (Kernel_version.Output (Ok kernel_version)) - (* ETHEREUM JSON-RPC API *) - | Accounts.Input _ -> return (Accounts.Output (Ok [])) - | Network_id.Input _ -> - let* (Qty chain_id) = Rollup_node_rpc.chain_id () in - let net_version = Z.to_string chain_id in - return (Network_id.Output (Ok net_version)) - | Chain_id.Input _ -> - let* chain_id = Rollup_node_rpc.chain_id () in - return (Chain_id.Output (Ok chain_id)) - | Get_balance.Input (Some (address, _block_param)) -> - let* balance = Rollup_node_rpc.balance address in - return (Get_balance.Output (Ok balance)) - | Get_storage_at.Input (Some (address, position, _block_param)) -> - let* value = Rollup_node_rpc.storage_at address position in - return (Get_storage_at.Output (Ok value)) - | Block_number.Input _ -> - let* block_number = Rollup_node_rpc.current_block_number () in - return (Block_number.Output (Ok block_number)) - | Get_block_by_number.Input (Some (block_param, full_transaction_object)) -> - let* block = - get_block_by_number - ~full_transaction_object - block_param - (module Rollup_node_rpc) +let decode : + type a. (module METHOD with type input = a) -> Data_encoding.json -> a = + fun (module M) v -> Data_encoding.Json.destruct M.input_encoding v + +let encode : + type a. (module METHOD with type output = a) -> a -> Data_encoding.json = + fun (module M) v -> Data_encoding.Json.construct M.output_encoding v + +let build : + type input output. + (module METHOD with type input = input and type output = output) -> + f:(input option -> (output, string) Either.t tzresult Lwt.t) -> + Data_encoding.json option -> + JSONRPC.value Lwt.t = + fun (module Method) ~f parameters -> + let open Lwt_syntax in + let decoded = Option.map (decode (module Method)) parameters in + let+ v = f decoded in + match v with + | Error err -> + let message = Format.asprintf "%a" pp_print_trace err in + Error JSONRPC.{code = -32000; message; data = None} + | Ok value -> ( + match value with + | Left output -> Ok (encode (module Method) output) + | Right message -> Error JSONRPC.{code = -32000; message; data = None}) + +let missing_parameter = Either.Right "Missing parameters" + +let dispatch_request (config : 'a Configuration.t) + ((module Backend_rpc : Services_backend_sig.S), _) + ({method_; parameters; id} : JSONRPC.request) : JSONRPC.response Lwt.t = + let open Lwt_syntax in + let open Ethereum_types in + let* value = + match map_method_name method_ with + | Unknown -> + return + (Error + JSONRPC. + { + code = -3200; + message = "Method not found"; + data = Some (`String method_); + }) + | Unsupported -> + return + (Error + JSONRPC. + { + code = -3200; + message = "Method not supported"; + data = Some (`String method_); + }) + (* Ethereum JSON-RPC API methods we support *) + | Method (Accounts.Method, module_) -> + let f (_ : unit option) = + let open Lwt_result_syntax in + return (Either.Left []) in - return (Get_block_by_number.Output (Ok block)) - | Get_block_by_hash.Input (Some (block_hash, full_transaction_object)) -> - let* block = - Rollup_node_rpc.block_by_hash ~full_transaction_object block_hash + build ~f module_ parameters + | Method (Network_id.Method, module_) -> + let f (_ : unit option) = + let open Lwt_result_syntax in + let* (Qty chain_id) = Backend_rpc.chain_id () in + return (Either.Left (Z.to_string chain_id)) in - return (Get_block_by_hash.Output (Ok block)) - | Get_code.Input (Some (address, _)) -> - let* code = Rollup_node_rpc.code address in - return (Get_code.Output (Ok code)) - | Gas_price.Input _ -> - let* base_fee = Rollup_node_rpc.base_fee_per_gas () in - return (Gas_price.Output (Ok base_fee)) - | Get_transaction_count.Input (Some (address, _)) -> - let* nonce = Tx_pool.nonce address in - return (Get_transaction_count.Output (Ok nonce)) - | Get_block_transaction_count_by_hash.Input (Some block_hash) -> - let* block = - Rollup_node_rpc.block_by_hash - ~full_transaction_object:false - block_hash + build ~f module_ parameters + | Method (Chain_id.Method, module_) -> + let f (_ : unit option) = + let open Lwt_result_syntax in + let* chain_id = Backend_rpc.chain_id () in + return (Either.Left chain_id) in - return - (Get_block_transaction_count_by_hash.Output - (Ok (block_transaction_count block))) - | Get_block_transaction_count_by_number.Input (Some block_param) -> - let* block = - get_block_by_number - ~full_transaction_object:false - block_param - (module Rollup_node_rpc) + build ~f module_ parameters + | Method (Get_balance.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some (address, _block_param) -> + let* balance = Backend_rpc.balance address in + return (Either.Left balance) in - return - (Get_block_transaction_count_by_number.Output - (Ok (block_transaction_count block))) - | Get_uncle_count_by_block_hash.Input (Some _block_hash) -> - (* A block cannot have uncles. *) - return (Get_uncle_count_by_block_hash.Output (Ok (Qty Z.zero))) - | Get_uncle_count_by_block_number.Input (Some _block_param) -> - (* A block cannot have uncles. *) - return (Get_uncle_count_by_block_number.Output (Ok (Qty Z.zero))) - | Get_transaction_receipt.Input (Some tx_hash) -> - let* receipt = Rollup_node_rpc.transaction_receipt tx_hash in - return (Get_transaction_receipt.Output (Ok receipt)) - | 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)) - | Get_transaction_by_block_hash_and_index.Input - (Some (block_hash, Qty index)) -> - let* block = - Rollup_node_rpc.block_by_hash - ~full_transaction_object:false - block_hash + build ~f module_ parameters + | Method (Get_storage_at.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some (address, position, _block_param) -> + let* value = Backend_rpc.storage_at address position in + return (Either.Left value) in - let* transaction_object = - get_transaction_from_index - block - (Z.to_int index) - (module Rollup_node_rpc) + build ~f module_ parameters + | Method (Block_number.Method, module_) -> + let f (_ : unit option) = + let open Lwt_result_syntax in + let* block_number = Backend_rpc.current_block_number () in + return (Either.Left block_number) in - return - (Get_transaction_by_block_hash_and_index.Output - (Ok transaction_object)) - | Get_transaction_by_block_number_and_index.Input - (Some (block_number, Qty index)) -> - let* block = - get_block_by_number - ~full_transaction_object:false - block_number - (module Rollup_node_rpc) + build ~f module_ parameters + | Method (Get_block_by_number.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some (block_param, full_transaction_object) -> + let* block = + get_block_by_number + ~full_transaction_object + block_param + (module Backend_rpc) + in + return (Either.Left block) + in + build ~f module_ parameters + | Method (Get_block_by_hash.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some (block_hash, full_transaction_object) -> + let* block = + Backend_rpc.block_by_hash ~full_transaction_object block_hash + in + return (Either.Left block) + in + build ~f module_ parameters + | Method (Get_code.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some (address, _) -> + let* code = Backend_rpc.code address in + return (Either.Left code) + in + build ~f module_ parameters + | Method (Gas_price.Method, module_) -> + let f (_ : unit option) = + let open Lwt_result_syntax in + let* base_fee = Backend_rpc.base_fee_per_gas () in + return (Either.Left base_fee) + in + build ~f module_ parameters + | Method (Get_transaction_count.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some (address, _) -> + let* nonce = Tx_pool.nonce address in + return (Either.Left nonce) + in + build ~f module_ parameters + | Method (Get_block_transaction_count_by_hash.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some block_hash -> + let* block = + Backend_rpc.block_by_hash + ~full_transaction_object:false + block_hash + in + return (Either.Left (block_transaction_count block)) + in + build ~f module_ parameters + | Method (Get_block_transaction_count_by_number.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some block_param -> + let* block = + get_block_by_number + ~full_transaction_object:false + block_param + (module Backend_rpc) + in + return (Either.Left (block_transaction_count block)) + in + build ~f module_ parameters + | Method (Get_uncle_count_by_block_hash.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some _block_param -> return (Either.Left (Qty Z.zero)) + in + build ~f module_ parameters + | Method (Get_uncle_count_by_block_number.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some _block_param -> return (Either.Left (Qty Z.zero)) + in + build ~f module_ parameters + | Method (Get_transaction_receipt.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some tx_hash -> + let* receipt = Backend_rpc.transaction_receipt tx_hash in + return (Either.Left receipt) + in + build ~f module_ parameters + | Method (Get_transaction_by_hash.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some tx_hash -> + let* transaction_object = + Backend_rpc.transaction_object tx_hash + in + return (Either.Left transaction_object) + in + build ~f module_ parameters + | Method (Get_transaction_by_block_hash_and_index.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some (block_hash, Qty index) -> + let* block = + Backend_rpc.block_by_hash + ~full_transaction_object:false + block_hash + in + let* transaction_object = + get_transaction_from_index + block + (Z.to_int index) + (module Backend_rpc) + in + return (Either.Left transaction_object) in - let* transaction_object = - get_transaction_from_index - block - (Z.to_int index) - (module Rollup_node_rpc) + build ~f module_ parameters + | Method (Get_transaction_by_block_number_and_index.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some (block_number, Qty index) -> + let* block = + get_block_by_number + ~full_transaction_object:false + block_number + (module Backend_rpc) + in + let* transaction_object = + get_transaction_from_index + block + (Z.to_int index) + (module Backend_rpc) + in + return (Either.Left transaction_object) in + build ~f module_ parameters + | Method (Get_uncle_by_block_hash_and_index.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some (_block_hash, _index) -> + (* A block cannot have uncles. *) + return (Either.Left None) + in + build ~f module_ parameters + | Method (Get_uncle_by_block_number_and_index.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some (_block_number, _index) -> + (* A block cannot have uncles. *) + return (Either.Left None) + in + build ~f module_ parameters + | Method (Send_raw_transaction.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some tx_raw -> ( + let* tx_hash = Tx_pool.add (Ethereum_types.hex_to_bytes tx_raw) in + match tx_hash with + | Ok tx_hash -> return (Either.Left tx_hash) + | Error reason -> + (* TODO: https://gitlab.com/tezos/tezos/-/issues/6229 *) + return (Either.Right reason)) + in + build ~f module_ parameters + | Method (Eth_call.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some (call, _) -> ( + let* call_result = Backend_rpc.simulate_call call in + match call_result with + | Ok result -> return (Either.Left result) + | Error () -> return (Either.Right "Call simulation failed")) + in + build ~f module_ parameters + | Method (Get_estimate_gas.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some (call, _) -> + let* gas = Backend_rpc.estimate_gas call in + return (Either.Left gas) + in + build ~f module_ parameters + | Method (Txpool_content.Method, module_) -> + let f (_ : unit option) = + let open Lwt_result_syntax in + return + (Either.Left + Ethereum_types. + {pending = AddressMap.empty; queued = AddressMap.empty}) + in + build ~f module_ parameters + | Method (Web3_clientVersion.Method, module_) -> + let f (_ : unit option) = + let open Lwt_result_syntax in + return (Either.Left client_version) + in + build ~f module_ parameters + | Method (Web3_sha3.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some data -> + let open Ethereum_types in + let (Hex h) = data in + let bytes = Hex.to_bytes_exn (`Hex h) in + let hash_bytes = Tezos_crypto.Hacl.Hash.Keccak_256.digest bytes in + let hash = Hex.of_bytes hash_bytes |> Hex.show in + return (Either.Left (Hash (Hex hash))) + in + build ~f module_ parameters + | Method (Get_logs.Method, module_) -> + let f input = + let open Lwt_result_syntax in + match input with + | None -> return missing_parameter + | Some filter -> + let* logs = + Filter_helpers.get_logs + config.log_filter + (module Backend_rpc) + filter + in + return (Either.Left logs) + in + build ~f module_ parameters + (* Internal RPC methods *) + | Method (Kernel_version.Method, module_) -> + let f (_ : unit option) = + let open Lwt_result_syntax in + let* kernel_version = Backend_rpc.kernel_version () in + return (Either.Left kernel_version) + in + build ~f module_ parameters + | _ -> Stdlib.failwith "The pattern matching of methods is not exhaustive" + in + return JSONRPC.{value; id} + +let dispatch_private_request (_config : 'a Configuration.t) + ((module Backend_rpc : Services_backend_sig.S), _) + ({method_; parameters; id} : JSONRPC.request) : JSONRPC.response Lwt.t = + let open Lwt_syntax in + let* value = + match map_method_name method_ with + | Unknown -> + return + (Error + JSONRPC. + { + code = -3200; + message = "Method not found"; + data = Some (`String method_); + }) + | Unsupported -> return - (Get_transaction_by_block_number_and_index.Output - (Ok transaction_object)) - | Get_uncle_by_block_hash_and_index.Input (Some (_hash, _index)) -> - (* A block cannot have uncles. *) - return (Get_uncle_by_block_hash_and_index.Output (Ok None)) - | Get_uncle_by_block_number_and_index.Input (Some (_number, _index)) -> - (* A block cannot have uncles. *) - return (Get_uncle_by_block_number_and_index.Output (Ok None)) - | Send_raw_transaction.Input (Some tx_raw) -> ( - let* tx_hash = Tx_pool.add tx_raw in - match tx_hash with - | Ok tx_hash -> return (Send_raw_transaction.Output (Ok tx_hash)) - | Error reason -> - (* TODO: https://gitlab.com/tezos/tezos/-/issues/6229 *) - return - (Send_raw_transaction.Output - (Error {code = -32000; message = reason; data = None})) - (* By default, the current dispatch handles the inputs *)) - | Eth_call.Input (Some (call, _)) -> ( - let* call_result = Rollup_node_rpc.simulate_call call in - match call_result with - | Ok result -> return (Eth_call.Output (Ok result)) - | Error () -> - return - (Eth_call.Output - (Error - JSONRPC. - { - code = -32000; - message = "Call simulation failed"; - data = None; - }))) - | Get_estimate_gas.Input (Some (call, _)) -> - let* gas = Rollup_node_rpc.estimate_gas call in - return (Get_estimate_gas.Output (Ok gas)) - | Txpool_content.Input _ -> - let* txpool = Rollup_node_rpc.txpool () in - return (Txpool_content.Output (Ok txpool)) - | Web3_clientVersion.Input _ -> - return (Web3_clientVersion.Output (Ok client_version)) - | Web3_sha3.Input (Some data) -> - let open Ethereum_types in - let (Hex h) = data in - let bytes = Hex.to_bytes_exn (`Hex h) in - let hash_bytes = Tezos_crypto.Hacl.Hash.Keccak_256.digest bytes in - let hash = Hex.of_bytes hash_bytes |> Hex.show in - return (Web3_sha3.Output (Ok (Hash (Hex hash)))) - | Get_logs.Input (Some filter) -> - let+ logs = - Filter_helpers.get_logs - config.log_filter - (module Rollup_node_rpc) - filter + (Error + JSONRPC. + { + code = -3200; + message = "Method not supported"; + data = Some (`String method_); + }) + | Method (Produce_block.Method, module_) -> + let f (_ : unit option) = + let open Lwt_result_syntax in + let* nb_transactions = + Tx_pool.produce_block ~force:true ~timestamp:(Helpers.now ()) + in + return + (Either.Left + (Ethereum_types.quantity_of_z @@ Z.of_int nb_transactions)) in - Get_logs.Output (Ok logs) - | _ -> Error_monad.failwith "Unsupported method\n%!" + build ~f module_ parameters + | _ -> Stdlib.failwith "The pattern matching of methods is not exhaustive" in - let* output = dispatch_input_aux input in - if config.verbose then - Data_encoding.Json.construct Output.encoding (Output.Box output, id) - |> Data_encoding.Json.to_string |> Printf.printf "%s\n%!" ; - return (output, id) + return JSONRPC.{value; id} -let dispatch config ctx dir = - Directory.register0 dir dispatch_service (fun () input -> +let generic_dispatch config ctx dir path dispatch_request = + Directory.register0 dir (dispatch_service ~path) (fun () input -> let open Lwt_result_syntax in match input with - | Singleton (Box input, rpc) -> - let+ output, rpc = dispatch_input config ctx (input, rpc) in - Singleton (Output.Box output, rpc) - | Batch inputs -> - let+ outputs = - List.map_es - (fun (Input.Box input, rpc) -> - let+ output, rpc = dispatch_input config ctx (input, rpc) in - (Output.Box output, rpc)) - inputs - in - Batch outputs) + | Singleton request -> + let*! response = dispatch_request config ctx request in + return (Singleton response) + | Batch requests -> + let*! outputs = List.map_s (dispatch_request config ctx) requests in + return (Batch outputs)) + +let dispatch_public config ctx dir = + generic_dispatch config ctx dir Path.root dispatch_request + +let dispatch_private config ctx dir = + generic_dispatch + config + ctx + dir + Path.(add_suffix root "private") + dispatch_private_request let directory config - ((module Rollup_node_rpc : Rollup_node.S), smart_rollup_address) = + ((module Rollup_node_rpc : Services_backend_sig.S), smart_rollup_address) = + Directory.empty |> version + |> dispatch_public + config + ((module Rollup_node_rpc : Services_backend_sig.S), smart_rollup_address) + +let private_directory config + ((module Rollup_node_rpc : Services_backend_sig.S), smart_rollup_address) = Directory.empty |> version - |> dispatch + |> dispatch_private config - ((module Rollup_node_rpc : Rollup_node.S), smart_rollup_address) + ((module Rollup_node_rpc : Services_backend_sig.S), smart_rollup_address) diff --git a/etherlink/bin_evm_node/lib_prod/services_backend_sig.ml b/etherlink/bin_evm_node/lib_prod/services_backend_sig.ml new file mode 100644 index 0000000000000000000000000000000000000000..8772d6288c020c51b2a138eede549d7e318a1bd6 --- /dev/null +++ b/etherlink/bin_evm_node/lib_prod/services_backend_sig.ml @@ -0,0 +1,120 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(*****************************************************************************) + +module type S = sig + (** [balance address] returns the [address]'s balance. *) + val balance : Ethereum_types.address -> Ethereum_types.quantity tzresult Lwt.t + + (** [nonce address] returns the [address]'s nonce. *) + val nonce : + Ethereum_types.address -> Ethereum_types.quantity option tzresult Lwt.t + + (** [code address] returns the [address]'s code. *) + val code : Ethereum_types.address -> Ethereum_types.hex tzresult Lwt.t + + (** [inject_raw_transactions ~timestamp ~smart_rollup_address + ~transactions] crafts the hashes and chunks of each transaction + of [transactions]. Injects the chunks and returns the hashes of + injected transactions. *) + val inject_raw_transactions : + timestamp:Time.Protocol.t -> + smart_rollup_address:string -> + transactions:string list -> + Ethereum_types.hash list tzresult Lwt.t + + (** [current_block ~full_transaction_object] returns the most recent + processed and stored block. + + If [full_transaction_object] is [true], returns the transaction objects, + the transactions hashes otherwise. + *) + val current_block : + full_transaction_object:bool -> Ethereum_types.block tzresult Lwt.t + + (** [current_block_number ()] returns the most recent processed and + stored block number. *) + val current_block_number : unit -> Ethereum_types.block_height tzresult Lwt.t + + (** [nth_block ~full_transaction_object n] returns the [n]th + processed and stored block. + + If [full_transaction_object] is [true], returns the transaction objects, + the transactions hashes otherwise. + *) + val nth_block : + full_transaction_object:bool -> Z.t -> Ethereum_types.block tzresult Lwt.t + + (** [block_by_hash ~full_transaction_object hash] returns the block with the + given [hash]. + + If [full_transaction_object] is [true], returns the transaction objects, + the transactions hashes otherwise. + *) + val block_by_hash : + full_transaction_object:bool -> + Ethereum_types.block_hash -> + Ethereum_types.block tzresult Lwt.t + + (** [transaction_receipt tx_hash] returns the receipt of [tx_hash]. *) + val transaction_receipt : + Ethereum_types.hash -> + Ethereum_types.transaction_receipt option 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 + + (** [chain_id ()] returns chain id defined by the rollup. *) + val chain_id : unit -> Ethereum_types.quantity tzresult Lwt.t + + (** [base_fee_per_gas ()] returns base fee defined by the rollup. *) + val base_fee_per_gas : unit -> Ethereum_types.quantity tzresult Lwt.t + + (** [kernel_version ()] returns the internal kernel version (i.e the + commit hash where the kernel was compiled). *) + val kernel_version : unit -> string tzresult Lwt.t + + (** [simulate_call call_info] asks the rollup to simulate a call, + and returns the result. *) + val simulate_call : + Ethereum_types.call -> (Ethereum_types.hash, unit) result tzresult Lwt.t + + (** [estimate_gas call_info] asks the rollup to simulate a call, and + returns the gas used to execute the call. *) + val estimate_gas : + Ethereum_types.call -> Ethereum_types.quantity tzresult Lwt.t + + (** [is_tx_valid tx_raw] checks if the transaction is valid. Checks + if the nonce is correct and returns the associated public key of + transaction. *) + val is_tx_valid : + string -> (Ethereum_types.address, string) result tzresult Lwt.t + + (** [storage_at address pos] returns the value at index [pos] of the + account [address]'s storage. *) + val storage_at : + Ethereum_types.address -> + Ethereum_types.quantity -> + Ethereum_types.hex tzresult Lwt.t +end + +module type Backend = sig + module READER : Durable_storage.READER + + module TxEncoder : Publisher.TxEncoder + + module Publisher : Publisher.Publisher + + module SimulatorBackend : Simulator.SimulationBackend +end + +module Make (Backend : Backend) : S = struct + include Durable_storage.Make (Backend.READER) + include Publisher.Make (Backend.TxEncoder) (Backend.Publisher) + include Simulator.Make (Backend.SimulatorBackend) +end diff --git a/etherlink/bin_evm_node/lib_prod/simulation.ml b/etherlink/bin_evm_node/lib_prod/simulation.ml index f6463369de45b1c41415b21b980992b775d3c3e3..31fe037c1544aae7c3935dd2ded3bdb0c0493cf5 100644 --- a/etherlink/bin_evm_node/lib_prod/simulation.ml +++ b/etherlink/bin_evm_node/lib_prod/simulation.ml @@ -52,8 +52,6 @@ let rlp_encode call = (* we aim to use [String.chunk_bytes] *) Rlp.encode rlp_form -let tx_rlp_encode tx_raw = `Hex tx_raw |> Hex.to_bytes_exn - type simulation_message = | Start | Simple of string @@ -94,21 +92,18 @@ let evaluation_tag = "\000" (** Tag indicating simulation is a validation *) let validation_tag = "\001" -(** [hex_str_of_binary_string s] translate a binary string into an hax string *) -let hex_str_of_binary_string s = s |> Hex.of_string |> Hex.show - (** [add_tag tag bytes] prefixes bytes by the given tag *) let add_tag tag bytes = tag ^ Bytes.to_string bytes |> String.to_bytes let encode_message = function - | Start -> hex_str_of_binary_string @@ simulation_tag - | Simple s -> hex_str_of_binary_string @@ simulation_tag ^ simple_tag ^ s + | Start -> simulation_tag + | Simple s -> simulation_tag ^ simple_tag ^ s | NewChunked n -> let n_le_str = Ethereum_types.u16_to_bytes n in - hex_str_of_binary_string @@ simulation_tag ^ new_chunked_tag ^ n_le_str + simulation_tag ^ new_chunked_tag ^ n_le_str | Chunk (i, c) -> let i_le_str = Ethereum_types.u16_to_bytes i in - hex_str_of_binary_string @@ simulation_tag ^ chunk_tag ^ i_le_str ^ c + simulation_tag ^ chunk_tag ^ i_le_str ^ c let encode call = let open Result_syntax in @@ -120,7 +115,7 @@ let encode call = let encode_tx tx = let open Result_syntax in let* messages = - tx |> tx_rlp_encode |> add_tag validation_tag |> split_in_messages + Bytes.of_string tx |> add_tag validation_tag |> split_in_messages in return @@ List.map encode_message messages @@ -151,8 +146,6 @@ module Encodings = struct log_kernel_debug_file : string option; } - let hex_string = conv Bytes.of_string Bytes.to_string bytes - let insight_request = union [ @@ -183,11 +176,11 @@ module Encodings = struct @@ obj4 (req "messages" - (list string) + (list (string' Hex)) ~description:"Serialized messages for simulation.") (opt "reveal_pages" - (list hex_string) + (list (string' Hex)) ~description:"Pages (at most 4kB) to be used for revelation ticks") (dft "insight_requests" @@ -221,6 +214,20 @@ module Encodings = struct (fun (result, success) -> {result; success}) (tup2 (option bytes) bool_as_bytes) + let insights_from_list l = + match l with + | [result; success] -> + Some + { + result; + success = + Option.bind success (fun s -> + s + |> Data_encoding.Binary.of_bytes Data_encoding.bool + |> Result.to_option); + } + | _ -> None + let eval_result = conv (fun {state_hash; status; output; inbox_level; num_ticks; insights} -> @@ -253,36 +260,25 @@ module Encodings = struct ~description:"PVM state values requested after the simulation") end -let parse_insights decode (r : Data_encoding.json) = - let s = Data_encoding.Json.destruct Encodings.eval_result r in - match decode s.insights with - | Some insight -> Lwt.return_ok insight - | None -> - Error_monad.failwith - "Couldn't parse insights: %s" - (Data_encoding.Json.to_string r) - -let decode_call_result {Encodings.result; success} = +let call_result {Encodings.result; success} = + let open Lwt_result_syntax in match (result, success) with | Some b, _ -> let v = b |> Hex.of_bytes |> Hex.show in - Some (Ok (Hash (Hex v))) + return (Ok (Hash (Hex v))) (* TODO: https://gitlab.com/tezos/tezos/-/issues/6752 better propagate errors and reverts messages from kernel to help the user debug their call *) - | None, Some false -> Some (Error ()) - | _ -> None + | None, Some false -> return (Error ()) + | _ -> failwith "Insights of 'call_result' cannot be parsed" -let call_result json = parse_insights decode_call_result json - -let decode_gas_estimation {Encodings.result; _} = - match result with - | Some b -> b |> Bytes.to_string |> Z.of_bits |> Option.some - | None -> None - -let gas_estimation json = +let gas_estimation {Encodings.result; _} = let open Lwt_result_syntax in - let* simulated_amount = parse_insights decode_gas_estimation json in + let* simulated_amount = + match result with + | Some b -> b |> Bytes.to_string |> Z.of_bits |> return + | _ -> failwith "Insights of 'gas_estimation' cannot be parsed" + in (* See EIP2200 for reference. But the tl;dr is: we cannot do the opcode SSTORE if we have less than 2300 gas available, even if we don't consume it. The simulated amount then gives an amount of gas insufficient @@ -293,18 +289,14 @@ let gas_estimation json = let simulated_amount = Z.(add simulated_amount (of_int 2300)) in return (quantity_of_z simulated_amount) -let decode_is_valid {Encodings.result; success} = +let is_tx_valid {Encodings.result; success} = + let open Lwt_result_syntax in match (result, success) with | Some payload, Some success -> if success then let address = Ethereum_types.decode_address payload in - Some (Ok address) + return (Ok address) else let error_msg = Bytes.to_string payload in - Some (Error error_msg) - | _ -> None - -let is_tx_valid json = - let open Lwt_result_syntax in - let* result = parse_insights decode_is_valid json in - return result + return (Error error_msg) + | _ -> failwith "Insights of 'is_tx_valid' is not [Some _, Some _]" diff --git a/etherlink/bin_evm_node/lib_prod/simulator.ml b/etherlink/bin_evm_node/lib_prod/simulator.ml new file mode 100644 index 0000000000000000000000000000000000000000..1762db6f6cefe620aab7d5ec9a0493d168e65d72 --- /dev/null +++ b/etherlink/bin_evm_node/lib_prod/simulator.ml @@ -0,0 +1,81 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(*****************************************************************************) + +module type SimulationBackend = sig + val simulate_and_read : + input:Simulation.Encodings.simulate_input -> + Simulation.Encodings.insights tzresult Lwt.t +end + +module Make (SimulationBackend : SimulationBackend) = struct + let simulate_call call = + let open Lwt_result_syntax in + let*? messages = Simulation.encode call in + let insight_requests = + [ + Simulation.Encodings.Durable_storage_key ["evm"; "simulation_result"]; + (* TODO: https://gitlab.com/tezos/tezos/-/issues/5900 + for now the status is not used but it should be for error handling *) + Simulation.Encodings.Durable_storage_key ["evm"; "simulation_status"]; + ] + in + let* results = + SimulationBackend.simulate_and_read + ~input: + { + messages; + reveal_pages = None; + insight_requests; + log_kernel_debug_file = Some "simulate_call"; + } + in + Simulation.call_result results + + let estimate_gas call = + let open Lwt_result_syntax in + let*? messages = Simulation.encode call in + let insight_requests = + [ + Simulation.Encodings.Durable_storage_key ["evm"; "simulation_gas"]; + (* TODO: https://gitlab.com/tezos/tezos/-/issues/5900 + for now the status is not used but it should be for error handling *) + Simulation.Encodings.Durable_storage_key ["evm"; "simulation_status"]; + ] + in + let* results = + SimulationBackend.simulate_and_read + ~input: + { + messages; + reveal_pages = None; + insight_requests; + log_kernel_debug_file = Some "estimate_gas"; + } + in + Simulation.gas_estimation results + + let is_tx_valid tx_raw = + let open Lwt_result_syntax in + let*? messages = Simulation.encode_tx tx_raw in + let insight_requests = + [ + Simulation.Encodings.Durable_storage_key ["evm"; "simulation_result"]; + Simulation.Encodings.Durable_storage_key ["evm"; "simulation_status"]; + ] + in + let* results = + SimulationBackend.simulate_and_read + ~input: + { + messages; + reveal_pages = None; + insight_requests; + log_kernel_debug_file = Some "tx_validity"; + } + in + Simulation.is_tx_valid results +end diff --git a/etherlink/bin_evm_node/lib_prod/transaction_format.ml b/etherlink/bin_evm_node/lib_prod/transaction_format.ml new file mode 100644 index 0000000000000000000000000000000000000000..3e51c177b760b8cf9951825e1945872d4f6f30b3 --- /dev/null +++ b/etherlink/bin_evm_node/lib_prod/transaction_format.ml @@ -0,0 +1,87 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(*****************************************************************************) + +(* The hard limit is 4096 but it needs to add the external message tag. *) +let max_input_size = 4095 + +let smart_rollup_address_size = 20 + +let transaction_tag_size = 1 + +let framing_protocol_tag_size = 1 + +type transaction = + | Simple of string + | NewChunked of (string * int * string) + | Chunk of string + +let encode_transaction ~smart_rollup_address kind = + let data = + match kind with + | Simple data -> "\000" ^ data + | NewChunked (tx_hash, len, all_chunk_hashes) -> + let number_of_chunks_bytes = Ethereum_types.u16_to_bytes len in + "\001" ^ tx_hash ^ number_of_chunks_bytes ^ all_chunk_hashes + | Chunk data -> "\002" ^ data + in + "\000" ^ smart_rollup_address ^ data + +let chunk_transaction ~tx_hash ~tx_raw = + let open Result_syntax in + let size_per_chunk = + max_input_size - framing_protocol_tag_size - smart_rollup_address_size + - transaction_tag_size - 2 (* Index as u16 *) + - (Ethereum_types.transaction_hash_size * 2) + in + let* chunks = String.chunk_bytes size_per_chunk (Bytes.of_string tx_raw) in + let all_chunk_hashes, chunks = + List.fold_left_i + (fun i (all_chunk_hashes, chunks) chunk -> + let chunk_hash = Ethereum_types.hash_raw_tx chunk in + let all_chunk_hashes = all_chunk_hashes ^ chunk_hash in + let chunk = + Chunk (tx_hash ^ Ethereum_types.u16_to_bytes i ^ chunk_hash ^ chunk) + in + (all_chunk_hashes, chunk :: chunks)) + ("", []) + chunks + in + let new_chunk_transaction = + NewChunked (tx_hash, List.length chunks, all_chunk_hashes) + in + return (tx_hash, new_chunk_transaction :: chunks) + +let make_evm_inbox_transactions tx_raw = + let open Result_syntax in + (* Maximum size describes the maximum size of [tx_raw] to fit + in a simple transaction. *) + let maximum_size = + max_input_size - framing_protocol_tag_size - smart_rollup_address_size + - transaction_tag_size - Ethereum_types.transaction_hash_size + in + let tx_hash = Ethereum_types.hash_raw_tx tx_raw in + if String.length tx_raw <= maximum_size then + (* Simple transaction, fits in a single input. *) + let tx = Simple (tx_hash ^ tx_raw) in + return (tx_hash, [tx]) + else chunk_transaction ~tx_hash ~tx_raw + +(** [make_encoded_messages ~smart_rollup_address raw_tx] returns the + hash of the transaction, and a list of transactions to include in the inbox. + - [smart_rollup_address] is encoded on 20 bytes + - [raw_tx] is an ethereum transaction in hex format (without the 0x prefix). + + All messages go through the same encoding, but will only be chunked if + necessary. *) +let make_encoded_messages ~smart_rollup_address tx_raw = + let open Result_syntax in + let* tx_hash, messages = make_evm_inbox_transactions tx_raw in + let tx_hash = + Ethereum_types.(Hash Hex.(of_string tx_hash |> show |> hex_of_string)) + in + let messages = List.map (encode_transaction ~smart_rollup_address) messages in + return (tx_hash, messages) diff --git a/etherlink/bin_evm_node/lib_prod/tx_pool.ml b/etherlink/bin_evm_node/lib_prod/tx_pool.ml index 0d274b742c3aa12d78deaec465934acbc90efada..42058cfdac62816ebf153de227ec651c41586703 100644 --- a/etherlink/bin_evm_node/lib_prod/tx_pool.ml +++ b/etherlink/bin_evm_node/lib_prod/tx_pool.ml @@ -12,7 +12,7 @@ module Pool = struct (** Transaction stored in the pool. *) type transaction = { index : int64; (* Global index of the transaction. *) - raw_tx : Ethereum_types.hex; (* Current transaction. *) + raw_tx : string; (* Current transaction. *) gas_price : Z.t; (* The maximum price the user can pay for fees. *) } @@ -24,7 +24,7 @@ module Pool = struct let empty : t = {transactions = Pkey_map.empty; global_index = Int64.zero} (** Add a transacion to the pool.*) - let add t pkey base_fee (raw_tx : Ethereum_types.hex) = + let add t pkey base_fee raw_tx = let open Result_syntax in let {transactions; global_index} = t in let* (Qty nonce) = Ethereum_types.transaction_nonce raw_tx in @@ -75,7 +75,7 @@ module Pool = struct let selected = selected |> Nonce_map.bindings |> List.map snd in (selected, {transactions; global_index}) - (** Removes from the pool the transactions matching the predicate + (** Removes from the pool the transactions matching the predicate for the given pkey. *) let remove pkey predicate t = let _txs, t = partition pkey predicate t in @@ -104,15 +104,23 @@ module Pool = struct aux current_nonce user_transactions |> Ethereum_types.quantity_of_z end +type mode = Proxy of {rollup_node_endpoint : Uri.t} | Sequencer + +type parameters = { + rollup_node : (module Services_backend_sig.S); + smart_rollup_address : string; + mode : mode; +} + module Types = struct type state = { - rollup_node : (module Rollup_node.S); + rollup_node : (module Services_backend_sig.S); smart_rollup_address : string; - mutable level : Ethereum_types.block_height; mutable pool : Pool.t; + mode : mode; } - type parameters = (module Rollup_node.S) * string + type nonrec parameters = parameters end module Name = struct @@ -131,9 +139,9 @@ end module Request = struct type ('a, 'b) t = | Add_transaction : - Ethereum_types.hex + string -> ((Ethereum_types.hash, string) result, tztrace) t - | New_l2_head : Ethereum_types.block_height -> (unit, tztrace) t + | Inject_transactions : (bool * Time.Protocol.t) -> (int, tztrace) t type view = View : _ t -> view @@ -148,28 +156,39 @@ module Request = struct ~title:"Add_transaction" (obj2 (req "request" (constant "add_transaction")) - (req "transaction" Ethereum_types.hex_encoding)) + (req "transaction" string)) (function | View (Add_transaction messages) -> Some ((), messages) | _ -> None) (fun ((), messages) -> View (Add_transaction messages)); case (Tag 1) - ~title:"New_l2_head" - (obj2 + ~title:"Inject_transactions" + (obj3 (req "request" (constant "new_l2_head")) - (req "block_height" Ethereum_types.block_height_encoding)) - (function View (New_l2_head b) -> Some ((), b) | _ -> None) - (fun ((), b) -> View (New_l2_head b)); + (req "force" bool) + (req "timestamp" Time.Protocol.encoding)) + (function + | View (Inject_transactions (force, timestamp)) -> + Some ((), force, timestamp) + | _ -> None) + (fun ((), force, timestamp) -> + View (Inject_transactions (force, timestamp))); ] let pp ppf (View r) = match r with | Add_transaction transaction -> - let (Ethereum_types.Hex transaction) = transaction in - Format.fprintf ppf "Add [%s] tx to tx-pool" transaction - | New_l2_head block_height -> - let (Ethereum_types.Block_height block_height) = block_height in - Format.fprintf ppf "New L2 head: %s" (Z.to_string block_height) + Format.fprintf + ppf + "Add [%s] tx to tx-pool" + (Hex.of_string transaction |> Hex.show) + | Inject_transactions (force, timestamp) -> + Format.fprintf + ppf + "Inject transactions at %a (force is %b)" + Time.Protocol.pp + timestamp + force end module Worker = Worker.MakeSingle (Name) (Request) (Types) @@ -192,7 +211,6 @@ let on_transaction state tx_raw = (* Add the tx to the pool*) let*? pool = Pool.add pool pkey base_fee tx_raw in (* compute the hash *) - let tx_raw = Ethereum_types.hex_to_bytes tx_raw in let tx_hash = Ethereum_types.hash_raw_tx tx_raw in let hash = Ethereum_types.hash_of_string Hex.(of_string tx_hash |> show) @@ -204,12 +222,10 @@ let on_transaction state tx_raw = state.pool <- pool ; return (Ok hash) -let on_head state block_height = +let inject_transactions ~force ~timestamp ~smart_rollup_address rollup_node pool + = let open Lwt_result_syntax in - let open Types in - let {rollup_node = (module Rollup_node); smart_rollup_address; pool; _} = - state - in + let (module Rollup_node : Services_backend_sig.S) = rollup_node in (* Get all the addresses in the tx-pool. *) let addresses = Pool.addresses pool in (* Get the nonce related to each address. *) @@ -261,29 +277,46 @@ let on_head state block_height = Int64.compare index_a index_b) |> List.map (fun Pool.{raw_tx; _} -> raw_tx) in - (* Send the txs to the rollup *) - let*! () = - Lwt_list.iter_s - (fun raw_tx -> - let open Lwt_syntax in - let+ hash_result = - Rollup_node.inject_raw_transaction ~smart_rollup_address raw_tx - in - match hash_result with - | Error _ -> - (* TODO: https://gitlab.com/tezos/tezos/-/issues/6569*) - Format.printf "[tx-pool] Error when sending transaction.\n%!" - | Ok _ -> - Format.printf - (* TODO: https://gitlab.com/tezos/tezos/-/issues/6569*) - "[tx-pool] Transaction %s sent to the rollup.\n%!" - (Ethereum_types.hex_to_string raw_tx)) - txs + + let n = List.length txs in + + if n > 0 || force then + (* Send the txs to the rollup *) + let*! hashes = + Rollup_node.inject_raw_transactions + ~timestamp + ~smart_rollup_address + ~transactions:txs + in + let nb_transactions = + match hashes with + | Error _ -> + (* TODO: https://gitlab.com/tezos/tezos/-/issues/6569*) + Format.printf "[tx-pool] Error when sending transaction.\n%!" ; + 0 + | Ok hashes -> + List.iter + (fun hash -> + Format.printf + (* TODO: https://gitlab.com/tezos/tezos/-/issues/6569*) + "[tx-pool] Transaction %s sent to the rollup.\n%!" + (Ethereum_types.hash_to_string hash)) + hashes ; + n + in + return (pool, nb_transactions) + else return (pool, 0) + +let on_head ~force ~timestamp state = + let open Lwt_result_syntax in + let open Types in + let {rollup_node; smart_rollup_address; pool; _} = state in + let* pool, nb_transactions = + inject_transactions ~force ~timestamp ~smart_rollup_address rollup_node pool in (* update the pool *) - state.level <- block_height ; state.pool <- pool ; - return_unit + return nb_transactions module Handlers = struct type self = worker @@ -297,20 +330,15 @@ module Handlers = struct match request with | Request.Add_transaction raw_tx -> protect @@ fun () -> on_transaction state raw_tx - | Request.New_l2_head block_height -> - protect @@ fun () -> on_head state block_height + | Request.Inject_transactions (force, timestamp) -> + protect @@ fun () -> on_head ~force ~timestamp state type launch_error = error trace - let on_launch _w () (rollup_node, smart_rollup_address) = + let on_launch _w () + ({rollup_node; smart_rollup_address; mode} : Types.parameters) = let state = - Types. - { - rollup_node; - smart_rollup_address; - level = Block_height Z.zero; - pool = Pool.empty; - } + Types.{rollup_node; smart_rollup_address; pool = Pool.empty; mode} in Lwt_result_syntax.return state @@ -349,43 +377,52 @@ let handle_request_error rq = | Error (Closed (Some errs)) -> Lwt.return_error errs | Error (Any exn) -> Lwt.return_error [Exn exn] -(** Sends New_l2_level each time there is a new l2 level -TODO: https://gitlab.com/tezos/tezos/-/issues/6079 -listen to the node instead of pulling the level each 5s -*) -let rec subscribe_l2_block worker = - let open Lwt_result_syntax in - let*! () = Lwt_unix.sleep 5.0 in - let state = Worker.state worker in - let Types.{rollup_node = (module Rollup_node_rpc); _} = state in - (* Get the current eth level.*) - let*! res = Rollup_node_rpc.current_block_number () in - match res with - | Error _ -> +let make_streamed_call ~rollup_node_endpoint = + let open Lwt_syntax in + let stream, push = Lwt_stream.create () in + let on_chunk _v = push (Some ()) and on_close () = push None in + let* _spill_all = + Tezos_rpc_http_client_unix.RPC_client_unix.call_streamed_service + [Media_type.json] + ~base:rollup_node_endpoint + Rollup_node_services.global_block_watcher + ~on_chunk + ~on_close + () + () + () + in + return stream + +let rec subscribe_l2_block ~stream_l2 worker = + let open Lwt_syntax in + let* new_head = Lwt_stream.get stream_l2 in + match new_head with + | Some () -> + let* _pushed = + Worker.Queue.push_request + worker + (Request.Inject_transactions (true, Helpers.now ())) + in + subscribe_l2_block ~stream_l2 worker + | None -> (* Kind of retry strategy *) Format.printf "Connection with the rollup node has been lost, retrying...\n" ; - subscribe_l2_block worker - | Ok block_number -> - if state.level != block_number then - let*! _pushed = - Worker.Queue.push_request worker (Request.New_l2_head block_number) - in - subscribe_l2_block worker - else subscribe_l2_block worker - -let start ((module Rollup_node_rpc : Rollup_node.S), smart_rollup_address) = + let* () = Lwt_unix.sleep 1. in + subscribe_l2_block ~stream_l2 worker + +let start ({mode; _} as parameters) = let open Lwt_result_syntax in - let+ worker = - Worker.launch - table - () - ((module Rollup_node_rpc), smart_rollup_address) - (module Handlers) - in + let+ worker = Worker.launch table () parameters (module Handlers) in let () = Lwt.dont_wait - (fun () -> subscribe_l2_block worker) + (fun () -> + match mode with + | Proxy {rollup_node_endpoint} -> + let*! stream_l2 = make_streamed_call ~rollup_node_endpoint in + subscribe_l2_block ~stream_l2 worker + | Sequencer -> Lwt.return_unit) (fun _ -> (* TODO: https://gitlab.com/tezos/tezos/-/issues/6569*) Format.printf "[tx-pool] Pool has been stopped.\n%!") @@ -417,3 +454,11 @@ let nonce pkey = | Some current_nonce -> Pool.next_nonce pkey current_nonce pool in return next_nonce + +let produce_block ~force ~timestamp = + let open Lwt_result_syntax in + let*? worker = Lazy.force worker in + Worker.Queue.push_request_and_wait + worker + (Request.Inject_transactions (force, timestamp)) + |> handle_request_error diff --git a/etherlink/bin_evm_node/lib_prod/tx_pool.mli b/etherlink/bin_evm_node/lib_prod/tx_pool.mli index 13aef691141a796f641af1c91e2963ff23321ce2..cb351cc44243f2b167a977eaa83f2f02e0086eda 100644 --- a/etherlink/bin_evm_node/lib_prod/tx_pool.mli +++ b/etherlink/bin_evm_node/lib_prod/tx_pool.mli @@ -5,19 +5,35 @@ (* *) (*****************************************************************************) -(** [start config] starts the tx-pool. The [config] represents the - Rollup_node rpc module and the address of the smart rollup. *) -val start : (module Rollup_node.S) * string -> unit tzresult Lwt.t +(* TODO: https://gitlab.com/tezos/tezos/-/issues/6672 + It should be created by the configuration, or at least using values of + the configuration. *) +type mode = Proxy of {rollup_node_endpoint : Uri.t} | Sequencer + +type parameters = { + rollup_node : (module Services_backend_sig.S); (** The backend RPC module. *) + smart_rollup_address : string; (** The address of the smart rollup. *) + mode : mode; +} + +(** [start parameters] starts the tx-pool *) +val start : parameters -> unit tzresult Lwt.t (** [shutdown ()] stops the tx-pool, waiting for the ongoing request to be processed. *) val shutdown : unit -> unit Lwt.t (** [add raw_tx] adds a raw eth transaction to the tx-pool. *) -val add : - Ethereum_types.hex -> (Ethereum_types.hash, string) result tzresult Lwt.t +val add : string -> (Ethereum_types.hash, string) result tzresult Lwt.t (** [nonce address] returns the nonce of the user Returns the first gap in the tx-pool, or the nonce stored on the rollup if no transactions are in the pool. *) val nonce : Ethereum_types.Address.t -> Ethereum_types.quantity tzresult Lwt.t + +(** [produce_block ~force ~timestamp] takes the transactions in the tx pool + and produces a block from it, returns the number of transaction in + the block. The block is not produced if the list of + transactions is empty and [force] is set to [false]. *) +val produce_block : + force:bool -> timestamp:Time.Protocol.t -> int tzresult Lwt.t diff --git a/etherlink/kernel_evm/kernel/tests/resources/ghostnet_evm_kernel.wasm b/etherlink/kernel_evm/kernel/tests/resources/ghostnet_evm_kernel.wasm index 06d7f7c87951d45a6771b25457a94c50ec21318e..a30b36302e7048371744ec89df2c0afa050f127f 100755 Binary files a/etherlink/kernel_evm/kernel/tests/resources/ghostnet_evm_kernel.wasm and b/etherlink/kernel_evm/kernel/tests/resources/ghostnet_evm_kernel.wasm differ diff --git a/manifest/main.ml b/manifest/main.ml index cc0aa492faf50e9104e136123279b6b0b99f2145..9232c5b64930d0a9a70761562ee7a5ab1b876dbe 100644 --- a/manifest/main.ml +++ b/manifest/main.ml @@ -8386,6 +8386,11 @@ let evm_node_lib_prod = evm_node_lib_prod_encoding |> open_; lwt_exit; evm_node_config |> open_; + octez_context_disk; + octez_context_encoding; + octez_scoru_wasm; + octez_scoru_wasm_helpers |> open_; + octez_scoru_wasm_debugger_lib |> open_; ] let evm_node_lib_dev_encoding =