Skip to content
Draft
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
26 changes: 13 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,17 @@ default = []
#lightning-liquidity = { version = "0.2.0", features = ["std"] }
#lightning-macros = { version = "0.2.0" }

lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "7fe3268475551b0664d315bfbc860416ca8fc774", features = ["std"] }
lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "7fe3268475551b0664d315bfbc860416ca8fc774" }
lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "7fe3268475551b0664d315bfbc860416ca8fc774", features = ["std"] }
lightning-net-tokio = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "7fe3268475551b0664d315bfbc860416ca8fc774" }
lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "7fe3268475551b0664d315bfbc860416ca8fc774", features = ["tokio"] }
lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "7fe3268475551b0664d315bfbc860416ca8fc774" }
lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "7fe3268475551b0664d315bfbc860416ca8fc774" }
lightning-block-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "7fe3268475551b0664d315bfbc860416ca8fc774", features = ["rest-client", "rpc-client", "tokio"] }
lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "7fe3268475551b0664d315bfbc860416ca8fc774", features = ["esplora-async-https", "time", "electrum-rustls-ring"] }
lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "7fe3268475551b0664d315bfbc860416ca8fc774", features = ["std"] }
lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "7fe3268475551b0664d315bfbc860416ca8fc774" }
lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9df02807c3a921657ca32029ae2054b9def86283", features = ["std"] }
lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9df02807c3a921657ca32029ae2054b9def86283" }
lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9df02807c3a921657ca32029ae2054b9def86283", features = ["std"] }
lightning-net-tokio = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9df02807c3a921657ca32029ae2054b9def86283" }
lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9df02807c3a921657ca32029ae2054b9def86283", features = ["tokio"] }
lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9df02807c3a921657ca32029ae2054b9def86283" }
lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9df02807c3a921657ca32029ae2054b9def86283" }
lightning-block-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9df02807c3a921657ca32029ae2054b9def86283", features = ["rest-client", "rpc-client", "tokio"] }
lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9df02807c3a921657ca32029ae2054b9def86283", features = ["esplora-async-https", "time", "electrum-rustls-ring"] }
lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9df02807c3a921657ca32029ae2054b9def86283", features = ["std"] }
lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9df02807c3a921657ca32029ae2054b9def86283" }

bdk_chain = { version = "0.23.0", default-features = false, features = ["std"] }
bdk_esplora = { version = "0.22.0", default-features = false, features = ["async-https-rustls", "tokio"]}
Expand Down Expand Up @@ -78,13 +78,13 @@ log = { version = "0.4.22", default-features = false, features = ["std"]}
vss-client = { package = "vss-client-ng", version = "0.4" }
prost = { version = "0.11.6", default-features = false}
#bitcoin-payment-instructions = { version = "0.6" }
bitcoin-payment-instructions = { git = "https://github.com/tnull/bitcoin-payment-instructions", rev = "b9f9991b42e9d71b3ca966818a93b158cf8f6c40" }
bitcoin-payment-instructions = { git = "https://github.com/tnull/bitcoin-payment-instructions", rev = "acdb0e203b93f21645d42d4edf5edf063e16b1a5" }

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["winbase"] }

[dev-dependencies]
lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "7fe3268475551b0664d315bfbc860416ca8fc774", features = ["std", "_test_utils"] }
lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9df02807c3a921657ca32029ae2054b9def86283", features = ["std", "_test_utils"] }
proptest = "1.0.0"
regex = "1.5.6"
criterion = { version = "0.7.0", features = ["async_tokio"] }
Expand Down
1 change: 1 addition & 0 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ interface Builder {
void set_node_alias(string node_alias);
[Throws=BuildError]
void set_async_payments_role(AsyncPaymentsRole? role);
void set_wallet_recovery_mode();
[Throws=BuildError]
Node build(NodeEntropy node_entropy);
[Throws=BuildError]
Expand Down
57 changes: 42 additions & 15 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ pub struct NodeBuilder {
async_payments_role: Option<AsyncPaymentsRole>,
runtime_handle: Option<tokio::runtime::Handle>,
pathfinding_scores_sync_config: Option<PathfindingScoresSyncConfig>,
recovery_mode: bool,
}

impl NodeBuilder {
Expand All @@ -261,6 +262,7 @@ impl NodeBuilder {
let log_writer_config = None;
let runtime_handle = None;
let pathfinding_scores_sync_config = None;
let recovery_mode = false;
Self {
config,
chain_data_source_config,
Expand All @@ -270,6 +272,7 @@ impl NodeBuilder {
runtime_handle,
async_payments_role: None,
pathfinding_scores_sync_config,
recovery_mode,
}
}

Expand Down Expand Up @@ -544,6 +547,16 @@ impl NodeBuilder {
Ok(self)
}

/// Configures the [`Node`] to resync chain data from genesis on first startup, recovering any
/// historical wallet funds.
///
/// This should only be set on first startup when importing an older wallet from a previously
/// used [`NodeEntropy`].
pub fn set_wallet_recovery_mode(&mut self) -> &mut Self {
self.recovery_mode = true;
self
}

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self, node_entropy: NodeEntropy) -> Result<Node, BuildError> {
Expand Down Expand Up @@ -679,6 +692,7 @@ impl NodeBuilder {
self.liquidity_source_config.as_ref(),
self.pathfinding_scores_sync_config.as_ref(),
self.async_payments_role,
self.recovery_mode,
seed_bytes,
runtime,
logger,
Expand Down Expand Up @@ -919,6 +933,15 @@ impl ArcedNodeBuilder {
self.inner.write().unwrap().set_async_payments_role(role).map(|_| ())
}

/// Configures the [`Node`] to resync chain data from genesis on first startup, recovering any
/// historical wallet funds.
///
/// This should only be set on first startup when importing an older wallet from a previously
/// used [`NodeEntropy`].
pub fn set_wallet_recovery_mode(&self) {
self.inner.write().unwrap().set_wallet_recovery_mode();
}

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self, node_entropy: Arc<NodeEntropy>) -> Result<Arc<Node>, BuildError> {
Expand Down Expand Up @@ -1033,8 +1056,8 @@ fn build_with_store_internal(
gossip_source_config: Option<&GossipSourceConfig>,
liquidity_source_config: Option<&LiquiditySourceConfig>,
pathfinding_scores_sync_config: Option<&PathfindingScoresSyncConfig>,
async_payments_role: Option<AsyncPaymentsRole>, seed_bytes: [u8; 64], runtime: Arc<Runtime>,
logger: Arc<Logger>, kv_store: Arc<DynStore>,
async_payments_role: Option<AsyncPaymentsRole>, recovery_mode: bool, seed_bytes: [u8; 64],
runtime: Arc<Runtime>, logger: Arc<Logger>, kv_store: Arc<DynStore>,
) -> Result<Node, BuildError> {
optionally_install_rustls_cryptoprovider();

Expand Down Expand Up @@ -1230,19 +1253,23 @@ fn build_with_store_internal(
BuildError::WalletSetupFailed
})?;

if let Some(best_block) = chain_tip_opt {
// Insert the first checkpoint if we have it, to avoid resyncing from genesis.
// TODO: Use a proper wallet birthday once BDK supports it.
let mut latest_checkpoint = wallet.latest_checkpoint();
let block_id =
bdk_chain::BlockId { height: best_block.height, hash: best_block.block_hash };
latest_checkpoint = latest_checkpoint.insert(block_id);
let update =
bdk_wallet::Update { chain: Some(latest_checkpoint), ..Default::default() };
wallet.apply_update(update).map_err(|e| {
log_error!(logger, "Failed to apply checkpoint during wallet setup: {}", e);
BuildError::WalletSetupFailed
})?;
if !recovery_mode {
if let Some(best_block) = chain_tip_opt {
// Insert the first checkpoint if we have it, to avoid resyncing from genesis.
// TODO: Use a proper wallet birthday once BDK supports it.
let mut latest_checkpoint = wallet.latest_checkpoint();
let block_id = bdk_chain::BlockId {
height: best_block.height,
hash: best_block.block_hash,
};
latest_checkpoint = latest_checkpoint.insert(block_id);
let update =
bdk_wallet::Update { chain: Some(latest_checkpoint), ..Default::default() };
wallet.apply_update(update).map_err(|e| {
log_error!(logger, "Failed to apply checkpoint during wallet setup: {}", e);
BuildError::WalletSetupFailed
})?;
}
}
wallet
},
Expand Down
133 changes: 133 additions & 0 deletions src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,16 +152,47 @@ impl Wallet {
pub(crate) fn apply_mempool_txs(
&self, unconfirmed_txs: Vec<(Transaction, u64)>, evicted_txids: Vec<(Txid, u64)>,
) -> Result<(), Error> {
if unconfirmed_txs.is_empty() {
return Ok(());
}

let mut locked_wallet = self.inner.lock().unwrap();

let chain_tip1 = locked_wallet.latest_checkpoint().block_id();
let wallet_txs1 = locked_wallet
.transactions()
.map(|wtx| (wtx.tx_node.txid, (wtx.tx_node.tx.clone(), wtx.chain_position)))
.collect::<std::collections::BTreeMap<
Txid,
(Arc<Transaction>, bdk_chain::ChainPosition<bdk_chain::ConfirmationBlockTime>),
>>();

locked_wallet.apply_unconfirmed_txs(unconfirmed_txs);
locked_wallet.apply_evicted_txs(evicted_txids);

let chain_tip2 = locked_wallet.latest_checkpoint().block_id();
let wallet_txs2 = locked_wallet
.transactions()
.map(|wtx| (wtx.tx_node.txid, (wtx.tx_node.tx.clone(), wtx.chain_position)))
.collect::<std::collections::BTreeMap<
Txid,
(Arc<Transaction>, bdk_chain::ChainPosition<bdk_chain::ConfirmationBlockTime>),
>>();

let events =
wallet_events(&mut *locked_wallet, chain_tip1, chain_tip2, wallet_txs1, wallet_txs2);

let mut locked_persister = self.persister.lock().unwrap();
locked_wallet.persist(&mut locked_persister).map_err(|e| {
log_error!(self.logger, "Failed to persist wallet: {}", e);
Error::PersistenceFailed
})?;

self.update_payment_store(&mut *locked_wallet, events).map_err(|e| {
log_error!(self.logger, "Failed to update payment store: {}", e);
Error::PersistenceFailed
})?;

Ok(())
}

Expand Down Expand Up @@ -1212,3 +1243,105 @@ impl ChangeDestinationSource for WalletKeysManager {
}
}
}

// FIXME/TODO: This is copied-over from bdk_wallet and only used to generate `WalletEvent`s after
// applying mempool transactions. We should drop this when BDK offers to generate events for
// mempool transactions natively.
pub(crate) fn wallet_events(
wallet: &mut bdk_wallet::Wallet, chain_tip1: bdk_chain::BlockId,
chain_tip2: bdk_chain::BlockId,
wallet_txs1: std::collections::BTreeMap<
Txid,
(Arc<Transaction>, bdk_chain::ChainPosition<bdk_chain::ConfirmationBlockTime>),
>,
wallet_txs2: std::collections::BTreeMap<
Txid,
(Arc<Transaction>, bdk_chain::ChainPosition<bdk_chain::ConfirmationBlockTime>),
>,
) -> Vec<WalletEvent> {
let mut events: Vec<WalletEvent> = Vec::new();

if chain_tip1 != chain_tip2 {
events.push(WalletEvent::ChainTipChanged { old_tip: chain_tip1, new_tip: chain_tip2 });
}

wallet_txs2.iter().for_each(|(txid2, (tx2, cp2))| {
if let Some((tx1, cp1)) = wallet_txs1.get(txid2) {
assert_eq!(tx1.compute_txid(), *txid2);
match (cp1, cp2) {
(
bdk_chain::ChainPosition::Unconfirmed { .. },
bdk_chain::ChainPosition::Confirmed { anchor, .. },
) => {
events.push(WalletEvent::TxConfirmed {
txid: *txid2,
tx: tx2.clone(),
block_time: *anchor,
old_block_time: None,
});
},
(
bdk_chain::ChainPosition::Confirmed { anchor, .. },
bdk_chain::ChainPosition::Unconfirmed { .. },
) => {
events.push(WalletEvent::TxUnconfirmed {
txid: *txid2,
tx: tx2.clone(),
old_block_time: Some(*anchor),
});
},
(
bdk_chain::ChainPosition::Confirmed { anchor: anchor1, .. },
bdk_chain::ChainPosition::Confirmed { anchor: anchor2, .. },
) => {
if *anchor1 != *anchor2 {
events.push(WalletEvent::TxConfirmed {
txid: *txid2,
tx: tx2.clone(),
block_time: *anchor2,
old_block_time: Some(*anchor1),
});
}
},
(
bdk_chain::ChainPosition::Unconfirmed { .. },
bdk_chain::ChainPosition::Unconfirmed { .. },
) => {
// do nothing if still unconfirmed
},
}
} else {
match cp2 {
bdk_chain::ChainPosition::Confirmed { anchor, .. } => {
events.push(WalletEvent::TxConfirmed {
txid: *txid2,
tx: tx2.clone(),
block_time: *anchor,
old_block_time: None,
});
},
bdk_chain::ChainPosition::Unconfirmed { .. } => {
events.push(WalletEvent::TxUnconfirmed {
txid: *txid2,
tx: tx2.clone(),
old_block_time: None,
});
},
}
}
});

// find tx that are no longer canonical
wallet_txs1.iter().for_each(|(txid1, (tx1, _))| {
if !wallet_txs2.contains_key(txid1) {
let conflicts = wallet.tx_graph().direct_conflicts(tx1).collect::<Vec<_>>();
if !conflicts.is_empty() {
events.push(WalletEvent::TxReplaced { txid: *txid1, tx: tx1.clone(), conflicts });
} else {
events.push(WalletEvent::TxDropped { txid: *txid1, tx: tx1.clone() });
}
}
});

events
}
Loading
Loading