commands: fill non_witness_utxo field in PSBT inputs for createspend

We are still in segwit v0, so signers need it!!
This commit is contained in:
Antoine Poinsot 2022-12-05 19:03:25 +01:00
parent ca771e3e00
commit 07e5ea3fd2
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
3 changed files with 67 additions and 12 deletions

View File

@ -57,6 +57,7 @@ pub enum CommandError {
/* out value */ bitcoin::Amount, /* out value */ bitcoin::Amount,
/* target feerate */ u64, /* target feerate */ u64,
), ),
FetchingTransaction(bitcoin::OutPoint),
SanityCheckFailure(Psbt), SanityCheckFailure(Psbt),
UnknownSpend(bitcoin::Txid), UnknownSpend(bitcoin::Txid),
// FIXME: when upgrading Miniscript put the actual error there // FIXME: when upgrading Miniscript put the actual error there
@ -82,6 +83,9 @@ impl fmt::Display for CommandError {
"Cannot create a {} sat/vb transaction with input value {} and output value {}", "Cannot create a {} sat/vb transaction with input value {} and output value {}",
feerate, in_val, out_val feerate, in_val, out_val
), ),
Self::FetchingTransaction(op) => {
write!(f, "Could not fetch transaction for coin {}", op)
}
Self::SanityCheckFailure(psbt) => write!( Self::SanityCheckFailure(psbt) => write!(
f, f,
"BUG! Please report this. Failed sanity checks for PSBT '{:?}'.", "BUG! Please report this. Failed sanity checks for PSBT '{:?}'.",
@ -283,7 +287,7 @@ impl DaemonControl {
} }
let mut db_conn = self.db.connection(); let mut db_conn = self.db.connection();
// Iterate through given outpoints to fetch the coins (hence checking there existence // Iterate through given outpoints to fetch the coins (hence checking their existence
// at the same time). We checked there is at least one, therefore after this loop the // at the same time). We checked there is at least one, therefore after this loop the
// list of coins is not empty. // list of coins is not empty.
// While doing so, we record the total input value of the transaction to later compute // While doing so, we record the total input value of the transaction to later compute
@ -292,12 +296,23 @@ impl DaemonControl {
let mut sat_vb = 0; let mut sat_vb = 0;
let mut txins = Vec::with_capacity(destinations.len()); let mut txins = Vec::with_capacity(destinations.len());
let mut psbt_ins = Vec::with_capacity(destinations.len()); let mut psbt_ins = Vec::with_capacity(destinations.len());
let mut spent_txs = HashMap::with_capacity(coins_outpoints.len());
let coins = db_conn.coins_by_outpoints(coins_outpoints); let coins = db_conn.coins_by_outpoints(coins_outpoints);
for op in coins_outpoints { for op in coins_outpoints {
// Get the coin from our in-DB unspent txos
let coin = coins.get(op).ok_or(CommandError::UnknownOutpoint(*op))?; let coin = coins.get(op).ok_or(CommandError::UnknownOutpoint(*op))?;
if coin.is_spent() { if coin.is_spent() {
return Err(CommandError::AlreadySpent(*op)); return Err(CommandError::AlreadySpent(*op));
} }
// Fetch the transaction that created it if necessary
if !spent_txs.contains_key(op) {
let tx = self
.bitcoin
.wallet_transaction(&op.txid)
.ok_or(CommandError::FetchingTransaction(*op))?;
spent_txs.insert(*op, tx.0);
}
in_value += coin.amount; in_value += coin.amount;
txins.push(bitcoin::TxIn { txins.push(bitcoin::TxIn {
previous_output: *op, previous_output: *op,
@ -306,6 +321,7 @@ impl DaemonControl {
..bitcoin::TxIn::default() ..bitcoin::TxIn::default()
}); });
// Populate the PSBT input with the information needed by signers.
let coin_desc = self.derived_desc(coin); let coin_desc = self.derived_desc(coin);
sat_vb += desc_sat_vb(&coin_desc); sat_vb += desc_sat_vb(&coin_desc);
let witness_script = Some(coin_desc.witness_script()); let witness_script = Some(coin_desc.witness_script());
@ -313,11 +329,13 @@ impl DaemonControl {
value: coin.amount.to_sat(), value: coin.amount.to_sat(),
script_pubkey: coin_desc.script_pubkey(), script_pubkey: coin_desc.script_pubkey(),
}); });
let non_witness_utxo = spent_txs.get(op).cloned();
let bip32_derivation = coin_desc.bip32_derivations(); let bip32_derivation = coin_desc.bip32_derivations();
psbt_ins.push(PsbtIn { psbt_ins.push(PsbtIn {
witness_script, witness_script,
witness_utxo, witness_utxo,
bip32_derivation, bip32_derivation,
non_witness_utxo,
..PsbtIn::default() ..PsbtIn::default()
}); });
} }
@ -728,14 +746,27 @@ mod tests {
#[test] #[test]
fn create_spend() { fn create_spend() {
let ms = DummyLiana::new(DummyBitcoind::new(), DummyDatabase::new());
let control = &ms.handle.control;
// Arguments sanity checking
let dummy_op = bitcoin::OutPoint::from_str( let dummy_op = bitcoin::OutPoint::from_str(
"3753a1d74c0af8dd0a0f3b763c14faf3bd9ed03cbdf33337a074fb0e9f6c7810:0", "3753a1d74c0af8dd0a0f3b763c14faf3bd9ed03cbdf33337a074fb0e9f6c7810:0",
) )
.unwrap(); .unwrap();
let mut dummy_bitcoind = DummyBitcoind::new();
dummy_bitcoind.txs.insert(
dummy_op.txid,
(
bitcoin::Transaction {
version: 2,
lock_time: bitcoin::PackedLockTime(0),
input: vec![],
output: vec![],
},
None,
),
);
let ms = DummyLiana::new(dummy_bitcoind, DummyDatabase::new());
let control = &ms.handle.control;
// Arguments sanity checking
let dummy_addr = let dummy_addr =
bitcoin::Address::from_str("bc1qnsexk3gnuyayu92fc3tczvc7k62u22a22ua2kv").unwrap(); bitcoin::Address::from_str("bc1qnsexk3gnuyayu92fc3tczvc7k62u22a22ua2kv").unwrap();
let dummy_value = 10_000; let dummy_value = 10_000;
@ -774,6 +805,7 @@ mod tests {
spend_block: None, spend_block: None,
}]); }]);
let res = control.create_spend(&destinations, &[dummy_op], 1).unwrap(); let res = control.create_spend(&destinations, &[dummy_op], 1).unwrap();
assert!(res.psbt.inputs[0].non_witness_utxo.is_some());
let tx = res.psbt.unsigned_tx; let tx = res.psbt.unsigned_tx;
assert_eq!(tx.input.len(), 1); assert_eq!(tx.input.len(), 1);
assert_eq!(tx.input[0].previous_output, dummy_op); assert_eq!(tx.input[0].previous_output, dummy_op);
@ -844,11 +876,6 @@ mod tests {
#[test] #[test]
fn update_spend() { fn update_spend() {
let ms = DummyLiana::new(DummyBitcoind::new(), DummyDatabase::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( let dummy_op_a = bitcoin::OutPoint::from_str(
"3753a1d74c0af8dd0a0f3b763c14faf3bd9ed03cbdf33337a074fb0e9f6c7810:0", "3753a1d74c0af8dd0a0f3b763c14faf3bd9ed03cbdf33337a074fb0e9f6c7810:0",
) )
@ -857,6 +884,22 @@ mod tests {
"4753a1d74c0af8dd0a0f3b763c14faf3bd9ed03cbdf33337a074fb0e9f6c7810:1", "4753a1d74c0af8dd0a0f3b763c14faf3bd9ed03cbdf33337a074fb0e9f6c7810:1",
) )
.unwrap(); .unwrap();
let mut dummy_bitcoind = DummyBitcoind::new();
let dummy_tx = bitcoin::Transaction {
version: 2,
lock_time: bitcoin::PackedLockTime(0),
input: vec![],
output: vec![],
};
dummy_bitcoind
.txs
.insert(dummy_op_a.txid, (dummy_tx.clone(), None));
dummy_bitcoind.txs.insert(dummy_op_b.txid, (dummy_tx, None));
let ms = DummyLiana::new(dummy_bitcoind, DummyDatabase::new());
let control = &ms.handle.control;
let mut db_conn = control.db().lock().unwrap().connection();
// Add two (unconfirmed) coins in DB
db_conn.new_unspent_coins(&[ db_conn.new_unspent_coins(&[
Coin { Coin {
outpoint: dummy_op_a, outpoint: dummy_op_a,

View File

@ -164,7 +164,8 @@ impl From<commands::CommandError> for Error {
| commands::CommandError::AlreadyRescanning => { | commands::CommandError::AlreadyRescanning => {
Error::new(ErrorCode::InvalidParams, e.to_string()) Error::new(ErrorCode::InvalidParams, e.to_string())
} }
commands::CommandError::SanityCheckFailure(_) commands::CommandError::FetchingTransaction(..)
| commands::CommandError::SanityCheckFailure(_)
| commands::CommandError::RescanTrigger(..) => { | commands::CommandError::RescanTrigger(..) => {
Error::new(ErrorCode::InternalError, e.to_string()) Error::new(ErrorCode::InternalError, e.to_string())
} }

View File

@ -3,7 +3,11 @@ import random
import time import time
from fixtures import * from fixtures import *
from test_framework.serializations import PSBT, PSBT_IN_PARTIAL_SIG from test_framework.serializations import (
PSBT,
PSBT_IN_PARTIAL_SIG,
PSBT_IN_NON_WITNESS_UTXO,
)
from test_framework.utils import wait_for, COIN, RpcError, get_txid, spend_coins from test_framework.utils import wait_for, COIN, RpcError, get_txid, spend_coins
@ -111,6 +115,13 @@ def test_create_spend(lianad, bitcoind):
assert len(spend_psbt.o) == 4 assert len(spend_psbt.o) == 4
assert len(spend_psbt.tx.vout) == 4 assert len(spend_psbt.tx.vout) == 4
# The transaction must contain the spent transaction for each input
spent_txs = [bitcoind.rpc.gettransaction(op[:64]) for op in outpoints]
for i, psbt_in in enumerate(spend_psbt.i):
assert psbt_in.map[PSBT_IN_NON_WITNESS_UTXO] == bytes.fromhex(
spent_txs[i]["hex"]
)
# We can sign it and broadcast it. # We can sign it and broadcast it.
signed_psbt = lianad.sign_psbt(PSBT.from_base64(res["psbt"])) signed_psbt = lianad.sign_psbt(PSBT.from_base64(res["psbt"]))
finalized_psbt = lianad.finalize_psbt(signed_psbt) finalized_psbt = lianad.finalize_psbt(signed_psbt)