Skip to content

Commit fdf6710

Browse files
febyejirandomlogin
andcommitted
Add CBF integration tests and documentation
Co-authored-by: Alexander Shevtsov <randomlogin76@gmail.com>
1 parent 26700a4 commit fdf6710

File tree

4 files changed

+345
-3
lines changed

4 files changed

+345
-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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,10 @@ pub(crate) fn random_chain_source<'a>(
272272
println!("Randomly setting up Bitcoind REST chain syncing...");
273273
TestChainSource::BitcoindRestSync(bitcoind)
274274
},
275+
4 => {
276+
println!("Randomly setting up CBF compact block filter syncing...");
277+
TestChainSource::Cbf(bitcoind)
278+
},
275279
_ => unreachable!(),
276280
}
277281
}

tests/integration_tests_rust.rs

Lines changed: 334 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use common::{
2424
open_channel, open_channel_push_amt, open_channel_with_all, premine_and_distribute_funds,
2525
premine_blocks, prepare_rbf, random_chain_source, random_config, random_listening_addresses,
2626
setup_bitcoind_and_electrsd, setup_builder, setup_node, setup_two_nodes, splice_in_with_all,
27-
wait_for_tx, TestChainSource, TestStoreType, TestSyncStore,
27+
wait_for_cbf_sync, wait_for_tx, TestChainSource, TestStoreType, TestSyncStore,
2828
};
2929
use ldk_node::config::{AsyncPaymentsRole, EsploraSyncConfig};
3030
use ldk_node::entropy::NodeEntropy;
@@ -2805,3 +2805,336 @@ async fn splice_in_with_all_balance() {
28052805
node_a.stop().unwrap();
28062806
node_b.stop().unwrap();
28072807
}
2808+
2809+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2810+
async fn start_stop_cbf() {
2811+
let (bitcoind, _electrsd) = setup_bitcoind_and_electrsd();
2812+
let chain_source = TestChainSource::Cbf(&bitcoind);
2813+
let node = setup_node(&chain_source, random_config(true));
2814+
2815+
assert!(node.status().is_running);
2816+
node.stop().unwrap();
2817+
assert_eq!(node.stop(), Err(NodeError::NotRunning));
2818+
2819+
node.start().unwrap();
2820+
assert_eq!(node.start(), Err(NodeError::AlreadyRunning));
2821+
assert!(node.status().is_running);
2822+
2823+
node.stop().unwrap();
2824+
}
2825+
2826+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2827+
async fn fee_rate_estimation_after_manual_sync_cbf() {
2828+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
2829+
let chain_source = TestChainSource::Cbf(&bitcoind);
2830+
let node = setup_node(&chain_source, random_config(true));
2831+
2832+
let addr = node.onchain_payment().new_address().unwrap();
2833+
premine_and_distribute_funds(
2834+
&bitcoind.client,
2835+
&electrsd.client,
2836+
vec![addr],
2837+
Amount::from_sat(100_000),
2838+
)
2839+
.await;
2840+
2841+
wait_for_cbf_sync(&node).await;
2842+
let first_fee_update = node.status().latest_fee_rate_cache_update_timestamp;
2843+
assert!(first_fee_update.is_some());
2844+
2845+
// Subsequent manual syncs should keep the fee cache populated.
2846+
node.sync_wallets().unwrap();
2847+
let second_fee_update = node.status().latest_fee_rate_cache_update_timestamp;
2848+
assert!(second_fee_update.is_some());
2849+
assert!(second_fee_update >= first_fee_update);
2850+
2851+
node.stop().unwrap();
2852+
}
2853+
2854+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2855+
async fn repeated_manual_sync_cbf() {
2856+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
2857+
let chain_source = TestChainSource::Cbf(&bitcoind);
2858+
let node = setup_node(&chain_source, random_config(true));
2859+
2860+
let addr = node.onchain_payment().new_address().unwrap();
2861+
let premine_amount_sat = 100_000;
2862+
2863+
premine_and_distribute_funds(
2864+
&bitcoind.client,
2865+
&electrsd.client,
2866+
vec![addr],
2867+
Amount::from_sat(premine_amount_sat),
2868+
)
2869+
.await;
2870+
2871+
wait_for_cbf_sync(&node).await;
2872+
assert_eq!(node.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
2873+
2874+
// Regression: the second manual sync must not block forever.
2875+
node.sync_wallets().unwrap();
2876+
assert_eq!(node.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
2877+
2878+
node.stop().unwrap();
2879+
}
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 =
2889+
ldk_node::config::CbfSyncConfig { background_sync_config: None, ..Default::default() };
2890+
2891+
let test_sync_store = TestSyncStore::new(config.node_config.storage_dir_path.clone().into());
2892+
2893+
setup_builder!(builder, config.node_config);
2894+
builder.set_chain_source_cbf(vec![peer_addr.clone()], Some(sync_config.clone()));
2895+
2896+
let node = builder
2897+
.build_with_store(config.node_entropy.clone().into(), test_sync_store.clone())
2898+
.unwrap();
2899+
node.start().unwrap();
2900+
2901+
let expected_node_id = node.node_id();
2902+
assert_eq!(node.start(), Err(NodeError::AlreadyRunning));
2903+
2904+
let funding_address = node.onchain_payment().new_address().unwrap();
2905+
assert_eq!(node.list_balances().total_onchain_balance_sats, 0);
2906+
2907+
let expected_amount = Amount::from_sat(100_000);
2908+
premine_and_distribute_funds(
2909+
&bitcoind.client,
2910+
&electrsd.client,
2911+
vec![funding_address],
2912+
expected_amount,
2913+
)
2914+
.await;
2915+
2916+
wait_for_cbf_sync(&node).await;
2917+
assert_eq!(node.list_balances().spendable_onchain_balance_sats, expected_amount.to_sat());
2918+
2919+
node.stop().unwrap();
2920+
assert_eq!(node.stop(), Err(NodeError::NotRunning));
2921+
2922+
node.start().unwrap();
2923+
assert_eq!(node.start(), Err(NodeError::AlreadyRunning));
2924+
2925+
node.stop().unwrap();
2926+
assert_eq!(node.stop(), Err(NodeError::NotRunning));
2927+
drop(node);
2928+
2929+
// Reinitialize from the same config and store.
2930+
setup_builder!(builder, config.node_config);
2931+
builder.set_chain_source_cbf(vec![peer_addr], Some(sync_config));
2932+
2933+
let reinitialized_node =
2934+
builder.build_with_store(config.node_entropy.into(), test_sync_store).unwrap();
2935+
reinitialized_node.start().unwrap();
2936+
assert_eq!(reinitialized_node.node_id(), expected_node_id);
2937+
2938+
// Balance should be persisted from the previous run.
2939+
assert_eq!(
2940+
reinitialized_node.list_balances().spendable_onchain_balance_sats,
2941+
expected_amount.to_sat()
2942+
);
2943+
2944+
wait_for_cbf_sync(&reinitialized_node).await;
2945+
assert_eq!(
2946+
reinitialized_node.list_balances().spendable_onchain_balance_sats,
2947+
expected_amount.to_sat()
2948+
);
2949+
2950+
reinitialized_node.stop().unwrap();
2951+
}
2952+
2953+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2954+
async fn onchain_wallet_recovery_cbf() {
2955+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
2956+
let chain_source = TestChainSource::Cbf(&bitcoind);
2957+
2958+
let original_config = random_config(true);
2959+
let original_node_entropy = original_config.node_entropy.clone();
2960+
let original_node = setup_node(&chain_source, original_config);
2961+
2962+
let premine_amount_sat = 100_000;
2963+
2964+
let addr_1 = original_node.onchain_payment().new_address().unwrap();
2965+
2966+
premine_and_distribute_funds(
2967+
&bitcoind.client,
2968+
&electrsd.client,
2969+
vec![addr_1],
2970+
Amount::from_sat(premine_amount_sat),
2971+
)
2972+
.await;
2973+
2974+
wait_for_cbf_sync(&original_node).await;
2975+
assert_eq!(original_node.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
2976+
2977+
let addr_2 = original_node.onchain_payment().new_address().unwrap();
2978+
2979+
let txid = bitcoind
2980+
.client
2981+
.send_to_address(&addr_2, Amount::from_sat(premine_amount_sat))
2982+
.unwrap()
2983+
.0
2984+
.parse()
2985+
.unwrap();
2986+
wait_for_tx(&electrsd.client, txid).await;
2987+
2988+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 1).await;
2989+
2990+
wait_for_cbf_sync(&original_node).await;
2991+
assert_eq!(
2992+
original_node.list_balances().spendable_onchain_balance_sats,
2993+
premine_amount_sat * 2
2994+
);
2995+
2996+
original_node.stop().unwrap();
2997+
drop(original_node);
2998+
2999+
// Now we start from scratch, only the seed remains the same.
3000+
let mut recovered_config = random_config(true);
3001+
recovered_config.node_entropy = original_node_entropy;
3002+
recovered_config.recovery_mode = true;
3003+
let recovered_node = setup_node(&chain_source, recovered_config);
3004+
3005+
wait_for_cbf_sync(&recovered_node).await;
3006+
assert_eq!(
3007+
recovered_node.list_balances().spendable_onchain_balance_sats,
3008+
premine_amount_sat * 2
3009+
);
3010+
3011+
// Check we sync even when skipping some addresses.
3012+
let _addr_3 = recovered_node.onchain_payment().new_address().unwrap();
3013+
let _addr_4 = recovered_node.onchain_payment().new_address().unwrap();
3014+
let _addr_5 = recovered_node.onchain_payment().new_address().unwrap();
3015+
let addr_6 = recovered_node.onchain_payment().new_address().unwrap();
3016+
3017+
let txid = bitcoind
3018+
.client
3019+
.send_to_address(&addr_6, Amount::from_sat(premine_amount_sat))
3020+
.unwrap()
3021+
.0
3022+
.parse()
3023+
.unwrap();
3024+
wait_for_tx(&electrsd.client, txid).await;
3025+
3026+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 1).await;
3027+
3028+
wait_for_cbf_sync(&recovered_node).await;
3029+
assert_eq!(
3030+
recovered_node.list_balances().spendable_onchain_balance_sats,
3031+
premine_amount_sat * 3
3032+
);
3033+
3034+
recovered_node.stop().unwrap();
3035+
}
3036+
3037+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
3038+
async fn onchain_send_receive_cbf() {
3039+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
3040+
let chain_source = TestChainSource::Cbf(&bitcoind);
3041+
let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false);
3042+
3043+
let addr_a = node_a.onchain_payment().new_address().unwrap();
3044+
let addr_b = node_b.onchain_payment().new_address().unwrap();
3045+
3046+
let premine_amount_sat = 1_100_000;
3047+
premine_and_distribute_funds(
3048+
&bitcoind.client,
3049+
&electrsd.client,
3050+
vec![addr_a.clone(), addr_b.clone()],
3051+
Amount::from_sat(premine_amount_sat),
3052+
)
3053+
.await;
3054+
3055+
wait_for_cbf_sync(&node_a).await;
3056+
node_b.sync_wallets().unwrap();
3057+
assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
3058+
assert_eq!(node_b.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
3059+
3060+
// Check on-chain payment tracking after premine.
3061+
let node_a_payments = node_a.list_payments();
3062+
let node_b_payments = node_b.list_payments();
3063+
for payments in [&node_a_payments, &node_b_payments] {
3064+
assert_eq!(payments.len(), 1);
3065+
}
3066+
for p in [node_a_payments.first().unwrap(), node_b_payments.first().unwrap()] {
3067+
assert_eq!(p.amount_msat, Some(premine_amount_sat * 1000));
3068+
assert_eq!(p.direction, PaymentDirection::Inbound);
3069+
assert_eq!(p.status, PaymentStatus::Pending);
3070+
match p.kind {
3071+
PaymentKind::Onchain { status, .. } => {
3072+
assert!(matches!(status, ConfirmationStatus::Confirmed { .. }));
3073+
},
3074+
_ => panic!("Unexpected payment kind"),
3075+
}
3076+
}
3077+
3078+
// Send from B to A.
3079+
let amount_to_send_sats = 54_321;
3080+
let txid =
3081+
node_b.onchain_payment().send_to_address(&addr_a, amount_to_send_sats, None).unwrap();
3082+
wait_for_tx(&electrsd.client, txid).await;
3083+
3084+
// Mine the transaction so CBF can see it.
3085+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await;
3086+
wait_for_cbf_sync(&node_a).await;
3087+
node_b.sync_wallets().unwrap();
3088+
3089+
let payment_id = PaymentId(txid.to_byte_array());
3090+
let payment_a = node_a.payment(&payment_id).unwrap();
3091+
match payment_a.kind {
3092+
PaymentKind::Onchain { txid: tx, status } => {
3093+
assert_eq!(tx, txid);
3094+
assert!(matches!(status, ConfirmationStatus::Confirmed { .. }));
3095+
},
3096+
_ => panic!("Unexpected payment kind"),
3097+
}
3098+
assert!(payment_a.fee_paid_msat > Some(0));
3099+
assert_eq!(payment_a.amount_msat, Some(amount_to_send_sats * 1000));
3100+
3101+
let payment_b = node_b.payment(&payment_id).unwrap();
3102+
match payment_b.kind {
3103+
PaymentKind::Onchain { txid: tx, status } => {
3104+
assert_eq!(tx, txid);
3105+
assert!(matches!(status, ConfirmationStatus::Confirmed { .. }));
3106+
},
3107+
_ => panic!("Unexpected payment kind"),
3108+
}
3109+
assert!(payment_b.fee_paid_msat > Some(0));
3110+
assert_eq!(payment_b.amount_msat, Some(amount_to_send_sats * 1000));
3111+
assert_eq!(payment_a.fee_paid_msat, payment_b.fee_paid_msat);
3112+
3113+
let onchain_fee_buffer_sat = 1000;
3114+
let expected_node_a_balance = premine_amount_sat + amount_to_send_sats;
3115+
assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, expected_node_a_balance);
3116+
assert!(
3117+
node_b.list_balances().spendable_onchain_balance_sats
3118+
> premine_amount_sat - amount_to_send_sats - onchain_fee_buffer_sat
3119+
);
3120+
assert!(
3121+
node_b.list_balances().spendable_onchain_balance_sats
3122+
< premine_amount_sat - amount_to_send_sats
3123+
);
3124+
3125+
// Test send_all_to_address.
3126+
let addr_b2 = node_b.onchain_payment().new_address().unwrap();
3127+
let txid = node_a.onchain_payment().send_all_to_address(&addr_b2, false, None).unwrap();
3128+
wait_for_tx(&electrsd.client, txid).await;
3129+
3130+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await;
3131+
wait_for_cbf_sync(&node_a).await;
3132+
node_b.sync_wallets().unwrap();
3133+
3134+
assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, 0);
3135+
assert_eq!(node_a.list_balances().total_onchain_balance_sats, 0);
3136+
assert!(node_b.list_balances().spendable_onchain_balance_sats > premine_amount_sat);
3137+
3138+
node_a.stop().unwrap();
3139+
node_b.stop().unwrap();
3140+
}

0 commit comments

Comments
 (0)