depolymerization

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

commit c16d2034a4ff46e12a08f7babb00053ee9685466
parent 17bbd0949052db402f9fbb532439fd03ab46d219
Author: Antoine A <>
Date:   Thu, 17 Feb 2022 15:59:17 +0100

btc-wire: simplify rpc code and improve documentation

Diffstat:
Mbtc-wire/src/bin/btc-wire-utils.rs | 6+++---
Mbtc-wire/src/config.rs | 13+++++--------
Mbtc-wire/src/lib.rs | 35+++++------------------------------
Mbtc-wire/src/loops/worker.rs | 10+++++-----
Mbtc-wire/src/main.rs | 2+-
Mbtc-wire/src/rpc.rs | 259+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mbtc-wire/src/rpc_utils.rs | 6+++---
Mbtc-wire/src/segwit.rs | 2+-
Mcommon/src/config.rs | 29+++++++++--------------------
Mcommon/src/log.rs | 22++++++++++++++++++++++
Mscript/prepare.sh | 21+++++++--------------
11 files changed, 197 insertions(+), 208 deletions(-)

diff --git a/btc-wire/src/bin/btc-wire-utils.rs b/btc-wire/src/bin/btc-wire-utils.rs @@ -76,7 +76,7 @@ pub fn auto_wallet(rpc: &mut Rpc, config: &BitcoinConfig, name: &str) -> (Rpc, A rpc.load_wallet(name).ok(); let mut wallet = Rpc::wallet(config, name).unwrap(); let addr = wallet - .get_new_address() + .gen_addr() .unwrap_or_else(|_| panic!("Failed to get wallet address {}", name)); (wallet, addr) } @@ -109,7 +109,7 @@ fn main() { Network::Regtest => { // Manually mine a block let (_, addr) = auto_wallet(&mut rpc, &btc_config, &to); - rpc.generate(1, &addr).unwrap(); + rpc.mine(1, &addr).unwrap(); } _ => { // Wait for next network block @@ -119,7 +119,7 @@ fn main() { } Cmd::Abandon { from } => { let (mut wire, _) = auto_wallet(&mut rpc, &btc_config, &from); - let list = wire.list_since_block(None, 1, false).unwrap(); + let list = wire.list_since_block(None, 1).unwrap(); for tx in list.transactions { if tx.category == Category::Send && tx.confirmations == 0 { wire.abandon_tx(&tx.txid).unwrap(); diff --git a/btc-wire/src/config.rs b/btc-wire/src/config.rs @@ -16,12 +16,11 @@ use std::{ net::SocketAddr, path::{Path, PathBuf}, - process::exit, str::FromStr, }; use bitcoin::Network; -use common::log::log::error; +use common::log::{fail, ExpectFail}; pub const WIRE_WALLET_NAME: &str = "wire"; @@ -69,13 +68,11 @@ impl BitcoinConfig { let main = conf.general_section(); if !main.contains_key("txindex") { - error!("require a bitcoind node running with 'txindex' option"); - exit(1); + fail("require a bitcoind node running with 'txindex' option"); } if !main.contains_key("maxtxfee") { - error!("require a bitcoind node running with 'maxtxfee' option"); - exit(1); + fail("require a bitcoind node running with 'maxtxfee' option"); } let network = if let Some("1") = main.get("testnet") { @@ -97,14 +94,14 @@ impl BitcoinConfig { let port = if let Some(addr) = section.and_then(|s| s.get("rpcport")) { addr.parse() - .expect("bitcoin config value 'rpcport' is not a valid port number") + .expect_fail("bitcoin config value 'rpcport' is not a valid port number") } else { rpc_port(network) }; let addr = if let Some(addr) = section.and_then(|s| s.get("rpcbind")) { SocketAddr::from_str(addr) - .expect("bitcoin config value 'rpcbind' is not a valid socket address") + .expect_fail("bitcoin config value 'rpcbind' is not a valid socket address") } else { ([127, 0, 0, 1], port).into() }; diff --git a/btc-wire/src/lib.rs b/btc-wire/src/lib.rs @@ -16,7 +16,7 @@ use std::str::FromStr; use bitcoin::{hashes::hex::FromHex, Address, Amount, Network, Txid}; -use rpc::{Rpc, Category, TransactionFull}; +use rpc::{Category, Rpc, Transaction}; use rpc_utils::{segwit_min_amount, sender_address}; use segwit::{decode_segwit_msg, encode_segwit_key}; @@ -70,7 +70,7 @@ impl Rpc { pub fn get_tx_segwit_key( &mut self, id: &Txid, - ) -> Result<(TransactionFull, [u8; 32]), GetSegwitErr> { + ) -> Result<(Transaction, [u8; 32]), GetSegwitErr> { let full = self.get_tx(id)?; let addresses: Vec<String> = full @@ -90,31 +90,11 @@ impl Rpc { Ok((full, metadata)) } - /// Send a transaction with metadata encoded using OP_RETURN - pub fn send_op_return( - &mut self, - to: &Address, - amount: &Amount, - metadata: &[u8], - subtract_fee: bool, - replaceable: bool, - ) -> rpc::Result<Txid> { - assert!(metadata.len() > 0, "No medatata"); - assert!(metadata.len() <= 80, "Max 80 bytes"); - self.send_custom( - &[], - [(to, amount)], - Some(metadata), - subtract_fee, - replaceable, - ) - } - /// Get detailed information about an in-wallet transaction and its op_return metadata pub fn get_tx_op_return( &mut self, id: &Txid, - ) -> Result<(TransactionFull, Vec<u8>), GetOpReturnErr> { + ) -> Result<(Transaction, Vec<u8>), GetOpReturnErr> { let full = self.get_tx(id)?; let op_return_out = full @@ -140,7 +120,7 @@ impl Rpc { &mut self, id: &Txid, bounce_fee: &Amount, - metadata: &[u8], + metadata: Option<&[u8]>, ) -> Result<Txid, rpc::Error> { let full = self.get_tx(id)?; let detail = &full.details[0]; @@ -150,11 +130,6 @@ impl Rpc { let sender = sender_address(self, &full)?; 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 = if metadata.is_empty() { - self.send(&sender, &bounce_amount, true)? - } else { - self.send_op_return(&sender, &bounce_amount, metadata, true, false)? - }; - Ok(id) + self.send(&sender, &bounce_amount, metadata, true) } } diff --git a/btc-wire/src/loops/worker.rs b/btc-wire/src/loops/worker.rs @@ -22,7 +22,7 @@ use std::{ use bitcoin::{hashes::Hash, Amount as BtcAmount, BlockHash, Txid}; use btc_wire::{ - rpc::{self, AutoRpcWallet, Category, ErrorCode, Rpc, TransactionFull}, + rpc::{self, AutoRpcWallet, Category, ErrorCode, Rpc, Transaction}, rpc_utils::sender_address, GetOpReturnErr, GetSegwitErr, }; @@ -166,7 +166,7 @@ fn sync_chain( BlockHash, ) = { // Get all transactions made since this block - let list = rpc.list_since_block(Some(&last_hash), min_confirmations, true)?; + let list = rpc.list_since_block(Some(&last_hash), min_confirmations)?; // Only keep ids and category let txs = list .transactions @@ -349,7 +349,7 @@ fn sync_chain_incoming_confirmed( /// Sync database with an outgoing withdraw transaction, return true if stuck fn sync_chain_withdraw( id: &Txid, - full: &TransactionFull, + full: &Transaction, wtid: &[u8; 32], rpc: &mut Rpc, db: &mut Client, @@ -559,7 +559,7 @@ fn withdraw(db: &mut Client, rpc: &mut Rpc) -> LoopResult<bool> { let url = sql_url(row, 4); let metadata = OutMetadata::Withdraw { wtid, url }; - let tx_id = rpc.send_op_return(&addr, &amount, &metadata.encode(), false, true)?; + let tx_id = rpc.send(&addr, &amount, Some(&metadata.encode()), false)?; fail_point("(injected) fail withdraw", 0.3)?; db.execute( "UPDATE tx_out SET status=$1, txid=$2 WHERE id=$3", @@ -583,7 +583,7 @@ fn bounce(db: &mut Client, rpc: &mut Rpc, fee: &BtcAmount) -> LoopResult<bool> { let bounced: Txid = sql_txid(row, 1); let metadata = OutMetadata::Bounce { bounced }; - match rpc.bounce(&bounced, fee, &metadata.encode()) { + match rpc.bounce(&bounced, fee, Some(&metadata.encode())) { Ok(it) => { fail_point("(injected) fail bounce", 0.3)?; db.execute( diff --git a/btc-wire/src/main.rs b/btc-wire/src/main.rs @@ -147,7 +147,7 @@ fn init(config: Option<PathBuf>, init: Init) { // Or generate a new one let new = Rpc::wallet(&btc_conf, WIRE_WALLET_NAME) .expect("Failed to connect to wallet bitcoin RPC server") - .get_new_address() + .gen_addr() .expect("Failed to generate new address") .to_string(); db.execute( diff --git a/btc-wire/src/rpc.rs b/btc-wire/src/rpc.rs @@ -16,7 +16,7 @@ //! This is a very simple RPC client designed only for a specific bitcoind version //! and to use on an secure localhost connection to a trusted node //! -//! No http format of body length check as we trust the node output +//! No http format or body length check as we trust the node output //! No asynchronous request as bitcoind put requests in a queue and process //! them synchronously and we do not want to fill this queue //! @@ -51,7 +51,7 @@ pub fn auto_rpc_wallet(config: BitcoinConfig, wallet: &'static str) -> AutoRpcWa .ok()?; Some(rpc) }, - |client| client.net_info().is_err(), + |client| client.get_chain_tips().is_err(), ) } @@ -65,7 +65,7 @@ pub fn auto_rpc_common(config: BitcoinConfig) -> AutoRpcCommon { .map_err(|err| error!("connect RPC: {}", err)) .ok() }, - |client| client.net_info().is_err(), + |client| client.get_chain_tips().is_err(), ) } @@ -78,17 +78,17 @@ struct RpcRequest<'a, T: serde::Serialize> { #[derive(Debug, serde::Deserialize)] #[serde(untagged)] -enum BtcResponse<T> { +enum RpcResponse<T> { RpcResponse { result: Option<T>, - error: Option<BtcErr>, + error: Option<RpcError>, id: u64, }, Error(String), } #[derive(Debug, serde::Deserialize)] -struct BtcErr { +struct RpcError { code: ErrorCode, message: String, } @@ -202,9 +202,9 @@ impl Rpc { } // Read body let amount = sock.read_until(b'\n', buf)?; - let response: BtcResponse<T> = serde_json::from_slice(&buf[..amount])?; + let response: RpcResponse<T> = serde_json::from_slice(&buf[..amount])?; match response { - BtcResponse::RpcResponse { result, error, id } => { + RpcResponse::RpcResponse { result, error, id } => { assert_eq!(self.id, id); self.id += 1; if let Some(ok) = result { @@ -219,23 +219,23 @@ impl Rpc { }) } } - BtcResponse::Error(msg) => Err(Error::Bitcoin(msg)), + RpcResponse::Error(msg) => Err(Error::Bitcoin(msg)), } } - pub fn net_info(&mut self) -> Result<Nothing> { - self.call("getnetworkinfo", &EMPTY) - } - - pub fn load_wallet(&mut self, name: &str) -> Result<Wallet> { - self.call("loadwallet", &[name]) - } + /* ----- Wallet management ----- */ /// Create encrypted native bitcoin wallet pub fn create_wallet(&mut self, name: &str, passwd: &str) -> Result<Wallet> { self.call("createwallet", &(name, (), (), passwd, (), true)) } + /// Load existing wallet + pub fn load_wallet(&mut self, name: &str) -> Result<Wallet> { + self.call("loadwallet", &[name]) + } + + /// Unlock loaded wallet pub fn unlock_wallet(&mut self, passwd: &str) -> Result<()> { // TODO Capped at 3yrs, is it enough ? match self.call("walletpassphrase", &(passwd, 100000000)) { @@ -244,114 +244,123 @@ impl Rpc { } } - pub fn get_new_address(&mut self) -> Result<Address> { + /* ----- Wallet utils ----- */ + + /// Generate a new address fot the current wallet + pub fn gen_addr(&mut self) -> Result<Address> { self.call("getnewaddress", &EMPTY) } - pub fn generate(&mut self, nb: u16, address: &Address) -> Result<Vec<BlockHash>> { + /// Get current balance amount + pub fn get_balance(&mut self) -> Result<Amount> { + let btc: f64 = self.call("getbalance", &EMPTY)?; + Ok(Amount::from_btc(btc).unwrap()) + } + + /* ----- Mining ----- */ + + /// Mine a certain amount of block to profit a given address + pub fn mine(&mut self, nb: u16, address: &Address) -> Result<Vec<BlockHash>> { self.call("generatetoaddress", &(nb, address)) } + /* ----- Getter ----- */ + + /// Get block hash at a given height pub fn get_block_hash(&mut self, height: u32) -> Result<BlockHash> { self.call("getblockhash", &[height]) } - pub fn wait_for_new_block(&mut self, timeout: u64) -> Result<Nothing> { - self.call("waitfornewblock", &[timeout]) + /// Get blockchain info + pub fn get_blockchain_info(&mut self) -> Result<BlockchainInfo> { + self.call("getblockchaininfo", &EMPTY) } - pub fn get_balance(&mut self) -> Result<Amount> { - let btc: f64 = self.call("getbalance", &EMPTY)?; - Ok(Amount::from_btc(btc).unwrap()) + /// Get chain tips + pub fn get_chain_tips(&mut self) -> Result<Vec<ChainTips>> { + self.call("getchaintips", &EMPTY) } - pub fn get_blockchain_info(&mut self) -> Result<BlockchainInfo> { - self.call("getblockchaininfo", &EMPTY) + /// Get wallet transaction info from id + pub fn get_tx(&mut self, id: &Txid) -> Result<Transaction> { + self.call("gettransaction", &(id, (), true)) } - pub fn send(&mut self, address: &Address, amount: &Amount, subtract_fee: bool) -> Result<Txid> { - let btc = amount.as_btc(); - self.call("sendtoaddress", &(address, btc, (), (), subtract_fee)) + /// Get transaction inputs and outputs + pub fn get_input_output(&mut self, id: &Txid) -> Result<InputOutput> { + self.call("getrawtransaction", &(id, true)) + } + + /* ----- Transactions ----- */ + + /// Send bitcoin transaction + pub fn send( + &mut self, + to: &Address, + amount: &Amount, + data: Option<&[u8]>, + subtract_fee: bool, + ) -> Result<Txid> { + self.send_custom([], [(to, amount)], data, subtract_fee) + .map(|it| it.txid) } - /// Send transaction to multiple recipients + /// Send bitcoin transaction with multiple recipients pub fn send_many<'a, 'b>( &mut self, - recipients: impl IntoIterator<Item = (&'a Address, &'b Amount)>, + to: impl IntoIterator<Item = (&'a Address, &'b Amount)>, ) -> Result<Txid> { - let amounts = Value::Object( - recipients - .into_iter() - .map(|(addr, amount)| (addr.to_string(), amount.as_btc().into())) - .collect(), - ); - self.call("sendmany", &("", amounts)) + self.send_custom([], to, None, false).map(|it| it.txid) } - pub fn send_custom<'a, 'b, 'c>( + fn send_custom<'a, 'b, 'c>( &mut self, - inputs: impl IntoIterator<Item = &'a Txid>, - outputs: impl IntoIterator<Item = (&'b Address, &'c Amount)>, + from: impl IntoIterator<Item = &'a Txid>, + to: impl IntoIterator<Item = (&'b Address, &'c Amount)>, data: Option<&[u8]>, subtract_fee: bool, - replaceable: bool, - ) -> Result<Txid> { - let mut outputs: Vec<Value> = outputs + ) -> Result<SendResult> { + // We use the experimental 'send' rpc command as it is the only capable to send metadata in a single rpc call + let inputs: Vec<_> = from + .into_iter() + .enumerate() + .map(|(i, id)| json!({"txid": id.to_string(), "vout": i})) + .collect(); + let mut outputs: Vec<Value> = to .into_iter() .map(|(addr, amount)| json!({&addr.to_string(): amount.as_btc()})) .collect(); - let len = outputs.len(); - let hex: String = self.call( - "createrawtransaction", + let nb_outputs = outputs.len(); + if let Some(data) = data { + assert!(data.len() > 0, "No medatata"); + assert!(data.len() <= 80, "Max 80 bytes"); + outputs.push(json!({ "data".to_string(): data.to_hex() })); + } + self.call( + "send", &( - Value::Array( - inputs - .into_iter() - .enumerate() - .map(|(i, id)| json!({"txid": id.to_string(), "vout": i})) - .collect(), - ), - Value::Array({ - if let Some(data) = data { - outputs.push(json!({ "data".to_string(): data.to_hex() })); - } - outputs - }), + outputs, (), - true, - ), - )?; - let funded: HexWrapper = self.call( - "fundrawtransaction", - &( - hex, - FundOption { + (), + (), + SendOption { + add_inputs: true, + inputs, subtract_fee_from_outputs: if subtract_fee { - (0..len).into_iter().collect() + (0..nb_outputs).into_iter().collect() } else { vec![] }, - replaceable, + replaceable: true, }, ), - )?; - let signed: HexWrapper = self.call("signrawtransactionwithwallet", &[&funded.hex])?; - self.call("sendrawtransaction", &[&signed.hex]) + ) } - pub fn bump_fee(&mut self, id: &Txid) -> Result<Bump> { + pub fn bump_fee(&mut self, id: &Txid) -> Result<BumpResult> { self.call("bumpfee", &[id]) } - pub fn list_since_block( - &mut self, - hash: Option<&BlockHash>, - confirmation: u16, - include_remove: bool, - ) -> Result<ListSinceBlock> { - self.call("listsinceblock", &(hash, confirmation, (), include_remove)) - } - pub fn abandon_tx(&mut self, id: &Txid) -> Result<()> { match self.call("abandontransaction", &[&id]) { Err(Error::Null) => Ok(()), @@ -359,22 +368,27 @@ impl Rpc { } } - pub fn get_chain_tips(&mut self) -> Result<Vec<ChainTips>> { - self.call("getchaintips", &EMPTY) - } + /* ----- Watcher ----- */ - pub fn get_tx(&mut self, id: &Txid) -> Result<TransactionFull> { - self.call("gettransaction", &(id, (), true)) + /// Block until a new block is mined + pub fn wait_for_new_block(&mut self, timeout: u64) -> Result<Nothing> { + self.call("waitfornewblock", &[timeout]) } - pub fn get_raw(&mut self, id: &Txid) -> Result<RawTransaction> { - self.call("getrawtransaction", &(id, true)) + /// List new and removed transaction since a block + pub fn list_since_block( + &mut self, + hash: Option<&BlockHash>, + confirmation: u16, + ) -> Result<ListSinceBlock> { + self.call("listsinceblock", &(hash, confirmation, (), true)) } } #[derive(Debug, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct FundOption { +pub struct SendOption { + pub add_inputs: bool, + pub inputs: Vec<Value>, pub subtract_fee_from_outputs: Vec<usize>, pub replaceable: bool, } @@ -382,23 +396,6 @@ pub struct FundOption { #[derive(Debug, serde::Deserialize)] pub struct Wallet { pub name: String, - pub warning: Option<String>, -} - -#[derive(Debug, serde::Deserialize)] -pub struct VoutScriptPubKey { - pub asm: String, - // nulldata do not have an address - pub address: Option<Address>, -} - -#[derive(Debug, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Vout { - #[serde(with = "bitcoin::util::amount::serde::as_btc")] - pub value: Amount, - pub n: u32, - pub script_pub_key: VoutScriptPubKey, } #[derive(Clone, Debug, serde::Deserialize)] @@ -409,16 +406,12 @@ pub struct BlockchainInfo { } #[derive(Debug, serde::Deserialize)] -pub struct Vin { - pub sequence: u32, - /// Not provided for coinbase txs. - pub txid: Option<Txid>, - /// Not provided for coinbase txs. - pub vout: Option<u32>, +pub struct BumpResult { + pub txid: Txid, } #[derive(Debug, serde::Deserialize)] -pub struct Bump { +pub struct SendResult { pub txid: Txid, } @@ -462,13 +455,38 @@ pub struct ListSinceBlock { } #[derive(Debug, serde::Deserialize)] -pub struct RawTransaction { +pub struct VoutScriptPubKey { + pub asm: String, + // nulldata do not have an address + pub address: Option<Address>, +} + +#[derive(Debug, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Vout { + #[serde(with = "bitcoin::util::amount::serde::as_btc")] + pub value: Amount, + pub n: u32, + pub script_pub_key: VoutScriptPubKey, +} + +#[derive(Debug, serde::Deserialize)] +pub struct Vin { + pub sequence: u32, + /// Not provided for coinbase txs. + pub txid: Option<Txid>, + /// Not provided for coinbase txs. + pub vout: Option<u32>, +} + +#[derive(Debug, serde::Deserialize)] +pub struct InputOutput { pub vin: Vec<Vin>, pub vout: Vec<Vout>, } #[derive(Debug, serde::Deserialize)] -pub struct TransactionFull { +pub struct Transaction { pub confirmations: i32, pub time: u64, #[serde(with = "bitcoin::util::amount::serde::as_btc")] @@ -478,12 +496,7 @@ pub struct TransactionFull { pub replaces_txid: Option<Txid>, pub replaced_by_txid: Option<Txid>, pub details: Vec<TransactionDetail>, - pub decoded: RawTransaction, -} - -#[derive(Debug, serde::Deserialize)] -pub struct HexWrapper { - pub hex: String, + pub decoded: InputOutput, } #[derive(Clone, PartialEq, Eq, serde::Deserialize, Debug)] diff --git a/btc-wire/src/rpc_utils.rs b/btc-wire/src/rpc_utils.rs @@ -17,7 +17,7 @@ use std::{path::PathBuf, str::FromStr}; use bitcoin::{Address, Amount}; -use crate::rpc::{self, Rpc, TransactionFull}; +use crate::rpc::{self, Rpc, Transaction}; pub const CLIENT: &str = "client"; pub const WIRE: &str = "wire"; @@ -48,9 +48,9 @@ pub fn segwit_min_amount() -> Amount { } /// Get the first sender address from a raw transaction -pub fn sender_address(rpc: &mut Rpc, full: &TransactionFull) -> rpc::Result<Address> { +pub fn sender_address(rpc: &mut Rpc, full: &Transaction) -> rpc::Result<Address> { let first = &full.decoded.vin[0]; - let tx = rpc.get_raw(&first.txid.unwrap())?; + let tx = rpc.get_input_output(&first.txid.unwrap())?; Ok(tx .vout .into_iter() diff --git a/btc-wire/src/segwit.rs b/btc-wire/src/segwit.rs @@ -71,7 +71,7 @@ pub enum DecodeSegWitErr { /// Decode a 32B key into from adresses pub fn decode_segwit_msg(segwit_addrs: &[impl AsRef<str>]) -> Result<[u8; 32], DecodeSegWitErr> { // Extract parts from every addresses - let mut decoded: Vec<(bool, [u8; 4], [u8; 16])> = segwit_addrs + let decoded: Vec<(bool, [u8; 4], [u8; 16])> = segwit_addrs .iter() .filter_map(|addr| { bech32::decode(addr.as_ref()).ok().and_then(|(_, wp, _)| { diff --git a/common/src/config.rs b/common/src/config.rs @@ -14,15 +14,15 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ use ini::{Ini, Properties}; -use log::error; use std::{ - fmt::Display, path::{Path, PathBuf}, - process::{exit, Command}, + process::Command, str::FromStr, }; use url::Url; +use crate::log::{fail, ExpectFail}; + pub trait Config: Sized { /// Load using taler-config fn load_taler_config(file: Option<&Path>, currency: Option<&'static str>) -> Self { @@ -32,9 +32,7 @@ pub trait Config: Sized { cmd.arg("-c"); cmd.arg(path); } - let output = cmd - .output() - .unwrap_or_else(|_| fail("Failed to execute taler-config")); + let output = cmd.output().expect_fail("Failed to execute taler-config"); if !output.status.success() { panic!( "taler-config failure:\n{}", @@ -152,7 +150,7 @@ pub fn load_eth_config(path: Option<&Path>) -> EthConfig { fn section<'a>(ini: &'a Ini, name: &str) -> &'a Properties { ini.section(Some(name)) - .unwrap_or_else(|| fail(format_args!("missing config section {}", name))) + .expect_fail(format_args!("missing config section {}", name)) } fn require<T>( @@ -161,7 +159,7 @@ fn require<T>( lambda: fn(properties: &Properties, name: &str) -> Option<T>, ) -> T { let result = lambda(properties, name); - result.unwrap_or_else(|| fail(format_args!("missing config {}", name))) + result.expect_fail(format_args!("missing config {}", name)) } fn string(properties: &Properties, name: &str) -> Option<String> { @@ -170,28 +168,19 @@ fn string(properties: &Properties, name: &str) -> Option<String> { fn path(properties: &Properties, name: &str) -> Option<PathBuf> { properties.get(name).map(|s| { - PathBuf::from_str(s) - .unwrap_or_else(|_| fail(format_args!("config value {} is not a valid path", name))) + PathBuf::from_str(s).expect_fail(format_args!("config value {} is not a valid path", name)) }) } fn nb<T: FromStr>(properties: &Properties, name: &str) -> Option<T> { properties.get(name).map(|s| { s.parse() - .unwrap_or_else(|_| fail(format_args!("config value {} is not a number", name))) + .expect_fail(format_args!("config value {} is not a number", name)) }) } fn url(properties: &Properties, name: &str) -> Option<Url> { properties.get(name).map(|s| { - Url::parse(s) - .unwrap_or_else(|_| fail(format_args!("config value {} is not a valid url", name))) + Url::parse(s).expect_fail(format_args!("config value {} is not a valid url", name)) }) } - -/* ----- Helper report functions ----- */ - -fn fail(msg: impl Display) -> ! { - error!("{}", msg); - exit(1); -} diff --git a/common/src/log.rs b/common/src/log.rs @@ -15,6 +15,8 @@ */ use flexi_logger::{DeferredNow, LogSpecification, Record}; pub use log; +use log::error; +use std::{fmt::Display, process::exit}; use time::{format_description::FormatItem, macros::format_description}; const TIME_FORMAT: &[FormatItem<'static>] = format_description!( @@ -45,4 +47,24 @@ pub fn init() { .unwrap(); } +pub trait ExpectFail<T> { + fn expect_fail(self, msg: impl Display) -> T; +} + +impl<T, E> ExpectFail<T> for Result<T, E> { + fn expect_fail(self, msg: impl Display) -> T { + self.unwrap_or_else(|_| fail(msg)) + } +} +impl<T> ExpectFail<T> for Option<T> { + fn expect_fail(self, msg: impl Display) -> T { + self.unwrap_or_else(|| fail(msg)) + } +} + +/// Log error message then exit +pub fn fail(msg: impl Display) -> ! { + error!("{}", msg); + exit(1); +} diff --git a/script/prepare.sh b/script/prepare.sh @@ -1,6 +1,6 @@ #!/bin/bash -## Install required dependencies to run tests +## Install required dependencies to run tests without sudo set -eu @@ -16,13 +16,9 @@ function cleanup() { trap cleanup EXIT -echo "Ⅰ - Install latest postgres from source" -cd $DIR -git clone --depth 1 https://git.postgresql.org/git/postgresql.git -cd postgresql -./configure --prefix ~/postgresql -make -make install +echo "Ⅰ - Find installed postgres version" +PG_VER=`pg_config --version | egrep -o '[0-9]{1,}' | head -1` +echo "Found version $PG_VER" echo "Ⅱ - Install bitcoind version 0.22" cd $DIR @@ -32,7 +28,6 @@ mkdir -pv ~/bitcoin tar xvzf btc.tar.gz mv -v bitcoin-22.0/* ~/bitcoin - echo "Ⅲ - Install Go Ethereum (Geth) v1.10.15 " cd $DIR curl -L https://gethstore.blob.core.windows.net/builds/geth-alltools-linux-amd64-1.10.15-8be800ff.tar.gz -o geth.tar.gz @@ -41,8 +36,6 @@ mkdir -pv ~/geth tar xvzf geth.tar.gz mv -v geth-alltools-linux-amd64-1.10.15-8be800ff/* ~/geth -echo "Ⅳ - Config" +echo "Ⅳ - PATH" -echo "Add ~/postgresql/bin to your path" -echo "Add ~/bitcoin/bin to your path" -echo "Add ~/geth to your path" -\ No newline at end of file +echo "Add PATH=\"$HOME/geth:$HOME/bitcoin/bin:/usr/lib/postgresql/$PG_VER/bin:$PATH:$PATH\" to your bash profile" +\ No newline at end of file