@@ -4,35 +4,47 @@ use crate::logger::{
4
4
log_error, log_given_level, log_info, log_internal, log_trace, log_warn, FilesystemLogger ,
5
5
Logger ,
6
6
} ;
7
+ use crate :: { scid_utils, LdkLiteConfig } ;
7
8
8
9
use lightning:: chain:: chaininterface:: { BroadcasterInterface , ConfirmationTarget , FeeEstimator } ;
9
10
use lightning:: chain:: WatchedOutput ;
10
- use lightning:: chain:: { Confirm , Filter } ;
11
+ use lightning:: chain:: { Access , AccessError , Confirm , Filter } ;
11
12
12
13
use bdk:: blockchain:: { Blockchain , EsploraBlockchain , GetBlockHash , GetHeight , GetTx } ;
13
14
use bdk:: database:: BatchDatabase ;
15
+ use bdk:: esplora_client;
14
16
use bdk:: wallet:: AddressIndex ;
15
17
use bdk:: { SignOptions , SyncOptions } ;
16
18
17
- use bitcoin:: { BlockHash , Script , Transaction , Txid } ;
19
+ use bitcoin:: { BlockHash , Script , Transaction , TxOut , Txid } ;
18
20
19
21
use std:: collections:: HashSet ;
20
- use std:: sync:: { Arc , Mutex } ;
22
+ use std:: sync:: { Arc , Mutex , RwLock } ;
21
23
22
24
/// The minimum feerate we are allowed to send, as specify by LDK.
23
25
const MIN_FEERATE : u32 = 253 ;
24
26
27
+ // The used 'stop gap' parameter used by BDK's wallet sync. This seems to configure the threshold
28
+ // number of blocks after which BDK stops looking for scripts belonging to the wallet.
29
+ const BDK_CLIENT_STOP_GAP : usize = 20 ;
30
+
31
+ // The number of concurrent requests made against the API provider.
32
+ const BDK_CLIENT_CONCURRENCY : u8 = 8 ;
33
+
25
34
pub struct LdkLiteChainAccess < D >
26
35
where
27
36
D : BatchDatabase ,
28
37
{
29
38
blockchain : EsploraBlockchain ,
39
+ client : Arc < esplora_client:: AsyncClient > ,
30
40
wallet : Mutex < bdk:: Wallet < D > > ,
31
41
queued_transactions : Mutex < Vec < Txid > > ,
32
42
watched_transactions : Mutex < Vec < Txid > > ,
33
43
queued_outputs : Mutex < Vec < WatchedOutput > > ,
34
44
watched_outputs : Mutex < Vec < WatchedOutput > > ,
35
45
last_sync_height : tokio:: sync:: Mutex < Option < u32 > > ,
46
+ tokio_runtime : RwLock < Option < Arc < tokio:: runtime:: Runtime > > > ,
47
+ config : Arc < LdkLiteConfig > ,
36
48
logger : Arc < FilesystemLogger > ,
37
49
}
38
50
@@ -41,26 +53,45 @@ where
41
53
D : BatchDatabase ,
42
54
{
43
55
pub ( crate ) fn new (
44
- blockchain : EsploraBlockchain , wallet : bdk:: Wallet < D > , logger : Arc < FilesystemLogger > ,
56
+ wallet : bdk:: Wallet < D > , config : Arc < LdkLiteConfig > , logger : Arc < FilesystemLogger > ,
45
57
) -> Self {
46
58
let wallet = Mutex :: new ( wallet) ;
47
59
let watched_transactions = Mutex :: new ( Vec :: new ( ) ) ;
48
60
let queued_transactions = Mutex :: new ( Vec :: new ( ) ) ;
49
61
let watched_outputs = Mutex :: new ( Vec :: new ( ) ) ;
50
62
let queued_outputs = Mutex :: new ( Vec :: new ( ) ) ;
51
63
let last_sync_height = tokio:: sync:: Mutex :: new ( None ) ;
64
+ let tokio_runtime = RwLock :: new ( None ) ;
65
+ // TODO: Check that we can be sure that the Esplora client re-connects in case of failure
66
+ // and and exits cleanly on drop. Otherwise we need to handle this/move it to the runtime?
67
+ let blockchain = EsploraBlockchain :: new ( & config. esplora_server_url , BDK_CLIENT_STOP_GAP )
68
+ . with_concurrency ( BDK_CLIENT_CONCURRENCY ) ;
69
+ let client_builder =
70
+ esplora_client:: Builder :: new ( & format ! ( "http://{}" , & config. esplora_server_url) ) ;
71
+ let client = Arc :: new ( client_builder. build_async ( ) . unwrap ( ) ) ;
52
72
Self {
53
73
blockchain,
74
+ client,
54
75
wallet,
55
76
queued_transactions,
56
77
watched_transactions,
57
78
queued_outputs,
58
79
watched_outputs,
59
80
last_sync_height,
81
+ tokio_runtime,
82
+ config,
60
83
logger,
61
84
}
62
85
}
63
86
87
+ pub ( crate ) fn set_runtime ( & self , tokio_runtime : Arc < tokio:: runtime:: Runtime > ) {
88
+ * self . tokio_runtime . write ( ) . unwrap ( ) = Some ( tokio_runtime) ;
89
+ }
90
+
91
+ pub ( crate ) fn drop_runtime ( & self ) {
92
+ * self . tokio_runtime . write ( ) . unwrap ( ) = None ;
93
+ }
94
+
64
95
pub ( crate ) async fn sync_wallet ( & self ) -> Result < ( ) , Error > {
65
96
let sync_options = SyncOptions { progress : None } ;
66
97
74
105
}
75
106
76
107
pub ( crate ) async fn sync ( & self , confirmables : Vec < & ( dyn Confirm + Sync ) > ) -> Result < ( ) , Error > {
77
- let client = & * self . blockchain ;
78
-
79
- let cur_height = client. get_height ( ) . await ?;
108
+ let cur_height = self . client . get_height ( ) . await ?;
80
109
81
110
let mut locked_last_sync_height = self . last_sync_height . lock ( ) . await ;
82
111
if cur_height >= locked_last_sync_height. unwrap_or ( 0 ) {
@@ -93,10 +122,8 @@ where
93
122
& self , confirmables : & Vec < & ( dyn Confirm + Sync ) > , cur_height : u32 ,
94
123
locked_last_sync_height : & mut tokio:: sync:: MutexGuard < ' _ , Option < u32 > > ,
95
124
) -> Result < ( ) , Error > {
96
- let client = & * self . blockchain ;
97
-
98
125
// Inform the interface of the new block.
99
- let cur_block_header = client. get_header ( cur_height) . await ?;
126
+ let cur_block_header = self . client . get_header ( cur_height) . await ?;
100
127
for c in confirmables {
101
128
c. best_block_updated ( & cur_block_header, cur_height) ;
102
129
}
@@ -108,8 +135,6 @@ where
108
135
async fn sync_transactions_confirmed (
109
136
& self , confirmables : & Vec < & ( dyn Confirm + Sync ) > ,
110
137
) -> Result < ( ) , Error > {
111
- let client = & * self . blockchain ;
112
-
113
138
// First, check the confirmation status of registered transactions as well as the
114
139
// status of dependent transactions of registered outputs.
115
140
@@ -131,12 +156,12 @@ where
131
156
let mut unconfirmed_registered_txs = Vec :: new ( ) ;
132
157
133
158
for txid in registered_txs {
134
- if let Some ( tx_status) = client. get_tx_status ( & txid) . await ? {
159
+ if let Some ( tx_status) = self . client . get_tx_status ( & txid) . await ? {
135
160
if tx_status. confirmed {
136
- if let Some ( tx) = client. get_tx ( & txid) . await ? {
161
+ if let Some ( tx) = self . client . get_tx ( & txid) . await ? {
137
162
if let Some ( block_height) = tx_status. block_height {
138
- let block_header = client. get_header ( block_height) . await ?;
139
- if let Some ( merkle_proof) = client. get_merkle_proof ( & txid) . await ? {
163
+ let block_header = self . client . get_header ( block_height) . await ?;
164
+ if let Some ( merkle_proof) = self . client . get_merkle_proof ( & txid) . await ? {
140
165
confirmed_txs. push ( (
141
166
tx,
142
167
block_height,
@@ -163,19 +188,20 @@ where
163
188
let mut unspent_registered_outputs = Vec :: new ( ) ;
164
189
165
190
for output in registered_outputs {
166
- if let Some ( output_status) = client
191
+ if let Some ( output_status) = self
192
+ . client
167
193
. get_output_status ( & output. outpoint . txid , output. outpoint . index as u64 )
168
194
. await ?
169
195
{
170
196
if output_status. spent {
171
197
if let Some ( spending_tx_status) = output_status. status {
172
198
if spending_tx_status. confirmed {
173
199
let spending_txid = output_status. txid . unwrap ( ) ;
174
- if let Some ( spending_tx) = client. get_tx ( & spending_txid) . await ? {
200
+ if let Some ( spending_tx) = self . client . get_tx ( & spending_txid) . await ? {
175
201
let block_height = spending_tx_status. block_height . unwrap ( ) ;
176
- let block_header = client. get_header ( block_height) . await ?;
202
+ let block_header = self . client . get_header ( block_height) . await ?;
177
203
if let Some ( merkle_proof) =
178
- client. get_merkle_proof ( & spending_txid) . await ?
204
+ self . client . get_merkle_proof ( & spending_txid) . await ?
179
205
{
180
206
confirmed_txs. push ( (
181
207
spending_tx,
@@ -217,13 +243,13 @@ where
217
243
async fn sync_transaction_unconfirmed (
218
244
& self , confirmables : & Vec < & ( dyn Confirm + Sync ) > ,
219
245
) -> Result < ( ) , Error > {
220
- let client = & * self . blockchain ;
221
246
// Query the interface for relevant txids and check whether they have been
222
247
// reorged-out of the chain.
223
248
let relevant_txids =
224
249
confirmables. iter ( ) . flat_map ( |c| c. get_relevant_txids ( ) ) . collect :: < HashSet < Txid > > ( ) ;
225
250
for txid in relevant_txids {
226
- let tx_unconfirmed = client
251
+ let tx_unconfirmed = self
252
+ . client
227
253
. get_tx_status ( & txid)
228
254
. await
229
255
. ok ( )
@@ -300,6 +326,63 @@ where
300
326
}
301
327
}
302
328
329
+ impl < D > Access for LdkLiteChainAccess < D >
330
+ where
331
+ D : BatchDatabase ,
332
+ {
333
+ fn get_utxo (
334
+ & self , genesis_hash : & BlockHash , short_channel_id : u64 ,
335
+ ) -> Result < TxOut , AccessError > {
336
+ if genesis_hash
337
+ != & bitcoin:: blockdata:: constants:: genesis_block ( self . config . network )
338
+ . header
339
+ . block_hash ( )
340
+ {
341
+ return Err ( AccessError :: UnknownChain ) ;
342
+ }
343
+
344
+ let locked_runtime = self . tokio_runtime . read ( ) . unwrap ( ) ;
345
+ if locked_runtime. as_ref ( ) . is_none ( ) {
346
+ return Err ( AccessError :: UnknownTx ) ;
347
+ }
348
+
349
+ let block_height = scid_utils:: block_from_scid ( & short_channel_id) ;
350
+ let tx_index = scid_utils:: tx_index_from_scid ( & short_channel_id) ;
351
+ let vout = scid_utils:: vout_from_scid ( & short_channel_id) ;
352
+
353
+ let block_hash = self
354
+ . blockchain
355
+ . get_block_hash ( block_height. into ( ) )
356
+ . map_err ( |_| AccessError :: UnknownTx ) ?;
357
+
358
+ let tokio_client = Arc :: clone ( & self . client ) ;
359
+ let txout_opt: Arc < Mutex < Option < TxOut > > > = Arc :: new ( Mutex :: new ( None ) ) ;
360
+ let txout_opt_tokio = Arc :: clone ( & txout_opt) ;
361
+
362
+ locked_runtime. as_ref ( ) . unwrap ( ) . spawn ( async move {
363
+ let txid_res =
364
+ tokio_client. get_txid_at_block_index ( & block_hash, tx_index as usize ) . await ;
365
+
366
+ if let Some ( txid) = txid_res. unwrap_or ( None ) {
367
+ let tx_res = tokio_client. get_tx ( & txid) . await ;
368
+
369
+ if let Some ( tx) = tx_res. unwrap_or ( None ) {
370
+ if let Some ( tx_out) = tx. output . get ( vout as usize ) {
371
+ * txout_opt_tokio. lock ( ) . unwrap ( ) = Some ( tx_out. clone ( ) ) ;
372
+ }
373
+ }
374
+ }
375
+ } ) ;
376
+
377
+ let locked_opt = txout_opt. lock ( ) . unwrap ( ) ;
378
+ if let Some ( tx_out) = & * locked_opt {
379
+ return Ok ( tx_out. clone ( ) ) ;
380
+ } else {
381
+ return Err ( AccessError :: UnknownTx ) ;
382
+ }
383
+ }
384
+ }
385
+
303
386
impl < D > Filter for LdkLiteChainAccess < D >
304
387
where
305
388
D : BatchDatabase ,
0 commit comments