Refactor to return results by value, add wallet list_unspent and related types

This commit is contained in:
Steve Myers 2021-06-30 17:44:46 -07:00
parent 59fe24818a
commit 060e54a718
7 changed files with 188 additions and 104 deletions

1
.gitignore vendored
View File

@ -1,7 +1,6 @@
target
build
Cargo.lock
*.h
/bdk-kotlin/local.properties
.gradle
wallet_db

18
README.md Normal file
View 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

View File

@ -2,6 +2,7 @@
set -eo pipefail -o xtrace
# rust
cargo fmt
cargo build
cargo test --features c-headers -- generate_headers

View File

@ -3,6 +3,7 @@
mod blockchain;
mod database;
mod error;
mod types;
mod wallet;
/// The following test function is necessary for the header generation.

34
src/types.rs Normal file
View 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)
}

View File

@ -1,82 +1,23 @@
use crate::blockchain::BlockchainConfig;
use crate::database::DatabaseConfig;
use crate::error::get_name;
use std::convert::TryFrom;
use ::safer_ffi::prelude::*;
use bdk::bitcoin::network::constants::Network::Testnet;
use bdk::blockchain::{
log_progress, AnyBlockchain, AnyBlockchainConfig, ConfigurableBlockchain,
ElectrumBlockchainConfig,
};
use bdk::blockchain::{log_progress, AnyBlockchain, AnyBlockchainConfig, ConfigurableBlockchain};
use bdk::database::{AnyDatabase, AnyDatabaseConfig, ConfigurableDatabase};
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};
#[derive_ReprC]
#[ReprC::opaque]
pub struct VoidResult {
raw: Result<(), Error>,
}
#[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)
}
use crate::blockchain::BlockchainConfig;
use crate::database::DatabaseConfig;
use crate::error::get_name;
use crate::types::{FfiResult, FfiResultVec};
#[derive_ReprC]
#[ReprC::opaque]
pub struct StringResult {
raw: Result<String, Error>,
}
#[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>,
pub struct OpaqueWallet {
raw: Wallet<AnyBlockchain, AnyDatabase>,
}
#[ffi_export]
@ -85,13 +26,83 @@ fn new_wallet_result(
change_descriptor: Option<char_p_ref>,
blockchain_config: &BlockchainConfig,
database_config: &DatabaseConfig,
) -> Box<WalletResult> {
) -> FfiResult<OpaqueWallet> {
let descriptor = descriptor.to_string();
let change_descriptor = change_descriptor.map(|s| s.to_string());
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 })
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(
@ -111,44 +122,64 @@ fn new_wallet(
Wallet::new(descriptor, change_descriptor, network, database, client)
}
#[ffi_export]
fn get_wallet_err(wallet_result: &WalletResult) -> Option<char_p_boxed> {
wallet_result
.raw
.as_ref()
.err()
.map(|e| char_p_boxed::try_from(get_name(&e)).unwrap())
// Non-opaque returned structs
#[derive_ReprC]
#[repr(C)]
#[derive(Debug, Clone)]
pub struct OutPoint {
/// 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]
fn get_wallet_ok<'lt>(wallet_result: &'lt WalletResult) -> Option<Box<WalletRef<'lt>>> {
wallet_result
.raw
.as_ref()
.ok()
.map(|w| Box::new(WalletRef { raw: w }))
impl From<&bdk::bitcoin::OutPoint> for OutPoint {
fn from(op: &bdk::bitcoin::OutPoint) -> Self {
OutPoint {
txid: char_p_boxed::try_from(op.txid.to_string()).unwrap(),
vout: op.vout,
}
}
}
#[ffi_export]
fn sync_wallet<'lt>(wallet_ref: &'lt WalletRef<'lt>) -> Box<VoidResult> {
let void_result = wallet_ref.raw.sync(log_progress(), Some(100));
Box::new(VoidResult { raw: void_result })
#[derive_ReprC]
#[repr(C)]
#[derive(Debug, Clone)]
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]
fn new_address<'lt>(wallet_ref: &'lt WalletRef<'lt>) -> Box<StringResult> {
let new_address = wallet_ref.raw.get_address(New);
let string_result = new_address.map(|a| a.to_string());
Box::new(StringResult { raw: string_result })
impl From<&bdk::bitcoin::TxOut> for TxOut {
fn from(to: &bdk::bitcoin::TxOut) -> Self {
TxOut {
value: to.value,
script_pubkey: char_p_boxed::try_from(to.script_pubkey.to_string()).unwrap(),
}
}
}
#[ffi_export]
fn free_wallet_result(wallet_result: Option<Box<WalletResult>>) {
drop(wallet_result)
#[derive_ReprC]
#[repr(C)]
#[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
#[ffi_export]
fn free_string(string: Option<char_p_boxed>) {
drop(string)
impl From<&bdk::LocalUtxo> for LocalUtxo {
fn from(lu: &bdk::LocalUtxo) -> Self {
LocalUtxo {
outpoint: OutPoint::from(&lu.outpoint),
txout: TxOut::from(&lu.txout),
keychain: lu.keychain as u16,
}
}
}

View File

@ -6,9 +6,9 @@ cargo test --features c-headers -- generate_headers
# cc
export LD_LIBRARY_PATH=`pwd`/target/debug
#valgrind --leak-check=full --show-leak-kinds=all cc/bdk_ffi_test
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)