summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2021-12-28 19:43:39 +0100
committerAntoine A <>2021-12-28 19:43:39 +0100
commit80005a6b383eeaf4593a25173727ba5c03fa2e74 (patch)
tree3f629166073fd747fc294f597b77199375f7c506
parent976967b98ec4120382e981f0d029cca30d68c908 (diff)
downloaddepolymerization-80005a6b383eeaf4593a25173727ba5c03fa2e74.tar.gz
depolymerization-80005a6b383eeaf4593a25173727ba5c03fa2e74.tar.bz2
depolymerization-80005a6b383eeaf4593a25173727ba5c03fa2e74.zip
btc-wire: add and test bouncing
-rw-r--r--btc-wire/src/bin/test.rs82
-rw-r--r--btc-wire/src/info.rs8
-rw-r--r--btc-wire/src/lib.rs38
-rw-r--r--btc-wire/src/main.rs191
-rw-r--r--btc-wire/src/rpc.rs36
-rw-r--r--script/setup.sh22
-rw-r--r--script/test_btc_fail.sh38
-rw-r--r--script/test_btc_stress.sh63
-rw-r--r--script/test_btc_wire.sh16
-rw-r--r--script/test_recover_db.sh12
-rw-r--r--test.conf2
11 files changed, 352 insertions, 156 deletions
diff --git a/btc-wire/src/bin/test.rs b/btc-wire/src/bin/test.rs
index f44495f..442e1a6 100644
--- a/btc-wire/src/bin/test.rs
+++ b/btc-wire/src/bin/test.rs
@@ -7,7 +7,6 @@ use btc_wire::{
rpc::{self, BtcRpc, Category},
rpc_utils::{default_data_dir, CLIENT, WIRE},
test::rand_key,
- BounceErr,
};
use owo_colors::OwoColorize;
@@ -160,7 +159,7 @@ pub fn main() {
// Send metadata
let msg = "J'aime le chocolat".as_bytes();
let id = client_rpc
- .send_op_return(&wire_addr, &test_amount, msg)
+ .send_op_return(&wire_addr, &test_amount, msg, false)
.unwrap();
// Check in mempool
assert!(
@@ -204,7 +203,7 @@ pub fn main() {
let before = client_rpc.get_balance().unwrap();
let send_id = client_rpc.send(&wire_addr, &test_amount, false).unwrap();
wait_for_tx(&mut client_rpc, &mut reserve_rpc, &[send_id]);
- let bounce_id = wire_rpc.bounce(&send_id, &bounce_fee).unwrap();
+ let bounce_id = wire_rpc.bounce(&send_id, &bounce_fee, &[]).unwrap();
wait_for_tx(&mut wire_rpc, &mut reserve_rpc, &[bounce_id]);
let bounce_tx_fee = wire_rpc.get_tx(&bounce_id).unwrap().details[0]
.fee
@@ -227,27 +226,65 @@ pub fn main() {
.send(&wire_addr, &Amount::from_sat(294), false)
.unwrap();
wait_for_tx(&mut client_rpc, &mut reserve_rpc, &[send_id]);
- assert!(match wire_rpc.bounce(&send_id, &bounce_fee) {
- Ok(_) => false,
- Err(err) => match err {
- BounceErr::AmountLessThanFee => true,
- _ => false,
- },
+ assert!(match wire_rpc.bounce(&send_id, &bounce_fee, &[]) {
+ Err(rpc::Error::RPC {
+ code: rpc::ErrorCode::RpcWalletInsufficientFunds,
+ msg: _,
+ }) => true,
+ _ => false,
});
});
+ runner.test("Bounce simple with metadata", || {
+ let before = client_rpc.get_balance().unwrap();
+ let send_id = client_rpc.send(&wire_addr, &test_amount, false).unwrap();
+ wait_for_tx(&mut client_rpc, &mut reserve_rpc, &[send_id]);
+ let bounce_id = wire_rpc
+ .bounce(&send_id, &bounce_fee, &[12, 34, 56, 78])
+ .unwrap();
+ wait_for_tx(&mut wire_rpc, &mut reserve_rpc, &[bounce_id]);
+ let bounce_tx_fee = wire_rpc.get_tx(&bounce_id).unwrap().details[0]
+ .fee
+ .unwrap()
+ .abs()
+ .to_unsigned()
+ .unwrap();
+ let send_tx_fee = client_rpc.get_tx(&send_id).unwrap().details[0]
+ .fee
+ .unwrap()
+ .abs()
+ .to_unsigned()
+ .unwrap();
+ wire_rpc.get_tx_op_return(&bounce_id).unwrap();
+ let after = client_rpc.get_balance().unwrap();
+ assert!(before >= after);
+ assert_eq!(before - after, bounce_tx_fee + bounce_fee + send_tx_fee);
+ });
+ runner.test("Bounce minimal amount with metadata", || {
+ let send_id = client_rpc
+ .send(&wire_addr, &Amount::from_sat(294), false)
+ .unwrap();
+ wait_for_tx(&mut client_rpc, &mut reserve_rpc, &[send_id]);
+ assert!(
+ match wire_rpc.bounce(&send_id, &bounce_fee, &[12, 34, 56]) {
+ Err(rpc::Error::RPC {
+ code: rpc::ErrorCode::RpcWalletError,
+ ..
+ }) => true,
+ _ => false,
+ }
+ );
+ });
runner.test("Bounce too small amount", || {
let send_id = client_rpc
.send(&wire_addr, &(Amount::from_sat(294) + bounce_fee), false)
.unwrap();
wait_for_tx(&mut client_rpc, &mut reserve_rpc, &[send_id]);
-
- assert!(match wire_rpc.bounce(&send_id, &bounce_fee) {
- Ok(_) => false,
- Err(err) => match err {
- BounceErr::RPC(rpc::Error::RPC { code, .. }) =>
- code == rpc::ErrorCode::RpcWalletInsufficientFunds,
- _ => false,
- },
+ assert!(match wire_rpc.bounce(&send_id, &bounce_fee, &[]) {
+ Err(rpc::Error::RPC {
+ code: rpc::ErrorCode::RpcWalletInsufficientFunds,
+ ..
+ }) => true,
+ _ => false,
});
});
runner.test("Bounce complex", || {
@@ -260,7 +297,12 @@ pub fn main() {
.into_iter()
.map(|addresses| {
client_rpc
- .send_custom(&[], addresses.iter().map(|addr| (addr, &test_amount)), None)
+ .send_custom(
+ &[],
+ addresses.iter().map(|addr| (addr, &test_amount)),
+ None,
+ false,
+ )
.unwrap()
})
.collect();
@@ -268,10 +310,10 @@ pub fn main() {
let before = client_rpc.get_balance().unwrap();
// Send a transaction with multiple input from multiple transaction of different outputs len
let send_id = client_rpc
- .send_custom(&txs, [(&wire_addr, &(test_amount * 3))], None)
+ .send_custom(&txs, [(&wire_addr, &(test_amount * 3))], None, false)
.unwrap();
wait_for_tx(&mut client_rpc, &mut reserve_rpc, &[send_id]);
- let bounce_id = wire_rpc.bounce(&send_id, &bounce_fee).unwrap();
+ let bounce_id = wire_rpc.bounce(&send_id, &bounce_fee, &[]).unwrap();
wait_for_tx(&mut wire_rpc, &mut reserve_rpc, &[bounce_id]);
let after = client_rpc.get_balance().unwrap();
let bounce_tx_fee = wire_rpc.get_tx(&bounce_id).unwrap().details[0]
diff --git a/btc-wire/src/info.rs b/btc-wire/src/info.rs
index db1a941..cc61b04 100644
--- a/btc-wire/src/info.rs
+++ b/btc-wire/src/info.rs
@@ -17,7 +17,7 @@ pub enum DecodeErr {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Info {
Transaction { wtid: [u8; 32], url: Url },
- Bounce { id: Txid },
+ Bounce { bounced: Txid },
}
// We leave a potential special meaning for u8::MAX
@@ -34,7 +34,7 @@ pub fn encode_info(info: &Info) -> Vec<u8> {
buffer.extend_from_slice(&packed);
return buffer;
}
- Info::Bounce { id } => {
+ Info::Bounce { bounced: id } => {
buffer.push(BOUNCE_BYTE);
buffer.extend_from_slice(id.as_ref());
}
@@ -63,7 +63,7 @@ pub fn decode_info(bytes: &[u8]) -> Result<Info, DecodeErr> {
})
}
BOUNCE_BYTE => Ok(Info::Bounce {
- id: Txid::from_slice(&bytes[1..])?,
+ bounced: Txid::from_slice(&bytes[1..])?,
}),
unknown => Err(DecodeErr::UnknownFirstByte(unknown)),
}
@@ -100,7 +100,7 @@ mod test {
for _ in 0..4 {
let id = rand_key();
let info = Info::Bounce {
- id: Txid::from_slice(&id).unwrap(),
+ bounced: Txid::from_slice(&id).unwrap(),
};
let encode = encode_info(&info);
let decoded = decode_info(&encode).unwrap();
diff --git a/btc-wire/src/lib.rs b/btc-wire/src/lib.rs
index 2317fa2..ed7d697 100644
--- a/btc-wire/src/lib.rs
+++ b/btc-wire/src/lib.rs
@@ -5,21 +5,11 @@ use rpc::{BtcRpc, Category, TransactionFull};
use rpc_utils::{segwit_min_amount, sender_address};
use segwit::{decode_segwit_msg, encode_segwit_key};
+pub mod config;
pub mod rpc;
pub mod rpc_utils;
pub mod segwit;
pub mod test;
-pub mod config;
-
-#[derive(Debug, thiserror::Error)]
-pub enum BounceErr {
- #[error("Expected 'receive' transaction")]
- NotAReceiveTransaction,
- #[error("Transaction amount less than bounce fee")]
- AmountLessThanFee,
- #[error(transparent)]
- RPC(#[from] rpc::Error),
-}
#[derive(Debug, thiserror::Error)]
pub enum GetSegwitErr {
@@ -92,10 +82,11 @@ impl BtcRpc {
to: &Address,
amount: &Amount,
metadata: &[u8],
+ subtract_fee: bool,
) -> rpc::Result<Txid> {
assert!(metadata.len() > 0, "No medatata");
assert!(metadata.len() <= 80, "Max 80 bytes");
- self.send_custom(&[], [(to, amount)], Some(metadata))
+ self.send_custom(&[], [(to, amount)], Some(metadata), subtract_fee)
}
/// Get detailed information about an in-wallet transaction and its op_return metadata
@@ -124,22 +115,25 @@ impl BtcRpc {
/// There is no reliable way to bounce a transaction as you cannot know if the addresses
/// used are shared or come from a third-party service. We only send back to the first input
/// address as a best-effort gesture.
- pub fn bounce(&mut self, id: &Txid, bounce_fee: &Amount) -> Result<Txid, BounceErr> {
+ pub fn bounce(
+ &mut self,
+ id: &Txid,
+ bounce_fee: &Amount,
+ metadata: &[u8],
+ ) -> Result<Txid, rpc::Error> {
let full = self.get_tx(id)?;
let detail = &full.details[0];
- if detail.category != Category::Receive {
- return Err(BounceErr::NotAReceiveTransaction);
- }
+ assert!(detail.category == Category::Receive);
let amount = detail.amount.to_unsigned().unwrap();
- if amount <= *bounce_fee {
- return Err(BounceErr::AmountLessThanFee);
- }
-
let sender = sender_address(self, &full)?;
- let bounce_amount = amount - *bounce_fee;
+ let bounce_amount = Amount::from_sat(amount.as_sat().saturating_sub(bounce_fee.as_sat()));
// Send refund making recipient pay the transaction fees
- let id = self.send(&sender, &bounce_amount, true)?;
+ let id = if metadata.is_empty() {
+ self.send(&sender, &bounce_amount, true)?
+ } else {
+ self.send_op_return(&sender, &bounce_amount, metadata, true)?
+ };
Ok(id)
}
}
diff --git a/btc-wire/src/main.rs b/btc-wire/src/main.rs
index e728b7f..29ad7ca 100644
--- a/btc-wire/src/main.rs
+++ b/btc-wire/src/main.rs
@@ -1,7 +1,7 @@
use bitcoin::{hashes::Hash, Address, Amount as BtcAmount, BlockHash, SignedAmount, Txid};
use btc_wire::{
config::BitcoinConfig,
- rpc::{BtcRpc, Category},
+ rpc::{self, BtcRpc, Category, ErrorCode},
rpc_utils::{default_data_dir, sender_address},
segwit::DecodeSegWitErr,
GetOpReturnErr, GetSegwitErr,
@@ -24,7 +24,7 @@ use url::Url;
use crate::{
fail_point::fail_point,
info::{encode_info, Info},
- status::TxStatus,
+ status::{BounceStatus, TxStatus},
};
mod fail_point;
@@ -68,7 +68,7 @@ fn last_hash(db: &mut Client) -> Result<Option<BlockHash>, postgres::Error> {
/// Listen for new proposed transactions and announce them on the bitcoin network
fn worker(mut rpc: AutoReconnectRPC, mut db: AutoReconnectSql, config: &Config) {
// Send a transaction on the blockchain, return true if more transactions with the same status remains
- fn send_tx(
+ fn send(
db: &mut Client,
rpc: &mut BtcRpc,
status: TxStatus,
@@ -93,7 +93,7 @@ fn worker(mut rpc: AutoReconnectRPC, mut db: AutoReconnectSql, config: &Config)
let metadata = encode_info(&info);
fail_point("Skip send_op_return", 0.2)?;
- match rpc.send_op_return(&addr, &amount, &metadata) {
+ match rpc.send_op_return(&addr, &amount, &metadata, false) {
Ok(tx_id) => {
fail_point("Fail update db", 0.2)?;
tx.execute(
@@ -101,7 +101,7 @@ fn worker(mut rpc: AutoReconnectRPC, mut db: AutoReconnectSql, config: &Config)
&[&(TxStatus::Sent as i16), &tx_id.as_ref(), &id],
)?;
let amount = btc_amount_to_taler_amount(&amount.to_signed().unwrap());
- info!("SEND >> {} {} in {}", addr, amount, tx_id);
+ info!("send {} {} in {}", addr, amount, tx_id);
}
Err(e) => {
info!("sender: RPC - {}", e);
@@ -116,6 +116,54 @@ fn worker(mut rpc: AutoReconnectRPC, mut db: AutoReconnectSql, config: &Config)
Ok(row.is_some())
}
+ // Bounce a transaction on the blockchain, return true if more bounce with the same status remains
+ fn bounce(
+ db: &mut Client,
+ rpc: &mut BtcRpc,
+ status: BounceStatus,
+ fee: &BtcAmount,
+ ) -> Result<bool, Box<dyn std::error::Error>> {
+ assert!(status == BounceStatus::Delayed || status == BounceStatus::Requested);
+ let mut tx = db.transaction()?;
+ // We lock the row with FOR UPDATE to prevent sending same transaction multiple time
+ let row = tx.query_opt(
+ "SELECT id, bounced FROM bounce WHERE status=$1 LIMIT 1 FOR UPDATE",
+ &[&(status as i16)],
+ )?;
+ if let Some(row) = &row {
+ let id: i32 = row.get(0);
+ let bounced: Txid = Txid::from_slice(row.get(1))?;
+ let info = Info::Bounce { bounced };
+ let metadata = encode_info(&info);
+
+ fail_point("Skip send_op_return", 0.2)?;
+ match rpc.bounce(&bounced, &fee, &metadata) {
+ Ok(it) => {
+ info!("bounce {} in {}", &bounced, &it);
+ tx.execute(
+ "UPDATE bounce SET txid = $1, status = $2 WHERE id = $3",
+ &[&it.as_ref(), &(BounceStatus::Sent as i16), &id],
+ )?;
+ }
+ Err(err) => match err {
+ rpc::Error::RPC {
+ code: ErrorCode::RpcWalletInsufficientFunds | ErrorCode::RpcWalletError,
+ msg,
+ } => {
+ info!("ignore bounce {} because {}", &bounced, msg);
+ tx.execute(
+ "UPDATE bounce SET status = $1 WHERE id = $2",
+ &[&(BounceStatus::Ignored as i16), &id],
+ )?;
+ }
+ _ => Err(err)?,
+ },
+ }
+ tx.commit()?;
+ }
+ Ok(row.is_some())
+ }
+
// TODO check if transactions are abandoned
let mut failed = false;
@@ -143,29 +191,15 @@ fn worker(mut rpc: AutoReconnectRPC, mut db: AutoReconnectSql, config: &Config)
// As we are now in sync with the blockchain if a transaction is in requested or delayed state it have not been sent
// Send delayed transactions
- while send_tx(db, rpc, TxStatus::Delayed)? {}
+ while send(db, rpc, TxStatus::Delayed)? {}
// Send requested transactions
- while send_tx(db, rpc, TxStatus::Requested)? {}
+ while send(db, rpc, TxStatus::Requested)? {}
- // Check if already bounced
- /*if nb > 0 && false {
- // We do not handle failures, bouncing is done in a best effort manner
- match rpc.bounce(&id, &BtcAmount::from_sat(config.bounce_fee)) {
- Ok(it) => {
- info!("bounce {} in {}", &id, &it);
- db.execute(
- "UPDATE bounce SET txid = $1 WHERE bounced = $2",
- &[&it.as_ref(), &id.as_ref()],
- )?;
- }
- Err(err) => match err {
- BounceErr::AmountLessThanFee => { /* Ignore */ }
- BounceErr::NotAReceiveTransaction | BounceErr::RPC(_) => {
- Err(err)?
- }
- },
- }
- }*/
+ let bounce_fee = BtcAmount::from_sat(config.bounce_fee);
+ // Send delayed bounce
+ while bounce(db, rpc, BounceStatus::Delayed, &bounce_fee)? {}
+ // Send requested bounce
+ while bounce(db, rpc, BounceStatus::Requested, &bounce_fee)? {}
Ok(())
})();
@@ -186,22 +220,22 @@ fn sync_chain(
) -> Result<(), Box<dyn std::error::Error>> {
// Get stored last_hash
let last_hash = last_hash(db)?;
- let confirmation = config.confirmation;
+ let min_confirmations = config.confirmation;
// Get a set of transactions ids to parse
- let (txs, lastblock): (HashMap<Txid, Category>, BlockHash) = {
+ let (txs, lastblock): (HashMap<Txid, (Category, i32)>, BlockHash) = {
// Get all transactions made since this block
- let list = rpc.list_since_block(last_hash.as_ref(), confirmation, true)?;
+ let list = rpc.list_since_block(last_hash.as_ref(), min_confirmations, true)?;
// Only keep ids and category
- let txs: HashMap<Txid, Category> = list
+ let txs = list
.transactions
.into_iter()
- .map(|tx| (tx.txid, tx.category))
+ .map(|tx| (tx.txid, (tx.category, tx.confirmations)))
.collect();
(txs, list.lastblock)
};
- for (id, category) in txs {
+ for (id, (category, confirmations)) in txs {
match category {
Category::Send => {
match rpc.get_tx_op_return(&id) {
@@ -211,14 +245,15 @@ fn sync_chain(
Ok(info) => match info {
Info::Transaction { wtid, url } => {
let row = tx.query_opt(
- "SELECT status, id FROM tx_out WHERE wtid=$1 FOR UPDATE",
+ "SELECT id, status FROM tx_out WHERE wtid=$1 FOR UPDATE",
&[&wtid.as_ref()],
)?;
if let Some(row) = row {
- let status: i16 = row.get(0);
- let _id: i32 = row.get(1);
+ let _id: i32 = row.get(0);
+ let status: i16 = row.get(1);
let status: TxStatus =
TxStatus::try_from(status as u8).unwrap();
+ // TODO match
if status != TxStatus::Sent {
tx.execute(
"UPDATE tx_out SET status=$1 where id=$2",
@@ -235,7 +270,7 @@ fn sync_chain(
full.details[0].address.as_ref().unwrap();
let amount =
btc_amount_to_taler_amount(&full.amount);
- info!("SEND >> {} {} in {}", addr, amount, &id);
+ info!("send {} {} in {}", addr, amount, &id);
}
}
} else {
@@ -249,16 +284,53 @@ fn sync_chain(
OsRng.fill_bytes(&mut request_uid);
let nb = tx.execute(
"INSERT INTO tx_out (_date, amount, wtid, debit_acc, credit_acc, exchange_url, status, txid, request_uid) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (wtid) DO NOTHING",
- &[&date, &amount.to_string(), &wtid.as_ref(), &btc_payto_url(&debit_addr).as_ref(), &btc_payto_url(credit_addr).as_ref(), &config.base_url.as_ref(), &(TxStatus::Sent as i16), &id.as_ref(), &request_uid.as_ref()
- ],
- )?;
+ &[&date, &amount.to_string(), &wtid.as_ref(), &btc_payto_url(&debit_addr).as_ref(), &btc_payto_url(credit_addr).as_ref(), &config.base_url.as_ref(), &(TxStatus::Sent as i16), &id.as_ref(), &request_uid.as_ref()],
+ )?;
if nb > 0 {
- warn!("watcher: found an unregistered outgoing address {} {} in tx {}", crockford_base32_encode(&wtid), &url, id);
+ warn!(
+ "recovered {} {} in tx {}",
+ crockford_base32_encode(&wtid),
+ &url,
+ id
+ );
}
}
}
- Info::Bounce { .. } => {
- // TODO
+ Info::Bounce { bounced } => {
+ let row = tx.query_opt(
+ "SELECT id, status FROM bounce WHERE bounced=$1 FOR UPDATE",
+ &[&bounced.as_ref()],
+ )?;
+ if let Some(row) = row {
+ let _id: i32 = row.get(0);
+ let status: i16 = row.get(1);
+ let status: BounceStatus =
+ BounceStatus::try_from(status as u8).unwrap();
+ assert!(status != BounceStatus::Ignored); // TODO
+ if status != BounceStatus::Sent {
+ tx.execute(
+ "UPDATE bounce SET status=$1 where id=$2",
+ &[&(BounceStatus::Sent as i16), &_id],
+ )?;
+ if status == BounceStatus::Delayed
+ || status == BounceStatus::Requested
+ {
+ warn!(
+ "watcher: bounce {} have been recovered automatically",
+ _id
+ );
+ info!("bounce {} in {}", &bounced, &id);
+ }
+ }
+ } else {
+ let nb = tx.execute(
+ "INSERT INTO bounce (bounced, txid, status) VALUES ($1, $2, $3) ON CONFLICT (txid) DO NOTHING",
+ &[&bounced.as_ref(), &id.as_ref(), &(BounceStatus::Sent as i16)],
+ )?;
+ if nb > 0 {
+ info!("recovered bounce {} in {}", &bounced, &id);
+ }
+ }
}
},
Err(err) => warn!("send: decode-info {} - {}", id, err),
@@ -271,9 +343,9 @@ fn sync_chain(
},
}
}
- Category::Receive => match rpc.get_tx_segwit_key(&id) {
- Ok((full, reserve_pub)) => {
- if full.confirmations >= confirmation as i32 {
+ Category::Receive if confirmations >= min_confirmations as i32 => {
+ match rpc.get_tx_segwit_key(&id) {
+ Ok((full, reserve_pub)) => {
let debit_addr = sender_address(rpc, &full)?;
let credit_addr = full.details[0].address.as_ref().unwrap();
let date = SystemTime::UNIX_EPOCH + Duration::from_secs(full.time);
@@ -282,22 +354,25 @@ fn sync_chain(
&date, &amount.to_string(), &reserve_pub.as_ref(), &btc_payto_url(&debit_addr).as_ref(), &btc_payto_url(credit_addr).as_ref()
])?;
if nb > 0 {
- info!("{} << {} {} in {}", &debit_addr, &credit_addr, &amount, &id);
+ info!(
+ "receive {} << {} {} in {}",
+ &debit_addr, &credit_addr, &amount, &id
+ );
}
}
+ Err(err) => match err {
+ GetSegwitErr::Decode(
+ DecodeSegWitErr::MissingSegWitAddress | DecodeSegWitErr::NoMagicIdMatch,
+ ) => {
+ // Request a bounce
+ db.execute("INSERT INTO bounce (bounced) VALUES ($1) ON CONFLICT (bounced) DO NOTHING", &[&id.as_ref()])?;
+ }
+ err => warn!("receive: {} {}", id, err),
+ },
}
- Err(err) => match err {
- GetSegwitErr::Decode(
- DecodeSegWitErr::MissingSegWitAddress | DecodeSegWitErr::NoMagicIdMatch,
- ) => {
- // Request a bounce
- db.execute("INSERT INTO bounce (bounced) VALUES ($1) ON CONFLICT (bounced) DO NOTHING", &[&id.as_ref()])?;
- }
- err => warn!("receive: {} {}", id, err),
- },
- },
- Category::Generate | Category::Immature | Category::Orphan => {
- // Ignore coinbase transactions
+ }
+ _ => {
+ // Ignore coinbase and unconfirmed send transactions
}
}
}
diff --git a/btc-wire/src/rpc.rs b/btc-wire/src/rpc.rs
index 8121e1e..bac9a6b 100644
--- a/btc-wire/src/rpc.rs
+++ b/btc-wire/src/rpc.rs
@@ -206,7 +206,13 @@ impl BtcRpc {
inputs: impl IntoIterator<Item = &'a Txid>,
outputs: impl IntoIterator<Item = (&'b Address, &'c Amount)>,
data: Option<&[u8]>,
+ subtract_fee: bool,
) -> Result<Txid> {
+ let mut outputs: Vec<Value> = outputs
+ .into_iter()
+ .map(|(addr, amount)| json!({&addr.to_string(): amount.as_btc()}))
+ .collect();
+ let len = outputs.len();
let hex: String = self.call(
"createrawtransaction",
&[
@@ -218,18 +224,26 @@ impl BtcRpc {
.collect(),
),
Value::Array({
- let mut vec: Vec<Value> = outputs
- .into_iter()
- .map(|(addr, amount)| json!({&addr.to_string(): amount.as_btc()}))
- .collect();
if let Some(data) = data {
- vec.push(json!({ "data".to_string(): data.to_hex() }));
+ outputs.push(json!({ "data".to_string(): data.to_hex() }));
}
- vec
+ outputs
}),
],
)?;
- let funded: HexWrapper = self.call("fundrawtransaction", &[hex])?;
+ let funded: HexWrapper = self.call(
+ "fundrawtransaction",
+ &(
+ hex,
+ FundOption {
+ subtract_fee_from_outputs: if subtract_fee {
+ (0..len).into_iter().collect()
+ } else {
+ vec![]
+ },
+ },
+ ),
+ )?;
let signed: HexWrapper = self.call("signrawtransactionwithwallet", &[&funded.hex])?;
self.call("sendrawtransaction", &[&signed.hex])
}
@@ -252,7 +266,13 @@ impl BtcRpc {
}
}
-#[derive(Debug, serde::Deserialize, serde::Serialize)]
+#[derive(Debug, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct FundOption {
+ pub subtract_fee_from_outputs: Vec<usize>,
+}
+
+#[derive(Debug, serde::Deserialize)]
pub struct Wallet {
pub name: String,
pub warning: Option<String>,
diff --git a/script/setup.sh b/script/setup.sh
index 827ff56..5ef9d1e 100644
--- a/script/setup.sh
+++ b/script/setup.sh
@@ -46,7 +46,6 @@ function setup_btc() {
WIRE=`$BTC_CLI -rpcwallet=wire getnewaddress`
mine_btc 101
$BTC_CLI -rpcwallet=reserve sendtoaddress $CLIENT 10 > /dev/null
- $BTC_CLI -rpcwallet=reserve sendtoaddress $WIRE 1 > /dev/null
mine_btc
}
@@ -67,10 +66,14 @@ function next_btc() {
# Check client and wire balance
function check_balance() {
- CLIENT_BALANCE=`$BTC_CLI -rpcwallet=client getbalance`
- WIRE_BALANCE=`$BTC_CLI -rpcwallet=wire getbalance`
- if [ "$CLIENT_BALANCE" != "$1" ] || [ "$WIRE_BALANCE" != "${2:-$WIRE_BALANCE}" ]; then
- echo "expected: client $1 wire ${2:-$WIRE_BALANCE} got: client $CLIENT_BALANCE wire $WIRE_BALANCE"
+ local CLIENT_BALANCE=`$BTC_CLI -rpcwallet=client getbalance`
+ local WIRE_BALANCE=`$BTC_CLI -rpcwallet=wire getbalance`
+ local CLIENT="${1:-*}"
+ if [ "$1" == "*" ]; then
+ local CLIENT="$CLIENT_BALANCE"
+ fi
+ if [ "$CLIENT_BALANCE" != "$CLIENT" ] || [ "$WIRE_BALANCE" != "${2:-$WIRE_BALANCE}" ]; then
+ echo "expected: client $CLIENT wire ${2:-$WIRE_BALANCE} got: client $CLIENT_BALANCE wire $WIRE_BALANCE"
exit 1
fi
}
@@ -109,16 +112,17 @@ function gateway() {
# usage: check_delta endpoint nb_txs amount_sequence
function check_delta() {
ALL=`curl -s ${BANK_ENDPOINT}history/$1`
+ PRE=${3:-0.0000}
for n in `$2`; do
- if ! `echo $ALL | grep BTC:0.0000$n > /dev/null`; then
- echo -n " missing tx with amount: BTC:0.0000$n"
+ if ! `echo $ALL | grep BTC:$PRE$n > /dev/null`; then
+ echo -n " missing tx with amount: BTC:$PRE$n"
return 1
fi
done
- NB=`echo $ALL | grep -o BTC:0.0000 | wc -l`
+ NB=`echo $ALL | grep -o BTC:$PRE | wc -l`
EXPECTED=`$2 | wc -w`
if [ "$EXPECTED" != "$NB" ]; then
echo -n " expected: $EXPECTED txs found $NB"
return 1
fi
-} \ No newline at end of file
+}
diff --git a/script/test_btc_fail.sh b/script/test_btc_fail.sh
index 3602364..c9fa930 100644
--- a/script/test_btc_fail.sh
+++ b/script/test_btc_fail.sh
@@ -35,26 +35,22 @@ echo ""
SEQ="seq 10 40"
-function check() {
- check_delta "$1?delta=-100" "$SEQ"
-}
-
echo "----- Handle incoming -----"
echo -n "Making wire transfer to exchange:"
for n in `$SEQ`; do
- btc-wire-cli -d $BTC_DIR transfer 0.0000$n
+ btc-wire-cli -d $BTC_DIR transfer 0.000$n
mine_btc # Mine transactions
done
next_btc # Trigger btc_wire
echo " OK"
echo -n "Requesting exchange incoming transaction list:"
-check incoming
+check_delta "incoming?delta=-100" "$SEQ" "0.000"
echo " OK"
echo -n "Check balance:"
-check_balance 9.99897979 1.00077500
+check_balance 9.99200479 0.00775000
echo " OK"
echo "----- Handle outgoing -----"
@@ -66,14 +62,34 @@ for n in `$SEQ`; do
-C payto://bitcoin/$CLIENT \
-a BTC:0.0000$n > /dev/null
done
-sleep 15
+sleep 20
mine_btc # Mine transactions
echo " OK"
echo -n "Requesting exchange outgoing transaction list:"
-check outgoing
+check_delta "outgoing?delta=-100" "$SEQ"
+echo " OK"
+
+echo -n "Check balance:"
+check_balance 9.99277979 0.00691331
+echo " OK"
+
+echo "----- Handle bounce -----"
+
+echo -n "Clear wire wallet:"
+$BTC_CLI -rpcwallet=wire sendtoaddress $CLIENT `$BTC_CLI -rpcwallet=wire getbalance` "" "" true > /dev/null
+echo " OK"
+
+echo -n "Making incomplete wire transfer to exchange:"
+for n in `$SEQ`; do
+ $BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.000$n > /dev/null
+ mine_btc
+done
+sleep 20
echo " OK"
echo -n "Check balance:"
-check_balance 9.99975479
-echo " OK" \ No newline at end of file
+check_balance "*" 0.00031000
+echo " OK"
+
+echo "All tests passed" \ No newline at end of file
diff --git a/script/test_btc_stress.sh b/script/test_btc_stress.sh
index b3b6ed7..6b6023f 100644
--- a/script/test_btc_stress.sh
+++ b/script/test_btc_stress.sh
@@ -28,22 +28,18 @@ init_btc
echo "Init bitcoin regtest"
setup_btc
echo "Start btc-wire stressed"
-stressed_btc_wire
+btc_wire
echo "Start gateway"
gateway
echo ""
SEQ="seq 10 99"
-function check() {
- check_delta "$1?delta=-100" "$SEQ"
-}
-
echo "----- Handle incoming -----"
echo -n "Making wire transfer to exchange:"
for n in `$SEQ`; do
- btc-wire-cli -d $BTC_DIR transfer 0.0000$n
+ btc-wire-cli -d $BTC_DIR transfer 0.000$n
mine_btc # Mine transactions
done
sleep 3 # Give time for btc_wire worker to process
@@ -52,11 +48,11 @@ sleep 3 # Give time for btc_wire worker to process
echo " OK"
echo -n "Requesting exchange incoming transaction list:"
-check incoming
+check_delta "incoming?delta=-100" "$SEQ" "0.000"
echo " OK"
echo -n "Check balance:"
-check_balance 9.99438310 1.00490500
+check_balance 9.95023810 0.04905000
echo " OK"
echo "----- Handle outgoing -----"
@@ -73,32 +69,71 @@ next_btc # Mine transactions
echo " OK"
echo -n "Requesting exchange outgoing transaction list:"
-check outgoing
+check_delta "outgoing?delta=-100" "$SEQ"
echo " OK"
echo -n "Check balance:"
-check_balance 9.99928810
+check_balance 9.95514310
echo " OK"
next_btc # Mine transactions
+
+echo "----- Recover DB -----"
+
+echo "Reset database"
+reset_db # Clear database tables
+mine_btc # Trigger worker
+sleep 10
+
+echo -n "Requesting exchange incoming transaction list:"
+check_delta "incoming?delta=-100" "$SEQ" "0.000"
+echo " OK"
+
+echo -n "Requesting exchange outgoing transaction list:"
+check_delta "outgoing?delta=-100" "$SEQ"
+echo " OK"
+
+echo -n "Check balance:"
+# Balance should not have changed
+check_balance 9.95514310
+echo " OK"
+
+echo "----- Handle bounce -----"
+
+echo -n "Clear wire wallet:"
+$BTC_CLI -rpcwallet=wire sendtoaddress $CLIENT `$BTC_CLI -rpcwallet=wire getbalance` "" "" true > /dev/null
+echo " OK"
+
+echo -n "Making incomplete wire transfer to exchange:"
+for n in `$SEQ`; do
+ $BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.000$n > /dev/null
+ mine_btc
+done
+sleep 20
+echo " OK"
+
+echo -n "Check balance:"
+check_balance "*" 0.00090000
+echo " OK"
+
echo "----- Recover DB -----"
echo "Reset database"
reset_db # Clear database tables
mine_btc # Trigger worker
-sleep 3
+sleep 10
echo -n "Requesting exchange incoming transaction list:"
-check incoming
+check_delta "incoming?delta=-100" "$SEQ" "0.000"
echo " OK"
echo -n "Requesting exchange outgoing transaction list:"
-check outgoing
+check_delta "outgoing?delta=-100" "$SEQ"
echo " OK"
echo -n "Check balance:"
# Balance should not have changed
-check_balance 9.99928810
+check_balance "*" 0.00090000
echo " OK"
echo "All tests passed" \ No newline at end of file
diff --git a/script/test_btc_wire.sh b/script/test_btc_wire.sh
index 7795a0e..442dcf3 100644
--- a/script/test_btc_wire.sh
+++ b/script/test_btc_wire.sh
@@ -33,25 +33,33 @@ echo "Start gateway"
gateway
echo ""
-echo "---- Gateway API -----"
+echo "---- Receive -----"
echo -n "Making wire transfer to exchange:"
btc-wire-cli -d $BTC_DIR transfer 0.00004
next_btc
-check_balance 9.99995209 1.00004000
+check_balance 9.99995209 0.00004000
echo " OK"
echo -n "Requesting exchange incoming transaction list:"
taler-exchange-wire-gateway-client -b $BANK_ENDPOINT -i | grep BTC:0.00004 > /dev/null
echo " OK"
+echo "---- Bounce-----"
+$BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.00042
+next_btc
+check_balance 9.99993883 0.00005000
+
+echo "---- Send -----"
+
echo -n "Making wire transfer from exchange:"
taler-exchange-wire-gateway-client \
-b $BANK_ENDPOINT \
-C payto://bitcoin/$CLIENT \
-a BTC:0.00002 > /dev/null
-next_btc
-check_balance 9.99997209 1.00001801
+sleep 0.3
+mine_btc
+check_balance 9.99995883 0.00002801
echo " OK"
echo -n "Requesting exchange's outgoing transaction list:"
diff --git a/script/test_recover_db.sh b/script/test_recover_db.sh
index 3bfc9f9..d90a541 100644
--- a/script/test_recover_db.sh
+++ b/script/test_recover_db.sh
@@ -37,7 +37,7 @@ echo "----- With DB -----"
echo "Making wire transfer to exchange:"
btc-wire-cli -d $BTC_DIR transfer 0.000042
next_btc
-check_balance 9.99995009 1.00004200
+check_balance 9.99995009 0.00004200
echo -n "Requesting exchange incoming transaction list:"
taler-exchange-wire-gateway-client -b $BANK_ENDPOINT -i | grep BTC:0.000042 > /dev/null && echo " OK" || echo " Failed"
@@ -45,10 +45,12 @@ echo "----- Without DB -----"
echo "Stop database"
sudo service postgresql stop > /dev/null
+echo "Making incomplete wire transfer to exchange"
+$BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.00042 &> /dev/null
echo "Making wire transfer to exchange:"
btc-wire-cli -d $BTC_DIR transfer 0.00004
next_btc
-check_balance 9.99990218 1.00008200
+check_balance 9.99948077 0.00050200
echo -n "Requesting exchange incoming transaction list:"
taler-exchange-wire-gateway-client -b $BANK_ENDPOINT -i 2>&1 | grep -q "504" && echo " OK" || echo " Failed"
@@ -65,8 +67,8 @@ taler-exchange-wire-gateway-client \
-C payto://bitcoin/$CLIENT \
-a BTC:0.00002 > /dev/null
sleep 1
-next_btc
-check_balance 9.99992218 1.00006001
+mine_btc
+check_balance 9.99990892 0.00007001
echo " OK"
echo -n "Requesting exchange's outgoing transaction list:"
@@ -95,6 +97,6 @@ done
echo ""
# Balance should not have changed
-check_balance 9.99992218 1.00006001
+check_balance 9.99990892 0.00007001
echo "All tests passed" \ No newline at end of file
diff --git a/test.conf b/test.conf
index d3f7053..e59092f 100644
--- a/test.conf
+++ b/test.conf
@@ -9,4 +9,4 @@ PAYTO = payto://bitcoin/bcrt1qgkgxkjj27g3f7s87mcvjjsghay7gh34cx39prj
CONFIRMATION = 1
BTC_WALLET = wire
BTC_DATA_DIR = ~/.bitcoin
-BOUNCE_FEE = 0 \ No newline at end of file
+BOUNCE_FEE = 1000 \ No newline at end of file