Skip to content

Commit 56f2e72

Browse files
authored
Merge pull request #4 from chainwayxyz/persistent-state
Persistent State Across Threads
2 parents 3dfe1e9 + 3bb86fe commit 56f2e72

File tree

6 files changed

+171
-19
lines changed

6 files changed

+171
-19
lines changed

src/client/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ impl RpcApiWrapper for bitcoincore_rpc::Client {
2323
}
2424

2525
/// Mock Bitcoin RPC client.
26+
#[derive(Clone)]
2627
pub struct Client {
2728
/// Bitcoin ledger.
2829
ledger: Ledger,

src/client/rpc_api.rs

+60-11
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
use super::Client;
77
use crate::ledger::Ledger;
88
use bitcoin::{
9-
address::NetworkChecked, consensus::encode, hashes::Hash, Address, Amount, BlockHash,
10-
SignedAmount, Transaction, TxIn, Wtxid,
9+
address::NetworkChecked, consensus::encode, hashes::Hash, params::Params, Address, Amount,
10+
BlockHash, SignedAmount, Transaction, TxIn, Wtxid,
1111
};
1212
use bitcoincore_rpc::{
1313
json::{
@@ -86,6 +86,28 @@ impl RpcApi for Client {
8686
) -> bitcoincore_rpc::Result<json::GetTransactionResult> {
8787
let raw_tx = self.get_raw_transaction(txid, None).unwrap();
8888

89+
let details: Vec<GetTransactionResultDetail> = raw_tx
90+
.output
91+
.iter()
92+
.map(|utxo| GetTransactionResultDetail {
93+
address: Some(
94+
Address::from_script(
95+
&utxo.script_pubkey,
96+
Params::new(bitcoin::Network::Regtest),
97+
)
98+
.unwrap()
99+
.as_unchecked()
100+
.clone(),
101+
),
102+
category: GetTransactionResultDetailCategory::Send,
103+
amount: SignedAmount::from_sat(utxo.value.to_sat() as i64),
104+
label: None,
105+
vout: 0,
106+
fee: None,
107+
abandoned: None,
108+
})
109+
.collect();
110+
89111
let res = GetTransactionResult {
90112
info: WalletTxInfo {
91113
confirmations: i32::MAX,
@@ -101,15 +123,7 @@ impl RpcApi for Client {
101123
},
102124
amount: SignedAmount::from_sat(raw_tx.output[0].value.to_sat() as i64),
103125
fee: None,
104-
details: vec![GetTransactionResultDetail {
105-
address: None,
106-
category: GetTransactionResultDetailCategory::Send,
107-
amount: SignedAmount::from_sat(raw_tx.output[0].value.to_sat() as i64),
108-
label: None,
109-
vout: 0,
110-
fee: None,
111-
abandoned: None,
112-
}],
126+
details,
113127
hex: encode::serialize(&raw_tx),
114128
};
115129

@@ -201,6 +215,41 @@ impl RpcApi for Client {
201215
) -> bitcoincore_rpc::Result<Amount> {
202216
Ok(self.ledger.calculate_balance()?)
203217
}
218+
219+
fn list_unspent(
220+
&self,
221+
_minconf: Option<usize>,
222+
_maxconf: Option<usize>,
223+
_addresses: Option<&[&Address<NetworkChecked>]>,
224+
_include_unsafe: Option<bool>,
225+
_query_options: Option<json::ListUnspentQueryOptions>,
226+
) -> bitcoincore_rpc::Result<Vec<json::ListUnspentResultEntry>> {
227+
let utxos = self.ledger.get_utxos();
228+
229+
Ok(utxos
230+
.iter()
231+
.map(|utxo| {
232+
let tx = self.ledger.get_transaction(utxo.txid).unwrap();
233+
let output = tx.output.get(utxo.vout as usize).unwrap();
234+
235+
json::ListUnspentResultEntry {
236+
txid: utxo.txid,
237+
vout: utxo.vout,
238+
address: None,
239+
label: None,
240+
redeem_script: None,
241+
witness_script: None,
242+
script_pub_key: output.script_pubkey.clone(),
243+
amount: output.value,
244+
confirmations: 101,
245+
spendable: true,
246+
solvable: true,
247+
descriptor: None,
248+
safe: true,
249+
}
250+
})
251+
.collect())
252+
}
204253
}
205254

206255
#[cfg(test)]

src/ledger/mod.rs

+7-6
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,23 @@ mod transactions;
2020
mod utxo;
2121

2222
/// Mock Bitcoin ledger.
23+
#[derive(Clone)]
2324
pub struct Ledger {
2425
/// User's keys and address.
25-
credentials: Arc<Mutex<Cell<Vec<UserCredential>>>>,
26+
credentials: Box<Arc<Mutex<Cell<Vec<UserCredential>>>>>,
2627
/// Happened transactions.
27-
transactions: Arc<Mutex<Cell<Vec<Transaction>>>>,
28+
transactions: Box<Arc<Mutex<Cell<Vec<Transaction>>>>>,
2829
/// Unspent transaction outputs.
29-
utxos: Arc<Mutex<Cell<Vec<OutPoint>>>>,
30+
utxos: Box<Arc<Mutex<Cell<Vec<OutPoint>>>>>,
3031
}
3132

3233
impl Ledger {
3334
/// Creates a new empty ledger.
3435
pub fn new() -> Self {
3536
Self {
36-
credentials: Arc::new(Mutex::new(Cell::new(Vec::new()))),
37-
utxos: Arc::new(Mutex::new(Cell::new(Vec::new()))),
38-
transactions: Arc::new(Mutex::new(Cell::new(Vec::new()))),
37+
credentials: Box::new(Arc::new(Mutex::new(Cell::new(Vec::new())))),
38+
utxos: Box::new(Arc::new(Mutex::new(Cell::new(Vec::new())))),
39+
transactions: Box::new(Arc::new(Mutex::new(Cell::new(Vec::new())))),
3940
}
4041
}
4142
}

src/test_common/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ pub fn create_witness() -> (WitnessProgram, Witness) {
4848
}
4949

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

5454
Address::from_witness_program(witness_program, bitcoin::Network::Regtest)

tests/address.rs

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//! Address related integration tests.
2+
3+
use bitcoin_mock_rpc::{Client, RpcApiWrapper};
4+
use bitcoincore_rpc::{Auth, RpcApi};
5+
use std::thread;
6+
7+
#[test]
8+
fn generate_to_address_multi_threaded() {
9+
// Bacause `thread::spawn` moves value to closure, cloning a new is needed. This is good,
10+
// because cloning an rpc struct should have a persistent ledger even though there are more than
11+
// one accessors.
12+
let rpc = Client::new("", Auth::None).unwrap();
13+
let cloned_rpc = rpc.clone();
14+
let address = rpc.get_new_address(None, None).unwrap().assume_checked();
15+
let cloned_address = address.clone();
16+
17+
let initial_balance = rpc.get_balance(None, None).unwrap();
18+
19+
thread::spawn(move || {
20+
cloned_rpc
21+
.generate_to_address(101, &cloned_address)
22+
.unwrap();
23+
24+
assert!(cloned_rpc.get_balance(None, None).unwrap() > initial_balance);
25+
})
26+
.join()
27+
.unwrap();
28+
29+
// Change made in other rpc connection should be available now.
30+
let changed_balance = rpc.get_balance(None, None).unwrap();
31+
assert!(changed_balance > initial_balance);
32+
33+
// Adding new blocks should add more funds.
34+
rpc.generate_to_address(101, &address).unwrap();
35+
assert!(rpc.get_balance(None, None).unwrap() > changed_balance);
36+
assert!(rpc.get_balance(None, None).unwrap() > initial_balance);
37+
}

tests/transaction.rs

+65-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,70 @@
11
//! Transaction related integration tests.
22
3+
use bitcoin_mock_rpc::{Client, RpcApiWrapper};
4+
use bitcoincore_rpc::{Auth, RpcApi};
5+
use std::thread;
6+
37
mod common;
8+
use common::test_common;
49

510
#[test]
6-
fn raw_transaction_test() {}
11+
fn send_to_address_multi_threaded() {
12+
// Bacause `thread::spawn` moves value to closure, cloning a new is needed. This is good,
13+
// because cloning an rpc struct should have a persistent ledger even though there are more than
14+
// one accessors.
15+
let rpc = Client::new("", Auth::None).unwrap();
16+
let cloned_rpc = rpc.clone();
17+
let address = rpc.get_new_address(None, None).unwrap().assume_checked();
18+
let deposit_address = test_common::create_address_from_witness();
19+
let cloned_deposit_address = deposit_address.clone();
20+
21+
rpc.generate_to_address(101, &address).unwrap();
22+
let initial_balance = rpc.get_balance(None, None).unwrap();
23+
let deposit_value = initial_balance / 4;
24+
25+
thread::spawn(move || {
26+
cloned_rpc
27+
.send_to_address(
28+
&cloned_deposit_address,
29+
deposit_value,
30+
None,
31+
None,
32+
None,
33+
None,
34+
None,
35+
None,
36+
)
37+
.unwrap();
38+
39+
assert_eq!(
40+
cloned_rpc.get_balance(None, None).unwrap(),
41+
initial_balance - deposit_value
42+
);
43+
})
44+
.join()
45+
.unwrap();
46+
47+
// Change made in other rpc connection should be available now.
48+
assert_eq!(
49+
rpc.get_balance(None, None).unwrap(),
50+
initial_balance - deposit_value
51+
);
52+
53+
// Adding new blocks should add more funds.
54+
rpc.send_to_address(
55+
&deposit_address,
56+
deposit_value,
57+
None,
58+
None,
59+
None,
60+
None,
61+
None,
62+
None,
63+
)
64+
.unwrap();
65+
assert_eq!(
66+
rpc.get_balance(None, None).unwrap(),
67+
initial_balance - deposit_value - deposit_value
68+
); // No multiplication over `Amount`.
69+
assert!(rpc.get_balance(None, None).unwrap() < initial_balance);
70+
}

0 commit comments

Comments
 (0)