commands: add a new 'update_spend' command
Store a Spend PSBT in database, and update it if it already exists. Merge signatures between the given PSBT and the one in db.
This commit is contained in:
parent
bafcadf398
commit
cf45ba0fa5
@ -366,6 +366,58 @@ impl DaemonControl {
|
||||
|
||||
Ok(CreateSpendResult { psbt })
|
||||
}
|
||||
|
||||
pub fn update_spend(&self, mut psbt: Psbt) -> Result<(), CommandError> {
|
||||
let mut db_conn = self.db.connection();
|
||||
let tx = &psbt.global.unsigned_tx;
|
||||
|
||||
// If the transaction already exists in DB, merge the signatures for each input on a best
|
||||
// effort basis.
|
||||
// We work on the newly provided PSBT, in case its content was updated.
|
||||
let txid = tx.txid();
|
||||
if let Some(db_psbt) = db_conn.spend_tx(&txid) {
|
||||
let db_tx = db_psbt.global.unsigned_tx;
|
||||
for i in 0..db_tx.input.len() {
|
||||
if tx
|
||||
.input
|
||||
.get(i)
|
||||
.map(|tx_in| tx_in.previous_output == db_tx.input[i].previous_output)
|
||||
!= Some(true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let psbtin = match psbt.inputs.get_mut(i) {
|
||||
Some(psbtin) => psbtin,
|
||||
None => continue,
|
||||
};
|
||||
let db_psbtin = match db_psbt.inputs.get(i) {
|
||||
Some(db_psbtin) => db_psbtin,
|
||||
None => continue,
|
||||
};
|
||||
psbtin
|
||||
.partial_sigs
|
||||
.extend(db_psbtin.partial_sigs.clone().into_iter());
|
||||
}
|
||||
} else {
|
||||
// If the transaction doesn't exist in DB already, sanity check its inputs.
|
||||
// FIXME: should we allow for external inputs?
|
||||
let outpoints: Vec<bitcoin::OutPoint> =
|
||||
tx.input.iter().map(|txin| txin.previous_output).collect();
|
||||
let coins = db_conn.coins_by_outpoints(&outpoints);
|
||||
if coins.len() != outpoints.len() {
|
||||
for op in outpoints {
|
||||
if coins.get(&op).is_none() {
|
||||
return Err(CommandError::UnknownOutpoint(op));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, insert (or update) the PSBT in database.
|
||||
db_conn.store_spend(&psbt);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@ -414,6 +466,7 @@ pub struct CreateSpendResult {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::testutils::*;
|
||||
use bitcoin::hashes::hex::FromHex;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
@ -557,4 +610,107 @@ mod tests {
|
||||
|
||||
ms.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_spend() {
|
||||
let ms = DummyMinisafe::new();
|
||||
let control = &ms.handle.control;
|
||||
let mut db_conn = control.db().lock().unwrap().connection();
|
||||
|
||||
// Add two (unconfirmed) coins in DB
|
||||
let dummy_op_a = bitcoin::OutPoint::from_str(
|
||||
"3753a1d74c0af8dd0a0f3b763c14faf3bd9ed03cbdf33337a074fb0e9f6c7810:0",
|
||||
)
|
||||
.unwrap();
|
||||
let dummy_op_b = bitcoin::OutPoint::from_str(
|
||||
"4753a1d74c0af8dd0a0f3b763c14faf3bd9ed03cbdf33337a074fb0e9f6c7810:1",
|
||||
)
|
||||
.unwrap();
|
||||
db_conn.new_unspent_coins(&[
|
||||
Coin {
|
||||
outpoint: dummy_op_a,
|
||||
block_height: None,
|
||||
amount: bitcoin::Amount::from_sat(100_000),
|
||||
derivation_index: bip32::ChildNumber::from(13),
|
||||
spend_txid: None,
|
||||
},
|
||||
Coin {
|
||||
outpoint: dummy_op_b,
|
||||
block_height: None,
|
||||
amount: bitcoin::Amount::from_sat(115_680),
|
||||
derivation_index: bip32::ChildNumber::from(34),
|
||||
spend_txid: None,
|
||||
},
|
||||
]);
|
||||
|
||||
// Now create three transactions spending those coins differently
|
||||
let dummy_addr_a =
|
||||
bitcoin::Address::from_str("bc1qnsexk3gnuyayu92fc3tczvc7k62u22a22ua2kv").unwrap();
|
||||
let dummy_addr_b =
|
||||
bitcoin::Address::from_str("bc1q39srgatmkp6k2ne3l52yhkjprdvunvspqydmkx").unwrap();
|
||||
let dummy_value_a = 50_000;
|
||||
let dummy_value_b = 60_000;
|
||||
let destinations_a: HashMap<bitcoin::Address, u64> =
|
||||
[(dummy_addr_a.clone(), dummy_value_a)]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
let destinations_b: HashMap<bitcoin::Address, u64> =
|
||||
[(dummy_addr_b.clone(), dummy_value_b)]
|
||||
.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 mut psbt_a = control
|
||||
.create_spend(&[dummy_op_a], &destinations_a, 1)
|
||||
.unwrap()
|
||||
.psbt;
|
||||
let txid_a = psbt_a.global.unsigned_tx.txid();
|
||||
let psbt_b = control
|
||||
.create_spend(&[dummy_op_b], &destinations_b, 10)
|
||||
.unwrap()
|
||||
.psbt;
|
||||
let txid_b = psbt_b.global.unsigned_tx.txid();
|
||||
let psbt_c = control
|
||||
.create_spend(&[dummy_op_a, dummy_op_b], &destinations_c, 100)
|
||||
.unwrap()
|
||||
.psbt;
|
||||
let txid_c = psbt_c.global.unsigned_tx.txid();
|
||||
|
||||
// We can store and query them all
|
||||
control.update_spend(psbt_a.clone()).unwrap();
|
||||
assert_eq!(db_conn.spend_tx(&txid_a).unwrap(), psbt_a);
|
||||
control.update_spend(psbt_b.clone()).unwrap();
|
||||
assert_eq!(db_conn.spend_tx(&txid_b).unwrap(), psbt_b);
|
||||
control.update_spend(psbt_c.clone()).unwrap();
|
||||
assert_eq!(db_conn.spend_tx(&txid_c).unwrap(), psbt_c);
|
||||
|
||||
// As well as update them, with or without new signatures
|
||||
psbt_a.inputs[0].partial_sigs.insert(bitcoin::PublicKey::from_str("023a664c5617412f0b292665b1fd9d766456a7a3b1614c7e7c5f411200ff1958ef").unwrap(), Vec::<u8>::from_hex("304402204004fcdbb9c0d0cbf585f58cee34dccb012efbd8fc2b0d5e97760045ae35803802201a0bd7ec2383e0b93748abc9946c8e17a8312e314dab85982aeba650e738cbf401").unwrap());
|
||||
control.update_spend(psbt_a.clone()).unwrap();
|
||||
assert_eq!(db_conn.spend_tx(&txid_a).unwrap(), psbt_a);
|
||||
control.update_spend(psbt_b.clone()).unwrap();
|
||||
assert_eq!(db_conn.spend_tx(&txid_b).unwrap(), psbt_b);
|
||||
control.update_spend(psbt_c.clone()).unwrap();
|
||||
assert_eq!(db_conn.spend_tx(&txid_c).unwrap(), psbt_c);
|
||||
|
||||
// We can't store a PSBT spending an external coin
|
||||
let external_op = bitcoin::OutPoint::from_str(
|
||||
"8753a1d74c0af8dd0a0f3b763c14faf3bd9ed03cbdf33337a074fb0e9f6c7810:2",
|
||||
)
|
||||
.unwrap();
|
||||
psbt_a.global.unsigned_tx.input[0].previous_output = external_op;
|
||||
assert_eq!(
|
||||
control.update_spend(psbt_a),
|
||||
Err(CommandError::UnknownOutpoint(external_op))
|
||||
);
|
||||
|
||||
ms.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user