diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 82f1301..1c685df 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -121,7 +121,11 @@ jobs: strategy: fail-fast: false matrix: - suite: [ordhook-cli, ordhook-core] + suite: + - ordhook-cli + - ordhook-core + - chainhook-sdk + - chainhook-postgres runs-on: ubuntu-latest defaults: run: diff --git a/Cargo.lock b/Cargo.lock index 65c7ee5..1776a98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,22 +2,13 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" -dependencies = [ - "gimli 0.28.1", -] - [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ - "gimli 0.31.1", + "gimli", ] [[package]] @@ -26,41 +17,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array 0.14.7", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - [[package]] name = "ahash" version = "0.8.11" @@ -71,7 +27,7 @@ dependencies = [ "getrandom 0.2.15", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -98,21 +54,6 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[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 = "ansi_term" version = "0.12.1" @@ -124,19 +65,13 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" dependencies = [ "backtrace", ] -[[package]] -name = "arbitrary" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" - [[package]] name = "arc-swap" version = "1.7.1" @@ -158,6 +93,16 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -177,18 +122,18 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -229,11 +174,11 @@ version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ - "addr2line 0.24.2", + "addr2line", "cfg-if", "libc", "miniz_oxide", - "object 0.36.5", + "object", "rustc-demangle", "windows-targets 0.52.6", ] @@ -262,12 +207,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - [[package]] name = "bech32" version = "0.10.0-beta" @@ -280,15 +219,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bindgen" version = "0.65.1" @@ -305,9 +235,9 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -316,7 +246,7 @@ version = "0.68.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cexpr", "clang-sys", "lazy_static", @@ -327,9 +257,9 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", - "syn 2.0.90", + "syn 2.0.98", "which", ] @@ -344,7 +274,7 @@ dependencies = [ "bitcoin_hashes", "hex-conservative", "hex_lit", - "secp256k1 0.28.2", + "secp256k1", "serde", ] @@ -400,21 +330,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "block-buffer" @@ -422,41 +340,20 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array 0.14.7", -] - -[[package]] -name = "bs58" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" - -[[package]] -name = "bs58" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "tinyvec", + "generic-array", ] [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "byte-slice-cast" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytemuck" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" [[package]] name = "byteorder" @@ -466,9 +363,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "bzip2-sys" @@ -483,9 +380,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.2" +version = "1.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" dependencies = [ "jobserver", "libc", @@ -528,7 +425,6 @@ name = "chainhook-postgres" version = "0.1.0" dependencies = [ "bytes", - "chainhook-sdk", "deadpool-postgres", "num-traits", "slog", @@ -539,68 +435,42 @@ dependencies = [ [[package]] name = "chainhook-sdk" -version = "0.12.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14eeb8a1a055202ca86122c9508dc4f9d9cd8643ef9f25773173b645c33b09ac" +version = "0.12.12" dependencies = [ + "assert-json-diff", "base58", - "base64 0.21.7", + "bitcoin", "bitcoincore-rpc", "bitcoincore-rpc-json", "chainhook-types", "crossbeam-channel", - "dashmap", - "futures", - "fxhash", "hex", "hiro-system-kit", - "hyper", "lazy_static", - "miniscript", - "prometheus", - "rand", - "regex", - "reqwest", + "reqwest 0.12.12", "rocket", - "schemars 0.8.21", "serde", "serde-hex", "serde_derive", "serde_json", - "stacks-codec", - "threadpool", + "test-case", "tokio", "zmq", ] [[package]] name = "chainhook-types" -version = "1.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63f0d358e5c530dd6888e4678ef4ba3f7782525653a1012d33e96a48020c418d" +version = "1.3.8" dependencies = [ + "bitcoin", "hex", - "schemars 0.8.21", + "schemars", "serde", "serde_derive", "serde_json", "strum", ] -[[package]] -name = "chrono" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-targets 0.52.6", -] - [[package]] name = "ciborium" version = "0.2.2" @@ -628,16 +498,6 @@ dependencies = [ "half", ] -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - [[package]] name = "clang-sys" version = "1.8.1" @@ -707,35 +567,6 @@ dependencies = [ "os_str_bytes", ] -[[package]] -name = "clarity-vm" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0af203c4eea9bb753ff2798c3bcf9f3fd8ea7a24bde7222d96f82fd9beb6d165" -dependencies = [ - "hashbrown 0.14.5", - "integer-sqrt", - "lazy_static", - "rand", - "rand_chacha", - "regex", - "rusqlite", - "serde", - "serde_derive", - "serde_json", - "serde_stacker", - "sha2-asm", - "slog", - "stacks-common", - "wasmtime", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "cookie" version = "0.18.1" @@ -763,15 +594,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "cpp_demangle" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" -dependencies = [ - "cfg-if", -] - [[package]] name = "cpp_demangle" version = "0.4.4" @@ -783,122 +605,13 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] -[[package]] -name = "cranelift-bforest" -version = "0.102.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e7e56668d2263f92b691cb9e4a2fcb186ca0384941fe420484322fa559c3329" -dependencies = [ - "cranelift-entity", -] - -[[package]] -name = "cranelift-codegen" -version = "0.102.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a9ff61938bf11615f55b80361288c68865318025632ea73c65c0b44fa16283c" -dependencies = [ - "bumpalo", - "cranelift-bforest", - "cranelift-codegen-meta", - "cranelift-codegen-shared", - "cranelift-control", - "cranelift-entity", - "cranelift-isle", - "gimli 0.28.1", - "hashbrown 0.14.5", - "log", - "regalloc2", - "smallvec 1.13.2", - "target-lexicon", -] - -[[package]] -name = "cranelift-codegen-meta" -version = "0.102.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50656bf19e3d4a153b404ff835b8b59e924cfa3682ebe0d3df408994f37983f6" -dependencies = [ - "cranelift-codegen-shared", -] - -[[package]] -name = "cranelift-codegen-shared" -version = "0.102.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388041deeb26109f1ea73c1812ea26bfd406c94cbce0bb5230aa44277e43b209" - -[[package]] -name = "cranelift-control" -version = "0.102.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39b7c512ffac527e5b5df9beae3d67ab85d07dca6d88942c16195439fedd1d3" -dependencies = [ - "arbitrary", -] - -[[package]] -name = "cranelift-entity" -version = "0.102.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb25f573701284fe2bcf88209d405342125df00764b396c923e11eafc94d892" -dependencies = [ - "serde", - "serde_derive", -] - -[[package]] -name = "cranelift-frontend" -version = "0.102.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57374fd11d72cf9ffb85ff64506ed831440818318f58d09f45b4185e5e9c376" -dependencies = [ - "cranelift-codegen", - "log", - "smallvec 1.13.2", - "target-lexicon", -] - -[[package]] -name = "cranelift-isle" -version = "0.102.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae769b235f6ea2f86623a3ff157cc04a4ff131dc9fe782c2ebd35f272043581e" - -[[package]] -name = "cranelift-native" -version = "0.102.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dc7bfb8f13a0526fe20db338711d9354729b861c336978380bb10f7f17dd207" -dependencies = [ - "cranelift-codegen", - "libc", - "target-lexicon", -] - -[[package]] -name = "cranelift-wasm" -version = "0.102.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c5f41a4af931b756be05af0dd374ce200aae2d52cea16b0beb07e8b52732c35" -dependencies = [ - "cranelift-codegen", - "cranelift-entity", - "cranelift-frontend", - "itertools", - "log", - "smallvec 1.13.2", - "wasmparser 0.116.1", - "wasmtime-types", -] - [[package]] name = "crc32fast" version = "1.4.2" @@ -923,18 +636,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -951,24 +664,24 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-common" @@ -976,20 +689,10 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.7", - "rand_core 0.6.4", + "generic-array", "typenum", ] -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - [[package]] name = "ctrlc" version = "3.4.5" @@ -1000,47 +703,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "curve25519-dalek" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26778518a7f6cffa1d25a44b602b62b979bd88adb9e99ffec546998cf3404839" -dependencies = [ - "byteorder", - "digest 0.8.1", - "rand_core 0.5.1", - "serde", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if", - "cpufeatures", - "curve25519-dalek-derive", - "digest 0.10.7", - "fiat-crypto", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "dashmap" version = "5.5.3" @@ -1056,9 +718,9 @@ dependencies = [ [[package]] name = "deadpool" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6541a3916932fe57768d4be0b1ffb5ec7cbf74ca8c903fdfd5c0fe8aa958f0ed" +checksum = "5ed5957ff93768adf7a65ab167a17835c3d2c3c50d084fe305174c112f468e2f" dependencies = [ "deadpool-runtime", "num_cpus", @@ -1067,9 +729,9 @@ dependencies = [ [[package]] name = "deadpool-postgres" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab8a4ea925ce79678034870834602a2980f4b88c09e97feb266496dbb4493d2" +checksum = "3d697d376cbfa018c23eb4caab1fd1883dd9c906a8c034e8d9a3cb06a7e0bef9" dependencies = [ "async-trait", "deadpool", @@ -1097,16 +759,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "der" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" -dependencies = [ - "const-oid", - "zeroize", -] - [[package]] name = "deranged" version = "0.3.11" @@ -1142,20 +794,11 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.90", -] - -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array 0.12.4", + "syn 2.0.98", ] [[package]] @@ -1180,16 +823,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "directories-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - [[package]] name = "dirs-next" version = "2.0.0" @@ -1219,40 +852,14 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "dyn-clone" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" - -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "serde", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" -dependencies = [ - "curve25519-dalek 4.1.3", - "ed25519", - "rand_core 0.6.4", - "serde", - "sha2", - "subtle", - "zeroize", -] +checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35" [[package]] name = "either" @@ -1286,7 +893,7 @@ checksum = "3bf679796c0322556351f287a51b49e48f7c4986e727b5dd78c972d30e2e16cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1311,29 +918,11 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" - -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "figment" @@ -1344,7 +933,7 @@ dependencies = [ "atomic 0.6.0", "pear", "serde", - "toml 0.8.19", + "toml 0.8.20", "uncased", "version_check", ] @@ -1373,18 +962,6 @@ dependencies = [ "winapi", ] -[[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 = "flate2" version = "1.0.35" @@ -1415,9 +992,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" [[package]] name = "form_urlencoded" @@ -1428,12 +1005,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - [[package]] name = "futures" version = "0.3.31" @@ -1490,7 +1061,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1532,19 +1103,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "fxprof-processed-profile" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27d12c0aed7f1e24276a241aadc4cb8ea9f83000f34bc062b7cc2d51e3b0fabd" -dependencies = [ - "bitflags 2.6.0", - "debugid", - "fxhash", - "serde", - "serde_json", -] - [[package]] name = "generator" version = "0.7.5" @@ -1558,15 +1116,6 @@ dependencies = [ "windows", ] -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1577,17 +1126,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.15" @@ -1602,24 +1140,15 @@ dependencies = [ ] [[package]] -name = "ghash" -version = "0.5.1" +name = "getrandom" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" dependencies = [ - "opaque-debug", - "polyval", -] - -[[package]] -name = "gimli" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" -dependencies = [ - "fallible-iterator 0.3.0", - "indexmap 2.7.0", - "stable_deref_trait", + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", ] [[package]] @@ -1630,9 +1159,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "h2" @@ -1645,8 +1174,8 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", - "indexmap 2.7.0", + "http 0.2.12", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", @@ -1669,25 +1198,11 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", - "serde", -] [[package]] name = "hashbrown" @@ -1700,15 +1215,6 @@ dependencies = [ "foldhash", ] -[[package]] -name = "hashlink" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" -dependencies = [ - "hashbrown 0.14.5", -] - [[package]] name = "heck" version = "0.3.3" @@ -1794,16 +1300,16 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.7", + "digest", ] [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1817,6 +1323,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -1824,15 +1341,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.2.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "httpdate" @@ -1851,8 +1391,8 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", - "http-body", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -1864,6 +1404,25 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec 1.13.2", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -1871,34 +1430,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", - "hyper", - "rustls", + "http 0.2.12", + "hyper 0.14.27", + "rustls 0.21.12", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", ] [[package]] -name = "iana-time-zone" -version = "0.1.61" +name = "hyper-rustls" +version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", + "futures-util", + "http 1.2.0", + "hyper 1.6.0", + "hyper-util", + "rustls 0.23.22", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.1", + "tower-service", + "webpki-roots 0.26.8", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "hyper-util" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ - "cc", + "bytes", + "futures-channel", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "hyper 1.6.0", + "pin-project-lite", + "socket2 0.5.8", + "tokio", + "tower-service", + "tracing", ] [[package]] @@ -2016,15 +1589,9 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] -[[package]] -name = "id-arena" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" - [[package]] name = "idna" version = "1.0.3" @@ -2046,26 +1613,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "impl-codec" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" -dependencies = [ - "parity-scale-codec", -] - -[[package]] -name = "impl-trait-for-tuples" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "indexmap" version = "1.9.3" @@ -2078,9 +1625,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -2094,7 +1641,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88" dependencies = [ "ahash", - "indexmap 2.7.0", + "indexmap 2.7.1", "is-terminal", "itoa", "log", @@ -2111,48 +1658,21 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array 0.14.7", -] - -[[package]] -name = "integer-sqrt" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" -dependencies = [ - "num-traits", -] - [[package]] name = "ipnet" -version = "2.10.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is-terminal" -version = "0.4.13" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi 0.4.0", "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", + "windows-sys 0.59.0", ] [[package]] @@ -2161,26 +1681,6 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" -[[package]] -name = "ittapi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b996fe614c41395cdaedf3cf408a9534851090959d90d54a535f675550b64b1" -dependencies = [ - "anyhow", - "ittapi-sys", - "log", -] - -[[package]] -name = "ittapi-sys" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5385394064fa2c886205dba02598013ce83d3e92d33dbdc0c52fe0e7bf4fc" -dependencies = [ - "cc", -] - [[package]] name = "jobserver" version = "0.1.32" @@ -2192,9 +1692,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -2221,15 +1721,6 @@ dependencies = [ "rayon", ] -[[package]] -name = "keccak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" -dependencies = [ - "cpufeatures", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -2242,17 +1733,11 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" -[[package]] -name = "leb128" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" - [[package]] name = "libc" -version = "0.2.167" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" @@ -2270,7 +1755,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "libc", "redox_syscall", ] @@ -2289,22 +1774,11 @@ dependencies = [ "libz-sys", ] -[[package]] -name = "libsqlite3-sys" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - [[package]] name = "libz-sys" -version = "1.1.20" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" +checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" dependencies = [ "cc", "pkg-config", @@ -2313,9 +1787,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "litemap" @@ -2335,9 +1809,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "loom" @@ -2356,22 +1830,13 @@ dependencies = [ [[package]] name = "lru" -version = "0.12.5" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" dependencies = [ "hashbrown 0.15.2", ] -[[package]] -name = "mach" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" -dependencies = [ - "libc", -] - [[package]] name = "maplit" version = "1.0.2" @@ -2400,7 +1865,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", - "digest 0.10.7", + "digest", ] [[package]] @@ -2409,15 +1874,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "memfd" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" -dependencies = [ - "rustix", -] - [[package]] name = "memmap2" version = "0.9.5" @@ -2427,24 +1883,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "mime" version = "0.3.17" @@ -2457,22 +1895,11 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniscript" -version = "11.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3127e10529a57a8f7fa9b1332c4c2f72baadaca6777798f910dff3c922620b14" -dependencies = [ - "bech32", - "bitcoin", - "bitcoin-internals", -] - [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] @@ -2497,7 +1924,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http", + "http 0.2.12", "httparse", "log", "memchr", @@ -2517,19 +1944,6 @@ dependencies = [ "getrandom 0.2.15", ] -[[package]] -name = "nix" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" -dependencies = [ - "bitflags 1.3.2", - "cc", - "cfg-if", - "libc", - "memoffset 0.6.5", -] - [[package]] name = "nix" version = "0.26.4" @@ -2547,7 +1961,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "cfg_aliases", "libc", @@ -2616,36 +2030,31 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" -dependencies = [ - "crc32fast", - "hashbrown 0.14.5", - "indexmap 2.7.0", - "memchr", -] - -[[package]] -name = "object" -version = "0.36.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.20.2" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +name = "ord" +version = "0.22.2" +dependencies = [ + "anyhow", + "bitcoin", + "chainhook-sdk", + "ciborium", + "serde", + "serde_derive", + "serde_json", +] [[package]] name = "ordhook" @@ -2654,11 +2063,13 @@ dependencies = [ "ansi_term", "anyhow", "atty", + "bitcoin", "chainhook-postgres", "chainhook-sdk", - "ciborium", + "chainhook-types", "crossbeam-channel", "dashmap", + "deadpool-postgres", "flate2", "flume", "futures", @@ -2666,20 +2077,20 @@ dependencies = [ "fxhash", "hex", "hiro-system-kit", - "hyper", + "hyper 0.14.27", "lazy_static", "lru", "maplit", "num_cpus", + "ord", "pprof", "progressing", "prometheus", - "rand", + "rand 0.9.0", "refinery", "regex", - "reqwest", + "reqwest 0.11.27", "rocksdb", - "schemars 0.8.16", "serde", "serde_derive", "serde_json", @@ -2687,20 +2098,23 @@ dependencies = [ "test-case", "threadpool", "tokio", - "uuid", + "tokio-postgres", ] [[package]] name = "ordhook-cli" version = "2.0.0" dependencies = [ + "chainhook-sdk", + "chainhook-types", "clap", "clap_generate", "ctrlc", + "hex", "hiro-system-kit", "num_cpus", "ordhook", - "reqwest", + "reqwest 0.11.27", "serde", "serde_derive", "serde_json", @@ -2720,54 +2134,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "p256k1" -version = "7.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8641765697df03a529cd21181fbccd1cad9f66086c24252b91c16bf38cdddd6" -dependencies = [ - "bitvec", - "bs58 0.4.0", - "cc", - "hex", - "itertools", - "num-traits", - "primitive-types", - "proc-macro2", - "quote", - "rand_core 0.6.4", - "rustfmt-wrapper", - "serde", - "sha2", - "syn 2.0.90", -] - -[[package]] -name = "parity-scale-codec" -version = "3.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" -dependencies = [ - "arrayvec", - "bitvec", - "byte-slice-cast", - "impl-trait-for-tuples", - "parity-scale-codec-derive", - "serde", -] - -[[package]] -name = "parity-scale-codec-derive" -version = "3.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "parking_lot" version = "0.12.3" @@ -2791,12 +2157,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pear" version = "0.2.9" @@ -2817,7 +2177,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -2834,27 +2194,27 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phf" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_shared", ] [[package]] name = "phf_shared" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher 0.3.11", + "siphasher", ] [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -2862,70 +2222,38 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" -[[package]] -name = "polynomial" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27abb6e4638dcecc65a92b50d7f1d87dd6dea987ba71db987b6bf881f4877e9d" -dependencies = [ - "num-traits", - "serde", -] - -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - [[package]] name = "postgres-protocol" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acda0ebdebc28befa84bee35e651e4c5f09073d668c7aed4cf7e23c3cda84b23" +checksum = "76ff0abab4a9b844b93ef7b81f1efc0a366062aaef2cd702c76256b5dc075c54" dependencies = [ "base64 0.22.1", "byteorder", "bytes", - "fallible-iterator 0.2.0", + "fallible-iterator", "hmac", "md-5", "memchr", - "rand", + "rand 0.9.0", "sha2", "stringprep", ] [[package]] name = "postgres-types" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66ea23a2d0e5734297357705193335e0a957696f34bed2f2faefacb2fec336f" +checksum = "613283563cd90e1dfc3518d548caee47e0e725455ed619881f5cf21f36de4b48" dependencies = [ "bytes", - "fallible-iterator 0.2.0", + "fallible-iterator", "postgres-protocol", ] @@ -2954,7 +2282,7 @@ dependencies = [ "smallvec 1.13.2", "symbolic-demangle", "tempfile", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2963,37 +2291,17 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] name = "prettyplease" -version = "0.2.25" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" dependencies = [ "proc-macro2", - "syn 2.0.90", -] - -[[package]] -name = "primitive-types" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" -dependencies = [ - "fixed-hash", - "impl-codec", - "uint", -] - -[[package]] -name = "proc-macro-crate" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" -dependencies = [ - "toml_edit", + "syn 2.0.98", ] [[package]] @@ -3022,9 +2330,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -3037,7 +2345,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "version_check", "yansi", ] @@ -3063,7 +2371,7 @@ dependencies = [ "memchr", "parking_lot", "protobuf", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3072,15 +2380,6 @@ version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" -[[package]] -name = "psm" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" -dependencies = [ - "cc", -] - [[package]] name = "quick-xml" version = "0.26.0" @@ -3091,19 +2390,65 @@ dependencies = [ ] [[package]] -name = "quote" -version = "1.0.37" +name = "quinn" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" dependencies = [ - "proc-macro2", + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.1", + "rustls 0.23.22", + "socket2 0.5.8", + "thiserror 2.0.11", + "tokio", + "tracing", ] [[package]] -name = "radium" -version = "0.7.0" +name = "quinn-proto" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +dependencies = [ + "bytes", + "getrandom 0.2.15", + "rand 0.8.5", + "ring", + "rustc-hash 2.1.1", + "rustls 0.23.22", + "rustls-pki-types", + "slab", + "thiserror 2.0.11", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.5.8", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] [[package]] name = "rand" @@ -3112,10 +2457,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.0", + "zerocopy 0.8.17", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -3127,12 +2483,13 @@ dependencies = [ ] [[package]] -name = "rand_core" -version = "0.5.1" +name = "rand_chacha" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ - "getrandom 0.1.16", + "ppv-lite86", + "rand_core 0.9.0", ] [[package]] @@ -3144,6 +2501,16 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +dependencies = [ + "getrandom 0.3.1", + "zerocopy 0.8.17", +] + [[package]] name = "rayon" version = "1.10.0" @@ -3166,11 +2533,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -3181,7 +2548,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3201,7 +2568,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -3225,12 +2592,12 @@ dependencies = [ "log", "regex", "serde", - "siphasher 1.0.1", - "thiserror", + "siphasher", + "thiserror 1.0.69", "time", "tokio", "tokio-postgres", - "toml 0.8.19", + "toml 0.8.20", "url", "walkdir", ] @@ -3246,20 +2613,7 @@ dependencies = [ "quote", "refinery-core", "regex", - "syn 2.0.90", -] - -[[package]] -name = "regalloc2" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" -dependencies = [ - "hashbrown 0.13.2", - "log", - "rustc-hash", - "slice-group-by", - "smallvec 1.13.2", + "syn 2.0.98", ] [[package]] @@ -3318,10 +2672,10 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.27", + "hyper-rustls 0.24.2", "ipnet", "js-sys", "log", @@ -3329,15 +2683,15 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls", - "rustls-pemfile", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 0.1.2", "system-configuration", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tokio-util", "tower-service", "url", @@ -3345,10 +2699,54 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots", + "webpki-roots 0.25.4", "winreg", ] +[[package]] +name = "reqwest" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-rustls 0.27.5", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.22", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tokio-rustls 0.26.1", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.26.8", + "windows-registry", +] + [[package]] name = "rgb" version = "0.8.50" @@ -3373,15 +2771,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "ripemd" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" -dependencies = [ - "digest 0.10.7", -] - [[package]] name = "rocket" version = "0.5.0" @@ -3396,14 +2785,14 @@ dependencies = [ "either", "figment", "futures", - "indexmap 2.7.0", + "indexmap 2.7.1", "log", "memchr", "multer", "num_cpus", "parking_lot", "pin-project-lite", - "rand", + "rand 0.8.5", "ref-cast", "rocket_codegen", "rocket_http", @@ -3422,33 +2811,33 @@ dependencies = [ [[package]] name = "rocket_codegen" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2238066abf75f21be6cd7dc1a09d5414a671f4246e384e49fe3f8a4936bd04c" +checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" dependencies = [ "devise", "glob", - "indexmap 2.7.0", + "indexmap 2.7.1", "proc-macro2", "quote", "rocket_http", - "syn 2.0.90", + "syn 2.0.98", "unicode-xid", "version_check", ] [[package]] name = "rocket_http" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a1663694d059fe5f943ea5481363e48050acedd241d46deb2e27f71110389e" +checksum = "e274915a20ee3065f611c044bd63c40757396b6dbc057d6046aec27f14f882b9" dependencies = [ "cookie", "either", "futures", - "http", - "hyper", - "indexmap 2.7.0", + "http 0.2.12", + "hyper 0.14.27", + "indexmap 2.7.1", "log", "memchr", "pear", @@ -3474,21 +2863,6 @@ dependencies = [ "librocksdb-sys", ] -[[package]] -name = "rusqlite" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" -dependencies = [ - "bitflags 1.3.2", - "fallible-iterator 0.2.0", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "serde_json", - "smallvec 1.13.2", -] - [[package]] name = "rustc-demangle" version = "0.1.24" @@ -3502,44 +2876,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] -name = "rustc-hex" -version = "2.1.0" +name = "rustc-hash" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustfmt-wrapper" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1adc9dfed5cc999077978cc7163b9282c5751c8d39827c4ea8c8c220ca5a440" -dependencies = [ - "serde", - "tempfile", - "thiserror", - "toml 0.8.19", - "toolchain_find", -] +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3550,10 +2902,24 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.23.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -3563,6 +2929,24 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +dependencies = [ + "web-time", +] + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -3574,16 +2958,27 @@ dependencies = [ ] [[package]] -name = "rustversion" -version = "1.0.18" +name = "rustls-webpki" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "same-file" @@ -3600,19 +2995,7 @@ version = "0.8.16" source = "git+https://github.com/hirosystems/schemars.git?branch=feat-chainhook-fixes#d0c10b50478a06198a54e5b90be460112b38b357" dependencies = [ "dyn-clone", - "schemars_derive 0.8.16", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" -dependencies = [ - "dyn-clone", - "schemars_derive 0.8.21", + "schemars_derive", "serde", "serde_json", ] @@ -3624,22 +3007,10 @@ source = "git+https://github.com/hirosystems/schemars.git?branch=feat-chainhook- dependencies = [ "proc-macro2", "quote", - "serde_derive_internals 0.26.0", + "serde_derive_internals", "syn 1.0.109", ] -[[package]] -name = "schemars_derive" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals 0.29.1", - "syn 2.0.90", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -3662,16 +3033,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "secp256k1" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" -dependencies = [ - "secp256k1-sys 0.6.1", - "serde", -] - [[package]] name = "secp256k1" version = "0.28.2" @@ -3679,20 +3040,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" dependencies = [ "bitcoin_hashes", - "rand", - "secp256k1-sys 0.9.2", + "rand 0.8.5", + "secp256k1-sys", "serde", ] -[[package]] -name = "secp256k1-sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" -dependencies = [ - "cc", -] - [[package]] name = "secp256k1-sys" version = "0.9.2" @@ -3702,17 +3054,11 @@ dependencies = [ "cc", ] -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" - [[package]] name = "serde" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] @@ -3730,13 +3076,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -3750,22 +3096,11 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "itoa", "memchr", @@ -3782,16 +3117,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_stacker" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "babfccff5773ff80657f0ecf553c7c516bdc2eb16389c0918b36b73e7015276e" -dependencies = [ - "serde", - "stacker", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3812,26 +3137,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.7", -] - -[[package]] -name = "sha2-asm" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c2f225be6502f2134e6bbb35bb5e2957e41ffa0495ed08bce2e2b4ca885da4" -dependencies = [ - "cc", -] - -[[package]] -name = "sha3" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" -dependencies = [ - "digest 0.10.7", - "keccak", + "digest", ] [[package]] @@ -3858,21 +3164,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - [[package]] name = "siphasher" version = "1.0.1" @@ -3888,12 +3179,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "slice-group-by" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" - [[package]] name = "slog" version = "2.7.0" @@ -4002,22 +3287,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "sptr" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" - [[package]] name = "stable-pattern" version = "0.1.0" @@ -4033,61 +3302,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "stacker" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" -dependencies = [ - "cc", - "cfg-if", - "libc", - "psm", - "windows-sys 0.59.0", -] - -[[package]] -name = "stacks-codec" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5791855bf9de767f55042f1a034a2575373c6433b0965f1efac14e7215f71b" -dependencies = [ - "clarity-vm", - "serde", - "wsts", -] - -[[package]] -name = "stacks-common" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c78cb37406e4b6a269f0b6aa0ee222922c9d30c263a43af7e005169e20edf12b" -dependencies = [ - "chrono", - "curve25519-dalek 2.0.0", - "ed25519-dalek", - "hashbrown 0.14.5", - "lazy_static", - "libc", - "nix 0.23.2", - "percent-encoding", - "rand", - "ripemd", - "rusqlite", - "secp256k1 0.24.3", - "serde", - "serde_derive", - "serde_json", - "sha2", - "sha3", - "slog", - "slog-json", - "slog-term", - "time", - "winapi", - "wsts", -] - [[package]] name = "state" version = "0.6.0" @@ -4097,12 +3311,6 @@ dependencies = [ "loom", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "str_stack" version = "0.1.0" @@ -4156,9 +3364,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "symbolic-common" -version = "12.12.3" +version = "12.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ba5365997a4e375660bed52f5b42766475d5bc8ceb1bb13fea09c469ea0f49" +checksum = "b6189977df1d6ec30c920647919d76f29fb8d8f25e8952e835b0fcda25e8f792" dependencies = [ "debugid", "memmap2", @@ -4168,11 +3376,11 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.12.3" +version = "12.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beff338b2788519120f38c59ff4bb15174f52a183e547bac3d6072c2c0aa48aa" +checksum = "d234917f7986498e7f62061438cee724bafb483fe84cfbe2486f68dce48240d7" dependencies = [ - "cpp_demangle 0.4.4", + "cpp_demangle", "rustc-demangle", "symbolic-common", ] @@ -4190,9 +3398,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -4205,6 +3413,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.1" @@ -4213,7 +3430,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -4246,7 +3463,7 @@ dependencies = [ "cfg-expr", "heck 0.5.0", "pkg-config", - "toml 0.8.19", + "toml 0.8.20", "version-compare", ] @@ -4256,12 +3473,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "tar" version = "0.4.43" @@ -4291,12 +3502,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand", + "getrandom 0.3.1", "once_cell", "rustix", "windows-sys 0.59.0", @@ -4340,7 +3552,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -4351,7 +3563,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "test-case-core", ] @@ -4367,7 +3579,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", ] [[package]] @@ -4378,7 +3599,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] @@ -4443,9 +3675,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] @@ -4458,9 +3690,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.42.0" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", @@ -4476,25 +3708,25 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "tokio-postgres" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b5d3742945bc7d7f210693b0c58ae542c6fd47b17adbbda0885f3dcb34a6bdb" +checksum = "6c95d533c83082bb6490e0189acaa0bbeef9084e60471b696ca6988cd0541fb0" dependencies = [ "async-trait", "byteorder", "bytes", - "fallible-iterator 0.2.0", + "fallible-iterator", "futures-channel", "futures-util", "log", @@ -4504,7 +3736,7 @@ dependencies = [ "pin-project-lite", "postgres-protocol", "postgres-types", - "rand", + "rand 0.9.0", "socket2 0.5.8", "tokio", "tokio-util", @@ -4517,15 +3749,25 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +dependencies = [ + "rustls 0.23.22", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -4534,9 +3776,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -4557,9 +3799,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", @@ -4578,11 +3820,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee" dependencies = [ - "indexmap 2.7.0", + "indexmap 2.7.1", "serde", "serde_spanned", "toml_datetime", @@ -4590,18 +3832,26 @@ dependencies = [ ] [[package]] -name = "toolchain_find" -version = "0.4.0" +name = "tower" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc8c9a7f0a2966e1acdaf0461023d0b01471eeead645370cf4c3f5cff153f2a" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ - "home", - "once_cell", - "regex", - "semver", - "walkdir", + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 1.0.2", + "tokio", + "tower-layer", + "tower-service", ] +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -4627,7 +3877,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -4690,18 +3940,6 @@ dependencies = [ "serde", ] -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "uncased" version = "0.9.10" @@ -4714,15 +3952,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-normalization" @@ -4745,28 +3983,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" -[[package]] -name = "unicode-width" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" - [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - [[package]] name = "untrusted" version = "0.9.0" @@ -4798,19 +4020,15 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.11.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" -dependencies = [ - "getrandom 0.2.15", - "rand", -] +checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0" [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -4849,18 +4067,21 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasite" version = "0.1.0" @@ -4869,35 +4090,35 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.47" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", @@ -4908,9 +4129,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4918,40 +4139,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" - -[[package]] -name = "wasm-encoder" -version = "0.36.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822b645bf4f2446b949776ffca47e2af60b167209ffb70814ef8779d299cd421" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ - "leb128", -] - -[[package]] -name = "wasm-encoder" -version = "0.221.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17a3bd88f2155da63a1f2fcb8a56377a24f0b6dfed12733bb5f544e86f690c5" -dependencies = [ - "leb128", - "wasmparser 0.221.2", + "unicode-ident", ] [[package]] @@ -4967,338 +4172,21 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wasmparser" -version = "0.116.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50" -dependencies = [ - "indexmap 2.7.0", - "semver", -] - -[[package]] -name = "wasmparser" -version = "0.221.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9845c470a2e10b61dd42c385839cdd6496363ed63b5c9e420b5488b77bd22083" -dependencies = [ - "bitflags 2.6.0", - "indexmap 2.7.0", - "semver", -] - -[[package]] -name = "wasmtime" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642e12d108e800215263e3b95972977f473957923103029d7d617db701d67ba4" -dependencies = [ - "anyhow", - "async-trait", - "bincode", - "bumpalo", - "cfg-if", - "fxprof-processed-profile", - "indexmap 2.7.0", - "libc", - "log", - "object 0.32.2", - "once_cell", - "paste", - "psm", - "rayon", - "serde", - "serde_derive", - "serde_json", - "target-lexicon", - "wasm-encoder 0.36.2", - "wasmparser 0.116.1", - "wasmtime-cache", - "wasmtime-component-macro", - "wasmtime-cranelift", - "wasmtime-environ", - "wasmtime-fiber", - "wasmtime-jit", - "wasmtime-runtime", - "wat", - "windows-sys 0.48.0", -] - -[[package]] -name = "wasmtime-asm-macros" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beada8bb15df52503de0a4c58de4357bfd2f96d9a44a6e547bad11efdd988b47" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "wasmtime-cache" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aba5bf44d044d25892c03fb3534373936ee204141ff92bac8297787ac7f22318" -dependencies = [ - "anyhow", - "base64 0.21.7", - "bincode", - "directories-next", - "log", - "rustix", - "serde", - "serde_derive", - "sha2", - "toml 0.5.11", - "windows-sys 0.48.0", - "zstd", -] - -[[package]] -name = "wasmtime-component-macro" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ccba556991465cca68d5a54769684bcf489fb532059da55105f851642d52c1" -dependencies = [ - "anyhow", - "proc-macro2", - "quote", - "syn 2.0.90", - "wasmtime-component-util", - "wasmtime-wit-bindgen", - "wit-parser", -] - -[[package]] -name = "wasmtime-component-util" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05492a177a6006cb73f034d6e9a6fad6da55b23c4398835cb0012b5fa51ecf67" - -[[package]] -name = "wasmtime-cranelift" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe2e7532f1d6adbcc57e69bb6a7c503f0859076d07a9b4b6aabe8021ff8a05fd" -dependencies = [ - "anyhow", - "cfg-if", - "cranelift-codegen", - "cranelift-control", - "cranelift-entity", - "cranelift-frontend", - "cranelift-native", - "cranelift-wasm", - "gimli 0.28.1", - "log", - "object 0.32.2", - "target-lexicon", - "thiserror", - "wasmparser 0.116.1", - "wasmtime-cranelift-shared", - "wasmtime-environ", - "wasmtime-versioned-export-macros", -] - -[[package]] -name = "wasmtime-cranelift-shared" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c98d5378a856cbf058d36278627dfabf0ed68a888142958c7ae8e6af507dafa" -dependencies = [ - "anyhow", - "cranelift-codegen", - "cranelift-control", - "cranelift-native", - "gimli 0.28.1", - "object 0.32.2", - "target-lexicon", - "wasmtime-environ", -] - -[[package]] -name = "wasmtime-environ" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6d33a9f421da810a070cd56add9bc51f852bd66afbb8b920489d6242f15b70e" -dependencies = [ - "anyhow", - "cranelift-entity", - "gimli 0.28.1", - "indexmap 2.7.0", - "log", - "object 0.32.2", - "serde", - "serde_derive", - "target-lexicon", - "thiserror", - "wasmparser 0.116.1", - "wasmtime-types", -] - -[[package]] -name = "wasmtime-fiber" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "404741f4c6d7f4e043be2e8b466406a2aee289ccdba22bf9eba6399921121b97" -dependencies = [ - "anyhow", - "cc", - "cfg-if", - "rustix", - "wasmtime-asm-macros", - "wasmtime-versioned-export-macros", - "windows-sys 0.48.0", -] - -[[package]] -name = "wasmtime-jit" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d0994a86d6dca5f7d9740d7f2bd0568be06d2014a550361dc1c397d289d81ef" -dependencies = [ - "addr2line 0.21.0", - "anyhow", - "bincode", - "cfg-if", - "cpp_demangle 0.3.5", - "gimli 0.28.1", - "ittapi", - "log", - "object 0.32.2", - "rustc-demangle", - "rustix", - "serde", - "serde_derive", - "target-lexicon", - "wasmtime-environ", - "wasmtime-jit-debug", - "wasmtime-jit-icache-coherence", - "wasmtime-runtime", - "windows-sys 0.48.0", -] - -[[package]] -name = "wasmtime-jit-debug" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0c4b74e606d1462d648631d5bc328e3d5b14e7f9d3ff93bc6db062fb8c5cd8" -dependencies = [ - "object 0.32.2", - "once_cell", - "rustix", - "wasmtime-versioned-export-macros", -] - -[[package]] -name = "wasmtime-jit-icache-coherence" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3090a69ba1476979e090aa7ed4bc759178bafdb65b22f98b9ba24fc6e7e578d5" -dependencies = [ - "cfg-if", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "wasmtime-runtime" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b993ac8380385ed67bf71b51b9553edcf1ab0801b78a805a067de581b9a3e88a" -dependencies = [ - "anyhow", - "cc", - "cfg-if", - "indexmap 2.7.0", - "libc", - "log", - "mach", - "memfd", - "memoffset 0.9.1", - "paste", - "rand", - "rustix", - "sptr", - "wasm-encoder 0.36.2", - "wasmtime-asm-macros", - "wasmtime-environ", - "wasmtime-fiber", - "wasmtime-jit-debug", - "wasmtime-versioned-export-macros", - "wasmtime-wmemcheck", - "windows-sys 0.48.0", -] - -[[package]] -name = "wasmtime-types" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5778112fcab2dc3d4371f4203ab8facf0c453dd94312b0a88dd662955e64e0" -dependencies = [ - "cranelift-entity", - "serde", - "serde_derive", - "thiserror", - "wasmparser 0.116.1", -] - -[[package]] -name = "wasmtime-versioned-export-macros" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50f51f8d79bfd2aa8e9d9a0ae7c2d02b45fe412e62ff1b87c0c81b07c738231" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - -[[package]] -name = "wasmtime-wit-bindgen" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b804dfd3d0c0d6d37aa21026fe7772ba1a769c89ee4f5c4f13b82d91d75216f" -dependencies = [ - "anyhow", - "heck 0.4.1", - "indexmap 2.7.0", - "wit-parser", -] - -[[package]] -name = "wasmtime-wmemcheck" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6060bc082cc32d9a45587c7640e29e3c7b89ada82677ac25d87850aaccb368" - -[[package]] -name = "wast" -version = "221.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc4470b9de917ba199157d1f0ae104f2ae362be728c43e68c571c7715bd629e" -dependencies = [ - "bumpalo", - "leb128", - "memchr", - "unicode-width", - "wasm-encoder 0.221.2", -] - -[[package]] -name = "wat" -version = "1.221.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1f3c6d82af47286494c6caea1d332037f5cbeeac82bbf5ef59cb8c201c466e" -dependencies = [ - "wast", -] - [[package]] name = "web-sys" -version = "0.3.74" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[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", @@ -5310,6 +4198,15 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "0.26.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "4.4.2" @@ -5374,11 +4271,32 @@ dependencies = [ ] [[package]] -name = "windows-core" -version = "0.52.0" +name = "windows-registry" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", "windows-targets 0.52.6", ] @@ -5532,9 +4450,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f" dependencies = [ "memchr", ] @@ -5550,20 +4468,12 @@ dependencies = [ ] [[package]] -name = "wit-parser" -version = "0.13.2" +name = "wit-bindgen-rt" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "316b36a9f0005f5aa4b03c39bc3728d045df136f8c13a73b7db4510dec725e08" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.7.0", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", + "bitflags 2.8.0", ] [[package]] @@ -5578,42 +4488,11 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" -[[package]] -name = "wsts" -version = "8.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "467aa8e40ed0277d19922fd0e7357c16552cb900e5138f61a48ac23c4b7878e0" -dependencies = [ - "aes-gcm", - "bs58 0.5.1", - "hashbrown 0.14.5", - "hex", - "num-traits", - "p256k1", - "polynomial", - "primitive-types", - "rand_core 0.6.4", - "serde", - "sha2", - "thiserror", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - [[package]] name = "xattr" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" dependencies = [ "libc", "linux-raw-sys", @@ -5649,7 +4528,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "synstructure", ] @@ -5660,7 +4539,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713" +dependencies = [ + "zerocopy-derive 0.8.17", ] [[package]] @@ -5671,7 +4559,18 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] @@ -5691,7 +4590,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "synstructure", ] @@ -5730,7 +4629,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -5754,32 +4653,3 @@ dependencies = [ "system-deps", "zeromq-src", ] - -[[package]] -name = "zstd" -version = "0.11.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml index 419e293..d274317 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,17 @@ [workspace] members = [ + "components/chainhook-sdk", "components/chainhook-postgres", + "components/chainhook-types-rs", "components/ordhook-cli", "components/ordhook-core", + "components/ord", ] default-members = ["components/ordhook-cli"] resolver = "2" + +[workspace.dependencies] +bitcoin = "0.31.2" +tokio-postgres = "0.7.10" +deadpool-postgres = "0.14.0" +refinery = { version = "0.8", features = ["tokio-postgres"] } diff --git a/api/ordinals/src/pg/pg-store.ts b/api/ordinals/src/pg/pg-store.ts index 4366625..5f09594 100644 --- a/api/ordinals/src/pg/pg-store.ts +++ b/api/ordinals/src/pg/pg-store.ts @@ -171,7 +171,12 @@ export class PgStore extends BasePgStore { i.fee AS genesis_fee, i.curse_type, i.ordinal_number AS sat_ordinal, - i.parent, + ( + SELECT ip.parent_inscription_id + FROM inscription_parents AS ip + WHERE ip.inscription_id = i.inscription_id + LIMIT 1 + ) AS parent, i.metadata, s.rarity AS sat_rarity, s.coinbase_height AS sat_coinbase_height, diff --git a/api/ordinals/tests/api/cache.test.ts b/api/ordinals/tests/api/cache.test.ts index f6f1b4b..e409ea0 100644 --- a/api/ordinals/tests/api/cache.test.ts +++ b/api/ordinals/tests/api/cache.test.ts @@ -55,7 +55,6 @@ describe('ETag cache', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 10000, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -156,7 +155,6 @@ describe('ETag cache', () => { delegate: null, metaprotocol: null, metadata: null, - parent: null, block_height: '778575', block_hash: '000000000000000000016bcbcc915c68bce367e18f09d0945dc6aacc0ee20121', tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', @@ -189,7 +187,6 @@ describe('ETag cache', () => { delegate: null, metaprotocol: null, metadata: null, - parent: null, block_height: '778576', block_hash: '00000000000000000000a9db2c5d6c5445e7191927d6981ec580ed3c8112e342', tx_id: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', @@ -270,7 +267,6 @@ describe('ETag cache', () => { delegate: null, metaprotocol: null, metadata: null, - parent: null, block_height: '778575', block_hash: randomHash(), tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', @@ -322,7 +318,6 @@ describe('ETag cache', () => { delegate: null, metaprotocol: null, metadata: null, - parent: null, block_height: '778576', block_hash: randomHash(), tx_id: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', @@ -366,7 +361,6 @@ describe('ETag cache', () => { delegate: null, metaprotocol: null, metadata: null, - parent: null, block_height: '778575', block_hash: randomHash(), tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', @@ -418,7 +412,6 @@ describe('ETag cache', () => { delegate: null, metaprotocol: null, metadata: null, - parent: null, block_height: '778576', block_hash: randomHash(), tx_id: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', diff --git a/api/ordinals/tests/api/inscriptions.test.ts b/api/ordinals/tests/api/inscriptions.test.ts index a567e8f..edb8273 100644 --- a/api/ordinals/tests/api/inscriptions.test.ts +++ b/api/ordinals/tests/api/inscriptions.test.ts @@ -11,6 +11,7 @@ import { insertTestInscriptionRecursion, inscriptionTransfer, randomHash, + insertTestInscriptionParent, } from '../helpers'; describe('/inscriptions', () => { @@ -57,7 +58,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -144,7 +144,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -242,7 +241,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 0, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -275,7 +273,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', delegate: null, timestamp: 0, output: 'f351d86c6e6cae3c64e297e7463095732f216875bcc1f3c03f950a492bb25421:0', @@ -287,6 +284,10 @@ describe('/inscriptions', () => { rarity: 'common', coinbase_height: '51483', }); + await insertTestInscriptionParent(db.sql, { + inscription_id: 'f351d86c6e6cae3c64e297e7463095732f216875bcc1f3c03f950a492bb25421i0', + parent_inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + }); const response = await fastify.inject({ method: 'GET', url: '/ordinals/v1/inscriptions/f351d86c6e6cae3c64e297e7463095732f216875bcc1f3c03f950a492bb25421i0', @@ -319,7 +320,6 @@ describe('/inscriptions', () => { pointer: null, metadata: JSON.stringify({ foo: 'bar', test: 1337 }), metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -361,7 +361,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -443,7 +442,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -525,7 +523,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -675,7 +672,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -788,7 +784,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -938,7 +933,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -981,7 +975,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -1014,7 +1007,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', timestamp: 1676913207, output: '42174ecc8a245841035793390bb53d63b3c2acb61366446f601b09e73b94b656:0', @@ -1059,7 +1051,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -1212,7 +1203,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -1245,7 +1235,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '7ac73ecd01b9da4a7eab904655416dbfe8e03f193e091761b5a63ad0963570cd:0', @@ -1574,7 +1563,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -1607,7 +1595,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -1714,7 +1701,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -1747,7 +1733,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -1874,7 +1859,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -1907,7 +1891,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -1970,7 +1953,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -2003,7 +1985,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -2069,7 +2050,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -2102,7 +2082,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -2164,7 +2143,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -2197,7 +2175,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -2261,7 +2238,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '25b372de3de0cb6fcc52c89a8bc3fb78eec596521ba20de16e53c1585be7c3fc:0', @@ -2307,7 +2283,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -2340,7 +2315,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -2388,7 +2362,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1677731361, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -2421,7 +2394,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1675312161, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -2477,7 +2449,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1677731361, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -2510,7 +2481,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1675312161, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -2566,7 +2536,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1677731361, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -2599,7 +2568,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1675312161, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -2655,7 +2623,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1677731361, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -2688,7 +2655,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1675312161, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -2743,7 +2709,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1677731361, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -2776,7 +2741,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1675312161, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -2823,7 +2787,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1677731361, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -2856,7 +2819,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1675312161, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -2913,7 +2875,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1677731361, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -2948,7 +2909,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1675312161, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -3012,7 +2972,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1677731361, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -3045,7 +3004,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1675312161, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -3105,7 +3063,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1677731361, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -3138,7 +3095,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1675312161, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -3197,7 +3153,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -3230,7 +3185,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -3263,7 +3217,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1:0', @@ -3321,7 +3274,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -3354,7 +3306,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -3387,7 +3338,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1:0', @@ -3445,7 +3395,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -3478,7 +3427,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -3511,7 +3459,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1:0', @@ -3569,7 +3516,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', @@ -3602,7 +3548,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -3635,7 +3580,6 @@ describe('/inscriptions', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207, output: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1:0', diff --git a/api/ordinals/tests/api/sats.test.ts b/api/ordinals/tests/api/sats.test.ts index 1aca567..2609c81 100644 --- a/api/ordinals/tests/api/sats.test.ts +++ b/api/ordinals/tests/api/sats.test.ts @@ -74,7 +74,6 @@ describe('/sats', () => { delegate: null, metaprotocol: null, metadata: null, - parent: null, block_height: '775617', block_hash: '163de66dc9c0949905bfe8e148bde04600223cf88d19f26fdbeba1d6e6fa0f88', tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', @@ -118,7 +117,6 @@ describe('/sats', () => { delegate: null, metaprotocol: null, metadata: null, - parent: null, block_height: '775617', block_hash: '163de66dc9c0949905bfe8e148bde04600223cf88d19f26fdbeba1d6e6fa0f88', tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', @@ -152,7 +150,6 @@ describe('/sats', () => { delegate: null, metaprotocol: null, metadata: null, - parent: null, block_height: '775618', block_hash: '000000000000000000002a244dc7dfcf8ab85e42d182531c27197fc125086f19', tx_id: 'b9cd9489fe30b81d007f753663d12766f1368721a87f4c69056c8215caa57993', diff --git a/api/ordinals/tests/api/status.test.ts b/api/ordinals/tests/api/status.test.ts index 1fc695f..968b022 100644 --- a/api/ordinals/tests/api/status.test.ts +++ b/api/ordinals/tests/api/status.test.ts @@ -67,7 +67,6 @@ describe('Status', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207000, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', @@ -100,7 +99,6 @@ describe('Status', () => { pointer: null, metadata: null, metaprotocol: null, - parent: null, delegate: null, timestamp: 1676913207000, output: 'a98d7055a77fa0b96cc31e30bb8bacf777382d1b67f1b7eca6f2014e961591c8:0', diff --git a/api/ordinals/tests/helpers.ts b/api/ordinals/tests/helpers.ts index 0f18461..c7594ee 100644 --- a/api/ordinals/tests/helpers.ts +++ b/api/ordinals/tests/helpers.ts @@ -75,7 +75,6 @@ type TestOrdinalsInscriptionsRow = { pointer: string | null; metadata: string | null; metaprotocol: string | null; - parent: string | null; delegate: string | null; timestamp: number; }; @@ -183,6 +182,17 @@ export async function insertTestInscriptionRecursion( await sql`INSERT INTO inscription_recursions ${sql(row)}`; } +type TestOrdinalsInscriptionParentsRow = { + inscription_id: string; + parent_inscription_id: string; +}; +export async function insertTestInscriptionParent( + sql: PgSqlClient, + row: TestOrdinalsInscriptionParentsRow +) { + await sql`INSERT INTO inscription_parents ${sql(row)}`; +} + export async function updateTestChainTip(sql: PgSqlClient, blockHeight: number) { await sql`UPDATE chain_tip SET block_height = ${blockHeight}`; } @@ -218,7 +228,6 @@ export async function inscriptionReveal(sql: PgSqlClient, reveal: TestOrdinalsIn pointer: reveal.pointer, metadata: reveal.metadata, metaprotocol: reveal.metaprotocol, - parent: reveal.parent, delegate: reveal.delegate, timestamp: reveal.timestamp, }); diff --git a/components/chainhook-postgres/Cargo.toml b/components/chainhook-postgres/Cargo.toml index 146bc29..331a125 100644 --- a/components/chainhook-postgres/Cargo.toml +++ b/components/chainhook-postgres/Cargo.toml @@ -5,12 +5,11 @@ edition = "2021" [dependencies] bytes = "1.3" -chainhook-sdk = { version = "=0.12.10" } +deadpool-postgres = { workspace = true } num-traits = "0.2.14" slog = { version = "2.7.0" } -tokio-postgres = "0.7.10" tokio = { version = "1.38.0", features = ["rt-multi-thread", "macros"] } -deadpool-postgres = "0.14.0" +tokio-postgres = { workspace = true } [dev-dependencies] test-case = "3.1.0" diff --git a/components/chainhook-postgres/src/lib.rs b/components/chainhook-postgres/src/lib.rs index 09cdeec..8650249 100644 --- a/components/chainhook-postgres/src/lib.rs +++ b/components/chainhook-postgres/src/lib.rs @@ -1,10 +1,7 @@ pub mod types; pub mod utils; -pub use deadpool_postgres; use deadpool_postgres::{Manager, ManagerConfig, Object, Pool, RecyclingMethod, Transaction}; -pub use tokio_postgres; - use tokio_postgres::{Client, Config, NoTls, Row}; /// Standard chunk size to use when we're batching multiple query inserts into a single SQL statement to save on DB round trips. diff --git a/components/chainhook-sdk/Cargo.toml b/components/chainhook-sdk/Cargo.toml new file mode 100644 index 0000000..81c9923 --- /dev/null +++ b/components/chainhook-sdk/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "chainhook-sdk" +version = "0.12.12" +description = "Stateless Transaction Indexing Engine for Stacks and Bitcoin" +license = "GPL-3.0" +edition = "2021" + +[dependencies] +serde = { version = "1", features = ["rc"] } +serde_json = { version = "1", features = ["arbitrary_precision"] } +serde-hex = "0.1.0" +serde_derive = "1" +hiro-system-kit = { version = "0.3.4", optional = true } +rocket = { version = "=0.5.0", features = ["json"] } +bitcoin = { workspace = true } +bitcoincore-rpc = "0.18.0" +bitcoincore-rpc-json = "0.18.0" +reqwest = { version = "0.12", default-features = false, features = [ + "blocking", + "json", + "rustls-tls", +] } +tokio = { version = "1.38.1", features = ["full"] } +base58 = "0.2.0" +crossbeam-channel = "0.5.6" +hex = "0.4.3" +zmq = "0.10.0" +lazy_static = "1.4.0" + +chainhook-types = { path = "../chainhook-types-rs" } + +[dev-dependencies] +assert-json-diff = "2.0.2" +test-case = "3.1.0" + +[features] +default = ["hiro-system-kit/log"] +debug = ["hiro-system-kit/debug"] +release = ["hiro-system-kit/release_debug", "hiro-system-kit/full_log_level_prefix"] diff --git a/components/chainhook-sdk/src/indexer/bitcoin/mod.rs b/components/chainhook-sdk/src/indexer/bitcoin/mod.rs new file mode 100644 index 0000000..d83af07 --- /dev/null +++ b/components/chainhook-sdk/src/indexer/bitcoin/mod.rs @@ -0,0 +1,470 @@ +use std::time::Duration; + +use crate::observer::BitcoinConfig; +use crate::utils::Context; +use bitcoincore_rpc::bitcoin::hashes::Hash; +use bitcoincore_rpc::bitcoin::{self, Amount, BlockHash}; +use bitcoincore_rpc::jsonrpc::error::RpcError; +use bitcoincore_rpc_json::GetRawTransactionResultVoutScriptPubKey; +use chainhook_types::bitcoin::{OutPoint, TxIn, TxOut}; +use chainhook_types::{ + BitcoinBlockData, BitcoinBlockMetadata, BitcoinNetwork, BitcoinTransactionData, + BitcoinTransactionMetadata, BlockHeader, BlockIdentifier, TransactionIdentifier, +}; +use hiro_system_kit::slog; +use reqwest::Client as HttpClient; +use serde::Deserialize; + +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BitcoinBlockFullBreakdown { + pub hash: String, + pub height: usize, + pub tx: Vec, + pub time: usize, + pub nonce: u32, + pub previousblockhash: Option, + pub confirmations: i32, +} + +impl BitcoinBlockFullBreakdown { + pub fn get_block_header(&self) -> BlockHeader { + // Block id + let hash = format!("0x{}", self.hash); + let block_identifier = BlockIdentifier { + index: self.height as u64, + hash, + }; + // Parent block id + let parent_block_hash = match self.previousblockhash { + Some(ref value) => format!("0x{}", value), + None => format!("0x{}", BlockHash::all_zeros()), + }; + let parent_block_identifier = BlockIdentifier { + index: (self.height - 1) as u64, + hash: parent_block_hash, + }; + BlockHeader { + block_identifier, + parent_block_identifier, + } + } +} + +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BitcoinTransactionFullBreakdown { + pub txid: String, + pub vin: Vec, + pub vout: Vec, +} + +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BitcoinTransactionInputFullBreakdown { + pub sequence: u32, + /// The raw scriptSig in case of a coinbase tx. + // #[serde(default, with = "bitcoincore_rpc_json::serde_hex::opt")] + // pub coinbase: Option>, + /// Not provided for coinbase txs. + pub txid: Option, + /// Not provided for coinbase txs. + pub vout: Option, + /// The scriptSig in case of a non-coinbase tx. + pub script_sig: Option, + /// Not provided for coinbase txs. + pub txinwitness: Option>, + pub prevout: Option, +} + +#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GetRawTransactionResultVinScriptSig { + pub hex: String, +} + +impl BitcoinTransactionInputFullBreakdown { + /// Whether this input is from a coinbase tx. If there is not a [BitcoinTransactionInputFullBreakdown::txid] field, the transaction is a coinbase transaction. + // Note: vout and script_sig fields are also not provided for coinbase transactions. + pub fn is_coinbase(&self) -> bool { + self.txid.is_none() + } +} + +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BitcoinTransactionInputPrevoutFullBreakdown { + pub height: u64, + #[serde(with = "bitcoin::amount::serde::as_btc")] + pub value: Amount, +} + +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BitcoinTransactionOutputFullBreakdown { + #[serde(with = "bitcoin::amount::serde::as_btc")] + pub value: Amount, + pub n: u32, + pub script_pub_key: GetRawTransactionResultVoutScriptPubKey, +} + +#[derive(Deserialize, Serialize)] +pub struct NewBitcoinBlock { + pub burn_block_hash: String, + pub burn_block_height: u64, + pub reward_slot_holders: Vec, + pub reward_recipients: Vec, + pub burn_amount: u64, +} + +#[allow(dead_code)] +#[derive(Deserialize, Serialize)] +pub struct RewardParticipant { + recipient: String, + amt: u64, +} + +pub fn build_http_client() -> HttpClient { + HttpClient::builder() + .timeout(Duration::from_secs(15)) + .http1_only() + .no_hickory_dns() + .connect_timeout(Duration::from_secs(15)) + .tcp_keepalive(Some(Duration::from_secs(15))) + .no_proxy() + .danger_accept_invalid_certs(true) + .build() + .expect("Unable to build http client") +} + +pub async fn download_and_parse_block_with_retry( + http_client: &HttpClient, + block_hash: &str, + bitcoin_config: &BitcoinConfig, + ctx: &Context, +) -> Result { + let mut errors_count = 0; + let max_retries = 20; + let block = loop { + match download_and_parse_block(http_client, block_hash, bitcoin_config, ctx).await { + Ok(result) => break result, + Err(e) => { + errors_count += 1; + if errors_count > 3 && errors_count < max_retries { + ctx.try_log(|logger| { + slog::warn!( + logger, + "unable to fetch and parse block #{block_hash}: will retry in a few seconds (attempt #{errors_count}). Error: {e}", + ) + }); + } else if errors_count == max_retries { + return Err(format!("unable to fetch and parse block #{block_hash} after {errors_count} attempts. Error: {e}")); + } + std::thread::sleep(std::time::Duration::from_secs(1)); + } + } + }; + Ok(block) +} + +pub async fn retrieve_block_hash_with_retry( + http_client: &HttpClient, + block_height: &u64, + bitcoin_config: &BitcoinConfig, + ctx: &Context, +) -> Result { + let mut errors_count = 0; + let max_retries = 10; + let block_hash = loop { + match retrieve_block_hash(http_client, block_height, bitcoin_config, ctx).await { + Ok(result) => break result, + Err(e) => { + errors_count += 1; + if errors_count > 3 && errors_count < max_retries { + ctx.try_log(|logger| { + slog::warn!( + logger, + "unable to retrieve block hash #{block_height}: will retry in a few seconds (attempt #{errors_count}). Error: {e}", + ) + }); + } else if errors_count == max_retries { + return Err(format!("unable to retrieve block hash #{block_height} after {errors_count} attempts. Error: {e}")); + } + std::thread::sleep(std::time::Duration::from_secs(2)); + } + } + }; + Ok(block_hash) +} + +pub async fn retrieve_block_hash( + http_client: &HttpClient, + block_height: &u64, + bitcoin_config: &BitcoinConfig, + _ctx: &Context, +) -> Result { + let body = json!({ + "jsonrpc": "1.0", + "id": "chainhook-cli", + "method": "getblockhash", + "params": [block_height] + }); + let block_hash = http_client + .post(&bitcoin_config.rpc_url) + .basic_auth(&bitcoin_config.username, Some(&bitcoin_config.password)) + .header("Content-Type", "application/json") + .header("Host", &bitcoin_config.rpc_url[7..]) + .json(&body) + .send() + .await + .map_err(|e| format!("unable to send request ({})", e))? + .json::() + .await + .map_err(|e| format!("unable to parse response ({})", e))? + .result::() + .map_err(|e| format!("unable to parse response ({})", e))?; + + Ok(block_hash) +} + +// not used internally by chainhook; exported for ordhook +pub async fn try_download_block_bytes_with_retry( + http_client: HttpClient, + block_height: u64, + bitcoin_config: BitcoinConfig, + ctx: Context, +) -> Result, String> { + let block_hash = + retrieve_block_hash_with_retry(&http_client, &block_height, &bitcoin_config, &ctx) + .await + .unwrap(); + + let mut errors_count = 0; + + let response = loop { + match download_block(&http_client, &block_hash, &bitcoin_config, &ctx).await { + Ok(result) => break result, + Err(_e) => { + errors_count += 1; + if errors_count > 1 { + ctx.try_log(|logger| { + slog::warn!( + logger, + "unable to fetch block #{block_hash}: will retry in a few seconds (attempt #{errors_count}).", + ) + }); + } + std::thread::sleep(std::time::Duration::from_millis(1500)); + continue; + } + } + }; + Ok(response) +} + +#[derive(Debug, Clone, Deserialize)] +pub struct RpcErrorResponse { + pub error: RpcError, +} + +pub async fn download_block( + http_client: &HttpClient, + block_hash: &str, + bitcoin_config: &BitcoinConfig, + _ctx: &Context, +) -> Result, String> { + let body = json!({ + "jsonrpc": "1.0", + "id": "chainhook-cli", + "method": "getblock", + "params": [block_hash, 3] + }); + let res = http_client + .post(&bitcoin_config.rpc_url) + .basic_auth(&bitcoin_config.username, Some(&bitcoin_config.password)) + .header("Content-Type", "application/json") + .header("Host", &bitcoin_config.rpc_url[7..]) + .json(&body) + .send() + .await + .map_err(|e| format!("unable to send request ({})", e))?; + + // Check status code + if !res.status().is_success() { + return Err(format!( + "http request unsuccessful ({:?})", + res.error_for_status() + )); + } + + let rpc_response_bytes = res + .bytes() + .await + .map_err(|e| format!("unable to get bytes ({})", e))? + .to_vec(); + + // Check rpc error presence + if let Ok(rpc_error) = serde_json::from_slice::(&rpc_response_bytes[..]) { + return Err(format!( + "rpc request unsuccessful ({})", + rpc_error.error.message + )); + } + + Ok(rpc_response_bytes) +} + +pub fn parse_downloaded_block( + downloaded_block: Vec, +) -> Result { + let block = serde_json::from_slice::(&downloaded_block[..]) + .map_err(|e| format!("unable to parse jsonrpc payload ({})", e))? + .result::() + .map_err(|e| format!("unable to parse block ({})", e))?; + Ok(block) +} + +pub async fn download_and_parse_block( + http_client: &HttpClient, + block_hash: &str, + bitcoin_config: &BitcoinConfig, + _ctx: &Context, +) -> Result { + let response = download_block(http_client, block_hash, bitcoin_config, _ctx).await?; + parse_downloaded_block(response) +} + +pub fn standardize_bitcoin_block( + block: BitcoinBlockFullBreakdown, + network: &BitcoinNetwork, + ctx: &Context, +) -> Result { + let mut transactions = vec![]; + let block_height = block.height as u64; + + ctx.try_log(|logger| slog::debug!(logger, "Standardizing Bitcoin block {}", block.hash,)); + + for (tx_index, mut tx) in block.tx.into_iter().enumerate() { + let txid = tx.txid.to_string(); + + let mut inputs = vec![]; + let mut sats_in = 0; + for (index, input) in tx.vin.drain(..).enumerate() { + if input.is_coinbase() { + continue; + } + let prevout = input.prevout.as_ref().ok_or(( + format!( + "error retrieving prevout for transaction {}, input #{} (block #{})", + tx.txid, index, block.height + ), + true, + ))?; + + let txid = input.txid.as_ref().ok_or(( + format!( + "error retrieving txid for transaction {}, input #{} (block #{})", + tx.txid, index, block.height + ), + true, + ))?; + + let vout = input.vout.ok_or(( + format!( + "error retrieving vout for transaction {}, input #{} (block #{})", + tx.txid, index, block.height + ), + true, + ))?; + + let script_sig = input.script_sig.ok_or(( + format!( + "error retrieving script_sig for transaction {}, input #{} (block #{})", + tx.txid, index, block.height + ), + true, + ))?; + + sats_in += prevout.value.to_sat(); + + inputs.push(TxIn { + previous_output: OutPoint { + txid: TransactionIdentifier::new(&txid.to_string()), + vout, + block_height: prevout.height, + value: prevout.value.to_sat(), + }, + script_sig: format!("0x{}", script_sig.hex), + sequence: input.sequence, + witness: input + .txinwitness + .unwrap_or(vec![]) + .to_vec() + .iter() + .map(|w| format!("0x{}", w)) + .collect::>(), + }); + } + + let mut outputs = vec![]; + let mut sats_out = 0; + for output in tx.vout.drain(..) { + let value = output.value.to_sat(); + sats_out += value; + outputs.push(TxOut { + value, + script_pubkey: format!("0x{}", hex::encode(&output.script_pub_key.hex)), + }); + } + + let tx = BitcoinTransactionData { + transaction_identifier: TransactionIdentifier { + hash: format!("0x{}", txid), + }, + operations: vec![], + metadata: BitcoinTransactionMetadata { + inputs, + outputs, + ordinal_operations: vec![], + brc20_operation: None, + proof: None, + fee: sats_in.saturating_sub(sats_out), + index: tx_index as u32, + }, + }; + transactions.push(tx); + } + + Ok(BitcoinBlockData { + block_identifier: BlockIdentifier { + hash: format!("0x{}", block.hash), + index: block_height, + }, + parent_block_identifier: BlockIdentifier { + hash: format!( + "0x{}", + block + .previousblockhash + .unwrap_or(BlockHash::all_zeros().to_string()) + ), + index: match block_height { + 0 => 0, + _ => block_height - 1, + }, + }, + timestamp: block.time as u32, + metadata: BitcoinBlockMetadata { + network: network.clone(), + }, + transactions, + }) +} + +#[cfg(test)] +pub mod tests; + +// Test vectors +// 1) Devnet PoB +// 2022-10-26T03:06:17.376341Z INFO chainhook_event_observer::indexer: BitcoinBlockData { block_identifier: BlockIdentifier { index: 104, hash: "0x210d0d095a75d88fc059cb97f453eee33b1833153fb1f81b9c3c031c26bb106b" }, parent_block_identifier: BlockIdentifier { index: 103, hash: "0x5d5a4b8113c35f20fb0b69b1fb1ae1b88461ea57e2a2e4c036f97fae70ca1abb" }, timestamp: 1666753576, transactions: [BitcoinTransactionData { transaction_identifier: TransactionIdentifier { hash: "0xfaaac1833dc4883e7ec28f61e35b41f896c395f8d288b1a177155de2abd6052f" }, operations: [], metadata: BitcoinTransactionMetadata { inputs: [TxIn { previous_output: OutPoint { txid: "0000000000000000000000000000000000000000000000000000000000000000", vout: 4294967295 }, script_sig: "01680101", sequence: 4294967295, witness: [] }], outputs: [TxOut { value: 5000017550, script_pubkey: "76a914ee9369fb719c0ba43ddf4d94638a970b84775f4788ac" }, TxOut { value: 0, script_pubkey: "6a24aa21a9ed4a190dfdc77e260409c2a693e6d3b8eca43afbc4bffb79ddcdcc9516df804d9b" }], stacks_operations: [] } }, BitcoinTransactionData { transaction_identifier: TransactionIdentifier { hash: "0x59193c24cb2325cd2271b89f790f958dcd4065088680ffbc201a0ebb2f3cbf25" }, operations: [], metadata: BitcoinTransactionMetadata { inputs: [TxIn { previous_output: OutPoint { txid: "9eebe848baaf8dd4810e4e4a91168e2e471c949439faf5d768750ca21d067689", vout: 3 }, script_sig: "483045022100a20f90e9e3c3bb7e558ad4fa65902d8cf6ce4bff1f5af0ac0a323b547385069c022021b9877abbc9d1eef175c7f712ac1b2d8f5ce566be542714effe42711e75b83801210239810ebf35e6f6c26062c99f3e183708d377720617c90a986859ec9c95d00be9", sequence: 4294967293, witness: [] }], outputs: [TxOut { value: 0, script_pubkey: "6a4c5069645b1681995f8e568287e0e4f5cbc1d6727dafb5e3a7822a77c69bd04208265aca9424d0337dac7d9e84371a2c91ece1891d67d3554bd9fdbe60afc6924d4b0773d90000006700010000006600012b" }, TxOut { value: 10000, script_pubkey: "76a914000000000000000000000000000000000000000088ac" }, TxOut { value: 10000, script_pubkey: "76a914000000000000000000000000000000000000000088ac" }, TxOut { value: 4999904850, script_pubkey: "76a914ee9369fb719c0ba43ddf4d94638a970b84775f4788ac" }], stacks_operations: [PobBlockCommitment(PobBlockCommitmentData { signers: [], stacks_block_hash: "0x5b1681995f8e568287e0e4f5cbc1d6727dafb5e3a7822a77c69bd04208265aca", amount: 10000 })] } }], metadata: BitcoinBlockMetadata } +// 2022-10-26T03:06:21.929157Z INFO chainhook_event_observer::indexer: BitcoinBlockData { block_identifier: BlockIdentifier { index: 105, hash: "0x0302c4c6063eb7199d3a565351bceeea9df4cb4aa09293194dbab277e46c2979" }, parent_block_identifier: BlockIdentifier { index: 104, hash: "0x210d0d095a75d88fc059cb97f453eee33b1833153fb1f81b9c3c031c26bb106b" }, timestamp: 1666753581, transactions: [BitcoinTransactionData { transaction_identifier: TransactionIdentifier { hash: "0xe7de433aa89c1f946f89133b0463b6cfebb26ad73b0771a79fd66c6acbfe3fb9" }, operations: [], metadata: BitcoinTransactionMetadata { inputs: [TxIn { previous_output: OutPoint { txid: "0000000000000000000000000000000000000000000000000000000000000000", vout: 4294967295 }, script_sig: "01690101", sequence: 4294967295, witness: [] }], outputs: [TxOut { value: 5000017600, script_pubkey: "76a914ee9369fb719c0ba43ddf4d94638a970b84775f4788ac" }, TxOut { value: 0, script_pubkey: "6a24aa21a9ed98ac3bc4e0c9ed53e3418a3bf3aa511dcd76088cf0e1c4fc71fb9755840d7a08" }], stacks_operations: [] } }, BitcoinTransactionData { transaction_identifier: TransactionIdentifier { hash: "0xe654501805d80d59ef0d95b57ad7a924f3be4a4dc0db5a785dfebe1f70c4e23e" }, operations: [], metadata: BitcoinTransactionMetadata { inputs: [TxIn { previous_output: OutPoint { txid: "59193c24cb2325cd2271b89f790f958dcd4065088680ffbc201a0ebb2f3cbf25", vout: 3 }, script_sig: "483045022100b59d2d07f68ea3a4f27a49979080a07b2432cfad9fc90e1edd0241496f0fd83f02205ac233f4cb68ada487f16339abedb7093948b683ba7d76b3b4058b2c0181a68901210239810ebf35e6f6c26062c99f3e183708d377720617c90a986859ec9c95d00be9", sequence: 4294967293, witness: [] }], outputs: [TxOut { value: 0, script_pubkey: "6a4c5069645b351bb015ef4f7dcdce4c9d95cbf157f85a3714626252cfc9078f3f1591ccdb13c3c7e22b34c4ffc2f6064a41df6fcd7f1b759d4f28b2f7cb6b27f283c868406e0000006800010000006600012c" }, TxOut { value: 10000, script_pubkey: "76a914000000000000000000000000000000000000000088ac" }, TxOut { value: 10000, script_pubkey: "76a914000000000000000000000000000000000000000088ac" }, TxOut { value: 4999867250, script_pubkey: "76a914ee9369fb719c0ba43ddf4d94638a970b84775f4788ac" }], stacks_operations: [PobBlockCommitment(PobBlockCommitmentData { signers: [], stacks_block_hash: "0x5b351bb015ef4f7dcdce4c9d95cbf157f85a3714626252cfc9078f3f1591ccdb", amount: 10000 })] } }], metadata: BitcoinBlockMetadata } +// 2022-10-26T03:07:53.298531Z INFO chainhook_event_observer::indexer: BitcoinBlockData { block_identifier: BlockIdentifier { index: 106, hash: "0x52eb2aa15aa99afc4b918a552cef13e8b6eed84b257be097ad954b4f37a7e98d" }, parent_block_identifier: BlockIdentifier { index: 105, hash: "0x0302c4c6063eb7199d3a565351bceeea9df4cb4aa09293194dbab277e46c2979" }, timestamp: 1666753672, transactions: [BitcoinTransactionData { transaction_identifier: TransactionIdentifier { hash: "0xd28d7f5411416f94b95e9f999d5ee8ded5543ba9daae9f612b80f01c5107862d" }, operations: [], metadata: BitcoinTransactionMetadata { inputs: [TxIn { previous_output: OutPoint { txid: "0000000000000000000000000000000000000000000000000000000000000000", vout: 4294967295 }, script_sig: "016a0101", sequence: 4294967295, witness: [] }], outputs: [TxOut { value: 5000017500, script_pubkey: "76a914ee9369fb719c0ba43ddf4d94638a970b84775f4788ac" }, TxOut { value: 0, script_pubkey: "6a24aa21a9ed71aaf7e5384879a1b112bf623ac8b46dd88b39c3d2c6f8a1d264fc4463e6356a" }], stacks_operations: [] } }, BitcoinTransactionData { transaction_identifier: TransactionIdentifier { hash: "0x72e8e43afc4362cf921ccc57fde3e07b4cb6fac5f306525c86d38234c18e21d1" }, operations: [], metadata: BitcoinTransactionMetadata { inputs: [TxIn { previous_output: OutPoint { txid: "e654501805d80d59ef0d95b57ad7a924f3be4a4dc0db5a785dfebe1f70c4e23e", vout: 3 }, script_sig: "4730440220798bb7d7fb14df35610db2ef04e5d5b6588440b7c429bf650a96f8570904052b02204a817e13e7296a24a8f6cc8737bddb55d1835e513ec2b9dcb03424e4536ae34c01210239810ebf35e6f6c26062c99f3e183708d377720617c90a986859ec9c95d00be9", sequence: 4294967293, witness: [] }], outputs: [TxOut { value: 0, script_pubkey: "6a4c5069645b504d310fc27c86a6b65d0b0e0297db1e185d3432fdab9fa96a1053407ed07b537b8b7d23c6309dfd24340e85b75cff11ad685f8b310c1d2098748a0fffb146ec00000069000100000066000128" }, TxOut { value: 20000, script_pubkey: "76a914000000000000000000000000000000000000000088ac" }, TxOut { value: 4999829750, script_pubkey: "76a914ee9369fb719c0ba43ddf4d94638a970b84775f4788ac" }], stacks_operations: [PobBlockCommitment(PobBlockCommitmentData { signers: [], stacks_block_hash: "0x5b504d310fc27c86a6b65d0b0e0297db1e185d3432fdab9fa96a1053407ed07b", amount: 20000 })] } }], metadata: BitcoinBlockMetadata } diff --git a/components/chainhook-sdk/src/indexer/bitcoin/tests.rs b/components/chainhook-sdk/src/indexer/bitcoin/tests.rs new file mode 100644 index 0000000..b43dcf4 --- /dev/null +++ b/components/chainhook-sdk/src/indexer/bitcoin/tests.rs @@ -0,0 +1,206 @@ +use super::super::tests::{helpers, process_bitcoin_blocks_and_check_expectations}; + +#[test] +fn test_bitcoin_vector_001() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_001()); +} + +#[test] +fn test_bitcoin_vector_002() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_002()); +} + +#[test] +fn test_bitcoin_vector_003() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_003()); +} + +#[test] +fn test_bitcoin_vector_004() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_004()); +} + +#[test] +fn test_bitcoin_vector_005() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_005()); +} + +#[test] +fn test_bitcoin_vector_006() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_006()); +} + +#[test] +fn test_bitcoin_vector_007() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_007()); +} + +#[test] +fn test_bitcoin_vector_008() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_008()); +} + +#[test] +fn test_bitcoin_vector_009() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_009()); +} + +#[test] +fn test_bitcoin_vector_010() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_010()); +} + +#[test] +fn test_bitcoin_vector_011() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_011()); +} + +#[test] +fn test_bitcoin_vector_012() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_012()); +} + +#[test] +fn test_bitcoin_vector_013() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_013()); +} + +#[test] +fn test_bitcoin_vector_014() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_014()); +} + +#[test] +fn test_bitcoin_vector_015() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_015()); +} + +#[test] +fn test_bitcoin_vector_016() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_016()); +} + +#[test] +fn test_bitcoin_vector_017() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_017()); +} + +#[test] +fn test_bitcoin_vector_018() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_018()); +} + +#[test] +fn test_bitcoin_vector_019() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_019()); +} + +#[test] +fn test_bitcoin_vector_020() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_020()); +} + +#[test] +fn test_bitcoin_vector_021() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_021()); +} + +#[test] +fn test_bitcoin_vector_022() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_022()); +} + +#[test] +fn test_bitcoin_vector_023() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_023()); +} + +#[test] +fn test_bitcoin_vector_024() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_024()); +} + +#[test] +fn test_bitcoin_vector_025() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_025()); +} + +#[test] +fn test_bitcoin_vector_026() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_026()); +} + +#[test] +fn test_bitcoin_vector_027() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_027()); +} + +#[test] +fn test_bitcoin_vector_028() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_028()); +} + +#[test] +fn test_bitcoin_vector_029() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_029()); +} + +#[test] +fn test_bitcoin_vector_030() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_030()); +} + +#[test] +fn test_bitcoin_vector_031() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_031()); +} + +#[test] +fn test_bitcoin_vector_032() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_032()); +} + +#[test] +fn test_bitcoin_vector_033() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_033()); +} + +#[test] +fn test_bitcoin_vector_034() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_034()); +} + +#[test] +fn test_bitcoin_vector_035() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_035()); +} + +#[test] +fn test_bitcoin_vector_036() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_036()); +} + +#[test] +fn test_bitcoin_vector_037() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_037()); +} + +#[test] +fn test_bitcoin_vector_038() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_038()); +} + +#[test] +fn test_bitcoin_vector_039() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_039()); +} + +#[test] +fn test_bitcoin_vector_040() { + process_bitcoin_blocks_and_check_expectations(helpers::bitcoin_shapes::get_vector_040()); +} + +// #[test] +// fn test_bitcoin_vector_041() { +// process_bitcoin_blocks_and_check_expectations(helpers::shapes::get_vector_041()); +// } diff --git a/components/chainhook-sdk/src/indexer/fork_scratch_pad.rs b/components/chainhook-sdk/src/indexer/fork_scratch_pad.rs new file mode 100644 index 0000000..d95c3a9 --- /dev/null +++ b/components/chainhook-sdk/src/indexer/fork_scratch_pad.rs @@ -0,0 +1,418 @@ +use crate::{ + indexer::{ChainSegment, ChainSegmentIncompatibility}, + utils::Context, +}; +use chainhook_types::{ + BlockHeader, BlockIdentifier, BlockchainEvent, BlockchainUpdatedWithHeaders, + BlockchainUpdatedWithReorg, +}; +use hiro_system_kit::slog; +use std::collections::{BTreeMap, BTreeSet, HashSet}; + +pub struct ForkScratchPad { + canonical_fork_id: usize, + orphans: BTreeSet, + forks: BTreeMap, + headers_store: BTreeMap, +} +pub const CONFIRMED_SEGMENT_MINIMUM_LENGTH: i32 = 7; +impl Default for ForkScratchPad { + fn default() -> Self { + Self::new() + } +} + +impl ForkScratchPad { + pub fn new() -> ForkScratchPad { + let mut forks = BTreeMap::new(); + forks.insert(0, ChainSegment::new()); + let headers_store = BTreeMap::new(); + ForkScratchPad { + canonical_fork_id: 0, + orphans: BTreeSet::new(), + forks, + headers_store, + } + } + + pub fn can_process_header(&self, header: &BlockHeader) -> bool { + if self.headers_store.is_empty() { + return true; + } + + self.headers_store + .contains_key(&header.parent_block_identifier) + } + + pub fn process_header( + &mut self, + header: BlockHeader, + ctx: &Context, + ) -> Result, String> { + ctx.try_log(|logger| slog::info!(logger, "Start processing {}", header.block_identifier)); + + // Keep block data in memory + let entry_exists = self + .headers_store + .insert(header.block_identifier.clone(), header.clone()); + if entry_exists.is_some() { + ctx.try_log(|logger| { + slog::warn!( + logger, + "Block {} has already been processed", + header.block_identifier + ) + }); + return Ok(None); + } + + for (i, fork) in self.forks.iter() { + ctx.try_log(|logger| slog::info!(logger, "Active fork {}: {}", i, fork)); + } + // Retrieve previous canonical fork + let previous_canonical_fork_id = self.canonical_fork_id; + let previous_canonical_fork = match self.forks.get(&previous_canonical_fork_id) { + Some(fork) => fork.clone(), + None => { + ctx.try_log(|logger| { + slog::error!(logger, "unable to retrieve previous bitcoin fork") + }); + return Ok(None); + } + }; + + let mut fork_updated = None; + for (_, fork) in self.forks.iter_mut() { + let (block_appended, mut new_fork) = fork.try_append_block(&header, ctx); + if block_appended { + if let Some(new_fork) = new_fork.take() { + let fork_id = self.forks.len(); + self.forks.insert(fork_id, new_fork); + fork_updated = self.forks.get_mut(&fork_id); + } else { + fork_updated = Some(fork); + } + // A block can only be added to one segment + break; + } + } + + let fork_updated = match fork_updated.take() { + Some(fork) => { + ctx.try_log(|logger| { + slog::debug!( + logger, + "Bitcoin {} successfully appended to {}", + header.block_identifier, + fork + ) + }); + fork + } + None => { + ctx.try_log(|logger| { + slog::error!( + logger, + "Unable to process Bitcoin {} - inboxed for later", + header.block_identifier + ) + }); + self.orphans.insert(header.block_identifier.clone()); + return Ok(None); + } + }; + + // Process former orphans + let orphans = self.orphans.clone(); + let mut orphans_to_untrack = HashSet::new(); + + let mut at_least_one_orphan_appended = true; + // As long as we are successful appending blocks that were previously unprocessable, + // Keep looping on this backlog + let mut applied = HashSet::new(); + let mut forks_created = vec![]; + while at_least_one_orphan_appended { + at_least_one_orphan_appended = false; + for orphan_block_identifier in orphans.iter() { + if applied.contains(orphan_block_identifier) { + continue; + } + let block = match self.headers_store.get(orphan_block_identifier) { + Some(block) => block.clone(), + None => continue, + }; + + let (orphan_appended, mut new_fork) = fork_updated.try_append_block(&block, ctx); + if orphan_appended { + applied.insert(orphan_block_identifier); + orphans_to_untrack.insert(orphan_block_identifier); + if let Some(new_fork) = new_fork.take() { + forks_created.push(new_fork); + } + } + at_least_one_orphan_appended = at_least_one_orphan_appended || orphan_appended; + } + } + + // Update orphans + for orphan in orphans_to_untrack.into_iter() { + ctx.try_log(|logger| slog::info!(logger, "Dequeuing orphan {}", orphan)); + self.orphans.remove(orphan); + } + + // Select canonical fork + let mut canonical_fork_id = 0; + let mut highest_height = 0; + for (fork_id, fork) in self.forks.iter() { + ctx.try_log(|logger| slog::info!(logger, "Active fork: {} - {}", fork_id, fork)); + if fork.get_length() >= highest_height { + highest_height = fork.get_length(); + canonical_fork_id = *fork_id; + } + } + ctx.try_log(|logger| { + slog::info!( + logger, + "Active fork selected as canonical: {}", + canonical_fork_id + ) + }); + + self.canonical_fork_id = canonical_fork_id; + // Generate chain event from the previous and current canonical forks + let canonical_fork = self.forks.get(&canonical_fork_id).unwrap().clone(); + if canonical_fork.eq(&previous_canonical_fork) { + ctx.try_log(|logger| slog::info!(logger, "Canonical fork unchanged")); + return Ok(None); + } + + let res = self.generate_block_chain_event(&canonical_fork, &previous_canonical_fork, ctx); + let mut chain_event = match res { + Ok(chain_event) => chain_event, + Err(ChainSegmentIncompatibility::ParentBlockUnknown) => { + self.canonical_fork_id = previous_canonical_fork_id; + return Ok(None); + } + _ => return Ok(None), + }; + + self.collect_and_prune_confirmed_blocks(&mut chain_event, ctx); + + Ok(Some(chain_event)) + } + + pub fn collect_and_prune_confirmed_blocks( + &mut self, + chain_event: &mut BlockchainEvent, + ctx: &Context, + ) { + let (tip, confirmed_blocks) = match chain_event { + BlockchainEvent::BlockchainUpdatedWithHeaders(ref mut event) => { + match event.new_headers.last() { + Some(tip) => (tip.block_identifier.clone(), &mut event.confirmed_headers), + None => return, + } + } + BlockchainEvent::BlockchainUpdatedWithReorg(ref mut event) => { + match event.headers_to_apply.last() { + Some(tip) => (tip.block_identifier.clone(), &mut event.confirmed_headers), + None => return, + } + } + }; + + let mut forks_to_prune = vec![]; + let mut ancestor_identifier = &tip; + + // Retrieve the whole canonical segment present in memory, ascending order + // [1] ... [6] [7] + let canonical_segment = { + let mut segment = vec![]; + while let Some(ancestor) = self.headers_store.get(ancestor_identifier) { + ancestor_identifier = &ancestor.parent_block_identifier; + segment.push(ancestor.block_identifier.clone()); + } + segment + }; + if canonical_segment.len() < CONFIRMED_SEGMENT_MINIMUM_LENGTH as usize { + return; + } + // Any block beyond 6th ancestor is considered as confirmed and can be pruned + let cut_off = &canonical_segment[(CONFIRMED_SEGMENT_MINIMUM_LENGTH - 2) as usize]; + + // Prune forks using the confirmed block + let mut blocks_to_prune = vec![]; + for (fork_id, fork) in self.forks.iter_mut() { + let mut res = fork.prune_confirmed_blocks(cut_off); + blocks_to_prune.append(&mut res); + if fork.block_ids.is_empty() { + forks_to_prune.push(*fork_id); + } + } + + // Prune orphans using the confirmed block + let iter = self.orphans.clone().into_iter(); + for orphan in iter { + if orphan.index < cut_off.index { + self.orphans.remove(&orphan); + blocks_to_prune.push(orphan); + } + } + + ctx.try_log(|logger| { + slog::debug!( + logger, + "Removing {} confirmed blocks from block store.", + canonical_segment[6..].len() + ) + }); + for confirmed_block in canonical_segment[6..].iter() { + let block = match self.headers_store.remove(confirmed_block) { + None => { + ctx.try_log(|logger| { + slog::error!(logger, "unable to retrieve data for {}", confirmed_block) + }); + return; + } + Some(block) => block, + }; + confirmed_blocks.push(block); + } + + // Prune data + ctx.try_log(|logger| { + slog::debug!( + logger, + "Pruning {} blocks and {} forks.", + blocks_to_prune.len(), + forks_to_prune.len() + ) + }); + for block_to_prune in blocks_to_prune { + self.headers_store.remove(&block_to_prune); + } + for fork_id in forks_to_prune { + self.forks.remove(&fork_id); + } + confirmed_blocks.reverse(); + } + + pub fn generate_block_chain_event( + &mut self, + canonical_segment: &ChainSegment, + other_segment: &ChainSegment, + ctx: &Context, + ) -> Result { + if other_segment.is_empty() { + let mut new_headers = vec![]; + for i in 0..canonical_segment.block_ids.len() { + let block_identifier = + &canonical_segment.block_ids[canonical_segment.block_ids.len() - 1 - i]; + let header = match self.headers_store.get(block_identifier) { + Some(block) => block.clone(), + None => { + ctx.try_log(|logger| { + slog::error!( + logger, + "unable to retrieve Bitcoin block {} from block store", + block_identifier + ) + }); + return Err(ChainSegmentIncompatibility::Unknown); + } + }; + new_headers.push(header) + } + return Ok(BlockchainEvent::BlockchainUpdatedWithHeaders( + BlockchainUpdatedWithHeaders { + new_headers, + confirmed_headers: vec![], + }, + )); + } + if let Ok(divergence) = canonical_segment.try_identify_divergence(other_segment, false, ctx) + { + if divergence.block_ids_to_rollback.is_empty() { + let mut new_headers = vec![]; + for i in 0..divergence.block_ids_to_apply.len() { + let block_identifier = &divergence.block_ids_to_apply[i]; + let header = match self.headers_store.get(block_identifier) { + Some(header) => header.clone(), + None => { + ctx.try_log(|logger| { + slog::error!( + logger, + "unable to retrieve Bitcoin block {} from block store", + block_identifier + ) + }); + return Err(ChainSegmentIncompatibility::Unknown); + } + }; + new_headers.push(header) + } + return Ok(BlockchainEvent::BlockchainUpdatedWithHeaders( + BlockchainUpdatedWithHeaders { + new_headers, + confirmed_headers: vec![], + }, + )); + } else { + return Ok(BlockchainEvent::BlockchainUpdatedWithReorg( + BlockchainUpdatedWithReorg { + headers_to_rollback: divergence + .block_ids_to_rollback + .iter() + .map(|block_id| { + let block = match self.headers_store.get(block_id) { + Some(block) => block.clone(), + None => { + ctx.try_log(|logger| { + slog::error!( + logger, + "unable to retrieve Bitcoin block {} from block store", + block_id + ) + }); + return Err(ChainSegmentIncompatibility::Unknown); + } + }; + Ok(block) + }) + .collect::, _>>()?, + headers_to_apply: divergence + .block_ids_to_apply + .iter() + .map(|block_id| { + let block = match self.headers_store.get(block_id) { + Some(block) => block.clone(), + None => { + ctx.try_log(|logger| { + slog::error!( + logger, + "unable to retrieve Bitcoin block {} from block store", + block_id + ) + }); + return Err(ChainSegmentIncompatibility::Unknown); + } + }; + Ok(block) + }) + .collect::, _>>()?, + confirmed_headers: vec![], + }, + )); + } + } + ctx.try_log(|logger| { + slog::debug!( + logger, + "Unable to infer chain event out of {} and {}", + canonical_segment, + other_segment + ) + }); + Err(ChainSegmentIncompatibility::ParentBlockUnknown) + } +} diff --git a/components/chainhook-sdk/src/indexer/mod.rs b/components/chainhook-sdk/src/indexer/mod.rs new file mode 100644 index 0000000..df21b35 --- /dev/null +++ b/components/chainhook-sdk/src/indexer/mod.rs @@ -0,0 +1,321 @@ +pub mod bitcoin; +pub mod fork_scratch_pad; + +use crate::utils::{AbstractBlock, Context}; + +use chainhook_types::{ + BitcoinBlockSignaling, BitcoinNetwork, BlockHeader, BlockIdentifier, BlockchainEvent, +}; +use hiro_system_kit::slog; + +use std::collections::VecDeque; + +use self::fork_scratch_pad::ForkScratchPad; + +#[derive(Deserialize, Debug, Clone, Default)] +pub struct AssetClassCache { + pub symbol: String, + pub decimals: u8, +} + +pub struct BitcoinChainContext {} + +impl Default for BitcoinChainContext { + fn default() -> Self { + Self::new() + } +} + +impl BitcoinChainContext { + pub fn new() -> BitcoinChainContext { + BitcoinChainContext {} + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct IndexerConfig { + pub bitcoin_network: BitcoinNetwork, + pub bitcoind_rpc_url: String, + pub bitcoind_rpc_username: String, + pub bitcoind_rpc_password: String, + pub bitcoin_block_signaling: BitcoinBlockSignaling, +} + +pub struct Indexer { + pub config: IndexerConfig, + bitcoin_blocks_pool: ForkScratchPad, + pub bitcoin_context: BitcoinChainContext, +} + +impl Indexer { + pub fn new(config: IndexerConfig) -> Indexer { + let bitcoin_blocks_pool = ForkScratchPad::new(); + let bitcoin_context = BitcoinChainContext::new(); + + Indexer { + config, + bitcoin_blocks_pool, + bitcoin_context, + } + } + + pub fn handle_bitcoin_header( + &mut self, + header: BlockHeader, + ctx: &Context, + ) -> Result, String> { + self.bitcoin_blocks_pool.process_header(header, ctx) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ChainSegment { + pub block_ids: VecDeque, +} + +#[derive(Clone, Debug)] +pub enum ChainSegmentIncompatibility { + OutdatedBlock, + OutdatedSegment, + BlockCollision, + ParentBlockUnknown, + AlreadyPresent, + Unknown, + BlockNotFound, +} + +#[derive(Debug)] +pub struct ChainSegmentDivergence { + block_ids_to_apply: Vec, + block_ids_to_rollback: Vec, +} + +impl Default for ChainSegment { + fn default() -> Self { + Self::new() + } +} + +impl ChainSegment { + pub fn new() -> ChainSegment { + let block_ids = VecDeque::new(); + ChainSegment { block_ids } + } + + fn is_empty(&self) -> bool { + self.block_ids.is_empty() + } + + fn is_block_id_newer_than_segment(&self, block_identifier: &BlockIdentifier) -> bool { + if let Some(tip) = self.block_ids.front() { + return block_identifier.index > (tip.index + 1); + } + false + } + + fn get_relative_index(&self, block_identifier: &BlockIdentifier) -> usize { + if let Some(tip) = self.block_ids.front() { + let segment_index = tip.index.saturating_sub(block_identifier.index); + return segment_index.try_into().unwrap(); + } + 0 + } + + fn can_append_block( + &self, + block: &dyn AbstractBlock, + ctx: &Context, + ) -> Result<(), ChainSegmentIncompatibility> { + if self.is_block_id_newer_than_segment(block.get_identifier()) { + // Chain segment looks outdated, we should just prune it? + return Err(ChainSegmentIncompatibility::OutdatedSegment); + } + let tip = match self.block_ids.front() { + Some(tip) => tip, + None => return Ok(()), + }; + ctx.try_log(|logger| { + slog::info!(logger, "Comparing {} with {}", tip, block.get_identifier()) + }); + if tip.index == block.get_parent_identifier().index { + match tip.hash == block.get_parent_identifier().hash { + true => return Ok(()), + false => return Err(ChainSegmentIncompatibility::ParentBlockUnknown), + } + } + if let Some(colliding_block) = self.get_block_id(block.get_identifier(), ctx) { + match colliding_block.eq(block.get_identifier()) { + true => return Err(ChainSegmentIncompatibility::AlreadyPresent), + false => return Err(ChainSegmentIncompatibility::BlockCollision), + } + } + Err(ChainSegmentIncompatibility::Unknown) + } + + fn get_block_id(&self, block_id: &BlockIdentifier, _ctx: &Context) -> Option<&BlockIdentifier> { + match self.block_ids.get(self.get_relative_index(block_id)) { + Some(res) => Some(res), + None => None, + } + } + + pub fn append_block_identifier(&mut self, block_identifier: &BlockIdentifier) { + self.block_ids.push_front(block_identifier.clone()); + } + + pub fn prune_confirmed_blocks(&mut self, cut_off: &BlockIdentifier) -> Vec { + let mut keep = vec![]; + let mut prune = vec![]; + + for block_id in self.block_ids.drain(..) { + if block_id.index >= cut_off.index { + keep.push(block_id); + } else { + prune.push(block_id); + } + } + for block_id in keep.into_iter() { + self.block_ids.push_back(block_id); + } + prune + } + + pub fn get_tip(&self) -> &BlockIdentifier { + self.block_ids.front().unwrap() + } + + pub fn get_length(&self) -> u64 { + self.block_ids.len().try_into().unwrap() + } + + pub fn keep_blocks_from_oldest_to_block_identifier( + &mut self, + block_identifier: &BlockIdentifier, + ) -> (bool, bool) { + let mut mutated = false; + loop { + match self.block_ids.pop_front() { + Some(tip) => { + if tip.eq(block_identifier) { + self.block_ids.push_front(tip); + break (true, mutated); + } + } + _ => break (false, mutated), + } + mutated = true; + } + } + + fn try_identify_divergence( + &self, + other_segment: &ChainSegment, + allow_reset: bool, + ctx: &Context, + ) -> Result { + let mut common_root = None; + let mut block_ids_to_rollback = vec![]; + let mut block_ids_to_apply = vec![]; + for cursor_segment_1 in other_segment.block_ids.iter() { + block_ids_to_apply.clear(); + for cursor_segment_2 in self.block_ids.iter() { + if cursor_segment_2.eq(cursor_segment_1) { + common_root = Some(cursor_segment_2.clone()); + break; + } + block_ids_to_apply.push(cursor_segment_2.clone()); + } + if common_root.is_some() { + break; + } + block_ids_to_rollback.push(cursor_segment_1.clone()); + } + ctx.try_log(|logger| { + slog::debug!(logger, "Blocks to rollback: {:?}", block_ids_to_rollback) + }); + ctx.try_log(|logger| slog::debug!(logger, "Blocks to apply: {:?}", block_ids_to_apply)); + block_ids_to_apply.reverse(); + match common_root.take() { + Some(_common_root) => Ok(ChainSegmentDivergence { + block_ids_to_rollback, + block_ids_to_apply, + }), + None if allow_reset => Ok(ChainSegmentDivergence { + block_ids_to_rollback, + block_ids_to_apply, + }), + None => Err(ChainSegmentIncompatibility::Unknown), + } + } + + fn try_append_block( + &mut self, + block: &dyn AbstractBlock, + ctx: &Context, + ) -> (bool, Option) { + let mut block_appended = false; + let mut fork = None; + ctx.try_log(|logger| { + slog::info!( + logger, + "Trying to append {} to {}", + block.get_identifier(), + self + ) + }); + match self.can_append_block(block, ctx) { + Ok(()) => { + self.append_block_identifier(block.get_identifier()); + block_appended = true; + } + Err(incompatibility) => { + ctx.try_log(|logger| { + slog::warn!(logger, "Will have to fork: {:?}", incompatibility) + }); + match incompatibility { + ChainSegmentIncompatibility::BlockCollision => { + let mut new_fork = self.clone(); + let (parent_found, _) = new_fork + .keep_blocks_from_oldest_to_block_identifier( + block.get_parent_identifier(), + ); + if parent_found { + ctx.try_log(|logger| slog::info!(logger, "Success")); + new_fork.append_block_identifier(block.get_identifier()); + fork = Some(new_fork); + block_appended = true; + } + } + ChainSegmentIncompatibility::OutdatedSegment => { + // TODO(lgalabru): test depth + // fork_ids_to_prune.push(fork_id); + } + ChainSegmentIncompatibility::ParentBlockUnknown => {} + ChainSegmentIncompatibility::OutdatedBlock => {} + ChainSegmentIncompatibility::Unknown => {} + ChainSegmentIncompatibility::AlreadyPresent => {} + ChainSegmentIncompatibility::BlockNotFound => {} + } + } + } + (block_appended, fork) + } +} + +impl std::fmt::Display for ChainSegment { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "Fork [{}], height = {}", + self.block_ids + .iter() + .map(|b| format!("{}", b)) + .collect::>() + .join(", "), + self.get_length() + ) + } +} + +#[cfg(test)] +pub mod tests; diff --git a/components/chainhook-sdk/src/indexer/tests/helpers/accounts.rs b/components/chainhook-sdk/src/indexer/tests/helpers/accounts.rs new file mode 100644 index 0000000..57b12da --- /dev/null +++ b/components/chainhook-sdk/src/indexer/tests/helpers/accounts.rs @@ -0,0 +1,79 @@ +pub fn deployer_stx_address() -> String { + "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM".to_string() +} + +pub fn wallet_1_stx_address() -> String { + "ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5".to_string() +} + +pub fn wallet_2_stx_address() -> String { + "ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG".to_string() +} + +pub fn wallet_3_stx_address() -> String { + "ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC".to_string() +} + +pub fn wallet_4_stx_address() -> String { + "ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND".to_string() +} + +pub fn wallet_5_stx_address() -> String { + "ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB".to_string() +} + +pub fn wallet_6_stx_address() -> String { + "ST3AM1A56AK2C1XAFJ4115ZSV26EB49BVQ10MGCS0".to_string() +} + +pub fn wallet_7_stx_address() -> String { + "ST3PF13W7Z0RRM42A8VZRVFQ75SV1K26RXEP8YGKJ".to_string() +} + +pub fn wallet_8_stx_address() -> String { + "ST3NBRSFKX28FQ2ZJ1MAKX58HKHSDGNV5N7R21XCP".to_string() +} + +pub fn wallet_9_stx_address() -> String { + "STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6".to_string() +} + +pub fn deployer_btc_address() -> String { + "mqVnk6NPRdhntvfm4hh9vvjiRkFDUuSYsH".to_string() +} + +pub fn wallet_1_btc_address() -> String { + "mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC".to_string() +} + +pub fn wallet_2_btc_address() -> String { + "muYdXKmX9bByAueDe6KFfHd5Ff1gdN9ErG".to_string() +} + +pub fn wallet_3_btc_address() -> String { + "mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7".to_string() +} + +pub fn wallet_4_btc_address() -> String { + "mg1C76bNTutiCDV3t9nWhZs3Dc8LzUufj8".to_string() +} + +pub fn wallet_5_btc_address() -> String { + "mweN5WVqadScHdA81aATSdcVr4B6dNokqx".to_string() +} + +pub fn wallet_6_btc_address() -> String { + "mzxXgV6e4BZSsz8zVHm3TmqbECt7mbuErt".to_string() +} + +pub fn wallet_7_btc_address() -> String { + "n37mwmru2oaVosgfuvzBwgV2ysCQRrLko7".to_string() +} + +pub fn wallet_8_btc_address() -> String { + "n2v875jbJ4RjBnTjgbfikDfnwsDV5iUByw".to_string() +} + +pub fn wallet_9_btc_address() -> String { + "mjSrB3wS4xab3kYqFktwBzfTdPg367ZJ2d".to_string() +} diff --git a/components/chainhook-sdk/src/indexer/tests/helpers/bitcoin_blocks.rs b/components/chainhook-sdk/src/indexer/tests/helpers/bitcoin_blocks.rs new file mode 100644 index 0000000..268ce42 --- /dev/null +++ b/components/chainhook-sdk/src/indexer/tests/helpers/bitcoin_blocks.rs @@ -0,0 +1,148 @@ +use chainhook_types::{ + BitcoinBlockData, BitcoinBlockMetadata, BitcoinTransactionData, BlockIdentifier, +}; + +pub fn generate_test_bitcoin_block( + fork_id: u8, + block_height: u64, + transactions: Vec, + parent: Option, +) -> BitcoinBlockData { + let mut hash = vec![ + fork_id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + + let parent_height = match block_height { + 0 => 0, + _ => block_height - 1, + }; + + let parent_block_identifier = match parent { + Some(parent) => { + assert_eq!(parent.block_identifier.index, parent_height); + parent.block_identifier.clone() + } + _ => { + let mut parent_hash = if parent_height == 1 { + vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ] + } else { + vec![ + fork_id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ] + }; + + parent_hash.append(&mut parent_height.to_be_bytes().to_vec()); + BlockIdentifier { + index: parent_height, + hash: format!("0x{}", hex::encode(&parent_hash[..])), + } + } + }; + hash.append(&mut block_height.to_be_bytes().to_vec()); + BitcoinBlockData { + block_identifier: BlockIdentifier { + index: block_height, + hash: format!("0x{}", hex::encode(&hash[..])), + }, + parent_block_identifier, + timestamp: 0, + transactions, + metadata: BitcoinBlockMetadata { + network: chainhook_types::BitcoinNetwork::Regtest, + }, + } +} + +pub fn A1(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(0, 1, vec![], parent) +} + +pub fn B1(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(1, 2, vec![], parent) +} + +pub fn B2(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(2, 2, vec![], parent) +} + +pub fn C1(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(1, 3, vec![], parent) +} + +pub fn C2(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(2, 3, vec![], parent) +} + +pub fn D1(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(1, 4, vec![], parent) +} + +pub fn D2(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(2, 4, vec![], parent) +} + +pub fn E1(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(1, 5, vec![], parent) +} + +pub fn E2(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(2, 5, vec![], parent) +} + +pub fn B3(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(3, 2, vec![], parent) +} + +pub fn C3(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(3, 3, vec![], parent) +} + +pub fn D3(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(3, 4, vec![], parent) +} + +pub fn E3(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(3, 5, vec![], parent) +} + +pub fn F1(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(1, 6, vec![], parent) +} + +pub fn F2(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(2, 6, vec![], parent) +} + +pub fn F3(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(3, 6, vec![], parent) +} + +pub fn G1(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(1, 7, vec![], parent) +} + +pub fn G2(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(2, 7, vec![], parent) +} + +pub fn G3(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(3, 7, vec![], parent) +} + +pub fn H1(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(1, 8, vec![], parent) +} + +pub fn H3(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(3, 8, vec![], parent) +} + +pub fn I1(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(1, 9, vec![], parent) +} + +pub fn I3(parent: Option) -> BitcoinBlockData { + generate_test_bitcoin_block(3, 9, vec![], parent) +} diff --git a/components/chainhook-sdk/src/indexer/tests/helpers/bitcoin_shapes.rs b/components/chainhook-sdk/src/indexer/tests/helpers/bitcoin_shapes.rs new file mode 100644 index 0000000..7388738 --- /dev/null +++ b/components/chainhook-sdk/src/indexer/tests/helpers/bitcoin_shapes.rs @@ -0,0 +1,2651 @@ +use crate::utils::Context; + +use super::super::BlockchainEventExpectation; +use super::bitcoin_blocks; +use chainhook_types::{BitcoinBlockData, BlockchainEvent}; +use hiro_system_kit::slog; + +pub fn expect_no_chain_update() -> BlockchainEventExpectation { + Box::new(move |chain_event_to_check: Option| { + assert!( + chain_event_to_check.is_none(), + "expected no Chain update, got {:?}", + chain_event_to_check + ); + }) +} + +pub fn expect_chain_updated_with_block( + expected_block: BitcoinBlockData, + confirmed_blocks: Vec, +) -> BlockchainEventExpectation { + expect_chain_updated_with_blocks(vec![expected_block], confirmed_blocks) +} + +pub fn expect_chain_updated_with_blocks( + expected_blocks: Vec, + confirmed_blocks: Vec, +) -> BlockchainEventExpectation { + let ctx = Context::empty(); + Box::new(move |chain_event_to_check: Option| { + assert!( + match chain_event_to_check { + Some(BlockchainEvent::BlockchainUpdatedWithHeaders(ref event)) => { + assert_eq!(expected_blocks.len(), event.new_headers.len()); + for (expected_block, new_block) in + expected_blocks.iter().zip(&event.new_headers) + { + ctx.try_log(|logger| { + slog::debug!( + logger, + "Checking {} and {}", + expected_block.block_identifier, + new_block.block_identifier + ) + }); + assert!( + new_block + .block_identifier + .eq(&expected_block.block_identifier), + "{} ≠ {}", + new_block.block_identifier, + expected_block.block_identifier + ); + } + assert_eq!(confirmed_blocks.len(), event.confirmed_headers.len()); + for (expected_block, confirmed_block) in + confirmed_blocks.iter().zip(&event.confirmed_headers) + { + ctx.try_log(|logger| { + slog::debug!( + logger, + "Checking {} and {}", + expected_block.block_identifier, + confirmed_block.block_identifier + ) + }); + assert!( + confirmed_block + .block_identifier + .eq(&expected_block.block_identifier), + "{} ≠ {}", + confirmed_block.block_identifier, + expected_block.block_identifier + ); + } + + true + } + _ => false, + }, + "expected ChainUpdatedWithBlocks, got {:?}", + chain_event_to_check + ); + }) +} + +pub fn expect_chain_updated_with_block_reorg( + mut blocks_to_rollback: Vec, + blocks_to_apply: Vec, + _confirmed_blocks: Vec, +) -> BlockchainEventExpectation { + blocks_to_rollback.reverse(); + Box::new(move |chain_event_to_check: Option| { + assert!( + match chain_event_to_check { + Some(BlockchainEvent::BlockchainUpdatedWithReorg(ref event)) => { + assert_eq!(blocks_to_rollback.len(), event.headers_to_rollback.len()); + assert_eq!(blocks_to_apply.len(), event.headers_to_apply.len()); + for (expected, block_update) in + blocks_to_rollback.iter().zip(&event.headers_to_rollback) + { + assert!( + expected.block_identifier.eq(&block_update.block_identifier), + "{} ≠ {}", + expected.block_identifier, + block_update.block_identifier + ); + } + for (expected, block_update) in + blocks_to_apply.iter().zip(&event.headers_to_apply) + { + assert!( + expected.block_identifier.eq(&block_update.block_identifier), + "{} ≠ {}", + expected.block_identifier, + block_update.block_identifier + ); + } + true + } + _ => false, + }, + "expected ChainUpdatedWithReorg, got {:?}", + chain_event_to_check + ); + }) +} + +// Test vectors: +// 001 to 020: Bitcoin anchored blocks received in order +// 021 to 040: Bitcoin anchored blocks received out of order + +/// Vector 001: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) +/// +pub fn get_vector_001() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + ] +} + +/// Vector 002: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(4) +/// \ B2(3) - C2(5) +/// +pub fn get_vector_002() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None)], + vec![bitcoin_blocks::B2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None)], + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![], + ), + ), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ] +} + +/// Vector 003: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) +/// \ B2(4) - C2(5) +/// +pub fn get_vector_003() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ] +} + +/// Vector 004: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(6) +/// \ B2(4) - C2(5) +/// +pub fn get_vector_004() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + ] +} + +/// Vector 005: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(6) - E1(7) +/// \ B2(4) - C2(5) +/// +pub fn get_vector_005() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block(bitcoin_blocks::E1(None), vec![]), + ), + ] +} + +/// Vector 006: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(6) - E1(7) - F1(8) +/// \ B2(4) - C2(5) +/// +pub fn get_vector_006() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block(bitcoin_blocks::E1(None), vec![]), + ), + ( + bitcoin_blocks::F1(None), + expect_chain_updated_with_block(bitcoin_blocks::F1(None), vec![]), + ), + ] +} + +/// Vector 007: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(6) - E1(7) - F1(8) - G1(9) +/// \ B2(4) - C2(5) +/// +pub fn get_vector_007() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block(bitcoin_blocks::E1(None), vec![]), + ), + ( + bitcoin_blocks::F1(None), + expect_chain_updated_with_block(bitcoin_blocks::F1(None), vec![]), + ), + ( + bitcoin_blocks::G1(None), + expect_chain_updated_with_block( + bitcoin_blocks::G1(None), + vec![bitcoin_blocks::A1(None)], + ), + ), + ] +} + +/// Vector 008: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(6) - E1(7) - F1(8) - G1(9) - H1(10) +/// \ B2(4) - C2(5) +/// +pub fn get_vector_008() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block(bitcoin_blocks::E1(None), vec![]), + ), + ( + bitcoin_blocks::F1(None), + expect_chain_updated_with_block(bitcoin_blocks::F1(None), vec![]), + ), + ( + bitcoin_blocks::G1(None), + expect_chain_updated_with_block( + bitcoin_blocks::G1(None), + vec![bitcoin_blocks::A1(None)], + ), + ), + ( + bitcoin_blocks::H1(None), + expect_chain_updated_with_block( + bitcoin_blocks::H1(None), + vec![bitcoin_blocks::B1(None)], + ), + ), + ] +} + +/// Vector 009: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(6) - E1(7) - F1(8) - G1(9) - H1(10) - I1(11) +/// \ B2(4) - C2(5) +/// +pub fn get_vector_009() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block(bitcoin_blocks::E1(None), vec![]), + ), + ( + bitcoin_blocks::F1(None), + expect_chain_updated_with_block(bitcoin_blocks::F1(None), vec![]), + ), + ( + bitcoin_blocks::G1(None), + expect_chain_updated_with_block( + bitcoin_blocks::G1(None), + vec![bitcoin_blocks::A1(None)], + ), + ), + ( + bitcoin_blocks::H1(None), + expect_chain_updated_with_block( + bitcoin_blocks::H1(None), + vec![bitcoin_blocks::B1(None)], + ), + ), + ( + bitcoin_blocks::I1(None), + expect_chain_updated_with_block( + bitcoin_blocks::I1(None), + vec![bitcoin_blocks::C1(None)], + ), + ), + ] +} + +/// Vector 010: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(6) - E1(7) - F1(8) - G1(9) - H1(10) - I1(11) +/// \ B2(4) - C2(5) - D2(12) +/// +pub fn get_vector_010() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block(bitcoin_blocks::E1(None), vec![]), + ), + ( + bitcoin_blocks::F1(None), + expect_chain_updated_with_block(bitcoin_blocks::F1(None), vec![]), + ), + ( + bitcoin_blocks::G1(None), + expect_chain_updated_with_block( + bitcoin_blocks::G1(None), + vec![bitcoin_blocks::A1(None)], + ), + ), + ( + bitcoin_blocks::H1(None), + expect_chain_updated_with_block( + bitcoin_blocks::H1(None), + vec![bitcoin_blocks::B1(None)], + ), + ), + ( + bitcoin_blocks::I1(None), + expect_chain_updated_with_block( + bitcoin_blocks::I1(None), + vec![bitcoin_blocks::C1(None)], + ), + ), + ] +} + +/// Vector 011: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(6) - E1(7) - F1(8) - G1(10) - H1(11) - I1(12) +/// \ \ E3(9) +/// \ B2(4) - C2(5) +/// +pub fn get_vector_011() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block(bitcoin_blocks::E1(None), vec![]), + ), + ( + bitcoin_blocks::F1(None), + expect_chain_updated_with_block(bitcoin_blocks::F1(None), vec![]), + ), + (bitcoin_blocks::E3(None), expect_no_chain_update()), + ( + bitcoin_blocks::G1(None), + expect_chain_updated_with_block( + bitcoin_blocks::G1(None), + vec![bitcoin_blocks::A1(None)], + ), + ), + ( + bitcoin_blocks::H1(None), + expect_chain_updated_with_block( + bitcoin_blocks::H1(None), + vec![bitcoin_blocks::B1(None)], + ), + ), + ( + bitcoin_blocks::I1(None), + expect_chain_updated_with_block( + bitcoin_blocks::I1(None), + vec![bitcoin_blocks::C1(None)], + ), + ), + ] +} + +/// Vector 012: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(6) - E1(7) - F1(8) - G1(10) - H1(12) - I1(13) +/// \ \ E3(9) - F3(11) +/// \ B2(4) - C2(5) +/// +pub fn get_vector_012() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block(bitcoin_blocks::E1(None), vec![]), + ), + ( + bitcoin_blocks::F1(None), + expect_chain_updated_with_block(bitcoin_blocks::F1(None), vec![]), + ), + (bitcoin_blocks::E3(None), expect_no_chain_update()), + ( + bitcoin_blocks::G1(None), + expect_chain_updated_with_block( + bitcoin_blocks::G1(None), + vec![bitcoin_blocks::A1(None)], + ), + ), + (bitcoin_blocks::F3(None), expect_no_chain_update()), + ( + bitcoin_blocks::H1(None), + expect_chain_updated_with_block( + bitcoin_blocks::H1(None), + vec![bitcoin_blocks::B1(None)], + ), + ), + ( + bitcoin_blocks::I1(None), + expect_chain_updated_with_block( + bitcoin_blocks::I1(None), + vec![bitcoin_blocks::C1(None)], + ), + ), + (bitcoin_blocks::D2(None), expect_no_chain_update()), + ] +} + +/// Vector 013: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(6) - E1(7) - F1(8) - G1(10) - H1(12) - I1(14) +/// \ \ E3(9) - F3(11) - G3(13) +/// \ B2(4) - C2(5) +/// +pub fn get_vector_013() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block(bitcoin_blocks::E1(None), vec![]), + ), + ( + bitcoin_blocks::F1(None), + expect_chain_updated_with_block(bitcoin_blocks::F1(None), vec![]), + ), + (bitcoin_blocks::E3(None), expect_no_chain_update()), + ( + bitcoin_blocks::G1(None), + expect_chain_updated_with_block( + bitcoin_blocks::G1(None), + vec![bitcoin_blocks::A1(None)], + ), + ), + (bitcoin_blocks::F3(None), expect_no_chain_update()), + ( + bitcoin_blocks::H1(None), + expect_chain_updated_with_block( + bitcoin_blocks::H1(None), + vec![bitcoin_blocks::B1(None)], + ), + ), + (bitcoin_blocks::G3(None), expect_no_chain_update()), + ( + bitcoin_blocks::I1(None), + expect_chain_updated_with_block( + bitcoin_blocks::I1(None), + vec![bitcoin_blocks::C1(None)], + ), + ), + ] +} + +/// Vector 014: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(6) - E1(7) - F1(8) - G1(10) - H1(12) - I1(14) +/// \ \ E3(9) - F3(11) - G3(13) - H3(15) +/// \ B2(4) - C2(5) +/// +pub fn get_vector_014() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block(bitcoin_blocks::E1(None), vec![]), + ), + ( + bitcoin_blocks::F1(None), + expect_chain_updated_with_block(bitcoin_blocks::F1(None), vec![]), + ), + (bitcoin_blocks::E3(None), expect_no_chain_update()), + ( + bitcoin_blocks::G1(None), + expect_chain_updated_with_block( + bitcoin_blocks::G1(None), + vec![bitcoin_blocks::A1(None)], + ), + ), + (bitcoin_blocks::F3(None), expect_no_chain_update()), + ( + bitcoin_blocks::H1(None), + expect_chain_updated_with_block( + bitcoin_blocks::H1(None), + vec![bitcoin_blocks::B1(None)], + ), + ), + (bitcoin_blocks::G3(None), expect_no_chain_update()), + ( + bitcoin_blocks::I1(None), + expect_chain_updated_with_block( + bitcoin_blocks::I1(None), + vec![bitcoin_blocks::C1(None)], + ), + ), + (bitcoin_blocks::H3(None), expect_no_chain_update()), + ] +} + +/// Vector 015: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(6) - E1(7) - F1(8) - G1(10) - H1(12) - I1(14) +/// \ \ E3(9) - F3(11) - G3(13) - H3(15) - I3(16) +/// \ B2(4) - C2(5) +/// +pub fn get_vector_015() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block(bitcoin_blocks::E1(None), vec![]), + ), + ( + bitcoin_blocks::F1(None), + expect_chain_updated_with_block(bitcoin_blocks::F1(None), vec![]), + ), + ( + bitcoin_blocks::E3(Some(bitcoin_blocks::D1(None))), + expect_no_chain_update(), + ), + ( + bitcoin_blocks::G1(None), + expect_chain_updated_with_block( + bitcoin_blocks::G1(None), + vec![bitcoin_blocks::A1(None)], + ), + ), + (bitcoin_blocks::F3(None), expect_no_chain_update()), + ( + bitcoin_blocks::H1(None), + expect_chain_updated_with_block( + bitcoin_blocks::H1(None), + vec![bitcoin_blocks::B1(None)], + ), + ), + (bitcoin_blocks::G3(None), expect_no_chain_update()), + ( + bitcoin_blocks::I1(None), + expect_chain_updated_with_block( + bitcoin_blocks::I1(None), + vec![bitcoin_blocks::C1(None)], + ), + ), + (bitcoin_blocks::H3(None), expect_no_chain_update()), + ( + bitcoin_blocks::I3(None), + expect_chain_updated_with_block_reorg( + vec![ + bitcoin_blocks::E1(None), + bitcoin_blocks::F1(None), + bitcoin_blocks::G1(None), + bitcoin_blocks::H1(None), + bitcoin_blocks::I1(None), + ], + vec![ + bitcoin_blocks::E3(Some(bitcoin_blocks::D1(None))), + bitcoin_blocks::F3(None), + bitcoin_blocks::G3(None), + bitcoin_blocks::H3(None), + bitcoin_blocks::I3(None), + ], + vec![], + ), + ), + ] +} + +/// Vector 016: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(6) - E1(7) - F1(8) - G1(10) - H1(12) - I1(14) +/// \ \ E3(9) - F3(11) - G3(13) - H3(15) - I3(16) +/// \ B2(4) - C2(5) - D2(17) +/// +pub fn get_vector_016() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block(bitcoin_blocks::E1(None), vec![]), + ), + ( + bitcoin_blocks::F1(None), + expect_chain_updated_with_block(bitcoin_blocks::F1(None), vec![]), + ), + ( + bitcoin_blocks::E3(Some(bitcoin_blocks::D1(None))), + expect_no_chain_update(), + ), + ( + bitcoin_blocks::G1(None), + expect_chain_updated_with_block( + bitcoin_blocks::G1(None), + vec![bitcoin_blocks::A1(None)], + ), + ), + (bitcoin_blocks::F3(None), expect_no_chain_update()), + ( + bitcoin_blocks::H1(None), + expect_chain_updated_with_block( + bitcoin_blocks::H1(None), + vec![bitcoin_blocks::B1(None)], + ), + ), + (bitcoin_blocks::G3(None), expect_no_chain_update()), + ( + bitcoin_blocks::I1(None), + expect_chain_updated_with_block( + bitcoin_blocks::I1(None), + vec![bitcoin_blocks::C1(None)], + ), + ), + (bitcoin_blocks::H3(None), expect_no_chain_update()), + ( + bitcoin_blocks::I3(None), + expect_chain_updated_with_block_reorg( + vec![ + bitcoin_blocks::E1(None), + bitcoin_blocks::F1(None), + bitcoin_blocks::G1(None), + bitcoin_blocks::H1(None), + bitcoin_blocks::I1(None), + ], + vec![ + bitcoin_blocks::E3(Some(bitcoin_blocks::D1(None))), + bitcoin_blocks::F3(None), + bitcoin_blocks::G3(None), + bitcoin_blocks::H3(None), + bitcoin_blocks::I3(None), + ], + vec![], + ), + ), + (bitcoin_blocks::D2(None), expect_no_chain_update()), + ] +} + +/// Vector 017: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(6) - E1(7) - F1(8) - G1(10) +/// \ \ E3(9) - F3(11) - G3(12) +/// \ B2(4) - C2(5) - D2(13) - E2(14) - F2(15) - G2(16) +/// +pub fn get_vector_017() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block(bitcoin_blocks::E1(None), vec![]), + ), + ( + bitcoin_blocks::F1(None), + expect_chain_updated_with_block(bitcoin_blocks::F1(None), vec![]), + ), + ( + bitcoin_blocks::E3(Some(bitcoin_blocks::D1(None))), + expect_no_chain_update(), + ), + ( + bitcoin_blocks::G1(None), + expect_chain_updated_with_block( + bitcoin_blocks::G1(None), + vec![bitcoin_blocks::A1(None)], + ), + ), + (bitcoin_blocks::F3(None), expect_no_chain_update()), + ( + bitcoin_blocks::G3(None), + expect_chain_updated_with_block_reorg( + vec![ + bitcoin_blocks::E1(None), + bitcoin_blocks::F1(None), + bitcoin_blocks::G1(None), + ], + vec![ + bitcoin_blocks::E3(Some(bitcoin_blocks::D1(None))), + bitcoin_blocks::F3(None), + bitcoin_blocks::G3(None), + ], + vec![], + ), + ), + (bitcoin_blocks::D2(None), expect_no_chain_update()), + (bitcoin_blocks::E2(None), expect_no_chain_update()), + (bitcoin_blocks::F2(None), expect_no_chain_update()), + (bitcoin_blocks::G2(None), expect_no_chain_update()), + ] +} + +/// Vector 018: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(6) - E1(7) - F1(8) +/// \ \ E3(9) - F3(10) +/// \ B2(4) - C2(5) - D2(11) - E2(12) - F2(13) - G2(14) +/// +pub fn get_vector_018() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block(bitcoin_blocks::E1(None), vec![]), + ), + ( + bitcoin_blocks::F1(None), + expect_chain_updated_with_block(bitcoin_blocks::F1(None), vec![]), + ), + ( + bitcoin_blocks::E3(Some(bitcoin_blocks::D1(None))), + expect_no_chain_update(), + ), + ( + bitcoin_blocks::F3(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::E1(None), bitcoin_blocks::F1(None)], + vec![ + bitcoin_blocks::E3(Some(bitcoin_blocks::D1(None))), + bitcoin_blocks::F3(None), + ], + vec![], + ), + ), + (bitcoin_blocks::D2(None), expect_no_chain_update()), + (bitcoin_blocks::E2(None), expect_no_chain_update()), + (bitcoin_blocks::F2(None), expect_no_chain_update()), + ( + bitcoin_blocks::G2(None), + expect_chain_updated_with_block_reorg( + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + bitcoin_blocks::E3(Some(bitcoin_blocks::D1(None))), + bitcoin_blocks::F3(None), + ], + vec![ + bitcoin_blocks::B2(None), + bitcoin_blocks::C2(None), + bitcoin_blocks::D2(None), + bitcoin_blocks::E2(None), + bitcoin_blocks::F2(None), + bitcoin_blocks::G2(None), + ], + vec![bitcoin_blocks::A1(None)], + ), + ), + ] +} + +/// Vector 019: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(6) - E1(7) - F1(8) - G1(15) +/// \ \ E3(9) - F3(10) +/// \ B2(4) - C2(5) - D2(11) - E2(12) - F2(13) - G2(14) +/// +pub fn get_vector_019() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block(bitcoin_blocks::E1(None), vec![]), + ), + ( + bitcoin_blocks::F1(None), + expect_chain_updated_with_block(bitcoin_blocks::F1(None), vec![]), + ), + ( + bitcoin_blocks::E3(Some(bitcoin_blocks::D1(None))), + expect_no_chain_update(), + ), + ( + bitcoin_blocks::F3(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::E1(None), bitcoin_blocks::F1(None)], + vec![ + bitcoin_blocks::E3(Some(bitcoin_blocks::D1(None))), + bitcoin_blocks::F3(None), + ], + vec![], + ), + ), + (bitcoin_blocks::D2(None), expect_no_chain_update()), + (bitcoin_blocks::E2(None), expect_no_chain_update()), + (bitcoin_blocks::F2(None), expect_no_chain_update()), + ( + bitcoin_blocks::G2(None), + expect_chain_updated_with_block_reorg( + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + bitcoin_blocks::E3(Some(bitcoin_blocks::D1(None))), + bitcoin_blocks::F3(None), + ], + vec![ + bitcoin_blocks::B2(None), + bitcoin_blocks::C2(None), + bitcoin_blocks::D2(None), + bitcoin_blocks::E2(None), + bitcoin_blocks::F2(None), + bitcoin_blocks::G2(None), + ], + vec![], + ), + ), + (bitcoin_blocks::G1(None), expect_no_chain_update()), + ] +} + +/// Vector 020: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(6) - E1(7) - F1(8) - G1(15) +/// \ \ E3(9) - F3(10) - G3(16) +/// \ B2(4) - C2(5) - D2(11) - E2(12) - F2(13) - G2(14) +/// +pub fn get_vector_020() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block(bitcoin_blocks::E1(None), vec![]), + ), + ( + bitcoin_blocks::F1(None), + expect_chain_updated_with_block(bitcoin_blocks::F1(None), vec![]), + ), + ( + bitcoin_blocks::E3(Some(bitcoin_blocks::D1(None))), + expect_no_chain_update(), + ), + ( + bitcoin_blocks::F3(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::E1(None), bitcoin_blocks::F1(None)], + vec![ + bitcoin_blocks::E3(Some(bitcoin_blocks::D1(None))), + bitcoin_blocks::F3(None), + ], + vec![], + ), + ), + (bitcoin_blocks::D2(None), expect_no_chain_update()), + (bitcoin_blocks::E2(None), expect_no_chain_update()), + (bitcoin_blocks::F2(None), expect_no_chain_update()), + ( + bitcoin_blocks::G2(None), + expect_chain_updated_with_block_reorg( + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + bitcoin_blocks::E3(Some(bitcoin_blocks::D1(None))), + bitcoin_blocks::F3(None), + ], + vec![ + bitcoin_blocks::B2(None), + bitcoin_blocks::C2(None), + bitcoin_blocks::D2(None), + bitcoin_blocks::E2(None), + bitcoin_blocks::F2(None), + bitcoin_blocks::G2(None), + ], + vec![bitcoin_blocks::A1(None)], + ), + ), + (bitcoin_blocks::G1(None), expect_no_chain_update()), + (bitcoin_blocks::G3(None), expect_no_chain_update()), + ] +} + +/// Vector 021: Generate the following blocks +/// +/// A1(1) - B1(3) - C1(2) +/// +pub fn get_vector_021() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + (bitcoin_blocks::C1(None), expect_no_chain_update()), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_blocks( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![], + ), + ), + ] +} + +/// Vector 022: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) +/// \ B2(5) - C2(4) +/// +pub fn get_vector_022() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::C2(None), expect_no_chain_update()), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ] +} + +/// Vector 023: Generate the following blocks +/// +/// A1(1) - B1(5) - C1(3) +/// \ B2(2) - C2(4) +/// +pub fn get_vector_023() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_block(bitcoin_blocks::B2(None), vec![]), + ), + (bitcoin_blocks::C1(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block(bitcoin_blocks::C2(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![], + ), + ), + ] +} + +/// Vector 024: Generate the following blocks +/// +/// A1(1) - B1(5) - C1(4) - D1(6) +/// \ B2(2) - C2(3) +/// +pub fn get_vector_024() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_block(bitcoin_blocks::B2(None), vec![]), + ), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block(bitcoin_blocks::C2(None), vec![]), + ), + (bitcoin_blocks::C1(None), expect_no_chain_update()), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block(bitcoin_blocks::D1(None), vec![]), + ), + ] +} + +/// Vector 025: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(4) - D1(5) - E1(6) +/// \ B2(3) - C2(7) +/// +pub fn get_vector_025() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None)], + vec![bitcoin_blocks::B2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None)], + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block(bitcoin_blocks::D1(None), vec![]), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block(bitcoin_blocks::E1(None), vec![]), + ), + (bitcoin_blocks::C2(None), expect_no_chain_update()), + ] +} + +/// Vector 026: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(8) - E1(7) - F1(6) +/// \ B2(5) - C2(4) +/// +pub fn get_vector_026() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + (bitcoin_blocks::C2(None), expect_no_chain_update()), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + (bitcoin_blocks::F1(None), expect_no_chain_update()), + (bitcoin_blocks::E1(None), expect_no_chain_update()), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + bitcoin_blocks::E1(None), + bitcoin_blocks::F1(None), + ], + vec![], + ), + ), + ] +} + +/// Vector 027: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(4) - D1(9) - E1(8) - F1(7) - G1(6) +/// \ B2(5) - C2(3) +/// +pub fn get_vector_027() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block(bitcoin_blocks::B1(None), vec![]), + ), + (bitcoin_blocks::C2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block(bitcoin_blocks::C1(None), vec![]), + ), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + (bitcoin_blocks::G1(None), expect_no_chain_update()), + (bitcoin_blocks::F1(None), expect_no_chain_update()), + (bitcoin_blocks::E1(None), expect_no_chain_update()), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + bitcoin_blocks::E1(None), + bitcoin_blocks::F1(None), + bitcoin_blocks::G1(None), + ], + vec![], + ), + ), + ] +} + +/// Vector 028: Generate the following blocks +/// +/// A1(1) - B1(8) - C1(10) - D1(3) - E1(6) - F1(2) - G1(5) - H1(4) +/// \ B2(7) - C2(9) +/// +pub fn get_vector_028() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + (bitcoin_blocks::F1(None), expect_no_chain_update()), + (bitcoin_blocks::D1(None), expect_no_chain_update()), + (bitcoin_blocks::H1(None), expect_no_chain_update()), + (bitcoin_blocks::G1(None), expect_no_chain_update()), + (bitcoin_blocks::E1(None), expect_no_chain_update()), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_block(bitcoin_blocks::B2(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None)], + vec![bitcoin_blocks::B1(None)], + vec![], + ), + ), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + bitcoin_blocks::E1(None), + bitcoin_blocks::F1(None), + bitcoin_blocks::G1(None), + bitcoin_blocks::H1(None), + ], + vec![], + ), + ), + ] +} + +/// Vector 029: Generate the following blocks +/// +/// A1(1) - B1(7) - C1(6) - D1(9) - E1(10) - F1(2) - G1(3) - H1(4) - I1(11) +/// \ B2(8) - C2(5) +/// +pub fn get_vector_029() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + (bitcoin_blocks::F1(None), expect_no_chain_update()), + (bitcoin_blocks::G1(None), expect_no_chain_update()), + (bitcoin_blocks::H1(None), expect_no_chain_update()), + (bitcoin_blocks::C2(None), expect_no_chain_update()), + (bitcoin_blocks::C1(None), expect_no_chain_update()), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_blocks( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![], + ), + ), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_blocks( + vec![ + bitcoin_blocks::E1(None), + bitcoin_blocks::F1(None), + bitcoin_blocks::G1(None), + bitcoin_blocks::H1(None), + ], + vec![bitcoin_blocks::A1(None), bitcoin_blocks::B1(None)], + ), + ), + ( + bitcoin_blocks::I1(None), + expect_chain_updated_with_blocks( + vec![bitcoin_blocks::I1(None)], + vec![bitcoin_blocks::C1(None)], + ), + ), + ] +} + +/// Vector 030: Generate the following blocks +/// +/// A1(1) - B1(9) - C1(8) - D1(7) - E1(6) - F1(5) - G1(4) - H1(3) - I1(2) +/// \ B2(11) - C2(10) +/// +pub fn get_vector_030() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + (bitcoin_blocks::I1(None), expect_no_chain_update()), + (bitcoin_blocks::H1(None), expect_no_chain_update()), + (bitcoin_blocks::G1(None), expect_no_chain_update()), + (bitcoin_blocks::F1(None), expect_no_chain_update()), + (bitcoin_blocks::E1(None), expect_no_chain_update()), + (bitcoin_blocks::D1(None), expect_no_chain_update()), + (bitcoin_blocks::C1(None), expect_no_chain_update()), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_blocks( + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + bitcoin_blocks::E1(None), + bitcoin_blocks::F1(None), + bitcoin_blocks::G1(None), + bitcoin_blocks::H1(None), + bitcoin_blocks::I1(None), + ], + vec![ + bitcoin_blocks::A1(None), + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + ], + ), + ), + (bitcoin_blocks::C2(None), expect_no_chain_update()), + (bitcoin_blocks::B2(None), expect_no_chain_update()), + ] +} + +/// Vector 031: Generate the following blocks +/// +/// A1(1) - B1(8) - C1(7) - D1(6) - E1(4) - F1(9) - G1(11) - H1(12) - I1(10) +/// \ \ E3(2) +/// \ B2(3) - C2(5) +/// +pub fn get_vector_031() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + (bitcoin_blocks::E3(None), expect_no_chain_update()), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_blocks(vec![bitcoin_blocks::B2(None)], vec![]), + ), + (bitcoin_blocks::E1(None), expect_no_chain_update()), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_blocks(vec![bitcoin_blocks::C2(None)], vec![]), + ), + (bitcoin_blocks::D1(None), expect_no_chain_update()), + (bitcoin_blocks::C1(None), expect_no_chain_update()), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + bitcoin_blocks::E1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::F1(None), + expect_chain_updated_with_blocks(vec![bitcoin_blocks::F1(None)], vec![]), + ), + (bitcoin_blocks::C2(None), expect_no_chain_update()), + (bitcoin_blocks::I1(None), expect_no_chain_update()), + ( + bitcoin_blocks::G1(None), + expect_chain_updated_with_blocks( + vec![bitcoin_blocks::G1(None)], + vec![bitcoin_blocks::A1(None)], + ), + ), + ( + bitcoin_blocks::H1(None), + expect_chain_updated_with_blocks( + vec![bitcoin_blocks::H1(None), bitcoin_blocks::I1(None)], + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + ), + ), + ] +} + +/// Vector 032: Generate the following blocks +/// +/// A1(1) - B1(3) - C1(5) - D1(2) - E1(8) - F1(10) - G1(13) - H1(12) - I1(11) +/// \ \ D3(7) - E3(9) +/// \ B2(4) - C2(6) +/// +pub fn get_vector_032() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + (bitcoin_blocks::D1(None), expect_no_chain_update()), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_blocks(vec![bitcoin_blocks::B1(None)], vec![]), + ), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None)], + vec![bitcoin_blocks::B2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + ], + vec![], + ), + ), + (bitcoin_blocks::C2(None), expect_no_chain_update()), + ( + bitcoin_blocks::D3(Some(bitcoin_blocks::C1(None))), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::D1(None)], + vec![bitcoin_blocks::D3(Some(bitcoin_blocks::C1(None)))], + vec![], + ), + ), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::D3(Some(bitcoin_blocks::C1(None)))], + vec![bitcoin_blocks::D1(None), bitcoin_blocks::E1(None)], + vec![], + ), + ), + ( + bitcoin_blocks::E3(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::D1(None), bitcoin_blocks::E1(None)], + vec![ + bitcoin_blocks::D3(Some(bitcoin_blocks::C1(None))), + bitcoin_blocks::E3(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::F1(None), + expect_chain_updated_with_block_reorg( + vec![ + bitcoin_blocks::D3(Some(bitcoin_blocks::C1(None))), + bitcoin_blocks::E3(None), + ], + vec![ + bitcoin_blocks::D1(None), + bitcoin_blocks::E1(None), + bitcoin_blocks::F1(None), + ], + vec![], + ), + ), + (bitcoin_blocks::I1(None), expect_no_chain_update()), + (bitcoin_blocks::H1(None), expect_no_chain_update()), + ( + bitcoin_blocks::G1(None), + expect_chain_updated_with_blocks( + vec![ + bitcoin_blocks::G1(None), + bitcoin_blocks::H1(None), + bitcoin_blocks::I1(None), + ], + vec![ + bitcoin_blocks::A1(None), + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + ], + ), + ), + ] +} + +/// Vector 033: Generate the following blocks +/// +/// A1(1) - B1(12) - C1(13) - D1(14) - E1(9) - F1(6) - G1(5) - H1(4) - I1(2) +/// \ \ D3(10) - E3(7) - F3(3) +/// \ B2(11) - C2(8) +/// +pub fn get_vector_033() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + (bitcoin_blocks::I1(None), expect_no_chain_update()), + (bitcoin_blocks::F3(None), expect_no_chain_update()), + (bitcoin_blocks::H1(None), expect_no_chain_update()), + (bitcoin_blocks::G1(None), expect_no_chain_update()), + (bitcoin_blocks::F1(None), expect_no_chain_update()), + (bitcoin_blocks::E3(None), expect_no_chain_update()), + (bitcoin_blocks::C2(None), expect_no_chain_update()), + (bitcoin_blocks::E1(None), expect_no_chain_update()), + ( + bitcoin_blocks::D3(Some(bitcoin_blocks::C1(None))), + expect_no_chain_update(), + ), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_blocks( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![], + ), + ), + (bitcoin_blocks::B1(None), expect_no_chain_update()), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D3(None), + bitcoin_blocks::E3(None), + bitcoin_blocks::F3(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::D1(None), + expect_chain_updated_with_block_reorg( + vec![ + bitcoin_blocks::D3(Some(bitcoin_blocks::C1(None))), + bitcoin_blocks::E3(None), + bitcoin_blocks::F3(None), + ], + vec![ + bitcoin_blocks::D1(None), + bitcoin_blocks::E1(None), + bitcoin_blocks::F1(None), + bitcoin_blocks::G1(None), + bitcoin_blocks::H1(None), + bitcoin_blocks::I1(None), + ], + vec![], + ), + ), + ] +} + +/// Vector 034: Generate the following blocks +/// +/// A1(1) - B1(12) - C1(14) - D1(7) - E1(2) - F1(4) - G1(6) - H1(9) - I1(13) +/// \ \ C3(5) - D3(3) - E3(8) - F3(15) +/// \ B2(10) - C2(11) +/// +pub fn get_vector_034() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + (bitcoin_blocks::E1(None), expect_no_chain_update()), + (bitcoin_blocks::D3(None), expect_no_chain_update()), + (bitcoin_blocks::F1(None), expect_no_chain_update()), + ( + bitcoin_blocks::C3(Some(bitcoin_blocks::B1(None))), + expect_no_chain_update(), + ), + (bitcoin_blocks::G1(None), expect_no_chain_update()), + (bitcoin_blocks::D1(None), expect_no_chain_update()), + (bitcoin_blocks::E3(None), expect_no_chain_update()), + (bitcoin_blocks::H1(None), expect_no_chain_update()), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_blocks(vec![bitcoin_blocks::B2(None)], vec![]), + ), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_blocks(vec![bitcoin_blocks::C2(None)], vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C3(Some(bitcoin_blocks::B1(None))), + bitcoin_blocks::D3(None), + bitcoin_blocks::E3(None), + ], + vec![], + ), + ), + (bitcoin_blocks::I1(None), expect_no_chain_update()), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block_reorg( + vec![ + bitcoin_blocks::C3(Some(bitcoin_blocks::B1(None))), + bitcoin_blocks::D3(None), + bitcoin_blocks::E3(None), + ], + vec![ + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + bitcoin_blocks::E1(None), + bitcoin_blocks::F1(None), + bitcoin_blocks::G1(None), + bitcoin_blocks::H1(None), + bitcoin_blocks::I1(None), + ], + vec![], + ), + ), + (bitcoin_blocks::F3(None), expect_no_chain_update()), + ] +} + +/// Vector 035: Generate the following blocks +/// +/// A1(1) - B1(5) - C1(4) - D1(8) - E1(10) - F1(13) - G1(12) - H1(15) - I1(14) +/// \ \ C3(6) - D3(7) - E3(11) - F3(9) - G3(16) +/// \ B2(2) - C2(3) +/// +pub fn get_vector_035() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_blocks(vec![bitcoin_blocks::B2(None)], vec![]), + ), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_blocks(vec![bitcoin_blocks::C2(None)], vec![]), + ), + (bitcoin_blocks::C1(None), expect_no_chain_update()), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None), bitcoin_blocks::C2(None)], + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![], + ), + ), + ( + bitcoin_blocks::C3(Some(bitcoin_blocks::B1(None))), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::C3(Some(bitcoin_blocks::B1(None)))], + vec![], + ), + ), + ( + bitcoin_blocks::D3(None), + expect_chain_updated_with_blocks(vec![bitcoin_blocks::D3(None)], vec![]), + ), + (bitcoin_blocks::D1(None), expect_no_chain_update()), + (bitcoin_blocks::F3(None), expect_no_chain_update()), + ( + bitcoin_blocks::E1(None), + expect_chain_updated_with_block_reorg( + vec![ + bitcoin_blocks::C3(Some(bitcoin_blocks::B1(None))), + bitcoin_blocks::D3(None), + ], + vec![ + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + bitcoin_blocks::E1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::E3(None), + expect_chain_updated_with_block_reorg( + vec![ + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + bitcoin_blocks::E1(None), + ], + vec![ + bitcoin_blocks::C3(Some(bitcoin_blocks::B1(None))), + bitcoin_blocks::D3(None), + bitcoin_blocks::E3(None), + bitcoin_blocks::F3(None), + ], + vec![], + ), + ), + (bitcoin_blocks::G1(None), expect_no_chain_update()), + ( + bitcoin_blocks::F1(None), + expect_chain_updated_with_block_reorg( + vec![ + bitcoin_blocks::C3(Some(bitcoin_blocks::B1(None))), + bitcoin_blocks::D3(None), + bitcoin_blocks::E3(None), + bitcoin_blocks::F3(None), + ], + vec![ + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + bitcoin_blocks::E1(None), + bitcoin_blocks::F1(None), + bitcoin_blocks::G1(None), + ], + vec![bitcoin_blocks::A1(None)], + ), + ), + (bitcoin_blocks::I1(None), expect_no_chain_update()), + ( + bitcoin_blocks::H1(None), + expect_chain_updated_with_blocks( + vec![bitcoin_blocks::H1(None), bitcoin_blocks::I1(None)], + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + ), + ), + ] +} + +/// Vector 036: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(4) - D1(9) - E1(16) - F1(6) - G1(15) +/// \ \ C3(6) - D3(7) - E3(17) - F3(11) - G3(12) +/// \ B2(3) - C2(8) - D2(5) - E2(14) - F2(13) - G2(10) +/// +pub fn get_vector_036() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_blocks(vec![bitcoin_blocks::B1(None)], vec![]), + ), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None)], + vec![bitcoin_blocks::B2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None)], + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![], + ), + ), + (bitcoin_blocks::D2(None), expect_no_chain_update()), + ( + bitcoin_blocks::C3(Some(bitcoin_blocks::B1(None))), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::C3(Some(bitcoin_blocks::B1(None)))], + vec![], + ), + ), + ( + bitcoin_blocks::D3(None), + expect_chain_updated_with_blocks(vec![bitcoin_blocks::D3(None)], vec![]), + ), + (bitcoin_blocks::C2(None), expect_no_chain_update()), + (bitcoin_blocks::D1(None), expect_no_chain_update()), + (bitcoin_blocks::G2(None), expect_no_chain_update()), + (bitcoin_blocks::F3(None), expect_no_chain_update()), + (bitcoin_blocks::G3(None), expect_no_chain_update()), + (bitcoin_blocks::F2(None), expect_no_chain_update()), + ( + bitcoin_blocks::E2(None), + expect_chain_updated_with_block_reorg( + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C3(Some(bitcoin_blocks::B1(None))), + bitcoin_blocks::D3(None), + ], + vec![ + bitcoin_blocks::B2(None), + bitcoin_blocks::C2(None), + bitcoin_blocks::D2(None), + bitcoin_blocks::E2(None), + bitcoin_blocks::F2(None), + bitcoin_blocks::G2(None), + ], + vec![bitcoin_blocks::A1(None)], + ), + ), + (bitcoin_blocks::G1(None), expect_no_chain_update()), + (bitcoin_blocks::E1(None), expect_no_chain_update()), + (bitcoin_blocks::E3(None), expect_no_chain_update()), + ] +} + +/// Vector 037: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(4) - D1(9) - E1(16) - F1(6) - G1(15) +/// \ B3(6) - C3(7) - D3(17) - E3(11) - F3(12) +/// \ B2(3) - C2(8) - D2(5) - E2(14) - F2(13) - G2(10) +/// +pub fn get_vector_037() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_blocks(vec![bitcoin_blocks::B1(None)], vec![]), + ), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None)], + vec![bitcoin_blocks::B2(None)], + vec![], + ), + ), + ( + bitcoin_blocks::C1(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B2(None)], + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![], + ), + ), + (bitcoin_blocks::D2(None), expect_no_chain_update()), + (bitcoin_blocks::B3(None), expect_no_chain_update()), + ( + bitcoin_blocks::C3(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B1(None), bitcoin_blocks::C1(None)], + vec![bitcoin_blocks::B3(None), bitcoin_blocks::C3(None)], + vec![], + ), + ), + ( + bitcoin_blocks::C2(None), + expect_chain_updated_with_block_reorg( + vec![bitcoin_blocks::B3(None), bitcoin_blocks::C3(None)], + vec![ + bitcoin_blocks::B2(None), + bitcoin_blocks::C2(None), + bitcoin_blocks::D2(None), + ], + vec![], + ), + ), + (bitcoin_blocks::D1(None), expect_no_chain_update()), + (bitcoin_blocks::G2(None), expect_no_chain_update()), + (bitcoin_blocks::E3(None), expect_no_chain_update()), + (bitcoin_blocks::F3(None), expect_no_chain_update()), + (bitcoin_blocks::F2(None), expect_no_chain_update()), + ( + bitcoin_blocks::E2(None), + expect_chain_updated_with_blocks( + vec![ + bitcoin_blocks::E2(None), + bitcoin_blocks::F2(None), + bitcoin_blocks::G2(None), + ], + vec![bitcoin_blocks::A1(None)], + ), + ), + (bitcoin_blocks::G1(None), expect_no_chain_update()), + (bitcoin_blocks::E1(None), expect_no_chain_update()), + (bitcoin_blocks::D3(None), expect_no_chain_update()), + ] +} + +/// Vector 038: Generate the following blocks +/// +/// A1(1) - B1(16) - C1(6) - D1(5) - E1(4) - F1(3) +/// \ B3(17) - C3(10) - D3(9) - E3(8) - F3(7) +/// \ B2(18) - C2(15) - D2(14) - E2(13) - F2(12) - G2(11) +/// +pub fn get_vector_038() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + (bitcoin_blocks::F1(None), expect_no_chain_update()), + (bitcoin_blocks::E1(None), expect_no_chain_update()), + (bitcoin_blocks::D1(None), expect_no_chain_update()), + (bitcoin_blocks::C1(None), expect_no_chain_update()), + (bitcoin_blocks::F3(None), expect_no_chain_update()), + (bitcoin_blocks::E3(None), expect_no_chain_update()), + (bitcoin_blocks::D3(None), expect_no_chain_update()), + (bitcoin_blocks::C3(None), expect_no_chain_update()), + (bitcoin_blocks::G2(None), expect_no_chain_update()), + (bitcoin_blocks::F2(None), expect_no_chain_update()), + (bitcoin_blocks::E2(None), expect_no_chain_update()), + (bitcoin_blocks::D2(None), expect_no_chain_update()), + (bitcoin_blocks::C2(None), expect_no_chain_update()), + ( + bitcoin_blocks::B1(None), + expect_chain_updated_with_blocks( + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + bitcoin_blocks::E1(None), + bitcoin_blocks::F1(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::B3(None), + expect_chain_updated_with_block_reorg( + vec![ + bitcoin_blocks::B1(None), + bitcoin_blocks::C1(None), + bitcoin_blocks::D1(None), + bitcoin_blocks::E1(None), + bitcoin_blocks::F1(None), + ], + vec![ + bitcoin_blocks::B3(None), + bitcoin_blocks::C3(None), + bitcoin_blocks::D3(None), + bitcoin_blocks::E3(None), + bitcoin_blocks::F3(None), + ], + vec![], + ), + ), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_block_reorg( + vec![ + bitcoin_blocks::B3(None), + bitcoin_blocks::C3(None), + bitcoin_blocks::D3(None), + bitcoin_blocks::E3(None), + bitcoin_blocks::F3(None), + ], + vec![ + bitcoin_blocks::B2(None), + bitcoin_blocks::C2(None), + bitcoin_blocks::D2(None), + bitcoin_blocks::E2(None), + bitcoin_blocks::F2(None), + bitcoin_blocks::G2(None), + ], + vec![], + ), + ), + ] +} + +/// Vector 039: Generate the following blocks +/// +/// A1(1) - B1(15) - C1(8) - D1(7) - E1(6) - F1(3) - G1(2) +/// \ \ E3(10) - F3(9) +/// \ B2(14) - C2(13) - D2(12) - E2(11) - F2(5) - G2(4) +/// +pub fn get_vector_039() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + (bitcoin_blocks::G1(None), expect_no_chain_update()), + (bitcoin_blocks::F1(None), expect_no_chain_update()), + (bitcoin_blocks::G2(None), expect_no_chain_update()), + (bitcoin_blocks::F2(None), expect_no_chain_update()), + (bitcoin_blocks::E1(None), expect_no_chain_update()), + (bitcoin_blocks::D1(None), expect_no_chain_update()), + (bitcoin_blocks::C1(None), expect_no_chain_update()), + (bitcoin_blocks::F3(None), expect_no_chain_update()), + (bitcoin_blocks::E3(None), expect_no_chain_update()), + (bitcoin_blocks::E2(None), expect_no_chain_update()), + (bitcoin_blocks::D2(None), expect_no_chain_update()), + (bitcoin_blocks::C2(None), expect_no_chain_update()), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_blocks( + vec![ + bitcoin_blocks::B2(None), + bitcoin_blocks::C2(None), + bitcoin_blocks::D2(None), + bitcoin_blocks::E2(None), + bitcoin_blocks::F2(None), + bitcoin_blocks::G2(None), + ], + vec![bitcoin_blocks::A1(None)], + ), + ), + (bitcoin_blocks::B1(None), expect_no_chain_update()), + ] +} + +/// Vector 040: Generate the following blocks +/// +/// A1(1) - B1(16) - C1(6) - D1(5) - E1(4) - F1(3) - G1(2) +/// \ \ E3(9) - F3(8) - G3(7) +/// \ B2(15) - C2(14) - D2(13) - E2(12) - F2(11) - G2(10) +/// +pub fn get_vector_040() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![ + ( + bitcoin_blocks::A1(None), + expect_chain_updated_with_block(bitcoin_blocks::A1(None), vec![]), + ), + (bitcoin_blocks::G1(None), expect_no_chain_update()), + (bitcoin_blocks::F1(None), expect_no_chain_update()), + (bitcoin_blocks::E1(None), expect_no_chain_update()), + (bitcoin_blocks::D1(None), expect_no_chain_update()), + (bitcoin_blocks::C1(None), expect_no_chain_update()), + (bitcoin_blocks::G3(None), expect_no_chain_update()), + (bitcoin_blocks::F3(None), expect_no_chain_update()), + ( + bitcoin_blocks::E3(Some(bitcoin_blocks::D1(None))), + expect_no_chain_update(), + ), + (bitcoin_blocks::G2(None), expect_no_chain_update()), + (bitcoin_blocks::F2(None), expect_no_chain_update()), + (bitcoin_blocks::E2(None), expect_no_chain_update()), + (bitcoin_blocks::D2(None), expect_no_chain_update()), + (bitcoin_blocks::C2(None), expect_no_chain_update()), + ( + bitcoin_blocks::B2(None), + expect_chain_updated_with_blocks( + vec![ + bitcoin_blocks::B2(None), + bitcoin_blocks::C2(None), + bitcoin_blocks::D2(None), + bitcoin_blocks::E2(None), + bitcoin_blocks::F2(None), + bitcoin_blocks::G2(None), + ], + vec![bitcoin_blocks::A1(None)], + ), + ), + (bitcoin_blocks::B1(None), expect_no_chain_update()), + ] +} + +/// Vector 041: Generate the following blocks +/// +/// A1(1) - B1(2) - C1(3) - D1(5) - E1(8) - F1(9) +/// \ C2(4) - D2(6) - E2(7) - F2(10) - G2(11) +/// +pub fn get_vector_041() -> Vec<(BitcoinBlockData, BlockchainEventExpectation)> { + vec![] +} diff --git a/components/chainhook-sdk/src/indexer/tests/helpers/mod.rs b/components/chainhook-sdk/src/indexer/tests/helpers/mod.rs new file mode 100644 index 0000000..ad63cc1 --- /dev/null +++ b/components/chainhook-sdk/src/indexer/tests/helpers/mod.rs @@ -0,0 +1,5 @@ +pub mod accounts; +#[allow(non_snake_case, unreachable_code)] +pub mod bitcoin_blocks; +pub mod bitcoin_shapes; +pub mod transactions; diff --git a/components/chainhook-sdk/src/indexer/tests/helpers/transactions.rs b/components/chainhook-sdk/src/indexer/tests/helpers/transactions.rs new file mode 100644 index 0000000..dd0c680 --- /dev/null +++ b/components/chainhook-sdk/src/indexer/tests/helpers/transactions.rs @@ -0,0 +1,70 @@ +use base58::FromBase58; +use bitcoincore_rpc::bitcoin::blockdata::opcodes; +use bitcoincore_rpc::bitcoin::blockdata::script::Builder as BitcoinScriptBuilder; +use chainhook_types::{bitcoin::TxOut, BitcoinTransactionData, BitcoinTransactionMetadata, TransactionIdentifier}; + +pub fn generate_test_tx_bitcoin_p2pkh_transfer( + txid: u64, + _sender: &str, + recipient: &str, + amount: u64, +) -> BitcoinTransactionData { + let mut hash = vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + hash.append(&mut txid.to_be_bytes().to_vec()); + + // Preparing metadata + let pubkey_hash = recipient + .from_base58() + .expect("Unable to get bytes from btc address"); + let slice = [ + pubkey_hash[1], + pubkey_hash[2], + pubkey_hash[3], + pubkey_hash[4], + pubkey_hash[5], + pubkey_hash[6], + pubkey_hash[7], + pubkey_hash[8], + pubkey_hash[9], + pubkey_hash[10], + pubkey_hash[11], + pubkey_hash[12], + pubkey_hash[13], + pubkey_hash[14], + pubkey_hash[15], + pubkey_hash[16], + pubkey_hash[17], + pubkey_hash[18], + pubkey_hash[19], + pubkey_hash[20], + ]; + let script = BitcoinScriptBuilder::new() + .push_opcode(opcodes::all::OP_DUP) + .push_opcode(opcodes::all::OP_HASH160) + .push_slice(slice) + .push_opcode(opcodes::all::OP_EQUALVERIFY) + .push_opcode(opcodes::all::OP_CHECKSIG) + .into_script(); + let outputs = vec![TxOut { + value: amount, + script_pubkey: format!("0x{}", hex::encode(script.as_bytes())), + }]; + + BitcoinTransactionData { + transaction_identifier: TransactionIdentifier { + hash: format!("0x{}", hex::encode(&hash[..])), + }, + operations: vec![], + metadata: BitcoinTransactionMetadata { + inputs: vec![], + outputs, + ordinal_operations: vec![], + brc20_operation: None, + proof: None, + fee: 0, + index: 0, + }, + } +} diff --git a/components/chainhook-sdk/src/indexer/tests/mod.rs b/components/chainhook-sdk/src/indexer/tests/mod.rs new file mode 100644 index 0000000..0cb3f6b --- /dev/null +++ b/components/chainhook-sdk/src/indexer/tests/mod.rs @@ -0,0 +1,19 @@ +pub mod helpers; +use crate::utils::{AbstractBlock, Context}; + +use super::fork_scratch_pad::ForkScratchPad; +use chainhook_types::{BitcoinBlockData, BlockchainEvent}; + +pub type BlockchainEventExpectation = Box)>; + +pub fn process_bitcoin_blocks_and_check_expectations( + steps: Vec<(BitcoinBlockData, BlockchainEventExpectation)>, +) { + let mut blocks_processor = ForkScratchPad::new(); + for (block, check_chain_event_expectations) in steps.into_iter() { + let chain_event = blocks_processor + .process_header(block.get_header(), &Context::empty()) + .unwrap(); + check_chain_event_expectations(chain_event); + } +} diff --git a/components/chainhook-sdk/src/lib.rs b/components/chainhook-sdk/src/lib.rs new file mode 100644 index 0000000..e5361c6 --- /dev/null +++ b/components/chainhook-sdk/src/lib.rs @@ -0,0 +1,13 @@ +extern crate serde; + +#[macro_use] +extern crate serde_derive; + +#[macro_use] +extern crate serde_json; + +pub use bitcoincore_rpc; + +pub mod indexer; +pub mod observer; +pub mod utils; diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs new file mode 100644 index 0000000..85bc1ab --- /dev/null +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -0,0 +1,752 @@ +mod zmq; + +use crate::indexer::bitcoin::{ + build_http_client, download_and_parse_block_with_retry, standardize_bitcoin_block, + BitcoinBlockFullBreakdown, +}; +use crate::utils::Context; + +use chainhook_types::{ + BitcoinBlockData, BitcoinBlockSignaling, BitcoinChainEvent, BitcoinChainUpdatedWithBlocksData, + BitcoinChainUpdatedWithReorgData, BitcoinNetwork, BlockIdentifier, BlockchainEvent, +}; +use hiro_system_kit; +use hiro_system_kit::slog; +use rocket::serde::Deserialize; +use rocket::Shutdown; +use std::collections::HashMap; +use std::error::Error; +use std::str; +use std::sync::mpsc::{Receiver, Sender}; + +#[derive(Deserialize)] +pub struct NewTransaction { + pub txid: String, + pub status: String, + pub raw_result: String, + pub raw_tx: String, +} + +#[derive(Clone, Debug)] +pub enum Event { + BitcoinChainEvent(BitcoinChainEvent), +} + +#[derive(Debug, Clone)] +pub struct EventObserverConfig { + pub bitcoind_rpc_username: String, + pub bitcoind_rpc_password: String, + pub bitcoind_rpc_url: String, + pub bitcoin_block_signaling: BitcoinBlockSignaling, + pub bitcoin_network: BitcoinNetwork, +} + +/// A builder that is used to create a general purpose [EventObserverConfig]. +/// +/// ## Examples +/// ``` +/// use chainhook_sdk::observer::EventObserverConfig; +/// use chainhook_sdk::observer::EventObserverConfigBuilder; +/// +/// fn get_config() -> Result { +/// EventObserverConfigBuilder::new() +/// .bitcoind_rpc_password("my_password") +/// .bitcoin_network("mainnet") +/// .finish() +/// } +/// ``` +#[derive(Deserialize, Debug, Clone)] +pub struct EventObserverConfigBuilder { + pub bitcoind_rpc_username: Option, + pub bitcoind_rpc_password: Option, + pub bitcoind_rpc_url: Option, + pub bitcoind_zmq_url: Option, + pub bitcoin_network: Option, +} + +impl Default for EventObserverConfigBuilder { + fn default() -> Self { + Self::new() + } +} + +impl EventObserverConfigBuilder { + pub fn new() -> Self { + EventObserverConfigBuilder { + bitcoind_rpc_username: None, + bitcoind_rpc_password: None, + bitcoind_rpc_url: None, + bitcoind_zmq_url: None, + bitcoin_network: None, + } + } + + /// Sets the bitcoind node's RPC username. + pub fn bitcoind_rpc_username(&mut self, username: &str) -> &mut Self { + self.bitcoind_rpc_username = Some(username.to_string()); + self + } + + /// Sets the bitcoind node's RPC password. + pub fn bitcoind_rpc_password(&mut self, password: &str) -> &mut Self { + self.bitcoind_rpc_password = Some(password.to_string()); + self + } + + /// Sets the bitcoind node's RPC url. + pub fn bitcoind_rpc_url(&mut self, url: &str) -> &mut Self { + self.bitcoind_rpc_url = Some(url.to_string()); + self + } + + /// Sets the bitcoind node's ZMQ url, used by the observer to receive new block events from bitcoind. + pub fn bitcoind_zmq_url(&mut self, url: &str) -> &mut Self { + self.bitcoind_zmq_url = Some(url.to_string()); + self + } + + /// Sets the Bitcoin network. Must be a valid bitcoin network string according to [BitcoinNetwork::from_str]. + pub fn bitcoin_network(&mut self, network: &str) -> &mut Self { + self.bitcoin_network = Some(network.to_string()); + self + } + + /// Attempts to convert a [EventObserverConfigBuilder] instance into an [EventObserverConfig], filling in + /// defaults as necessary according to [EventObserverConfig::default]. + /// + /// This function will return an error if the `bitcoin_network` or `stacks_network` strings are set and are not a valid [BitcoinNetwork] or [StacksNetwork]. + /// + pub fn finish(&self) -> Result { + EventObserverConfig::new_using_overrides(Some(self)) + } +} + +impl EventObserverConfig { + pub fn default() -> Self { + EventObserverConfig { + bitcoind_rpc_username: "devnet".into(), + bitcoind_rpc_password: "devnet".into(), + bitcoind_rpc_url: "http://localhost:18443".into(), + bitcoin_block_signaling: BitcoinBlockSignaling::ZeroMQ( + "tcp://localhost:18543".to_string(), + ), + bitcoin_network: BitcoinNetwork::Regtest, + } + } + + pub fn get_bitcoin_config(&self) -> BitcoinConfig { + BitcoinConfig { + username: self.bitcoind_rpc_username.clone(), + password: self.bitcoind_rpc_password.clone(), + rpc_url: self.bitcoind_rpc_url.clone(), + network: self.bitcoin_network.clone(), + bitcoin_block_signaling: self.bitcoin_block_signaling.clone(), + } + } + + /// Helper to allow overriding some default fields in creating a new EventObserverConfig. + /// + /// *Note: This is used by external crates, so it should not be removed, even if not used internally by Chainhook.* + pub fn new_using_overrides( + overrides: Option<&EventObserverConfigBuilder>, + ) -> Result { + let bitcoin_network = + if let Some(network) = overrides.and_then(|c| c.bitcoin_network.as_ref()) { + BitcoinNetwork::from_str(network)? + } else { + BitcoinNetwork::Regtest + }; + + let config = EventObserverConfig { + bitcoind_rpc_username: overrides + .and_then(|c| c.bitcoind_rpc_username.clone()) + .unwrap_or_else(|| "devnet".to_string()), + bitcoind_rpc_password: overrides + .and_then(|c| c.bitcoind_rpc_password.clone()) + .unwrap_or_else(|| "devnet".to_string()), + bitcoind_rpc_url: overrides + .and_then(|c| c.bitcoind_rpc_url.clone()) + .unwrap_or_else(|| "http://localhost:18443".to_string()), + bitcoin_block_signaling: overrides + .and_then(|c| c.bitcoind_zmq_url.as_ref()) + .map(|url| BitcoinBlockSignaling::ZeroMQ(url.clone())) + .unwrap_or_else(|| { + BitcoinBlockSignaling::ZeroMQ("tcp://localhost:18543".to_string()) + }), + bitcoin_network, + }; + Ok(config) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ObserverCommand { + ProcessBitcoinBlock(BitcoinBlockFullBreakdown), + CacheBitcoinBlock(BitcoinBlockData), + PropagateBitcoinChainEvent(BlockchainEvent), + Terminate, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct HookExpirationData { + pub hook_uuid: String, + pub block_height: u64, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct MempoolAdmissionData { + pub tx_data: String, + pub tx_description: String, +} + +#[derive(Clone, Debug)] +pub enum ObserverEvent { + Error(String), + Fatal(String), + Info(String), + Terminate, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +/// JSONRPC Request +pub struct BitcoinRPCRequest { + /// The name of the RPC call + pub method: String, + /// Parameters to the RPC call + pub params: serde_json::Value, + /// Identifier for this Request, which should appear in the response + pub id: serde_json::Value, + /// jsonrpc field, MUST be "2.0" + pub jsonrpc: serde_json::Value, +} + +#[derive(Debug, Clone)] +pub struct BitcoinConfig { + pub username: String, + pub password: String, + pub rpc_url: String, + pub network: BitcoinNetwork, + pub bitcoin_block_signaling: BitcoinBlockSignaling, +} + +#[derive(Debug, Clone)] +pub struct BitcoinBlockDataCached { + pub block: BitcoinBlockData, + pub processed_by_sidecar: bool, +} + +pub struct ObserverSidecar { + pub bitcoin_blocks_mutator: Option<( + crossbeam_channel::Sender<(Vec, Vec)>, + crossbeam_channel::Receiver>, + )>, + pub bitcoin_chain_event_notifier: Option>, +} + +impl ObserverSidecar { + fn perform_bitcoin_sidecar_mutations( + &self, + blocks: Vec, + blocks_ids_to_rollback: Vec, + ctx: &Context, + ) -> Vec { + if let Some(ref block_mutator) = self.bitcoin_blocks_mutator { + ctx.try_log(|logger| slog::info!(logger, "Sending blocks to pre-processor",)); + let _ = block_mutator + .0 + .send((blocks.clone(), blocks_ids_to_rollback)); + ctx.try_log(|logger| slog::info!(logger, "Waiting for blocks from pre-processor",)); + match block_mutator.1.recv() { + Ok(updated_blocks) => { + ctx.try_log(|logger| slog::info!(logger, "Block received from pre-processor",)); + updated_blocks + } + Err(e) => { + ctx.try_log(|logger| { + slog::error!( + logger, + "Unable to receive block from pre-processor {}", + e.to_string() + ) + }); + blocks + } + } + } else { + blocks + } + } + + fn notify_chain_event(&self, chain_event: &BitcoinChainEvent, _ctx: &Context) { + if let Some(ref notifier) = self.bitcoin_chain_event_notifier { + match chain_event { + BitcoinChainEvent::ChainUpdatedWithBlocks(data) => { + for block in data.new_blocks.iter() { + let _ = notifier.send(HandleBlock::ApplyBlock(block.clone())); + } + } + BitcoinChainEvent::ChainUpdatedWithReorg(data) => { + for block in data.blocks_to_rollback.iter() { + let _ = notifier.send(HandleBlock::UndoBlock(block.clone())); + } + for block in data.blocks_to_apply.iter() { + let _ = notifier.send(HandleBlock::ApplyBlock(block.clone())); + } + } + } + } + } +} + +/// A helper struct used to configure and call [start_event_observer], which spawns a thread to observer chain events. +/// +/// ### Examples +/// ``` +/// use chainhook_sdk::observer::EventObserverBuilder; +/// use chainhook_sdk::observer::EventObserverConfig; +/// use chainhook_sdk::observer::ObserverCommand; +/// use chainhook_sdk::utils::Context; +/// use std::error::Error; +/// use std::sync::mpsc::{Receiver, Sender}; +/// +/// fn start_event_observer( +/// config: EventObserverConfig, +/// observer_commands_tx: &Sender, +/// observer_commands_rx: Receiver, +/// ctx: &Context, +/// )-> Result<(), Box> { +/// EventObserverBuilder::new( +/// config, +/// &observer_commands_tx, +/// observer_commands_rx, +/// &ctx +/// ) +/// .start() +/// } +/// ``` +pub struct EventObserverBuilder { + config: EventObserverConfig, + observer_commands_tx: Sender, + observer_commands_rx: Receiver, + ctx: Context, + observer_events_tx: Option>, + observer_sidecar: Option, +} + +impl EventObserverBuilder { + pub fn new( + config: EventObserverConfig, + observer_commands_tx: &Sender, + observer_commands_rx: Receiver, + ctx: &Context, + ) -> Self { + EventObserverBuilder { + config, + observer_commands_tx: observer_commands_tx.clone(), + observer_commands_rx, + ctx: ctx.clone(), + observer_events_tx: None, + observer_sidecar: None, + } + } + + /// Sets the `observer_events_tx` Sender. Set this and listen on the corresponding + /// Receiver to be notified of every [ObserverEvent]. + pub fn events_tx( + &mut self, + observer_events_tx: crossbeam_channel::Sender, + ) -> &mut Self { + self.observer_events_tx = Some(observer_events_tx); + self + } + + /// Sets a sidecar for the observer. See [ObserverSidecar]. + pub fn sidecar(&mut self, sidecar: ObserverSidecar) -> &mut Self { + self.observer_sidecar = Some(sidecar); + self + } + + /// Starts the event observer, calling [start_event_observer]. This function consumes the + /// [EventObserverBuilder] and spawns a new thread to run the observer. + pub fn start(self) -> Result<(), Box> { + start_event_observer( + self.config, + self.observer_commands_tx, + self.observer_commands_rx, + self.observer_events_tx, + self.observer_sidecar, + self.ctx, + ) + } +} + +/// Spawns a thread to observe blockchain events. Use [EventObserverBuilder] to configure easily. +pub fn start_event_observer( + config: EventObserverConfig, + observer_commands_tx: Sender, + observer_commands_rx: Receiver, + observer_events_tx: Option>, + observer_sidecar: Option, + ctx: Context, +) -> Result<(), Box> { + match config.bitcoin_block_signaling { + BitcoinBlockSignaling::ZeroMQ(ref url) => { + ctx.try_log(|logger| { + slog::info!(logger, "Observing Bitcoin chain events via ZeroMQ: {}", url) + }); + let context_cloned = ctx.clone(); + let event_observer_config_moved = config.clone(); + let observer_commands_tx_moved = observer_commands_tx.clone(); + let _ = hiro_system_kit::thread_named("Chainhook event observer") + .spawn(move || { + let future = start_bitcoin_event_observer( + event_observer_config_moved, + observer_commands_tx_moved, + observer_commands_rx, + observer_events_tx.clone(), + observer_sidecar, + context_cloned.clone(), + ); + match hiro_system_kit::nestable_block_on(future) { + Ok(_) => {} + Err(e) => { + if let Some(tx) = observer_events_tx { + context_cloned.try_log(|logger| { + slog::crit!( + logger, + "Chainhook event observer thread failed with error: {e}", + ) + }); + let _ = tx.send(ObserverEvent::Terminate); + } + } + } + }) + .expect("unable to spawn thread"); + } + } + Ok(()) +} + +pub async fn start_bitcoin_event_observer( + config: EventObserverConfig, + _observer_commands_tx: Sender, + observer_commands_rx: Receiver, + observer_events_tx: Option>, + observer_sidecar: Option, + ctx: Context, +) -> Result<(), Box> { + let ctx_moved = ctx.clone(); + let config_moved = config.clone(); + let _ = hiro_system_kit::thread_named("ZMQ handler").spawn(move || { + let future = zmq::start_zeromq_runloop(&config_moved, _observer_commands_tx, &ctx_moved); + hiro_system_kit::nestable_block_on(future); + }); + + // This loop is used for handling background jobs, emitted by HTTP calls. + start_observer_commands_handler( + config, + observer_commands_rx, + observer_events_tx, + None, + observer_sidecar, + ctx, + ) + .await +} + +pub enum HandleBlock { + ApplyBlock(BitcoinBlockData), + UndoBlock(BitcoinBlockData), +} + +pub async fn start_observer_commands_handler( + config: EventObserverConfig, + observer_commands_rx: Receiver, + observer_events_tx: Option>, + ingestion_shutdown: Option, + observer_sidecar: Option, + ctx: Context, +) -> Result<(), Box> { + let mut bitcoin_block_store: HashMap = HashMap::new(); + let http_client = build_http_client(); + let store_update_required = observer_sidecar + .as_ref() + .and_then(|s| s.bitcoin_blocks_mutator.as_ref()) + .is_some(); + + loop { + let command = match observer_commands_rx.recv() { + Ok(cmd) => cmd, + Err(e) => { + ctx.try_log(|logger| { + slog::crit!(logger, "Error: broken channel {}", e.to_string()) + }); + break; + } + }; + match command { + ObserverCommand::Terminate => { + break; + } + ObserverCommand::ProcessBitcoinBlock(mut block_data) => { + let block_hash = block_data.hash.to_string(); + let mut attempts = 0; + let max_attempts = 10; + let block = loop { + match standardize_bitcoin_block( + block_data.clone(), + &config.bitcoin_network, + &ctx, + ) { + Ok(block) => break Some(block), + Err((e, refetch_block)) => { + attempts += 1; + if attempts > max_attempts { + break None; + } + ctx.try_log(|logger| { + slog::warn!(logger, "Error standardizing block: {}", e) + }); + if refetch_block { + block_data = match download_and_parse_block_with_retry( + &http_client, + &block_hash, + &config.get_bitcoin_config(), + &ctx, + ) + .await + { + Ok(block) => block, + Err(e) => { + ctx.try_log(|logger| { + slog::warn!( + logger, + "unable to download_and_parse_block: {}", + e.to_string() + ) + }); + continue; + } + }; + } + } + }; + }; + let Some(block) = block else { + ctx.try_log(|logger| { + slog::crit!( + logger, + "Could not process bitcoin block after {} attempts.", + attempts + ) + }); + break; + }; + + bitcoin_block_store.insert( + block.block_identifier.clone(), + BitcoinBlockDataCached { + block, + processed_by_sidecar: false, + }, + ); + } + ObserverCommand::CacheBitcoinBlock(block) => { + bitcoin_block_store.insert( + block.block_identifier.clone(), + BitcoinBlockDataCached { + block, + processed_by_sidecar: false, + }, + ); + } + ObserverCommand::PropagateBitcoinChainEvent(blockchain_event) => { + ctx.try_log(|logger| { + slog::info!(logger, "Handling PropagateBitcoinChainEvent command") + }); + let mut confirmed_blocks = vec![]; + + // Update Chain event before propagation + let (chain_event, _) = match blockchain_event { + BlockchainEvent::BlockchainUpdatedWithHeaders(data) => { + let mut blocks_to_mutate = vec![]; + let mut new_blocks = vec![]; + let mut new_tip = 0; + + for header in data.new_headers.iter() { + if header.block_identifier.index > new_tip { + new_tip = header.block_identifier.index; + } + + if store_update_required { + let Some(block) = + bitcoin_block_store.remove(&header.block_identifier) + else { + continue; + }; + blocks_to_mutate.push(block); + } else { + let Some(cache) = bitcoin_block_store.get(&header.block_identifier) + else { + continue; + }; + new_blocks.push(cache.block.clone()); + }; + } + + if let Some(ref sidecar) = observer_sidecar { + let updated_blocks = sidecar.perform_bitcoin_sidecar_mutations( + blocks_to_mutate, + vec![], + &ctx, + ); + for cache in updated_blocks.into_iter() { + bitcoin_block_store + .insert(cache.block.block_identifier.clone(), cache.clone()); + new_blocks.push(cache.block); + } + } + + for header in data.confirmed_headers.iter() { + match bitcoin_block_store.remove(&header.block_identifier) { + Some(res) => { + confirmed_blocks.push(res.block); + } + None => { + ctx.try_log(|logger| { + slog::error!( + logger, + "Unable to retrieve confirmed bitcoin block {}", + header.block_identifier + ) + }); + } + } + } + + ( + BitcoinChainEvent::ChainUpdatedWithBlocks( + BitcoinChainUpdatedWithBlocksData { + new_blocks, + confirmed_blocks: confirmed_blocks.clone(), + }, + ), + new_tip, + ) + } + BlockchainEvent::BlockchainUpdatedWithReorg(data) => { + let mut blocks_to_rollback = vec![]; + + let mut blocks_to_mutate = vec![]; + let mut blocks_to_apply = vec![]; + let mut new_tip = 0; + + for header in data.headers_to_apply.iter() { + if header.block_identifier.index > new_tip { + new_tip = header.block_identifier.index; + } + + if store_update_required { + let Some(block) = + bitcoin_block_store.remove(&header.block_identifier) + else { + continue; + }; + blocks_to_mutate.push(block); + } else { + let Some(cache) = bitcoin_block_store.get(&header.block_identifier) + else { + continue; + }; + blocks_to_apply.push(cache.block.clone()); + }; + } + + let mut blocks_ids_to_rollback: Vec = vec![]; + + for header in data.headers_to_rollback.iter() { + match bitcoin_block_store.get(&header.block_identifier) { + Some(cache) => { + blocks_ids_to_rollback.push(header.block_identifier.clone()); + blocks_to_rollback.push(cache.block.clone()); + } + None => { + ctx.try_log(|logger| { + slog::error!( + logger, + "Unable to retrieve bitcoin block {}", + header.block_identifier + ) + }); + } + } + } + + if let Some(ref sidecar) = observer_sidecar { + let updated_blocks = sidecar.perform_bitcoin_sidecar_mutations( + blocks_to_mutate, + blocks_ids_to_rollback, + &ctx, + ); + for cache in updated_blocks.into_iter() { + bitcoin_block_store + .insert(cache.block.block_identifier.clone(), cache.clone()); + blocks_to_apply.push(cache.block); + } + } + + for header in data.confirmed_headers.iter() { + match bitcoin_block_store.remove(&header.block_identifier) { + Some(res) => { + confirmed_blocks.push(res.block); + } + None => { + ctx.try_log(|logger| { + slog::error!( + logger, + "Unable to retrieve confirmed bitcoin block {}", + header.block_identifier + ) + }); + } + } + } + + ( + BitcoinChainEvent::ChainUpdatedWithReorg( + BitcoinChainUpdatedWithReorgData { + blocks_to_apply, + blocks_to_rollback, + confirmed_blocks: confirmed_blocks.clone(), + }, + ), + new_tip, + ) + } + }; + + if let Some(ref sidecar) = observer_sidecar { + sidecar.notify_chain_event(&chain_event, &ctx) + } + } + } + } + terminate(ingestion_shutdown, observer_events_tx, &ctx); + Ok(()) +} + +fn terminate( + ingestion_shutdown: Option, + observer_events_tx: Option>, + ctx: &Context, +) { + ctx.try_log(|logger| slog::info!(logger, "Handling Termination command")); + if let Some(ingestion_shutdown) = ingestion_shutdown { + ingestion_shutdown.notify(); + } + if let Some(ref tx) = observer_events_tx { + let _ = tx.send(ObserverEvent::Info("Terminating event observer".into())); + let _ = tx.send(ObserverEvent::Terminate); + } +} diff --git a/components/chainhook-sdk/src/observer/zmq.rs b/components/chainhook-sdk/src/observer/zmq.rs new file mode 100644 index 0000000..4e6d435 --- /dev/null +++ b/components/chainhook-sdk/src/observer/zmq.rs @@ -0,0 +1,154 @@ +use chainhook_types::BitcoinBlockSignaling; +use hiro_system_kit::slog; +use std::sync::mpsc::Sender; +use zmq::Socket; + +use crate::{ + indexer::{ + bitcoin::{build_http_client, download_and_parse_block_with_retry}, + fork_scratch_pad::ForkScratchPad, + }, + utils::Context, +}; +use std::collections::VecDeque; + +use super::{EventObserverConfig, ObserverCommand}; + +fn new_zmq_socket() -> Socket { + let context = zmq::Context::new(); + let socket = context.socket(zmq::SUB).unwrap(); + assert!(socket.set_subscribe(b"hashblock").is_ok()); + assert!(socket.set_rcvhwm(0).is_ok()); + // We override the OS default behavior: + assert!(socket.set_tcp_keepalive(1).is_ok()); + // The keepalive routine will wait for 5 minutes + assert!(socket.set_tcp_keepalive_idle(300).is_ok()); + // And then resend it every 60 seconds + assert!(socket.set_tcp_keepalive_intvl(60).is_ok()); + // 120 times + assert!(socket.set_tcp_keepalive_cnt(120).is_ok()); + socket +} + +pub async fn start_zeromq_runloop( + config: &EventObserverConfig, + observer_commands_tx: Sender, + ctx: &Context, +) { + let BitcoinBlockSignaling::ZeroMQ(ref bitcoind_zmq_url) = config.bitcoin_block_signaling; + + let bitcoind_zmq_url = bitcoind_zmq_url.clone(); + let bitcoin_config = config.get_bitcoin_config(); + let http_client = build_http_client(); + + ctx.try_log(|logger| { + slog::info!( + logger, + "Waiting for ZMQ connection acknowledgment from bitcoind" + ) + }); + + let mut socket = new_zmq_socket(); + assert!(socket.connect(&bitcoind_zmq_url).is_ok()); + ctx.try_log(|logger| slog::info!(logger, "Waiting for ZMQ messages from bitcoind")); + + let mut bitcoin_blocks_pool = ForkScratchPad::new(); + + loop { + let msg = match socket.recv_multipart(0) { + Ok(msg) => msg, + Err(e) => { + ctx.try_log(|logger| { + slog::error!(logger, "Unable to receive ZMQ message: {}", e.to_string()) + }); + socket = new_zmq_socket(); + assert!(socket.connect(&bitcoind_zmq_url).is_ok()); + continue; + } + }; + let (topic, data, _sequence) = (&msg[0], &msg[1], &msg[2]); + + if !topic.eq(b"hashblock") { + ctx.try_log(|logger| slog::error!(logger, "Topic not supported",)); + continue; + } + + let block_hash = hex::encode(data); + + ctx.try_log(|logger| slog::info!(logger, "Bitcoin block hash announced #{block_hash}",)); + + let mut block_hashes: VecDeque = VecDeque::new(); + block_hashes.push_front(block_hash); + + while let Some(block_hash) = block_hashes.pop_front() { + let block = match download_and_parse_block_with_retry( + &http_client, + &block_hash, + &bitcoin_config, + ctx, + ) + .await + { + Ok(block) => block, + Err(e) => { + ctx.try_log(|logger| { + slog::warn!( + logger, + "unable to download_and_parse_block: {}", + e.to_string() + ) + }); + continue; + } + }; + + let header = block.get_block_header(); + ctx.try_log(|logger| { + slog::info!( + logger, + "Bitcoin block #{} dispatched for processing", + block.height + ) + }); + + let _ = observer_commands_tx.send(ObserverCommand::ProcessBitcoinBlock(block)); + + if bitcoin_blocks_pool.can_process_header(&header) { + match bitcoin_blocks_pool.process_header(header, ctx) { + Ok(Some(event)) => { + let _ = observer_commands_tx + .send(ObserverCommand::PropagateBitcoinChainEvent(event)); + } + Err(e) => { + ctx.try_log(|logger| { + slog::warn!(logger, "Unable to append block: {:?}", e) + }); + } + Ok(None) => { + ctx.try_log(|logger| slog::warn!(logger, "Unable to append block")); + } + } + } else { + // Handle a behaviour specific to ZMQ usage in bitcoind. + // Considering a simple re-org: + // A (1) - B1 (2) - C1 (3) + // \ B2 (4) - C2 (5) - D2 (6) + // When D2 is being discovered (making A -> B2 -> C2 -> D2 the new canonical fork) + // it looks like ZMQ is only publishing D2. + // Without additional operation, we end up with a block that we can't append. + let parent_block_hash = header + .parent_block_identifier + .get_hash_bytes_str() + .to_string(); + ctx.try_log(|logger| { + slog::info!( + logger, + "Possible re-org detected, retrieving parent block {parent_block_hash}" + ) + }); + block_hashes.push_front(block_hash); + block_hashes.push_front(parent_block_hash); + } + } + } +} diff --git a/components/chainhook-sdk/src/utils/mod.rs b/components/chainhook-sdk/src/utils/mod.rs new file mode 100644 index 0000000..8c5bdeb --- /dev/null +++ b/components/chainhook-sdk/src/utils/mod.rs @@ -0,0 +1,365 @@ +use std::{ + collections::{BTreeSet, VecDeque}, + fs::{self, OpenOptions}, + io::{Read, Write}, + path::PathBuf, +}; + +use chainhook_types::{BitcoinBlockData, BlockHeader, BlockIdentifier}; +use hiro_system_kit::slog::{self, Logger}; +use reqwest::RequestBuilder; + +#[derive(Clone)] +pub struct Context { + pub logger: Option, + pub tracer: bool, +} + +impl Context { + pub fn empty() -> Context { + Context { + logger: None, + tracer: false, + } + } + + pub fn try_log(&self, closure: F) + where + F: FnOnce(&Logger), + { + if let Some(ref logger) = self.logger { + closure(logger) + } + } + + pub fn expect_logger(&self) -> &Logger { + self.logger.as_ref().unwrap() + } +} + +pub trait AbstractBlock { + fn get_identifier(&self) -> &BlockIdentifier; + fn get_parent_identifier(&self) -> &BlockIdentifier; + fn get_header(&self) -> BlockHeader { + BlockHeader { + block_identifier: self.get_identifier().clone(), + parent_block_identifier: self.get_parent_identifier().clone(), + } + } +} + +impl AbstractBlock for BlockHeader { + fn get_identifier(&self) -> &BlockIdentifier { + &self.block_identifier + } + + fn get_parent_identifier(&self) -> &BlockIdentifier { + &self.parent_block_identifier + } +} + +impl AbstractBlock for BitcoinBlockData { + fn get_identifier(&self) -> &BlockIdentifier { + &self.block_identifier + } + + fn get_parent_identifier(&self) -> &BlockIdentifier { + &self.parent_block_identifier + } +} + +pub async fn send_request( + request_builder: RequestBuilder, + attempts_max: u16, + attempts_interval_sec: u16, + ctx: &Context, +) -> Result<(), String> { + let mut retry = 0; + loop { + let request_builder = match request_builder.try_clone() { + Some(rb) => rb, + None => { + ctx.try_log(|logger| slog::warn!(logger, "unable to clone request builder")); + return Err("internal server error: unable to clone request builder".to_string()); + } + }; + let err_msg = match request_builder.send().await { + Ok(res) => { + if res.status().is_success() { + ctx.try_log(|logger| slog::debug!(logger, "Trigger {} successful", res.url())); + return Ok(()); + } else { + retry += 1; + let err_msg = + format!("Trigger {} failed with status {}", res.url(), res.status()); + ctx.try_log(|logger| slog::warn!(logger, "{}", err_msg)); + err_msg + } + } + Err(e) => { + retry += 1; + let err_msg = format!("unable to send request {}", e); + ctx.try_log(|logger| slog::warn!(logger, "{}", err_msg)); + err_msg + } + }; + if retry >= attempts_max { + let msg: String = format!( + "unable to send request after several retries. most recent error: {}", + err_msg + ); + ctx.try_log(|logger| slog::warn!(logger, "{}", msg)); + return Err(msg); + } + std::thread::sleep(std::time::Duration::from_secs(attempts_interval_sec.into())); + } +} + +pub fn file_append(path: String, bytes: Vec, ctx: &Context) -> Result<(), String> { + let mut file_path = match std::env::current_dir() { + Err(e) => { + let msg = format!("unable to retrieve current_dir {}", e); + ctx.try_log(|logger| slog::warn!(logger, "{}", msg)); + return Err(msg); + } + Ok(p) => p, + }; + file_path.push(path); + if !file_path.exists() { + match std::fs::File::create(&file_path) { + Ok(ref mut file) => { + let _ = file.write_all(&bytes); + } + Err(e) => { + let msg = format!("unable to create file {}: {}", file_path.display(), e); + ctx.try_log(|logger| slog::warn!(logger, "{}", msg)); + return Err(msg); + } + } + } + + let mut file = match OpenOptions::new() + .create(false) + .append(true) + .open(file_path) + { + Err(e) => { + let msg = format!("unable to open file {}", e); + ctx.try_log(|logger| slog::warn!(logger, "{}", msg)); + return Err(msg); + } + Ok(p) => p, + }; + + let utf8 = match String::from_utf8(bytes) { + Ok(string) => string, + Err(e) => { + let msg = format!("unable serialize bytes as utf8 string {}", e); + ctx.try_log(|logger| slog::warn!(logger, "{}", msg)); + return Err(msg); + } + }; + + if let Err(e) = writeln!(file, "{}", utf8) { + let msg = format!("unable to open file {}", e); + ctx.try_log(|logger| slog::warn!(logger, "{}", msg)); + eprintln!("Couldn't write to file: {}", e); + return Err(msg); + } + + Ok(()) +} + +#[derive(Debug)] +pub enum BlockHeightsError { + ExceedsMaxEntries(u64, u64), + StartLargerThanEnd, +} + +pub enum BlockHeights { + BlockRange(u64, u64), + Blocks(Vec), +} +pub const MAX_BLOCK_HEIGHTS_ENTRIES: u64 = 1_000_000; +impl BlockHeights { + pub fn get_sorted_entries(&self) -> Result, BlockHeightsError> { + let mut entries = VecDeque::new(); + match &self { + BlockHeights::BlockRange(start, end) => { + if start > end { + return Err(BlockHeightsError::StartLargerThanEnd); + } + if (end - start) > MAX_BLOCK_HEIGHTS_ENTRIES { + return Err(BlockHeightsError::ExceedsMaxEntries( + MAX_BLOCK_HEIGHTS_ENTRIES, + end - start, + )); + } + for i in *start..=*end { + entries.push_back(i); + } + } + BlockHeights::Blocks(heights) => { + if heights.len() as u64 > MAX_BLOCK_HEIGHTS_ENTRIES { + return Err(BlockHeightsError::ExceedsMaxEntries( + MAX_BLOCK_HEIGHTS_ENTRIES, + heights.len() as u64, + )); + } + let mut sorted_entries = heights.clone(); + sorted_entries.sort(); + let mut unique_sorted_entries = BTreeSet::new(); + for entry in sorted_entries.into_iter() { + unique_sorted_entries.insert(entry); + } + for entry in unique_sorted_entries.into_iter() { + entries.push_back(entry) + } + } + } + Ok(entries) + } +} + +#[test] +fn test_block_heights_range_construct() { + let range = BlockHeights::BlockRange(0, 10); + let mut entries = range.get_sorted_entries().unwrap(); + + let mut cursor = 0; + while let Some(entry) = entries.pop_front() { + assert_eq!(entry, cursor); + cursor += 1; + } + assert_eq!(11, cursor); +} + +#[test] +fn test_block_heights_range_limits_entries() { + let range = BlockHeights::BlockRange(0, MAX_BLOCK_HEIGHTS_ENTRIES + 1); + match range.get_sorted_entries() { + Ok(_) => panic!("Expected block heights range to error when exceeding max entries"), + Err(e) => match e { + BlockHeightsError::ExceedsMaxEntries(_, _) => {} + BlockHeightsError::StartLargerThanEnd => { + panic!("Wrong error reported from exceeding block heights range max entries") + } + }, + }; +} + +#[test] +fn test_block_heights_range_enforces_order() { + let range = BlockHeights::BlockRange(1, 0); + match range.get_sorted_entries() { + Ok(_) => panic!("Expected block heights range to error when exceeding max entries"), + Err(e) => match e { + BlockHeightsError::ExceedsMaxEntries(_, _) => { + panic!("Wrong error reported from supplying start/end out of order in block heights range") + } + BlockHeightsError::StartLargerThanEnd => {} + }, + }; +} + +#[test] +fn test_block_heights_blocks_construct() { + let range = BlockHeights::Blocks(vec![0, 3, 5, 6, 6, 10, 9]); + let expected = vec![0, 3, 5, 6, 9, 10]; + let entries = range.get_sorted_entries().unwrap(); + + for (entry, expectation) in entries.iter().zip(expected) { + assert_eq!(*entry, expectation); + } +} + +#[test] +fn test_block_heights_blocks_limits_entries() { + let mut too_big = vec![]; + for i in 0..MAX_BLOCK_HEIGHTS_ENTRIES + 1 { + too_big.push(i); + } + let range = BlockHeights::Blocks(too_big); + match range.get_sorted_entries() { + Ok(_) => panic!("Expected block heights blocks to error when exceeding max entries"), + Err(e) => match e { + BlockHeightsError::ExceedsMaxEntries(_, _) => {} + BlockHeightsError::StartLargerThanEnd => { + panic!("Wrong error reported from exceeding block heights blocks max entries") + } + }, + }; +} + +pub fn read_file_content_at_path(file_path: &PathBuf) -> Result, String> { + use std::fs::File; + use std::io::BufReader; + + let file = File::open(file_path.clone()) + .map_err(|e| format!("unable to read file {}\n{:?}", file_path.display(), e))?; + let mut file_reader = BufReader::new(file); + let mut file_buffer = vec![]; + file_reader + .read_to_end(&mut file_buffer) + .map_err(|e| format!("unable to read file {}\n{:?}", file_path.display(), e))?; + Ok(file_buffer) +} + +pub fn write_file_content_at_path(file_path: &PathBuf, content: &[u8]) -> Result<(), String> { + use std::fs::File; + let mut parent_directory = file_path.clone(); + parent_directory.pop(); + fs::create_dir_all(&parent_directory).map_err(|e| { + format!( + "unable to create parent directory {}\n{}", + parent_directory.display(), + e + ) + })?; + let mut file = File::create(file_path) + .map_err(|e| format!("unable to open file {}\n{}", file_path.display(), e))?; + file.write_all(content) + .map_err(|e| format!("unable to write file {}\n{}", file_path.display(), e))?; + Ok(()) +} + +// TODO: Fold these macros into one generic macro with configurable log levels. +#[macro_export] +macro_rules! try_info { + ($a:expr, $tag:expr, $($args:tt)*) => { + $a.try_log(|l| slog::info!(l, $tag, $($args)*)); + }; + ($a:expr, $tag:expr) => { + $a.try_log(|l| slog::info!(l, $tag)); + }; +} + +#[macro_export] +macro_rules! try_debug { + ($a:expr, $tag:expr, $($args:tt)*) => { + $a.try_log(|l| slog::debug!(l, $tag, $($args)*)); + }; + ($a:expr, $tag:expr) => { + $a.try_log(|l| slog::debug!(l, $tag)); + }; +} + +#[macro_export] +macro_rules! try_warn { + ($a:expr, $tag:expr, $($args:tt)*) => { + $a.try_log(|l| slog::warn!(l, $tag, $($args)*)); + }; + ($a:expr, $tag:expr) => { + $a.try_log(|l| slog::warn!(l, $tag)); + }; +} + +#[macro_export] +macro_rules! try_error { + ($a:expr, $tag:expr, $($args:tt)*) => { + $a.try_log(|l| slog::error!(l, $tag, $($args)*)); + }; + ($a:expr, $tag:expr) => { + $a.try_log(|l| slog::error!(l, $tag)); + }; +} diff --git a/components/chainhook-types-rs/Cargo.toml b/components/chainhook-types-rs/Cargo.toml new file mode 100644 index 0000000..dbc41ab --- /dev/null +++ b/components/chainhook-types-rs/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "chainhook-types" +description = "Bitcoin and Stacks data schemas, based on the Rosetta specification" +license = "MIT" +version = "1.3.8" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bitcoin = { workspace = true } +serde = "1" +serde_json = "1" +serde_derive = "1" +strum = { version = "0.23.0", features = ["derive"] } +schemars = { version = "0.8.16", git = "https://github.com/hirosystems/schemars.git", branch = "feat-chainhook-fixes" } +hex = "0.4.3" diff --git a/components/chainhook-types-rs/src/bitcoin.rs b/components/chainhook-types-rs/src/bitcoin.rs new file mode 100644 index 0000000..7718b65 --- /dev/null +++ b/components/chainhook-types-rs/src/bitcoin.rs @@ -0,0 +1,82 @@ +use crate::TransactionIdentifier; + +/// A transaction input, which defines old coins to be consumed +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Serialize, Deserialize)] +pub struct TxIn { + /// The reference to the previous output that is being used an an input. + pub previous_output: OutPoint, + /// The script which pushes values on the stack which will cause + /// the referenced output's script to be accepted. + pub script_sig: String, + /// The sequence number, which suggests to miners which of two + /// conflicting transactions should be preferred, or 0xFFFFFFFF + /// to ignore this feature. This is generally never used since + /// the miner behaviour cannot be enforced. + pub sequence: u32, + /// Witness data: an array of byte-arrays. + /// Note that this field is *not* (de)serialized with the rest of the TxIn in + /// Encodable/Decodable, as it is (de)serialized at the end of the full + /// Transaction. It *is* (de)serialized with the rest of the TxIn in other + /// (de)serialization routines. + pub witness: Vec, +} + +/// A transaction output, which defines new coins to be created from old ones. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Serialize, Deserialize)] +pub struct TxOut { + /// The value of the output, in satoshis. + pub value: u64, + /// The script which must be satisfied for the output to be spent. + pub script_pubkey: String, +} + +/// A reference to a transaction output. +#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] +pub struct OutPoint { + /// The referenced transaction's txid. + pub txid: TransactionIdentifier, + /// The index of the referenced output in its transaction's vout. + pub vout: u32, + /// The value of the referenced. + pub value: u64, + /// The script which must be satisfied for the output to be spent. + pub block_height: u64, +} + +impl TxOut { + pub fn get_script_pubkey_bytes(&self) -> Vec { + hex::decode(&self.get_script_pubkey_hex()).expect("not provided for coinbase txs") + } + + pub fn get_script_pubkey_hex(&self) -> &str { + &self.script_pubkey[2..] + } +} + +/// The Witness is the data used to unlock bitcoins since the [segwit upgrade](https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki) +/// +/// Can be logically seen as an array of byte-arrays `Vec>` and indeed you can convert from +/// it [`Witness::from_vec`] and convert into it [`Witness::to_vec`]. +/// +/// For serialization and deserialization performance it is stored internally as a single `Vec`, +/// saving some allocations. +/// +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Serialize, Deserialize)] +pub struct Witness { + /// contains the witness `Vec>` serialization without the initial varint indicating the + /// number of elements (which is stored in `witness_elements`) + content: Vec, + + /// Number of elements in the witness. + /// It is stored separately (instead of as VarInt in the initial part of content) so that method + /// like [`Witness::push`] doesn't have case requiring to shift the entire array + witness_elements: usize, + + /// If `witness_elements > 0` it's a valid index pointing to the last witness element in `content` + /// (Including the varint specifying the length of the element) + last: usize, + + /// If `witness_elements > 1` it's a valid index pointing to the second-to-last witness element in `content` + /// (Including the varint specifying the length of the element) + second_to_last: usize, +} diff --git a/components/chainhook-types-rs/src/lib.rs b/components/chainhook-types-rs/src/lib.rs new file mode 100644 index 0000000..5c5f6f7 --- /dev/null +++ b/components/chainhook-types-rs/src/lib.rs @@ -0,0 +1,18 @@ +extern crate serde; + +#[macro_use] +extern crate serde_derive; + +pub mod bitcoin; +mod ordinals; +mod processors; +mod rosetta; + +pub use ordinals::*; +pub use processors::*; +pub use rosetta::*; + +#[derive(Clone, Debug)] +pub enum Chain { + Bitcoin, +} diff --git a/components/chainhook-types-rs/src/ordinals.rs b/components/chainhook-types-rs/src/ordinals.rs new file mode 100644 index 0000000..71aa7db --- /dev/null +++ b/components/chainhook-types-rs/src/ordinals.rs @@ -0,0 +1,163 @@ +use serde_json::Value; + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum OrdinalOperation { + InscriptionRevealed(OrdinalInscriptionRevealData), + InscriptionTransferred(OrdinalInscriptionTransferData), +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct OrdinalInscriptionTransferData { + pub ordinal_number: u64, + pub destination: OrdinalInscriptionTransferDestination, + pub satpoint_pre_transfer: String, + pub satpoint_post_transfer: String, + pub post_transfer_output_value: Option, + pub tx_index: usize, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(tag = "type", content = "value", rename_all = "snake_case")] +pub enum OrdinalInscriptionTransferDestination { + Transferred(String), + SpentInFees, + Burnt(String), +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub enum OrdinalInscriptionCurseType { + DuplicateField, + IncompleteField, + NotAtOffsetZero, + NotInFirstInput, + Pointer, + Pushnum, + Reinscription, + Stutter, + UnrecognizedEvenField, + Generic, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct OrdinalInscriptionCharms { + pub coin: bool, + pub cursed: bool, + pub epic: bool, + pub legendary: bool, + pub lost: bool, + pub nineball: bool, + pub rare: bool, + pub reinscription: bool, + pub unbound: bool, + pub uncommon: bool, + pub vindicated: bool, + pub mythic: bool, + pub burned: bool, + pub palindrome: bool, +} + +impl OrdinalInscriptionCharms { + pub fn none() -> Self { + OrdinalInscriptionCharms { + coin: false, + cursed: false, + epic: false, + legendary: false, + lost: false, + nineball: false, + rare: false, + reinscription: false, + unbound: false, + uncommon: false, + vindicated: false, + mythic: false, + burned: false, + palindrome: false, + } + } +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct OrdinalInscriptionRevealData { + pub content_bytes: String, + pub content_type: String, + pub content_length: usize, + pub inscription_number: OrdinalInscriptionNumber, + pub inscription_fee: u64, + pub inscription_output_value: u64, + pub inscription_id: String, + pub inscription_input_index: usize, + pub inscription_pointer: Option, + pub inscriber_address: Option, + pub delegate: Option, + pub metaprotocol: Option, + pub metadata: Option, + pub parents: Vec, + pub ordinal_number: u64, + pub ordinal_block_height: u64, + pub ordinal_offset: u64, + pub tx_index: usize, + pub transfers_pre_inscription: u32, + pub satpoint_post_inscription: String, + pub curse_type: Option, + pub charms: OrdinalInscriptionCharms, +} + +impl OrdinalInscriptionNumber { + pub fn zero() -> Self { + OrdinalInscriptionNumber { + jubilee: 0, + classic: 0, + } + } +} + +impl OrdinalInscriptionRevealData { + pub fn get_inscription_number(&self) -> i64 { + self.inscription_number.jubilee + } +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct OrdinalInscriptionNumber { + pub classic: i64, + pub jubilee: i64, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct Brc20TokenDeployData { + pub tick: String, + pub max: String, + pub lim: String, + pub dec: String, + pub address: String, + pub inscription_id: String, + pub self_mint: bool, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct Brc20BalanceData { + pub tick: String, + pub amt: String, + pub address: String, + pub inscription_id: String, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct Brc20TransferData { + pub tick: String, + pub amt: String, + pub sender_address: String, + pub receiver_address: String, + pub inscription_id: String, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum Brc20Operation { + Deploy(Brc20TokenDeployData), + Mint(Brc20BalanceData), + Transfer(Brc20BalanceData), + TransferSend(Brc20TransferData), +} diff --git a/components/chainhook-types-rs/src/processors.rs b/components/chainhook-types-rs/src/processors.rs new file mode 100644 index 0000000..116b9b5 --- /dev/null +++ b/components/chainhook-types-rs/src/processors.rs @@ -0,0 +1,40 @@ +use std::collections::BTreeMap; + +use super::{BitcoinBlockData, BitcoinTransactionData}; +use serde_json::Value as JsonValue; + +pub struct ProcessedBitcoinTransaction { + pub tx: BitcoinTransactionData, + pub metadata: BTreeMap, +} + +pub struct ProcessedBitcoinBlock { + pub tx: BitcoinBlockData, + pub metadata: BTreeMap, +} + +pub enum ProcessingContext { + Scanning, + Streaming, +} + +pub trait BitcoinProtocolProcessor { + fn register(&mut self); + fn process_block( + &mut self, + block: &mut ProcessedBitcoinBlock, + processing_context: ProcessingContext, + ); + fn process_transaction( + &mut self, + transaction: &mut ProcessedBitcoinTransaction, + processing_context: ProcessingContext, + ); +} + +pub fn run_processor

(mut p: P) +where + P: BitcoinProtocolProcessor, +{ + p.register(); +} diff --git a/components/chainhook-types-rs/src/rosetta.rs b/components/chainhook-types-rs/src/rosetta.rs new file mode 100644 index 0000000..c02b117 --- /dev/null +++ b/components/chainhook-types-rs/src/rosetta.rs @@ -0,0 +1,462 @@ +use crate::bitcoin::{TxIn, TxOut}; +use crate::ordinals::OrdinalOperation; +use crate::Brc20Operation; +use schemars::JsonSchema; +use std::cmp::Ordering; +use std::fmt::Display; +use std::hash::{Hash, Hasher}; + +/// BlockIdentifier uniquely identifies a block in a particular network. +#[derive(Debug, Clone, Deserialize, Serialize, Default)] +pub struct BlockIdentifier { + /// Also known as the block height. + pub index: u64, + pub hash: String, +} + +impl BlockIdentifier { + pub fn get_hash_bytes_str(&self) -> &str { + &self.hash[2..] + } + + pub fn get_hash_bytes(&self) -> Vec { + hex::decode(&self.get_hash_bytes_str()).unwrap() + } +} + +impl Display for BlockIdentifier { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "Block #{} ({}...{})", + self.index, + &self.hash.as_str()[0..6], + &self.hash.as_str()[62..] + ) + } +} + +impl Hash for BlockIdentifier { + fn hash(&self, state: &mut H) { + self.hash.hash(state); + } +} + +impl Ord for BlockIdentifier { + fn cmp(&self, other: &Self) -> Ordering { + (other.index, &other.hash).cmp(&(self.index, &self.hash)) + } +} + +impl PartialOrd for BlockIdentifier { + fn partial_cmp(&self, other: &Self) -> Option { + Some(other.cmp(self)) + } +} + +impl PartialEq for BlockIdentifier { + fn eq(&self, other: &Self) -> bool { + self.hash == other.hash + } +} + +impl Eq for BlockIdentifier {} + +/// BitcoinBlock contain an array of Transactions that occurred at a particular +/// BlockIdentifier. A hard requirement for blocks returned by Rosetta +/// implementations is that they MUST be _inalterable_: once a client has +/// requested and received a block identified by a specific BlockIndentifier, +/// all future calls for that same BlockIdentifier must return the same block +/// contents. +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct BitcoinBlockData { + pub block_identifier: BlockIdentifier, + pub parent_block_identifier: BlockIdentifier, + /// The timestamp of the block in milliseconds since the Unix Epoch. The + /// timestamp is stored in milliseconds because some blockchains produce + /// blocks more often than once a second. + pub timestamp: u32, + pub transactions: Vec, + pub metadata: BitcoinBlockMetadata, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct BitcoinBlockMetadata { + pub network: BitcoinNetwork, +} + +/// The timestamp of the block in milliseconds since the Unix Epoch. The +/// timestamp is stored in milliseconds because some blockchains produce blocks +/// more often than once a second. +#[derive(Debug, Clone, PartialEq, PartialOrd, Deserialize, Serialize)] +pub struct Timestamp(i64); + +/// Transactions contain an array of Operations that are attributable to the +/// same TransactionIdentifier. +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct BitcoinTransactionData { + pub transaction_identifier: TransactionIdentifier, + pub operations: Vec, + /// Transactions that are related to other transactions should include the + /// transaction_identifier of these transactions in the metadata. + pub metadata: BitcoinTransactionMetadata, +} + +/// Extra data for Transaction +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct BitcoinTransactionMetadata { + pub inputs: Vec, + pub outputs: Vec, + pub ordinal_operations: Vec, + pub brc20_operation: Option, + pub proof: Option, + pub fee: u64, + pub index: u32, +} + +/// The transaction_identifier uniquely identifies a transaction in a particular +/// network and block or in the mempool. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Hash, PartialOrd, Ord)] +pub struct TransactionIdentifier { + /// Any transactions that are attributable only to a block (ex: a block + /// event) should use the hash of the block as the identifier. + pub hash: String, +} + +impl TransactionIdentifier { + pub fn new(txid: &str) -> Self { + let lowercased_txid = txid.to_lowercase(); + Self { + hash: match lowercased_txid.starts_with("0x") { + true => lowercased_txid, + false => format!("0x{}", lowercased_txid), + }, + } + } + + pub fn get_hash_bytes_str(&self) -> &str { + &self.hash[2..] + } + + pub fn get_hash_bytes(&self) -> Vec { + hex::decode(&self.get_hash_bytes_str()).unwrap() + } + + pub fn get_8_hash_bytes(&self) -> [u8; 8] { + let bytes = self.get_hash_bytes(); + [ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], + ] + } +} + +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum::EnumIter, strum::IntoStaticStr, +)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum OperationType { + Credit, + Debit, + Lock, +} + +#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)] +pub struct OperationMetadata { + /// Has to be specified for ADD_KEY, REMOVE_KEY, and STAKE operations + #[serde(skip_serializing_if = "Option::is_none")] + pub public_key: Option, + // TODO(lgalabru): ??? + //#[serde(skip_serializing_if = "Option::is_none")] + // pub access_key: Option, + /// Has to be specified for DEPLOY_CONTRACT operation + #[serde(skip_serializing_if = "Option::is_none")] + pub code: Option, + /// Has to be specified for FUNCTION_CALL operation + #[serde(skip_serializing_if = "Option::is_none")] + pub method_name: Option, + /// Has to be specified for FUNCTION_CALL operation + #[serde(skip_serializing_if = "Option::is_none")] + pub args: Option, +} + +/// PublicKey contains a public key byte array for a particular CurveType +/// encoded in hex. Note that there is no PrivateKey struct as this is NEVER the +/// concern of an implementation. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct PublicKey { + /// Hex-encoded public key bytes in the format specified by the CurveType. + pub hex_bytes: Option, + pub curve_type: CurveType, +} + +/// CurveType is the type of cryptographic curve associated with a PublicKey. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum CurveType { + /// `y (255-bits) || x-sign-bit (1-bit)` - `32 bytes` () + Edwards25519, + /// SEC compressed - `33 bytes` () + Secp256k1, +} + +/// Operations contain all balance-changing information within a transaction. +/// They are always one-sided (only affect 1 AccountIdentifier) and can +/// succeed or fail independently from a Transaction. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Operation { + pub operation_identifier: OperationIdentifier, + + /// Restrict referenced related_operations to identifier indexes < the + /// current operation_identifier.index. This ensures there exists a clear + /// DAG-structure of relations. Since operations are one-sided, one could + /// imagine relating operations in a single transfer or linking operations + /// in a call tree. + #[serde(skip_serializing_if = "Option::is_none")] + pub related_operations: Option>, + + /// The network-specific type of the operation. Ensure that any type that + /// can be returned here is also specified in the NetworkStatus. This can + /// be very useful to downstream consumers that parse all block data. + #[serde(rename = "type")] + pub type_: OperationType, + + /// The network-specific status of the operation. Status is not defined on + /// the transaction object because blockchains with smart contracts may have + /// transactions that partially apply. Blockchains with atomic transactions + /// (all operations succeed or all operations fail) will have the same + /// status for each operation. + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, + + pub account: AccountIdentifier, + + #[serde(skip_serializing_if = "Option::is_none")] + pub amount: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, +} + +/// The operation_identifier uniquely identifies an operation within a +/// transaction. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct OperationIdentifier { + /// The operation index is used to ensure each operation has a unique + /// identifier within a transaction. This index is only relative to the + /// transaction and NOT GLOBAL. The operations in each transaction should + /// start from index 0. To clarify, there may not be any notion of an + /// operation index in the blockchain being described. + pub index: u32, + + /// Some blockchains specify an operation index that is essential for + /// client use. For example, Bitcoin uses a network_index to identify + /// which UTXO was used in a transaction. network_index should not be + /// populated if there is no notion of an operation index in a blockchain + /// (typically most account-based blockchains). + #[serde(skip_serializing_if = "Option::is_none")] + pub network_index: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, strum::EnumIter)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum OperationStatusKind { + Success, +} + +/// The account_identifier uniquely identifies an account within a network. All +/// fields in the account_identifier are utilized to determine this uniqueness +/// (including the metadata field, if populated). +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +pub struct AccountIdentifier { + /// The address may be a cryptographic public key (or some encoding of it) + /// or a provided username. + pub address: String, + + #[serde(skip_serializing_if = "Option::is_none")] + pub sub_account: Option, + /* Rosetta Spec also optionally provides: + * + * /// Blockchains that utilize a username model (where the address is not a + * /// derivative of a cryptographic public key) should specify the public + * /// key(s) owned by the address in metadata. + * #[serde(skip_serializing_if = "Option::is_none")] + * pub metadata: Option, */ +} + +/// An account may have state specific to a contract address (ERC-20 token) +/// and/or a stake (delegated balance). The sub_account_identifier should +/// specify which state (if applicable) an account instantiation refers to. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +pub struct SubAccountIdentifier { + /// The SubAccount address may be a cryptographic value or some other + /// identifier (ex: bonded) that uniquely specifies a SubAccount. + pub address: SubAccount, + /* Rosetta Spec also optionally provides: + * + * /// If the SubAccount address is not sufficient to uniquely specify a + * /// SubAccount, any other identifying information can be stored here. It is + * /// important to note that two SubAccounts with identical addresses but + * /// differing metadata will not be considered equal by clients. + * #[serde(skip_serializing_if = "Option::is_none")] + * pub metadata: Option, */ +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum SubAccount { + LiquidBalanceForStorage, + Locked, +} + +/// Amount is some Value of a Currency. It is considered invalid to specify a +/// Value without a Currency. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Amount { + /// Value of the transaction in atomic units represented as an + /// arbitrary-sized signed integer. For example, 1 BTC would be represented + /// by a value of 100000000. + pub value: u128, + + pub currency: Currency, + /* Rosetta Spec also optionally provides: + * + * #[serde(skip_serializing_if = "Option::is_none")] + * pub metadata: Option, */ +} + +/// Currency is composed of a canonical Symbol and Decimals. This Decimals value +/// is used to convert an Amount.Value from atomic units (Satoshis) to standard +/// units (Bitcoins). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Currency { + /// Canonical symbol associated with a currency. + pub symbol: String, + + /// Number of decimal places in the standard unit representation of the + /// amount. For example, BTC has 8 decimals. Note that it is not possible + /// to represent the value of some currency in atomic units that is not base + /// 10. + pub decimals: u32, + + /// Any additional information related to the currency itself. + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum CurrencyStandard { + Sip09, + Sip10, + None, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct CurrencyMetadata { + pub asset_class_identifier: String, + pub asset_identifier: Option, + pub standard: CurrencyStandard, +} + +#[allow(dead_code)] +#[derive(Debug, Clone, PartialEq, Serialize)] +pub enum BlockchainEvent { + BlockchainUpdatedWithHeaders(BlockchainUpdatedWithHeaders), + BlockchainUpdatedWithReorg(BlockchainUpdatedWithReorg), +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct BlockchainUpdatedWithHeaders { + pub new_headers: Vec, + pub confirmed_headers: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct BlockchainUpdatedWithReorg { + pub headers_to_rollback: Vec, + pub headers_to_apply: Vec, + pub confirmed_headers: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct BlockHeader { + pub block_identifier: BlockIdentifier, + pub parent_block_identifier: BlockIdentifier, +} + +#[allow(dead_code)] +#[derive(Debug, Clone, PartialEq, Serialize)] +pub enum BitcoinChainEvent { + ChainUpdatedWithBlocks(BitcoinChainUpdatedWithBlocksData), + ChainUpdatedWithReorg(BitcoinChainUpdatedWithReorgData), +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct BitcoinChainUpdatedWithBlocksData { + pub new_blocks: Vec, + pub confirmed_blocks: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct BitcoinChainUpdatedWithReorgData { + pub blocks_to_rollback: Vec, + pub blocks_to_apply: Vec, + pub confirmed_blocks: Vec, +} + +#[allow(dead_code)] +#[derive( + Debug, PartialEq, Eq, Clone, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub enum BitcoinNetwork { + Regtest, + Testnet, + Signet, + Mainnet, +} + +impl std::fmt::Display for BitcoinNetwork { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_str()) + } +} +impl BitcoinNetwork { + pub fn from_str(network: &str) -> Result { + let value = match network { + "regtest" => BitcoinNetwork::Regtest, + "testnet" => BitcoinNetwork::Testnet, + "mainnet" => BitcoinNetwork::Mainnet, + "signet" => BitcoinNetwork::Signet, + _ => { + return Err(format!( + "network '{}' unsupported (mainnet, testnet, regtest, signet)", + network + )) + } + }; + Ok(value) + } + + pub fn as_str(&self) -> &str { + match self { + BitcoinNetwork::Regtest => "regtest", + BitcoinNetwork::Testnet => "testnet", + BitcoinNetwork::Mainnet => "mainnet", + BitcoinNetwork::Signet => "signet", + } + } +} + +#[derive(Deserialize, Debug, Clone, PartialEq)] +pub enum BitcoinBlockSignaling { + ZeroMQ(String), +} + +impl BitcoinBlockSignaling { + pub fn is_bitcoind_zmq_block_signaling_expected(&self) -> bool { + match &self { + _ => false, + } + } +} diff --git a/components/ord/Cargo.toml b/components/ord/Cargo.toml new file mode 100644 index 0000000..65d42be --- /dev/null +++ b/components/ord/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ord" +version = "0.22.2" +edition = "2021" + +[dependencies] +anyhow = { version = "1.0.56", features = ["backtrace"] } +bitcoin = { workspace = true } +chainhook-sdk = { path = "../chainhook-sdk" } +ciborium = "0.2.1" +serde = "1" +serde_derive = "1" +serde_json = "1" diff --git a/components/ord/README.md b/components/ord/README.md new file mode 100644 index 0000000..88ffb06 --- /dev/null +++ b/components/ord/README.md @@ -0,0 +1 @@ +This code is manually imported from [ordinals/ord](https://github.com/ordinals/ord) and it is used for all ordinal inscription parsing. \ No newline at end of file diff --git a/components/ord/src/chain.rs b/components/ord/src/chain.rs new file mode 100644 index 0000000..6842ccf --- /dev/null +++ b/components/ord/src/chain.rs @@ -0,0 +1,153 @@ +#[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum Chain { + #[default] + Mainnet, + Regtest, + Signet, + Testnet, + Testnet4, +} + +impl Chain { + // pub(crate) fn network(self) -> Network { + // self.into() + // } + + // pub(crate) fn bech32_hrp(self) -> KnownHrp { + // match self { + // Self::Mainnet => KnownHrp::Mainnet, + // Self::Regtest => KnownHrp::Regtest, + // Self::Signet | Self::Testnet | Self::Testnet4 => KnownHrp::Testnets, + // } + // } + + // pub(crate) fn default_rpc_port(self) -> u16 { + // match self { + // Self::Mainnet => 8332, + // Self::Regtest => 18443, + // Self::Signet => 38332, + // Self::Testnet => 18332, + // Self::Testnet4 => 48332, + // } + // } + + pub(crate) fn inscription_content_size_limit(self) -> Option { + match self { + Self::Mainnet | Self::Regtest => None, + Self::Testnet | Self::Testnet4 | Self::Signet => Some(1024), + } + } + + pub(crate) fn first_inscription_height(self) -> u32 { + match self { + Self::Mainnet => 767430, + Self::Regtest => 0, + Self::Signet => 112402, + Self::Testnet => 2413343, + Self::Testnet4 => 0, + } + } + + // pub(crate) fn first_rune_height(self) -> u32 { + // Rune::first_rune_height(self.into()) + // } + + pub(crate) fn jubilee_height(self) -> u32 { + match self { + Self::Mainnet => 824544, + Self::Regtest => 110, + Self::Signet => 175392, + Self::Testnet => 2544192, + Self::Testnet4 => 0, + } + } + + // pub(crate) fn genesis_block(self) -> Block { + // chainhook_sdk::bitcoin::blockdata::constants::genesis_block(self.network()) + // } + + // pub(crate) fn genesis_coinbase_outpoint(self) -> OutPoint { + // OutPoint { + // txid: self.genesis_block().coinbase().unwrap().compute_txid(), + // vout: 0, + // } + // } + + // pub(crate) fn address_from_script(self, script: &Script) -> Result { + // Address::from_script(script, self.network()).snafu_context(error::AddressConversion) + // } + + // pub(crate) fn join_with_data_dir(self, data_dir: impl AsRef) -> PathBuf { + // match self { + // Self::Mainnet => data_dir.as_ref().to_owned(), + // Self::Regtest => data_dir.as_ref().join("regtest"), + // Self::Signet => data_dir.as_ref().join("signet"), + // Self::Testnet => data_dir.as_ref().join("testnet3"), + // Self::Testnet4 => data_dir.as_ref().join("testnet4"), + // } + // } +} + +// impl From for Network { +// fn from(chain: Chain) -> Network { +// match chain { +// Chain::Mainnet => Network::Bitcoin, +// Chain::Regtest => Network::Regtest, +// Chain::Signet => Network::Signet, +// Chain::Testnet => Network::Testnet, +// Chain::Testnet4 => Network::Testnet4, +// } +// } +// } + +// impl Display for Chain { +// fn fmt(&self, f: &mut Formatter) -> fmt::Result { +// write!( +// f, +// "{}", +// match self { +// Self::Mainnet => "mainnet", +// Self::Regtest => "regtest", +// Self::Signet => "signet", +// Self::Testnet => "testnet", +// Self::Testnet4 => "testnet4", +// } +// ) +// } +// } + +// impl FromStr for Chain { +// type Err = SnafuError; + +// fn from_str(s: &str) -> Result { +// match s { +// "mainnet" => Ok(Self::Mainnet), +// "regtest" => Ok(Self::Regtest), +// "signet" => Ok(Self::Signet), +// "testnet" => Ok(Self::Testnet), +// "testnet4" => Ok(Self::Testnet4), +// _ => Err(SnafuError::InvalidChain { +// chain: s.to_string(), +// }), +// } +// } +// } + +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[test] +// fn from_str() { +// assert_eq!("mainnet".parse::().unwrap(), Chain::Mainnet); +// assert_eq!("regtest".parse::().unwrap(), Chain::Regtest); +// assert_eq!("signet".parse::().unwrap(), Chain::Signet); +// assert_eq!("testnet".parse::().unwrap(), Chain::Testnet); +// assert_eq!("testnet4".parse::().unwrap(), Chain::Testnet4); +// assert_eq!( +// "foo".parse::().unwrap_err().to_string(), +// "Invalid chain `foo`" +// ); +// } +// } diff --git a/components/ord/src/charm.rs b/components/ord/src/charm.rs new file mode 100644 index 0000000..a3d5462 --- /dev/null +++ b/components/ord/src/charm.rs @@ -0,0 +1,167 @@ +use std::{ + fmt::{self, Display, Formatter}, + str::FromStr, +}; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Charm { + Coin = 0, + Cursed = 1, + Epic = 2, + Legendary = 3, + Lost = 4, + Nineball = 5, + Rare = 6, + Reinscription = 7, + Unbound = 8, + Uncommon = 9, + Vindicated = 10, + Mythic = 11, + Burned = 12, + Palindrome = 13, +} + +impl Charm { + pub const ALL: [Self; 14] = [ + Self::Coin, + Self::Uncommon, + Self::Rare, + Self::Epic, + Self::Legendary, + Self::Mythic, + Self::Nineball, + Self::Palindrome, + Self::Reinscription, + Self::Cursed, + Self::Unbound, + Self::Lost, + Self::Vindicated, + Self::Burned, + ]; + + pub fn flag(self) -> u16 { + 1 << self as u16 + } + + pub fn set(self, charms: &mut u16) { + *charms |= self.flag(); + } + + pub fn is_set(self, charms: u16) -> bool { + charms & self.flag() != 0 + } + + pub fn unset(self, charms: u16) -> u16 { + charms & !self.flag() + } + + pub fn icon(self) -> &'static str { + match self { + Self::Burned => "🔥", + Self::Coin => "🪙", + Self::Cursed => "👹", + Self::Epic => "🪻", + Self::Legendary => "🌝", + Self::Lost => "🤔", + Self::Mythic => "🎃", + Self::Nineball => "\u{39}\u{fe0f}\u{20e3}", + Self::Palindrome => "🦋", + Self::Rare => "🧿", + Self::Reinscription => "♻️", + Self::Unbound => "🔓", + Self::Uncommon => "🌱", + Self::Vindicated => "\u{2764}\u{fe0f}\u{200d}\u{1f525}", + } + } + + pub fn charms(charms: u16) -> Vec { + Self::ALL + .into_iter() + .filter(|charm| charm.is_set(charms)) + .collect() + } +} + +impl Display for Charm { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!( + f, + "{}", + match self { + Self::Burned => "burned", + Self::Coin => "coin", + Self::Cursed => "cursed", + Self::Epic => "epic", + Self::Legendary => "legendary", + Self::Lost => "lost", + Self::Mythic => "mythic", + Self::Nineball => "nineball", + Self::Palindrome => "palindrome", + Self::Rare => "rare", + Self::Reinscription => "reinscription", + Self::Unbound => "unbound", + Self::Uncommon => "uncommon", + Self::Vindicated => "vindicated", + } + ) + } +} + +impl FromStr for Charm { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(match s { + "burned" => Self::Burned, + "coin" => Self::Coin, + "cursed" => Self::Cursed, + "epic" => Self::Epic, + "legendary" => Self::Legendary, + "lost" => Self::Lost, + "mythic" => Self::Mythic, + "nineball" => Self::Nineball, + "palindrome" => Self::Palindrome, + "rare" => Self::Rare, + "reinscription" => Self::Reinscription, + "unbound" => Self::Unbound, + "uncommon" => Self::Uncommon, + "vindicated" => Self::Vindicated, + _ => return Err(format!("invalid charm `{s}`")), + }) + } +} + +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[test] +// fn flag() { +// assert_eq!(Charm::Coin.flag(), 0b1); +// assert_eq!(Charm::Cursed.flag(), 0b10); +// } + +// #[test] +// fn set() { +// let mut flags = 0; +// assert!(!Charm::Coin.is_set(flags)); +// Charm::Coin.set(&mut flags); +// assert!(Charm::Coin.is_set(flags)); +// } + +// #[test] +// fn unset() { +// let mut flags = 0; +// Charm::Coin.set(&mut flags); +// assert!(Charm::Coin.is_set(flags)); +// let flags = Charm::Coin.unset(flags); +// assert!(!Charm::Coin.is_set(flags)); +// } + +// #[test] +// fn from_str() { +// for charm in Charm::ALL { +// assert_eq!(charm.to_string().parse::().unwrap(), charm); +// } +// } +// } diff --git a/components/ord/src/decimal_sat.rs b/components/ord/src/decimal_sat.rs new file mode 100644 index 0000000..9591047 --- /dev/null +++ b/components/ord/src/decimal_sat.rs @@ -0,0 +1,52 @@ +use super::{height::Height, sat::Sat}; + +#[derive(PartialEq, Debug)] +pub struct DecimalSat { + pub height: Height, + pub offset: u64, +} + +impl From for DecimalSat { + fn from(sat: Sat) -> Self { + Self { + height: sat.height(), + offset: sat.third(), + } + } +} + +// impl Display for DecimalSat { +// fn fmt(&self, f: &mut Formatter) -> fmt::Result { +// write!(f, "{}.{}", self.height, self.offset) +// } +// } + +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[test] +// fn decimal() { +// assert_eq!( +// Sat(0).decimal(), +// DecimalSat { +// height: Height(0), +// offset: 0 +// } +// ); +// assert_eq!( +// Sat(1).decimal(), +// DecimalSat { +// height: Height(0), +// offset: 1 +// } +// ); +// assert_eq!( +// Sat(2099999997689999).decimal(), +// DecimalSat { +// height: Height(6929999), +// offset: 0 +// } +// ); +// } +// } diff --git a/components/ordhook-core/src/ord/degree.rs b/components/ord/src/degree.rs similarity index 62% rename from components/ordhook-core/src/ord/degree.rs rename to components/ord/src/degree.rs index 4ae8d45..aff9723 100644 --- a/components/ordhook-core/src/ord/degree.rs +++ b/components/ord/src/degree.rs @@ -1,35 +1,33 @@ -use sat::Sat; - -use super::*; +use super::{sat::Sat, *}; #[derive(PartialEq, Debug)] pub struct Degree { - pub hour: u32, - pub minute: u32, - pub second: u32, - pub third: u64, + pub hour: u32, + pub minute: u32, + pub second: u32, + pub third: u64, } // impl Display for Degree { -// fn fmt(&self, f: &mut Formatter) -> fmt::Result { -// write!( -// f, -// "{}°{}′{}″{}‴", -// self.hour, self.minute, self.second, self.third -// ) -// } +// fn fmt(&self, f: &mut Formatter) -> fmt::Result { +// write!( +// f, +// "{}°{}′{}″{}‴", +// self.hour, self.minute, self.second, self.third +// ) +// } // } impl From for Degree { - fn from(sat: Sat) -> Self { - let height = sat.height().n(); - Degree { - hour: (height / (CYCLE_EPOCHS * SUBSIDY_HALVING_INTERVAL)) as u32, - minute: (height % SUBSIDY_HALVING_INTERVAL) as u32, - second: (height % DIFFCHANGE_INTERVAL) as u32, - third: sat.third(), + fn from(sat: Sat) -> Self { + let height = sat.height().n(); + Degree { + hour: height / (CYCLE_EPOCHS * SUBSIDY_HALVING_INTERVAL), + minute: height % SUBSIDY_HALVING_INTERVAL, + second: height % DIFFCHANGE_INTERVAL, + third: sat.third(), + } } - } } // #[cfg(test)] diff --git a/components/ord/src/envelope.rs b/components/ord/src/envelope.rs new file mode 100644 index 0000000..0fd265d --- /dev/null +++ b/components/ord/src/envelope.rs @@ -0,0 +1,1011 @@ +use { + super::{inscription::Inscription, tag::Tag}, + bitcoin::{ + blockdata::{ + opcodes, + script::{ + Instruction::{self, Op, PushBytes}, + Instructions, + }, + }, + script, Script, Transaction, + }, + std::{collections::BTreeMap, iter::Peekable}, +}; + +pub(crate) const PROTOCOL_ID: [u8; 3] = *b"ord"; +pub(crate) const BODY_TAG: [u8; 0] = []; + +type Result = std::result::Result; +type RawEnvelope = Envelope>>; +pub type ParsedEnvelope = Envelope; + +#[derive(Default, PartialEq, Clone, Serialize, Deserialize, Debug, Eq)] +pub struct Envelope { + pub input: u32, + pub offset: u32, + pub payload: T, + pub pushnum: bool, + pub stutter: bool, +} + +impl From for ParsedEnvelope { + fn from(envelope: RawEnvelope) -> Self { + let body = envelope + .payload + .iter() + .enumerate() + .position(|(i, push)| i % 2 == 0 && push.is_empty()); + + let mut fields: BTreeMap<&[u8], Vec<&[u8]>> = BTreeMap::new(); + + let mut incomplete_field = false; + + for item in envelope.payload[..body.unwrap_or(envelope.payload.len())].chunks(2) { + match item { + [key, value] => fields.entry(key).or_default().push(value), + _ => incomplete_field = true, + } + } + + let duplicate_field = fields.iter().any(|(_key, values)| values.len() > 1); + + let content_encoding = Tag::ContentEncoding.take(&mut fields); + let content_type = Tag::ContentType.take(&mut fields); + let delegate = Tag::Delegate.take(&mut fields); + let metadata = Tag::Metadata.take(&mut fields); + let metaprotocol = Tag::Metaprotocol.take(&mut fields); + let parents = Tag::Parent.take_array(&mut fields); + let pointer = Tag::Pointer.take(&mut fields); + let rune = Tag::Rune.take(&mut fields); + + let unrecognized_even_field = fields + .keys() + .any(|tag| tag.first().map(|lsb| lsb % 2 == 0).unwrap_or_default()); + + Self { + payload: Inscription { + body: body.map(|i| { + envelope.payload[i + 1..] + .iter() + .flatten() + .cloned() + .collect() + }), + content_encoding, + content_type, + delegate, + duplicate_field, + incomplete_field, + metadata, + metaprotocol, + parents, + pointer, + rune, + unrecognized_even_field, + }, + input: envelope.input, + offset: envelope.offset, + pushnum: envelope.pushnum, + stutter: envelope.stutter, + } + } +} + +impl ParsedEnvelope { + pub(crate) fn from_transaction(transaction: &Transaction) -> Vec { + RawEnvelope::from_transaction(transaction) + .into_iter() + .map(|envelope| envelope.into()) + .collect() + } +} + +impl RawEnvelope { + pub(crate) fn from_transaction(transaction: &Transaction) -> Vec { + let mut envelopes = Vec::new(); + + for (i, input) in transaction.input.iter().enumerate() { + if let Some(tapscript) = input.witness.tapscript() { + if let Ok(input_envelopes) = Self::from_tapscript(tapscript, i) { + envelopes.extend(input_envelopes); + } + } + } + + envelopes + } + + pub fn from_tapscript(tapscript: &Script, input: usize) -> Result> { + let mut envelopes = Vec::new(); + + let mut instructions = tapscript.instructions().peekable(); + + let mut stuttered = false; + while let Some(instruction) = instructions.next().transpose()? { + if instruction == PushBytes((&[]).into()) { + let (stutter, envelope) = + Self::from_instructions(&mut instructions, input, envelopes.len(), stuttered)?; + if let Some(envelope) = envelope { + envelopes.push(envelope); + } else { + stuttered = stutter; + } + } + } + + Ok(envelopes) + } + + fn accept(instructions: &mut Peekable, instruction: Instruction) -> Result { + if instructions.peek() == Some(&Ok(instruction)) { + instructions.next().transpose()?; + Ok(true) + } else { + Ok(false) + } + } + + fn from_instructions( + instructions: &mut Peekable, + input: usize, + offset: usize, + stutter: bool, + ) -> Result<(bool, Option)> { + if !Self::accept(instructions, Op(opcodes::all::OP_IF))? { + let stutter = instructions.peek() == Some(&Ok(PushBytes((&[]).into()))); + return Ok((stutter, None)); + } + + if !Self::accept(instructions, PushBytes((&PROTOCOL_ID).into()))? { + let stutter = instructions.peek() == Some(&Ok(PushBytes((&[]).into()))); + return Ok((stutter, None)); + } + + let mut pushnum = false; + + let mut payload = Vec::new(); + + loop { + match instructions.next().transpose()? { + None => return Ok((false, None)), + Some(Op(opcodes::all::OP_ENDIF)) => { + return Ok(( + false, + Some(Envelope { + input: input.try_into().unwrap(), + offset: offset.try_into().unwrap(), + payload, + pushnum, + stutter, + }), + )); + } + Some(Op(opcodes::all::OP_PUSHNUM_NEG1)) => { + pushnum = true; + payload.push(vec![0x81]); + } + Some(Op(opcodes::all::OP_PUSHNUM_1)) => { + pushnum = true; + payload.push(vec![1]); + } + Some(Op(opcodes::all::OP_PUSHNUM_2)) => { + pushnum = true; + payload.push(vec![2]); + } + Some(Op(opcodes::all::OP_PUSHNUM_3)) => { + pushnum = true; + payload.push(vec![3]); + } + Some(Op(opcodes::all::OP_PUSHNUM_4)) => { + pushnum = true; + payload.push(vec![4]); + } + Some(Op(opcodes::all::OP_PUSHNUM_5)) => { + pushnum = true; + payload.push(vec![5]); + } + Some(Op(opcodes::all::OP_PUSHNUM_6)) => { + pushnum = true; + payload.push(vec![6]); + } + Some(Op(opcodes::all::OP_PUSHNUM_7)) => { + pushnum = true; + payload.push(vec![7]); + } + Some(Op(opcodes::all::OP_PUSHNUM_8)) => { + pushnum = true; + payload.push(vec![8]); + } + Some(Op(opcodes::all::OP_PUSHNUM_9)) => { + pushnum = true; + payload.push(vec![9]); + } + Some(Op(opcodes::all::OP_PUSHNUM_10)) => { + pushnum = true; + payload.push(vec![10]); + } + Some(Op(opcodes::all::OP_PUSHNUM_11)) => { + pushnum = true; + payload.push(vec![11]); + } + Some(Op(opcodes::all::OP_PUSHNUM_12)) => { + pushnum = true; + payload.push(vec![12]); + } + Some(Op(opcodes::all::OP_PUSHNUM_13)) => { + pushnum = true; + payload.push(vec![13]); + } + Some(Op(opcodes::all::OP_PUSHNUM_14)) => { + pushnum = true; + payload.push(vec![14]); + } + Some(Op(opcodes::all::OP_PUSHNUM_15)) => { + pushnum = true; + payload.push(vec![15]); + } + Some(Op(opcodes::all::OP_PUSHNUM_16)) => { + pushnum = true; + payload.push(vec![16]); + } + Some(PushBytes(push)) => { + payload.push(push.as_bytes().to_vec()); + } + Some(_) => return Ok((false, None)), + } + } + } +} + +// #[cfg(test)] +// mod tests { +// use super::*; + +// fn parse(witnesses: &[Witness]) -> Vec { +// ParsedEnvelope::from_transaction(&Transaction { +// version: Version(2), +// lock_time: LockTime::ZERO, +// input: witnesses +// .iter() +// .map(|witness| TxIn { +// previous_output: OutPoint::null(), +// script_sig: ScriptBuf::new(), +// sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, +// witness: witness.clone(), +// }) +// .collect(), +// output: Vec::new(), +// }) +// } + +// #[test] +// fn empty() { +// assert_eq!(parse(&[Witness::new()]), Vec::new()) +// } + +// #[test] +// fn ignore_key_path_spends() { +// assert_eq!( +// parse(&[Witness::from_slice(&[script::Builder::new() +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::all::OP_IF) +// .push_slice(PROTOCOL_ID) +// .push_opcode(opcodes::all::OP_ENDIF) +// .into_script() +// .into_bytes()])]), +// Vec::new() +// ); +// } + +// #[test] +// fn ignore_key_path_spends_with_annex() { +// assert_eq!( +// parse(&[Witness::from_slice(&[ +// script::Builder::new() +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::all::OP_IF) +// .push_slice(PROTOCOL_ID) +// .push_opcode(opcodes::all::OP_ENDIF) +// .into_script() +// .into_bytes(), +// vec![0x50] +// ])]), +// Vec::new() +// ); +// } + +// #[test] +// fn parse_from_tapscript() { +// assert_eq!( +// parse(&[Witness::from_slice(&[ +// script::Builder::new() +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::all::OP_IF) +// .push_slice(PROTOCOL_ID) +// .push_opcode(opcodes::all::OP_ENDIF) +// .into_script() +// .into_bytes(), +// Vec::new() +// ])]), +// vec![ParsedEnvelope { ..default() }] +// ); +// } + +// #[test] +// fn ignore_unparsable_scripts() { +// let mut script_bytes = script::Builder::new() +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::all::OP_IF) +// .push_slice(PROTOCOL_ID) +// .push_opcode(opcodes::all::OP_ENDIF) +// .into_script() +// .into_bytes(); +// script_bytes.push(0x01); + +// assert_eq!( +// parse(&[Witness::from_slice(&[script_bytes, Vec::new()])]), +// Vec::new() +// ); +// } + +// #[test] +// fn no_inscription() { +// assert_eq!( +// parse(&[Witness::from_slice(&[ +// ScriptBuf::new().into_bytes(), +// Vec::new() +// ])]), +// Vec::new() +// ); +// } + +// #[test] +// fn duplicate_field() { +// assert_eq!( +// parse(&[envelope(&[ +// &PROTOCOL_ID, +// Tag::Nop.bytes().as_slice(), +// &[], +// &Tag::Nop.bytes(), +// &[] +// ])]), +// vec![ParsedEnvelope { +// payload: Inscription { +// duplicate_field: true, +// ..default() +// }, +// ..default() +// }] +// ); +// } + +// #[test] +// fn with_content_type() { +// assert_eq!( +// parse(&[envelope(&[ +// &PROTOCOL_ID, +// &Tag::ContentType.bytes(), +// b"text/plain;charset=utf-8", +// &[], +// b"ord", +// ])]), +// vec![ParsedEnvelope { +// payload: inscription("text/plain;charset=utf-8", "ord"), +// ..default() +// }] +// ); +// } + +// #[test] +// fn with_content_encoding() { +// assert_eq!( +// parse(&[envelope(&[ +// &PROTOCOL_ID, +// &Tag::ContentType.bytes(), +// b"text/plain;charset=utf-8", +// &[9], +// b"br", +// &[], +// b"ord", +// ])]), +// vec![ParsedEnvelope { +// payload: Inscription { +// content_encoding: Some("br".as_bytes().to_vec()), +// ..inscription("text/plain;charset=utf-8", "ord") +// }, +// ..default() +// }] +// ); +// } + +// #[test] +// fn with_unknown_tag() { +// assert_eq!( +// parse(&[envelope(&[ +// &PROTOCOL_ID, +// &Tag::ContentType.bytes(), +// b"text/plain;charset=utf-8", +// Tag::Nop.bytes().as_slice(), +// b"bar", +// &[], +// b"ord", +// ])]), +// vec![ParsedEnvelope { +// payload: inscription("text/plain;charset=utf-8", "ord"), +// ..default() +// }] +// ); +// } + +// #[test] +// fn no_body() { +// assert_eq!( +// parse(&[envelope(&[ +// &PROTOCOL_ID, +// &Tag::ContentType.bytes(), +// b"text/plain;charset=utf-8" +// ])]), +// vec![ParsedEnvelope { +// payload: Inscription { +// content_type: Some(b"text/plain;charset=utf-8".to_vec()), +// ..default() +// }, +// ..default() +// }], +// ); +// } + +// #[test] +// fn no_content_type() { +// assert_eq!( +// parse(&[envelope(&[b"ord", &[], b"foo"])]), +// vec![ParsedEnvelope { +// payload: Inscription { +// body: Some(b"foo".to_vec()), +// ..default() +// }, +// ..default() +// }], +// ); +// } + +// #[test] +// fn valid_body_in_multiple_pushes() { +// assert_eq!( +// parse(&[envelope(&[ +// &PROTOCOL_ID, +// &Tag::ContentType.bytes(), +// b"text/plain;charset=utf-8", +// &[], +// b"foo", +// b"bar" +// ])]), +// vec![ParsedEnvelope { +// payload: inscription("text/plain;charset=utf-8", "foobar"), +// ..default() +// }], +// ); +// } + +// #[test] +// fn valid_body_in_zero_pushes() { +// assert_eq!( +// parse(&[envelope(&[ +// &PROTOCOL_ID, +// &Tag::ContentType.bytes(), +// b"text/plain;charset=utf-8", +// &[] +// ])]), +// vec![ParsedEnvelope { +// payload: inscription("text/plain;charset=utf-8", ""), +// ..default() +// }] +// ); +// } + +// #[test] +// fn valid_body_in_multiple_empty_pushes() { +// assert_eq!( +// parse(&[envelope(&[ +// &PROTOCOL_ID, +// &Tag::ContentType.bytes(), +// b"text/plain;charset=utf-8", +// &[], +// &[], +// &[], +// &[], +// &[], +// &[], +// ])]), +// vec![ParsedEnvelope { +// payload: inscription("text/plain;charset=utf-8", ""), +// ..default() +// }], +// ); +// } + +// #[test] +// fn valid_ignore_trailing() { +// let script = script::Builder::new() +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::all::OP_IF) +// .push_slice(PROTOCOL_ID) +// .push_slice([1]) +// .push_slice(b"text/plain;charset=utf-8") +// .push_slice([]) +// .push_slice(b"ord") +// .push_opcode(opcodes::all::OP_ENDIF) +// .push_opcode(opcodes::all::OP_CHECKSIG) +// .into_script(); + +// assert_eq!( +// parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), +// vec![ParsedEnvelope { +// payload: inscription("text/plain;charset=utf-8", "ord"), +// ..default() +// }], +// ); +// } + +// #[test] +// fn valid_ignore_preceding() { +// let script = script::Builder::new() +// .push_opcode(opcodes::all::OP_CHECKSIG) +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::all::OP_IF) +// .push_slice(PROTOCOL_ID) +// .push_slice([1]) +// .push_slice(b"text/plain;charset=utf-8") +// .push_slice([]) +// .push_slice(b"ord") +// .push_opcode(opcodes::all::OP_ENDIF) +// .into_script(); + +// assert_eq!( +// parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), +// vec![ParsedEnvelope { +// payload: inscription("text/plain;charset=utf-8", "ord"), +// ..default() +// }], +// ); +// } + +// #[test] +// fn multiple_inscriptions_in_a_single_witness() { +// let script = script::Builder::new() +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::all::OP_IF) +// .push_slice(PROTOCOL_ID) +// .push_slice([1]) +// .push_slice(b"text/plain;charset=utf-8") +// .push_slice([]) +// .push_slice(b"foo") +// .push_opcode(opcodes::all::OP_ENDIF) +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::all::OP_IF) +// .push_slice(PROTOCOL_ID) +// .push_slice([1]) +// .push_slice(b"text/plain;charset=utf-8") +// .push_slice([]) +// .push_slice(b"bar") +// .push_opcode(opcodes::all::OP_ENDIF) +// .into_script(); + +// assert_eq!( +// parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), +// vec![ +// ParsedEnvelope { +// payload: inscription("text/plain;charset=utf-8", "foo"), +// ..default() +// }, +// ParsedEnvelope { +// payload: inscription("text/plain;charset=utf-8", "bar"), +// offset: 1, +// ..default() +// }, +// ], +// ); +// } + +// #[test] +// fn invalid_utf8_does_not_render_inscription_invalid() { +// assert_eq!( +// parse(&[envelope(&[ +// &PROTOCOL_ID, +// &Tag::ContentType.bytes(), +// b"text/plain;charset=utf-8", +// &[], +// &[0b10000000] +// ])]), +// vec![ParsedEnvelope { +// payload: inscription("text/plain;charset=utf-8", [0b10000000]), +// ..default() +// },], +// ); +// } + +// #[test] +// fn no_endif() { +// let script = script::Builder::new() +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::all::OP_IF) +// .push_slice(PROTOCOL_ID) +// .into_script(); + +// assert_eq!( +// parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), +// Vec::new(), +// ); +// } + +// #[test] +// fn no_op_false() { +// let script = script::Builder::new() +// .push_opcode(opcodes::all::OP_IF) +// .push_slice(PROTOCOL_ID) +// .push_opcode(opcodes::all::OP_ENDIF) +// .into_script(); + +// assert_eq!( +// parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), +// Vec::new(), +// ); +// } + +// #[test] +// fn empty_envelope() { +// assert_eq!(parse(&[envelope(&[])]), Vec::new()); +// } + +// #[test] +// fn wrong_protocol_identifier() { +// assert_eq!(parse(&[envelope(&[b"foo"])]), Vec::new()); +// } + +// #[test] +// fn extract_from_transaction() { +// assert_eq!( +// parse(&[envelope(&[ +// &PROTOCOL_ID, +// &Tag::ContentType.bytes(), +// b"text/plain;charset=utf-8", +// &[], +// b"ord" +// ])]), +// vec![ParsedEnvelope { +// payload: inscription("text/plain;charset=utf-8", "ord"), +// ..default() +// }], +// ); +// } + +// #[test] +// fn extract_from_second_input() { +// assert_eq!( +// parse(&[Witness::new(), inscription("foo", [1; 1040]).to_witness()]), +// vec![ParsedEnvelope { +// payload: inscription("foo", [1; 1040]), +// input: 1, +// ..default() +// }] +// ); +// } + +// #[test] +// fn extract_from_second_envelope() { +// let mut builder = script::Builder::new(); +// builder = inscription("foo", [1; 100]).append_reveal_script_to_builder(builder); +// builder = inscription("bar", [1; 100]).append_reveal_script_to_builder(builder); + +// assert_eq!( +// parse(&[Witness::from_slice(&[ +// builder.into_script().into_bytes(), +// Vec::new() +// ])]), +// vec![ +// ParsedEnvelope { +// payload: inscription("foo", [1; 100]), +// ..default() +// }, +// ParsedEnvelope { +// payload: inscription("bar", [1; 100]), +// offset: 1, +// ..default() +// } +// ] +// ); +// } + +// #[test] +// fn inscribe_png() { +// assert_eq!( +// parse(&[envelope(&[ +// &PROTOCOL_ID, +// &Tag::ContentType.bytes(), +// b"image/png", +// &[], +// &[1; 100] +// ])]), +// vec![ParsedEnvelope { +// payload: inscription("image/png", [1; 100]), +// ..default() +// }] +// ); +// } + +// #[test] +// fn chunked_data_is_parsable() { +// let mut witness = Witness::new(); + +// witness.push(inscription("foo", [1; 1040]).append_reveal_script(script::Builder::new())); + +// witness.push([]); + +// assert_eq!( +// parse(&[witness]), +// vec![ParsedEnvelope { +// payload: inscription("foo", [1; 1040]), +// ..default() +// }] +// ); +// } + +// #[test] +// fn round_trip_with_no_fields() { +// let mut witness = Witness::new(); + +// witness.push(Inscription::default().append_reveal_script(script::Builder::new())); + +// witness.push([]); + +// assert_eq!( +// parse(&[witness]), +// vec![ParsedEnvelope { +// payload: Inscription::default(), +// ..default() +// }], +// ); +// } + +// #[test] +// fn unknown_odd_fields_are_ignored() { +// assert_eq!( +// parse(&[envelope(&[&PROTOCOL_ID, &Tag::Nop.bytes(), &[0]])]), +// vec![ParsedEnvelope { +// payload: Inscription::default(), +// ..default() +// }], +// ); +// } + +// #[test] +// fn unknown_even_fields() { +// assert_eq!( +// parse(&[envelope(&[&PROTOCOL_ID, &[22], &[0]])]), +// vec![ParsedEnvelope { +// payload: Inscription { +// unrecognized_even_field: true, +// ..default() +// }, +// ..default() +// }], +// ); +// } + +// #[test] +// fn pointer_field_is_recognized() { +// assert_eq!( +// parse(&[envelope(&[&PROTOCOL_ID, &[2], &[1]])]), +// vec![ParsedEnvelope { +// payload: Inscription { +// pointer: Some(vec![1]), +// ..default() +// }, +// ..default() +// }], +// ); +// } + +// #[test] +// fn duplicate_pointer_field_makes_inscription_unbound() { +// assert_eq!( +// parse(&[envelope(&[&PROTOCOL_ID, &[2], &[1], &[2], &[0]])]), +// vec![ParsedEnvelope { +// payload: Inscription { +// pointer: Some(vec![1]), +// duplicate_field: true, +// unrecognized_even_field: true, +// ..default() +// }, +// ..default() +// }], +// ); +// } + +// #[test] +// fn tag_66_makes_inscriptions_unbound() { +// assert_eq!( +// parse(&[envelope(&[&PROTOCOL_ID, &Tag::Unbound.bytes(), &[1]])]), +// vec![ParsedEnvelope { +// payload: Inscription { +// unrecognized_even_field: true, +// ..default() +// }, +// ..default() +// }], +// ); +// } + +// #[test] +// fn incomplete_field() { +// assert_eq!( +// parse(&[envelope(&[&PROTOCOL_ID, &[99]])]), +// vec![ParsedEnvelope { +// payload: Inscription { +// incomplete_field: true, +// ..default() +// }, +// ..default() +// }], +// ); +// } + +// #[test] +// fn metadata_is_parsed_correctly() { +// assert_eq!( +// parse(&[envelope(&[&PROTOCOL_ID, &Tag::Metadata.bytes(), &[]])]), +// vec![ParsedEnvelope { +// payload: Inscription { +// metadata: Some(Vec::new()), +// ..default() +// }, +// ..default() +// }] +// ); +// } + +// #[test] +// fn metadata_is_parsed_correctly_from_chunks() { +// assert_eq!( +// parse(&[envelope(&[ +// &PROTOCOL_ID, +// &Tag::Metadata.bytes(), +// &[0], +// &Tag::Metadata.bytes(), +// &[1] +// ])]), +// vec![ParsedEnvelope { +// payload: Inscription { +// metadata: Some(vec![0, 1]), +// duplicate_field: true, +// ..default() +// }, +// ..default() +// }] +// ); +// } + +// #[test] +// fn pushnum_opcodes_are_parsed_correctly() { +// const PUSHNUMS: &[(opcodes::Opcode, u8)] = &[ +// (opcodes::all::OP_PUSHNUM_NEG1, 0x81), +// (opcodes::all::OP_PUSHNUM_1, 1), +// (opcodes::all::OP_PUSHNUM_2, 2), +// (opcodes::all::OP_PUSHNUM_3, 3), +// (opcodes::all::OP_PUSHNUM_4, 4), +// (opcodes::all::OP_PUSHNUM_5, 5), +// (opcodes::all::OP_PUSHNUM_6, 6), +// (opcodes::all::OP_PUSHNUM_7, 7), +// (opcodes::all::OP_PUSHNUM_8, 8), +// (opcodes::all::OP_PUSHNUM_9, 9), +// (opcodes::all::OP_PUSHNUM_10, 10), +// (opcodes::all::OP_PUSHNUM_11, 11), +// (opcodes::all::OP_PUSHNUM_12, 12), +// (opcodes::all::OP_PUSHNUM_13, 13), +// (opcodes::all::OP_PUSHNUM_14, 14), +// (opcodes::all::OP_PUSHNUM_15, 15), +// (opcodes::all::OP_PUSHNUM_16, 16), +// ]; + +// for &(op, value) in PUSHNUMS { +// let script = script::Builder::new() +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::all::OP_IF) +// .push_slice(PROTOCOL_ID) +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(op) +// .push_opcode(opcodes::all::OP_ENDIF) +// .into_script(); + +// assert_eq!( +// parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), +// vec![ParsedEnvelope { +// payload: Inscription { +// body: Some(vec![value]), +// ..default() +// }, +// pushnum: true, +// ..default() +// }], +// ); +// } +// } + +// #[test] +// fn stuttering() { +// let script = script::Builder::new() +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::all::OP_IF) +// .push_slice(PROTOCOL_ID) +// .push_opcode(opcodes::all::OP_ENDIF) +// .into_script(); + +// assert_eq!( +// parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), +// vec![ParsedEnvelope { +// payload: Default::default(), +// stutter: true, +// ..default() +// }], +// ); + +// let script = script::Builder::new() +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::all::OP_IF) +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::all::OP_IF) +// .push_slice(PROTOCOL_ID) +// .push_opcode(opcodes::all::OP_ENDIF) +// .into_script(); + +// assert_eq!( +// parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), +// vec![ParsedEnvelope { +// payload: Default::default(), +// stutter: true, +// ..default() +// }], +// ); + +// let script = script::Builder::new() +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::all::OP_IF) +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::all::OP_IF) +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::all::OP_IF) +// .push_slice(PROTOCOL_ID) +// .push_opcode(opcodes::all::OP_ENDIF) +// .into_script(); + +// assert_eq!( +// parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), +// vec![ParsedEnvelope { +// payload: Default::default(), +// stutter: true, +// ..default() +// }], +// ); + +// let script = script::Builder::new() +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::all::OP_AND) +// .push_opcode(opcodes::OP_FALSE) +// .push_opcode(opcodes::all::OP_IF) +// .push_slice(PROTOCOL_ID) +// .push_opcode(opcodes::all::OP_ENDIF) +// .into_script(); + +// assert_eq!( +// parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), +// vec![ParsedEnvelope { +// payload: Default::default(), +// stutter: false, +// ..default() +// }], +// ); +// } +// } diff --git a/components/ord/src/epoch.rs b/components/ord/src/epoch.rs new file mode 100644 index 0000000..889db8d --- /dev/null +++ b/components/ord/src/epoch.rs @@ -0,0 +1,242 @@ +use super::{height::Height, sat::Sat, *}; + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, PartialOrd)] +pub struct Epoch(pub u32); + +impl Epoch { + pub const STARTING_SATS: [Sat; 34] = [ + Sat(0), + Sat(1050000000000000), + Sat(1575000000000000), + Sat(1837500000000000), + Sat(1968750000000000), + Sat(2034375000000000), + Sat(2067187500000000), + Sat(2083593750000000), + Sat(2091796875000000), + Sat(2095898437500000), + Sat(2097949218750000), + Sat(2098974609270000), + Sat(2099487304530000), + Sat(2099743652160000), + Sat(2099871825870000), + Sat(2099935912620000), + Sat(2099967955890000), + Sat(2099983977420000), + Sat(2099991988080000), + Sat(2099995993410000), + Sat(2099997995970000), + Sat(2099998997250000), + Sat(2099999497890000), + Sat(2099999748210000), + Sat(2099999873370000), + Sat(2099999935950000), + Sat(2099999967240000), + Sat(2099999982780000), + Sat(2099999990550000), + Sat(2099999994330000), + Sat(2099999996220000), + Sat(2099999997060000), + Sat(2099999997480000), + Sat(Sat::SUPPLY), + ]; + pub const FIRST_POST_SUBSIDY: Epoch = Self(33); + + pub fn subsidy(self) -> u64 { + if self < Self::FIRST_POST_SUBSIDY { + (50 * COIN_VALUE) >> self.0 + } else { + 0 + } + } + + pub fn starting_sat(self) -> Sat { + *Self::STARTING_SATS + .get(usize::try_from(self.0).unwrap()) + .unwrap_or_else(|| Self::STARTING_SATS.last().unwrap()) + } + + pub fn starting_height(self) -> Height { + Height(self.0 * SUBSIDY_HALVING_INTERVAL) + } +} + +impl PartialEq for Epoch { + fn eq(&self, other: &u32) -> bool { + self.0 == *other + } +} + +impl From for Epoch { + fn from(sat: Sat) -> Self { + if sat < Self::STARTING_SATS[1] { + Epoch(0) + } else if sat < Self::STARTING_SATS[2] { + Epoch(1) + } else if sat < Self::STARTING_SATS[3] { + Epoch(2) + } else if sat < Self::STARTING_SATS[4] { + Epoch(3) + } else if sat < Self::STARTING_SATS[5] { + Epoch(4) + } else if sat < Self::STARTING_SATS[6] { + Epoch(5) + } else if sat < Self::STARTING_SATS[7] { + Epoch(6) + } else if sat < Self::STARTING_SATS[8] { + Epoch(7) + } else if sat < Self::STARTING_SATS[9] { + Epoch(8) + } else if sat < Self::STARTING_SATS[10] { + Epoch(9) + } else if sat < Self::STARTING_SATS[11] { + Epoch(10) + } else if sat < Self::STARTING_SATS[12] { + Epoch(11) + } else if sat < Self::STARTING_SATS[13] { + Epoch(12) + } else if sat < Self::STARTING_SATS[14] { + Epoch(13) + } else if sat < Self::STARTING_SATS[15] { + Epoch(14) + } else if sat < Self::STARTING_SATS[16] { + Epoch(15) + } else if sat < Self::STARTING_SATS[17] { + Epoch(16) + } else if sat < Self::STARTING_SATS[18] { + Epoch(17) + } else if sat < Self::STARTING_SATS[19] { + Epoch(18) + } else if sat < Self::STARTING_SATS[20] { + Epoch(19) + } else if sat < Self::STARTING_SATS[21] { + Epoch(20) + } else if sat < Self::STARTING_SATS[22] { + Epoch(21) + } else if sat < Self::STARTING_SATS[23] { + Epoch(22) + } else if sat < Self::STARTING_SATS[24] { + Epoch(23) + } else if sat < Self::STARTING_SATS[25] { + Epoch(24) + } else if sat < Self::STARTING_SATS[26] { + Epoch(25) + } else if sat < Self::STARTING_SATS[27] { + Epoch(26) + } else if sat < Self::STARTING_SATS[28] { + Epoch(27) + } else if sat < Self::STARTING_SATS[29] { + Epoch(28) + } else if sat < Self::STARTING_SATS[30] { + Epoch(29) + } else if sat < Self::STARTING_SATS[31] { + Epoch(30) + } else if sat < Self::STARTING_SATS[32] { + Epoch(31) + } else if sat < Self::STARTING_SATS[33] { + Epoch(32) + } else { + Epoch(33) + } + } +} + +impl From for Epoch { + fn from(height: Height) -> Self { + Self(height.0 / SUBSIDY_HALVING_INTERVAL) + } +} + +// #[cfg(test)] +// mod tests { +// use super::super::*; + +// #[test] +// fn starting_sat() { +// assert_eq!(Epoch(0).starting_sat(), 0); +// assert_eq!( +// Epoch(1).starting_sat(), +// Epoch(0).subsidy() * u64::from(SUBSIDY_HALVING_INTERVAL) +// ); +// assert_eq!( +// Epoch(2).starting_sat(), +// (Epoch(0).subsidy() + Epoch(1).subsidy()) * u64::from(SUBSIDY_HALVING_INTERVAL) +// ); +// assert_eq!(Epoch(33).starting_sat(), Sat(Sat::SUPPLY)); +// assert_eq!(Epoch(34).starting_sat(), Sat(Sat::SUPPLY)); +// } + +// #[test] +// fn starting_sats() { +// let mut sat = 0; + +// let mut epoch_sats = Vec::new(); + +// for epoch in 0..34 { +// epoch_sats.push(sat); +// sat += u64::from(SUBSIDY_HALVING_INTERVAL) * Epoch(epoch).subsidy(); +// } + +// assert_eq!(Epoch::STARTING_SATS.as_slice(), epoch_sats); +// assert_eq!(Epoch::STARTING_SATS.len(), 34); +// } + +// #[test] +// fn subsidy() { +// assert_eq!(Epoch(0).subsidy(), 5000000000); +// assert_eq!(Epoch(1).subsidy(), 2500000000); +// assert_eq!(Epoch(32).subsidy(), 1); +// assert_eq!(Epoch(33).subsidy(), 0); +// } + +// #[test] +// fn starting_height() { +// assert_eq!(Epoch(0).starting_height(), 0); +// assert_eq!(Epoch(1).starting_height(), SUBSIDY_HALVING_INTERVAL); +// assert_eq!(Epoch(2).starting_height(), SUBSIDY_HALVING_INTERVAL * 2); +// } + +// #[test] +// fn from_height() { +// assert_eq!(Epoch::from(Height(0)), 0); +// assert_eq!(Epoch::from(Height(SUBSIDY_HALVING_INTERVAL)), 1); +// assert_eq!(Epoch::from(Height(SUBSIDY_HALVING_INTERVAL) + 1), 1); +// } + +// #[test] +// fn from_sat() { +// for (epoch, starting_sat) in Epoch::STARTING_SATS.into_iter().enumerate() { +// if epoch > 0 { +// assert_eq!( +// Epoch::from(Sat(starting_sat.n() - 1)), +// Epoch(u32::try_from(epoch).unwrap() - 1) +// ); +// } +// assert_eq!( +// Epoch::from(starting_sat), +// Epoch(u32::try_from(epoch).unwrap()) +// ); +// assert_eq!( +// Epoch::from(starting_sat + 1), +// Epoch(u32::try_from(epoch).unwrap()) +// ); +// } +// assert_eq!(Epoch::from(Sat(0)), 0); +// assert_eq!(Epoch::from(Sat(1)), 0); +// assert_eq!(Epoch::from(Epoch(1).starting_sat()), 1); +// assert_eq!(Epoch::from(Epoch(1).starting_sat() + 1), 1); +// assert_eq!(Epoch::from(Sat(u64::MAX)), 33); +// } + +// #[test] +// fn eq() { +// assert_eq!(Epoch(0), 0); +// assert_eq!(Epoch(100), 100); +// } + +// #[test] +// fn first_post_subsidy() { +// assert_eq!(Epoch::FIRST_POST_SUBSIDY.subsidy(), 0); +// assert!(Epoch(Epoch::FIRST_POST_SUBSIDY.0 - 1).subsidy() > 0); +// } +// } diff --git a/components/ord/src/height.rs b/components/ord/src/height.rs new file mode 100644 index 0000000..7f53760 --- /dev/null +++ b/components/ord/src/height.rs @@ -0,0 +1,124 @@ +use std::ops::Add; + +use super::{epoch::Epoch, sat::Sat, *}; + +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)] +pub struct Height(pub u32); + +impl Height { + pub fn n(self) -> u32 { + self.0 + } + + pub fn subsidy(self) -> u64 { + Epoch::from(self).subsidy() + } + + pub fn starting_sat(self) -> Sat { + let epoch = Epoch::from(self); + let epoch_starting_sat = epoch.starting_sat(); + let epoch_starting_height = epoch.starting_height(); + epoch_starting_sat + u64::from(self.n() - epoch_starting_height.n()) * epoch.subsidy() + } + + pub fn period_offset(self) -> u32 { + self.0 % DIFFCHANGE_INTERVAL + } +} + +impl Add for Height { + type Output = Self; + + fn add(self, other: u32) -> Height { + Self(self.0 + other) + } +} + +// impl Sub for Height { +// type Output = Self; + +// fn sub(self, other: u32) -> Height { +// Self(self.0 - other) +// } +// } + +// impl PartialEq for Height { +// fn eq(&self, other: &u32) -> bool { +// self.0 == *other +// } +// } + +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[test] +// fn n() { +// assert_eq!(Height(0).n(), 0); +// assert_eq!(Height(1).n(), 1); +// } + +// #[test] +// fn add() { +// assert_eq!(Height(0) + 1, 1); +// assert_eq!(Height(1) + 100, 101); +// } + +// #[test] +// fn sub() { +// assert_eq!(Height(1) - 1, 0); +// assert_eq!(Height(100) - 50, 50); +// } + +// #[test] +// fn eq() { +// assert_eq!(Height(0), 0); +// assert_eq!(Height(100), 100); +// } + +// #[test] +// fn from_str() { +// assert_eq!("0".parse::().unwrap(), 0); +// assert!("foo".parse::().is_err()); +// } + +// #[test] +// fn subsidy() { +// assert_eq!(Height(0).subsidy(), 5000000000); +// assert_eq!(Height(1).subsidy(), 5000000000); +// assert_eq!(Height(SUBSIDY_HALVING_INTERVAL - 1).subsidy(), 5000000000); +// assert_eq!(Height(SUBSIDY_HALVING_INTERVAL).subsidy(), 2500000000); +// assert_eq!(Height(SUBSIDY_HALVING_INTERVAL + 1).subsidy(), 2500000000); +// } + +// #[test] +// fn starting_sat() { +// assert_eq!(Height(0).starting_sat(), 0); +// assert_eq!(Height(1).starting_sat(), 5000000000); +// assert_eq!( +// Height(SUBSIDY_HALVING_INTERVAL - 1).starting_sat(), +// (u64::from(SUBSIDY_HALVING_INTERVAL) - 1) * 5000000000 +// ); +// assert_eq!( +// Height(SUBSIDY_HALVING_INTERVAL).starting_sat(), +// u64::from(SUBSIDY_HALVING_INTERVAL) * 5000000000 +// ); +// assert_eq!( +// Height(SUBSIDY_HALVING_INTERVAL + 1).starting_sat(), +// u64::from(SUBSIDY_HALVING_INTERVAL) * 5000000000 + 2500000000 +// ); +// assert_eq!( +// Height(u32::MAX).starting_sat(), +// *Epoch::STARTING_SATS.last().unwrap() +// ); +// } + +// #[test] +// fn period_offset() { +// assert_eq!(Height(0).period_offset(), 0); +// assert_eq!(Height(1).period_offset(), 1); +// assert_eq!(Height(DIFFCHANGE_INTERVAL - 1).period_offset(), 2015); +// assert_eq!(Height(DIFFCHANGE_INTERVAL).period_offset(), 0); +// assert_eq!(Height(DIFFCHANGE_INTERVAL + 1).period_offset(), 1); +// } +// } diff --git a/components/ord/src/inscription.rs b/components/ord/src/inscription.rs new file mode 100644 index 0000000..a445a65 --- /dev/null +++ b/components/ord/src/inscription.rs @@ -0,0 +1,904 @@ +use { + super::{inscription_id::InscriptionId, media::Media, tag::Tag, *}, + bitcoin::{constants::MAX_SCRIPT_ELEMENT_SIZE, hashes::Hash, opcodes, script, ScriptBuf, Txid}, + ciborium::Value, + std::{io::Cursor, str}, +}; + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq, Default)] +pub struct Inscription { + pub body: Option>, + pub content_encoding: Option>, + pub content_type: Option>, + pub delegate: Option>, + pub duplicate_field: bool, + pub incomplete_field: bool, + pub metadata: Option>, + pub metaprotocol: Option>, + pub parents: Vec>, + pub pointer: Option>, + pub rune: Option>, + pub unrecognized_even_field: bool, +} + +impl Inscription { + // pub fn new( + // chain: Chain, + // compress: bool, + // delegate: Option, + // metadata: Option>, + // metaprotocol: Option, + // parents: Vec, + // path: Option, + // pointer: Option, + // rune: Option, + // ) -> Result { + // let path = path.as_ref(); + + // let (body, content_type, content_encoding) = if let Some(path) = path { + // let body = fs::read(path).with_context(|| format!("io error reading {}", path.display()))?; + + // let content_type = Media::content_type_for_path(path)?.0; + + // let (body, content_encoding) = if compress { + // let compression_mode = Media::content_type_for_path(path)?.1; + // let mut compressed = Vec::new(); + + // { + // CompressorWriter::with_params( + // &mut compressed, + // body.len(), + // &BrotliEncoderParams { + // lgblock: 24, + // lgwin: 24, + // mode: compression_mode, + // quality: 11, + // size_hint: body.len(), + // ..default() + // }, + // ) + // .write_all(&body)?; + + // let mut decompressor = brotli::Decompressor::new(compressed.as_slice(), compressed.len()); + + // let mut decompressed = Vec::new(); + + // decompressor.read_to_end(&mut decompressed)?; + + // ensure!(decompressed == body, "decompression roundtrip failed"); + // } + + // if compressed.len() < body.len() { + // (compressed, Some("br".as_bytes().to_vec())) + // } else { + // (body, None) + // } + // } else { + // (body, None) + // }; + + // if let Some(limit) = chain.inscription_content_size_limit() { + // let len = body.len(); + // if len > limit { + // bail!("content size of {len} bytes exceeds {limit} byte limit for {chain} inscriptions"); + // } + // } + + // (Some(body), Some(content_type), content_encoding) + // } else { + // (None, None, None) + // }; + + // Ok(Self { + // body, + // content_encoding, + // content_type: content_type.map(|content_type| content_type.into()), + // delegate: delegate.map(|delegate| delegate.value()), + // metadata, + // metaprotocol: metaprotocol.map(|metaprotocol| metaprotocol.into_bytes()), + // parents: parents.iter().map(|parent| parent.value()).collect(), + // pointer: pointer.map(Self::pointer_value), + // rune: rune.map(|rune| rune.commitment()), + // ..default() + // }) + // } + + pub fn pointer_value(pointer: u64) -> Vec { + let mut bytes = pointer.to_le_bytes().to_vec(); + + while bytes.last().copied() == Some(0) { + bytes.pop(); + } + + bytes + } + + pub fn append_reveal_script_to_builder(&self, mut builder: script::Builder) -> script::Builder { + builder = builder + .push_opcode(opcodes::OP_FALSE) + .push_opcode(opcodes::all::OP_IF) + .push_slice(envelope::PROTOCOL_ID); + + Tag::ContentType.append(&mut builder, &self.content_type); + Tag::ContentEncoding.append(&mut builder, &self.content_encoding); + Tag::Metaprotocol.append(&mut builder, &self.metaprotocol); + Tag::Parent.append_array(&mut builder, &self.parents); + Tag::Delegate.append(&mut builder, &self.delegate); + Tag::Pointer.append(&mut builder, &self.pointer); + Tag::Metadata.append(&mut builder, &self.metadata); + Tag::Rune.append(&mut builder, &self.rune); + + if let Some(body) = &self.body { + builder = builder.push_slice(envelope::BODY_TAG); + for chunk in body.chunks(MAX_SCRIPT_ELEMENT_SIZE) { + builder = builder.push_slice::<&script::PushBytes>(chunk.try_into().unwrap()); + } + } + + builder.push_opcode(opcodes::all::OP_ENDIF) + } + + // #[cfg(test)] + // pub(crate) fn append_reveal_script(&self, builder: script::Builder) -> ScriptBuf { + // self.append_reveal_script_to_builder(builder).into_script() + // } + + pub fn append_batch_reveal_script_to_builder( + inscriptions: &[Inscription], + mut builder: script::Builder, + ) -> script::Builder { + for inscription in inscriptions { + builder = inscription.append_reveal_script_to_builder(builder); + } + + builder + } + + pub fn append_batch_reveal_script( + inscriptions: &[Inscription], + builder: script::Builder, + ) -> ScriptBuf { + Inscription::append_batch_reveal_script_to_builder(inscriptions, builder).into_script() + } + + fn inscription_id_field(field: Option<&[u8]>) -> Option { + let value = field.as_ref()?; + + if value.len() < Txid::LEN { + return None; + } + + if value.len() > Txid::LEN + 4 { + return None; + } + + let (txid, index) = value.split_at(Txid::LEN); + + if let Some(last) = index.last() { + // Accept fixed length encoding with 4 bytes (with potential trailing zeroes) + // or variable length (no trailing zeroes) + if index.len() != 4 && *last == 0 { + return None; + } + } + + let txid = Txid::from_slice(txid).unwrap(); + + let index = [ + index.first().copied().unwrap_or(0), + index.get(1).copied().unwrap_or(0), + index.get(2).copied().unwrap_or(0), + index.get(3).copied().unwrap_or(0), + ]; + + let index = u32::from_le_bytes(index); + + Some(InscriptionId { txid, index }) + } + + pub fn media(&self) -> Media { + if self.body.is_none() { + return Media::Unknown; + } + + let Some(content_type) = self.content_type() else { + return Media::Unknown; + }; + + content_type.parse().unwrap_or(Media::Unknown) + } + + pub fn body(&self) -> Option<&[u8]> { + Some(self.body.as_ref()?) + } + + pub fn into_body(self) -> Option> { + self.body + } + + pub fn content_length(&self) -> Option { + Some(self.body()?.len()) + } + + pub fn content_type(&self) -> Option<&str> { + str::from_utf8(self.content_type.as_ref()?).ok() + } + + // pub fn content_encoding(&self) -> Option { + // HeaderValue::from_str(str::from_utf8(self.content_encoding.as_ref()?).unwrap_or_default()) + // .ok() + // } + + pub fn delegate(&self) -> Option { + Self::inscription_id_field(self.delegate.as_deref()) + } + + pub fn metadata(&self) -> Option { + ciborium::from_reader(Cursor::new(self.metadata.as_ref()?)).ok() + } + + pub fn metaprotocol(&self) -> Option<&str> { + str::from_utf8(self.metaprotocol.as_ref()?).ok() + } + + pub fn parents(&self) -> Vec { + self.parents + .iter() + .filter_map(|parent| Self::inscription_id_field(Some(parent))) + .collect() + } + + pub fn pointer(&self) -> Option { + let value = self.pointer.as_ref()?; + + if value.iter().skip(8).copied().any(|byte| byte != 0) { + return None; + } + + let pointer = [ + value.first().copied().unwrap_or(0), + value.get(1).copied().unwrap_or(0), + value.get(2).copied().unwrap_or(0), + value.get(3).copied().unwrap_or(0), + value.get(4).copied().unwrap_or(0), + value.get(5).copied().unwrap_or(0), + value.get(6).copied().unwrap_or(0), + value.get(7).copied().unwrap_or(0), + ]; + + Some(u64::from_le_bytes(pointer)) + } + + // #[cfg(test)] + // pub(crate) fn to_witness(&self) -> Witness { + // let builder = script::Builder::new(); + + // let script = self.append_reveal_script(builder); + + // let mut witness = Witness::new(); + + // witness.push(script); + // witness.push([]); + + // witness + // } + + // pub fn hidden(&self) -> bool { + // use regex::bytes::Regex; + + // const BVM_NETWORK: &[u8] = b"\ + //

bvm.network

"; + + // lazy_static! { + // static ref BRC_420: Regex = + // Regex::new(r"^\s*/content/[[:xdigit:]]{64}i\d+\s*$").unwrap(); + // } + + // self.body() + // .map(|body| BRC_420.is_match(body) || body.starts_with(BVM_NETWORK)) + // .unwrap_or_default() + // || self.metaprotocol.is_some() + // || matches!(self.media(), Media::Code(_) | Media::Text | Media::Unknown) + // } +} + +// #[cfg(test)] +// mod tests { +// use {super::*, std::io::Write}; + +// #[test] +// fn reveal_script_chunks_body() { +// assert_eq!( +// inscription("foo", []) +// .append_reveal_script(script::Builder::new()) +// .instructions() +// .count(), +// 7 +// ); + +// assert_eq!( +// inscription("foo", [0; 1]) +// .append_reveal_script(script::Builder::new()) +// .instructions() +// .count(), +// 8 +// ); + +// assert_eq!( +// inscription("foo", [0; 520]) +// .append_reveal_script(script::Builder::new()) +// .instructions() +// .count(), +// 8 +// ); + +// assert_eq!( +// inscription("foo", [0; 521]) +// .append_reveal_script(script::Builder::new()) +// .instructions() +// .count(), +// 9 +// ); + +// assert_eq!( +// inscription("foo", [0; 1040]) +// .append_reveal_script(script::Builder::new()) +// .instructions() +// .count(), +// 9 +// ); + +// assert_eq!( +// inscription("foo", [0; 1041]) +// .append_reveal_script(script::Builder::new()) +// .instructions() +// .count(), +// 10 +// ); +// } + +// #[test] +// fn reveal_script_chunks_metadata() { +// assert_eq!( +// Inscription { +// metadata: None, +// ..default() +// } +// .append_reveal_script(script::Builder::new()) +// .instructions() +// .count(), +// 4 +// ); + +// assert_eq!( +// Inscription { +// metadata: Some(Vec::new()), +// ..default() +// } +// .append_reveal_script(script::Builder::new()) +// .instructions() +// .count(), +// 4 +// ); + +// assert_eq!( +// Inscription { +// metadata: Some(vec![0; 1]), +// ..default() +// } +// .append_reveal_script(script::Builder::new()) +// .instructions() +// .count(), +// 6 +// ); + +// assert_eq!( +// Inscription { +// metadata: Some(vec![0; 520]), +// ..default() +// } +// .append_reveal_script(script::Builder::new()) +// .instructions() +// .count(), +// 6 +// ); + +// assert_eq!( +// Inscription { +// metadata: Some(vec![0; 521]), +// ..default() +// } +// .append_reveal_script(script::Builder::new()) +// .instructions() +// .count(), +// 8 +// ); +// } + +// #[test] +// fn inscription_with_no_parent_field_has_no_parent() { +// assert!(Inscription { +// parents: Vec::new(), +// ..default() +// } +// .parents() +// .is_empty()); +// } + +// #[test] +// fn inscription_with_parent_field_shorter_than_txid_length_has_no_parent() { +// assert!(Inscription { +// parents: vec![Vec::new()], +// ..default() +// } +// .parents() +// .is_empty()); +// } + +// #[test] +// fn inscription_with_parent_field_longer_than_txid_and_index_has_no_parent() { +// assert!(Inscription { +// parents: vec![vec![1; 37]], +// ..default() +// } +// .parents() +// .is_empty()); +// } + +// #[test] +// fn inscription_with_parent_field_index_with_trailing_zeroes_and_fixed_length_has_parent() { +// let mut parent = vec![1; 36]; + +// parent[35] = 0; + +// assert!(!Inscription { +// parents: vec![parent], +// ..default() +// } +// .parents() +// .is_empty()); +// } + +// #[test] +// fn inscription_with_parent_field_index_with_trailing_zeroes_and_variable_length_has_no_parent() { +// let mut parent = vec![1; 35]; + +// parent[34] = 0; + +// assert!(Inscription { +// parents: vec![parent], +// ..default() +// } +// .parents() +// .is_empty()); +// } + +// #[test] +// fn inscription_delegate_txid_is_deserialized_correctly() { +// assert_eq!( +// Inscription { +// delegate: Some(vec![ +// 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, +// 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, +// 0x1e, 0x1f, +// ]), +// ..default() +// } +// .delegate() +// .unwrap() +// .txid, +// "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100" +// .parse() +// .unwrap() +// ); +// } + +// #[test] +// fn inscription_parent_txid_is_deserialized_correctly() { +// assert_eq!( +// Inscription { +// parents: vec![vec![ +// 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, +// 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, +// 0x1e, 0x1f, +// ]], +// ..default() +// } +// .parents(), +// [ +// "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100i0" +// .parse() +// .unwrap() +// ], +// ); +// } + +// #[test] +// fn inscription_parent_with_zero_byte_index_field_is_deserialized_correctly() { +// assert_eq!( +// Inscription { +// parents: vec![vec![1; 32]], +// ..default() +// } +// .parents(), +// [ +// "0101010101010101010101010101010101010101010101010101010101010101i0" +// .parse() +// .unwrap() +// ], +// ); +// } + +// #[test] +// fn inscription_parent_with_one_byte_index_field_is_deserialized_correctly() { +// assert_eq!( +// Inscription { +// parents: vec![vec![ +// 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +// 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +// 0xff, 0xff, 0x01 +// ]], +// ..default() +// } +// .parents(), +// [ +// "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffi1" +// .parse() +// .unwrap() +// ], +// ); +// } + +// #[test] +// fn inscription_parent_with_two_byte_index_field_is_deserialized_correctly() { +// assert_eq!( +// Inscription { +// parents: vec![vec![ +// 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +// 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +// 0xff, 0xff, 0x01, 0x02 +// ]], +// ..default() +// } +// .parents(), +// [ +// "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffi513" +// .parse() +// .unwrap() +// ], +// ); +// } + +// #[test] +// fn inscription_parent_with_three_byte_index_field_is_deserialized_correctly() { +// assert_eq!( +// Inscription { +// parents: vec![vec![ +// 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +// 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +// 0xff, 0xff, 0x01, 0x02, 0x03 +// ]], +// ..default() +// } +// .parents(), +// [ +// "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffi197121" +// .parse() +// .unwrap() +// ], +// ); +// } + +// #[test] +// fn inscription_parent_with_four_byte_index_field_is_deserialized_correctly() { +// assert_eq!( +// Inscription { +// parents: vec![vec![ +// 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +// 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +// 0xff, 0xff, 0x01, 0x02, 0x03, 0x04, +// ]], +// ..default() +// } +// .parents(), +// [ +// "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffi67305985" +// .parse() +// .unwrap() +// ], +// ); +// } + +// #[test] +// fn inscription_parent_returns_multiple_parents() { +// assert_eq!( +// Inscription { +// parents: vec![ +// vec![ +// 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +// 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +// 0xff, 0xff, 0xff, 0xff, 0x01, 0x02, 0x03, 0x04, +// ], +// vec![ +// 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +// 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +// 0xff, 0xff, 0xff, 0xff, 0x00, 0x02, 0x03, 0x04, +// ] +// ], +// ..default() +// } +// .parents(), +// [ +// "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffi67305985" +// .parse() +// .unwrap(), +// "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffi67305984" +// .parse() +// .unwrap() +// ], +// ); +// } + +// #[test] +// fn metadata_function_decodes_metadata() { +// assert_eq!( +// Inscription { +// metadata: Some(vec![0x44, 0, 1, 2, 3]), +// ..default() +// } +// .metadata() +// .unwrap(), +// Value::Bytes(vec![0, 1, 2, 3]), +// ); +// } + +// #[test] +// fn metadata_function_returns_none_if_no_metadata() { +// assert_eq!( +// Inscription { +// metadata: None, +// ..default() +// } +// .metadata(), +// None, +// ); +// } + +// #[test] +// fn metadata_function_returns_none_if_metadata_fails_to_parse() { +// assert_eq!( +// Inscription { +// metadata: Some(vec![0x44]), +// ..default() +// } +// .metadata(), +// None, +// ); +// } + +// #[test] +// fn pointer_decode() { +// assert_eq!( +// Inscription { +// pointer: None, +// ..default() +// } +// .pointer(), +// None +// ); +// assert_eq!( +// Inscription { +// pointer: Some(vec![0]), +// ..default() +// } +// .pointer(), +// Some(0), +// ); +// assert_eq!( +// Inscription { +// pointer: Some(vec![1, 2, 3, 4, 5, 6, 7, 8]), +// ..default() +// } +// .pointer(), +// Some(0x0807060504030201), +// ); +// assert_eq!( +// Inscription { +// pointer: Some(vec![1, 2, 3, 4, 5, 6]), +// ..default() +// } +// .pointer(), +// Some(0x0000060504030201), +// ); +// assert_eq!( +// Inscription { +// pointer: Some(vec![1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0]), +// ..default() +// } +// .pointer(), +// Some(0x0807060504030201), +// ); +// assert_eq!( +// Inscription { +// pointer: Some(vec![1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 1]), +// ..default() +// } +// .pointer(), +// None, +// ); +// assert_eq!( +// Inscription { +// pointer: Some(vec![1, 2, 3, 4, 5, 6, 7, 8, 1]), +// ..default() +// } +// .pointer(), +// None, +// ); +// } + +// #[test] +// fn pointer_encode() { +// assert_eq!( +// Inscription { +// pointer: None, +// ..default() +// } +// .to_witness(), +// envelope(&[b"ord"]), +// ); + +// assert_eq!( +// Inscription { +// pointer: Some(vec![1, 2, 3]), +// ..default() +// } +// .to_witness(), +// envelope(&[b"ord", &[2], &[1, 2, 3]]), +// ); +// } + +// #[test] +// fn pointer_value() { +// let mut file = tempfile::Builder::new().suffix(".txt").tempfile().unwrap(); + +// write!(file, "foo").unwrap(); + +// let inscription = Inscription::new( +// Chain::Mainnet, +// false, +// None, +// None, +// None, +// Vec::new(), +// Some(file.path().to_path_buf()), +// None, +// None, +// ) +// .unwrap(); + +// assert_eq!(inscription.pointer, None); + +// let inscription = Inscription::new( +// Chain::Mainnet, +// false, +// None, +// None, +// None, +// Vec::new(), +// Some(file.path().to_path_buf()), +// Some(0), +// None, +// ) +// .unwrap(); + +// assert_eq!(inscription.pointer, Some(Vec::new())); + +// let inscription = Inscription::new( +// Chain::Mainnet, +// false, +// None, +// None, +// None, +// Vec::new(), +// Some(file.path().to_path_buf()), +// Some(1), +// None, +// ) +// .unwrap(); + +// assert_eq!(inscription.pointer, Some(vec![1])); + +// let inscription = Inscription::new( +// Chain::Mainnet, +// false, +// None, +// None, +// None, +// Vec::new(), +// Some(file.path().to_path_buf()), +// Some(256), +// None, +// ) +// .unwrap(); + +// assert_eq!(inscription.pointer, Some(vec![0, 1])); +// } + +// #[test] +// fn hidden() { +// #[track_caller] +// fn case(content_type: Option<&str>, body: Option<&str>, expected: bool) { +// assert_eq!( +// Inscription { +// content_type: content_type.map(|content_type| content_type.as_bytes().into()), +// body: body.map(|content_type| content_type.as_bytes().into()), +// ..default() +// } +// .hidden(), +// expected +// ); +// } + +// case(None, None, true); +// case(Some("foo"), Some(""), true); +// case(Some("text/plain"), None, true); +// case( +// Some("text/plain"), +// Some("The fox jumped. The cow danced."), +// true, +// ); +// case(Some("text/plain;charset=utf-8"), Some("foo"), true); +// case(Some("text/plain;charset=cn-big5"), Some("foo"), true); +// case(Some("application/json"), Some("foo"), true); +// case( +// Some("text/markdown"), +// Some("/content/09a8d837ec0bcaec668ecf405e696a16bee5990863659c224ff888fb6f8f45e7i0"), +// true, +// ); +// case( +// Some("text/html"), +// Some("/content/09a8d837ec0bcaec668ecf405e696a16bee5990863659c224ff888fb6f8f45e7i0"), +// true, +// ); +// case(Some("application/yaml"), Some(""), true); +// case( +// Some("text/html;charset=utf-8"), +// Some("/content/09a8d837ec0bcaec668ecf405e696a16bee5990863659c224ff888fb6f8f45e7i0"), +// true, +// ); +// case( +// Some("text/html"), +// Some(" /content/09a8d837ec0bcaec668ecf405e696a16bee5990863659c224ff888fb6f8f45e7i0 \n"), +// true, +// ); +// case( +// Some("text/html"), +// Some( +// r#"

bvm.network

"#, +// ), +// true, +// ); +// case( +// Some("text/html"), +// Some( +// r#"

bvm.network

foo"#, +// ), +// true, +// ); + +// assert!(Inscription { +// content_type: Some("text/plain".as_bytes().into()), +// body: Some(b"{\xc3\x28}".as_slice().into()), +// ..default() +// } +// .hidden()); + +// assert!(Inscription { +// content_type: Some("text/html".as_bytes().into()), +// body: Some("hello".as_bytes().into()), +// metaprotocol: Some(Vec::new()), +// ..default() +// } +// .hidden()); +// } +// } diff --git a/components/ord/src/inscription_id.rs b/components/ord/src/inscription_id.rs new file mode 100644 index 0000000..6214bdd --- /dev/null +++ b/components/ord/src/inscription_id.rs @@ -0,0 +1,193 @@ +use std::fmt::{self, Display, Formatter}; + +use bitcoin::Txid; + +#[derive(Debug, PartialEq, Copy, Clone, Hash, Eq, PartialOrd, Ord)] +pub struct InscriptionId { + pub txid: Txid, + pub index: u32, +} + +// impl Default for InscriptionId { +// fn default() -> Self { +// Self { +// txid: Txid::all_zeros(), +// index: 0, +// } +// } +// } + +// impl InscriptionId { +// pub(crate) fn value(self) -> Vec { +// let index = self.index.to_le_bytes(); +// let mut index_slice = index.as_slice(); + +// while index_slice.last().copied() == Some(0) { +// index_slice = &index_slice[0..index_slice.len() - 1]; +// } + +// self +// .txid +// .to_byte_array() +// .iter() +// .chain(index_slice) +// .copied() +// .collect() +// } +// } + +impl Display for InscriptionId { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}i{}", self.txid, self.index) + } +} + +// #[derive(Debug)] +// pub enum ParseError { +// Character(char), +// Length(usize), +// Separator(char), +// Txid(bitcoin::hex::HexToArrayError), +// Index(std::num::ParseIntError), +// } + +// impl Display for ParseError { +// fn fmt(&self, f: &mut Formatter) -> fmt::Result { +// match self { +// Self::Character(c) => write!(f, "invalid character: '{c}'"), +// Self::Length(len) => write!(f, "invalid length: {len}"), +// Self::Separator(c) => write!(f, "invalid separator: `{c}`"), +// Self::Txid(err) => write!(f, "invalid txid: {err}"), +// Self::Index(err) => write!(f, "invalid index: {err}"), +// } +// } +// } + +// impl std::error::Error for ParseError {} + +// impl FromStr for InscriptionId { +// type Err = ParseError; + +// fn from_str(s: &str) -> Result { +// if let Some(char) = s.chars().find(|char| !char.is_ascii()) { +// return Err(ParseError::Character(char)); +// } + +// const TXID_LEN: usize = 64; +// const MIN_LEN: usize = TXID_LEN + 2; + +// if s.len() < MIN_LEN { +// return Err(ParseError::Length(s.len())); +// } + +// let txid = &s[..TXID_LEN]; + +// let separator = s.chars().nth(TXID_LEN).unwrap(); + +// if separator != 'i' { +// return Err(ParseError::Separator(separator)); +// } + +// let vout = &s[TXID_LEN + 1..]; + +// Ok(Self { +// txid: txid.parse().map_err(ParseError::Txid)?, +// index: vout.parse().map_err(ParseError::Index)?, +// }) +// } +// } + +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[test] +// fn display() { +// assert_eq!( +// inscription_id(1).to_string(), +// "1111111111111111111111111111111111111111111111111111111111111111i1", +// ); +// assert_eq!( +// InscriptionId { +// txid: txid(1), +// index: 0, +// } +// .to_string(), +// "1111111111111111111111111111111111111111111111111111111111111111i0", +// ); +// assert_eq!( +// InscriptionId { +// txid: txid(1), +// index: 0xFFFFFFFF, +// } +// .to_string(), +// "1111111111111111111111111111111111111111111111111111111111111111i4294967295", +// ); +// } + +// #[test] +// fn from_str() { +// assert_eq!( +// "1111111111111111111111111111111111111111111111111111111111111111i1" +// .parse::() +// .unwrap(), +// inscription_id(1), +// ); +// assert_eq!( +// "1111111111111111111111111111111111111111111111111111111111111111i4294967295" +// .parse::() +// .unwrap(), +// InscriptionId { +// txid: txid(1), +// index: 0xFFFFFFFF, +// }, +// ); +// assert_eq!( +// "1111111111111111111111111111111111111111111111111111111111111111i4294967295" +// .parse::() +// .unwrap(), +// InscriptionId { +// txid: txid(1), +// index: 0xFFFFFFFF, +// }, +// ); +// } + +// #[test] +// fn from_str_bad_character() { +// assert_matches!( +// "→".parse::(), +// Err(ParseError::Character('→')), +// ); +// } + +// #[test] +// fn from_str_bad_length() { +// assert_matches!("foo".parse::(), Err(ParseError::Length(3))); +// } + +// #[test] +// fn from_str_bad_separator() { +// assert_matches!( +// "0000000000000000000000000000000000000000000000000000000000000000x0".parse::(), +// Err(ParseError::Separator('x')), +// ); +// } + +// #[test] +// fn from_str_bad_index() { +// assert_matches!( +// "0000000000000000000000000000000000000000000000000000000000000000ifoo" +// .parse::(), +// Err(ParseError::Index(_)), +// ); +// } + +// #[test] +// fn from_str_bad_txid() { +// assert_matches!( +// "x000000000000000000000000000000000000000000000000000000000000000i0".parse::(), +// Err(ParseError::Txid(_)), +// ); +// } +// } diff --git a/components/ord/src/lib.rs b/components/ord/src/lib.rs new file mode 100644 index 0000000..38406fe --- /dev/null +++ b/components/ord/src/lib.rs @@ -0,0 +1,27 @@ +#![allow(dead_code)] +#![allow(unused_variables)] + +#[macro_use] +extern crate serde_derive; + +type Result = std::result::Result; + +pub mod chain; +pub mod charm; +pub mod decimal_sat; +pub mod degree; +pub mod envelope; +pub mod epoch; +pub mod height; +pub mod inscription; +pub mod inscription_id; +pub mod media; +pub mod rarity; +pub mod sat; +pub mod sat_point; +pub mod tag; + +pub const SUBSIDY_HALVING_INTERVAL: u32 = 210_000; +pub const DIFFCHANGE_INTERVAL: u32 = 2016; +pub const CYCLE_EPOCHS: u32 = 6; +pub const COIN_VALUE: u64 = 100_000_000; diff --git a/components/ord/src/media.rs b/components/ord/src/media.rs new file mode 100644 index 0000000..d972e6c --- /dev/null +++ b/components/ord/src/media.rs @@ -0,0 +1,229 @@ +use std::str::FromStr; + +use anyhow::{anyhow, Error}; +use self::{ImageRendering::*, Language::*, Media::*}; + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum Media { + Audio, + Code(Language), + Font, + Iframe, + Image(ImageRendering), + Markdown, + Model, + Pdf, + Text, + Unknown, + Video, +} + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum Language { + Css, + JavaScript, + Json, + Python, + Yaml, +} + +// impl Display for Language { +// fn fmt(&self, f: &mut Formatter) -> fmt::Result { +// write!( +// f, +// "{}", +// match self { +// Self::Css => "css", +// Self::JavaScript => "javascript", +// Self::Json => "json", +// Self::Python => "python", +// Self::Yaml => "yaml", +// } +// ) +// } +// } + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum ImageRendering { + Auto, + Pixelated, +} + +// impl Display for ImageRendering { +// fn fmt(&self, f: &mut Formatter) -> fmt::Result { +// write!( +// f, +// "{}", +// match self { +// Self::Auto => "auto", +// Self::Pixelated => "pixelated", +// } +// ) +// } +// } + + impl Media { + #[rustfmt::skip] + const TABLE: &'static [(&'static str, Media, &'static [&'static str])] = &[ + ("application/cbor", Unknown, &["cbor"]), + ("application/json", Code(Json), &["json"]), + ("application/octet-stream", Unknown, &["bin"]), + ("application/pdf", Pdf, &["pdf"]), + ("application/pgp-signature", Text, &["asc"]), + ("application/protobuf", Unknown, &["binpb"]), + ("application/x-bittorrent", Unknown, &["torrent"]), + ("application/x-javascript", Code(JavaScript), &[]), + ("application/yaml", Code(Yaml), &["yaml", "yml"]), + ("audio/flac", Audio, &["flac"]), + ("audio/mpeg", Audio, &["mp3"]), + ("audio/ogg;codecs=opus", Audio, &["opus"]), + ("audio/wav", Audio, &["wav"]), + ("font/otf", Font, &["otf"]), + ("font/ttf", Font, &["ttf"]), + ("font/woff", Font, &["woff"]), + ("font/woff2", Font, &["woff2"]), + ("image/apng", Image(Pixelated), &["apng"]), + ("image/avif", Image(Auto), &["avif"]), + ("image/gif", Image(Pixelated), &["gif"]), + ("image/jpeg", Image(Pixelated), &["jpg", "jpeg"]), + ("image/jxl", Image(Auto), &[]), + ("image/png", Image(Pixelated), &["png"]), + ("image/svg+xml", Iframe, &["svg"]), + ("image/webp", Image(Pixelated), &["webp"]), + ("model/gltf+json", Model, &["gltf"]), + ("model/gltf-binary", Model, &["glb"]), + ("model/stl", Unknown, &["stl"]), + ("text/css", Code(Css), &["css"]), + ("text/html", Iframe, &[]), + ("text/html;charset=utf-8", Iframe, &["html"]), + ("text/javascript", Code(JavaScript), &["js", "mjs"]), + ("text/markdown", Markdown, &[]), + ("text/markdown;charset=utf-8", Markdown, &["md"]), + ("text/plain", Text, &[]), + ("text/plain;charset=utf-8", Text, &["txt"]), + ("text/x-python", Code(Python), &["py"]), + ("video/mp4", Video, &["mp4"]), + ("video/webm", Video, &["webm"]), + ]; + +// pub(crate) fn content_type_for_path( +// path: &Path, +// ) -> Result<(&'static str, BrotliEncoderMode), Error> { +// let extension = path +// .extension() +// .ok_or_else(|| anyhow!("file must have extension"))? +// .to_str() +// .ok_or_else(|| anyhow!("unrecognized extension"))?; + +// let extension = extension.to_lowercase(); + +// if extension == "mp4" { +// Media::check_mp4_codec(path)?; +// } + +// for (content_type, mode, _, extensions) in Self::TABLE { +// if extensions.contains(&extension.as_str()) { +// return Ok((*content_type, *mode)); +// } +// } + +// let mut extensions = Self::TABLE +// .iter() +// .flat_map(|(_, _, _, extensions)| extensions.first().cloned()) +// .collect::>(); + +// extensions.sort(); + +// Err(anyhow!( +// "unsupported file extension `.{extension}`, supported extensions: {}", +// extensions.join(" "), +// )) +// } + +// pub(crate) fn check_mp4_codec(path: &Path) -> Result<(), Error> { +// let f = File::open(path)?; +// let size = f.metadata()?.len(); +// let reader = BufReader::new(f); + +// let mp4 = Mp4Reader::read_header(reader, size)?; + +// for track in mp4.tracks().values() { +// if let TrackType::Video = track.track_type()? { +// let media_type = track.media_type()?; +// if media_type != MediaType::H264 { +// return Err(anyhow!( +// "Unsupported video codec, only H.264 is supported in MP4: {media_type}" +// )); +// } +// } +// } + +// Ok(()) +// } + } + +impl FromStr for Media { + type Err = Error; + + fn from_str(s: &str) -> Result { + for entry in Self::TABLE { + if entry.0 == s { + return Ok(entry.1); + } + } + + Err(anyhow!("unknown content type: {s}")) + } +} + +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[test] +// fn for_extension() { +// assert_eq!( +// Media::content_type_for_path(Path::new("pepe.jpg")).unwrap(), +// ("image/jpeg", BrotliEncoderMode::BROTLI_MODE_GENERIC) +// ); +// assert_eq!( +// Media::content_type_for_path(Path::new("pepe.jpeg")).unwrap(), +// ("image/jpeg", BrotliEncoderMode::BROTLI_MODE_GENERIC) +// ); +// assert_eq!( +// Media::content_type_for_path(Path::new("pepe.JPG")).unwrap(), +// ("image/jpeg", BrotliEncoderMode::BROTLI_MODE_GENERIC) +// ); +// assert_eq!( +// Media::content_type_for_path(Path::new("pepe.txt")).unwrap(), +// ( +// "text/plain;charset=utf-8", +// BrotliEncoderMode::BROTLI_MODE_TEXT +// ) +// ); +// assert_regex_match!( +// Media::content_type_for_path(Path::new("pepe.foo")).unwrap_err(), +// r"unsupported file extension `\.foo`, supported extensions: apng .*" +// ); +// } + +// #[test] +// fn h264_in_mp4_is_allowed() { +// assert!(Media::check_mp4_codec(Path::new("examples/h264.mp4")).is_ok(),); +// } + +// #[test] +// fn av1_in_mp4_is_rejected() { +// assert!(Media::check_mp4_codec(Path::new("examples/av1.mp4")).is_err(),); +// } + +// #[test] +// fn no_duplicate_extensions() { +// let mut set = HashSet::new(); +// for (_, _, _, extensions) in Media::TABLE { +// for extension in *extensions { +// assert!(set.insert(extension), "duplicate extension `{extension}`"); +// } +// } +// } +// } diff --git a/components/ord/src/rarity.rs b/components/ord/src/rarity.rs new file mode 100644 index 0000000..991d080 --- /dev/null +++ b/components/ord/src/rarity.rs @@ -0,0 +1,231 @@ +use std::{fmt::{self, Display, Formatter}, str::FromStr}; + +use super::{degree::Degree, sat::Sat, *}; + +#[derive( + Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd +)] +pub enum Rarity { + Common, + Uncommon, + Rare, + Epic, + Legendary, + Mythic, +} + +impl Rarity { + pub const ALL: [Rarity; 6] = [ + Rarity::Common, + Rarity::Uncommon, + Rarity::Rare, + Rarity::Epic, + Rarity::Legendary, + Rarity::Mythic, + ]; + + pub fn supply(self) -> u64 { + match self { + Self::Common => 2_099_999_990_760_000, + Self::Uncommon => 6_926_535, + Self::Rare => 3_432, + Self::Epic => 27, + Self::Legendary => 5, + Self::Mythic => 1, + } + } +} + +impl From for u8 { + fn from(rarity: Rarity) -> Self { + rarity as u8 + } +} + +impl TryFrom for Rarity { + type Error = u8; + + fn try_from(rarity: u8) -> Result { + match rarity { + 0 => Ok(Self::Common), + 1 => Ok(Self::Uncommon), + 2 => Ok(Self::Rare), + 3 => Ok(Self::Epic), + 4 => Ok(Self::Legendary), + 5 => Ok(Self::Mythic), + n => Err(n), + } + } +} + +impl Display for Rarity { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!( + f, + "{}", + match self { + Self::Common => "common", + Self::Uncommon => "uncommon", + Self::Rare => "rare", + Self::Epic => "epic", + Self::Legendary => "legendary", + Self::Mythic => "mythic", + } + ) + } +} + +impl From for Rarity { + fn from(sat: Sat) -> Self { + let Degree { + hour, + minute, + second, + third, + } = sat.degree(); + + if hour == 0 && minute == 0 && second == 0 && third == 0 { + Self::Mythic + } else if minute == 0 && second == 0 && third == 0 { + Self::Legendary + } else if minute == 0 && third == 0 { + Self::Epic + } else if second == 0 && third == 0 { + Self::Rare + } else if third == 0 { + Self::Uncommon + } else { + Self::Common + } + } +} + +impl FromStr for Rarity { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "common" => Ok(Self::Common), + "uncommon" => Ok(Self::Uncommon), + "rare" => Ok(Self::Rare), + "epic" => Ok(Self::Epic), + "legendary" => Ok(Self::Legendary), + "mythic" => Ok(Self::Mythic), + _ => Err(format!("invalid rarity `{s}`")), + } + } +} + +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[test] +// fn rarity() { +// assert_eq!(Sat(0).rarity(), Rarity::Mythic); +// assert_eq!(Sat(1).rarity(), Rarity::Common); + +// assert_eq!(Sat(50 * COIN_VALUE - 1).rarity(), Rarity::Common); +// assert_eq!(Sat(50 * COIN_VALUE).rarity(), Rarity::Uncommon); +// assert_eq!(Sat(50 * COIN_VALUE + 1).rarity(), Rarity::Common); + +// assert_eq!( +// Sat(50 * COIN_VALUE * u64::from(DIFFCHANGE_INTERVAL) - 1).rarity(), +// Rarity::Common +// ); +// assert_eq!( +// Sat(50 * COIN_VALUE * u64::from(DIFFCHANGE_INTERVAL)).rarity(), +// Rarity::Rare +// ); +// assert_eq!( +// Sat(50 * COIN_VALUE * u64::from(DIFFCHANGE_INTERVAL) + 1).rarity(), +// Rarity::Common +// ); + +// assert_eq!( +// Sat(50 * COIN_VALUE * u64::from(SUBSIDY_HALVING_INTERVAL) - 1).rarity(), +// Rarity::Common +// ); +// assert_eq!( +// Sat(50 * COIN_VALUE * u64::from(SUBSIDY_HALVING_INTERVAL)).rarity(), +// Rarity::Epic +// ); +// assert_eq!( +// Sat(50 * COIN_VALUE * u64::from(SUBSIDY_HALVING_INTERVAL) + 1).rarity(), +// Rarity::Common +// ); + +// assert_eq!(Sat(2067187500000000 - 1).rarity(), Rarity::Common); +// assert_eq!(Sat(2067187500000000).rarity(), Rarity::Legendary); +// assert_eq!(Sat(2067187500000000 + 1).rarity(), Rarity::Common); +// } + +// #[test] +// fn from_str_and_deserialize_ok() { +// #[track_caller] +// fn case(s: &str, expected: Rarity) { +// let actual = s.parse::().unwrap(); +// assert_eq!(actual, expected); +// let round_trip = actual.to_string().parse::().unwrap(); +// assert_eq!(round_trip, expected); +// let serialized = serde_json::to_string(&expected).unwrap(); +// assert!(serde_json::from_str::(&serialized).is_ok()); +// } + +// case("common", Rarity::Common); +// case("uncommon", Rarity::Uncommon); +// case("rare", Rarity::Rare); +// case("epic", Rarity::Epic); +// case("legendary", Rarity::Legendary); +// case("mythic", Rarity::Mythic); +// } + +// #[test] +// fn conversions_with_u8() { +// for expected in Rarity::ALL { +// let n: u8 = expected.into(); +// let actual = Rarity::try_from(n).unwrap(); +// assert_eq!(actual, expected); +// } + +// assert_eq!(Rarity::try_from(6), Err(6)); +// } + +// #[test] +// fn error() { +// assert_eq!("foo".parse::().unwrap_err(), "invalid rarity `foo`"); +// } + +// #[test] +// fn supply() { +// let mut i = 0; + +// let mut supply = HashMap::::new(); + +// for height in 0.. { +// let subsidy = Height(height).subsidy(); + +// if subsidy == 0 { +// break; +// } + +// *supply.entry(Sat(i).rarity()).or_default() += 1; + +// *supply.entry(Rarity::Common).or_default() += subsidy.saturating_sub(1); + +// i += subsidy; +// } + +// for (rarity, supply) in &supply { +// assert_eq!( +// rarity.supply(), +// *supply, +// "invalid supply for rarity {rarity}" +// ); +// } + +// assert_eq!(supply.values().sum::(), Sat::SUPPLY); + +// assert_eq!(supply.len(), Rarity::ALL.len()); +// } +// } diff --git a/components/ord/src/sat.rs b/components/ord/src/sat.rs new file mode 100644 index 0000000..a64993b --- /dev/null +++ b/components/ord/src/sat.rs @@ -0,0 +1,841 @@ +use std::ops::Add; + +use super::{ + charm::Charm, decimal_sat::DecimalSat, degree::Degree, epoch::Epoch, height::Height, + rarity::Rarity, *, +}; + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd, Deserialize, Serialize)] +#[serde(transparent)] +pub struct Sat(pub u64); + +impl Sat { + pub const LAST: Self = Self(Self::SUPPLY - 1); + pub const SUPPLY: u64 = 2099999997690000; + + pub fn n(self) -> u64 { + self.0 + } + + pub fn degree(self) -> Degree { + self.into() + } + + pub fn height(self) -> Height { + self.epoch().starting_height() + + u32::try_from(self.epoch_position() / self.epoch().subsidy()).unwrap() + } + + pub fn cycle(self) -> u32 { + Epoch::from(self).0 / CYCLE_EPOCHS + } + + pub fn nineball(self) -> bool { + self.n() >= 50 * COIN_VALUE * 9 && self.n() < 50 * COIN_VALUE * 10 + } + + pub fn palindrome(self) -> bool { + let mut n = self.0; + let mut reversed = 0; + + while n > 0 { + reversed = reversed * 10 + n % 10; + n /= 10; + } + + self.0 == reversed + } + + pub fn percentile(self) -> String { + format!("{}%", (self.0 as f64 / Self::LAST.0 as f64) * 100.0) + } + + pub fn epoch(self) -> Epoch { + self.into() + } + + pub fn period(self) -> u32 { + self.height().n() / DIFFCHANGE_INTERVAL + } + + pub fn third(self) -> u64 { + self.epoch_position() % self.epoch().subsidy() + } + + pub fn epoch_position(self) -> u64 { + self.0 - self.epoch().starting_sat().0 + } + + pub fn decimal(self) -> DecimalSat { + self.into() + } + + pub fn rarity(self) -> Rarity { + self.into() + } + + /// Is this sat common or not? Much faster than `Sat::rarity()`. + pub fn common(self) -> bool { + // The block rewards for epochs 0 through 9 are all multiples + // of 9765625 (the epoch 9 reward), so any sat from epoch 9 or + // earlier that isn't divisible by 9765625 is definitely common. + if self < Epoch(10).starting_sat() && self.0 % Epoch(9).subsidy() != 0 { + return true; + } + + // Fall back to the full calculation. + let epoch = self.epoch(); + (self.0 - epoch.starting_sat().0) % epoch.subsidy() != 0 + } + + pub fn coin(self) -> bool { + self.n() % COIN_VALUE == 0 + } + + pub fn name(self) -> String { + let mut x = Self::SUPPLY - self.0; + let mut name = String::new(); + while x > 0 { + name.push( + "abcdefghijklmnopqrstuvwxyz" + .chars() + .nth(((x - 1) % 26) as usize) + .unwrap(), + ); + x = (x - 1) / 26; + } + name.chars().rev().collect() + } + + pub fn charms(self) -> u16 { + let mut charms = 0; + + if self.nineball() { + Charm::Nineball.set(&mut charms); + } + + if self.palindrome() { + Charm::Palindrome.set(&mut charms); + } + + if self.coin() { + Charm::Coin.set(&mut charms); + } + + match self.rarity() { + Rarity::Common => {} + Rarity::Epic => Charm::Epic.set(&mut charms), + Rarity::Legendary => Charm::Legendary.set(&mut charms), + Rarity::Mythic => Charm::Mythic.set(&mut charms), + Rarity::Rare => Charm::Rare.set(&mut charms), + Rarity::Uncommon => Charm::Uncommon.set(&mut charms), + } + + charms + } + + // fn from_name(s: &str) -> Result { + // let mut x = 0; + // for c in s.chars() { + // match c { + // 'a'..='z' => { + // x = x * 26 + c as u64 - 'a' as u64 + 1; + // if x > Self::SUPPLY { + // return Err(ErrorKind::NameRange.error(s)); + // } + // } + // _ => return Err(ErrorKind::NameCharacter.error(s)), + // } + // } + // Ok(Sat(Self::SUPPLY - x)) + // } + + // fn from_degree(degree: &str) -> Result { + // let (cycle_number, rest) = degree + // .split_once('°') + // .ok_or_else(|| ErrorKind::MissingDegree.error(degree))?; + + // let cycle_number = cycle_number + // .parse::() + // .map_err(|source| ErrorKind::ParseInt { source }.error(degree))?; + + // let (epoch_offset, rest) = rest + // .split_once('′') + // .ok_or_else(|| ErrorKind::MissingMinute.error(degree))?; + + // let epoch_offset = epoch_offset + // .parse::() + // .map_err(|source| ErrorKind::ParseInt { source }.error(degree))?; + + // if epoch_offset >= SUBSIDY_HALVING_INTERVAL { + // return Err(ErrorKind::EpochOffset.error(degree)); + // } + + // let (period_offset, rest) = rest + // .split_once('″') + // .ok_or_else(|| ErrorKind::MissingSecond.error(degree))?; + + // let period_offset = period_offset + // .parse::() + // .map_err(|source| ErrorKind::ParseInt { source }.error(degree))?; + + // if period_offset >= DIFFCHANGE_INTERVAL { + // return Err(ErrorKind::PeriodOffset.error(degree)); + // } + + // let cycle_start_epoch = cycle_number * CYCLE_EPOCHS; + + // const HALVING_INCREMENT: u32 = SUBSIDY_HALVING_INTERVAL % DIFFCHANGE_INTERVAL; + + // // For valid degrees the relationship between epoch_offset and period_offset + // // will increment by 336 every halving. + // let relationship = period_offset + SUBSIDY_HALVING_INTERVAL * CYCLE_EPOCHS - epoch_offset; + + // if relationship % HALVING_INCREMENT != 0 { + // return Err(ErrorKind::EpochPeriodMismatch.error(degree)); + // } + + // let epochs_since_cycle_start = relationship % DIFFCHANGE_INTERVAL / HALVING_INCREMENT; + + // let epoch = cycle_start_epoch + epochs_since_cycle_start; + + // let height = Height(epoch * SUBSIDY_HALVING_INTERVAL + epoch_offset); + + // let (block_offset, rest) = match rest.split_once('‴') { + // Some((block_offset, rest)) => ( + // block_offset + // .parse::() + // .map_err(|source| ErrorKind::ParseInt { source }.error(degree))?, + // rest, + // ), + // None => (0, rest), + // }; + + // if !rest.is_empty() { + // return Err(ErrorKind::TrailingCharacters.error(degree)); + // } + + // if block_offset >= height.subsidy() { + // return Err(ErrorKind::BlockOffset.error(degree)); + // } + + // Ok(height.starting_sat() + block_offset) + // } + + // fn from_decimal(decimal: &str) -> Result { + // let (height, offset) = decimal + // .split_once('.') + // .ok_or_else(|| ErrorKind::MissingPeriod.error(decimal))?; + + // let height = Height( + // height + // .parse() + // .map_err(|source| ErrorKind::ParseInt { source }.error(decimal))?, + // ); + + // let offset = offset + // .parse::() + // .map_err(|source| ErrorKind::ParseInt { source }.error(decimal))?; + + // if offset >= height.subsidy() { + // return Err(ErrorKind::BlockOffset.error(decimal)); + // } + + // Ok(height.starting_sat() + offset) + // } + + // fn from_percentile(percentile: &str) -> Result { + // if !percentile.ends_with('%') { + // return Err(ErrorKind::Percentile.error(percentile)); + // } + + // let percentile_string = percentile; + + // let percentile = percentile[..percentile.len() - 1] + // .parse::() + // .map_err(|source| ErrorKind::ParseFloat { source }.error(percentile))?; + + // if percentile < 0.0 { + // return Err(ErrorKind::Percentile.error(percentile_string)); + // } + + // let last = Sat::LAST.n() as f64; + + // let n = (percentile / 100.0 * last).round(); + + // if n > last { + // return Err(ErrorKind::Percentile.error(percentile_string)); + // } + + // #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] + // Ok(Sat(n as u64)) + // } +} + +// #[derive(Debug, Error)] +// pub struct Error { +// input: String, +// kind: ErrorKind, +// } + +// impl Display for Error { +// fn fmt(&self, f: &mut Formatter) -> fmt::Result { +// write!(f, "failed to parse sat `{}`: {}", self.input, self.kind) +// } +// } + +// #[derive(Debug, Error)] +// pub enum ErrorKind { +// IntegerRange, +// NameRange, +// NameCharacter, +// Percentile, +// BlockOffset, +// MissingPeriod, +// TrailingCharacters, +// MissingDegree, +// MissingMinute, +// MissingSecond, +// PeriodOffset, +// EpochOffset, +// EpochPeriodMismatch, +// ParseInt { source: ParseIntError }, +// ParseFloat { source: ParseFloatError }, +// } + +// impl ErrorKind { +// fn error(self, input: &str) -> Error { +// Error { +// input: input.to_string(), +// kind: self, +// } +// } +// } + +// impl Display for ErrorKind { +// fn fmt(&self, f: &mut Formatter) -> fmt::Result { +// match self { +// Self::IntegerRange => write!(f, "invalid integer range"), +// Self::NameRange => write!(f, "invalid name range"), +// Self::NameCharacter => write!(f, "invalid character in name"), +// Self::Percentile => write!(f, "invalid percentile"), +// Self::BlockOffset => write!(f, "invalid block offset"), +// Self::MissingPeriod => write!(f, "missing period"), +// Self::TrailingCharacters => write!(f, "trailing character"), +// Self::MissingDegree => write!(f, "missing degree symbol"), +// Self::MissingMinute => write!(f, "missing minute symbol"), +// Self::MissingSecond => write!(f, "missing second symbol"), +// Self::PeriodOffset => write!(f, "invalid period offset"), +// Self::EpochOffset => write!(f, "invalid epoch offset"), +// Self::EpochPeriodMismatch => write!( +// f, +// "relationship between epoch offset and period offset must be multiple of 336" +// ), +// Self::ParseInt { source } => write!(f, "invalid integer: {source}"), +// Self::ParseFloat { source } => write!(f, "invalid float: {source}"), +// } +// } +// } + +// impl PartialEq for Sat { +// fn eq(&self, other: &u64) -> bool { +// self.0 == *other +// } +// } + +// impl PartialOrd for Sat { +// fn partial_cmp(&self, other: &u64) -> Option { +// self.0.partial_cmp(other) +// } +// } + +impl Add for Sat { + type Output = Self; + + fn add(self, other: u64) -> Sat { + Sat(self.0 + other) + } +} + +// impl AddAssign for Sat { +// fn add_assign(&mut self, other: u64) { +// *self = Sat(self.0 + other); +// } +// } + +// impl FromStr for Sat { +// type Err = Error; + +// fn from_str(s: &str) -> Result { +// if s.chars().any(|c| c.is_ascii_lowercase()) { +// Self::from_name(s) +// } else if s.contains('°') { +// Self::from_degree(s) +// } else if s.contains('%') { +// Self::from_percentile(s) +// } else if s.contains('.') { +// Self::from_decimal(s) +// } else { +// let sat = Self( +// s.parse() +// .map_err(|source| ErrorKind::ParseInt { source }.error(s))?, +// ); +// if sat > Self::LAST { +// Err(ErrorKind::IntegerRange.error(s)) +// } else { +// Ok(sat) +// } +// } +// } +// } + +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[test] +// fn n() { +// assert_eq!(Sat(1).n(), 1); +// assert_eq!(Sat(100).n(), 100); +// } + +// #[test] +// fn height() { +// assert_eq!(Sat(0).height(), 0); +// assert_eq!(Sat(1).height(), 0); +// assert_eq!(Sat(Epoch(0).subsidy()).height(), 1); +// assert_eq!(Sat(Epoch(0).subsidy() * 2).height(), 2); +// assert_eq!( +// Epoch(2).starting_sat().height(), +// SUBSIDY_HALVING_INTERVAL * 2 +// ); +// assert_eq!(Sat(50 * COIN_VALUE).height(), 1); +// assert_eq!(Sat(2099999997689999).height(), 6929999); +// assert_eq!(Sat(2099999997689998).height(), 6929998); +// } + +// #[test] +// fn name() { +// assert_eq!(Sat(0).name(), "nvtdijuwxlp"); +// assert_eq!(Sat(1).name(), "nvtdijuwxlo"); +// assert_eq!(Sat(26).name(), "nvtdijuwxkp"); +// assert_eq!(Sat(27).name(), "nvtdijuwxko"); +// assert_eq!(Sat(2099999997689999).name(), "a"); +// assert_eq!(Sat(2099999997689999 - 1).name(), "b"); +// assert_eq!(Sat(2099999997689999 - 25).name(), "z"); +// assert_eq!(Sat(2099999997689999 - 26).name(), "aa"); +// } + +// #[test] +// fn number() { +// assert_eq!(Sat(2099999997689999).n(), 2099999997689999); +// } + +// #[test] +// fn degree() { +// assert_eq!(Sat(0).degree().to_string(), "0°0′0″0‴"); +// assert_eq!(Sat(1).degree().to_string(), "0°0′0″1‴"); +// assert_eq!( +// Sat(50 * COIN_VALUE - 1).degree().to_string(), +// "0°0′0″4999999999‴" +// ); +// assert_eq!(Sat(50 * COIN_VALUE).degree().to_string(), "0°1′1″0‴"); +// assert_eq!(Sat(50 * COIN_VALUE + 1).degree().to_string(), "0°1′1″1‴"); +// assert_eq!( +// Sat(50 * COIN_VALUE * u64::from(DIFFCHANGE_INTERVAL) - 1) +// .degree() +// .to_string(), +// "0°2015′2015″4999999999‴" +// ); +// assert_eq!( +// Sat(50 * COIN_VALUE * u64::from(DIFFCHANGE_INTERVAL)) +// .degree() +// .to_string(), +// "0°2016′0″0‴" +// ); +// assert_eq!( +// Sat(50 * COIN_VALUE * u64::from(DIFFCHANGE_INTERVAL) + 1) +// .degree() +// .to_string(), +// "0°2016′0″1‴" +// ); +// assert_eq!( +// Sat(50 * COIN_VALUE * u64::from(SUBSIDY_HALVING_INTERVAL) - 1) +// .degree() +// .to_string(), +// "0°209999′335″4999999999‴" +// ); +// assert_eq!( +// Sat(50 * COIN_VALUE * u64::from(SUBSIDY_HALVING_INTERVAL)) +// .degree() +// .to_string(), +// "0°0′336″0‴" +// ); +// assert_eq!( +// Sat(50 * COIN_VALUE * u64::from(SUBSIDY_HALVING_INTERVAL) + 1) +// .degree() +// .to_string(), +// "0°0′336″1‴" +// ); +// assert_eq!( +// Sat(2067187500000000 - 1).degree().to_string(), +// "0°209999′2015″156249999‴" +// ); +// assert_eq!(Sat(2067187500000000).degree().to_string(), "1°0′0″0‴"); +// assert_eq!(Sat(2067187500000000 + 1).degree().to_string(), "1°0′0″1‴"); +// } + +// #[test] +// fn invalid_degree_bugfix() { +// // Break glass in case of emergency: +// // for height in 0..(2 * CYCLE_EPOCHS * Epoch::BLOCKS) { +// // // 1054200000000000 +// // let expected = Height(height).starting_sat(); +// // // 0°1680′0″0‴ +// // let degree = expected.degree(); +// // // 2034637500000000 +// // let actual = degree.to_string().parse::().unwrap(); +// // assert_eq!( +// // actual, expected, +// // "Sat at height {height} did not round-trip from degree {degree} successfully" +// // ); +// // } +// assert_eq!(Sat(1054200000000000).degree().to_string(), "0°1680′0″0‴"); +// assert_eq!(parse("0°1680′0″0‴").unwrap(), 1054200000000000); +// assert_eq!( +// Sat(1914226250000000).degree().to_string(), +// "0°122762′794″0‴" +// ); +// assert_eq!(parse("0°122762′794″0‴").unwrap(), 1914226250000000); +// } + +// #[test] +// fn period() { +// assert_eq!(Sat(0).period(), 0); +// assert_eq!(Sat(10080000000000).period(), 1); +// assert_eq!(Sat(2099999997689999).period(), 3437); +// assert_eq!(Sat(10075000000000).period(), 0); +// assert_eq!(Sat(10080000000000 - 1).period(), 0); +// assert_eq!(Sat(10080000000000).period(), 1); +// assert_eq!(Sat(10080000000000 + 1).period(), 1); +// assert_eq!(Sat(10085000000000).period(), 1); +// assert_eq!(Sat(2099999997689999).period(), 3437); +// } + +// #[test] +// fn epoch() { +// assert_eq!(Sat(0).epoch(), 0); +// assert_eq!(Sat(1).epoch(), 0); +// assert_eq!( +// Sat(50 * COIN_VALUE * u64::from(SUBSIDY_HALVING_INTERVAL)).epoch(), +// 1 +// ); +// assert_eq!(Sat(2099999997689999).epoch(), 32); +// } + +// #[test] +// fn epoch_position() { +// assert_eq!(Epoch(0).starting_sat().epoch_position(), 0); +// assert_eq!((Epoch(0).starting_sat() + 100).epoch_position(), 100); +// assert_eq!(Epoch(1).starting_sat().epoch_position(), 0); +// assert_eq!(Epoch(2).starting_sat().epoch_position(), 0); +// } + +// #[test] +// fn subsidy_position() { +// assert_eq!(Sat(0).third(), 0); +// assert_eq!(Sat(1).third(), 1); +// assert_eq!( +// Sat(Height(0).subsidy() - 1).third(), +// Height(0).subsidy() - 1 +// ); +// assert_eq!(Sat(Height(0).subsidy()).third(), 0); +// assert_eq!(Sat(Height(0).subsidy() + 1).third(), 1); +// assert_eq!( +// Sat(Epoch(1).starting_sat().n() + Epoch(1).subsidy()).third(), +// 0 +// ); +// assert_eq!(Sat::LAST.third(), 0); +// } + +// #[test] +// fn supply() { +// let mut mined = 0; + +// for height in 0.. { +// let subsidy = Height(height).subsidy(); + +// if subsidy == 0 { +// break; +// } + +// mined += subsidy; +// } + +// assert_eq!(Sat::SUPPLY, mined); +// } + +// #[test] +// fn last() { +// assert_eq!(Sat::LAST, Sat::SUPPLY - 1); +// } + +// #[test] +// fn eq() { +// assert_eq!(Sat(0), 0); +// assert_eq!(Sat(1), 1); +// } + +// #[test] +// fn partial_ord() { +// assert!(Sat(1) > 0); +// assert!(Sat(0) < 1); +// } + +// #[test] +// fn add() { +// assert_eq!(Sat(0) + 1, 1); +// assert_eq!(Sat(1) + 100, 101); +// } + +// #[test] +// fn add_assign() { +// let mut sat = Sat(0); +// sat += 1; +// assert_eq!(sat, 1); +// sat += 100; +// assert_eq!(sat, 101); +// } + +// fn parse(s: &str) -> Result { +// s.parse::().map_err(|e| e.to_string()) +// } + +// #[test] +// fn from_str_decimal() { +// assert_eq!(parse("0.0").unwrap(), 0); +// assert_eq!(parse("0.1").unwrap(), 1); +// assert_eq!(parse("1.0").unwrap(), 50 * COIN_VALUE); +// assert_eq!(parse("6929999.0").unwrap(), 2099999997689999); +// assert!(parse("0.5000000000").is_err()); +// assert!(parse("6930000.0").is_err()); +// } + +// #[test] +// fn from_str_degree() { +// assert_eq!(parse("0°0′0″0‴").unwrap(), 0); +// assert_eq!(parse("0°0′0″").unwrap(), 0); +// assert_eq!(parse("0°0′0″1‴").unwrap(), 1); +// assert_eq!(parse("0°2015′2015″0‴").unwrap(), 10075000000000); +// assert_eq!(parse("0°2016′0″0‴").unwrap(), 10080000000000); +// assert_eq!(parse("0°2017′1″0‴").unwrap(), 10085000000000); +// assert_eq!(parse("0°2016′0″1‴").unwrap(), 10080000000001); +// assert_eq!(parse("0°2017′1″1‴").unwrap(), 10085000000001); +// assert_eq!(parse("0°209999′335″0‴").unwrap(), 1049995000000000); +// assert_eq!(parse("0°0′336″0‴").unwrap(), 1050000000000000); +// assert_eq!(parse("0°0′672″0‴").unwrap(), 1575000000000000); +// assert_eq!(parse("0°209999′1007″0‴").unwrap(), 1837498750000000); +// assert_eq!(parse("0°0′1008″0‴").unwrap(), 1837500000000000); +// assert_eq!(parse("1°0′0″0‴").unwrap(), 2067187500000000); +// assert_eq!(parse("2°0′0″0‴").unwrap(), 2099487304530000); +// assert_eq!(parse("3°0′0″0‴").unwrap(), 2099991988080000); +// assert_eq!(parse("4°0′0″0‴").unwrap(), 2099999873370000); +// assert_eq!(parse("5°0′0″0‴").unwrap(), 2099999996220000); +// assert_eq!(parse("5°0′336″0‴").unwrap(), 2099999997060000); +// assert_eq!(parse("5°0′672″0‴").unwrap(), 2099999997480000); +// assert_eq!(parse("5°1′673″0‴").unwrap(), 2099999997480001); +// assert_eq!(parse("5°209999′1007″0‴").unwrap(), 2099999997689999); +// } + +// #[test] +// fn from_str_number() { +// assert_eq!(parse("0").unwrap(), 0); +// assert_eq!(parse("2099999997689999").unwrap(), 2099999997689999); +// assert!(parse("2099999997690000").is_err()); +// } + +// #[test] +// fn from_str_degree_invalid_cycle_number() { +// assert!(parse("5°0′0″0‴").is_ok()); +// assert!(parse("6°0′0″0‴").is_err()); +// } + +// #[test] +// fn from_str_degree_invalid_epoch_offset() { +// assert!(parse("0°209999′335″0‴").is_ok()); +// assert!(parse("0°210000′336″0‴").is_err()); +// } + +// #[test] +// fn from_str_degree_invalid_period_offset() { +// assert!(parse("0°2015′2015″0‴").is_ok()); +// assert!(parse("0°2016′2016″0‴").is_err()); +// } + +// #[test] +// fn from_str_degree_invalid_block_offset() { +// assert!(parse("0°0′0″4999999999‴").is_ok()); +// assert!(parse("0°0′0″5000000000‴").is_err()); +// assert!(parse("0°209999′335″4999999999‴").is_ok()); +// assert!(parse("0°0′336″4999999999‴").is_err()); +// } + +// #[test] +// fn from_str_degree_invalid_period_block_relationship() { +// assert!(parse("0°2015′2015″0‴").is_ok()); +// assert!(parse("0°2016′0″0‴").is_ok()); +// assert!(parse("0°2016′1″0‴").is_err()); +// assert!(parse("0°0′336″0‴").is_ok()); +// } + +// #[test] +// fn from_str_degree_post_distribution() { +// assert!(parse("5°209999′1007″0‴").is_ok()); +// assert!(parse("5°0′1008″0‴").is_err()); +// } + +// #[test] +// fn from_str_name() { +// assert_eq!(parse("nvtdijuwxlp").unwrap(), 0); +// assert_eq!(parse("a").unwrap(), 2099999997689999); +// assert!(parse("(").is_err()); +// assert!(parse("").is_err()); +// assert!(parse("nvtdijuwxlq").is_err()); +// assert!(parse("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").is_err()); +// } + +// #[test] +// fn cycle() { +// assert_eq!( +// SUBSIDY_HALVING_INTERVAL * CYCLE_EPOCHS % DIFFCHANGE_INTERVAL, +// 0 +// ); + +// for i in 1..CYCLE_EPOCHS { +// assert_ne!(i * SUBSIDY_HALVING_INTERVAL % DIFFCHANGE_INTERVAL, 0); +// } + +// assert_eq!( +// CYCLE_EPOCHS * SUBSIDY_HALVING_INTERVAL % DIFFCHANGE_INTERVAL, +// 0 +// ); + +// assert_eq!(Sat(0).cycle(), 0); +// assert_eq!(Sat(2067187500000000 - 1).cycle(), 0); +// assert_eq!(Sat(2067187500000000).cycle(), 1); +// assert_eq!(Sat(2067187500000000 + 1).cycle(), 1); +// } + +// #[test] +// fn third() { +// assert_eq!(Sat(0).third(), 0); +// assert_eq!(Sat(50 * COIN_VALUE - 1).third(), 4999999999); +// assert_eq!(Sat(50 * COIN_VALUE).third(), 0); +// assert_eq!(Sat(50 * COIN_VALUE + 1).third(), 1); +// } + +// #[test] +// fn percentile() { +// assert_eq!(Sat(0).percentile(), "0%"); +// assert_eq!(Sat(Sat::LAST.n() / 2).percentile(), "49.99999999999998%"); +// assert_eq!(Sat::LAST.percentile(), "100%"); +// } + +// #[test] +// fn from_percentile() { +// "-1%".parse::().unwrap_err(); +// "101%".parse::().unwrap_err(); +// } + +// #[test] +// fn percentile_round_trip() { +// #[track_caller] +// fn case(n: u64) { +// let expected = Sat(n); +// let actual = expected.percentile().parse::().unwrap(); +// assert_eq!(expected, actual); +// } + +// for n in 0..1024 { +// case(n); +// case(Sat::LAST.n() / 2 + n); +// case(Sat::LAST.n() - n); +// case(Sat::LAST.n() / (n + 1)); +// } +// } + +// #[test] +// fn common() { +// #[track_caller] +// fn case(n: u64) { +// assert_eq!(Sat(n).common(), Sat(n).rarity() == Rarity::Common); +// } + +// case(0); +// case(1); +// case(50 * COIN_VALUE - 1); +// case(50 * COIN_VALUE); +// case(50 * COIN_VALUE + 1); +// case(2067187500000000 - 1); +// case(2067187500000000); +// case(2067187500000000 + 1); +// } + +// #[test] +// fn common_fast_path() { +// // Exhaustively test the Sat::common() fast path on every +// // uncommon sat. +// for height in 0..Epoch::FIRST_POST_SUBSIDY.starting_height().0 { +// let height = Height(height); +// assert!(!Sat::common(height.starting_sat())); +// } +// } + +// #[test] +// fn coin() { +// assert!(Sat(0).coin()); +// assert!(!Sat(COIN_VALUE - 1).coin()); +// assert!(Sat(COIN_VALUE).coin()); +// assert!(!Sat(COIN_VALUE + 1).coin()); +// } + +// #[test] +// fn nineball() { +// for height in 0..10 { +// let sat = Sat(height * 50 * COIN_VALUE); +// assert_eq!( +// sat.nineball(), +// sat.height() == 9, +// "nineball: {} height: {}", +// sat.nineball(), +// sat.height() +// ); +// } +// } + +// #[test] +// fn error_display() { +// assert_eq!( +// Error { +// input: "foo".into(), +// kind: ErrorKind::Percentile +// } +// .to_string(), +// "failed to parse sat `foo`: invalid percentile", +// ); +// } + +// #[test] +// fn palindrome() { +// assert!(Sat(0).palindrome()); +// assert!(!Sat(10).palindrome()); +// assert!(Sat(11).palindrome()); +// } + +// #[test] +// fn palindrome_charm() { +// assert!(Charm::Palindrome.is_set(Sat(0).charms())); +// assert!(!Charm::Palindrome.is_set(Sat(10).charms())); +// assert!(Charm::Palindrome.is_set(Sat(11).charms())); +// } +// } diff --git a/components/ord/src/sat_point.rs b/components/ord/src/sat_point.rs new file mode 100644 index 0000000..ccdb0ff --- /dev/null +++ b/components/ord/src/sat_point.rs @@ -0,0 +1,149 @@ +use bitcoin::OutPoint; + +/// A satpoint identifies the location of a sat in an output. +/// +/// The string representation of a satpoint consists of that of an outpoint, +/// which identifies and output, followed by `:OFFSET`. For example, the string +/// representation of the first sat of the genesis block coinbase output is +/// `000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f:0:0`, +/// that of the second sat of the genesis block coinbase output is +/// `000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f:0:1`, and +/// so on and so on. +#[derive(Debug, PartialEq, Copy, Clone, Eq, PartialOrd, Ord, Default, Hash)] +pub struct SatPoint { + pub outpoint: OutPoint, + pub offset: u64, +} + +// impl Display for SatPoint { +// fn fmt(&self, f: &mut Formatter) -> fmt::Result { +// write!(f, "{}:{}", self.outpoint, self.offset) +// } +// } + +// impl Encodable for SatPoint { +// fn consensus_encode( +// &self, +// s: &mut S, +// ) -> Result { +// let len = self.outpoint.consensus_encode(s)?; +// Ok(len + self.offset.consensus_encode(s)?) +// } +// } + +// impl Decodable for SatPoint { +// fn consensus_decode( +// d: &mut D, +// ) -> Result { +// Ok(SatPoint { +// outpoint: Decodable::consensus_decode(d)?, +// offset: Decodable::consensus_decode(d)?, +// }) +// } +// } + +// impl FromStr for SatPoint { +// type Err = Error; + +// fn from_str(s: &str) -> Result { +// let (outpoint, offset) = s.rsplit_once(':').ok_or_else(|| Error::Colon(s.into()))?; + +// Ok(SatPoint { +// outpoint: outpoint +// .parse::() +// .map_err(|err| Error::Outpoint { +// outpoint: outpoint.into(), +// err, +// })?, +// offset: offset.parse::().map_err(|err| Error::Offset { +// offset: offset.into(), +// err, +// })?, +// }) +// } +// } + +// #[derive(Debug, Error)] +// pub enum Error { +// #[error("satpoint `{0}` missing colon")] +// Colon(String), +// #[error("satpoint offset `{offset}` invalid: {err}")] +// Offset { offset: String, err: ParseIntError }, +// #[error("satpoint outpoint `{outpoint}` invalid: {err}")] +// Outpoint { +// outpoint: String, +// err: ParseOutPointError, +// }, +// } + +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[test] +// fn error() { +// assert_eq!( +// "foo".parse::().unwrap_err().to_string(), +// "satpoint `foo` missing colon" +// ); + +// assert_eq!( +// "foo:bar".parse::().unwrap_err().to_string(), +// "satpoint outpoint `foo` invalid: OutPoint not in : format" +// ); + +// assert_eq!( +// "1111111111111111111111111111111111111111111111111111111111111111:1:bar" +// .parse::() +// .unwrap_err() +// .to_string(), +// "satpoint offset `bar` invalid: invalid digit found in string" +// ); +// } + +// #[test] +// fn from_str_ok() { +// assert_eq!( +// "1111111111111111111111111111111111111111111111111111111111111111:1:1" +// .parse::() +// .unwrap(), +// SatPoint { +// outpoint: "1111111111111111111111111111111111111111111111111111111111111111:1" +// .parse() +// .unwrap(), +// offset: 1, +// } +// ); +// } + +// #[test] +// fn from_str_err() { +// "abc".parse::().unwrap_err(); + +// "abc:xyz".parse::().unwrap_err(); + +// "1111111111111111111111111111111111111111111111111111111111111111:1" +// .parse::() +// .unwrap_err(); + +// "1111111111111111111111111111111111111111111111111111111111111111:1:foo" +// .parse::() +// .unwrap_err(); +// } + +// #[test] +// fn deserialize_ok() { +// assert_eq!( +// serde_json::from_str::( +// "\"1111111111111111111111111111111111111111111111111111111111111111:1:1\"" +// ) +// .unwrap(), +// SatPoint { +// outpoint: "1111111111111111111111111111111111111111111111111111111111111111:1" +// .parse() +// .unwrap(), +// offset: 1, +// } +// ); +// } +// } diff --git a/components/ord/src/tag.rs b/components/ord/src/tag.rs new file mode 100644 index 0000000..1a8e850 --- /dev/null +++ b/components/ord/src/tag.rs @@ -0,0 +1,102 @@ +use std::{collections::BTreeMap, mem}; + +use bitcoin::{constants::MAX_SCRIPT_ELEMENT_SIZE, script}; + +#[derive(Copy, Clone)] +#[repr(u8)] +pub(crate) enum Tag { + Pointer = 2, + #[allow(unused)] + Unbound = 66, + + ContentType = 1, + Parent = 3, + Metadata = 5, + Metaprotocol = 7, + ContentEncoding = 9, + Delegate = 11, + Rune = 13, + #[allow(unused)] + Note = 15, + #[allow(unused)] + Nop = 255, +} + +impl Tag { + fn chunked(self) -> bool { + matches!(self, Self::Metadata) + } + + pub(crate) fn bytes(self) -> [u8; 1] { + [self as u8] + } + + pub(crate) fn append(self, builder: &mut script::Builder, value: &Option>) { + if let Some(value) = value { + let mut tmp = script::Builder::new(); + mem::swap(&mut tmp, builder); + + if self.chunked() { + for chunk in value.chunks(MAX_SCRIPT_ELEMENT_SIZE) { + tmp = tmp + .push_slice::<&script::PushBytes>(self.bytes().as_slice().try_into().unwrap()) + .push_slice::<&script::PushBytes>(chunk.try_into().unwrap()); + } + } else { + tmp = tmp + .push_slice::<&script::PushBytes>(self.bytes().as_slice().try_into().unwrap()) + .push_slice::<&script::PushBytes>(value.as_slice().try_into().unwrap()); + } + + mem::swap(&mut tmp, builder); + } + } + + pub(crate) fn append_array(self, builder: &mut script::Builder, values: &Vec>) { + let mut tmp = script::Builder::new(); + mem::swap(&mut tmp, builder); + + for value in values { + tmp = tmp + .push_slice::<&script::PushBytes>(self.bytes().as_slice().try_into().unwrap()) + .push_slice::<&script::PushBytes>(value.as_slice().try_into().unwrap()); + } + + mem::swap(&mut tmp, builder); + } + + pub(crate) fn take(self, fields: &mut BTreeMap<&[u8], Vec<&[u8]>>) -> Option> { + if self.chunked() { + let value = fields.remove(self.bytes().as_slice())?; + + if value.is_empty() { + None + } else { + Some(value.into_iter().flatten().cloned().collect()) + } + } else { + let values = fields.get_mut(self.bytes().as_slice())?; + + if values.is_empty() { + None + } else { + let value = values.remove(0).to_vec(); + + if values.is_empty() { + fields.remove(self.bytes().as_slice()); + } + + Some(value) + } + } + } + + pub(crate) fn take_array(self, fields: &mut BTreeMap<&[u8], Vec<&[u8]>>) -> Vec> { + fields + .remove(self.bytes().as_slice()) + .unwrap_or_default() + .into_iter() + .map(|v| v.to_vec()) + .collect() + } +} diff --git a/components/ordhook-cli/Cargo.toml b/components/ordhook-cli/Cargo.toml index c903108..33eb896 100644 --- a/components/ordhook-cli/Cargo.toml +++ b/components/ordhook-cli/Cargo.toml @@ -10,6 +10,9 @@ path = "src/main.rs" [dependencies] ordhook = { path = "../ordhook-core" } +chainhook-types = { path = "../chainhook-types-rs" } +chainhook-sdk = { path = "../chainhook-sdk" } +hex = "0.4.3" num_cpus = "1.16.0" serde = "1" serde_json = "1" diff --git a/components/ordhook-cli/src/cli/mod.rs b/components/ordhook-cli/src/cli/mod.rs index c557a23..6c92134 100644 --- a/components/ordhook-cli/src/cli/mod.rs +++ b/components/ordhook-cli/src/cli/mod.rs @@ -1,16 +1,8 @@ use crate::config::file::ConfigFile; use crate::config::generator::generate_config; +use chainhook_sdk::utils::{BlockHeights, Context}; use clap::{Parser, Subcommand}; use hiro_system_kit; -use ordhook::chainhook_sdk::chainhooks::types::{ - BitcoinChainhookSpecification, HttpHook, InscriptionFeedData, OrdinalsMetaProtocol, -}; -use ordhook::chainhook_sdk::chainhooks::types::{ - BitcoinPredicateType, HookAction, OrdinalOperations, -}; -use ordhook::chainhook_sdk::utils::BlockHeights; -use ordhook::chainhook_sdk::utils::Context; -use ordhook::config::Config; use ordhook::core::first_inscription_height; use ordhook::core::pipeline::bitcoind_download_blocks; use ordhook::core::pipeline::processors::block_archiving::start_block_archiving_processor; @@ -22,7 +14,6 @@ use ordhook::db::cursor::BlockBytesCursor; use ordhook::db::{migrate_dbs, reset_dbs}; use ordhook::service::Service; use ordhook::try_info; -use std::collections::HashSet; use std::path::PathBuf; use std::thread::sleep; use std::time::Duration; @@ -383,7 +374,7 @@ async fn handle_command(opts: Opts, ctx: &Context) -> Result<(), String> { info!(ctx.expect_logger(), "--------------------"); info!(ctx.expect_logger(), "Block: {i}"); for tx in block.iter_tx() { - info!(ctx.expect_logger(), "Tx: {}", ordhook::hex::encode(tx.txid)); + info!(ctx.expect_logger(), "Tx: {}", hex::encode(tx.txid)); } } } @@ -440,52 +431,3 @@ async fn handle_command(opts: Opts, ctx: &Context) -> Result<(), String> { } Ok(()) } - -pub fn build_predicate_from_cli( - config: &Config, - post_to: &str, - block_heights: Option<&BlockHeights>, - start_block: Option, - auth_token: Option, - is_streaming: bool, -) -> Result { - // Retrieve last block height known, and display it - let (start_block, end_block, blocks) = match (start_block, block_heights) { - (None, Some(BlockHeights::BlockRange(start, end))) => (Some(*start), Some(*end), None), - (None, Some(BlockHeights::Blocks(blocks))) => (None, None, Some(blocks.clone())), - (Some(start), None) => (Some(start), None, None), - _ => unreachable!(), - }; - let mut meta_protocols: Option> = None; - if config.meta_protocols.brc20 { - let mut meta = HashSet::::new(); - meta.insert(OrdinalsMetaProtocol::All); - meta_protocols = Some(meta.clone()); - } - let predicate = BitcoinChainhookSpecification { - network: config.network.bitcoin_network.clone(), - uuid: post_to.to_string(), - owner_uuid: None, - name: post_to.to_string(), - version: 1, - start_block, - end_block, - blocks, - expire_after_occurrence: None, - include_proof: false, - include_inputs: false, - include_outputs: false, - include_witness: false, - expired_at: None, - enabled: is_streaming, - predicate: BitcoinPredicateType::OrdinalsProtocol(OrdinalOperations::InscriptionFeed( - InscriptionFeedData { meta_protocols }, - )), - action: HookAction::HttpPost(HttpHook { - url: post_to.to_string(), - authorization_header: format!("Bearer {}", auth_token.unwrap_or("".to_string())), - }), - }; - - Ok(predicate) -} diff --git a/components/ordhook-cli/src/config/file.rs b/components/ordhook-cli/src/config/file.rs index 2bf2571..fbcbae5 100644 --- a/components/ordhook-cli/src/config/file.rs +++ b/components/ordhook-cli/src/config/file.rs @@ -1,7 +1,4 @@ -use ordhook::chainhook_sdk::observer::DEFAULT_INGESTION_PORT; -use ordhook::chainhook_sdk::types::{ - BitcoinBlockSignaling, BitcoinNetwork, StacksNetwork, StacksNodeConfig, -}; +use chainhook_types::{BitcoinBlockSignaling, BitcoinNetwork}; use ordhook::config::{ Config, IndexerConfig, LogConfig, MetaProtocolsConfig, ResourcesConfig, SnapshotConfig, SnapshotConfigDownloadUrls, StorageConfig, DEFAULT_BITCOIND_RPC_THREADS, @@ -44,11 +41,11 @@ impl ConfigFile { } pub fn from_config_file(config_file: ConfigFile) -> Result { - let (_, bitcoin_network) = match config_file.network.mode.as_str() { - "devnet" => (StacksNetwork::Devnet, BitcoinNetwork::Regtest), - "testnet" => (StacksNetwork::Testnet, BitcoinNetwork::Testnet), - "mainnet" => (StacksNetwork::Mainnet, BitcoinNetwork::Mainnet), - "signet" => (StacksNetwork::Testnet, BitcoinNetwork::Signet), + let bitcoin_network = match config_file.network.mode.as_str() { + "devnet" => BitcoinNetwork::Regtest, + "testnet" => BitcoinNetwork::Testnet, + "mainnet" => BitcoinNetwork::Mainnet, + "signet" => BitcoinNetwork::Signet, _ => return Err("network.mode not supported".to_string()), }; @@ -126,9 +123,7 @@ impl ConfigFile { bitcoind_rpc_password: config_file.network.bitcoind_rpc_password.to_string(), bitcoin_block_signaling: match config_file.network.bitcoind_zmq_url { Some(ref zmq_url) => BitcoinBlockSignaling::ZeroMQ(zmq_url.clone()), - None => BitcoinBlockSignaling::Stacks(StacksNodeConfig::default_localhost( - DEFAULT_INGESTION_PORT, - )), + None => BitcoinBlockSignaling::ZeroMQ("".to_string()), }, bitcoin_network, prometheus_monitoring_port: config_file.network.prometheus_monitoring_port, diff --git a/components/ordhook-cli/src/config/generator.rs b/components/ordhook-cli/src/config/generator.rs index f0be9d3..a8dcfa1 100644 --- a/components/ordhook-cli/src/config/generator.rs +++ b/components/ordhook-cli/src/config/generator.rs @@ -1,4 +1,4 @@ -use ordhook::chainhook_sdk::types::BitcoinNetwork; +use chainhook_types::BitcoinNetwork; pub fn generate_config(network: &BitcoinNetwork) -> String { let network = format!("{:?}", network); diff --git a/components/ordhook-core/Cargo.toml b/components/ordhook-core/Cargo.toml index d737132..30e4b2e 100644 --- a/components/ordhook-core/Cargo.toml +++ b/components/ordhook-core/Cargo.toml @@ -9,10 +9,11 @@ serde = "1" serde_json = "1" serde_derive = "1" hex = "0.4.3" -rand = "0.8.5" -lru = "0.12.3" -chainhook-sdk = { version = "=0.12.10", features = ["zeromq"] } -# chainhook-sdk = { version = "=0.12.10", path = "../../../chainhook/components/chainhook-sdk", features = ["zeromq"] } +rand = "0.9.0" +lru = "0.13.0" +bitcoin = { workspace = true } +chainhook-sdk = { path = "../chainhook-sdk" } +chainhook-types = { path = "../chainhook-types-rs" } hiro-system-kit = "0.3.1" reqwest = { version = "0.11", default-features = false, features = [ "stream", @@ -27,12 +28,10 @@ flume = "0.11.0" ansi_term = "0.12.1" atty = "0.2.14" crossbeam-channel = "0.5.8" -uuid = { version = "1.3.0", features = ["v4", "fast-rng"] } threadpool = "1.8.1" dashmap = "5.4.0" fxhash = "0.2.1" anyhow = { version = "1.0.56", features = ["backtrace"] } -schemars = { version = "0.8.16", git = "https://github.com/hirosystems/schemars.git", branch = "feat-chainhook-fixes" } progressing = '3' futures = "0.3.28" rocksdb = { version = "0.21.0", default-features = false, features = [ @@ -41,12 +40,14 @@ rocksdb = { version = "0.21.0", default-features = false, features = [ pprof = { version = "0.14.0", features = ["flamegraph"], optional = true } hyper = { version = "=0.14.27" } lazy_static = { version = "1.4.0" } -ciborium = "0.2.1" regex = "1.10.3" prometheus = "0.13.3" chainhook-postgres = { path = "../chainhook-postgres" } -refinery = { version = "0.8", features = ["tokio-postgres"] } +tokio-postgres = { workspace = true } +deadpool-postgres = { workspace = true } +refinery = { workspace = true } maplit = "1.0.2" +ord = { path = "../ord" } [dev-dependencies] test-case = "3.1.0" diff --git a/components/ordhook-core/src/config/mod.rs b/components/ordhook-core/src/config/mod.rs index 007f498..06fc688 100644 --- a/components/ordhook-core/src/config/mod.rs +++ b/components/ordhook-core/src/config/mod.rs @@ -1,8 +1,6 @@ pub use chainhook_postgres::PgConnectionConfig; use chainhook_sdk::observer::EventObserverConfig; -use chainhook_sdk::types::{ - BitcoinBlockSignaling, BitcoinNetwork, StacksNetwork, StacksNodeConfig, -}; +use chainhook_types::{BitcoinBlockSignaling, BitcoinNetwork}; use std::path::PathBuf; const DEFAULT_MAINNET_ORDINALS_SQLITE_ARCHIVE: &str = @@ -101,19 +99,11 @@ impl ResourcesConfig { impl Config { pub fn get_event_observer_config(&self) -> EventObserverConfig { EventObserverConfig { - bitcoin_rpc_proxy_enabled: true, - chainhook_config: None, - ingestion_port: DEFAULT_INGESTION_PORT, bitcoind_rpc_username: self.network.bitcoind_rpc_username.clone(), bitcoind_rpc_password: self.network.bitcoind_rpc_password.clone(), bitcoind_rpc_url: self.network.bitcoind_rpc_url.clone(), bitcoin_block_signaling: self.network.bitcoin_block_signaling.clone(), - display_logs: false, - cache_path: self.storage.working_dir.clone(), bitcoin_network: self.network.bitcoin_network.clone(), - stacks_network: StacksNetwork::Devnet, - prometheus_monitoring_port: None, - data_handler_tx: None, } } @@ -166,8 +156,8 @@ impl Config { bitcoind_rpc_url: "http://0.0.0.0:18443".into(), bitcoind_rpc_username: "devnet".into(), bitcoind_rpc_password: "devnet".into(), - bitcoin_block_signaling: BitcoinBlockSignaling::Stacks( - StacksNodeConfig::default_localhost(DEFAULT_INGESTION_PORT), + bitcoin_block_signaling: BitcoinBlockSignaling::ZeroMQ( + "http://0.0.0.0:18543".into(), ), bitcoin_network: BitcoinNetwork::Regtest, prometheus_monitoring_port: None, @@ -210,8 +200,8 @@ impl Config { bitcoind_rpc_url: "http://0.0.0.0:18332".into(), bitcoind_rpc_username: "devnet".into(), bitcoind_rpc_password: "devnet".into(), - bitcoin_block_signaling: BitcoinBlockSignaling::Stacks( - StacksNodeConfig::default_localhost(DEFAULT_INGESTION_PORT), + bitcoin_block_signaling: BitcoinBlockSignaling::ZeroMQ( + "http://0.0.0.0:18543".into(), ), bitcoin_network: BitcoinNetwork::Testnet, prometheus_monitoring_port: Some(9153), @@ -257,8 +247,8 @@ impl Config { bitcoind_rpc_url: "http://0.0.0.0:8332".into(), bitcoind_rpc_username: "devnet".into(), bitcoind_rpc_password: "devnet".into(), - bitcoin_block_signaling: BitcoinBlockSignaling::Stacks( - StacksNodeConfig::default_localhost(DEFAULT_INGESTION_PORT), + bitcoin_block_signaling: BitcoinBlockSignaling::ZeroMQ( + "http://0.0.0.0:18543".into(), ), bitcoin_network: BitcoinNetwork::Mainnet, prometheus_monitoring_port: Some(9153), diff --git a/components/ordhook-core/src/core/meta_protocols/brc20/brc20_pg.rs b/components/ordhook-core/src/core/meta_protocols/brc20/brc20_pg.rs index b5e582f..1c21f75 100644 --- a/components/ordhook-core/src/core/meta_protocols/brc20/brc20_pg.rs +++ b/components/ordhook-core/src/core/meta_protocols/brc20/brc20_pg.rs @@ -1,15 +1,15 @@ use std::collections::HashMap; use chainhook_postgres::{ - deadpool_postgres::GenericClient, - tokio_postgres::{types::ToSql, Client}, types::{PgNumericU128, PgNumericU64}, utils, FromPgRow, BATCH_QUERY_CHUNK_SIZE, }; -use chainhook_sdk::types::{ +use chainhook_types::{ BitcoinBlockData, Brc20BalanceData, Brc20Operation, Brc20TokenDeployData, Brc20TransferData, }; use refinery::embed_migrations; +use deadpool_postgres::GenericClient; +use tokio_postgres::{types::ToSql, Client}; use super::{ models::{DbOperation, DbToken}, @@ -574,12 +574,12 @@ pub async fn rollback_block_operations( #[cfg(test)] mod test { + use deadpool_postgres::GenericClient; use chainhook_postgres::{ - deadpool_postgres::GenericClient, pg_begin, pg_pool_client, types::{PgBigIntU32, PgNumericU128, PgNumericU64, PgSmallIntU8}, }; - use chainhook_sdk::types::{ + use chainhook_types::{ BlockIdentifier, OrdinalInscriptionTransferDestination, TransactionIdentifier, }; diff --git a/components/ordhook-core/src/core/meta_protocols/brc20/cache.rs b/components/ordhook-core/src/core/meta_protocols/brc20/cache.rs index c71c6d6..fae0de9 100644 --- a/components/ordhook-core/src/core/meta_protocols/brc20/cache.rs +++ b/components/ordhook-core/src/core/meta_protocols/brc20/cache.rs @@ -3,14 +3,12 @@ use std::{ num::NonZeroUsize, }; -use chainhook_postgres::{ - deadpool_postgres::GenericClient, - types::{PgBigIntU32, PgNumericU128, PgNumericU64, PgSmallIntU8}, -}; -use chainhook_sdk::types::{ +use chainhook_postgres::types::{PgBigIntU32, PgNumericU128, PgNumericU64, PgSmallIntU8}; +use chainhook_types::{ BlockIdentifier, OrdinalInscriptionRevealData, OrdinalInscriptionTransferData, TransactionIdentifier, }; +use deadpool_postgres::GenericClient; use lru::LruCache; use maplit::hashmap; @@ -491,7 +489,7 @@ impl Brc20MemoryCache { #[cfg(test)] mod test { use chainhook_postgres::{pg_begin, pg_pool_client}; - use chainhook_sdk::types::{BitcoinNetwork, BlockIdentifier, TransactionIdentifier}; + use chainhook_types::{BitcoinNetwork, BlockIdentifier, TransactionIdentifier}; use test_case::test_case; use crate::{ diff --git a/components/ordhook-core/src/core/meta_protocols/brc20/index.rs b/components/ordhook-core/src/core/meta_protocols/brc20/index.rs index 2b786e1..23e47f8 100644 --- a/components/ordhook-core/src/core/meta_protocols/brc20/index.rs +++ b/components/ordhook-core/src/core/meta_protocols/brc20/index.rs @@ -1,13 +1,11 @@ use std::collections::HashMap; -use chainhook_postgres::deadpool_postgres::Transaction; -use chainhook_sdk::{ - types::{ - BitcoinBlockData, BlockIdentifier, Brc20BalanceData, Brc20Operation, Brc20TokenDeployData, - Brc20TransferData, OrdinalInscriptionTransferData, OrdinalOperation, TransactionIdentifier, - }, - utils::Context, +use chainhook_sdk::utils::Context; +use chainhook_types::{ + BitcoinBlockData, BlockIdentifier, Brc20BalanceData, Brc20Operation, Brc20TokenDeployData, + Brc20TransferData, OrdinalInscriptionTransferData, OrdinalOperation, TransactionIdentifier, }; +use deadpool_postgres::Transaction; use crate::{core::meta_protocols::brc20::u128_amount_to_decimals_str, try_info}; @@ -266,7 +264,7 @@ mod test { use std::collections::HashMap; use chainhook_postgres::{pg_begin, pg_pool_client}; - use chainhook_sdk::types::{ + use chainhook_types::{ Brc20BalanceData, Brc20Operation, Brc20TokenDeployData, Brc20TransferData, OrdinalInscriptionTransferDestination, OrdinalOperation, }; diff --git a/components/ordhook-core/src/core/meta_protocols/brc20/mod.rs b/components/ordhook-core/src/core/meta_protocols/brc20/mod.rs index 724fda4..19d4240 100644 --- a/components/ordhook-core/src/core/meta_protocols/brc20/mod.rs +++ b/components/ordhook-core/src/core/meta_protocols/brc20/mod.rs @@ -1,4 +1,4 @@ -use chainhook_sdk::types::BitcoinNetwork; +use chainhook_types::BitcoinNetwork; pub mod brc20_pg; pub mod cache; diff --git a/components/ordhook-core/src/core/meta_protocols/brc20/models/db_operation.rs b/components/ordhook-core/src/core/meta_protocols/brc20/models/db_operation.rs index c2e7f82..7d5727d 100644 --- a/components/ordhook-core/src/core/meta_protocols/brc20/models/db_operation.rs +++ b/components/ordhook-core/src/core/meta_protocols/brc20/models/db_operation.rs @@ -1,8 +1,8 @@ use chainhook_postgres::{ - tokio_postgres::Row, types::{PgBigIntU32, PgNumericU128, PgNumericU64}, FromPgRow, }; +use tokio_postgres::Row; #[derive(Debug, Clone)] pub struct DbOperation { diff --git a/components/ordhook-core/src/core/meta_protocols/brc20/models/db_token.rs b/components/ordhook-core/src/core/meta_protocols/brc20/models/db_token.rs index 97f1e96..0fb2d7d 100644 --- a/components/ordhook-core/src/core/meta_protocols/brc20/models/db_token.rs +++ b/components/ordhook-core/src/core/meta_protocols/brc20/models/db_token.rs @@ -1,8 +1,8 @@ use chainhook_postgres::{ - tokio_postgres::Row, types::{PgBigIntU32, PgNumericU128, PgNumericU64, PgSmallIntU8}, FromPgRow, }; +use tokio_postgres::Row; #[derive(Debug, Clone, PartialEq, Eq)] pub struct DbToken { diff --git a/components/ordhook-core/src/core/meta_protocols/brc20/parser.rs b/components/ordhook-core/src/core/meta_protocols/brc20/parser.rs index 9d68966..b4bddc2 100644 --- a/components/ordhook-core/src/core/meta_protocols/brc20/parser.rs +++ b/components/ordhook-core/src/core/meta_protocols/brc20/parser.rs @@ -1,5 +1,5 @@ -use crate::ord::inscription::Inscription; -use crate::ord::media::{Language, Media}; +use ord::inscription::Inscription; +use ord::media::{Language, Media}; #[derive(PartialEq, Debug, Clone)] pub struct ParsedBrc20TokenDeployData { @@ -59,7 +59,8 @@ pub fn amt_has_valid_decimals(amt: &str, max_decimals: u8) -> bool { } fn parse_float_numeric_value(n: &str, max_decimals: u8) -> Option { - if n.chars().all(|c| c.is_ascii_digit() || c == '.') && !n.starts_with('.') && !n.ends_with('.') { + if n.chars().all(|c| c.is_ascii_digit() || c == '.') && !n.starts_with('.') && !n.ends_with('.') + { if !amt_has_valid_decimals(n, max_decimals) { return None; } @@ -147,14 +148,16 @@ pub fn parse_brc20_operation( } else { limit = max.clone(); } - return Ok(Some(ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { - tick: json.tick.to_lowercase(), - display_tick: json.tick.clone(), - max, - lim: limit, - dec: decimals.to_string(), - self_mint, - }))); + return Ok(Some(ParsedBrc20Operation::Deploy( + ParsedBrc20TokenDeployData { + tick: json.tick.to_lowercase(), + display_tick: json.tick.clone(), + max, + lim: limit, + dec: decimals.to_string(), + self_mint, + }, + ))); } Err(_) => match serde_json::from_slice::(inscription_body) { Ok(json) => { @@ -201,10 +204,10 @@ pub fn parse_brc20_operation( #[cfg(test)] mod test { use super::{parse_brc20_operation, ParsedBrc20Operation}; - use crate::{ - core::meta_protocols::brc20::parser::{ParsedBrc20BalanceData, ParsedBrc20TokenDeployData}, - ord::inscription::Inscription, + use crate::core::meta_protocols::brc20::parser::{ + ParsedBrc20BalanceData, ParsedBrc20TokenDeployData, }; + use ord::inscription::Inscription; use test_case::test_case; struct InscriptionBuilder { @@ -241,7 +244,8 @@ mod test { incomplete_field: false, metadata: None, metaprotocol: None, - parent: None, + parents: vec![], + rune: None, pointer: None, unrecognized_even_field: false, delegate: None, diff --git a/components/ordhook-core/src/core/meta_protocols/brc20/test_utils.rs b/components/ordhook-core/src/core/meta_protocols/brc20/test_utils.rs index 0f52072..b3daaa4 100644 --- a/components/ordhook-core/src/core/meta_protocols/brc20/test_utils.rs +++ b/components/ordhook-core/src/core/meta_protocols/brc20/test_utils.rs @@ -1,9 +1,7 @@ -use chainhook_sdk::{ - types::{ - OrdinalInscriptionNumber, OrdinalInscriptionRevealData, OrdinalInscriptionTransferData, - OrdinalInscriptionTransferDestination, - }, - utils::Context, +use chainhook_sdk::utils::Context; +use chainhook_types::{ + OrdinalInscriptionCharms, OrdinalInscriptionNumber, OrdinalInscriptionRevealData, + OrdinalInscriptionTransferData, OrdinalInscriptionTransferDestination, }; pub fn get_test_ctx() -> Context { @@ -20,7 +18,7 @@ pub struct Brc20RevealBuilder { pub inscriber_address: Option, pub inscription_id: String, pub ordinal_number: u64, - pub parent: Option, + pub parents: Vec, } impl Brc20RevealBuilder { @@ -34,7 +32,7 @@ impl Brc20RevealBuilder { inscription_id: "9bb2314d666ae0b1db8161cb373fcc1381681f71445c4e0335aa80ea9c37fcddi0" .to_string(), ordinal_number: 0, - parent: None, + parents: vec![], } } @@ -61,8 +59,8 @@ impl Brc20RevealBuilder { self } - pub fn parent(mut self, val: Option) -> Self { - self.parent = val; + pub fn parents(mut self, val: Vec) -> Self { + self.parents = val; self } @@ -81,7 +79,7 @@ impl Brc20RevealBuilder { delegate: None, metaprotocol: None, metadata: None, - parent: self.parent, + parents: self.parents, ordinal_number: self.ordinal_number, ordinal_block_height: 767430, ordinal_offset: 0, @@ -90,6 +88,7 @@ impl Brc20RevealBuilder { satpoint_post_inscription: "9bb2314d666ae0b1db8161cb373fcc1381681f71445c4e0335aa80ea9c37fcdd:0:0".to_string(), curse_type: None, + charms: OrdinalInscriptionCharms::none(), } } } @@ -108,7 +107,8 @@ impl Brc20TransferBuilder { destination: OrdinalInscriptionTransferDestination::Transferred( "bc1pls75sfwullhygkmqap344f5cqf97qz95lvle6fvddm0tpz2l5ffslgq3m0".to_string(), ), - satpoint_post_transfer: "e45957c419f130cd5c88cdac3eb1caf2d118aee20c17b15b74a611be395a065d:0:0".to_string(), + satpoint_post_transfer: + "e45957c419f130cd5c88cdac3eb1caf2d118aee20c17b15b74a611be395a065d:0:0".to_string(), tx_index: 0, } } diff --git a/components/ordhook-core/src/core/meta_protocols/brc20/verifier.rs b/components/ordhook-core/src/core/meta_protocols/brc20/verifier.rs index 333e71c..a8bc722 100644 --- a/components/ordhook-core/src/core/meta_protocols/brc20/verifier.rs +++ b/components/ordhook-core/src/core/meta_protocols/brc20/verifier.rs @@ -1,11 +1,11 @@ use std::collections::HashMap; -use chainhook_postgres::deadpool_postgres::Transaction; -use chainhook_sdk::types::{ +use chainhook_types::{ BitcoinNetwork, BlockIdentifier, OrdinalInscriptionRevealData, OrdinalInscriptionTransferData, OrdinalInscriptionTransferDestination, TransactionIdentifier, }; use chainhook_sdk::utils::Context; +use deadpool_postgres::Transaction; use crate::try_debug; @@ -106,7 +106,7 @@ pub async fn verify_brc20_operation( return Ok(None); }; if data.tick.len() == 5 { - let Some(parent) = &reveal.parent else { + if reveal.parents.len() == 0 { try_debug!( ctx, "BRC-20: Attempting to mint self-minted token {} without a parent ref", @@ -114,7 +114,7 @@ pub async fn verify_brc20_operation( ); return Ok(None); }; - if parent != &token.inscription_id { + if !reveal.parents.contains(&token.inscription_id) { try_debug!( ctx, "BRC-20: Mint attempt for self-minted token {} does not point to deploy as parent", @@ -297,7 +297,7 @@ pub async fn verify_brc20_transfers( #[cfg(test)] mod test { use chainhook_postgres::{pg_begin, pg_pool_client}; - use chainhook_sdk::types::{ + use chainhook_types::{ BitcoinNetwork, BlockIdentifier, OrdinalInscriptionRevealData, OrdinalInscriptionTransferData, OrdinalInscriptionTransferDestination, TransactionIdentifier, @@ -587,7 +587,7 @@ mod test { tick: "$pepe".to_string(), amt: "100.00".to_string(), }), - Brc20RevealBuilder::new().inscription_number(1).parent(Some("test".to_string())).build() + Brc20RevealBuilder::new().inscription_number(1).parents(vec!["test".to_string()]).build() => Ok(None); "with mint with wrong parent pointer" )] @@ -598,7 +598,7 @@ mod test { }), Brc20RevealBuilder::new() .inscription_number(1) - .parent(Some("9bb2314d666ae0b1db8161cb373fcc1381681f71445c4e0335aa80ea9c37fcddi0".to_string())) + .parents(vec!["9bb2314d666ae0b1db8161cb373fcc1381681f71445c4e0335aa80ea9c37fcddi0".to_string()]) .build() => Ok(Some(VerifiedBrc20Operation::TokenMint(VerifiedBrc20BalanceData { tick: "$pepe".to_string(), diff --git a/components/ordhook-core/src/core/mod.rs b/components/ordhook-core/src/core/mod.rs index a56cae3..3e501bc 100644 --- a/components/ordhook-core/src/core/mod.rs +++ b/components/ordhook-core/src/core/mod.rs @@ -10,7 +10,8 @@ use fxhash::{FxBuildHasher, FxHasher}; use std::hash::BuildHasherDefault; use std::ops::Div; -use chainhook_sdk::{types::BitcoinNetwork, utils::Context}; +use chainhook_sdk::utils::Context; +use chainhook_types::BitcoinNetwork; use crate::{ config::Config, @@ -125,8 +126,7 @@ pub async fn should_sync_rocks_db( let blocks_db = open_blocks_db_with_retry(true, &config, &ctx); let last_compressed_block = find_last_block_inserted(&blocks_db) as u64; let ord_client = pg_pool_client(&pg_pools.ordinals).await?; - let last_indexed_block = match ordinals_pg::get_chain_tip_block_height(&ord_client).await? - { + let last_indexed_block = match ordinals_pg::get_chain_tip_block_height(&ord_client).await? { Some(last_indexed_block) => last_indexed_block, None => 0, }; @@ -148,8 +148,7 @@ pub async fn should_sync_ordinals_db( let mut start_block = find_last_block_inserted(&blocks_db) as u64; let ord_client = pg_pool_client(&pg_pools.ordinals).await?; - match ordinals_pg::get_chain_tip_block_height(&ord_client).await? - { + match ordinals_pg::get_chain_tip_block_height(&ord_client).await? { Some(height) => { if find_pinned_block_bytes_at_block_height(height as u32, 3, &blocks_db, &ctx).is_none() { diff --git a/components/ordhook-core/src/core/pipeline/mod.rs b/components/ordhook-core/src/core/pipeline/mod.rs index fdd3545..8ba330e 100644 --- a/components/ordhook-core/src/core/pipeline/mod.rs +++ b/components/ordhook-core/src/core/pipeline/mod.rs @@ -1,7 +1,7 @@ pub mod processors; use chainhook_sdk::observer::BitcoinConfig; -use chainhook_sdk::types::BitcoinBlockData; +use chainhook_types::BitcoinBlockData; use chainhook_sdk::utils::Context; use crossbeam_channel::bounded; use std::collections::{HashMap, VecDeque}; diff --git a/components/ordhook-core/src/core/pipeline/processors/block_archiving.rs b/components/ordhook-core/src/core/pipeline/processors/block_archiving.rs index f72ec06..a7a2fd2 100644 --- a/components/ordhook-core/src/core/pipeline/processors/block_archiving.rs +++ b/components/ordhook-core/src/core/pipeline/processors/block_archiving.rs @@ -1,4 +1,5 @@ -use chainhook_sdk::{types::BitcoinBlockData, utils::Context}; +use chainhook_sdk::utils::Context; +use chainhook_types::BitcoinBlockData; use crossbeam_channel::{Sender, TryRecvError}; use rocksdb::DB; use std::{ diff --git a/components/ordhook-core/src/core/pipeline/processors/inscription_indexing.rs b/components/ordhook-core/src/core/pipeline/processors/inscription_indexing.rs index 1b235f2..ebb8e27 100644 --- a/components/ordhook-core/src/core/pipeline/processors/inscription_indexing.rs +++ b/components/ordhook-core/src/core/pipeline/processors/inscription_indexing.rs @@ -6,10 +6,8 @@ use std::{ }; use chainhook_postgres::{pg_begin, pg_pool_client}; -use chainhook_sdk::{ - types::{BitcoinBlockData, TransactionIdentifier}, - utils::Context, -}; +use chainhook_sdk::utils::Context; +use chainhook_types::{BitcoinBlockData, TransactionIdentifier}; use crossbeam_channel::{Sender, TryRecvError}; use dashmap::DashMap; diff --git a/components/ordhook-core/src/core/protocol/inscription_parsing.rs b/components/ordhook-core/src/core/protocol/inscription_parsing.rs index d4fa550..1e06da8 100644 --- a/components/ordhook-core/src/core/protocol/inscription_parsing.rs +++ b/components/ordhook-core/src/core/protocol/inscription_parsing.rs @@ -1,12 +1,13 @@ -use chainhook_sdk::bitcoincore_rpc_json::bitcoin::Txid; +use bitcoin::hash_types::Txid; +use bitcoin::Witness; use chainhook_sdk::indexer::bitcoin::BitcoinTransactionFullBreakdown; use chainhook_sdk::indexer::bitcoin::{standardize_bitcoin_block, BitcoinBlockFullBreakdown}; -use chainhook_sdk::types::{ - BitcoinBlockData, BitcoinNetwork, BitcoinTransactionData, BlockIdentifier, - OrdinalInscriptionCurseType, OrdinalInscriptionNumber, OrdinalInscriptionRevealData, - OrdinalInscriptionTransferData, OrdinalOperation, -}; use chainhook_sdk::utils::Context; +use chainhook_types::{ + BitcoinBlockData, BitcoinNetwork, BitcoinTransactionData, BlockIdentifier, + OrdinalInscriptionCharms, OrdinalInscriptionCurseType, OrdinalInscriptionNumber, + OrdinalInscriptionRevealData, OrdinalInscriptionTransferData, OrdinalOperation, +}; use serde_json::json; use std::collections::{BTreeMap, HashMap}; use std::str::FromStr; @@ -14,11 +15,11 @@ use std::str::FromStr; use crate::config::Config; use crate::core::meta_protocols::brc20::brc20_activation_height; use crate::core::meta_protocols::brc20::parser::{parse_brc20_operation, ParsedBrc20Operation}; -use crate::ord::envelope::{Envelope, ParsedEnvelope, RawEnvelope}; -use crate::ord::inscription::Inscription; -use crate::ord::inscription_id::InscriptionId; use crate::try_warn; -use {chainhook_sdk::bitcoincore_rpc::bitcoin::Witness, std::str}; +use ord::envelope::{Envelope, ParsedEnvelope}; +use ord::inscription::Inscription; +use ord::inscription_id::InscriptionId; +use std::str; pub fn parse_inscriptions_from_witness( input_index: usize, @@ -27,7 +28,7 @@ pub fn parse_inscriptions_from_witness( ) -> Option> { let witness = Witness::from_slice(&witness_bytes); let tapscript = witness.tapscript()?; - let envelopes: Vec> = RawEnvelope::from_tapscript(tapscript, input_index) + let envelopes: Vec> = Envelope::from_tapscript(tapscript, input_index) .ok()? .into_iter() .map(|e| ParsedEnvelope::from(e)) @@ -64,7 +65,12 @@ pub fn parse_inscriptions_from_witness( let mut content_bytes = "0x".to_string(); content_bytes.push_str(&hex::encode(&inscription_content_bytes)); - let parent = envelope.payload.parent().and_then(|i| Some(i.to_string())); + let parents = envelope + .payload + .parents() + .iter() + .map(|i| i.to_string()) + .collect(); let delegate = envelope .payload .delegate() @@ -75,12 +81,9 @@ pub fn parse_inscriptions_from_witness( .and_then(|p| Some(p.to_string())); let metadata = envelope.payload.metadata().and_then(|m| Some(json!(m))); + // Most of these fields will be calculated later when we know for certain which satoshi contains this inscription. let reveal_data = OrdinalInscriptionRevealData { - content_type: envelope - .payload - .content_type() - .unwrap_or("unknown") - .to_string(), + content_type: envelope.payload.content_type().unwrap_or("").to_string(), content_bytes, content_length: inscription_content_bytes.len(), inscription_id: inscription_id.to_string(), @@ -91,7 +94,7 @@ pub fn parse_inscriptions_from_witness( inscription_fee: 0, inscription_number: OrdinalInscriptionNumber::zero(), inscriber_address: None, - parent, + parents, delegate, metaprotocol, metadata, @@ -101,6 +104,7 @@ pub fn parse_inscriptions_from_witness( transfers_pre_inscription: 0, satpoint_post_inscription: format!(""), curse_type, + charms: OrdinalInscriptionCharms::none(), }; inscriptions.push((reveal_data, envelope.payload)); } @@ -246,21 +250,19 @@ pub fn get_inscriptions_transferred_in_block( mod test { use std::collections::HashMap; + use bitcoin::Amount; use chainhook_sdk::{ - bitcoin::Amount, indexer::bitcoin::{ BitcoinBlockFullBreakdown, BitcoinTransactionFullBreakdown, BitcoinTransactionInputFullBreakdown, BitcoinTransactionInputPrevoutFullBreakdown, GetRawTransactionResultVinScriptSig, }, - types::{ - BitcoinBlockData, BitcoinNetwork, BitcoinTransactionData, - OrdinalInscriptionTransferData, OrdinalInscriptionTransferDestination, - OrdinalOperation, - }, utils::Context, }; - + use chainhook_types::{ + BitcoinBlockData, BitcoinNetwork, BitcoinTransactionData, OrdinalInscriptionTransferData, + OrdinalInscriptionTransferDestination, OrdinalOperation, + }; use test_case::test_case; use crate::{ diff --git a/components/ordhook-core/src/core/protocol/inscription_sequencing.rs b/components/ordhook-core/src/core/protocol/inscription_sequencing.rs index f9c4c3e..7391177 100644 --- a/components/ordhook-core/src/core/protocol/inscription_sequencing.rs +++ b/components/ordhook-core/src/core/protocol/inscription_sequencing.rs @@ -4,28 +4,26 @@ use std::{ sync::Arc, }; -use chainhook_postgres::deadpool_postgres::Transaction; -use chainhook_sdk::{ - bitcoincore_rpc_json::bitcoin::Network, - types::{ - BitcoinBlockData, BitcoinNetwork, BitcoinTransactionData, BlockIdentifier, - OrdinalInscriptionCurseType, OrdinalInscriptionTransferDestination, OrdinalOperation, - TransactionIdentifier, - }, - utils::Context, +use bitcoin::Network; +use chainhook_sdk::utils::Context; +use chainhook_types::{ + BitcoinBlockData, BitcoinNetwork, BitcoinTransactionData, BlockIdentifier, + OrdinalInscriptionCurseType, OrdinalInscriptionTransferDestination, OrdinalOperation, + TransactionIdentifier, }; use crossbeam_channel::unbounded; use dashmap::DashMap; +use deadpool_postgres::Transaction; use fxhash::FxHasher; use crate::{ config::Config, core::resolve_absolute_pointer, db::{self, cursor::TransactionBytesCursor, ordinals_pg}, - ord::height::Height, try_debug, try_error, try_info, utils::format_inscription_id, }; +use ord::height::Height; use std::sync::mpsc::channel; @@ -407,7 +405,7 @@ pub async fn augment_block_with_inscriptions( let mut sats_overflows = VecDeque::new(); let network = get_bitcoin_network(&block.metadata.network); - let coinbase_subsidy = Height(block.block_identifier.index).subsidy(); + let coinbase_subsidy = Height(block.block_identifier.index as u32).subsidy(); let coinbase_tx = &block.transactions[0].clone(); let mut cumulated_fees = 0u64; diff --git a/components/ordhook-core/src/core/protocol/satoshi_numbering.rs b/components/ordhook-core/src/core/protocol/satoshi_numbering.rs index 1f2b569..14b0868 100644 --- a/components/ordhook-core/src/core/protocol/satoshi_numbering.rs +++ b/components/ordhook-core/src/core/protocol/satoshi_numbering.rs @@ -1,5 +1,5 @@ -use chainhook_sdk::types::{BlockIdentifier, OrdinalInscriptionNumber, TransactionIdentifier}; use chainhook_sdk::utils::Context; +use chainhook_types::{BlockIdentifier, OrdinalInscriptionNumber, TransactionIdentifier}; use dashmap::DashMap; use fxhash::FxHasher; use std::hash::BuildHasherDefault; @@ -9,9 +9,9 @@ use crate::config::Config; use crate::db::blocks::find_pinned_block_bytes_at_block_height; use crate::db::cursor::{BlockBytesCursor, TransactionBytesCursor}; -use crate::ord::height::Height; -use crate::ord::sat::Sat; use crate::try_error; +use ord::height::Height; +use ord::sat::Sat; #[derive(Clone, Debug)] pub struct TraversalResult { @@ -25,7 +25,7 @@ pub struct TraversalResult { impl TraversalResult { pub fn get_ordinal_coinbase_height(&self) -> u64 { let sat = Sat(self.ordinal_number); - sat.height().n() + sat.height().n() as u64 } pub fn get_ordinal_coinbase_offset(&self) -> u64 { @@ -313,10 +313,8 @@ pub fn compute_satoshi_number( mod test { use std::{hash::BuildHasherDefault, sync::Arc}; - use chainhook_sdk::{ - types::{bitcoin::TxOut, BlockIdentifier, TransactionIdentifier}, - utils::Context, - }; + use chainhook_sdk::utils::Context; + use chainhook_types::{bitcoin::TxOut, BlockIdentifier, TransactionIdentifier}; use dashmap::DashMap; use fxhash::FxHasher; diff --git a/components/ordhook-core/src/core/protocol/satoshi_tracking.rs b/components/ordhook-core/src/core/protocol/satoshi_tracking.rs index f746822..01f0f88 100644 --- a/components/ordhook-core/src/core/protocol/satoshi_tracking.rs +++ b/components/ordhook-core/src/core/protocol/satoshi_tracking.rs @@ -1,21 +1,20 @@ use std::collections::HashSet; -use chainhook_postgres::deadpool_postgres::Transaction; -use chainhook_sdk::{ - bitcoincore_rpc_json::bitcoin::{Address, Network, ScriptBuf}, - types::{ - BitcoinBlockData, BitcoinTransactionData, BlockIdentifier, OrdinalInscriptionTransferData, OrdinalInscriptionTransferDestination, OrdinalOperation - }, - utils::Context, +use bitcoin::{Address, Network, ScriptBuf}; +use chainhook_sdk::utils::Context; +use chainhook_types::{ + BitcoinBlockData, BitcoinTransactionData, BlockIdentifier, OrdinalInscriptionTransferData, + OrdinalInscriptionTransferDestination, OrdinalOperation, }; +use deadpool_postgres::Transaction; use crate::{ core::{compute_next_satpoint_data, SatPosition}, db::ordinals_pg, - ord::height::Height, try_info, utils::format_outpoint_to_watch, }; +use ord::height::Height; use super::inscription_sequencing::get_bitcoin_network; @@ -51,7 +50,7 @@ pub async fn augment_block_with_transfers( ctx: &Context, ) -> Result<(), String> { let network = get_bitcoin_network(&block.metadata.network); - let coinbase_subsidy = Height(block.block_identifier.index).subsidy(); + let coinbase_subsidy = Height(block.block_identifier.index as u32).subsidy(); let coinbase_tx = &block.transactions[0].clone(); let mut cumulated_fees = 0; for (tx_index, tx) in block.transactions.iter_mut().enumerate() { @@ -261,9 +260,9 @@ pub async fn augment_transaction_with_ordinal_transfers( #[cfg(test)] mod test { - use chainhook_sdk::{ - bitcoin::Network, types::OrdinalInscriptionTransferDestination, utils::Context, - }; + use bitcoin::Network; + use chainhook_sdk::utils::Context; + use chainhook_types::OrdinalInscriptionTransferDestination; use crate::core::test_builders::{TestTransactionBuilder, TestTxInBuilder, TestTxOutBuilder}; diff --git a/components/ordhook-core/src/core/protocol/sequence_cursor.rs b/components/ordhook-core/src/core/protocol/sequence_cursor.rs index c41b376..a02bd28 100644 --- a/components/ordhook-core/src/core/protocol/sequence_cursor.rs +++ b/components/ordhook-core/src/core/protocol/sequence_cursor.rs @@ -1,5 +1,6 @@ -use chainhook_postgres::deadpool_postgres::GenericClient; -use chainhook_sdk::{bitcoin::Network, types::OrdinalInscriptionNumber}; +use bitcoin::Network; +use chainhook_types::OrdinalInscriptionNumber; +use deadpool_postgres::GenericClient; use crate::db::ordinals_pg; @@ -142,8 +143,8 @@ impl SequenceCursor { #[cfg(test)] mod test { + use bitcoin::Network; use chainhook_postgres::{pg_begin, pg_pool_client}; - use chainhook_sdk::bitcoin::Network; use test_case::test_case; diff --git a/components/ordhook-core/src/core/test_builders.rs b/components/ordhook-core/src/core/test_builders.rs index 6ae9fc9..6b206dd 100644 --- a/components/ordhook-core/src/core/test_builders.rs +++ b/components/ordhook-core/src/core/test_builders.rs @@ -1,8 +1,5 @@ -use chainhook_sdk::types::{ - bitcoin::{OutPoint, TxIn, TxOut}, - BitcoinBlockData, BitcoinBlockMetadata, BitcoinNetwork, BitcoinTransactionData, - BitcoinTransactionMetadata, BlockIdentifier, Brc20Operation, OrdinalInscriptionNumber, - OrdinalInscriptionRevealData, OrdinalOperation, TransactionIdentifier, +use chainhook_types::{ + bitcoin::{OutPoint, TxIn, TxOut}, BitcoinBlockData, BitcoinBlockMetadata, BitcoinNetwork, BitcoinTransactionData, BitcoinTransactionMetadata, BlockIdentifier, Brc20Operation, OrdinalInscriptionCharms, OrdinalInscriptionNumber, OrdinalInscriptionRevealData, OrdinalOperation, TransactionIdentifier }; pub struct TestBlockBuilder { @@ -96,7 +93,7 @@ impl TestTransactionBuilder { delegate: None, metaprotocol: None, metadata: None, - parent: None, + parents: vec![], ordinal_number: 0, ordinal_block_height: 0, ordinal_offset: 0, @@ -104,6 +101,7 @@ impl TestTransactionBuilder { transfers_pre_inscription: 0, satpoint_post_inscription: "b61b0172d95e266c18aea0c624db987e971a5d6d4ebc2aaed85da4642d635735:0:0".to_string(), curse_type: None, + charms: OrdinalInscriptionCharms::none(), }, )]; tx @@ -157,7 +155,6 @@ impl TestTransactionBuilder { inputs: self.inputs, outputs: self.outputs, ordinal_operations: self.ordinal_operations, - stacks_operations: vec![], brc20_operation: self.brc20_operation, proof: None, fee: 0, diff --git a/components/ordhook-core/src/db/blocks.rs b/components/ordhook-core/src/db/blocks.rs index 6bf3f02..595fcc6 100644 --- a/components/ordhook-core/src/db/blocks.rs +++ b/components/ordhook-core/src/db/blocks.rs @@ -1,7 +1,7 @@ use std::{path::PathBuf, thread::sleep, time::Duration}; use chainhook_sdk::utils::Context; -use rand::{thread_rng, Rng}; +use rand::{rng, Rng}; use rocksdb::{DBPinnableSlice, Options, DB}; use crate::{config::Config, try_error, try_warn}; @@ -141,13 +141,13 @@ pub fn find_pinned_block_bytes_at_block_height<'a>( // read_options.fill_cache(true); // read_options.set_verify_checksums(false); let mut backoff: f64 = 1.0; - let mut rng = thread_rng(); + let mut rng = rng(); loop { match blocks_db.get_pinned(block_height.to_be_bytes()) { Ok(Some(res)) => return Some(res), _ => { attempt += 1; - backoff = 2.0 * backoff + (backoff * rng.gen_range(0.0..1.0)); + backoff = 2.0 * backoff + (backoff * rng.random_range(0.0..1.0)); let duration = std::time::Duration::from_millis((backoff * 1_000.0) as u64); try_warn!( ctx, @@ -175,14 +175,14 @@ pub fn find_block_bytes_at_block_height<'a>( // read_options.fill_cache(true); // read_options.set_verify_checksums(false); let mut backoff: f64 = 1.0; - let mut rng = thread_rng(); + let mut rng = rng(); loop { match blocks_db.get(block_height.to_be_bytes()) { Ok(Some(res)) => return Some(res), _ => { attempt += 1; - backoff = 2.0 * backoff + (backoff * rng.gen_range(0.0..1.0)); + backoff = 2.0 * backoff + (backoff * rng.random_range(0.0..1.0)); let duration = std::time::Duration::from_millis((backoff * 1_000.0) as u64); try_warn!( ctx, @@ -237,7 +237,7 @@ pub fn delete_blocks_in_block_range( #[cfg(test)] pub fn insert_standardized_block( - block: &chainhook_sdk::types::BitcoinBlockData, + block: &chainhook_types::BitcoinBlockData, blocks_db_rw: &DB, ctx: &Context, ) { diff --git a/components/ordhook-core/src/db/cursor.rs b/components/ordhook-core/src/db/cursor.rs index 31b8881..1be610b 100644 --- a/components/ordhook-core/src/db/cursor.rs +++ b/components/ordhook-core/src/db/cursor.rs @@ -1,7 +1,8 @@ use std::io::Cursor; use std::io::{Read, Write}; -use chainhook_sdk::{indexer::bitcoin::BitcoinBlockFullBreakdown, types::BitcoinBlockData}; +use chainhook_sdk::indexer::bitcoin::BitcoinBlockFullBreakdown; +use chainhook_types::BitcoinBlockData; #[derive(Debug)] pub struct BlockBytesCursor<'a> { @@ -368,9 +369,9 @@ mod tests { use super::*; use chainhook_sdk::{ indexer::bitcoin::{parse_downloaded_block, standardize_bitcoin_block}, - types::BitcoinNetwork, utils::Context, }; + use chainhook_types::BitcoinNetwork; #[test] fn test_block_cursor_roundtrip() { diff --git a/components/ordhook-core/src/db/mod.rs b/components/ordhook-core/src/db/mod.rs index def8ce4..609f2a1 100644 --- a/components/ordhook-core/src/db/mod.rs +++ b/components/ordhook-core/src/db/mod.rs @@ -37,9 +37,7 @@ pub async fn reset_dbs(config: &Config, ctx: &Context) -> Result<(), String> { Ok(()) } -pub async fn pg_reset_db( - pg_client: &mut chainhook_postgres::tokio_postgres::Client, -) -> Result<(), String> { +pub async fn pg_reset_db(pg_client: &mut tokio_postgres::Client) -> Result<(), String> { pg_client .batch_execute( " @@ -77,17 +75,49 @@ pub fn pg_test_config() -> chainhook_postgres::PgConnectionConfig { } #[cfg(test)] -pub fn pg_test_connection_pool() -> chainhook_postgres::deadpool_postgres::Pool { +pub fn pg_test_connection_pool() -> deadpool_postgres::Pool { chainhook_postgres::pg_pool(&pg_test_config()).unwrap() } #[cfg(test)] -pub async fn pg_test_connection() -> chainhook_postgres::tokio_postgres::Client { +pub async fn pg_test_connection() -> tokio_postgres::Client { chainhook_postgres::pg_connect(&pg_test_config()) .await .unwrap() } +#[cfg(test)] +pub async fn pg_test_clear_db(pg_client: &mut tokio_postgres::Client) { + match pg_client + .batch_execute( + " + DO $$ DECLARE + r RECORD; + BEGIN + FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()) LOOP + EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE'; + END LOOP; + END $$; + DO $$ DECLARE + r RECORD; + BEGIN + FOR r IN (SELECT typname FROM pg_type WHERE typtype = 'e' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema())) LOOP + EXECUTE 'DROP TYPE IF EXISTS ' || quote_ident(r.typname) || ' CASCADE'; + END LOOP; + END $$;", + ) + .await { + Ok(rows) => rows, + Err(e) => { + println!( + "error rolling back test migrations: {}", + e.to_string() + ); + std::process::exit(1); + } + }; +} + /// Drops DB files in a test environment. #[cfg(test)] pub fn drop_all_dbs(config: &Config) { diff --git a/components/ordhook-core/src/db/models/db_current_location.rs b/components/ordhook-core/src/db/models/db_current_location.rs index 004ee66..46187ca 100644 --- a/components/ordhook-core/src/db/models/db_current_location.rs +++ b/components/ordhook-core/src/db/models/db_current_location.rs @@ -1,12 +1,12 @@ use chainhook_postgres::{ - tokio_postgres::Row, types::{PgBigIntU32, PgNumericU64}, FromPgRow, }; -use chainhook_sdk::types::{ +use chainhook_types::{ BlockIdentifier, OrdinalInscriptionRevealData, OrdinalInscriptionTransferData, OrdinalInscriptionTransferDestination, TransactionIdentifier, }; +use tokio_postgres::Row; use crate::core::protocol::satoshi_tracking::parse_output_and_offset_from_satpoint; diff --git a/components/ordhook-core/src/db/models/db_inscription.rs b/components/ordhook-core/src/db/models/db_inscription.rs index 2686e9f..cd999db 100644 --- a/components/ordhook-core/src/db/models/db_inscription.rs +++ b/components/ordhook-core/src/db/models/db_inscription.rs @@ -1,12 +1,12 @@ use chainhook_postgres::{ - tokio_postgres::Row, types::{PgBigIntU32, PgNumericU64}, FromPgRow, }; -use chainhook_sdk::types::{ +use chainhook_types::{ BlockIdentifier, OrdinalInscriptionCurseType, OrdinalInscriptionRevealData, TransactionIdentifier, }; +use tokio_postgres::Row; #[derive(Debug, Clone, PartialEq, Eq)] pub struct DbInscription { @@ -30,7 +30,6 @@ pub struct DbInscription { pub pointer: Option, pub metadata: Option, pub metaprotocol: Option, - pub parent: Option, pub delegate: Option, pub timestamp: PgBigIntU32, } @@ -81,7 +80,6 @@ impl DbInscription { pointer: reveal.inscription_pointer.map(|p| PgNumericU64(p)), metadata: reveal.metadata.as_ref().map(|m| m.to_string()), metaprotocol: reveal.metaprotocol.clone(), - parent: reveal.parent.clone(), delegate: reveal.delegate.clone(), timestamp: PgBigIntU32(timestamp), } @@ -111,7 +109,6 @@ impl FromPgRow for DbInscription { pointer: row.get("pointer"), metadata: row.get("metadata"), metaprotocol: row.get("metaprotocol"), - parent: row.get("parent"), delegate: row.get("delegate"), timestamp: row.get("timestamp"), } diff --git a/components/ordhook-core/src/db/models/db_inscription_parent.rs b/components/ordhook-core/src/db/models/db_inscription_parent.rs new file mode 100644 index 0000000..11d0008 --- /dev/null +++ b/components/ordhook-core/src/db/models/db_inscription_parent.rs @@ -0,0 +1,20 @@ +use chainhook_types::OrdinalInscriptionRevealData; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DbInscriptionParent { + pub inscription_id: String, + pub parent_inscription_id: String, +} + +impl DbInscriptionParent { + pub fn from_reveal(reveal: &OrdinalInscriptionRevealData) -> Result, String> { + Ok(reveal + .parents + .iter() + .map(|p| DbInscriptionParent { + inscription_id: reveal.inscription_id.clone(), + parent_inscription_id: p.clone(), + }) + .collect()) + } +} diff --git a/components/ordhook-core/src/db/models/db_inscription_recursion.rs b/components/ordhook-core/src/db/models/db_inscription_recursion.rs index 0a679dc..f6d8417 100644 --- a/components/ordhook-core/src/db/models/db_inscription_recursion.rs +++ b/components/ordhook-core/src/db/models/db_inscription_recursion.rs @@ -1,4 +1,4 @@ -use chainhook_sdk::types::OrdinalInscriptionRevealData; +use chainhook_types::OrdinalInscriptionRevealData; use regex::Regex; lazy_static! { @@ -33,7 +33,7 @@ impl DbInscriptionRecursion { #[cfg(test)] mod test { - use chainhook_sdk::types::{OrdinalInscriptionNumber, OrdinalInscriptionRevealData}; + use chainhook_types::{OrdinalInscriptionCharms, OrdinalInscriptionNumber, OrdinalInscriptionRevealData}; use super::DbInscriptionRecursion; @@ -53,7 +53,7 @@ mod test { delegate: None, metaprotocol: None, metadata: None, - parent: None, + parents: vec![], ordinal_number: 959876891264081, ordinal_block_height: 191975, ordinal_offset: 0, @@ -61,6 +61,7 @@ mod test { transfers_pre_inscription: 0, satpoint_post_inscription: "e47a70a218dfa746ba410b1c057403bb481523d830562fd8dec61ec4d2915e5f:0:0".to_string(), curse_type: None, + charms: OrdinalInscriptionCharms::none(), }; let recursions = DbInscriptionRecursion::from_reveal(&reveal).unwrap(); assert_eq!(2, recursions.len()); diff --git a/components/ordhook-core/src/db/models/db_location.rs b/components/ordhook-core/src/db/models/db_location.rs index acc195e..b3564a1 100644 --- a/components/ordhook-core/src/db/models/db_location.rs +++ b/components/ordhook-core/src/db/models/db_location.rs @@ -1,12 +1,12 @@ use chainhook_postgres::{ - tokio_postgres::Row, types::{PgBigIntU32, PgNumericU64}, FromPgRow, }; -use chainhook_sdk::types::{ +use chainhook_types::{ BlockIdentifier, OrdinalInscriptionRevealData, OrdinalInscriptionTransferData, OrdinalInscriptionTransferDestination, TransactionIdentifier, }; +use tokio_postgres::Row; use crate::core::protocol::satoshi_tracking::parse_output_and_offset_from_satpoint; diff --git a/components/ordhook-core/src/db/models/db_satoshi.rs b/components/ordhook-core/src/db/models/db_satoshi.rs index 536ed11..53fb723 100644 --- a/components/ordhook-core/src/db/models/db_satoshi.rs +++ b/components/ordhook-core/src/db/models/db_satoshi.rs @@ -1,7 +1,8 @@ -use chainhook_postgres::{tokio_postgres::Row, types::PgNumericU64, FromPgRow}; -use chainhook_sdk::types::OrdinalInscriptionRevealData; +use chainhook_postgres::{types::PgNumericU64, FromPgRow}; +use chainhook_types::OrdinalInscriptionRevealData; +use tokio_postgres::Row; -use crate::ord::{rarity::Rarity, sat::Sat}; +use ord::{rarity::Rarity, sat::Sat}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct DbSatoshi { diff --git a/components/ordhook-core/src/db/models/mod.rs b/components/ordhook-core/src/db/models/mod.rs index d68bcf7..b2f0508 100644 --- a/components/ordhook-core/src/db/models/mod.rs +++ b/components/ordhook-core/src/db/models/mod.rs @@ -1,6 +1,7 @@ mod db_current_location; mod db_inscription; mod db_inscription_recursion; +mod db_inscription_parent; mod db_location; mod db_satoshi; @@ -9,3 +10,4 @@ pub use db_inscription::DbInscription; pub use db_inscription_recursion::DbInscriptionRecursion; pub use db_location::DbLocation; pub use db_satoshi::DbSatoshi; +pub use db_inscription_parent::DbInscriptionParent; diff --git a/components/ordhook-core/src/db/ordinals_pg.rs b/components/ordhook-core/src/db/ordinals_pg.rs index 763c5c4..fc984a8 100644 --- a/components/ordhook-core/src/db/ordinals_pg.rs +++ b/components/ordhook-core/src/db/ordinals_pg.rs @@ -1,16 +1,16 @@ use std::collections::{BTreeMap, HashMap}; use chainhook_postgres::{ - deadpool_postgres::GenericClient, - tokio_postgres::{types::ToSql, Client}, types::{PgBigIntU32, PgNumericU64}, utils, }; -use chainhook_sdk::types::{ +use chainhook_types::{ bitcoin::TxIn, BitcoinBlockData, OrdinalInscriptionNumber, OrdinalOperation, TransactionIdentifier, }; +use deadpool_postgres::GenericClient; use refinery::embed_migrations; +use tokio_postgres::{types::ToSql, Client}; use crate::{ core::protocol::{satoshi_numbering::TraversalResult, satoshi_tracking::WatchedSatpoint}, @@ -18,7 +18,8 @@ use crate::{ }; use super::models::{ - DbCurrentLocation, DbInscription, DbInscriptionRecursion, DbLocation, DbSatoshi, + DbCurrentLocation, DbInscription, DbInscriptionParent, DbInscriptionRecursion, DbLocation, + DbSatoshi, }; embed_migrations!("../../migrations/ordinals"); @@ -257,7 +258,6 @@ async fn insert_inscriptions( params.push(&row.pointer); params.push(&row.metadata); params.push(&row.metaprotocol); - params.push(&row.parent); params.push(&row.delegate); params.push(&row.timestamp); } @@ -266,9 +266,9 @@ async fn insert_inscriptions( &format!("INSERT INTO inscriptions (inscription_id, ordinal_number, number, classic_number, block_height, block_hash, tx_id, tx_index, address, mime_type, content_type, content_length, content, fee, curse_type, recursive, input_index, pointer, metadata, - metaprotocol, parent, delegate, timestamp) + metaprotocol, delegate, timestamp) VALUES {} - ON CONFLICT (number) DO NOTHING", utils::multi_row_query_param_str(chunk.len(), 23)), + ON CONFLICT (number) DO NOTHING", utils::multi_row_query_param_str(chunk.len(), 22)), ¶ms, ) .await @@ -307,6 +307,36 @@ async fn insert_inscription_recursions( Ok(()) } +async fn insert_inscription_parents( + inscription_parents: &Vec, + client: &T, +) -> Result<(), String> { + if inscription_parents.len() == 0 { + return Ok(()); + } + for chunk in inscription_parents.chunks(500) { + let mut params: Vec<&(dyn ToSql + Sync)> = vec![]; + for row in chunk.iter() { + params.push(&row.inscription_id); + params.push(&row.parent_inscription_id); + } + client + .query( + &format!( + "INSERT INTO inscription_parents + (inscription_id, parent_inscription_id) + VALUES {} + ON CONFLICT (inscription_id, parent_inscription_id) DO NOTHING", + utils::multi_row_query_param_str(chunk.len(), 2) + ), + ¶ms, + ) + .await + .map_err(|e| format!("insert_inscription_parents: {e}"))?; + } + Ok(()) +} + async fn insert_locations( locations: &Vec, client: &T, @@ -695,6 +725,7 @@ pub async fn insert_block( let mut inscriptions = vec![]; let mut locations = vec![]; let mut inscription_recursions = vec![]; + let mut inscription_parents = vec![]; let mut current_locations: HashMap = HashMap::new(); let mut mime_type_counts = HashMap::new(); let mut sat_rarity_counts = HashMap::new(); @@ -737,6 +768,7 @@ pub async fn insert_block( inscription.recursive = true; } inscription_recursions.extend(recursions); + inscription_parents.extend(DbInscriptionParent::from_reveal(reveal)?); inscriptions.push(inscription); locations.push(DbLocation::from_reveal( reveal, @@ -809,6 +841,7 @@ pub async fn insert_block( insert_inscriptions(&inscriptions, client).await?; insert_inscription_recursions(&inscription_recursions, client).await?; + insert_inscription_parents(&inscription_parents, client).await?; insert_locations(&locations, client).await?; insert_satoshis(&satoshis, client).await?; insert_current_locations(¤t_locations, client).await?; @@ -972,15 +1005,14 @@ pub async fn rollback_block(block_height: u64, client: &T) -> #[cfg(test)] mod test { use chainhook_postgres::{ - deadpool_postgres::GenericClient, pg_begin, pg_pool_client, types::{PgBigIntU32, PgNumericU64}, FromPgRow, }; - use chainhook_sdk::types::{ - OrdinalInscriptionNumber, OrdinalInscriptionRevealData, OrdinalInscriptionTransferData, - OrdinalInscriptionTransferDestination, OrdinalOperation, + use chainhook_types::{ + OrdinalInscriptionCharms, OrdinalInscriptionNumber, OrdinalInscriptionRevealData, OrdinalInscriptionTransferData, OrdinalInscriptionTransferDestination, OrdinalOperation }; + use deadpool_postgres::GenericClient; use crate::{ core::test_builders::{TestBlockBuilder, TestTransactionBuilder}, @@ -1163,7 +1195,7 @@ mod test { delegate: None, metaprotocol: None, metadata: None, - parent: None, + parents: vec![], ordinal_number: 7000, ordinal_block_height: 0, ordinal_offset: 0, @@ -1171,6 +1203,7 @@ mod test { transfers_pre_inscription: 0, satpoint_post_inscription: "b61b0172d95e266c18aea0c624db987e971a5d6d4ebc2aaed85da4642d635735:0:0".to_string(), curse_type: None, + charms: OrdinalInscriptionCharms::none(), }, )) .build() diff --git a/components/ordhook-core/src/lib.rs b/components/ordhook-core/src/lib.rs index 71bfc8e..b2b67e1 100644 --- a/components/ordhook-core/src/lib.rs +++ b/components/ordhook-core/src/lib.rs @@ -9,13 +9,9 @@ extern crate lazy_static; extern crate serde; -pub extern crate chainhook_sdk; -pub extern crate hex; - pub mod config; pub mod core; pub mod db; pub mod download; -pub mod ord; pub mod service; pub mod utils; diff --git a/components/ordhook-core/src/ord/chain.rs b/components/ordhook-core/src/ord/chain.rs deleted file mode 100644 index 73349ce..0000000 --- a/components/ordhook-core/src/ord/chain.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::{ - fmt::{Display, Formatter}, - path::{Path, PathBuf}, -}; - -use chainhook_sdk::bitcoincore_rpc::bitcoin::{self, Address, Block, Network, Script}; - -use super::*; - -#[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum Chain { - #[default] - Mainnet, - Testnet, - Signet, - Regtest, -} - -impl Chain { - pub fn from_bitcoin_network(network: &BitcoinNetwork) -> Chain { - match network { - BitcoinNetwork::Mainnet => Chain::Mainnet, - BitcoinNetwork::Testnet => Chain::Testnet, - BitcoinNetwork::Regtest => Chain::Regtest, - BitcoinNetwork::Signet => Chain::Signet, - } - } - - pub(crate) fn network(self) -> Network { - match self { - Self::Mainnet => Network::Bitcoin, - Self::Testnet => Network::Testnet, - Self::Signet => Network::Signet, - Self::Regtest => Network::Regtest, - } - } - - pub(crate) fn default_rpc_port(self) -> u16 { - match self { - Self::Mainnet => 8332, - Self::Regtest => 18443, - Self::Signet => 38332, - Self::Testnet => 18332, - } - } - - pub(crate) fn inscription_content_size_limit(self) -> Option { - match self { - Self::Mainnet | Self::Regtest => None, - Self::Testnet | Self::Signet => Some(1024), - } - } - - pub fn first_inscription_height(self) -> u64 { - match self { - Self::Mainnet => 767430, - Self::Regtest => 0, - Self::Signet => 112402, - Self::Testnet => 2413343, - } - } - - pub(crate) fn genesis_block(self) -> Block { - bitcoin::blockdata::constants::genesis_block(self.network()) - } - - pub fn address_from_script(self, script: &Script) -> Result { - Address::from_script(script, self.network()) - } - - pub(crate) fn join_with_data_dir(self, data_dir: &Path) -> PathBuf { - match self { - Self::Mainnet => data_dir.to_owned(), - Self::Testnet => data_dir.join("testnet3"), - Self::Signet => data_dir.join("signet"), - Self::Regtest => data_dir.join("regtest"), - } - } -} - -impl Display for Chain { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::Mainnet => "mainnet", - Self::Regtest => "regtest", - Self::Signet => "signet", - Self::Testnet => "testnet", - } - ) - } -} diff --git a/components/ordhook-core/src/ord/deserialize_from_str.rs b/components/ordhook-core/src/ord/deserialize_from_str.rs deleted file mode 100644 index 784f163..0000000 --- a/components/ordhook-core/src/ord/deserialize_from_str.rs +++ /dev/null @@ -1,22 +0,0 @@ -use std::{fmt::Display, str::FromStr}; - -use serde::{Deserialize, Deserializer}; - -use super::*; - -pub(crate) struct DeserializeFromStr(pub(crate) T); - -impl<'de, T: FromStr> Deserialize<'de> for DeserializeFromStr -where - T::Err: Display, -{ - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Ok(Self( - FromStr::from_str(&String::deserialize(deserializer)?) - .map_err(serde::de::Error::custom)?, - )) - } -} diff --git a/components/ordhook-core/src/ord/envelope.rs b/components/ordhook-core/src/ord/envelope.rs deleted file mode 100644 index e1bb452..0000000 --- a/components/ordhook-core/src/ord/envelope.rs +++ /dev/null @@ -1,1006 +0,0 @@ -use std::collections::BTreeMap; - -use chainhook_sdk::bitcoin::{Script, Transaction}; - -use super::inscription::Inscription; - -use { - chainhook_sdk::bitcoin::blockdata::{ - opcodes, - script::{ - self, - Instruction::{self, Op, PushBytes}, - Instructions, - }, - }, - std::iter::Peekable, -}; - -pub const PROTOCOL_ID: [u8; 3] = *b"ord"; - -pub const BODY_TAG: [u8; 0] = []; -pub const CONTENT_TYPE_TAG: [u8; 1] = [1]; -pub const POINTER_TAG: [u8; 1] = [2]; -pub const PARENT_TAG: [u8; 1] = [3]; -pub const METADATA_TAG: [u8; 1] = [5]; -pub const METAPROTOCOL_TAG: [u8; 1] = [7]; -pub const CONTENT_ENCODING_TAG: [u8; 1] = [9]; -pub const DELEGATE_TAG: [u8; 1] = [11]; - -type Result = std::result::Result; -pub type RawEnvelope = Envelope>>; -pub type ParsedEnvelope = Envelope; - -#[derive(Debug, Default, PartialEq, Clone)] -pub struct Envelope { - pub input: u32, - pub offset: u32, - pub payload: T, - pub pushnum: bool, - pub stutter: bool, -} - -fn remove_field(fields: &mut BTreeMap<&[u8], Vec<&[u8]>>, field: &[u8]) -> Option> { - let values = fields.get_mut(field)?; - - if values.is_empty() { - None - } else { - let value = values.remove(0).to_vec(); - - if values.is_empty() { - fields.remove(field); - } - - Some(value) - } -} - -fn remove_and_concatenate_field( - fields: &mut BTreeMap<&[u8], Vec<&[u8]>>, - field: &[u8], -) -> Option> { - let value = fields.remove(field)?; - - if value.is_empty() { - None - } else { - Some(value.into_iter().flatten().cloned().collect()) - } -} - -impl From for ParsedEnvelope { - fn from(envelope: RawEnvelope) -> Self { - let body = envelope - .payload - .iter() - .enumerate() - .position(|(i, push)| i % 2 == 0 && push.is_empty()); - - let mut fields: BTreeMap<&[u8], Vec<&[u8]>> = BTreeMap::new(); - - let mut incomplete_field = false; - - for item in envelope.payload[..body.unwrap_or(envelope.payload.len())].chunks(2) { - match item { - [key, value] => fields.entry(key).or_default().push(value), - _ => incomplete_field = true, - } - } - - let duplicate_field = fields.iter().any(|(_key, values)| values.len() > 1); - - let content_encoding = remove_field(&mut fields, &CONTENT_ENCODING_TAG); - let content_type = remove_field(&mut fields, &CONTENT_TYPE_TAG); - let delegate = remove_field(&mut fields, &DELEGATE_TAG); - let metadata = remove_and_concatenate_field(&mut fields, &METADATA_TAG); - let metaprotocol = remove_field(&mut fields, &METAPROTOCOL_TAG); - let parent = remove_field(&mut fields, &PARENT_TAG); - let pointer = remove_field(&mut fields, &POINTER_TAG); - - let unrecognized_even_field = fields - .keys() - .any(|tag| tag.first().map(|lsb| lsb % 2 == 0).unwrap_or_default()); - - Self { - payload: Inscription { - body: body.map(|i| { - envelope.payload[i + 1..] - .iter() - .flatten() - .cloned() - .collect() - }), - metaprotocol, - parent, - delegate, - content_encoding, - content_type, - duplicate_field, - incomplete_field, - metadata, - pointer, - unrecognized_even_field, - }, - input: envelope.input, - offset: envelope.offset, - pushnum: envelope.pushnum, - stutter: envelope.stutter, - } - } -} - -impl ParsedEnvelope { - pub fn from_transaction(transaction: &Transaction) -> Vec { - RawEnvelope::from_transaction(transaction) - .into_iter() - .map(|envelope| envelope.into()) - .collect() - } -} - -impl RawEnvelope { - pub fn from_transaction(transaction: &Transaction) -> Vec { - let mut envelopes = Vec::new(); - - for (i, input) in transaction.input.iter().enumerate() { - if let Some(tapscript) = input.witness.tapscript() { - if let Ok(input_envelopes) = Self::from_tapscript(tapscript, i) { - envelopes.extend(input_envelopes); - } - } - } - - envelopes - } - - pub fn from_tapscript(tapscript: &Script, input: usize) -> Result> { - let mut envelopes = Vec::new(); - - let mut instructions = tapscript.instructions().peekable(); - - let mut stuttered = false; - while let Some(instruction) = instructions.next().transpose()? { - if instruction == PushBytes((&[]).into()) { - let (stutter, envelope) = - Self::from_instructions(&mut instructions, input, envelopes.len(), stuttered)?; - if let Some(envelope) = envelope { - envelopes.push(envelope); - } else { - stuttered = stutter; - } - } - } - - Ok(envelopes) - } - - fn accept(instructions: &mut Peekable, instruction: Instruction) -> Result { - if instructions.peek() == Some(&Ok(instruction)) { - instructions.next().transpose()?; - Ok(true) - } else { - Ok(false) - } - } - - fn from_instructions( - instructions: &mut Peekable, - input: usize, - offset: usize, - stutter: bool, - ) -> Result<(bool, Option)> { - if !Self::accept(instructions, Op(opcodes::all::OP_IF))? { - let stutter = instructions.peek() == Some(&Ok(PushBytes((&[]).into()))); - return Ok((stutter, None)); - } - - if !Self::accept(instructions, PushBytes((&PROTOCOL_ID).into()))? { - let stutter = instructions.peek() == Some(&Ok(PushBytes((&[]).into()))); - return Ok((stutter, None)); - } - - let mut pushnum = false; - - let mut payload = Vec::new(); - - loop { - match instructions.next().transpose()? { - None => return Ok((false, None)), - Some(Op(opcodes::all::OP_ENDIF)) => { - return Ok(( - false, - Some(Envelope { - input: input.try_into().unwrap(), - offset: offset.try_into().unwrap(), - payload, - pushnum, - stutter, - }), - )); - } - Some(Op(opcodes::all::OP_PUSHNUM_NEG1)) => { - pushnum = true; - payload.push(vec![0x81]); - } - Some(Op(opcodes::all::OP_PUSHNUM_1)) => { - pushnum = true; - payload.push(vec![1]); - } - Some(Op(opcodes::all::OP_PUSHNUM_2)) => { - pushnum = true; - payload.push(vec![2]); - } - Some(Op(opcodes::all::OP_PUSHNUM_3)) => { - pushnum = true; - payload.push(vec![3]); - } - Some(Op(opcodes::all::OP_PUSHNUM_4)) => { - pushnum = true; - payload.push(vec![4]); - } - Some(Op(opcodes::all::OP_PUSHNUM_5)) => { - pushnum = true; - payload.push(vec![5]); - } - Some(Op(opcodes::all::OP_PUSHNUM_6)) => { - pushnum = true; - payload.push(vec![6]); - } - Some(Op(opcodes::all::OP_PUSHNUM_7)) => { - pushnum = true; - payload.push(vec![7]); - } - Some(Op(opcodes::all::OP_PUSHNUM_8)) => { - pushnum = true; - payload.push(vec![8]); - } - Some(Op(opcodes::all::OP_PUSHNUM_9)) => { - pushnum = true; - payload.push(vec![9]); - } - Some(Op(opcodes::all::OP_PUSHNUM_10)) => { - pushnum = true; - payload.push(vec![10]); - } - Some(Op(opcodes::all::OP_PUSHNUM_11)) => { - pushnum = true; - payload.push(vec![11]); - } - Some(Op(opcodes::all::OP_PUSHNUM_12)) => { - pushnum = true; - payload.push(vec![12]); - } - Some(Op(opcodes::all::OP_PUSHNUM_13)) => { - pushnum = true; - payload.push(vec![13]); - } - Some(Op(opcodes::all::OP_PUSHNUM_14)) => { - pushnum = true; - payload.push(vec![14]); - } - Some(Op(opcodes::all::OP_PUSHNUM_15)) => { - pushnum = true; - payload.push(vec![15]); - } - Some(Op(opcodes::all::OP_PUSHNUM_16)) => { - pushnum = true; - payload.push(vec![16]); - } - Some(PushBytes(push)) => { - payload.push(push.as_bytes().to_vec()); - } - Some(_) => return Ok((false, None)), - } - } - } -} - -#[cfg(test)] -mod tests { - use chainhook_sdk::bitcoin::{ - self, script::PushBytesBuf, transaction::Version, OutPoint, ScriptBuf, Sequence, TxIn, - Witness, - }; - - use {super::*, chainhook_sdk::bitcoin::absolute::LockTime}; - - fn inscription(content_type: &str, body: impl AsRef<[u8]>) -> Inscription { - Inscription::new(Some(content_type.into()), Some(body.as_ref().into())) - } - - fn envelope(payload: &[&[u8]]) -> Witness { - let mut builder = script::Builder::new() - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::all::OP_IF); - - for data in payload { - let mut buf = PushBytesBuf::new(); - buf.extend_from_slice(data).unwrap(); - builder = builder.push_slice(buf); - } - - let script = builder.push_opcode(opcodes::all::OP_ENDIF).into_script(); - - Witness::from_slice(&[script.into_bytes(), Vec::new()]) - } - - fn parse(witnesses: &[Witness]) -> Vec { - ParsedEnvelope::from_transaction(&Transaction { - version: Version::ONE, - lock_time: LockTime::ZERO, - input: witnesses - .iter() - .map(|witness| TxIn { - previous_output: OutPoint::null(), - script_sig: ScriptBuf::new(), - sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, - witness: witness.clone(), - }) - .collect(), - output: Vec::new(), - }) - } - - #[test] - fn empty() { - assert_eq!(parse(&[Witness::new()]), Vec::new()) - } - - #[test] - fn ignore_key_path_spends() { - assert_eq!( - parse(&[Witness::from_slice(&[bitcoin::script::Builder::new() - .push_opcode(bitcoin::opcodes::OP_FALSE) - .push_opcode(bitcoin::opcodes::all::OP_IF) - .push_slice(b"ord") - .push_opcode(bitcoin::opcodes::all::OP_ENDIF) - .into_script() - .into_bytes()])]), - Vec::new() - ); - } - - #[test] - fn ignore_key_path_spends_with_annex() { - assert_eq!( - parse(&[Witness::from_slice(&[ - bitcoin::script::Builder::new() - .push_opcode(bitcoin::opcodes::OP_FALSE) - .push_opcode(bitcoin::opcodes::all::OP_IF) - .push_slice(b"ord") - .push_opcode(bitcoin::opcodes::all::OP_ENDIF) - .into_script() - .into_bytes(), - vec![0x50] - ])]), - Vec::new() - ); - } - - #[test] - fn parse_from_tapscript() { - assert_eq!( - parse(&[Witness::from_slice(&[ - bitcoin::script::Builder::new() - .push_opcode(bitcoin::opcodes::OP_FALSE) - .push_opcode(bitcoin::opcodes::all::OP_IF) - .push_slice(b"ord") - .push_opcode(bitcoin::opcodes::all::OP_ENDIF) - .into_script() - .into_bytes(), - Vec::new() - ])]), - vec![ParsedEnvelope { - ..Default::default() - }] - ); - } - - #[test] - fn ignore_unparsable_scripts() { - let mut script_bytes = bitcoin::script::Builder::new() - .push_opcode(bitcoin::opcodes::OP_FALSE) - .push_opcode(bitcoin::opcodes::all::OP_IF) - .push_slice(b"ord") - .push_opcode(bitcoin::opcodes::all::OP_ENDIF) - .into_script() - .into_bytes(); - script_bytes.push(0x01); - - assert_eq!( - parse(&[Witness::from_slice(&[script_bytes, Vec::new()])]), - Vec::new() - ); - } - - #[test] - fn no_inscription() { - assert_eq!( - parse(&[Witness::from_slice(&[ - ScriptBuf::new().into_bytes(), - Vec::new() - ])]), - Vec::new() - ); - } - - #[test] - fn duplicate_field() { - assert_eq!( - parse(&[envelope(&[b"ord", &[255], &[], &[255], &[]])]), - vec![ParsedEnvelope { - payload: Inscription { - duplicate_field: true, - ..Default::default() - }, - ..Default::default() - }] - ); - } - - #[test] - fn with_content_type() { - assert_eq!( - parse(&[envelope(&[ - b"ord", - &[1], - b"text/plain;charset=utf-8", - &[], - b"ord", - ])]), - vec![ParsedEnvelope { - payload: inscription("text/plain;charset=utf-8", "ord"), - ..Default::default() - }] - ); - } - - #[test] - fn with_content_encoding() { - assert_eq!( - parse(&[envelope(&[ - b"ord", - &[1], - b"text/plain;charset=utf-8", - &[9], - b"br", - &[], - b"ord", - ])]), - vec![ParsedEnvelope { - payload: Inscription { - content_encoding: Some("br".as_bytes().to_vec()), - ..inscription("text/plain;charset=utf-8", "ord") - }, - ..Default::default() - }] - ); - } - - #[test] - fn no_body() { - assert_eq!( - parse(&[envelope(&[b"ord", &[1], b"text/plain;charset=utf-8"])]), - vec![ParsedEnvelope { - payload: Inscription { - content_type: Some(b"text/plain;charset=utf-8".to_vec()), - ..Default::default() - }, - ..Default::default() - }], - ); - } - - #[test] - fn no_content_type() { - assert_eq!( - parse(&[envelope(&[b"ord", &[], b"foo"])]), - vec![ParsedEnvelope { - payload: Inscription { - body: Some(b"foo".to_vec()), - ..Default::default() - }, - ..Default::default() - }], - ); - } - - #[test] - fn valid_body_in_multiple_pushes() { - assert_eq!( - parse(&[envelope(&[ - b"ord", - &[1], - b"text/plain;charset=utf-8", - &[], - b"foo", - b"bar" - ])]), - vec![ParsedEnvelope { - payload: inscription("text/plain;charset=utf-8", "foobar"), - ..Default::default() - }], - ); - } - - #[test] - fn valid_body_in_zero_pushes() { - assert_eq!( - parse(&[envelope(&[b"ord", &[1], b"text/plain;charset=utf-8", &[]])]), - vec![ParsedEnvelope { - payload: inscription("text/plain;charset=utf-8", ""), - ..Default::default() - }] - ); - } - - #[test] - fn valid_body_in_multiple_empty_pushes() { - assert_eq!( - parse(&[envelope(&[ - b"ord", - &[1], - b"text/plain;charset=utf-8", - &[], - &[], - &[], - &[], - &[], - &[], - ])]), - vec![ParsedEnvelope { - payload: inscription("text/plain;charset=utf-8", ""), - ..Default::default() - }], - ); - } - - #[test] - fn valid_ignore_trailing() { - let script = script::Builder::new() - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::all::OP_IF) - .push_slice(b"ord") - .push_slice([1]) - .push_slice(b"text/plain;charset=utf-8") - .push_slice([]) - .push_slice(b"ord") - .push_opcode(opcodes::all::OP_ENDIF) - .push_opcode(opcodes::all::OP_CHECKSIG) - .into_script(); - - assert_eq!( - parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), - vec![ParsedEnvelope { - payload: inscription("text/plain;charset=utf-8", "ord"), - ..Default::default() - }], - ); - } - - #[test] - fn valid_ignore_preceding() { - let script = script::Builder::new() - .push_opcode(opcodes::all::OP_CHECKSIG) - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::all::OP_IF) - .push_slice(b"ord") - .push_slice([1]) - .push_slice(b"text/plain;charset=utf-8") - .push_slice([]) - .push_slice(b"ord") - .push_opcode(opcodes::all::OP_ENDIF) - .into_script(); - - assert_eq!( - parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), - vec![ParsedEnvelope { - payload: inscription("text/plain;charset=utf-8", "ord"), - ..Default::default() - }], - ); - } - - #[test] - fn multiple_inscriptions_in_a_single_witness() { - let script = script::Builder::new() - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::all::OP_IF) - .push_slice(b"ord") - .push_slice([1]) - .push_slice(b"text/plain;charset=utf-8") - .push_slice([]) - .push_slice(b"foo") - .push_opcode(opcodes::all::OP_ENDIF) - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::all::OP_IF) - .push_slice(b"ord") - .push_slice([1]) - .push_slice(b"text/plain;charset=utf-8") - .push_slice([]) - .push_slice(b"bar") - .push_opcode(opcodes::all::OP_ENDIF) - .into_script(); - - assert_eq!( - parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), - vec![ - ParsedEnvelope { - payload: inscription("text/plain;charset=utf-8", "foo"), - ..Default::default() - }, - ParsedEnvelope { - payload: inscription("text/plain;charset=utf-8", "bar"), - offset: 1, - ..Default::default() - }, - ], - ); - } - - #[test] - fn invalid_utf8_does_not_render_inscription_invalid() { - assert_eq!( - parse(&[envelope(&[ - b"ord", - &[1], - b"text/plain;charset=utf-8", - &[], - &[0b10000000] - ])]), - vec![ParsedEnvelope { - payload: inscription("text/plain;charset=utf-8", [0b10000000]), - ..Default::default() - },], - ); - } - - #[test] - fn no_endif() { - let script = script::Builder::new() - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::all::OP_IF) - .push_slice(b"ord") - .into_script(); - - assert_eq!( - parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), - Vec::new(), - ); - } - - #[test] - fn no_op_false() { - let script = script::Builder::new() - .push_opcode(opcodes::all::OP_IF) - .push_slice(b"ord") - .push_opcode(opcodes::all::OP_ENDIF) - .into_script(); - - assert_eq!( - parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), - Vec::new(), - ); - } - - #[test] - fn empty_envelope() { - assert_eq!(parse(&[envelope(&[])]), Vec::new()); - } - - #[test] - fn wrong_protocol_identifier() { - assert_eq!(parse(&[envelope(&[b"foo"])]), Vec::new()); - } - - #[test] - fn extract_from_transaction() { - assert_eq!( - parse(&[envelope(&[ - b"ord", - &[1], - b"text/plain;charset=utf-8", - &[], - b"ord" - ])]), - vec![ParsedEnvelope { - payload: inscription("text/plain;charset=utf-8", "ord"), - ..Default::default() - }], - ); - } - - #[test] - fn extract_from_second_input() { - assert_eq!( - parse(&[Witness::new(), inscription("foo", [1; 1040]).to_witness()]), - vec![ParsedEnvelope { - payload: inscription("foo", [1; 1040]), - input: 1, - ..Default::default() - }] - ); - } - - #[test] - fn extract_from_second_envelope() { - let mut builder = script::Builder::new(); - builder = inscription("foo", [1; 100]).append_reveal_script_to_builder(builder); - builder = inscription("bar", [1; 100]).append_reveal_script_to_builder(builder); - - assert_eq!( - parse(&[Witness::from_slice(&[ - builder.into_script().into_bytes(), - Vec::new() - ])]), - vec![ - ParsedEnvelope { - payload: inscription("foo", [1; 100]), - ..Default::default() - }, - ParsedEnvelope { - payload: inscription("bar", [1; 100]), - offset: 1, - ..Default::default() - } - ] - ); - } - - #[test] - fn inscribe_png() { - assert_eq!( - parse(&[envelope(&[b"ord", &[1], b"image/png", &[], &[1; 100]])]), - vec![ParsedEnvelope { - payload: inscription("image/png", [1; 100]), - ..Default::default() - }] - ); - } - - #[test] - fn chunked_data_is_parsable() { - let mut witness = Witness::new(); - - witness.push(&inscription("foo", [1; 1040]).append_reveal_script(script::Builder::new())); - - witness.push([]); - - assert_eq!( - parse(&[witness]), - vec![ParsedEnvelope { - payload: inscription("foo", [1; 1040]), - ..Default::default() - }] - ); - } - - #[test] - fn round_trip_with_no_fields() { - let mut witness = Witness::new(); - - witness.push(Inscription::default().append_reveal_script(script::Builder::new())); - - witness.push([]); - - assert_eq!( - parse(&[witness]), - vec![ParsedEnvelope { - payload: Inscription::default(), - ..Default::default() - }], - ); - } - - #[test] - fn unknown_even_fields() { - assert_eq!( - parse(&[envelope(&[b"ord", &[22], &[0]])]), - vec![ParsedEnvelope { - payload: Inscription { - unrecognized_even_field: true, - ..Default::default() - }, - ..Default::default() - }], - ); - } - - #[test] - fn pointer_field_is_recognized() { - assert_eq!( - parse(&[envelope(&[b"ord", &[2], &[1]])]), - vec![ParsedEnvelope { - payload: Inscription { - pointer: Some(vec![1]), - ..Default::default() - }, - ..Default::default() - }], - ); - } - - #[test] - fn duplicate_pointer_field_makes_inscription_unbound() { - assert_eq!( - parse(&[envelope(&[b"ord", &[2], &[1], &[2], &[0]])]), - vec![ParsedEnvelope { - payload: Inscription { - pointer: Some(vec![1]), - duplicate_field: true, - unrecognized_even_field: true, - ..Default::default() - }, - ..Default::default() - }], - ); - } - - #[test] - fn incomplete_field() { - assert_eq!( - parse(&[envelope(&[b"ord", &[99]])]), - vec![ParsedEnvelope { - payload: Inscription { - incomplete_field: true, - ..Default::default() - }, - ..Default::default() - }], - ); - } - - #[test] - fn metadata_is_parsed_correctly() { - assert_eq!( - parse(&[envelope(&[b"ord", &[5], &[]])]), - vec![ParsedEnvelope { - payload: Inscription { - metadata: Some(vec![]), - ..Default::default() - }, - ..Default::default() - }] - ); - } - - #[test] - fn metadata_is_parsed_correctly_from_chunks() { - assert_eq!( - parse(&[envelope(&[b"ord", &[5], &[0], &[5], &[1]])]), - vec![ParsedEnvelope { - payload: Inscription { - metadata: Some(vec![0, 1]), - duplicate_field: true, - ..Default::default() - }, - ..Default::default() - }] - ); - } - - #[test] - fn pushnum_opcodes_are_parsed_correctly() { - pub use chainhook_sdk::bitcoin::opcodes; - const PUSHNUMS: &[(opcodes::Opcode, u8)] = &[ - (opcodes::all::OP_PUSHNUM_NEG1, 0x81), - (opcodes::all::OP_PUSHNUM_1, 1), - (opcodes::all::OP_PUSHNUM_2, 2), - (opcodes::all::OP_PUSHNUM_3, 3), - (opcodes::all::OP_PUSHNUM_4, 4), - (opcodes::all::OP_PUSHNUM_5, 5), - (opcodes::all::OP_PUSHNUM_6, 6), - (opcodes::all::OP_PUSHNUM_7, 7), - (opcodes::all::OP_PUSHNUM_8, 8), - (opcodes::all::OP_PUSHNUM_9, 9), - (opcodes::all::OP_PUSHNUM_10, 10), - (opcodes::all::OP_PUSHNUM_11, 11), - (opcodes::all::OP_PUSHNUM_12, 12), - (opcodes::all::OP_PUSHNUM_13, 13), - (opcodes::all::OP_PUSHNUM_14, 14), - (opcodes::all::OP_PUSHNUM_15, 15), - (opcodes::all::OP_PUSHNUM_16, 16), - ]; - - for &(op, value) in PUSHNUMS { - let script = script::Builder::new() - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::all::OP_IF) - .push_slice(b"ord") - .push_opcode(opcodes::OP_FALSE) - .push_opcode(op) - .push_opcode(opcodes::all::OP_ENDIF) - .into_script(); - - assert_eq!( - parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), - vec![ParsedEnvelope { - payload: Inscription { - body: Some(vec![value]), - ..Default::default() - }, - pushnum: true, - ..Default::default() - }], - ); - } - } - - #[test] - fn stuttering() { - let script = script::Builder::new() - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::all::OP_IF) - .push_slice(b"ord") - .push_opcode(opcodes::all::OP_ENDIF) - .into_script(); - - assert_eq!( - parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), - vec![ParsedEnvelope { - payload: Default::default(), - stutter: true, - ..Default::default() - }], - ); - - let script = script::Builder::new() - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::all::OP_IF) - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::all::OP_IF) - .push_slice(b"ord") - .push_opcode(opcodes::all::OP_ENDIF) - .into_script(); - - assert_eq!( - parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), - vec![ParsedEnvelope { - payload: Default::default(), - stutter: true, - ..Default::default() - }], - ); - - let script = script::Builder::new() - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::all::OP_IF) - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::all::OP_IF) - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::all::OP_IF) - .push_slice(b"ord") - .push_opcode(opcodes::all::OP_ENDIF) - .into_script(); - - assert_eq!( - parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), - vec![ParsedEnvelope { - payload: Default::default(), - stutter: true, - ..Default::default() - }], - ); - - let script = script::Builder::new() - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::all::OP_AND) - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::all::OP_IF) - .push_slice(b"ord") - .push_opcode(opcodes::all::OP_ENDIF) - .into_script(); - - assert_eq!( - parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), - vec![ParsedEnvelope { - payload: Default::default(), - stutter: false, - ..Default::default() - }], - ); - } -} diff --git a/components/ordhook-core/src/ord/epoch.rs b/components/ordhook-core/src/ord/epoch.rs deleted file mode 100644 index 71d7764..0000000 --- a/components/ordhook-core/src/ord/epoch.rs +++ /dev/null @@ -1,237 +0,0 @@ -use super::{height::Height, sat::Sat, COIN_VALUE, SUBSIDY_HALVING_INTERVAL}; - -#[derive(Copy, Clone, Eq, PartialEq, Debug, PartialOrd)] -pub(crate) struct Epoch(pub(crate) u64); - -impl Epoch { - pub(crate) const STARTING_SATS: [Sat; 34] = [ - Sat(0), - Sat(1050000000000000), - Sat(1575000000000000), - Sat(1837500000000000), - Sat(1968750000000000), - Sat(2034375000000000), - Sat(2067187500000000), - Sat(2083593750000000), - Sat(2091796875000000), - Sat(2095898437500000), - Sat(2097949218750000), - Sat(2098974609270000), - Sat(2099487304530000), - Sat(2099743652160000), - Sat(2099871825870000), - Sat(2099935912620000), - Sat(2099967955890000), - Sat(2099983977420000), - Sat(2099991988080000), - Sat(2099995993410000), - Sat(2099997995970000), - Sat(2099998997250000), - Sat(2099999497890000), - Sat(2099999748210000), - Sat(2099999873370000), - Sat(2099999935950000), - Sat(2099999967240000), - Sat(2099999982780000), - Sat(2099999990550000), - Sat(2099999994330000), - Sat(2099999996220000), - Sat(2099999997060000), - Sat(2099999997480000), - Sat(Sat::SUPPLY), - ]; - pub(crate) const FIRST_POST_SUBSIDY: Epoch = Self(33); - - pub(crate) fn subsidy(self) -> u64 { - if self < Self::FIRST_POST_SUBSIDY { - (50 * COIN_VALUE) >> self.0 - } else { - 0 - } - } - - pub(crate) fn starting_sat(self) -> Sat { - *Self::STARTING_SATS - .get(usize::try_from(self.0).unwrap()) - .unwrap_or_else(|| Self::STARTING_SATS.last().unwrap()) - } - - pub(crate) fn starting_height(self) -> Height { - Height(self.0 * SUBSIDY_HALVING_INTERVAL) - } -} - -impl PartialEq for Epoch { - fn eq(&self, other: &u64) -> bool { - self.0 == *other - } -} - -impl From for Epoch { - fn from(sat: Sat) -> Self { - if sat < Self::STARTING_SATS[1] { - Epoch(0) - } else if sat < Self::STARTING_SATS[2] { - Epoch(1) - } else if sat < Self::STARTING_SATS[3] { - Epoch(2) - } else if sat < Self::STARTING_SATS[4] { - Epoch(3) - } else if sat < Self::STARTING_SATS[5] { - Epoch(4) - } else if sat < Self::STARTING_SATS[6] { - Epoch(5) - } else if sat < Self::STARTING_SATS[7] { - Epoch(6) - } else if sat < Self::STARTING_SATS[8] { - Epoch(7) - } else if sat < Self::STARTING_SATS[9] { - Epoch(8) - } else if sat < Self::STARTING_SATS[10] { - Epoch(9) - } else if sat < Self::STARTING_SATS[11] { - Epoch(10) - } else if sat < Self::STARTING_SATS[12] { - Epoch(11) - } else if sat < Self::STARTING_SATS[13] { - Epoch(12) - } else if sat < Self::STARTING_SATS[14] { - Epoch(13) - } else if sat < Self::STARTING_SATS[15] { - Epoch(14) - } else if sat < Self::STARTING_SATS[16] { - Epoch(15) - } else if sat < Self::STARTING_SATS[17] { - Epoch(16) - } else if sat < Self::STARTING_SATS[18] { - Epoch(17) - } else if sat < Self::STARTING_SATS[19] { - Epoch(18) - } else if sat < Self::STARTING_SATS[20] { - Epoch(19) - } else if sat < Self::STARTING_SATS[21] { - Epoch(20) - } else if sat < Self::STARTING_SATS[22] { - Epoch(21) - } else if sat < Self::STARTING_SATS[23] { - Epoch(22) - } else if sat < Self::STARTING_SATS[24] { - Epoch(23) - } else if sat < Self::STARTING_SATS[25] { - Epoch(24) - } else if sat < Self::STARTING_SATS[26] { - Epoch(25) - } else if sat < Self::STARTING_SATS[27] { - Epoch(26) - } else if sat < Self::STARTING_SATS[28] { - Epoch(27) - } else if sat < Self::STARTING_SATS[29] { - Epoch(28) - } else if sat < Self::STARTING_SATS[30] { - Epoch(29) - } else if sat < Self::STARTING_SATS[31] { - Epoch(30) - } else if sat < Self::STARTING_SATS[32] { - Epoch(31) - } else if sat < Self::STARTING_SATS[33] { - Epoch(32) - } else { - Epoch(33) - } - } -} - -impl From for Epoch { - fn from(height: Height) -> Self { - Self(height.0 / SUBSIDY_HALVING_INTERVAL) - } -} - -#[cfg(test)] -mod tests { - - use crate::ord::{epoch::Epoch, height::Height, sat::Sat, SUBSIDY_HALVING_INTERVAL}; - - #[test] - fn starting_sat() { - assert_eq!(Epoch(0).starting_sat(), 0); - assert_eq!( - Epoch(1).starting_sat(), - Epoch(0).subsidy() * SUBSIDY_HALVING_INTERVAL - ); - assert_eq!( - Epoch(2).starting_sat(), - (Epoch(0).subsidy() + Epoch(1).subsidy()) * SUBSIDY_HALVING_INTERVAL - ); - assert_eq!(Epoch(33).starting_sat(), Sat(Sat::SUPPLY)); - assert_eq!(Epoch(34).starting_sat(), Sat(Sat::SUPPLY)); - } - - #[test] - fn starting_sats() { - let mut sat = 0; - - let mut epoch_sats = Vec::new(); - - for epoch in 0..34 { - epoch_sats.push(sat); - sat += SUBSIDY_HALVING_INTERVAL * Epoch(epoch).subsidy(); - } - - assert_eq!(Epoch::STARTING_SATS.as_slice(), epoch_sats); - assert_eq!(Epoch::STARTING_SATS.len(), 34); - } - - #[test] - fn subsidy() { - assert_eq!(Epoch(0).subsidy(), 5000000000); - assert_eq!(Epoch(1).subsidy(), 2500000000); - assert_eq!(Epoch(32).subsidy(), 1); - assert_eq!(Epoch(33).subsidy(), 0); - } - - #[test] - fn starting_height() { - assert_eq!(Epoch(0).starting_height(), 0); - assert_eq!(Epoch(1).starting_height(), SUBSIDY_HALVING_INTERVAL); - assert_eq!(Epoch(2).starting_height(), SUBSIDY_HALVING_INTERVAL * 2); - } - - #[test] - fn from_height() { - assert_eq!(Epoch::from(Height(0)), 0); - assert_eq!(Epoch::from(Height(SUBSIDY_HALVING_INTERVAL)), 1); - assert_eq!(Epoch::from(Height(SUBSIDY_HALVING_INTERVAL) + 1), 1); - } - - #[test] - fn from_sat() { - for (epoch, starting_sat) in Epoch::STARTING_SATS.into_iter().enumerate() { - if epoch > 0 { - assert_eq!( - Epoch::from(Sat(starting_sat.n() - 1)), - Epoch(epoch as u64 - 1) - ); - } - assert_eq!(Epoch::from(starting_sat), Epoch(epoch as u64)); - assert_eq!(Epoch::from(starting_sat + 1), Epoch(epoch as u64)); - } - assert_eq!(Epoch::from(Sat(0)), 0); - assert_eq!(Epoch::from(Sat(1)), 0); - assert_eq!(Epoch::from(Epoch(1).starting_sat()), 1); - assert_eq!(Epoch::from(Epoch(1).starting_sat() + 1), 1); - assert_eq!(Epoch::from(Sat(u64::max_value())), 33); - } - - #[test] - fn eq() { - assert_eq!(Epoch(0), 0); - assert_eq!(Epoch(100), 100); - } - - #[test] - fn first_post_subsidy() { - assert_eq!(Epoch::FIRST_POST_SUBSIDY.subsidy(), 0); - assert!((Epoch(Epoch::FIRST_POST_SUBSIDY.0 - 1)).subsidy() > 0); - } -} diff --git a/components/ordhook-core/src/ord/height.rs b/components/ordhook-core/src/ord/height.rs deleted file mode 100644 index a7455d6..0000000 --- a/components/ordhook-core/src/ord/height.rs +++ /dev/null @@ -1,50 +0,0 @@ -use std::ops::{Add, Sub}; - -use super::{epoch::Epoch, sat::Sat, *}; -// use std::fmt::Display; - -#[derive(Copy, Clone, Debug, Ord, Eq, PartialEq, PartialOrd)] -pub struct Height(pub u64); - -impl Height { - pub fn n(self) -> u64 { - self.0 - } - - pub fn subsidy(self) -> u64 { - Epoch::from(self).subsidy() - } - - pub fn starting_sat(self) -> Sat { - let epoch = Epoch::from(self); - let epoch_starting_sat = epoch.starting_sat(); - let epoch_starting_height = epoch.starting_height(); - epoch_starting_sat + (self - epoch_starting_height.n()).n() * epoch.subsidy() - } - - pub fn period_offset(self) -> u64 { - self.0 % DIFFCHANGE_INTERVAL - } -} - -impl Add for Height { - type Output = Self; - - fn add(self, other: u64) -> Height { - Self(self.0 + other) - } -} - -impl Sub for Height { - type Output = Self; - - fn sub(self, other: u64) -> Height { - Self(self.0 - other) - } -} - -impl PartialEq for Height { - fn eq(&self, other: &u64) -> bool { - self.0 == *other - } -} diff --git a/components/ordhook-core/src/ord/inscription.rs b/components/ordhook-core/src/ord/inscription.rs deleted file mode 100644 index a3fbc72..0000000 --- a/components/ordhook-core/src/ord/inscription.rs +++ /dev/null @@ -1,660 +0,0 @@ -use std::io::Cursor; - -use chainhook_sdk::bitcoin::{hashes::Hash, Txid}; - -use super::{inscription_id::InscriptionId, media::Media}; - -use { - super::*, - chainhook_sdk::bitcoin::{ - blockdata::{ - opcodes, - script::{self, PushBytesBuf}, - }, - ScriptBuf, - }, - std::str, -}; - -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq, Default)] -pub struct Inscription { - pub body: Option>, - pub content_encoding: Option>, - pub content_type: Option>, - pub duplicate_field: bool, - pub incomplete_field: bool, - pub metadata: Option>, - pub metaprotocol: Option>, - pub parent: Option>, - pub pointer: Option>, - pub unrecognized_even_field: bool, - pub delegate: Option>, -} - -impl Inscription { - #[cfg(test)] - pub(crate) fn new(content_type: Option>, body: Option>) -> Self { - Self { - content_type, - body, - ..Default::default() - } - } - - pub(crate) fn pointer_value(pointer: u64) -> Vec { - let mut bytes = pointer.to_le_bytes().to_vec(); - - while bytes.last().copied() == Some(0) { - bytes.pop(); - } - - bytes - } - - pub(crate) fn append_reveal_script_to_builder( - &self, - mut builder: script::Builder, - ) -> script::Builder { - builder = builder - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::all::OP_IF) - .push_slice(envelope::PROTOCOL_ID); - - if let Some(content_type) = self.content_type.clone() { - builder = builder - .push_slice(envelope::CONTENT_TYPE_TAG) - .push_slice(PushBytesBuf::try_from(content_type).unwrap()); - } - - if let Some(content_encoding) = self.content_encoding.clone() { - builder = builder - .push_slice(envelope::CONTENT_ENCODING_TAG) - .push_slice(PushBytesBuf::try_from(content_encoding).unwrap()); - } - - if let Some(protocol) = self.metaprotocol.clone() { - builder = builder - .push_slice(envelope::METAPROTOCOL_TAG) - .push_slice(PushBytesBuf::try_from(protocol).unwrap()); - } - - if let Some(parent) = self.parent.clone() { - builder = builder - .push_slice(envelope::PARENT_TAG) - .push_slice(PushBytesBuf::try_from(parent).unwrap()); - } - - if let Some(pointer) = self.pointer.clone() { - builder = builder - .push_slice(envelope::POINTER_TAG) - .push_slice(PushBytesBuf::try_from(pointer).unwrap()); - } - - if let Some(metadata) = &self.metadata { - for chunk in metadata.chunks(520) { - builder = builder.push_slice(envelope::METADATA_TAG); - builder = builder.push_slice(PushBytesBuf::try_from(chunk.to_vec()).unwrap()); - } - } - - if let Some(body) = &self.body { - builder = builder.push_slice(envelope::BODY_TAG); - for chunk in body.chunks(520) { - builder = builder.push_slice(PushBytesBuf::try_from(chunk.to_vec()).unwrap()); - } - } - - builder.push_opcode(opcodes::all::OP_ENDIF) - } - - #[cfg(test)] - pub(crate) fn append_reveal_script(&self, builder: script::Builder) -> ScriptBuf { - self.append_reveal_script_to_builder(builder).into_script() - } - - pub(crate) fn append_batch_reveal_script_to_builder( - inscriptions: &[Inscription], - mut builder: script::Builder, - ) -> script::Builder { - for inscription in inscriptions { - builder = inscription.append_reveal_script_to_builder(builder); - } - - builder - } - - pub(crate) fn append_batch_reveal_script( - inscriptions: &[Inscription], - builder: script::Builder, - ) -> ScriptBuf { - Inscription::append_batch_reveal_script_to_builder(inscriptions, builder).into_script() - } - - pub(crate) fn media(&self) -> Media { - if self.body.is_none() { - return Media::Unknown; - } - - let Some(content_type) = self.content_type() else { - return Media::Unknown; - }; - - content_type.parse().unwrap_or(Media::Unknown) - } - - pub(crate) fn body(&self) -> Option<&[u8]> { - Some(self.body.as_ref()?) - } - - pub(crate) fn into_body(self) -> Option> { - self.body - } - - pub(crate) fn content_length(&self) -> Option { - Some(self.body()?.len()) - } - - pub(crate) fn content_type(&self) -> Option<&str> { - str::from_utf8(self.content_type.as_ref()?).ok() - } - - pub(crate) fn metaprotocol(&self) -> Option<&str> { - str::from_utf8(self.metaprotocol.as_ref()?).ok() - } - - fn inscription_id_field(field: &Option>) -> Option { - let value = field.as_ref()?; - - if value.len() < Txid::LEN { - return None; - } - - if value.len() > Txid::LEN + 4 { - return None; - } - - let (txid, index) = value.split_at(Txid::LEN); - - if let Some(last) = index.last() { - // Accept fixed length encoding with 4 bytes (with potential trailing zeroes) - // or variable length (no trailing zeroes) - if index.len() != 4 && *last == 0 { - return None; - } - } - - let txid = Txid::from_slice(txid).unwrap(); - - let index = [ - index.first().copied().unwrap_or(0), - index.get(1).copied().unwrap_or(0), - index.get(2).copied().unwrap_or(0), - index.get(3).copied().unwrap_or(0), - ]; - - let index = u32::from_le_bytes(index); - - Some(InscriptionId { txid, index }) - } - - pub(crate) fn delegate(&self) -> Option { - Self::inscription_id_field(&self.delegate) - } - - pub(crate) fn metadata(&self) -> Option { - ciborium::from_reader(Cursor::new(self.metadata.as_ref()?)).ok() - } - - pub(crate) fn parent(&self) -> Option { - use chainhook_sdk::bitcoin::hash_types::Txid as TXID_LEN; - let value = self.parent.as_ref()?; - - if value.len() < TXID_LEN::LEN { - return None; - } - - if value.len() > TXID_LEN::LEN + 4 { - return None; - } - - let (txid, index) = value.split_at(TXID_LEN::LEN); - - if let Some(last) = index.last() { - // Accept fixed length encoding with 4 bytes (with potential trailing zeroes) - // or variable length (no trailing zeroes) - if index.len() != 4 && *last == 0 { - return None; - } - } - - let txid = Txid::from_slice(txid).unwrap(); - - let index = [ - index.first().copied().unwrap_or(0), - index.get(1).copied().unwrap_or(0), - index.get(2).copied().unwrap_or(0), - index.get(3).copied().unwrap_or(0), - ]; - - let index = u32::from_le_bytes(index); - - Some(InscriptionId { txid, index }) - } - - pub(crate) fn pointer(&self) -> Option { - let value = self.pointer.as_ref()?; - - if value.iter().skip(8).copied().any(|byte| byte != 0) { - return None; - } - - let pointer = [ - value.first().copied().unwrap_or(0), - value.get(1).copied().unwrap_or(0), - value.get(2).copied().unwrap_or(0), - value.get(3).copied().unwrap_or(0), - value.get(4).copied().unwrap_or(0), - value.get(5).copied().unwrap_or(0), - value.get(6).copied().unwrap_or(0), - value.get(7).copied().unwrap_or(0), - ]; - - Some(u64::from_le_bytes(pointer)) - } - - #[cfg(test)] - pub(crate) fn to_witness(&self) -> chainhook_sdk::bitcoin::Witness { - let builder = script::Builder::new(); - - let script = self.append_reveal_script(builder); - - let mut witness = chainhook_sdk::bitcoin::Witness::new(); - - witness.push(script); - witness.push([]); - - witness - } -} - -#[cfg(test)] -mod tests { - use chainhook_sdk::bitcoin::Witness; - - use super::*; - - fn inscription(content_type: &str, body: impl AsRef<[u8]>) -> Inscription { - Inscription::new(Some(content_type.into()), Some(body.as_ref().into())) - } - - fn envelope(payload: &[&[u8]]) -> Witness { - let mut builder = script::Builder::new() - .push_opcode(opcodes::OP_FALSE) - .push_opcode(opcodes::all::OP_IF); - - for data in payload { - let mut buf = PushBytesBuf::new(); - buf.extend_from_slice(data).unwrap(); - builder = builder.push_slice(buf); - } - - let script = builder.push_opcode(opcodes::all::OP_ENDIF).into_script(); - - Witness::from_slice(&[script.into_bytes(), Vec::new()]) - } - - #[test] - fn reveal_script_chunks_body() { - assert_eq!( - inscription("foo", []) - .append_reveal_script(script::Builder::new()) - .instructions() - .count(), - 7 - ); - - assert_eq!( - inscription("foo", [0; 1]) - .append_reveal_script(script::Builder::new()) - .instructions() - .count(), - 8 - ); - - assert_eq!( - inscription("foo", [0; 520]) - .append_reveal_script(script::Builder::new()) - .instructions() - .count(), - 8 - ); - - assert_eq!( - inscription("foo", [0; 521]) - .append_reveal_script(script::Builder::new()) - .instructions() - .count(), - 9 - ); - - assert_eq!( - inscription("foo", [0; 1040]) - .append_reveal_script(script::Builder::new()) - .instructions() - .count(), - 9 - ); - - assert_eq!( - inscription("foo", [0; 1041]) - .append_reveal_script(script::Builder::new()) - .instructions() - .count(), - 10 - ); - } - - #[test] - fn reveal_script_chunks_metadata() { - assert_eq!( - Inscription { - metadata: None, - ..Default::default() - } - .append_reveal_script(script::Builder::new()) - .instructions() - .count(), - 4 - ); - - assert_eq!( - Inscription { - metadata: Some(Vec::new()), - ..Default::default() - } - .append_reveal_script(script::Builder::new()) - .instructions() - .count(), - 4 - ); - - assert_eq!( - Inscription { - metadata: Some(vec![0; 1]), - ..Default::default() - } - .append_reveal_script(script::Builder::new()) - .instructions() - .count(), - 6 - ); - - assert_eq!( - Inscription { - metadata: Some(vec![0; 520]), - ..Default::default() - } - .append_reveal_script(script::Builder::new()) - .instructions() - .count(), - 6 - ); - - assert_eq!( - Inscription { - metadata: Some(vec![0; 521]), - ..Default::default() - } - .append_reveal_script(script::Builder::new()) - .instructions() - .count(), - 8 - ); - } - - #[test] - fn inscription_with_no_parent_field_has_no_parent() { - assert!(Inscription { - parent: None, - ..Default::default() - } - .parent() - .is_none()); - } - - #[test] - fn inscription_with_parent_field_shorter_than_txid_length_has_no_parent() { - assert!(Inscription { - parent: Some(vec![]), - ..Default::default() - } - .parent() - .is_none()); - } - - #[test] - fn inscription_with_parent_field_longer_than_txid_and_index_has_no_parent() { - assert!(Inscription { - parent: Some(vec![1; 37]), - ..Default::default() - } - .parent() - .is_none()); - } - - #[test] - fn inscription_with_parent_field_index_with_trailing_zeroes_and_fixed_length_has_parent() { - let mut parent = vec![1; 36]; - - parent[35] = 0; - - assert!(Inscription { - parent: Some(parent), - ..Default::default() - } - .parent() - .is_some()); - } - - #[test] - fn inscription_with_parent_field_index_with_trailing_zeroes_and_variable_length_has_no_parent() - { - let mut parent = vec![1; 35]; - - parent[34] = 0; - - assert!(Inscription { - parent: Some(parent), - ..Default::default() - } - .parent() - .is_none()); - } - - #[test] - fn inscription_parent_txid_is_deserialized_correctly() { - assert_eq!( - Inscription { - parent: Some(vec![ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, - 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, - 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, - ]), - ..Default::default() - } - .parent() - .unwrap() - .txid, - "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100" - .parse() - .unwrap() - ); - } - - #[test] - fn inscription_parent_with_zero_byte_index_field_is_deserialized_correctly() { - assert_eq!( - Inscription { - parent: Some(vec![1; 32]), - ..Default::default() - } - .parent() - .unwrap() - .index, - 0 - ); - } - - #[test] - fn inscription_parent_with_one_byte_index_field_is_deserialized_correctly() { - assert_eq!( - Inscription { - parent: Some(vec![ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01 - ]), - ..Default::default() - } - .parent() - .unwrap() - .index, - 1 - ); - } - - #[test] - fn inscription_parent_with_two_byte_index_field_is_deserialized_correctly() { - assert_eq!( - Inscription { - parent: Some(vec![ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x02 - ]), - ..Default::default() - } - .parent() - .unwrap() - .index, - 0x0201, - ); - } - - #[test] - fn inscription_parent_with_three_byte_index_field_is_deserialized_correctly() { - assert_eq!( - Inscription { - parent: Some(vec![ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x02, 0x03 - ]), - ..Default::default() - } - .parent() - .unwrap() - .index, - 0x030201, - ); - } - - #[test] - fn inscription_parent_with_four_byte_index_field_is_deserialized_correctly() { - assert_eq!( - Inscription { - parent: Some(vec![ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x02, 0x03, 0x04, - ]), - ..Default::default() - } - .parent() - .unwrap() - .index, - 0x04030201, - ); - } - - #[test] - fn pointer_decode() { - assert_eq!( - Inscription { - pointer: None, - ..Default::default() - } - .pointer(), - None - ); - assert_eq!( - Inscription { - pointer: Some(vec![0]), - ..Default::default() - } - .pointer(), - Some(0), - ); - assert_eq!( - Inscription { - pointer: Some(vec![1, 2, 3, 4, 5, 6, 7, 8]), - ..Default::default() - } - .pointer(), - Some(0x0807060504030201), - ); - assert_eq!( - Inscription { - pointer: Some(vec![1, 2, 3, 4, 5, 6]), - ..Default::default() - } - .pointer(), - Some(0x0000060504030201), - ); - assert_eq!( - Inscription { - pointer: Some(vec![1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0]), - ..Default::default() - } - .pointer(), - Some(0x0807060504030201), - ); - assert_eq!( - Inscription { - pointer: Some(vec![1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 1]), - ..Default::default() - } - .pointer(), - None, - ); - assert_eq!( - Inscription { - pointer: Some(vec![1, 2, 3, 4, 5, 6, 7, 8, 1]), - ..Default::default() - } - .pointer(), - None, - ); - } - - #[test] - fn pointer_encode() { - assert_eq!( - Inscription { - pointer: None, - ..Default::default() - } - .to_witness(), - envelope(&[b"ord"]), - ); - - assert_eq!( - Inscription { - pointer: Some(vec![1, 2, 3]), - ..Default::default() - } - .to_witness(), - envelope(&[b"ord", &[2], &[1, 2, 3]]), - ); - } -} diff --git a/components/ordhook-core/src/ord/inscription_id.rs b/components/ordhook-core/src/ord/inscription_id.rs deleted file mode 100644 index 0ce336a..0000000 --- a/components/ordhook-core/src/ord/inscription_id.rs +++ /dev/null @@ -1,231 +0,0 @@ -use std::{ - fmt::{Display, Formatter}, - str::FromStr, -}; - -use chainhook_sdk::bitcoin::Txid; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -use super::deserialize_from_str::DeserializeFromStr; - -#[derive(Debug, PartialEq, Copy, Clone, Hash, Eq)] -pub struct InscriptionId { - pub(crate) txid: Txid, - pub(crate) index: u32, -} - -impl<'de> Deserialize<'de> for InscriptionId { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Ok(DeserializeFromStr::deserialize(deserializer)?.0) - } -} - -impl Serialize for InscriptionId { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.collect_str(self) - } -} - -impl Display for InscriptionId { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - write!(f, "{}i{}", self.txid, self.index) - } -} - -#[derive(Debug)] -pub enum ParseError { - Character(char), - Length(usize), - Separator(char), - Txid(chainhook_sdk::bitcoin::hashes::hex::HexToArrayError), - Index(std::num::ParseIntError), -} - -impl Display for ParseError { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - match self { - Self::Character(c) => write!(f, "invalid character: '{c}'"), - Self::Length(len) => write!(f, "invalid length: {len}"), - Self::Separator(c) => write!(f, "invalid seprator: `{c}`"), - Self::Txid(err) => write!(f, "invalid txid: {err}"), - Self::Index(err) => write!(f, "invalid index: {err}"), - } - } -} - -impl std::error::Error for ParseError {} - -impl FromStr for InscriptionId { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - if let Some(char) = s.chars().find(|char| !char.is_ascii()) { - return Err(ParseError::Character(char)); - } - - const TXID_LEN: usize = 64; - const MIN_LEN: usize = TXID_LEN + 2; - - if s.len() < MIN_LEN { - return Err(ParseError::Length(s.len())); - } - - let txid = &s[..TXID_LEN]; - - let separator = s.chars().nth(TXID_LEN).unwrap(); - - if separator != 'i' { - return Err(ParseError::Separator(separator)); - } - - let vout = &s[TXID_LEN + 1..]; - - Ok(Self { - txid: txid.parse().map_err(ParseError::Txid)?, - index: vout.parse().map_err(ParseError::Index)?, - }) - } -} - -impl From for InscriptionId { - fn from(txid: Txid) -> Self { - Self { txid, index: 0 } - } -} - -#[cfg(test)] -mod tests { - - macro_rules! assert_matches { - ($expression:expr, $( $pattern:pat_param )|+ $( if $guard:expr )? $(,)?) => { - match $expression { - $( $pattern )|+ $( if $guard )? => {} - left => panic!( - "assertion failed: (left ~= right)\n left: `{:?}`\n right: `{}`", - left, - stringify!($($pattern)|+ $(if $guard)?) - ), - } - } - } - - use super::*; - - fn txid(n: u64) -> Txid { - let hex = format!("{n:x}"); - - if hex.is_empty() || hex.len() > 1 { - panic!(); - } - - hex.repeat(64).parse().unwrap() - } - - pub(crate) fn inscription_id(n: u32) -> InscriptionId { - let hex = format!("{n:x}"); - - if hex.is_empty() || hex.len() > 1 { - panic!(); - } - - format!("{}i{n}", hex.repeat(64)).parse().unwrap() - } - - #[test] - fn display() { - assert_eq!( - inscription_id(1).to_string(), - "1111111111111111111111111111111111111111111111111111111111111111i1", - ); - assert_eq!( - InscriptionId { - txid: txid(1), - index: 0, - } - .to_string(), - "1111111111111111111111111111111111111111111111111111111111111111i0", - ); - assert_eq!( - InscriptionId { - txid: txid(1), - index: 0xFFFFFFFF, - } - .to_string(), - "1111111111111111111111111111111111111111111111111111111111111111i4294967295", - ); - } - - #[test] - fn from_str() { - assert_eq!( - "1111111111111111111111111111111111111111111111111111111111111111i1" - .parse::() - .unwrap(), - inscription_id(1), - ); - assert_eq!( - "1111111111111111111111111111111111111111111111111111111111111111i4294967295" - .parse::() - .unwrap(), - InscriptionId { - txid: txid(1), - index: 0xFFFFFFFF, - }, - ); - assert_eq!( - "1111111111111111111111111111111111111111111111111111111111111111i4294967295" - .parse::() - .unwrap(), - InscriptionId { - txid: txid(1), - index: 0xFFFFFFFF, - }, - ); - } - - #[test] - fn from_str_bad_character() { - assert_matches!( - "→".parse::(), - Err(ParseError::Character('→')), - ); - } - - #[test] - fn from_str_bad_length() { - assert_matches!("foo".parse::(), Err(ParseError::Length(3))); - } - - #[test] - fn from_str_bad_separator() { - assert_matches!( - "0000000000000000000000000000000000000000000000000000000000000000x0" - .parse::(), - Err(ParseError::Separator('x')), - ); - } - - #[test] - fn from_str_bad_index() { - assert_matches!( - "0000000000000000000000000000000000000000000000000000000000000000ifoo" - .parse::(), - Err(ParseError::Index(_)), - ); - } - - #[test] - fn from_str_bad_txid() { - assert_matches!( - "x000000000000000000000000000000000000000000000000000000000000000i0" - .parse::(), - Err(ParseError::Txid(_)), - ); - } -} diff --git a/components/ordhook-core/src/ord/media.rs b/components/ordhook-core/src/ord/media.rs deleted file mode 100644 index 2c64817..0000000 --- a/components/ordhook-core/src/ord/media.rs +++ /dev/null @@ -1,102 +0,0 @@ -use std::{ - fmt::{Display, Formatter}, - str::FromStr, -}; - -use anyhow::{anyhow, Error}; - -#[derive(Debug, PartialEq, Copy, Clone)] -pub(crate) enum Media { - Audio, - Code(Language), - Font, - Iframe, - Image, - Markdown, - Model, - Pdf, - Text, - Unknown, - Video, -} - -#[derive(Debug, PartialEq, Copy, Clone)] -pub(crate) enum Language { - Css, - JavaScript, - Json, - Python, - Yaml, -} - -impl Display for Language { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::Css => "css", - Self::JavaScript => "javascript", - Self::Json => "json", - Self::Python => "python", - Self::Yaml => "yaml", - } - ) - } -} - -impl Media { - #[rustfmt::skip] - const TABLE: &'static [(&'static str, Media, &'static [&'static str])] = &[ - ("application/cbor", Media::Unknown, &["cbor"]), - ("application/json", Media::Code(Language::Json), &["json"]), - ("application/octet-stream", Media::Unknown, &["bin"]), - ("application/pdf", Media::Pdf, &["pdf"]), - ("application/pgp-signature", Media::Text, &["asc"]), - ("application/protobuf", Media::Unknown, &["binpb"]), - ("application/x-javascript", Media::Code(Language::JavaScript), &[]), - ("application/yaml", Media::Code(Language::Yaml), &["yaml", "yml"]), - ("audio/flac", Media::Audio, &["flac"]), - ("audio/mpeg", Media::Audio, &["mp3"]), - ("audio/wav", Media::Audio, &["wav"]), - ("font/otf", Media::Font, &["otf"]), - ("font/ttf", Media::Font, &["ttf"]), - ("font/woff", Media::Font, &["woff"]), - ("font/woff2", Media::Font, &["woff2"]), - ("image/apng", Media::Image, &["apng"]), - ("image/avif", Media::Image, &[]), - ("image/gif", Media::Image, &["gif"]), - ("image/jpeg", Media::Image, &["jpg", "jpeg"]), - ("image/png", Media::Image, &["png"]), - ("image/svg+xml", Media::Iframe, &["svg"]), - ("image/webp", Media::Image, &["webp"]), - ("model/gltf+json", Media::Model, &["gltf"]), - ("model/gltf-binary", Media::Model, &["glb"]), - ("model/stl", Media::Unknown, &["stl"]), - ("text/css", Media::Code(Language::Css), &["css"]), - ("text/html", Media::Iframe, &[]), - ("text/html;charset=utf-8", Media::Iframe, &["html"]), - ("text/javascript", Media::Code(Language::JavaScript), &["js"]), - ("text/markdown", Media::Markdown, &[]), - ("text/markdown;charset=utf-8", Media::Markdown, &["md"]), - ("text/plain", Media::Text, &[]), - ("text/plain;charset=utf-8", Media::Text, &["txt"]), - ("text/x-python", Media::Code(Language::Python), &["py"]), - ("video/mp4", Media::Video, &["mp4"]), - ("video/webm", Media::Video, &["webm"]), - ]; -} - -impl FromStr for Media { - type Err = Error; - - fn from_str(s: &str) -> Result { - for entry in Self::TABLE { - if entry.0 == s { - return Ok(entry.1); - } - } - - Err(anyhow!("unknown content type: {s}")) - } -} diff --git a/components/ordhook-core/src/ord/mod.rs b/components/ordhook-core/src/ord/mod.rs deleted file mode 100644 index 4a4560d..0000000 --- a/components/ordhook-core/src/ord/mod.rs +++ /dev/null @@ -1,26 +0,0 @@ -#![allow(dead_code)] -#![allow(unused_variables)] - -type Result = std::result::Result; - -use chainhook_sdk::types::BitcoinNetwork; - -pub mod chain; -pub mod deserialize_from_str; -pub mod envelope; -pub mod epoch; -pub mod height; -pub mod inscription; -pub mod inscription_id; -pub mod media; -pub mod sat; -pub mod sat_point; -pub mod degree; -pub mod rarity; - -const DIFFCHANGE_INTERVAL: u64 = - chainhook_sdk::bitcoincore_rpc::bitcoin::blockdata::constants::DIFFCHANGE_INTERVAL as u64; -const SUBSIDY_HALVING_INTERVAL: u64 = - chainhook_sdk::bitcoincore_rpc::bitcoin::blockdata::constants::SUBSIDY_HALVING_INTERVAL as u64; -const CYCLE_EPOCHS: u64 = 6; -pub const COIN_VALUE: u64 = 100_000_000; diff --git a/components/ordhook-core/src/ord/rarity.rs b/components/ordhook-core/src/ord/rarity.rs deleted file mode 100644 index b68dd1d..0000000 --- a/components/ordhook-core/src/ord/rarity.rs +++ /dev/null @@ -1,184 +0,0 @@ -use std::fmt::{Display, Formatter}; - -use degree::Degree; -use sat::Sat; - -use super::*; - -#[derive(Debug, PartialEq, PartialOrd, Copy, Clone)] -pub enum Rarity { - Common, - Uncommon, - Rare, - Epic, - Legendary, - Mythic, -} - -impl From for u8 { - fn from(rarity: Rarity) -> Self { - rarity as u8 - } -} - -// impl TryFrom for Rarity { -// type Error = u8; - -// fn try_from(rarity: u8) -> Result { -// match rarity { -// 0 => Ok(Self::Common), -// 1 => Ok(Self::Uncommon), -// 2 => Ok(Self::Rare), -// 3 => Ok(Self::Epic), -// 4 => Ok(Self::Legendary), -// 5 => Ok(Self::Mythic), -// n => Err(n), -// } -// } -// } - -impl Display for Rarity { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::Common => "common", - Self::Uncommon => "uncommon", - Self::Rare => "rare", - Self::Epic => "epic", - Self::Legendary => "legendary", - Self::Mythic => "mythic", - } - ) - } -} - -impl From for Rarity { - fn from(sat: Sat) -> Self { - let Degree { - hour, - minute, - second, - third, - } = sat.degree(); - - if hour == 0 && minute == 0 && second == 0 && third == 0 { - Self::Mythic - } else if minute == 0 && second == 0 && third == 0 { - Self::Legendary - } else if minute == 0 && third == 0 { - Self::Epic - } else if second == 0 && third == 0 { - Self::Rare - } else if third == 0 { - Self::Uncommon - } else { - Self::Common - } - } -} - -// impl FromStr for Rarity { -// type Err = String; - -// fn from_str(s: &str) -> Result { -// match s { -// "common" => Ok(Self::Common), -// "uncommon" => Ok(Self::Uncommon), -// "rare" => Ok(Self::Rare), -// "epic" => Ok(Self::Epic), -// "legendary" => Ok(Self::Legendary), -// "mythic" => Ok(Self::Mythic), -// _ => Err(format!("invalid rarity `{s}`")), -// } -// } -// } - -// #[cfg(test)] -// mod tests { -// use super::*; - -// #[test] -// fn rarity() { -// assert_eq!(Sat(0).rarity(), Rarity::Mythic); -// assert_eq!(Sat(1).rarity(), Rarity::Common); - -// assert_eq!(Sat(50 * COIN_VALUE - 1).rarity(), Rarity::Common); -// assert_eq!(Sat(50 * COIN_VALUE).rarity(), Rarity::Uncommon); -// assert_eq!(Sat(50 * COIN_VALUE + 1).rarity(), Rarity::Common); - -// assert_eq!( -// Sat(50 * COIN_VALUE * u64::from(DIFFCHANGE_INTERVAL) - 1).rarity(), -// Rarity::Common -// ); -// assert_eq!( -// Sat(50 * COIN_VALUE * u64::from(DIFFCHANGE_INTERVAL)).rarity(), -// Rarity::Rare -// ); -// assert_eq!( -// Sat(50 * COIN_VALUE * u64::from(DIFFCHANGE_INTERVAL) + 1).rarity(), -// Rarity::Common -// ); - -// assert_eq!( -// Sat(50 * COIN_VALUE * u64::from(SUBSIDY_HALVING_INTERVAL) - 1).rarity(), -// Rarity::Common -// ); -// assert_eq!( -// Sat(50 * COIN_VALUE * u64::from(SUBSIDY_HALVING_INTERVAL)).rarity(), -// Rarity::Epic -// ); -// assert_eq!( -// Sat(50 * COIN_VALUE * u64::from(SUBSIDY_HALVING_INTERVAL) + 1).rarity(), -// Rarity::Common -// ); - -// assert_eq!(Sat(2067187500000000 - 1).rarity(), Rarity::Common); -// assert_eq!(Sat(2067187500000000).rarity(), Rarity::Legendary); -// assert_eq!(Sat(2067187500000000 + 1).rarity(), Rarity::Common); -// } - -// #[test] -// fn from_str_and_deserialize_ok() { -// #[track_caller] -// fn case(s: &str, expected: Rarity) { -// let actual = s.parse::().unwrap(); -// assert_eq!(actual, expected); -// let round_trip = actual.to_string().parse::().unwrap(); -// assert_eq!(round_trip, expected); -// let serialized = serde_json::to_string(&expected).unwrap(); -// assert!(serde_json::from_str::(&serialized).is_ok()); -// } - -// case("common", Rarity::Common); -// case("uncommon", Rarity::Uncommon); -// case("rare", Rarity::Rare); -// case("epic", Rarity::Epic); -// case("legendary", Rarity::Legendary); -// case("mythic", Rarity::Mythic); -// } - -// #[test] -// fn conversions_with_u8() { -// for &expected in &[ -// Rarity::Common, -// Rarity::Uncommon, -// Rarity::Rare, -// Rarity::Epic, -// Rarity::Legendary, -// Rarity::Mythic, -// ] { -// let n: u8 = expected.into(); -// let actual = Rarity::try_from(n).unwrap(); -// assert_eq!(actual, expected); -// } - -// assert_eq!(Rarity::try_from(6), Err(6)); -// } - -// #[test] -// fn error() { -// assert_eq!("foo".parse::().unwrap_err(), "invalid rarity `foo`"); -// } -// } diff --git a/components/ordhook-core/src/ord/sat.rs b/components/ordhook-core/src/ord/sat.rs deleted file mode 100644 index 8dc5818..0000000 --- a/components/ordhook-core/src/ord/sat.rs +++ /dev/null @@ -1,214 +0,0 @@ -use std::ops::{Add, AddAssign}; - -use degree::Degree; - -use super::{epoch::Epoch, height::Height, *}; - -#[derive(Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd, Deserialize, Serialize)] -#[serde(transparent)] -pub struct Sat(pub u64); - -impl Sat { - pub(crate) const LAST: Self = Self(Self::SUPPLY - 1); - pub(crate) const SUPPLY: u64 = 2099999997690000; - - pub fn degree(self) -> Degree { - self.into() - } - - pub(crate) fn n(self) -> u64 { - self.0 - } - - pub(crate) fn height(self) -> Height { - self.epoch().starting_height() + self.epoch_position() / self.epoch().subsidy() - } - - pub(crate) fn cycle(self) -> u64 { - Epoch::from(self).0 / CYCLE_EPOCHS - } - - pub(crate) fn percentile(self) -> String { - format!("{}%", (self.0 as f64 / Self::LAST.0 as f64) * 100.0) - } - - pub(crate) fn epoch(self) -> Epoch { - self.into() - } - - pub(crate) fn third(self) -> u64 { - self.epoch_position() % self.epoch().subsidy() - } - - pub(crate) fn epoch_position(self) -> u64 { - self.0 - self.epoch().starting_sat().0 - } - - /// `Sat::rarity` is expensive and is called frequently when indexing. - /// Sat::is_common only checks if self is `Rarity::Common` but is - /// much faster. - pub(crate) fn is_common(self) -> bool { - let epoch = self.epoch(); - (self.0 - epoch.starting_sat().0) % epoch.subsidy() != 0 - } - - pub(crate) fn name(self) -> String { - let mut x = Self::SUPPLY - self.0; - let mut name = String::new(); - while x > 0 { - name.push( - "abcdefghijklmnopqrstuvwxyz" - .chars() - .nth(((x - 1) % 26) as usize) - .unwrap(), - ); - x = (x - 1) / 26; - } - name.chars().rev().collect() - } -} - -impl PartialEq for Sat { - fn eq(&self, other: &u64) -> bool { - self.0 == *other - } -} - -impl PartialOrd for Sat { - fn partial_cmp(&self, other: &u64) -> Option { - self.0.partial_cmp(other) - } -} - -impl Add for Sat { - type Output = Self; - - fn add(self, other: u64) -> Sat { - Sat(self.0 + other) - } -} - -impl AddAssign for Sat { - fn add_assign(&mut self, other: u64) { - *self = Sat(self.0 + other); - } -} - -#[cfg(test)] -mod tests { - use super::COIN_VALUE; - - use super::*; - - #[test] - fn n() { - assert_eq!(Sat(1).n(), 1); - assert_eq!(Sat(100).n(), 100); - } - - #[test] - fn name() { - assert_eq!(Sat(0).name(), "nvtdijuwxlp"); - assert_eq!(Sat(1).name(), "nvtdijuwxlo"); - assert_eq!(Sat(26).name(), "nvtdijuwxkp"); - assert_eq!(Sat(27).name(), "nvtdijuwxko"); - assert_eq!(Sat(2099999997689999).name(), "a"); - assert_eq!(Sat(2099999997689999 - 1).name(), "b"); - assert_eq!(Sat(2099999997689999 - 25).name(), "z"); - assert_eq!(Sat(2099999997689999 - 26).name(), "aa"); - } - - #[test] - fn number() { - assert_eq!(Sat(2099999997689999).n(), 2099999997689999); - } - - #[test] - fn epoch_position() { - assert_eq!(Epoch(0).starting_sat().epoch_position(), 0); - assert_eq!((Epoch(0).starting_sat() + 100).epoch_position(), 100); - assert_eq!(Epoch(1).starting_sat().epoch_position(), 0); - assert_eq!(Epoch(2).starting_sat().epoch_position(), 0); - } - - #[test] - fn subsidy_position() { - assert_eq!(Sat(0).third(), 0); - assert_eq!(Sat(1).third(), 1); - assert_eq!( - Sat(Height(0).subsidy() - 1).third(), - Height(0).subsidy() - 1 - ); - assert_eq!(Sat(Height(0).subsidy()).third(), 0); - assert_eq!(Sat(Height(0).subsidy() + 1).third(), 1); - assert_eq!( - Sat(Epoch(1).starting_sat().n() + Epoch(1).subsidy()).third(), - 0 - ); - assert_eq!(Sat::LAST.third(), 0); - } - - #[test] - fn supply() { - let mut mined = 0; - - for height in 0.. { - let subsidy = Height(height).subsidy(); - - if subsidy == 0 { - break; - } - - mined += subsidy; - } - - assert_eq!(Sat::SUPPLY, mined); - } - - #[test] - fn last() { - assert_eq!(Sat::LAST, Sat::SUPPLY - 1); - } - - #[test] - fn eq() { - assert_eq!(Sat(0), 0); - assert_eq!(Sat(1), 1); - } - - #[test] - fn partial_ord() { - assert!(Sat(1) > 0); - assert!(Sat(0) < 1); - } - - #[test] - fn add() { - assert_eq!(Sat(0) + 1, 1); - assert_eq!(Sat(1) + 100, 101); - } - - #[test] - fn add_assign() { - let mut sat = Sat(0); - sat += 1; - assert_eq!(sat, 1); - sat += 100; - assert_eq!(sat, 101); - } - - #[test] - fn third() { - assert_eq!(Sat(0).third(), 0); - assert_eq!(Sat(50 * COIN_VALUE - 1).third(), 4999999999); - assert_eq!(Sat(50 * COIN_VALUE).third(), 0); - assert_eq!(Sat(50 * COIN_VALUE + 1).third(), 1); - } - - #[test] - fn percentile() { - assert_eq!(Sat(0).percentile(), "0%"); - assert_eq!(Sat(Sat::LAST.n() / 2).percentile(), "49.99999999999998%"); - assert_eq!(Sat::LAST.percentile(), "100%"); - } -} diff --git a/components/ordhook-core/src/ord/sat_point.rs b/components/ordhook-core/src/ord/sat_point.rs deleted file mode 100644 index 5f60541..0000000 --- a/components/ordhook-core/src/ord/sat_point.rs +++ /dev/null @@ -1,128 +0,0 @@ -use std::{ - fmt::{Display, Formatter}, - io, - str::FromStr, -}; - -use chainhook_sdk::bitcoincore_rpc::bitcoin::{ - self, - consensus::{Decodable, Encodable}, - OutPoint, -}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -use super::{deserialize_from_str::DeserializeFromStr, Result}; - -#[derive(Debug, PartialEq, Copy, Clone, Eq, PartialOrd, Ord)] -pub struct SatPoint { - pub(crate) outpoint: OutPoint, - pub(crate) offset: u64, -} - -impl Display for SatPoint { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - write!(f, "{}:{}", self.outpoint, self.offset) - } -} - -impl Encodable for SatPoint { - fn consensus_encode(&self, s: &mut S) -> Result { - let len = self.outpoint.consensus_encode(s)?; - Ok(len + self.offset.consensus_encode(s)?) - } -} - -impl Decodable for SatPoint { - fn consensus_decode( - d: &mut D, - ) -> Result { - Ok(SatPoint { - outpoint: Decodable::consensus_decode(d)?, - offset: Decodable::consensus_decode(d)?, - }) - } -} - -impl Serialize for SatPoint { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.collect_str(self) - } -} - -impl<'de> Deserialize<'de> for SatPoint { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Ok(DeserializeFromStr::deserialize(deserializer)?.0) - } -} - -impl FromStr for SatPoint { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - let (outpoint, offset) = s - .rsplit_once(':') - .ok_or_else(|| anyhow::anyhow!("invalid satpoint: {s}"))?; - - Ok(SatPoint { - outpoint: outpoint.parse()?, - offset: offset.parse()?, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn from_str_ok() { - assert_eq!( - "1111111111111111111111111111111111111111111111111111111111111111:1:1" - .parse::() - .unwrap(), - SatPoint { - outpoint: "1111111111111111111111111111111111111111111111111111111111111111:1" - .parse() - .unwrap(), - offset: 1, - } - ); - } - - #[test] - fn from_str_err() { - "abc".parse::().unwrap_err(); - - "abc:xyz".parse::().unwrap_err(); - - "1111111111111111111111111111111111111111111111111111111111111111:1" - .parse::() - .unwrap_err(); - - "1111111111111111111111111111111111111111111111111111111111111111:1:foo" - .parse::() - .unwrap_err(); - } - - #[test] - fn deserialize_ok() { - assert_eq!( - serde_json::from_str::( - "\"1111111111111111111111111111111111111111111111111111111111111111:1:1\"" - ) - .unwrap(), - SatPoint { - outpoint: "1111111111111111111111111111111111111111111111111111111111111111:1" - .parse() - .unwrap(), - offset: 1, - } - ); - } -} diff --git a/components/ordhook-core/src/service/mod.rs b/components/ordhook-core/src/service/mod.rs index d6a49c0..3edc98a 100644 --- a/components/ordhook-core/src/service/mod.rs +++ b/components/ordhook-core/src/service/mod.rs @@ -18,15 +18,15 @@ use crate::db::ordinals_pg; use crate::utils::bitcoind::bitcoind_wait_for_chain_tip; use crate::utils::monitoring::{start_serving_prometheus_metrics, PrometheusMonitoring}; use crate::{try_error, try_info}; -use chainhook_postgres::deadpool_postgres::Pool; use chainhook_postgres::{pg_begin, pg_pool, pg_pool_client}; use chainhook_sdk::observer::{ start_event_observer, BitcoinBlockDataCached, ObserverEvent, ObserverSidecar, }; -use chainhook_sdk::types::BlockIdentifier; +use chainhook_types::BlockIdentifier; use chainhook_sdk::utils::{BlockHeights, Context}; use crossbeam_channel::select; use dashmap::DashMap; +use deadpool_postgres::Pool; use fxhash::FxHasher; use std::collections::BTreeMap; @@ -135,7 +135,6 @@ impl Service { observer_command_rx, Some(observer_event_tx), Some(zmq_observer_sidecar), - None, inner_ctx, ); diff --git a/components/ordhook-core/src/utils/mod.rs b/components/ordhook-core/src/utils/mod.rs index af9a4da..48d625f 100644 --- a/components/ordhook-core/src/utils/mod.rs +++ b/components/ordhook-core/src/utils/mod.rs @@ -8,7 +8,7 @@ use std::{ path::PathBuf, }; -use chainhook_sdk::types::TransactionIdentifier; +use chainhook_types::TransactionIdentifier; pub fn read_file_content_at_path(file_path: &PathBuf) -> Result, String> { use std::fs::File; diff --git a/dockerfiles/components/ordhook.dockerfile b/dockerfiles/components/ordhook.dockerfile index c6d0853..08aaace 100644 --- a/dockerfiles/components/ordhook.dockerfile +++ b/dockerfiles/components/ordhook.dockerfile @@ -12,9 +12,7 @@ RUN rustup update 1.81 && rustup default 1.81 RUN mkdir /out COPY ./Cargo.toml /src/Cargo.toml COPY ./Cargo.lock /src/Cargo.lock -COPY ./components/chainhook-postgres /src/components/chainhook-postgres -COPY ./components/ordhook-core /src/components/ordhook-core -COPY ./components/ordhook-cli /src/components/ordhook-cli +COPY ./components /src/components COPY ./migrations /src/migrations RUN cargo build --features release --release diff --git a/migrations/ordinals/V15__inscription_parents.sql b/migrations/ordinals/V15__inscription_parents.sql new file mode 100644 index 0000000..5cc9e52 --- /dev/null +++ b/migrations/ordinals/V15__inscription_parents.sql @@ -0,0 +1,14 @@ +CREATE TABLE inscription_parents ( + inscription_id TEXT NOT NULL, + parent_inscription_id TEXT NOT NULL +); +ALTER TABLE inscription_parents ADD PRIMARY KEY (inscription_id, parent_inscription_id); +ALTER TABLE inscription_parents ADD CONSTRAINT inscription_parents FOREIGN KEY(inscription_id) REFERENCES inscriptions(inscription_id) ON DELETE CASCADE; + +-- Migrate from old `parent` column in `inscriptions` table. +INSERT INTO inscription_parents (inscription_id, parent_inscription_id) ( + SELECT inscription_id, parent AS parent_inscription_id + FROM inscriptions + WHERE parent IS NOT NULL +); +ALTER TABLE inscriptions DROP COLUMN parent;