Skip to content

Commit 9af3fdd

Browse files
committed
Add initial chain access implementation
1 parent 49fdef5 commit 9af3fdd

File tree

1 file changed

+341
-0
lines changed

1 file changed

+341
-0
lines changed

src/access.rs

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
use crate::error::LdkLiteError as Error;
2+
#[allow(unused_imports)]
3+
use crate::logger::{
4+
log_error, log_given_level, log_info, log_internal, log_trace, log_warn, FilesystemLogger,
5+
Logger,
6+
};
7+
8+
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
9+
use lightning::chain::WatchedOutput;
10+
use lightning::chain::{Confirm, Filter};
11+
12+
use bdk::blockchain::{Blockchain, EsploraBlockchain, GetBlockHash, GetHeight, GetTx};
13+
use bdk::database::BatchDatabase;
14+
use bdk::wallet::AddressIndex;
15+
use bdk::{SignOptions, SyncOptions};
16+
17+
use bitcoin::{BlockHash, Script, Transaction, Txid};
18+
19+
use std::sync::{Arc, Mutex};
20+
use std::time::Instant;
21+
22+
pub struct LdkLiteChainAccess<D>
23+
where
24+
D: BatchDatabase,
25+
{
26+
blockchain: EsploraBlockchain,
27+
wallet: Mutex<bdk::Wallet<D>>,
28+
queued_transactions: Mutex<Vec<Txid>>,
29+
watched_transactions: Mutex<Vec<Txid>>,
30+
queued_outputs: Mutex<Vec<WatchedOutput>>,
31+
watched_outputs: Mutex<Vec<WatchedOutput>>,
32+
last_sync_height: Mutex<Option<u32>>,
33+
logger: Arc<FilesystemLogger>,
34+
}
35+
36+
impl<D> LdkLiteChainAccess<D>
37+
where
38+
D: BatchDatabase,
39+
{
40+
pub(crate) fn new(
41+
blockchain: EsploraBlockchain, wallet: bdk::Wallet<D>, logger: Arc<FilesystemLogger>,
42+
) -> Self {
43+
let wallet = Mutex::new(wallet);
44+
let watched_transactions = Mutex::new(Vec::new());
45+
let queued_transactions = Mutex::new(Vec::new());
46+
let watched_outputs = Mutex::new(Vec::new());
47+
let queued_outputs = Mutex::new(Vec::new());
48+
let last_sync_height = Mutex::new(None);
49+
Self {
50+
blockchain,
51+
wallet,
52+
queued_transactions,
53+
watched_transactions,
54+
queued_outputs,
55+
watched_outputs,
56+
last_sync_height,
57+
logger,
58+
}
59+
}
60+
61+
pub(crate) fn sync_wallet(&self) -> Result<(), Error> {
62+
let sync_options = SyncOptions { progress: None };
63+
64+
let now = Instant::now();
65+
self.wallet
66+
.lock()
67+
.unwrap()
68+
.sync(&self.blockchain, sync_options)
69+
.map_err(|e| Error::Bdk(e))?;
70+
71+
log_info!(
72+
self.logger,
73+
"On-chain wallet sync finished in {} seconds.",
74+
now.elapsed().as_secs()
75+
);
76+
77+
Ok(())
78+
}
79+
80+
pub(crate) fn sync(&self, confirmables: Vec<&dyn Confirm>) -> Result<(), Error> {
81+
let now = Instant::now();
82+
let client = &*self.blockchain;
83+
84+
let cur_height = client.get_height()?;
85+
86+
let mut locked_last_sync_height = self.last_sync_height.lock().unwrap();
87+
if cur_height >= locked_last_sync_height.unwrap_or(0) {
88+
{
89+
// First, inform the interface of the new block.
90+
let cur_block_header = client.get_header(cur_height)?;
91+
for c in &confirmables {
92+
c.best_block_updated(&cur_block_header, cur_height);
93+
}
94+
95+
*locked_last_sync_height = Some(cur_height);
96+
}
97+
98+
{
99+
// First, check the confirmation status of registered transactions as well as the
100+
// status of dependent transactions of registered outputs.
101+
let mut locked_queued_transactions = self.queued_transactions.lock().unwrap();
102+
let mut locked_queued_outputs = self.queued_outputs.lock().unwrap();
103+
let mut locked_watched_transactions = self.watched_transactions.lock().unwrap();
104+
let mut locked_watched_outputs = self.watched_outputs.lock().unwrap();
105+
106+
let mut confirmed_txs = Vec::new();
107+
108+
// Check in the current queue, as well as in registered transactions leftover from
109+
// previous iterations.
110+
let mut registered_txs: Vec<Txid> = locked_watched_transactions
111+
.iter()
112+
.chain(locked_queued_transactions.iter())
113+
.cloned()
114+
.collect();
115+
116+
registered_txs.sort_unstable_by(|txid1, txid2| txid1.cmp(&txid2));
117+
registered_txs.dedup_by(|txid1, txid2| txid1.eq(&txid2));
118+
119+
// Remember all registered but unconfirmed transactions for future processing.
120+
let mut unconfirmed_registered_txs = Vec::new();
121+
122+
for txid in registered_txs {
123+
if let Some(tx_status) = client.get_tx_status(&txid)? {
124+
if tx_status.confirmed {
125+
if let Some(tx) = client.get_tx(&txid)? {
126+
if let Some(block_height) = tx_status.block_height {
127+
let block_header = client.get_header(block_height)?;
128+
if let Some(merkle_proof) = client.get_merkle_proof(&txid)? {
129+
confirmed_txs.push((
130+
tx,
131+
block_height,
132+
block_header,
133+
merkle_proof.pos,
134+
));
135+
continue;
136+
}
137+
}
138+
}
139+
}
140+
}
141+
unconfirmed_registered_txs.push(txid);
142+
}
143+
144+
// Check all registered outputs for dependent spending transactions.
145+
let registered_outputs: Vec<WatchedOutput> = locked_watched_outputs
146+
.iter()
147+
.chain(locked_queued_outputs.iter())
148+
.cloned()
149+
.collect();
150+
151+
// Remember all registered outputs that haven't been spent for future processing.
152+
let mut unspent_registered_outputs = Vec::new();
153+
154+
for output in registered_outputs {
155+
if let Some(output_status) = client
156+
.get_output_status(&output.outpoint.txid, output.outpoint.index as u64)?
157+
{
158+
if output_status.spent {
159+
if let Some(spending_tx_status) = output_status.status {
160+
if spending_tx_status.confirmed {
161+
let spending_txid = output_status.txid.unwrap();
162+
if let Some(spending_tx) = client.get_tx(&spending_txid)? {
163+
let block_height = spending_tx_status.block_height.unwrap();
164+
let block_header = client.get_header(block_height)?;
165+
if let Some(merkle_proof) =
166+
client.get_merkle_proof(&spending_txid)?
167+
{
168+
confirmed_txs.push((
169+
spending_tx,
170+
block_height,
171+
block_header,
172+
merkle_proof.pos,
173+
));
174+
continue;
175+
}
176+
}
177+
}
178+
}
179+
}
180+
}
181+
unspent_registered_outputs.push(output);
182+
}
183+
184+
// Sort all confirmed transactions by block height and feed them to the interface
185+
// in order.
186+
confirmed_txs.sort_unstable_by(
187+
|(_, block_height1, _, _), (_, block_height2, _, _)| {
188+
block_height1.cmp(&block_height2)
189+
},
190+
);
191+
for (tx, block_height, block_header, pos) in confirmed_txs {
192+
for c in &confirmables {
193+
c.transactions_confirmed(&block_header, &[(pos, &tx)], block_height);
194+
}
195+
}
196+
197+
*locked_watched_transactions = unconfirmed_registered_txs;
198+
*locked_queued_transactions = Vec::new();
199+
*locked_watched_outputs = unspent_registered_outputs;
200+
*locked_queued_outputs = Vec::new();
201+
}
202+
203+
{
204+
// Query the interface for relevant txids and check whether they have been
205+
// reorged-out of the chain.
206+
let unconfirmed_txids = confirmables
207+
.iter()
208+
.flat_map(|c| c.get_relevant_txids())
209+
.filter(|txid| {
210+
client
211+
.get_tx_status(txid)
212+
.ok()
213+
.unwrap_or(None)
214+
.map_or(true, |status| !status.confirmed)
215+
})
216+
.collect::<Vec<Txid>>();
217+
218+
// Mark all relevant unconfirmed transactions as unconfirmed.
219+
for txid in &unconfirmed_txids {
220+
for c in &confirmables {
221+
c.transaction_unconfirmed(txid);
222+
}
223+
}
224+
}
225+
}
226+
227+
// TODO: check whether new outputs have been registered by now and process them
228+
log_info!(
229+
self.logger,
230+
"Lightning wallet sync finished in {} seconds.",
231+
now.elapsed().as_secs()
232+
);
233+
Ok(())
234+
}
235+
236+
pub(crate) fn create_funding_transaction(
237+
&self, output_script: &Script, value_sats: u64, confirmation_target: ConfirmationTarget,
238+
) -> Result<Transaction, Error> {
239+
let num_blocks = num_blocks_from_conf_target(confirmation_target);
240+
let fee_rate = self.blockchain.estimate_fee(num_blocks)?;
241+
242+
let locked_wallet = self.wallet.lock().unwrap();
243+
let mut tx_builder = locked_wallet.build_tx();
244+
245+
tx_builder.add_recipient(output_script.clone(), value_sats).fee_rate(fee_rate).enable_rbf();
246+
247+
let (mut psbt, _) = tx_builder.finish()?;
248+
249+
// We double-check that no inputs try to spend non-witness outputs. As we use a SegWit
250+
// wallet descriptor this technically can't ever happen, but better safe than sorry.
251+
for input in &psbt.inputs {
252+
if input.non_witness_utxo.is_some() {
253+
return Err(Error::FundingTxNonWitnessOuputSpend);
254+
}
255+
}
256+
257+
let finalized = locked_wallet.sign(&mut psbt, SignOptions::default())?;
258+
if !finalized {
259+
return Err(Error::FundingTxNotFinalized);
260+
}
261+
262+
Ok(psbt.extract_tx())
263+
}
264+
265+
pub(crate) fn get_new_address(&self) -> Result<bitcoin::Address, Error> {
266+
let address_info = self.wallet.lock().unwrap().get_address(AddressIndex::New)?;
267+
Ok(address_info.address)
268+
}
269+
}
270+
271+
impl<D> FeeEstimator for LdkLiteChainAccess<D>
272+
where
273+
D: BatchDatabase,
274+
{
275+
fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 {
276+
let num_blocks = num_blocks_from_conf_target(confirmation_target);
277+
// TODO: make this an unwrap_or?
278+
// TODO: double-check here https://github.com/bitcoindevkit/bdk/pull/678/commits/03a5b223800b0fafd0e7c2c82bf4943ac9d5ae58
279+
// TODO: switch to https://github.com/bitcoindevkit/bdk/pull/678 once that is merged
280+
self.blockchain.estimate_fee(num_blocks).unwrap().fee_wu(1000) as u32
281+
}
282+
}
283+
284+
impl<D> BroadcasterInterface for LdkLiteChainAccess<D>
285+
where
286+
D: BatchDatabase,
287+
{
288+
fn broadcast_transaction(&self, tx: &Transaction) {
289+
self.blockchain.broadcast(tx).ok();
290+
}
291+
}
292+
293+
impl<D> Filter for LdkLiteChainAccess<D>
294+
where
295+
D: BatchDatabase,
296+
{
297+
fn register_tx(&self, txid: &Txid, _script_pubkey: &Script) {
298+
self.queued_transactions.lock().unwrap().push(*txid);
299+
}
300+
301+
fn register_output(&self, output: WatchedOutput) -> Option<(usize, Transaction)> {
302+
self.queued_outputs.lock().unwrap().push(output);
303+
// TODO: Remove return after rust-lightning#1663 gets merged
304+
return None;
305+
}
306+
}
307+
308+
impl<D> GetHeight for LdkLiteChainAccess<D>
309+
where
310+
D: BatchDatabase,
311+
{
312+
fn get_height(&self) -> Result<u32, bdk::Error> {
313+
self.blockchain.get_height()
314+
}
315+
}
316+
317+
impl<D> GetBlockHash for LdkLiteChainAccess<D>
318+
where
319+
D: BatchDatabase,
320+
{
321+
fn get_block_hash(&self, height: u64) -> Result<BlockHash, bdk::Error> {
322+
self.blockchain.get_block_hash(height)
323+
}
324+
}
325+
326+
impl<D> GetTx for LdkLiteChainAccess<D>
327+
where
328+
D: BatchDatabase,
329+
{
330+
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, bdk::Error> {
331+
self.blockchain.get_tx(txid)
332+
}
333+
}
334+
335+
fn num_blocks_from_conf_target(confirmation_target: ConfirmationTarget) -> usize {
336+
match confirmation_target {
337+
ConfirmationTarget::Background => 6,
338+
ConfirmationTarget::Normal => 3,
339+
ConfirmationTarget::HighPriority => 1,
340+
}
341+
}

0 commit comments

Comments
 (0)