diff --git a/.gitignore b/.gitignore index 38bee55c..7f7311b4 100644 --- a/.gitignore +++ b/.gitignore @@ -191,3 +191,6 @@ public/ # WASM builds *.wasm + +# Debug logs +*.log diff --git a/Cargo.lock b/Cargo.lock index 92c56ec0..aec050d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstyle" version = "1.0.14" @@ -19,9 +28,15 @@ checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + +[[package]] +name = "autocfg" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base16ct" @@ -90,9 +105,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "block-buffer" @@ -115,13 +130,44 @@ dependencies = [ "zeroize", ] +[[package]] +name = "built" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" +dependencies = [ + "cargo-lock", + "git2", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cargo-lock" +version = "10.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06acb4f71407ba205a07cb453211e0e6a67b21904e47f6ba1f9589e38f2e454" +dependencies = [ + "petgraph", + "semver", + "serde", + "toml", + "url", +] + [[package]] name = "cc" -version = "1.2.60" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -131,6 +177,44 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "windows-link", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" version = "4.6.1" @@ -138,6 +222,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -151,6 +236,18 @@ dependencies = [ "terminal_size", ] +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "clap_lex" version = "1.1.0" @@ -163,12 +260,30 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -203,6 +318,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -230,12 +351,22 @@ name = "dash-dev" version = "0.0.0" dependencies = [ "bitcoin-consensus-encoding 0.2.0", + "built", + "chrono", + "ciborium", + "clap", + "dash-num", + "dash-params", + "dash-pow", "dash-primitives", "dash-types", "hex-conservative", + "indicatif", "json5", + "rayon", "serde", "serde_json", + "sysinfo", ] [[package]] @@ -261,6 +392,7 @@ dependencies = [ "dash-dev", "dash-num", "dash-params", + "dash-pkc", "dash-primitives", "dash-script", "dash-types", @@ -385,6 +517,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "divan" version = "0.1.21" @@ -425,9 +568,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elliptic-curve" @@ -447,6 +590,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "equivalent" version = "1.0.2" @@ -460,7 +609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -479,6 +628,21 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures-core" version = "0.3.32" @@ -504,9 +668,9 @@ checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-timer" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" [[package]] name = "futures-util" @@ -543,6 +707,31 @@ dependencies = [ "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "git2" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "url", +] + [[package]] name = "glob" version = "0.3.3" @@ -560,11 +749,28 @@ dependencies = [ "subtle", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -596,6 +802,133 @@ dependencies = [ "digest", ] +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -606,12 +939,45 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indicatif" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "web-time", +] + [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + [[package]] name = "json5" version = "0.4.1" @@ -637,9 +1003,21 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.185" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libgit2-sys" +version = "0.18.5+1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "005d6ae6eac1912906073e069f7db60b1fa98e052a68227824afe3e3a1c59ca2" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] [[package]] name = "libm" @@ -647,17 +1025,59 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +[[package]] +name = "libz-sys" +version = "1.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] [[package]] name = "num_cpus" @@ -669,6 +1089,24 @@ dependencies = [ "libc", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pest" version = "2.8.6" @@ -712,19 +1150,50 @@ dependencies = [ "sha2", ] +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + [[package]] name = "proc-macro-crate" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit", + "toml_edit 0.25.12+spec-1.1.0", ] [[package]] @@ -738,20 +1207,26 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.17", ] [[package]] @@ -776,9 +1251,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -805,9 +1280,9 @@ checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "relative-path" @@ -874,12 +1349,18 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] -name = "sec1" -version = "0.7.3" +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "sec1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ @@ -895,6 +1376,10 @@ name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] [[package]] name = "serde" @@ -928,9 +1413,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -939,6 +1424,15 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "sha2" version = "0.10.9" @@ -952,9 +1446,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signature" @@ -972,6 +1466,18 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "subtle" version = "2.6.1" @@ -980,15 +1486,39 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sysinfo" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "windows", +] + [[package]] name = "terminal_size" version = "0.4.4" @@ -996,7 +1526,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1008,6 +1538,37 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -1019,14 +1580,28 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow", + "winnow 1.0.3", ] [[package]] @@ -1035,14 +1610,20 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow", + "winnow 1.0.3", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "ucd-trie" @@ -1056,6 +1637,30 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -1068,12 +1673,213 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link", + "windows-result 0.4.1", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -1083,29 +1889,211 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" -version = "1.0.2" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", diff --git a/contrib/build_docs.py b/contrib/build_docs.py index c0527a5e..392603f2 100755 --- a/contrib/build_docs.py +++ b/contrib/build_docs.py @@ -220,6 +220,6 @@ def main() -> int: if __name__ == "__main__": try: sys.exit(main()) - except Exception as exc: + except Exception as exc: # noqa: BLE001 print(exc, file=sys.stderr) sys.exit(RETCODE_ERR) diff --git a/contrib/codeql/decl.ql b/contrib/codeql/decl.ql index d5065378..641653bd 100644 --- a/contrib/codeql/decl.ql +++ b/contrib/codeql/decl.ql @@ -36,37 +36,6 @@ predicate outOfOrder( ) } -/** Holds if `v` is a bare `Unknown` variant without associated data. */ -predicate bareUnknownVariant(Enum e, Variant v) { - isSourceType(e) and - v = e.getVariantList().getAVariant() and - v.getName().getText() = "Unknown" and - not exists(v.getFieldList()) -} - -/** Gets the NumCodec type parameter for enum `e`. */ -string numCodecType(Enum e) { - exists(Impl i | - fileOf(i) = fileOf(e) and - implSelfName(i) = e.getName().getText() and - implTraitName(i) = "NumCodec" and - result = - i.getTrait() - .(PathTypeRepr) - .getPath() - .getSegment() - .getGenericArgList() - .getGenericArg(0) - .(TypeArg) - .getTypeRepr() - .(PathTypeRepr) - .getPath() - .getSegment() - .getIdentifier() - .getText() - ) -} - from Locatable item, string message where exists(TypeItem t, string name | @@ -84,18 +53,4 @@ where fmt("{0} (slot {1})", priorSlot.toString(), priorSlot.getOrder().toString())) ) ) - or - exists(Enum e | - bareUnknownVariant(e, item) and - ( - exists(string ty | - ty = numCodecType(e) and - message = - fmt("{0}::Unknown must carry the raw value (e.g. Unknown({1}))", e.getName().getText(), ty) - ) - or - not exists(numCodecType(e)) and - message = fmt("{0}::Unknown must carry the raw value", e.getName().getText()) - ) - ) select item, message diff --git a/contrib/codeql/import.ql b/contrib/codeql/import.ql index f3f4d095..1c1aac62 100644 --- a/contrib/codeql/import.ql +++ b/contrib/codeql/import.ql @@ -5,7 +5,7 @@ * * @id base-sdk/import-rules * @name Import grouping and ordering rules - * @description Enforces import ordering with blank-line constraints, prohibits non-prelude alloc imports. + * @description Enforces import ordering with blank-line constraints. * @kind problem * @precision high * @problem.severity warning @@ -43,50 +43,35 @@ predicate hasCfgGatedGap(File f, int startAfter, int endBefore) { ) } -/** Holds if `f` is inside a crate excluded from prelude rules. */ -private predicate isPreludeExcluded(File f) { - exists(string name | - name = preludeExcludeCrate() and - f.getAbsolutePath().matches("%/" + name + "/%") - ) -} - -/** Holds if `u` imports directly from `alloc` outside `prelude.rs`. */ -private predicate directAllocImport(Use u) { - usePrefix(u) = "alloc" and - not fileOf(u).getBaseName() = "prelude.rs" and - not fileOf(u).getAbsolutePath().matches("%/prelude/mod.rs") and - not isPreludeExcluded(fileOf(u)) -} - from Locatable item, string message where - ( - exists(Locatable prev, File f, int groupA, int groupB, int endA, int effStartB | - consecutivePreamble(prev, item, f, groupA, groupB, endA, effStartB) and - ( - // Group decreased: wrong order. - groupA > groupB and - message = fmt("{0} must appear before {1}", groupLabel(groupB), groupLabel(groupA)) - or - // Group increased but no blank line between them. - groupA < groupB and - effStartB - endA < 2 and - message = - fmt("missing blank line between {0} and {1}", groupLabel(groupA), groupLabel(groupB)) - or - // Same group but spurious blank line within it. - groupA = groupB and - effStartB - endA > 1 and - not hasCfgGatedGap(f, endA, effStartB) and - message = fmt("unexpected blank line within {0} group", groupLabel(groupA)) - ) - ) - or - exists(Use u | - item = u and - directAllocImport(u) and - message = "use crate::prelude instead of direct alloc import" + exists(Locatable prev, File f, int groupA, int groupB, int endA, int effStartB | + consecutivePreamble(prev, item, f, groupA, groupB, endA, effStartB) and + ( + // Group decreased: wrong order. + groupA > groupB and + message = fmt("{0} must appear before {1}", groupLabel(groupB), groupLabel(groupA)) + or + // Group increased but no blank line between them. + groupA < groupB and + effStartB - endA < 2 and + message = + fmt("missing blank line between {0} and {1}", groupLabel(groupA), groupLabel(groupB)) + or + // Same group but spurious blank line within it. + groupA = groupB and + effStartB - endA > 1 and + not hasCfgGatedGap(f, endA, effStartB) and + message = fmt("unexpected blank line within {0} group", groupLabel(groupA)) ) ) + or + // Foreign module re-export from {lib,mod}.rs. + exists(Use u | + item = u and + fileRelPath(fileOf(u), _) and + isForeignReexport(u) and + not isMacroReexport(u) and + message = "pub use " + usePrefix(u) + ":: re-exports from a foreign crate" + ) select item, message diff --git a/contrib/codeql/lib/imports.qll b/contrib/codeql/lib/imports.qll index bd805f45..09ba2125 100644 --- a/contrib/codeql/lib/imports.qll +++ b/contrib/codeql/lib/imports.qll @@ -53,6 +53,40 @@ predicate isPublicMod(Module m) { not exists(m.getVisibility().getPath()) } +/** + * Holds if `u` lives inside a `__private` module (intentional + * crate-level re-exports for macro support). + */ +predicate isMacroReexport(Use u) { + exists(Module priv | + priv.getName().getText() = "__private" and + u.getParentNode() = priv.getItemList() + ) +} + +/** + * Holds if `u` is a `pub use` that re-exports from a foreign crate. + * The first path segment is not `crate`/`self`/`super` and does not + * match any `mod` declaration in the same file. + * + * The file-level check handles modules wrapped in macros (e.g. + * `pub_if_internal!`, `cfg_if!`) whose AST parent differs from the use + * site. + */ +predicate isForeignReexport(Use u) { + isPublicUse(u) and + exists(string prefix | + prefix = usePrefix(u) and + not prefix = "crate" and + not prefix = "self" and + not prefix = "super" and + not exists(Module m | + m.getName().getText() = prefix and + fileOf(m) = fileOf(u) + ) + ) +} + /** * Gets the use-declaration base group (ignoring pub/priv). * 2 = crate/super, 3 = external, 4 = alloc/core/std. diff --git a/contrib/codeql/lib/policy.qll b/contrib/codeql/lib/policy.qll index 4b6ea381..060d9e99 100644 --- a/contrib/codeql/lib/policy.qll +++ b/contrib/codeql/lib/policy.qll @@ -100,10 +100,19 @@ string requiredTrait() { result = ["Clone", "Debug", "Eq", "Hash", "PartialEq"] /** Gets a required serde trait name. */ string requiredSerdeTrait() { result = ["Serialize", "Deserialize"] } -/** Holds if `t` is codec infrastructure (decoder or encoder wrappers). */ +/** Holds if `t` is codec infrastructure (decoder, encoder, or buffer types). */ predicate isCodecType(TypeItem t) { t.getName().getText().matches("%Decoder%") or - t.getName().getText().matches("%Encoder%") + t.getName().getText().matches("%Encoder%") or + t.getName().getText() = "ArrayBuf" +} + +/** Holds if `t` lives in a crate with no public API. */ +predicate isPrivateCrate(TypeItem t) { + exists(string path | + path = fileOf(t).getAbsolutePath() and + path.matches("%/pkgs/dev/%") + ) } /** Holds if `t` is a source type eligible for the "must derive" check. */ @@ -113,6 +122,7 @@ predicate isCheckableType(TypeItem t) { not isCodecType(t) and not isSecretType(t) and not isIteratorType(t) and + not isPrivateCrate(t) and not hasUnexpandedDerive(t) } @@ -205,12 +215,6 @@ predicate isSerdeExempt(TypeItem t) { not implementsTrait(t, "PartialEq") } -/** Crate directory names excluded from prelude enforcement. */ -string preludeExcludeCrate() { - result = "samples/parser" or - result = "samples/solver" -} - /** Holds if file `f` is in a crate evaluated by decl ordering. */ predicate isEvaluatedCrate(File f) { f.getAbsolutePath().matches("%/pkgs/types/%") or diff --git a/contrib/codeql/secret.ql b/contrib/codeql/secret.ql deleted file mode 100644 index fffe3f9b..00000000 --- a/contrib/codeql/secret.ql +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2026-present, The Dash Core developers - * SPDX-License-Identifier: MIT - * See the accompanying file LICENSE or https://opensource.org/license/MIT - * - * @id base-sdk/secret-rules - * @name Secret type restrictions - * @description Secret types must not derive Debug. - * @kind problem - * @precision high - * @problem.severity warning - * @tags security - */ - -import lib.filters -import lib.policy -import lib.traits -import rust - -from TypeItem t -where - isSourceType(t) and - isSecretType(t) and - hasDerivedImpl(t, "Debug") -select t, "secret type must not derive Debug; impl manually to redact contents" diff --git a/contrib/common.py b/contrib/common.py index 0980f56b..c1f61e62 100644 --- a/contrib/common.py +++ b/contrib/common.py @@ -21,11 +21,54 @@ if TYPE_CHECKING: from collections.abc import Callable +# ANSI escape codes for terminal output. +ANSI_BOLD = "\033[1m" +ANSI_DIM = "\033[2m" +ANSI_GREEN = "\033[32m" +ANSI_RED = "\033[31m" +ANSI_RESET = "\033[0m" + +# Assumed base branch for codebase. +DEFAULT_BASE = "develop" + +# Return codes. RETCODE_ERR = 1 RETCODE_PASS = 0 RETCODE_SKIP = 77 +def format_table( + headers: tuple[str, ...], + rows: list[tuple[str, ...]], + status_colors: dict[str, str] | None = None, +) -> str: + """Render a markdown table with optional color on the last column.""" + colors = status_colors or {} + widths = [ + max(len(h), *(len(r[i]) for r in rows), 0) for i, h in enumerate(headers) + ] + + def fmt(cells: tuple[str, ...], *, color: bool = False) -> str: + parts: list[str] = [] + for i, cell in enumerate(cells): + pre = post = "" + if color and i == len(cells) - 1 and colors: + pre = colors.get(cell, ANSI_DIM) + post = ANSI_RESET + pad = widths[i] - len(cell) + parts.append(f" {pre}{cell}{post}{' ' * pad} ") + return f"|{'|'.join(parts)}|" + + sep = "|" + "|".join("-" * (w + 2) for w in widths) + "|" + return "\n".join( + [ + fmt(headers), + sep, + *(fmt(r, color=True) for r in rows), + ] + ) + + def find_up( start: Path, predicate: Callable[[Path], bool], diff --git a/contrib/git_filter.py b/contrib/git_filter.py new file mode 100755 index 00000000..94a71866 --- /dev/null +++ b/contrib/git_filter.py @@ -0,0 +1,278 @@ +#!/usr/bin/env python3 +# coding: latin-1 + +# +# Copyright (c) 2026-present, The Dash Core developers +# SPDX-License-Identifier: MIT +# See the accompanying file LICENSE or https://opensource.org/license/MIT +# + +"""Run a command against every commit in a range. + +Creates a temporary worktree, checks out each commit, runs the +command, records the result, then removes the worktree. + +Usage: + + ./contrib/git_filter.py -- cargo fmt --check + ./contrib/git_filter.py feature -- cargo test -F full + ./contrib/git_filter.py base_branch feature -- cargo test + ./contrib/git_filter.py --fast-fail -- cargo clippy + ./contrib/git_filter.py --env RUSTFLAGS=-Dwarnings -- cargo check +""" + +from __future__ import annotations + +import argparse +import atexit +import os +import shlex +import signal +import subprocess +import sys +import tempfile +from dataclasses import dataclass + +from common import ( + ANSI_BOLD, + ANSI_GREEN, + ANSI_RED, + ANSI_RESET, + DEFAULT_BASE, + RETCODE_ERR, + RETCODE_PASS, + RETCODE_SKIP, + format_table, + require_bin, + root_dir, +) + +_STATUS_COLORS = {"PASS": ANSI_GREEN, "FAIL": ANSI_RED} +_GIT = "" # set in main() + + +@dataclass +class CommitResult: + hash: str + subject: str + status: str = "pending" + + +def _git(*args: str, cwd: str) -> subprocess.CompletedProcess[str]: + """Run a git command and return the result.""" + return subprocess.run( # noqa: S603 + [_GIT, *args], + capture_output=True, + check=False, + cwd=cwd, + encoding="utf-8", + errors="replace", + ) + + +def _git_ok(*args: str, cwd: str) -> str: + """Run a git command, raise on failure, return stdout.""" + r = _git(*args, cwd=cwd) + if r.returncode != 0: + msg = r.stderr.strip() or r.stdout.strip() + raise RuntimeError(f"git {args[0]}: {msg}") + return r.stdout.strip() + + +def _enumerate_commits(base: str, tip: str, cwd: str) -> list[CommitResult]: + """List commits in base..tip order (oldest first).""" + out = _git_ok( + "log", "--reverse", "--format=%H%x00%s", f"{base}..{tip}", cwd=cwd, + ) + return [ + CommitResult(*line.split("\0", 1)) + for line in out.splitlines() + if line.strip() + ] + + +def _results_table(commits: list[CommitResult]) -> str: + """Build a markdown summary table.""" + headers = ("Hash", "Description", "Status") + rows = [(c.hash[:8], c.subject, c.status) for c in commits] + return format_table(headers, rows, _STATUS_COLORS) + + +_CHILD_SHUTDOWN_TIMEOUT = 5 + + +def _terminate_child( + child: subprocess.Popen[bytes] | None, sig: int, +) -> None: + """Forward *sig* to the child's process group and reap it.""" + if child is None or child.poll() is not None: + return + try: + os.killpg(child.pid, sig) + except OSError: + return + try: + child.wait(timeout=_CHILD_SHUTDOWN_TIMEOUT) + except subprocess.TimeoutExpired: + os.killpg(child.pid, signal.SIGKILL) + child.wait() + + +def _remove_worktree(root: str, wt_path: str) -> None: + _git("worktree", "remove", "--force", wt_path, cwd=root) + + +def _parse_args() -> argparse.Namespace: + try: + sep = sys.argv.index("--") + except ValueError: + sep = None + + parser = argparse.ArgumentParser( + description="Run a command against every commit in a range.", + usage="%(prog)s [options] [base [tip]] -- ", + ) + parser.add_argument( + "--fast-fail", + action="store_true", + help="stop on first failure, mark rest as SKIP", + ) + parser.add_argument( + "--env", + action="append", + default=[], + metavar="K=V", + help="set environment variable (repeatable)", + ) + parser.add_argument( + "refs", nargs="*", metavar="ref", help="base and/or tip ref (0, 1, or 2)" + ) + + if sep is None: + if "-h" in sys.argv or "--help" in sys.argv: + parser.parse_args(["--help"]) + parser.error("missing -- separator before command") + args = parser.parse_args(sys.argv[1:sep]) + args.exec_cmd = sys.argv[sep + 1 :] + if not args.exec_cmd: + parser.error("no command after --") + if len(args.refs) > 2: + parser.error("too many refs (expected 0-2)") + return args + + +def main() -> int: + global _GIT + _GIT = require_bin("git") + args = _parse_args() + root = str(root_dir()) + + refs = args.refs + base = refs[0] if len(refs) >= 2 else DEFAULT_BASE + tip = refs[-1] if refs else "HEAD" + + base_hash = _git_ok("rev-parse", base, cwd=root) + tip_hash = _git_ok("rev-parse", tip, cwd=root) + if base_hash == tip_hash: + print(f"{base} and {tip} are identical ({base_hash[:8]})") + return RETCODE_SKIP + + commits = _enumerate_commits(base, tip, root) + if not commits: + print(f"no commits in {base}..{tip}") + return RETCODE_SKIP + + env = os.environ.copy() + for pair in args.env: + if "=" not in pair: + print(f"error: bad --env value: {pair}", file=sys.stderr) + return RETCODE_ERR + k, v = pair.split("=", 1) + if not k: + print(f"error: empty key in --env value: {pair}", file=sys.stderr) + return RETCODE_ERR + env[k] = v + + wt_dir = tempfile.mkdtemp(prefix="git-filter-") + wt_ready = False + cleaned = False + + def cleanup() -> None: + nonlocal cleaned + if cleaned: + return + cleaned = True + if wt_ready: + _remove_worktree(root, wt_dir) + elif os.path.isdir(wt_dir): + os.rmdir(wt_dir) + + atexit.register(cleanup) + _git_ok("worktree", "add", "--detach", "--quiet", wt_dir, cwd=root) + wt_ready = True + child: subprocess.Popen[bytes] | None = None + prev_handlers = {} + for sig in (signal.SIGINT, signal.SIGTERM): + prev_handlers[sig] = signal.getsignal(sig) + + def handler(_signum: int, _frame: object, *, s: int = sig) -> None: + try: + _terminate_child(child, s) + finally: + try: + cleanup() + finally: + signal.signal(s, prev_handlers[s]) + os.kill(os.getpid(), s) + sys.exit(128 + s) + + signal.signal(sig, handler) + + exec_str = shlex.join(args.exec_cmd) + n = len(commits) + print(f"{ANSI_BOLD}running {n} commit(s): {base}..{tip}{ANSI_RESET}") + print(f" command: {exec_str}") + if args.env: + env_keys = [p.split("=", 1)[0] + "=***" for p in args.env] + print(f" env: {' '.join(env_keys)}") + print() + + failed = False + try: + for cr in commits: + _git_ok("checkout", "--quiet", "--force", cr.hash, cwd=wt_dir) + _git_ok("clean", "-fdx", cwd=wt_dir) + print(f"--- {cr.hash[:8]} {cr.subject} ---") + child = subprocess.Popen( # noqa: S603 + args.exec_cmd, + cwd=wt_dir, + env=env, + start_new_session=True, + ) + rc = child.wait() + child = None + if rc == 0: + cr.status = "PASS" + else: + cr.status = "FAIL" + failed = True + if args.fast_fail: + for rest in commits: + if rest.status == "pending": + rest.status = "SKIP" + break + finally: + cleanup() + atexit.unregister(cleanup) + + print(f"\n{ANSI_BOLD}Program:{ANSI_RESET} {exec_str}") + print(f"\n{_results_table(commits)}\n") + return RETCODE_ERR if failed else RETCODE_PASS + + +if __name__ == "__main__": + try: + sys.exit(main()) + except Exception as exc: # noqa: BLE001 + print(exc, file=sys.stderr) + sys.exit(RETCODE_ERR) diff --git a/contrib/lint/lint_codeql.py b/contrib/lint/lint_codeql.py index d9f537d3..cb047e4b 100755 --- a/contrib/lint/lint_codeql.py +++ b/contrib/lint/lint_codeql.py @@ -291,6 +291,6 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": try: sys.exit(main()) - except Exception as exc: + except Exception as exc: # noqa: BLE001 print(exc, file=sys.stderr) sys.exit(RETCODE_ERR) diff --git a/contrib/lint/lint_javascript.py b/contrib/lint/lint_javascript.py index 2ecbd4ce..6caf4e06 100755 --- a/contrib/lint/lint_javascript.py +++ b/contrib/lint/lint_javascript.py @@ -51,6 +51,6 @@ def main() -> int: if __name__ == "__main__": try: sys.exit(main()) - except Exception as exc: + except Exception as exc: # noqa: BLE001 print(exc, file=sys.stderr) sys.exit(RETCODE_ERR) diff --git a/contrib/lint/lint_markdown.py b/contrib/lint/lint_markdown.py index a7da1259..6169f097 100755 --- a/contrib/lint/lint_markdown.py +++ b/contrib/lint/lint_markdown.py @@ -50,6 +50,6 @@ def main() -> int: if __name__ == "__main__": try: sys.exit(main()) - except Exception as exc: + except Exception as exc: # noqa: BLE001 print(exc, file=sys.stderr) sys.exit(RETCODE_ERR) diff --git a/contrib/lint/lint_python.py b/contrib/lint/lint_python.py index 357f6944..f39f26cd 100755 --- a/contrib/lint/lint_python.py +++ b/contrib/lint/lint_python.py @@ -29,6 +29,6 @@ def main() -> int: if __name__ == "__main__": try: sys.exit(main()) - except Exception as exc: + except Exception as exc: # noqa: BLE001 print(exc, file=sys.stderr) sys.exit(RETCODE_ERR) diff --git a/contrib/lint/lint_rust.py b/contrib/lint/lint_rust.py index 343f8ddb..14b7d730 100644 --- a/contrib/lint/lint_rust.py +++ b/contrib/lint/lint_rust.py @@ -35,9 +35,23 @@ def main() -> int: cmd += ["--manifest-path", str(manifest_path)] result = subprocess.run( # noqa: S603 cmd, + capture_output=True, check=False, cwd=str(repo_root), + text=True, ) + if result.stdout: + sys.stdout.write(result.stdout) + if result.stderr: + # Filter nightly-only rustfmt warnings that appear on stable. + filtered = "\n".join( + ln for ln in result.stderr.splitlines() + if not (ln.startswith("Warning:") and ( + "unstable features" in ln or "has been stabilized" in ln + )) + ) + if filtered.strip(): + sys.stderr.write(filtered + "\n") if result.returncode != 0: failed = True @@ -47,6 +61,6 @@ def main() -> int: if __name__ == "__main__": try: sys.exit(main()) - except Exception as exc: + except Exception as exc: # noqa: BLE001 print(exc, file=sys.stderr) sys.exit(RETCODE_ERR) diff --git a/contrib/lint/lint_semgrep.py b/contrib/lint/lint_semgrep.py index 44d46b11..5b11878b 100755 --- a/contrib/lint/lint_semgrep.py +++ b/contrib/lint/lint_semgrep.py @@ -57,6 +57,6 @@ def main() -> int: if __name__ == "__main__": try: sys.exit(main()) - except Exception as exc: + except Exception as exc: # noqa: BLE001 print(exc, file=sys.stderr) sys.exit(RETCODE_ERR) diff --git a/contrib/lint_all.py b/contrib/lint_all.py index d5111490..7f74a3db 100755 --- a/contrib/lint_all.py +++ b/contrib/lint_all.py @@ -16,7 +16,16 @@ from pathlib import Path from typing import cast -from common import RETCODE_ERR, RETCODE_PASS, RETCODE_SKIP +from common import ( + ANSI_BOLD, + ANSI_GREEN, + ANSI_RED, + ANSI_RESET, + RETCODE_ERR, + RETCODE_PASS, + RETCODE_SKIP, + format_table, +) @dataclass @@ -34,16 +43,9 @@ def status(self) -> str: return "pass" if self.retcode == RETCODE_PASS else "fail" -GREEN = "\033[32m" -RED = "\033[31m" -BOLD = "\033[1m" -DIM = "\033[2m" -RESET = "\033[0m" - - def _print_stream(prefix: str, line: str, *, is_stderr: bool) -> None: - color = RED if is_stderr else GREEN - print(f"{color}({prefix}){RESET} {line}") + color = ANSI_RED if is_stderr else ANSI_GREEN + print(f"{color}({prefix}){ANSI_RESET} {line}") async def _read_stream( @@ -92,48 +94,22 @@ def _discover_linters(lint_dir: Path) -> list[Path]: return sorted(lint_dir.glob("lint_*.py")) -def _format_table(results: list[LintResult]) -> str: - status_icons = { - "pass": f"{GREEN}pass{RESET}", - "fail": f"{RED}fail{RESET}", - "skip": f"{DIM}skip{RESET}", - } +_STATUS_COLORS = {"pass": ANSI_GREEN, "fail": ANSI_RED} + +def _results_table(results: list[LintResult]) -> str: headers = ("name", "time", "stdout", "stderr", "status") - rows: list[tuple[str, str, str, str, str]] = [] - for r in results: - rows.append( - ( - r.name, - f"{r.elapsed:.2f}s", - str(len(r.stdout_lines)), - str(len(r.stderr_lines)), - r.status, - ) + rows: list[tuple[str, ...]] = [ + ( + r.name, + f"{r.elapsed:.2f}s", + str(len(r.stdout_lines)), + str(len(r.stderr_lines)), + r.status, ) - - widths = [len(h) for h in headers] - for row in rows: - for i, cell in enumerate(row): - widths[i] = max(widths[i], len(cell)) - - def fmt_row(cells: tuple[str, ...], *, color: bool = False) -> str: - parts: list[str] = [] - for i, cell in enumerate(cells): - if color and i == len(cells) - 1: - display = status_icons.get(cell, cell) - parts.append(f" {display}{' ' * (widths[i] - len(cell))} ") - else: - parts.append(f" {cell:<{widths[i]}} ") - return f"|{'|'.join(parts)}|" - - lines: list[str] = [] - lines.append(fmt_row(headers)) - lines.append("|" + "|".join("-" * (w + 2) for w in widths) + "|") - for row in rows: - lines.append(fmt_row(row, color=True)) - - return "\n".join(lines) + for r in results + ] + return format_table(headers, rows, _STATUS_COLORS) async def _main() -> int: @@ -145,12 +121,12 @@ async def _main() -> int: print("no lint_*.py scripts found", file=sys.stderr) return RETCODE_ERR - print(f"{BOLD}running {len(scripts)} linter(s)...{RESET}\n") + print(f"{ANSI_BOLD}running {len(scripts)} linter(s)...{ANSI_RESET}\n") results = await asyncio.gather(*[_run_linter(s) for s in scripts]) results = list(results) - print(f"\n{_format_table(results)}\n") + print(f"\n{_results_table(results)}\n") return ( RETCODE_ERR if any(r.status == "fail" for r in results) else RETCODE_PASS diff --git a/contrib/semgrep/prelude.yml b/contrib/semgrep/prelude.yml deleted file mode 100644 index 87df0cf4..00000000 --- a/contrib/semgrep/prelude.yml +++ /dev/null @@ -1,18 +0,0 @@ -rules: - - id: alloc-only-in-prelude - message: "use crate::prelude instead of alloc:: directly" - severity: ERROR - languages: [rust] - paths: - include: [/pkgs/**/*.rs] - exclude: [/pkgs/**/prelude.rs, /pkgs/**/prelude/mod.rs] - pattern-regex: '\buse\s+(::)?alloc::' - - - id: prelude-named-import - message: "use crate::prelude::* instead of named imports from prelude" - severity: ERROR - languages: [rust] - paths: - include: [/pkgs/**/*.rs] - exclude: [/pkgs/**/prelude.rs, /pkgs/**/prelude/mod.rs] - pattern-regex: '\buse\s+crate::prelude::[^*]' diff --git a/contrib/semgrep/serde.yml b/contrib/semgrep/serde.yml deleted file mode 100644 index b19a688e..00000000 --- a/contrib/semgrep/serde.yml +++ /dev/null @@ -1,8 +0,0 @@ -rules: - - id: disambiguate-serde-derive - message: "use ::serde:: instead of serde:: in derive attributes" - severity: ERROR - languages: [rust] - paths: - include: [/pkgs/**/*.rs] - pattern-regex: 'derive\([^)]*(?\s+for\b' + + - id: style-no-get-prefix + message: "getters omit the get_ prefix" + severity: ERROR + languages: [rust] + paths: + include: [/pkgs/**/*.rs, /contrib/samples/**/*.rs] + exclude: [/pkgs/**/tests/**, /pkgs/**/bench/**] + patterns: + - pattern-either: + - pattern: | + fn $FN(&self, ...) { ... } + - pattern: | + fn $FN(&self) { ... } + - pattern: | + fn $FN(&mut self, ...) { ... } + - pattern: | + fn $FN(&mut self) { ... } + - pattern: | + fn $FN(&self, ...) -> $RET { ... } + - pattern: | + fn $FN(&self) -> $RET { ... } + - pattern: | + fn $FN(&mut self, ...) -> $RET { ... } + - pattern: | + fn $FN(&mut self) -> $RET { ... } + - metavariable-regex: + metavariable: $FN + regex: 'get_[a-z].*' diff --git a/pkgs/dev/Cargo.toml b/pkgs/dev/Cargo.toml index 55c40d74..cbd75f94 100644 --- a/pkgs/dev/Cargo.toml +++ b/pkgs/dev/Cargo.toml @@ -5,32 +5,75 @@ edition = "2021" license = "MIT" publish = false +[[bin]] +name = "bsdk-util" +path = "src/bin/bsdk_util/main.rs" +required-features = ["bin"] + [features] default = [] std = [ "dep:json5", "dep:serde_json", "bitcoin-consensus-encoding/std", + "ciborium?/std", "dash-primitives/std", "dash-types/std", "hex-conservative/std", ] full = ["std", "serde"] serde = ["dep:serde", "dash-primitives/serde", "dash-types/serde"] +bin = [ + "full", + "dep:chrono", + "dep:ciborium", + "dep:clap", + "dep:dash-num", + "dep:dash-params", + "dep:dash-pow", + "dep:indicatif", + "dep:rayon", + "dep:sysinfo", +] _internal = [] +[build-dependencies] +built = { version = "0.7", features = ["dependency-tree", "git2"] } + [dependencies] bitcoin-consensus-encoding = { version = "0.2", default-features = false, features = [ "alloc", ] } +dash-num = { version = "0.0.0", path = "../num", optional = true } +dash-params = { version = "0.0.0", path = "../params", optional = true } +dash-pow = { version = "0.0.0", path = "../pow", optional = true } dash-primitives = { version = "0.0.0", path = "../primitives" } dash-types = { version = "0.0.0", path = "../types", default-features = false } hex-conservative = { version = "0.3", default-features = false, features = [ "alloc", ] } +chrono = { version = "0.4", default-features = false, features = [ + "clock", +], optional = true } +ciborium = { version = "0.2", optional = true } +clap = { version = "4", default-features = false, features = [ + "derive", + "std", + "help", + "usage", + "error-context", +], optional = true } +indicatif = { version = "0.17", default-features = false, optional = true } json5 = { version = "0.4", optional = true } -serde = { version = "1", default-features = false, features = ["derive", "alloc"], optional = true } +rayon = { version = "1", optional = true } +serde = { version = "1", default-features = false, features = [ + "derive", + "alloc", +], optional = true } serde_json = { version = "1", optional = true } +sysinfo = { version = "0.33", default-features = false, features = [ + "system", +], optional = true } [lints] workspace = true diff --git a/pkgs/dev/build.rs b/pkgs/dev/build.rs new file mode 100644 index 00000000..3d2ad493 --- /dev/null +++ b/pkgs/dev/build.rs @@ -0,0 +1,14 @@ +// +// Copyright (c) 2026-present, The Dash Core developers +// SPDX-License-Identifier: MIT +// See the accompanying file LICENSE or https://opensource.org/license/MIT +// + +//! Build-time entrypoint. + +#[expect(clippy::expect_used, reason = "build script")] +fn main() { + if std::env::var("CARGO_FEATURE_BIN").is_ok() { + built::write_built_file().expect("failed to acquire build-time information"); + } +} diff --git a/pkgs/dev/src/bin/bsdk_util/bspcheck.rs b/pkgs/dev/src/bin/bsdk_util/bspcheck.rs new file mode 100644 index 00000000..31d62bd0 --- /dev/null +++ b/pkgs/dev/src/bin/bsdk_util/bspcheck.rs @@ -0,0 +1,576 @@ +// +// Copyright (c) 2026-present, The Dash Core developers +// SPDX-License-Identifier: MIT +// See the accompanying file LICENSE or https://opensource.org/license/MIT +// + +//! Linearized chain verification. + +use crate::logging; +use crate::policy; +use crate::Application; + +use dash_primitives::{Block, BlockHash, BlockInvalid}; +use dash_types::codec::{BaseCodec, Checkable, DecodeError}; +use indicatif::{ProgressBar, ProgressStyle}; +use rayon::prelude::*; + +use std::fmt; +use std::fs; +use std::fs::File; +use std::io::{self, BufReader, Read}; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::Mutex; +use std::time::Instant; + +/// Errors that can occur during chain verification. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum BootstrapError { + /// A resource or configuration error during initialization. + Config(String), + /// An I/O error occurred while reading the input stream. + Io(String), + /// The input contained no frames at all. + EmptyInput, + /// The first frame's magic bytes do not match any known network. + UnknownMagic { magic: [u8; 4] }, + /// A frame's magic bytes do not match the detected network. + BadMagic { + block: u64, + expected: [u8; 4], + actual: [u8; 4], + }, + /// A frame payload exceeds the maximum allowed size. + OversizedFrame { block: u64, size: u32, max: u32 }, + /// A block's raw bytes could not be decoded into a `Block`. + Decode { block: u64, error: DecodeError }, + /// Re-encoding a decoded block did not reproduce the original bytes. + WireRoundTrip { block: u64 }, + /// CBOR serialization failed. + CborEncode { block: u64, error: String }, + /// CBOR deserialization failed. + CborDecode { block: u64, error: String }, + /// CBOR round-trip did not reproduce the original block. + CborRoundTrip { block: u64 }, + /// A block failed structural consistency checks. + Check { block: u64, error: BlockInvalid }, + /// The genesis block does not match the expected hash for the network. + GenesisAnchor { expected: BlockHash, actual: BlockHash }, + /// Aggregate error after `--no-fastfail` finishes with failures. + Summary { errors: u64 }, +} + +impl fmt::Display for BootstrapError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Config(e) => write!(f, "configuration error: {e}"), + Self::Io(e) => write!(f, "i/o error: {e}"), + Self::EmptyInput => write!(f, "empty input"), + Self::UnknownMagic { magic: m } => { + write!(f, "unknown magic: 0x{:02x}{:02x}{:02x}{:02x}", m[0], m[1], m[2], m[3]) + } + Self::BadMagic { + block, + expected: e, + actual: a, + } => { + write!( + f, + "block {block}: bad magic 0x{:02x}{:02x}{:02x}{:02x} (expected 0x{:02x}{:02x}{:02x}{:02x})", + a[0], a[1], a[2], a[3], e[0], e[1], e[2], e[3], + ) + } + Self::OversizedFrame { block, size, max } => { + write!(f, "block {block}: frame size {size} exceeds maximum {max}") + } + Self::Decode { block, error } => write!(f, "block {block}: decode failed: {error}"), + Self::WireRoundTrip { block } => write!(f, "block {block}: wire round-trip mismatch"), + Self::CborEncode { block, error } => write!(f, "block {block}: cbor encode failed: {error}"), + Self::CborDecode { block, error } => write!(f, "block {block}: cbor decode failed: {error}"), + Self::CborRoundTrip { block } => write!(f, "block {block}: cbor round-trip mismatch"), + Self::Check { block, error } => write!(f, "block {block}: check failed: {error}"), + Self::GenesisAnchor { expected, actual } => { + write!(f, "genesis anchor mismatch: expected {expected}, got {actual}") + } + Self::Summary { errors } => write!(f, "{errors} block(s) failed verification"), + } + } +} + +impl std::error::Error for BootstrapError {} + +impl From for BootstrapError { + fn from(e: io::Error) -> Self { + Self::Io(format!("{} ({})", e, e.kind())) + } +} + +mod magic { + use super::BootstrapError; + use crate::Application; + + use dash_params::types::{ChainParams, MessageStart}; + + const KNOWN_NETWORKS: &[&ChainParams] = &[ + &dash_params::main::PARAMS, + &dash_params::test3::PARAMS, + &dash_params::regtest::PARAMS, + ]; + + fn detect(magic: MessageStart) -> Result<&'static ChainParams, BootstrapError> { + KNOWN_NETWORKS + .iter() + .find(|p| p.message_start == magic) + .copied() + .ok_or(BootstrapError::UnknownMagic { magic }) + } + + /// Identify the network from magic bytes and log the result. + pub fn check(magic: MessageStart, app: &Application) -> Result<&'static ChainParams, BootstrapError> { + let params = detect(magic)?; + let [a, b, c, d] = magic; + crate::logging::log_msg( + app, + &format!( + "Magic 0x{a:02x}{b:02x}{c:02x}{d:02x} corresponds to \"{}\"", + params.network_id + ), + ); + Ok(params) + } +} + +mod genesis { + use super::BootstrapError; + use crate::Application; + + use dash_num::Hash256; + use dash_primitives::BlockHash; + use dash_types::codec::DecodeError; + + /// Verify genesis block header hash matches the network. + pub fn check(data: &[u8], expected: Hash256, app: &Application) -> Result { + if data.len() < 80 { + return Err(BootstrapError::Decode { + block: 0, + error: DecodeError::Eof { + needed: 80, + remaining: data.len(), + }, + }); + } + let genesis_hash = BlockHash::from(dash_pow::hash(&data[..80])); + let expected_hash = BlockHash::from(expected); + if genesis_hash != expected_hash { + return Err(BootstrapError::GenesisAnchor { + expected: expected_hash, + actual: genesis_hash, + }); + } + crate::logging::log_msg(app, &format!("Genesis anchor: {genesis_hash}")); + Ok(genesis_hash) + } +} + +mod diskfmt { + use super::BootstrapError; + use crate::policy; + use crate::Application; + + use std::io::{self, Read}; + use std::time::{Duration, Instant}; + + #[derive(Clone, Debug, Eq, Hash, PartialEq)] + pub struct FrameHeader { + pub magic: [u8; 4], + pub size: u32, + } + + /// Read an 8-byte linearized chain frame header, return `None` on clean EOF. + pub fn read_frame_header(reader: &mut impl Read) -> Result, BootstrapError> { + let mut buf = [0u8; 8]; + // Read the first byte to distinguish clean EOF from a truncated header. + match reader.read_exact(&mut buf[..1]) { + Ok(()) => {} + Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None), + Err(e) => return Err(BootstrapError::Io(e.to_string())), + } + reader + .read_exact(&mut buf[1..]) + .map_err(|e| BootstrapError::Io(format!("truncated frame header: {e}")))?; + Ok(Some(FrameHeader { + magic: [buf[0], buf[1], buf[2], buf[3]], + size: u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]), + })) + } + + pub type Chunk = Vec<(u64, Vec)>; + + /// Result of a single chunk read: frames, bytes consumed, whether + /// EOF was reached, and an optional pre-read header that did not + /// fit in this chunk's budget. + pub struct ChunkResult { + pub frames: Chunk, + pub bytes: u64, + pub eof: bool, + pub pending: Option, + } + + /// Read frames into a chunk, stopping at `budget_bytes`. A + /// `pending` header from a previous call is consumed first, + /// avoiding stream misalignment at chunk boundaries. + pub fn read_chunk( + reader: &mut impl Read, + expected_magic: [u8; 4], + start_index: u64, + budget_bytes: u64, + report_secs: u64, + app: &Application, + pending: Option, + ) -> Result { + let mut frames = Vec::new(); + let mut cumulative: u64 = 0; + let mut index = start_index; + let mut last_log_time = Instant::now(); + let mut last_log_index = start_index; + let throttle_duration = Duration::from_secs(report_secs); + let mut next_header = pending; + + loop { + let header = match next_header.take() { + Some(h) => h, + None => match read_frame_header(reader)? { + Some(h) => h, + None => { + return Ok(ChunkResult { + frames, + bytes: cumulative, + eof: true, + pending: None, + }); + } + }, + }; + + if header.magic != expected_magic { + return Err(BootstrapError::BadMagic { + block: index, + expected: expected_magic, + actual: header.magic, + }); + } + + if header.size > policy::MAX_FRAME_SIZE { + return Err(BootstrapError::OversizedFrame { + block: index, + size: header.size, + max: policy::MAX_FRAME_SIZE, + }); + } + + let frame_bytes = policy::FRAME_HEADER_LEN + header.size as u64; + if !frames.is_empty() && cumulative + frame_bytes > budget_bytes { + return Ok(ChunkResult { + frames, + bytes: cumulative, + eof: false, + pending: Some(header), + }); + } + + let mut data = vec![0u8; header.size as usize]; + reader.read_exact(&mut data)?; + cumulative += frame_bytes; + frames.push((index, data)); + + let blocks_since_log = index - last_log_index; + let time_since_log = last_log_time.elapsed(); + if blocks_since_log >= policy::REPORT_BLOCK_INTERVAL && time_since_log >= throttle_duration { + crate::logging::log_msg(app, &format!("Read {} blocks from input", index + 1)); + last_log_time = Instant::now(); + last_log_index = index; + } + + index += 1; + } + } +} + +fn verify_block(index: u64, data: &[u8]) -> Result<(), BootstrapError> { + let block = Block::decode(&mut &data[..]).map_err(|e| BootstrapError::Decode { block: index, error: e })?; + + let mut re_encoded = Vec::with_capacity(data.len()); + block.encode(&mut re_encoded); + if re_encoded != data { + return Err(BootstrapError::WireRoundTrip { block: index }); + } + + let mut cbor_bytes = Vec::new(); + ciborium::into_writer(&block, &mut cbor_bytes).map_err(|e| BootstrapError::CborEncode { + block: index, + error: e.to_string(), + })?; + let decoded_from_cbor: Block = + ciborium::from_reader(&cbor_bytes[..]).map_err(|e: ciborium::de::Error| BootstrapError::CborDecode { + block: index, + error: e.to_string(), + })?; + if decoded_from_cbor != block { + return Err(BootstrapError::CborRoundTrip { block: index }); + } + + if let Some(e) = block.check() { + return Err(BootstrapError::Check { block: index, error: e }); + } + + Ok(()) +} + +pub fn run( + app: &Application, + file: &str, + threads: usize, + memory_mib: u64, + report_freq: u64, + no_fastfail: bool, + progress: bool, +) -> Result<(), BootstrapError> { + let start = Instant::now(); + let from_stdin = file == "-"; + + let max_threads = crate::platform::system_threads(); + let effective_threads = if threads == 0 { + policy::default_threads() + } else { + threads.clamp(1, max_threads) + }; + let pool = rayon::ThreadPoolBuilder::new() + .num_threads(effective_threads) + .build() + .map_err(|e| BootstrapError::Config(format!("failed to create thread pool: {e}")))?; + let thread_count = pool.current_num_threads(); + + let budget_mib = if memory_mib == 0 { + policy::default_memory_mib() + } else { + memory_mib + }; + let budget_bytes = budget_mib.saturating_mul(1024 * 1024); + + logging::log_msg( + app, + &format!("Threads: {thread_count}, memory budget: {budget_mib} MiB, report every {report_freq}s"), + ); + + let (input, file_size): (Box, Option) = if from_stdin { + logging::log_msg(app, "Reading from stdin (streaming)"); + (Box::new(io::stdin().lock()), None) + } else { + let full_path = fs::canonicalize(file).unwrap_or_else(|_| file.into()); + let metadata = fs::metadata(file)?; + let size_mib = metadata.len() / (1024 * 1024); + logging::log_msg(app, &format!("Reading {} ({size_mib} MiB)", full_path.display())); + (Box::new(File::open(file)?), Some(metadata.len())) + }; + + let mut reader = BufReader::with_capacity(policy::READ_BUFFER_BYTES, input); + + let first_header = diskfmt::read_frame_header(&mut reader)?.ok_or(BootstrapError::EmptyInput)?; + + let params = magic::check(first_header.magic, app)?; + + if first_header.size > policy::MAX_FRAME_SIZE { + return Err(BootstrapError::OversizedFrame { + block: 0, + size: first_header.size, + max: policy::MAX_FRAME_SIZE, + }); + } + + let mut genesis_data = vec![0u8; first_header.size as usize]; + reader.read_exact(&mut genesis_data)?; + let genesis_bytes = policy::FRAME_HEADER_LEN + first_header.size as u64; + + genesis::check(&genesis_data, params.consensus.hash_genesis_block, app)?; + + if progress { + let bar = match file_size { + Some(total) => { + let b = ProgressBar::new(total); + b.set_style( + ProgressStyle::default_bar() + .template("{bar:40} {bytes}/{total_bytes} ({eta})") + .unwrap_or_else(|_| ProgressStyle::default_bar()), + ); + b + } + None => { + let b = ProgressBar::new_spinner(); + b.set_style( + ProgressStyle::default_spinner() + .template("{spinner} {bytes} ({elapsed})") + .unwrap_or_else(|_| ProgressStyle::default_spinner()), + ); + b + } + }; + bar.inc(genesis_bytes); + app.pb.set(bar).ok(); + } + + logging::log_msg(app, &format!("Dispatching verification across {thread_count} threads")); + + verify_chunks( + app, + &pool, + &mut reader, + params, + genesis_data, + budget_bytes, + report_freq, + no_fastfail, + start, + ) +} + +#[expect(clippy::too_many_arguments)] +fn verify_chunks( + app: &Application, + pool: &rayon::ThreadPool, + reader: &mut BufReader>, + params: &dash_params::types::ChainParams, + genesis_data: Vec, + budget_bytes: u64, + report_secs: u64, + no_fastfail: bool, + start: Instant, +) -> Result<(), BootstrapError> { + let interrupted = AtomicBool::new(false); + let first_error: Mutex> = Mutex::new(None); + let error_count = AtomicU64::new(0); + let ok_count = AtomicU64::new(0); + + match verify_block(0, &genesis_data) { + Ok(()) => { + ok_count.fetch_add(1, Ordering::Relaxed); + } + Err(e) => { + logging::log_msg(app, &format!("Error: {e}")); + error_count.fetch_add(1, Ordering::Relaxed); + if !no_fastfail { + return Err(e); + } + } + } + + drop(genesis_data); + + let mut block_index: u64 = 1; + let mut chunk_num: u64 = 0; + let mut pending_header: Option = None; + + loop { + if !no_fastfail && interrupted.load(Ordering::Acquire) { + break; + } + + let cr = match diskfmt::read_chunk( + reader, + params.message_start, + block_index, + budget_bytes, + report_secs, + app, + pending_header.take(), + ) { + Ok(v) => v, + Err(e) => { + if let Some(bar) = app.pb.get() { + bar.finish_and_clear(); + } + return Err(e); + } + }; + + if cr.frames.is_empty() { + break; + } + + let chunk_len = cr.frames.len() as u64; + let reached_eof = cr.eof; + pending_header = cr.pending; + + pool.install(|| { + cr.frames.par_iter().for_each(|(idx, data)| { + if !no_fastfail && interrupted.load(Ordering::Acquire) { + return; + } + + match verify_block(*idx, data) { + Ok(()) => { + ok_count.fetch_add(1, Ordering::Relaxed); + } + Err(e) => { + logging::log_msg(app, &format!("Error: {e}")); + error_count.fetch_add(1, Ordering::Relaxed); + if !no_fastfail { + interrupted.store(true, Ordering::Release); + logging::log_msg(app, "Interrupting remaining blocks"); + let mut guard = first_error.lock().unwrap_or_else(|p| p.into_inner()); + if guard.is_none() { + *guard = Some(e); + } + } + } + } + }); + }); + + if let Some(bar) = app.pb.get() { + bar.inc(cr.bytes); + } + + logging::log_msg( + app, + &format!( + "Chunk {chunk_num}: verified {chunk_len} blocks ({ok} ok)", + ok = ok_count.load(Ordering::Relaxed) + ), + ); + + block_index += chunk_len; + chunk_num += 1; + + if reached_eof || (!no_fastfail && interrupted.load(Ordering::Acquire)) { + break; + } + } + + if let Some(bar) = app.pb.get() { + bar.finish_and_clear(); + } + + let ok = ok_count.load(Ordering::Relaxed); + let errs = error_count.load(Ordering::Relaxed); + let verified = ok + errs; + let abandoned = block_index.saturating_sub(verified); + + let runtime = logging::format_runtime(start.elapsed()); + logging::log_msg(app, &format!("Verified: {ok}/{verified} blocks in {runtime}")); + if abandoned > 0 { + logging::log_msg(app, &format!("Abandoned: {abandoned} blocks (interrupted)")); + } + + if errs > 0 { + if no_fastfail { + return Err(BootstrapError::Summary { errors: errs }); + } + if let Some(e) = first_error.lock().unwrap_or_else(|p| p.into_inner()).take() { + return Err(e); + } + return Err(BootstrapError::Summary { errors: errs }); + } + + logging::log_msg(app, "All blocks passed verification"); + Ok(()) +} diff --git a/pkgs/dev/src/bin/bsdk_util/logging.rs b/pkgs/dev/src/bin/bsdk_util/logging.rs new file mode 100644 index 00000000..252d9f70 --- /dev/null +++ b/pkgs/dev/src/bin/bsdk_util/logging.rs @@ -0,0 +1,84 @@ +// +// Copyright (c) 2026-present, The Dash Core developers +// SPDX-License-Identifier: MIT +// See the accompanying file LICENSE or https://opensource.org/license/MIT +// + +//! Logging, formatting and text manipulation. + +use crate::Application; + +use built_info::{DIRECT_DEPENDENCIES, GIT_COMMIT_HASH_SHORT, PKG_VERSION, RUSTC_VERSION}; +use chrono::{TimeDelta, Utc}; + +use std::io::Write; + +mod built_info { + include!(concat!(env!("OUT_DIR"), "/built.rs")); +} + +/// Write a timestamped message to stderr and optionally to a log file. +pub fn log_msg(app: &Application, msg: &str) { + let ts = Utc::now().format("%Y-%m-%dT%H:%M:%SZ"); + let line = format!("[{ts}] {msg}"); + + match app.pb.get() { + Some(bar) => bar.println(&line), + None => eprintln!("{line}"), + } + + if let Ok(mut guard) = app.log.lock() { + if let Some(ref mut w) = *guard { + if writeln!(w, "{line}").is_err() { + *guard = None; + eprintln!("warning: log file write failed, logging disabled"); + } + } + } +} + +/// Print a startup banner with version and build metadata. +pub fn print_banner(app: &Application) { + eprintln!(); + eprintln!(); + + let git_hash = GIT_COMMIT_HASH_SHORT.unwrap_or("unknown"); + + log_msg(app, &format!("Base SDK Debug Utility v{PKG_VERSION} ({git_hash})")); + log_msg(app, "Copyright (c) 2026, The Dash Core developers"); + log_msg(app, ""); + log_msg(app, "Build Info:"); + log_msg(app, &format!(" {RUSTC_VERSION}")); + for &(name, version) in DIRECT_DEPENDENCIES.iter() { + if name.starts_with("dash-") { + log_msg(app, &format!(" {name} v{version}")); + } + } + log_msg(app, ""); +} + +/// Format a duration as a compact human-readable string. +pub fn format_runtime(elapsed: std::time::Duration) -> String { + let delta = TimeDelta::from_std(elapsed).unwrap_or(TimeDelta::zero()); + let days = delta.num_days(); + let hours = delta.num_hours() % 24; + let minutes = delta.num_minutes() % 60; + let seconds = delta.num_seconds() % 60; + let mut parts = Vec::new(); + if days > 0 { + parts.push(format!("{days}d")); + } + if hours > 0 { + parts.push(format!("{hours}h")); + } + if minutes > 0 { + parts.push(format!("{minutes}m")); + } + if parts.is_empty() { + let millis = delta.num_milliseconds() % 1000; + parts.push(format!("{seconds}.{millis:03}s")); + } else { + parts.push(format!("{seconds}s")); + } + parts.join(" ") +} diff --git a/pkgs/dev/src/bin/bsdk_util/main.rs b/pkgs/dev/src/bin/bsdk_util/main.rs new file mode 100644 index 00000000..9a8f6cb5 --- /dev/null +++ b/pkgs/dev/src/bin/bsdk_util/main.rs @@ -0,0 +1,147 @@ +// +// Copyright (c) 2026-present, The Dash Core developers +// SPDX-License-Identifier: MIT +// See the accompanying file LICENSE or https://opensource.org/license/MIT +// + +//! Entrypoint for Base SDK Debug Utility. + +mod bspcheck; +mod logging; +mod platform; +mod policy; + +use clap::{Parser, Subcommand}; +use indicatif::ProgressBar; + +use std::env; +use std::fmt; +use std::fs::{File, OpenOptions}; +use std::io::BufWriter; +use std::process::ExitCode; +use std::sync::{Mutex, OnceLock}; + +/// Application shared state. +pub struct Application { + /// Progress bar, set at most once after file size is known. + pub pb: OnceLock, + /// Optional log-file writer. + pub log: Mutex>>, + /// Path to the log file on disk. + pub log_path: String, +} + +impl fmt::Debug for Application { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Application") + .field("pb", &self.pb.get().is_some()) + .field("log_path", &self.log_path) + .finish_non_exhaustive() + } +} + +impl Application { + fn new(log_path: Option) -> Self { + let log_path = log_path.unwrap_or_default(); + let log = if log_path.is_empty() { + Mutex::new(None) + } else { + match OpenOptions::new().write(true).create_new(true).open(&log_path) { + Ok(f) => Mutex::new(Some(BufWriter::new(f))), + Err(e) => { + eprintln!("warning: could not create log file: {e}"); + Mutex::new(None) + } + } + }; + Self { + pb: OnceLock::new(), + log, + log_path, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Parser)] +#[command(name = "bsdk-util", about = "Base SDK Debug Utility")] +struct Cli { + /// Write a log file to this path. + #[arg(long, global = true)] + log: Option, + + #[command(subcommand)] + command: Command, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Subcommand)] +enum Command { + /// Verify every block in a linearized chain. + Bspcheck { + /// Number of verification threads (0 = auto). + #[arg(short = 'j', long, default_value_t = 0)] + threads: usize, + + /// Max memory for block data in MiB (0 = auto). + #[arg(short = 'm', long, default_value_t = 0)] + memory: u64, + + /// Minimum seconds between progress reports. + #[arg(short = 'r', long, default_value_t = 5)] + report_freq: u64, + + /// Continue processing after errors instead of aborting. + #[arg(short = 'n', long)] + no_fastfail: bool, + + /// Show a progress bar. + #[arg(short = 'p', long)] + progress: bool, + + /// Path to a linearized chain, or "-" for stdin. + file: String, + }, +} + +fn main() -> ExitCode { + let cli = Cli::parse(); + let app = Application::new(cli.log); + + logging::print_banner(&app); + + let args: Vec = env::args().skip(1).collect(); + let joined_args = args.join(" "); + logging::log_msg( + &app, + &format!( + "Running on {} {} ({}) with args \"{joined_args}\"", + env::consts::OS, + env::consts::FAMILY, + env::consts::ARCH, + ), + ); + + let result = match cli.command { + Command::Bspcheck { + threads, + memory, + report_freq, + no_fastfail, + progress, + file, + } => bspcheck::run(&app, &file, threads, memory, report_freq, no_fastfail, progress), + }; + + match result { + Ok(()) => { + if app.log.lock().ok().and_then(|g| g.as_ref().map(|_| ())).is_some() { + logging::log_msg(&app, &format!("Log saved to {}", app.log_path)); + } + ExitCode::SUCCESS + } + Err(e) => { + logging::log_msg(&app, &format!("Fatal: {e}")); + eprintln!("fatal: {e}"); + ExitCode::FAILURE + } + } +} diff --git a/pkgs/dev/src/bin/bsdk_util/platform.rs b/pkgs/dev/src/bin/bsdk_util/platform.rs new file mode 100644 index 00000000..8c2cabef --- /dev/null +++ b/pkgs/dev/src/bin/bsdk_util/platform.rs @@ -0,0 +1,21 @@ +// +// Copyright (c) 2026-present, The Dash Core developers +// SPDX-License-Identifier: MIT +// See the accompanying file LICENSE or https://opensource.org/license/MIT +// + +//! Platform-specific utilities. + +use sysinfo::{MemoryRefreshKind, RefreshKind, System}; + +/// Total physical RAM in bytes, as reported by the operating system. +pub fn system_memory_bytes() -> u64 { + let memory = MemoryRefreshKind::nothing().with_ram(); + let refresh = RefreshKind::nothing().with_memory(memory); + System::new_with_specifics(refresh).total_memory() +} + +/// Number of logical CPUs available, falling back to 1. +pub fn system_threads() -> usize { + std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1) +} diff --git a/pkgs/dev/src/bin/bsdk_util/policy.rs b/pkgs/dev/src/bin/bsdk_util/policy.rs new file mode 100644 index 00000000..58ea6594 --- /dev/null +++ b/pkgs/dev/src/bin/bsdk_util/policy.rs @@ -0,0 +1,32 @@ +// +// Copyright (c) 2026-present, The Dash Core developers +// SPDX-License-Identifier: MIT +// See the accompanying file LICENSE or https://opensource.org/license/MIT +// + +//! Tunables and default values. + +use crate::platform; + +/// Size of the buffered reader in bytes (4 MiB). +pub const READ_BUFFER_BYTES: usize = 4 * 1024 * 1024; + +/// Bytes per bootstrap.dat frame header (4-byte magic + u32 size). +pub const FRAME_HEADER_LEN: u64 = 8; + +/// Maximum allowed frame payload (128 MiB). +pub const MAX_FRAME_SIZE: u32 = 128 * 1024 * 1024; + +/// Fixed block-count component of the read-progress throttle. +pub const REPORT_BLOCK_INTERVAL: u64 = 1000; + +/// Default memory budget: min(system_ram / 2, 4096 MiB). +pub fn default_memory_mib() -> u64 { + let sys_mib = platform::system_memory_bytes() / (1024 * 1024); + (sys_mib / 2).clamp(256, 4096) +} + +/// Default thread count: half of available parallelism, minimum 1. +pub fn default_threads() -> usize { + (platform::system_threads() / 2).max(1) +} diff --git a/pkgs/num/src/arith256.rs b/pkgs/num/src/arith256.rs index 5a92cb1f..fd391b0c 100644 --- a/pkgs/num/src/arith256.rs +++ b/pkgs/num/src/arith256.rs @@ -418,7 +418,7 @@ impl Arith256 { return Self::ONE; } let d = self.wrapping_inc(); - // !self = 2^256 - 1 - self, so (!self) / (self + 1) + 1 ≈ 2^256 / (self + 1) + // !self = 2^256 - 1 - self, so (!self) / (self + 1) + 1 ~ 2^256 / (self + 1) self.bitwise_not().div_rem(d).0.wrapping_inc() } diff --git a/pkgs/num/src/compact.rs b/pkgs/num/src/compact.rs index 380cad14..1e4406d3 100644 --- a/pkgs/num/src/compact.rs +++ b/pkgs/num/src/compact.rs @@ -13,7 +13,7 @@ use dash_types::impl_num; use core::fmt; -/// Compact difficulty target — a newtype around the consensus `nBits` u32. +/// Compact difficulty target -- a newtype around the consensus `nBits` u32. /// /// Construct directly via `CompactTarget(0x1d00ffff)`. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] diff --git a/pkgs/num/src/hash.rs b/pkgs/num/src/hash.rs index b48a4f1d..a99a0c9b 100644 --- a/pkgs/num/src/hash.rs +++ b/pkgs/num/src/hash.rs @@ -306,7 +306,7 @@ macro_rules! define_hash { dash_types::codec::take::<$n>(data).map(Self::from_bytes) } - fn encode(&self, buf: &mut ::alloc::vec::Vec) { + fn encode(&self, buf: &mut impl crate::__private::dash_types::codec::EncodeBuf) { buf.extend_from_slice(&self.0); } } diff --git a/pkgs/num/src/lib.rs b/pkgs/num/src/lib.rs index b882d2b4..aff68d06 100644 --- a/pkgs/num/src/lib.rs +++ b/pkgs/num/src/lib.rs @@ -28,6 +28,7 @@ pub mod util; #[doc(hidden)] pub mod __private { pub use bitcoin_consensus_encoding; + pub use dash_types; } pub use arith::ArithInt; diff --git a/pkgs/num/src/util.rs b/pkgs/num/src/util.rs index 01fca47a..47e40ab9 100644 --- a/pkgs/num/src/util.rs +++ b/pkgs/num/src/util.rs @@ -18,7 +18,7 @@ macro_rules! impl_hash { .map(Self::from_bytes) } - fn encode(&self, buf: &mut ::alloc::vec::Vec) { + fn encode(&self, buf: &mut impl $crate::__private::dash_types::codec::EncodeBuf) { buf.extend_from_slice(self.as_bytes()); } } diff --git a/pkgs/p2p_core/Cargo.toml b/pkgs/p2p_core/Cargo.toml index ffebb17d..15a6fd46 100644 --- a/pkgs/p2p_core/Cargo.toml +++ b/pkgs/p2p_core/Cargo.toml @@ -6,10 +6,11 @@ license = "MIT" [features] default = [] -std = ["dash-primitives/std", "dash-script/std", "dash-types/std"] +std = ["dash-pkc/std", "dash-primitives/std", "dash-script/std", "dash-types/std"] serde = [ "dep:serde", "dash-num/serde", + "dash-pkc/serde", "dash-primitives/serde", "dash-script/serde", "dash-types/serde", @@ -26,6 +27,7 @@ bitcoin-units = { version = "0.3", default-features = false, features = [ "alloc", ] } dash-num = { version = "0.0.0", path = "../num" } +dash-pkc = { version = "0.0.0", path = "../pkc" } dash-script = { version = "0.0.0", path = "../script" } dash-params = { version = "0.0.0", path = "../params" } dash-primitives = { version = "0.0.0", path = "../primitives" } diff --git a/pkgs/p2p_core/src/msg/addr.rs b/pkgs/p2p_core/src/msg/addr.rs index 8f7088dd..a831e633 100644 --- a/pkgs/p2p_core/src/msg/addr.rs +++ b/pkgs/p2p_core/src/msg/addr.rs @@ -11,7 +11,7 @@ use crate::prelude::*; use crate::primitives::ServiceFlags; use dash_primitives::{AddrV2, ServiceV1}; -use dash_types::codec::{self, BaseCodec, DecodeError}; +use dash_types::codec::{self, BaseCodec, DecodeError, EncodeBuf}; use core::fmt; @@ -57,7 +57,7 @@ impl BaseCodec for AddrV2Entry { }) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { self.time.encode(buf); codec::write_compact_u64(self.services.0, buf); self.addr.encode(buf); diff --git a/pkgs/p2p_core/src/msg/compact_filters.rs b/pkgs/p2p_core/src/msg/compact_filters.rs index ea44b7ff..59dd58c6 100644 --- a/pkgs/p2p_core/src/msg/compact_filters.rs +++ b/pkgs/p2p_core/src/msg/compact_filters.rs @@ -11,7 +11,7 @@ use crate::prelude::*; use bitcoin_units::BlockHeight; use dash_primitives::BlockHash; -use dash_types::codec::{BaseCodec, DecodeError}; +use dash_types::codec::{BaseCodec, DecodeError, EncodeBuf}; dash_types::make_num! { /// BIP157 filter type, encoded as a single byte on the wire. @@ -46,7 +46,7 @@ impl BaseCodec for GetCFilters { }) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { self.filter_type.0.encode(buf); self.start_height.to_u32().encode(buf); self.stop_hash.encode(buf); @@ -94,7 +94,7 @@ impl BaseCodec for GetCFHeaders { }) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { self.filter_type.0.encode(buf); self.start_height.to_u32().encode(buf); self.stop_hash.encode(buf); diff --git a/pkgs/p2p_core/src/msg/headers.rs b/pkgs/p2p_core/src/msg/headers.rs index 373a369c..7bb223bf 100644 --- a/pkgs/p2p_core/src/msg/headers.rs +++ b/pkgs/p2p_core/src/msg/headers.rs @@ -11,7 +11,7 @@ use crate::prelude::*; use crate::primitives::ProtocolVersion; use dash_primitives::{BlockHash, BlockHeader, MerkleRoot}; -use dash_types::codec::{self, BaseCodec, DecodeError}; +use dash_types::codec::{self, BaseCodec, DecodeError, EncodeBuf}; /// Maximum headers per message. const MAX_HEADERS: usize = 2_000; @@ -66,7 +66,7 @@ impl BaseCodec for Headers { Ok(Self { headers }) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { codec::write_compact_size(self.headers.len(), buf); for h in &self.headers { h.version.encode(buf); diff --git a/pkgs/p2p_core/src/msg/headers2.rs b/pkgs/p2p_core/src/msg/headers2.rs index d99e920d..a977d072 100644 --- a/pkgs/p2p_core/src/msg/headers2.rs +++ b/pkgs/p2p_core/src/msg/headers2.rs @@ -11,7 +11,7 @@ use crate::prelude::*; use crate::primitives::{CompressionState, ProtocolVersion}; use dash_primitives::BlockHash; -use dash_types::codec::{self, BaseCodec, DecodeError}; +use dash_types::codec::{self, BaseCodec, DecodeError, EncodeBuf}; /// Maximum headers per message. const MAX_HEADERS: usize = 2_000; @@ -55,7 +55,7 @@ impl BaseCodec for Headers2 { Ok(Self { headers }) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { codec::write_compact_size(self.headers.len(), buf); let mut state = CompressionState::new(); for h in &self.headers { diff --git a/pkgs/p2p_core/src/primitives/compressed_header.rs b/pkgs/p2p_core/src/primitives/compressed_header.rs index 5b791a2d..5fc861ba 100644 --- a/pkgs/p2p_core/src/primitives/compressed_header.rs +++ b/pkgs/p2p_core/src/primitives/compressed_header.rs @@ -9,7 +9,7 @@ use crate::prelude::*; use dash_primitives::{BlockHash, BlockHeader, MerkleRoot}; -use dash_types::codec::{BaseCodec, DecodeError}; +use dash_types::codec::{BaseCodec, DecodeError, EncodeBuf}; // Bitfield layout (1 byte): // bits 0-2: version offset (0 = full version present, 1-7 = MRU cache index) @@ -134,7 +134,7 @@ impl CompressionState { } /// Encodes one header in compressed form, advancing the state. - pub fn encode_header(&mut self, header: &BlockHeader, buf: &mut Vec) { + pub fn encode_header(&mut self, header: &BlockHeader, buf: &mut impl EncodeBuf) { let mut flags: u8 = 0; let version_offset = match self.find_version(header.version) { diff --git a/pkgs/p2p_core/src/primitives/mn_list.rs b/pkgs/p2p_core/src/primitives/mn_list.rs index 4f77663f..d30ae112 100644 --- a/pkgs/p2p_core/src/primitives/mn_list.rs +++ b/pkgs/p2p_core/src/primitives/mn_list.rs @@ -9,11 +9,9 @@ use crate::codec::{codec_p2p, impl_p2p}; use crate::prelude::*; -use dash_primitives::{ - BlockHash, BlsPublicKeyBytes, BlsSignatureBytes, Commitment, KeyId, LlmqType, MnType, PlatformNodeId, ServiceV1, - Transaction, TxHash, -}; -use dash_types::codec::{BaseCodec, DecodeError, NumCodec}; +use dash_pkc::{BlsPublicKeyBytes, BlsSignatureBytes}; +use dash_primitives::{BlockHash, Commitment, KeyId, LlmqType, MnType, PlatformNodeId, ServiceV1, Transaction, TxHash}; +use dash_types::codec::{BaseCodec, DecodeError, EncodeBuf, NumCodec}; use core::fmt; @@ -84,7 +82,7 @@ impl BaseCodec for SimplifiedMnListEntry { }) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { self.version.encode(buf); self.pro_reg_tx_hash.encode(buf); self.confirmed_hash.encode(buf); diff --git a/pkgs/p2p_core/src/primitives/user_agent.rs b/pkgs/p2p_core/src/primitives/user_agent.rs index 339ef7d5..a64aed51 100644 --- a/pkgs/p2p_core/src/primitives/user_agent.rs +++ b/pkgs/p2p_core/src/primitives/user_agent.rs @@ -9,7 +9,7 @@ use crate::codec::impl_p2p; use crate::prelude::*; -use dash_types::codec::{self, BaseCodec, DecodeError}; +use dash_types::codec::{self, BaseCodec, DecodeError, EncodeBuf}; use core::fmt; @@ -42,7 +42,7 @@ impl BaseCodec for UserAgent { Ok(Self(raw.to_vec())) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { self.0.encode(buf); } } diff --git a/pkgs/params/src/mainnet.rs b/pkgs/params/src/mainnet.rs index f4c01a36..ab7df022 100644 --- a/pkgs/params/src/mainnet.rs +++ b/pkgs/params/src/mainnet.rs @@ -9,6 +9,10 @@ use crate::prelude::*; use crate::types::*; +use dash_num::{Arith256, Hash256}; +use dash_primitives::{ + double_sha256, Block, BlockHash, BlockHeader, MerkleRoot, OutPoint, Script, Transaction, TxHash, TxIn, TxOut, TxType, +}; use hex_literal::hex; /// Returns the mainnet genesis block. diff --git a/pkgs/params/src/regtest.rs b/pkgs/params/src/regtest.rs index d9267297..63dd9e67 100644 --- a/pkgs/params/src/regtest.rs +++ b/pkgs/params/src/regtest.rs @@ -9,6 +9,10 @@ use crate::prelude::*; use crate::types::*; +use dash_num::{Arith256, Hash256}; +use dash_primitives::{ + double_sha256, Block, BlockHash, BlockHeader, MerkleRoot, OutPoint, Script, Transaction, TxHash, TxIn, TxOut, TxType, +}; use hex_literal::hex; /// Returns the regtest genesis block. diff --git a/pkgs/params/src/test3.rs b/pkgs/params/src/test3.rs index b32c2b3f..038a12a7 100644 --- a/pkgs/params/src/test3.rs +++ b/pkgs/params/src/test3.rs @@ -9,6 +9,10 @@ use crate::prelude::*; use crate::types::*; +use dash_num::{Arith256, Hash256}; +use dash_primitives::{ + double_sha256, Block, BlockHash, BlockHeader, MerkleRoot, OutPoint, Script, Transaction, TxHash, TxIn, TxOut, TxType, +}; use hex_literal::hex; /// Returns the testnet genesis block. diff --git a/pkgs/params/src/types.rs b/pkgs/params/src/types.rs index 156417a6..381674c5 100644 --- a/pkgs/params/src/types.rs +++ b/pkgs/params/src/types.rs @@ -7,12 +7,7 @@ //! Shared type definitions for chain parameters. pub(crate) use bitcoin_units::BlockHeight; - -pub use dash_num::{Arith256, Hash256}; -pub use dash_primitives::double_sha256; -pub use dash_primitives::{ - Block, BlockHash, BlockHeader, MerkleRoot, OutPoint, Script, Transaction, TxHash, TxIn, TxOut, TxType, -}; +use dash_num::{Arith256, Hash256}; /// P2P network message start bytes (magic). pub type MessageStart = [u8; 4]; diff --git a/pkgs/params/tests/genesis_valid.rs b/pkgs/params/tests/genesis_valid.rs index 3aa2f130..9ab72a72 100644 --- a/pkgs/params/tests/genesis_valid.rs +++ b/pkgs/params/tests/genesis_valid.rs @@ -6,15 +6,11 @@ //! Genesis validation test. -use bitcoin_consensus_encoding::encode_to_vec; -use dash_params::types::{Block, ChainParams, MerkleRoot}; +use dash_params::types::ChainParams; +use dash_primitives::{Block, BlockHash, MerkleRoot}; use hex_literal::hex; use rstest::rstest; -fn header_bytes(block: &Block) -> Vec { - encode_to_vec(&block.header) -} - #[rstest] #[case::mainnet( dash_params::main::genesis(), @@ -43,7 +39,6 @@ fn genesis_block_hash_matches( // PoW hash the 80-byte header and verify it matches the genesis // hash declared in the consensus parameters. - let raw = header_bytes(&genesis); - let pow_hash = dash_pow::hash(&raw); - assert_eq!(pow_hash, params.consensus.hash_genesis_block); + let expected = BlockHash::from(params.consensus.hash_genesis_block); + assert_eq!(genesis.header.hash(), expected); } diff --git a/pkgs/pkc/src/bls_chia/hash.rs b/pkgs/pkc/src/bls_chia/hash.rs index f4649f90..d29bb20b 100644 --- a/pkgs/pkc/src/bls_chia/hash.rs +++ b/pkgs/pkc/src/bls_chia/hash.rs @@ -92,7 +92,7 @@ pub(super) fn hash_to_g2(msg: &[u8; 32]) -> blst_p2 { let mut sum = blst_p2::default(); unsafe { blst_p2_add_or_double(&mut sum, &p0, &p1) }; - // Step 5: clear the cofactor via Budroni–Pintore. + // Step 5: clear the cofactor via Budroni-Pintore. mul_cof_b12(&sum) } diff --git a/pkgs/pkc/src/bls_chia/sk.rs b/pkgs/pkc/src/bls_chia/sk.rs index 351b3ab8..13c189c8 100644 --- a/pkgs/pkc/src/bls_chia/sk.rs +++ b/pkgs/pkc/src/bls_chia/sk.rs @@ -60,7 +60,7 @@ impl SecretKey { PublicKey::from_inner(aff) } - /// Sign a 32-byte message hash using the legacy scheme (no DST, Shallue–van + /// Sign a 32-byte message hash using the legacy scheme (no DST, Shallue-van /// de Woestijne hash-to-G2). #[expect(unsafe_code, reason = "blst C FFI")] pub fn sign(&self, msg: &[u8; 32]) -> Signature { diff --git a/pkgs/pkc/src/common/bls/threshold.rs b/pkgs/pkc/src/common/bls/threshold.rs index b51458cc..e155951e 100644 --- a/pkgs/pkc/src/common/bls/threshold.rs +++ b/pkgs/pkc/src/common/bls/threshold.rs @@ -40,7 +40,7 @@ pub(crate) fn interpolate_g2(ids: &[blst_fr], points: &[blst_p2]) -> blst_p2 { let n = ids.len(); // Compute Lagrange coefficients at x=0: - // λ_i = ∏_{j≠i} id_j / (id_j - id_i) + // L_i = prod_{j!=i} id_j / (id_j - id_i) let coeffs = compute_lagrange_coeffs(ids); let mut result = blst_p2::default(); @@ -64,7 +64,7 @@ fn compute_lagrange_coeffs(ids: &[blst_fr]) -> Vec { let mut coeffs = Vec::with_capacity(n); for i in 0..n { - // λ_i = ∏_{j≠i} ids[j] / (ids[j] - ids[i]) + // L_i = prod_{j!=i} ids[j] / (ids[j] - ids[i]) let mut num = fr_one(); let mut den = fr_one(); diff --git a/pkgs/pkc/src/lib.rs b/pkgs/pkc/src/lib.rs index f40b24df..a5e8d01d 100644 --- a/pkgs/pkc/src/lib.rs +++ b/pkgs/pkc/src/lib.rs @@ -26,8 +26,6 @@ pub mod k256; #[cfg(feature = "std")] pub mod worker; -pub use dash_num::Hash256; - dash_types::make_bytes! { /// Raw BLS public key bytes (48 bytes, unvalidated). BlsPublicKeyBytes, 48 diff --git a/pkgs/pkc/tests/bls_chia_llmq.rs b/pkgs/pkc/tests/bls_chia_llmq.rs index 583e338d..33ac6ce6 100644 --- a/pkgs/pkc/tests/bls_chia_llmq.rs +++ b/pkgs/pkc/tests/bls_chia_llmq.rs @@ -11,8 +11,8 @@ mod common; +use dash_num::Hash256; use dash_pkc::bls_chia::{aggregate_pk, aggregate_sig, threshold, PublicKey, SecretKey, Signature}; -use dash_pkc::Hash256; use hex_conservative::DisplayHex; #[test] diff --git a/pkgs/pkc/tests/bls_ietf_llmq.rs b/pkgs/pkc/tests/bls_ietf_llmq.rs index 09bf2103..3e1ba527 100644 --- a/pkgs/pkc/tests/bls_ietf_llmq.rs +++ b/pkgs/pkc/tests/bls_ietf_llmq.rs @@ -15,8 +15,8 @@ mod common; +use dash_num::Hash256; use dash_pkc::bls_ietf::{aggregate_pk, aggregate_sig, threshold, PublicKey, SecretKey, Signature}; -use dash_pkc::Hash256; use hex_conservative::DisplayHex; #[test] diff --git a/pkgs/pkc/tests/common/mod.rs b/pkgs/pkc/tests/common/mod.rs index 0a41c7ff..77eae2a3 100644 --- a/pkgs/pkc/tests/common/mod.rs +++ b/pkgs/pkc/tests/common/mod.rs @@ -45,17 +45,17 @@ pub fn hex_to_96(s: &str) -> [u8; 96] { decode_hex(s).try_into().unwrap() } -pub fn hash_from_hex(s: &str) -> dash_pkc::Hash256 { - dash_pkc::Hash256::from_hex(s).unwrap() +pub fn hash_from_hex(s: &str) -> dash_num::Hash256 { + dash_num::Hash256::from_hex(s).unwrap() } -pub fn make_id(i: u32) -> dash_pkc::Hash256 { +pub fn make_id(i: u32) -> dash_num::Hash256 { let mut bytes = [0u8; 32]; bytes[28..32].copy_from_slice(&i.to_be_bytes()); - dash_pkc::Hash256::from_bytes(bytes) + dash_num::Hash256::from_bytes(bytes) } -pub fn sequential_ids(n: usize) -> Vec { +pub fn sequential_ids(n: usize) -> Vec { (1..=n).map(|i| make_id(i as u32)).collect() } diff --git a/pkgs/primitives/Cargo.toml b/pkgs/primitives/Cargo.toml index 3908b791..f065b4d1 100644 --- a/pkgs/primitives/Cargo.toml +++ b/pkgs/primitives/Cargo.toml @@ -15,6 +15,7 @@ std = [ "dash-num/std", "dash-pkc/std", "dash-script/std", + "dash-pow/std", "dash-types/std", "hex-conservative/std", ] @@ -43,6 +44,7 @@ bitcoin-units = { version = "0.3", default-features = false, features = [ ] } dash-num = { version = "0.0.0", path = "../num" } dash-pkc = { version = "0.0.0", path = "../pkc", default-features = false } +dash-pow = { version = "0.0.0", path = "../pow" } dash-script = { version = "0.0.0", path = "../script" } dash-types = { version = "0.0.0", path = "../types", default-features = false } cfg-if = "1" @@ -55,7 +57,6 @@ serde = { version = "1", default-features = false, features = [ [dev-dependencies] dash-dev = { version = "0.0.0", path = "../dev", features = ["full"] } -dash-pow = { version = "0.0.0", path = "../pow" } hex-literal = "0.4" rstest = "0.25" serde = { version = "1", features = ["derive"] } diff --git a/pkgs/primitives/src/block.rs b/pkgs/primitives/src/block.rs index e09dbbb9..e66a808a 100644 --- a/pkgs/primitives/src/block.rs +++ b/pkgs/primitives/src/block.rs @@ -10,8 +10,9 @@ use crate::codec_type; use crate::prelude::*; use crate::transaction::{Transaction, TxInvalid}; -use dash_num::{make_hash, Hash256}; -use dash_types::codec::Checkable; +use dash_num::{make_hash, Arith256, CompactTarget, Hash256}; +use dash_pow::hash as hash_x11; +use dash_types::codec::{ArrayBuf, BaseCodec, Checkable}; use core::fmt; @@ -61,6 +62,15 @@ codec_type!(BlockHeader { nonce, }); +impl BlockHeader { + /// Proof-of-work hash from the serialized 80-byte header. + pub fn hash(&self) -> BlockHash { + let mut buf = ArrayBuf::<80>::new(); + self.encode(&mut buf); + BlockHash::from(hash_x11(&buf.into_array())) + } +} + impl fmt::Display for BlockHeader { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( @@ -74,6 +84,8 @@ impl fmt::Display for BlockHeader { /// Block validation failure. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum BlockInvalid { + /// Decoded target is negative, zero, overflows, or the hash exceeds it. + BadProofOfWork, /// `bad-blk-length` BadBlockLength { size: usize }, /// `bad-cb-missing` @@ -89,6 +101,7 @@ pub enum BlockInvalid { impl fmt::Display for BlockInvalid { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::BadProofOfWork => write!(f, "bad-proof-of-work"), Self::BadBlockLength { size } => write!(f, "bad-blk-length: {size} bytes"), Self::MissingCoinbase => write!(f, "bad-cb-missing"), Self::MultipleCoinbases { index } => write!(f, "bad-cb-multiple: tx {index}"), @@ -114,6 +127,12 @@ impl Checkable for Block { type Error = BlockInvalid; fn check(&self) -> Option { + let pow_hash = Arith256::from(Hash256::from(self.header.hash())); + let decoded = CompactTarget(self.header.bits).decode(); + if decoded.negative || decoded.value.is_zero() || decoded.overflow || pow_hash > decoded.value { + return Some(BlockInvalid::BadProofOfWork); + } + if self.transactions.is_empty() { return Some(BlockInvalid::BadBlockLength { size: 0 }); } @@ -176,9 +195,8 @@ mod tests { if let Some(e) = details.check() { panic!("{label}: check: {e}"); } - let pow_hash = crate::BlockHash::from(dash_pow::hash(&raw[..80])); let expected = crate::BlockHash::from_hex(label).unwrap(); - assert_eq!(pow_hash, expected, "{label}: pow hash"); + assert_eq!(details.header.hash(), expected, "{label}: pow hash"); }); assert_serde_rt("blocks", &items); } diff --git a/pkgs/primitives/src/codec.rs b/pkgs/primitives/src/codec.rs index 3a7a749a..8b327918 100644 --- a/pkgs/primitives/src/codec.rs +++ b/pkgs/primitives/src/codec.rs @@ -31,7 +31,7 @@ macro_rules! codec_type { }) } - fn encode(&self, buf: &mut ::alloc::vec::Vec) { + fn encode(&self, buf: &mut impl $crate::__private::dash_types::codec::EncodeBuf) { $($crate::__private::dash_types::codec::BaseCodec::encode(&self.$field, buf);)+ } } diff --git a/pkgs/primitives/src/lib.rs b/pkgs/primitives/src/lib.rs index 33659946..30590dbc 100644 --- a/pkgs/primitives/src/lib.rs +++ b/pkgs/primitives/src/lib.rs @@ -35,7 +35,6 @@ pub use block::{ Block, BlockHash, BlockHeader, BlockInvalid, MerkleRoot, MAX_DIP0001_BLOCK_SIZE, MAX_LEGACY_BLOCK_SIZE, }; pub use codec::MAX_SPTX_PAYLOAD_SIZE; -pub use dash_pkc::{BlsPublicKeyBytes, BlsSignatureBytes, EcdsaPublicKeyBytes, EcdsaSignatureBytes}; pub use gov::{ GovData, GovObject, GovObjectType, GovVote, Proposal, ProposalInvalid, Trigger, VoteOutcome, VoteSignal, }; diff --git a/pkgs/primitives/src/payload/cbtx.rs b/pkgs/primitives/src/payload/cbtx.rs index aa8e31cb..ae447df1 100644 --- a/pkgs/primitives/src/payload/cbtx.rs +++ b/pkgs/primitives/src/payload/cbtx.rs @@ -7,12 +7,11 @@ //! CoinbaseCommitment coinbase commitment payload (type 5). use crate::codec::impl_payload; -use crate::prelude::*; use crate::MerkleRoot; use bitcoin_units::BlockHeight; use dash_pkc::BlsSignatureBytes; -use dash_types::codec::{self, BaseCodec, Checkable, DecodeError}; +use dash_types::codec::{self, BaseCodec, Checkable, DecodeError, EncodeBuf}; use core::fmt; @@ -74,7 +73,7 @@ impl BaseCodec for CoinbaseCommitment { }) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { self.version.encode(buf); self.height.to_u32().encode(buf); self.merkle_root_mn_list.encode(buf); diff --git a/pkgs/primitives/src/payload/mod.rs b/pkgs/primitives/src/payload/mod.rs index 1711d348..8ea5c80a 100644 --- a/pkgs/primitives/src/payload/mod.rs +++ b/pkgs/primitives/src/payload/mod.rs @@ -330,6 +330,8 @@ pub enum PayloadInvalid { AssetUnlock(AssetUnlockInvalid), /// Quorum commitment check failed. Commitment(CommitmentInvalid), + /// Unrecognized special transaction. + UnknownType(TxType), } impl fmt::Display for PayloadInvalid { @@ -341,6 +343,7 @@ impl fmt::Display for PayloadInvalid { Self::AssetLock(e) => e.fmt(f), Self::AssetUnlock(e) => e.fmt(f), Self::Commitment(e) => e.fmt(f), + Self::UnknownType(t) => write!(f, "bad-txns-type: {t}"), } } } @@ -365,6 +368,11 @@ impl Checkable for SpecialPayload { } impl SpecialPayload { + /// Returns `true` for unrecognized transaction types. + pub fn is_unknown(&self) -> bool { + matches!(self, Self::Unknown { .. }) + } + /// Decodes a payload from its transaction type and raw bytes. /// /// Returns `Unknown` for unrecognized types rather than an error, ensuring diff --git a/pkgs/primitives/src/payload/proregtx.rs b/pkgs/primitives/src/payload/proregtx.rs index 71767e6f..f317fd66 100644 --- a/pkgs/primitives/src/payload/proregtx.rs +++ b/pkgs/primitives/src/payload/proregtx.rs @@ -17,7 +17,7 @@ use crate::types::{NITrait, NetInfo, NetInfoV1, NetInfoV2, ServiceV1}; use crate::TxHash; use dash_pkc::BlsPublicKeyBytes; -use dash_types::codec::{BaseCodec, Checkable, DecodeError, NumCodec}; +use dash_types::codec::{BaseCodec, Checkable, DecodeError, EncodeBuf, NumCodec}; use core::fmt; @@ -143,7 +143,7 @@ impl BaseCodec for ProRegTx { }) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { self.version.encode(buf); self.mn_type.to_base().encode(buf); self.mode.encode(buf); diff --git a/pkgs/primitives/src/payload/proupservtx.rs b/pkgs/primitives/src/payload/proupservtx.rs index e5731d7d..7a5b0b57 100644 --- a/pkgs/primitives/src/payload/proupservtx.rs +++ b/pkgs/primitives/src/payload/proupservtx.rs @@ -9,13 +9,12 @@ use super::proregtx::{check_platform_fields, PlatformNodeId}; use super::{check_sptx_netinfo, InputsHash, MnType, ProTxInvalid, PROTX_VERSION_BASIC_BLS, PROTX_VERSION_EXT_ADDR}; use crate::codec::impl_payload; -use crate::prelude::*; use crate::script::Script; use crate::types::{NITrait, NetInfo, NetInfoV1, NetInfoV2, ServiceV1}; use crate::TxHash; use dash_pkc::BlsSignatureBytes; -use dash_types::codec::{BaseCodec, Checkable, DecodeError, NumCodec}; +use dash_types::codec::{BaseCodec, Checkable, DecodeError, EncodeBuf, NumCodec}; use core::fmt; @@ -97,7 +96,7 @@ impl BaseCodec for ProUpServTx { }) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { self.version.encode(buf); if self.version >= 2 { self.mn_type.to_base().encode(buf); diff --git a/pkgs/primitives/src/payload/quorum.rs b/pkgs/primitives/src/payload/quorum.rs index f27afe18..2e6936dc 100644 --- a/pkgs/primitives/src/payload/quorum.rs +++ b/pkgs/primitives/src/payload/quorum.rs @@ -8,12 +8,11 @@ use super::QuorumHash; use crate::codec::impl_payload; -use crate::prelude::*; use crate::support::{DynBitset, LlmqType}; use dash_num::{make_hash, Hash256}; use dash_pkc::{BlsPublicKeyBytes, BlsSignatureBytes}; -use dash_types::codec::{BaseCodec, Checkable, DecodeError, NumCodec}; +use dash_types::codec::{BaseCodec, Checkable, DecodeError, EncodeBuf, NumCodec}; use core::fmt; @@ -82,7 +81,7 @@ impl BaseCodec for Commitment { }) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { self.version.encode(buf); self.llmq_type.to_base().encode(buf); self.quorum_hash.encode(buf); @@ -136,7 +135,7 @@ impl BaseCodec for FinalCommitment { }) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { self.version.encode(buf); self.height.to_u32().encode(buf); self.commitment.encode(buf); diff --git a/pkgs/primitives/src/script.rs b/pkgs/primitives/src/script.rs index 00493a18..49522017 100644 --- a/pkgs/primitives/src/script.rs +++ b/pkgs/primitives/src/script.rs @@ -8,7 +8,7 @@ use crate::prelude::*; -use dash_types::codec::{BaseCodec, DecodeError}; +use dash_types::codec::{BaseCodec, DecodeError, EncodeBuf}; use dash_types::{impl_type, make_bytes}; use core::fmt; @@ -26,7 +26,7 @@ impl BaseCodec for Script { Vec::decode(data).map(Self) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { self.0.encode(buf); } } diff --git a/pkgs/primitives/src/support.rs b/pkgs/primitives/src/support.rs index 0c551cb4..0e43e8ba 100644 --- a/pkgs/primitives/src/support.rs +++ b/pkgs/primitives/src/support.rs @@ -8,7 +8,7 @@ use crate::prelude::*; -use dash_types::codec::{self, BaseCodec, DecodeError, NumCodec}; +use dash_types::codec::{self, BaseCodec, DecodeError, EncodeBuf, NumCodec}; use dash_types::{impl_num, impl_type}; use core::fmt; @@ -202,22 +202,26 @@ impl BaseCodec for DynBitset { }) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { codec::write_compact_u64(self.num_bits, buf); let required = (self.num_bits as usize).div_ceil(8); let src = &self.data; let take = src.len().min(required); - buf.extend_from_slice(&src[..take]); + if take > 0 { + buf.extend_from_slice(&src[..take - 1]); + // Mask padding bits in the final source byte. + let remainder = (self.num_bits % 8) as u32; + let last_byte = if remainder != 0 { + src[take - 1] & ((1u8 << remainder) - 1) + } else { + src[take - 1] + }; + buf.push(last_byte); + } // Pad with zero bytes if data is shorter than required. for _ in take..required { buf.push(0); } - // Clear padding bits in the final byte. - let remainder = (self.num_bits % 8) as u32; - if remainder != 0 && required > 0 { - let last = buf.len() - 1; - buf[last] &= (1u8 << remainder) - 1; - } } } diff --git a/pkgs/primitives/src/transaction.rs b/pkgs/primitives/src/transaction.rs index 2e6f78d7..3b28f094 100644 --- a/pkgs/primitives/src/transaction.rs +++ b/pkgs/primitives/src/transaction.rs @@ -8,13 +8,13 @@ //! special transactions. use crate::codec_type; -use crate::payload::TxType; +use crate::payload::{PayloadError, PayloadInvalid, TxType}; use crate::prelude::*; use crate::script::Script; use bitcoin_units::Amount; use dash_num::{make_hash, Hash256}; -use dash_types::codec::{self, BaseCodec, Checkable, DecodeError, NumCodec}; +use dash_types::codec::{self, BaseCodec, Checkable, DecodeError, EncodeBuf, NumCodec}; use dash_types::impl_type; use core::fmt; @@ -110,7 +110,7 @@ impl BaseCodec for TxOut { }) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { self.value.to_sat().encode(buf); self.script_pubkey.encode(buf); } @@ -145,6 +145,10 @@ pub enum TxInvalid { NullPrevout { index: usize }, /// `bad-txns-payload-not-allowed` PayloadNotAllowed, + /// `bad-txns-payload-decode` + PayloadDecode(PayloadError), + /// `bad-txns-payload-check` + PayloadCheck(PayloadInvalid), } impl fmt::Display for TxInvalid { @@ -160,6 +164,8 @@ impl fmt::Display for TxInvalid { Self::BadCoinbaseScriptLength { len } => write!(f, "bad-cb-length: {len}"), Self::NullPrevout { index } => write!(f, "bad-txns-prevout-null: input {index}"), Self::PayloadNotAllowed => write!(f, "bad-txns-payload-not-allowed"), + Self::PayloadDecode(e) => write!(f, "bad-txns-payload-decode: {e}"), + Self::PayloadCheck(e) => write!(f, "bad-txns-payload-check: {e}"), } } } @@ -213,7 +219,7 @@ impl BaseCodec for Transaction { }) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { self.raw_version().encode(buf); self.inputs.encode(buf); self.outputs.encode(buf); @@ -293,6 +299,20 @@ impl Checkable for Transaction { } } + if let Some(result) = self.decode_payload() { + match result { + Ok(ref payload) if payload.is_unknown() => { + return Some(TxInvalid::PayloadCheck(PayloadInvalid::UnknownType(self.tx_type))); + } + Ok(payload) => { + if let Some(e) = payload.check() { + return Some(TxInvalid::PayloadCheck(e)); + } + } + Err(e) => return Some(TxInvalid::PayloadDecode(e)), + } + } + None } } diff --git a/pkgs/primitives/src/types/addrv1.rs b/pkgs/primitives/src/types/addrv1.rs index 73e8f4eb..9c7ee0b3 100644 --- a/pkgs/primitives/src/types/addrv1.rs +++ b/pkgs/primitives/src/types/addrv1.rs @@ -8,9 +8,8 @@ use super::addrv2::{AddrV2, ServiceV2}; use super::netaddr::{NetAddr, NetAddrError, NetworkType}; -use crate::prelude::*; -use dash_types::codec::{self, BaseCodec, Checkable, DecodeError}; +use dash_types::codec::{self, BaseCodec, Checkable, DecodeError, EncodeBuf}; use dash_types::{impl_bytes, impl_type, type_cvrt}; use core::fmt; @@ -70,6 +69,15 @@ impl AddrV1 { } } +impl From<[u8; 4]> for AddrV1 { + fn from(octets: [u8; 4]) -> Self { + let mut arr = [0u8; 16]; + arr[..12].copy_from_slice(&IPV4_MAPPED_PREFIX); + arr[12..].copy_from_slice(&octets); + Self(arr) + } +} + impl From for [u8; 16] { fn from(val: AddrV1) -> Self { val.0 @@ -223,7 +231,7 @@ impl BaseCodec for ServiceV1 { }) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { self.addr.encode(buf); buf.extend_from_slice(&self.port.to_be_bytes()); } @@ -293,6 +301,7 @@ pub(super) fn split_service_str(s: &str) -> Result<(&str, u16), NetAddrError> { #[expect(clippy::unwrap_used, reason = "test code")] mod tests { use super::*; + use crate::prelude::*; use hex_literal::hex; use rstest::rstest; diff --git a/pkgs/primitives/src/types/addrv2.rs b/pkgs/primitives/src/types/addrv2.rs index 86b3197c..034e3478 100644 --- a/pkgs/primitives/src/types/addrv2.rs +++ b/pkgs/primitives/src/types/addrv2.rs @@ -12,7 +12,7 @@ use super::util::{base16_dec, base16_enc, base32r_dec, base32r_enc}; use crate::prelude::*; use bitcoin_hashes::sha3_256; -use dash_types::codec::{self, BaseCodec, Checkable, DecodeError, NumCodec}; +use dash_types::codec::{self, BaseCodec, Checkable, DecodeError, EncodeBuf, NumCodec}; use dash_types::{impl_type, type_cvrt}; use core::fmt; @@ -97,7 +97,7 @@ impl BaseCodec for AddrV2 { } } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { self.network().to_base().encode(buf); let bytes = self.bytes(); codec::write_compact_size(bytes.len(), buf); @@ -343,7 +343,7 @@ impl BaseCodec for ServiceV2 { Ok(Self { addr, port }) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { self.addr.encode(buf); buf.extend_from_slice(&self.port.to_be_bytes()); } diff --git a/pkgs/primitives/src/types/netaddr.rs b/pkgs/primitives/src/types/netaddr.rs index c8d46504..74cb4538 100644 --- a/pkgs/primitives/src/types/netaddr.rs +++ b/pkgs/primitives/src/types/netaddr.rs @@ -366,26 +366,94 @@ pub trait NetAddr { b.len() == 4 && b[0] == 100 && (b[1] & 0xc0) == 64 } + /// RFC 4291: IPv4-mapped IPv6 (::ffff:0:0/96). + fn is_rfc4291(&self) -> bool { + if !self.is_ipv6() { + return false; + } + let b = self.bytes(); + b.len() == 16 && b[0..10] == [0; 10] && b[10] == 0xff && b[11] == 0xff + } + + /// RFC 4862: IPv6 stateless address autoconfiguration (fe80::/64). + fn is_rfc4862(&self) -> bool { + if !self.is_ipv6() { + return false; + } + let b = self.bytes(); + b.len() == 16 && b[0] == 0xfe && b[1] == 0x80 && b[2..8] == [0; 6] + } + + /// RFC 5737: documentation IPv4 (TEST-NET-1/2/3). + fn is_rfc5737(&self) -> bool { + if !self.is_ipv4() { + return false; + } + let b = self.bytes(); + b.len() == 4 + && ((b[0] == 192 && b[1] == 0 && b[2] == 2) + || (b[0] == 198 && b[1] == 51 && b[2] == 100) + || (b[0] == 203 && b[1] == 0 && b[2] == 113)) + } + + /// RFC 6666: discard prefix (100::/64). + fn is_rfc6666(&self) -> bool { + if !self.is_ipv6() { + return false; + } + let b = self.bytes(); + b.len() == 16 && b[0] == 0x01 && b[1] == 0x00 && b[2..8] == [0; 6] + } + + /// RFC 6890: IETF protocol assignments (192.0.0.0/24). + fn is_rfc6890(&self) -> bool { + if !self.is_ipv4() { + return false; + } + let b = self.bytes(); + b.len() == 4 && b[0] == 192 && b[1] == 0 && b[2] == 0 + } + + /// RFC 7343: ORCHIDv2 IPv6 (2001:20::/28). + fn is_rfc7343(&self) -> bool { + if !self.is_ipv6() { + return false; + } + let b = self.bytes(); + b.len() == 16 && b[0] == 0x20 && b[1] == 0x01 && b[2] == 0x00 && (b[3] & 0xf0) == 0x20 + } + /// Returns `true` when the address is globally routable. + /// + /// This classification matches the reference implementation's `IsRoutable()` + /// and is used in consensus-adjacent paths (masternode registration and + /// service validation). Do not add or remove ranges without coordinating + /// with upstream. fn is_routable(&self) -> bool { if self.is_null() { return false; } - if self.is_local() { - return false; - } if self.is_privacy_net() { return true; } if self.is_ipv4() { let b = self.bytes(); - if b.len() == 4 && (b[0] == 0 || b[0] >= 224) { + // 0/8 (current-net), 127/8 (loopback), 224+ (multicast/reserved) + if b.len() == 4 && (b[0] == 0 || b[0] == 127 || b[0] >= 224) { return false; } - return !self.is_rfc1918() && !self.is_rfc2544() && !self.is_rfc3927() && !self.is_rfc6598(); + return !self.is_rfc1918() + && !self.is_rfc2544() + && !self.is_rfc3927() + && !self.is_rfc5737() + && !self.is_rfc6598(); } if self.is_ipv6() { let b = self.bytes(); + // ::1 loopback + if b.len() == 16 && b[..15] == [0; 15] && b[15] == 1 { + return false; + } // ff00::/8 multicast if b.len() == 16 && b[0] == 0xff { return false; @@ -393,264 +461,268 @@ pub trait NetAddr { return !self.is_rfc3849() && !self.is_rfc4193() && !self.is_rfc4843() - && !self.is_rfc6052() - && !self.is_rfc6145(); + && !self.is_rfc4862() + && !self.is_rfc7343(); } false } -} - -#[cfg(test)] -mod tests { - use super::*; - - use rstest::rstest; - - /// Minimal test adapter: 4-byte IPv4 address. - struct Ipv4([u8; 4]); - impl NetAddr for Ipv4 { - fn bytes(&self) -> &[u8] { - &self.0 - } - fn network(&self) -> NetworkType { - NetworkType::Ipv4 - } - fn is_ipv4(&self) -> bool { - true + /// Returns `true` when the address is expected to be reachable on the public + /// internet. + /// + /// This is a strict superset of the ranges excluded by + /// [`is_routable`](Self::is_routable) and additionally covers RFC ranges + /// that the reference implementation does not yet classify. Unlike + /// `is_routable`, this function is **not** consensus-stable and may evolve + /// as new RFCs are published. + fn is_reachable(&self) -> bool { + if !self.is_routable() || self.is_local() { + return false; } - fn is_ipv6(&self) -> bool { - false + if self.is_ipv4() { + return !self.is_rfc6890(); } - fn is_null(&self) -> bool { - self.0 == [0; 4] + if self.is_ipv6() { + return !self.is_rfc4291() && !self.is_rfc6052() && !self.is_rfc6145() && !self.is_rfc6666(); } + true } +} - /// Minimal test adapter: 16-byte IPv6 address. - struct Ipv6([u8; 16]); +#[expect(clippy::unwrap_used, reason = "test code")] +#[cfg(test)] +mod tests { + use super::*; + use crate::types::{AddrV1, AddrV2}; - impl NetAddr for Ipv6 { - fn bytes(&self) -> &[u8] { - &self.0 - } - fn network(&self) -> NetworkType { - NetworkType::Ipv6 - } - fn is_ipv4(&self) -> bool { - false - } - fn is_ipv6(&self) -> bool { - true - } - fn is_null(&self) -> bool { - self.0 == [0; 16] - } - } + use rstest::rstest; - fn v4(a: u8, b: u8, c: u8, d: u8) -> Ipv4 { - Ipv4([a, b, c, d]) - } + use core::str::FromStr; - fn v6(bytes: [u8; 16]) -> Ipv6 { - Ipv6(bytes) + fn addr(s: &str) -> AddrV1 { + AddrV1::from_str(s).unwrap() } #[rstest] - #[case::rfc1918_10(v4(10, 0, 0, 1), true)] - #[case::rfc1918_172(v4(172, 31, 255, 255), true)] - #[case::rfc1918_192(v4(192, 168, 1, 1), true)] - #[case::rfc1918_public(v4(8, 8, 8, 8), false)] - fn rfc1918(#[case] addr: Ipv4, #[case] expected: bool) { - assert_eq!(addr.is_rfc1918(), expected); + #[case::rfc1918_10("10.0.0.1", true)] + #[case::rfc1918_172("172.31.255.255", true)] + #[case::rfc1918_192("192.168.1.1", true)] + #[case::rfc1918_public("8.8.8.8", false)] + fn rfc1918(#[case] s: &str, #[case] expected: bool) { + assert_eq!(addr(s).is_rfc1918(), expected); } #[rstest] - #[case::rfc2544_lo(v4(198, 18, 0, 0), true)] - #[case::rfc2544_hi(v4(198, 19, 255, 255), true)] - #[case::rfc2544_below(v4(198, 17, 0, 0), false)] - fn rfc2544(#[case] addr: Ipv4, #[case] expected: bool) { - assert_eq!(addr.is_rfc2544(), expected); + #[case::rfc2544_lo("198.18.0.0", true)] + #[case::rfc2544_hi("198.19.255.255", true)] + #[case::rfc2544_below("198.17.0.0", false)] + fn rfc2544(#[case] s: &str, #[case] expected: bool) { + assert_eq!(addr(s).is_rfc2544(), expected); } #[rstest] fn rfc3849() { - let mut b = [0u8; 16]; - b[0] = 0x20; - b[1] = 0x01; - b[2] = 0x0d; - b[3] = 0xb8; - assert!(v6(b).is_rfc3849()); + assert!(addr("[2001:db8::1]").is_rfc3849()); } #[rstest] - #[case::rfc3927_yes(v4(169, 254, 1, 1), true)] - #[case::rfc3927_no(v4(169, 253, 1, 1), false)] - fn rfc3927(#[case] addr: Ipv4, #[case] expected: bool) { - assert_eq!(addr.is_rfc3927(), expected); + #[case::rfc3927_yes("169.254.1.1", true)] + #[case::rfc3927_no("169.253.1.1", false)] + fn rfc3927(#[case] s: &str, #[case] expected: bool) { + assert_eq!(addr(s).is_rfc3927(), expected); } #[rstest] fn rfc4193() { - let mut b = [0u8; 16]; - b[0] = 0xfc; - assert!(v6(b).is_rfc4193()); - b[0] = 0xfd; - assert!(v6(b).is_rfc4193()); - b[0] = 0xfe; - assert!(!v6(b).is_rfc4193()); + assert!(addr("[fc00::1]").is_rfc4193()); + assert!(addr("[fd00::1]").is_rfc4193()); + assert!(!addr("[fe00::1]").is_rfc4193()); } #[rstest] fn rfc4843() { - let mut b = [0u8; 16]; - b[0] = 0x20; - b[1] = 0x01; - b[2] = 0x00; - b[3] = 0x10; - assert!(v6(b).is_rfc4843()); - b[3] = 0x1f; - assert!(v6(b).is_rfc4843()); - b[3] = 0x20; - assert!(!v6(b).is_rfc4843()); + assert!(addr("[2001:10::1]").is_rfc4843()); + assert!(addr("[2001:1f::1]").is_rfc4843()); + assert!(!addr("[2001:20::1]").is_rfc4843()); } #[rstest] fn rfc6052() { - let mut b = [0u8; 16]; - b[0] = 0x00; - b[1] = 0x64; - b[2] = 0xff; - b[3] = 0x9b; - assert!(v6(b).is_rfc6052()); - b[4] = 1; - assert!(!v6(b).is_rfc6052()); + assert!(addr("[64:ff9b::1]").is_rfc6052()); + assert!(!addr("[64:ff9b:1::1]").is_rfc6052()); } #[rstest] fn rfc6145() { - let mut b = [0u8; 16]; - b[8] = 0xff; - b[9] = 0xff; - assert!(v6(b).is_rfc6145()); - b[0] = 1; - assert!(!v6(b).is_rfc6145()); + assert!(addr("[::ffff:0:0:1]").is_rfc6145()); + assert!(!addr("[1::ffff:0:0:1]").is_rfc6145()); } #[rstest] - #[case::rfc6598_lo(v4(100, 64, 0, 0), true)] - #[case::rfc6598_hi(v4(100, 127, 255, 255), true)] - #[case::rfc6598_below(v4(100, 63, 0, 0), false)] - fn rfc6598(#[case] addr: Ipv4, #[case] expected: bool) { - assert_eq!(addr.is_rfc6598(), expected); + #[case::rfc6598_lo("100.64.0.0", true)] + #[case::rfc6598_hi("100.127.255.255", true)] + #[case::rfc6598_below("100.63.0.0", false)] + fn rfc6598(#[case] s: &str, #[case] expected: bool) { + assert_eq!(addr(s).is_rfc6598(), expected); } #[rstest] - #[case::local_v4(v4(127, 0, 0, 1), true)] - #[case::local_link(v4(169, 254, 0, 1), true)] - #[case::local_public(v4(8, 8, 8, 8), false)] - fn local_v4(#[case] addr: Ipv4, #[case] expected: bool) { - assert_eq!(addr.is_local(), expected); + #[case::local_v4("127.0.0.1", true)] + #[case::local_link("169.254.0.1", true)] + #[case::local_public("8.8.8.8", false)] + fn local_v4(#[case] s: &str, #[case] expected: bool) { + assert_eq!(addr(s).is_local(), expected); } #[rstest] fn local_v6() { - let mut loopback = [0u8; 16]; - loopback[15] = 1; - assert!(v6(loopback).is_local()); + assert!(addr("[::1]").is_local()); + assert!(addr("[fe80::1]").is_local()); + assert!(!addr("[2001::1]").is_local()); + } - let mut link_local = [0u8; 16]; - link_local[0] = 0xfe; - link_local[1] = 0x80; - assert!(v6(link_local).is_local()); + #[rstest] + #[case::routable_v4("8.8.8.8", true)] + #[case::not_routable_loopback("127.0.0.1", false)] + #[case::not_routable_private("10.0.0.1", false)] + #[case::not_routable_null("0.0.0.0", false)] + #[case::not_routable_multicast("224.0.0.1", false)] + #[case::not_routable_multicast_hi("239.255.255.255", false)] + fn routable_v4(#[case] s: &str, #[case] expected: bool) { + assert_eq!(addr(s).is_routable(), expected); + } - let mut global = [0u8; 16]; - global[0] = 0x20; - global[1] = 0x01; - global[15] = 1; - assert!(!v6(global).is_local()); + #[rstest] + fn routable_v6() { + assert!(addr("[2001::1]").is_routable()); + assert!(!addr("[2001:db8::1]").is_routable()); + assert!(!addr("[2001:10::1]").is_routable()); + assert!(!addr("[2001:20::1]").is_routable()); + assert!(!addr("[fe80::1]").is_routable()); + assert!(!addr("[ff02::1]").is_routable()); } #[rstest] - #[case::routable_v4(v4(8, 8, 8, 8), true)] - #[case::not_routable_loopback(v4(127, 0, 0, 1), false)] - #[case::not_routable_private(v4(10, 0, 0, 1), false)] - #[case::not_routable_null(v4(0, 0, 0, 0), false)] - #[case::not_routable_multicast(v4(224, 0, 0, 1), false)] - #[case::not_routable_multicast_hi(v4(239, 255, 255, 255), false)] - fn routable_v4(#[case] addr: Ipv4, #[case] expected: bool) { - assert_eq!(addr.is_routable(), expected); + #[case::test_net_1("192.0.2.1", false)] + #[case::test_net_2("198.51.100.1", false)] + #[case::test_net_3("203.0.113.1", false)] + fn routable_rfc5737(#[case] s: &str, #[case] expected: bool) { + assert_eq!(addr(s).is_routable(), expected); } #[rstest] - fn routable_v6() { - let mut global = [0u8; 16]; - global[0] = 0x20; - global[1] = 0x01; - global[15] = 1; - assert!(v6(global).is_routable()); - - let mut doc = [0u8; 16]; - doc[0] = 0x20; - doc[1] = 0x01; - doc[2] = 0x0d; - doc[3] = 0xb8; - doc[15] = 1; - assert!(!v6(doc).is_routable()); - - let mut mcast = [0u8; 16]; - mcast[0] = 0xff; - mcast[1] = 0x02; - mcast[15] = 1; - assert!(!v6(mcast).is_routable()); + #[case::ietf_proto("192.0.0.1", true)] + #[case::nat64("[64:ff9b::1]", true)] + #[case::siit("[::ffff:0:0:1]", true)] + #[case::discard_v6("[100::1]", true)] + fn routable_matches_upstream(#[case] s: &str, #[case] expected: bool) { + // Routable per the reference implementation but excluded by is_reachable(). + assert_eq!(addr(s).is_routable(), expected); } #[rstest] fn null() { - assert!(v4(0, 0, 0, 0).is_null()); - assert!(!v4(1, 2, 3, 4).is_null()); - assert!(v6([0; 16]).is_null()); - let mut nonzero = [0u8; 16]; - nonzero[15] = 1; - assert!(!v6(nonzero).is_null()); + assert!(AddrV1::default().is_null()); + assert!(!addr("1.2.3.4").is_null()); + assert!(!addr("[::1]").is_null()); } #[rstest] fn invariant_rfc1918_implies_ipv4() { - let addr = v4(10, 0, 0, 1); - if addr.is_rfc1918() { - assert!(addr.is_ipv4()); + let a = addr("10.0.0.1"); + if a.is_rfc1918() { + assert!(a.is_ipv4()); } } #[rstest] fn invariant_rfc3849_implies_ipv6() { - let mut b = [0u8; 16]; - b[0] = 0x20; - b[1] = 0x01; - b[2] = 0x0d; - b[3] = 0xb8; - let addr = v6(b); - if addr.is_rfc3849() { - assert!(addr.is_ipv6()); + let a = addr("[2001:db8::1]"); + if a.is_rfc3849() { + assert!(a.is_ipv6()); } } #[rstest] fn invariant_rfc4193_implies_ipv6() { - let mut b = [0u8; 16]; - b[0] = 0xfc; - let addr = v6(b); - if addr.is_rfc4193() { - assert!(addr.is_ipv6()); + let a = addr("[fc00::1]"); + if a.is_rfc4193() { + assert!(a.is_ipv6()); } } #[rstest] fn invariant_local_implies_not_routable() { - let addr = v4(127, 0, 0, 1); - if addr.is_local() { - assert!(!addr.is_routable()); + let a = addr("127.0.0.1"); + if a.is_local() { + assert!(!a.is_routable()); } } + + #[rstest] + #[case::mapped_yes("[::ffff:8.8.8.8]", true)] + #[case::mapped_no("[2001::1]", false)] + fn rfc4291(#[case] s: &str, #[case] expected: bool) { + // AddrV1 treats ::ffff:x.x.x.x as IPv4, test via AddrV2. + let a = AddrV2::from_str(s).unwrap(); + assert_eq!(a.is_rfc4291(), expected); + } + + #[rstest] + #[case::link_local("[fe80::1]", true)] + #[case::link_local_boundary("[fe80:1::1]", false)] + #[case::not_link_local("[fe81::1]", false)] + fn rfc4862(#[case] s: &str, #[case] expected: bool) { + assert_eq!(addr(s).is_rfc4862(), expected); + } + + #[rstest] + #[case::test_net_1("192.0.2.1", true)] + #[case::test_net_2("198.51.100.1", true)] + #[case::test_net_3("203.0.113.1", true)] + #[case::not_test_net("192.0.1.1", false)] + #[case::not_test_net_2("203.0.114.1", false)] + fn rfc5737(#[case] s: &str, #[case] expected: bool) { + assert_eq!(addr(s).is_rfc5737(), expected); + } + + #[rstest] + fn rfc6666() { + assert!(addr("[100::1]").is_rfc6666()); + assert!(!addr("[100:1::1]").is_rfc6666()); + } + + #[rstest] + #[case::ietf_yes("192.0.0.170", true)] + #[case::ietf_no("192.0.1.1", false)] + fn rfc6890(#[case] s: &str, #[case] expected: bool) { + assert_eq!(addr(s).is_rfc6890(), expected); + } + + #[rstest] + fn rfc7343() { + assert!(addr("[2001:20::1]").is_rfc7343()); + assert!(addr("[2001:2f::1]").is_rfc7343()); + assert!(!addr("[2001:30::1]").is_rfc7343()); + } + + #[rstest] + #[case::ietf_proto("192.0.0.1", false)] + #[case::nat64("[64:ff9b::1]", false)] + #[case::siit("[::ffff:0:0:1]", false)] + #[case::discard_v6("[100::1]", false)] + #[case::public_v4("8.8.8.8", true)] + #[case::public_v6("[2001::1]", true)] + #[case::link_local_64("[fe80::1]", false)] + #[case::link_local_10("[fe80:40::1]", false)] + fn reachable(#[case] s: &str, #[case] expected: bool) { + assert_eq!(addr(s).is_reachable(), expected); + } + + #[rstest] + fn reachable_ipv4_mapped_v6() { + // AddrV1 treats ::ffff:x.x.x.x as IPv4, test via AddrV2. + let a = AddrV2::from_str("[::ffff:8.8.8.8]").unwrap(); + assert!(!a.is_reachable()); + } } diff --git a/pkgs/primitives/src/types/netinfo.rs b/pkgs/primitives/src/types/netinfo.rs index 9d3fa545..84e1ffc6 100644 --- a/pkgs/primitives/src/types/netinfo.rs +++ b/pkgs/primitives/src/types/netinfo.rs @@ -10,7 +10,7 @@ use super::netaddr::{is_bad_port, NetAddr}; use super::{AddrV2, NetAddrError, ServiceV1, ServiceV2}; use crate::prelude::*; -use dash_types::codec::{self, BaseCodec, Checkable, DecodeError, NumCodec}; +use dash_types::codec::{self, BaseCodec, Checkable, DecodeError, EncodeBuf, NumCodec}; use dash_types::{impl_num, impl_type}; use core::fmt; @@ -220,7 +220,7 @@ impl BaseCodec for NIEntry { } } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { match self { Self::Service(svc) => { NIEntryCode::Service.to_base().encode(buf); @@ -365,7 +365,7 @@ impl BaseCodec for NetInfoV2 { Ok(Self { version, entries }) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { self.version.encode(buf); codec::write_compact_size(self.entries.len(), buf); for (purpose, group) in &self.entries { @@ -532,7 +532,7 @@ impl BaseCodec for NetInfoV1 { Ok(Self(ServiceV1::decode(data)?)) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { self.0.encode(buf); } } diff --git a/pkgs/types/src/codec.rs b/pkgs/types/src/codec.rs index 589a94df..25b91b39 100644 --- a/pkgs/types/src/codec.rs +++ b/pkgs/types/src/codec.rs @@ -155,13 +155,80 @@ pub fn read_compact_size(data: &mut &[u8], limit: usize) -> Result { + fn push(&mut self, byte: u8) { + self.push(byte); + } + + fn extend_from_slice(&mut self, data: &[u8]) { + self.extend_from_slice(data); + } +} + +/// Fixed-size encode buffer backed by `[u8; N]`. +/// +/// # Panics +/// +/// Writing more than `N` bytes (via the [`EncodeBuf`] impl) panics +/// with an index-out-of-bounds. Callers must ensure the encoded +/// representation fits within the declared capacity. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct ArrayBuf { + buf: [u8; N], + len: usize, +} + +impl ArrayBuf { + /// Creates an empty buffer. + pub const fn new() -> Self { + Self { buf: [0u8; N], len: 0 } + } + + /// Returns the written bytes as a fixed array. + /// + /// # Panics + /// + /// Panics if exactly `N` bytes were not written. + pub fn into_array(self) -> [u8; N] { + assert!(self.len == N, "expected {N} bytes, wrote {}", self.len); + self.buf + } +} + +impl Default for ArrayBuf { + fn default() -> Self { + Self::new() + } +} + +impl EncodeBuf for ArrayBuf { + fn push(&mut self, byte: u8) { + self.buf[self.len] = byte; + self.len += 1; + } + + fn extend_from_slice(&mut self, data: &[u8]) { + self.buf[self.len..self.len + data.len()].copy_from_slice(data); + self.len += data.len(); + } +} + /// Encodes a `usize` as a CompactSize integer. -pub fn write_compact_size(value: usize, buf: &mut Vec) { +pub fn write_compact_size(value: usize, buf: &mut impl EncodeBuf) { write_compact_u64(value as u64, buf); } /// Encodes a `u64` as a CompactSize integer. -pub fn write_compact_u64(value: u64, buf: &mut Vec) { +pub fn write_compact_u64(value: u64, buf: &mut impl EncodeBuf) { match value { 0..=0xFC => buf.push(value as u8), 0xFD..=0xFFFF => { @@ -198,7 +265,7 @@ pub trait BaseCodec: Sized { fn decode(data: &mut &[u8]) -> Result; /// Encodes into the buffer. - fn encode(&self, buf: &mut Vec); + fn encode(&self, buf: &mut impl EncodeBuf); } impl BaseCodec for u8 { @@ -206,7 +273,7 @@ impl BaseCodec for u8 { Ok(take::<1>(data)?[0]) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { buf.push(*self); } } @@ -216,7 +283,7 @@ impl BaseCodec for i8 { Ok(take::<1>(data)?[0] as i8) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { buf.push(*self as u8); } } @@ -226,7 +293,7 @@ impl BaseCodec for u16 { take::<2>(data).map(Self::from_le_bytes) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { buf.extend_from_slice(&self.to_le_bytes()); } } @@ -236,7 +303,7 @@ impl BaseCodec for i16 { take::<2>(data).map(Self::from_le_bytes) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { buf.extend_from_slice(&self.to_le_bytes()); } } @@ -246,7 +313,7 @@ impl BaseCodec for u32 { take::<4>(data).map(Self::from_le_bytes) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { buf.extend_from_slice(&self.to_le_bytes()); } } @@ -256,7 +323,7 @@ impl BaseCodec for i32 { take::<4>(data).map(Self::from_le_bytes) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { buf.extend_from_slice(&self.to_le_bytes()); } } @@ -266,7 +333,7 @@ impl BaseCodec for u64 { take::<8>(data).map(Self::from_le_bytes) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { buf.extend_from_slice(&self.to_le_bytes()); } } @@ -276,7 +343,7 @@ impl BaseCodec for i64 { take::<8>(data).map(Self::from_le_bytes) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { buf.extend_from_slice(&self.to_le_bytes()); } } @@ -294,7 +361,7 @@ impl BaseCodec for bool { } } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { buf.push(u8::from(*self)); } } @@ -304,7 +371,7 @@ impl BaseCodec for [u8; N] { take::(data) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { buf.extend_from_slice(self); } } @@ -325,7 +392,7 @@ impl BaseCodec for Vec { Ok(items) } - fn encode(&self, buf: &mut Vec) { + fn encode(&self, buf: &mut impl EncodeBuf) { write_compact_size(self.len(), buf); for item in self { item.encode(buf); diff --git a/pkgs/types/src/hex.rs b/pkgs/types/src/hex.rs index 0cb56fef..65c3d96d 100644 --- a/pkgs/types/src/hex.rs +++ b/pkgs/types/src/hex.rs @@ -19,7 +19,7 @@ macro_rules! impl_bytes { $crate::codec::take::<$n>(data).map(|b| Self(b)) } - fn encode(&self, buf: &mut ::alloc::vec::Vec) { + fn encode(&self, buf: &mut impl $crate::codec::EncodeBuf) { buf.extend_from_slice(self.as_bytes()); } } diff --git a/pkgs/types/src/serialize.rs b/pkgs/types/src/serialize.rs index 2a37b23f..d661dabf 100644 --- a/pkgs/types/src/serialize.rs +++ b/pkgs/types/src/serialize.rs @@ -6,6 +6,7 @@ //! Reusable serde helpers for `#[serde(with = "...")]`. +// nosemgrep: use-pub-roots-only pub use crate::hex::serde as hex; /// UTF-8 serde for `Vec` fields that hold text. diff --git a/pkgs/types/src/uint.rs b/pkgs/types/src/uint.rs index f15e58a9..57536ddc 100644 --- a/pkgs/types/src/uint.rs +++ b/pkgs/types/src/uint.rs @@ -30,7 +30,7 @@ macro_rules! impl_num { }) } - fn encode(&self, buf: &mut ::alloc::vec::Vec) { + fn encode(&self, buf: &mut impl $crate::codec::EncodeBuf) { buf.extend_from_slice( &>::to_base(self) .to_le_bytes(), diff --git a/pyproject.toml b/pyproject.toml index bbad3125..faa6d302 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ target-version = "py311" select = [ "ANN", # flake8-annotations "B", # flake8-bugbear + "BLE", # flake8-blind-except "E", # pycodestyle errors "F", # pyflakes "I", # isort