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:
parent
ca771e3e00
commit
07e5ea3fd2
@ -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,
|
||||||
|
|||||||
@ -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())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user