Skip to content

Commit bcb9ca6

Browse files
testing the new potential kyoto client
1 parent 87dcdd3 commit bcb9ca6

File tree

7 files changed

+154
-68
lines changed

7 files changed

+154
-68
lines changed

bdk-android/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
plugins {
22
id("com.android.library").version("8.13.2").apply(false)
3-
id("org.jetbrains.kotlin.android").version("2.1.10").apply(false)
3+
id("org.jetbrains.kotlin.android").version("2.3.0").apply(false)
44
id("org.jetbrains.dokka").version("2.1.0").apply(false)
55
id("com.vanniktech.maven.publish").version("0.36.0").apply(false)
66
}

bdk-android/lib/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ dependencies {
5656
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
5757
androidTestImplementation("org.jetbrains.kotlin:kotlin-test:1.6.10")
5858
androidTestImplementation("org.jetbrains.kotlin:kotlin-test-junit:1.6.10")
59+
androidTestImplementation("org.kotlinbitcointools:regtest-toolbox:0.2.0")
5960
}
6061

6162
mavenPublishing {
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package org.bitcoindevkit
2+
3+
import kotlin.test.Test
4+
import androidx.test.ext.junit.runners.AndroidJUnit4
5+
import androidx.test.platform.app.InstrumentationRegistry
6+
import kotlinx.coroutines.delay
7+
import kotlinx.coroutines.launch
8+
import kotlinx.coroutines.runBlocking
9+
import org.junit.runner.RunWith
10+
// import org.kotlinbitcointools.regtesttoolbox.regenv.RegEnv
11+
import java.io.File
12+
import kotlin.time.Duration.Companion.seconds
13+
14+
@RunWith(AndroidJUnit4::class)
15+
class CbfSyncTest {
16+
private val conn: Persister = Persister.newInMemory()
17+
18+
private val kyotoDataDir: String by lazy {
19+
val context = InstrumentationRegistry.getInstrumentation().targetContext
20+
val dir = File(context.filesDir, "kyoto_test_data")
21+
dir.mkdirs()
22+
dir.absolutePath
23+
}
24+
25+
@Test
26+
fun syncWithKyotoClient() {
27+
runBlocking {
28+
// println("Hello, Compact Block Filters!")
29+
// val regtestEnv = RegEnv.connectTo(walletName = "faucet", username = "regtest", password = "password")
30+
31+
val wallet: Wallet = Wallet.createSingle(
32+
descriptor = BIP86_DESCRIPTOR,
33+
network = Network.REGTEST,
34+
persister = conn
35+
)
36+
val newAddress = wallet.revealNextAddress(KeychainKind.EXTERNAL).address
37+
println("New address: $newAddress")
38+
delay(2.seconds)
39+
40+
// regtestEnv.send(newAddress.toString(), 0.12345678, 2.0)
41+
// regtestEnv.mine(2)
42+
43+
val ip: IpAddress = IpAddress.fromIpv4(10u, 0u, 2u, 2u)
44+
val peer1: Peer = Peer(ip, 18444u, false)
45+
val peers: List<Peer> = listOf(peer1)
46+
val node = CbfBuilder()
47+
.peers(peers)
48+
.connections(1u)
49+
.scanType(ScanType.Sync)
50+
.dataDir(kyotoDataDir)
51+
.build(wallet)
52+
53+
println("Test is complete")
54+
val client = node.start()
55+
56+
val warningJob = launch {
57+
try {
58+
while (true) println("Warning: ${client.nextWarning()}")
59+
} catch (e: CbfException) { println("Warning channel closed: $e") }
60+
}
61+
val infoJob = launch {
62+
try {
63+
while (true) println("Info: ${client.nextInfo()}")
64+
} catch (e: CbfException) { println("Info channel closed: $e") }
65+
}
66+
67+
val update: Update = client.update()
68+
delay(8.seconds)
69+
wallet.applyUpdate(update)
70+
71+
val balance = wallet.balance().total.toSat()
72+
assert(balance > 0uL)
73+
74+
warningJob.cancel()
75+
infoJob.cancel()
76+
client.shutdown()
77+
}
78+
}
79+
}

bdk-ffi/Cargo.lock

Lines changed: 2 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bdk-ffi/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ path = "uniffi-bindgen.rs"
1818
bdk_wallet = { version = "2.3.0", features = ["all-keys", "keys-bip39", "rusqlite"] }
1919
bdk_esplora = { version = "0.22.1", default-features = false, features = ["std", "blocking", "blocking-https-rustls"] }
2020
bdk_electrum = { version = "0.23.2", default-features = false, features = ["use-rustls-ring"] }
21-
bdk_kyoto = { version = "0.15.4" }
21+
bdk_kyoto = { git = "https://github.com/rustaceanrob/bdk-kyoto.git", branch = "26-2-24-typestate" }
22+
#bdk_kyoto = { version = "0.15.4" }
2223

2324
uniffi = { version = "=0.30.0", features = ["cli"]}
2425
thiserror = "2.0.17"

bdk-ffi/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,8 @@ pub enum TxidParseError {
797797
pub enum CbfError {
798798
#[error("the node is no longer running")]
799799
NodeStopped,
800+
#[error("the node has already been started")]
801+
AlreadyStarted,
800802
}
801803

802804
// ------------------------------------------------------------------------

bdk-ffi/src/kyoto.rs

Lines changed: 67 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
use bdk_kyoto::bip157::lookup_host;
1+
use bdk_kyoto::bip157::{lookup_host};
22
use bdk_kyoto::bip157::tokio;
33
use bdk_kyoto::bip157::AddrV2;
44
use bdk_kyoto::bip157::Network;
5-
use bdk_kyoto::bip157::Node;
65
use bdk_kyoto::bip157::ServiceFlags;
76
use bdk_kyoto::builder::Builder as BDKCbfBuilder;
87
use bdk_kyoto::builder::BuilderExt;
9-
use bdk_kyoto::HeaderCheckpoint;
10-
use bdk_kyoto::LightClient as BDKLightClient;
8+
use bdk_kyoto::{HeaderCheckpoint, Idle, LightClient};
119
use bdk_kyoto::Receiver;
1210
use bdk_kyoto::RejectReason;
1311
use bdk_kyoto::Requester;
@@ -37,51 +35,37 @@ const CWD_PATH: &str = ".";
3735
const TCP_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(2);
3836
const MESSAGE_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5);
3937

40-
/// Receive a [`CbfClient`] and [`CbfNode`].
41-
#[derive(Debug, uniffi::Record)]
42-
pub struct CbfComponents {
43-
/// Publish events to the node, like broadcasting transactions or adding scripts.
44-
pub client: Arc<CbfClient>,
45-
/// The node to run and fetch transactions for a [`Wallet`].
46-
pub node: Arc<CbfNode>,
38+
/// A compact block filters node that has been configured but not yet started.
39+
///
40+
/// Built via [`CbfBuilder`]. Call [`CbfNode::start`] to prepare and spawn the node, receiving a
41+
/// [`CbfClient`] to interact with the running node.
42+
///
43+
/// `CbfNode::start` may only be called once. Calling it a second time returns
44+
/// [`CbfError::AlreadyStarted`].
45+
#[derive(uniffi::Object)]
46+
pub struct CbfNode {
47+
client: std::sync::Mutex<Option<LightClient<Idle>>>,
4748
}
4849

49-
/// A [`CbfClient`] handles wallet updates from a [`CbfNode`].
50-
#[derive(Debug, uniffi::Object)]
50+
/// Interact with a running compact block filters node.
51+
///
52+
/// Obtained by calling [`CbfNode::start`]. Provides access to three independent
53+
/// channels — each message is consumed by exactly one caller, so dedicate a single
54+
/// task or thread to draining each channel:
55+
///
56+
/// * [`CbfClient::next_info`] — progress and connection events.
57+
/// * [`CbfClient::next_warning`] — non-fatal issues the node encountered.
58+
/// * [`CbfClient::update`] — wallet updates ready to be applied to a [`Wallet`].
59+
///
60+
/// Transactions can be broadcast and peers added at any time via the remaining methods.
61+
#[derive(uniffi::Object)]
5162
pub struct CbfClient {
52-
sender: Arc<Requester>,
63+
sender: Requester,
5364
info_rx: Mutex<Receiver<bdk_kyoto::Info>>,
5465
warning_rx: Mutex<UnboundedReceiver<bdk_kyoto::Warning>>,
5566
update_rx: Mutex<UpdateSubscriber>,
5667
}
5768

58-
/// A [`CbfNode`] gathers transactions for a [`Wallet`].
59-
/// To receive [`Update`] for [`Wallet`], refer to the
60-
/// [`CbfClient`]. The [`CbfNode`] will run until instructed
61-
/// to stop.
62-
#[derive(Debug, uniffi::Object)]
63-
pub struct CbfNode {
64-
node: std::sync::Mutex<Option<Node>>,
65-
}
66-
67-
#[uniffi::export]
68-
impl CbfNode {
69-
/// Start the node on a detached OS thread and immediately return.
70-
pub fn run(self: Arc<Self>) {
71-
let mut lock = self.node.lock().unwrap();
72-
let node = lock.take().expect("cannot call run more than once");
73-
std::thread::spawn(|| {
74-
tokio::runtime::Builder::new_multi_thread()
75-
.enable_all()
76-
.build()
77-
.unwrap()
78-
.block_on(async move {
79-
let _ = node.run().await;
80-
})
81-
});
82-
}
83-
}
84-
8569
/// Build a BIP 157/158 light client to fetch transactions for a `Wallet`.
8670
///
8771
/// Options:
@@ -178,7 +162,7 @@ impl CbfBuilder {
178162
}
179163

180164
/// Construct a [`CbfComponents`] for a [`Wallet`].
181-
pub fn build(&self, wallet: &Wallet) -> CbfComponents {
165+
pub fn build(&self, wallet: &Wallet) -> CbfNode {
182166
let wallet = wallet.get_wallet();
183167

184168
let mut trusted_peers = Vec::new();
@@ -241,31 +225,51 @@ impl CbfBuilder {
241225
builder = builder.socks5_proxy((addr, port));
242226
}
243227

244-
let BDKLightClient {
245-
requester,
246-
info_subscriber,
247-
warning_subscriber,
248-
update_subscriber,
249-
node,
250-
} = builder
228+
let light_client_idle = builder
251229
.build_with_wallet(&wallet, scan_type)
252230
.expect("networks match by definition");
253231

254-
let node = CbfNode {
255-
node: std::sync::Mutex::new(Some(node)),
256-
};
232+
CbfNode {
233+
client: std::sync::Mutex::new(Some(light_client_idle)),
234+
}
235+
}
236+
}
257237

258-
let client = CbfClient {
259-
sender: Arc::new(requester),
260-
info_rx: Mutex::new(info_subscriber),
261-
warning_rx: Mutex::new(warning_subscriber),
262-
update_rx: Mutex::new(update_subscriber),
263-
};
238+
#[uniffi::export]
239+
impl CbfNode {
240+
/// Subscribe to log and update channels, then spawn the node on a background thread.
241+
///
242+
/// Returns a [`CbfClient`] that can be used to receive wallet updates, info and warning
243+
/// messages, and to broadcast transactions.
244+
///
245+
/// This method may only be called once. A second call returns [`CbfError::AlreadyStarted`].
246+
pub fn start(&self) -> Result<Arc<CbfClient>, CbfError> {
247+
let light_client = self
248+
.client
249+
.lock()
250+
.unwrap()
251+
.take()
252+
.ok_or(CbfError::AlreadyStarted)?;
264253

265-
CbfComponents {
266-
client: Arc::new(client),
267-
node: Arc::new(node),
268-
}
254+
let (subscribed, logging, updates) = light_client.subscribe();
255+
let (active, node) = subscribed.managed_start();
256+
257+
std::thread::spawn(|| {
258+
tokio::runtime::Builder::new_multi_thread()
259+
.enable_all()
260+
.build()
261+
.unwrap()
262+
.block_on(async move {
263+
let _ = node.run().await;
264+
})
265+
});
266+
267+
Ok(Arc::new(CbfClient {
268+
sender: active.requester(),
269+
info_rx: Mutex::new(logging.info_subscriber),
270+
warning_rx: Mutex::new(logging.warning_subscriber),
271+
update_rx: Mutex::new(updates),
272+
}))
269273
}
270274
}
271275

@@ -340,7 +344,7 @@ impl CbfClient {
340344
/// Add another [`Peer`] to attempt a connection with.
341345
pub fn connect(&self, peer: Peer) -> Result<(), CbfError> {
342346
self.sender
343-
.add_peer(peer)
347+
.add_peer(TrustedPeer::from(peer))
344348
.map_err(|_| CbfError::NodeStopped)
345349
}
346350

0 commit comments

Comments
 (0)