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
|
||||
build
|
||||
Cargo.lock
|
||||
*.h
|
||||
/bdk-kotlin/local.properties
|
||||
.gradle
|
||||
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
|
||||
|
||||
# rust
|
||||
cargo fmt
|
||||
cargo build
|
||||
cargo test --features c-headers -- generate_headers
|
||||
|
||||
|
@ -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
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 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
6
test.sh
6
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 --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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user