Skip to content

Commit 36b836a

Browse files
committed
Add CBF integration tests and documentation
1 parent 507eea5 commit 36b836a

File tree

4 files changed

+284
-3
lines changed

4 files changed

+284
-3
lines changed

.github/workflows/rust.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,11 @@ jobs:
8080
- name: Test on Rust ${{ matrix.toolchain }}
8181
if: "matrix.platform != 'windows-latest'"
8282
run: |
83-
RUSTFLAGS="--cfg no_download --cfg cycle_tests" cargo test
83+
RUSTFLAGS="--cfg no_download --cfg cycle_tests" cargo test -- --skip cbf
84+
- name: Test CBF on Rust ${{ matrix.toolchain }}
85+
if: "matrix.platform != 'windows-latest'"
86+
run: |
87+
RUSTFLAGS="--cfg no_download --cfg cycle_tests" cargo test cbf -- --test-threads=1
8488
- name: Test with UniFFI support on Rust ${{ matrix.toolchain }}
8589
if: "matrix.platform != 'windows-latest' && matrix.build-uniffi"
8690
run: |

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ fn main() {
6161
LDK Node currently comes with a decidedly opinionated set of design choices:
6262

6363
- On-chain data is handled by the integrated [BDK][bdk] wallet.
64-
- Chain data may currently be sourced from the Bitcoin Core RPC interface, or from an [Electrum][electrum] or [Esplora][esplora] server.
64+
- Chain data may currently be sourced from the Bitcoin Core RPC interface, from an [Electrum][electrum] or [Esplora][esplora] server, or via [compact block filters (BIP 157)][bip157].
6565
- Wallet and channel state may be persisted to an [SQLite][sqlite] database, to file system, or to a custom back-end to be implemented by the user.
6666
- Gossip data may be sourced via Lightning's peer-to-peer network or the [Rapid Gossip Sync](https://docs.rs/lightning-rapid-gossip-sync/*/lightning_rapid_gossip_sync/) protocol.
6767
- Entropy for the Lightning and on-chain wallets may be sourced from raw bytes or a [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) mnemonic. In addition, LDK Node offers the means to generate and persist the entropy bytes to disk.
@@ -80,6 +80,7 @@ The Minimum Supported Rust Version (MSRV) is currently 1.85.0.
8080
[bdk]: https://bitcoindevkit.org/
8181
[electrum]: https://github.com/spesmilo/electrum-protocol
8282
[esplora]: https://github.com/Blockstream/esplora
83+
[bip157]: https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki
8384
[sqlite]: https://sqlite.org/
8485
[rust]: https://www.rust-lang.org/
8586
[swift]: https://www.swift.org/

tests/common/mod.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,16 @@ pub(crate) fn setup_bitcoind_and_electrsd() -> (BitcoinD, ElectrsD) {
245245
pub(crate) fn random_chain_source<'a>(
246246
bitcoind: &'a BitcoinD, electrsd: &'a ElectrsD,
247247
) -> TestChainSource<'a> {
248-
let r = rand::random_range(0..3);
248+
// Allow forcing a specific backend via LDK_TEST_CHAIN_SOURCE env var.
249+
// Valid values: "esplora", "electrum", "bitcoind-rpc", "bitcoind-rest", "cbf"
250+
let r = match std::env::var("LDK_TEST_CHAIN_SOURCE").ok().as_deref() {
251+
Some("esplora") => 0,
252+
Some("electrum") => 1,
253+
Some("bitcoind-rpc") => 2,
254+
Some("bitcoind-rest") => 3,
255+
Some("cbf") => 4,
256+
_ => rand::random_range(0..5),
257+
};
249258
match r {
250259
0 => {
251260
println!("Randomly setting up Esplora chain syncing...");
@@ -263,6 +272,10 @@ pub(crate) fn random_chain_source<'a>(
263272
println!("Randomly setting up Bitcoind REST chain syncing...");
264273
TestChainSource::BitcoindRestSync(bitcoind)
265274
},
275+
4 => {
276+
println!("Randomly setting up CBF compact block filter syncing...");
277+
TestChainSource::Cbf(bitcoind)
278+
},
266279
_ => unreachable!(),
267280
}
268281
}

tests/integration_tests_rust.rs

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2877,3 +2877,266 @@ async fn repeated_manual_sync_cbf() {
28772877

28782878
node.stop().unwrap();
28792879
}
2880+
2881+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2882+
async fn start_stop_reinit_cbf() {
2883+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
2884+
let config = random_config(true);
2885+
2886+
let p2p_socket = bitcoind.params.p2p_socket.expect("P2P must be enabled for CBF");
2887+
let peer_addr = format!("{}", p2p_socket);
2888+
let sync_config = ldk_node::config::CbfSyncConfig {
2889+
background_sync_config: None,
2890+
timeouts_config: Default::default(),
2891+
};
2892+
2893+
let test_sync_store = TestSyncStore::new(config.node_config.storage_dir_path.clone().into());
2894+
2895+
setup_builder!(builder, config.node_config);
2896+
builder.set_chain_source_cbf(vec![peer_addr.clone()], Some(sync_config.clone()));
2897+
2898+
let node = builder
2899+
.build_with_store(config.node_entropy.clone().into(), test_sync_store.clone())
2900+
.unwrap();
2901+
node.start().unwrap();
2902+
2903+
let expected_node_id = node.node_id();
2904+
assert_eq!(node.start(), Err(NodeError::AlreadyRunning));
2905+
2906+
let funding_address = node.onchain_payment().new_address().unwrap();
2907+
assert_eq!(node.list_balances().total_onchain_balance_sats, 0);
2908+
2909+
let expected_amount = Amount::from_sat(100_000);
2910+
premine_and_distribute_funds(
2911+
&bitcoind.client,
2912+
&electrsd.client,
2913+
vec![funding_address],
2914+
expected_amount,
2915+
)
2916+
.await;
2917+
2918+
wait_for_cbf_sync(&node).await;
2919+
assert_eq!(node.list_balances().spendable_onchain_balance_sats, expected_amount.to_sat());
2920+
2921+
node.stop().unwrap();
2922+
assert_eq!(node.stop(), Err(NodeError::NotRunning));
2923+
2924+
node.start().unwrap();
2925+
assert_eq!(node.start(), Err(NodeError::AlreadyRunning));
2926+
2927+
node.stop().unwrap();
2928+
assert_eq!(node.stop(), Err(NodeError::NotRunning));
2929+
drop(node);
2930+
2931+
// Reinitialize from the same config and store.
2932+
setup_builder!(builder, config.node_config);
2933+
builder.set_chain_source_cbf(vec![peer_addr], Some(sync_config));
2934+
2935+
let reinitialized_node =
2936+
builder.build_with_store(config.node_entropy.into(), test_sync_store).unwrap();
2937+
reinitialized_node.start().unwrap();
2938+
assert_eq!(reinitialized_node.node_id(), expected_node_id);
2939+
2940+
// Balance should be persisted from the previous run.
2941+
assert_eq!(
2942+
reinitialized_node.list_balances().spendable_onchain_balance_sats,
2943+
expected_amount.to_sat()
2944+
);
2945+
2946+
wait_for_cbf_sync(&reinitialized_node).await;
2947+
assert_eq!(
2948+
reinitialized_node.list_balances().spendable_onchain_balance_sats,
2949+
expected_amount.to_sat()
2950+
);
2951+
2952+
reinitialized_node.stop().unwrap();
2953+
}
2954+
2955+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2956+
async fn onchain_wallet_recovery_cbf() {
2957+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
2958+
let chain_source = TestChainSource::Cbf(&bitcoind);
2959+
2960+
let original_config = random_config(true);
2961+
let original_node_entropy = original_config.node_entropy.clone();
2962+
let original_node = setup_node(&chain_source, original_config);
2963+
2964+
let premine_amount_sat = 100_000;
2965+
2966+
let addr_1 = original_node.onchain_payment().new_address().unwrap();
2967+
2968+
premine_and_distribute_funds(
2969+
&bitcoind.client,
2970+
&electrsd.client,
2971+
vec![addr_1],
2972+
Amount::from_sat(premine_amount_sat),
2973+
)
2974+
.await;
2975+
2976+
wait_for_cbf_sync(&original_node).await;
2977+
assert_eq!(original_node.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
2978+
2979+
let addr_2 = original_node.onchain_payment().new_address().unwrap();
2980+
2981+
let txid = bitcoind
2982+
.client
2983+
.send_to_address(&addr_2, Amount::from_sat(premine_amount_sat))
2984+
.unwrap()
2985+
.0
2986+
.parse()
2987+
.unwrap();
2988+
wait_for_tx(&electrsd.client, txid).await;
2989+
2990+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 1).await;
2991+
2992+
wait_for_cbf_sync(&original_node).await;
2993+
assert_eq!(
2994+
original_node.list_balances().spendable_onchain_balance_sats,
2995+
premine_amount_sat * 2
2996+
);
2997+
2998+
original_node.stop().unwrap();
2999+
drop(original_node);
3000+
3001+
// Now we start from scratch, only the seed remains the same.
3002+
let mut recovered_config = random_config(true);
3003+
recovered_config.node_entropy = original_node_entropy;
3004+
recovered_config.recovery_mode = true;
3005+
let recovered_node = setup_node(&chain_source, recovered_config);
3006+
3007+
wait_for_cbf_sync(&recovered_node).await;
3008+
assert_eq!(
3009+
recovered_node.list_balances().spendable_onchain_balance_sats,
3010+
premine_amount_sat * 2
3011+
);
3012+
3013+
// Check we sync even when skipping some addresses.
3014+
let _addr_3 = recovered_node.onchain_payment().new_address().unwrap();
3015+
let _addr_4 = recovered_node.onchain_payment().new_address().unwrap();
3016+
let _addr_5 = recovered_node.onchain_payment().new_address().unwrap();
3017+
let addr_6 = recovered_node.onchain_payment().new_address().unwrap();
3018+
3019+
let txid = bitcoind
3020+
.client
3021+
.send_to_address(&addr_6, Amount::from_sat(premine_amount_sat))
3022+
.unwrap()
3023+
.0
3024+
.parse()
3025+
.unwrap();
3026+
wait_for_tx(&electrsd.client, txid).await;
3027+
3028+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 1).await;
3029+
3030+
wait_for_cbf_sync(&recovered_node).await;
3031+
assert_eq!(
3032+
recovered_node.list_balances().spendable_onchain_balance_sats,
3033+
premine_amount_sat * 3
3034+
);
3035+
3036+
recovered_node.stop().unwrap();
3037+
}
3038+
3039+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
3040+
async fn onchain_send_receive_cbf() {
3041+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
3042+
let chain_source = TestChainSource::Cbf(&bitcoind);
3043+
let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false);
3044+
3045+
let addr_a = node_a.onchain_payment().new_address().unwrap();
3046+
let addr_b = node_b.onchain_payment().new_address().unwrap();
3047+
3048+
let premine_amount_sat = 1_100_000;
3049+
premine_and_distribute_funds(
3050+
&bitcoind.client,
3051+
&electrsd.client,
3052+
vec![addr_a.clone(), addr_b.clone()],
3053+
Amount::from_sat(premine_amount_sat),
3054+
)
3055+
.await;
3056+
3057+
wait_for_cbf_sync(&node_a).await;
3058+
node_b.sync_wallets().unwrap();
3059+
assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
3060+
assert_eq!(node_b.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
3061+
3062+
// Check on-chain payment tracking after premine.
3063+
let node_a_payments = node_a.list_payments();
3064+
let node_b_payments = node_b.list_payments();
3065+
for payments in [&node_a_payments, &node_b_payments] {
3066+
assert_eq!(payments.len(), 1);
3067+
}
3068+
for p in [node_a_payments.first().unwrap(), node_b_payments.first().unwrap()] {
3069+
assert_eq!(p.amount_msat, Some(premine_amount_sat * 1000));
3070+
assert_eq!(p.direction, PaymentDirection::Inbound);
3071+
assert_eq!(p.status, PaymentStatus::Pending);
3072+
match p.kind {
3073+
PaymentKind::Onchain { status, .. } => {
3074+
assert!(matches!(status, ConfirmationStatus::Confirmed { .. }));
3075+
},
3076+
_ => panic!("Unexpected payment kind"),
3077+
}
3078+
}
3079+
3080+
// Send from B to A.
3081+
let amount_to_send_sats = 54_321;
3082+
let txid =
3083+
node_b.onchain_payment().send_to_address(&addr_a, amount_to_send_sats, None).unwrap();
3084+
wait_for_tx(&electrsd.client, txid).await;
3085+
3086+
// Mine the transaction so CBF can see it.
3087+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await;
3088+
wait_for_cbf_sync(&node_a).await;
3089+
node_b.sync_wallets().unwrap();
3090+
3091+
let payment_id = PaymentId(txid.to_byte_array());
3092+
let payment_a = node_a.payment(&payment_id).unwrap();
3093+
match payment_a.kind {
3094+
PaymentKind::Onchain { txid: tx, status } => {
3095+
assert_eq!(tx, txid);
3096+
assert!(matches!(status, ConfirmationStatus::Confirmed { .. }));
3097+
},
3098+
_ => panic!("Unexpected payment kind"),
3099+
}
3100+
assert!(payment_a.fee_paid_msat > Some(0));
3101+
assert_eq!(payment_a.amount_msat, Some(amount_to_send_sats * 1000));
3102+
3103+
let payment_b = node_b.payment(&payment_id).unwrap();
3104+
match payment_b.kind {
3105+
PaymentKind::Onchain { txid: tx, status } => {
3106+
assert_eq!(tx, txid);
3107+
assert!(matches!(status, ConfirmationStatus::Confirmed { .. }));
3108+
},
3109+
_ => panic!("Unexpected payment kind"),
3110+
}
3111+
assert!(payment_b.fee_paid_msat > Some(0));
3112+
assert_eq!(payment_b.amount_msat, Some(amount_to_send_sats * 1000));
3113+
assert_eq!(payment_a.fee_paid_msat, payment_b.fee_paid_msat);
3114+
3115+
let onchain_fee_buffer_sat = 1000;
3116+
let expected_node_a_balance = premine_amount_sat + amount_to_send_sats;
3117+
assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, expected_node_a_balance);
3118+
assert!(
3119+
node_b.list_balances().spendable_onchain_balance_sats
3120+
> premine_amount_sat - amount_to_send_sats - onchain_fee_buffer_sat
3121+
);
3122+
assert!(
3123+
node_b.list_balances().spendable_onchain_balance_sats
3124+
< premine_amount_sat - amount_to_send_sats
3125+
);
3126+
3127+
// Test send_all_to_address.
3128+
let addr_b2 = node_b.onchain_payment().new_address().unwrap();
3129+
let txid = node_a.onchain_payment().send_all_to_address(&addr_b2, false, None).unwrap();
3130+
wait_for_tx(&electrsd.client, txid).await;
3131+
3132+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await;
3133+
wait_for_cbf_sync(&node_a).await;
3134+
node_b.sync_wallets().unwrap();
3135+
3136+
assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, 0);
3137+
assert_eq!(node_a.list_balances().total_onchain_balance_sats, 0);
3138+
assert!(node_b.list_balances().spendable_onchain_balance_sats > premine_amount_sat);
3139+
3140+
node_a.stop().unwrap();
3141+
node_b.stop().unwrap();
3142+
}

0 commit comments

Comments
 (0)