diff --git a/Cargo.lock b/Cargo.lock index 280ede47d..0210d2af7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + [[package]] name = "arrayvec" version = "0.7.6" @@ -105,30 +111,6 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "byte-slice-cast" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bzip2-sys" version = "0.1.13+1.0.8" @@ -141,9 +123,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.57" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "jobserver", @@ -177,32 +159,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "const_format" -version = "0.2.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - [[package]] name = "either" version = "1.15.0" @@ -225,11 +181,17 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "ethnum" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" + [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "find-msvc-tools" @@ -238,44 +200,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] -name = "fixed-hash" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" -dependencies = [ - "byteorder", - "rand", - "rustc-hex", - "static_assertions", -] - -[[package]] -name = "funty" -version = "2.0.0" +name = "foldhash" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "getrandom" -version = "0.2.17" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "wasi", + "r-efi 5.3.0", + "wasip2", ] [[package]] name = "getrandom" -version = "0.3.4" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "wasip2", + "wasip3", ] [[package]] @@ -286,9 +238,24 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" @@ -312,33 +279,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" [[package]] -name = "impl-codec" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d40b9d5e17727407e55028eafc22b2dc68781786e6d7eb8a21103f5058e3a14" -dependencies = [ - "parity-scale-codec", -] - -[[package]] -name = "impl-trait-for-tuples" -version = "0.2.3" +name = "id-arena" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.17.0", + "serde", + "serde_core", ] [[package]] @@ -350,6 +305,12 @@ dependencies = [ "either", ] +[[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" @@ -360,11 +321,17 @@ dependencies = [ "libc", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" -version = "0.2.183" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libloading" @@ -393,9 +360,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.25" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52f4c29e2a68ac30c9087e1b772dc9f44a2b66ed44edf2266cf2be9b03dafc1" +checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" dependencies = [ "cc", "pkg-config", @@ -408,6 +375,12 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + [[package]] name = "lz4-sys" version = "1.11.1+lz4-1.10.0" @@ -447,68 +420,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "parity-scale-codec" -version = "3.7.5" +name = "pkg-config" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" -dependencies = [ - "arrayvec", - "bitvec", - "byte-slice-cast", - "const_format", - "impl-trait-for-tuples", - "parity-scale-codec-derive", - "rustversion", - "serde", -] +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] -name = "parity-scale-codec-derive" -version = "3.7.5" +name = "prettyplease" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ - "proc-macro-crate", "proc-macro2", - "quote", "syn", ] -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "primitive-types" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721a1da530b5a2633218dc9f75713394c983c352be88d2d7c9ee85e2c4c21794" -dependencies = [ - "fixed-hash", - "impl-codec", - "uint", -] - -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit", -] - [[package]] name = "proc-macro2" version = "1.0.106" @@ -534,40 +460,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" +name = "r-efi" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "regex" @@ -610,15 +506,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustc-hex" -version = "2.1.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustix" @@ -633,12 +523,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - [[package]] name = "secp256k1" version = "0.29.1" @@ -658,6 +542,12 @@ dependencies = [ "cc", ] +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.228" @@ -688,16 +578,23 @@ dependencies = [ ] [[package]] -name = "shlex" -version = "1.3.0" +name = "serde_json" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] [[package]] -name = "static_assertions" -version = "1.1.0" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "syn" @@ -710,12 +607,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "tempfile" version = "3.27.0" @@ -723,7 +614,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys", @@ -749,60 +640,18 @@ dependencies = [ "syn", ] -[[package]] -name = "toml_datetime" -version = "1.1.0+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.25.8+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" -dependencies = [ - "indexmap", - "toml_datetime", - "toml_parser", - "winnow", -] - -[[package]] -name = "toml_parser" -version = "1.1.0+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" -dependencies = [ - "winnow", -] - [[package]] name = "trust" version = "0.1.0" dependencies = [ "bitcoin", + "ethnum", "hex", - "primitive-types", "rocksdb", "tempfile", "thiserror", ] -[[package]] -name = "uint" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "unicode-ident" version = "1.0.24" @@ -821,12 +670,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "wasi" -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.2+wasi-0.2.9" @@ -836,6 +679,49 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "windows-link" version = "0.2.1" @@ -851,50 +737,100 @@ dependencies = [ "windows-link", ] -[[package]] -name = "winnow" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" -dependencies = [ - "memchr", -] - [[package]] name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] [[package]] -name = "wyz" -version = "0.5.1" +name = "wit-bindgen-core" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ - "tap", + "anyhow", + "heck", + "wit-parser", ] [[package]] -name = "zerocopy" -version = "0.8.47" +name = "wit-bindgen-rust" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ - "zerocopy-derive", + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", ] [[package]] -name = "zerocopy-derive" -version = "0.8.47" +name = "wit-bindgen-rust-macro" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" dependencies = [ + "anyhow", + "prettyplease", "proc-macro2", "quote", "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", ] +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + [[package]] name = "zstd-sys" version = "2.0.16+zstd.1.5.7" diff --git a/crates/trust/Cargo.toml b/crates/trust/Cargo.toml index 01c5903ea..f06f0d6ea 100644 --- a/crates/trust/Cargo.toml +++ b/crates/trust/Cargo.toml @@ -6,8 +6,8 @@ license = "MIT" [dependencies] bitcoin = "=0.32.8" +ethnum = "=1.5.2" hex = "=0.4.3" -primitive-types = "=0.14.0" rocksdb = "=0.24.0" thiserror = "=2.0.18" diff --git a/crates/trust/src/trust.rs b/crates/trust/src/trust.rs index 84fcb2392..d0a56b210 100644 --- a/crates/trust/src/trust.rs +++ b/crates/trust/src/trust.rs @@ -1,10 +1,11 @@ -use crate::trust_db::{TrustDB, TrustDBError, TrustDBTable::MetadataCF}; +use crate::trust_db::{ + DB_METADATA_KEY_UPSTREAM_STATE_ID, TrustDB, TrustDBError, TrustDBTable::MetadataCF, +}; use bitcoin::Network; use std::path::Path; +use std::sync::Arc; use thiserror::Error; -static DB_METADATA_KEY_UPSTREAM_STATE_ID: &str = "upstream_state_id"; - #[derive(Error, Debug)] pub enum TrustError { #[error("TrustDB error: {0}")] @@ -40,7 +41,7 @@ impl TrustConfig { // XXX remove when fields are used #[allow(dead_code)] pub struct Trust { - db: TrustDB, + db: Arc, cfg: TrustConfig, network: Network, } @@ -62,13 +63,7 @@ impl Trust { } pub fn set_upstream_state_id(&self, upstream_state_id: &[u8; 32]) { - self.db - .put( - &MetadataCF, - DB_METADATA_KEY_UPSTREAM_STATE_ID, - upstream_state_id, - ) - .expect("could not insert into metadata table: {e}") + self.db.set_upstream_state_id(upstream_state_id); } pub fn get_upstream_state_id(&self) -> Result<[u8; 32]> { @@ -91,12 +86,12 @@ impl Trust { /// Pass None for default chain genesis block. pub fn external_header_setup( &self, - genesis_override: Option<(&bitcoin::block::Header, u64, primitive_types::U256)>, + genesis_override: Option<(&bitcoin::block::Header, u64, ethnum::U256)>, upstream_state_id: &[u8; 32], ) -> Result<()> { let mut genesis = &bitcoin::blockdata::constants::genesis_block(self.network).header; let mut height = u64::MIN; - let mut diff = primitive_types::U256::zero(); + let mut diff = ethnum::U256::ZERO; if let Some(o) = genesis_override { (genesis, height, diff) = o } @@ -212,14 +207,7 @@ mod tests { let cfg = TrustConfig::new_default_config(tmp.unwrap()); let trust = Trust::new(cfg).unwrap(); - trust - .db - .put( - &MetadataCF, - DB_METADATA_KEY_UPSTREAM_STATE_ID, - b"this_is_a_test_for_this_test_yes", - ) - .unwrap(); + trust.set_upstream_state_id(b"this_is_a_test_for_this_test_yes"); match trust.get_upstream_state_id() { Ok(u) => assert_eq!(u, *b"this_is_a_test_for_this_test_yes"), @@ -236,23 +224,9 @@ mod tests { let cfg = TrustConfig::new_default_config(tmp.unwrap()); let trust = Trust::new(cfg).unwrap(); - trust - .db - .put( - &MetadataCF, - DB_METADATA_KEY_UPSTREAM_STATE_ID, - b"this_is_a_test_for_this_test_yes", - ) - .unwrap(); - - trust - .db - .put( - &MetadataCF, - DB_METADATA_KEY_UPSTREAM_STATE_ID, - b"this_is_a_test_for_this_test____", - ) - .unwrap(); + trust.set_upstream_state_id(b"this_is_a_test_for_this_test_yes"); + + trust.set_upstream_state_id(b"this_is_a_test_for_this_test____"); match trust.get_upstream_state_id() { Ok(u) => assert_eq!(u, *b"this_is_a_test_for_this_test____"), @@ -321,7 +295,7 @@ mod tests { let header = &bitcoin::blockdata::constants::genesis_block(bitcoin::Network::Testnet).header; let height = 12345; - let diff = primitive_types::U256::from(99999); + let diff = ethnum::U256::from(99999_u32); let genesis_override = Some((header, height, diff)); @@ -372,7 +346,7 @@ mod tests { let genesis = &bitcoin::blockdata::constants::genesis_block(bitcoin::Network::Bitcoin).header; - let genesis_override = Some((genesis, 0, primitive_types::U256::zero())); + let genesis_override = Some((genesis, 0, ethnum::U256::ZERO)); match trust.external_header_setup(genesis_override, usi) { Err(e) => match e { diff --git a/crates/trust/src/trust_db.rs b/crates/trust/src/trust_db.rs index 171202bc0..fe45e645f 100644 --- a/crates/trust/src/trust_db.rs +++ b/crates/trust/src/trust_db.rs @@ -3,18 +3,19 @@ use self::TrustDBTable::*; use bitcoin::block::Header; use bitcoin::consensus::{Decodable, Encodable}; use bitcoin::hashes::Hash; + +use ethnum::U256; use hex::encode; -use primitive_types::U256; use rocksdb::{ColumnFamilyDescriptor, ColumnFamilyRef, OptimisticTransactionDB, Options}; use std::cmp::Ordering; use std::path::Path; use std::slice::Iter; -use std::sync::Arc; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; use thiserror::Error; const BHS_CANONICAL_TIP_KEY: &str = "canonicaltip"; const BHS_GENESIS_KEY: &str = "genesis"; +pub static DB_METADATA_KEY_UPSTREAM_STATE_ID: &str = "upstream_state_id"; #[derive(Error, Debug)] pub enum TrustDBError { @@ -26,6 +27,12 @@ pub enum TrustDBError { Duplicate(String), #[error("Genesis already exists with block hash: {0}")] GenesisExists(String), + #[error("Invalid parameters: {0}")] + InvalidParams(String), + #[error("Invalid tip: {0}")] + InvalidTip(String), + #[error("Dangling header: {0}")] + DanglingChild(String), #[error("{0}")] Other(String), } @@ -52,7 +59,7 @@ impl From<&EncodedHeader> for BlockHeader { let header = Header::consensus_decode(&mut &enc[8..88]).expect("encoded header should be decodable"); let height = u64::from_be_bytes(enc[..8].try_into().unwrap()); - let difficulty = U256::from_big_endian(&enc[88..]); + let difficulty = U256::from_be_bytes(enc[88..].try_into().unwrap()); Self { hash: header.block_hash(), height, @@ -79,7 +86,7 @@ impl From<&BlockHeader> for EncodedHeader { enc[8..88].copy_from_slice(&hdrb); // encode diff - let diffb = value.difficulty.to_big_endian(); + let diffb = value.difficulty.to_be_bytes(); enc[88..].copy_from_slice(&diffb); enc @@ -103,6 +110,23 @@ impl InsertType { } } +#[derive(Debug, PartialEq)] +pub enum RemoveType { + ChainDescend, // Removal walked the canonical chain backwards, but existing chain is still canonical + ChainFork, // Removal walked canonical chain backwards far enough that another chain is now canonical + ForkDescend, // Removal walked a non-canonical chain backwards, no change to canonical chain remaining canonical +} + +impl RemoveType { + pub fn as_str(&self) -> &str { + match self { + RemoveType::ChainDescend => "canonical chain descend", + RemoveType::ChainFork => "canonical descend changed canonical", + RemoveType::ForkDescend => "fork chain descend", + } + } +} + pub enum TrustDBTable { HeadersCF, MetadataCF, @@ -127,12 +151,12 @@ impl TrustDBTable { pub type BatchHook<'a> = (&'a TrustDBTable, &'a [u8], &'a [u8]); pub struct TrustDB { - db: Arc, - genesis_block_mtx: Mutex, + db: OptimisticTransactionDB, + update_mtx: Mutex<()>, } impl TrustDB { - pub fn open>(path: P) -> Result { + pub fn open>(path: P) -> Result> { let path = path.as_ref(); let mut opts = Options::default(); opts.create_if_missing(true); @@ -151,10 +175,10 @@ impl TrustDB { cfs.push(ColumnFamilyDescriptor::new(b.as_str(), cf_opts)); } let db = OptimisticTransactionDB::open_cf_descriptors(&opts, path, cfs)?; - Ok(Self { - db: Arc::new(db), - genesis_block_mtx: Mutex::new(bitcoin::BlockHash::all_zeros()), - }) + Ok(Arc::new(Self { + db, + update_mtx: Mutex::new(()), + })) } fn get_cf(&self, cf: &TrustDBTable) -> ColumnFamilyRef<'_> { @@ -163,7 +187,7 @@ impl TrustDB { .unwrap_or_else(|| panic!("CF '{}' must exist after successful open", cf.as_str())) } - pub fn put(&self, cf: &TrustDBTable, key: K, value: V) -> Result<()> + fn put(&self, cf: &TrustDBTable, key: K, value: V) -> Result<()> where K: AsRef<[u8]>, V: AsRef<[u8]>, @@ -181,7 +205,11 @@ impl TrustDB { Err(TrustDBError::NotFound(encode(key_ref))) } - pub fn del>(&self, cf: &TrustDBTable, key: K) -> Result<()> { + // we use this function only in tests at the time of writing this, + // so rust thinks it's "unused". allow it to be "unused" (even though + // it is used) for now + #[allow(unused)] + fn del>(&self, cf: &TrustDBTable, key: K) -> Result<()> { self.db.delete_cf(self.get_cf(cf), key.as_ref())?; Ok(()) } @@ -212,6 +240,17 @@ impl TrustDB { (height, hash) } + pub fn set_upstream_state_id(&self, upstream_state_id: &[u8; 32]) { + let _lock = self.update_mtx.lock().unwrap(); + + self.put( + &MetadataCF, + DB_METADATA_KEY_UPSTREAM_STATE_ID, + upstream_state_id, + ) + .expect("could not insert into metadata table: {e}") + } + pub fn block_header_genesis_insert( &self, header: &Header, @@ -226,25 +265,14 @@ impl TrustDB { // if two threads were to perform these steps at the same time we may // get: 1 1 2 2. if this is the case then we would insert two genesis // blocks - let mut locked_genesis_block_hash = self.genesis_block_mtx.lock().unwrap(); + let _lock = self.update_mtx.lock().unwrap(); let bhash = header.block_hash(); if self.has(&HeadersCF, bhash) { return Err(TrustDBError::Duplicate(bhash.to_string())); } - // ensure that a different genesis block does not exist in the db - // with a few strategies - - // 1. if we have the existing genesis block hash stored in the mutex, - // then we know we have an existing genesis block inserted - if *locked_genesis_block_hash != bitcoin::BlockHash::all_zeros() { - return Err(TrustDBError::GenesisExists( - locked_genesis_block_hash.to_string(), - )); - } - - // 2. this may be our first time entering this function, so the mutex + // this may be our first time entering this function, so the mutex // would not have a block hash stored within it, check the database let existing_genesis = self.get(&HeadersCF, BHS_GENESIS_KEY); match existing_genesis { @@ -268,18 +296,14 @@ impl TrustDB { let existing_genesis_block_header = BlockHeader::from(&existing_genesis_block_header_encoded); - // since we found an existing genesis, store its hash in the - // mutex for future calls if needed - *locked_genesis_block_hash = existing_genesis_block_header.hash; - return Err(TrustDBError::GenesisExists( existing_genesis_block_header.hash.to_string(), )); } } - let mut cdiff = U256::from_big_endian(&header.work().to_be_bytes()); - if !diff.is_zero() { + let mut cdiff = U256::from_be_bytes(header.work().to_be_bytes()); + if diff != U256::ZERO { cdiff = diff; } @@ -302,10 +326,6 @@ impl TrustDB { bh_batch.commit()?; - // if we have successfully inserted a genesis block, store it in the - // mutex - *locked_genesis_block_hash = header.block_hash(); - Ok(()) } @@ -334,8 +354,65 @@ impl TrustDB { .as_ref() .try_into() .expect("heighthash key should be valid size"); + let (_, hash) = TrustDB::key_to_height_hash(&enc); - bhs.push(self.block_header_by_hash(hash)?); + let res = self.block_header_by_hash(hash); + match res { + Err(TrustDBError::NotFound(_)) => { + panic!("height hash key with hash {hash} not in headers table"); + } + Err(e) => return Err(e), + Ok(v) => bhs.push(v), + } + } + if bhs.is_empty() { + return Err(TrustDBError::NotFound(height.to_string())); + } + Ok(bhs) + } + + /// `block_header_by_hash_tx` emulates the behavior of `block_header_by_hash` + /// within a transaction's snapshot view of the database. + fn block_header_by_hash_tx( + &self, + tx: &rocksdb::Transaction, + hash: bitcoin::BlockHash, + ) -> Result { + let full_header = tx.get_cf(self.get_cf(&HeadersCF), hash)?; + let bhb = match full_header { + Some(val) => val.try_into().expect("header data should be valid size"), + None => return Err(TrustDBError::NotFound(hash.to_string())), + }; + Ok(BlockHeader::from(&bhb)) + } + + /// `block_headers_by_height_tx` emulates the behavior of + /// `block_headers_by_height` within a transaction's + /// snapshot view of the database. + fn block_headers_by_height_tx( + &self, + tx: &rocksdb::Transaction, + height: u64, + ) -> Result> { + let h = height.to_be_bytes(); + let it = tx.prefix_iterator_cf(self.get_cf(&HeightHashCF), h); + let mut bhs: Vec = Vec::new(); + for item in it { + let (hh, _) = item?; + let enc: [u8; Self::HH_KEY_SIZE] = hh + .as_ref() + .try_into() + .expect("heighthash key should be valid size"); + + let (_, hash) = TrustDB::key_to_height_hash(&enc); + let res = self.block_header_by_hash(hash); + match res { + Err(TrustDBError::NotFound(_)) => { + panic!("height hash key with hash {hash} not in headers table"); + } + Err(e) => return Err(e), + Ok(v) => bhs.push(v), + } } if bhs.is_empty() { return Err(TrustDBError::NotFound(height.to_string())); @@ -355,10 +432,15 @@ impl TrustDB { hooks: &[BatchHook], ) -> Result<(InsertType, BlockHeader, BlockHeader, usize)> { if headers.is_empty() { - return Err(TrustDBError::Other( - "block headers insert: invalid".to_string(), + return Err(TrustDBError::InvalidParams( + "block headers insert: empty header set".to_string(), )); } + + // Prevent other calls that change the inner state of the DB + // from running concurrently. + let _guard = self.update_mtx.lock().unwrap(); + let batch = self.db.transaction(); // Ensure we can connect these blockheaders. This also obtains the @@ -379,9 +461,7 @@ impl TrustDB { let headers = &headers[x..]; if headers.is_empty() { - return Err(TrustDBError::Other( - "block headers insert: duplicate".to_string(), - )); + return Err(TrustDBError::Duplicate("headers set".to_string())); } // Ensure contiguity of new headers. @@ -389,7 +469,7 @@ impl TrustDB { for (i, rbh) in headers.iter().enumerate() { let hash = rbh.block_hash(); if rbh.prev_blockhash != prev_bhh { - return Err(TrustDBError::Other(format!( + return Err(TrustDBError::InvalidParams(format!( "header with hash {} at index {} does not connect to \ previous header with hash {} at index {}", hash, @@ -402,7 +482,7 @@ impl TrustDB { } let mut wbh = &headers[0]; - let pbh = self.block_header_by_hash(wbh.prev_blockhash)?; + let pbh = self.block_header_by_hash_tx(&batch, wbh.prev_blockhash)?; let bbh: EncodedHeader = batch .get_cf(self.get_cf(&HeadersCF), BHS_CANONICAL_TIP_KEY)? @@ -426,7 +506,7 @@ impl TrustDB { // pre set values because we start with previous value height += 1; cdiff = cdiff - .checked_add(U256::from_big_endian(&wbh.work().to_be_bytes())) + .checked_add(U256::from_be_bytes(wbh.work().to_be_bytes())) .ok_or(TrustDBError::Other( "work accumulation overflow".to_string(), ))?; @@ -483,29 +563,365 @@ impl TrustDB { Ok((it, *cbh, lbh, headers.len())) } + /// BlockHeadersRemove decodes and removes the passed blockheaders into the + /// database. Additionally it updates the canonical height/hash. + /// On return it informs the caller about the removal type which is self-evident + /// from the headers and post-removal canonical tip passed in as a convenience, + /// as well as the header the batch of headers was removed from which is now + /// the tip of that particular chain. + /// + /// The caller of this function must pass in the tipAfterRemoval which they + /// *know* to be the correct canonical tip after removal of the passed-in blocks. + /// This is critical to ensure that an operator of a TBC instance in External + /// Header mode can set a specific header as canonical in the event that removal + /// of header(s) results in a split tip where two or more headers are all at + /// the highest cumulative difficulty and TBC would otherwise have to choose one + /// without knowing what the upstream consumer considered canonical. + /// + /// This function is only intended to be used on a database which is used by + /// an instance of TBC running in External Header mode, where the header consensus + /// view needs to be walked back to account for information no longer being + /// known by an upstream consumer. For example, an L2 reorg could remove Bitcoin + /// consensus information from the L2 protocol's knowledge, so the External Header + /// mode TBC instance needs to represent Bitcoin consensus knowledge of the L2 + /// protocol at the older tip height so that the full indexed TBC instance can + /// be moved to the correct indexed state to return queries that are consistent + /// with the L2's view of Bitcoin at that previous L2 block, otherwise L2 nodes + /// that processed the reorg versus L2 nodes that were always on the reorged-onto + /// chain could have a state divergence since queries against TBC would not be + /// deterministic between both types of nodes. + /// + /// All of the headers passed to the remove function must exist in the database. + /// + /// Headers must be ordered from lowest height to highest and must be contiguous, + /// meaning if header 0 is at height H, header N-1 must be at height H+N and for + /// each header N its previous block hash must be the hash of header N-1. + /// + /// The last header in the array must be the current tip of its chain (whether + /// canonical or fork); in other words the database must not have knowledge of + /// any headers who reference the last header as their previous block as this removal + /// would result in a dangling orphan chain segment in the database. A block can have + /// multiple children and calling this function with non-contiguous (non-linear) + /// blocks is not allowed, but this is correct behavior as removing chunks of + /// headers in the reverse order they were originally added will ensure that + /// a header being removed only has a maximum of one child (which must be included + /// in the headers passed to this function). + /// + /// For example given a chain: + /// + /// _______/-[2a]-[3a]-[4a] + /// + /// [ G]-[ 1]-[2b]-[3b]-[4b]-[5b] + /// + /// ____________\-[3c]-[4c]-[5c]-[6c] + /// + /// Where the tip is [6c], the next removal could for example be: + /// + /// [3a]-[4a] + /// [3b]-[4b]-[5b] + /// [5c]-[6c] (and pass in tipAfterRemoval=[5b]) + /// + /// But the next removal could not for example be: + /// + /// [2a]-[3a] // Leaves [4a] dangling + /// [2b]-[3b]-[4b]-[5b] // Leaves "c" fork dangling + /// + /// The upstream user of a TBC instance in External Header mode is expected + /// to always remove chunks of headers in the opposite order they were + /// originally added. While this is not checked explicitly, failure to do so + /// can result in these types of dangling chain scenarios. In the above example, + /// block [2b] must have been added at or before the time of adding [3b] and [3c]. + /// + /// It could have either been: + /// Update #1: ADD [2b]-[3b]-[4b]-[5b] + /// Update #2: ADD [3c]-[4c]-[5c]-[6c] + /// OR + /// Update #1: ADD [2b]-[3c]-[4c]-[5c]-[6c] + /// Update #2: ADD [3b]-[4b]-[5b] + /// (Or some similar order where some of the higher b/c blocks were added back + /// and forth between the chains or split into multiple smaller updates.) + /// + /// Assuming the upstream caller needs to remove the entire b and c chains: + /// + /// If it was the first order, then we would expect upstream caller to first + /// remove [3c]-...-[6c] (undo update #2), and then remove [2b]-...-[5b] (undo + /// update #1), which would never leave a chain dangling. + /// + /// Similarly, if it was the second order, then we would expect upstream caller + /// to first remove [3b]-...-[5b] (undo update #2), and then remove [2b]-...-[6c] + /// (undo update #1) which would also never leave a chain dangling. + /// + /// If the upstream caller removed [2b]-...-[5b] first, then they did not + /// remove headers in the same order that they added them, because it would + /// have been impossible to originally add [2b] after adding [3c]. + /// + /// Calling this function with the incorrect tipAfterRemoval WILL FAIL as that + /// indicates incorrect upstream behavior. + /// + /// If any of the above requirements are not true, this function will return + /// an error. If this function returns an error, no changes have been made to + /// the underlying database state as all validity checks are done before db + /// modifications are applied. + /// + /// If an upstreamCursor is provided, it is updated atomically in the database + /// along with the state transition of removing the block headers. + pub fn block_headers_remove( + &self, + headers: &[Header], + tip_after_removal: &Header, + hooks: &[BatchHook], + ) -> Result<(RemoveType, BlockHeader)> { + if headers.is_empty() { + return Err(TrustDBError::InvalidParams( + "block headers remove: empty header set".to_string(), + )); + } + + // Ensure contiguity of headers. + let mut prev_bhh = headers[0].prev_blockhash; + for (i, rbh) in headers.iter().enumerate() { + let hash = rbh.block_hash(); + if rbh.prev_blockhash != prev_bhh { + return Err(TrustDBError::InvalidParams(format!( + "block headers remove: header with hash {} at index {} does \ + not connect to previous header with hash {} at index {}", + hash, + i, + prev_bhh, + i - 1 + ))); + } + prev_bhh = hash + } + + // Prevent other calls that change the inner state of the DB + // from running concurrently. + let _guard = self.update_mtx.lock().unwrap(); + + let batch = self.db.transaction(); + + // Get current canonical tip for later use + let origin_tip = batch.get_cf(self.get_cf(&HeadersCF), BHS_CANONICAL_TIP_KEY)?; + let bhb = match origin_tip { + Some(val) => val + .try_into() + .expect("canonical tip data should be valid size"), + None => return Err(TrustDBError::NotFound("canonical tip".to_string())), + }; + let origin_tip = BlockHeader::from(&bhb); + + let headers_parsed = headers; + + // Looking up each full header (with height and cumulative difficulty) + // in the next check; store so that later we have the data to create deletion + // keys. + let mut full_headers: Vec = vec![]; + // Check that each header exists in the database, and that no header + // to remove has a child unless that child is also going to be removed; + // no dangling chains will be left. Also check that none of the blocks + // to be removed match the tip the caller wants to be canonical after + // the removal. + let tip_after_removal_hash = tip_after_removal.block_hash(); + for (i, header_to_check) in headers_parsed.iter().enumerate() { + let hash = header_to_check.block_hash(); + + // Ensure that the header which should be canonical after removal is not one + // of the blocks to remove + if tip_after_removal_hash == hash { + return Err(TrustDBError::InvalidParams(format!( + "block headers remove: cannot remove \ + header with hash {hash} when that is \ + supposed to be the tip after removal" + ))); + } + + // Get full header that has height in it for the block to remove we are checking + let full_header = self.block_header_by_hash_tx(&batch, hash)?; + + // Save the full header from database (with height and cumulative difficulty) + full_headers.push(full_header); + let next_height = full_header.height + 1; + + // Get all headers from the database that could possibly be children + let res = self.block_headers_by_height_tx(&batch, next_height); + let potential_children = match res { + Err(TrustDBError::NotFound(_)) => continue, + Err(e) => return Err(e), + Ok(v) => v, + }; + + // Check all potential children. If one has our header to remove's hash as their + // previous block, then make sure it is in the removal list. Two or more cannot + // be in our removal list because they would have failed contiguous check prior. + for to_check in potential_children { + if to_check.header.prev_blockhash != hash { + // Not a child of header to remove + continue; + } + + // This is a child of the header we are going to remove, make sure it is + // also going to be removed. + if i == (headers_parsed.len() - 1) { + // We do not have another header in our removal list, meaning it would + // be left dangling. + return Err(TrustDBError::DanglingChild(format!( + "want to remove header with hash {} but it is the last \ + header in our removal list, and database has a child \ + header with hash {}", + hash, to_check.hash + ))); + } + + // This check will always fail if there are two children which claim the + // current header as a child, as one of them will not match the next + // header to remove, which is the only block which could be the removed + // child. + let next = headers_parsed[i + 1].block_hash(); + if next != to_check.hash { + // The header of the confirmed child does not match the next header to + // remove, meaning it would be left dangling. + return Err(TrustDBError::DanglingChild(format!( + "want to remove header with hash {}, but database has a \ + child header with hash {}", + hash, to_check.hash + ))); + } + } + } + + // Ensure that the tip which the caller claims should be canonical after the + // removal is a valid block in the database. + let tip_after_removal_from_db = self + .block_header_by_hash_tx(&batch, tip_after_removal_hash) + .map_err(|e| { + TrustDBError::NotFound(format!( + "block headers remove: cannot find tip after removal header \ + with hash {} in database: {}", + tip_after_removal_hash, e + )) + })?; + + for (i, rbh) in full_headers.iter().enumerate() { + // Check that the raw header we retrieved from the database matches the + // header we expected to move as an additional sanity check. + let expected_hash = headers_parsed[i].block_hash(); + if expected_hash != rbh.header.block_hash() { + panic!( + "block headers remove: unexpected internal error, header with hash \ + {} at position {} in headers to remove does not match header with \ + hash {} retrieved from db", + expected_hash, + i, + rbh.header.block_hash() + ); + } + } + + // Insert each block header deletion into the batch (for header itself and + // height-header association) + for (i, rbh) in headers_parsed.iter().enumerate() { + // Delete header i + let bhash = rbh.block_hash(); + let fh = full_headers[i]; + batch.delete_cf(self.get_cf(&HeadersCF), bhash)?; + + // Delete height mapping for header i + let hh_key = TrustDB::height_hash_to_key(fh.height, bhash); + batch.delete_cf(self.get_cf(&HeightHashCF), hh_key)?; + } + + // Check if proposed tip after removal has children. + let next_height = tip_after_removal_from_db.height + 1; + let res = self.block_headers_by_height_tx(&batch, next_height); + match res { + Err(TrustDBError::NotFound(_)) => (), + Err(e) => return Err(e), + Ok(potential_children) => { + for to_check in potential_children { + if to_check.header.prev_blockhash == tip_after_removal_hash { + // Expected tip has children, so cannot be the actual tip. + return Err(TrustDBError::InvalidTip( + "block headers remove: passed in \ + tip after removal has children" + .to_string(), + )); + } + } + } + }; + + // Insert updated canonical tip after removal of the provided block headers + let tip = BlockHeader { + hash: tip_after_removal_from_db.hash, + difficulty: tip_after_removal_from_db.difficulty, + header: *tip_after_removal, + height: tip_after_removal_from_db.height, + }; + let tip_enc: EncodedHeader = (&tip).into(); + batch.put_cf(self.get_cf(&HeadersCF), BHS_CANONICAL_TIP_KEY, tip_enc)?; + + // Get parent block from database + let ggp = headers_parsed[0].prev_blockhash; + let res = self.block_header_by_hash_tx(&batch, ggp); + let parent_to_removal_set = res.map_err(|e| { + TrustDBError::NotFound(format!( + "block headers remove: cannot find previous header \ + (with hash {}) to lowest header removed (with hash {}) \ + in database: {}", + ggp, + headers_parsed[0].block_hash(), + e + )) + })?; + + let origin_tip_hash = origin_tip.hash; + let heaviest_removed_hash = headers_parsed[headers_parsed.len() - 1].block_hash(); + + let removal_type: RemoveType; + if tip_after_removal_hash == parent_to_removal_set.hash { + // Canonical tip set by caller is the parent to the blocks removed + removal_type = RemoveType::ChainDescend; + } else if tip_after_removal_hash == origin_tip_hash { + // Canonical tip did not change, meaning blocks we removed were on a non-canonical chain + removal_type = RemoveType::ForkDescend; + } else if origin_tip_hash == heaviest_removed_hash { + // The original canonical tip was a block we removed, but parent to removal set is + // not the new canonical per first condition, therefore we descended the canonical + // chain far enough that a previous fork is now canonical + removal_type = RemoveType::ChainFork; + } else { + // This should never happen, one of the above three conditions must be true. + // Do this before the end of function so we don't apply database changes. + return Err(TrustDBError::Other( + "none of the chain geometry checks applies to this removal".to_string(), + )); + } + + // Call post hooks if set. + for hook in hooks { + batch.put_cf(self.get_cf(hook.0), hook.1, hook.2)?; + } + + batch.commit()?; + Ok((removal_type, parent_to_removal_set)) + } + // TODOs // Caching // Other Tables (missing blocks) // Debug logging } -impl Clone for TrustDB { - fn clone(&self) -> Self { - TrustDB { - db: Arc::clone(&self.db), - genesis_block_mtx: Mutex::new(bitcoin::BlockHash::all_zeros()), - } - } -} - #[cfg(test)] mod tests { + use std::vec; + use super::*; use bitcoin::BlockHash; use bitcoin::hashes::Hash; use tempfile::tempdir; - fn new_test_db() -> TrustDB { + fn new_test_db() -> Arc { let tmp = tempdir().expect("temp dir should have been created"); TrustDB::open(tmp.path()).expect("database should open") } @@ -598,7 +1014,7 @@ mod tests { let bh = &BlockHeader { hash: header.block_hash(), height: 99999, - difficulty: U256::from(12345), + difficulty: U256::from(12345_u32), header, }; @@ -616,16 +1032,16 @@ mod tests { assert_eq!(&enc[8..88], &expected_header); // check diff encoding - let expected_diff = bh.difficulty.to_big_endian(); + let expected_diff = bh.difficulty.to_be_bytes(); assert_eq!(&enc[88..], &expected_diff); } #[test] fn test_decode_block_header() { let test_table = vec![ - (0, U256::zero()), + (0, U256::ZERO), (u64::MAX, U256::MAX), - (99999, U256::from(12345)), + (99999, U256::from(12345_u32)), ]; let header = create_test_header(BlockHash::all_zeros(), 1); @@ -690,7 +1106,7 @@ mod tests { let db = new_test_db(); let header = create_test_header(BlockHash::all_zeros(), 1); let height = 0; - let diff = U256::from(12345); + let diff = U256::from(12345_u32); let mut res = db.block_header_genesis_insert(&header, height, diff); assert!(res.is_ok()); @@ -718,7 +1134,7 @@ mod tests { let db = new_test_db(); let header = create_test_header(BlockHash::all_zeros(), 1); let height = 0; - let diff = U256::zero(); // should use header's work + let diff = U256::ZERO; // should use header's work let res = db.block_header_genesis_insert(&header, height, diff); assert!(res.is_ok()); @@ -730,7 +1146,7 @@ mod tests { let stored = stored.unwrap(); let stored_array: [u8; BlockHeader::SIZE] = stored.try_into().unwrap(); let dec = BlockHeader::from(&stored_array); - let work = U256::from_big_endian(&header.work().to_be_bytes()); + let work = U256::from_be_bytes(header.work().to_be_bytes()); assert_ne!(work, diff); assert_eq!(dec.difficulty, work); } @@ -740,7 +1156,7 @@ mod tests { let db = new_test_db(); let header = create_test_header(BlockHash::all_zeros(), 1); let height = 99999; - let diff = U256::from(12345); + let diff = U256::from(12345_u32); // check if fails before insert let mut best = db.block_header_best(); @@ -767,7 +1183,7 @@ mod tests { let db = new_test_db(); let header = create_test_header(BlockHash::all_zeros(), 1); let height = 99999; - let diff = U256::from(12345); + let diff = U256::from(12345_u32); // check if fails before insert let mut res = db.block_header_by_hash(header.block_hash()); @@ -802,7 +1218,7 @@ mod tests { fn test_block_headers_by_height() { let db = new_test_db(); let height = 99999; - let diff = U256::from(12345); + let diff = U256::from(12345_u32); // check if fails before insert let mut retrieved = db.block_headers_by_height(height); @@ -845,7 +1261,7 @@ mod tests { let header1 = create_test_header(BlockHash::all_zeros(), 1); let mut header2 = create_test_header(BlockHash::all_zeros(), 2); header2.prev_blockhash = header1.block_hash(); - let diff = U256::from(12345); + let diff = U256::from(12345_u32); let insert = db.block_header_genesis_insert(&header1, height1, diff); assert!(insert.is_ok()); @@ -877,44 +1293,11 @@ mod tests { let db = new_test_db(); let correct_genesis = create_test_header(BlockHash::all_zeros(), 0); - db.block_header_genesis_insert(&correct_genesis, 0, U256::from(1)) + db.block_header_genesis_insert(&correct_genesis, 0, U256::from(1_u32)) .unwrap(); let incorrect_genesis = create_test_header(BlockHash::all_zeros(), 2); - let res = db.block_header_genesis_insert(&incorrect_genesis, 0, U256::from(2)); - - match res { - Err(TrustDBError::GenesisExists(val)) => { - assert_eq!(val, correct_genesis.block_hash().to_string()); - } - Err(e) => { - panic!("unexpected error: {}", e) - } - _ => panic!("expected an error"), - } - } - - #[test] - fn test_block_headers_insert_multiple_different_genesis_blocks_skip_mtx() { - let db = new_test_db(); - - let correct_genesis = create_test_header(BlockHash::all_zeros(), 0); - db.block_header_genesis_insert(&correct_genesis, 0, U256::from(1)) - .unwrap(); - - { - // unset mutex to ensure that we hit db, do this in its own scope - // to unset the mutex before we call block_header_genesis_insert - // again - let mut locked = db.genesis_block_mtx.lock().unwrap(); - if *locked == bitcoin::BlockHash::all_zeros() { - panic!("mtx value should have been set"); - } - *locked = bitcoin::BlockHash::all_zeros(); - } - - let incorrect_genesis = create_test_header(BlockHash::all_zeros(), 2); - let res = db.block_header_genesis_insert(&incorrect_genesis, 0, U256::from(2)); + let res = db.block_header_genesis_insert(&incorrect_genesis, 0, U256::from(2_u32)); match res { Err(TrustDBError::GenesisExists(val)) => { @@ -933,7 +1316,7 @@ mod tests { let mut genesis = create_test_header(BlockHash::all_zeros(), 0); genesis.prev_blockhash = bitcoin::BlockHash::hash(&[1, 2, 3]); - db.block_header_genesis_insert(&genesis, 0, U256::from(1)) + db.block_header_genesis_insert(&genesis, 0, U256::from(1_u32)) .unwrap(); let res = db.block_header_by_hash(genesis.block_hash()).unwrap(); @@ -941,7 +1324,7 @@ mod tests { let genesis_block_header = BlockHeader { hash: genesis.block_hash(), height: 0, - difficulty: U256::from(1), + difficulty: U256::from(1_u32), header: genesis, }; @@ -1025,7 +1408,7 @@ mod tests { let db = new_test_db(); let genesis = create_test_header(BlockHash::all_zeros(), 0); - db.block_header_genesis_insert(&genesis, 0, U256::from(1)) + db.block_header_genesis_insert(&genesis, 0, U256::from(1_u32)) .unwrap(); let headers = [genesis]; @@ -1051,7 +1434,7 @@ mod tests { let db = new_test_db(); let genesis = create_test_header(BlockHash::all_zeros(), 0); - db.block_header_genesis_insert(&genesis, 0, U256::from(1)) + db.block_header_genesis_insert(&genesis, 0, U256::from(1_u32)) .unwrap(); let h1 = create_test_header(genesis.block_hash(), 1); @@ -1123,7 +1506,7 @@ mod tests { let db = new_test_db(); let genesis = create_test_header(BlockHash::all_zeros(), 0); - db.block_header_genesis_insert(&genesis, 0, U256::from(1)) + db.block_header_genesis_insert(&genesis, 0, U256::from(1_u32)) .unwrap(); let h1 = create_test_header(genesis.block_hash(), 1); @@ -1146,7 +1529,7 @@ mod tests { let db = new_test_db(); let genesis = create_test_header(BlockHash::all_zeros(), 0); - db.block_header_genesis_insert(&genesis, 0, U256::from(1)) + db.block_header_genesis_insert(&genesis, 0, U256::from(1_u32)) .unwrap(); let h1 = create_test_header(genesis.block_hash(), 1); @@ -1172,7 +1555,7 @@ mod tests { let db = new_test_db(); let genesis = create_test_header(BlockHash::all_zeros(), 0); - db.block_header_genesis_insert(&genesis, 0, U256::from(1)) + db.block_header_genesis_insert(&genesis, 0, U256::from(1_u32)) .unwrap(); let h1 = create_test_header(genesis.block_hash(), 1); @@ -1182,4 +1565,452 @@ mod tests { let res = db.block_headers_insert(&headers, &[]); assert!(res.is_err(), "expected error") } + + #[test] + fn test_block_headers_remove_errors() { + struct Test { + name: &'static str, + pre_insert: Vec
, + to_remove: Vec
, + post_tip: Header, + error_type: TrustDBError, + } + + let genesis = create_test_header(BlockHash::all_zeros(), 0); + let h1 = create_test_header(genesis.block_hash(), 1); + let h2 = create_test_header(h1.block_hash(), 2); + let fake_hash = bitcoin::hashes::Hash::hash(&[42u8; 32]); + let fake_block = create_test_header(fake_hash, 2); + + let test_cases = vec![ + Test { + name: "empty set", + pre_insert: vec![], + to_remove: vec![], + post_tip: genesis, + error_type: TrustDBError::InvalidParams("".to_string()), + }, + Test { + name: "non-contiguous set", + pre_insert: vec![], + to_remove: vec![h1, fake_block], + post_tip: genesis, + error_type: TrustDBError::InvalidParams("".to_string()), + }, + Test { + name: "missing header", + pre_insert: vec![], + to_remove: vec![h1], + post_tip: genesis, + error_type: TrustDBError::NotFound("".to_string()), + }, + Test { + name: "invalid tip", + pre_insert: vec![h1], + to_remove: vec![h1], + post_tip: fake_block, + error_type: TrustDBError::NotFound("".to_string()), + }, + Test { + name: "tip in set", + pre_insert: vec![h1], + to_remove: vec![h1], + post_tip: h1, + error_type: TrustDBError::InvalidParams("".to_string()), + }, + Test { + name: "dangling children", + pre_insert: vec![h1, h2], + to_remove: vec![h1], + post_tip: genesis, + error_type: TrustDBError::DanglingChild("".to_string()), + }, + ]; + + for t in test_cases { + let db = new_test_db(); + db.block_header_genesis_insert(&genesis, 0, U256::from(1_u32)) + .unwrap(); + + if !t.pre_insert.is_empty() { + db.block_headers_insert(&t.pre_insert, &[]).unwrap(); + } + + let result = db.block_headers_remove(&t.to_remove, &t.post_tip, &[]); + assert!(result.is_err(), "test '{}' should have failed", t.name); + + let err = result.unwrap_err(); + + if std::mem::discriminant(&t.error_type) != std::mem::discriminant(&err) { + panic!("unexpected error: {}", err) + } + } + } + + #[test] + fn test_block_headers_remove_orphan() { + let db = new_test_db(); + let genesis = create_test_header(BlockHash::all_zeros(), 0); + db.block_header_genesis_insert(&genesis, 0, U256::from(1_u32)) + .unwrap(); + + let fake_parent = bitcoin::hashes::Hash::hash(&[88u8; 32]); + let orphan = create_test_header(fake_parent, 1); + + let orphan_bh = BlockHeader { + hash: orphan.block_hash(), + height: 1, + header: orphan, + difficulty: U256::from(1_u32), + }; + let enc: EncodedHeader = (&orphan_bh).into(); + db.put(&HeadersCF, orphan.block_hash(), enc).unwrap(); + + let hh_key = TrustDB::height_hash_to_key(1, orphan.block_hash()); + db.put(&HeightHashCF, hh_key, []).unwrap(); + + let result = db.block_headers_remove(&[orphan], &genesis, &[]); + match result { + Err(TrustDBError::NotFound(_)) => (), + Err(e) => panic!("unexpected error {e}"), + _ => panic!("expected error"), + } + } + + #[test] + fn test_block_headers_remove_invalid_tip() { + let db = new_test_db(); + let genesis = create_test_header(BlockHash::all_zeros(), 0); + db.block_header_genesis_insert(&genesis, 0, U256::from(1_u32)) + .unwrap(); + + let h1 = create_test_header(genesis.block_hash(), 1); + let h2 = create_test_header(h1.block_hash(), 20); + let h3 = create_test_header(h2.block_hash(), 30); + let headers = [h1, h2, h3]; + db.block_headers_insert(&headers, &[]).unwrap(); + + let fork1 = create_test_header(h1.block_hash(), 2); + let headers = [fork1]; + db.block_headers_insert(&headers, &[]).unwrap(); + + let res = db.block_headers_remove(&[h2, h3], &h1, &[]); + match res { + Err(TrustDBError::InvalidTip(_)) => (), + Err(e) => panic!("unexpected error: {e}"), + _ => panic!("expected error"), + } + } + + #[test] + fn test_block_headers_remove_types() { + use self::RemoveType::*; + + struct Test { + name: &'static str, + pre_insert_first: Vec
, + pre_insert_second: Vec
, + to_remove: Vec
, + post_tip: Header, + expected_type: RemoveType, + } + + let genesis = create_test_header(BlockHash::all_zeros(), 0); + let h1 = create_test_header(genesis.block_hash(), 1); + let h2 = create_test_header(h1.block_hash(), 2); + let h3 = create_test_header(h2.block_hash(), 3); + let fork1 = create_test_header_with_bits(h1.block_hash(), 100, 0x1d010000); + let fork2 = create_test_header_with_bits(fork1.block_hash(), 101, 0x1d010000); + + let test_cases = vec![ + Test { + name: "chain descend", + pre_insert_first: vec![h1, h2], + pre_insert_second: vec![], + to_remove: vec![h2], + post_tip: h1, + expected_type: ChainDescend, + }, + Test { + name: "chain fork", + pre_insert_first: vec![h1, h2, h3], + pre_insert_second: vec![fork1, fork2], + to_remove: vec![h2, h3], + post_tip: fork2, + expected_type: ChainFork, + }, + Test { + name: "fork descend", + pre_insert_first: vec![h1, h2], + pre_insert_second: vec![fork1], + to_remove: vec![fork1], + post_tip: h2, + expected_type: ForkDescend, + }, + ]; + + for t in test_cases { + let db = new_test_db(); + db.block_header_genesis_insert(&genesis, 0, U256::from(1_u32)) + .unwrap(); + + if !t.pre_insert_first.is_empty() { + db.block_headers_insert(&t.pre_insert_first, &[]).unwrap(); + } + + if !t.pre_insert_second.is_empty() { + db.block_headers_insert(&t.pre_insert_second, &[]).unwrap(); + } + + let result = db.block_headers_remove(&t.to_remove, &t.post_tip, &[]); + assert!(result.is_ok(), "test '{}' fail: {:?}", t.name, result.err()); + + let (rt, _) = result.unwrap(); + assert_eq!( + rt, t.expected_type, + "test '{}': expected {:?}, got {:?}", + t.name, t.expected_type, rt + ); + + let best = db.block_header_best().unwrap(); + assert_eq!( + best.hash, + t.post_tip.block_hash(), + "test '{}': invalid canonical tip", + t.name + ); + + for header in t.to_remove { + let result = db.block_header_by_hash(header.block_hash()); + assert!( + result.is_err(), + "test '{}': removed header {} still in database", + t.name, + header.block_hash() + ); + } + } + } + + #[test] + fn test_block_headers_remove_integration() { + let db = new_test_db(); + + let genesis = create_test_header(BlockHash::all_zeros(), 0); + db.block_header_genesis_insert(&genesis, 0, U256::from(1_u32)) + .unwrap(); + + let h1 = create_test_header(genesis.block_hash(), 1); + let h2 = create_test_header(h1.block_hash(), 2); + let h3 = create_test_header(h2.block_hash(), 3); + let h4 = create_test_header(h3.block_hash(), 4); + let headers = [h1, h2, h3, h4]; + db.block_headers_insert(&headers, &[]).unwrap(); + + let (remove_type, parent_header) = db.block_headers_remove(&[h3, h4], &h2, &[]).unwrap(); + + assert_eq!(remove_type, RemoveType::ChainDescend); + assert_eq!(parent_header.hash, h2.block_hash()); + + let best = db.block_header_best().unwrap(); + assert_eq!(best.hash, h2.block_hash()); + + for header in &[h3, h4] { + assert!(db.block_header_by_hash(header.block_hash()).is_err()); + } + + // Test hooks + let key = b"test_key"; + let value = b"test_value"; + let hooks = vec![(&MetadataCF, key.as_ref(), value.as_ref())]; + + let h5 = create_test_header(h2.block_hash(), 3); + db.block_headers_insert(&[h5], &[]).unwrap(); + db.block_headers_remove(&[h5], &h2, &hooks).unwrap(); + + let retrieved = db.get(&MetadataCF, key).unwrap(); + assert_eq!(retrieved, value); + } + + #[test] + fn test_headers_insert_race() { + use std::sync::{Arc, Barrier}; + use std::thread; + + const NUM_THREADS: usize = 8; + + let db = new_test_db(); + let genesis = create_test_header(BlockHash::all_zeros(), 0); + db.block_header_genesis_insert(&genesis, 0, U256::from(1_u32)) + .unwrap(); + + let h1 = create_test_header(genesis.block_hash(), 1); + let h2 = create_test_header(h1.block_hash(), 2); + + let barrier = Arc::new(Barrier::new(NUM_THREADS)); + let mut handles = Vec::new(); + + for _ in 0..NUM_THREADS { + let db_clone = Arc::clone(&db); + let bc = Arc::clone(&barrier); + + let handle = thread::spawn(move || { + bc.wait(); + db_clone.block_headers_insert(&[h1, h2], &[]) + }); + + handles.push(handle); + } + + let mut results = Vec::new(); + for handle in handles { + results.push(handle.join().unwrap()); + } + + let mut found = false; + for res in results { + match res { + Ok(_) => { + if found { + panic!("multiple threads succeeded") + } + found = true; + } + Err(TrustDBError::Duplicate(_)) => (), + Err(e) => panic!("unexpected error {e}"), + } + } + if !found { + panic!("expected one success") + } + } + + #[test] + fn test_headers_remove_race() { + use std::sync::{Arc, Barrier}; + use std::thread; + + const NUM_THREADS: usize = 8; + + let db = new_test_db(); + let genesis = create_test_header(BlockHash::all_zeros(), 0); + db.block_header_genesis_insert(&genesis, 0, U256::from(1_u32)) + .unwrap(); + + let h1 = create_test_header(genesis.block_hash(), 1); + let h2 = create_test_header(h1.block_hash(), 2); + db.block_headers_insert(&[h1, h2], &[]).unwrap(); + + let barrier = Arc::new(Barrier::new(NUM_THREADS)); + let mut handles = Vec::new(); + + for _ in 0..NUM_THREADS { + let db_clone = Arc::clone(&db); + let bc = Arc::clone(&barrier); + + let handle = thread::spawn(move || { + bc.wait(); + db_clone.block_headers_remove(&[h1, h2], &genesis, &[]) + }); + + handles.push(handle); + } + + let mut results = Vec::new(); + for handle in handles { + results.push(handle.join().unwrap()); + } + + let mut found = false; + for res in results { + match res { + Ok(_) => { + if found { + panic!("multiple threads succeeded") + } + found = true; + } + Err(TrustDBError::NotFound(_)) => (), + Err(e) => panic!("unexpected error {e}"), + } + } + if !found { + panic!("expected one success") + } + } + + #[test] + fn test_update_headers_race() { + use std::sync::{Arc, Barrier}; + use std::thread; + + let db = new_test_db(); + let genesis = create_test_header(BlockHash::all_zeros(), 0); + db.block_header_genesis_insert(&genesis, 0, U256::from(1_u32)) + .unwrap(); + + let h1 = create_test_header(genesis.block_hash(), 1); + let h2 = create_test_header(h1.block_hash(), 2); + db.block_headers_insert(&[h1, h2], &[]).unwrap(); + + let h3 = create_test_header(h2.block_hash(), 3); + + // Test inserting and removing at the same time, for the chain: + // g -> h1 -> h2 + // try and insert h3 and remove h2 concurrently + + let barrier = Arc::new(Barrier::new(2)); + + // Removal + let db_remove = Arc::clone(&db); + let bc_remove = Arc::clone(&barrier); + + let remove = thread::spawn(move || { + bc_remove.wait(); + db_remove.block_headers_remove(&[h2], &h1, &[]) + }); + + // Insertion + let db_insert = Arc::clone(&db); + let bc_insert = Arc::clone(&barrier); + + let insert = thread::spawn(move || { + bc_insert.wait(); + db_insert.block_headers_insert(&[h3], &[]) + }); + + let remove_res = remove.join().unwrap(); + let insert_res = insert.join().unwrap(); + + if remove_res.is_ok() { + match insert_res { + Ok(_) => panic!("expected failed insertion"), + Err(TrustDBError::NotFound(_)) => (), + Err(e) => panic!("unexpected insertion error: {e}"), + } + } else if insert_res.is_ok() { + match remove_res { + Ok(_) => panic!("expected failed removal"), + Err(TrustDBError::DanglingChild(_)) => (), + Err(e) => panic!("unexpected removal error: {e}"), + } + } else { + panic!("insertion and deletion both failed") + } + } + + #[test] + fn test_clone_trust_db() { + let db = new_test_db(); + let cloned_db = Arc::clone(&db); + + assert_eq!(std::ptr::addr_of!(db.db), std::ptr::addr_of!(cloned_db.db)); + assert!(std::ptr::eq(&db.db, &cloned_db.db)); + + assert_eq!( + std::ptr::addr_of!(db.update_mtx), + std::ptr::addr_of!(cloned_db.update_mtx) + ); + assert!(std::ptr::eq(&db.update_mtx, &cloned_db.update_mtx)); + } }