Skip to content

Commit 41a6c67

Browse files
authored
Merge pull request #1870 from tnull/2022-11-add-transaction-sync-crate
Add transaction sync crate
2 parents 137b77c + ce8b5ba commit 41a6c67

File tree

8 files changed

+1015
-9
lines changed

8 files changed

+1015
-9
lines changed

.github/workflows/build.yml

+43-9
Original file line numberDiff line numberDiff line change
@@ -12,52 +12,68 @@ jobs:
1212
beta,
1313
# 1.41.1 is MSRV for Rust-Lightning, lightning-invoice, and lightning-persister
1414
1.41.1,
15-
# 1.45.2 is MSRV for lightning-net-tokio, lightning-block-sync, lightning-background-processor, and coverage generation
15+
# 1.45.2 is MSRV for lightning-net-tokio, lightning-block-sync, lightning-background-processor
1616
1.45.2,
1717
# 1.47.0 will be the MSRV for no-std builds using hashbrown once core2 is updated
18-
1.47.0]
18+
1.47.0,
19+
# 1.59.0 is the MSRV for lightning-transaction-sync
20+
1.59.0]
1921
include:
2022
- toolchain: stable
2123
build-net-tokio: true
2224
build-no-std: true
2325
build-futures: true
26+
build-tx-sync: true
27+
coverage: true
2428
- toolchain: stable
2529
platform: macos-latest
2630
build-net-tokio: true
2731
build-no-std: true
2832
build-futures: true
33+
build-tx-sync: true
2934
- toolchain: beta
3035
platform: macos-latest
3136
build-net-tokio: true
3237
build-no-std: true
3338
build-futures: true
39+
build-tx-sync: true
3440
- toolchain: stable
3541
platform: windows-latest
3642
build-net-tokio: true
3743
build-no-std: true
3844
build-futures: true
45+
build-tx-sync: false
3946
- toolchain: beta
4047
platform: windows-latest
4148
build-net-tokio: true
4249
build-no-std: true
4350
build-futures: true
51+
build-tx-sync: false
4452
- toolchain: beta
4553
build-net-tokio: true
4654
build-no-std: true
4755
build-futures: true
56+
build-tx-sync: true
4857
- toolchain: 1.41.1
4958
build-no-std: false
5059
test-log-variants: true
5160
build-futures: false
61+
build-tx-sync: false
5262
- toolchain: 1.45.2
5363
build-net-old-tokio: true
5464
build-net-tokio: true
5565
build-no-std: false
5666
build-futures: true
57-
coverage: true
67+
build-tx-sync: false
5868
- toolchain: 1.47.0
5969
build-futures: true
6070
build-no-std: true
71+
build-tx-sync: false
72+
- toolchain: 1.59.0
73+
build-net-tokio: false
74+
build-no-std: false
75+
build-futures: false
76+
build-tx-sync: true
6177
runs-on: ${{ matrix.platform }}
6278
steps:
6379
- name: Checkout source code
@@ -73,10 +89,10 @@ jobs:
7389
run: cargo update -p tokio --precise "1.14.0" --verbose
7490
env:
7591
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
76-
- name: Build on Rust ${{ matrix.toolchain }} with net-tokio
77-
if: "matrix.build-net-tokio && !matrix.coverage"
92+
- name: Build on Rust ${{ matrix.toolchain }} with net-tokio and tx-sync
93+
if: "matrix.build-net-tokio && !matrix.coverage && matrix.build-tx-sync"
7894
run: cargo build --verbose --color always
79-
- name: Build on Rust ${{ matrix.toolchain }} with net-tokio and full code-linking for coverage generation
95+
- name: Build on Rust ${{ matrix.toolchain }} with net-tokio, tx-sync, and full code-linking for coverage generation
8096
if: matrix.coverage
8197
run: RUSTFLAGS="-C link-dead-code" cargo build --verbose --color always
8298
- name: Build on Rust ${{ matrix.toolchain }}
@@ -108,14 +124,32 @@ jobs:
108124
RUSTFLAGS="-C link-dead-code" cargo build --verbose --color always --features rpc-client
109125
RUSTFLAGS="-C link-dead-code" cargo build --verbose --color always --features rpc-client,rest-client
110126
RUSTFLAGS="-C link-dead-code" cargo build --verbose --color always --features rpc-client,rest-client,tokio
127+
- name: Build Transaction Sync Clients on Rust ${{ matrix.toolchain }} with features
128+
if: "matrix.build-tx-sync && !matrix.coverage"
129+
run: |
130+
cd lightning-transaction-sync
131+
cargo build --verbose --color always --features esplora-blocking
132+
cargo build --verbose --color always --features esplora-async
133+
- name: Build transaction sync clients on Rust ${{ matrix.toolchain }} with features and full code-linking for coverage generation
134+
if: "matrix.build-tx-sync && matrix.coverage"
135+
run: |
136+
cd lightning-transaction-sync
137+
RUSTFLAGS="-C link-dead-code" cargo build --verbose --color always --features esplora-blocking
138+
RUSTFLAGS="-C link-dead-code" cargo build --verbose --color always --features esplora-async
139+
- name: Test transaction sync clients on Rust ${{ matrix.toolchain }} with features
140+
if: "matrix.build-tx-sync"
141+
run: |
142+
cd lightning-transaction-sync
143+
cargo test --verbose --color always --features esplora-blocking
144+
cargo test --verbose --color always --features esplora-async
111145
- name: Test backtrace-debug builds on Rust ${{ matrix.toolchain }}
112146
if: "matrix.toolchain == 'stable'"
113147
run: |
114148
cd lightning && cargo test --verbose --color always --features backtrace
115149
- name: Test on Rust ${{ matrix.toolchain }} with net-tokio
116-
if: "matrix.build-net-tokio && !matrix.coverage"
150+
if: "matrix.build-net-tokio && !matrix.coverage && matrix.build-tx-sync"
117151
run: cargo test --verbose --color always
118-
- name: Test on Rust ${{ matrix.toolchain }} with net-tokio and full code-linking for coverage generation
152+
- name: Test on Rust ${{ matrix.toolchain }} with net-tokio, tx-sync, and full code-linking for coverage generation
119153
if: matrix.coverage
120154
run: RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always
121155
- name: Test no-std builds on Rust ${{ matrix.toolchain }}
@@ -349,7 +383,7 @@ jobs:
349383
linting:
350384
runs-on: ubuntu-latest
351385
env:
352-
TOOLCHAIN: 1.47.0
386+
TOOLCHAIN: stable
353387
steps:
354388
- name: Checkout source code
355389
uses: actions/checkout@v3

Cargo.toml

+1
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

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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 }
28+
29+
[dev-dependencies]
30+
electrsd = { version = "0.22.0", features = ["legacy", "esplora_a33e97e1", "bitcoind_23_0"] }
31+
electrum-client = "0.12.0"
32+
once_cell = "1.16.0"
33+
tokio = { version = "1.14.0", features = ["full"] }
+75
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+
}
+63
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)