Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 0.7.0-rc.55 (Synonym Fork)
# 0.7.0-rc.56 (Synonym Fork)

## Bug Fixes

Expand Down Expand Up @@ -65,6 +65,7 @@

## Synonym Fork Additions

- Added rolling lookahead for derived on-chain accounts after their initial full scan.
- Added configurable Electrum batch size and stop gap for full scans of non-primary on-chain
wallets, while preserving the existing primary-wallet behavior and defaults.
- Added derived-account lifecycle parity with account-`0` wallets: configured accounts load on
Expand All @@ -83,9 +84,9 @@
`Config::onchain_wallet_accounts` to load them on each build. BDK data remains persisted per
account after unload
- Derived accounts full-scan after registration, then use incremental sync. Apps issuing addresses
from an exported xpub reveal their highest issued index before syncing; descriptor origins use
the real account path; channel preflight requires an account-`0` SegWit builder before counting
non-Legacy (including derived) funds
from an exported xpub can reveal their highest issued index to cover gaps beyond the rolling
lookahead; descriptor origins use the real account path; channel preflight requires an account-`0`
SegWit builder before counting non-Legacy (including derived) funds
- Bitcoind Listen synchronizes every wallet from its own checkpoint alongside the Lightning
listeners, handles shorter and equal-height forked tips, and replays the mempool when the
loaded account set changes
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ exclude = ["bindings/uniffi-bindgen"]

[package]
name = "ldk-node"
version = "0.7.0-rc.55"
version = "0.7.0-rc.56"
authors = ["Elias Rohrer <dev@tnull.de>"]
homepage = "https://lightningdevkit.org/"
license = "MIT OR Apache-2.0"
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

import PackageDescription

let tag = "v0.7.0-rc.55"
let checksum = "098d48d602fe000f1a9dbc7e758a0fe7bd8720c12b0036c38edf38283edd55f3"
let tag = "v0.7.0-rc.56"
let checksum = "91a31d12acc142ffaa405f4cae8eda0a414f71cf3bd0c4ef26b420a741483617"
let url = "https://github.com/synonymdev/ldk-node/releases/download/\(tag)/LDKNodeFFI.xcframework.zip"

let package = Package(
Expand Down
2 changes: 1 addition & 1 deletion bindings/kotlin/ldk-node-android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ android.useAndroidX=true
android.enableJetifier=true
kotlin.code.style=official
group=com.synonym
version=0.7.0-rc.55
version=0.7.0-rc.56
Binary file not shown.
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion bindings/kotlin/ldk-node-jvm/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx1536m
kotlin.code.style=official
group=com.synonym
version=0.7.0-rc.55
version=0.7.0-rc.56
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion bindings/python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "ldk_node"
version = "0.7.0-rc.55"
version = "0.7.0-rc.56"
authors = [
{ name="Elias Rohrer", email="dev@tnull.de" },
]
Expand Down
154 changes: 141 additions & 13 deletions crates/bdk-wallet-aggregate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,39 @@ where
Ok(wallet.start_sync_with_revealed_spks().build())
}

/// Build an incremental sync request including external lookahead scripts.
///
/// The wallet's configured lookahead determines how many unrevealed scripts are included.
pub fn wallet_incremental_sync_request_with_lookahead(
&self, key: &K,
) -> Result<SyncRequest<(KeychainKind, u32)>, Error> {
let wallet = self.wallets.get(key).ok_or(Error::WalletNotFound)?;
let mut request = wallet.start_sync_with_revealed_spks();
let external_lookahead = wallet.spk_index().lookahead();
if external_lookahead == 0 {
return Ok(request.build());
}

let start_index = match wallet.derivation_index(KeychainKind::External) {
Some(BIP32_MAX_NORMAL_INDEX) => return Ok(request.build()),
Some(index) => index + 1,
None => 0,
};
let end_index =
start_index.saturating_add(external_lookahead - 1).min(BIP32_MAX_NORMAL_INDEX);
let lookahead_spks = (start_index..=end_index)
.map(|index| {
wallet
.spk_index()
.spk_at_index(KeychainKind::External, index)
.map(|spk| ((KeychainKind::External, index), spk))
.ok_or(Error::WalletOperationFailed)
})
.collect::<Result<Vec<_>, _>>()?;
request = request.spks_with_indexes(lookahead_spks);
Ok(request.build())
}

/// Apply a chain update to the primary wallet.
///
/// Returns the wallet events and a list of all transaction IDs in the
Expand Down Expand Up @@ -1698,31 +1731,52 @@ mod tests {
P: WalletPersister,
P::Error: std::fmt::Debug,
{
PersistedWallet::create(
persister,
Wallet::create(
Bip84(xprv, KeychainKind::External),
Bip84(xprv, KeychainKind::Internal),
)
.network(Network::Regtest),
create_empty_wallet_from_xprv_with_lookahead(persister, xprv, None)
}

fn create_empty_wallet_from_xprv_with_lookahead<P>(
persister: &mut P, xprv: Xpriv, lookahead: Option<u32>,
) -> PersistedWallet<P>
where
P: WalletPersister,
P::Error: std::fmt::Debug,
{
let mut params = Wallet::create(
Bip84(xprv, KeychainKind::External),
Bip84(xprv, KeychainKind::Internal),
)
.unwrap()
.network(Network::Regtest);
if let Some(lookahead) = lookahead {
params = params.lookahead(lookahead);
}
PersistedWallet::create(persister, params).unwrap()
}

fn load_empty_wallet<P>(persister: &mut P) -> PersistedWallet<P>
where
P: WalletPersister,
P::Error: std::fmt::Debug,
{
load_empty_wallet_with_lookahead(persister, None)
}

fn load_empty_wallet_with_lookahead<P>(
persister: &mut P, lookahead: Option<u32>,
) -> PersistedWallet<P>
where
P: WalletPersister,
P::Error: std::fmt::Debug,
{
let xprv = test_xprv();
Wallet::load()
let mut params = Wallet::load()
.descriptor(KeychainKind::External, Some(Bip84(xprv, KeychainKind::External)))
.descriptor(KeychainKind::Internal, Some(Bip84(xprv, KeychainKind::Internal)))
.extract_keys()
.check_network(Network::Regtest)
.load_wallet(persister)
.unwrap()
.expect("wallet should have been persisted")
.check_network(Network::Regtest);
if let Some(lookahead) = lookahead {
params = params.lookahead(lookahead);
}
params.load_wallet(persister).unwrap().expect("wallet should have been persisted")
}

#[test]
Expand Down Expand Up @@ -2283,6 +2337,80 @@ mod tests {
assert_ne!(next.address, peeked.address);
}

#[test]
fn incremental_lookahead_does_not_advance_receive_index() {
let mut persister = NoopPersister;
let wallet =
create_empty_wallet_from_xprv_with_lookahead(&mut persister, test_xprv(), Some(3));
let mut aggregate = AggregateWallet::<u8, _>::new(wallet, persister, 0, vec![]);

let request = aggregate.wallet_incremental_sync_request_with_lookahead(&0).unwrap();
assert_eq!(request.progress().spks_remaining, 3);
assert_eq!(aggregate.new_address_info_for(&0).unwrap().index, 0);
}

#[test]
fn incremental_lookahead_rolls_forward_after_activity() {
let mut persister = NoopPersister;
let wallet =
create_empty_wallet_from_xprv_with_lookahead(&mut persister, test_xprv(), Some(3));
let mut aggregate = AggregateWallet::<u8, _>::new(wallet, persister, 0, vec![]);
let address = aggregate.address_info_for(&0, KeychainKind::External, 2).unwrap().address;
let transaction = Transaction {
version: bitcoin::transaction::Version::TWO,
lock_time: LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint { txid: Txid::from_byte_array([0x18; 32]), vout: 0 },
..Default::default()
}],
output: vec![TxOut {
value: Amount::from_sat(50_000),
script_pubkey: address.script_pubkey(),
}],
};

aggregate.apply_mempool_txs(vec![(transaction, 0)], vec![]).unwrap();

let request = aggregate.wallet_incremental_sync_request_with_lookahead(&0).unwrap();
assert_eq!(request.progress().spks_remaining, 6);
assert_eq!(aggregate.new_address_info_for(&0).unwrap().index, 3);
}

#[test]
fn incremental_lookahead_activity_persists_the_active_index() {
let mut create_persister = MemoryPersister::default();
let wallet = create_empty_wallet_from_xprv_with_lookahead(
&mut create_persister,
test_xprv(),
Some(3),
);
let aggregate_persister = create_persister.clone();
{
let mut aggregate =
AggregateWallet::<u8, _>::new(wallet, aggregate_persister, 0, vec![]);
let address =
aggregate.address_info_for(&0, KeychainKind::External, 2).unwrap().address;
let transaction = Transaction {
version: bitcoin::transaction::Version::TWO,
lock_time: LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint { txid: Txid::from_byte_array([0x19; 32]), vout: 0 },
..Default::default()
}],
output: vec![TxOut {
value: Amount::from_sat(50_000),
script_pubkey: address.script_pubkey(),
}],
};
aggregate.apply_mempool_txs(vec![(transaction, 0)], vec![]).unwrap();
}

let mut reload_persister = create_persister.clone();
let wallet = load_empty_wallet_with_lookahead(&mut reload_persister, Some(3));
let mut aggregate = AggregateWallet::<u8, _>::new(wallet, reload_persister, 0, vec![]);
assert_eq!(aggregate.new_address_info_for(&0).unwrap().index, 3);
}

#[test]
fn address_info_for_index_supports_internal_keychain_without_advancing_receive_index() {
let mut persister = NoopPersister;
Expand Down
Loading
Loading