From 6d9b6a92cb4acf3a6d0293a4551116889c777336 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Fri, 23 May 2025 14:45:52 +0200 Subject: [PATCH 1/6] FA bridge watchtower: add indexes and whitelist tables --- etherlink/fa-bridge-watchtower/db.ml | 2 +- .../migrations/001_whitelist.sql | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 etherlink/fa-bridge-watchtower/migrations/001_whitelist.sql diff --git a/etherlink/fa-bridge-watchtower/db.ml b/etherlink/fa-bridge-watchtower/db.ml index 066c2ad4a78e..5729edf92eab 100644 --- a/etherlink/fa-bridge-watchtower/db.ml +++ b/etherlink/fa-bridge-watchtower/db.ml @@ -10,7 +10,7 @@ open Caqti_request.Infix open Caqti_type.Std (** Current version for migrations. *) -let version = 0 +let version = 1 module Contract = Tezos_raw_protocol_alpha.Alpha_context.Contract diff --git a/etherlink/fa-bridge-watchtower/migrations/001_whitelist.sql b/etherlink/fa-bridge-watchtower/migrations/001_whitelist.sql new file mode 100644 index 000000000000..9ab7b9fc5e3a --- /dev/null +++ b/etherlink/fa-bridge-watchtower/migrations/001_whitelist.sql @@ -0,0 +1,17 @@ +----------------------------------------------------------------- +-- SPDX-License-Identifier: MIT -- +-- Copyright (c) 2025 Functori -- +-- Copyright (c) 2025 Nomadic Labs -- +----------------------------------------------------------------- + +CREATE INDEX idx_proxy ON deposits(proxy); +CREATE INDEX idx_ticket_hash ON deposits(ticket_hash); +CREATE INDEX idx_receiver ON deposits(receiver); + +CREATE TABLE whitelist ( + proxy BLOB, + ticket_hash BLOB +); + +CREATE INDEX wid_proxy ON whitelist(proxy); +CREATE INDEX wid_ticket_hash ON whitelist(ticket_hash); -- GitLab From c424eb55e33467f07171ae9be8ded0d44f863a15 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Fri, 23 May 2025 14:45:35 +0200 Subject: [PATCH 2/6] FA bridge watchtower: register whitelist in DB This is to allow to write efficient filtering queries with the indexes. --- etherlink/fa-bridge-watchtower/db.ml | 41 ++++++++++++++++++++++++++ etherlink/fa-bridge-watchtower/main.ml | 1 + 2 files changed, 42 insertions(+) diff --git a/etherlink/fa-bridge-watchtower/db.ml b/etherlink/fa-bridge-watchtower/db.ml index 5729edf92eab..4ead384ae92a 100644 --- a/etherlink/fa-bridge-watchtower/db.ml +++ b/etherlink/fa-bridge-watchtower/db.ml @@ -442,3 +442,44 @@ module Pointers = struct with_connection db conn @@ fun conn -> Sqlite.Db.find conn Q.get () end end + +module Whitelist = struct + module Q = struct + open Types + + let insert = + (t2 (option address) (option hash) ->. unit) + @@ {sql| + INSERT INTO whitelist + (proxy, ticket_hash) + VALUES (?, ?) + |sql} + + let clear = (unit ->. unit) @@ {sql| DELETE FROM whitelist |sql} + end + + let insert ?conn db pth = + with_connection db conn @@ fun conn -> Sqlite.Db.exec conn Q.insert pth + + let clear ?conn db = + with_connection db conn @@ fun conn -> Sqlite.Db.exec conn Q.clear () + + let register db (whitelist : Config.whitelist_item list option) = + let open Lwt_result_syntax in + with_transaction db @@ fun conn -> + let* () = clear ~conn db in + match whitelist with + | None -> + (* Matches every deposit log *) + insert ~conn db (None, None) + | Some whitelist -> + List.iter_es + (fun {Config.proxy; ticket_hashes} -> + match ticket_hashes with + | None -> insert ~conn db (proxy, None) + | Some ticket_hashes -> + List.iter_es + (fun ticket_hash -> insert ~conn db (proxy, Some ticket_hash)) + ticket_hashes) + whitelist +end diff --git a/etherlink/fa-bridge-watchtower/main.ml b/etherlink/fa-bridge-watchtower/main.ml index 4a7c3a4a6d03..9e9152cdd310 100644 --- a/etherlink/fa-bridge-watchtower/main.ml +++ b/etherlink/fa-bridge-watchtower/main.ml @@ -162,6 +162,7 @@ let run_command = | None -> failwith "secret key not provided neither in config, cli nor env" in + let* () = Db.Whitelist.register db config.whitelist in let*! notify_ws_change = match config.rpc with | None -> Lwt.return ignore -- GitLab From 61ee7d7be3847e437d112a9ce496783fc5d19a03 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Tue, 3 Jun 2025 17:07:48 +0200 Subject: [PATCH 3/6] FA bridge watchtower: monitor all deposits config --- etherlink/fa-bridge-watchtower/config.ml | 24 +++++++++++++++---- .../fa-bridge-watchtower/etherlink_monitor.ml | 3 ++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/etherlink/fa-bridge-watchtower/config.ml b/etherlink/fa-bridge-watchtower/config.ml index ada92a7291f4..931cb8980ec6 100644 --- a/etherlink/fa-bridge-watchtower/config.ml +++ b/etherlink/fa-bridge-watchtower/config.ml @@ -28,6 +28,7 @@ type t = { secret_key : secret_key option; (** Optional secret key for signing *) whitelist : whitelist_item list option; (** Optional list of whitelisted addresses *) + monitor_all_deposits : bool; (** Whether to store all deposits in the DB. *) } let ctxt = Efunc_core.Eth.Crypto.context () @@ -73,6 +74,7 @@ let default = rpc = default_rpc_config; secret_key = default_secret_key; whitelist = default_whitelist; + monitor_all_deposits = false; } let rpc_encoding = @@ -109,14 +111,22 @@ let encoding = rpc; secret_key; whitelist; + monitor_all_deposits; } -> - (evm_node_endpoint, gas_limit, max_fee_per_gas, rpc, secret_key, whitelist)) + ( evm_node_endpoint, + gas_limit, + max_fee_per_gas, + rpc, + secret_key, + whitelist, + monitor_all_deposits )) (fun ( evm_node_endpoint, gas_limit, max_fee_per_gas, rpc, secret_key, - whitelist ) -> + whitelist, + monitor_all_deposits ) -> { evm_node_endpoint; gas_limit; @@ -124,8 +134,9 @@ let encoding = rpc; secret_key; whitelist; + monitor_all_deposits; }) - (obj6 + (obj7 (dft "evm_node_endpoint" ~description:"URL of the EVM node" @@ -157,7 +168,12 @@ let encoding = (opt "whitelist" ~description:"Optional list of whitelisted items" - (list whitelist_item_encoding))) + (list whitelist_item_encoding)) + (dft + "monitor_all_deposits" + ~description:"Monitor and store all deposits in the DB." + bool + false)) (** [load_file ~data_dir] attempts to load the configuration file from the specified data directory. Returns [None] if the file doesn't exist or [Some diff --git a/etherlink/fa-bridge-watchtower/etherlink_monitor.ml b/etherlink/fa-bridge-watchtower/etherlink_monitor.ml index 8cc82aaf6f4e..950432d6e841 100644 --- a/etherlink/fa-bridge-watchtower/etherlink_monitor.ml +++ b/etherlink/fa-bridge-watchtower/etherlink_monitor.ml @@ -873,7 +873,8 @@ let start db ~config ~notify_ws_change ~first_block = sk = secret_key; chain_id; gas_limit = Z.of_int64 config.gas_limit; - whitelist = config.whitelist; + whitelist = + (if config.monitor_all_deposits then None else config.whitelist); nonce = Ethereum_types.Qty.zero; } in -- GitLab From f4825cdb7af85a7910fbbf00923de70992e41d19 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Tue, 3 Jun 2025 17:17:45 +0200 Subject: [PATCH 4/6] FA bridge watchtower: filter on whitelist in DB when fetching --- etherlink/fa-bridge-watchtower/db.ml | 104 ++++++++++++++++++++++++--- 1 file changed, 96 insertions(+), 8 deletions(-) diff --git a/etherlink/fa-bridge-watchtower/db.ml b/etherlink/fa-bridge-watchtower/db.ml index 4ead384ae92a..a29138bd5569 100644 --- a/etherlink/fa-bridge-watchtower/db.ml +++ b/etherlink/fa-bridge-watchtower/db.ml @@ -304,7 +304,7 @@ module Deposits = struct VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) |sql} - let unclaimed = + let unclaimed_ignore_whitelist = (unit ->* deposit) @@ {sql| SELECT @@ -314,7 +314,22 @@ module Deposits = struct ORDER BY nonce DESC |sql} - let unclaimed_full = + let unclaimed = + (unit ->* deposit) + @@ {sql| + SELECT + d.nonce, d.proxy, d.ticket_hash, d.receiver, d.amount + FROM deposits d + JOIN whitelist w + ON ( + (d.proxy = w.proxy or w.proxy IS NULL) + AND + (d.ticket_hash = w.ticket_hash or w.ticket_hash IS NULL)) + where d.exec_transactionHash IS NULL + ORDER BY nonce DESC + |sql} + + let unclaimed_full_ignore_whitelist = (unit ->* t2 deposit log_info) @@ {sql| SELECT @@ -326,7 +341,24 @@ module Deposits = struct ORDER BY nonce DESC |sql} - let list = + let unclaimed_full = + (unit ->* t2 deposit log_info) + @@ {sql| + SELECT + nonce, d.proxy, d.ticket_hash, receiver, amount, + log_transactionHash, log_transactionIndex, log_logIndex, log_blockHash, + log_blockNumber, log_removed + FROM deposits d + JOIN whitelist w + ON ( + (d.proxy = w.proxy or w.proxy IS NULL) + AND + (d.ticket_hash = w.ticket_hash or w.ticket_hash IS NULL)) + where exec_transactionHash IS NULL + ORDER BY nonce DESC + |sql} + + let list_ignore_whitelist = (t2 int int ->* deposit_log) @@ {sql| SELECT @@ -339,7 +371,25 @@ module Deposits = struct ORDER BY nonce DESC LIMIT ? OFFSET ? |sql} - let list_by_receiver = + let list = + (t2 int int ->* deposit_log) + @@ {sql| + SELECT + nonce, d.proxy, d.ticket_hash, receiver, amount, + log_transactionHash, log_transactionIndex, log_logIndex, log_blockHash, + log_blockNumber, log_removed, + exec_transactionHash, exec_transactionIndex, exec_blockHash, + exec_blockNumber + FROM deposits d + JOIN whitelist w + ON ( + (d.proxy = w.proxy or w.proxy IS NULL) + AND + (d.ticket_hash = w.ticket_hash or w.ticket_hash IS NULL)) + ORDER BY nonce DESC LIMIT ? OFFSET ? + |sql} + + let list_by_receiver_ignore_whitelist = (t3 address int int ->* deposit_log) @@ {sql| SELECT @@ -353,6 +403,25 @@ module Deposits = struct ORDER BY nonce DESC LIMIT ? OFFSET ? |sql} + let list_by_receiver = + (t3 address int int ->* deposit_log) + @@ {sql| + SELECT + nonce, d.proxy, d.ticket_hash, receiver, amount, + log_transactionHash, log_transactionIndex, log_logIndex, log_blockHash, + log_blockNumber, log_removed, + exec_transactionHash, exec_transactionIndex, exec_blockHash, + exec_blockNumber + FROM deposits d + JOIN whitelist w + ON ( + (d.proxy = w.proxy or w.proxy IS NULL) + AND + (d.ticket_hash = w.ticket_hash or w.ticket_hash IS NULL)) + WHERE receiver = ? + ORDER BY nonce DESC LIMIT ? OFFSET ? + |sql} + let set_claimed = (t2 execution_info level ->. unit) @@ {sql| @@ -393,10 +462,6 @@ module Deposits = struct with_connection db conn @@ fun conn -> Sqlite.Db.rev_collect_list conn Q.unclaimed_full () - let set_claimed ?conn db nonce execution_info = - with_connection db conn @@ fun conn -> - Sqlite.Db.exec conn Q.set_claimed (execution_info, nonce) - let list ?conn db ~limit ~offset = with_connection db conn @@ fun conn -> Sqlite.Db.collect_list conn Q.list (limit, offset) @@ -405,6 +470,29 @@ module Deposits = struct with_connection db conn @@ fun conn -> Sqlite.Db.collect_list conn Q.list_by_receiver (addr, limit, offset) + let get_unclaimed_ignore_whitelist ?conn db = + with_connection db conn @@ fun conn -> + Sqlite.Db.rev_collect_list conn Q.unclaimed_ignore_whitelist () + + let get_unclaimed_full_ignore_whitelist ?conn db = + with_connection db conn @@ fun conn -> + Sqlite.Db.rev_collect_list conn Q.unclaimed_full_ignore_whitelist () + + let list_ignore_whitelist ?conn db ~limit ~offset = + with_connection db conn @@ fun conn -> + Sqlite.Db.collect_list conn Q.list_ignore_whitelist (limit, offset) + + let list_by_receiver_ignore_whitelist ?conn db addr ~limit ~offset = + with_connection db conn @@ fun conn -> + Sqlite.Db.collect_list + conn + Q.list_by_receiver_ignore_whitelist + (addr, limit, offset) + + let set_claimed ?conn db nonce execution_info = + with_connection db conn @@ fun conn -> + Sqlite.Db.exec conn Q.set_claimed (execution_info, nonce) + let delete_before ?conn db level = with_connection db conn @@ fun conn -> Sqlite.Db.exec conn Q.delete_before level -- GitLab From 2fdb2a87aba17135dfe1ec26a18dfed259ddacda Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Tue, 3 Jun 2025 17:22:14 +0200 Subject: [PATCH 5/6] FA bridge watchtower: ?ignore_whitelist=true query parameter --- etherlink/fa-bridge-watchtower/rpc_server.ml | 44 +++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/etherlink/fa-bridge-watchtower/rpc_server.ml b/etherlink/fa-bridge-watchtower/rpc_server.ml index c6c6794b8f4d..bc6e614ef964 100644 --- a/etherlink/fa-bridge-watchtower/rpc_server.ml +++ b/etherlink/fa-bridge-watchtower/rpc_server.ml @@ -354,9 +354,31 @@ let () = "/unclaimed" Data_encoding.(list (merge_objs Encodings.deposit Encodings.log_info)) ~description:"List unclaimed deposits" - @@ fun _ _ ctx -> + ~query: + (* TODO: query parameters are reported by hand for now *) + [ + { + parameter = + { + name = "ignore_whitelist"; + description = Some "List deposits independently of whitelist"; + schema = Tezos_openapi.Openapi.Schema.boolean (); + }; + required = false; + }; + ] + @@ fun req _ ctx -> let open Lwt_result_syntax in - let* deposits = Db.Deposits.get_unclaimed_full ctx.db in + let ignore_whitelist = + Dream.query req "ignore_whitelist" + |> Option.map bool_of_string + |> Option.value ~default:false + in + let* deposits = + if ignore_whitelist then + Db.Deposits.get_unclaimed_full_ignore_whitelist ctx.db + else Db.Deposits.get_unclaimed_full ctx.db + in List.map_es (fun (deposit, log) -> let+ deposit = deposit_with_token_info ctx deposit in @@ -414,11 +436,23 @@ let () = let receiver = Dream.query req "receiver" |> Option.map Ethereum_types.Address.of_string in + let ignore_whitelist = + Dream.query req "ignore_whitelist" + |> Option.map bool_of_string + |> Option.value ~default:false + in let* deposits = - match receiver with - | None -> Db.Deposits.list ctx.db ~limit ~offset - | Some receiver -> + match (receiver, ignore_whitelist) with + | None, false -> Db.Deposits.list ctx.db ~limit ~offset + | Some receiver, false -> Db.Deposits.list_by_receiver ctx.db receiver ~limit ~offset + | None, true -> Db.Deposits.list_ignore_whitelist ctx.db ~limit ~offset + | Some receiver, true -> + Db.Deposits.list_by_receiver_ignore_whitelist + ctx.db + receiver + ~limit + ~offset in List.map_es (fun Db.{deposit; log_info; claimed} -> -- GitLab From a6d6dce5a2dd22e80ea2e5307511e29f9d61684d Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Tue, 3 Jun 2025 17:26:13 +0200 Subject: [PATCH 6/6] FA bridge watchtower: monitor_all_deposits CLI switch --- etherlink/fa-bridge-watchtower/config.ml | 11 +++++++---- etherlink/fa-bridge-watchtower/main.ml | 24 +++++++++++++++++++++--- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/etherlink/fa-bridge-watchtower/config.ml b/etherlink/fa-bridge-watchtower/config.ml index 931cb8980ec6..dffc5b907358 100644 --- a/etherlink/fa-bridge-watchtower/config.ml +++ b/etherlink/fa-bridge-watchtower/config.ml @@ -188,10 +188,10 @@ let load_file ~data_dir = let config = Data_encoding.Json.destruct encoding json in return_some config -(** [patch_config ~secret_key ~evm_node_endpoint config] updates the - configuration with the provided secret key and EVM node endpoint if they are - present. Returns the updated configuration. *) -let patch_config ~secret_key ~evm_node_endpoint config = +(** [patch_config ~secret_key ~evm_node_endpoint ~monitor_all_deposits config] + updates the configuration with the provided secret key and EVM node endpoint + if they are present. Returns the updated configuration. *) +let patch_config ~secret_key ~evm_node_endpoint ~monitor_all_deposits config = let config = Option.fold secret_key ~none:config ~some:(fun secret_key -> {config with secret_key = Some secret_key}) @@ -200,4 +200,7 @@ let patch_config ~secret_key ~evm_node_endpoint config = Option.fold evm_node_endpoint ~none:config ~some:(fun evm_node_endpoint -> {config with evm_node_endpoint}) in + let config = + if monitor_all_deposits then {config with monitor_all_deposits} else config + in config diff --git a/etherlink/fa-bridge-watchtower/main.ml b/etherlink/fa-bridge-watchtower/main.ml index 9e9152cdd310..7b9922218ba8 100644 --- a/etherlink/fa-bridge-watchtower/main.ml +++ b/etherlink/fa-bridge-watchtower/main.ml @@ -82,6 +82,12 @@ module Arg = struct @@ Tezos_clic.parameter (fun _ x -> Lwt_result.return (Ethereum_types.Qty (Z.of_string x))) + let monitor_all_deposits = + Tezos_clic.switch + ~long:"monitor-all-deposits" + ~doc:"Monitor and store all deposits in the DB." + () + let level_param ~desc = Tezos_clic.param ~name:"level" ~desc @@ Tezos_clic.parameter (fun _ x -> @@ -147,15 +153,27 @@ let run_command = om_command ~group:run_group ~desc:"Start FA bridge watchtower" - (args3 Arg.evm_node_endpoint Arg.secret_key Arg.first_block) + (args4 + Arg.evm_node_endpoint + Arg.secret_key + Arg.first_block + Arg.monitor_all_deposits) (prefixes ["run"] @@ stop) - (fun {data_dir; verbosity} (evm_node_endpoint, secret_key, first_block) _ -> + (fun {data_dir; verbosity} + (evm_node_endpoint, secret_key, first_block, monitor_all_deposits) + _ -> let open Lwt_result_syntax in let* loaded_config = Config.load_file ~data_dir in let config = Option.value ~default:Config.default loaded_config in let*! () = log_config ~verbosity ~data_dir in let* db = Db.init ~data_dir `Read_write in - let config = Config.patch_config config ~secret_key ~evm_node_endpoint in + let config = + Config.patch_config + config + ~secret_key + ~evm_node_endpoint + ~monitor_all_deposits + in let* () = match config.secret_key with | Some _ -> return_unit -- GitLab