diff --git a/etherlink/bin_node/lib_dev/injector.ml b/etherlink/bin_node/lib_dev/injector.ml index 2cb5aaf56c38cead2073c1b63890f6593c906ea4..1f6f055f35ff72388f5af7dd5f3f1e049ea7cb1f 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 7ef3544330a2c51d83a75fb4e569a170ce649cbe..d13e27764acb276b1a61cc93330f85ea77c4bd84 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 cee90c95f3399946a4da9cd16b31dea00fc36e99..0b458db2dc9e2bfb0fd61a47aa90dce558464337 100644 --- a/etherlink/bin_node/lib_dev/rpc.ml +++ b/etherlink/bin_node/lib_dev/rpc.ml @@ -61,114 +61,192 @@ 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 - - let content () = - Lwt_result.return - Ethereum_types.{pending = AddressMap.empty; queued = AddressMap.empty} - - let shutdown () = Lwt_result_syntax.return_unit - - let clear () = Lwt_result_syntax.return_unit - - let tx_queue_tick ~evm_node_endpoint:_ = Lwt_result_syntax.return_unit +module Forward_container + (Tx : Tx_queue_types.L2_transaction) + (C : sig + val public_endpoint : Uri.t - let tx_queue_beacon ~evm_node_endpoint:_ ~tick_interval:_ = - Lwt_result_syntax.return_unit + val private_endpoint : Uri.t - let lock_transactions () = Lwt_result_syntax.return_unit - - let unlock_transactions () = Lwt_result_syntax.return_unit - - let is_locked () = Lwt_result_syntax.return_false - - let confirm_transactions ~clear_pending_queue_after:_ ~confirmed_txs:_ = - Lwt_result_syntax.return_unit - - let pop_transactions ~maximum_cumulative_size:_ ~validate_tx:_ - ~initial_validation_state:_ = - Lwt_result_syntax.return_nil + 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 = Tx.address + and type legacy_transaction_object = Tx.legacy + and type transaction_object = Tx.t = struct + type address = Tx.address + + type legacy_transaction_object = Tx.legacy + + type transaction_object = Tx.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 : Tx.legacy) ~raw_tx = + let open Lwt_syntax in + let* () = + Internal_event.Simple.emit + forwarding_transaction + (Tx.hash_of_tx_object tx_object) + 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 clear () = Lwt_result_syntax.return_unit + + let tx_queue_tick ~evm_node_endpoint:_ = Lwt_result_syntax.return_unit + + let tx_queue_beacon ~evm_node_endpoint:_ ~tick_interval:_ = + Lwt_result_syntax.return_unit + + let lock_transactions () = Lwt_result_syntax.return_unit + + let unlock_transactions () = Lwt_result_syntax.return_unit + + let is_locked () = Lwt_result_syntax.return_false + + let confirm_transactions ~clear_pending_queue_after:_ ~confirmed_txs:_ = + Lwt_result_syntax.return_unit + + let pop_transactions ~maximum_cumulative_size:_ ~validate_tx:_ + ~initial_validation_state:_ = + Lwt_result_syntax.return_nil +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 + = + 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 + "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) () = @@ -207,6 +285,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 diff --git a/etherlink/bin_node/lib_dev/rpc_encodings.ml b/etherlink/bin_node/lib_dev/rpc_encodings.ml index f68aace7cc63b1fe67ebb8c52d643089a71dc25c..5730d18d13ce4552f8aa17d9fa79bb4657dfb76d 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 2de83d6fb2774eb9212165aac8d4fbf4f546ede8..566dacaf34960d2bb5e82e6c0b89ff5bb0479e48 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 7f9cb2249f209d70e729e90d5b5fe7d4d6d7e39b..e0cba86ef3394df3e559770d9f702e670afb9e44 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 b0b22be5b94bf935a1d564e2c356428d10f9a612..b034c21a22dba3eee4e57691383acdde643124ac 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 49b5e26be5c04361912cfa7773290935d63fc146..410ea577f507f62fb7502a6884cc73a0a3dec898 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 2e0ca089f0c3b5d356721fd36c89ee4304dd0658..557c2d0260a261c39fe5a6ba6bb139fd588fdb61 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