lianad: update rust-miniscript (and rust-bitcoin) dependencies
Rust-bitcoin, that we use through rust-miniscript, has seen plenty of breaking changes in the latest version. I've tried to keep the necessary changes here minimal, still it had to be a single commit to keep it hygienic. But i'll try to summarize the main things here. Tobin also wrote a guide about the release at https://rust-bitcoin.org/blog/release-0.30.0/. The most verbose change in this commit is probably due to the `Address` type overhaul. It's overengineered if you ask me but hey here we are. I tried to keep network validation in commands, and otherwise passing around unchecked addresses (to avoid having to pass around a global state between our various components). Another non-obvious change was changes in hash types upstream and the removal of `ToHex`, forcing us to get the hex representation of a txid through its `Display` implementation. It is however displayed backward in this case ("little-endian" if you will), and we need a regular hex encoding for some queries to the database. We needed to make sure we didn't implement any silent bug here. The rest (Script type changes, PSBT serialization updates, ..) is probably self-explanatory.
This commit is contained in:
parent
3bea69170f
commit
e28010915f
46
Cargo.lock
generated
46
Cargo.lock
generated
@ -70,29 +70,45 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f"
|
||||
dependencies = [
|
||||
"bitcoin_hashes",
|
||||
"bitcoin_hashes 0.11.0",
|
||||
"serde",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitcoin"
|
||||
version = "0.29.2"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3"
|
||||
checksum = "b36f4c848f6bd9ff208128f08751135846cc23ae57d66ab10a22efff1c675f3c"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bech32",
|
||||
"bitcoin_hashes",
|
||||
"bitcoin-private",
|
||||
"bitcoin_hashes 0.12.0",
|
||||
"hex_lit",
|
||||
"secp256k1",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitcoin-private"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57"
|
||||
|
||||
[[package]]
|
||||
name = "bitcoin_hashes"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4"
|
||||
|
||||
[[package]]
|
||||
name = "bitcoin_hashes"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d7066118b13d4b20b23645932dfb3a81ce7e29f95726c2036fa33cd7b092501"
|
||||
dependencies = [
|
||||
"bitcoin-private",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@ -196,6 +212,12 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex_lit"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.4"
|
||||
@ -269,10 +291,12 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "miniscript"
|
||||
version = "9.0.0"
|
||||
source = "git+https://github.com/darosior/rust-miniscript?branch=multipath_descriptors_on_9.0#3104519501ce6ad15b36dcec759936f4d3bd3980"
|
||||
version = "10.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1eb102b66b2127a872dbcc73095b7b47aeb9d92f7b03c2b2298253ffc82c7594"
|
||||
dependencies = [
|
||||
"bitcoin",
|
||||
"bitcoin-private",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@ -391,20 +415,20 @@ checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
||||
|
||||
[[package]]
|
||||
name = "secp256k1"
|
||||
version = "0.24.0"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7649a0b3ffb32636e60c7ce0d70511eda9c52c658cd0634e194d5a19943aeff"
|
||||
checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f"
|
||||
dependencies = [
|
||||
"bitcoin_hashes",
|
||||
"bitcoin_hashes 0.12.0",
|
||||
"secp256k1-sys",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secp256k1-sys"
|
||||
version = "0.6.1"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b"
|
||||
checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
@ -25,7 +25,7 @@ daemon = ["libc"]
|
||||
|
||||
[dependencies]
|
||||
# For managing transactions (it re-exports the bitcoin crate)
|
||||
miniscript = { git = "https://github.com/darosior/rust-miniscript", branch = "multipath_descriptors_on_9.0", features = ["serde", "compiler"] }
|
||||
miniscript = { version = "10.0", features = ["serde", "compiler", "base64"] }
|
||||
|
||||
# Don't reinvent the wheel
|
||||
dirs = "5.0"
|
||||
|
||||
@ -27,7 +27,7 @@ use jsonrpc::{
|
||||
};
|
||||
|
||||
use miniscript::{
|
||||
bitcoin::{self, hashes::hex::FromHex},
|
||||
bitcoin::{self, address, hashes::hex::FromHex},
|
||||
descriptor,
|
||||
};
|
||||
|
||||
@ -651,6 +651,7 @@ impl BitcoinD {
|
||||
bitcoin::Network::Testnet => "test",
|
||||
bitcoin::Network::Regtest => "regtest",
|
||||
bitcoin::Network::Signet => "signet",
|
||||
_ => "Unknown network, undefined at the time of writing",
|
||||
};
|
||||
if bitcoind_net != bip70_net {
|
||||
return Err(BitcoindError::NetworkMismatch(
|
||||
@ -1072,7 +1073,7 @@ pub struct LSBlockEntry {
|
||||
pub outpoint: bitcoin::OutPoint,
|
||||
pub amount: bitcoin::Amount,
|
||||
pub block_height: Option<i32>,
|
||||
pub address: bitcoin::Address,
|
||||
pub address: bitcoin::Address<address::NetworkUnchecked>,
|
||||
pub parent_descs: Vec<descriptor::Descriptor<descriptor::DescriptorPublicKey>>,
|
||||
}
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ use crate::{
|
||||
|
||||
use std::{fmt, sync};
|
||||
|
||||
use miniscript::bitcoin;
|
||||
use miniscript::bitcoin::{self, address};
|
||||
|
||||
/// Information about a block
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Copy)]
|
||||
@ -408,5 +408,5 @@ pub struct UTxO {
|
||||
pub outpoint: bitcoin::OutPoint,
|
||||
pub amount: bitcoin::Amount,
|
||||
pub block_height: Option<i32>,
|
||||
pub address: bitcoin::Address,
|
||||
pub address: bitcoin::Address<address::NetworkUnchecked>,
|
||||
}
|
||||
|
||||
@ -31,16 +31,28 @@ fn update_coins(
|
||||
descs: &[descriptors::SinglePathLianaDesc],
|
||||
secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>,
|
||||
) -> UpdatedCoins {
|
||||
let network = db_conn.network();
|
||||
let curr_coins = db_conn.coins(CoinType::All);
|
||||
log::debug!("Current coins: {:?}", curr_coins);
|
||||
|
||||
// Start by fetching newly received coins.
|
||||
let mut received = Vec::new();
|
||||
for utxo in bit.received_coins(previous_tip, descs) {
|
||||
let UTxO {
|
||||
outpoint,
|
||||
amount,
|
||||
address,
|
||||
..
|
||||
} = utxo;
|
||||
// We can only really treat them if we know the derivation index that was used.
|
||||
if let Some((derivation_index, is_change)) =
|
||||
db_conn.derivation_index_by_address(&utxo.address)
|
||||
{
|
||||
let address = match address.require_network(network) {
|
||||
Ok(addr) => addr,
|
||||
Err(e) => {
|
||||
log::error!("Invalid network for address: {}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if let Some((derivation_index, is_change)) = db_conn.derivation_index_by_address(&address) {
|
||||
// First of if we are receiving coins that are beyond our next derivation index,
|
||||
// adjust it.
|
||||
if derivation_index > db_conn.receive_index() {
|
||||
@ -52,9 +64,6 @@ fn update_coins(
|
||||
|
||||
// Now record this coin as a newly received one.
|
||||
if !curr_coins.contains_key(&utxo.outpoint) {
|
||||
let UTxO {
|
||||
outpoint, amount, ..
|
||||
} = utxo;
|
||||
let coin = Coin {
|
||||
outpoint,
|
||||
amount,
|
||||
@ -71,7 +80,7 @@ fn update_coins(
|
||||
log::error!(
|
||||
"Could not get derivation index for coin '{}' (address: '{}')",
|
||||
&utxo.outpoint,
|
||||
&utxo.address
|
||||
&address
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,8 +11,8 @@ use crate::{
|
||||
};
|
||||
|
||||
use utils::{
|
||||
deser_amount_from_sats, deser_base64, deser_hex, ser_amount, ser_base64, ser_hex,
|
||||
to_base64_string,
|
||||
deser_addr_assume_checked, deser_amount_from_sats, deser_fromstr, deser_hex, ser_amount,
|
||||
ser_hex, ser_to_string,
|
||||
};
|
||||
|
||||
use std::{
|
||||
@ -23,8 +23,9 @@ use std::{
|
||||
|
||||
use miniscript::{
|
||||
bitcoin::{
|
||||
self,
|
||||
util::psbt::{Input as PsbtIn, Output as PsbtOut, PartiallySignedTransaction as Psbt},
|
||||
self, address,
|
||||
locktime::absolute,
|
||||
psbt::{Input as PsbtIn, Output as PsbtOut, PartiallySignedTransaction as Psbt},
|
||||
},
|
||||
psbt::PsbtExt,
|
||||
};
|
||||
@ -49,7 +50,7 @@ pub enum CommandError {
|
||||
InvalidFeerate(/* sats/vb */ u64),
|
||||
UnknownOutpoint(bitcoin::OutPoint),
|
||||
AlreadySpent(bitcoin::OutPoint),
|
||||
AddressNetwork(bitcoin::Address, /* Expected */ bitcoin::Network),
|
||||
Address(bitcoin::address::Error),
|
||||
InvalidOutputValue(bitcoin::Amount),
|
||||
InsufficientFunds(
|
||||
/* in value */ bitcoin::Amount,
|
||||
@ -77,10 +78,9 @@ impl fmt::Display for CommandError {
|
||||
Self::InvalidFeerate(sats_vb) => write!(f, "Invalid feerate: {} sats/vb.", sats_vb),
|
||||
Self::AlreadySpent(op) => write!(f, "Coin at '{}' is already spent.", op),
|
||||
Self::UnknownOutpoint(op) => write!(f, "Unknown outpoint '{}'.", op),
|
||||
Self::AddressNetwork(addr, expected) => write!(
|
||||
Self::Address(e) => write!(
|
||||
f,
|
||||
"Invalid network for address '{}'. Our network is '{}' but address is for '{}'.",
|
||||
addr, expected, addr.network
|
||||
"Address error: {}", e
|
||||
),
|
||||
Self::InvalidOutputValue(amount) => write!(f, "Invalid output value '{}'.", amount),
|
||||
Self::InsufficientFunds(in_val, out_val, feerate) => if let Some(out_val) = out_val {
|
||||
@ -115,7 +115,7 @@ impl fmt::Display for CommandError {
|
||||
Self::SanityCheckFailure(psbt) => write!(
|
||||
f,
|
||||
"BUG! Please report this. Failed sanity checks for PSBT '{}'.",
|
||||
to_base64_string(psbt)
|
||||
psbt
|
||||
),
|
||||
Self::UnknownSpend(txid) => write!(f, "Unknown spend transaction '{}'.", txid),
|
||||
Self::SpendFinalization(e) => {
|
||||
@ -141,7 +141,7 @@ impl std::error::Error for CommandError {}
|
||||
// Sanity check the value of a transaction output.
|
||||
fn check_output_value(value: bitcoin::Amount) -> Result<(), CommandError> {
|
||||
// NOTE: the network parameter isn't used upstream
|
||||
if value.to_sat() > bitcoin::blockdata::constants::max_money(bitcoin::Network::Bitcoin)
|
||||
if value.to_sat() > bitcoin::blockdata::constants::MAX_MONEY
|
||||
|| value.to_sat() < DUST_OUTPUT_SATS
|
||||
{
|
||||
Err(CommandError::InvalidOutputValue(value))
|
||||
@ -235,19 +235,14 @@ impl DaemonControl {
|
||||
}
|
||||
|
||||
// Check whether this address is valid for the network we are operating on.
|
||||
fn validate_address(&self, addr: &bitcoin::Address) -> Result<(), CommandError> {
|
||||
// NOTE: signet uses testnet addresses
|
||||
if addr.network == self.config.bitcoin_config.network
|
||||
|| (addr.network == bitcoin::Network::Testnet
|
||||
&& self.config.bitcoin_config.network == bitcoin::Network::Signet)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(CommandError::AddressNetwork(
|
||||
addr.clone(),
|
||||
self.config.bitcoin_config.network,
|
||||
))
|
||||
fn validate_address(
|
||||
&self,
|
||||
addr: bitcoin::Address<address::NetworkUnchecked>,
|
||||
) -> Result<bitcoin::Address, CommandError> {
|
||||
// NOTE: signet uses testnet addresses, and legacy addresses on regtest use testnet
|
||||
// encoding.
|
||||
addr.require_network(self.config.bitcoin_config.network)
|
||||
.map_err(CommandError::Address)
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,7 +282,7 @@ impl DaemonControl {
|
||||
.receive_descriptor()
|
||||
.derive(index, &self.secp)
|
||||
.address(self.config.bitcoin_config.network);
|
||||
GetAddressResult { address }
|
||||
GetAddressResult::new(address)
|
||||
}
|
||||
|
||||
/// Get a list of all known coins.
|
||||
@ -323,7 +318,7 @@ impl DaemonControl {
|
||||
|
||||
pub fn create_spend(
|
||||
&self,
|
||||
destinations: &HashMap<bitcoin::Address, u64>,
|
||||
destinations: &HashMap<bitcoin::Address<bitcoin::address::NetworkUnchecked>, u64>,
|
||||
coins_outpoints: &[bitcoin::OutPoint],
|
||||
feerate_vb: u64,
|
||||
) -> Result<CreateSpendResult, CommandError> {
|
||||
@ -396,7 +391,7 @@ impl DaemonControl {
|
||||
let mut txouts = Vec::with_capacity(destinations.len());
|
||||
let mut psbt_outs = Vec::with_capacity(destinations.len());
|
||||
for (address, value_sat) in destinations {
|
||||
self.validate_address(address)?;
|
||||
let address = self.validate_address(address.clone())?;
|
||||
|
||||
let amount = bitcoin::Amount::from_sat(*value_sat);
|
||||
check_output_value(amount)?;
|
||||
@ -409,7 +404,7 @@ impl DaemonControl {
|
||||
// If it's an address of ours, signal it as change to signing devices by adding the
|
||||
// BIP32 derivation path to the PSBT output.
|
||||
let bip32_derivation =
|
||||
if let Some((index, is_change)) = db_conn.derivation_index_by_address(address) {
|
||||
if let Some((index, is_change)) = db_conn.derivation_index_by_address(&address) {
|
||||
let desc = if is_change {
|
||||
self.config.main_descriptor.change_descriptor()
|
||||
} else {
|
||||
@ -430,7 +425,7 @@ impl DaemonControl {
|
||||
// isn't much less than what was asked (and obviously that fees aren't negative).
|
||||
let mut tx = bitcoin::Transaction {
|
||||
version: 2,
|
||||
lock_time: bitcoin::PackedLockTime(0), // TODO: randomized anti fee sniping
|
||||
lock_time: absolute::LockTime::Blocks(absolute::Height::ZERO), // TODO: randomized anti fee sniping
|
||||
input: txins,
|
||||
output: txouts,
|
||||
};
|
||||
@ -691,21 +686,21 @@ impl DaemonControl {
|
||||
/// Note that not all coins may be spendable through a single recovery path at the same time.
|
||||
pub fn create_recovery(
|
||||
&self,
|
||||
address: bitcoin::Address,
|
||||
address: bitcoin::Address<address::NetworkUnchecked>,
|
||||
feerate_vb: u64,
|
||||
timelock: Option<u16>,
|
||||
) -> Result<CreateRecoveryResult, CommandError> {
|
||||
if feerate_vb < 1 {
|
||||
return Err(CommandError::InvalidFeerate(feerate_vb));
|
||||
}
|
||||
self.validate_address(&address)?;
|
||||
let address = self.validate_address(address)?;
|
||||
let mut db_conn = self.db.connection();
|
||||
|
||||
// The transaction template. We'll fill-in the inputs afterward.
|
||||
let mut psbt = Psbt {
|
||||
unsigned_tx: bitcoin::Transaction {
|
||||
version: 2,
|
||||
lock_time: bitcoin::PackedLockTime(0), // TODO: anti-fee sniping
|
||||
lock_time: absolute::LockTime::Blocks(absolute::Height::ZERO), // TODO: anti-fee sniping
|
||||
input: Vec::new(),
|
||||
output: vec![bitcoin::TxOut {
|
||||
script_pubkey: address.script_pubkey(),
|
||||
@ -813,7 +808,18 @@ pub struct GetInfoResult {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GetAddressResult {
|
||||
pub address: bitcoin::Address,
|
||||
#[serde(deserialize_with = "deser_addr_assume_checked")]
|
||||
address: bitcoin::Address,
|
||||
}
|
||||
|
||||
impl GetAddressResult {
|
||||
pub fn new(address: bitcoin::Address) -> Self {
|
||||
Self { address }
|
||||
}
|
||||
|
||||
pub fn address(&self) -> &bitcoin::Address {
|
||||
&self.address
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
@ -843,13 +849,13 @@ pub struct ListCoinsResult {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct CreateSpendResult {
|
||||
#[serde(serialize_with = "ser_base64", deserialize_with = "deser_base64")]
|
||||
#[serde(serialize_with = "ser_to_string", deserialize_with = "deser_fromstr")]
|
||||
pub psbt: Psbt,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ListSpendEntry {
|
||||
#[serde(serialize_with = "ser_base64", deserialize_with = "deser_base64")]
|
||||
#[serde(serialize_with = "ser_to_string", deserialize_with = "deser_fromstr")]
|
||||
pub psbt: Psbt,
|
||||
pub updated_at: Option<u32>,
|
||||
}
|
||||
@ -874,7 +880,7 @@ pub struct TransactionInfo {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct CreateRecoveryResult {
|
||||
#[serde(serialize_with = "ser_base64", deserialize_with = "deser_base64")]
|
||||
#[serde(serialize_with = "ser_to_string", deserialize_with = "deser_fromstr")]
|
||||
pub psbt: Psbt,
|
||||
}
|
||||
|
||||
@ -884,14 +890,13 @@ mod tests {
|
||||
use crate::{bitcoin::Block, database::BlockInfo, testutils::*};
|
||||
|
||||
use bitcoin::{
|
||||
bip32::{self, ChildNumber},
|
||||
blockdata::transaction::{TxIn, TxOut},
|
||||
util::bip32::ChildNumber,
|
||||
OutPoint, PackedLockTime, Script, Sequence, Transaction, Txid, Witness,
|
||||
locktime::absolute,
|
||||
OutPoint, ScriptBuf, Sequence, Transaction, Txid, Witness,
|
||||
};
|
||||
use std::str::FromStr;
|
||||
|
||||
use bitcoin::util::bip32;
|
||||
|
||||
#[test]
|
||||
fn getinfo() {
|
||||
let ms = DummyLiana::new(DummyBitcoind::new(), DummyDatabase::new());
|
||||
@ -913,6 +918,7 @@ mod tests {
|
||||
"bc1q9ksrc647hx8zp2cewl8p5f487dgux3777yees8rjcx46t4daqzzqt7yga8"
|
||||
)
|
||||
.unwrap()
|
||||
.assume_checked()
|
||||
);
|
||||
// We won't get the same twice.
|
||||
let addr2 = control.get_new_address().address;
|
||||
@ -933,7 +939,7 @@ mod tests {
|
||||
(
|
||||
bitcoin::Transaction {
|
||||
version: 2,
|
||||
lock_time: bitcoin::PackedLockTime(0),
|
||||
lock_time: absolute::LockTime::Blocks(absolute::Height::ZERO),
|
||||
input: vec![],
|
||||
output: vec![],
|
||||
},
|
||||
@ -947,10 +953,11 @@ mod tests {
|
||||
let dummy_addr =
|
||||
bitcoin::Address::from_str("bc1qnsexk3gnuyayu92fc3tczvc7k62u22a22ua2kv").unwrap();
|
||||
let dummy_value = 10_000;
|
||||
let mut destinations: HashMap<bitcoin::Address, u64> = [(dummy_addr.clone(), dummy_value)]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
let mut destinations: HashMap<bitcoin::Address<address::NetworkUnchecked>, u64> =
|
||||
[(dummy_addr.clone(), dummy_value)]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
assert_eq!(
|
||||
control.create_spend(&destinations, &[], 1),
|
||||
Err(CommandError::NoOutpoint)
|
||||
@ -982,7 +989,10 @@ mod tests {
|
||||
assert_eq!(tx.input.len(), 1);
|
||||
assert_eq!(tx.input[0].previous_output, dummy_op);
|
||||
assert_eq!(tx.output.len(), 2);
|
||||
assert_eq!(tx.output[0].script_pubkey, dummy_addr.script_pubkey());
|
||||
assert_eq!(
|
||||
tx.output[0].script_pubkey,
|
||||
dummy_addr.payload.script_pubkey()
|
||||
);
|
||||
assert_eq!(tx.output[0].value, dummy_value);
|
||||
|
||||
// Transaction is 1 in (P2WSH satisfaction), 2 outs. At 1sat/vb, it's 171 sats fees.
|
||||
@ -1025,22 +1035,19 @@ mod tests {
|
||||
);
|
||||
|
||||
// If we ask to create an output for an address from another network, it will fail.
|
||||
let invalid_addr = bitcoin::Address {
|
||||
network: bitcoin::Network::Testnet,
|
||||
payload: dummy_addr.payload.clone(),
|
||||
};
|
||||
let invalid_destinations: HashMap<bitcoin::Address, u64> =
|
||||
let invalid_addr =
|
||||
bitcoin::Address::new(bitcoin::Network::Testnet, dummy_addr.payload.clone());
|
||||
let invalid_destinations: HashMap<bitcoin::Address<address::NetworkUnchecked>, u64> =
|
||||
[(invalid_addr.clone(), dummy_value)]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
assert_eq!(
|
||||
assert!(matches!(
|
||||
control.create_spend(&invalid_destinations, &[dummy_op], 1),
|
||||
Err(CommandError::AddressNetwork(
|
||||
invalid_addr,
|
||||
bitcoin::Network::Bitcoin
|
||||
Err(CommandError::Address(
|
||||
address::Error::NetworkValidation { .. }
|
||||
))
|
||||
);
|
||||
));
|
||||
|
||||
// If we ask for a large, but valid, output we won't get a change output. 95_000 because we
|
||||
// won't create an output lower than 5k sats.
|
||||
@ -1050,7 +1057,10 @@ mod tests {
|
||||
assert_eq!(tx.input.len(), 1);
|
||||
assert_eq!(tx.input[0].previous_output, dummy_op);
|
||||
assert_eq!(tx.output.len(), 1);
|
||||
assert_eq!(tx.output[0].script_pubkey, dummy_addr.script_pubkey());
|
||||
assert_eq!(
|
||||
tx.output[0].script_pubkey,
|
||||
dummy_addr.payload.script_pubkey()
|
||||
);
|
||||
assert_eq!(tx.output[0].value, 95_000);
|
||||
|
||||
// Now if we mark the coin as spent, we won't create another Spend transaction containing
|
||||
@ -1104,7 +1114,7 @@ mod tests {
|
||||
let mut dummy_bitcoind = DummyBitcoind::new();
|
||||
let dummy_tx = bitcoin::Transaction {
|
||||
version: 2,
|
||||
lock_time: bitcoin::PackedLockTime(0),
|
||||
lock_time: absolute::LockTime::Blocks(absolute::Height::ZERO),
|
||||
input: vec![],
|
||||
output: vec![],
|
||||
};
|
||||
@ -1145,17 +1155,17 @@ mod tests {
|
||||
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> =
|
||||
let destinations_a: HashMap<bitcoin::Address<address::NetworkUnchecked>, u64> =
|
||||
[(dummy_addr_a.clone(), dummy_value_a)]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
let destinations_b: HashMap<bitcoin::Address, u64> =
|
||||
let destinations_b: HashMap<bitcoin::Address<address::NetworkUnchecked>, u64> =
|
||||
[(dummy_addr_b.clone(), dummy_value_b)]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
let destinations_c: HashMap<bitcoin::Address, u64> =
|
||||
let destinations_c: HashMap<bitcoin::Address<address::NetworkUnchecked>, u64> =
|
||||
[(dummy_addr_a, dummy_value_a), (dummy_addr_b, dummy_value_b)]
|
||||
.iter()
|
||||
.cloned()
|
||||
@ -1185,7 +1195,7 @@ mod tests {
|
||||
assert_eq!(db_conn.spend_tx(&txid_c).unwrap(), psbt_c);
|
||||
|
||||
// As well as update them, with or without new signatures
|
||||
let sig = bitcoin::EcdsaSig::from_str("304402204004fcdbb9c0d0cbf585f58cee34dccb012efbd8fc2b0d5e97760045ae35803802201a0bd7ec2383e0b93748abc9946c8e17a8312e314dab85982aeba650e738cbf401").unwrap();
|
||||
let sig = bitcoin::ecdsa::Signature::from_str("304402204004fcdbb9c0d0cbf585f58cee34dccb012efbd8fc2b0d5e97760045ae35803802201a0bd7ec2383e0b93748abc9946c8e17a8312e314dab85982aeba650e738cbf401").unwrap();
|
||||
psbt_a.inputs[0].partial_sigs.insert(
|
||||
bitcoin::PublicKey::from_str(
|
||||
"023a664c5617412f0b292665b1fd9d766456a7a3b1614c7e7c5f411200ff1958ef",
|
||||
@ -1224,68 +1234,68 @@ mod tests {
|
||||
|
||||
let deposit1: Transaction = Transaction {
|
||||
version: 1,
|
||||
lock_time: PackedLockTime(1),
|
||||
lock_time: absolute::LockTime::Blocks(absolute::Height::from_consensus(1).unwrap()),
|
||||
input: vec![TxIn {
|
||||
witness: Witness::new(),
|
||||
previous_output: outpoint,
|
||||
script_sig: Script::new(),
|
||||
script_sig: ScriptBuf::new(),
|
||||
sequence: Sequence(0),
|
||||
}],
|
||||
output: vec![TxOut {
|
||||
script_pubkey: Script::new(),
|
||||
script_pubkey: ScriptBuf::new(),
|
||||
value: 100_000_000,
|
||||
}],
|
||||
};
|
||||
|
||||
let deposit2: Transaction = Transaction {
|
||||
version: 1,
|
||||
lock_time: PackedLockTime(1),
|
||||
lock_time: absolute::LockTime::Blocks(absolute::Height::from_consensus(1).unwrap()),
|
||||
input: vec![TxIn {
|
||||
witness: Witness::new(),
|
||||
previous_output: outpoint,
|
||||
script_sig: Script::new(),
|
||||
script_sig: ScriptBuf::new(),
|
||||
sequence: Sequence(0),
|
||||
}],
|
||||
output: vec![TxOut {
|
||||
script_pubkey: Script::new(),
|
||||
script_pubkey: ScriptBuf::new(),
|
||||
value: 2000,
|
||||
}],
|
||||
};
|
||||
|
||||
let deposit3: Transaction = Transaction {
|
||||
version: 1,
|
||||
lock_time: PackedLockTime(1),
|
||||
lock_time: absolute::LockTime::Blocks(absolute::Height::from_consensus(1).unwrap()),
|
||||
input: vec![TxIn {
|
||||
witness: Witness::new(),
|
||||
previous_output: outpoint,
|
||||
script_sig: Script::new(),
|
||||
script_sig: ScriptBuf::new(),
|
||||
sequence: Sequence(0),
|
||||
}],
|
||||
output: vec![TxOut {
|
||||
script_pubkey: Script::new(),
|
||||
script_pubkey: ScriptBuf::new(),
|
||||
value: 3000,
|
||||
}],
|
||||
};
|
||||
|
||||
let spend_tx: Transaction = Transaction {
|
||||
version: 1,
|
||||
lock_time: PackedLockTime(1),
|
||||
lock_time: absolute::LockTime::Blocks(absolute::Height::from_consensus(1).unwrap()),
|
||||
input: vec![TxIn {
|
||||
witness: Witness::new(),
|
||||
previous_output: OutPoint {
|
||||
txid: deposit1.txid(),
|
||||
vout: 0,
|
||||
},
|
||||
script_sig: Script::new(),
|
||||
script_sig: ScriptBuf::new(),
|
||||
sequence: Sequence(0),
|
||||
}],
|
||||
output: vec![
|
||||
TxOut {
|
||||
script_pubkey: Script::new(),
|
||||
script_pubkey: ScriptBuf::new(),
|
||||
value: 4000,
|
||||
},
|
||||
TxOut {
|
||||
script_pubkey: Script::new(),
|
||||
script_pubkey: ScriptBuf::new(),
|
||||
value: 100_000_000 - 4000 - 1000,
|
||||
},
|
||||
],
|
||||
@ -1447,45 +1457,45 @@ mod tests {
|
||||
|
||||
let tx1: Transaction = Transaction {
|
||||
version: 1,
|
||||
lock_time: PackedLockTime(1),
|
||||
lock_time: absolute::LockTime::Blocks(absolute::Height::from_consensus(1).unwrap()),
|
||||
input: vec![TxIn {
|
||||
witness: Witness::new(),
|
||||
previous_output: outpoint,
|
||||
script_sig: Script::new(),
|
||||
script_sig: ScriptBuf::new(),
|
||||
sequence: Sequence(0),
|
||||
}],
|
||||
output: vec![TxOut {
|
||||
script_pubkey: Script::new(),
|
||||
script_pubkey: ScriptBuf::new(),
|
||||
value: 100_000_000,
|
||||
}],
|
||||
};
|
||||
|
||||
let tx2: Transaction = Transaction {
|
||||
version: 1,
|
||||
lock_time: PackedLockTime(1),
|
||||
lock_time: absolute::LockTime::Blocks(absolute::Height::from_consensus(1).unwrap()),
|
||||
input: vec![TxIn {
|
||||
witness: Witness::new(),
|
||||
previous_output: outpoint,
|
||||
script_sig: Script::new(),
|
||||
script_sig: ScriptBuf::new(),
|
||||
sequence: Sequence(0),
|
||||
}],
|
||||
output: vec![TxOut {
|
||||
script_pubkey: Script::new(),
|
||||
script_pubkey: ScriptBuf::new(),
|
||||
value: 2000,
|
||||
}],
|
||||
};
|
||||
|
||||
let tx3: Transaction = Transaction {
|
||||
version: 1,
|
||||
lock_time: PackedLockTime(1),
|
||||
lock_time: absolute::LockTime::Blocks(absolute::Height::from_consensus(1).unwrap()),
|
||||
input: vec![TxIn {
|
||||
witness: Witness::new(),
|
||||
previous_output: outpoint,
|
||||
script_sig: Script::new(),
|
||||
script_sig: ScriptBuf::new(),
|
||||
sequence: Sequence(0),
|
||||
}],
|
||||
output: vec![TxOut {
|
||||
script_pubkey: Script::new(),
|
||||
script_pubkey: ScriptBuf::new(),
|
||||
value: 3000,
|
||||
}],
|
||||
};
|
||||
|
||||
@ -1,6 +1,36 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use miniscript::bitcoin::{self, consensus, hashes::hex::FromHex};
|
||||
use serde::{de, Deserialize, Deserializer, Serializer};
|
||||
|
||||
pub fn deser_fromstr<'de, D, T>(deserializer: D) -> Result<T, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: FromStr,
|
||||
<T as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
let string = String::deserialize(deserializer)?;
|
||||
T::from_str(&string).map_err(de::Error::custom)
|
||||
}
|
||||
|
||||
pub fn ser_to_string<T: std::fmt::Display, S: Serializer>(
|
||||
field: T,
|
||||
s: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
s.serialize_str(&field.to_string())
|
||||
}
|
||||
|
||||
/// Deserialize an address from string, assuming the network was checked.
|
||||
pub fn deser_addr_assume_checked<'de, D>(deserializer: D) -> Result<bitcoin::Address, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let string = String::deserialize(deserializer)?;
|
||||
bitcoin::Address::from_str(&string)
|
||||
.map(|addr| addr.assume_checked())
|
||||
.map_err(de::Error::custom)
|
||||
}
|
||||
|
||||
/// Serialize an amount as sats
|
||||
pub fn ser_amount<S: Serializer>(amount: &bitcoin::Amount, s: S) -> Result<S::Ok, S::Error> {
|
||||
s.serialize_u64(amount.to_sat())
|
||||
@ -15,28 +45,6 @@ where
|
||||
Ok(bitcoin::Amount::from_sat(a))
|
||||
}
|
||||
|
||||
pub fn to_base64_string<T: consensus::Encodable>(t: T) -> String {
|
||||
base64::encode(consensus::serialize(&t))
|
||||
}
|
||||
|
||||
pub fn ser_base64<S, T>(t: T, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
T: consensus::Encodable,
|
||||
{
|
||||
s.serialize_str(&to_base64_string(t))
|
||||
}
|
||||
|
||||
pub fn deser_base64<'de, D, T>(d: D) -> Result<T, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: consensus::Decodable,
|
||||
{
|
||||
let s = String::deserialize(d)?;
|
||||
let s = base64::decode(s).map_err(de::Error::custom)?;
|
||||
consensus::deserialize(&s).map_err(de::Error::custom)
|
||||
}
|
||||
|
||||
pub fn ser_hex<S, T>(t: T, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
|
||||
@ -14,10 +14,7 @@ use crate::{
|
||||
|
||||
use std::{collections::HashMap, sync};
|
||||
|
||||
use miniscript::bitcoin::{
|
||||
self, secp256k1,
|
||||
util::{bip32, psbt::PartiallySignedTransaction as Psbt},
|
||||
};
|
||||
use miniscript::bitcoin::{self, bip32, psbt::PartiallySignedTransaction as Psbt, secp256k1};
|
||||
|
||||
pub trait DatabaseInterface: Send {
|
||||
fn connection(&self) -> Box<dyn DatabaseConnection>;
|
||||
@ -220,8 +217,12 @@ impl DatabaseConnection for SqliteConn {
|
||||
&mut self,
|
||||
address: &bitcoin::Address,
|
||||
) -> Option<(bip32::ChildNumber, bool)> {
|
||||
self.db_address(address)
|
||||
.map(|db_addr| (db_addr.derivation_index, address == &db_addr.change_address))
|
||||
self.db_address(address).map(|db_addr| {
|
||||
(
|
||||
db_addr.derivation_index,
|
||||
address == &db_addr.change_address.assume_checked(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn coins_by_outpoints(
|
||||
|
||||
@ -28,11 +28,11 @@ use crate::{
|
||||
use std::{cmp, convert::TryInto, fmt, io, path};
|
||||
|
||||
use miniscript::bitcoin::{
|
||||
self,
|
||||
self, bip32,
|
||||
consensus::encode,
|
||||
hashes::hex::ToHex,
|
||||
hashes::{sha256, Hash},
|
||||
psbt::PartiallySignedTransaction as Psbt,
|
||||
secp256k1,
|
||||
util::{bip32, psbt::PartiallySignedTransaction as Psbt},
|
||||
};
|
||||
|
||||
const DB_VERSION: i64 = 1;
|
||||
@ -84,6 +84,24 @@ impl From<rusqlite::Error> for SqliteDbError {
|
||||
}
|
||||
}
|
||||
|
||||
// In Bitcoin land, txids are usually displayed in reverse byte order. This is what rust-bitcoin
|
||||
// implements as `fmt::Display` for `bitcoin::Txid`. However, we store them as raw bytes in the
|
||||
// database and it so happens we sometimes have to look for a txid in hex, in which case we want
|
||||
// the "frontward" hex serialization. This is a hack to implement it.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct FrontwardHexTxid(bitcoin::Txid);
|
||||
|
||||
impl fmt::Display for FrontwardHexTxid {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{:x}",
|
||||
// sha256 isn't displayed in reverse byte order (contrary to sha256d).
|
||||
sha256::Hash::from_byte_array(self.0.to_byte_array())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FreshDbOptions {
|
||||
pub(self) bitcoind_network: bitcoin::Network,
|
||||
@ -217,7 +235,7 @@ impl SqliteConn {
|
||||
db_tx
|
||||
.execute(
|
||||
"UPDATE tip SET blockheight = (?1), blockhash = (?2)",
|
||||
rusqlite::params![tip.height, tip.hash.to_vec()],
|
||||
rusqlite::params![tip.height, tip.hash[..].to_vec()],
|
||||
)
|
||||
.map(|_| ())
|
||||
})
|
||||
@ -369,7 +387,7 @@ impl SqliteConn {
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
|
||||
rusqlite::params![
|
||||
WALLET_ID,
|
||||
coin.outpoint.txid.to_vec(),
|
||||
coin.outpoint.txid[..].to_vec(),
|
||||
coin.outpoint.vout,
|
||||
coin.amount.to_sat(),
|
||||
deriv_index,
|
||||
@ -388,7 +406,7 @@ impl SqliteConn {
|
||||
for outpoint in outpoints {
|
||||
db_tx.execute(
|
||||
"DELETE FROM coins WHERE txid = ?1 AND vout = ?2",
|
||||
rusqlite::params![outpoint.txid.to_vec(), outpoint.vout,],
|
||||
rusqlite::params![outpoint.txid[..].to_vec(), outpoint.vout,],
|
||||
)?;
|
||||
}
|
||||
|
||||
@ -406,7 +424,7 @@ impl SqliteConn {
|
||||
for (outpoint, height, time) in outpoints {
|
||||
db_tx.execute(
|
||||
"UPDATE coins SET blockheight = ?1, blocktime = ?2 WHERE txid = ?3 AND vout = ?4",
|
||||
rusqlite::params![height, time, outpoint.txid.to_vec(), outpoint.vout,],
|
||||
rusqlite::params![height, time, outpoint.txid[..].to_vec(), outpoint.vout,],
|
||||
)?;
|
||||
}
|
||||
|
||||
@ -424,7 +442,11 @@ impl SqliteConn {
|
||||
for (outpoint, spend_txid) in outpoints {
|
||||
db_tx.execute(
|
||||
"UPDATE coins SET spend_txid = ?1 WHERE txid = ?2 AND vout = ?3",
|
||||
rusqlite::params![spend_txid.to_vec(), outpoint.txid.to_vec(), outpoint.vout,],
|
||||
rusqlite::params![
|
||||
spend_txid[..].to_vec(),
|
||||
outpoint.txid[..].to_vec(),
|
||||
outpoint.vout,
|
||||
],
|
||||
)?;
|
||||
}
|
||||
|
||||
@ -444,10 +466,10 @@ impl SqliteConn {
|
||||
db_tx.execute(
|
||||
"UPDATE coins SET spend_txid = ?1, spend_block_height = ?2, spend_block_time = ?3 WHERE txid = ?4 AND vout = ?5",
|
||||
rusqlite::params![
|
||||
spend_txid.to_vec(),
|
||||
spend_txid[..].to_vec(),
|
||||
height,
|
||||
time,
|
||||
outpoint.txid.to_vec(),
|
||||
outpoint.txid[..].to_vec(),
|
||||
outpoint.vout,
|
||||
],
|
||||
)?;
|
||||
@ -473,10 +495,11 @@ impl SqliteConn {
|
||||
// SELECT * FROM coins WHERE (txid, vout) IN ((txidA, voutA), (txidB, voutB));
|
||||
let mut query = "SELECT * FROM coins WHERE (txid, vout) IN (VALUES ".to_string();
|
||||
for (i, outpoint) in outpoints.iter().enumerate() {
|
||||
// NOTE: the txid is not stored as little-endian. Convert it to vec first.
|
||||
// NOTE: SQLite doesn't know Satoshi decided txids would be displayed as little-endian
|
||||
// hex.
|
||||
query += &format!(
|
||||
"(x'{}', {})",
|
||||
&outpoint.txid.to_vec().to_hex(),
|
||||
FrontwardHexTxid(outpoint.txid),
|
||||
outpoint.vout
|
||||
);
|
||||
if i != outpoints.len() - 1 {
|
||||
@ -495,7 +518,7 @@ impl SqliteConn {
|
||||
db_query(
|
||||
&mut self.conn,
|
||||
"SELECT * FROM spend_transactions WHERE txid = ?1",
|
||||
rusqlite::params![txid.to_vec()],
|
||||
rusqlite::params![txid[..].to_vec()],
|
||||
|row| row.try_into(),
|
||||
)
|
||||
.expect("Db must not fail")
|
||||
@ -504,14 +527,13 @@ impl SqliteConn {
|
||||
|
||||
/// Insert a new Spend transaction or replace an existing one.
|
||||
pub fn store_spend(&mut self, psbt: &Psbt) {
|
||||
let txid = psbt.unsigned_tx.txid().to_vec();
|
||||
let psbt = encode::serialize(psbt);
|
||||
let txid = &psbt.unsigned_tx.txid()[..].to_vec();
|
||||
|
||||
db_exec(&mut self.conn, |db_tx| {
|
||||
db_tx.execute(
|
||||
"INSERT into spend_transactions (psbt, txid, updated_at) VALUES (?1, ?2, ?3) \
|
||||
ON CONFLICT DO UPDATE SET psbt=excluded.psbt",
|
||||
rusqlite::params![psbt, txid, curr_timestamp()],
|
||||
rusqlite::params![psbt.serialize(), txid, curr_timestamp()],
|
||||
)?;
|
||||
Ok(())
|
||||
})
|
||||
@ -564,7 +586,7 @@ impl SqliteConn {
|
||||
db_exec(&mut self.conn, |db_tx| {
|
||||
db_tx.execute(
|
||||
"DELETE FROM spend_transactions WHERE txid = ?1",
|
||||
rusqlite::params![txid.to_vec()],
|
||||
rusqlite::params![txid[..].to_vec()],
|
||||
)?;
|
||||
Ok(())
|
||||
})
|
||||
@ -593,7 +615,7 @@ impl SqliteConn {
|
||||
)?;
|
||||
db_tx.execute(
|
||||
"UPDATE tip SET blockheight = (?1), blockhash = (?2)",
|
||||
rusqlite::params![new_tip.height, new_tip.hash.to_vec()],
|
||||
rusqlite::params![new_tip.height, new_tip.hash[..].to_vec()],
|
||||
)?;
|
||||
Ok(())
|
||||
})
|
||||
@ -612,7 +634,7 @@ mod tests {
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use bitcoin::{hashes::Hash, util::bip32};
|
||||
use bitcoin::{bip32, hashes::Hash};
|
||||
|
||||
// The database schema used by the first versions of Liana (database version 0). Used to test
|
||||
// migrations starting from the first version.
|
||||
@ -687,7 +709,7 @@ CREATE TABLE spend_transactions (
|
||||
";
|
||||
|
||||
fn psbt_from_str(psbt_str: &str) -> Psbt {
|
||||
bitcoin::consensus::deserialize(&base64::decode(psbt_str).unwrap()).unwrap()
|
||||
Psbt::from_str(psbt_str).unwrap()
|
||||
}
|
||||
|
||||
fn dummy_options() -> FreshDbOptions {
|
||||
@ -1449,14 +1471,13 @@ CREATE TABLE spend_transactions (
|
||||
// The helper that was used to store Spend transaction in previous versions of the software
|
||||
// when there was no associated timestamp.
|
||||
fn store_spend_old(conn: &mut rusqlite::Connection, psbt: &Psbt) {
|
||||
let txid = psbt.unsigned_tx.txid().to_vec();
|
||||
let psbt = encode::serialize(psbt);
|
||||
let txid = &psbt.unsigned_tx.txid()[..].to_vec();
|
||||
|
||||
db_exec(conn, |db_tx| {
|
||||
db_tx.execute(
|
||||
"INSERT into spend_transactions (psbt, txid) VALUES (?1, ?2) \
|
||||
ON CONFLICT DO UPDATE SET psbt=excluded.psbt",
|
||||
rusqlite::params![psbt, txid],
|
||||
rusqlite::params![psbt.serialize(), txid],
|
||||
)?;
|
||||
Ok(())
|
||||
})
|
||||
|
||||
@ -3,9 +3,7 @@ use crate::descriptors::LianaDescriptor;
|
||||
use std::{convert::TryFrom, str::FromStr};
|
||||
|
||||
use miniscript::bitcoin::{
|
||||
self,
|
||||
consensus::encode,
|
||||
util::{bip32, psbt::PartiallySignedTransaction as Psbt},
|
||||
self, address, bip32, consensus::encode, psbt::PartiallySignedTransaction as Psbt,
|
||||
};
|
||||
|
||||
pub const SCHEMA: &str = "\
|
||||
@ -219,8 +217,8 @@ impl TryFrom<&rusqlite::Row<'_>> for DbCoin {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DbAddress {
|
||||
pub receive_address: bitcoin::Address,
|
||||
pub change_address: bitcoin::Address,
|
||||
pub receive_address: bitcoin::Address<address::NetworkUnchecked>,
|
||||
pub change_address: bitcoin::Address<address::NetworkUnchecked>,
|
||||
pub derivation_index: bip32::ChildNumber,
|
||||
}
|
||||
|
||||
@ -264,7 +262,7 @@ impl TryFrom<&rusqlite::Row<'_>> for DbSpendTransaction {
|
||||
let id: i64 = row.get(0)?;
|
||||
|
||||
let psbt: Vec<u8> = row.get(1)?;
|
||||
let psbt: Psbt = encode::deserialize(&psbt).expect("We only store valid PSBTs");
|
||||
let psbt = Psbt::deserialize(&psbt).expect("We only store valid PSBTs");
|
||||
|
||||
let txid: Vec<u8> = row.get(2)?;
|
||||
let txid: bitcoin::Txid = encode::deserialize(&txid).expect("We only store valid txids");
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use miniscript::{
|
||||
bitcoin::{util::bip32, Sequence},
|
||||
bitcoin::{bip32, Sequence},
|
||||
descriptor,
|
||||
policy::{compiler, Concrete as ConcretePolicy, Liftable, Semantic as SemanticPolicy},
|
||||
ScriptContext,
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
use miniscript::{
|
||||
bitcoin::{
|
||||
self,
|
||||
self, bip32,
|
||||
hashes::{hash160, ripemd160, sha256},
|
||||
util::bip32,
|
||||
},
|
||||
hash256, MiniscriptKey, ToPublicKey,
|
||||
};
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
use miniscript::{
|
||||
bitcoin::{
|
||||
self, secp256k1,
|
||||
util::{
|
||||
bip32,
|
||||
psbt::{Input as PsbtIn, Psbt},
|
||||
},
|
||||
self, bip32,
|
||||
psbt::{Input as PsbtIn, Psbt},
|
||||
secp256k1,
|
||||
},
|
||||
descriptor, translate_hash_clone, ForEachKey, TranslatePk, Translator,
|
||||
};
|
||||
@ -367,11 +365,11 @@ impl DerivedSinglePathLianaDesc {
|
||||
.expect("A P2WSH always has an address")
|
||||
}
|
||||
|
||||
pub fn script_pubkey(&self) -> bitcoin::Script {
|
||||
pub fn script_pubkey(&self) -> bitcoin::ScriptBuf {
|
||||
self.0.script_pubkey()
|
||||
}
|
||||
|
||||
pub fn witness_script(&self) -> bitcoin::Script {
|
||||
pub fn witness_script(&self) -> bitcoin::ScriptBuf {
|
||||
self.0.explicit_script().expect("Not a Taproot descriptor")
|
||||
}
|
||||
|
||||
@ -733,7 +731,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn psbt_from_str(psbt_str: &str) -> Psbt {
|
||||
bitcoin::consensus::deserialize(&base64::decode(psbt_str).unwrap()).unwrap()
|
||||
Psbt::from_str(psbt_str).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -973,7 +971,7 @@ mod tests {
|
||||
"0282574238aea21ec72ffffd8d8a981a30004b5794c8094ff394fec79509a5834f",
|
||||
)
|
||||
.unwrap();
|
||||
let dummy_sig = bitcoin::EcdsaSig::from_str ("30440220264d47ed3fd613e4ac34303c59a0e558d41e487a68af5c5d4bb790f6ccf218ab02203213fe4d51729f9852a28f7d22b2ecb2b096eaf07ad44638af77e4bdbdd4462901").unwrap();
|
||||
let dummy_sig = bitcoin::ecdsa::Signature::from_str("30440220264d47ed3fd613e4ac34303c59a0e558d41e487a68af5c5d4bb790f6ccf218ab02203213fe4d51729f9852a28f7d22b2ecb2b096eaf07ad44638af77e4bdbdd4462901").unwrap();
|
||||
let dummy_der_path = bip32::DerivationPath::from_str("m/0/1").unwrap();
|
||||
let fingerprint = prim_path.thresh_origins().1.into_iter().next().unwrap().0;
|
||||
psbt.inputs[0]
|
||||
|
||||
@ -5,7 +5,7 @@ use crate::{
|
||||
|
||||
use std::{collections::HashMap, convert::TryInto, str::FromStr};
|
||||
|
||||
use miniscript::bitcoin::{self, consensus, util::psbt::PartiallySignedTransaction as Psbt};
|
||||
use miniscript::bitcoin::{self, psbt::PartiallySignedTransaction as Psbt};
|
||||
|
||||
fn create_spend(control: &DaemonControl, params: Params) -> Result<serde_json::Value, Error> {
|
||||
let destinations = params
|
||||
@ -19,7 +19,7 @@ fn create_spend(control: &DaemonControl, params: Params) -> Result<serde_json::V
|
||||
let amount: u64 = v.as_i64()?.try_into().ok()?;
|
||||
Some((addr, amount))
|
||||
})
|
||||
.collect::<Option<HashMap<bitcoin::Address, u64>>>()
|
||||
.collect::<Option<HashMap<bitcoin::Address<bitcoin::address::NetworkUnchecked>, u64>>>()
|
||||
})
|
||||
.ok_or_else(|| Error::invalid_params("Invalid 'destinations' parameter."))?;
|
||||
let outpoints = params
|
||||
@ -51,8 +51,7 @@ fn update_spend(control: &DaemonControl, params: Params) -> Result<serde_json::V
|
||||
.get(0, "psbt")
|
||||
.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())
|
||||
.and_then(|s| Psbt::from_str(s).ok())
|
||||
.ok_or_else(|| Error::invalid_params("Invalid 'psbt' parameter."))?;
|
||||
control.update_spend(psbt)?;
|
||||
|
||||
|
||||
@ -155,7 +155,7 @@ impl From<commands::CommandError> for Error {
|
||||
| commands::CommandError::UnknownOutpoint(..)
|
||||
| commands::CommandError::InvalidFeerate(..)
|
||||
| commands::CommandError::AlreadySpent(..)
|
||||
| commands::CommandError::AddressNetwork(..)
|
||||
| commands::CommandError::Address(..)
|
||||
| commands::CommandError::InvalidOutputValue(..)
|
||||
| commands::CommandError::InsufficientFunds(..)
|
||||
| commands::CommandError::InsaneFees(..)
|
||||
|
||||
@ -222,6 +222,10 @@ fn maybe_delete_watchonly_wallet(
|
||||
miniscript::bitcoin::Network::Testnet
|
||||
| miniscript::bitcoin::Network::Signet
|
||||
| miniscript::bitcoin::Network::Regtest => parent_dir.join("wallets").join(wallet_name),
|
||||
net => panic!(
|
||||
"Unsupported network '{}', unknown at the time of writing.",
|
||||
net
|
||||
),
|
||||
};
|
||||
|
||||
if wallet_path.exists() {
|
||||
|
||||
@ -67,7 +67,7 @@ fn additional_data() -> Result<[u8; 32], RandomnessError> {
|
||||
engine.input(&pid.to_be_bytes());
|
||||
// TODO: get some more contextual information
|
||||
|
||||
Ok(*sha256::Hash::from_engine(engine).as_inner())
|
||||
Ok(sha256::Hash::from_engine(engine).to_byte_array())
|
||||
}
|
||||
|
||||
/// Get 32 random bytes. This is mainly based on OS-provided randomness (`getrandom` or
|
||||
@ -86,7 +86,7 @@ pub fn random_bytes() -> Result<[u8; 32], RandomnessError> {
|
||||
engine.input(&additional_data()?);
|
||||
// TODO: add more sources of randomness
|
||||
|
||||
Ok(*sha256::Hash::from_engine(engine).as_inner())
|
||||
Ok(sha256::Hash::from_engine(engine).to_byte_array())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -15,14 +15,11 @@ use std::{
|
||||
|
||||
use miniscript::bitcoin::{
|
||||
self,
|
||||
bip32::{self, Error as Bip32Error},
|
||||
ecdsa,
|
||||
hashes::Hash,
|
||||
secp256k1,
|
||||
util::{
|
||||
bip32::{self, Error as Bip32Error},
|
||||
ecdsa,
|
||||
psbt::Psbt,
|
||||
sighash,
|
||||
},
|
||||
psbt::Psbt,
|
||||
secp256k1, sighash,
|
||||
};
|
||||
|
||||
/// An error related to using a signer.
|
||||
@ -78,7 +75,7 @@ fn create_dir(path: &path::Path) -> io::Result<()> {
|
||||
|
||||
// TODO: permissions on Windows..
|
||||
#[cfg(not(unix))]
|
||||
return { fs::create_dir_all(path) };
|
||||
fs::create_dir_all(path)
|
||||
}
|
||||
|
||||
// Create a file with no permission for the group and other users, and only read permissions for
|
||||
@ -259,7 +256,7 @@ impl HotSigner {
|
||||
let sighash = sighash_cache
|
||||
.segwit_signature_hash(i, witscript, value, sig_type)
|
||||
.map_err(|_| SignerError::InsanePsbt)?;
|
||||
let sighash = secp256k1::Message::from_slice(sighash.as_hash().as_inner())
|
||||
let sighash = secp256k1::Message::from_slice(sighash.as_byte_array())
|
||||
.expect("Sighash is always 32 bytes.");
|
||||
|
||||
// Then provide a signature for all the keys they asked for.
|
||||
@ -277,7 +274,7 @@ impl HotSigner {
|
||||
let sig = secp.sign_ecdsa_low_r(&sighash, &privkey.inner);
|
||||
psbt.inputs[i].partial_sigs.insert(
|
||||
pubkey,
|
||||
ecdsa::EcdsaSig {
|
||||
ecdsa::Signature {
|
||||
sig,
|
||||
hash_ty: sig_type,
|
||||
},
|
||||
@ -301,7 +298,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::{descriptors, testutils::*};
|
||||
use miniscript::{
|
||||
bitcoin::util::psbt::Input as PsbtIn,
|
||||
bitcoin::{locktime::absolute, psbt::Input as PsbtIn},
|
||||
descriptor::{DerivPaths, DescriptorMultiXKey, DescriptorPublicKey, Wildcard},
|
||||
};
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
@ -434,7 +431,7 @@ mod tests {
|
||||
let mut dummy_psbt = Psbt {
|
||||
unsigned_tx: bitcoin::Transaction {
|
||||
version: 2,
|
||||
lock_time: bitcoin::PackedLockTime(0),
|
||||
lock_time: absolute::LockTime::Blocks(absolute::Height::ZERO),
|
||||
input: vec![bitcoin::TxIn {
|
||||
sequence: bitcoin::Sequence::ENABLE_RBF_NO_LOCKTIME,
|
||||
previous_output: bitcoin::OutPoint::from_str(
|
||||
@ -449,6 +446,7 @@ mod tests {
|
||||
"bc1qvklensptw5lk7d470ds60pcpsr0psdpgyvwepv",
|
||||
)
|
||||
.unwrap()
|
||||
.payload
|
||||
.script_pubkey(),
|
||||
}],
|
||||
},
|
||||
|
||||
@ -9,9 +9,7 @@ use std::{collections::HashMap, env, fs, io, path, process, str::FromStr, sync,
|
||||
|
||||
use miniscript::{
|
||||
bitcoin::{
|
||||
self, secp256k1,
|
||||
util::{bip32, psbt::PartiallySignedTransaction as Psbt},
|
||||
Transaction, Txid,
|
||||
self, bip32, psbt::PartiallySignedTransaction as Psbt, secp256k1, Transaction, Txid,
|
||||
},
|
||||
descriptor,
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user