From 19d8d5273e8102cd52e9763145a69afc4f038fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Tue, 6 May 2025 13:58:06 +0200 Subject: [PATCH 1/3] Etherlink/Node/RPC: move definition of the forward container We turn the anonymous Tx_container module implementing the forward-request container of the RPC mode into a toplevel functor. This will allow us to instantiate the functor a second time in the case of Tezlink --- etherlink/bin_node/lib_dev/rpc.ml | 215 ++++++++++++++++-------------- 1 file changed, 117 insertions(+), 98 deletions(-) diff --git a/etherlink/bin_node/lib_dev/rpc.ml b/etherlink/bin_node/lib_dev/rpc.ml index cee90c95f339..98c1efd94645 100644 --- a/etherlink/bin_node/lib_dev/rpc.ml +++ b/etherlink/bin_node/lib_dev/rpc.ml @@ -61,114 +61,133 @@ let set_metrics_confirmed_levels (ctxt : Evm_ro_context.t) = Metrics.set_l1_level ~level:l1_level | None -> () -let container_forward_request ~public_endpoint ~private_endpoint ~keep_alive : - L2_types.evm_chain_family Services_backend_sig.tx_container = - Services_backend_sig.Evm_tx_container - (module struct - type address = Ethereum_types.address - - type legacy_transaction_object = Ethereum_types.legacy_transaction_object - - type transaction_object = Transaction_object.t - - let rpc_error = - Internal_event.Simple.declare_2 - ~section:Events.section - ~name:"local_node_rpc_failure" - ~msg:"local node failed answering {rpc} with {message}" - ~level:Error - ("rpc", Data_encoding.string) - ("message", Data_encoding.string) - - let forwarding_transaction = - Internal_event.Simple.declare_1 - ~section:Events.section - ~name:"forward_transaction" - ~msg:"forwarding transaction {tx_hash} to local node" - ~level:Info - ~pp1:(fun fmt Ethereum_types.(Hash (Hex h)) -> - Format.fprintf fmt "%10s" h) - ("tx_hash", Ethereum_types.hash_encoding) - - let get_or_emit_error ~rpc_name res = - let open Lwt_result_syntax in - match res with - | Ok res -> return_some res - | Error msg -> - let*! () = Internal_event.Simple.emit rpc_error (rpc_name, msg) in - return_none - - let nonce ~next_nonce address = - let open Lwt_result_syntax in - let* res = - Injector.get_transaction_count - ~keep_alive - ~base:public_endpoint - address - (* The function [nonce] is only ever called when - requesting the nonce for the pending block. It's - safe to assume the pending block. *) - Ethereum_types.Block_parameter.(Block_parameter Pending) - in - let* nonce = get_or_emit_error ~rpc_name:"get_transaction_count" res in - match nonce with - | Some nonce -> return nonce - | None -> - (*we return the known next_nonce instead of failing *) - return next_nonce - - let add ~next_nonce:_ - (tx_object : Ethereum_types.legacy_transaction_object) ~raw_tx = - let open Lwt_syntax in - let* () = - Internal_event.Simple.emit forwarding_transaction tx_object.hash - in - Injector.inject_transaction - ~keep_alive - ~base:private_endpoint - ~tx_object - ~raw_tx:(Ethereum_types.hex_to_bytes raw_tx) - - let find hash = - let open Lwt_result_syntax in - let* res = - Injector.get_transaction_by_hash - ~keep_alive - ~base:public_endpoint - hash - in - let* tx_object = - get_or_emit_error ~rpc_name:"get_transaction_by_hash" res - in - let tx_object = Option.join tx_object in - return tx_object +module Forward_container (C : sig + val public_endpoint : Uri.t + + val private_endpoint : Uri.t + + val keep_alive : bool +end) : + Services_backend_sig.Tx_container + with type address = Ethereum_types.address + and type legacy_transaction_object = + Ethereum_types.legacy_transaction_object + and type transaction_object = Transaction_object.t = struct + type address = Ethereum_types.address + + type legacy_transaction_object = Ethereum_types.legacy_transaction_object + + type transaction_object = Transaction_object.t + + let rpc_error = + Internal_event.Simple.declare_2 + ~section:Events.section + ~name:"local_node_rpc_failure" + ~msg:"local node failed answering {rpc} with {message}" + ~level:Error + ("rpc", Data_encoding.string) + ("message", Data_encoding.string) + + let forwarding_transaction = + Internal_event.Simple.declare_1 + ~section:Events.section + ~name:"forward_transaction" + ~msg:"forwarding transaction {tx_hash} to local node" + ~level:Info + ~pp1:(fun fmt Ethereum_types.(Hash (Hex h)) -> + Format.fprintf fmt "%10s" h) + ("tx_hash", Ethereum_types.hash_encoding) + + let get_or_emit_error ~rpc_name res = + let open Lwt_result_syntax in + match res with + | Ok res -> return_some res + | Error msg -> + let*! () = Internal_event.Simple.emit rpc_error (rpc_name, msg) in + return_none + + let nonce ~next_nonce address = + let open Lwt_result_syntax in + let* res = + Injector.get_transaction_count + ~keep_alive:C.keep_alive + ~base:C.public_endpoint + address + (* The function [nonce] is only ever called when + requesting the nonce for the pending block. It's + safe to assume the pending block. *) + Ethereum_types.Block_parameter.(Block_parameter Pending) + in + let* nonce = get_or_emit_error ~rpc_name:"get_transaction_count" res in + match nonce with + | Some nonce -> return nonce + | None -> + (*we return the known next_nonce instead of failing *) + return next_nonce + + let add ~next_nonce:_ (tx_object : Ethereum_types.legacy_transaction_object) + ~raw_tx = + let open Lwt_syntax in + let* () = + Internal_event.Simple.emit forwarding_transaction tx_object.hash + in + Injector.inject_transaction + ~keep_alive:C.keep_alive + ~base:C.private_endpoint + ~tx_object + ~raw_tx:(Ethereum_types.hex_to_bytes raw_tx) + + let find hash = + let open Lwt_result_syntax in + let* res = + Injector.get_transaction_by_hash + ~keep_alive:C.keep_alive + ~base:C.public_endpoint + hash + in + let* tx_object = + get_or_emit_error ~rpc_name:"get_transaction_by_hash" res + in + let tx_object = Option.join tx_object in + return tx_object + + let content () = + Lwt_result.return + Ethereum_types.{pending = AddressMap.empty; queued = AddressMap.empty} + + let shutdown () = Lwt_result_syntax.return_unit - let content () = - Lwt_result.return - Ethereum_types.{pending = AddressMap.empty; queued = AddressMap.empty} + let clear () = Lwt_result_syntax.return_unit - let shutdown () = Lwt_result_syntax.return_unit + let tx_queue_tick ~evm_node_endpoint:_ = Lwt_result_syntax.return_unit - let clear () = Lwt_result_syntax.return_unit + let tx_queue_beacon ~evm_node_endpoint:_ ~tick_interval:_ = + Lwt_result_syntax.return_unit - let tx_queue_tick ~evm_node_endpoint:_ = Lwt_result_syntax.return_unit + let lock_transactions () = Lwt_result_syntax.return_unit - let tx_queue_beacon ~evm_node_endpoint:_ ~tick_interval:_ = - Lwt_result_syntax.return_unit + let unlock_transactions () = Lwt_result_syntax.return_unit - let lock_transactions () = Lwt_result_syntax.return_unit + let is_locked () = Lwt_result_syntax.return_false - let unlock_transactions () = Lwt_result_syntax.return_unit + let confirm_transactions ~clear_pending_queue_after:_ ~confirmed_txs:_ = + Lwt_result_syntax.return_unit - let is_locked () = Lwt_result_syntax.return_false + let pop_transactions ~maximum_cumulative_size:_ ~validate_tx:_ + ~initial_validation_state:_ = + Lwt_result_syntax.return_nil +end + +let container_forward_request ~public_endpoint ~private_endpoint ~keep_alive : + L2_types.evm_chain_family Services_backend_sig.tx_container = + Services_backend_sig.Evm_tx_container + (module Forward_container (struct + let public_endpoint = public_endpoint - let confirm_transactions ~clear_pending_queue_after:_ ~confirmed_txs:_ = - Lwt_result_syntax.return_unit + let private_endpoint = private_endpoint - let pop_transactions ~maximum_cumulative_size:_ ~validate_tx:_ - ~initial_validation_state:_ = - Lwt_result_syntax.return_nil - end) + let keep_alive = keep_alive + end)) let main ~data_dir ~evm_node_endpoint ?evm_node_private_endpoint ~(config : Configuration.t) () = -- GitLab From db3bdf21d685e055c18aa0d538de059e68e21086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Tue, 6 May 2025 23:12:28 +0200 Subject: [PATCH 2/3] Tezlink/Node/RPC: implement forward container in Tezlink case --- etherlink/bin_node/lib_dev/rpc.ml | 112 +++++++++++++++++++++++------- 1 file changed, 85 insertions(+), 27 deletions(-) diff --git a/etherlink/bin_node/lib_dev/rpc.ml b/etherlink/bin_node/lib_dev/rpc.ml index 98c1efd94645..9fd5c8f4f457 100644 --- a/etherlink/bin_node/lib_dev/rpc.ml +++ b/etherlink/bin_node/lib_dev/rpc.ml @@ -61,23 +61,45 @@ let set_metrics_confirmed_levels (ctxt : Evm_ro_context.t) = Metrics.set_l1_level ~level:l1_level | None -> () -module Forward_container (C : sig - val public_endpoint : Uri.t - - val private_endpoint : Uri.t - - val keep_alive : bool -end) : +module Forward_container + (Tx : Tx_queue_types.L2_transaction) + (C : sig + val public_endpoint : Uri.t + + val private_endpoint : Uri.t + + val keep_alive : bool + end) + (Injector : sig + val get_transaction_count : + keep_alive:bool -> + base:Uri.t -> + Tx.address -> + Ethereum_types.Block_parameter.extended -> + (Ethereum_types.quantity, string) result tzresult Lwt.t + + val inject_transaction : + keep_alive:bool -> + base:Uri.t -> + tx_object:Tx.legacy -> + raw_tx:string -> + (Ethereum_types.hash, string) result tzresult Lwt.t + + val get_transaction_by_hash : + keep_alive:bool -> + base:Uri.t -> + Ethereum_types.hash -> + (Tx.t option, string) result tzresult Lwt.t + end) : Services_backend_sig.Tx_container - with type address = Ethereum_types.address - and type legacy_transaction_object = - Ethereum_types.legacy_transaction_object - and type transaction_object = Transaction_object.t = struct - type address = Ethereum_types.address + with type address = Tx.address + and type legacy_transaction_object = Tx.legacy + and type transaction_object = Tx.t = struct + type address = Tx.address - type legacy_transaction_object = Ethereum_types.legacy_transaction_object + type legacy_transaction_object = Tx.legacy - type transaction_object = Transaction_object.t + type transaction_object = Tx.t let rpc_error = Internal_event.Simple.declare_2 @@ -125,11 +147,12 @@ end) : (*we return the known next_nonce instead of failing *) return next_nonce - let add ~next_nonce:_ (tx_object : Ethereum_types.legacy_transaction_object) - ~raw_tx = + let add ~next_nonce:_ (tx_object : Tx.legacy) ~raw_tx = let open Lwt_syntax in let* () = - Internal_event.Simple.emit forwarding_transaction tx_object.hash + Internal_event.Simple.emit + forwarding_transaction + (Tx.hash_of_tx_object tx_object) in Injector.inject_transaction ~keep_alive:C.keep_alive @@ -178,16 +201,50 @@ end) : Lwt_result_syntax.return_nil end -let container_forward_request ~public_endpoint ~private_endpoint ~keep_alive : - L2_types.evm_chain_family Services_backend_sig.tx_container = - Services_backend_sig.Evm_tx_container - (module Forward_container (struct - let public_endpoint = public_endpoint - - let private_endpoint = private_endpoint - - let keep_alive = keep_alive - end)) +let container_forward_request (type f) ~(chain_family : f L2_types.chain_family) + ~public_endpoint ~private_endpoint ~keep_alive : + f Services_backend_sig.tx_container = + match chain_family with + | EVM -> + Services_backend_sig.Evm_tx_container + (module Forward_container + (Tx_queue_types.Eth_transaction_object) + (struct + let public_endpoint = public_endpoint + + let private_endpoint = private_endpoint + + let keep_alive = keep_alive + end) + (Injector)) + | Michelson -> + Services_backend_sig.Michelson_tx_container + (module Forward_container + (Tx_queue_types.Tezlink_operation) + (struct + let public_endpoint = public_endpoint + + let private_endpoint = private_endpoint + + let keep_alive = keep_alive + end) + (struct + let get_transaction_count ~keep_alive:_ ~base:_ _ _ = + failwith + "TODO: implement get_transaction_count in the Tezlink \ + case (using counter RPC)" + + let inject_transaction ~keep_alive:_ ~base:_ ~tx_object:_ + ~raw_tx:_ = + failwith + "TODO: implement inject_transaction in the Tezlink \ + case (using injection/operation RPC)" + + let get_transaction_by_hash ~keep_alive:_ ~base:_ _ = + failwith + "TODO: implement get_transaction_by_hash in the \ + Tezlink case" + end)) let main ~data_dir ~evm_node_endpoint ?evm_node_private_endpoint ~(config : Configuration.t) () = @@ -226,6 +283,7 @@ let main ~data_dir ~evm_node_endpoint ?evm_node_private_endpoint | Some private_endpoint, _ -> let forward_request = container_forward_request + ~chain_family:L2_types.EVM ~keep_alive:config.keep_alive ~public_endpoint:evm_node_endpoint ~private_endpoint -- GitLab From e80bd3d250bf27c6a1f7273765eb0942aeb3adbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Tue, 6 May 2025 23:33:27 +0200 Subject: [PATCH 3/3] Tezlink/Node: add a private inject_tezlink_operation RPC This is the Tezlink equivalent of Etherlink's `inject_transaction`. --- etherlink/bin_node/lib_dev/injector.ml | 14 +++++++++ etherlink/bin_node/lib_dev/injector.mli | 7 +++++ etherlink/bin_node/lib_dev/rpc.ml | 12 ++++--- etherlink/bin_node/lib_dev/rpc_encodings.ml | 15 +++++++++ etherlink/bin_node/lib_dev/rpc_encodings.mli | 5 +++ etherlink/bin_node/lib_dev/services.ml | 31 +++++++++++++++++++ .../bin_node/lib_dev/services_backend_sig.ml | 5 +-- .../bin_node/lib_dev/tezlink/tezos_types.ml | 11 +++++++ .../bin_node/lib_dev/tezlink/tezos_types.mli | 2 ++ 9 files changed, 95 insertions(+), 7 deletions(-) diff --git a/etherlink/bin_node/lib_dev/injector.ml b/etherlink/bin_node/lib_dev/injector.ml index 2cb5aaf56c38..1f6f055f35ff 100644 --- a/etherlink/bin_node/lib_dev/injector.ml +++ b/etherlink/bin_node/lib_dev/injector.ml @@ -52,6 +52,12 @@ let inject_transaction_request tx_object raw_tx = ~input_encoding:Inject_transaction.input_encoding (tx_object, raw_tx) +let inject_tezlink_operation_request op raw_op = + construct_rpc_call + ~method_:Inject_tezlink_operation.method_ + ~input_encoding:Inject_tezlink_operation.input_encoding + (op, raw_op) + let send_raw_transaction ~keep_alive ~base ~raw_tx = call_rpc_service ~keep_alive @@ -68,6 +74,14 @@ let inject_transaction ~keep_alive ~base ~tx_object ~raw_tx = (inject_transaction_request tx_object raw_tx) Inject_transaction.output_encoding +let inject_tezlink_operation ~keep_alive ~base ~op ~raw_op = + call_rpc_service + ~keep_alive + ~base + ~path:Resto.Path.(root / "private") + (inject_tezlink_operation_request op raw_op) + Inject_tezlink_operation.output_encoding + let get_transaction_count_request address block_param = construct_rpc_call ~method_:Get_transaction_count.method_ diff --git a/etherlink/bin_node/lib_dev/injector.mli b/etherlink/bin_node/lib_dev/injector.mli index 7ef3544330a2..d13e27764acb 100644 --- a/etherlink/bin_node/lib_dev/injector.mli +++ b/etherlink/bin_node/lib_dev/injector.mli @@ -22,6 +22,13 @@ val inject_transaction : raw_tx:string -> (Ethereum_types.hash, string) result tzresult Lwt.t +val inject_tezlink_operation : + keep_alive:bool -> + base:Uri.t -> + op:Tezos_types.Operation.t -> + raw_op:bytes -> + (Ethereum_types.hash, string) result tzresult Lwt.t + val get_transaction_count : keep_alive:bool -> base:Uri.t -> diff --git a/etherlink/bin_node/lib_dev/rpc.ml b/etherlink/bin_node/lib_dev/rpc.ml index 9fd5c8f4f457..0b458db2dc9e 100644 --- a/etherlink/bin_node/lib_dev/rpc.ml +++ b/etherlink/bin_node/lib_dev/rpc.ml @@ -234,11 +234,13 @@ let container_forward_request (type f) ~(chain_family : f L2_types.chain_family) "TODO: implement get_transaction_count in the Tezlink \ case (using counter RPC)" - let inject_transaction ~keep_alive:_ ~base:_ ~tx_object:_ - ~raw_tx:_ = - failwith - "TODO: implement inject_transaction in the Tezlink \ - case (using injection/operation RPC)" + let inject_transaction ~keep_alive ~base ~tx_object ~raw_tx + = + Injector.inject_tezlink_operation + ~keep_alive + ~base + ~op:tx_object + ~raw_op:(Bytes.of_string raw_tx) let get_transaction_by_hash ~keep_alive:_ ~base:_ _ = failwith diff --git a/etherlink/bin_node/lib_dev/rpc_encodings.ml b/etherlink/bin_node/lib_dev/rpc_encodings.ml index f68aace7cc63..5730d18d13ce 100644 --- a/etherlink/bin_node/lib_dev/rpc_encodings.ml +++ b/etherlink/bin_node/lib_dev/rpc_encodings.ml @@ -843,6 +843,20 @@ module Inject_transaction = struct type ('input, 'output) method_ += Method : (input, output) method_ end +module Inject_tezlink_operation = struct + type input = Tezos_types.Operation.t * bytes + + type output = Ethereum_types.hash + + let input_encoding = Data_encoding.(tup2 Tezos_types.Operation.encoding bytes) + + let output_encoding = Ethereum_types.hash_encoding + + let method_ = "injectTezlinkOperation" + + type ('input, 'output) method_ += Method : (input, output) method_ +end + module Durable_state_value = struct type input = Durable_storage_path.path * Ethereum_types.Block_parameter.extended @@ -1181,6 +1195,7 @@ let multichain_sequencer_supported_methods : (module METHOD) list = (* Private RPCs *) (module Produce_block); (module Inject_transaction); + (module Inject_tezlink_operation); (module Durable_state_value); (module Durable_state_subkeys); (module Replay_block); diff --git a/etherlink/bin_node/lib_dev/rpc_encodings.mli b/etherlink/bin_node/lib_dev/rpc_encodings.mli index 2de83d6fb277..566dacaf3496 100644 --- a/etherlink/bin_node/lib_dev/rpc_encodings.mli +++ b/etherlink/bin_node/lib_dev/rpc_encodings.mli @@ -307,6 +307,11 @@ module Inject_transaction : with type input = Ethereum_types.legacy_transaction_object * string and type output = Ethereum_types.hash +module Inject_tezlink_operation : + METHOD + with type input = Tezos_types.Operation.t * bytes + and type output = Ethereum_types.hash + module Durable_state_value : METHOD with type input = diff --git a/etherlink/bin_node/lib_dev/services.ml b/etherlink/bin_node/lib_dev/services.ml index 7f9cb2249f20..e0cba86ef339 100644 --- a/etherlink/bin_node/lib_dev/services.ml +++ b/etherlink/bin_node/lib_dev/services.ml @@ -1194,6 +1194,37 @@ let dispatch_private_request (type f) ~websocket rpc_error (Rpc_errors.transaction_rejected reason None)) in build_with_input ~f module_ parameters + | Method (Inject_tezlink_operation.Method, module_) -> + let open Lwt_result_syntax in + let f ((op : Tezos_types.Operation.t), raw_op) = + Octez_telemetry.Trace.( + add_attrs (fun () -> + (* TODO: https://gitlab.com/tezos/tezos/-/issues/8014 + Add an attribute for Tezlink operations (in base58) + instead of reusing the Etherlink hexadecimal + identifier. *) + Telemetry.Attributes. + [Transaction.hash (Tezos_types.Operation.hash_operation op)])) ; + let* hash = + let* (module Tx_container) = + match tx_container with + | Evm_tx_container _ -> + failwith + "Unsupported JSONRPC method in Etherlink: \ + injectTezlinkOperation" + | Michelson_tx_container m -> return m + in + Tx_container.add + ~next_nonce:(Ethereum_types.Qty op.counter) + op + ~raw_tx:(Ethereum_types.hex_of_bytes raw_op) + in + match hash with + | Ok hash -> rpc_ok hash + | Error reason -> + rpc_error (Rpc_errors.transaction_rejected reason None) + in + build_with_input ~f module_ parameters | Method (Durable_state_value.Method, module_) -> let f (path, block) = let open Lwt_result_syntax in diff --git a/etherlink/bin_node/lib_dev/services_backend_sig.ml b/etherlink/bin_node/lib_dev/services_backend_sig.ml index b0b22be5b94b..b034c21a22db 100644 --- a/etherlink/bin_node/lib_dev/services_backend_sig.ml +++ b/etherlink/bin_node/lib_dev/services_backend_sig.ml @@ -288,7 +288,8 @@ type 'f tx_container = and type transaction_object = Transaction_object.t) -> L2_types.evm_chain_family tx_container | Michelson_tx_container : - (module Tx_container) + (module Tx_container + with type legacy_transaction_object = Tezos_types.Operation.t) -> L2_types.michelson_chain_family tx_container (** Some functions of the Tx_container module, such as [add], have @@ -318,4 +319,4 @@ type 'f tx_container = let tx_container_module (type f) (tx_container : f tx_container) = match tx_container with | Evm_tx_container m -> (m :> (module Tx_container)) - | Michelson_tx_container m -> m + | Michelson_tx_container m -> (m :> (module Tx_container)) diff --git a/etherlink/bin_node/lib_dev/tezlink/tezos_types.ml b/etherlink/bin_node/lib_dev/tezlink/tezos_types.ml index 49b5e26be5c0..410ea577f507 100644 --- a/etherlink/bin_node/lib_dev/tezlink/tezos_types.ml +++ b/etherlink/bin_node/lib_dev/tezlink/tezos_types.ml @@ -69,6 +69,17 @@ module Operation = struct raw : bytes; } + let encoding : t Data_encoding.t = + let open Data_encoding in + conv + (fun {source; counter; op; raw} -> (source, counter, op, raw)) + (fun (source, counter, op, raw) -> {source; counter; op; raw}) + (tup4 + Signature.V1.Public_key_hash.encoding + z + (dynamic_size Tezlink_imports.Alpha_context.Operation.encoding) + bytes) + let hash_operation {source = _; counter = _; op; raw = _} = let hash = ImportedOperation.hash_packed op in let (`Hex hex) = Operation_hash.to_hex hash in diff --git a/etherlink/bin_node/lib_dev/tezlink/tezos_types.mli b/etherlink/bin_node/lib_dev/tezlink/tezos_types.mli index 2e0ca089f0c3..557c2d0260a2 100644 --- a/etherlink/bin_node/lib_dev/tezlink/tezos_types.mli +++ b/etherlink/bin_node/lib_dev/tezlink/tezos_types.mli @@ -53,6 +53,8 @@ module Operation : sig } val hash_operation : t -> Ethereum_types.hash + + val encoding : t Data_encoding.t end module Tez : sig -- GitLab