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:
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