diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/BlockchainConfig.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/BlockchainConfig.kt new file mode 100644 index 0000000..e604975 --- /dev/null +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/BlockchainConfig.kt @@ -0,0 +1,25 @@ +package org.bitcoindevkit.bdk + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +abstract class BlockchainConfig() : LibBase() { + private val log: Logger = LoggerFactory.getLogger(BlockchainConfig::class.java) + abstract val blockchainConfigT: LibJna.BlockchainConfig_t + + protected fun finalize() { + libJna.free_blockchain_config(blockchainConfigT) + log.debug("$blockchainConfigT freed") + } +} + +class ElectrumConfig( + url: String, + socks5: String?, + retry: Short, + timeout: Short +) : BlockchainConfig() { + + private val log: Logger = LoggerFactory.getLogger(ElectrumConfig::class.java) + override val blockchainConfigT = libJna.new_electrum_config(url, socks5, retry, timeout) +} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/DatabaseConfig.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/DatabaseConfig.kt new file mode 100644 index 0000000..317965b --- /dev/null +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/DatabaseConfig.kt @@ -0,0 +1,26 @@ +package org.bitcoindevkit.bdk + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +abstract class DatabaseConfig() : LibBase() { + private val log: Logger = LoggerFactory.getLogger(DatabaseConfig::class.java) + abstract val databaseConfigT: LibJna.DatabaseConfig_t + + protected fun finalize() { + libJna.free_database_config(databaseConfigT) + log.debug("$databaseConfigT freed") + } +} + +class MemoryConfig() : DatabaseConfig() { + + private val log: Logger = LoggerFactory.getLogger(MemoryConfig::class.java) + override val databaseConfigT = libJna.new_memory_config() +} + +class SledConfig(path: String, treeName:String) : DatabaseConfig() { + + private val log: Logger = LoggerFactory.getLogger(SledConfig::class.java) + override val databaseConfigT = libJna.new_sled_config(path, treeName) +} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt index c1de293..6e9d04b 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt @@ -48,6 +48,18 @@ interface LibJna : Library { // WalletRef_t * wallet_ref); fun free_wallet_ref(wallet_ref: WalletRef_t) + // typedef struct BlockchainConfig BlockchainConfig_t; + class BlockchainConfig_t : PointerType { + constructor() : super() + constructor(pointer: Pointer) : super(pointer) + } + + // typedef struct DatabaseConfig DatabaseConfig_t; + class DatabaseConfig_t : PointerType { + constructor() : super() + constructor(pointer: Pointer) : super(pointer) + } + // typedef struct WalletResult WalletResult_t; class WalletResult_t : PointerType { constructor() : super() @@ -55,13 +67,15 @@ interface LibJna : Library { } // WalletResult_t * new_wallet_result ( - // char const * name, // char const * descriptor, - // char const * change_descriptor); + // char const * change_descriptor, + // BlockchainConfig_t const * blockchain_config, + // DatabaseConfig_t const * database_config); fun new_wallet_result( - name: String, descriptor: String, - changeDescriptor: String? + changeDescriptor: String?, + blockchainConfig: BlockchainConfig_t, + databaseConfig: DatabaseConfig_t, ): WalletResult_t // char * get_wallet_err ( @@ -87,4 +101,32 @@ interface LibJna : Library { // void free_string ( // char * string); fun free_string(string: Pointer?) + + // BlockchainConfig_t * new_electrum_config ( + // char const * url, + // char const * socks5, + // int16_t retry, + // int16_t timeout); + fun new_electrum_config( + url: String, + socks5: String?, + retry: Short, + timeout: Short + ): BlockchainConfig_t + + // void free_blockchain_config ( + // BlockchainConfig_t * blockchain_config); + fun free_blockchain_config( blockchain_config: BlockchainConfig_t) + + // DatabaseConfig_t * new_memory_config (void); + fun new_memory_config(): DatabaseConfig_t + + // DatabaseConfig_t * new_sled_config ( + // char const * path, + // char const * tree_name); + fun new_sled_config(path: String, tree_name: String): DatabaseConfig_t + + // void free_database_config ( + // DatabaseConfig_t * database_config); + fun free_database_config( database_config: DatabaseConfig_t) } diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt index 7b62e24..e2a8e12 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt @@ -4,15 +4,16 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory class Wallet constructor( - name: String, descriptor: String, changeDescriptor: String?, + blockchainConfig: BlockchainConfig, + databaseConfig: DatabaseConfig, ) : LibBase() { val log: Logger = LoggerFactory.getLogger(Wallet::class.java) private val walletResult = - WalletResult(libJna.new_wallet_result(name, descriptor, changeDescriptor)) + WalletResult(libJna.new_wallet_result(descriptor, changeDescriptor, blockchainConfig.blockchainConfigT, databaseConfig.databaseConfigT)) private val walletRefT = walletResult.value() fun sync() { diff --git a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt index 70e70ec..60d02de 100644 --- a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt @@ -22,17 +22,21 @@ abstract class LibTest : LibBase() { @Test fun walletResultError() { + val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) + val databaseConfig = MemoryConfig() val jnaException = assertThrows(JnaException::class.java) { - Wallet("bad", "bad", "bad") + Wallet("bad", "bad", blockchainConfig, databaseConfig) } assertEquals(jnaException.err, JnaError.Descriptor) } @Test fun walletResultFinalize() { + val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) + val databaseConfig = MemoryConfig() val names = listOf("one", "two", "three") names.map { - val wallet = Wallet(it, desc, change) + val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) assertNotNull(wallet) } System.gc() @@ -41,14 +45,18 @@ abstract class LibTest : LibBase() { @Test fun walletSync() { - val wallet = Wallet(name, desc, change) + val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) + val databaseConfig = MemoryConfig() + val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) assertNotNull(wallet) wallet.sync() } @Test fun walletNewAddress() { - val wallet = Wallet(name, desc, change) + val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) + val databaseConfig = MemoryConfig() + val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) assertNotNull(wallet) val address = wallet.getAddress() assertNotNull(address) diff --git a/cc/bdk_ffi_test.c b/cc/bdk_ffi_test.c index cd01a7b..487b20f 100644 --- a/cc/bdk_ffi_test.c +++ b/cc/bdk_ffi_test.c @@ -8,8 +8,14 @@ int main (int argc, char const * const argv[]) { // test new wallet error { - WalletResult_t *wallet_result = new_wallet_result("bad", "bad", NULL); + BlockchainConfig_t *bc_config = new_electrum_config("ssl://electrum.blockstream.info:60002", NULL, 5, 30); + //DatabaseConfig_t *db_config = new_sled_config("/home/steve/.bdk", "test_wallet"); + DatabaseConfig_t *db_config = new_memory_config(); + + WalletResult_t *wallet_result = new_wallet_result("bad", "bad", bc_config, db_config); assert(wallet_result != NULL); + free_blockchain_config(bc_config); + free_database_config(db_config); char *wallet_err = get_wallet_err(wallet_result); assert(wallet_err != NULL); assert( 0 == strcmp(wallet_err,"Descriptor") ); @@ -18,16 +24,22 @@ int main (int argc, char const * const argv[]) assert(wallet_ref == NULL); free_string(wallet_err); free_wallet_result(wallet_result); + } // test new wallet { - char const *name = "test_wallet"; char const *desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"; char const *change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"; - WalletResult_t *wallet_result = new_wallet_result(name, desc, change); + BlockchainConfig_t *bc_config = new_electrum_config("ssl://electrum.blockstream.info:60002", NULL, 5, 30); + //DatabaseConfig_t *db_config = new_sled_config("/home/steve/.bdk", "test_wallet"); + DatabaseConfig_t *db_config = new_memory_config(); + + WalletResult_t *wallet_result = new_wallet_result(desc, change, bc_config, db_config); assert(wallet_result != NULL); + free_blockchain_config(bc_config); + free_database_config(db_config); char *wallet_err = get_wallet_err(wallet_result); assert(wallet_err == NULL); WalletRef_t *wallet_ref = get_wallet_ok(wallet_result); @@ -65,6 +77,7 @@ int main (int argc, char const * const argv[]) // verify sync_wallet after free_wallet fails (core dumped) ////VoidResult_t sync_result2 = sync_wallet(wallet_result); + } return EXIT_SUCCESS; diff --git a/src/blockchain.rs b/src/blockchain.rs new file mode 100644 index 0000000..b3ecd8b --- /dev/null +++ b/src/blockchain.rs @@ -0,0 +1,104 @@ +use ::safer_ffi::prelude::*; +use bdk::blockchain::{AnyBlockchainConfig, ElectrumBlockchainConfig}; +use safer_ffi::boxed::Box; +use safer_ffi::char_p::char_p_ref; + +#[derive_ReprC] +#[ReprC::opaque] +#[derive(Debug)] +pub struct BlockchainConfig { + pub raw: AnyBlockchainConfig, +} + +#[ffi_export] +fn new_electrum_config( + url: char_p_ref, + socks5: Option, + retry: i16, + timeout: i16, +) -> Box { + let url = url.to_string(); + let socks5 = socks5.map(|s| s.to_string()); + let retry = short_to_u8(retry); + let timeout = short_to_optional_u8(timeout); + + let electrum_config = AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig { + url, + socks5, + retry, + timeout, + }); + Box::new(BlockchainConfig { + raw: electrum_config, + }) +} + +#[ffi_export] +fn free_blockchain_config(blockchain_config: Box) { + drop(blockchain_config); +} + +// TODO compact_filter rocksdb not compiling on android, switch to sqlite? +//#[derive_ReprC] +//#[repr(C)] +//#[derive(Debug)] +//pub struct BitcoinPeerConfig { +// pub address: char_p_boxed, +// pub socks5: Option, +// pub socks5_credentials: Option>>, +//} +// +//impl From<&BitcoinPeerConfig> for BdkBitcoinPeerConfig { +// fn from(config: &BitcoinPeerConfig) -> Self { +// let address = config.address.to_string(); +// let socks5 = config.socks5.as_ref().map(|p| p.to_string()); +// let socks5_credentials = config +// .socks5_credentials.as_ref() +// .map(|c| (c._0.to_string(), c._1.to_string())); +// +// BdkBitcoinPeerConfig { +// address, +// socks5: socks5, +// socks5_credentials: socks5_credentials, +// } +// } +//} +// +// +//#[ffi_export] +//fn new_compact_filters_config<'lt>( +// peers: c_slice::Ref<'lt, BitcoinPeerConfig>, +// network: char_p_ref, +// storage_dir: char_p_ref, +// skip_blocks: usize, +//) -> Box { +// let peers = peers.iter().map(|p| p.into()).collect(); +// let network = Network::from_str(network.to_str()).unwrap(); +// let storage_dir = storage_dir.to_string(); +// let skip_blocks = Some(skip_blocks); +// let cf_config = AnyBlockchainConfig::CompactFilters(CompactFiltersBlockchainConfig { +// peers, +// network, +// storage_dir, +// skip_blocks, +// }); +// Box::new(BlockchainConfig { raw: cf_config }) +//} + +// utility functions + +fn short_to_optional_u8(short: i16) -> Option { + if short < 0 { + None + } else { + Some(short_to_u8(short)) + } +} + +fn short_to_u8(short: i16) -> u8 { + if short < 0 { + u8::MIN + } else { + u8::try_from(short).unwrap_or(u8::MAX) + } +} diff --git a/src/database.rs b/src/database.rs new file mode 100644 index 0000000..76dc333 --- /dev/null +++ b/src/database.rs @@ -0,0 +1,32 @@ +use ::safer_ffi::prelude::*; +use bdk::database::any::SledDbConfiguration; +use bdk::database::AnyDatabaseConfig; +use safer_ffi::boxed::Box; +use safer_ffi::char_p::char_p_ref; + +#[derive_ReprC] +#[ReprC::opaque] +#[derive(Debug)] +pub struct DatabaseConfig { + pub raw: AnyDatabaseConfig, +} + +#[ffi_export] +fn new_memory_config() -> Box { + let memory_config = AnyDatabaseConfig::Memory(()); + Box::new(DatabaseConfig { raw: memory_config }) +} + +#[ffi_export] +fn new_sled_config(path: char_p_ref, tree_name: char_p_ref) -> Box { + let path = path.to_string(); + let tree_name = tree_name.to_string(); + + let sled_config = AnyDatabaseConfig::Sled(SledDbConfiguration { path, tree_name }); + Box::new(DatabaseConfig { raw: sled_config }) +} + +#[ffi_export] +fn free_database_config(database_config: Box) { + drop(database_config); +} diff --git a/src/error.rs b/src/error.rs index 3aa6d82..6af73f0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -38,8 +38,8 @@ pub fn get_name(error: &bdk::Error) -> String { Error::Hex(_) => "Hex", Error::Psbt(_) => "Psbt", Error::Electrum(_) => "Electrum", - // Error::Esplora(_) => {} - // Error::CompactFilters(_) => {} + // Error::Esplora(_) => "Esplora", + // Error::CompactFilters(_) => "CompactFilters", Error::Sled(_) => "Sled", } .to_string() diff --git a/src/lib.rs b/src/lib.rs index 3c75153..72e9910 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ #![deny(unsafe_code)] /* No `unsafe` needed! */ +mod blockchain; +mod database; mod error; mod wallet; diff --git a/src/wallet.rs b/src/wallet.rs index 988bede..93ea73c 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -10,6 +10,8 @@ use bdk::wallet::AddressIndex::New; use bdk::{Error, Wallet}; use safer_ffi::boxed::Box; use safer_ffi::char_p::{char_p_boxed, char_p_ref}; +use crate::blockchain::BlockchainConfig; +use crate::database::DatabaseConfig; #[derive_ReprC] #[ReprC::opaque] @@ -79,34 +81,30 @@ pub struct WalletResult { #[ffi_export] fn new_wallet_result( - name: char_p_ref, descriptor: char_p_ref, change_descriptor: Option, + blockchain_config: &BlockchainConfig, + database_config: &DatabaseConfig, ) -> Box { - let name = name.to_string(); + let descriptor = descriptor.to_string(); let change_descriptor = change_descriptor.map(|s| s.to_string()); - let wallet_result = new_wallet(name, descriptor, change_descriptor); + let bc_config = &blockchain_config.raw; + let db_config = &database_config.raw; + let wallet_result = new_wallet(descriptor, change_descriptor, bc_config, db_config); Box::new(WalletResult { raw: wallet_result }) } fn new_wallet( - _name: String, descriptor: String, change_descriptor: Option, + blockchain_config: &AnyBlockchainConfig, + database_config: &AnyDatabaseConfig, ) -> Result, Error> { let network = Testnet; - let electrum_config = AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig { - url: "ssl://electrum.blockstream.info:60002".to_string(), - socks5: None, - retry: 5, - timeout: None, - }); - let blockchain_config = electrum_config; - let client = AnyBlockchain::from_config(&blockchain_config)?; - let database_config = AnyDatabaseConfig::Memory(()); - let database = AnyDatabase::from_config(&database_config)?; + let client = AnyBlockchain::from_config(blockchain_config)?; + let database = AnyDatabase::from_config(database_config)?; let descriptor: &str = descriptor.as_str(); let change_descriptor: Option<&str> = change_descriptor.as_deref(); diff --git a/test.sh b/test.sh index f6f86d5..03855c5 100755 --- a/test.sh +++ b/test.sh @@ -6,9 +6,9 @@ cargo test --features c-headers -- generate_headers # cc export LD_LIBRARY_PATH=`pwd`/target/debug -#valgrind --leak-check=full cc/bdk_ffi_test +#valgrind --leak-check=full --show-leak-kinds=all cc/bdk_ffi_test cc/bdk_ffi_test -# bdk-kotlin +## bdk-kotlin (cd bdk-kotlin && gradle test) (cd bdk-kotlin && gradle :android:connectedDebugAndroidTest)