daemon: clippification

That's a minimal API break because of some error variants that were
Box'd.
This commit is contained in:
Antoine Poinsot 2022-10-12 17:03:55 +02:00
parent 2a3214460e
commit 8b4866158b
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
16 changed files with 123 additions and 150 deletions

View File

@ -183,7 +183,7 @@ impl BitcoinD {
.url(&watchonly_url)
.map_err(BitcoindError::from)?
.timeout(Duration::from_secs(RPC_SOCKET_TIMEOUT))
.cookie_auth(cookie_string.clone())
.cookie_auth(cookie_string)
.build(),
);
@ -249,7 +249,7 @@ impl BitcoinD {
let res = resp.result().map_err(BitcoindError::Server)?;
log::trace!("Got from bitcoind: {:#?}", res);
return Ok(res);
Ok(res)
}
Err(e) => Err(BitcoindError::Server(e)),
}
@ -289,16 +289,14 @@ impl BitcoinD {
fn get_bitcoind_version(&self) -> u64 {
self.make_node_request("getnetworkinfo", &[])
.get("version")
.map(Json::as_u64)
.flatten()
.and_then(Json::as_u64)
.expect("Missing or invalid 'version' in 'getnetworkinfo' result?")
}
fn get_network_bip70(&self) -> String {
self.make_node_request("getblockchaininfo", &[])
.get("chain")
.map(Json::as_str)
.flatten()
.and_then(Json::as_str)
.expect("Missing or invalid 'chain' in 'getblockchaininfo' result?")
.to_string()
}
@ -341,7 +339,7 @@ impl BitcoinD {
),
);
if let Some(warning) = res.get("warning").map(Json::as_str).flatten() {
if let Some(warning) = res.get("warning").and_then(Json::as_str) {
if !warning.is_empty() {
return Some(warning.to_string());
}
@ -365,12 +363,9 @@ impl BitcoinD {
let all_succeeded = res
.as_array()
.map(|results| {
results.iter().all(|res| {
res.get("success")
.map(Json::as_bool)
.flatten()
.unwrap_or(false)
})
results
.iter()
.all(|res| res.get("success").and_then(Json::as_bool).unwrap_or(false))
})
.unwrap_or(false);
if all_succeeded {
@ -615,7 +610,7 @@ impl BitcoinD {
.get("txid")
.and_then(Json::as_str)
.expect("A valid txid must be present");
if visited_txs.contains(&spending_txid) || &spent_txid == spending_txid {
if visited_txs.contains(&spending_txid) || spent_txid == spending_txid {
continue;
} else {
visited_txs.insert(spending_txid);
@ -729,7 +724,7 @@ impl From<Json> for LSBlockRes {
.get("transactions")
.and_then(Json::as_array)
.expect("Array must be present")
.into_iter()
.iter()
.filter_map(|j| {
if j.get("category")
.and_then(Json::as_str)
@ -770,7 +765,7 @@ impl From<Json> for GetTxRes {
.and_then(Json::as_array)
.map(|array| {
array
.into_iter()
.iter()
.map(|v| {
bitcoin::Txid::from_str(v.as_str().expect("wrong json format")).unwrap()
})

View File

@ -130,8 +130,8 @@ impl BitcoinInterface for d::BitcoinD {
let mut spent = Vec::with_capacity(outpoints.len());
for op in outpoints {
if self.is_spent(&op) {
let spending_txid = if let Some(txid) = self.get_spender_txid(&op) {
if self.is_spent(op) {
let spending_txid = if let Some(txid) = self.get_spender_txid(op) {
txid
} else {
// TODO: better handling of this edge case.
@ -180,7 +180,7 @@ impl BitcoinInterface for d::BitcoinD {
let tx: Option<&d::GetTxRes> = match cache.get(txid) {
Some(tx) => tx.as_ref(),
None => {
let tx = self.get_transaction(&txid);
let tx = self.get_transaction(txid);
txs_to_cache.push((*txid, tx));
txs_to_cache.last().unwrap().1.as_ref()
}

View File

@ -30,7 +30,7 @@ fn update_coins(
// Start by fetching newly received coins.
let curr_coins = db_conn.unspent_coins();
let mut received = Vec::new();
for utxo in bit.received_coins(&previous_tip) {
for utxo in bit.received_coins(previous_tip) {
if let Some(derivation_index) = db_conn.derivation_index_by_address(&utxo.address) {
if !curr_coins.contains_key(&utxo.outpoint) {
let UTxO {
@ -119,7 +119,7 @@ fn new_tip(bit: &impl BitcoinInterface, current_tip: &BlockChainTip) -> Option<B
if bitcoin_tip.height > current_tip.height {
// Make sure we are on the same chain.
if bit.is_in_chain(&current_tip) {
if bit.is_in_chain(current_tip) {
// All good, we just moved forward.
return Some(bitcoin_tip);
}

View File

@ -109,7 +109,7 @@ fn sanity_check_psbt(psbt: &Psbt) -> Result<(), CommandError> {
value_in += psbtin
.witness_utxo
.as_ref()
.ok_or(CommandError::SanityCheckFailure(psbt.clone()))?
.ok_or_else(|| CommandError::SanityCheckFailure(psbt.clone()))?
.value;
}
@ -117,17 +117,17 @@ fn sanity_check_psbt(psbt: &Psbt) -> Result<(), CommandError> {
let value_out: u64 = tx.output.iter().map(|o| o.value).sum();
let abs_fee = value_in
.checked_sub(value_out)
.ok_or(CommandError::SanityCheckFailure(psbt.clone()))?;
.ok_or_else(|| CommandError::SanityCheckFailure(psbt.clone()))?;
if abs_fee > MAX_FEE {
return Err(CommandError::SanityCheckFailure(psbt.clone()));
}
// Check the feerate isn't insane.
let tx_vb: u64 = tx_vbytes(&tx);
let tx_vb: u64 = tx_vbytes(tx);
let feerate_sats_vb = abs_fee
.checked_div(tx_vb)
.ok_or(CommandError::SanityCheckFailure(psbt.clone()))?;
if feerate_sats_vb > MAX_FEERATE || feerate_sats_vb < 1 {
.ok_or_else(|| CommandError::SanityCheckFailure(psbt.clone()))?;
if !(1..=MAX_FEERATE).contains(&feerate_sats_vb) {
return Err(CommandError::SanityCheckFailure(psbt.clone()));
}
@ -188,7 +188,7 @@ impl DaemonControl {
let address = self
.config
.main_descriptor
.derive(index.into(), &self.secp)
.derive(index, &self.secp)
.address(self.config.bitcoin_config.network);
GetAddressResult { address }
}
@ -320,7 +320,7 @@ impl DaemonControl {
// Get the change address to create a dummy change txo.
// TODO: decent change management
let first_coin = coins
.get(&coins_outpoints.get(0).expect("We checked it wasn't empty"))
.get(coins_outpoints.get(0).expect("We checked it wasn't empty"))
.expect("We checked they were all present");
let coin_desc = self.derived_desc(first_coin.derivation_index);
let mut change_txo = bitcoin::TxOut {
@ -696,13 +696,11 @@ mod tests {
.iter()
.cloned()
.collect();
let destinations_c: HashMap<bitcoin::Address, u64> = [
(dummy_addr_a.clone(), dummy_value_a),
(dummy_addr_b.clone(), dummy_value_b),
]
.iter()
.cloned()
.collect();
let destinations_c: HashMap<bitcoin::Address, u64> =
[(dummy_addr_a, dummy_value_a), (dummy_addr_b, dummy_value_b)]
.iter()
.cloned()
.collect();
let mut psbt_a = control
.create_spend(&[dummy_op_a], &destinations_a, 1)
.unwrap()

View File

@ -48,11 +48,7 @@ pub fn change_index(psbt: &Psbt) -> Option<usize> {
};
let tx = &psbt.global.unsigned_tx;
for i in (0..tx.output.len()).rev() {
if &tx.output[i].script_pubkey == first_coin_spk {
return Some(i);
}
}
None
(0..tx.output.len())
.rev()
.find(|&i| &tx.output[i].script_pubkey == first_coin_spk)
}

View File

@ -113,7 +113,7 @@ pub enum ConfigError {
DatadirNotFound,
FileNotFound,
ReadingFile(String),
UnexpectedDescriptor(InheritanceDescriptor),
UnexpectedDescriptor(Box<InheritanceDescriptor>),
Unexpected(String),
}
@ -187,7 +187,7 @@ impl Config {
/// futile duplication.
pub fn from_file(custom_path: Option<PathBuf>) -> Result<Config, ConfigError> {
let config_file =
custom_path.unwrap_or(config_file_path().ok_or_else(|| ConfigError::DatadirNotFound)?);
custom_path.unwrap_or(config_file_path().ok_or(ConfigError::DatadirNotFound)?);
let config = toml::from_slice::<Config>(&std::fs::read(&config_file)?)
.map_err(|e| ConfigError::ReadingFile(format!("Parsing configuration file: {}", e)))?;

View File

@ -61,16 +61,16 @@ pub trait DatabaseConnection {
fn list_spending_coins(&mut self) -> HashMap<bitcoin::OutPoint, Coin>;
/// Store new UTxOs. Coins must not already be in database.
fn new_unspent_coins<'a>(&mut self, coins: &[Coin]);
fn new_unspent_coins(&mut self, coins: &[Coin]);
/// Mark a set of coins as being confirmed at a specified height and block time.
fn confirm_coins<'a>(&mut self, outpoints: &[(bitcoin::OutPoint, i32, u32)]);
fn confirm_coins(&mut self, outpoints: &[(bitcoin::OutPoint, i32, u32)]);
/// Mark a set of coins as being spent by a specified txid of a pending transaction.
fn spend_coins<'a>(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid)]);
fn spend_coins(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid)]);
/// Mark a set of coins as spent by a specified txid at a specified block time.
fn confirm_spend<'a>(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid, u32)]);
fn confirm_spend(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid, u32)]);
/// Get specific coins from the database.
fn coins_by_outpoints(
@ -107,7 +107,7 @@ impl DatabaseConnection for SqliteConn {
}
fn update_tip(&mut self, tip: &BlockChainTip) {
self.update_tip(&tip)
self.update_tip(tip)
}
fn derivation_index(&mut self) -> bip32::ChildNumber {
@ -186,7 +186,7 @@ impl DatabaseConnection for SqliteConn {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Coin {
pub outpoint: bitcoin::OutPoint,
pub block_height: Option<i32>,
@ -221,12 +221,6 @@ impl std::convert::From<DbCoin> for Coin {
}
}
impl std::hash::Hash for Coin {
fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
self.outpoint.hash(h)
}
}
impl Coin {
pub fn is_confirmed(&self) -> bool {
self.block_height.is_some()

View File

@ -36,7 +36,7 @@ pub enum SqliteDbError {
FileNotFound(path::PathBuf),
UnsupportedVersion(i64),
InvalidNetwork(bitcoin::Network),
DescriptorMismatch(InheritanceDescriptor),
DescriptorMismatch(Box<InheritanceDescriptor>),
Rusqlite(rusqlite::Error),
}
@ -102,7 +102,7 @@ impl SqliteDb {
log::info!("Created a fresh database at {}.", db_path.display());
}
if !db_path.exists() {
return Err(SqliteDbError::FileNotFound(db_path.to_path_buf()));
return Err(SqliteDbError::FileNotFound(db_path));
}
Ok(SqliteDb { db_path })
@ -139,7 +139,9 @@ impl SqliteDb {
// The config and db descriptors must match!
let db_wallet = conn.db_wallet();
if &db_wallet.main_descriptor != main_descriptor {
return Err(SqliteDbError::DescriptorMismatch(db_wallet.main_descriptor));
return Err(SqliteDbError::DescriptorMismatch(
db_wallet.main_descriptor.into(),
));
}
Ok(())
@ -215,15 +217,13 @@ impl SqliteConn {
let network = self.db_tip().network;
db_exec(&mut self.conn, |db_tx| {
let db_wallet: DbWallet = db_tx_query(
&db_tx,
"SELECT * FROM wallets",
rusqlite::params![],
|row| row.try_into(),
)
.expect("Db must not fail")
.pop()
.expect("There is always a row in the wallet table");
let db_wallet: DbWallet =
db_tx_query(db_tx, "SELECT * FROM wallets", rusqlite::params![], |row| {
row.try_into()
})
.expect("Db must not fail")
.pop()
.expect("There is always a row in the wallet table");
let next_index: u32 = db_wallet
.deposit_derivation_index
.increment()
@ -240,7 +240,7 @@ impl SqliteConn {
let next_la_index = next_index + LOOK_AHEAD_LIMIT - 1;
let next_la_address = db_wallet
.main_descriptor
.derive(next_la_index.into(), &secp)
.derive(next_la_index.into(), secp)
.address(network);
db_tx
.execute(
@ -470,7 +470,7 @@ mod tests {
.iter()
.collect();
let options = dummy_options();
let db = SqliteDb::new(db_path.clone(), Some(options.clone()), &secp).unwrap();
let db = SqliteDb::new(db_path, Some(options.clone()), &secp).unwrap();
(tmp_dir, options, secp, db)
}
@ -510,7 +510,7 @@ mod tests {
let db = SqliteDb::new(db_path.clone(), Some(options.clone()), &secp).unwrap();
db.sanity_check(bitcoin::Network::Bitcoin, &options.main_descriptor)
.unwrap();
let db = SqliteDb::new(db_path.clone(), None, &secp).unwrap();
let db = SqliteDb::new(db_path, None, &secp).unwrap();
db.sanity_check(bitcoin::Network::Bitcoin, &options.main_descriptor)
.unwrap();
@ -607,14 +607,8 @@ mod tests {
assert_eq!(coins[0].outpoint, coin_b.outpoint);
let coins = conn.db_coins(&[coin_a.outpoint, coin_b.outpoint]);
assert_eq!(coins.len(), 2);
assert!(coins
.iter()
.find(|c| c.outpoint == coin_a.outpoint)
.is_some());
assert!(coins
.iter()
.find(|c| c.outpoint == coin_b.outpoint)
.is_some());
assert!(coins.iter().any(|c| c.outpoint == coin_a.outpoint));
assert!(coins.iter().any(|c| c.outpoint == coin_b.outpoint));
// Now if we confirm one, it'll be marked as such.
let height = 174500;

View File

@ -126,7 +126,7 @@ impl TryFrom<&rusqlite::Row<'_>> for DbWallet {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DbCoin {
pub id: i64,
pub wallet_id: i64,
@ -139,12 +139,6 @@ pub struct DbCoin {
pub spent_at: Option<u32>,
}
impl std::hash::Hash for DbCoin {
fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
self.outpoint.hash(h)
}
}
impl TryFrom<&rusqlite::Row<'_>> for DbCoin {
type Error = rusqlite::Error;

View File

@ -28,13 +28,9 @@ where
P::Item: rusqlite::ToSql,
F: FnMut(&rusqlite::Row<'_>) -> rusqlite::Result<T>,
{
// rustc says 'borrowed value does not live long enough'
let x = tx
.prepare(stmt_str)?
tx.prepare(stmt_str)?
.query_map(params, f)?
.collect::<rusqlite::Result<Vec<T>>>();
x
.collect::<rusqlite::Result<Vec<T>>>()
}
/// Internal helper for queries boilerplate
@ -49,13 +45,9 @@ where
P::Item: rusqlite::ToSql,
F: FnMut(&rusqlite::Row<'_>) -> rusqlite::Result<T>,
{
// rustc says 'borrowed value does not live long enough'
let x = conn
.prepare(stmt_str)?
conn.prepare(stmt_str)?
.query_map(params, f)?
.collect::<rusqlite::Result<Vec<T>>>();
x
.collect::<rusqlite::Result<Vec<T>>>()
}
// Sqlite supports up to i64, thus rusqlite prevents us from inserting u64's.

View File

@ -106,7 +106,7 @@ impl str::FromStr for DerivedPublicKey {
return Err(DescCreationError::DerivedKeyParsing);
}
let key = bitcoin::PublicKey::from_str(&key_str)
let key = bitcoin::PublicKey::from_str(key_str)
.map_err(|_| DescCreationError::DerivedKeyParsing)?;
Ok(DerivedPublicKey {
@ -434,31 +434,31 @@ mod tests {
#[test]
fn inheritance_descriptor_creation() {
let owner_key = descriptor::DescriptorPublicKey::from_str(&"xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/*").unwrap();
let heir_key = descriptor::DescriptorPublicKey::from_str(&"xpub688Hn4wScQAAiYJLPg9yH27hUpfZAUnmJejRQBCiwfP5PEDzjWMNW1wChcninxr5gyavFqbbDjdV1aK5USJz8NDVjUy7FRQaaqqXHh5SbXe/*").unwrap();
let owner_key = descriptor::DescriptorPublicKey::from_str("xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/*").unwrap();
let heir_key = descriptor::DescriptorPublicKey::from_str("xpub688Hn4wScQAAiYJLPg9yH27hUpfZAUnmJejRQBCiwfP5PEDzjWMNW1wChcninxr5gyavFqbbDjdV1aK5USJz8NDVjUy7FRQaaqqXHh5SbXe/*").unwrap();
let timelock = 52560;
assert_eq!(InheritanceDescriptor::new(owner_key.clone(), heir_key.clone(), timelock).unwrap().to_string(), "wsh(or_d(pk(xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/*),and_v(v:pkh(xpub688Hn4wScQAAiYJLPg9yH27hUpfZAUnmJejRQBCiwfP5PEDzjWMNW1wChcninxr5gyavFqbbDjdV1aK5USJz8NDVjUy7FRQaaqqXHh5SbXe/*),older(52560))))#eeyujkt7");
// We prevent footguns with timelocks
InheritanceDescriptor::new(owner_key.clone(), heir_key.clone(), 0x00_01_0f_00).unwrap_err();
InheritanceDescriptor::new(owner_key.clone(), heir_key.clone(), (1 << 31) + 1).unwrap_err();
InheritanceDescriptor::new(owner_key.clone(), heir_key.clone(), (1 << 22) + 1).unwrap_err();
InheritanceDescriptor::new(owner_key, heir_key, (1 << 22) + 1).unwrap_err();
let owner_key = descriptor::DescriptorPublicKey::from_str(&"[aabb0011/10/4893]xpub661MyMwAqRbcFG59fiikD8UV762quhruT8K8bdjqy6N2o3LG7yohoCdLg1m2HAY1W6rfBrtauHkBhbfA4AQ3iazaJj5wVPhwgaRCHBW2DBg/*").unwrap();
let heir_key = descriptor::DescriptorPublicKey::from_str(&"xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/24/32/*").unwrap();
let owner_key = descriptor::DescriptorPublicKey::from_str("[aabb0011/10/4893]xpub661MyMwAqRbcFG59fiikD8UV762quhruT8K8bdjqy6N2o3LG7yohoCdLg1m2HAY1W6rfBrtauHkBhbfA4AQ3iazaJj5wVPhwgaRCHBW2DBg/*").unwrap();
let heir_key = descriptor::DescriptorPublicKey::from_str("xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/24/32/*").unwrap();
let timelock = 57600;
assert_eq!(InheritanceDescriptor::new(owner_key.clone(), heir_key, timelock).unwrap().to_string(), "wsh(or_d(pk([aabb0011/10/4893]xpub661MyMwAqRbcFG59fiikD8UV762quhruT8K8bdjqy6N2o3LG7yohoCdLg1m2HAY1W6rfBrtauHkBhbfA4AQ3iazaJj5wVPhwgaRCHBW2DBg/*),and_v(v:pkh(xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/24/32/*),older(57600))))#8kamh6y8");
// We can't pass a raw key, an xpub that is not deriveable, or only hardened derivable
let heir_key = descriptor::DescriptorPublicKey::from_str(&"xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/0/354").unwrap();
let heir_key = descriptor::DescriptorPublicKey::from_str("xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/0/354").unwrap();
InheritanceDescriptor::new(owner_key.clone(), heir_key, timelock).unwrap_err();
let heir_key = descriptor::DescriptorPublicKey::from_str(&"xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/0/*'").unwrap();
let heir_key = descriptor::DescriptorPublicKey::from_str("xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/0/*'").unwrap();
InheritanceDescriptor::new(owner_key.clone(), heir_key, timelock).unwrap_err();
let heir_key = descriptor::DescriptorPublicKey::from_str(
&"02e24913be26dbcfdf8e8e94870b28725cdae09b448b6c127767bf0154e3a3c8e5",
"02e24913be26dbcfdf8e8e94870b28725cdae09b448b6c127767bf0154e3a3c8e5",
)
.unwrap();
InheritanceDescriptor::new(owner_key.clone(), heir_key, timelock).unwrap_err();
InheritanceDescriptor::new(owner_key, heir_key, timelock).unwrap_err();
}
#[test]

View File

@ -10,38 +10,38 @@ use miniscript::bitcoin::{self, consensus, util::psbt::PartiallySignedTransactio
fn create_spend(control: &DaemonControl, params: Params) -> Result<serde_json::Value, Error> {
let outpoints = params
.get(0, "outpoints")
.ok_or(Error::invalid_params("Missing 'outpoints' parameter."))?
.ok_or_else(|| Error::invalid_params("Missing 'outpoints' parameter."))?
.as_array()
.and_then(|arr| {
arr.into_iter()
arr.iter()
.map(|entry| {
entry
.as_str()
.and_then(|e| bitcoin::OutPoint::from_str(&e).ok())
.and_then(|e| bitcoin::OutPoint::from_str(e).ok())
})
.collect::<Option<Vec<bitcoin::OutPoint>>>()
})
.ok_or(Error::invalid_params("Invalid 'outpoints' parameter."))?;
.ok_or_else(|| Error::invalid_params("Invalid 'outpoints' parameter."))?;
let destinations = params
.get(1, "destinations")
.ok_or(Error::invalid_params("Missing 'destinations' parameter."))?
.ok_or_else(|| Error::invalid_params("Missing 'destinations' parameter."))?
.as_object()
.and_then(|obj| {
obj.into_iter()
.map(|(k, v)| {
let addr = bitcoin::Address::from_str(&k).ok()?;
let addr = bitcoin::Address::from_str(k).ok()?;
let amount: u64 = v.as_i64()?.try_into().ok()?;
Some((addr, amount))
})
.collect::<Option<HashMap<bitcoin::Address, u64>>>()
})
.ok_or(Error::invalid_params("Invalid 'destinations' parameter."))?;
.ok_or_else(|| Error::invalid_params("Invalid 'destinations' parameter."))?;
let feerate: u64 = params
.get(2, "feerate")
.ok_or(Error::invalid_params("Missing 'feerate' parameter."))?
.ok_or_else(|| Error::invalid_params("Missing 'feerate' parameter."))?
.as_i64()
.and_then(|i| i.try_into().ok())
.ok_or(Error::invalid_params("Invalid 'feerate' parameter."))?;
.ok_or_else(|| Error::invalid_params("Invalid 'feerate' parameter."))?;
let res = control.create_spend(&outpoints, &destinations, feerate)?;
Ok(serde_json::json!(&res))
@ -50,11 +50,11 @@ fn create_spend(control: &DaemonControl, params: Params) -> Result<serde_json::V
fn update_spend(control: &DaemonControl, params: Params) -> Result<serde_json::Value, Error> {
let psbt: Psbt = params
.get(0, "psbt")
.ok_or(Error::invalid_params("Missing 'psbt' parameter."))?
.ok_or_else(|| Error::invalid_params("Missing 'psbt' parameter."))?
.as_str()
.and_then(|s| base64::decode(&s).ok())
.and_then(|bytes| consensus::deserialize(&bytes).ok())
.ok_or(Error::invalid_params("Invalid 'feerate' parameter."))?;
.ok_or_else(|| Error::invalid_params("Invalid 'feerate' parameter."))?;
control.update_spend(psbt)?;
Ok(serde_json::json!({}))
@ -63,10 +63,10 @@ fn update_spend(control: &DaemonControl, params: Params) -> Result<serde_json::V
fn delete_spend(control: &DaemonControl, params: Params) -> Result<serde_json::Value, Error> {
let txid = params
.get(0, "txid")
.ok_or(Error::invalid_params("Missing 'txid' parameter."))?
.ok_or_else(|| Error::invalid_params("Missing 'txid' parameter."))?
.as_str()
.and_then(|s| bitcoin::Txid::from_str(&s).ok())
.ok_or(Error::invalid_params("Invalid 'feerate' parameter."))?;
.and_then(|s| bitcoin::Txid::from_str(s).ok())
.ok_or_else(|| Error::invalid_params("Invalid 'feerate' parameter."))?;
control.delete_spend(&txid);
Ok(serde_json::json!({}))
@ -76,15 +76,17 @@ fn delete_spend(control: &DaemonControl, params: Params) -> Result<serde_json::V
pub fn handle_request(control: &DaemonControl, req: Request) -> Result<Response, Error> {
let result = match req.method.as_str() {
"createspend" => {
let params = req.params.ok_or(Error::invalid_params(
"Missing 'outpoints', 'destinations' and 'feerate' parameters.",
))?;
let params = req.params.ok_or_else(|| {
Error::invalid_params(
"Missing 'outpoints', 'destinations' and 'feerate' parameters.",
)
})?;
create_spend(control, params)?
}
"delspendtx" => {
let params = req
.params
.ok_or(Error::invalid_params("Missing 'txid' parameter."))?;
.ok_or_else(|| Error::invalid_params("Missing 'txid' parameter."))?;
delete_spend(control, params)?
}
"getinfo" => serde_json::json!(&control.get_info()),
@ -95,7 +97,7 @@ pub fn handle_request(control: &DaemonControl, req: Request) -> Result<Response,
"updatespend" => {
let params = req
.params
.ok_or(Error::invalid_params("Missing 'psbt' parameter."))?;
.ok_or_else(|| Error::invalid_params("Missing 'psbt' parameter."))?;
update_spend(control, params)?
}
_ => {

View File

@ -7,7 +7,7 @@ use std::{error, fmt};
use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
#[serde(untagged)]
pub enum Params {
@ -29,7 +29,7 @@ impl Params {
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
#[serde(untagged)]
pub enum ReqId {
@ -38,7 +38,7 @@ pub enum ReqId {
}
/// A JSONRPC2 request. See https://www.jsonrpc.org/specification#request_object.
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Request {
/// Version. Must be "2.0".
@ -52,7 +52,7 @@ pub struct Request {
}
/// JSONRPC2 error codes. See https://www.jsonrpc.org/specification#error_object.
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ErrorCode {
/// The method does not exist / is not available.
MethodNotFound,
@ -64,9 +64,9 @@ pub enum ErrorCode {
ServerError(i64),
}
impl Into<i64> for &ErrorCode {
fn into(self) -> i64 {
match self {
impl From<&ErrorCode> for i64 {
fn from(code: &ErrorCode) -> i64 {
match code {
ErrorCode::MethodNotFound => -32601,
ErrorCode::InvalidParams => -32602,
ErrorCode::InternalError => -32603,
@ -105,7 +105,7 @@ impl Serialize for ErrorCode {
}
/// JSONRPC2 error response. See https://www.jsonrpc.org/specification#error_object.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Error {
pub code: ErrorCode,
@ -164,7 +164,7 @@ impl From<commands::CommandError> for Error {
}
/// JSONRPC2 response. See https://www.jsonrpc.org/specification#response_object.
#[derive(Clone, Debug, PartialEq, Serialize)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Response {
/// Version. Must be "2.0".

View File

@ -196,7 +196,9 @@ pub fn rpcserver_setup(socket_path: &path::Path) -> Result<net::UnixListener, io
// Create the socket with RW permissions only for the user
#[cfg(not(test))]
let old_umask = unsafe { libc::umask(0o177) };
let listener = bind(&socket_path);
#[allow(clippy::all)]
let listener = bind(socket_path);
#[cfg(not(test))]
unsafe {
libc::umask(old_umask);
@ -249,7 +251,7 @@ mod tests {
fn write_messages(socket_path: &path::Path, messages: &[&[u8]]) {
let mut client = net::UnixStream::connect(&socket_path).unwrap();
for mess in messages {
client.write_all(&mess).unwrap();
client.write_all(mess).unwrap();
// Simulate throttling, this mimics real conditions and actually triggered a crash.
thread::sleep(time::Duration::from_millis(50));
}
@ -305,7 +307,7 @@ mod tests {
let t = read_one_command(&socket_path);
let req = br#"{"jsonrpc": "2.0", "id": 0, "method": "test", "params": ["a", 10]}"#;
let parsed_req: Request = serde_json::from_slice(req).unwrap();
let tmp: Vec<Vec<u8>> = req.into_iter().map(|c| vec![*c]).collect();
let tmp: Vec<Vec<u8>> = req.iter().map(|c| vec![*c]).collect();
let mut to_send: Vec<&[u8]> = tmp.iter().map(|v| v.as_slice()).collect();
to_send.push(b"\n");
write_messages(&socket_path, &to_send);

View File

@ -497,7 +497,7 @@ mod tests {
}
// Send them a response to 'listwallets' with the watchonly wallet path
fn complete_wallet_check<'a>(server: &net::TcpListener, watchonly_wallet_path: &'a str) {
fn complete_wallet_check(server: &net::TcpListener, watchonly_wallet_path: &str) {
let net_resp = [
"HTTP/1.1 200\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[\"".as_bytes(),
watchonly_wallet_path.as_bytes(),
@ -511,7 +511,7 @@ mod tests {
}
// Send them a response to 'listdescriptors' with the main descriptor
fn complete_desc_check<'a>(server: &net::TcpListener, desc: &'a str) {
fn complete_desc_check(server: &net::TcpListener, desc: &str) {
let net_resp = [
"HTTP/1.1 200\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"descriptors\":[{\"desc\":\"".as_bytes(),
desc.as_bytes(),
@ -525,7 +525,7 @@ mod tests {
}
// Send them a response to 'getblockhash' with the genesis block hash
fn complete_tip_init<'a>(server: &net::TcpListener) {
fn complete_tip_init(server: &net::TcpListener) {
let net_resp = [
"HTTP/1.1 200\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f\"}\n".as_bytes(),
]
@ -537,7 +537,7 @@ mod tests {
}
// Send them a response to 'getblockchaininfo' saying we are far from being synced
fn complete_sync_check<'a>(server: &net::TcpListener) {
fn complete_sync_check(server: &net::TcpListener) {
let net_resp = [
"HTTP/1.1 200\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"verificationprogress\":0.1}}\n".as_bytes(),
]
@ -590,7 +590,7 @@ mod tests {
};
let bitcoind_config = BitcoindConfig {
addr,
cookie_path: cookie.clone(),
cookie_path: cookie,
};
// Create a dummy config with this bitcoind
@ -599,7 +599,7 @@ mod tests {
let config = Config {
bitcoin_config,
bitcoind_config: Some(bitcoind_config),
data_dir: Some(data_dir.clone()),
data_dir: Some(data_dir),
#[cfg(unix)]
daemon: false,
log_level: log::LevelFilter::Debug,

View File

@ -82,6 +82,12 @@ impl DummyDb {
}
}
impl Default for DummyDb {
fn default() -> DummyDb {
DummyDb::new()
}
}
impl DatabaseInterface for sync::Arc<sync::RwLock<DummyDb>> {
fn connection(&self) -> Box<dyn DatabaseConnection> {
Box::new(DummyDbConn { db: self.clone() })
@ -121,8 +127,8 @@ impl DatabaseConnection for DummyDbConn {
fn list_spending_coins(&mut self) -> HashMap<bitcoin::OutPoint, Coin> {
let mut result = HashMap::new();
for (k, v) in self.db.read().unwrap().coins.iter() {
if !v.spend_txid.is_none() {
result.insert(k.clone(), v.clone());
if v.spend_txid.is_some() {
result.insert(*k, v.clone());
}
}
result
@ -163,7 +169,7 @@ impl DatabaseConnection for DummyDbConn {
for (op, spend_txid, time) in outpoints {
let mut db = self.db.write().unwrap();
let spent = &mut db.coins.get_mut(op).unwrap();
assert!(!spent.spend_txid.is_none());
assert!(spent.spend_txid.is_some());
assert!(spent.spent_at.is_none());
spent.spend_txid = Some(*spend_txid);
spent.spent_at = Some(*time);
@ -185,7 +191,7 @@ impl DatabaseConnection for DummyDbConn {
.coins
.clone()
.into_iter()
.filter(|(op, _)| outpoints.contains(&op))
.filter(|(op, _)| outpoints.contains(op))
.collect()
}
@ -260,7 +266,7 @@ impl DummyMinisafe {
let config = Config {
bitcoin_config,
bitcoind_config: None,
data_dir: Some(data_dir.clone()),
data_dir: Some(data_dir),
#[cfg(unix)]
daemon: false,
log_level: log::LevelFilter::Debug,