Refactor to return results by value, add wallet list_unspent and related types
This commit is contained in:
parent
59fe24818a
commit
060e54a718
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,7 +1,6 @@
|
|||||||
target
|
target
|
||||||
build
|
build
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
*.h
|
|
||||||
/bdk-kotlin/local.properties
|
/bdk-kotlin/local.properties
|
||||||
.gradle
|
.gradle
|
||||||
wallet_db
|
wallet_db
|
||||||
|
18
README.md
Normal file
18
README.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
|
||||||
|
Adding new structs and functions
|
||||||
|
|
||||||
|
1. Create C safe Rust structs and related functions using safer-ffi
|
||||||
|
|
||||||
|
2. Test generated library and `bdk_ffi.h` file with c language tests in `cc/bdk_ffi_test.c`
|
||||||
|
|
||||||
|
3. Use `build.sh` and `test.sh` to build c test program and verify functionality and
|
||||||
|
memory de-allocation via `valgrind`
|
||||||
|
|
||||||
|
4. Update the kotlin native interface LibJna.kt in the `bdk-kotlin` `jvm` module to match `bdk_ffi.h`
|
||||||
|
|
||||||
|
5. Create kotlin wrapper classes and interfaces as needed
|
||||||
|
|
||||||
|
6. Add tests to `bdk-kotlin` `test-fixtures` module
|
||||||
|
|
||||||
|
7. Use `build.sh` and `test.sh` to build and test `bdk-kotlin` `jvm` and `android` modules
|
1
build.sh
1
build.sh
@ -2,6 +2,7 @@
|
|||||||
set -eo pipefail -o xtrace
|
set -eo pipefail -o xtrace
|
||||||
|
|
||||||
# rust
|
# rust
|
||||||
|
cargo fmt
|
||||||
cargo build
|
cargo build
|
||||||
cargo test --features c-headers -- generate_headers
|
cargo test --features c-headers -- generate_headers
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
mod blockchain;
|
mod blockchain;
|
||||||
mod database;
|
mod database;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod types;
|
||||||
mod wallet;
|
mod wallet;
|
||||||
|
|
||||||
/// The following test function is necessary for the header generation.
|
/// The following test function is necessary for the header generation.
|
||||||
|
34
src/types.rs
Normal file
34
src/types.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use ::safer_ffi::prelude::*;
|
||||||
|
use safer_ffi::char_p::char_p_boxed;
|
||||||
|
|
||||||
|
#[derive_ReprC]
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FfiResult<T> {
|
||||||
|
pub ok: Option<repr_c::Box<T>>,
|
||||||
|
pub err: Option<repr_c::Box<char_p_boxed>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive_ReprC]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct FfiResultVec<T> {
|
||||||
|
pub ok: repr_c::Vec<T>,
|
||||||
|
pub err: Option<repr_c::Box<char_p_boxed>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ffi_export]
|
||||||
|
fn free_string_result(string_result: FfiResult<char_p_boxed>) {
|
||||||
|
drop(string_result)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ffi_export]
|
||||||
|
fn free_void_result(void_result: FfiResult<()>) {
|
||||||
|
drop(void_result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO do we need this? remove?
|
||||||
|
/// Frees a Rust-allocated string
|
||||||
|
#[ffi_export]
|
||||||
|
fn free_string(string: Option<char_p_boxed>) {
|
||||||
|
drop(string)
|
||||||
|
}
|
231
src/wallet.rs
231
src/wallet.rs
@ -1,82 +1,23 @@
|
|||||||
use crate::blockchain::BlockchainConfig;
|
use std::convert::TryFrom;
|
||||||
use crate::database::DatabaseConfig;
|
|
||||||
use crate::error::get_name;
|
|
||||||
use ::safer_ffi::prelude::*;
|
use ::safer_ffi::prelude::*;
|
||||||
use bdk::bitcoin::network::constants::Network::Testnet;
|
use bdk::bitcoin::network::constants::Network::Testnet;
|
||||||
use bdk::blockchain::{
|
use bdk::blockchain::{log_progress, AnyBlockchain, AnyBlockchainConfig, ConfigurableBlockchain};
|
||||||
log_progress, AnyBlockchain, AnyBlockchainConfig, ConfigurableBlockchain,
|
|
||||||
ElectrumBlockchainConfig,
|
|
||||||
};
|
|
||||||
use bdk::database::{AnyDatabase, AnyDatabaseConfig, ConfigurableDatabase};
|
use bdk::database::{AnyDatabase, AnyDatabaseConfig, ConfigurableDatabase};
|
||||||
use bdk::wallet::AddressIndex::New;
|
use bdk::wallet::AddressIndex::New;
|
||||||
use bdk::{Error, Wallet};
|
use bdk::{Error, Wallet};
|
||||||
use safer_ffi::boxed::Box;
|
use safer_ffi::boxed::Box;
|
||||||
use safer_ffi::char_p::{char_p_boxed, char_p_ref};
|
use safer_ffi::char_p::{char_p_boxed, char_p_ref};
|
||||||
|
|
||||||
#[derive_ReprC]
|
use crate::blockchain::BlockchainConfig;
|
||||||
#[ReprC::opaque]
|
use crate::database::DatabaseConfig;
|
||||||
pub struct VoidResult {
|
use crate::error::get_name;
|
||||||
raw: Result<(), Error>,
|
use crate::types::{FfiResult, FfiResultVec};
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn get_void_err(void_result: &VoidResult) -> Option<char_p_boxed> {
|
|
||||||
void_result
|
|
||||||
.raw
|
|
||||||
.as_ref()
|
|
||||||
.err()
|
|
||||||
.map(|e| char_p_boxed::try_from(get_name(e)).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn free_void_result(void_result: Option<Box<VoidResult>>) {
|
|
||||||
drop(void_result)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive_ReprC]
|
#[derive_ReprC]
|
||||||
#[ReprC::opaque]
|
#[ReprC::opaque]
|
||||||
pub struct StringResult {
|
pub struct OpaqueWallet {
|
||||||
raw: Result<String, Error>,
|
raw: Wallet<AnyBlockchain, AnyDatabase>,
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn get_string_ok(string_result: &StringResult) -> Option<char_p_boxed> {
|
|
||||||
string_result
|
|
||||||
.raw
|
|
||||||
.as_ref()
|
|
||||||
.ok()
|
|
||||||
.map(|s| char_p_boxed::try_from(s.clone()).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn get_string_err(string_result: &StringResult) -> Option<char_p_boxed> {
|
|
||||||
string_result
|
|
||||||
.raw
|
|
||||||
.as_ref()
|
|
||||||
.err()
|
|
||||||
.map(|e| char_p_boxed::try_from(get_name(e)).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn free_string_result(string_result: Option<Box<StringResult>>) {
|
|
||||||
drop(string_result)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive_ReprC]
|
|
||||||
#[ReprC::opaque]
|
|
||||||
pub struct WalletRef<'lt> {
|
|
||||||
raw: &'lt Wallet<AnyBlockchain, AnyDatabase>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn free_wallet_ref(wallet_ref: Option<Box<WalletRef>>) {
|
|
||||||
drop(wallet_ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive_ReprC]
|
|
||||||
#[ReprC::opaque]
|
|
||||||
pub struct WalletResult {
|
|
||||||
raw: Result<Wallet<AnyBlockchain, AnyDatabase>, Error>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ffi_export]
|
#[ffi_export]
|
||||||
@ -85,13 +26,83 @@ fn new_wallet_result(
|
|||||||
change_descriptor: Option<char_p_ref>,
|
change_descriptor: Option<char_p_ref>,
|
||||||
blockchain_config: &BlockchainConfig,
|
blockchain_config: &BlockchainConfig,
|
||||||
database_config: &DatabaseConfig,
|
database_config: &DatabaseConfig,
|
||||||
) -> Box<WalletResult> {
|
) -> FfiResult<OpaqueWallet> {
|
||||||
let descriptor = descriptor.to_string();
|
let descriptor = descriptor.to_string();
|
||||||
let change_descriptor = change_descriptor.map(|s| s.to_string());
|
let change_descriptor = change_descriptor.map(|s| s.to_string());
|
||||||
let bc_config = &blockchain_config.raw;
|
let bc_config = &blockchain_config.raw;
|
||||||
let db_config = &database_config.raw;
|
let db_config = &database_config.raw;
|
||||||
let wallet_result = new_wallet(descriptor, change_descriptor, bc_config, db_config);
|
let wallet_result = new_wallet(descriptor, change_descriptor, bc_config, db_config);
|
||||||
Box::new(WalletResult { raw: wallet_result })
|
|
||||||
|
match wallet_result {
|
||||||
|
Ok(w) => FfiResult {
|
||||||
|
ok: Some(Box::new(OpaqueWallet { raw: w })),
|
||||||
|
err: None,
|
||||||
|
},
|
||||||
|
Err(e) => FfiResult {
|
||||||
|
ok: None,
|
||||||
|
err: Some(Box::new(char_p_boxed::try_from(get_name(&e)).unwrap())),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ffi_export]
|
||||||
|
fn free_wallet_result(wallet_result: FfiResult<OpaqueWallet>) {
|
||||||
|
drop(wallet_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ffi_export]
|
||||||
|
fn free_unspent_result(unspent_result: FfiResultVec<LocalUtxo>) {
|
||||||
|
drop(unspent_result)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ffi_export]
|
||||||
|
fn sync_wallet(opaque_wallet: &OpaqueWallet) -> FfiResult<()> {
|
||||||
|
let void_result = opaque_wallet.raw.sync(log_progress(), Some(100));
|
||||||
|
match void_result {
|
||||||
|
Ok(v) => FfiResult {
|
||||||
|
ok: Some(Box::new(v)),
|
||||||
|
err: None,
|
||||||
|
},
|
||||||
|
Err(e) => FfiResult {
|
||||||
|
ok: None,
|
||||||
|
err: Some(Box::new(char_p_boxed::try_from(get_name(&e)).unwrap())),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ffi_export]
|
||||||
|
fn new_address(opaque_wallet: &OpaqueWallet) -> FfiResult<char_p_boxed> {
|
||||||
|
let new_address = opaque_wallet.raw.get_address(New);
|
||||||
|
let string_result = new_address.map(|a| a.to_string());
|
||||||
|
match string_result {
|
||||||
|
Ok(a) => FfiResult {
|
||||||
|
ok: Some(Box::new(char_p_boxed::try_from(a).unwrap())),
|
||||||
|
err: None,
|
||||||
|
},
|
||||||
|
Err(e) => FfiResult {
|
||||||
|
ok: None,
|
||||||
|
err: Some(Box::new(char_p_boxed::try_from(get_name(&e)).unwrap())),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ffi_export]
|
||||||
|
fn list_unspent(opaque_wallet: &OpaqueWallet) -> FfiResultVec<LocalUtxo> {
|
||||||
|
let unspent_result = opaque_wallet.raw.list_unspent();
|
||||||
|
|
||||||
|
match unspent_result {
|
||||||
|
Ok(v) => FfiResultVec {
|
||||||
|
ok: {
|
||||||
|
let ve: Vec<LocalUtxo> = v.iter().map(|lu| LocalUtxo::from(lu)).collect();
|
||||||
|
repr_c::Vec::from(ve)
|
||||||
|
},
|
||||||
|
err: None,
|
||||||
|
},
|
||||||
|
Err(e) => FfiResultVec {
|
||||||
|
ok: repr_c::Vec::EMPTY,
|
||||||
|
err: Some(Box::new(char_p_boxed::try_from(get_name(&e)).unwrap())),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_wallet(
|
fn new_wallet(
|
||||||
@ -111,44 +122,64 @@ fn new_wallet(
|
|||||||
Wallet::new(descriptor, change_descriptor, network, database, client)
|
Wallet::new(descriptor, change_descriptor, network, database, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ffi_export]
|
// Non-opaque returned structs
|
||||||
fn get_wallet_err(wallet_result: &WalletResult) -> Option<char_p_boxed> {
|
|
||||||
wallet_result
|
#[derive_ReprC]
|
||||||
.raw
|
#[repr(C)]
|
||||||
.as_ref()
|
#[derive(Debug, Clone)]
|
||||||
.err()
|
pub struct OutPoint {
|
||||||
.map(|e| char_p_boxed::try_from(get_name(&e)).unwrap())
|
/// The referenced transaction's txid, as hex string
|
||||||
|
pub txid: char_p_boxed,
|
||||||
|
/// The index of the referenced output in its transaction's vout
|
||||||
|
pub vout: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ffi_export]
|
impl From<&bdk::bitcoin::OutPoint> for OutPoint {
|
||||||
fn get_wallet_ok<'lt>(wallet_result: &'lt WalletResult) -> Option<Box<WalletRef<'lt>>> {
|
fn from(op: &bdk::bitcoin::OutPoint) -> Self {
|
||||||
wallet_result
|
OutPoint {
|
||||||
.raw
|
txid: char_p_boxed::try_from(op.txid.to_string()).unwrap(),
|
||||||
.as_ref()
|
vout: op.vout,
|
||||||
.ok()
|
}
|
||||||
.map(|w| Box::new(WalletRef { raw: w }))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ffi_export]
|
#[derive_ReprC]
|
||||||
fn sync_wallet<'lt>(wallet_ref: &'lt WalletRef<'lt>) -> Box<VoidResult> {
|
#[repr(C)]
|
||||||
let void_result = wallet_ref.raw.sync(log_progress(), Some(100));
|
#[derive(Debug, Clone)]
|
||||||
Box::new(VoidResult { raw: void_result })
|
pub struct TxOut {
|
||||||
|
/// The value of the output, in satoshis
|
||||||
|
pub value: u64,
|
||||||
|
/// The script which must satisfy for the output to be spent, as hex string
|
||||||
|
pub script_pubkey: char_p_boxed,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ffi_export]
|
impl From<&bdk::bitcoin::TxOut> for TxOut {
|
||||||
fn new_address<'lt>(wallet_ref: &'lt WalletRef<'lt>) -> Box<StringResult> {
|
fn from(to: &bdk::bitcoin::TxOut) -> Self {
|
||||||
let new_address = wallet_ref.raw.get_address(New);
|
TxOut {
|
||||||
let string_result = new_address.map(|a| a.to_string());
|
value: to.value,
|
||||||
Box::new(StringResult { raw: string_result })
|
script_pubkey: char_p_boxed::try_from(to.script_pubkey.to_string()).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ffi_export]
|
#[derive_ReprC]
|
||||||
fn free_wallet_result(wallet_result: Option<Box<WalletResult>>) {
|
#[repr(C)]
|
||||||
drop(wallet_result)
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LocalUtxo {
|
||||||
|
/// Reference to a transaction output
|
||||||
|
pub outpoint: OutPoint,
|
||||||
|
/// Transaction output
|
||||||
|
pub txout: TxOut,
|
||||||
|
/// Type of keychain, as short 0 for "external" or 1 for "internal"
|
||||||
|
pub keychain: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Frees a Rust-allocated string
|
impl From<&bdk::LocalUtxo> for LocalUtxo {
|
||||||
#[ffi_export]
|
fn from(lu: &bdk::LocalUtxo) -> Self {
|
||||||
fn free_string(string: Option<char_p_boxed>) {
|
LocalUtxo {
|
||||||
drop(string)
|
outpoint: OutPoint::from(&lu.outpoint),
|
||||||
|
txout: TxOut::from(&lu.txout),
|
||||||
|
keychain: lu.keychain as u16,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
6
test.sh
6
test.sh
@ -6,9 +6,9 @@ cargo test --features c-headers -- generate_headers
|
|||||||
|
|
||||||
# cc
|
# cc
|
||||||
export LD_LIBRARY_PATH=`pwd`/target/debug
|
export LD_LIBRARY_PATH=`pwd`/target/debug
|
||||||
#valgrind --leak-check=full --show-leak-kinds=all cc/bdk_ffi_test
|
valgrind --leak-check=full --show-leak-kinds=all cc/bdk_ffi_test
|
||||||
cc/bdk_ffi_test
|
#cc/bdk_ffi_test
|
||||||
|
|
||||||
## bdk-kotlin
|
# bdk-kotlin
|
||||||
(cd bdk-kotlin && gradle test)
|
(cd bdk-kotlin && gradle test)
|
||||||
(cd bdk-kotlin && gradle :android:connectedDebugAndroidTest)
|
(cd bdk-kotlin && gradle :android:connectedDebugAndroidTest)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user