Skip to content

Commit d548d1c

Browse files
committed
Add initial chain access implementation
1 parent 3f25117 commit d548d1c

File tree

1 file changed

+325
-0
lines changed

1 file changed

+325
-0
lines changed

src/access.rs

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

0 commit comments

Comments
 (0)