Merge #1555: upgrade bitcoin dependency to 0.32 and related

c5f5284f57e37e578cc3001a7da1790c724437a3 upgrade bitcoin to 0.32 and related (Michael Mallan)

Pull request description:

  This is to upgrade bitcoin to 0.32 and related dependencies accordingly.

  Note that the bdk_electrum crate has not been upgraded to the most recent version available as the Electrum syncing there takes much longer due to the fetching and validation of Merkle proofs. There are ongoing changes in relation to that (https://github.com/bitcoindevkit/bdk/issues/1699) and so we can further upgrade this dependency once those changes have been completed.

  Non-BDK changes to the liana and lianad crates are taken from commit 20ee8b1e7b77cbf65942bc4f083afea95f2d1506 in draft PR https://github.com/wizardsardine/liana/pull/1228, which this replaces.

  (Once this PR has been tested and approved, we can release a new version of the async_hwi crate and point to that.)

ACKs for top commit:
  edouardparis:
    ACK c5f5284f57e37e578cc3001a7da1790c724437a3

Tree-SHA512: 3cb1a2060ecc4c6b9631df0eadf5692ad35495b9303edbb80a437aca60ae062a7227f20ddeb5eb722cf57dd80bfa752372a14d26b8f306844237bfdb5f41e51a
This commit is contained in:
edouardparis 2025-01-27 18:04:11 +01:00
commit 3c8e412384
No known key found for this signature in database
GPG Key ID: E65F7A089C20DC8F
33 changed files with 563 additions and 357 deletions

284
Cargo.lock generated
View File

@ -267,14 +267,13 @@ dependencies = [
[[package]]
name = "async-hwi"
version = "0.0.25"
version = "0.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2bf351445dda3f867da2effb866be894cc88f7648a67b93d1f462fde026a6d"
checksum = "3b67f67913279ff8009b46fde618b87d46f7c4306c7e93342fed09c6bb5dea09"
dependencies = [
"async-trait",
"bitbox-api",
"bitcoin 0.31.2",
"bitcoin 0.32.4",
"bitcoin",
"coldcard",
"futures",
"hidapi",
@ -409,6 +408,31 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "aws-lc-rs"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c2b7ddaa2c56a367ad27a094ad8ef4faacf8a617c2575acb2ba88949df999ca"
dependencies = [
"aws-lc-sys",
"paste",
"zeroize",
]
[[package]]
name = "aws-lc-sys"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71b2ddd3ada61a305e1d8bb6c005d1eaa7d14d903681edfc400406d523a9b491"
dependencies = [
"bindgen",
"cc",
"cmake",
"dunce",
"fs_extra",
"paste",
]
[[package]]
name = "backtrace"
version = "0.3.74"
@ -481,11 +505,11 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bdk_chain"
version = "0.15.0"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c601c4dc7e6c3efa538a0afbb43b964cefab9a9b5e8f352fa0ca38145448a5e7"
checksum = "163b064557cee078e8ee5dd2c88944204506f7b2b1524f78e8fcba38c346da7b"
dependencies = [
"bitcoin 0.31.2",
"bitcoin",
"miniscript",
]
@ -497,26 +521,43 @@ checksum = "3c084bf76f0f67546fc814ffa82044144be1bb4618183a15016c162f8b087ad4"
[[package]]
name = "bdk_electrum"
version = "0.14.0"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28906275aeb1f71dc32045670f06c8a26fb17cc62151a99f7425d258f4bda589"
checksum = "01aa7f890313a33b27bcdcbb8efe98450710fce5af51e7ab013ef6d179252ffa"
dependencies = [
"bdk_chain",
"electrum-client",
]
[[package]]
name = "bech32"
version = "0.10.0-beta"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea"
[[package]]
name = "bech32"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d"
[[package]]
name = "bindgen"
version = "0.69.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
dependencies = [
"bitflags 2.6.0",
"cexpr",
"clang-sys",
"itertools 0.12.1",
"lazy_static",
"lazycell",
"log",
"prettyplease 0.2.25",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn 2.0.87",
"which",
]
[[package]]
name = "bip39"
version = "2.1.0"
@ -557,7 +598,7 @@ checksum = "8349407999d3653dbbd3f75182742a6c8ad73f424ef83fcf5b87ead0c8ce2528"
dependencies = [
"async-trait",
"base32",
"bitcoin 0.32.4",
"bitcoin",
"byteorder",
"chrono",
"getrandom",
@ -576,23 +617,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "bitcoin"
version = "0.31.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c85783c2fe40083ea54a33aa2f0ba58831d90fcd190f5bdc47e74e84d2a96ae"
dependencies = [
"base64 0.21.7",
"bech32 0.10.0-beta",
"bitcoin-internals 0.2.0",
"bitcoin_hashes 0.13.0",
"core2",
"hex-conservative 0.1.2",
"hex_lit",
"secp256k1 0.28.2",
"serde",
]
[[package]]
name = "bitcoin"
version = "0.32.4"
@ -601,14 +625,14 @@ checksum = "788902099d47c8682efe6a7afb01c8d58b9794ba66c06affd81c3d6b560743eb"
dependencies = [
"base58ck",
"base64 0.21.7",
"bech32 0.11.0",
"bech32",
"bitcoin-internals 0.3.0",
"bitcoin-io",
"bitcoin-units",
"bitcoin_hashes 0.14.0",
"hex-conservative 0.2.1",
"hex_lit",
"secp256k1 0.29.1",
"secp256k1",
"serde",
]
@ -617,9 +641,6 @@ name = "bitcoin-internals"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb"
dependencies = [
"serde",
]
[[package]]
name = "bitcoin-internals"
@ -668,9 +689,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b"
dependencies = [
"bitcoin-internals 0.2.0",
"core2",
"hex-conservative 0.1.2",
"serde",
]
[[package]]
@ -895,6 +914,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -962,6 +990,17 @@ dependencies = [
"zeroize",
]
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading 0.8.5",
]
[[package]]
name = "clipboard-win"
version = "5.4.0"
@ -1001,6 +1040,15 @@ dependencies = [
"x11rb",
]
[[package]]
name = "cmake"
version = "0.1.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e"
dependencies = [
"cc",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
@ -1182,15 +1230,6 @@ dependencies = [
"libc",
]
[[package]]
name = "core2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "239fa3ae9b63c2dc74bd3fa852d4792b8b305ae64eeede946265b6af62f1fff3"
dependencies = [
"memchr",
]
[[package]]
name = "cosmic-text"
version = "0.10.0"
@ -1507,6 +1546,12 @@ dependencies = [
"linux-raw-sys 0.6.5",
]
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "ecdsa"
version = "0.16.9"
@ -1529,15 +1574,15 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "electrum-client"
version = "0.19.0"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89008f106be6f303695522f2f4c1f28b40c3e8367ed8b3bb227f1f882cb52cc2"
checksum = "8c7b1f8783238bb18e6e137875b0a66f3dffe6c7ea84066e05d033cf180b150f"
dependencies = [
"bitcoin 0.31.2",
"bitcoin",
"byteorder",
"libc",
"log",
"rustls",
"rustls 0.23.21",
"serde",
"serde_json",
"webpki-roots",
@ -1860,6 +1905,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "futures"
version = "0.3.31"
@ -2048,6 +2099,12 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3"
[[package]]
name = "glob"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "glow"
version = "0.13.1"
@ -2269,9 +2326,6 @@ name = "hex-conservative"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20"
dependencies = [
"core2",
]
[[package]]
name = "hex-conservative"
@ -2392,7 +2446,7 @@ dependencies = [
"futures-util",
"http",
"hyper",
"rustls",
"rustls 0.21.12",
"tokio",
"tokio-rustls",
]
@ -2985,6 +3039,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "lebe"
version = "0.5.2"
@ -3030,12 +3090,12 @@ dependencies = [
[[package]]
name = "ledger_bitcoin_client"
version = "0.4.1"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8606a9c7375fb139e68fc1ca7cf9c6709566eeca448ff33e37632d8a4302eefe"
checksum = "38bb192e14da7725505c49791fde863bafee2b28dede2f37e05559fe72c29416"
dependencies = [
"async-trait",
"bitcoin 0.31.2",
"bitcoin",
"miniscript",
]
@ -3059,7 +3119,7 @@ dependencies = [
"arbitrary",
"liana",
"libfuzzer-sys",
"secp256k1 0.28.2",
"secp256k1",
]
[[package]]
@ -3100,7 +3160,7 @@ dependencies = [
name = "liana-ui"
version = "0.1.0"
dependencies = [
"bitcoin 0.31.2",
"bitcoin",
"chrono",
"iced",
]
@ -3385,14 +3445,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "miniscript"
version = "11.2.0"
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3127e10529a57a8f7fa9b1332c4c2f72baadaca6777798f910dff3c922620b14"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniscript"
version = "12.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bd3c9608217b0d6fa9c9c8ddd875b85ab72bd4311cfc8db35e1b5a08fc11f4d"
dependencies = [
"bech32 0.10.0-beta",
"bitcoin 0.31.2",
"bitcoin-internals 0.2.0",
"bech32",
"bitcoin",
"serde",
]
@ -3572,6 +3637,16 @@ dependencies = [
"zeroize",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@ -4142,6 +4217,16 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "prettyplease"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033"
dependencies = [
"proc-macro2",
"syn 2.0.87",
]
[[package]]
name = "proc-macro-crate"
version = "3.2.0"
@ -4212,7 +4297,7 @@ dependencies = [
"log",
"multimap",
"petgraph",
"prettyplease",
"prettyplease 0.1.25",
"prost 0.11.9",
"prost-types",
"regex",
@ -4477,7 +4562,7 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls",
"rustls 0.21.12",
"rustls-pemfile",
"serde",
"serde_json",
@ -4649,10 +4734,25 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
dependencies = [
"log",
"ring",
"rustls-webpki",
"rustls-webpki 0.101.7",
"sct",
]
[[package]]
name = "rustls"
version = "0.23.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8"
dependencies = [
"aws-lc-rs",
"log",
"once_cell",
"rustls-pki-types",
"rustls-webpki 0.102.8",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
@ -4662,6 +4762,12 @@ dependencies = [
"base64 0.21.7",
]
[[package]]
name = "rustls-pki-types"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37"
[[package]]
name = "rustls-webpki"
version = "0.101.7"
@ -4672,6 +4778,18 @@ dependencies = [
"untrusted",
]
[[package]]
name = "rustls-webpki"
version = "0.102.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [
"aws-lc-rs",
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustybuzz"
version = "0.10.0"
@ -4769,17 +4887,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "secp256k1"
version = "0.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10"
dependencies = [
"bitcoin_hashes 0.13.0",
"secp256k1-sys 0.9.2",
"serde",
]
[[package]]
name = "secp256k1"
version = "0.29.1"
@ -4787,19 +4894,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
dependencies = [
"bitcoin_hashes 0.14.0",
"secp256k1-sys 0.10.1",
"secp256k1-sys",
"serde",
]
[[package]]
name = "secp256k1-sys"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb"
dependencies = [
"cc",
]
[[package]]
name = "secp256k1-sys"
version = "0.10.1"
@ -5466,7 +5564,7 @@ version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
dependencies = [
"rustls",
"rustls 0.21.12",
"tokio",
]

View File

@ -10,7 +10,7 @@ cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.4"
arbitrary = { version = "1", features = ["derive"] }
secp256k1 = { version = "0.28", features = ["global-context-less-secure"] }
secp256k1 = { version = "0.29", features = ["global-context-less-secure"] }
[dependencies.liana]

View File

@ -35,7 +35,7 @@ impl PathConfig {
.expect("Valid pubkey: NUMS from BIP341");
let dummy_fg = [0, 0, path_index, 0].into();
let xpub = bip32::Xpub {
network: Network::Bitcoin,
network: Network::Bitcoin.into(),
depth: 0,
parent_fingerprint: dummy_fg,
child_number: 0.into(),

View File

@ -15,7 +15,7 @@ path = "src/main.rs"
[dependencies]
async-trait = "0.1"
async-hwi = { version = "0.0.25" }
async-hwi = { version = "0.0.26" }
liana = { path = "../liana" }
lianad = { path = "../lianad", default-features = false, features = ["nonblocking_shutdown"] }
liana-ui = { path = "../liana-ui" }

View File

@ -325,7 +325,7 @@ impl Action for BroadcastAction {
return Command::perform(
async move {
daemon
.broadcast_spend_tx(&psbt.unsigned_tx.txid())
.broadcast_spend_tx(&psbt.unsigned_tx.compute_txid())
.await
.map_err(|e| e.into())
},
@ -378,7 +378,7 @@ impl Action for DeleteAction {
return Command::perform(
async move {
daemon
.delete_spend_tx(&psbt.unsigned_tx.txid())
.delete_spend_tx(&psbt.unsigned_tx.compute_txid())
.await
.map_err(|e| e.into())
},
@ -705,7 +705,8 @@ impl Action for UpdateAction {
Message::View(view::Message::ImportSpend(view::ImportSpendMessage::PsbtEdited(s))) => {
self.updated.value = s;
if let Ok(psbt) = Psbt::from_str(&self.updated.value) {
self.updated.valid = tx.psbt.unsigned_tx.txid() == psbt.unsigned_tx.txid();
self.updated.valid =
tx.psbt.unsigned_tx.compute_txid() == psbt.unsigned_tx.compute_txid();
} else {
self.updated.valid = false;
}

View File

@ -88,7 +88,8 @@ impl State for PsbtsPanel {
self.spend_txs = txs;
if let Some(tx) = &self.selected_tx {
if let Some(tx) = self.spend_txs.iter().find(|spend_tx| {
spend_tx.psbt.unsigned_tx.txid() == tx.tx.psbt.unsigned_tx.txid()
spend_tx.psbt.unsigned_tx.compute_txid()
== tx.tx.psbt.unsigned_tx.compute_txid()
}) {
let tx = psbt::PsbtState::new(self.wallet.clone(), tx.clone(), true);
let cmd = tx.load(daemon);

View File

@ -597,7 +597,7 @@ impl Step for DefineSpend {
!recipient.label.value.is_empty()
&& Address::from_str(&recipient.address.value)
.unwrap()
.payload()
.assume_checked()
.matches_script_pubkey(&output.script_pubkey)
&& output.value.to_sat() == recipient.amount().unwrap()
})
@ -605,7 +605,7 @@ impl Step for DefineSpend {
{
draft.labels.insert(
OutPoint {
txid: psbt.unsigned_tx.txid(),
txid: psbt.unsigned_tx.compute_txid(),
vout: i as u32,
}
.to_string(),
@ -671,7 +671,7 @@ impl Recipient {
}
if let Ok(address) = Address::from_str(&self.address.value) {
if amount <= address.payload().script_pubkey().dust_value() {
if amount <= address.assume_checked().script_pubkey().minimal_non_dust() {
return Err(Error::Unexpected(
"Amount must be superior to script dust value".to_string(),
));
@ -761,14 +761,16 @@ impl Step for SaveSpend {
if tx.is_batch() {
if let Some(label) = &draft.batch_label {
tx.labels
.insert(tx.psbt.unsigned_tx.txid().to_string(), label.clone());
tx.labels.insert(
tx.psbt.unsigned_tx.compute_txid().to_string(),
label.clone(),
);
}
} else if let Some(recipient) = draft.recipients.first() {
if !recipient.label.value.is_empty() {
let label = recipient.label.value.clone();
tx.labels
.insert(tx.psbt.unsigned_tx.txid().to_string(), label);
.insert(tx.psbt.unsigned_tx.compute_txid().to_string(), label);
}
}

View File

@ -160,8 +160,11 @@ impl State for TransactionsPanel {
self.selected_tx = self.txs.get(i).cloned();
// Clear modal if it's for a different tx.
if let TransactionsModal::CreateRbf(modal) = &self.modal {
if Some(modal.tx.tx.txid())
!= self.selected_tx.as_ref().map(|selected| selected.tx.txid())
if Some(modal.tx.tx.compute_txid())
!= self
.selected_tx
.as_ref()
.map(|selected| selected.tx.compute_txid())
{
self.modal = TransactionsModal::None;
}
@ -177,7 +180,7 @@ impl State for TransactionsPanel {
let outpoints: Vec<_> = (0..tx.tx.output.len())
.map(|vout| {
OutPoint::new(
tx.tx.txid(),
tx.tx.compute_txid(),
vout.try_into()
.expect("number of transaction outputs must fit in u32"),
)
@ -458,7 +461,7 @@ async fn rbf(
is_cancel: bool,
feerate_vb: Option<u64>,
) -> Result<Txid, Error> {
let previous_txid = previous_tx.tx.txid();
let previous_txid = previous_tx.tx.compute_txid();
let psbt = match daemon
.rbf_psbt(&previous_txid, is_cancel, feerate_vb)
.await?
@ -474,7 +477,7 @@ async fn rbf(
if !is_cancel {
let mut labels = HashMap::<LabelItem, Option<String>>::new();
let new_txid = psbt.unsigned_tx.txid();
let new_txid = psbt.unsigned_tx.compute_txid();
for item in previous_tx.labelled() {
if let Some(label) = previous_tx.labels.get(&item.to_string()) {
match item {
@ -506,5 +509,5 @@ async fn rbf(
}
daemon.update_spend_tx(&psbt).await?;
Ok(psbt.unsigned_tx.txid())
Ok(psbt.unsigned_tx.compute_txid())
}

View File

@ -237,9 +237,9 @@ pub fn payment_view<'a>(
labels_editing: &'a HashMap<String, form::Value<String>>,
warning: Option<&'a Error>,
) -> Element<'a, Message> {
let txid = tx.tx.txid().to_string();
let txid = tx.tx.compute_txid().to_string();
let outpoint = bitcoin::OutPoint {
txid: tx.tx.txid(),
txid: tx.tx.compute_txid(),
vout: output_index as u32,
}
.to_string();
@ -330,10 +330,14 @@ pub fn payment_view<'a>(
.push(
Row::new()
.align_items(Alignment::Center)
.push(Container::new(text(format!("{}", tx.tx.txid())).small()))
.push(Container::new(
text(format!("{}", tx.tx.compute_txid())).small(),
))
.push(
Button::new(icon::clipboard_icon())
.on_press(Message::Clipboard(tx.tx.txid().to_string()))
.on_press(Message::Clipboard(
tx.tx.compute_txid().to_string(),
))
.style(theme::Button::TransparentBorder),
)
.width(Length::Shrink),
@ -342,8 +346,9 @@ pub fn payment_view<'a>(
.spacing(5),
))
.push(
button::secondary(None, "See transaction details")
.on_press(Message::Menu(Menu::TransactionPreSelected(tx.tx.txid()))),
button::secondary(None, "See transaction details").on_press(Message::Menu(
Menu::TransactionPreSelected(tx.tx.compute_txid()),
)),
)
.spacing(20),
)

View File

@ -268,7 +268,7 @@ pub fn spend_header<'a>(
tx: &'a SpendTx,
labels_editing: &'a HashMap<String, form::Value<String>>,
) -> Element<'a, Message> {
let txid = tx.psbt.unsigned_tx.txid().to_string();
let txid = tx.psbt.unsigned_tx.compute_txid().to_string();
Column::new()
.spacing(20)
.push(if let Some(outpoint) = tx.is_single_payment() {
@ -357,13 +357,13 @@ pub fn spend_overview_view<'a>(
Row::new()
.push(p1_bold("Tx ID").width(Length::Fill))
.push(
p2_regular(tx.psbt.unsigned_tx.txid().to_string())
p2_regular(tx.psbt.unsigned_tx.compute_txid().to_string())
.style(color::GREY_3),
)
.push(
Button::new(icon::clipboard_icon().style(color::GREY_3))
.on_press(Message::Clipboard(
tx.psbt.unsigned_tx.txid().to_string(),
tx.psbt.unsigned_tx.compute_txid().to_string(),
))
.style(theme::Button::TransparentBorder),
)
@ -744,7 +744,7 @@ pub fn outputs_view<'a>(
|col: Column<'a, Message>, (i, output)| {
col.spacing(10).push(payment_view(
i,
tx.txid(),
tx.compute_txid(),
output,
network,
labels,

View File

@ -125,7 +125,7 @@ fn spend_tx_list_view(i: usize, tx: &SpendTx) -> Element<'_, Message> {
})
.push_maybe(
tx.labels
.get(&tx.psbt.unsigned_tx.txid().to_string())
.get(&tx.psbt.unsigned_tx.compute_txid().to_string())
.map(p1_regular),
)
.spacing(10)

View File

@ -108,7 +108,9 @@ fn tx_list_view(i: usize, tx: &HistoryTransaction) -> Element<'_, Message> {
.push_maybe(if let Some(outpoint) = tx.is_single_payment() {
tx.labels.get(&outpoint.to_string()).map(p1_regular)
} else {
tx.labels.get(&tx.tx.txid().to_string()).map(p1_regular)
tx.labels
.get(&tx.tx.compute_txid().to_string())
.map(p1_regular)
})
.push_maybe(tx.time.map(|t| {
Container::new(
@ -297,7 +299,7 @@ pub fn tx_view<'a>(
labels_editing: &'a HashMap<String, form::Value<String>>,
warning: Option<&'a Error>,
) -> Element<'a, Message> {
let txid = tx.tx.txid().to_string();
let txid = tx.tx.compute_txid().to_string();
dashboard(
&Menu::Transactions,
cache,

View File

@ -141,7 +141,7 @@ pub trait Daemon: Debug {
// TODO: Use filters in `list_spend_txs` command.
let mut txs = self.list_spend_txs().await?.spend_txs;
if let Some(txids) = txids {
txs.retain(|tx| txids.contains(&tx.psbt.unsigned_tx.txid()));
txs.retain(|tx| txids.contains(&tx.psbt.unsigned_tx.compute_txid()));
}
let outpoints: Vec<_> = txs
.iter()
@ -201,7 +201,7 @@ pub trait Daemon: Debug {
(0..tx.tx.output.len())
.map(|vout| {
OutPoint::new(
tx.tx.txid(),
tx.tx.compute_txid(),
vout.try_into()
.expect("number of transaction outputs must fit in u32"),
)
@ -220,7 +220,7 @@ pub trait Daemon: Debug {
let mut tx_coins = Vec::new();
let mut change_indexes = Vec::new();
for coin in &coins {
if coin.outpoint.txid == tx.tx.txid() {
if coin.outpoint.txid == tx.tx.compute_txid() {
change_indexes.push(coin.outpoint.vout as usize)
} else if tx
.tx
@ -298,7 +298,7 @@ pub trait Daemon: Debug {
let mut tx_coins = Vec::new();
let mut change_indexes = Vec::new();
for coin in &coins {
if coin.outpoint.txid == tx.tx.txid() {
if coin.outpoint.txid == tx.tx.compute_txid() {
change_indexes.push(coin.outpoint.vout as usize)
} else if tx
.tx

View File

@ -92,7 +92,7 @@ impl SpendTx {
let mut coins_map = HashMap::<OutPoint, Coin>::with_capacity(coins.len());
for coin in coins {
if let Some(info) = coin.spend_info {
if info.txid == psbt.unsigned_tx.txid() {
if info.txid == psbt.unsigned_tx.compute_txid() {
if info.height.is_some() {
status = SpendStatus::Spent
} else {
@ -158,7 +158,7 @@ impl SpendTx {
.filter_map(|(i, _)| {
if !change_indexes.contains(&i) {
Some(OutPoint {
txid: psbt.unsigned_tx.txid(),
txid: psbt.unsigned_tx.compute_txid(),
vout: i as u32,
})
} else {
@ -247,7 +247,7 @@ impl Labelled for SpendTx {
}
fn labelled(&self) -> Vec<LabelItem> {
let mut items = Vec::new();
let txid = self.psbt.unsigned_tx.txid();
let txid = self.psbt.unsigned_tx.compute_txid();
items.push(LabelItem::Txid(txid));
for coin in self.coins.values() {
items.push(LabelItem::Address(coin.address.clone()));
@ -307,7 +307,7 @@ impl HistoryTransaction {
let kind = if coins.is_empty() {
if change_indexes.len() == 1 {
TransactionKind::IncomingSinglePayment(OutPoint {
txid: tx.txid(),
txid: tx.compute_txid(),
vout: change_indexes[0] as u32,
})
} else {
@ -315,7 +315,7 @@ impl HistoryTransaction {
change_indexes
.iter()
.map(|i| OutPoint {
txid: tx.txid(),
txid: tx.compute_txid(),
vout: *i as u32,
})
.collect(),
@ -331,7 +331,7 @@ impl HistoryTransaction {
.filter_map(|(i, _)| {
if !change_indexes.contains(&i) {
Some(OutPoint {
txid: tx.txid(),
txid: tx.compute_txid(),
vout: i as u32,
})
} else {
@ -356,7 +356,7 @@ impl HistoryTransaction {
Self {
labels: HashMap::new(),
kind,
txid: tx.txid(),
txid: tx.compute_txid(),
tx,
coins: coins_map,
change_indexes,
@ -481,7 +481,7 @@ pub fn payments_from_tx(history_tx: HistoryTransaction) -> Vec<Payment> {
return array;
}
let outpoint = OutPoint {
txid: history_tx.tx.txid(),
txid: history_tx.tx.compute_txid(),
vout: output_index as u32,
};
let label = history_tx.labels.get(&outpoint.to_string()).cloned();
@ -528,7 +528,7 @@ impl Labelled for HistoryTransaction {
}
fn labelled(&self) -> Vec<LabelItem> {
let mut items = Vec::new();
let txid = self.tx.txid();
let txid = self.tx.compute_txid();
items.push(LabelItem::Txid(txid));
for coin in self.coins.values() {
items.push(LabelItem::Address(coin.address.clone()));

View File

@ -56,16 +56,16 @@ pub fn check_key_network(key: &DescriptorPublicKey, network: Network) -> bool {
match key {
DescriptorPublicKey::XPub(key) => {
if network == Network::Bitcoin {
key.xkey.network == Network::Bitcoin
key.xkey.network == Network::Bitcoin.into()
} else {
key.xkey.network == Network::Testnet
key.xkey.network == Network::Testnet.into()
}
}
DescriptorPublicKey::MultiXPub(key) => {
if network == Network::Bitcoin {
key.xkey.network == Network::Bitcoin
key.xkey.network == Network::Bitcoin.into()
} else {
key.xkey.network == Network::Testnet
key.xkey.network == Network::Testnet.into()
}
}
_ => true,
@ -210,9 +210,9 @@ impl super::DescriptorEditModal for EditXpubModal {
let fingerprint = self.hot_signer.lock().unwrap().fingerprint();
let derivation_path = default_derivation_path(self.network);
let key_str = format!(
"[{}{}]{}",
"[{}/{}]{}",
fingerprint,
derivation_path.to_string().trim_start_matches('m'),
derivation_path.to_string().trim_start_matches("m/"),
self.hot_signer
.lock()
.unwrap()
@ -274,9 +274,9 @@ impl super::DescriptorEditModal for EditXpubModal {
self.form_xpub.valid = false;
} else if let Some((fingerprint, _)) = key.origin {
self.form_xpub.valid = if self.network == Network::Bitcoin {
key.xkey.network == Network::Bitcoin
key.xkey.network == Network::Bitcoin.into()
} else {
key.xkey.network == Network::Testnet
key.xkey.network == Network::Testnet.into()
};
if self.form_xpub.valid {
self.chosen_signer = Some(Key {
@ -398,6 +398,8 @@ impl super::DescriptorEditModal for EditXpubModal {
}
pub fn default_derivation_path(network: Network) -> DerivationPath {
// Note that "m" is ignored when parsing string and could be removed:
// https://github.com/rust-bitcoin/rust-bitcoin/pull/2677
DerivationPath::from_str({
if network == Network::Bitcoin {
"m/48'/0'/0'/2'"
@ -427,3 +429,28 @@ pub async fn get_extended_pubkey(
xkey,
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_derivation_path() {
assert_eq!(
default_derivation_path(Network::Bitcoin).to_string(),
"48'/0'/0'/2'"
);
assert_eq!(
default_derivation_path(Network::Testnet).to_string(),
"48'/1'/0'/2'"
);
assert_eq!(
default_derivation_path(Network::Signet).to_string(),
"48'/1'/0'/2'"
);
assert_eq!(
default_derivation_path(Network::Regtest).to_string(),
"48'/1'/0'/2'"
);
}
}

View File

@ -54,9 +54,9 @@ impl SignerXpubs {
let derivation_path = default_derivation_path(network);
// We keep only one for the moment.
self.xpubs = vec![format!(
"[{}{}]{}",
"[{}/{}]{}",
signer.fingerprint(),
derivation_path.to_string().trim_start_matches('m'),
derivation_path.to_string().trim_start_matches("m/"),
signer.get_extended_pubkey(&derivation_path)
)];
}

View File

@ -1140,7 +1140,7 @@ fn history_tx_from_api(value: api::Transaction, network: Network) -> HistoryTran
}
}
let mut changes_indexes = Vec::new();
let txid = value.raw.txid().to_string();
let txid = value.raw.compute_txid().to_string();
for (index, output) in value.outputs.iter().enumerate() {
labels.insert(format!("{}:{}", txid, index), output.label.clone());
if let Some(address) = &output.address {
@ -1196,7 +1196,7 @@ fn spend_tx_from_api(
}
}
let mut changes_indexes = Vec::new();
let txid = value.raw.unsigned_tx.txid().to_string();
let txid = value.raw.unsigned_tx.compute_txid().to_string();
for (index, output) in value.outputs.iter().enumerate() {
labels.insert(format!("{}:{}", txid, index), output.label.clone());
if let Some(address) = &output.address {

View File

@ -7,5 +7,5 @@ edition = "2021"
[dependencies]
iced = { version = "0.12.1", default-features = false, features = ["svg", "image", "lazy", "qr_code", "advanced", "webgl"] }
bitcoin = "0.31"
bitcoin = "0.32"
chrono = "0.4"

View File

@ -10,7 +10,7 @@ description = "Liana development kit"
[dependencies]
# For managing transactions (it re-exports the bitcoin crate)
miniscript = { version = "11.0", features = ["serde", "compiler", "base64"] }
miniscript = { version = "12.0", features = ["serde", "compiler", "base64"] }
# Coin selection algorithms for spend transaction creation.
bdk_coin_select = "0.3"

View File

@ -2,11 +2,11 @@ use miniscript::{
bitcoin::{
self, bip32,
hashes::{sha256, Hash},
secp256k1, Sequence,
secp256k1,
},
descriptor,
policy::{Concrete as ConcretePolicy, Liftable, Semantic as SemanticPolicy},
ScriptContext,
RelLockTime, ScriptContext, Threshold,
};
use std::{
@ -14,6 +14,7 @@ use std::{
convert::TryFrom,
error, fmt,
str::FromStr,
sync,
};
#[derive(Debug)]
@ -71,9 +72,10 @@ impl error::Error for LianaPolicyError {}
fn is_single_key_or_multisig(policy: &SemanticPolicy<descriptor::DescriptorPublicKey>) -> bool {
match policy {
SemanticPolicy::Key(..) => true,
SemanticPolicy::Threshold(_, subs) => {
subs.iter().all(|sub| matches!(sub, SemanticPolicy::Key(_)))
}
SemanticPolicy::Thresh(thresh) => thresh
.data()
.iter()
.all(|sub| matches!(sub.as_ref(), SemanticPolicy::Key(_))),
_ => false,
}
}
@ -185,11 +187,13 @@ impl PathInfo {
) -> Result<PathInfo, LianaPolicyError> {
match policy {
SemanticPolicy::Key(key) => Ok(PathInfo::Single(key)),
SemanticPolicy::Threshold(k, subs) => {
let keys: Result<_, LianaPolicyError> = subs
SemanticPolicy::Thresh(thresh) if thresh.k() > 0 && thresh.n() >= thresh.k() => {
let k = thresh.k();
let keys: Result<_, LianaPolicyError> = thresh
.into_data()
.into_iter()
.map(|sub| match sub {
SemanticPolicy::Key(key) => Ok(key),
.map(|sub| match sub.as_ref() {
SemanticPolicy::Key(key) => Ok(key.clone()),
_ => Err(LianaPolicyError::IncompatibleDesc),
})
.collect();
@ -210,7 +214,7 @@ impl PathInfo {
// special case n == len(keys) (i.e. it's an N-of-N multisig), it is normalized as
// `thresh(n+1, older(x), key1, key2, ...)`.
let (k, subs) = match policy {
SemanticPolicy::Threshold(k, subs) => (k, subs),
SemanticPolicy::Thresh(thresh) => (thresh.k(), thresh.into_data()),
_ => return Err(LianaPolicyError::IncompatibleDesc),
};
if k == 2 && subs.len() == 2 {
@ -218,29 +222,29 @@ impl PathInfo {
// of the same form as a primary path.
let tl_value = subs
.iter()
.find_map(|s| match s {
SemanticPolicy::Older(val) => Some(csv_check(val.0)),
.find_map(|s| match s.as_ref() {
SemanticPolicy::Older(val) => Some(csv_check(val.to_consensus_u32())),
_ => None,
})
.ok_or(LianaPolicyError::IncompatibleDesc)??;
let keys_sub = subs
.into_iter()
.find(is_single_key_or_multisig)
.find(|sub| is_single_key_or_multisig(sub.as_ref()))
.ok_or(LianaPolicyError::IncompatibleDesc)?;
PathInfo::from_primary_path(keys_sub).map(|info| (tl_value, info))
PathInfo::from_primary_path(keys_sub.as_ref().clone()).map(|info| (tl_value, info))
} else if k == subs.len() && subs.len() > 2 {
// The N-of-N case. All subs but the threshold must be keys (if one had been thresh()
// of keys it would have been normalized).
let mut tl_value = None;
let mut keys = Vec::with_capacity(subs.len());
for sub in subs {
match sub {
SemanticPolicy::Key(key) => keys.push(key),
match sub.as_ref() {
SemanticPolicy::Key(key) => keys.push(key.clone()),
SemanticPolicy::Older(val) => {
if tl_value.is_some() {
return Err(LianaPolicyError::IncompatibleDesc);
}
tl_value = Some(csv_check(val.0)?);
tl_value = Some(csv_check(val.to_consensus_u32())?);
}
_ => return Err(LianaPolicyError::IncompatibleDesc),
}
@ -348,16 +352,21 @@ impl PathInfo {
}
/// Get a Miniscript Policy for this path.
pub fn into_ms_policy(self) -> ConcretePolicy<descriptor::DescriptorPublicKey> {
match self {
pub fn into_ms_policy(
self,
) -> Result<ConcretePolicy<descriptor::DescriptorPublicKey>, LianaPolicyError> {
Ok(match self {
PathInfo::Single(key) => ConcretePolicy::Key(key),
PathInfo::Multi(thresh, keys) => ConcretePolicy::Threshold(
thresh,
keys.into_iter()
.map(|key| ConcretePolicy::Key(key).into())
.collect(),
PathInfo::Multi(thresh, keys) => ConcretePolicy::Thresh(
Threshold::new(
thresh,
keys.into_iter()
.map(|key| sync::Arc::new(ConcretePolicy::Key(key)))
.collect(),
)
.map_err(|e| LianaPolicyError::InvalidPolicy(miniscript::Error::Threshold(e)))?,
),
}
})
}
}
@ -586,13 +595,10 @@ impl LianaPolicy {
if *desc_int_xpub == unspend_int_xpub {
tree_policy
} else {
SemanticPolicy::Threshold(
1,
vec![
SemanticPolicy::Key(desc.internal_key().clone()),
tree_policy,
],
)
SemanticPolicy::Thresh(Threshold::or(
sync::Arc::new(SemanticPolicy::Key(desc.internal_key().clone())),
sync::Arc::new(tree_policy),
))
}
} else {
// A Liana descriptor must contain a timelocked path.
@ -609,15 +615,23 @@ impl LianaPolicy {
// primary path with at least one key, and at least one timelocked recovery path with at
// least one key.
let subs = match policy {
SemanticPolicy::Threshold(1, subs) => Some(subs),
_ => None,
}
.ok_or(LianaPolicyError::IncompatibleDesc)?;
SemanticPolicy::Thresh(thresh) if thresh.is_or() && thresh.n() > 1 => {
thresh.into_data()
}
_ => return Err(LianaPolicyError::IncompatibleDesc),
};
// Fetch all spending paths' semantic policies. The primary path is identified as the only
// one that isn't timelocked.
let (mut primary_path, mut recovery_paths) = (None::<PathInfo>, BTreeMap::new());
for sub in subs {
// Rust-Miniscript now forces the policy in thresholds to be wrapped into an Arc. Since
// we lift the policy from the descriptor right above, there is necessarily a single
// reference per Arc, so it's safe to unwrap. This avoids having to clone every single
// sub below.
let sub =
sync::Arc::try_unwrap(sub).expect("Only a single reference, created right above.");
// This is a (multi)key check. It must be the primary path.
if is_single_key_or_multisig(&sub) {
// We only support a single primary path. But it may be that the primary path is a
@ -626,7 +640,7 @@ impl LianaPolicy {
// thresh(2, older(42), pk(C)))`.
if let Some(prim_path) = primary_path {
if let SemanticPolicy::Key(key) = sub {
primary_path = Some(prim_path.with_added_key(key));
primary_path = Some(prim_path.with_added_key(key.clone()));
} else {
return Err(LianaPolicyError::IncompatibleDesc);
}
@ -669,7 +683,10 @@ impl LianaPolicy {
&self.recovery_paths
}
fn into_policy(self) -> miniscript::policy::Concrete<descriptor::DescriptorPublicKey> {
fn into_policy(
self,
) -> Result<miniscript::policy::Concrete<descriptor::DescriptorPublicKey>, LianaPolicyError>
{
let LianaPolicy {
primary_path,
recovery_paths,
@ -677,18 +694,21 @@ impl LianaPolicy {
} = self;
// Start with the primary spending path. We'll then or() all the recovery paths to it.
let primary_keys = primary_path.into_ms_policy();
let primary_keys = primary_path.into_ms_policy()?;
// Incrementally create the top-level policy using all recovery paths.
assert!(!recovery_paths.is_empty());
recovery_paths
.into_iter()
.fold(primary_keys, |tl_policy, (timelock, path_info)| {
let timelock = ConcretePolicy::Older(Sequence::from_height(timelock));
let keys = path_info.into_ms_policy();
.try_fold(primary_keys, |tl_policy, (timelock, path_info)| {
let timelock = ConcretePolicy::Older(RelLockTime::from_height(timelock));
let keys = path_info.into_ms_policy()?;
let recovery_branch = ConcretePolicy::And(vec![keys.into(), timelock.into()]);
// We assume the larger the timelock the less likely a branch would be used.
ConcretePolicy::Or(vec![(99, tl_policy.into()), (1, recovery_branch.into())])
Ok(ConcretePolicy::Or(vec![
(99, tl_policy.into()),
(1, recovery_branch.into()),
]))
})
}
@ -713,12 +733,12 @@ impl LianaPolicy {
depth: 0,
parent_fingerprint: [0; 4].into(),
child_number: 0.into(),
network: bitcoin::Network::Regtest,
network: bitcoin::Network::Regtest.into(),
},
derivation_path: vec![].into(),
wildcard: descriptor::Wildcard::None,
});
let policy = self.into_policy();
let policy = self.into_policy()?;
let desc = policy
.clone()
.compile_tr(Some(dummy_internal_key.clone()))
@ -743,7 +763,7 @@ impl LianaPolicy {
}
} else {
let ms = self
.into_policy()
.into_policy()?
.compile::<miniscript::Segwitv0>()
.map_err(|e| LianaPolicyError::InvalidPolicy(e.into()))?;
miniscript::Segwitv0::check_local_validity(&ms).expect("Miniscript must be sane");

View File

@ -203,7 +203,7 @@ impl LianaDescriptor {
pub fn all_xpubs_net_is(&self, expected_net: bitcoin::Network) -> bool {
self.multi_desc.for_each_key(|xpub| {
if let descriptor::DescriptorPublicKey::MultiXPub(xpub) = xpub {
xpub.xkey.network == expected_net
xpub.xkey.network == expected_net.into()
} else {
false
}
@ -299,10 +299,14 @@ impl LianaDescriptor {
// empty witness). But this method is used to account between a completely "nude"
// transaction (and therefore no Segwit marker nor empty witness in inputs) and a
// satisfied transaction.
self.multi_desc
(self
.multi_desc
.max_weight_to_satisfy()
.expect("Always satisfiable")
+ 1
.to_wu()
+ 1)
.try_into()
.expect("Sat weight must fit in usize.")
}
}
@ -810,7 +814,7 @@ mod tests {
[(26352, recovery_keys.clone())].iter().cloned().collect(),
)
.unwrap();
assert_eq!(LianaDescriptor::new(policy).to_string(), "wsh(or_d(multi(3,[aabb0011/48'/0'/0'/2']xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/0/<0;1>/*,[aabb0012/48'/0'/0'/2']xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/0/<0;1>/*,[aabb0013/48'/0'/0'/2']xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/0/<0;1>/*),and_v(v:thresh(2,pkh([aabb0011/48'/0'/0'/2']xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/1/<0;1>/*),a:pkh([aabb0012/48'/0'/0'/2']xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/1/<0;1>/*),a:pkh([aabb0013/48'/0'/0'/2']xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/1/<0;1>/*)),older(26352))))#prj7nktq");
assert_eq!(LianaDescriptor::new(policy).to_string(), "wsh(or_i(and_v(v:thresh(2,pkh([aabb0011/48'/0'/0'/2']xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/1/<0;1>/*),a:pkh([aabb0012/48'/0'/0'/2']xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/1/<0;1>/*),a:pkh([aabb0013/48'/0'/0'/2']xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/1/<0;1>/*)),older(26352)),and_v(v:and_v(v:pk([aabb0011/48'/0'/0'/2']xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/0/<0;1>/*),pk([aabb0012/48'/0'/0'/2']xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/0/<0;1>/*)),pk([aabb0013/48'/0'/0'/2']xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/0/<0;1>/*))))#c7nf353n");
// Same under Taproot.
let policy = LianaPolicy::new(
@ -818,7 +822,7 @@ mod tests {
[(26352, recovery_keys)].iter().cloned().collect(),
)
.unwrap();
assert_eq!(LianaDescriptor::new(policy.clone()).to_string(), "tr(xpub661MyMwAqRbcFERisZuMzFcfg3Ur3dKB17kb8iEG89ZJYMHTWqKQGRdLjTXC6Byr8kjKo6JabFfRCm3ETM4woq7DxUXuUxxRFHfog4Peh41/<0;1>/*,{and_v(v:multi_a(2,[aabb0011/48'/0'/0'/2']xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/1/<0;1>/*,[aabb0012/48'/0'/0'/2']xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/1/<0;1>/*,[aabb0013/48'/0'/0'/2']xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/1/<0;1>/*),older(26352)),multi_a(3,[aabb0011/48'/0'/0'/2']xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/0/<0;1>/*,[aabb0012/48'/0'/0'/2']xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/0/<0;1>/*,[aabb0013/48'/0'/0'/2']xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/0/<0;1>/*)})#tugn7xtx");
assert_eq!(LianaDescriptor::new(policy.clone()).to_string(), "tr(xpub661MyMwAqRbcFERisZuMzFcfg3Ur3dKB17kb8iEG89ZJYMHTWqKQGRdLjTXC6Byr8kjKo6JabFfRCm3ETM4woq7DxUXuUxxRFHfog4Peh41/<0;1>/*,{and_v(v:multi_a(2,[aabb0011/48'/0'/0'/2']xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/1/<0;1>/*,[aabb0012/48'/0'/0'/2']xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/1/<0;1>/*,[aabb0013/48'/0'/0'/2']xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/1/<0;1>/*),older(26352)),and_v(v:and_v(v:pk([aabb0011/48'/0'/0'/2']xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/0/<0;1>/*),pk([aabb0012/48'/0'/0'/2']xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/0/<0;1>/*)),pk([aabb0013/48'/0'/0'/2']xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/0/<0;1>/*))})#eey6zfhr");
// Another derivation step before the wildcard is taken into account.
// desc_b is the very same descriptor as desc_a, except the very first xpub's derivation
@ -856,7 +860,7 @@ mod tests {
[(26352, recovery_keys.clone())].iter().cloned().collect(),
)
.unwrap();
assert_eq!(LianaDescriptor::new(policy).to_string(), "wsh(or_d(multi(3,[aabb0011/48'/0'/0'/2']xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*,[aabb0012/48'/0'/0'/2']xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/<0;1>/*,[aabb0013/48'/0'/0'/2']xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/<0;1>/*),and_v(v:thresh(2,pkh([aabb0011/48'/0'/0'/2']xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<2;3>/*),a:pkh([aabb0012/48'/0'/0'/2']xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/<2;3>/*),a:pkh([aabb0013/48'/0'/0'/2']xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/<2;3>/*)),older(26352))))#d2h994td");
assert_eq!(LianaDescriptor::new(policy).to_string(), "wsh(or_i(and_v(v:thresh(2,pkh([aabb0011/48'/0'/0'/2']xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<2;3>/*),a:pkh([aabb0012/48'/0'/0'/2']xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/<2;3>/*),a:pkh([aabb0013/48'/0'/0'/2']xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/<2;3>/*)),older(26352)),and_v(v:and_v(v:pk([aabb0011/48'/0'/0'/2']xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*),pk([aabb0012/48'/0'/0'/2']xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/<0;1>/*)),pk([aabb0013/48'/0'/0'/2']xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/<0;1>/*))))#tjdnx6vm");
// Same under Taproot.
let policy = LianaPolicy::new(
@ -864,7 +868,7 @@ mod tests {
[(26352, recovery_keys)].iter().cloned().collect(),
)
.unwrap();
assert_eq!(LianaDescriptor::new(policy.clone()).to_string(), "tr(xpub661MyMwAqRbcFERisZuMzFcfg3Ur3dKB17kb8iEG89ZJYMHTWqKQGRdLjTXC6Byr8kjKo6JabFfRCm3ETM4woq7DxUXuUxxRFHfog4Peh41/<0;1>/*,{and_v(v:multi_a(2,[aabb0011/48'/0'/0'/2']xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<2;3>/*,[aabb0012/48'/0'/0'/2']xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/<2;3>/*,[aabb0013/48'/0'/0'/2']xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/<2;3>/*),older(26352)),multi_a(3,[aabb0011/48'/0'/0'/2']xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*,[aabb0012/48'/0'/0'/2']xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/<0;1>/*,[aabb0013/48'/0'/0'/2']xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/<0;1>/*)})#ayju5dfr");
assert_eq!(LianaDescriptor::new(policy.clone()).to_string(), "tr(xpub661MyMwAqRbcFERisZuMzFcfg3Ur3dKB17kb8iEG89ZJYMHTWqKQGRdLjTXC6Byr8kjKo6JabFfRCm3ETM4woq7DxUXuUxxRFHfog4Peh41/<0;1>/*,{and_v(v:multi_a(2,[aabb0011/48'/0'/0'/2']xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<2;3>/*,[aabb0012/48'/0'/0'/2']xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/<2;3>/*,[aabb0013/48'/0'/0'/2']xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/<2;3>/*),older(26352)),and_v(v:and_v(v:pk([aabb0011/48'/0'/0'/2']xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*),pk([aabb0012/48'/0'/0'/2']xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/<0;1>/*)),pk([aabb0013/48'/0'/0'/2']xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/<0;1>/*))})#d06ehu7c");
// We prevent footguns with timelocks by requiring a u16. Note how the following wouldn't
// compile:

View File

@ -249,9 +249,9 @@ impl HotSigner {
.as_ref()
.ok_or(SignerError::IncompletePsbt)?
.value;
let sig_type = sighash::EcdsaSighashType::All;
let sighash_type = sighash::EcdsaSighashType::All;
let sighash = sighash_cache
.p2wsh_signature_hash(input_index, witscript, value, sig_type)
.p2wsh_signature_hash(input_index, witscript, value, sighash_type)
.map_err(|_| SignerError::InsanePsbt)?;
let sighash = secp256k1::Message::from_digest_slice(sighash.as_byte_array())
.expect("Sighash is always 32 bytes.");
@ -266,12 +266,12 @@ impl HotSigner {
if pubkey.inner != *curr_pubkey {
return Err(SignerError::InsanePsbt);
}
let sig = secp.sign_ecdsa_low_r(&sighash, &privkey.inner);
let signature = secp.sign_ecdsa_low_r(&sighash, &privkey.inner);
psbt_in.partial_sigs.insert(
pubkey,
ecdsa::Signature {
sig,
hash_ty: sig_type,
signature,
sighash_type,
},
);
}
@ -289,7 +289,7 @@ impl HotSigner {
psbt_in: &mut PsbtIn,
input_index: usize,
) -> Result<(), SignerError> {
let sig_type = sighash::TapSighashType::Default;
let sighash_type = sighash::TapSighashType::Default;
let prevouts = sighash::Prevouts::All(prevouts);
// If the details of the internal key are filled, provide a keypath signature.
@ -305,14 +305,14 @@ impl HotSigner {
}
let keypair = keypair.tap_tweak(secp, psbt_in.tap_merkle_root).to_inner();
let sighash = sighash_cache
.taproot_key_spend_signature_hash(input_index, &prevouts, sig_type)
.taproot_key_spend_signature_hash(input_index, &prevouts, sighash_type)
.map_err(|_| SignerError::InsanePsbt)?;
let sighash = secp256k1::Message::from_digest_slice(sighash.as_byte_array())
.expect("Sighash is always 32 bytes.");
let sig = secp.sign_schnorr_no_aux_rand(&sighash, &keypair);
let signature = secp.sign_schnorr_no_aux_rand(&sighash, &keypair);
let sig = bitcoin::taproot::Signature {
sig,
hash_ty: sig_type,
signature,
sighash_type,
};
psbt_in.tap_key_sig = Some(sig);
}
@ -334,15 +334,15 @@ impl HotSigner {
input_index,
&prevouts,
*leaf_hash,
sig_type,
sighash_type,
)
.map_err(|_| SignerError::InsanePsbt)?;
let sighash = secp256k1::Message::from_digest_slice(sighash.as_byte_array())
.expect("Sighash is always 32 bytes.");
let sig = secp.sign_schnorr_no_aux_rand(&sighash, &keypair);
let signature = secp.sign_schnorr_no_aux_rand(&sighash, &keypair);
let sig = bitcoin::taproot::Signature {
sig,
hash_ty: sig_type,
signature,
sighash_type,
};
psbt_in.tap_script_sigs.insert((*pubkey, *leaf_hash), sig);
}
@ -400,7 +400,7 @@ impl HotSigner {
/// BIP32 encoding of those keys (xpubs, tpubs, ..) but does not affect any data (whether it is
/// the keys or the mnemonics).
pub fn set_network(&mut self, network: bitcoin::Network) {
self.master_xpriv.network = network;
self.master_xpriv.network = network.into();
}
}
@ -582,7 +582,7 @@ mod tests {
"bc1qvklensptw5lk7d470ds60pcpsr0psdpgyvwepv",
)
.unwrap()
.payload()
.assume_checked()
.script_pubkey(),
}],
},
@ -842,7 +842,7 @@ mod tests {
"bc1qvklensptw5lk7d470ds60pcpsr0psdpgyvwepv",
)
.unwrap()
.payload()
.assume_checked()
.script_pubkey(),
}],
},

View File

@ -157,7 +157,7 @@ fn sanity_check_psbt(
// Check for dust outputs
for txo in psbt.unsigned_tx.output.iter() {
if txo.value < txo.script_pubkey.dust_value() {
if txo.value < txo.script_pubkey.minimal_non_dust() {
return Err(SpendCreationError::SanityCheckFailure(psbt.clone()));
}
}

View File

@ -23,11 +23,10 @@ nonblocking_shutdown = []
[dependencies]
liana = { path = "../liana" }
# For managing transactions (it re-exports the bitcoin crate)
miniscript = { version = "11.0", features = ["serde", "compiler", "base64"] }
miniscript = { version = "12.0", features = ["serde", "compiler", "base64"] }
# For Electrum backend. This is the latest version with the same bitcoin version as
# the miniscript dependency.
bdk_electrum = { version = "0.14" }
# For Electrum backend.
bdk_electrum = { version = "0.15" }
# Don't reinvent the wheel
dirs = "5.0"

View File

@ -8,7 +8,7 @@ use bdk_electrum::{
BlockId, ChainPosition, ConfirmationHeightAnchor, TxGraph,
},
electrum_client::{self, Config, ElectrumApi},
ElectrumExt,
BdkElectrumClient,
};
use super::utils::{
@ -50,7 +50,7 @@ impl std::fmt::Display for Error {
}
}
pub struct Client(electrum_client::Client);
pub struct Client(BdkElectrumClient<electrum_client::Client>);
impl Client {
/// Create a new client and perform sanity checks.
@ -70,11 +70,13 @@ impl Client {
let client =
bdk_electrum::electrum_client::Client::from_config(&electrum_config.addr, config)
.map_err(Error::Server)?;
Ok(Self(client))
let bdk_electrum_client = BdkElectrumClient::new(client);
Ok(Self(bdk_electrum_client))
}
pub fn chain_tip(&self) -> Result<BlockChainTip, Error> {
self.0
.inner
.block_headers_subscribe()
.map_err(Error::Server)
.map(|notif| BlockChainTip {
@ -84,7 +86,7 @@ impl Client {
}
fn genesis_block_header(&self) -> Result<bitcoin::block::Header, Error> {
self.0.block_header(0).map_err(Error::Server)
self.0.inner.block_header(0).map_err(Error::Server)
}
pub fn genesis_block_timestamp(&self) -> Result<u32, Error> {
@ -105,11 +107,17 @@ impl Client {
pub fn tip_time(&self) -> Result<u32, Error> {
let tip_height = self.chain_tip()?.height;
self.0
.inner
.block_header(height_usize_from_i32(tip_height))
.map_err(Error::Server)
.map(|bh| bh.time)
}
/// Returns a reference to the wrapped `BdkElectrumClient`.
pub fn bdk_electrum_client(&self) -> &BdkElectrumClient<electrum_client::Client> {
&self.0
}
fn sync_with_confirmation_height_anchor(
&self,
request: SyncRequest,
@ -207,9 +215,9 @@ impl Client {
// As they are descendants, we can assume they are all unconfirmed.
while !desc_ops.is_empty() {
log::debug!("Syncing descendant outpoints: {:?}", desc_ops);
let request = SyncRequest::from_chain_tip(local_chain.tip())
.cache_graph_txs(&graph)
.chain_outpoints(desc_ops.clone());
self.0.populate_tx_cache(&graph);
let request =
SyncRequest::from_chain_tip(local_chain.tip()).chain_outpoints(desc_ops.clone());
// Fetch prev txouts to ensure we have all required txs in the graph to calculate fees.
// An unconfirmed descendant may have a confirmed parent that we wouldn't have in our graph.
let sync_result = self.sync_with_confirmation_height_anchor(request, true)?;
@ -256,9 +264,9 @@ impl Client {
.collect();
while !anc_txids.is_empty() {
log::debug!("Syncing ancestor txids: {:?}", anc_txids);
let request = SyncRequest::from_chain_tip(local_chain.tip())
.cache_graph_txs(&graph)
.chain_txids(anc_txids.clone());
self.0.populate_tx_cache(&graph);
let request =
SyncRequest::from_chain_tip(local_chain.tip()).chain_txids(anc_txids.clone());
// We expect to have prev txouts for all unconfirmed ancestors in our graph so no need to fetch them here.
// Note we keep iterating through ancestors until we find one that is confirmed and only need to calculate
// fees for unconfirmed transactions.
@ -315,7 +323,9 @@ impl Client {
let mut anc_fees = base_fee;
// Ancestor size includes that of `txid`.
let mut anc_size = base_size;
for desc_txid in graph.walk_descendants(tx.txid(), |_, desc_txid| Some(desc_txid)) {
for desc_txid in
graph.walk_descendants(tx.compute_txid(), |_, desc_txid| Some(desc_txid))
{
log::debug!("Getting fee for desc txid '{}'.", desc_txid);
let desc_tx = graph
.get_tx(desc_txid)
@ -326,11 +336,14 @@ impl Client {
desc_fees += fee;
}
for anc_tx in graph.walk_ancestors(tx, |_, anc_tx| Some(anc_tx)) {
log::debug!("Getting fee and size for anc txid '{}'.", anc_tx.txid());
log::debug!(
"Getting fee and size for anc txid '{}'.",
anc_tx.compute_txid()
);
if let Some(ChainPosition::Unconfirmed(_)) = graph.get_chain_position(
&local_chain,
local_chain.tip().block_id(),
anc_tx.txid(),
anc_tx.compute_txid(),
) {
let fee = graph
.calculate_fee(&anc_tx)
@ -338,14 +351,17 @@ impl Client {
anc_fees += fee;
anc_size += anc_tx.vsize();
} else {
log::debug!("Ancestor txid '{}' is not unconfirmed.", anc_tx.txid());
log::debug!(
"Ancestor txid '{}' is not unconfirmed.",
anc_tx.compute_txid()
);
continue;
}
}
let fees = MempoolEntryFees {
base: bitcoin::Amount::from_sat(base_fee),
ancestor: bitcoin::Amount::from_sat(anc_fees),
descendant: bitcoin::Amount::from_sat(desc_fees),
base: base_fee,
ancestor: anc_fees,
descendant: desc_fees,
};
let entry = MempoolEntry {
vsize: base_size.try_into().expect("tx size must fit into u64"),

View File

@ -137,10 +137,13 @@ impl Electrum {
const FETCH_PREV_TXOUTS: bool = false;
const STOP_GAP: usize = 200;
// TODO: See if this caching can be done in a more optimal way, e.g. only new txs after syncing.
self.client
.bdk_electrum_client()
.populate_tx_cache(self.bdk_wallet.graph());
let (chain_update, mut graph_update, keychain_update) = if !self.is_rescanning() {
log::debug!("Performing sync.");
let mut request = SyncRequest::from_chain_tip(local_chain_tip.clone())
.cache_graph_txs(self.bdk_wallet.graph());
let mut request = SyncRequest::from_chain_tip(local_chain_tip.clone());
let all_spks: Vec<_> = self
.bdk_wallet
@ -162,8 +165,7 @@ impl Electrum {
} else {
log::info!("Performing full scan.");
// Either local_chain has height 0 or we want to trigger a full scan.
let mut request = FullScanRequest::from_chain_tip(local_chain_tip.clone())
.cache_graph_txs(self.bdk_wallet.graph());
let mut request = FullScanRequest::from_chain_tip(local_chain_tip.clone());
for (k, spks) in self.bdk_wallet.index().all_unbounded_spk_iters() {
request = request.set_spks_for_keychain(k, spks);
@ -228,7 +230,7 @@ impl Electrum {
// so that conflicts can be properly handled. We use `sync_count` instead of current time
// in seconds to ensure strictly increasing values between poller iterations.
for tx in &graph_update.initial_changeset().txs {
let txid = tx.txid();
let txid = tx.compute_txid();
if let Some(ChainPosition::Unconfirmed(_)) = graph_update.get_chain_position(
self.local_chain(),
self.local_chain().tip().block_id(),

View File

@ -46,7 +46,7 @@ pub fn block_info_from_anchor(anchor: ConfirmationTimeHeightAnchor) -> BlockInfo
/// Get the transaction's outpoints.
pub fn outpoints_from_tx(tx: &bitcoin::Transaction) -> Vec<bitcoin::OutPoint> {
let txid = tx.txid();
let txid = tx.compute_txid();
(0..tx.output.len())
.map(|i| {
bitcoin::OutPoint::new(txid, i.try_into().expect("num tx outputs must fit in u32"))

View File

@ -206,8 +206,11 @@ impl BdkWallet {
let tx_graph = self.graph.graph();
let txo_index = &self.graph.index;
let tip_id = self.local_chain.tip().block_id();
let wallet_txos =
tx_graph.filter_chain_txouts(&self.local_chain, tip_id, txo_index.outpoints());
let wallet_txos = tx_graph.filter_chain_txouts(
&self.local_chain,
tip_id,
txo_index.outpoints().iter().copied(),
);
let mut wallet_coins = HashMap::new();
// Go through all the wallet txos and create a coin for each.
for ((k, i), full_txo) in wallet_txos {

View File

@ -48,7 +48,7 @@ pub enum CommandError {
UnknownOutpoint(bitcoin::OutPoint),
AlreadySpent(bitcoin::OutPoint),
ImmatureCoinbase(bitcoin::OutPoint),
Address(bitcoin::address::Error),
Address(bitcoin::address::ParseError),
SpendCreation(SpendCreationError),
InsufficientFunds(
/* in value */ bitcoin::Amount,
@ -630,7 +630,7 @@ impl DaemonControl {
// 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();
let txid = tx.compute_txid();
if let Some(db_psbt) = db_conn.spend_tx(&txid) {
let db_tx = db_psbt.unsigned_tx;
for i in 0..db_tx.input.len() {
@ -711,7 +711,7 @@ impl DaemonControl {
.into_iter()
.filter_map(|(psbt, updated_at)| {
if let Some(set) = &txids_set {
if !set.contains(&psbt.unsigned_tx.txid()) {
if !set.contains(&psbt.unsigned_tx.compute_txid()) {
return None;
}
}
@ -1453,7 +1453,7 @@ mod tests {
input: vec![],
output: vec![],
};
let dummy_op = bitcoin::OutPoint::new(dummy_tx.txid(), 0);
let dummy_op = bitcoin::OutPoint::new(dummy_tx.compute_txid(), 0);
let ms = DummyLiana::new(DummyBitcoind::new(), DummyDatabase::new());
let control = &ms.control();
let mut db_conn = control.db().lock().unwrap().connection();
@ -1522,7 +1522,7 @@ mod tests {
assert!(warnings.is_empty());
assert_eq!(
tx.output[0].script_pubkey,
dummy_addr.payload().script_pubkey()
dummy_addr.assume_checked_ref().script_pubkey()
);
assert_eq!(tx.output[0].value.to_sat(), dummy_value);
@ -1572,13 +1572,13 @@ mod tests {
// If we ask to create an output for an address from another network, it will fail.
let invalid_addr =
bitcoin::Address::new(bitcoin::Network::Testnet, dummy_addr.payload().clone());
bitcoin::Address::from_str("tb1qfufcrdyarcg5eph608c6l8vktrc9re6agu4se2").unwrap();
let invalid_destinations: HashMap<bitcoin::Address<address::NetworkUnchecked>, u64> =
[(invalid_addr, dummy_value)].iter().cloned().collect();
assert!(matches!(
control.create_spend(&invalid_destinations, &[dummy_op], 1, None),
Err(CommandError::Address(
address::Error::NetworkValidation { .. }
address::error::ParseError::NetworkValidation { .. }
))
));
@ -1599,7 +1599,7 @@ mod tests {
assert_eq!(tx.output.len(), 1);
assert_eq!(
tx.output[0].script_pubkey,
dummy_addr.payload().script_pubkey()
dummy_addr.assume_checked_ref().script_pubkey()
);
assert_eq!(tx.output[0].value.to_sat(), 95_000);
// change = 100_000 - 95_000 - /* fee without change */ 127 - /* extra fee for change output */ 43 = 4830
@ -1818,7 +1818,7 @@ mod tests {
assert_eq!(tx_auto.output.len(), 2);
assert_eq!(
tx_auto.output[0].script_pubkey,
dummy_addr.payload().script_pubkey()
dummy_addr.assume_checked().script_pubkey()
);
assert_eq!(tx_auto.output[0].value, Amount::from_sat(80_000));
@ -2053,7 +2053,7 @@ mod tests {
} else {
panic!("expect successful spend creation")
};
let txid_a = psbt_a.unsigned_tx.txid();
let txid_a = psbt_a.unsigned_tx.compute_txid();
let psbt_b = if let CreateSpendResult::Success { psbt, .. } = control
.create_spend(&destinations_b, &[dummy_op_b], 10, None)
.unwrap()
@ -2062,7 +2062,7 @@ mod tests {
} else {
panic!("expect successful spend creation")
};
let txid_b = psbt_b.unsigned_tx.txid();
let txid_b = psbt_b.unsigned_tx.compute_txid();
let psbt_c = if let CreateSpendResult::Success { psbt, .. } = control
.create_spend(&destinations_c, &[dummy_op_a, dummy_op_b], 100, None)
.unwrap()
@ -2071,7 +2071,7 @@ mod tests {
} else {
panic!("expect successful spend creation")
};
let txid_c = psbt_c.unsigned_tx.txid();
let txid_c = psbt_c.unsigned_tx.compute_txid();
// We can store and query them all
control.update_spend(psbt_a.clone()).unwrap();
@ -2139,7 +2139,7 @@ mod tests {
inputs: vec![],
outputs: vec![],
};
let dummy_txid_a = dummy_psbt_a.unsigned_tx.txid();
let dummy_txid_a = dummy_psbt_a.unsigned_tx.compute_txid();
dummy_bitcoind.txs.insert(dummy_txid_a, (dummy_tx_a, None));
let ms = DummyLiana::new(dummy_bitcoind, DummyDatabase::new());
let control = &ms.control();
@ -2246,7 +2246,7 @@ mod tests {
input: vec![TxIn {
witness: Witness::new(),
previous_output: OutPoint {
txid: deposit1.txid(),
txid: deposit1.compute_txid(),
vout: 0,
},
script_sig: ScriptBuf::new(),
@ -2271,14 +2271,14 @@ mod tests {
is_change: false,
is_immature: false,
outpoint: OutPoint {
txid: deposit1.txid(),
txid: deposit1.compute_txid(),
vout: 0,
},
block_info: Some(BlockInfo { height: 1, time: 1 }),
spend_block: Some(BlockInfo { height: 3, time: 3 }),
derivation_index: ChildNumber::from(0),
amount: bitcoin::Amount::from_sat(100_000_000),
spend_txid: Some(spend_tx.txid()),
spend_txid: Some(spend_tx.compute_txid()),
is_from_self: false,
},
// Deposit 2
@ -2286,7 +2286,7 @@ mod tests {
is_change: false,
is_immature: false,
outpoint: OutPoint {
txid: deposit2.txid(),
txid: deposit2.compute_txid(),
vout: 0,
},
block_info: Some(BlockInfo { height: 2, time: 2 }),
@ -2300,7 +2300,7 @@ mod tests {
Coin {
is_change: true,
is_immature: false,
outpoint: OutPoint::new(spend_tx.txid(), 1),
outpoint: OutPoint::new(spend_tx.compute_txid(), 1),
block_info: Some(BlockInfo { height: 3, time: 3 }),
spend_block: None,
derivation_index: ChildNumber::from(2),
@ -2313,7 +2313,7 @@ mod tests {
is_change: false,
is_immature: false,
outpoint: OutPoint {
txid: deposit3.txid(),
txid: deposit3.compute_txid(),
vout: 0,
},
block_info: Some(BlockInfo { height: 4, time: 4 }),
@ -2327,7 +2327,7 @@ mod tests {
let mut txs_map = HashMap::new();
txs_map.insert(
deposit1.txid(),
deposit1.compute_txid(),
(
deposit1.clone(),
Some(Block {
@ -2341,7 +2341,7 @@ mod tests {
),
);
txs_map.insert(
deposit2.txid(),
deposit2.compute_txid(),
(
deposit2.clone(),
Some(Block {
@ -2355,7 +2355,7 @@ mod tests {
),
);
txs_map.insert(
spend_tx.txid(),
spend_tx.compute_txid(),
(
spend_tx.clone(),
Some(Block {
@ -2369,7 +2369,7 @@ mod tests {
),
);
txs_map.insert(
deposit3.txid(),
deposit3.compute_txid(),
(
deposit3.clone(),
Some(Block {
@ -2478,7 +2478,7 @@ mod tests {
let mut txs_map = HashMap::new();
txs_map.insert(
tx1.txid(),
tx1.compute_txid(),
(
tx1.clone(),
Some(Block {
@ -2492,7 +2492,7 @@ mod tests {
),
);
txs_map.insert(
tx2.txid(),
tx2.compute_txid(),
(
tx2.clone(),
Some(Block {
@ -2506,7 +2506,7 @@ mod tests {
),
);
txs_map.insert(
tx3.txid(),
tx3.compute_txid(),
(
tx3.clone(),
Some(Block {
@ -2547,12 +2547,14 @@ mod tests {
}
}
let transactions = control.list_transactions(&[tx1.txid()]).transactions;
let transactions = control
.list_transactions(&[tx1.compute_txid()])
.transactions;
assert_eq!(transactions.len(), 1);
assert_eq!(transactions[0].tx, tx1);
let transactions = control
.list_transactions(&[tx1.txid(), tx2.txid(), tx3.txid()])
.list_transactions(&[tx1.compute_txid(), tx2.compute_txid(), tx3.compute_txid()])
.transactions;
assert_eq!(transactions.len(), 3);

View File

@ -611,7 +611,7 @@ 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 txid = &psbt.unsigned_tx.compute_txid()[..].to_vec();
db_exec(&mut self.conn, |db_tx| {
db_tx.execute(
@ -752,7 +752,7 @@ impl SqliteConn {
pub fn new_txs(&mut self, txs: &[bitcoin::Transaction]) {
db_exec(&mut self.conn, |db_tx| {
for tx in txs {
let txid = &tx.txid()[..].to_vec();
let txid = &tx.compute_txid()[..].to_vec();
let tx_ser = bitcoin::consensus::serialize(tx);
db_tx.execute(
"INSERT INTO transactions (txid, tx, num_inputs, num_outputs, is_coinbase) \
@ -893,7 +893,7 @@ impl SqliteConn {
w_txs.len(),
w_txs
.iter()
.map(|t| t.transaction.txid())
.map(|t| t.transaction.compute_txid())
.collect::<HashSet<_>>()
.len(),
"database must not contain inconsistent block info for the same txid"
@ -1430,7 +1430,7 @@ CREATE TABLE labels (
conn.new_txs(&txs);
// Add one unconfirmed coin.
let outpoint_a = bitcoin::OutPoint::new(txs.first().unwrap().txid(), 1);
let outpoint_a = bitcoin::OutPoint::new(txs.first().unwrap().compute_txid(), 1);
let coin_a = Coin {
outpoint: outpoint_a,
is_immature: false,
@ -1477,7 +1477,7 @@ CREATE TABLE labels (
.is_empty());
// Add a second coin.
let outpoint_b = bitcoin::OutPoint::new(txs.get(1).unwrap().txid(), 12);
let outpoint_b = bitcoin::OutPoint::new(txs.get(1).unwrap().compute_txid(), 12);
let coin_b = Coin {
outpoint: outpoint_b,
is_immature: false,
@ -1558,7 +1558,7 @@ CREATE TABLE labels (
.all(|c| [coin_a.outpoint, coin_b.outpoint].contains(&c.outpoint))));
// Now if we spend one, it'll be marked as such.
conn.spend_coins(&[(coin_a.outpoint, txs.get(2).unwrap().txid())]);
conn.spend_coins(&[(coin_a.outpoint, txs.get(2).unwrap().compute_txid())]);
assert!([
conn.coins(&[CoinStatus::Spending], &[]),
conn.coins(&[CoinStatus::Spending], &[outpoint_a]),
@ -1581,7 +1581,7 @@ CREATE TABLE labels (
// Now we confirm the spend.
conn.confirm_spend(&[(
coin_a.outpoint,
txs.get(2).unwrap().txid(),
txs.get(2).unwrap().compute_txid(),
128_097,
3_000_000,
)]);
@ -1612,7 +1612,7 @@ CREATE TABLE labels (
.all(|c| [coin_a.outpoint, coin_b.outpoint].contains(&c.outpoint))));
// Add a third and fourth coin.
let outpoint_c = bitcoin::OutPoint::new(txs.get(3).unwrap().txid(), 42);
let outpoint_c = bitcoin::OutPoint::new(txs.get(3).unwrap().compute_txid(), 42);
let coin_c = Coin {
outpoint: outpoint_c,
is_immature: false,
@ -1624,7 +1624,7 @@ CREATE TABLE labels (
spend_block: None,
is_from_self: false,
};
let outpoint_d = bitcoin::OutPoint::new(txs.get(4).unwrap().txid(), 43);
let outpoint_d = bitcoin::OutPoint::new(txs.get(4).unwrap().compute_txid(), 43);
let coin_d = Coin {
outpoint: outpoint_d,
is_immature: false,
@ -1668,7 +1668,7 @@ CREATE TABLE labels (
.all(|c| [coin_b.outpoint, coin_c.outpoint].contains(&c.outpoint))));
// Now spend second coin, even though it is still unconfirmed.
conn.spend_coins(&[(coin_b.outpoint, txs.get(5).unwrap().txid())]);
conn.spend_coins(&[(coin_b.outpoint, txs.get(5).unwrap().compute_txid())]);
// The coin shows as spending.
assert!([
conn.coins(&[CoinStatus::Spending], &[]),
@ -1741,7 +1741,7 @@ CREATE TABLE labels (
// Add one, we'll get it.
let coin_a = Coin {
outpoint: bitcoin::OutPoint::new(txs.first().unwrap().txid(), 1),
outpoint: bitcoin::OutPoint::new(txs.first().unwrap().compute_txid(), 1),
is_immature: false,
block_info: None,
amount: bitcoin::Amount::from_sat(98765),
@ -1784,7 +1784,7 @@ CREATE TABLE labels (
// Add a second one (this one is change), we'll get both.
let coin_b = Coin {
outpoint: bitcoin::OutPoint::new(txs.get(1).unwrap().txid(), 12),
outpoint: bitcoin::OutPoint::new(txs.get(1).unwrap().compute_txid(), 12),
is_immature: false,
block_info: None,
amount: bitcoin::Amount::from_sat(1111),
@ -1837,7 +1837,7 @@ CREATE TABLE labels (
assert!(coins[1].block_info.is_none());
// Now if we spend one, it'll be marked as such.
conn.spend_coins(&[(coin_a.outpoint, txs.get(2).unwrap().txid())]);
conn.spend_coins(&[(coin_a.outpoint, txs.get(2).unwrap().compute_txid())]);
let coin = conn
.coins(&[], &[coin_a.outpoint])
.into_iter()
@ -1855,7 +1855,7 @@ CREATE TABLE labels (
assert!(coin.spend_txid.is_none());
// Spend it back. We will see it as 'spending'
conn.spend_coins(&[(coin_a.outpoint, txs.get(2).unwrap().txid())]);
conn.spend_coins(&[(coin_a.outpoint, txs.get(2).unwrap().compute_txid())]);
let outpoints: HashSet<bitcoin::OutPoint> = conn
.list_spending_coins()
.into_iter()
@ -1876,7 +1876,12 @@ CREATE TABLE labels (
// Now if we confirm the spend.
let height = 128_097;
let time = 3_000_000;
conn.confirm_spend(&[(coin_a.outpoint, txs.get(2).unwrap().txid(), height, time)]);
conn.confirm_spend(&[(
coin_a.outpoint,
txs.get(2).unwrap().compute_txid(),
height,
time,
)]);
// the coin is not in a spending state.
let outpoints: HashSet<bitcoin::OutPoint> = conn
.list_spending_coins()
@ -1908,7 +1913,7 @@ CREATE TABLE labels (
// Add an immature coin. As all coins it's first registered as unconfirmed (even though
// it's not).
let coin_imma = Coin {
outpoint: bitcoin::OutPoint::new(txs.get(3).unwrap().txid(), 42),
outpoint: bitcoin::OutPoint::new(txs.get(3).unwrap().compute_txid(), 42),
is_immature: true,
block_info: None,
amount: bitcoin::Amount::from_sat(424242),
@ -2080,7 +2085,7 @@ CREATE TABLE labels (
// TODO: immature deposits
let coins = [
Coin {
outpoint: bitcoin::OutPoint::new(txs.first().unwrap().txid(), 1),
outpoint: bitcoin::OutPoint::new(txs.first().unwrap().compute_txid(), 1),
is_immature: false,
block_info: None,
amount: bitcoin::Amount::from_sat(98765),
@ -2091,7 +2096,7 @@ CREATE TABLE labels (
is_from_self: false,
},
Coin {
outpoint: bitcoin::OutPoint::new(txs.get(1).unwrap().txid(), 2),
outpoint: bitcoin::OutPoint::new(txs.get(1).unwrap().compute_txid(), 2),
is_immature: false,
block_info: Some(BlockInfo {
height: 101_095,
@ -2105,7 +2110,7 @@ CREATE TABLE labels (
is_from_self: false,
},
Coin {
outpoint: bitcoin::OutPoint::new(txs.get(2).unwrap().txid(), 3),
outpoint: bitcoin::OutPoint::new(txs.get(2).unwrap().compute_txid(), 3),
is_immature: false,
block_info: Some(BlockInfo {
height: 101_099,
@ -2114,7 +2119,7 @@ CREATE TABLE labels (
amount: bitcoin::Amount::from_sat(98765),
derivation_index: bip32::ChildNumber::from_normal_idx(1000).unwrap(),
is_change: false,
spend_txid: Some(txs.get(3).unwrap().txid()),
spend_txid: Some(txs.get(3).unwrap().compute_txid()),
spend_block: Some(BlockInfo {
height: 101_199,
time: 1_231_678,
@ -2122,7 +2127,7 @@ CREATE TABLE labels (
is_from_self: false,
},
Coin {
outpoint: bitcoin::OutPoint::new(txs.get(4).unwrap().txid(), 4),
outpoint: bitcoin::OutPoint::new(txs.get(4).unwrap().compute_txid(), 4),
is_immature: false,
block_info: Some(BlockInfo {
height: 101_100,
@ -2136,7 +2141,7 @@ CREATE TABLE labels (
is_from_self: false,
},
Coin {
outpoint: bitcoin::OutPoint::new(txs.get(5).unwrap().txid(), 5),
outpoint: bitcoin::OutPoint::new(txs.get(5).unwrap().compute_txid(), 5),
is_immature: false,
block_info: Some(BlockInfo {
height: 101_102,
@ -2145,7 +2150,7 @@ CREATE TABLE labels (
amount: bitcoin::Amount::from_sat(98765),
derivation_index: bip32::ChildNumber::from_normal_idx(100000).unwrap(),
is_change: false,
spend_txid: Some(txs.get(6).unwrap().txid()),
spend_txid: Some(txs.get(6).unwrap().compute_txid()),
spend_block: Some(BlockInfo {
height: 101_105,
time: 1_201_678,
@ -2288,7 +2293,7 @@ CREATE TABLE labels (
let coins = [
Coin {
outpoint: bitcoin::OutPoint::new(txs.first().unwrap().txid(), 1),
outpoint: bitcoin::OutPoint::new(txs.first().unwrap().compute_txid(), 1),
is_immature: false,
block_info: None,
amount: bitcoin::Amount::from_sat(98765),
@ -2299,7 +2304,7 @@ CREATE TABLE labels (
is_from_self: false,
},
Coin {
outpoint: bitcoin::OutPoint::new(txs.get(1).unwrap().txid(), 2),
outpoint: bitcoin::OutPoint::new(txs.get(1).unwrap().compute_txid(), 2),
is_immature: false,
block_info: Some(BlockInfo {
height: 101_095,
@ -2313,7 +2318,7 @@ CREATE TABLE labels (
is_from_self: false,
},
Coin {
outpoint: bitcoin::OutPoint::new(txs.get(2).unwrap().txid(), 3),
outpoint: bitcoin::OutPoint::new(txs.get(2).unwrap().compute_txid(), 3),
is_immature: false,
block_info: Some(BlockInfo {
height: 101_099,
@ -2322,7 +2327,7 @@ CREATE TABLE labels (
amount: bitcoin::Amount::from_sat(98765),
derivation_index: bip32::ChildNumber::from_normal_idx(1000).unwrap(),
is_change: false,
spend_txid: Some(txs.get(3).unwrap().txid()),
spend_txid: Some(txs.get(3).unwrap().compute_txid()),
spend_block: Some(BlockInfo {
height: 101_199,
time: 1_123_000,
@ -2330,7 +2335,7 @@ CREATE TABLE labels (
is_from_self: false,
},
Coin {
outpoint: bitcoin::OutPoint::new(txs.get(4).unwrap().txid(), 4),
outpoint: bitcoin::OutPoint::new(txs.get(4).unwrap().compute_txid(), 4),
is_immature: true,
block_info: Some(BlockInfo {
height: 101_100,
@ -2344,7 +2349,7 @@ CREATE TABLE labels (
is_from_self: false,
},
Coin {
outpoint: bitcoin::OutPoint::new(txs.get(5).unwrap().txid(), 5),
outpoint: bitcoin::OutPoint::new(txs.get(5).unwrap().compute_txid(), 5),
is_immature: false,
block_info: Some(BlockInfo {
height: 101_102,
@ -2353,7 +2358,7 @@ CREATE TABLE labels (
amount: bitcoin::Amount::from_sat(98765),
derivation_index: bip32::ChildNumber::from_normal_idx(100000).unwrap(),
is_change: false,
spend_txid: Some(txs.get(6).unwrap().txid()),
spend_txid: Some(txs.get(6).unwrap().compute_txid()),
spend_block: Some(BlockInfo {
height: 101_105,
time: 1_126_000,
@ -2381,12 +2386,12 @@ CREATE TABLE labels (
let db_txids = conn.db_list_txids(1_123_000, 1_127_000, 10);
// Ordered by desc block time.
let expected_txids = [6, 5, 4, 3].map(|i| txs.get(i).unwrap().txid());
let expected_txids = [6, 5, 4, 3].map(|i| txs.get(i).unwrap().compute_txid());
assert_eq!(&db_txids[..], &expected_txids,);
let db_txids = conn.db_list_txids(1_123_000, 1_127_000, 2);
// Ordered by desc block time.
let expected_txids = [6, 5].map(|i| txs.get(i).unwrap().txid());
let expected_txids = [6, 5].map(|i| txs.get(i).unwrap().compute_txid());
assert_eq!(&db_txids[..], &expected_txids,);
}
@ -2412,7 +2417,7 @@ CREATE TABLE labels (
let coins = [
Coin {
outpoint: bitcoin::OutPoint::new(txs.first().unwrap().txid(), 1),
outpoint: bitcoin::OutPoint::new(txs.first().unwrap().compute_txid(), 1),
is_immature: false,
block_info: None,
amount: bitcoin::Amount::from_sat(98765),
@ -2423,7 +2428,7 @@ CREATE TABLE labels (
is_from_self: false,
},
Coin {
outpoint: bitcoin::OutPoint::new(txs.get(1).unwrap().txid(), 2),
outpoint: bitcoin::OutPoint::new(txs.get(1).unwrap().compute_txid(), 2),
is_immature: false,
block_info: Some(BlockInfo {
height: 101_095,
@ -2437,7 +2442,7 @@ CREATE TABLE labels (
is_from_self: false,
},
Coin {
outpoint: bitcoin::OutPoint::new(txs.get(2).unwrap().txid(), 3),
outpoint: bitcoin::OutPoint::new(txs.get(2).unwrap().compute_txid(), 3),
is_immature: false,
block_info: Some(BlockInfo {
height: 101_099,
@ -2446,7 +2451,7 @@ CREATE TABLE labels (
amount: bitcoin::Amount::from_sat(98765),
derivation_index: bip32::ChildNumber::from_normal_idx(1000).unwrap(),
is_change: false,
spend_txid: Some(txs.get(3).unwrap().txid()),
spend_txid: Some(txs.get(3).unwrap().compute_txid()),
spend_block: Some(BlockInfo {
height: 101_199,
time: 1_123_000,
@ -2454,7 +2459,7 @@ CREATE TABLE labels (
is_from_self: false,
},
Coin {
outpoint: bitcoin::OutPoint::new(txs.get(4).unwrap().txid(), 4),
outpoint: bitcoin::OutPoint::new(txs.get(4).unwrap().compute_txid(), 4),
is_immature: true,
block_info: Some(BlockInfo {
height: 101_100,
@ -2468,7 +2473,7 @@ CREATE TABLE labels (
is_from_self: false,
},
Coin {
outpoint: bitcoin::OutPoint::new(txs.get(5).unwrap().txid(), 5),
outpoint: bitcoin::OutPoint::new(txs.get(5).unwrap().compute_txid(), 5),
is_immature: false,
block_info: Some(BlockInfo {
height: 101_102,
@ -2477,7 +2482,7 @@ CREATE TABLE labels (
amount: bitcoin::Amount::from_sat(98765),
derivation_index: bip32::ChildNumber::from_normal_idx(100000).unwrap(),
is_change: false,
spend_txid: Some(txs.get(6).unwrap().txid()),
spend_txid: Some(txs.get(6).unwrap().compute_txid()),
spend_block: Some(BlockInfo {
height: 101_105,
time: 1_126_000,
@ -2529,7 +2534,7 @@ CREATE TABLE labels (
let mut db_txids = conn.db_list_saved_txids();
db_txids.sort();
let mut expected_txids: Vec<_> = txs.iter().map(|tx| tx.txid()).collect();
let mut expected_txids: Vec<_> = txs.iter().map(|tx| tx.compute_txid()).collect();
expected_txids.sort();
assert_eq!(&db_txids[..], &expected_txids,);
}
@ -2579,7 +2584,7 @@ CREATE TABLE labels (
.enumerate()
.map(|(i, tx)| Coin {
outpoint: bitcoin::OutPoint {
txid: tx.txid(),
txid: tx.compute_txid(),
vout: i as u32,
},
is_immature: (i % 10) == 0,
@ -2595,7 +2600,7 @@ CREATE TABLE labels (
None
},
spend_txid: if i % 20 == 0 {
Some(spend_txs[i / 20].0.txid())
Some(spend_txs[i / 20].0.compute_txid())
} else {
None
},
@ -2648,8 +2653,10 @@ CREATE TABLE labels (
conn.confirm_spend(&confirmed_spent_coins);
// For easy lookup, map each tx to its txid.
let bitcoin_txs: HashMap<_, _> =
bitcoin_txs.into_iter().map(|tx| (tx.txid(), tx)).collect();
let bitcoin_txs: HashMap<_, _> = bitcoin_txs
.into_iter()
.map(|tx| (tx.compute_txid(), tx))
.collect();
let block_info_from_coins: HashSet<_> = coins
.iter()
@ -2705,14 +2712,22 @@ CREATE TABLE labels (
assert_eq!(txids.len(), indices.len());
let mut db_txs = conn.list_wallet_transactions(&txids);
db_txs.sort_by(|a, b| a.transaction.txid().cmp(&b.transaction.txid()));
db_txs.sort_by(|a, b| {
a.transaction
.compute_txid()
.cmp(&b.transaction.compute_txid())
});
let mut expected_txs: Vec<_> = txids
.iter()
.collect::<HashSet<_>>() // remove duplicates
.into_iter()
.map(|txid| wallet_txs_from_coins.get(txid).unwrap().clone())
.collect();
expected_txs.sort_by(|a, b| a.transaction.txid().cmp(&b.transaction.txid()));
expected_txs.sort_by(|a, b| {
a.transaction
.compute_txid()
.cmp(&b.transaction.compute_txid())
});
assert_eq!(&db_txs[..], &expected_txs[..],);
}
}
@ -2742,7 +2757,7 @@ CREATE TABLE labels (
let tx_a = dummy_tx(1, 0);
let tx_b = dummy_tx(1, 1);
let coin_tx_a: Coin = Coin {
outpoint: bitcoin::OutPoint::new(tx_a.txid(), 0),
outpoint: bitcoin::OutPoint::new(tx_a.compute_txid(), 0),
is_immature: false,
amount: bitcoin::Amount::from_sat(1_000_000),
derivation_index: bip32::ChildNumber::from_normal_idx(0).unwrap(),
@ -2753,7 +2768,7 @@ CREATE TABLE labels (
is_from_self: false,
};
let coin_tx_b: Coin = Coin {
outpoint: bitcoin::OutPoint::new(tx_b.txid(), 0),
outpoint: bitcoin::OutPoint::new(tx_b.compute_txid(), 0),
is_immature: false,
amount: bitcoin::Amount::from_sat(1_000_000),
derivation_index: bip32::ChildNumber::from_normal_idx(1).unwrap(),
@ -2776,7 +2791,7 @@ CREATE TABLE labels (
// Spend `coin_tx_a` in `tx_c` with change `coin_tx_c`.
let tx_c = dummy_tx(1, 2);
let coin_tx_c: Coin = Coin {
outpoint: bitcoin::OutPoint::new(tx_c.txid(), 0),
outpoint: bitcoin::OutPoint::new(tx_c.compute_txid(), 0),
is_immature: false,
amount: bitcoin::Amount::from_sat(1_000_000),
derivation_index: bip32::ChildNumber::from_normal_idx(2).unwrap(),
@ -2787,7 +2802,7 @@ CREATE TABLE labels (
is_from_self: false,
};
conn.new_txs(&[tx_c.clone()]);
conn.spend_coins(&[(coin_tx_a.outpoint, tx_c.txid())]);
conn.spend_coins(&[(coin_tx_a.outpoint, tx_c.compute_txid())]);
conn.new_unspent_coins(&[coin_tx_c]);
// Although `coin_tx_c` has only one parent, `coin_tx_a` is
@ -2800,7 +2815,7 @@ CREATE TABLE labels (
// Now refresh `coin_tx_c` in `tx_d`, creating `coin_tx_d`.
let tx_d = dummy_tx(1, 3);
let coin_tx_d: Coin = Coin {
outpoint: bitcoin::OutPoint::new(tx_d.txid(), 0),
outpoint: bitcoin::OutPoint::new(tx_d.compute_txid(), 0),
is_immature: false,
amount: bitcoin::Amount::from_sat(1_000_000),
derivation_index: bip32::ChildNumber::from_normal_idx(3).unwrap(),
@ -2811,7 +2826,7 @@ CREATE TABLE labels (
is_from_self: false,
};
conn.new_txs(&[tx_d.clone()]);
conn.spend_coins(&[(coin_tx_c.outpoint, tx_d.txid())]);
conn.spend_coins(&[(coin_tx_c.outpoint, tx_d.compute_txid())]);
conn.new_unspent_coins(&[coin_tx_d]);
// All coins are unconfirmed and none are from self.
@ -2823,7 +2838,7 @@ CREATE TABLE labels (
// together in `tx_e`, creating `coin_tx_e`.
let tx_e = dummy_tx(2, 4); // 2 inputs
let coin_tx_e: Coin = Coin {
outpoint: bitcoin::OutPoint::new(tx_e.txid(), 0),
outpoint: bitcoin::OutPoint::new(tx_e.compute_txid(), 0),
is_immature: false,
amount: bitcoin::Amount::from_sat(1_000_000),
derivation_index: bip32::ChildNumber::from_normal_idx(4).unwrap(),
@ -2835,8 +2850,8 @@ CREATE TABLE labels (
};
conn.new_txs(&[tx_e.clone()]);
conn.spend_coins(&[
(coin_tx_b.outpoint, tx_e.txid()),
(coin_tx_d.outpoint, tx_e.txid()),
(coin_tx_b.outpoint, tx_e.compute_txid()),
(coin_tx_d.outpoint, tx_e.compute_txid()),
]);
conn.new_unspent_coins(&[coin_tx_e]);
@ -2848,7 +2863,7 @@ CREATE TABLE labels (
// Finally, refresh `coin_tx_e` in transaction `tx_f`, creating `coin_tx_f`.
let tx_f = dummy_tx(1, 5);
let coin_tx_f: Coin = Coin {
outpoint: bitcoin::OutPoint::new(tx_f.txid(), 0),
outpoint: bitcoin::OutPoint::new(tx_f.compute_txid(), 0),
is_immature: false,
amount: bitcoin::Amount::from_sat(1_000_000),
derivation_index: bip32::ChildNumber::from_normal_idx(5).unwrap(),
@ -2859,7 +2874,7 @@ CREATE TABLE labels (
is_from_self: false,
};
conn.new_txs(&[tx_f.clone()]);
conn.spend_coins(&[(coin_tx_e.outpoint, tx_f.txid())]);
conn.spend_coins(&[(coin_tx_e.outpoint, tx_f.compute_txid())]);
conn.new_unspent_coins(&[coin_tx_f]);
// Still no coins are from self.
@ -2872,7 +2887,7 @@ CREATE TABLE labels (
(coin_tx_a.outpoint, 100, 1_000),
(coin_tx_c.outpoint, 101, 1_001),
]);
conn.confirm_spend(&[(coin_tx_a.outpoint, tx_c.txid(), 101, 1_001)]);
conn.confirm_spend(&[(coin_tx_a.outpoint, tx_c.compute_txid(), 101, 1_001)]);
// Coins are still not marked as from self.
assert!(conn.coins(&[], &[]).iter().all(|c| !c.is_from_self));
// Now update from self for coins confirmed after 101, which excludes the two coins above.
@ -3082,7 +3097,7 @@ CREATE TABLE labels (
// 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 txid = &psbt.unsigned_tx.compute_txid()[..].to_vec();
db_exec(conn, |db_tx| {
db_tx.execute(
@ -3134,14 +3149,14 @@ CREATE TABLE labels (
let mut conn = rusqlite::Connection::open(&db_path).unwrap();
store_coin_old(
&mut conn,
&bitcoin::OutPoint::new(bitcoin_txs.first().unwrap().txid(), 5),
&bitcoin::OutPoint::new(bitcoin_txs.first().unwrap().compute_txid(), 5),
bitcoin::Amount::from_sat(14_000),
24.into(),
true,
);
store_coin_old(
&mut conn,
&bitcoin::OutPoint::new(bitcoin_txs.get(1).unwrap().txid(), 2),
&bitcoin::OutPoint::new(bitcoin_txs.get(1).unwrap().compute_txid(), 2),
bitcoin::Amount::from_sat(392_093_123),
24_567.into(),
false,
@ -3190,7 +3205,7 @@ CREATE TABLE labels (
};
conn.new_txs(&[tx.clone()]);
conn.new_unspent_coins(&[Coin {
outpoint: bitcoin::OutPoint::new(tx.txid(), 1),
outpoint: bitcoin::OutPoint::new(tx.compute_txid(), 1),
is_immature: true,
block_info: None,
amount: bitcoin::Amount::from_sat(98765),
@ -3269,12 +3284,14 @@ CREATE TABLE labels (
// - coin_e is the unconfirmed output of coin_d's spend.
// - coin_imma_a is confirmed.
// - coin_imma_b is still immature.
let coin_d_outpoint = bitcoin::OutPoint::new(bitcoin_txs.get(3).unwrap().txid(), 1456);
let coin_e_outpoint = bitcoin::OutPoint::new(bitcoin_txs.get(4).unwrap().txid(), 4633);
let coin_d_outpoint =
bitcoin::OutPoint::new(bitcoin_txs.get(3).unwrap().compute_txid(), 1456);
let coin_e_outpoint =
bitcoin::OutPoint::new(bitcoin_txs.get(4).unwrap().compute_txid(), 4633);
let coin_a = DbCoinV3 {
id: 1,
wallet_id: WALLET_ID,
outpoint: bitcoin::OutPoint::new(bitcoin_txs.first().unwrap().txid(), 1),
outpoint: bitcoin::OutPoint::new(bitcoin_txs.first().unwrap().compute_txid(), 1),
is_immature: false,
amount: bitcoin::Amount::from_sat(1231001),
derivation_index: bip32::ChildNumber::from_normal_idx(101).unwrap(),
@ -3283,7 +3300,7 @@ CREATE TABLE labels (
height: 175500,
time: 1755001001,
}),
spend_txid: Some(bitcoin_txs.get(7).unwrap().txid()),
spend_txid: Some(bitcoin_txs.get(7).unwrap().compute_txid()),
spend_block: Some(DbBlockInfo {
height: 245500,
time: 1755003000,
@ -3292,7 +3309,7 @@ CREATE TABLE labels (
let coin_b = DbCoinV3 {
id: 2,
wallet_id: WALLET_ID,
outpoint: bitcoin::OutPoint::new(bitcoin_txs.get(1).unwrap().txid(), 19234),
outpoint: bitcoin::OutPoint::new(bitcoin_txs.get(1).unwrap().compute_txid(), 19234),
is_immature: false,
amount: bitcoin::Amount::from_sat(23145),
derivation_index: bip32::ChildNumber::from_normal_idx(10).unwrap(),
@ -3307,7 +3324,7 @@ CREATE TABLE labels (
let coin_c = DbCoinV3 {
id: 3,
wallet_id: WALLET_ID,
outpoint: bitcoin::OutPoint::new(bitcoin_txs.get(2).unwrap().txid(), 932),
outpoint: bitcoin::OutPoint::new(bitcoin_txs.get(2).unwrap().compute_txid(), 932),
is_immature: false,
amount: bitcoin::Amount::from_sat(354764),
derivation_index: bip32::ChildNumber::from_normal_idx(3401).unwrap(),
@ -3346,7 +3363,7 @@ CREATE TABLE labels (
let coin_imma_a = DbCoinV3 {
id: 6,
wallet_id: WALLET_ID,
outpoint: bitcoin::OutPoint::new(bitcoin_txs.get(5).unwrap().txid(), 5),
outpoint: bitcoin::OutPoint::new(bitcoin_txs.get(5).unwrap().compute_txid(), 5),
is_immature: true,
amount: bitcoin::Amount::from_sat(4564347),
derivation_index: bip32::ChildNumber::from_normal_idx(453).unwrap(),
@ -3361,7 +3378,7 @@ CREATE TABLE labels (
let coin_imma_b = DbCoinV3 {
id: 7,
wallet_id: WALLET_ID,
outpoint: bitcoin::OutPoint::new(bitcoin_txs.get(6).unwrap().txid(), 19234),
outpoint: bitcoin::OutPoint::new(bitcoin_txs.get(6).unwrap().compute_txid(), 19234),
is_immature: true,
amount: bitcoin::Amount::from_sat(731453),
derivation_index: bip32::ChildNumber::from_normal_idx(98).unwrap(),
@ -3470,7 +3487,7 @@ CREATE TABLE labels (
id: i.try_into().unwrap(),
wallet_id: WALLET_ID,
outpoint: bitcoin::OutPoint {
txid: tx.txid(),
txid: tx.compute_txid(),
vout: i as u32,
},
is_immature: (i % 10) == 0,
@ -3486,7 +3503,7 @@ CREATE TABLE labels (
None
},
spend_txid: if i % 20 == 0 {
Some(spend_txs[i / 20].0.txid())
Some(spend_txs[i / 20].0.compute_txid())
} else {
None
},
@ -3528,7 +3545,7 @@ CREATE TABLE labels (
let db = SqliteDb::new(db_path.clone(), None, &secp).unwrap();
let mut conn = db.connection().unwrap();
let txids: Vec<_> = bitcoin_txs.iter().map(|tx| tx.txid()).collect();
let txids: Vec<_> = bitcoin_txs.iter().map(|tx| tx.compute_txid()).collect();
let bitcoin_txs_in_db: HashSet<_> = conn
.list_wallet_transactions(&txids)
.into_iter()

View File

@ -322,7 +322,7 @@ impl TryFrom<&rusqlite::Row<'_>> for DbSpendTransaction {
let txid: Vec<u8> = row.get(2)?;
let txid: bitcoin::Txid = encode::deserialize(&txid).expect("We only store valid txids");
assert_eq!(txid, psbt.unsigned_tx.txid());
assert_eq!(txid, psbt.unsigned_tx.compute_txid());
let updated_at = row.get(3)?;

View File

@ -260,7 +260,7 @@ fn migrate_v4_to_v5(
)?;
for bitcoin_tx in bitcoin_txs {
let txid = &bitcoin_tx.txid()[..].to_vec();
let txid = &bitcoin_tx.compute_txid()[..].to_vec();
let bitcoin_tx_ser = bitcoin::consensus::serialize(bitcoin_tx);
db_tx.execute(
"INSERT INTO transactions (txid, tx) VALUES (?1, ?2);",

View File

@ -381,7 +381,7 @@ impl DatabaseConnection for DummyDatabase {
}
fn store_spend(&mut self, psbt: &Psbt) {
let txid = psbt.unsigned_tx.txid();
let txid = psbt.unsigned_tx.compute_txid();
self.db
.write()
.unwrap()
@ -480,7 +480,11 @@ impl DatabaseConnection for DummyDatabase {
fn new_txs(&mut self, txs: &[bitcoin::Transaction]) {
for tx in txs {
self.db.write().unwrap().txs.insert(tx.txid(), tx.clone());
self.db
.write()
.unwrap()
.txs
.insert(tx.compute_txid(), tx.clone());
}
}