Skip to content

Commit 436e4a3

Browse files
committed
Add probing service
Introduce a background probing service that periodically dispatches probes to improve the scorer's liquidity estimates. Includes two built-in strategies.
1 parent fae2746 commit 436e4a3

File tree

8 files changed

+1439
-7
lines changed

8 files changed

+1439
-7
lines changed

examples/bip157_wallet_sync.rs

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// This file is Copyright its original authors, visible in version control history.
2+
//
3+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5+
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
6+
// accordance with one or both of these licenses.
7+
8+
//! BIP157 wallet sync experiment.
9+
//!
10+
//! Creates a BDK wallet using the same setup as ldk-node (BIP84 / P2WPKH, derived from a BIP39
11+
//! mnemonic), then syncs it against a local signet Bitcoin node via compact block filters.
12+
//!
13+
//! # Prerequisites
14+
//!
15+
//! Your bitcoind must be started with compact filter support:
16+
//! -blockfilterindex=1 -peerblockfilters=1
17+
//!
18+
//! # Usage
19+
//!
20+
//! First run — generates a fresh mnemonic and prints the receive address:
21+
//! cargo run --example bip157_wallet_sync
22+
//!
23+
//! Subsequent runs — reuse the same wallet:
24+
//! MNEMONIC="word1 word2 ..." cargo run --example bip157_wallet_sync
25+
//!
26+
//! For faster sync, set a recent checkpoint (query your node first):
27+
//! bitcoin-cli -signet getblockcount
28+
//! bitcoin-cli -signet getbestblockhash
29+
//! Then update CHECKPOINT_HEIGHT / CHECKPOINT_HASH below.
30+
31+
use bdk_chain::BlockId;
32+
use bdk_wallet::template::Bip84;
33+
use bdk_wallet::{KeychainKind, Update, Wallet};
34+
use bip157::chain::{BlockHeaderChanges, ChainState};
35+
use bip157::{Builder, Client, Event, HeaderCheckpoint, Magic, Network, ScriptBuf, TrustedPeer};
36+
use bip39::Mnemonic;
37+
use bitcoin::bip32::Xpriv;
38+
use std::collections::HashSet;
39+
use std::path::PathBuf;
40+
41+
// Kyoto speaks the Bitcoin P2P protocol — no RPC username/password needed.
42+
// These are the known peers of the mutiny signet network.
43+
const REMOTE_PEER: &str = "45.79.52.207:38333";
44+
const LOCAL_PEER: &str = "127.0.0.1:38333";
45+
46+
/// Signet genesis block. Replace with a recent block for faster sync — any block before your
47+
/// wallet's first transaction will do.
48+
const CHECKPOINT_HEIGHT: u32 = 2919361;
49+
const CHECKPOINT_HASH: &str = "000000cdac0592cf9a50dcae2ca91854801d7bb6d12affa5a0cbb4b378fbab1d";
50+
51+
/// How many addresses to pre-derive and watch per keychain (external + internal).
52+
const LOOKAHEAD: u32 = 20;
53+
54+
#[tokio::main]
55+
async fn main() {
56+
// ── Wallet ────────────────────────────────────────────────────────────────
57+
58+
let mnemonic = match std::env::var("MNEMONIC") {
59+
Ok(s) => Mnemonic::parse(s).expect("Invalid mnemonic in MNEMONIC env var"),
60+
Err(_) => {
61+
let m = Mnemonic::generate(12).unwrap();
62+
println!("Generated mnemonic (save this for next run):\n {m}\n");
63+
m
64+
},
65+
};
66+
67+
let seed = mnemonic.to_seed("");
68+
69+
// Identical derivation to ldk-node (see builder.rs):
70+
// Xpriv::new_master → Bip84(xprv, External) + Bip84(xprv, Internal)
71+
let xprv = Xpriv::new_master(bitcoin::Network::Signet, &seed).unwrap();
72+
let descriptor = Bip84(xprv, KeychainKind::External);
73+
let change_descriptor = Bip84(xprv, KeychainKind::Internal);
74+
75+
let mut wallet = Wallet::create(descriptor, change_descriptor)
76+
.network(bitcoin::Network::Signet)
77+
.create_wallet_no_persist()
78+
.unwrap();
79+
80+
// Reveal the first receive address and collect scripts to watch.
81+
let receive_addr = wallet.reveal_next_address(KeychainKind::External);
82+
println!("Receive address: {}\n", receive_addr.address);
83+
println!("Send some signet BTC to it, then this example will detect it.\n");
84+
85+
let scripts: HashSet<ScriptBuf> = (0..LOOKAHEAD)
86+
.flat_map(|i| {
87+
[
88+
wallet.peek_address(KeychainKind::External, i).address.script_pubkey(),
89+
wallet.peek_address(KeychainKind::Internal, i).address.script_pubkey(),
90+
]
91+
})
92+
.collect();
93+
94+
println!("Watching {} scripts across {} lookahead addresses.", scripts.len(), LOOKAHEAD);
95+
96+
// ── Kyoto ─────────────────────────────────────────────────────────────────
97+
98+
// Mutiny signet uses a custom signetchallenge; its P2P magic differs from the default signet.
99+
// Computed as SHA256d("bitcoin signet:" || compact_size(len) || challenge)[0:4]
100+
// challenge = 512102f7561d208dd9ae99bf497273e16f389bdbd6c4742ddb8e6b216e64fa2928ad8f51ae
101+
let mutiny_magic = Magic::from_bytes([0xa5, 0xdf, 0x2d, 0xcb]);
102+
103+
let remote_peer: TrustedPeer = REMOTE_PEER.parse::<std::net::SocketAddr>().unwrap().into();
104+
let local_peer: TrustedPeer = LOCAL_PEER.parse::<std::net::SocketAddr>().unwrap().into();
105+
106+
let checkpoint = HeaderCheckpoint::new(CHECKPOINT_HEIGHT, CHECKPOINT_HASH.parse().unwrap());
107+
108+
// data_dir persists the address book so that on subsequent runs kyoto does not fall back to
109+
// DNS seeds (which return default-signet peers, not mutiny-signet peers).
110+
let data_dir = PathBuf::from("/tmp/bip157-mutiny-signet");
111+
112+
let (node, client) = Builder::new(Network::Signet)
113+
.add_peer(remote_peer)
114+
.add_peer(local_peer)
115+
.data_dir(data_dir)
116+
.chain_state(ChainState::Checkpoint(checkpoint))
117+
.custom_magic(mutiny_magic)
118+
.build();
119+
120+
tokio::task::spawn(async move { node.run().await });
121+
122+
let Client { requester, mut info_rx, mut warn_rx, mut event_rx } = client;
123+
124+
// ── Event loop ────────────────────────────────────────────────────────────
125+
126+
loop {
127+
tokio::select! {
128+
event = event_rx.recv() => {
129+
let Some(event) = event else { break };
130+
match event {
131+
// Advance BDK's local chain tip for every connected header, so
132+
// confirmation depths are correct even for non-matching blocks.
133+
Event::ChainUpdate(BlockHeaderChanges::Connected(header)) => {
134+
let cp = wallet.latest_checkpoint().insert(BlockId {
135+
height: header.height,
136+
hash: header.block_hash(),
137+
});
138+
wallet.apply_update(Update { chain: Some(cp), ..Default::default() }).unwrap();
139+
},
140+
141+
// Check each block's compact filter against our watched scripts.
142+
Event::IndexedFilter(filter) => {
143+
if filter.contains_any(scripts.iter()) {
144+
println!("Filter match at height {}! Downloading block...",filter.height());
145+
match requester.get_block(filter.block_hash()).await {
146+
Ok(indexed_block) => {
147+
wallet.apply_block_events(&indexed_block.block, indexed_block.height).unwrap();
148+
let b = wallet.balance();
149+
println!( " Balance: confirmed={} trusted_pending={}", b.confirmed, b.trusted_pending,);
150+
},
151+
Err(e) => eprintln!(" Failed to fetch block: {e:?}"),
152+
}
153+
}
154+
},
155+
156+
// Caught up to the current tip — print balance and keep watching
157+
// for new blocks arriving on the network.
158+
Event::FiltersSynced(update) => {
159+
let b = wallet.balance();
160+
println!(
161+
"\nCaught up to height {}. Balance: confirmed={} trusted_pending={}",
162+
update.tip().height,
163+
b.confirmed,
164+
b.trusted_pending,
165+
);
166+
println!("Watching for new blocks... (Ctrl+C to stop)\n");
167+
},
168+
169+
_ => {},
170+
}
171+
},
172+
Some(info) = info_rx.recv() => println!("[kyoto] {info}"),
173+
Some(warn) = warn_rx.recv() => eprintln!("[kyoto] {warn}"),
174+
}
175+
}
176+
177+
let _ = requester.shutdown();
178+
}

0 commit comments

Comments
 (0)