Skip to content

Commit

Permalink
Merge pull request #4 from chainwayxyz/persistent-state
Browse files Browse the repository at this point in the history
Persistent State Across Threads
  • Loading branch information
ceyhunsen authored Jul 1, 2024
2 parents 3dfe1e9 + 3bb86fe commit 56f2e72
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 19 deletions.
1 change: 1 addition & 0 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ impl RpcApiWrapper for bitcoincore_rpc::Client {
}

/// Mock Bitcoin RPC client.
#[derive(Clone)]
pub struct Client {
/// Bitcoin ledger.
ledger: Ledger,
Expand Down
71 changes: 60 additions & 11 deletions src/client/rpc_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
use super::Client;
use crate::ledger::Ledger;
use bitcoin::{
address::NetworkChecked, consensus::encode, hashes::Hash, Address, Amount, BlockHash,
SignedAmount, Transaction, TxIn, Wtxid,
address::NetworkChecked, consensus::encode, hashes::Hash, params::Params, Address, Amount,
BlockHash, SignedAmount, Transaction, TxIn, Wtxid,
};
use bitcoincore_rpc::{
json::{
Expand Down Expand Up @@ -86,6 +86,28 @@ impl RpcApi for Client {
) -> bitcoincore_rpc::Result<json::GetTransactionResult> {
let raw_tx = self.get_raw_transaction(txid, None).unwrap();

let details: Vec<GetTransactionResultDetail> = raw_tx
.output
.iter()
.map(|utxo| GetTransactionResultDetail {
address: Some(
Address::from_script(
&utxo.script_pubkey,
Params::new(bitcoin::Network::Regtest),
)
.unwrap()
.as_unchecked()
.clone(),
),
category: GetTransactionResultDetailCategory::Send,
amount: SignedAmount::from_sat(utxo.value.to_sat() as i64),
label: None,
vout: 0,
fee: None,
abandoned: None,
})
.collect();

let res = GetTransactionResult {
info: WalletTxInfo {
confirmations: i32::MAX,
Expand All @@ -101,15 +123,7 @@ impl RpcApi for Client {
},
amount: SignedAmount::from_sat(raw_tx.output[0].value.to_sat() as i64),
fee: None,
details: vec![GetTransactionResultDetail {
address: None,
category: GetTransactionResultDetailCategory::Send,
amount: SignedAmount::from_sat(raw_tx.output[0].value.to_sat() as i64),
label: None,
vout: 0,
fee: None,
abandoned: None,
}],
details,
hex: encode::serialize(&raw_tx),
};

Expand Down Expand Up @@ -201,6 +215,41 @@ impl RpcApi for Client {
) -> bitcoincore_rpc::Result<Amount> {
Ok(self.ledger.calculate_balance()?)
}

fn list_unspent(
&self,
_minconf: Option<usize>,
_maxconf: Option<usize>,
_addresses: Option<&[&Address<NetworkChecked>]>,
_include_unsafe: Option<bool>,
_query_options: Option<json::ListUnspentQueryOptions>,
) -> bitcoincore_rpc::Result<Vec<json::ListUnspentResultEntry>> {
let utxos = self.ledger.get_utxos();

Ok(utxos
.iter()
.map(|utxo| {
let tx = self.ledger.get_transaction(utxo.txid).unwrap();
let output = tx.output.get(utxo.vout as usize).unwrap();

json::ListUnspentResultEntry {
txid: utxo.txid,
vout: utxo.vout,
address: None,
label: None,
redeem_script: None,
witness_script: None,
script_pub_key: output.script_pubkey.clone(),
amount: output.value,
confirmations: 101,
spendable: true,
solvable: true,
descriptor: None,
safe: true,
}
})
.collect())
}
}

#[cfg(test)]
Expand Down
13 changes: 7 additions & 6 deletions src/ledger/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,23 @@ mod transactions;
mod utxo;

/// Mock Bitcoin ledger.
#[derive(Clone)]
pub struct Ledger {
/// User's keys and address.
credentials: Arc<Mutex<Cell<Vec<UserCredential>>>>,
credentials: Box<Arc<Mutex<Cell<Vec<UserCredential>>>>>,
/// Happened transactions.
transactions: Arc<Mutex<Cell<Vec<Transaction>>>>,
transactions: Box<Arc<Mutex<Cell<Vec<Transaction>>>>>,
/// Unspent transaction outputs.
utxos: Arc<Mutex<Cell<Vec<OutPoint>>>>,
utxos: Box<Arc<Mutex<Cell<Vec<OutPoint>>>>>,
}

impl Ledger {
/// Creates a new empty ledger.
pub fn new() -> Self {
Self {
credentials: Arc::new(Mutex::new(Cell::new(Vec::new()))),
utxos: Arc::new(Mutex::new(Cell::new(Vec::new()))),
transactions: Arc::new(Mutex::new(Cell::new(Vec::new()))),
credentials: Box::new(Arc::new(Mutex::new(Cell::new(Vec::new())))),
utxos: Box::new(Arc::new(Mutex::new(Cell::new(Vec::new())))),
transactions: Box::new(Arc::new(Mutex::new(Cell::new(Vec::new())))),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/test_common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ pub fn create_witness() -> (WitnessProgram, Witness) {
}

#[allow(unused)]
pub fn create_address() -> Address {
pub fn create_address_from_witness() -> Address {
let witness_program = create_witness().0;

Address::from_witness_program(witness_program, bitcoin::Network::Regtest)
Expand Down
37 changes: 37 additions & 0 deletions tests/address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//! Address related integration tests.
use bitcoin_mock_rpc::{Client, RpcApiWrapper};
use bitcoincore_rpc::{Auth, RpcApi};
use std::thread;

#[test]
fn generate_to_address_multi_threaded() {
// Bacause `thread::spawn` moves value to closure, cloning a new is needed. This is good,
// because cloning an rpc struct should have a persistent ledger even though there are more than
// one accessors.
let rpc = Client::new("", Auth::None).unwrap();
let cloned_rpc = rpc.clone();
let address = rpc.get_new_address(None, None).unwrap().assume_checked();
let cloned_address = address.clone();

let initial_balance = rpc.get_balance(None, None).unwrap();

thread::spawn(move || {
cloned_rpc
.generate_to_address(101, &cloned_address)
.unwrap();

assert!(cloned_rpc.get_balance(None, None).unwrap() > initial_balance);
})
.join()
.unwrap();

// Change made in other rpc connection should be available now.
let changed_balance = rpc.get_balance(None, None).unwrap();
assert!(changed_balance > initial_balance);

// Adding new blocks should add more funds.
rpc.generate_to_address(101, &address).unwrap();
assert!(rpc.get_balance(None, None).unwrap() > changed_balance);
assert!(rpc.get_balance(None, None).unwrap() > initial_balance);
}
66 changes: 65 additions & 1 deletion tests/transaction.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,70 @@
//! Transaction related integration tests.
use bitcoin_mock_rpc::{Client, RpcApiWrapper};
use bitcoincore_rpc::{Auth, RpcApi};
use std::thread;

mod common;
use common::test_common;

#[test]
fn raw_transaction_test() {}
fn send_to_address_multi_threaded() {
// Bacause `thread::spawn` moves value to closure, cloning a new is needed. This is good,
// because cloning an rpc struct should have a persistent ledger even though there are more than
// one accessors.
let rpc = Client::new("", Auth::None).unwrap();
let cloned_rpc = rpc.clone();
let address = rpc.get_new_address(None, None).unwrap().assume_checked();
let deposit_address = test_common::create_address_from_witness();
let cloned_deposit_address = deposit_address.clone();

rpc.generate_to_address(101, &address).unwrap();
let initial_balance = rpc.get_balance(None, None).unwrap();
let deposit_value = initial_balance / 4;

thread::spawn(move || {
cloned_rpc
.send_to_address(
&cloned_deposit_address,
deposit_value,
None,
None,
None,
None,
None,
None,
)
.unwrap();

assert_eq!(
cloned_rpc.get_balance(None, None).unwrap(),
initial_balance - deposit_value
);
})
.join()
.unwrap();

// Change made in other rpc connection should be available now.
assert_eq!(
rpc.get_balance(None, None).unwrap(),
initial_balance - deposit_value
);

// Adding new blocks should add more funds.
rpc.send_to_address(
&deposit_address,
deposit_value,
None,
None,
None,
None,
None,
None,
)
.unwrap();
assert_eq!(
rpc.get_balance(None, None).unwrap(),
initial_balance - deposit_value - deposit_value
); // No multiplication over `Amount`.
assert!(rpc.get_balance(None, None).unwrap() < initial_balance);
}

0 comments on commit 56f2e72

Please sign in to comment.