diff --git a/Cargo.lock b/Cargo.lock index c6f2c3be..3a16ebef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,6 +93,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "ascii-canvas" version = "3.0.0" @@ -284,6 +290,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes", +] + [[package]] name = "base64" version = "0.13.1" @@ -304,9 +320,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bech32" -version = "0.8.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" [[package]] name = "beef" @@ -321,6 +337,7 @@ dependencies = [ "bitcoin", "canbench-rs", "candid 0.10.8", + "getrandom 0.2.10", "hex", "ic-btc-canister", "ic-btc-interface", @@ -370,22 +387,55 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitcoin" -version = "0.28.2" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d30fb43d287492017964a1fd7d3f82e8cc760818471c6ef2d44111e317d5c3" +checksum = "ea507acc1cd80fc084ace38544bbcf7ced7c2aa65b653b102de0ce718df668f6" dependencies = [ + "base58ck", "bech32", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", "bitcoin_hashes", + "hex-conservative", + "hex_lit", "secp256k1", "serde", ] +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoin-io" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340e09e8399c7bd8912f495af6aa58bea0c9214773417ffaa8f6460f93aaee56" + +[[package]] +name = "bitcoin-units" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +dependencies = [ + "bitcoin-internals", + "serde", +] + [[package]] name = "bitcoin_hashes" -version = "0.10.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006cc91e1a1d99819bc5b8214be3555c1f0611b169f527a1fdc54ed1f2b745b0" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" dependencies = [ + "bitcoin-io", + "hex-conservative", "serde", ] @@ -395,6 +445,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.9.0" @@ -454,6 +516,12 @@ version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "byteorder" version = "1.5.0" @@ -899,6 +967,7 @@ version = "0.1.0" dependencies = [ "bitcoin", "candid 0.10.8", + "getrandom 0.2.10", "ic-btc-test-utils", "ic-cdk 0.12.1", "ic-cdk-macros 0.8.4", @@ -1025,6 +1094,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1062,6 +1143,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.28" @@ -1300,6 +1387,21 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec 0.7.4", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + [[package]] name = "hmac" version = "0.12.1" @@ -1491,6 +1593,7 @@ dependencies = [ "ic-btc-interface", "ic-btc-validation", "ic-stable-structures", + "primitive-types", "serde", "serde_bytes", ] @@ -1502,6 +1605,7 @@ dependencies = [ "bitcoin", "csv", "hex", + "primitive-types", "proptest 0.9.6", ] @@ -1647,6 +1751,26 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -2074,6 +2198,32 @@ dependencies = [ "group", ] +[[package]] +name = "parity-scale-codec" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2287753623c76f953acd29d15d8100bcab84d29db78fb6f352adb3c53e83b967" +dependencies = [ + "arrayvec 0.7.4", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6937b5e67bfba3351b87b040d48352a2fcb6ad72f81855412ce97b45c8f110" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "parking" version = "2.1.0" @@ -2214,7 +2364,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad9940b913ee56ddd94aec2d3cd179dd47068236f42a1a6415ccf9d880ce2a61" dependencies = [ - "arrayvec", + "arrayvec 0.5.2", "typed-arena", ] @@ -2224,11 +2374,22 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "563c9d701c3a31dfffaaf9ce23507ba09cbe0b9125ba176d15e629b0235e9acc" dependencies = [ - "arrayvec", + "arrayvec 0.5.2", "typed-arena", "unicode-segmentation", ] +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -2312,6 +2473,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.6.5" @@ -2649,6 +2816,12 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustix" version = "0.37.25" @@ -2783,6 +2956,7 @@ version = "0.1.0" dependencies = [ "bitcoin", "candid 0.10.8", + "getrandom 0.2.10", "ic-btc-test-utils", "ic-cdk 0.12.1", "ic-cdk-macros 0.8.4", @@ -2795,6 +2969,7 @@ version = "0.1.0" dependencies = [ "bitcoin", "candid 0.10.8", + "getrandom 0.2.10", "ic-btc-test-utils", "ic-cdk 0.12.1", "ic-cdk-macros 0.8.4", @@ -2854,20 +3029,21 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.22.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295642060261c80709ac034f52fca8e5a9fa2c7d341ded5cdb164b7c33768b2a" +checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3" dependencies = [ - "rand 0.6.5", + "bitcoin_hashes", + "rand 0.8.5", "secp256k1-sys", "serde", ] [[package]] name = "secp256k1-sys" -version = "0.5.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152e20a0fd0519390fc43ab404663af8a0b794273d2a91d60ad4a39f13ffe110" +checksum = "1433bd67156263443f14d603720b082dd3121779323fce20cba2aa07b874bc1b" dependencies = [ "cc", ] @@ -3128,6 +3304,12 @@ dependencies = [ "rusty-leveldb", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "string_cache" version = "0.8.7" @@ -3198,6 +3380,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.6.0" @@ -3442,6 +3630,18 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unarray" version = "0.1.4" @@ -3886,6 +4086,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zeroize" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index a00e15c5..ac5f62a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ members = [ resolver = "2" [workspace.dependencies] -bitcoin = "0.28.1" +bitcoin = "0.32.2" byteorder = "1.4.3" canbench-rs = { version = "0.1.1" } candid = "0.10.6" @@ -34,6 +34,7 @@ candid_parser = { version = "0.1.4" } ciborium = "0.2.1" clap = { version = "4.0.11", features = ["derive"] } futures = "0.3.28" +getrandom = { version = "0.2", default-features = false, features = ["custom"] } hex = "0.4.3" ic-btc-canister = { path = "./canister" } ic-btc-interface = { path = "./interface" } @@ -46,6 +47,7 @@ ic-http = { path = "./ic-http" } ic-metrics-encoder = "1.0.0" ic-stable-structures = "0.5.2" lazy_static = "1.4.0" +primitive-types = "0.12" serde = "1.0.171" serde_bytes = "0.11" serde_json = "1.0.94" diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml index 8a667c86..9eafe297 100644 --- a/benchmarks/Cargo.toml +++ b/benchmarks/Cargo.toml @@ -9,9 +9,10 @@ path = "src/main.rs" bench = false [dependencies] -bitcoin = { workspace = true, features = ["use-serde"]} +bitcoin = { workspace = true, features = ["serde"]} canbench-rs = { workspace = true } candid = { workspace = true } +getrandom = { workspace = true } hex = { workspace = true } ic-cdk = { workspace = true } ic-cdk-macros = { workspace = true } diff --git a/benchmarks/src/main.rs b/benchmarks/src/main.rs index d36982ff..ae4b7cc3 100644 --- a/benchmarks/src/main.rs +++ b/benchmarks/src/main.rs @@ -1,5 +1,5 @@ use bitcoin::consensus::Decodable; -use bitcoin::{consensus::Encodable, Block as BitcoinBlock, BlockHeader}; +use bitcoin::{block::Header as BlockHeader, consensus::Encodable, Block as BitcoinBlock}; use canbench_rs::{bench, bench_fn, BenchResult}; use ic_btc_canister::{types::BlockHeaderBlob, with_state_mut}; use ic_btc_interface::{InitConfig, Network}; @@ -22,7 +22,7 @@ fn init() { .split('\n') .map(|block_hex| { let block_bytes = hex::decode(block_hex).unwrap(); - Block::new(BitcoinBlock::consensus_decode(block_bytes.as_slice()).unwrap()) + Block::new(BitcoinBlock::consensus_decode(&mut block_bytes.as_slice()).unwrap()) }) .collect(), ); @@ -173,3 +173,8 @@ fn pre_upgrade_with_many_unstable_blocks() -> BenchResult { } fn main() {} + +getrandom::register_custom_getrandom!(always_fail); +pub fn always_fail(_buf: &mut [u8]) -> Result<(), getrandom::Error> { + Err(getrandom::Error::UNSUPPORTED) +} diff --git a/bootstrap/main-state-builder/Cargo.lock b/bootstrap/main-state-builder/Cargo.lock index f3061eae..ca9dd4ee 100644 --- a/bootstrap/main-state-builder/Cargo.lock +++ b/bootstrap/main-state-builder/Cargo.lock @@ -54,17 +54,33 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes", +] + [[package]] name = "bech32" -version = "0.8.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" [[package]] name = "binread" @@ -91,22 +107,55 @@ dependencies = [ [[package]] name = "bitcoin" -version = "0.28.2" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d30fb43d287492017964a1fd7d3f82e8cc760818471c6ef2d44111e317d5c3" +checksum = "ea507acc1cd80fc084ace38544bbcf7ced7c2aa65b653b102de0ce718df668f6" dependencies = [ + "base58ck", "bech32", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", "bitcoin_hashes", + "hex-conservative", + "hex_lit", "secp256k1", "serde", ] +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoin-io" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340e09e8399c7bd8912f495af6aa58bea0c9214773417ffaa8f6460f93aaee56" + +[[package]] +name = "bitcoin-units" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +dependencies = [ + "bitcoin-internals", + "serde", +] + [[package]] name = "bitcoin_hashes" -version = "0.10.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006cc91e1a1d99819bc5b8214be3555c1f0611b169f527a1fdc54ed1f2b745b0" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" dependencies = [ + "bitcoin-io", + "hex-conservative", "serde", ] @@ -367,6 +416,21 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec 0.7.4", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + [[package]] name = "ic-btc-canister" version = "0.1.0" @@ -389,7 +453,7 @@ dependencies = [ [[package]] name = "ic-btc-interface" -version = "0.2.0" +version = "0.2.2" dependencies = [ "candid", "serde", @@ -585,7 +649,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "563c9d701c3a31dfffaaf9ce23507ba09cbe0b9125ba176d15e629b0235e9acc" dependencies = [ - "arrayvec", + "arrayvec 0.5.2", "typed-arena", "unicode-segmentation", ] @@ -639,19 +703,20 @@ checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" [[package]] name = "secp256k1" -version = "0.22.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295642060261c80709ac034f52fca8e5a9fa2c7d341ded5cdb164b7c33768b2a" +checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3" dependencies = [ + "bitcoin_hashes", "secp256k1-sys", "serde", ] [[package]] name = "secp256k1-sys" -version = "0.5.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152e20a0fd0519390fc43ab404663af8a0b794273d2a91d60ad4a39f13ffe110" +checksum = "1433bd67156263443f14d603720b082dd3121779323fce20cba2aa07b874bc1b" dependencies = [ "cc", ] diff --git a/bootstrap/main-state-builder/Cargo.toml b/bootstrap/main-state-builder/Cargo.toml index 316fb6f8..9bc06288 100644 --- a/bootstrap/main-state-builder/Cargo.toml +++ b/bootstrap/main-state-builder/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -bitcoin = "0.28.1" +bitcoin = "0.32.2" clap = { version = "4.0.11", features = ["derive"] } ciborium = "0.2.1" hex = "0.4.3" diff --git a/bootstrap/main-state-builder/src/main.rs b/bootstrap/main-state-builder/src/main.rs index 9271adc4..d54dd782 100644 --- a/bootstrap/main-state-builder/src/main.rs +++ b/bootstrap/main-state-builder/src/main.rs @@ -61,7 +61,7 @@ fn read_block(reader: &mut BufReader) -> Block { let mut block = String::new(); reader.read_line(&mut block).unwrap(); let block = hex::decode(block.replace('\n', "")).unwrap(); - Block::new(BitcoinBlock::consensus_decode(block.as_slice()).unwrap()) + Block::new(BitcoinBlock::consensus_decode(&mut block.as_slice()).unwrap()) } fn main() { diff --git a/bootstrap/state-builder/src/build_address_utxos.rs b/bootstrap/state-builder/src/build_address_utxos.rs index 80884019..f108a75b 100644 --- a/bootstrap/state-builder/src/build_address_utxos.rs +++ b/bootstrap/state-builder/src/build_address_utxos.rs @@ -6,7 +6,7 @@ //! --network testnet \ //! --output balances.bin \ //! --utxos-dump-path utxos-dump.csv -use bitcoin::{Address as BitcoinAddress, Script, Txid as BitcoinTxid}; +use bitcoin::{Address as BitcoinAddress, ScriptBuf, Txid as BitcoinTxid}; use clap::Parser; use ic_btc_canister::types::{into_bitcoin_network, Address, AddressUtxo}; use ic_btc_interface::Network; @@ -51,7 +51,7 @@ fn main() { let line = line.unwrap(); let parts: Vec<_> = line.split(',').collect(); - let txid = Txid::from(BitcoinTxid::from_str(parts[1]).unwrap().to_vec()); + let txid = Txid::from(BitcoinTxid::from_str(parts[1]).unwrap().as_ref()); let vout: u32 = parts[2].parse().unwrap(); let address_str = parts[5]; let height: u32 = parts[0].parse().unwrap(); @@ -63,16 +63,16 @@ fn main() { // Load the address. The UTXO dump tool we use doesn't output all the addresses // we support, so if parsing the address itself fails, we try parsing the script directly. - let address = if let Ok(address) = BitcoinAddress::from_str(address_str) { - Some(address) - } else { - BitcoinAddress::from_script( - &Script::from(hex::decode(script).expect("script must be valid hex")), - into_bitcoin_network(args.network), - ) - }; + let address = BitcoinAddress::from_str(address_str) + .map(|address| address.assume_checked()) + .or_else(|_| { + BitcoinAddress::from_script( + &ScriptBuf::from(hex::decode(script).expect("script must be valid hex")), + into_bitcoin_network(args.network), + ) + }); - if let Some(address) = address { + if let Ok(address) = address { let address: Address = address.into(); address_utxos diff --git a/bootstrap/state-builder/src/build_balances.rs b/bootstrap/state-builder/src/build_balances.rs index c00d2069..2cb2f158 100644 --- a/bootstrap/state-builder/src/build_balances.rs +++ b/bootstrap/state-builder/src/build_balances.rs @@ -6,7 +6,7 @@ //! --network testnet \ //! --output balances.bin \ //! --utxos-dump-path utxos-dump.csv -use bitcoin::{Address as BitcoinAddress, Script}; +use bitcoin::{Address as BitcoinAddress, ScriptBuf}; use clap::Parser; use ic_btc_canister::types::{into_bitcoin_network, Address}; use ic_btc_interface::Network; @@ -59,16 +59,16 @@ fn main() { // Load the address. The UTXO dump tool we use doesn't output all the addresses // we support, so if parsing the address itself fails, we try parsing the script directly. - let address = if let Ok(address) = BitcoinAddress::from_str(address_str) { - Some(address) - } else { - BitcoinAddress::from_script( - &Script::from(hex::decode(script).expect("script must be valid hex")), - into_bitcoin_network(args.network), - ) - }; - - if let Some(address) = address { + let address = BitcoinAddress::from_str(address_str) + .map(|address| address.assume_checked()) + .or_else(|_| { + BitcoinAddress::from_script( + &ScriptBuf::from(hex::decode(script).expect("script must be valid hex")), + into_bitcoin_network(args.network), + ) + }); + + if let Ok(address) = address { let address: Address = address.into(); // Update the balance of the address. diff --git a/bootstrap/state-builder/src/build_utxos.rs b/bootstrap/state-builder/src/build_utxos.rs index eba65fd1..3b00da79 100644 --- a/bootstrap/state-builder/src/build_utxos.rs +++ b/bootstrap/state-builder/src/build_utxos.rs @@ -6,7 +6,7 @@ //! --network testnet \ //! --output output-dir \ //! --utxos-dump-path utxos-dump.csv -use bitcoin::{Address, Txid as BitcoinTxid}; +use bitcoin::{Address, Amount, Txid as BitcoinTxid}; use clap::Parser; use ic_btc_canister::{types::TxOut, with_state, with_state_mut}; use ic_btc_interface::{Flag, InitConfig, Network}; @@ -81,7 +81,7 @@ fn main() { let line = line.unwrap(); let parts: Vec<_> = line.split(',').collect(); - let txid = Txid::from(BitcoinTxid::from_str(parts[1]).unwrap().to_vec()); + let txid = Txid::from(BitcoinTxid::from_str(parts[1]).unwrap().as_ref()); let vout: u32 = parts[2].parse().unwrap(); let amount: u64 = parts[3].parse().unwrap(); let script = parts[6]; @@ -96,15 +96,15 @@ fn main() { // Instead of using the scripts from the database, we can infer the script from the // address. Otherwise, we use the script in the chainstate database as-is. let script = match Address::from_str(address_str) { - Ok(address) => address.script_pubkey().as_bytes().to_vec(), + Ok(address) => address.assume_checked().script_pubkey().as_bytes().to_vec(), Err(_) => hex::decode(script).unwrap(), }; // Insert the UTXO let outpoint = OutPoint { txid, vout }; - if !bitcoin::Script::from(script.clone()).is_provably_unspendable() { + if !bitcoin::ScriptBuf::from(script.clone()).is_op_return() { let txout = TxOut { - value: amount, + value: Amount::from_sat(amount), script_pubkey: script, }; diff --git a/bootstrap/state-builder/src/main.rs b/bootstrap/state-builder/src/main.rs index 239c7ec1..175afa02 100644 --- a/bootstrap/state-builder/src/main.rs +++ b/bootstrap/state-builder/src/main.rs @@ -7,7 +7,7 @@ //! --network testnet \ //! --blocks-path /path/to/data/testnet3 \ //! --tip 000000002ce019cc4a8f2af62b3ecf7c30a19d29828b25268a0194dbac3cac50 -use bitcoin::{consensus::Decodable, BlockHash, BlockHeader}; +use bitcoin::{block::Header as BlockHeader, consensus::Decodable, io::Cursor, BlockHash}; use byteorder::{LittleEndian, ReadBytesExt}; use clap::Parser; use ic_btc_canister::{ @@ -21,7 +21,7 @@ use rusty_leveldb::{Options, DB}; use std::{ collections::BTreeMap, fs::File, - io::{Cursor, Read, Seek, SeekFrom, Write}, + io::{Read, Seek, SeekFrom, Write}, path::{Path, PathBuf}, str::FromStr, }; @@ -50,7 +50,7 @@ struct Args { } // How to read Bitcoin's varint format. -trait VarIntRead: std::io::Read { +trait VarIntRead: bitcoin::io::Read { fn read_varint(&mut self) -> usize { let mut n = 0; loop { @@ -100,7 +100,7 @@ fn get_block_info( block_hash: &BlockHash, ) -> Option<(Height, FileNumber, FileOffset, BlockHash)> { let mut key: Vec = vec![98]; - key.extend(block_hash.to_vec()); + key.extend((block_hash.as_ref() as &[u8]).to_vec()); let value = db.get(&key).unwrap(); diff --git a/canbench_results.yml b/canbench_results.yml index 99c853a1..8ccbffb2 100644 --- a/canbench_results.yml +++ b/canbench_results.yml @@ -1,36 +1,36 @@ benches: get_metrics: total: - instructions: 86997214 + instructions: 93349358 heap_increase: 0 stable_memory_increase: 0 scopes: {} insert_300_blocks: total: - instructions: 561767622 - heap_increase: 6 + instructions: 567848759 + heap_increase: 7 stable_memory_increase: 0 scopes: {} insert_block_headers: total: - instructions: 3895777071 - heap_increase: 2 + instructions: 3913080916 + heap_increase: 1 stable_memory_increase: 0 scopes: {} insert_block_headers_multiple_times: total: - instructions: 13898426217 + instructions: 13975563882 heap_increase: 7 stable_memory_increase: 0 scopes: {} pre_upgrade_with_many_unstable_blocks: total: - instructions: 5809876449 + instructions: 5795439685 heap_increase: 4097 stable_memory_increase: 1792 scopes: serialize_blocktree: - instructions: 2382106854 + instructions: 2366746456 heap_increase: 2048 stable_memory_increase: 0 serialize_blocktree_flatten: @@ -38,7 +38,7 @@ benches: heap_increase: 0 stable_memory_increase: 0 serialize_blocktree_serialize_seq: - instructions: 2381902913 + instructions: 2366542515 heap_increase: 2048 stable_memory_increase: 0 version: 0.1.1 diff --git a/canister/Cargo.toml b/canister/Cargo.toml index efa036a1..3cac269b 100644 --- a/canister/Cargo.toml +++ b/canister/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bitcoin = { workspace = true, features = ["use-serde"] } +bitcoin = { workspace = true, features = ["serde"] } # An optional dependency to benchmark parts of the code. canbench-rs = { workspace = true, optional = true } candid = { workspace = true } diff --git a/canister/src/address_utxoset.rs b/canister/src/address_utxoset.rs index 929013d6..5729ec4c 100644 --- a/canister/src/address_utxoset.rs +++ b/canister/src/address_utxoset.rs @@ -67,7 +67,7 @@ impl<'a> AddressUtxoSet<'a> { }); self.added_utxos.insert(Utxo { outpoint: outpoint.clone(), - value: txout.value, + value: txout.value.to_sat(), height, }); } @@ -96,7 +96,7 @@ impl<'a> AddressUtxoSet<'a> { Utxo { outpoint, height, - value: tx_out.value, + value: tx_out.value.to_sat(), } }); diff --git a/canister/src/api/fee_percentiles.rs b/canister/src/api/fee_percentiles.rs index ef3a11f4..efb7e9d8 100644 --- a/canister/src/api/fee_percentiles.rs +++ b/canister/src/api/fee_percentiles.rs @@ -124,10 +124,11 @@ fn get_tx_fee_per_byte( .get_tx_out(&outpoint) .unwrap_or_else(|| panic!("tx out of outpoint {:?} must exist", outpoint)) .0 - .value; + .value + .to_sat(); } for tx_out in tx.output() { - satoshi -= tx_out.value; + satoshi -= tx_out.value.to_sat(); } if tx.vsize() > 0 { @@ -602,7 +603,7 @@ mod test { .with_output(&random_p2pkh_address(Network::Regtest), balance) .build(); - let witness = Witness::from_vec(vec![ + let witness = Witness::from_slice(&[ vec![0u8, 2u8], vec![4u8, 2u8], vec![3u8, 2u8], @@ -619,8 +620,8 @@ mod test { .build(); // Check that vsize() is not the same as size() of a transaction. - assert_ne!(tx.vsize(), tx.size()); - assert_eq!(tx_without_witness.vsize(), tx_without_witness.size()); + assert_ne!(tx.vsize(), tx.total_size()); + assert_eq!(tx_without_witness.vsize(), tx_without_witness.total_size()); let blocks = vec![ BlockBuilder::with_prev_header(genesis_block(Network::Regtest).header()) @@ -636,7 +637,7 @@ mod test { // Coinbase txs are ignored, so the percentiles should be the fee / vbyte of the second transaction. assert_ne!( get_current_fee_percentiles_with_number_of_transactions(s, 1), - vec![fee_in_millisatoshi / tx.size() as u64; PERCENTILE_BUCKETS] + vec![fee_in_millisatoshi / tx.total_size() as u64; PERCENTILE_BUCKETS] ); assert_eq!( get_current_fee_percentiles_with_number_of_transactions(s, 1), diff --git a/canister/src/api/get_balance.rs b/canister/src/api/get_balance.rs index 0a9acdb1..e631252d 100644 --- a/canister/src/api/get_balance.rs +++ b/canister/src/api/get_balance.rs @@ -69,7 +69,7 @@ fn get_balance_private(request: GetBalanceRequest) -> Result Result Result<(), Sen })); // Decode the transaction as a sanity check that it's valid. - let tx = Transaction::consensus_decode(request.transaction.as_slice()) + let tx = Transaction::consensus_decode(&mut request.transaction.as_slice()) .map_err(|_| SendTransactionError::MalformedTransaction)?; - runtime::print(&format!("[send_transaction] Tx ID: {}", tx.txid())); + runtime::print(&format!("[send_transaction] Tx ID: {}", tx.compute_txid())); // Bump the counter for the number of (valid) requests received. with_state_mut(|s| { @@ -41,6 +41,7 @@ pub async fn send_transaction(request: SendTransactionRequest) -> Result<(), Sen #[cfg(test)] mod test { use super::*; + use bitcoin::{absolute::LockTime, transaction::Version}; use ic_btc_interface::{Fees, Flag, InitConfig, Network, NetworkInRequest}; fn empty_transaction() -> Vec { @@ -48,8 +49,8 @@ mod test { use bitcoin::consensus::Encodable; Transaction { - version: 0, - lock_time: 0, + version: Version::ONE, + lock_time: LockTime::ZERO, input: vec![], output: vec![], } diff --git a/canister/src/block_header_store.rs b/canister/src/block_header_store.rs index 8df323d4..b81fdb6e 100644 --- a/canister/src/block_header_store.rs +++ b/canister/src/block_header_store.rs @@ -1,6 +1,6 @@ use crate::{memory::Memory, types::BlockHeaderBlob}; +use bitcoin::block::Header as BlockHeader; use bitcoin::consensus::{Decodable, Encodable}; -use bitcoin::BlockHeader; use ic_btc_interface::Height; use ic_btc_types::{Block, BlockHash}; use ic_stable_structures::StableBTreeMap; @@ -84,7 +84,7 @@ impl BlockHeaderStore { } fn deserialize_block_header(block_header_blob: BlockHeaderBlob) -> BlockHeader { - BlockHeader::consensus_decode(block_header_blob.as_slice()) + BlockHeader::consensus_decode(&mut block_header_blob.as_slice()) .expect("block header decoding must succeed") } diff --git a/canister/src/blocktree.rs b/canister/src/blocktree.rs index 6dd7e975..deea5e5f 100644 --- a/canister/src/blocktree.rs +++ b/canister/src/blocktree.rs @@ -146,7 +146,7 @@ impl BlockTree { Some((block_subtree, _)) => { assert_eq!( block_subtree.root.block_hash().to_vec(), - block.header().prev_blockhash.to_vec() + block.header().prev_blockhash.as_ref() as &[u8] ); // Add the block as a successor. block_subtree.children.push(BlockTree::new(block)); @@ -388,7 +388,7 @@ mod test { for i in 1..chain.len() { assert_eq!( chain[i - 1].block_hash().to_vec(), - chain[i].header().prev_blockhash.to_vec() + chain[i].header().prev_blockhash.as_ref() as &[u8] ) } } @@ -429,7 +429,7 @@ mod test { for i in 1..chain.len() { assert_eq!( chain[i - 1].block_hash().to_vec(), - chain[i].header().prev_blockhash.to_vec() + chain[i].header().prev_blockhash.as_ref() as &[u8] ) } } diff --git a/canister/src/heartbeat.rs b/canister/src/heartbeat.rs index f784c271..2b63a4bd 100644 --- a/canister/src/heartbeat.rs +++ b/canister/src/heartbeat.rs @@ -152,7 +152,7 @@ fn maybe_process_response() { )); for block_bytes in response.blocks.iter() { // Deserialize the block. - let block = match BitcoinBlock::consensus_decode(block_bytes.as_slice()) { + let block = match BitcoinBlock::consensus_decode(&mut block_bytes.as_slice()) { Ok(block) => block, Err(err) => { print(&format!( @@ -270,7 +270,7 @@ mod test { types::{Address, BlockBlob, GetSuccessorsCompleteResponse, GetSuccessorsPartialResponse}, utxo_set::IngestingBlock, }; - use bitcoin::BlockHeader; + use bitcoin::block::Header as BlockHeader; use ic_btc_interface::{InitConfig, Network}; fn build_block(prev_header: &BlockHeader, address: Address, num_transactions: u128) -> Block { diff --git a/canister/src/state.rs b/canister/src/state.rs index 94fa06cd..dda545bd 100644 --- a/canister/src/state.rs +++ b/canister/src/state.rs @@ -11,7 +11,7 @@ use crate::{ validation::ValidationContext, UtxoSet, }; -use bitcoin::{consensus::Decodable, BlockHeader}; +use bitcoin::{block::Header as BlockHeader, consensus::Decodable}; use candid::Principal; use ic_btc_interface::{Fees, Flag, Height, MillisatoshiPerByte, Network}; use ic_btc_types::{Block, BlockHash, OutPoint}; @@ -207,7 +207,7 @@ pub fn insert_next_block_headers(state: &mut State, next_block_headers: &[BlockH break; } - let block_header = match BlockHeader::consensus_decode(block_header_blob.as_slice()) { + let block_header = match BlockHeader::consensus_decode(&mut block_header_blob.as_slice()) { Ok(header) => header, Err(err) => { print(&format!( diff --git a/canister/src/test_utils.rs b/canister/src/test_utils.rs index 3702c3ae..a5ec5d7f 100644 --- a/canister/src/test_utils.rs +++ b/canister/src/test_utils.rs @@ -3,8 +3,9 @@ use crate::{ types::{into_bitcoin_network, Address}, }; use bitcoin::{ - hashes::Hash, secp256k1::rand::rngs::OsRng, secp256k1::Secp256k1, Address as BitcoinAddress, - BlockHeader, PublicKey, Script, WScriptHash, Witness, + absolute::LockTime, block::Header as BlockHeader, hashes::Hash, secp256k1::rand::rngs::OsRng, + secp256k1::rand::RngCore, secp256k1::Secp256k1, Address as BitcoinAddress, CompressedPublicKey, + PublicKey, ScriptBuf, WScriptHash, Witness, }; use ic_btc_interface::Network; use ic_btc_test_utils::{ @@ -12,8 +13,8 @@ use ic_btc_test_utils::{ }; use ic_btc_types::{Block, OutPoint, Transaction}; use ic_stable_structures::{BoundedStorable, Memory, StableBTreeMap}; -use proptest::prelude::RngCore; use std::{ + convert::TryFrom, ops::{Bound, RangeBounds}, str::FromStr, }; @@ -21,10 +22,10 @@ use std::{ /// Generates a random P2PKH address. pub fn random_p2pkh_address(network: Network) -> Address { let secp = Secp256k1::new(); - let mut rng = OsRng::new().unwrap(); + let mut rng = OsRng; BitcoinAddress::p2pkh( - &PublicKey::new(secp.generate_keypair(&mut rng).1), + PublicKey::new(secp.generate_keypair(&mut rng).1), into_bitcoin_network(network), ) .into() @@ -36,21 +37,23 @@ pub fn random_p2tr_address(network: Network) -> Address { pub fn random_p2wpkh_address(network: Network) -> Address { let secp = Secp256k1::new(); - let mut rng = OsRng::new().unwrap(); + let mut rng = OsRng; BitcoinAddress::p2wpkh( - &PublicKey::new(secp.generate_keypair(&mut rng).1), + &CompressedPublicKey::try_from(PublicKey::new(secp.generate_keypair(&mut rng).1)) + .expect("failed to create p2wpkh address"), into_bitcoin_network(network), ) - .expect("failed to create p2wpkh address") .into() } pub fn random_p2wsh_address(network: Network) -> Address { - let mut rng = OsRng::new().unwrap(); + let mut rng = OsRng; let mut hash = [0u8; 32]; rng.fill_bytes(&mut hash); BitcoinAddress::p2wsh( - &Script::new_v0_p2wsh(&WScriptHash::from_hash(Hash::from_slice(&hash).unwrap())), + &ScriptBuf::new_p2wsh(&WScriptHash::from_raw_hash( + Hash::from_slice(&hash).unwrap(), + )), into_bitcoin_network(network), ) .into() @@ -215,14 +218,16 @@ impl TransactionBuilder { pub fn with_lock_time(self, i: u32) -> Self { Self { - builder: self.builder.with_lock_time(i), + builder: self.builder.with_lock_time(LockTime::from_consensus(i)), } } pub fn with_output(self, address: &Address, value: u64) -> Self { Self { builder: self.builder.with_output( - &BitcoinAddress::from_str(&address.to_string()).unwrap(), + &BitcoinAddress::from_str(&address.to_string()) + .unwrap() + .assume_checked(), value, ), } diff --git a/canister/src/tests.rs b/canister/src/tests.rs index e589e3fe..cfdea411 100644 --- a/canister/src/tests.rs +++ b/canister/src/tests.rs @@ -13,7 +13,7 @@ use crate::{ }; use crate::{init, test_utils::random_p2pkh_address}; use bitcoin::consensus::{Decodable, Encodable}; -use bitcoin::{Block as BitcoinBlock, BlockHeader}; +use bitcoin::{block::Header as BlockHeader, Block as BitcoinBlock}; use byteorder::{LittleEndian, ReadBytesExt}; use ic_btc_interface::{Flag, GetUtxosResponse, InitConfig, Network, Txid, UtxosFilter}; use ic_btc_interface::{OutPoint, Utxo}; diff --git a/canister/src/types.rs b/canister/src/types.rs index ade1b3b8..e35276ba 100644 --- a/canister/src/types.rs +++ b/canister/src/types.rs @@ -1,5 +1,5 @@ use bitcoin::{ - Address as BitcoinAddress, Network as BitcoinNetwork, Script, TxOut as BitcoinTxOut, + Address as BitcoinAddress, Amount, Network as BitcoinNetwork, ScriptBuf, TxOut as BitcoinTxOut, }; use candid::CandidType; use ic_btc_interface::{ @@ -25,7 +25,7 @@ const EXPECTED_PAGE_LENGTH: usize = 72; /// A Bitcoin transaction's output. #[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] pub struct TxOut { - pub value: u64, + pub value: Amount, pub script_pubkey: Vec, } @@ -104,9 +104,9 @@ pub trait Storable { impl Storable for (TxOut, Height) { fn to_bytes(&self) -> Vec { vec![ - self.0.value.to_bytes().to_vec(), // Store the value (8 bytes) - self.0.script_pubkey.clone(), // Then the script (size varies) - Storable::to_bytes(&self.1), // Then the height (4 bytes) + self.0.value.to_sat().to_bytes().to_vec(), // Store the value (8 bytes) + self.0.script_pubkey.clone(), // Then the script (size varies) + Storable::to_bytes(&self.1), // Then the height (4 bytes) ] .into_iter() .flatten() @@ -116,7 +116,7 @@ impl Storable for (TxOut, Height) { fn from_bytes(mut bytes: Vec) -> Self { let height = ::from_bytes(bytes.split_off(bytes.len() - 4)); let script_pubkey = bytes.split_off(8); - let value = u64::from_bytes(Cow::Owned(bytes)); + let value = Amount::from_sat(u64::from_bytes(Cow::Owned(bytes))); ( TxOut { value, @@ -315,11 +315,11 @@ impl BoundedStorable for BlockHeaderBlob { const IS_FIXED_SIZE: bool = true; } -impl From<&bitcoin::BlockHeader> for BlockHeaderBlob { - fn from(header: &bitcoin::BlockHeader) -> Self { +impl From<&bitcoin::block::Header> for BlockHeaderBlob { + fn from(header: &bitcoin::block::Header) -> Self { use bitcoin::consensus::Encodable; let mut block_header_blob = vec![]; - bitcoin::BlockHeader::consensus_encode(header, &mut block_header_blob).unwrap(); + bitcoin::block::Header::consensus_encode(header, &mut block_header_blob).unwrap(); Self(block_header_blob) } } @@ -433,9 +433,9 @@ pub struct Address(String); impl Address { /// Creates a new address from a bitcoin script. - pub fn from_script(script: &Script, network: Network) -> Result { + pub fn from_script(script: &ScriptBuf, network: Network) -> Result { let address = BitcoinAddress::from_script(script, into_bitcoin_network(network)) - .ok_or(InvalidAddress)?; + .map_err(|_| InvalidAddress)?; // Due to a bug in the bitcoin crate, it is possible in some extremely rare cases // that `Address:from_script` succeeds even if the address is invalid. @@ -464,7 +464,7 @@ impl FromStr for Address { fn from_str(s: &str) -> Result { BitcoinAddress::from_str(s) - .map(|address| Address(address.to_string())) + .map(|address| Address(address.assume_checked().to_string())) .map_err(|_| InvalidAddress) } } @@ -655,7 +655,7 @@ fn address_handles_script_edge_case() { // (https://github.com/rust-bitcoin/rust-bitcoin/issues/995) // // This test verifies that we're protecting ourselves from that case. - let script = Script::from(vec![ + let script = ScriptBuf::from(vec![ 0, 17, 97, 69, 142, 51, 3, 137, 205, 4, 55, 238, 159, 227, 100, 29, 112, 204, 24, ]); diff --git a/canister/src/unstable_blocks.rs b/canister/src/unstable_blocks.rs index 35e2eaf8..f5178f57 100644 --- a/canister/src/unstable_blocks.rs +++ b/canister/src/unstable_blocks.rs @@ -6,7 +6,7 @@ use crate::{ types::{Address, TxOut}, UtxoSet, }; -use bitcoin::BlockHeader; +use bitcoin::block::Header as BlockHeader; use ic_btc_interface::{Height, Network}; use ic_btc_types::{Block, BlockHash, OutPoint}; use outpoints_cache::OutPointsCache; diff --git a/canister/src/unstable_blocks/next_block_headers.rs b/canister/src/unstable_blocks/next_block_headers.rs index 01c4415c..796023d9 100644 --- a/canister/src/unstable_blocks/next_block_headers.rs +++ b/canister/src/unstable_blocks/next_block_headers.rs @@ -1,4 +1,4 @@ -use bitcoin::BlockHeader; +use bitcoin::block::Header as BlockHeader; use ic_btc_interface::Height; use ic_btc_types::BlockHash; use serde::{Deserialize, Serialize}; diff --git a/canister/src/unstable_blocks/outpoints_cache.rs b/canister/src/unstable_blocks/outpoints_cache.rs index 455861cb..67970a92 100644 --- a/canister/src/unstable_blocks/outpoints_cache.rs +++ b/canister/src/unstable_blocks/outpoints_cache.rs @@ -109,7 +109,7 @@ impl OutPointsCache { }; if let Ok(address) = Address::from_script( - &bitcoin::Script::from(txout.script_pubkey.clone()), + &bitcoin::ScriptBuf::from(txout.script_pubkey.clone()), utxos.network(), ) { let entry = removed_outpoints.entry(address).or_insert(vec![]); diff --git a/canister/src/utxo_set.rs b/canister/src/utxo_set.rs index d5367ebd..6774f727 100644 --- a/canister/src/utxo_set.rs +++ b/canister/src/utxo_set.rs @@ -4,7 +4,7 @@ use crate::{ runtime::{inc_performance_counter, performance_counter, print}, types::{Address, AddressUtxo, AddressUtxoRange, Slicing, TxOut, Utxo}, }; -use bitcoin::{Script, TxOut as BitcoinTxOut}; +use bitcoin::{Amount, ScriptBuf, TxOut as BitcoinTxOut}; use ic_btc_interface::{Height, Network, Satoshi}; use ic_btc_types::{Block, BlockHash, OutPoint, Transaction, Txid}; use ic_stable_structures::{storable::Blob, BoundedStorable, StableBTreeMap, Storable as _}; @@ -168,13 +168,17 @@ impl UtxoSet { // Add any removed outpoints back to the balance. for outpoint in utxos_delta.get_removed_outpoints(address) { let (tx_out, _) = utxos_delta.get_utxo(outpoint).expect("UTXO must exist"); - balance = balance.checked_add(tx_out.value).expect("Cannot overflow"); + balance = balance + .checked_add(tx_out.value.to_sat()) + .expect("Cannot overflow"); } // Remove any added outpoints from the balance. for outpoint in utxos_delta.get_added_outpoints(address) { let (tx_out, _) = utxos_delta.get_utxo(outpoint).expect("UTXO must exist"); - balance = balance.checked_sub(tx_out.value).expect("Cannot underflow"); + balance = balance + .checked_sub(tx_out.value.to_sat()) + .expect("Cannot underflow"); } } @@ -310,7 +314,7 @@ impl UtxoSet { match self.utxos.remove(&outpoint) { Some((txout, height)) => { if let Ok(address) = Address::from_script( - &Script::from(txout.script_pubkey.clone()), + &ScriptBuf::from(txout.script_pubkey.clone()), self.network, ) { let address_utxo = AddressUtxo { @@ -330,13 +334,13 @@ impl UtxoSet { ); // Update the balance of the address. - if txout.value != 0 { + if txout.value != Amount::ZERO { let address_balance = self.balances.get(&address).unwrap_or_else(|| { panic!("Address {} must exist in the balances map (trying to remove outpoint {:?})", address, input.previous_output); }); - match address_balance - txout.value { + match address_balance - txout.value.to_sat() { // Remove the address from the map if balance is zero. 0 => self.balances.remove(&address), // Update the balance in the map. @@ -369,7 +373,7 @@ impl UtxoSet { return Slicing::Paused(vout); } - if !(output.script_pubkey.is_provably_unspendable()) { + if !(output.script_pubkey.is_op_return()) { let ins_start = performance_counter(); let txid = tx.txid(); stats.ins_txids += performance_counter() - ins_start; @@ -416,7 +420,7 @@ impl UtxoSet { // Update the balance of the address. let address_balance = self.balances.get(&address).unwrap_or(0); self.balances - .insert(address.clone(), address_balance + output.value); + .insert(address.clone(), address_balance + output.value.to_sat()); utxos_delta.insert(address, outpoint.clone(), tx_out.clone(), self.next_height); } @@ -442,7 +446,7 @@ impl UtxoSet { #[cfg(test)] pub fn get_total_supply(&self) -> Satoshi { - self.utxos.iter().map(|(_, (v, _))| v.value).sum() + self.utxos.iter().map(|(_, (v, _))| v.value.to_sat()).sum() } } @@ -571,6 +575,7 @@ mod test { use crate::test_utils::{random_p2pkh_address, BlockBuilder, TransactionBuilder}; use crate::{address_utxoset::AddressUtxoSet, unstable_blocks::UnstableBlocks}; use bitcoin::blockdata::{opcodes::all::OP_RETURN, script::Builder}; + use bitcoin::{absolute::LockTime, transaction::Version}; use ic_btc_interface::Network; use proptest::prelude::*; use std::collections::BTreeSet; @@ -598,8 +603,8 @@ mod test { let coinbase_empty_tx = Transaction::new(bitcoin::Transaction { output: vec![], input: vec![], - version: 1, - lock_time: 0, + version: Version::ONE, + lock_time: LockTime::ZERO, }); ingest_tx(&mut utxo, &coinbase_empty_tx); @@ -617,12 +622,12 @@ mod test { let block = BlockBuilder::genesis() .with_transaction(Transaction::new(bitcoin::Transaction { output: vec![BitcoinTxOut { - value: 50_0000_0000, + value: Amount::from_sat(50_0000_0000), script_pubkey: Builder::new().push_opcode(OP_RETURN).into_script(), }], input: vec![], - version: 1, - lock_time: 0, + version: Version::ONE, + lock_time: LockTime::ZERO, })) .build(); diff --git a/canister/src/validation.rs b/canister/src/validation.rs index 59ae8cdf..69aab3cf 100644 --- a/canister/src/validation.rs +++ b/canister/src/validation.rs @@ -1,5 +1,5 @@ use crate::{blocktree::BlockDoesNotExtendTree, state::State, unstable_blocks}; -use bitcoin::BlockHeader; +use bitcoin::block::Header as BlockHeader; use ic_btc_validation::HeaderStore; /// A structure passed to the validation crate to validate a specific block header. @@ -52,7 +52,7 @@ impl<'a> ValidationContext<'a> { impl<'a> HeaderStore for ValidationContext<'a> { fn get_with_block_hash(&self, hash: &bitcoin::BlockHash) -> Option { // Check if the header is in the chain. - let hash = ic_btc_types::BlockHash::from(hash.to_vec()); + let hash = ic_btc_types::BlockHash::from(hash.as_ref()); for item in self.chain.iter() { if item.1 == hash { return Some(*item.0); diff --git a/e2e-tests/disable-api-if-not-fully-synced-flag/Cargo.toml b/e2e-tests/disable-api-if-not-fully-synced-flag/Cargo.toml index de45fc3a..fb30a674 100644 --- a/e2e-tests/disable-api-if-not-fully-synced-flag/Cargo.toml +++ b/e2e-tests/disable-api-if-not-fully-synced-flag/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] bitcoin = { workspace = true } candid = { workspace = true } +getrandom = { workspace = true } ic-btc-test-utils = { workspace = true } ic-cdk = { workspace = true } ic-cdk-macros = { workspace = true } diff --git a/e2e-tests/disable-api-if-not-fully-synced-flag/src/main.rs b/e2e-tests/disable-api-if-not-fully-synced-flag/src/main.rs index fadd6460..3eeab084 100644 --- a/e2e-tests/disable-api-if-not-fully-synced-flag/src/main.rs +++ b/e2e-tests/disable-api-if-not-fully-synced-flag/src/main.rs @@ -1,6 +1,6 @@ use bitcoin::{ - blockdata::constants::genesis_block, consensus::Encodable, Address, Block, BlockHeader, - Network as BitcoinNetwork, + block::Header as BlockHeader, blockdata::constants::genesis_block, consensus::Encodable, + Address, Block, Network as BitcoinNetwork, }; use candid::CandidType; use ic_btc_test_utils::{BlockBuilder, TransactionBuilder}; @@ -87,7 +87,7 @@ fn init() { let block = BlockBuilder::with_prev_header(prev_header) .with_transaction( TransactionBuilder::new() - .with_output(&Address::from_str(ADDRESS).unwrap(), 1) + .with_output(&Address::from_str(ADDRESS).unwrap().assume_checked(), 1) .build(), ) .build(); @@ -99,7 +99,7 @@ fn init() { let next_block = BlockBuilder::with_prev_header(prev_header) .with_transaction( TransactionBuilder::new() - .with_output(&Address::from_str(ADDRESS).unwrap(), 1) + .with_output(&Address::from_str(ADDRESS).unwrap().assume_checked(), 1) .build(), ) .build(); @@ -160,3 +160,8 @@ fn append_block_header(block_header: &BlockHeader) { } fn main() {} + +getrandom::register_custom_getrandom!(always_fail); +pub fn always_fail(_buf: &mut [u8]) -> Result<(), getrandom::Error> { + Err(getrandom::Error::UNSUPPORTED) +} diff --git a/e2e-tests/scenario-1/Cargo.toml b/e2e-tests/scenario-1/Cargo.toml index 0a4faffe..0aae3922 100644 --- a/e2e-tests/scenario-1/Cargo.toml +++ b/e2e-tests/scenario-1/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] bitcoin = { workspace = true } candid = { workspace = true } +getrandom = { workspace = true } ic-btc-test-utils = { workspace = true } ic-cdk = { workspace = true } ic-cdk-macros = { workspace = true } diff --git a/e2e-tests/scenario-1/src/main.rs b/e2e-tests/scenario-1/src/main.rs index 79b3b9f8..14537411 100644 --- a/e2e-tests/scenario-1/src/main.rs +++ b/e2e-tests/scenario-1/src/main.rs @@ -80,10 +80,13 @@ fn init() { // Block 1: A single transaction that gives ADDRESS_1 50 BTC split over 10k inputs. let mut tx_1 = TransactionBuilder::new(); for _ in 0..10_000 { - tx_1 = tx_1.with_output(&Address::from_str(ADDRESS_1).unwrap(), 500_000); + tx_1 = tx_1.with_output( + &Address::from_str(ADDRESS_1).unwrap().assume_checked(), + 500_000, + ); } let tx_1 = tx_1.build(); - let tx_1_id = tx_1.txid(); + let tx_1_id = tx_1.compute_txid(); let block_1 = BlockBuilder::with_prev_header(genesis_block(network).header) .with_transaction(tx_1) @@ -102,7 +105,10 @@ fn init() { }, None, ) - .with_output(&Address::from_str(ADDRESS_2).unwrap(), 500_000) + .with_output( + &Address::from_str(ADDRESS_2).unwrap().assume_checked(), + 500_000, + ) .build(), ) } @@ -119,7 +125,10 @@ fn init() { let block_3 = BlockBuilder::with_prev_header(block_2.header) .with_transaction( TransactionBuilder::new() - .with_output(&Address::from_str(ADDRESS_3).unwrap(), 500_000) + .with_output( + &Address::from_str(ADDRESS_3).unwrap().assume_checked(), + 500_000, + ) .build(), ) .build(); @@ -128,7 +137,10 @@ fn init() { let block_4 = BlockBuilder::with_prev_header(block_3.header) .with_transaction( TransactionBuilder::new() - .with_output(&Address::from_str(ADDRESS_4).unwrap(), 500_000) + .with_output( + &Address::from_str(ADDRESS_4).unwrap().assume_checked(), + 500_000, + ) .build(), ) .build(); @@ -141,12 +153,15 @@ fn init() { TransactionBuilder::new() .with_input( OutPoint { - txid: block_2_tx.txid(), + txid: block_2_tx.compute_txid(), vout: 0, }, None, ) - .with_output(&Address::from_str(ADDRESS_5).unwrap(), 500_000) + .with_output( + &Address::from_str(ADDRESS_5).unwrap().assume_checked(), + 500_000, + ) .build(), ) } @@ -227,3 +242,8 @@ fn append_block(block: &Block) { } fn main() {} + +getrandom::register_custom_getrandom!(always_fail); +pub fn always_fail(_buf: &mut [u8]) -> Result<(), getrandom::Error> { + Err(getrandom::Error::UNSUPPORTED) +} diff --git a/e2e-tests/scenario-2/Cargo.toml b/e2e-tests/scenario-2/Cargo.toml index 6c3a5521..b26a0bc3 100644 --- a/e2e-tests/scenario-2/Cargo.toml +++ b/e2e-tests/scenario-2/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] bitcoin = { workspace = true } candid = { workspace = true } +getrandom = { workspace = true } ic-btc-test-utils = { workspace = true } ic-cdk = { workspace = true } ic-cdk-macros = { workspace = true } diff --git a/e2e-tests/scenario-2/src/main.rs b/e2e-tests/scenario-2/src/main.rs index 4636d684..8f486c3c 100644 --- a/e2e-tests/scenario-2/src/main.rs +++ b/e2e-tests/scenario-2/src/main.rs @@ -1,5 +1,5 @@ use bitcoin::{ - blockdata::constants::genesis_block, consensus::Encodable, Address, Block, + absolute::LockTime, blockdata::constants::genesis_block, consensus::Encodable, Address, Block, Network as BitcoinNetwork, }; use candid::CandidType; @@ -88,8 +88,8 @@ fn init() { // A transaction giving 1 satoshi to the address. block = block.with_transaction( TransactionBuilder::new() - .with_lock_time(i) - .with_output(&Address::from_str(ADDRESS).unwrap(), 1) + .with_lock_time(LockTime::from_consensus(i)) + .with_output(&Address::from_str(ADDRESS).unwrap().assume_checked(), 1) .build(), ); } @@ -136,3 +136,8 @@ fn append_block(block: &Block) { } fn main() {} + +getrandom::register_custom_getrandom!(always_fail); +pub fn always_fail(_buf: &mut [u8]) -> Result<(), getrandom::Error> { + Err(getrandom::Error::UNSUPPORTED) +} diff --git a/ic-http/example_canister/Cargo.toml b/ic-http/example_canister/Cargo.toml deleted file mode 100644 index bead498e..00000000 --- a/ic-http/example_canister/Cargo.toml +++ /dev/null @@ -1,4 +0,0 @@ -[workspace] -members = [ - "src/canister_backend", -] diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index de6b521d..d565ef26 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -10,5 +10,5 @@ include = ["src", "Cargo.toml", "../LICENSE"] repository = "https://github.com/dfinity/bitcoin-canister" [dependencies] -bitcoin = {version = "0.28.1", features = ["rand"]} # needed for generating secp256k1 keys. +bitcoin = { workspace = true, features = ["rand-std"] } # needed for generating secp256k1 keys. ic-btc-types = { workspace = true } diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index 8ee0d086..6ca56758 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -1,8 +1,9 @@ use bitcoin::blockdata::constants::genesis_block; use bitcoin::{ - secp256k1::rand::rngs::OsRng, secp256k1::Secp256k1, util::uint::Uint256, Address, - Block as BitcoinBlock, BlockHash, BlockHeader, KeyPair, Network, OutPoint, PublicKey, Script, - Transaction, TxIn, TxMerkleNode, TxOut, Witness, XOnlyPublicKey, + absolute::LockTime, block::Header as BlockHeader, block::Version, hashes::Hash, key::Keypair, + secp256k1::rand::rngs::OsRng, secp256k1::Secp256k1, transaction::Version as TransactionVersion, + Address, Amount, Block as BitcoinBlock, Network, OutPoint, PublicKey, ScriptBuf, Sequence, + Target, Transaction, TxIn, TxMerkleNode, TxOut, Witness, XOnlyPublicKey, }; use ic_btc_types::Block; use std::str::FromStr; @@ -10,16 +11,16 @@ use std::str::FromStr; /// Generates a random P2PKH address. pub fn random_p2pkh_address(network: Network) -> Address { let secp = Secp256k1::new(); - let mut rng = OsRng::new().unwrap(); + let mut rng = OsRng; - Address::p2pkh(&PublicKey::new(secp.generate_keypair(&mut rng).1), network) + Address::p2pkh(PublicKey::new(secp.generate_keypair(&mut rng).1), network) } pub fn random_p2tr_address(network: Network) -> Address { let secp = Secp256k1::new(); - let mut rng = OsRng::new().unwrap(); - let key_pair = KeyPair::new(&secp, &mut rng); - let xonly = XOnlyPublicKey::from_keypair(&key_pair); + let mut rng = OsRng; + let key_pair = Keypair::new(&secp, &mut rng); + let (xonly, _parity) = XOnlyPublicKey::from_keypair(&key_pair); Address::p2tr(&secp, xonly, None, network) } @@ -27,8 +28,8 @@ pub fn random_p2tr_address(network: Network) -> Address { fn coinbase_input() -> TxIn { TxIn { previous_output: OutPoint::null(), - script_sig: Script::new(), - sequence: 0xffffffff, + script_sig: ScriptBuf::new(), + sequence: Sequence(0xffffffff), witness: Witness::new(), } } @@ -66,10 +67,11 @@ impl BlockBuilder { self.transactions }; - let merkle_root = - bitcoin::util::hash::bitcoin_merkle_root(txdata.iter().map(|tx| tx.txid().as_hash())) - .unwrap(); - let merkle_root = TxMerkleNode::from_hash(merkle_root); + let merkle_root = bitcoin::merkle_tree::calculate_root( + txdata.iter().map(|tx| tx.compute_txid().to_raw_hash()), + ) + .unwrap(); + let merkle_root = TxMerkleNode::from_raw_hash(merkle_root); let header = match self.prev_header { Some(prev_header) => header(&prev_header, merkle_root), @@ -99,7 +101,7 @@ pub fn build_regtest_chain(num_blocks: u32, num_transactions_per_block: u32) -> for _ in 0..num_transactions_per_block { transactions.push( TransactionBuilder::coinbase() - .with_output(&address, value) + .with_output(address.assume_checked_ref(), value) .build(), ); // Vary the value of the transaction to ensure that @@ -120,21 +122,16 @@ pub fn build_regtest_chain(num_blocks: u32, num_transactions_per_block: u32) -> } fn genesis(merkle_root: TxMerkleNode) -> BlockHeader { - let target = Uint256([ - 0xffffffffffffffffu64, - 0xffffffffffffffffu64, - 0xffffffffffffffffu64, - 0x7fffffffffffffffu64, - ]); - let bits = BlockHeader::compact_target_from_u256(&target); + let target = Target::MAX_ATTAINABLE_REGTEST; + let bits = target.to_compact_lossy(); let mut header = BlockHeader { - version: 1, + version: Version::ONE, time: 0, nonce: 0, bits, merkle_root, - prev_blockhash: BlockHash::default(), + prev_blockhash: Hash::all_zeros(), }; solve(&mut header); @@ -144,7 +141,7 @@ fn genesis(merkle_root: TxMerkleNode) -> BlockHeader { pub struct TransactionBuilder { input: Vec, output: Vec, - lock_time: u32, + lock_time: LockTime, } impl TransactionBuilder { @@ -152,7 +149,7 @@ impl TransactionBuilder { Self { input: vec![], output: vec![], - lock_time: 0, + lock_time: LockTime::ZERO, } } @@ -160,7 +157,7 @@ impl TransactionBuilder { Self { input: vec![coinbase_input()], output: vec![], - lock_time: 0, + lock_time: LockTime::ZERO, } } @@ -172,8 +169,8 @@ impl TransactionBuilder { let witness = witness.map_or(Witness::new(), |w| w); let input = TxIn { previous_output, - script_sig: Script::new(), - sequence: 0xffffffff, + script_sig: ScriptBuf::new(), + sequence: Sequence(0xffffffff), witness, }; self.input.push(input); @@ -182,13 +179,13 @@ impl TransactionBuilder { pub fn with_output(mut self, address: &Address, value: u64) -> Self { self.output.push(TxOut { - value, + value: Amount::from_sat(value), script_pubkey: address.script_pubkey(), }); self } - pub fn with_lock_time(mut self, time: u32) -> Self { + pub fn with_lock_time(mut self, time: LockTime) -> Self { self.lock_time = time; self } @@ -203,7 +200,7 @@ impl TransactionBuilder { let output = if self.output.is_empty() { // Use default of 50 BTC. vec![TxOut { - value: 50_0000_0000, + value: Amount::from_sat(50_0000_0000), script_pubkey: random_p2pkh_address(Network::Regtest).script_pubkey(), }] } else { @@ -211,7 +208,7 @@ impl TransactionBuilder { }; Transaction { - version: 1, + version: TransactionVersion::ONE, lock_time: self.lock_time, input, output, @@ -227,10 +224,10 @@ impl Default for TransactionBuilder { fn header(prev_header: &BlockHeader, merkle_root: TxMerkleNode) -> BlockHeader { let time = prev_header.time + 60 * 10; // 10 minutes. - let bits = BlockHeader::compact_target_from_u256(&prev_header.target()); + let bits = prev_header.target().to_compact_lossy(); let mut header = BlockHeader { - version: 1, + version: Version::ONE, time, nonce: 0, bits, @@ -244,7 +241,7 @@ fn header(prev_header: &BlockHeader, merkle_root: TxMerkleNode) -> BlockHeader { fn solve(header: &mut BlockHeader) { let target = header.target(); - while header.validate_pow(&target).is_err() { + while header.validate_pow(target).is_err() { header.nonce += 1; } } @@ -253,26 +250,26 @@ fn solve(header: &mut BlockHeader) { mod test { mod transaction_builder { use crate::{random_p2pkh_address, TransactionBuilder}; - use bitcoin::{Network, OutPoint}; + use bitcoin::{Amount, Network, OutPoint}; #[test] fn new_build() { let tx = TransactionBuilder::new().build(); - assert!(tx.is_coin_base()); + assert!(tx.is_coinbase()); assert_eq!(tx.input.len(), 1); assert_eq!(tx.input[0].previous_output, OutPoint::null()); assert_eq!(tx.output.len(), 1); - assert_eq!(tx.output[0].value, 50_0000_0000); + assert_eq!(tx.output[0].value, Amount::from_sat(50_0000_0000)); } #[test] fn coinbase() { let tx = TransactionBuilder::coinbase().build(); - assert!(tx.is_coin_base()); + assert!(tx.is_coinbase()); assert_eq!(tx.input.len(), 1); assert_eq!(tx.input[0].previous_output, OutPoint::null()); assert_eq!(tx.output.len(), 1); - assert_eq!(tx.output[0].value, 50_0000_0000); + assert_eq!(tx.output[0].value, Amount::from_sat(50_0000_0000)); } #[test] @@ -286,7 +283,7 @@ mod test { .build(); TransactionBuilder::coinbase() - .with_input(bitcoin::OutPoint::new(coinbase_tx.txid(), 0), None); + .with_input(bitcoin::OutPoint::new(coinbase_tx.compute_txid(), 0), None); } #[test] @@ -296,11 +293,11 @@ mod test { .with_output(&address, 1000) .build(); - assert!(tx.is_coin_base()); + assert!(tx.is_coinbase()); assert_eq!(tx.input.len(), 1); assert_eq!(tx.input[0].previous_output, OutPoint::null()); assert_eq!(tx.output.len(), 1); - assert_eq!(tx.output[0].value, 1000); + assert_eq!(tx.output[0].value, Amount::from_sat(1000)); assert_eq!(tx.output[0].script_pubkey, address.script_pubkey()); } @@ -313,13 +310,13 @@ mod test { .with_output(&address_1, 2000) .build(); - assert!(tx.is_coin_base()); + assert!(tx.is_coinbase()); assert_eq!(tx.input.len(), 1); assert_eq!(tx.input[0].previous_output, OutPoint::null()); assert_eq!(tx.output.len(), 2); - assert_eq!(tx.output[0].value, 1000); + assert_eq!(tx.output[0].value, Amount::from_sat(1000)); assert_eq!(tx.output[0].script_pubkey, address_0.script_pubkey()); - assert_eq!(tx.output[1].value, 2000); + assert_eq!(tx.output[1].value, Amount::from_sat(2000)); assert_eq!(tx.output[1].script_pubkey, address_1.script_pubkey()); } @@ -331,16 +328,16 @@ mod test { .build(); let tx = TransactionBuilder::new() - .with_input(bitcoin::OutPoint::new(coinbase_tx.txid(), 0), None) + .with_input(bitcoin::OutPoint::new(coinbase_tx.compute_txid(), 0), None) .build(); - assert!(!tx.is_coin_base()); + assert!(!tx.is_coinbase()); assert_eq!(tx.input.len(), 1); assert_eq!( tx.input[0].previous_output, - bitcoin::OutPoint::new(coinbase_tx.txid(), 0) + bitcoin::OutPoint::new(coinbase_tx.compute_txid(), 0) ); assert_eq!(tx.output.len(), 1); - assert_eq!(tx.output[0].value, 50_0000_0000); + assert_eq!(tx.output[0].value, Amount::from_sat(50_0000_0000)); } #[test] @@ -354,21 +351,27 @@ mod test { .build(); let tx = TransactionBuilder::new() - .with_input(bitcoin::OutPoint::new(coinbase_tx_0.txid(), 0), None) - .with_input(bitcoin::OutPoint::new(coinbase_tx_1.txid(), 0), None) + .with_input( + bitcoin::OutPoint::new(coinbase_tx_0.compute_txid(), 0), + None, + ) + .with_input( + bitcoin::OutPoint::new(coinbase_tx_1.compute_txid(), 0), + None, + ) .build(); - assert!(!tx.is_coin_base()); + assert!(!tx.is_coinbase()); assert_eq!(tx.input.len(), 2); assert_eq!( tx.input[0].previous_output, - bitcoin::OutPoint::new(coinbase_tx_0.txid(), 0) + bitcoin::OutPoint::new(coinbase_tx_0.compute_txid(), 0) ); assert_eq!( tx.input[1].previous_output, - bitcoin::OutPoint::new(coinbase_tx_1.txid(), 0) + bitcoin::OutPoint::new(coinbase_tx_1.compute_txid(), 0) ); assert_eq!(tx.output.len(), 1); - assert_eq!(tx.output[0].value, 50_0000_0000); + assert_eq!(tx.output[0].value, Amount::from_sat(50_0000_0000)); } } } diff --git a/types/Cargo.toml b/types/Cargo.toml index abdf0d66..32d8c1ff 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -6,12 +6,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bitcoin = { workspace = true, features = ["use-serde"] } +bitcoin = { workspace = true, features = ["serde"] } candid = { workspace = true } hex = { workspace = true } ic-btc-interface = { workspace = true } ic-btc-validation = { workspace = true } ic-stable-structures = { workspace = true } +primitive-types = { workspace = true } serde = { workspace = true } serde_bytes = { workspace = true } diff --git a/types/src/lib.rs b/types/src/lib.rs index e03d566e..21d9f0d1 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -2,8 +2,7 @@ //! NOTE: These types are _not_ part of the interface. use bitcoin::{ - util::uint::Uint256, Block as BitcoinBlock, Network as BitcoinNetwork, - OutPoint as BitcoinOutPoint, + Block as BitcoinBlock, Network as BitcoinNetwork, OutPoint as BitcoinOutPoint, Target, }; use candid::CandidType; use ic_btc_interface::{Network, Txid as PublicTxid}; @@ -37,7 +36,7 @@ impl Block { } } - pub fn header(&self) -> &bitcoin::BlockHeader { + pub fn header(&self) -> &bitcoin::block::Header { &self.block.header } @@ -63,14 +62,21 @@ impl Block { pub fn consensus_encode(&self, buffer: &mut Vec) -> Result { use bitcoin::consensus::Encodable; - self.block.consensus_encode(buffer) + self.block + .consensus_encode(buffer) + .map_err(|err| err.into()) } // Computes the difficulty given a block's target. // The definition here corresponds to what is referred as "bdiff" in // https://en.bitcoin.it/wiki/Difficulty - pub fn target_difficulty(network: Network, target: Uint256) -> u64 { - (ic_btc_validation::max_target(&into_bitcoin_network(network)) / target).low_u64() + pub fn target_difficulty(network: Network, target: Target) -> u64 { + use primitive_types::U256; + + let max_target = ic_btc_validation::max_target(&into_bitcoin_network(network)); + let max_target = U256::from_big_endian(&max_target.to_be_bytes()); + let target = U256::from_big_endian(&target.to_be_bytes()); + (max_target / target).low_u64() } pub fn internal_bitcoin_block(&self) -> &BitcoinBlock { @@ -99,7 +105,7 @@ impl Transaction { } pub fn is_coin_base(&self) -> bool { - self.tx.is_coin_base() + self.tx.is_coinbase() } pub fn input(&self) -> &[bitcoin::TxIn] { @@ -114,15 +120,24 @@ impl Transaction { self.tx.vsize() } + pub fn base_size(&self) -> usize { + self.tx.base_size() + } + + pub fn total_size(&self) -> usize { + self.tx.total_size() + } + + #[deprecated(note = "Use total_size() instead")] pub fn size(&self) -> usize { - self.tx.size() + self.total_size() } pub fn txid(&self) -> Txid { if self.txid.borrow().is_none() { // Compute the txid as it wasn't computed already. // `tx.txid()` is an expensive call, so it's useful to cache. - let txid = Txid::from(self.tx.txid().to_vec()); + let txid = Txid::from(self.tx.compute_txid().as_ref()); self.txid.borrow_mut().replace(txid); } @@ -155,13 +170,21 @@ impl From> for Txid { } } +impl From<&[u8]> for Txid { + fn from(bytes: &[u8]) -> Self { + Self { + bytes: bytes.to_vec(), + } + } +} + impl FromStr for Txid { type Err = String; fn from_str(txid: &str) -> Result { use bitcoin::Txid as BitcoinTxid; - let bytes = BitcoinTxid::from_str(txid).unwrap().to_vec(); - Ok(Self::from(bytes)) + let bytes = BitcoinTxid::from_str(txid).unwrap(); + Ok(Self::from(bytes.as_ref())) } } @@ -244,9 +267,15 @@ impl From> for BlockHash { } } +impl From<&[u8]> for BlockHash { + fn from(bytes: &[u8]) -> Self { + Self::from(bytes.to_vec()) + } +} + impl From for BlockHash { fn from(block_hash: bitcoin::BlockHash) -> Self { - Self(block_hash.to_vec()) + Self::from(block_hash.as_ref()) } } @@ -254,10 +283,10 @@ impl FromStr for BlockHash { type Err = String; fn from_str(s: &str) -> Result { - Ok(Self( + Ok(Self::from( bitcoin::BlockHash::from_str(s) .map_err(|e| e.to_string())? - .to_vec(), + .as_ref(), )) } } @@ -315,7 +344,7 @@ impl OutPoint { impl From<&BitcoinOutPoint> for OutPoint { fn from(bitcoin_outpoint: &BitcoinOutPoint) -> Self { Self { - txid: Txid::from(bitcoin_outpoint.txid.to_vec()), + txid: Txid::from(bitcoin_outpoint.txid.as_ref()), vout: bitcoin_outpoint.vout, } } @@ -326,7 +355,7 @@ impl From for bitcoin::OutPoint { use bitcoin::hashes::Hash; Self { - txid: bitcoin::Txid::from_hash( + txid: bitcoin::Txid::from_raw_hash( Hash::from_slice(outpoint.txid.as_bytes()).expect("txid must be valid"), ), vout: outpoint.vout, @@ -365,7 +394,7 @@ fn target_difficulty() { assert_eq!( Block::target_difficulty( Network::Mainnet, - bitcoin::BlockHeader::u256_from_compact_target(0x1b0404cb) + Target::from_compact(bitcoin::CompactTarget::from_consensus(0x1b0404cb)) ), 16_307 ); @@ -375,7 +404,7 @@ fn target_difficulty() { assert_eq!( Block::target_difficulty( Network::Mainnet, - bitcoin::BlockHeader::u256_from_compact_target(386397584) + Target::from_compact(bitcoin::CompactTarget::from_consensus(386397584)) ), 35_364_065_900_457 ); @@ -385,7 +414,7 @@ fn target_difficulty() { assert_eq!( Block::target_difficulty( Network::Mainnet, - bitcoin::BlockHeader::u256_from_compact_target(386877668) + Target::from_compact(bitcoin::CompactTarget::from_consensus(386877668)) ), 18_415_156_832_118 ); @@ -395,7 +424,7 @@ fn target_difficulty() { assert_eq!( Block::target_difficulty( Network::Testnet, - bitcoin::BlockHeader::u256_from_compact_target(422681968) + Target::from_compact(bitcoin::CompactTarget::from_consensus(422681968)) ), 86_564_599 ); @@ -405,7 +434,7 @@ fn target_difficulty() { assert_eq!( Block::target_difficulty( Network::Testnet, - bitcoin::BlockHeader::u256_from_compact_target(457142912) + Target::from_compact(bitcoin::CompactTarget::from_consensus(457142912)) ), 1_032 ); diff --git a/validation/Cargo.toml b/validation/Cargo.toml index 1111d534..fb0cfd4d 100644 --- a/validation/Cargo.toml +++ b/validation/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/dfinity/bitcoin-canister" [dependencies] bitcoin = { workspace = true } +primitive-types = { workspace = true } [dev-dependencies] csv = "1.1" diff --git a/validation/src/constants.rs b/validation/src/constants.rs index ba4e4f81..ba1bd9cf 100644 --- a/validation/src/constants.rs +++ b/validation/src/constants.rs @@ -1,4 +1,4 @@ -use bitcoin::{util::uint::Uint256, Network}; +use bitcoin::{CompactTarget, Network, Target}; use crate::BlockHeight; @@ -8,45 +8,14 @@ pub const DIFFICULTY_ADJUSTMENT_INTERVAL: BlockHeight = 6 * 24 * 14; /// Needed to help test check for the 20 minute testnet/regtest rule pub const TEN_MINUTES: u32 = 60 * 10; -/// Bitcoin mainnet maximum target value -const BITCOIN_MAX_TARGET: Uint256 = Uint256([ - 0x0000000000000000, - 0x0000000000000000, - 0x0000000000000000, - 0x00000000ffff0000, -]); - -/// Bitcoin testnet maximum target value -const TESTNET_MAX_TARGET: Uint256 = Uint256([ - 0x0000000000000000, - 0x0000000000000000, - 0x0000000000000000, - 0x00000000ffff0000, -]); - -/// Bitcoin regtest maximum target value -const REGTEST_MAX_TARGET: Uint256 = Uint256([ - 0x0000000000000000, - 0x0000000000000000, - 0x0000000000000000, - 0x7fffff0000000000, -]); - -/// Bitcoin signet maximum target value -const SIGNET_MAX_TARGET: Uint256 = Uint256([ - 0x0000000000000000u64, - 0x0000000000000000u64, - 0x0000000000000000u64, - 0x00000377ae000000u64, -]); - /// Returns the maximum difficulty target depending on the network -pub fn max_target(network: &Network) -> Uint256 { +pub fn max_target(network: &Network) -> Target { match network { - Network::Bitcoin => BITCOIN_MAX_TARGET, - Network::Testnet => TESTNET_MAX_TARGET, - Network::Regtest => REGTEST_MAX_TARGET, - Network::Signet => SIGNET_MAX_TARGET, + Network::Bitcoin => Target::MAX_ATTAINABLE_MAINNET, + Network::Testnet => Target::MAX_ATTAINABLE_TESTNET, + Network::Regtest => Target::MAX_ATTAINABLE_REGTEST, + Network::Signet => Target::MAX_ATTAINABLE_SIGNET, + _ => unreachable!(), } } @@ -56,17 +25,19 @@ pub fn no_pow_retargeting(network: &Network) -> bool { match network { Network::Bitcoin | Network::Testnet | Network::Signet => false, Network::Regtest => true, + _ => unreachable!(), } } /// Returns the PoW limit bits of the bitcoin network -pub fn pow_limit_bits(network: &Network) -> u32 { - match network { +pub fn pow_limit_bits(network: &Network) -> CompactTarget { + CompactTarget::from_consensus(match network { Network::Bitcoin => 0x1d00ffff, Network::Testnet => 0x1d00ffff, Network::Regtest => 0x207fffff, Network::Signet => 0x1e0377ae, - } + _ => unreachable!(), + }) } #[cfg(test)] diff --git a/validation/src/header.rs b/validation/src/header.rs index 2206398b..7b0902d5 100644 --- a/validation/src/header.rs +++ b/validation/src/header.rs @@ -1,4 +1,6 @@ -use bitcoin::{util::uint::Uint256, BlockHash, BlockHeader, Network}; +use bitcoin::{ + block::Header as BlockHeader, block::ValidationError, BlockHash, CompactTarget, Network, Target, +}; use crate::{ constants::{ @@ -76,15 +78,15 @@ pub fn validate_header( return Err(ValidateHeaderError::TargetDifficultyAboveMax); } - if header.validate_pow(&header_target).is_err() { + if header.validate_pow(header_target).is_err() { return Err(ValidateHeaderError::InvalidPoWForHeaderTarget); } let target = get_next_target(network, store, &prev_header, prev_height, header.time); - if let Err(err) = header.validate_pow(&target) { + if let Err(err) = header.validate_pow(Target::from_compact(target)) { match err { - bitcoin::Error::BlockBadProofOfWork => println!("bad proof of work"), - bitcoin::Error::BlockBadTarget => println!("bad target"), + ValidationError::BadProofOfWork => println!("bad proof of work"), + ValidationError::BadTarget => println!("bad target"), _ => {} }; return Err(ValidateHeaderError::InvalidPoWForComputedTarget); @@ -149,7 +151,7 @@ fn get_next_target( prev_header: &BlockHeader, prev_height: BlockHeight, timestamp: u32, -) -> Uint256 { +) -> CompactTarget { match network { Network::Testnet | Network::Regtest => { if (prev_height + 1) % DIFFICULTY_ADJUSTMENT_INTERVAL != 0 { @@ -161,29 +163,20 @@ fn get_next_target( if timestamp > prev_header.time + TEN_MINUTES * 2 { //If no block has been found in 20 minutes, then use the maximum difficulty // target - max_target(network) + max_target(network).to_compact_lossy() } else { //If the block has been found within 20 minutes, then use the previous // difficulty target that is not equal to the maximum difficulty target - BlockHeader::u256_from_compact_target(find_next_difficulty_in_chain( - network, - store, - prev_header, - prev_height, - )) + find_next_difficulty_in_chain(network, store, prev_header, prev_height) } } else { - BlockHeader::u256_from_compact_target(compute_next_difficulty( - network, - store, - prev_header, - prev_height, - )) + compute_next_difficulty(network, store, prev_header, prev_height) } } - Network::Bitcoin | Network::Signet => BlockHeader::u256_from_compact_target( - compute_next_difficulty(network, store, prev_header, prev_height), - ), + Network::Bitcoin | Network::Signet => { + compute_next_difficulty(network, store, prev_header, prev_height) + } + _ => unreachable!(), } } @@ -199,7 +192,7 @@ fn find_next_difficulty_in_chain( store: &impl HeaderStore, prev_header: &BlockHeader, prev_height: BlockHeight, -) -> u32 { +) -> CompactTarget { // This is the maximum difficulty target for the network let pow_limit_bits = pow_limit_bits(network); match network { @@ -236,6 +229,7 @@ fn find_next_difficulty_in_chain( pow_limit_bits } Network::Bitcoin | Network::Signet => pow_limit_bits, + _ => unreachable!(), } } @@ -246,7 +240,8 @@ fn compute_next_difficulty( store: &impl HeaderStore, prev_header: &BlockHeader, prev_height: BlockHeight, -) -> u32 { +) -> CompactTarget { + use primitive_types::U256; // Difficulty is adjusted only once in every interval of 2 weeks (2016 blocks) // If an interval boundary is not reached, then previous difficulty target is // returned Regtest network doesn't adjust PoW difficult levels. For @@ -289,16 +284,14 @@ fn compute_next_difficulty( // Computing new difficulty target. // new difficulty target = old difficult target * (adjusted_interval / // 2_weeks); - let mut target = prev_header.target(); - target = target.mul_u32(adjusted_interval); - target = target / Uint256::from_u64(target_adjustment_interval_time as u64).unwrap(); + let mut target = U256::from_big_endian(&prev_header.target().to_be_bytes()); + target *= U256::from(adjusted_interval); + target /= U256::from(target_adjustment_interval_time); + let target = Target::from_be_bytes(target.into()); // Adjusting the newly computed difficulty target so that it doesn't exceed the // max_difficulty_target limit - target = Uint256::min(target, max_target(network)); - - // Converting the target (Uint256) into a 32 bit representation used by Bitcoin - BlockHeader::compact_target_from_u256(&target) + target.min(max_target(network)).to_compact_lossy() } #[cfg(test)] @@ -306,7 +299,9 @@ mod test { use std::{collections::HashMap, path::PathBuf, str::FromStr}; - use bitcoin::{consensus::deserialize, hashes::hex::FromHex, TxMerkleNode}; + use bitcoin::{ + block::Version, consensus::deserialize, hashes::hex::FromHex, hashes::Hash, TxMerkleNode, + }; use csv::Reader; use proptest::prelude::*; @@ -409,11 +404,15 @@ mod test { for result in rdr.records() { let record = result.unwrap(); let header = BlockHeader { - version: i32::from_str_radix(record.get(0).unwrap(), 16).unwrap(), + version: Version::from_consensus( + i32::from_str_radix(record.get(0).unwrap(), 16).unwrap(), + ), prev_blockhash: BlockHash::from_str(record.get(1).unwrap()).unwrap(), merkle_root: TxMerkleNode::from_str(record.get(2).unwrap()).unwrap(), time: u32::from_str_radix(record.get(3).unwrap(), 16).unwrap(), - bits: u32::from_str_radix(record.get(4).unwrap(), 16).unwrap(), + bits: CompactTarget::from_consensus( + u32::from_str_radix(record.get(4).unwrap(), 16).unwrap(), + ), nonce: u32::from_str_radix(record.get(5).unwrap(), 16).unwrap(), }; headers.push(header); @@ -513,17 +512,17 @@ mod test { store.add(header_705602); let mut header = BlockHeader { - version: 0x20800004, - prev_blockhash: BlockHash::from_hex( + version: Version::from_consensus(0x20800004), + prev_blockhash: BlockHash::from_str( "00000000000000000001eea12c0de75000c2546da22f7bf42d805c1d2769b6ef", ) .unwrap(), - merkle_root: TxMerkleNode::from_hex( + merkle_root: TxMerkleNode::from_str( "c120ff2ae1363593a0b92e0d281ec341a0cc989b4ee836dc3405c9f4215242a6", ) .unwrap(), time: 1634590600, - bits: 0x170e0408, + bits: CompactTarget::from_consensus(0x170e0408), nonce: 0xb48e8b0a, }; assert!(is_timestamp_valid(&store, &header, MOCK_CURRENT_TIME).is_ok()); @@ -632,7 +631,7 @@ mod test { for line in rdr.lines() { let header = line.unwrap(); let header = hex::decode(header.trim()).unwrap(); - let header = BlockHeader::consensus_decode(header.as_slice()).unwrap(); + let header = BlockHeader::consensus_decode(&mut header.as_slice()).unwrap(); headers.push(header); } @@ -651,7 +650,7 @@ mod test { // Assert that the expected next target matches the next header's target. assert_eq!( expected_next_target, - BlockHeader::u256_from_compact_target(headers[i + 1].bits) + headers[i + 1].bits ); }); } @@ -674,18 +673,18 @@ mod test { ); } - fn genesis_header(bits: u32) -> BlockHeader { + fn genesis_header(bits: CompactTarget) -> BlockHeader { BlockHeader { - version: 1, - prev_blockhash: Default::default(), - merkle_root: Default::default(), + version: Version::ONE, + prev_blockhash: Hash::all_zeros(), + merkle_root: Hash::all_zeros(), time: 1296688602, bits, nonce: 0, } } - fn next_block_header(prev: BlockHeader, bits: u32) -> BlockHeader { + fn next_block_header(prev: BlockHeader, bits: CompactTarget) -> BlockHeader { BlockHeader { prev_blockhash: prev.block_hash(), time: prev.time + TEN_MINUTES, @@ -698,7 +697,7 @@ mod test { /// proof of work for the first header. fn create_chain( network: &Network, - initial_pow: u32, + initial_pow: CompactTarget, chain_length: u32, ) -> (SimpleHeaderStore, BlockHeader) { let pow_limit = pow_limit_bits(network); @@ -724,7 +723,7 @@ mod test { // Arrange. let network = Network::Regtest; - let expected_pow = 7; // Some non-limit PoW, the actual value is not important. + let expected_pow = CompactTarget::from_consensus(7); // Some non-limit PoW, the actual value is not important. for chain_length in 1..10 { let (store, last_header) = create_chain(&network, expected_pow, chain_length); assert_eq!(store.height() + 1, chain_length); @@ -737,7 +736,7 @@ mod test { last_header.time + TEN_MINUTES, ); // Assert. - assert_eq!(target, BlockHeader::u256_from_compact_target(expected_pow)); + assert_eq!(target, expected_pow); } } }