Skip to content

Commit bc490b0

Browse files
committed
Add CBF integration tests for restart, recovery, and on-chain send/receive
1 parent 77bffa4 commit bc490b0

File tree

3 files changed

+280
-7
lines changed

3 files changed

+280
-7
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: |

tests/common/mod.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -606,12 +606,18 @@ pub(crate) async fn wait_for_outpoint_spend<E: ElectrumApi>(electrs: &E, outpoin
606606
pub(crate) async fn wait_for_cbf_sync(node: &TestNode) {
607607
let before = node.status().latest_onchain_wallet_sync_timestamp;
608608
let mut delay = Duration::from_millis(200);
609-
for _ in 0..30 {
610-
if node.sync_wallets().is_ok() {
611-
let after = node.status().latest_onchain_wallet_sync_timestamp;
612-
if after > before {
613-
return;
614-
}
609+
for i in 0..30 {
610+
match node.sync_wallets() {
611+
Ok(()) => {
612+
let after = node.status().latest_onchain_wallet_sync_timestamp;
613+
if after > before {
614+
return;
615+
}
616+
eprintln!("wait_for_cbf_sync: attempt {i}, sync ok but ts unchanged (before={before:?}, after={after:?})");
617+
},
618+
Err(e) => {
619+
eprintln!("wait_for_cbf_sync: attempt {i}, sync error: {e:?}");
620+
},
615621
}
616622
tokio::time::sleep(delay).await;
617623
if delay < Duration::from_secs(2) {

tests/integration_tests_rust.rs

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

29772977
node.stop().unwrap();
29782978
}
2979+
2980+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2981+
async fn start_stop_reinit_cbf() {
2982+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
2983+
let config = random_config(true);
2984+
2985+
let p2p_socket = bitcoind.params.p2p_socket.expect("P2P must be enabled for CBF");
2986+
let peer_addr = format!("{}", p2p_socket);
2987+
let sync_config = ldk_node::config::CbfSyncConfig {
2988+
background_sync_config: None,
2989+
timeouts_config: Default::default(),
2990+
};
2991+
2992+
let test_sync_store = TestSyncStore::new(config.node_config.storage_dir_path.clone().into());
2993+
2994+
setup_builder!(builder, config.node_config);
2995+
builder.set_chain_source_cbf(vec![peer_addr.clone()], Some(sync_config.clone()));
2996+
2997+
let node = builder
2998+
.build_with_store(config.node_entropy.clone().into(), test_sync_store.clone())
2999+
.unwrap();
3000+
node.start().unwrap();
3001+
3002+
let expected_node_id = node.node_id();
3003+
assert_eq!(node.start(), Err(NodeError::AlreadyRunning));
3004+
3005+
let funding_address = node.onchain_payment().new_address().unwrap();
3006+
assert_eq!(node.list_balances().total_onchain_balance_sats, 0);
3007+
3008+
let expected_amount = Amount::from_sat(100_000);
3009+
premine_and_distribute_funds(
3010+
&bitcoind.client,
3011+
&electrsd.client,
3012+
vec![funding_address],
3013+
expected_amount,
3014+
)
3015+
.await;
3016+
3017+
wait_for_cbf_sync(&node).await;
3018+
assert_eq!(node.list_balances().spendable_onchain_balance_sats, expected_amount.to_sat());
3019+
3020+
node.stop().unwrap();
3021+
assert_eq!(node.stop(), Err(NodeError::NotRunning));
3022+
3023+
node.start().unwrap();
3024+
assert_eq!(node.start(), Err(NodeError::AlreadyRunning));
3025+
3026+
node.stop().unwrap();
3027+
assert_eq!(node.stop(), Err(NodeError::NotRunning));
3028+
drop(node);
3029+
3030+
// Reinitialize from the same config and store.
3031+
setup_builder!(builder, config.node_config);
3032+
builder.set_chain_source_cbf(vec![peer_addr], Some(sync_config));
3033+
3034+
let reinitialized_node =
3035+
builder.build_with_store(config.node_entropy.into(), test_sync_store).unwrap();
3036+
reinitialized_node.start().unwrap();
3037+
assert_eq!(reinitialized_node.node_id(), expected_node_id);
3038+
3039+
// Balance should be persisted from the previous run.
3040+
assert_eq!(
3041+
reinitialized_node.list_balances().spendable_onchain_balance_sats,
3042+
expected_amount.to_sat()
3043+
);
3044+
3045+
wait_for_cbf_sync(&reinitialized_node).await;
3046+
assert_eq!(
3047+
reinitialized_node.list_balances().spendable_onchain_balance_sats,
3048+
expected_amount.to_sat()
3049+
);
3050+
3051+
reinitialized_node.stop().unwrap();
3052+
}
3053+
3054+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
3055+
async fn onchain_wallet_recovery_cbf() {
3056+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
3057+
let chain_source = TestChainSource::Cbf(&bitcoind);
3058+
3059+
let original_config = random_config(true);
3060+
let original_node_entropy = original_config.node_entropy.clone();
3061+
let original_node = setup_node(&chain_source, original_config);
3062+
3063+
let premine_amount_sat = 100_000;
3064+
3065+
let addr_1 = original_node.onchain_payment().new_address().unwrap();
3066+
3067+
premine_and_distribute_funds(
3068+
&bitcoind.client,
3069+
&electrsd.client,
3070+
vec![addr_1],
3071+
Amount::from_sat(premine_amount_sat),
3072+
)
3073+
.await;
3074+
3075+
wait_for_cbf_sync(&original_node).await;
3076+
assert_eq!(original_node.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
3077+
3078+
let addr_2 = original_node.onchain_payment().new_address().unwrap();
3079+
3080+
let txid = bitcoind
3081+
.client
3082+
.send_to_address(&addr_2, Amount::from_sat(premine_amount_sat))
3083+
.unwrap()
3084+
.0
3085+
.parse()
3086+
.unwrap();
3087+
wait_for_tx(&electrsd.client, txid).await;
3088+
3089+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 1).await;
3090+
3091+
wait_for_cbf_sync(&original_node).await;
3092+
assert_eq!(
3093+
original_node.list_balances().spendable_onchain_balance_sats,
3094+
premine_amount_sat * 2
3095+
);
3096+
3097+
original_node.stop().unwrap();
3098+
drop(original_node);
3099+
3100+
// Now we start from scratch, only the seed remains the same.
3101+
let mut recovered_config = random_config(true);
3102+
recovered_config.node_entropy = original_node_entropy;
3103+
recovered_config.recovery_mode = true;
3104+
let recovered_node = setup_node(&chain_source, recovered_config);
3105+
3106+
wait_for_cbf_sync(&recovered_node).await;
3107+
assert_eq!(
3108+
recovered_node.list_balances().spendable_onchain_balance_sats,
3109+
premine_amount_sat * 2
3110+
);
3111+
3112+
// Check we sync even when skipping some addresses.
3113+
let _addr_3 = recovered_node.onchain_payment().new_address().unwrap();
3114+
let _addr_4 = recovered_node.onchain_payment().new_address().unwrap();
3115+
let _addr_5 = recovered_node.onchain_payment().new_address().unwrap();
3116+
let addr_6 = recovered_node.onchain_payment().new_address().unwrap();
3117+
3118+
let txid = bitcoind
3119+
.client
3120+
.send_to_address(&addr_6, Amount::from_sat(premine_amount_sat))
3121+
.unwrap()
3122+
.0
3123+
.parse()
3124+
.unwrap();
3125+
wait_for_tx(&electrsd.client, txid).await;
3126+
3127+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 1).await;
3128+
3129+
wait_for_cbf_sync(&recovered_node).await;
3130+
assert_eq!(
3131+
recovered_node.list_balances().spendable_onchain_balance_sats,
3132+
premine_amount_sat * 3
3133+
);
3134+
3135+
recovered_node.stop().unwrap();
3136+
}
3137+
3138+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
3139+
async fn onchain_send_receive_cbf() {
3140+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
3141+
let chain_source = TestChainSource::Cbf(&bitcoind);
3142+
let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false);
3143+
3144+
let addr_a = node_a.onchain_payment().new_address().unwrap();
3145+
let addr_b = node_b.onchain_payment().new_address().unwrap();
3146+
3147+
let premine_amount_sat = 1_100_000;
3148+
premine_and_distribute_funds(
3149+
&bitcoind.client,
3150+
&electrsd.client,
3151+
vec![addr_a.clone(), addr_b.clone()],
3152+
Amount::from_sat(premine_amount_sat),
3153+
)
3154+
.await;
3155+
3156+
wait_for_cbf_sync(&node_a).await;
3157+
node_b.sync_wallets().unwrap();
3158+
assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
3159+
assert_eq!(node_b.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
3160+
3161+
// Check on-chain payment tracking after premine.
3162+
let node_a_payments = node_a.list_payments();
3163+
let node_b_payments = node_b.list_payments();
3164+
for payments in [&node_a_payments, &node_b_payments] {
3165+
assert_eq!(payments.len(), 1);
3166+
}
3167+
for p in [node_a_payments.first().unwrap(), node_b_payments.first().unwrap()] {
3168+
assert_eq!(p.amount_msat, Some(premine_amount_sat * 1000));
3169+
assert_eq!(p.direction, PaymentDirection::Inbound);
3170+
assert_eq!(p.status, PaymentStatus::Pending);
3171+
match p.kind {
3172+
PaymentKind::Onchain { status, .. } => {
3173+
assert!(matches!(status, ConfirmationStatus::Confirmed { .. }));
3174+
},
3175+
_ => panic!("Unexpected payment kind"),
3176+
}
3177+
}
3178+
3179+
// Send from B to A.
3180+
let amount_to_send_sats = 54_321;
3181+
let txid =
3182+
node_b.onchain_payment().send_to_address(&addr_a, amount_to_send_sats, None).unwrap();
3183+
wait_for_tx(&electrsd.client, txid).await;
3184+
3185+
// Mine the transaction so CBF can see it.
3186+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await;
3187+
wait_for_cbf_sync(&node_a).await;
3188+
node_b.sync_wallets().unwrap();
3189+
3190+
let payment_id = PaymentId(txid.to_byte_array());
3191+
let payment_a = node_a.payment(&payment_id).unwrap();
3192+
match payment_a.kind {
3193+
PaymentKind::Onchain { txid: tx, status } => {
3194+
assert_eq!(tx, txid);
3195+
assert!(matches!(status, ConfirmationStatus::Confirmed { .. }));
3196+
},
3197+
_ => panic!("Unexpected payment kind"),
3198+
}
3199+
assert!(payment_a.fee_paid_msat > Some(0));
3200+
assert_eq!(payment_a.amount_msat, Some(amount_to_send_sats * 1000));
3201+
3202+
let payment_b = node_b.payment(&payment_id).unwrap();
3203+
match payment_b.kind {
3204+
PaymentKind::Onchain { txid: tx, status } => {
3205+
assert_eq!(tx, txid);
3206+
assert!(matches!(status, ConfirmationStatus::Confirmed { .. }));
3207+
},
3208+
_ => panic!("Unexpected payment kind"),
3209+
}
3210+
assert!(payment_b.fee_paid_msat > Some(0));
3211+
assert_eq!(payment_b.amount_msat, Some(amount_to_send_sats * 1000));
3212+
assert_eq!(payment_a.fee_paid_msat, payment_b.fee_paid_msat);
3213+
3214+
let onchain_fee_buffer_sat = 1000;
3215+
let expected_node_a_balance = premine_amount_sat + amount_to_send_sats;
3216+
assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, expected_node_a_balance);
3217+
assert!(
3218+
node_b.list_balances().spendable_onchain_balance_sats
3219+
> premine_amount_sat - amount_to_send_sats - onchain_fee_buffer_sat
3220+
);
3221+
assert!(
3222+
node_b.list_balances().spendable_onchain_balance_sats
3223+
< premine_amount_sat - amount_to_send_sats
3224+
);
3225+
3226+
// Test send_all_to_address.
3227+
let addr_b2 = node_b.onchain_payment().new_address().unwrap();
3228+
let txid = node_a.onchain_payment().send_all_to_address(&addr_b2, false, None).unwrap();
3229+
wait_for_tx(&electrsd.client, txid).await;
3230+
3231+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await;
3232+
wait_for_cbf_sync(&node_a).await;
3233+
node_b.sync_wallets().unwrap();
3234+
3235+
assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, 0);
3236+
assert_eq!(node_a.list_balances().total_onchain_balance_sats, 0);
3237+
assert!(node_b.list_balances().spendable_onchain_balance_sats > premine_amount_sat);
3238+
3239+
node_a.stop().unwrap();
3240+
node_b.stop().unwrap();
3241+
}

0 commit comments

Comments
 (0)