depolymerization

wire gateway for Bitcoin/Ethereum
Log | Files | Refs | Submodules | README | LICENSE

commit 77018cb9ffa12d861b127b6b16564357958ed6dd
parent 7b62eb839c99aa99868858fd5a974197510ad748
Author: Antoine A <>
Date:   Wed, 16 Mar 2022 17:54:57 +0100

Use a common metadata format

Diffstat:
MCargo.lock | 3+--
MREADME.md | 2+-
Mbtc-wire/Cargo.toml | 2--
Mbtc-wire/src/lib.rs | 1-
Mbtc-wire/src/loops/worker.rs | 10+++++++---
Dbtc-wire/src/metadata.rs | 128-------------------------------------------------------------------------------
Mcommon/Cargo.toml | 3++-
Mcommon/src/lib.rs | 3++-
Acommon/src/metadata.rs | 179+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Meth-wire/Cargo.toml | 2--
Meth-wire/src/lib.rs | 5++---
Meth-wire/src/loops/worker.rs | 7++++---
Deth-wire/src/metadata.rs | 177-------------------------------------------------------------------------------
Minstrumentation/src/btc.rs | 6+++---
Minstrumentation/src/eth.rs | 6+++---
15 files changed, 204 insertions(+), 330 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -138,7 +138,6 @@ dependencies = [ "serde_json", "serde_repr", "thiserror", - "uri-pack", ] [[package]] @@ -264,6 +263,7 @@ dependencies = [ "serde_with", "thiserror", "time", + "uri-pack", "url", "zeroize", ] @@ -518,7 +518,6 @@ dependencies = [ "serde_json", "serde_repr", "thiserror", - "uri-pack", ] [[package]] diff --git a/README.md b/README.md @@ -193,7 +193,7 @@ Only the wire adapter need to have the password stored in its environment. Wire log use an ASCII code for transaction logs: -- `<<` for an credit +- `<<` for a credit - `>>` for a debit - `||` for a bounce diff --git a/btc-wire/Cargo.toml b/btc-wire/Cargo.toml @@ -25,8 +25,6 @@ serde_json = "1.0.79" serde_repr = "0.1.7" # Error macros thiserror = "1.0.30" -# Optimized uri binary format -uri-pack = { path = "../uri-pack" } base64 = "0.13.0" # Common lib common = { path = "../common" } diff --git a/btc-wire/src/lib.rs b/btc-wire/src/lib.rs @@ -30,7 +30,6 @@ use segwit::{decode_segwit_msg, encode_segwit_key}; use taler_utils::taler_to_btc; pub mod btc_config; -pub mod metadata; pub mod rpc; pub mod rpc_utils; pub mod segwit; diff --git a/btc-wire/src/loops/worker.rs b/btc-wire/src/loops/worker.rs @@ -22,7 +22,6 @@ use std::{ use bitcoin::{hashes::Hash, Amount as BtcAmount, BlockHash, Txid}; use btc_wire::{ - metadata::OutMetadata, rpc::{self, AutoRpcWallet, Category, ErrorCode, Rpc, Transaction}, rpc_utils::sender_address, taler_utils::{btc_payto_url, btc_to_taler}, @@ -31,6 +30,7 @@ use btc_wire::{ use common::{ api_common::base32, log::log::{error, info, warn}, + metadata::OutMetadata, postgres, reconnect::AutoReconnectDb, sql::{sql_array, sql_url}, @@ -536,7 +536,9 @@ fn sync_chain_outgoing( OutMetadata::Withdraw { wtid, .. } => { return sync_chain_withdraw(id, &full, &wtid, rpc, db, confirmations, state); } - OutMetadata::Bounce { bounced } => sync_chain_bounce(id, &bounced, db, confirmations)?, + OutMetadata::Bounce { bounced } => { + sync_chain_bounce(id, &Txid::from_inner(bounced), db, confirmations)? + } }, Ok((_, Err(e))) => warn!("send: decode-info {} - {}", id, e), Err(e) => match e { @@ -584,7 +586,9 @@ fn bounce(db: &mut Client, rpc: &mut Rpc, fee: &BtcAmount) -> LoopResult<bool> { if let Some(row) = &row { let id: i32 = row.get(0); let bounced: Txid = sql_txid(row, 1); - let metadata = OutMetadata::Bounce { bounced }; + let metadata = OutMetadata::Bounce { + bounced: *bounced.as_inner(), + }; match rpc.bounce(&bounced, fee, Some(&metadata.encode())) { Ok(it) => { diff --git a/btc-wire/src/metadata.rs b/btc-wire/src/metadata.rs @@ -1,128 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2022 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ -use bitcoin::{hashes::Hash, Txid}; -use common::url::Url; - -#[derive(Debug, Clone, Copy, thiserror::Error)] -pub enum DecodeErr { - #[error("Unknown first byte: {0}")] - UnknownFirstByte(u8), - #[error(transparent)] - UriPack(#[from] uri_pack::DecodeErr), - #[error(transparent)] - Hash(#[from] bitcoin::hashes::Error), - #[error("Unexpected end of file")] - UnexpectedEOF, -} - -// TODO use a common url format with eth-wire - -/// Encoded metadata for outgoing transaction -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum OutMetadata { - Withdraw { wtid: [u8; 32], url: Url }, - Bounce { bounced: Txid }, -} - -// We leave a potential special meaning for u8::MAX -const BOUNCE_BYTE: u8 = u8::MAX - 1; - -impl OutMetadata { - pub fn encode(&self) -> Vec<u8> { - let mut buffer = Vec::new(); - match self { - OutMetadata::Withdraw { wtid, url } => { - buffer.push(if url.scheme() == "http" { 1 } else { 0 }); - buffer.extend_from_slice(wtid); - let parts = format!("{}{}", url.domain().unwrap_or(""), url.path()); - let packed = uri_pack::pack_uri(&parts).unwrap(); - buffer.extend_from_slice(&packed); - return buffer; - } - OutMetadata::Bounce { bounced: id } => { - buffer.push(BOUNCE_BYTE); - buffer.extend_from_slice(id.as_ref()); - } - } - return buffer; - } - - pub fn decode(bytes: &[u8]) -> Result<Self, DecodeErr> { - if bytes.is_empty() { - return Err(DecodeErr::UnexpectedEOF); - } - match bytes[0] { - 0..=1 => { - if bytes.len() < 33 { - return Err(DecodeErr::UnexpectedEOF); - } - let packed = format!( - "http{}://{}", - if bytes[0] == 0 { "s" } else { "" }, - uri_pack::unpack_uri(&bytes[33..])?, - ); - let url = Url::parse(&packed).unwrap(); - Ok(OutMetadata::Withdraw { - wtid: bytes[1..33].try_into().unwrap(), - url, - }) - } - BOUNCE_BYTE => Ok(OutMetadata::Bounce { - bounced: Txid::from_slice(&bytes[1..])?, - }), - unknown => Err(DecodeErr::UnknownFirstByte(unknown)), - } - } -} - -#[cfg(test)] -mod test { - use bitcoin::{hashes::Hash, Txid}; - use common::{rand_slice, url::Url}; - - use crate::metadata::OutMetadata; - - #[test] - fn decode_encode_tx() { - let urls = [ - "https://git.taler.net/", - "https://git.taler.net/depolymerization.git/", - "http://git.taler.net/", - "http://git.taler.net/depolymerization.git/", - ]; - for url in urls { - let wtid = rand_slice(); - let url = Url::parse(url).unwrap(); - let info = OutMetadata::Withdraw { wtid, url }; - let encoded = info.encode(); - let decoded = OutMetadata::decode(&encoded).unwrap(); - assert_eq!(decoded, info); - } - } - - #[test] - fn decode_encode_bounce() { - for _ in 0..4 { - let id: [u8; 32] = rand_slice(); - let info = OutMetadata::Bounce { - bounced: Txid::from_slice(&id).unwrap(), - }; - let encoded = info.encode(); - let decoded = OutMetadata::decode(&encoded).unwrap(); - assert_eq!(decoded, info); - } - } -} diff --git a/common/Cargo.toml b/common/Cargo.toml @@ -35,4 +35,5 @@ postgres = "0.19.2" rand = { version = "0.8.5", features = ["getrandom"] } # Securely zero memory zeroize = "1.5.3" - +# Optimized uri binary format +uri-pack = { path = "../uri-pack" } diff --git a/common/src/lib.rs b/common/src/lib.rs @@ -27,12 +27,13 @@ pub use url; pub mod api_common; pub mod api_wire; pub mod config; +pub mod currency; pub mod error_codes; pub mod log; +pub mod metadata; pub mod reconnect; pub mod sql; pub mod status; -pub mod currency; /// Secure random slice generator using getrandom pub fn rand_slice<const N: usize>() -> [u8; N] { diff --git a/common/src/metadata.rs b/common/src/metadata.rs @@ -0,0 +1,179 @@ +/* + This file is part of TALER + Copyright (C) 2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ + +use std::fmt::Debug; + +use url::Url; + +#[derive(Debug, Clone, thiserror::Error)] +pub enum DecodeErr { + #[error("Unknown first byte: {0}")] + UnknownFirstByte(u8), + #[error(transparent)] + UriPack(#[from] uri_pack::DecodeErr), + #[error("Unexpected end of file")] + UnexpectedEOF, +} + +/// Encoded metadata for outgoing transaction +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum OutMetadata { + Withdraw { wtid: [u8; 32], url: Url }, + Bounce { bounced: [u8; 32] }, +} + +// We leave a potential special meaning for u8::MAX +const BOUNCE_BYTE: u8 = u8::MAX - 1; + +impl OutMetadata { + pub fn encode(&self) -> Vec<u8> { + let mut buffer = Vec::new(); + match self { + OutMetadata::Withdraw { wtid, url } => { + buffer.push(if url.scheme() == "http" { 1 } else { 0 }); + buffer.extend_from_slice(wtid); + let parts = format!("{}{}", url.domain().unwrap_or(""), url.path()); + let packed = uri_pack::pack_uri(&parts).unwrap(); + buffer.extend_from_slice(&packed); + } + OutMetadata::Bounce { bounced } => { + buffer.push(BOUNCE_BYTE); + buffer.extend_from_slice(bounced.as_slice()); + } + } + return buffer; + } + + pub fn decode(bytes: &[u8]) -> Result<Self, DecodeErr> { + if bytes.is_empty() { + return Err(DecodeErr::UnexpectedEOF); + } + match bytes[0] { + 0..=1 => { + if bytes.len() < 33 { + return Err(DecodeErr::UnexpectedEOF); + } + let packed = format!( + "http{}://{}", + if bytes[0] == 0 { "s" } else { "" }, + uri_pack::unpack_uri(&bytes[33..])?, + ); + let url = Url::parse(&packed).unwrap(); + Ok(OutMetadata::Withdraw { + wtid: bytes[1..33].try_into().unwrap(), + url, + }) + } + BOUNCE_BYTE => { + if bytes.len() < 33 { + return Err(DecodeErr::UnexpectedEOF); + } + Ok(OutMetadata::Bounce { + bounced: bytes[1..33].try_into().unwrap(), + }) + } + unknown => Err(DecodeErr::UnknownFirstByte(unknown)), + } + } +} + +/// Encoded metadata for incoming transaction +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum InMetadata { + Deposit { reserve_pub: [u8; 32] }, +} + +impl InMetadata { + pub fn encode(&self) -> Vec<u8> { + let mut buffer = Vec::new(); + match self { + InMetadata::Deposit { reserve_pub } => { + buffer.push(0); + buffer.extend_from_slice(reserve_pub); + } + } + return buffer; + } + + pub fn decode(bytes: &[u8]) -> Result<Self, DecodeErr> { + if bytes.is_empty() { + return Err(DecodeErr::UnexpectedEOF); + } + match bytes[0] { + 0 => { + if bytes.len() < 33 { + return Err(DecodeErr::UnexpectedEOF); + } + Ok(InMetadata::Deposit { + reserve_pub: bytes[1..33].try_into().unwrap(), + }) + } + unknown => Err(DecodeErr::UnknownFirstByte(unknown)), + } + } +} + +#[cfg(test)] +mod test { + + use url::Url; + + use crate::{ + metadata::{InMetadata, OutMetadata}, + rand_slice, + }; + + #[test] + fn decode_encode_deposit() { + for _ in 0..4 { + let metadata = InMetadata::Deposit { + reserve_pub: rand_slice(), + }; + let encoded = metadata.encode(); + let decoded = InMetadata::decode(&encoded).unwrap(); + assert_eq!(decoded, metadata); + } + } + + #[test] + fn decode_encode_withdraw() { + let urls = [ + "https://git.taler.net/", + "https://git.taler.net/depolymerization.git/", + "http://git.taler.net/", + "http://git.taler.net/depolymerization.git/", + ]; + for url in urls { + let wtid = rand_slice(); + let url = Url::parse(url).unwrap(); + let metadata = OutMetadata::Withdraw { wtid, url }; + let encoded = metadata.encode(); + let decoded = OutMetadata::decode(&encoded).unwrap(); + assert_eq!(decoded, metadata); + } + } + + #[test] + fn decode_encode_bounce() { + for _ in 0..4 { + let id: [u8; 32] = rand_slice(); + let info = OutMetadata::Bounce { bounced: id }; + let encoded = info.encode(); + let decoded = OutMetadata::decode(&encoded).unwrap(); + assert_eq!(decoded, info); + } + } +} diff --git a/eth-wire/Cargo.toml b/eth-wire/Cargo.toml @@ -23,7 +23,5 @@ ethereum-types = { version = "0.13.1", default-features = false, features = [ ] } # Error macros thiserror = "1.0.30" -# Optimized uri binary format -uri-pack = { path = "../uri-pack" } # Common lib common = { path = "../common" } diff --git a/eth-wire/src/lib.rs b/eth-wire/src/lib.rs @@ -26,17 +26,16 @@ use common::{ config::TalerConfig, currency::{Currency, CurrencyEth}, log::{fail, OrFail}, + metadata::{InMetadata, OutMetadata}, postgres, url::Url, }; use ethereum_types::{Address, H160, H256, U256, U64}; -use metadata::{InMetadata, OutMetadata}; use rpc::{hex::Hex, Rpc, RpcClient, RpcStream, Transaction}; use rpc_utils::default_data_dir; use serde::de::DeserializeOwned; use taler_util::{eth_payto_addr, taler_to_eth}; -pub mod metadata; pub mod rpc; mod rpc_utils; pub mod taler_util; @@ -88,7 +87,7 @@ pub trait RpcExtended: RpcClient { .get_transaction(&hash)? .expect("Cannot bounce a non existent transaction"); let bounce_value = tx.value.saturating_sub(bounce_fee); - let metadata = OutMetadata::Bounce { bounced: hash }; + let metadata = OutMetadata::Bounce { bounced: hash.0 }; let mut request = rpc::TransactionRequest { from: tx.to.expect("Cannot bounce contract transaction"), to: tx.from.expect("Cannot bounce coinbase transaction"), diff --git a/eth-wire/src/loops/worker.rs b/eth-wire/src/loops/worker.rs @@ -18,16 +18,16 @@ use std::{fmt::Write, sync::atomic::Ordering, time::SystemTime}; use common::{ api_common::base32, log::log::{error, info, warn}, + metadata::{InMetadata, OutMetadata}, postgres::{fallible_iterator::FallibleIterator, Client}, reconnect::AutoReconnectDb, sql::{sql_array, sql_url}, status::{BounceStatus, WithdrawStatus}, }; use eth_wire::{ - metadata::{InMetadata, OutMetadata}, - rpc::{self, AutoRpcWallet, Rpc, Transaction, TransactionRequest, RpcClient}, + rpc::{self, AutoRpcWallet, Rpc, RpcClient, Transaction, TransactionRequest}, taler_util::{eth_payto_url, eth_to_taler}, - SyncState, SyncTransaction, RpcExtended, + RpcExtended, SyncState, SyncTransaction, }; use ethereum_types::{Address, H256, U256}; @@ -365,6 +365,7 @@ fn sync_chain_outgoing(tx: &SyncTransaction, db: &mut Client, state: &WireState) } } OutMetadata::Bounce { bounced } => { + let bounced = H256::from_slice(&bounced); // Get previous bounce let row = db.query_opt( "SELECT id, status FROM bounce WHERE bounced=$1", diff --git a/eth-wire/src/metadata.rs b/eth-wire/src/metadata.rs @@ -1,177 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2022 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ - -use common::url::Url; -use ethereum_types::H256; - -#[derive(Debug, Clone, Copy, thiserror::Error)] -pub enum DecodeErr { - #[error("Unknown first byte: {0}")] - UnknownFirstByte(u8), - #[error(transparent)] - UriPack(#[from] uri_pack::DecodeErr), - #[error("Unexpected end of file")] - UnexpectedEOF, -} - -/// Encoded metadata for outgoing transaction -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum OutMetadata { - Withdraw { wtid: [u8; 32], url: Url }, - Bounce { bounced: H256 }, -} - -// We leave a potential special meaning for u8::MAX -const BOUNCE_BYTE: u8 = u8::MAX - 1; - -impl OutMetadata { - pub fn encode(&self) -> Vec<u8> { - let mut buffer = Vec::new(); - match self { - OutMetadata::Withdraw { wtid, url } => { - buffer.push(if url.scheme() == "http" { 1 } else { 0 }); - buffer.extend_from_slice(wtid); - let parts = format!("{}{}", url.domain().unwrap_or(""), url.path()); - let packed = uri_pack::pack_uri(&parts).unwrap(); - buffer.extend_from_slice(&packed); - } - OutMetadata::Bounce { bounced } => { - buffer.push(BOUNCE_BYTE); - buffer.extend_from_slice(bounced.as_bytes()); - } - } - return buffer; - } - - pub fn decode(bytes: &[u8]) -> Result<Self, DecodeErr> { - if bytes.is_empty() { - return Err(DecodeErr::UnexpectedEOF); - } - match bytes[0] { - 0..=1 => { - if bytes.len() < 33 { - return Err(DecodeErr::UnexpectedEOF); - } - let packed = format!( - "http{}://{}", - if bytes[0] == 0 { "s" } else { "" }, - uri_pack::unpack_uri(&bytes[33..])?, - ); - let url = Url::parse(&packed).unwrap(); - Ok(OutMetadata::Withdraw { - wtid: bytes[1..33].try_into().unwrap(), - url, - }) - } - BOUNCE_BYTE => { - if bytes.len() < 33 { - return Err(DecodeErr::UnexpectedEOF); - } - Ok(OutMetadata::Bounce { - bounced: H256::from_slice(&bytes[1..33]), - }) - } - unknown => Err(DecodeErr::UnknownFirstByte(unknown)), - } - } -} - -/// Encoded metadata for incoming transaction -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum InMetadata { - Deposit { reserve_pub: [u8; 32] }, -} - -impl InMetadata { - pub fn encode(&self) -> Vec<u8> { - let mut buffer = Vec::new(); - match self { - InMetadata::Deposit { reserve_pub } => { - buffer.push(0); - buffer.extend_from_slice(reserve_pub); - } - } - return buffer; - } - - pub fn decode(bytes: &[u8]) -> Result<Self, DecodeErr> { - if bytes.is_empty() { - return Err(DecodeErr::UnexpectedEOF); - } - match bytes[0] { - 0 => { - if bytes.len() < 33 { - return Err(DecodeErr::UnexpectedEOF); - } - Ok(InMetadata::Deposit { - reserve_pub: bytes[1..33].try_into().unwrap(), - }) - } - unknown => Err(DecodeErr::UnknownFirstByte(unknown)), - } - } -} - -#[cfg(test)] -mod test { - use common::{rand_slice, url::Url}; - use ethereum_types::H256; - - use crate::metadata::{InMetadata, OutMetadata}; - - #[test] - fn decode_encode_deposit() { - for _ in 0..4 { - let metadata = InMetadata::Deposit { - reserve_pub: rand_slice(), - }; - let encoded = metadata.encode(); - let decoded = InMetadata::decode(&encoded).unwrap(); - assert_eq!(decoded, metadata); - } - } - - #[test] - fn decode_encode_withdraw() { - let urls = [ - "https://git.taler.net/", - "https://git.taler.net/depolymerization.git/", - "http://git.taler.net/", - "http://git.taler.net/depolymerization.git/", - ]; - for url in urls { - let wtid = rand_slice(); - let url = Url::parse(url).unwrap(); - let metadata = OutMetadata::Withdraw { wtid, url }; - let encoded = metadata.encode(); - let decoded = OutMetadata::decode(&encoded).unwrap(); - assert_eq!(decoded, metadata); - } - } - - #[test] - fn decode_encode_bounce() { - for _ in 0..4 { - let id: [u8; 32] = rand_slice(); - let metadata = OutMetadata::Bounce { - bounced: H256::from_slice(&id), - }; - let encoded = metadata.encode(); - let decoded = OutMetadata::decode(&encoded).unwrap(); - assert_eq!(decoded, metadata); - } - } -} diff --git a/instrumentation/src/btc.rs b/instrumentation/src/btc.rs @@ -16,15 +16,14 @@ use std::{path::Path, time::Duration}; -use bitcoin::{Amount, BlockHash, Network, SignedAmount}; +use bitcoin::{Amount, BlockHash, Network, SignedAmount, Txid, hashes::Hash}; use btc_wire::{ - metadata::OutMetadata, rpc::{self, Category, ErrorCode, Rpc, Transaction}, rpc_utils, taler_utils::{btc_payto_url, btc_to_taler}, WireState, }; -use common::rand_slice; +use common::{rand_slice, metadata::OutMetadata}; use crate::{check_incoming, check_outgoing, print_now, transfer}; @@ -151,6 +150,7 @@ pub fn btc_test(config: Option<&Path>, base_url: &str) { match metadata { OutMetadata::Withdraw { .. } => {} OutMetadata::Bounce { bounced } => { + let bounced = Txid::from_inner(bounced); if bounced == bounce_id { break 'l tx; } else if bounced == send_min_id { diff --git a/instrumentation/src/eth.rs b/instrumentation/src/eth.rs @@ -1,13 +1,12 @@ use std::{path::Path, time::Duration}; -use common::rand_slice; +use common::{metadata::OutMetadata, rand_slice}; use eth_wire::{ - metadata::OutMetadata, rpc::{hex::Hex, Rpc, RpcClient, TransactionRequest}, taler_util::{eth_payto_url, eth_to_taler, TRUNC}, RpcExtended, SyncState, WireState, }; -use ethereum_types::U256; +use ethereum_types::{H256, U256}; use crate::{check_incoming, check_outgoing, print_now, transfer}; @@ -107,6 +106,7 @@ pub fn eth_test(config: Option<&Path>, base_url: &str) { match metadata { OutMetadata::Withdraw { .. } => {} OutMetadata::Bounce { bounced } => { + let bounced = H256::from_slice(&bounced); if bounced == bounce_id { break 'l tx; } else if bounced == zero_id {