Skip to content

Commit 2f6260e

Browse files
committed
Add transaction sync crate
This crate provides utilities for syncing LDK via the transaction-based `Confirm` interface. The initial implementation facilitates synchronization with an Esplora backend server.
1 parent ad40573 commit 2f6260e

File tree

6 files changed

+629
-0
lines changed

6 files changed

+629
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
members = [
44
"lightning",
55
"lightning-block-sync",
6+
"lightning-transaction-sync",
67
"lightning-invoice",
78
"lightning-net-tokio",
89
"lightning-persister",

lightning-transaction-sync/Cargo.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[package]
2+
name = "lightning-transaction-sync"
3+
version = "0.0.113"
4+
authors = ["Elias Rohrer"]
5+
license = "MIT OR Apache-2.0"
6+
repository = "http://github.com/lightningdevkit/rust-lightning"
7+
description = """
8+
Utilities for syncing LDK via the transaction-based `Confirm` interface.
9+
"""
10+
edition = "2018"
11+
12+
[package.metadata.docs.rs]
13+
all-features = true
14+
rustdoc-args = ["--cfg", "docsrs"]
15+
16+
[features]
17+
default = []
18+
esplora-async = ["async-interface", "esplora-client/async", "futures"]
19+
esplora-blocking = ["esplora-client/blocking"]
20+
async-interface = []
21+
22+
[dependencies]
23+
lightning = { version = "0.0.113", path = "../lightning" }
24+
bitcoin = "0.29.0"
25+
bdk-macros = "0.6"
26+
futures = { version = "0.3", optional = true }
27+
esplora-client = { version = "0.3.0", default-features = false, optional = true }
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use lightning::chain::WatchedOutput;
2+
use bitcoin::{Txid, BlockHash, Transaction, BlockHeader, OutPoint};
3+
4+
use std::collections::{HashSet, HashMap};
5+
6+
7+
// Represents the current state.
8+
pub(crate) struct SyncState {
9+
// Transactions that were previously processed, but must not be forgotten
10+
// yet since they still need to be monitored for confirmation on-chain.
11+
pub watched_transactions: HashSet<Txid>,
12+
// Outputs that were previously processed, but must not be forgotten yet as
13+
// as we still need to monitor any spends on-chain.
14+
pub watched_outputs: HashMap<OutPoint, WatchedOutput>,
15+
// The tip hash observed during our last sync.
16+
pub last_sync_hash: Option<BlockHash>,
17+
// Indicates whether we need to resync, e.g., after encountering an error.
18+
pub pending_sync: bool,
19+
}
20+
21+
impl SyncState {
22+
pub fn new() -> Self {
23+
Self {
24+
watched_transactions: HashSet::new(),
25+
watched_outputs: HashMap::new(),
26+
last_sync_hash: None,
27+
pending_sync: false,
28+
}
29+
}
30+
}
31+
32+
33+
// A queue that is to be filled by `Filter` and drained during the next syncing round.
34+
pub(crate) struct FilterQueue {
35+
// Transactions that were registered via the `Filter` interface and have to be processed.
36+
pub transactions: HashSet<Txid>,
37+
// Outputs that were registered via the `Filter` interface and have to be processed.
38+
pub outputs: HashMap<OutPoint, WatchedOutput>,
39+
}
40+
41+
impl FilterQueue {
42+
pub fn new() -> Self {
43+
Self {
44+
transactions: HashSet::new(),
45+
outputs: HashMap::new(),
46+
}
47+
}
48+
49+
// Processes the transaction and output queues and adds them to the given [`SyncState`].
50+
//
51+
// Returns `true` if new items had been registered.
52+
pub fn process_queues(&mut self, sync_state: &mut SyncState) -> bool {
53+
let mut pending_registrations = false;
54+
55+
if !self.transactions.is_empty() {
56+
pending_registrations = true;
57+
58+
sync_state.watched_transactions.extend(self.transactions.drain());
59+
}
60+
61+
if !self.outputs.is_empty() {
62+
pending_registrations = true;
63+
64+
sync_state.watched_outputs.extend(self.outputs.drain());
65+
}
66+
pending_registrations
67+
}
68+
}
69+
70+
pub(crate) struct ConfirmedTx {
71+
pub tx: Transaction,
72+
pub block_header: BlockHeader,
73+
pub block_height: u32,
74+
pub pos: usize,
75+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use std::fmt;
2+
3+
#[derive(Debug)]
4+
/// An error that possibly needs to be handled by the user.
5+
pub enum TxSyncError {
6+
/// A transaction sync failed and needs to be retried eventually.
7+
Failed,
8+
}
9+
10+
impl std::error::Error for TxSyncError {}
11+
12+
impl fmt::Display for TxSyncError {
13+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
14+
match *self {
15+
Self::Failed => write!(f, "Failed to conduct transaction sync."),
16+
}
17+
}
18+
}
19+
20+
#[derive(Debug)]
21+
#[cfg(any(feature = "esplora-blocking", feature = "esplora-async"))]
22+
pub(crate) enum InternalError {
23+
/// A transaction sync failed and needs to be retried eventually.
24+
Failed,
25+
/// An inconsisteny was encounterd during transaction sync.
26+
Inconsistency,
27+
}
28+
29+
#[cfg(any(feature = "esplora-blocking", feature = "esplora-async"))]
30+
impl fmt::Display for InternalError {
31+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32+
match *self {
33+
Self::Failed => write!(f, "Failed to conduct transaction sync."),
34+
Self::Inconsistency => {
35+
write!(f, "Encountered an inconsisteny during transaction sync.")
36+
}
37+
}
38+
}
39+
}
40+
41+
#[cfg(any(feature = "esplora-blocking", feature = "esplora-async"))]
42+
impl std::error::Error for InternalError {}
43+
44+
#[cfg(any(feature = "esplora-blocking", feature = "esplora-async"))]
45+
impl From<esplora_client::Error> for TxSyncError {
46+
fn from(_e: esplora_client::Error) -> Self {
47+
Self::Failed
48+
}
49+
}
50+
51+
#[cfg(any(feature = "esplora-blocking", feature = "esplora-async"))]
52+
impl From<esplora_client::Error> for InternalError {
53+
fn from(_e: esplora_client::Error) -> Self {
54+
Self::Failed
55+
}
56+
}
57+
58+
#[cfg(any(feature = "esplora-blocking", feature = "esplora-async"))]
59+
impl From<InternalError> for TxSyncError {
60+
fn from(_e: InternalError) -> Self {
61+
Self::Failed
62+
}
63+
}

0 commit comments

Comments
 (0)