diff --git a/Cargo.lock b/Cargo.lock index 17bcf43..222fdc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,14 +18,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "ahash" -version = "0.7.7" +name = "aead" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "getrandom 0.2.11", - "once_cell", - "version_check", + "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]] @@ -50,6 +74,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -83,6 +113,12 @@ dependencies = [ "backtrace", ] +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + [[package]] name = "arc-swap" version = "1.6.0" @@ -98,12 +134,6 @@ dependencies = [ "nodrop", ] -[[package]] -name = "arrayref" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" - [[package]] name = "arrayvec" version = "0.7.4" @@ -190,18 +220,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base-x" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" - -[[package]] -name = "base58" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" - [[package]] name = "base58" version = "0.2.0" @@ -238,6 +256,15 @@ 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" @@ -354,25 +381,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] -name = "block-buffer" -version = "0.7.3" +name = "bitvec" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ - "block-padding 0.1.5", - "byte-tools", - "byteorder", - "generic-array 0.12.4", -] - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "block-padding 0.2.1", - "generic-array 0.14.7", + "funty", + "radium", + "tap", + "wyz", ] [[package]] @@ -385,19 +402,19 @@ dependencies = [ ] [[package]] -name = "block-padding" -version = "0.1.5" +name = "bs58" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", -] +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] -name = "block-padding" -version = "0.2.1" +name = "bs58" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] [[package]] name = "bumpalo" @@ -406,10 +423,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] -name = "byte-tools" -version = "0.3.1" +name = "byte-slice-cast" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] name = "bytemuck" @@ -477,11 +494,11 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chainhook-sdk" -version = "0.12.5" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9f1d697633a4b8394f185f41634faaf555ba04f2881ac19e1db4b1bd0130155" +checksum = "8fd54af8c8b55ce530013e2dd510d76be08e9132c9918f81d8a0d3d86b6e4bde" dependencies = [ - "base58 0.2.0", + "base58", "base64 0.21.5", "bitcoincore-rpc", "bitcoincore-rpc-json", @@ -491,11 +508,12 @@ dependencies = [ "futures", "fxhash", "hex", - "hiro-system-kit 0.3.1", + "hiro-system-kit", "hyper", "lazy_static", "miniscript", - "rand 0.8.5", + "prometheus", + "rand", "regex", "reqwest", "rocket", @@ -504,7 +522,7 @@ dependencies = [ "serde-hex", "serde_derive", "serde_json", - "stacks-rpc-client", + "stacks-codec", "threadpool", "tokio", "zmq", @@ -512,9 +530,9 @@ dependencies = [ [[package]] name = "chainhook-types" -version = "1.3.3" +version = "1.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44b67edc1e9b87382a973d203eada222554a774d9fae55e7d40fb2accb372716" +checksum = "63f0d358e5c530dd6888e4678ef4ba3f7782525653a1012d33e96a48020c418d" dependencies = [ "hex", "schemars 0.8.16", @@ -565,6 +583,16 @@ 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.6.1" @@ -634,66 +662,34 @@ dependencies = [ "os_str_bytes", ] -[[package]] -name = "clarity-repl" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0935839b51a01d7436b31420713e0a9698d2920cfa0493482c9dbb4fe0a696" -dependencies = [ - "ansi_term", - "atty", - "chrono", - "clarity-vm", - "getrandom 0.2.11", - "hiro-system-kit 0.1.0", - "integer-sqrt", - "lazy_static", - "regex", - "reqwest", - "serde", - "serde_derive", - "serde_json", - "sha2 0.10.8", - "sha3 0.9.1", -] - [[package]] name = "clarity-vm" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90cebf93044798d7729008e9a467b16578aead7f0b3568219a2b20b421b683db" +checksum = "0af203c4eea9bb753ff2798c3bcf9f3fd8ea7a24bde7222d96f82fd9beb6d165" dependencies = [ + "hashbrown 0.14.3", "integer-sqrt", "lazy_static", - "rand 0.7.3", - "rand_chacha 0.2.2", + "rand", + "rand_chacha", "regex", - "rstest", - "rstest_reuse", "rusqlite", "serde", "serde_derive", "serde_json", "serde_stacker", "sha2-asm", + "slog", "stacks-common", - "time 0.2.27", + "wasmtime", ] [[package]] -name = "clear_on_drop" -version = "0.2.5" +name = "const-oid" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38508a63f4979f0048febc9966fadbd48e5dab31fd0ec6a3f151bbf4a74f7423" -dependencies = [ - "cc", -] - -[[package]] -name = "const_fn" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "convert_case" @@ -706,12 +702,12 @@ dependencies = [ [[package]] name = "cookie" -version = "0.17.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ "percent-encoding", - "time 0.3.30", + "time", "version_check", ] @@ -731,6 +727,15 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[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.3" @@ -749,6 +754,115 @@ 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", + "hashbrown 0.14.3", + "log", + "regalloc2", + "smallvec 1.11.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.11.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.11.2", + "wasmparser", + "wasmtime-types", +] + [[package]] name = "crc32fast" version = "1.3.2" @@ -802,7 +916,7 @@ dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset", + "memoffset 0.9.0", ] [[package]] @@ -837,29 +951,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array 0.14.7", + "rand_core 0.6.4", "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" -dependencies = [ - "generic-array 0.12.4", - "subtle 1.0.0", -] - -[[package]] -name = "crypto-mac" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" -dependencies = [ - "generic-array 0.14.7", - "subtle 2.5.0", -] - [[package]] name = "ctor" version = "0.2.6" @@ -870,6 +965,15 @@ dependencies = [ "syn 2.0.41", ] +[[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.1" @@ -890,10 +994,38 @@ dependencies = [ "digest 0.8.1", "rand_core 0.5.1", "serde", - "subtle 2.5.0", + "subtle", "zeroize", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "platforms", + "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.41", +] + [[package]] name = "darling" version = "0.13.4" @@ -951,6 +1083,16 @@ 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.10" @@ -1002,24 +1144,14 @@ dependencies = [ "generic-array 0.12.4", ] -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array 0.14.7", -] - [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", + "block-buffer", "crypto-common", - "subtle 2.5.0", ] [[package]] @@ -1033,6 +1165,16 @@ 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" @@ -1054,12 +1196,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "discard" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" - [[package]] name = "dyn-clone" version = "1.0.16" @@ -1067,16 +1203,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" [[package]] -name = "ed25519-dalek" -version = "1.0.0-pre.3" +name = "ed25519" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978710b352437433c97b2bff193f2fb1dfd58a093f863dd95e225a19baa599a2" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "clear_on_drop", - "curve25519-dalek", - "rand 0.7.3", + "pkcs8", "serde", - "sha2 0.8.2", + "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.2", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", ] [[package]] @@ -1110,18 +1259,18 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - [[package]] name = "fallible-iterator" 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" @@ -1134,6 +1283,12 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "fiat-crypto" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" + [[package]] name = "figment" version = "0.10.12" @@ -1172,6 +1327,18 @@ 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.28" @@ -1209,6 +1376,12 @@ 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.29" @@ -1307,6 +1480,19 @@ 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.4.1", + "debugid", + "fxhash", + "serde", + "serde_json", +] + [[package]] name = "generator" version = "0.7.5" @@ -1363,11 +1549,26 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +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.1.0", + "stable_deref_trait", +] [[package]] name = "glob" @@ -1400,34 +1601,39 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash 0.7.7", -] - [[package]] name = "hashbrown" 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.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", + "serde", +] [[package]] name = "hashlink" -version = "0.7.0" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.11.2", + "hashbrown 0.14.3", ] [[package]] @@ -1480,22 +1686,9 @@ checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" [[package]] name = "hiro-system-kit" -version = "0.1.0" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69a7ca4bddaacbc8180886d378ad8c2b9f74217503002346719b57adbd83124" -dependencies = [ - "ansi_term", - "atty", - "futures", - "lazy_static", - "tokio", -] - -[[package]] -name = "hiro-system-kit" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a96596d2b33489f33f79b2df6f340dbbd2baba05a251715bb84661d33bf1c9" +checksum = "4d3bf5cf007a9973ef9b7dffe8d63fa5228e9bb1aa012e89da2b9f1e7119ce6e" dependencies = [ "ansi_term", "atty", @@ -1506,60 +1699,10 @@ dependencies = [ "slog-json", "slog-scope", "slog-term", + "time", "tokio", ] -[[package]] -name = "hmac" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" -dependencies = [ - "crypto-mac 0.7.0", - "digest 0.8.1", -] - -[[package]] -name = "hmac" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" -dependencies = [ - "crypto-mac 0.8.0", - "digest 0.9.0", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "hmac-drbg" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b" -dependencies = [ - "digest 0.8.1", - "generic-array 0.12.4", - "hmac 0.7.1", -] - -[[package]] -name = "hmac-drbg" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" -dependencies = [ - "digest 0.9.0", - "generic-array 0.14.7", - "hmac 0.8.1", -] - [[package]] name = "home" version = "0.5.9" @@ -1664,6 +1807,12 @@ dependencies = [ "cc", ] +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + [[package]] name = "ident_case" version = "1.0.1" @@ -1680,6 +1829,26 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1699,6 +1868,7 @@ checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown 0.14.3", + "serde", ] [[package]] @@ -1707,7 +1877,7 @@ version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "321f0f839cd44a4686e9504b0a62b4d69a50b62072144c71c68f5873c167b8d9" dependencies = [ - "ahash 0.8.6", + "ahash", "indexmap 2.1.0", "is-terminal", "itoa", @@ -1725,6 +1895,15 @@ 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" @@ -1751,12 +1930,41 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[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.27" @@ -1817,6 +2025,12 @@ 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.151" @@ -1868,75 +2082,11 @@ dependencies = [ "libz-sys", ] -[[package]] -name = "libsecp256k1" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962" -dependencies = [ - "arrayref", - "crunchy", - "digest 0.8.1", - "hmac-drbg 0.2.0", - "rand 0.7.3", - "sha2 0.8.2", - "subtle 2.5.0", - "typenum", -] - -[[package]] -name = "libsecp256k1" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" -dependencies = [ - "arrayref", - "base64 0.13.1", - "digest 0.9.0", - "hmac-drbg 0.3.0", - "libsecp256k1-core", - "libsecp256k1-gen-ecmult", - "libsecp256k1-gen-genmult", - "rand 0.8.5", - "serde", - "sha2 0.9.9", - "typenum", -] - -[[package]] -name = "libsecp256k1-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" -dependencies = [ - "crunchy", - "digest 0.9.0", - "subtle 2.5.0", -] - -[[package]] -name = "libsecp256k1-gen-ecmult" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" -dependencies = [ - "libsecp256k1-core", -] - -[[package]] -name = "libsecp256k1-gen-genmult" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" -dependencies = [ - "libsecp256k1-core", -] - [[package]] name = "libsqlite3-sys" -version = "0.24.2" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" +checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa" dependencies = [ "cc", "pkg-config", @@ -1991,6 +2141,24 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "lru" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" +dependencies = [ + "hashbrown 0.14.3", +] + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "matchers" version = "0.1.0" @@ -2012,6 +2180,15 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[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.2" @@ -2021,6 +2198,15 @@ 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.0" @@ -2030,12 +2216,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memzero" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93c0d11ac30a033511ae414355d80f70d9f29a44a49140face477117a1ee90db" - [[package]] name = "mime" version = "0.3.17" @@ -2155,7 +2335,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "semver 1.0.20", + "semver", "syn 2.0.41", ] @@ -2168,6 +2348,19 @@ dependencies = [ "libloading 0.8.1", ] +[[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" @@ -2216,6 +2409,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-format" version = "0.4.4" @@ -2260,14 +2459,17 @@ version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ + "crc32fast", + "hashbrown 0.14.3", + "indexmap 2.1.0", "memchr", ] [[package]] name = "okapi" -version = "0.7.0-rc.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce66b6366e049880a35c378123fddb630b1a1a3c37fa1ca70caaf4a09f6e2893" +checksum = "9a64853d7ab065474e87696f7601cee817d200e86c42e04004e005cb3e20c3c5" dependencies = [ "log", "schemars 0.8.16", @@ -2281,12 +2483,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - [[package]] name = "opaque-debug" version = "0.3.0" @@ -2310,13 +2506,15 @@ dependencies = [ "futures-util", "fxhash", "hex", - "hiro-system-kit 0.3.1", + "hiro-system-kit", "hyper", "lazy_static", + "lru", "num_cpus", "pprof", "progressing", - "rand 0.8.5", + "rand", + "regex", "reqwest", "rocket", "rocket_okapi", @@ -2327,6 +2525,7 @@ dependencies = [ "serde_derive", "serde_json", "tar", + "test-case", "threadpool", "tokio", "uuid", @@ -2339,7 +2538,7 @@ dependencies = [ "clap", "clap_generate", "ctrlc", - "hiro-system-kit 0.3.1", + "hiro-system-kit", "num_cpus", "ordhook", "reqwest", @@ -2355,7 +2554,7 @@ name = "ordhook-sdk-js" version = "0.6.0" dependencies = [ "crossbeam-channel", - "hiro-system-kit 0.3.1", + "hiro-system-kit", "napi", "napi-build", "napi-derive", @@ -2376,6 +2575,54 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256k1" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a40a031a559eb38c35a14096f21c366254501a06d41c4b327d2a7515d713a5b7" +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.41", +] + +[[package]] +name = "parity-scale-codec" +version = "3.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -2400,27 +2647,10 @@ dependencies = [ ] [[package]] -name = "password-hash" -version = "0.5.0" +name = "paste" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle 2.5.0", -] - -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest 0.10.7", - "hmac 0.12.1", - "password-hash", - "sha2 0.10.8", -] +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pear" @@ -2430,7 +2660,7 @@ checksum = "4ccca0f6c17acc81df8e242ed473ec144cbf5c98037e69aa6d144780aad103c8" dependencies = [ "inlinable_string", "pear_codegen", - "yansi 1.0.0-rc.1", + "yansi", ] [[package]] @@ -2457,17 +2687,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pest" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" -dependencies = [ - "memchr", - "thiserror", - "ucd-trie", -] - [[package]] name = "pin-project-lite" version = "0.2.13" @@ -2480,12 +2699,50 @@ 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.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "platforms" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" + +[[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 = "powerfmt" version = "0.2.0" @@ -2529,6 +2786,26 @@ dependencies = [ "syn 2.0.41", ] +[[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 = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +dependencies = [ + "toml_edit 0.20.7", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -2553,12 +2830,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" version = "1.0.70" @@ -2578,7 +2849,7 @@ dependencies = [ "quote", "syn 2.0.41", "version_check", - "yansi 1.0.0-rc.1", + "yansi", ] [[package]] @@ -2590,6 +2861,27 @@ dependencies = [ "log", ] +[[package]] +name = "prometheus" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot", + "protobuf", + "thiserror", +] + +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" + [[package]] name = "psm" version = "0.1.21" @@ -2618,17 +2910,10 @@ dependencies = [ ] [[package]] -name = "rand" -version = "0.7.3" +name = "radium" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" @@ -2637,20 +2922,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", + "rand_chacha", "rand_core 0.6.4", ] -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - [[package]] name = "rand_chacha" version = "0.3.1" @@ -2679,15 +2954,6 @@ dependencies = [ "getrandom 0.2.11", ] -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - [[package]] name = "rayon" version = "1.8.0" @@ -2749,14 +3015,27 @@ dependencies = [ ] [[package]] -name = "regex" -version = "1.10.2" +name = "regalloc2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" +dependencies = [ + "hashbrown 0.13.2", + "log", + "rustc-hash", + "slice-group-by", + "smallvec 1.11.2", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.3", + "regex-automata 0.4.6", "regex-syntax 0.8.2", ] @@ -2771,9 +3050,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -2868,9 +3147,9 @@ dependencies = [ [[package]] name = "rocket" -version = "0.5.0-rc.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58734f7401ae5cfd129685b48f61182331745b357b96f2367f01aebaf1cc9cc9" +checksum = "9e7bb57ccb26670d73b6a47396c83139447b9e7878cab627fdfe9ea8da489150" dependencies = [ "async-stream", "async-trait", @@ -2880,15 +3159,14 @@ dependencies = [ "either", "figment", "futures", - "indexmap 1.9.3", - "is-terminal", + "indexmap 2.1.0", "log", "memchr", "multer", "num_cpus", "parking_lot", "pin-project-lite", - "rand 0.8.5", + "rand", "ref-cast", "rocket_codegen", "rocket_http", @@ -2896,43 +3174,44 @@ dependencies = [ "serde_json", "state", "tempfile", - "time 0.3.30", + "time", "tokio", "tokio-stream", "tokio-util", "ubyte", "version_check", - "yansi 0.5.1", + "yansi", ] [[package]] name = "rocket_codegen" -version = "0.5.0-rc.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7093353f14228c744982e409259fb54878ba9563d08214f2d880d59ff2fc508b" +checksum = "a2238066abf75f21be6cd7dc1a09d5414a671f4246e384e49fe3f8a4936bd04c" dependencies = [ "devise", "glob", - "indexmap 1.9.3", + "indexmap 2.1.0", "proc-macro2", "quote", "rocket_http", "syn 2.0.41", "unicode-xid", + "version_check", ] [[package]] name = "rocket_http" -version = "0.5.0-rc.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936012c99162a03a67f37f9836d5f938f662e26f2717809761a9ac46432090f4" +checksum = "37a1663694d059fe5f943ea5481363e48050acedd241d46deb2e27f71110389e" dependencies = [ "cookie", "either", "futures", "http", "hyper", - "indexmap 1.9.3", + "indexmap 2.1.0", "log", "memchr", "pear", @@ -2943,18 +3222,17 @@ dependencies = [ "smallvec 1.11.2", "stable-pattern", "state", - "time 0.3.30", + "time", "tokio", "uncased", ] [[package]] name = "rocket_okapi" -version = "0.8.0-rc.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "742098674565c8f0c35c77444f90344aafedebb71cfee9cdbf0185acc6b9cdb7" +checksum = "e059407ecef9ee2f071fc971e10444fcf942149deb028879d6d8ca61a7ce9edc" dependencies = [ - "either", "log", "okapi", "rocket", @@ -2966,9 +3244,9 @@ dependencies = [ [[package]] name = "rocket_okapi_codegen" -version = "0.8.0-rc.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c43f8edc57d88750a220b0ec1870a36c1106204ec99cc35131b49de3b954a4a" +checksum = "cfb96114e69e5d7f80bfa0948cbc0120016e9b460954abe9eed37e9a2ad3f999" dependencies = [ "darling", "proc-macro2", @@ -2987,42 +3265,17 @@ dependencies = [ "librocksdb-sys", ] -[[package]] -name = "rstest" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2288c66aeafe3b2ed227c981f364f9968fa952ef0b30e84ada4486e7ee24d00a" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "rustc_version 0.4.0", - "syn 1.0.109", -] - -[[package]] -name = "rstest_reuse" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32c6cfaae58c048728261723a72b80a0aa9f3768e9a7da3b302a24d262525219" -dependencies = [ - "quote", - "rustc_version 0.3.3", - "syn 1.0.109", -] - [[package]] name = "rusqlite" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85127183a999f7db96d1a976a309eebbfb6ea3b0b400ddd8340190129de6eb7a" +checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" dependencies = [ "bitflags 1.3.2", - "fallible-iterator", + "fallible-iterator 0.2.0", "fallible-streaming-iterator", "hashlink", "libsqlite3-sys", - "memchr", "serde_json", "smallvec 1.11.2", ] @@ -3040,22 +3293,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] -name = "rustc_version" -version = "0.2.3" +name = "rustc-hex" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver 0.9.0", -] - -[[package]] -name = "rustc_version" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" -dependencies = [ - "semver 0.11.0", -] +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" [[package]] name = "rustc_version" @@ -3063,7 +3304,20 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.20", + "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.8", + "toolchain_find", ] [[package]] @@ -3217,7 +3471,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acea373acb8c21ecb5a23741452acd2593ed44ee3d343e72baaa143bc89d0d5" dependencies = [ "bitcoin_hashes", - "rand 0.8.5", + "rand", "secp256k1-sys 0.9.1", "serde", ] @@ -3240,45 +3494,12 @@ dependencies = [ "cc", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser 0.7.0", -] - -[[package]] -name = "semver" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser 0.10.2", -] - [[package]] name = "semver" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "semver-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] - [[package]] name = "serde" version = "1.0.193" @@ -3363,46 +3584,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha1" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" -dependencies = [ - "sha1_smol", -] - -[[package]] -name = "sha1_smol" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" - -[[package]] -name = "sha2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" -dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug 0.2.3", -] - -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug 0.3.0", -] - [[package]] name = "sha2" version = "0.10.8" @@ -3423,18 +3604,6 @@ dependencies = [ "cc", ] -[[package]] -name = "sha3" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "keccak", - "opaque-debug 0.3.0", -] - [[package]] name = "sha3" version = "0.10.8" @@ -3469,6 +3638,15 @@ 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 = "slab" version = "0.4.9" @@ -3478,6 +3656,12 @@ 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" @@ -3515,7 +3699,7 @@ dependencies = [ "serde", "serde_json", "slog", - "time 0.3.30", + "time", ] [[package]] @@ -3539,7 +3723,7 @@ dependencies = [ "slog", "term", "thread_local", - "time 0.3.30", + "time", ] [[package]] @@ -3586,6 +3770,22 @@ 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" @@ -3615,113 +3815,61 @@ dependencies = [ ] [[package]] -name = "stacks-common" -version = "0.0.2" +name = "stacks-codec" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c887baefb047fb7cd1c82d5a62e1deaca4b9ea9c701f965407db6c39a3fc902f" +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", + "curve25519-dalek 2.0.0", "ed25519-dalek", + "hashbrown 0.14.3", "lazy_static", "libc", + "nix 0.23.2", "percent-encoding", - "rand 0.7.3", + "rand", "ripemd", "rusqlite", "secp256k1 0.24.3", "serde", "serde_derive", "serde_json", - "sha2 0.10.8", - "sha3 0.10.8", - "time 0.2.27", -] - -[[package]] -name = "stacks-rpc-client" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab91f2053d36c8caedfdb2b98ace2cf852aba6d27c40ae92e2b35d946504a6e" -dependencies = [ - "clarity-repl", - "hmac 0.12.1", - "libsecp256k1 0.7.1", - "pbkdf2", - "reqwest", - "serde", - "serde_derive", - "serde_json", - "sha2 0.10.8", - "tiny-hderive", -] - -[[package]] -name = "standback" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" -dependencies = [ - "version_check", + "sha2", + "sha3", + "slog", + "slog-json", + "slog-term", + "time", + "winapi", + "wsts", ] [[package]] name = "state" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" +checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8" dependencies = [ "loom", ] [[package]] -name = "stdweb" -version = "0.4.20" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" -dependencies = [ - "discard", - "rustc_version 0.2.3", - "stdweb-derive", - "stdweb-internal-macros", - "stdweb-internal-runtime", - "wasm-bindgen", -] - -[[package]] -name = "stdweb-derive" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_derive", - "syn 1.0.109", -] - -[[package]] -name = "stdweb-internal-macros" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" -dependencies = [ - "base-x", - "proc-macro2", - "quote", - "serde", - "serde_derive", - "serde_json", - "sha1", - "syn 1.0.109", -] - -[[package]] -name = "stdweb-internal-runtime" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "str_stack" @@ -3757,12 +3905,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" - [[package]] name = "subtle" version = "2.5.0" @@ -3787,7 +3929,7 @@ version = "12.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a99812da4020a67e76c4eb41f08c87364c14170495ff780f30dd519c221a68" dependencies = [ - "cpp_demangle", + "cpp_demangle 0.4.3", "rustc-demangle", "symbolic-common", ] @@ -3854,6 +3996,12 @@ 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.40" @@ -3914,6 +4062,39 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.41", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.41", + "test-case-core", +] + [[package]] name = "textwrap" version = "0.16.0" @@ -3961,33 +4142,19 @@ dependencies = [ [[package]] name = "time" -version = "0.2.27" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" -dependencies = [ - "const_fn", - "libc", - "standback", - "stdweb", - "time-macros 0.1.1", - "version_check", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", "itoa", "libc", + "num-conv", "num_threads", "powerfmt", "serde", "time-core", - "time-macros 0.2.15", + "time-macros", ] [[package]] @@ -3998,49 +4165,14 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.1.1" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" -dependencies = [ - "proc-macro-hack", - "time-macros-impl", -] - -[[package]] -name = "time-macros" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" dependencies = [ + "num-conv", "time-core", ] -[[package]] -name = "time-macros-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "standback", - "syn 1.0.109", -] - -[[package]] -name = "tiny-hderive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b874a4992538d4b2f4fbbac11b9419d685f4b39bdc3fed95b04e07bfd76040" -dependencies = [ - "base58 0.1.0", - "hmac 0.7.1", - "libsecp256k1 0.3.5", - "memzero", - "sha2 0.8.2", -] - [[package]] name = "tinyvec" version = "1.6.0" @@ -4140,7 +4272,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.21.0", ] [[package]] @@ -4152,6 +4284,17 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_edit" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap 2.1.0", + "toml_datetime", + "winnow", +] + [[package]] name = "toml_edit" version = "0.21.0" @@ -4165,6 +4308,19 @@ dependencies = [ "winnow", ] +[[package]] +name = "toolchain_find" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc8c9a7f0a2966e1acdaf0461023d0b01471eeead645370cf4c3f5cff153f2a" +dependencies = [ + "home", + "once_cell", + "regex", + "semver", + "walkdir", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -4254,10 +4410,16 @@ dependencies = [ ] [[package]] -name = "ucd-trie" -version = "0.1.6" +name = "uint" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] [[package]] name = "uncased" @@ -4296,12 +4458,28 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "unicode-xid" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[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" @@ -4326,7 +4504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" dependencies = [ "getrandom 0.2.11", - "rand 0.8.5", + "rand", ] [[package]] @@ -4450,6 +4628,24 @@ version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +[[package]] +name = "wasm-encoder" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "822b645bf4f2446b949776ffca47e2af60b167209ffb70814ef8779d299cd421" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-encoder" +version = "0.202.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd106365a7f5f7aa3c1916a98cbb3ad477f5ff96ddb130285a91c6e7429e67a" +dependencies = [ + "leb128", +] + [[package]] name = "wasm-streams" version = "0.3.0" @@ -4463,6 +4659,322 @@ 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.1.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.1.0", + "libc", + "log", + "object", + "once_cell", + "paste", + "psm", + "rayon", + "serde", + "serde_derive", + "serde_json", + "target-lexicon", + "wasm-encoder 0.36.2", + "wasmparser", + "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.5", + "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.41", + "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", + "log", + "object", + "target-lexicon", + "thiserror", + "wasmparser", + "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", + "object", + "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", + "indexmap 2.1.0", + "log", + "object", + "serde", + "serde_derive", + "target-lexicon", + "thiserror", + "wasmparser", + "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", + "anyhow", + "bincode", + "cfg-if", + "cpp_demangle 0.3.5", + "gimli", + "ittapi", + "log", + "object", + "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", + "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.1.0", + "libc", + "log", + "mach", + "memfd", + "memoffset 0.9.0", + "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", +] + +[[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.41", +] + +[[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.1.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 = "202.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbcb11204515c953c9b42ede0a46a1c5e17f82af05c4fae201a8efff1b0f4fe" +dependencies = [ + "bumpalo", + "leb128", + "memchr", + "unicode-width", + "wasm-encoder 0.202.0", +] + +[[package]] +name = "wat" +version = "1.202.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de4b15a47135c56a3573406e9977b9518787a6154459b4842a9b9d3d1684848" +dependencies = [ + "wast", +] + [[package]] name = "web-sys" version = "0.3.66" @@ -4691,6 +5203,54 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-parser" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "316b36a9f0005f5aa4b03c39bc3728d045df136f8c13a73b7db4510dec725e08" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.1.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", +] + +[[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.3", + "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.1.3" @@ -4702,17 +5262,14 @@ dependencies = [ "rustix", ] -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" - [[package]] name = "yansi" version = "1.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377" +dependencies = [ + "is-terminal", +] [[package]] name = "zerocopy" @@ -4771,3 +5328,32 @@ 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.10+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/components/ordhook-cli/src/cli/mod.rs b/components/ordhook-cli/src/cli/mod.rs index c4d5875..111f8e3 100644 --- a/components/ordhook-cli/src/cli/mod.rs +++ b/components/ordhook-cli/src/cli/mod.rs @@ -3,7 +3,9 @@ use crate::config::generator::generate_config; use clap::{Parser, Subcommand}; use hiro_system_kit; use ordhook::chainhook_sdk::bitcoincore_rpc::{Auth, Client, RpcApi}; -use ordhook::chainhook_sdk::chainhooks::types::{BitcoinChainhookSpecification, HttpHook}; +use ordhook::chainhook_sdk::chainhooks::types::{ + BitcoinChainhookSpecification, HttpHook, InscriptionFeedData, +}; use ordhook::chainhook_sdk::chainhooks::types::{ BitcoinPredicateType, ChainhookFullSpecification, HookAction, OrdinalOperations, }; @@ -22,15 +24,14 @@ use ordhook::core::pipeline::processors::start_inscription_indexing_processor; use ordhook::core::protocol::inscription_parsing::parse_inscriptions_and_standardize_block; use ordhook::core::protocol::satoshi_numbering::compute_satoshi_number; use ordhook::db::{ - delete_data_in_ordhook_db, find_all_inscription_transfers, find_all_inscriptions_in_block, - find_all_transfers_in_block, find_block_bytes_at_block_height, find_inscription_with_id, - find_last_block_inserted, find_latest_inscription_block_height, find_missing_blocks, - get_default_ordhook_db_file_path, initialize_ordhook_db, open_ordhook_db_conn_rocks_db_loop, - open_readonly_ordhook_db_conn, open_readonly_ordhook_db_conn_rocks_db, - open_readwrite_ordhook_db_conn, BlockBytesCursor, + delete_data_in_ordhook_db, find_all_inscriptions_in_block, find_all_transfers_in_block, + find_block_bytes_at_block_height, find_inscription_with_id, find_last_block_inserted, + find_latest_inscription_block_height, find_missing_blocks, get_default_ordhook_db_file_path, + open_ordhook_db_conn_rocks_db_loop, open_readonly_ordhook_db_conn, + open_readonly_ordhook_db_conn_rocks_db, BlockBytesCursor, }; use ordhook::download::download_ordinals_dataset_if_required; -use ordhook::hex; +use ordhook::{hex, initialize_db}; use ordhook::scan::bitcoin::scan_bitcoin_chainstate_via_rpc_using_predicate; use ordhook::service::observers::initialize_observers_db; use ordhook::service::{start_observer_forwarding, Service}; @@ -258,7 +259,7 @@ impl RepairStorageCommand { } _ => unreachable!(), }; - blocks.into() + blocks.unwrap().into() } } @@ -540,7 +541,10 @@ async fn handle_command(opts: Opts, ctx: &Context) -> Result<(), String> { // If post-to: // - Replay that requires connection to bitcoind let block_heights = parse_blocks_heights_spec(&cmd.blocks_interval, &cmd.blocks); - let mut block_range = block_heights.get_sorted_entries(); + let mut block_range = block_heights + .get_sorted_entries() + .map_err(|_e| format!("Block start / end block spec invalid"))?; + if let Some(ref post_to) = cmd.post_to { info!(ctx.expect_logger(), "A fully synchronized bitcoind node is required for retrieving inscriptions content."); info!( @@ -579,8 +583,8 @@ async fn handle_command(opts: Opts, ctx: &Context) -> Result<(), String> { let mut total_inscriptions = 0; let mut total_transfers = 0; - let inscriptions_db_conn = - initialize_ordhook_db(&config.expected_cache_path(), ctx); + let db_connections = initialize_db(&config, ctx); + let inscriptions_db_conn = db_connections.ordhook; while let Some(block_height) = block_range.pop_front() { let inscriptions = find_all_inscriptions_in_block(&block_height, &inscriptions_db_conn, ctx); @@ -654,18 +658,6 @@ async fn handle_command(opts: Opts, ctx: &Context) -> Result<(), String> { inscription.inscription_number.jubilee, inscription.ordinal_number ); - let transfers = find_all_inscription_transfers( - &inscription.get_inscription_id(), - &inscriptions_db_conn, - ctx, - ); - for (transfer, block_height) in transfers.iter().skip(1) { - println!( - "\t→ Transferred in transaction {} (block #{block_height})", - transfer.transaction_identifier_location.hash - ); - } - println!("Number of transfers: {}", transfers.len() - 1); } Command::Scan(ScanCommand::Transaction(cmd)) => { let config: Config = @@ -710,7 +702,7 @@ async fn handle_command(opts: Opts, ctx: &Context) -> Result<(), String> { let config = ConfigFile::default(cmd.regtest, cmd.testnet, cmd.mainnet, &cmd.config_path)?; - let _ = initialize_ordhook_db(&config.expected_cache_path(), ctx); + initialize_db(&config, ctx); let inscriptions_db_conn = open_readonly_ordhook_db_conn(&config.expected_cache_path(), ctx)?; @@ -790,7 +782,7 @@ async fn handle_command(opts: Opts, ctx: &Context) -> Result<(), String> { }, Command::Db(OrdhookDbCommand::New(cmd)) => { let config = ConfigFile::default(false, false, false, &cmd.config_path)?; - initialize_ordhook_db(&config.expected_cache_path(), ctx); + initialize_db(&config, ctx); open_ordhook_db_conn_rocks_db_loop( true, &config.expected_cache_path(), @@ -801,7 +793,7 @@ async fn handle_command(opts: Opts, ctx: &Context) -> Result<(), String> { } Command::Db(OrdhookDbCommand::Sync(cmd)) => { let config = ConfigFile::default(false, false, false, &cmd.config_path)?; - initialize_ordhook_db(&config.expected_cache_path(), ctx); + initialize_db(&config, ctx); let service = Service::new(config, ctx.clone()); service.update_state(None).await?; } @@ -914,15 +906,6 @@ async fn handle_command(opts: Opts, ctx: &Context) -> Result<(), String> { } Command::Db(OrdhookDbCommand::Drop(cmd)) => { let config = ConfigFile::default(false, false, false, &cmd.config_path)?; - let blocks_db = open_ordhook_db_conn_rocks_db_loop( - true, - &config.expected_cache_path(), - config.resources.ulimit, - config.resources.memory_available, - ctx, - ); - let inscriptions_db_conn_rw = - open_readwrite_ordhook_db_conn(&config.expected_cache_path(), ctx)?; println!( "{} blocks will be deleted. Confirm? [Y/n]", @@ -934,13 +917,7 @@ async fn handle_command(opts: Opts, ctx: &Context) -> Result<(), String> { return Err("Deletion aborted".to_string()); } - delete_data_in_ordhook_db( - cmd.start_block, - cmd.end_block, - &blocks_db, - &inscriptions_db_conn_rw, - ctx, - )?; + delete_data_in_ordhook_db(cmd.start_block, cmd.end_block, &config, ctx)?; info!( ctx.expect_logger(), "Cleaning ordhook_db: {} blocks dropped", @@ -1011,7 +988,11 @@ pub fn build_predicate_from_cli( include_witness: false, expired_at: None, enabled: true, - predicate: BitcoinPredicateType::OrdinalsProtocol(OrdinalOperations::InscriptionFeed), + predicate: BitcoinPredicateType::OrdinalsProtocol(OrdinalOperations::InscriptionFeed( + InscriptionFeedData { + meta_protocols: None, + }, + )), action: HookAction::HttpPost(HttpHook { url: post_to.to_string(), authorization_header: format!("Bearer {}", auth_token.unwrap_or("".to_string())), diff --git a/components/ordhook-cli/src/config/file.rs b/components/ordhook-cli/src/config/file.rs index 5cc0a3c..67a1f5d 100644 --- a/components/ordhook-cli/src/config/file.rs +++ b/components/ordhook-cli/src/config/file.rs @@ -4,9 +4,9 @@ use ordhook::chainhook_sdk::types::{ BitcoinBlockSignaling, BitcoinNetwork, StacksNetwork, StacksNodeConfig, }; use ordhook::config::{ - Config, LogConfig, PredicatesApi, PredicatesApiConfig, ResourcesConfig, SnapshotConfig, - StorageConfig, DEFAULT_BITCOIND_RPC_THREADS, DEFAULT_BITCOIND_RPC_TIMEOUT, - DEFAULT_CONTROL_PORT, DEFAULT_MEMORY_AVAILABLE, DEFAULT_ULIMIT, + Config, LogConfig, MetaProtocolsConfig, PredicatesApi, PredicatesApiConfig, ResourcesConfig, + SnapshotConfig, StorageConfig, DEFAULT_BITCOIND_RPC_THREADS, DEFAULT_BITCOIND_RPC_TIMEOUT, + DEFAULT_BRC20_LRU_CACHE_SIZE, DEFAULT_CONTROL_PORT, DEFAULT_MEMORY_AVAILABLE, DEFAULT_ULIMIT, }; use std::fs::File; use std::io::{BufReader, Read}; @@ -19,6 +19,7 @@ pub struct ConfigFile { pub network: NetworkConfigFile, pub logs: Option, pub snapshot: Option, + pub meta_protocols: Option, } impl ConfigFile { @@ -94,6 +95,10 @@ impl ConfigFile { .resources .expected_observers_count .unwrap_or(1), + brc20_lru_cache_size: config_file + .resources + .brc20_lru_cache_size + .unwrap_or(DEFAULT_BRC20_LRU_CACHE_SIZE), }, network: IndexerConfig { bitcoind_rpc_url: config_file.network.bitcoind_rpc_url.to_string(), @@ -123,6 +128,13 @@ impl ConfigFile { .and_then(|l| l.chainhook_internals) .unwrap_or(true), }, + meta_protocols: MetaProtocolsConfig { + brc20: config_file + .meta_protocols + .as_ref() + .and_then(|l| l.brc20) + .unwrap_or(false), + }, }; Ok(config) } @@ -168,6 +180,11 @@ pub struct SnapshotConfigFile { pub download_url: Option, } +#[derive(Deserialize, Debug, Clone)] +pub struct MetaProtocolsConfigFile { + pub brc20: Option, +} + #[derive(Deserialize, Debug, Clone)] pub struct ResourcesConfigFile { pub ulimit: Option, @@ -176,6 +193,7 @@ pub struct ResourcesConfigFile { pub bitcoind_rpc_threads: Option, pub bitcoind_rpc_timeout: Option, pub expected_observers_count: Option, + pub brc20_lru_cache_size: Option, } #[derive(Deserialize, Debug, Clone)] diff --git a/components/ordhook-core/Cargo.toml b/components/ordhook-core/Cargo.toml index 37bd588..c3ea4aa 100644 --- a/components/ordhook-core/Cargo.toml +++ b/components/ordhook-core/Cargo.toml @@ -10,8 +10,9 @@ serde_json = "1" serde_derive = "1" hex = "0.4.3" rand = "0.8.5" -chainhook-sdk = { version = "=0.12.5", features = ["zeromq"] } -# chainhook-sdk = { version = "=0.12.1", path = "../../../chainhook/components/chainhook-sdk", features = ["zeromq"] } +lru = "0.12.3" +chainhook-sdk = { version = "=0.12.8", features = ["zeromq"] } +# chainhook-sdk = { version = "=0.12.5", path = "../../../chainhook/components/chainhook-sdk", features = ["zeromq"] } hiro-system-kit = "0.3.1" reqwest = { version = "0.11", default-features = false, features = [ "stream", @@ -29,10 +30,10 @@ crossbeam-channel = "0.5.8" uuid = { version = "1.3.0", features = ["v4", "fast-rng"] } threadpool = "1.8.1" rocket_okapi = "0.8.0-rc.3" -rocket = { version = "=0.5.0-rc.3", features = ["json"] } +rocket = { version = "0.5.0", features = ["json"] } dashmap = "5.4.0" fxhash = "0.2.1" -rusqlite = { version = "0.27.0", features = ["bundled"] } +rusqlite = { version = "0.28.0", features = ["bundled"] } anyhow = { version = "1.0.56", features = ["backtrace"] } schemars = { version = "0.8.10", git = "https://github.com/hirosystems/schemars.git", branch = "feat-chainhook-fixes" } progressing = '3' @@ -44,6 +45,10 @@ pprof = { version = "0.13.0", features = ["flamegraph"], optional = true } hyper = { version = "=0.14.27" } lazy_static = { version = "1.4.0" } ciborium = "0.2.1" +regex = "1.10.3" + +[dev-dependencies] +test-case = "3.1.0" # [profile.release] # debug = true diff --git a/components/ordhook-core/src/config/mod.rs b/components/ordhook-core/src/config/mod.rs index fb782f6..e9acd02 100644 --- a/components/ordhook-core/src/config/mod.rs +++ b/components/ordhook-core/src/config/mod.rs @@ -15,6 +15,7 @@ pub const DEFAULT_ULIMIT: usize = 2048; pub const DEFAULT_MEMORY_AVAILABLE: usize = 8; pub const DEFAULT_BITCOIND_RPC_THREADS: usize = 4; pub const DEFAULT_BITCOIND_RPC_TIMEOUT: u32 = 15; +pub const DEFAULT_BRC20_LRU_CACHE_SIZE: usize = 50_000; #[derive(Clone, Debug)] pub struct Config { @@ -23,9 +24,15 @@ pub struct Config { pub resources: ResourcesConfig, pub network: IndexerConfig, pub snapshot: SnapshotConfig, + pub meta_protocols: MetaProtocolsConfig, pub logs: LogConfig, } +#[derive(Clone, Debug)] +pub struct MetaProtocolsConfig { + pub brc20: bool, +} + #[derive(Clone, Debug)] pub struct LogConfig { pub ordinals_internals: bool, @@ -73,6 +80,7 @@ pub struct ResourcesConfig { pub bitcoind_rpc_threads: usize, pub bitcoind_rpc_timeout: u32, pub expected_observers_count: usize, + pub brc20_lru_cache_size: usize, } impl ResourcesConfig { @@ -103,6 +111,7 @@ impl Config { BitcoinNetwork::Signet => 112402, }, logs: self.logs.clone(), + meta_protocols: self.meta_protocols.clone(), } } @@ -119,6 +128,7 @@ impl Config { cache_path: self.storage.working_dir.clone(), bitcoin_network: self.network.bitcoin_network.clone(), stacks_network: self.network.stacks_network.clone(), + prometheus_monitoring_port: None, data_handler_tx: None, } } @@ -172,6 +182,7 @@ impl Config { bitcoind_rpc_threads: DEFAULT_BITCOIND_RPC_THREADS, bitcoind_rpc_timeout: DEFAULT_BITCOIND_RPC_TIMEOUT, expected_observers_count: 1, + brc20_lru_cache_size: DEFAULT_BRC20_LRU_CACHE_SIZE, }, network: IndexerConfig { bitcoind_rpc_url: "http://0.0.0.0:18443".into(), @@ -187,6 +198,7 @@ impl Config { ordinals_internals: true, chainhook_internals: false, }, + meta_protocols: MetaProtocolsConfig { brc20: false }, } } @@ -204,6 +216,7 @@ impl Config { bitcoind_rpc_threads: DEFAULT_BITCOIND_RPC_THREADS, bitcoind_rpc_timeout: DEFAULT_BITCOIND_RPC_TIMEOUT, expected_observers_count: 1, + brc20_lru_cache_size: DEFAULT_BRC20_LRU_CACHE_SIZE, }, network: IndexerConfig { bitcoind_rpc_url: "http://0.0.0.0:18332".into(), @@ -219,6 +232,7 @@ impl Config { ordinals_internals: true, chainhook_internals: false, }, + meta_protocols: MetaProtocolsConfig { brc20: false }, } } @@ -236,6 +250,7 @@ impl Config { bitcoind_rpc_threads: DEFAULT_BITCOIND_RPC_THREADS, bitcoind_rpc_timeout: DEFAULT_BITCOIND_RPC_TIMEOUT, expected_observers_count: 1, + brc20_lru_cache_size: DEFAULT_BRC20_LRU_CACHE_SIZE, }, network: IndexerConfig { bitcoind_rpc_url: "http://0.0.0.0:8332".into(), @@ -251,6 +266,7 @@ impl Config { ordinals_internals: true, chainhook_internals: false, }, + meta_protocols: MetaProtocolsConfig { brc20: false }, } } } diff --git a/components/ordhook-core/src/core/meta_protocols/brc20/cache.rs b/components/ordhook-core/src/core/meta_protocols/brc20/cache.rs new file mode 100644 index 0000000..2696675 --- /dev/null +++ b/components/ordhook-core/src/core/meta_protocols/brc20/cache.rs @@ -0,0 +1,546 @@ +use std::num::NonZeroUsize; + +use chainhook_sdk::{ + types::{BlockIdentifier, OrdinalInscriptionRevealData, OrdinalInscriptionTransferData}, + utils::Context, +}; +use lru::LruCache; +use rusqlite::{Connection, Transaction}; + +use crate::core::meta_protocols::brc20::db::get_unsent_token_transfer; + +use super::{ + db::{ + get_token, get_token_available_balance_for_address, get_token_minted_supply, + insert_ledger_rows, insert_token_rows, Brc20DbLedgerRow, Brc20DbTokenRow, + }, + verifier::{VerifiedBrc20BalanceData, VerifiedBrc20TokenDeployData, VerifiedBrc20TransferData}, +}; + +/// Keeps BRC20 DB rows before they're inserted into SQLite. Use `flush` to insert. +pub struct Brc20DbCache { + ledger_rows: Vec, + token_rows: Vec, +} + +impl Brc20DbCache { + fn new() -> Self { + Brc20DbCache { + ledger_rows: Vec::new(), + token_rows: Vec::new(), + } + } + + pub fn flush(&mut self, db_tx: &Transaction, ctx: &Context) { + if self.token_rows.len() > 0 { + insert_token_rows(&self.token_rows, db_tx, ctx); + self.token_rows.clear(); + } + if self.ledger_rows.len() > 0 { + insert_ledger_rows(&self.ledger_rows, db_tx, ctx); + self.ledger_rows.clear(); + } + } +} + +/// In-memory cache that keeps verified token data to avoid excessive reads to the database. +pub struct Brc20MemoryCache { + tokens: LruCache, + token_minted_supplies: LruCache, + token_addr_avail_balances: LruCache, // key format: "tick:address" + unsent_transfers: LruCache, + ignored_inscriptions: LruCache, + pub db_cache: Brc20DbCache, +} + +impl Brc20MemoryCache { + pub fn new(lru_size: usize) -> Self { + Brc20MemoryCache { + tokens: LruCache::new(NonZeroUsize::new(lru_size).unwrap()), + token_minted_supplies: LruCache::new(NonZeroUsize::new(lru_size).unwrap()), + token_addr_avail_balances: LruCache::new(NonZeroUsize::new(lru_size).unwrap()), + unsent_transfers: LruCache::new(NonZeroUsize::new(lru_size).unwrap()), + ignored_inscriptions: LruCache::new(NonZeroUsize::new(lru_size).unwrap()), + db_cache: Brc20DbCache::new(), + } + } + + pub fn get_token( + &mut self, + tick: &str, + db_tx: &Transaction, + ctx: &Context, + ) -> Option { + if let Some(token) = self.tokens.get(&tick.to_string()) { + return Some(token.clone()); + } + self.handle_cache_miss(db_tx, ctx); + match get_token(tick, db_tx, ctx) { + Some(db_token) => { + self.tokens.put(tick.to_string(), db_token.clone()); + return Some(db_token); + } + None => return None, + } + } + + pub fn get_token_minted_supply( + &mut self, + tick: &str, + db_tx: &Transaction, + ctx: &Context, + ) -> Option { + if let Some(minted) = self.token_minted_supplies.get(&tick.to_string()) { + return Some(minted.clone()); + } + self.handle_cache_miss(db_tx, ctx); + if let Some(minted_supply) = get_token_minted_supply(tick, db_tx, ctx) { + self.token_minted_supplies + .put(tick.to_string(), minted_supply); + return Some(minted_supply); + } + return None; + } + + pub fn get_token_address_avail_balance( + &mut self, + tick: &str, + address: &str, + db_tx: &Transaction, + ctx: &Context, + ) -> Option { + let key = format!("{}:{}", tick, address); + if let Some(balance) = self.token_addr_avail_balances.get(&key) { + return Some(balance.clone()); + } + self.handle_cache_miss(db_tx, ctx); + if let Some(balance) = get_token_available_balance_for_address(tick, address, db_tx, ctx) { + self.token_addr_avail_balances.put(key, balance); + return Some(balance); + } + return None; + } + + pub fn get_unsent_token_transfer( + &mut self, + ordinal_number: u64, + db_tx: &Transaction, + ctx: &Context, + ) -> Option { + // Use `get` instead of `contains` so we promote this value in the LRU. + if let Some(_) = self.ignored_inscriptions.get(&ordinal_number) { + return None; + } + if let Some(row) = self.unsent_transfers.get(&ordinal_number) { + return Some(row.clone()); + } + self.handle_cache_miss(db_tx, ctx); + match get_unsent_token_transfer(ordinal_number, db_tx, ctx) { + Some(row) => { + self.unsent_transfers.put(ordinal_number, row.clone()); + return Some(row); + } + None => { + // Inscription is not relevant for BRC20. + self.ignore_inscription(ordinal_number); + return None; + } + } + } + + /// Marks an ordinal number as ignored so we don't bother computing its transfers for BRC20 purposes. + pub fn ignore_inscription(&mut self, ordinal_number: u64) { + self.ignored_inscriptions.put(ordinal_number, true); + } + + pub fn insert_token_deploy( + &mut self, + data: &VerifiedBrc20TokenDeployData, + reveal: &OrdinalInscriptionRevealData, + block_identifier: &BlockIdentifier, + tx_index: u64, + _db_tx: &Connection, + _ctx: &Context, + ) { + let token = Brc20DbTokenRow { + inscription_id: reveal.inscription_id.clone(), + inscription_number: reveal.inscription_number.jubilee as u64, + block_height: block_identifier.index, + tick: data.tick.clone(), + max: data.max, + lim: data.lim, + dec: data.dec, + address: data.address.clone(), + self_mint: data.self_mint, + }; + self.tokens.put(token.tick.clone(), token.clone()); + self.token_minted_supplies.put(token.tick.clone(), 0.0); + self.token_addr_avail_balances + .put(format!("{}:{}", token.tick, data.address), 0.0); + self.db_cache.token_rows.push(token); + self.db_cache.ledger_rows.push(Brc20DbLedgerRow { + inscription_id: reveal.inscription_id.clone(), + inscription_number: reveal.inscription_number.jubilee as u64, + ordinal_number: reveal.ordinal_number, + block_height: block_identifier.index, + tx_index, + tick: data.tick.clone(), + address: data.address.clone(), + avail_balance: 0.0, + trans_balance: 0.0, + operation: "deploy".to_string(), + }); + self.ignore_inscription(reveal.ordinal_number); + } + + pub fn insert_token_mint( + &mut self, + data: &VerifiedBrc20BalanceData, + reveal: &OrdinalInscriptionRevealData, + block_identifier: &BlockIdentifier, + tx_index: u64, + db_tx: &Transaction, + ctx: &Context, + ) { + let Some(minted) = self.get_token_minted_supply(&data.tick, db_tx, ctx) else { + unreachable!("BRC-20 deployed token should have a minted supply entry"); + }; + self.token_minted_supplies + .put(data.tick.clone(), minted + data.amt); + let balance = self + .get_token_address_avail_balance(&data.tick, &data.address, db_tx, ctx) + .unwrap_or(0.0); + self.token_addr_avail_balances.put( + format!("{}:{}", data.tick, data.address), + balance + data.amt, // Increase for minter. + ); + self.db_cache.ledger_rows.push(Brc20DbLedgerRow { + inscription_id: reveal.inscription_id.clone(), + inscription_number: reveal.inscription_number.jubilee as u64, + ordinal_number: reveal.ordinal_number, + block_height: block_identifier.index, + tx_index, + tick: data.tick.clone(), + address: data.address.clone(), + avail_balance: data.amt, + trans_balance: 0.0, + operation: "mint".to_string(), + }); + self.ignore_inscription(reveal.ordinal_number); + } + + pub fn insert_token_transfer( + &mut self, + data: &VerifiedBrc20BalanceData, + reveal: &OrdinalInscriptionRevealData, + block_identifier: &BlockIdentifier, + tx_index: u64, + db_tx: &Transaction, + ctx: &Context, + ) { + let Some(balance) = + self.get_token_address_avail_balance(&data.tick, &data.address, db_tx, ctx) + else { + unreachable!("BRC-20 transfer insert attempted for an address with no balance"); + }; + self.token_addr_avail_balances.put( + format!("{}:{}", data.tick, data.address), + balance - data.amt, // Decrease for sender. + ); + let ledger_row = Brc20DbLedgerRow { + inscription_id: reveal.inscription_id.clone(), + inscription_number: reveal.inscription_number.jubilee as u64, + ordinal_number: reveal.ordinal_number, + block_height: block_identifier.index, + tx_index, + tick: data.tick.clone(), + address: data.address.clone(), + avail_balance: data.amt * -1.0, + trans_balance: data.amt, + operation: "transfer".to_string(), + }; + self.unsent_transfers + .put(reveal.ordinal_number, ledger_row.clone()); + self.db_cache.ledger_rows.push(ledger_row); + self.ignored_inscriptions.pop(&reveal.ordinal_number); // Just in case. + } + + pub fn insert_token_transfer_send( + &mut self, + data: &VerifiedBrc20TransferData, + transfer: &OrdinalInscriptionTransferData, + block_identifier: &BlockIdentifier, + tx_index: u64, + db_tx: &Transaction, + ctx: &Context, + ) { + let transfer_row = self.get_unsent_transfer_row(transfer.ordinal_number, db_tx, ctx); + self.db_cache.ledger_rows.push(Brc20DbLedgerRow { + inscription_id: transfer_row.inscription_id.clone(), + inscription_number: transfer_row.inscription_number, + ordinal_number: transfer.ordinal_number, + block_height: block_identifier.index, + tx_index, + tick: data.tick.clone(), + address: data.sender_address.clone(), + avail_balance: 0.0, + trans_balance: data.amt * -1.0, + operation: "transfer_send".to_string(), + }); + self.db_cache.ledger_rows.push(Brc20DbLedgerRow { + inscription_id: transfer_row.inscription_id.clone(), + inscription_number: transfer_row.inscription_number, + ordinal_number: transfer.ordinal_number, + block_height: block_identifier.index, + tx_index, + tick: data.tick.clone(), + address: data.receiver_address.clone(), + avail_balance: data.amt, + trans_balance: 0.0, + operation: "transfer_receive".to_string(), + }); + let balance = self + .get_token_address_avail_balance(&data.tick, &data.receiver_address, db_tx, ctx) + .unwrap_or(0.0); + self.token_addr_avail_balances.put( + format!("{}:{}", data.tick, data.receiver_address), + balance + data.amt, // Increase for receiver. + ); + // We're not interested in further transfers. + self.unsent_transfers.pop(&transfer.ordinal_number); + self.ignore_inscription(transfer.ordinal_number); + } + + // + // + // + + fn get_unsent_transfer_row( + &mut self, + ordinal_number: u64, + db_tx: &Transaction, + ctx: &Context, + ) -> Brc20DbLedgerRow { + if let Some(transfer) = self.unsent_transfers.get(&ordinal_number) { + return transfer.clone(); + } + self.handle_cache_miss(db_tx, ctx); + let Some(transfer) = get_unsent_token_transfer(ordinal_number, db_tx, ctx) else { + unreachable!("Invalid transfer ordinal number {}", ordinal_number) + }; + self.unsent_transfers.put(ordinal_number, transfer.clone()); + return transfer; + } + + fn handle_cache_miss(&mut self, db_tx: &Transaction, ctx: &Context) { + // TODO: Measure this event somewhere + self.db_cache.flush(db_tx, ctx); + } +} + +#[cfg(test)] +mod test { + use chainhook_sdk::types::{BitcoinNetwork, BlockIdentifier}; + use test_case::test_case; + + use crate::core::meta_protocols::brc20::{ + db::initialize_brc20_db, + parser::{ParsedBrc20BalanceData, ParsedBrc20Operation}, + test_utils::{get_test_ctx, Brc20RevealBuilder}, + verifier::{ + verify_brc20_operation, VerifiedBrc20BalanceData, VerifiedBrc20Operation, + VerifiedBrc20TokenDeployData, + }, + }; + + use super::Brc20MemoryCache; + + #[test] + fn test_brc20_memory_cache_transfer_miss() { + let ctx = get_test_ctx(); + let mut conn = initialize_brc20_db(None, &ctx); + let tx = conn.transaction().unwrap(); + // LRU size as 1 so we can test a miss. + let mut cache = Brc20MemoryCache::new(1); + cache.insert_token_deploy( + &VerifiedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + self_mint: false, + }, + &Brc20RevealBuilder::new().inscription_number(0).build(), + &BlockIdentifier { + index: 800000, + hash: "00000000000000000002d8ba402150b259ddb2b30a1d32ab4a881d4653bceb5b" + .to_string(), + }, + 0, + &tx, + &ctx, + ); + let block = BlockIdentifier { + index: 800002, + hash: "00000000000000000002d8ba402150b259ddb2b30a1d32ab4a881d4653bceb5b".to_string(), + }; + let address1 = "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(); + let address2 = "bc1pngjqgeamkmmhlr6ft5yllgdmfllvcvnw5s7ew2ler3rl0z47uaesrj6jte".to_string(); + cache.insert_token_mint( + &VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 1000.0, + address: address1.clone(), + }, + &Brc20RevealBuilder::new().inscription_number(1).build(), + &block, + 0, + &tx, + &ctx, + ); + cache.insert_token_transfer( + &VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 100.0, + address: address1.clone(), + }, + &Brc20RevealBuilder::new().inscription_number(2).build(), + &block, + 1, + &tx, + &ctx, + ); + // These mint+transfer from a 2nd address will delete the first address' entries from cache. + cache.insert_token_mint( + &VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 1000.0, + address: address2.clone(), + }, + &Brc20RevealBuilder::new() + .inscription_number(3) + .inscriber_address(Some(address2.clone())) + .build(), + &block, + 2, + &tx, + &ctx, + ); + cache.insert_token_transfer( + &VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 100.0, + address: address2.clone(), + }, + &Brc20RevealBuilder::new() + .inscription_number(4) + .inscriber_address(Some(address2.clone())) + .build(), + &block, + 3, + &tx, + &ctx, + ); + // Validate another transfer from the first address. Should pass because we still have 900 avail balance. + let result = verify_brc20_operation( + &ParsedBrc20Operation::Transfer(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "100".to_string(), + }), + &Brc20RevealBuilder::new() + .inscription_number(5) + .inscriber_address(Some(address1.clone())) + .build(), + &block, + &BitcoinNetwork::Mainnet, + &mut cache, + &tx, + &ctx, + ); + assert!( + result + == Ok(VerifiedBrc20Operation::TokenTransfer( + VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 100.0, + address: address1 + } + )) + ) + } + + #[test_case(500.0 => Ok(Some(500.0)); "with transfer amt")] + #[test_case(1000.0 => Ok(Some(0.0)); "with transfer to zero")] + fn test_brc20_memory_cache_transfer_avail_balance(amt: f64) -> Result, String> { + let ctx = get_test_ctx(); + let mut conn = initialize_brc20_db(None, &ctx); + let tx = conn.transaction().unwrap(); + let mut cache = Brc20MemoryCache::new(10); + cache.insert_token_deploy( + &VerifiedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + self_mint: false, + }, + &Brc20RevealBuilder::new().inscription_number(0).build(), + &BlockIdentifier { + index: 800000, + hash: "00000000000000000002d8ba402150b259ddb2b30a1d32ab4a881d4653bceb5b" + .to_string(), + }, + 0, + &tx, + &ctx, + ); + cache.insert_token_mint( + &VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 1000.0, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + }, + &Brc20RevealBuilder::new().inscription_number(1).build(), + &BlockIdentifier { + index: 800001, + hash: "00000000000000000002d8ba402150b259ddb2b30a1d32ab4a881d4653bceb5b" + .to_string(), + }, + 0, + &tx, + &ctx, + ); + assert!( + cache.get_token_address_avail_balance( + "pepe", + "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp", + &tx, + &ctx, + ) == Some(1000.0) + ); + cache.insert_token_transfer( + &VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + }, + &Brc20RevealBuilder::new().inscription_number(2).build(), + &BlockIdentifier { + index: 800002, + hash: "00000000000000000002d8ba402150b259ddb2b30a1d32ab4a881d4653bceb5b" + .to_string(), + }, + 0, + &tx, + &ctx, + ); + Ok(cache.get_token_address_avail_balance( + "pepe", + "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp", + &tx, + &ctx, + )) + } +} diff --git a/components/ordhook-core/src/core/meta_protocols/brc20/db.rs b/components/ordhook-core/src/core/meta_protocols/brc20/db.rs new file mode 100644 index 0000000..baa9c18 --- /dev/null +++ b/components/ordhook-core/src/core/meta_protocols/brc20/db.rs @@ -0,0 +1,429 @@ +use std::{collections::HashMap, path::PathBuf}; + +use crate::db::{ + create_or_open_readwrite_db, format_inscription_id, open_existing_readonly_db, + perform_query_one, perform_query_set, +}; +use chainhook_sdk::{ + types::{ + BitcoinTransactionData, BlockIdentifier, Brc20BalanceData, Brc20Operation, + Brc20TokenDeployData, Brc20TransferData, + }, + utils::Context, +}; +use rusqlite::{Connection, ToSql, Transaction}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Brc20DbTokenRow { + pub inscription_id: String, + pub inscription_number: u64, + pub block_height: u64, + pub tick: String, + pub max: f64, + pub lim: f64, + pub dec: u64, + pub address: String, + pub self_mint: bool, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Brc20DbLedgerRow { + pub inscription_id: String, + pub inscription_number: u64, + pub ordinal_number: u64, + pub block_height: u64, + pub tx_index: u64, + pub tick: String, + pub address: String, + pub avail_balance: f64, + pub trans_balance: f64, + pub operation: String, +} + +pub fn get_default_brc20_db_file_path(base_dir: &PathBuf) -> PathBuf { + let mut destination_path = base_dir.clone(); + destination_path.push("brc20.sqlite"); + destination_path +} + +pub fn initialize_brc20_db(base_dir: Option<&PathBuf>, ctx: &Context) -> Connection { + let db_path = base_dir.map(|dir| get_default_brc20_db_file_path(dir)); + let conn = create_or_open_readwrite_db(db_path.as_ref(), ctx); + if let Err(e) = conn.execute( + "CREATE TABLE IF NOT EXISTS tokens ( + inscription_id TEXT NOT NULL PRIMARY KEY, + inscription_number INTEGER NOT NULL, + block_height INTEGER NOT NULL, + tick TEXT NOT NULL, + max REAL NOT NULL, + lim REAL NOT NULL, + dec INTEGER NOT NULL, + address TEXT NOT NULL, + self_mint BOOL NOT NULL, + UNIQUE (inscription_id), + UNIQUE (inscription_number), + UNIQUE (tick) + )", + [], + ) { + ctx.try_log(|logger| warn!(logger, "Unable to create table tokens: {}", e.to_string())); + } else { + if let Err(e) = conn.execute( + "CREATE INDEX IF NOT EXISTS index_tokens_on_block_height ON tokens(block_height);", + [], + ) { + ctx.try_log(|logger| warn!(logger, "unable to create brc20.sqlite: {}", e.to_string())); + } + } + if let Err(e) = conn.execute( + "CREATE TABLE IF NOT EXISTS ledger ( + inscription_id TEXT NOT NULL, + inscription_number INTEGER NOT NULL, + ordinal_number INTEGER NOT NULL, + block_height INTEGER NOT NULL, + tx_index INTEGER NOT NULL, + tick TEXT NOT NULL, + address TEXT NOT NULL, + avail_balance REAL NOT NULL, + trans_balance REAL NOT NULL, + operation TEXT NOT NULL CHECK(operation IN ('deploy', 'mint', 'transfer', 'transfer_send', 'transfer_receive')) + )", + [], + ) { + ctx.try_log(|logger| warn!(logger, "Unable to create table ledger: {}", e.to_string())); + } else { + if let Err(e) = conn.execute( + "CREATE INDEX IF NOT EXISTS index_ledger_on_tick_address ON ledger(tick, address);", + [], + ) { + ctx.try_log(|logger| warn!(logger, "unable to create brc20.sqlite: {}", e.to_string())); + } + if let Err(e) = conn.execute( + "CREATE INDEX IF NOT EXISTS index_ledger_on_ordinal_number_operation ON ledger(ordinal_number, operation);", + [], + ) { + ctx.try_log(|logger| warn!(logger, "unable to create brc20.sqlite: {}", e.to_string())); + } + if let Err(e) = conn.execute( + "CREATE INDEX IF NOT EXISTS index_ledger_on_block_height_operation ON ledger(block_height, operation);", + [], + ) { + ctx.try_log(|logger| warn!(logger, "unable to create brc20.sqlite: {}", e.to_string())); + } + if let Err(e) = conn.execute( + "CREATE INDEX IF NOT EXISTS index_ledger_on_inscription_id ON ledger(inscription_id);", + [], + ) { + ctx.try_log(|logger| warn!(logger, "unable to create brc20.sqlite: {}", e.to_string())); + } + if let Err(e) = conn.execute( + "CREATE INDEX IF NOT EXISTS index_ledger_on_inscription_number ON ledger(inscription_number);", + [], + ) { + ctx.try_log(|logger| warn!(logger, "unable to create brc20.sqlite: {}", e.to_string())); + } + } + + conn +} + +pub fn open_readwrite_brc20_db_conn( + base_dir: &PathBuf, + ctx: &Context, +) -> Result { + let db_path = get_default_brc20_db_file_path(&base_dir); + let conn = create_or_open_readwrite_db(Some(&db_path), ctx); + Ok(conn) +} + +pub fn open_readonly_brc20_db_conn( + base_dir: &PathBuf, + ctx: &Context, +) -> Result { + let db_path = get_default_brc20_db_file_path(&base_dir); + let conn = open_existing_readonly_db(&db_path, ctx); + Ok(conn) +} + +pub fn delete_activity_in_block_range( + start_block: u32, + end_block: u32, + db_tx: &Connection, + ctx: &Context, +) { + while let Err(e) = db_tx.execute( + "DELETE FROM ledger WHERE block_height >= ?1 AND block_height <= ?2", + rusqlite::params![&start_block, &end_block], + ) { + ctx.try_log(|logger| warn!(logger, "unable to query brc20.sqlite: {}", e.to_string())); + std::thread::sleep(std::time::Duration::from_secs(1)); + } + while let Err(e) = db_tx.execute( + "DELETE FROM tokens WHERE block_height >= ?1 AND block_height <= ?2", + rusqlite::params![&start_block, &end_block], + ) { + ctx.try_log(|logger| warn!(logger, "unable to query brc20.sqlite: {}", e.to_string())); + std::thread::sleep(std::time::Duration::from_secs(1)); + } +} + +pub fn get_token(tick: &str, db_tx: &Connection, ctx: &Context) -> Option { + let args: &[&dyn ToSql] = &[&tick.to_sql().unwrap()]; + let query = " + SELECT tick, max, lim, dec, address, inscription_id, inscription_number, block_height, self_mint + FROM tokens + WHERE tick = ? + "; + perform_query_one(query, args, &db_tx, ctx, |row| Brc20DbTokenRow { + tick: row.get(0).unwrap(), + max: row.get(1).unwrap(), + lim: row.get(2).unwrap(), + dec: row.get(3).unwrap(), + address: row.get(4).unwrap(), + inscription_id: row.get(5).unwrap(), + inscription_number: row.get(6).unwrap(), + block_height: row.get(7).unwrap(), + self_mint: row.get(8).unwrap(), + }) +} + +pub fn get_token_minted_supply(tick: &str, db_tx: &Transaction, ctx: &Context) -> Option { + let args: &[&dyn ToSql] = &[&tick.to_sql().unwrap()]; + let query = " + SELECT COALESCE(SUM(avail_balance + trans_balance), 0.0) AS minted + FROM ledger + WHERE tick = ? + "; + perform_query_one(query, args, &db_tx, ctx, |row| row.get(0).unwrap()).unwrap_or(None) +} + +pub fn get_token_available_balance_for_address( + tick: &str, + address: &str, + db_tx: &Transaction, + ctx: &Context, +) -> Option { + let args: &[&dyn ToSql] = &[&tick.to_sql().unwrap(), &address.to_sql().unwrap()]; + let query = " + SELECT SUM(avail_balance) AS avail_balance + FROM ledger + WHERE tick = ? AND address = ? + "; + perform_query_one(query, args, &db_tx, ctx, |row| row.get(0).unwrap()).unwrap_or(None) +} + +pub fn get_unsent_token_transfer( + ordinal_number: u64, + db_tx: &Connection, + ctx: &Context, +) -> Option { + let args: &[&dyn ToSql] = &[ + &ordinal_number.to_sql().unwrap(), + &ordinal_number.to_sql().unwrap(), + ]; + let query = " + SELECT inscription_id, inscription_number, ordinal_number, block_height, tx_index, tick, address, avail_balance, trans_balance, operation + FROM ledger + WHERE ordinal_number = ? AND operation = 'transfer' + AND NOT EXISTS ( + SELECT 1 FROM ledger WHERE ordinal_number = ? AND operation = 'transfer_send' + ) + LIMIT 1 + "; + perform_query_one(query, args, &db_tx, ctx, |row| Brc20DbLedgerRow { + inscription_id: row.get(0).unwrap(), + inscription_number: row.get(1).unwrap(), + ordinal_number: row.get(2).unwrap(), + block_height: row.get(3).unwrap(), + tx_index: row.get(4).unwrap(), + tick: row.get(5).unwrap(), + address: row.get(6).unwrap(), + avail_balance: row.get(7).unwrap(), + trans_balance: row.get(8).unwrap(), + operation: row.get(9).unwrap(), + }) +} + +pub fn get_transfer_send_receiver_address( + ordinal_number: u64, + db_tx: &Connection, + ctx: &Context, +) -> Option { + let args: &[&dyn ToSql] = &[&ordinal_number.to_sql().unwrap()]; + let query = " + SELECT address + FROM ledger + WHERE ordinal_number = ? AND operation = 'transfer_receive' + LIMIT 1 + "; + perform_query_one(query, args, &db_tx, ctx, |row| row.get(0).unwrap()) +} + +pub fn insert_ledger_rows(rows: &Vec, db_tx: &Transaction, ctx: &Context) { + match db_tx.prepare_cached("INSERT INTO ledger + (inscription_id, inscription_number, ordinal_number, block_height, tx_index, tick, address, avail_balance, trans_balance, operation) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") { + Ok(mut stmt) => { + for row in rows.iter() { + while let Err(e) = stmt.execute(rusqlite::params![ + &row.inscription_id, + &row.inscription_number, + &row.ordinal_number, + &row.block_height, + &row.tx_index, + &row.tick, + &row.address, + &row.avail_balance, + &row.trans_balance, + &row.operation + ]) { + ctx.try_log(|logger| warn!(logger, "unable to insert into brc20.sqlite: {}", e.to_string())); + std::thread::sleep(std::time::Duration::from_secs(1)); + } + } + }, + Err(error) => ctx.try_log(|logger| warn!(logger, "unable to prepare statement for brc20.sqlite: {}", error.to_string())) + } +} + +pub fn insert_token_rows(rows: &Vec, db_tx: &Transaction, ctx: &Context) { + match db_tx.prepare_cached( + "INSERT INTO tokens + (inscription_id, inscription_number, block_height, tick, max, lim, dec, address, self_mint) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", + ) { + Ok(mut stmt) => { + for row in rows.iter() { + while let Err(e) = stmt.execute(rusqlite::params![ + &row.inscription_id, + &row.inscription_number, + &row.block_height, + &row.tick, + &row.max, + &row.lim, + &row.dec, + &row.address, + &row.self_mint, + ]) { + ctx.try_log(|logger| { + warn!( + logger, + "unable to insert into brc20.sqlite: {}", + e.to_string() + ) + }); + std::thread::sleep(std::time::Duration::from_secs(1)); + } + } + } + Err(error) => ctx.try_log(|logger| { + warn!( + logger, + "unable to prepare statement for brc20.sqlite: {}", + error.to_string() + ) + }), + } +} + +pub fn get_brc20_operations_on_block( + block_identifier: &BlockIdentifier, + db_tx: &Connection, + ctx: &Context, +) -> HashMap { + let args: &[&dyn ToSql] = &[&block_identifier.index.to_sql().unwrap()]; + let query = " + SELECT + inscription_id, inscription_number, ordinal_number, block_height, tx_index, tick, address, avail_balance, trans_balance, operation + FROM ledger AS l + WHERE block_height = ? AND operation <> 'transfer_receive' + "; + let mut map = HashMap::new(); + let rows = perform_query_set(query, args, &db_tx, &ctx, |row| Brc20DbLedgerRow { + inscription_id: row.get(0).unwrap(), + inscription_number: row.get(1).unwrap(), + ordinal_number: row.get(2).unwrap(), + block_height: row.get(3).unwrap(), + tx_index: row.get(4).unwrap(), + tick: row.get(5).unwrap(), + address: row.get(6).unwrap(), + avail_balance: row.get(7).unwrap(), + trans_balance: row.get(8).unwrap(), + operation: row.get(9).unwrap(), + }); + for row in rows.iter() { + map.insert(row.inscription_id.clone(), row.clone()); + } + map +} + +pub fn augment_transaction_with_brc20_operation_data( + tx: &mut BitcoinTransactionData, + token_map: &mut HashMap, + block_ledger_map: &mut HashMap, + db_conn: &Connection, + ctx: &Context, +) { + let inscription_id = format_inscription_id(&tx.transaction_identifier, 0); + let Some(entry) = block_ledger_map.remove(inscription_id.as_str()) else { + return; + }; + if token_map.get(&entry.tick) == None { + let Some(row) = get_token(&entry.tick, &db_conn, &ctx) else { + unreachable!("BRC-20 token not found when processing operation"); + }; + token_map.insert(entry.tick.clone(), row); + } + let token = token_map + .get(&entry.tick) + .expect("Token not present in map"); + let dec = token.dec as usize; + match entry.operation.as_str() { + "deploy" => { + tx.metadata.brc20_operation = Some(Brc20Operation::Deploy(Brc20TokenDeployData { + tick: token.tick.clone(), + max: format!("{:.precision$}", token.max, precision = dec), + lim: format!("{:.precision$}", token.lim, precision = dec), + dec: token.dec.to_string(), + address: token.address.clone(), + inscription_id: token.inscription_id.clone(), + self_mint: token.self_mint, + })); + } + "mint" => { + tx.metadata.brc20_operation = Some(Brc20Operation::Mint(Brc20BalanceData { + tick: entry.tick.clone(), + amt: format!("{:.precision$}", entry.avail_balance, precision = dec), + address: entry.address.clone(), + inscription_id: entry.inscription_id.clone(), + })); + } + "transfer" => { + tx.metadata.brc20_operation = Some(Brc20Operation::Transfer(Brc20BalanceData { + tick: entry.tick.clone(), + amt: format!("{:.precision$}", entry.trans_balance, precision = dec), + address: entry.address.clone(), + inscription_id: entry.inscription_id.clone(), + })); + } + "transfer_send" => { + let Some(receiver_address) = + get_transfer_send_receiver_address(entry.ordinal_number, &db_conn, &ctx) + else { + unreachable!("Unable to fetch receiver address for transfer_send operation"); + }; + tx.metadata.brc20_operation = Some(Brc20Operation::TransferSend(Brc20TransferData { + tick: entry.tick.clone(), + amt: format!( + "{:.precision$}", + entry.trans_balance * -1.0, + precision = dec + ), + sender_address: entry.address.clone(), + receiver_address, + inscription_id: entry.inscription_id, + })); + } + _ => {} + } +} diff --git a/components/ordhook-core/src/core/meta_protocols/brc20/mod.rs b/components/ordhook-core/src/core/meta_protocols/brc20/mod.rs new file mode 100644 index 0000000..5641a7c --- /dev/null +++ b/components/ordhook-core/src/core/meta_protocols/brc20/mod.rs @@ -0,0 +1,25 @@ +use chainhook_sdk::types::BitcoinNetwork; + +pub mod db; +pub mod parser; +pub mod verifier; +pub mod cache; +pub mod test_utils; + +pub fn brc20_activation_height(network: &BitcoinNetwork) -> u64 { + match network { + BitcoinNetwork::Mainnet => 779832, + BitcoinNetwork::Regtest => todo!(), + BitcoinNetwork::Testnet => todo!(), + BitcoinNetwork::Signet => todo!(), + } +} + +pub fn brc20_self_mint_activation_height(network: &BitcoinNetwork) -> u64 { + match network { + BitcoinNetwork::Mainnet => 837090, + BitcoinNetwork::Regtest => todo!(), + BitcoinNetwork::Testnet => todo!(), + BitcoinNetwork::Signet => todo!(), + } +} diff --git a/components/ordhook-core/src/core/meta_protocols/brc20/parser.rs b/components/ordhook-core/src/core/meta_protocols/brc20/parser.rs new file mode 100644 index 0000000..ed1b811 --- /dev/null +++ b/components/ordhook-core/src/core/meta_protocols/brc20/parser.rs @@ -0,0 +1,651 @@ +use regex::Regex; + +use crate::ord::inscription::Inscription; +use crate::ord::media::{Language, Media}; + +#[derive(PartialEq, Debug, Clone)] +pub struct ParsedBrc20TokenDeployData { + pub tick: String, + pub max: f64, + pub lim: f64, + pub dec: u64, + pub self_mint: bool, +} + +#[derive(PartialEq, Debug, Clone)] +pub struct ParsedBrc20BalanceData { + pub tick: String, + // Keep as `String` instead of `f64` so we can later decide if it was inscribed with a correct + // number of decimals during verification, depending on the token's deployed definition. + pub amt: String, +} + +impl ParsedBrc20BalanceData { + pub fn float_amt(&self) -> f64 { + self.amt.parse::().unwrap() + } +} + +#[derive(PartialEq, Debug, Clone)] +pub enum ParsedBrc20Operation { + Deploy(ParsedBrc20TokenDeployData), + Mint(ParsedBrc20BalanceData), + Transfer(ParsedBrc20BalanceData), +} + +#[derive(Deserialize)] +struct Brc20DeployJson { + p: String, + op: String, + tick: String, + max: String, + lim: Option, + dec: Option, + self_mint: Option, +} + +#[derive(Deserialize)] +struct Brc20MintOrTransferJson { + p: String, + op: String, + tick: String, + amt: String, +} + +lazy_static! { + pub static ref NUMERIC_FLOAT_REGEX: Regex = + Regex::new(r#"^(([0-9]+)|([0-9]*\.?[0-9]+))$"#.into()).unwrap(); + pub static ref NUMERIC_INT_REGEX: Regex = Regex::new(r#"^([0-9]+)$"#.into()).unwrap(); +} + +pub fn amt_has_valid_decimals(amt: &str, max_decimals: u64) -> bool { + if amt.contains('.') + && amt.split('.').nth(1).map_or(0, |s| s.chars().count()) as u64 > max_decimals + { + return false; + } + true +} + +fn parse_float_numeric_value(n: &str, max_decimals: u64) -> Option { + if NUMERIC_FLOAT_REGEX.is_match(&n) { + if !amt_has_valid_decimals(n, max_decimals) { + return None; + } + match n.parse::() { + Ok(parsed) => { + if parsed > u64::MAX as f64 { + return None; + } + return Some(parsed); + } + _ => return None, + }; + } + None +} + +fn parse_int_numeric_value(n: &str) -> Option { + if NUMERIC_INT_REGEX.is_match(&n) { + match n.parse::() { + Ok(parsed) => { + if parsed > u64::MAX { + return None; + } + return Some(parsed); + } + _ => return None, + }; + } + None +} + +/// Attempts to parse an `Inscription` into a BRC20 operation by following the rules explained in +/// https://layer1.gitbook.io/layer1-foundation/protocols/brc-20/indexing +pub fn parse_brc20_operation( + inscription: &Inscription, +) -> Result, String> { + match inscription.media() { + Media::Code(Language::Json) | Media::Text => {} + _ => return Ok(None), + }; + let Some(inscription_body) = inscription.body() else { + return Ok(None); + }; + match serde_json::from_slice::(inscription_body) { + Ok(json) => { + if json.p != "brc-20" || json.op != "deploy" { + return Ok(None); + } + let mut deploy = ParsedBrc20TokenDeployData { + tick: json.tick.to_lowercase(), + max: 0.0, + lim: 0.0, + dec: 18, + self_mint: false, + }; + if json.self_mint == Some("true".to_string()) { + if json.tick.len() != 5 { + return Ok(None); + } + deploy.self_mint = true; + } else if json.tick.len() != 4 { + return Ok(None); + } + if let Some(dec) = json.dec { + let Some(parsed_dec) = parse_int_numeric_value(&dec) else { + return Ok(None); + }; + if parsed_dec > 18 { + return Ok(None); + } + deploy.dec = parsed_dec; + } + let Some(parsed_max) = parse_float_numeric_value(&json.max, deploy.dec) else { + return Ok(None); + }; + if parsed_max == 0.0 { + if deploy.self_mint { + deploy.max = u64::MAX as f64; + } else { + return Ok(None); + } + } else { + deploy.max = parsed_max; + } + if let Some(lim) = json.lim { + let Some(parsed_lim) = parse_float_numeric_value(&lim, deploy.dec) else { + return Ok(None); + }; + if parsed_lim == 0.0 { + return Ok(None); + } + deploy.lim = parsed_lim; + } else { + deploy.lim = deploy.max; + } + return Ok(Some(ParsedBrc20Operation::Deploy(deploy))); + } + Err(_) => match serde_json::from_slice::(inscription_body) { + Ok(json) => { + if json.p != "brc-20" || json.tick.len() < 4 || json.tick.len() > 5 { + return Ok(None); + } + let op_str = json.op.as_str(); + match op_str { + "mint" | "transfer" => { + let Some(parsed_amt) = parse_float_numeric_value(&json.amt, 18) else { + return Ok(None); + }; + if parsed_amt == 0.0 { + return Ok(None); + } + match op_str { + "mint" => { + return Ok(Some(ParsedBrc20Operation::Mint( + ParsedBrc20BalanceData { + tick: json.tick.to_lowercase(), + amt: json.amt.clone(), + }, + ))); + } + "transfer" => { + return Ok(Some(ParsedBrc20Operation::Transfer( + ParsedBrc20BalanceData { + tick: json.tick.to_lowercase(), + amt: json.amt.clone(), + }, + ))); + } + _ => return Ok(None), + } + } + _ => return Ok(None), + } + } + Err(_) => return Ok(None), + }, + }; +} + +#[cfg(test)] +mod test { + use super::{parse_brc20_operation, ParsedBrc20Operation}; + use crate::{ + core::meta_protocols::brc20::parser::{ParsedBrc20BalanceData, ParsedBrc20TokenDeployData}, + ord::inscription::Inscription, + }; + use test_case::test_case; + + struct InscriptionBuilder { + body: Option>, + content_encoding: Option>, + content_type: Option>, + } + + impl InscriptionBuilder { + fn new() -> Self { + InscriptionBuilder { + body: Some(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "21000000", "lim": "1000", "dec": "6"}"#.as_bytes().to_vec()), + content_encoding: Some("utf-8".as_bytes().to_vec()), + content_type: Some("text/plain".as_bytes().to_vec()), + } + } + + fn body(mut self, val: &str) -> Self { + self.body = Some(val.as_bytes().to_vec()); + self + } + + fn content_type(mut self, val: &str) -> Self { + self.content_type = Some(val.as_bytes().to_vec()); + self + } + + fn build(self) -> Inscription { + Inscription { + body: self.body, + content_encoding: self.content_encoding, + content_type: self.content_type, + duplicate_field: false, + incomplete_field: false, + metadata: None, + metaprotocol: None, + parent: None, + pointer: None, + unrecognized_even_field: false, + delegate: None, + } + } + } + + #[test_case( + InscriptionBuilder::new().build() + => Ok(Some(ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 6, + self_mint: false, + }))); "with deploy" + )] + #[test_case( + InscriptionBuilder::new().body(&String::from("{\"p\":\"brc-20\",\"op\":\"deploy\",\"tick\":\"X\0\0Z\",\"max\":\"21000000\",\"lim\":\"1000\",\"dec\":\"6\"}")).build() + => Ok(None); "with deploy null bytes" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "PEPE", "max": "21000000", "lim": "1000", "dec": "6"}"#).build() + => Ok(Some(ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 6, + self_mint: false, + }))); "with deploy uppercase" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "21000000", "lim": "1000"}"#).build() + => Ok(Some(ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + self_mint: false, + }))); "with deploy without dec" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "21000000"}"#).build() + => Ok(Some(ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 21000000.0, + dec: 18, + self_mint: false, + }))); "with deploy without lim or dec" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "21000000", "dec": "7"}"#).build() + => Ok(Some(ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 21000000.0, + dec: 7, + self_mint: false, + }))); "with deploy without lim" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "😉", "max": "21000000", "lim": "1000", "dec": "6"}"#).build() + => Ok(Some(ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { + tick: "😉".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 6, + self_mint: false, + }))); "with deploy 4-byte emoji tick" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "a b", "max": "21000000", "lim": "1000", "dec": "6"}"#).build() + => Ok(Some(ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { + tick: "a b".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 6, + self_mint: false, + }))); "with deploy 4-byte space tick" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "$pepe", "max": "21000000", "lim": "1000", "dec": "6", "self_mint": "true"}"#).build() + => Ok(Some(ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { + tick: "$pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 6, + self_mint: true, + }))); "with deploy 5-byte self mint" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "$pepe", "max": "0", "lim": "1000", "dec": "6", "self_mint": "true"}"#).build() + => Ok(Some(ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { + tick: "$pepe".to_string(), + max: u64::MAX as f64, + lim: 1000.0, + dec: 6, + self_mint: true, + }))); "with deploy self mint max 0" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "$pepe", "max": "21000000", "lim": "1000", "dec": "6"}"#).build() + => Ok(None); "with deploy 5-byte no self mint" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "21000000", "lim": "1000", "dec": "6", "foo": 99}"#).build() + => Ok(Some(ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 6, + self_mint: false, + }))); "with deploy extra fields" + )] + #[test_case( + InscriptionBuilder::new().content_type("text/html").build() + => Ok(None); "with invalid content_type" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p": "brc-20", "op": "deploy", "tick": "PEPE", "max": "21000000""#).build() + => Ok(None); "with invalid JSON" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc20", "op": "deploy", "tick": "pepe", "max": "21000000", "lim": "1000", "dec": "6",}"#).build() + => Ok(None); "with deploy JSON5" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"P":"brc20", "OP": "deploy", "TICK": "pepe", "MAX": "21000000", "LIM": "1000", "DEC": "6"}"#).build() + => Ok(None); "with deploy uppercase fields" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc20", "op": "deploy", "tick": "pepe", "max": "21000000", "lim": "1000", "dec": "6"}"#).build() + => Ok(None); "with deploy incorrect p field" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploi", "tick": "pepe", "max": "21000000", "lim": "1000", "dec": "6"}"#).build() + => Ok(None); "with deploy incorrect op field" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pep", "max": "21000000", "lim": "1000", "dec": "6"}"#).build() + => Ok(None); "with deploy short tick length" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepepepe", "max": "21000000", "lim": "1000", "dec": "6"}"#).build() + => Ok(None); "with deploy long tick length" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "21000000.", "lim": "1000", "dec": "6"}"#).build() + => Ok(None); "with deploy malformatted max" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "21000000", "lim": " 1000 ", "dec": "6"}"#).build() + => Ok(None); "with deploy malformatted lim" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "21000000", "lim": "1000.", "dec": "6.0"}"#).build() + => Ok(None); "with deploy malformatted dec" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": 21000000, "lim": "1000", "dec": "6"}"#).build() + => Ok(None); "with deploy int max" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "21000000", "lim": 1000, "dec": "6"}"#).build() + => Ok(None); "with deploy int lim" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "21000000", "lim": "1000", "dec": 6}"#).build() + => Ok(None); "with deploy int dec" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "", "lim": "1000", "dec": "6"}"#).build() + => Ok(None); "with deploy empty max" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "21000000", "lim": "", "dec": "6"}"#).build() + => Ok(None); "with deploy empty lim" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "21000000", "lim": "1000", "dec": ""}"#).build() + => Ok(None); "with deploy empty dec" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "99996744073709551615", "lim": "1000", "dec": "6"}"#).build() + => Ok(None); "with deploy large max" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "21000000", "lim": "99996744073709551615", "dec": "6"}"#).build() + => Ok(None); "with deploy large lim" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "21000000", "lim": "1000", "dec": "99996744073709551615"}"#).build() + => Ok(None); "with deploy large dec" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "0", "lim": "1000", "dec": "6"}"#).build() + => Ok(None); "with deploy zero max" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "21000000", "lim": "0", "dec": "6"}"#).build() + => Ok(None); "with deploy zero lim" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "21000000", "lim": "1000", "dec": "0"}"#).build() + => Ok(Some(ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 0, + self_mint: false, + }))); "with deploy zero dec" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "21000000.000", "lim": "1000", "dec": "0"}"#).build() + => Ok(None); "with deploy extra max decimals" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "deploy", "tick": "pepe", "max": "21000000", "lim": "1000.000", "dec": "0"}"#).build() + => Ok(None); "with deploy extra lim decimals" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "mint", "tick": "pepe", "amt": "1000"}"#).build() + => Ok(Some(ParsedBrc20Operation::Mint(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "1000".to_string() + }))); "with mint" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "mint", "tick": "PEPE", "amt": "1000"}"#).build() + => Ok(Some(ParsedBrc20Operation::Mint(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "1000".to_string() + }))); "with mint uppercase" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "mint", "tick": "😉", "amt": "1000"}"#).build() + => Ok(Some(ParsedBrc20Operation::Mint(ParsedBrc20BalanceData { + tick: "😉".to_string(), + amt: "1000".to_string() + }))); "with mint 4-byte emoji tick" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "mint", "tick": "$pepe", "amt": "1000"}"#).build() + => Ok(Some(ParsedBrc20Operation::Mint(ParsedBrc20BalanceData { + tick: "$pepe".to_string(), + amt: "1000".to_string() + }))); "with mint 5-byte tick" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "mint", "tick": "a b", "amt": "1000"}"#).build() + => Ok(Some(ParsedBrc20Operation::Mint(ParsedBrc20BalanceData { + tick: "a b".to_string(), + amt: "1000".to_string() + }))); "with mint 4-byte space tick" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "mint", "tick": "a b", "amt": "1000", "bar": "test"}"#).build() + => Ok(Some(ParsedBrc20Operation::Mint(ParsedBrc20BalanceData { + tick: "a b".to_string(), + amt: "1000".to_string() + }))); "with mint extra fields" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "mint", "tick": "a b", "amt": "1000",}"#).build() + => Ok(None); "with mint JSON5" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"P":"brc-20", "OP": "mint", "TICK": "a b", "AMT": "1000"}"#).build() + => Ok(None); "with mint uppercase fields" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc20", "op": "mint", "tick": "pepe", "amt": "1000"}"#).build() + => Ok(None); "with mint incorrect p field" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "mintt", "tick": "pepe", "amt": "1000"}"#).build() + => Ok(None); "with mint incorrect op field" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "mint", "tick": "pepe"}"#).build() + => Ok(None); "with mint without amt" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "mint", "tick": "pep", "amt": "1000"}"#).build() + => Ok(None); "with mint short tick length" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "mint", "tick": "pepepepe", "amt": "1000"}"#).build() + => Ok(None); "with mint long tick length" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "mint", "tick": "pepe", "amt": 1000}"#).build() + => Ok(None); "with mint int amt" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "mint", "tick": "pepe", "amt": ""}"#).build() + => Ok(None); "with mint empty amt" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "mint", "tick": "pepe", "amt": "0"}"#).build() + => Ok(None); "with mint zero amt" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "mint", "tick": "pepe", "amt": "99996744073709551615"}"#).build() + => Ok(None); "with mint large amt" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "transfer", "tick": "pepe", "amt": "1000"}"#).build() + => Ok(Some(ParsedBrc20Operation::Transfer(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "1000".to_string() + }))); "with transfer" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "transfer", "tick": "PEPE", "amt": "1000"}"#).build() + => Ok(Some(ParsedBrc20Operation::Transfer(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "1000".to_string() + }))); "with transfer uppercase" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "transfer", "tick": "😉", "amt": "1000"}"#).build() + => Ok(Some(ParsedBrc20Operation::Transfer(ParsedBrc20BalanceData { + tick: "😉".to_string(), + amt: "1000".to_string() + }))); "with transfer 4-byte emoji tick" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "transfer", "tick": "$pepe", "amt": "1000"}"#).build() + => Ok(Some(ParsedBrc20Operation::Transfer(ParsedBrc20BalanceData { + tick: "$pepe".to_string(), + amt: "1000".to_string() + }))); "with transfer 5-byte tick" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "transfer", "tick": "a b", "amt": "1000"}"#).build() + => Ok(Some(ParsedBrc20Operation::Transfer(ParsedBrc20BalanceData { + tick: "a b".to_string(), + amt: "1000".to_string() + }))); "with transfer 4-byte space tick" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "transfer", "tick": "a b", "amt": "1000", "bar": "test"}"#).build() + => Ok(Some(ParsedBrc20Operation::Transfer(ParsedBrc20BalanceData { + tick: "a b".to_string(), + amt: "1000".to_string() + }))); "with transfer extra fields" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "transfer", "tick": "pepe", "amt": "1000",}"#).build() + => Ok(None); "with transfer JSON5" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"P":"brc-20", "OP": "transfer", "TICK": "a b", "AMT": "1000"}"#).build() + => Ok(None); "with transfer uppercase fields" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc20", "op": "transfer", "tick": "pepe", "amt": "1000"}"#).build() + => Ok(None); "with transfer incorrect p field" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "transferzz", "tick": "pepe", "amt": "1000"}"#).build() + => Ok(None); "with transfer incorrect op field" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "transfer", "tick": "pepe"}"#).build() + => Ok(None); "with transfer without amt" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "transfer", "tick": "pep", "amt": "1000"}"#).build() + => Ok(None); "with transfer short tick length" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "transfer", "tick": "pepepepe", "amt": "1000"}"#).build() + => Ok(None); "with transfer long tick length" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "transfer", "tick": "pepe", "amt": 1000}"#).build() + => Ok(None); "with transfer int amt" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "transfer", "tick": "pepe", "amt": ""}"#).build() + => Ok(None); "with transfer empty amt" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "transfer", "tick": "pepe", "amt": "0"}"#).build() + => Ok(None); "with transfer zero amt" + )] + #[test_case( + InscriptionBuilder::new().body(r#"{"p":"brc-20", "op": "transfer", "tick": "pepe", "amt": "99996744073709551615"}"#).build() + => Ok(None); "with transfer large amt" + )] + fn test_brc20_parse(inscription: Inscription) -> Result, String> { + parse_brc20_operation(&inscription) + } +} 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 new file mode 100644 index 0000000..00b3b6e --- /dev/null +++ b/components/ordhook-core/src/core/meta_protocols/brc20/test_utils.rs @@ -0,0 +1,127 @@ +use chainhook_sdk::{types::{OrdinalInscriptionNumber, OrdinalInscriptionRevealData, OrdinalInscriptionTransferData, OrdinalInscriptionTransferDestination}, utils::Context}; + +pub fn get_test_ctx() -> Context { + let logger = hiro_system_kit::log::setup_logger(); + let _guard = hiro_system_kit::log::setup_global_logger(logger.clone()); + Context { + logger: Some(logger), + tracer: false, + } +} + +pub struct Brc20RevealBuilder { + pub inscription_number: OrdinalInscriptionNumber, + pub inscriber_address: Option, + pub inscription_id: String, + pub ordinal_number: u64, + pub parent: Option, +} + +impl Brc20RevealBuilder { + pub fn new() -> Self { + Brc20RevealBuilder { + inscription_number: OrdinalInscriptionNumber { + classic: 0, + jubilee: 0, + }, + inscriber_address: Some("324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string()), + inscription_id: + "9bb2314d666ae0b1db8161cb373fcc1381681f71445c4e0335aa80ea9c37fcddi0".to_string(), + ordinal_number: 0, + parent: None, + } + } + + pub fn inscription_number(mut self, val: i64) -> Self { + self.inscription_number = OrdinalInscriptionNumber { + classic: val, + jubilee: val, + }; + self + } + + pub fn inscriber_address(mut self, val: Option) -> Self { + self.inscriber_address = val; + self + } + + pub fn inscription_id(mut self, val: &str) -> Self { + self.inscription_id = val.to_string(); + self + } + + pub fn ordinal_number(mut self, val: u64) -> Self { + self.ordinal_number = val; + self + } + + pub fn parent(mut self, val: Option) -> Self { + self.parent = val; + self + } + + pub fn build(self) -> OrdinalInscriptionRevealData { + OrdinalInscriptionRevealData { + content_bytes: "".to_string(), + content_type: "text/plain".to_string(), + content_length: 10, + inscription_number: self.inscription_number, + inscription_fee: 100, + inscription_output_value: 10000, + inscription_id: self.inscription_id, + inscription_input_index: 0, + inscription_pointer: None, + inscriber_address: self.inscriber_address, + delegate: None, + metaprotocol: None, + metadata: None, + parent: self.parent, + ordinal_number: self.ordinal_number, + ordinal_block_height: 767430, + ordinal_offset: 0, + tx_index: 0, + transfers_pre_inscription: 0, + satpoint_post_inscription: + "9bb2314d666ae0b1db8161cb373fcc1381681f71445c4e0335aa80ea9c37fcdd:0:0" + .to_string(), + curse_type: None, + } + } +} + +pub struct Brc20TransferBuilder { + pub ordinal_number: u64, + pub destination: OrdinalInscriptionTransferDestination, +} + +impl Brc20TransferBuilder { + pub fn new() -> Self { + Brc20TransferBuilder { + ordinal_number: 0, + destination: OrdinalInscriptionTransferDestination::Transferred( + "bc1pls75sfwullhygkmqap344f5cqf97qz95lvle6fvddm0tpz2l5ffslgq3m0".to_string(), + ), + } + } + + pub fn ordinal_number(mut self, val: u64) -> Self { + self.ordinal_number = val; + self + } + + pub fn destination(mut self, val: OrdinalInscriptionTransferDestination) -> Self { + self.destination = val; + self + } + + pub fn build(self) -> OrdinalInscriptionTransferData { + OrdinalInscriptionTransferData { + ordinal_number: self.ordinal_number, + destination: self.destination, + satpoint_pre_transfer: "".to_string(), + satpoint_post_transfer: "".to_string(), + post_transfer_output_value: Some(500), + 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 new file mode 100644 index 0000000..28fedb3 --- /dev/null +++ b/components/ordhook-core/src/core/meta_protocols/brc20/verifier.rs @@ -0,0 +1,996 @@ +use chainhook_sdk::types::{ + BitcoinNetwork, BlockIdentifier, OrdinalInscriptionRevealData, OrdinalInscriptionTransferData, + OrdinalInscriptionTransferDestination, +}; +use chainhook_sdk::utils::Context; +use rusqlite::Transaction; + +use super::brc20_self_mint_activation_height; +use super::cache::Brc20MemoryCache; +use super::parser::{amt_has_valid_decimals, ParsedBrc20Operation}; + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct VerifiedBrc20TokenDeployData { + pub tick: String, + pub max: f64, + pub lim: f64, + pub dec: u64, + pub address: String, + pub self_mint: bool, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct VerifiedBrc20BalanceData { + pub tick: String, + pub amt: f64, + pub address: String, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct VerifiedBrc20TransferData { + pub tick: String, + pub amt: f64, + pub sender_address: String, + pub receiver_address: String, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub enum VerifiedBrc20Operation { + TokenDeploy(VerifiedBrc20TokenDeployData), + TokenMint(VerifiedBrc20BalanceData), + TokenTransfer(VerifiedBrc20BalanceData), + TokenTransferSend(VerifiedBrc20TransferData), +} + +pub fn verify_brc20_operation( + operation: &ParsedBrc20Operation, + reveal: &OrdinalInscriptionRevealData, + block_identifier: &BlockIdentifier, + network: &BitcoinNetwork, + cache: &mut Brc20MemoryCache, + db_tx: &Transaction, + ctx: &Context, +) -> Result { + let Some(inscriber_address) = reveal.inscriber_address.clone() else { + return Err(format!("Invalid inscriber address")); + }; + if inscriber_address.is_empty() { + return Err(format!("Empty inscriber address")); + } + if reveal.inscription_number.classic < 0 { + return Err(format!("Inscription is cursed")); + } + match operation { + ParsedBrc20Operation::Deploy(data) => { + if cache.get_token(&data.tick, db_tx, ctx).is_some() { + return Err(format!("Token {} already exists", &data.tick)); + } + if data.self_mint && block_identifier.index < brc20_self_mint_activation_height(network) + { + return Err(format!( + "Self-minted token deploy {} prohibited before activation height", + &data.tick + )); + } + return Ok(VerifiedBrc20Operation::TokenDeploy( + VerifiedBrc20TokenDeployData { + tick: data.tick.clone(), + max: data.max, + lim: data.lim, + dec: data.dec, + address: inscriber_address, + self_mint: data.self_mint, + }, + )); + } + ParsedBrc20Operation::Mint(data) => { + let Some(token) = cache.get_token(&data.tick, db_tx, ctx) else { + return Err(format!( + "Token {} does not exist on mint attempt", + &data.tick + )); + }; + if data.tick.len() == 5 { + let Some(parent) = &reveal.parent else { + return Err(format!( + "Attempting to mint self-minted token {} without a parent ref", + &data.tick + )); + }; + if parent != &token.inscription_id { + return Err(format!( + "Mint attempt for self-minted token {} does not point to deploy as parent", + &data.tick + )); + } + } + if data.float_amt() > token.lim { + return Err(format!( + "Cannot mint more than {} tokens for {}, attempted to mint {}", + token.lim, token.tick, data.amt + )); + } + if !amt_has_valid_decimals(&data.amt, token.dec) { + return Err(format!( + "Invalid decimals in amt field for {} mint, attempting to mint {}", + token.tick, data.amt + )); + } + let Some(minted_supply) = cache.get_token_minted_supply(&data.tick, db_tx, ctx) else { + unreachable!("BRC-20 token exists but does not have entries in the ledger"); + }; + let remaining_supply = token.max - minted_supply; + if remaining_supply == 0.0 { + return Err(format!( + "No supply available for {} mint, attempted to mint {}, remaining {}", + token.tick, data.amt, remaining_supply + )); + } + let real_mint_amt = data.float_amt().min(token.lim.min(remaining_supply)); + return Ok(VerifiedBrc20Operation::TokenMint( + VerifiedBrc20BalanceData { + tick: token.tick, + amt: real_mint_amt, + address: inscriber_address, + }, + )); + } + ParsedBrc20Operation::Transfer(data) => { + let Some(token) = cache.get_token(&data.tick, db_tx, ctx) else { + return Err(format!( + "Token {} does not exist on transfer attempt", + &data.tick + )); + }; + if !amt_has_valid_decimals(&data.amt, token.dec) { + return Err(format!( + "Invalid decimals in amt field for {} transfer, attempting to transfer {}", + token.tick, data.amt + )); + } + let Some(avail_balance) = cache.get_token_address_avail_balance( + &token.tick, + &inscriber_address, + db_tx, + ctx, + ) else { + return Err(format!("Balance does not exist for {} transfer, attempting to transfer {}", token.tick, data.amt)); + }; + if avail_balance < data.float_amt() { + return Err(format!("Insufficient balance for {} transfer, attempting to transfer {}, only {} available", token.tick, data.amt, avail_balance)); + } + return Ok(VerifiedBrc20Operation::TokenTransfer( + VerifiedBrc20BalanceData { + tick: token.tick, + amt: data.float_amt(), + address: inscriber_address, + }, + )); + } + }; +} + +pub fn verify_brc20_transfer( + transfer: &OrdinalInscriptionTransferData, + cache: &mut Brc20MemoryCache, + db_tx: &Transaction, + ctx: &Context, +) -> Result { + let Some(transfer_row) = cache.get_unsent_token_transfer(transfer.ordinal_number, db_tx, ctx) + else { + return Err(format!( + "No BRC-20 transfer in ordinal {} or transfer already sent", + transfer.ordinal_number + )); + }; + match &transfer.destination { + OrdinalInscriptionTransferDestination::Transferred(receiver_address) => { + return Ok(VerifiedBrc20TransferData { + tick: transfer_row.tick.clone(), + amt: transfer_row.trans_balance, + sender_address: transfer_row.address.clone(), + receiver_address: receiver_address.to_string(), + }); + } + OrdinalInscriptionTransferDestination::SpentInFees => { + return Ok(VerifiedBrc20TransferData { + tick: transfer_row.tick.clone(), + amt: transfer_row.trans_balance, + sender_address: transfer_row.address.clone(), + receiver_address: transfer_row.address.clone(), // Return to sender + }); + } + OrdinalInscriptionTransferDestination::Burnt(_) => { + return Ok(VerifiedBrc20TransferData { + tick: transfer_row.tick.clone(), + amt: transfer_row.trans_balance, + sender_address: transfer_row.address.clone(), + receiver_address: "".to_string(), + }); + } + }; +} + +#[cfg(test)] +mod test { + use chainhook_sdk::types::{ + BitcoinNetwork, BlockIdentifier, OrdinalInscriptionRevealData, + OrdinalInscriptionTransferData, OrdinalInscriptionTransferDestination, + }; + use test_case::test_case; + + use crate::core::meta_protocols::brc20::{ + cache::Brc20MemoryCache, + db::initialize_brc20_db, + parser::{ParsedBrc20BalanceData, ParsedBrc20Operation, ParsedBrc20TokenDeployData}, + test_utils::{get_test_ctx, Brc20RevealBuilder, Brc20TransferBuilder}, + verifier::{ + VerifiedBrc20BalanceData, VerifiedBrc20Operation, VerifiedBrc20TokenDeployData, + }, + }; + + use super::{verify_brc20_operation, verify_brc20_transfer, VerifiedBrc20TransferData}; + + #[test_case( + ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + self_mint: false, + }), + (Brc20RevealBuilder::new().inscriber_address(None).build(), 830000) + => Err("Invalid inscriber address".to_string()); "with invalid address" + )] + #[test_case( + ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { + tick: "$pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + self_mint: true, + }), + (Brc20RevealBuilder::new().build(), 830000) + => Err("Self-minted token deploy $pepe prohibited before activation height".to_string()); + "with self mint before activation" + )] + #[test_case( + ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { + tick: "$pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + self_mint: true, + }), + (Brc20RevealBuilder::new().build(), 840000) + => Ok(VerifiedBrc20Operation::TokenDeploy(VerifiedBrc20TokenDeployData { + tick: "$pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + self_mint: true, + })); + "with valid self mint" + )] + #[test_case( + ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + self_mint: false, + }), + (Brc20RevealBuilder::new().inscriber_address(Some("".to_string())).build(), 830000) + => Err("Empty inscriber address".to_string()); "with empty address" + )] + #[test_case( + ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + self_mint: false, + }), + (Brc20RevealBuilder::new().inscription_number(-1).build(), 830000) + => Err("Inscription is cursed".to_string()); "with cursed inscription" + )] + #[test_case( + ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + self_mint: false, + }), + (Brc20RevealBuilder::new().build(), 830000) + => Ok( + VerifiedBrc20Operation::TokenDeploy(VerifiedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + self_mint: false, + }) + ); "with deploy" + )] + #[test_case( + ParsedBrc20Operation::Mint(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "1000.0".to_string(), + }), + (Brc20RevealBuilder::new().build(), 830000) + => Err("Token pepe does not exist on mint attempt".to_string()); + "with mint non existing token" + )] + #[test_case( + ParsedBrc20Operation::Transfer(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "1000.0".to_string(), + }), + (Brc20RevealBuilder::new().build(), 830000) + => Err("Token pepe does not exist on transfer attempt".to_string()); + "with transfer non existing token" + )] + fn test_brc20_verify_for_empty_db( + op: ParsedBrc20Operation, + args: (OrdinalInscriptionRevealData, u64), + ) -> Result { + let ctx = get_test_ctx(); + let mut conn = initialize_brc20_db(None, &ctx); + let tx = conn.transaction().unwrap(); + verify_brc20_operation( + &op, + &args.0, + &BlockIdentifier { + index: args.1, + hash: "00000000000000000002d8ba402150b259ddb2b30a1d32ab4a881d4653bceb5b" + .to_string(), + }, + &BitcoinNetwork::Mainnet, + &mut Brc20MemoryCache::new(50), + &tx, + &ctx, + ) + } + + #[test_case( + ParsedBrc20Operation::Deploy(ParsedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + self_mint: false, + }), + Brc20RevealBuilder::new().inscription_number(1).build() + => Err("Token pepe already exists".to_string()); "with deploy existing token" + )] + #[test_case( + ParsedBrc20Operation::Mint(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "1000.0".to_string(), + }), + Brc20RevealBuilder::new().inscription_number(1).build() + => Ok(VerifiedBrc20Operation::TokenMint(VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 1000.0, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + })); "with mint" + )] + #[test_case( + ParsedBrc20Operation::Mint(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "10000.0".to_string(), + }), + Brc20RevealBuilder::new().inscription_number(1).build() + => Err("Cannot mint more than 1000 tokens for pepe, attempted to mint 10000.0".to_string()); + "with mint over lim" + )] + #[test_case( + ParsedBrc20Operation::Mint(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "100.000000000000000000000".to_string(), + }), + Brc20RevealBuilder::new().inscription_number(1).build() + => Err("Invalid decimals in amt field for pepe mint, attempting to mint 100.000000000000000000000".to_string()); + "with mint invalid decimals" + )] + #[test_case( + ParsedBrc20Operation::Transfer(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "100.0".to_string(), + }), + Brc20RevealBuilder::new().inscription_number(1).build() + => Err("Insufficient balance for pepe transfer, attempting to transfer 100.0, only 0 available".to_string()); + "with transfer on zero balance" + )] + #[test_case( + ParsedBrc20Operation::Transfer(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "100.000000000000000000000".to_string(), + }), + Brc20RevealBuilder::new().inscription_number(1).build() + => Err("Invalid decimals in amt field for pepe transfer, attempting to transfer 100.000000000000000000000".to_string()); + "with transfer invalid decimals" + )] + fn test_brc20_verify_for_existing_token( + op: ParsedBrc20Operation, + reveal: OrdinalInscriptionRevealData, + ) -> Result { + let ctx = get_test_ctx(); + let mut conn = initialize_brc20_db(None, &ctx); + let tx = conn.transaction().unwrap(); + let block = BlockIdentifier { + index: 835727, + hash: "00000000000000000002d8ba402150b259ddb2b30a1d32ab4a881d4653bceb5b".to_string(), + }; + let mut cache = Brc20MemoryCache::new(10); + cache.insert_token_deploy( + &VerifiedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + self_mint: false, + }, + &Brc20RevealBuilder::new().inscription_number(0).build(), + &block, + 0, + &tx, + &ctx, + ); + verify_brc20_operation( + &op, + &reveal, + &block, + &BitcoinNetwork::Mainnet, + &mut cache, + &tx, + &ctx, + ) + } + + #[test_case( + ParsedBrc20Operation::Mint(ParsedBrc20BalanceData { + tick: "$pepe".to_string(), + amt: "100.00".to_string(), + }), + Brc20RevealBuilder::new().inscription_number(1).build() + => Err("Attempting to mint self-minted token $pepe without a parent ref".to_string()); + "with mint without parent pointer" + )] + #[test_case( + ParsedBrc20Operation::Mint(ParsedBrc20BalanceData { + tick: "$pepe".to_string(), + amt: "100.00".to_string(), + }), + Brc20RevealBuilder::new().inscription_number(1).parent(Some("test".to_string())).build() + => Err("Mint attempt for self-minted token $pepe does not point to deploy as parent".to_string()); + "with mint with wrong parent pointer" + )] + #[test_case( + ParsedBrc20Operation::Mint(ParsedBrc20BalanceData { + tick: "$pepe".to_string(), + amt: "100.00".to_string(), + }), + Brc20RevealBuilder::new() + .inscription_number(1) + .parent(Some("9bb2314d666ae0b1db8161cb373fcc1381681f71445c4e0335aa80ea9c37fcddi0".to_string())) + .build() + => Ok(VerifiedBrc20Operation::TokenMint(VerifiedBrc20BalanceData { + tick: "$pepe".to_string(), + amt: 100.0, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string() + })); + "with mint with valid parent" + )] + fn test_brc20_verify_for_existing_self_mint_token( + op: ParsedBrc20Operation, + reveal: OrdinalInscriptionRevealData, + ) -> Result { + let ctx = get_test_ctx(); + let mut conn = initialize_brc20_db(None, &ctx); + let tx = conn.transaction().unwrap(); + let block = BlockIdentifier { + index: 840000, + hash: "00000000000000000002d8ba402150b259ddb2b30a1d32ab4a881d4653bceb5b".to_string(), + }; + let mut cache = Brc20MemoryCache::new(10); + cache.insert_token_deploy( + &VerifiedBrc20TokenDeployData { + tick: "$pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + self_mint: true, + }, + &Brc20RevealBuilder::new().inscription_number(0).build(), + &block, + 0, + &tx, + &ctx, + ); + verify_brc20_operation( + &op, + &reveal, + &block, + &BitcoinNetwork::Mainnet, + &mut cache, + &tx, + &ctx, + ) + } + + #[test_case( + ParsedBrc20Operation::Mint(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "1000.0".to_string(), + }), + Brc20RevealBuilder::new().inscription_number(2).build() + => Err("No supply available for pepe mint, attempted to mint 1000.0, remaining 0".to_string()); + "with mint on no more supply" + )] + fn test_brc20_verify_for_minted_out_token( + op: ParsedBrc20Operation, + reveal: OrdinalInscriptionRevealData, + ) -> Result { + let ctx = get_test_ctx(); + let mut conn = initialize_brc20_db(None, &ctx); + let tx = conn.transaction().unwrap(); + let block = BlockIdentifier { + index: 835727, + hash: "00000000000000000002d8ba402150b259ddb2b30a1d32ab4a881d4653bceb5b".to_string(), + }; + let mut cache = Brc20MemoryCache::new(10); + cache.insert_token_deploy( + &VerifiedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + self_mint: false, + }, + &Brc20RevealBuilder::new().inscription_number(0).build(), + &block, + 0, + &tx, + &ctx, + ); + cache.insert_token_mint( + &VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 21000000.0, // For testing + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + }, + &Brc20RevealBuilder::new().inscription_number(1).build(), + &block, + 1, + &tx, + &ctx, + ); + verify_brc20_operation( + &op, + &reveal, + &block, + &BitcoinNetwork::Mainnet, + &mut cache, + &tx, + &ctx, + ) + } + + #[test_case( + ParsedBrc20Operation::Mint(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "1000.0".to_string(), + }), + Brc20RevealBuilder::new().inscription_number(2).build() + => Ok(VerifiedBrc20Operation::TokenMint(VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 500.0, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + })); "with mint on low supply" + )] + fn test_brc20_verify_for_almost_minted_out_token( + op: ParsedBrc20Operation, + reveal: OrdinalInscriptionRevealData, + ) -> Result { + let ctx = get_test_ctx(); + let mut conn = initialize_brc20_db(None, &ctx); + let tx = conn.transaction().unwrap(); + let block = BlockIdentifier { + index: 835727, + hash: "00000000000000000002d8ba402150b259ddb2b30a1d32ab4a881d4653bceb5b".to_string(), + }; + let mut cache = Brc20MemoryCache::new(10); + cache.insert_token_deploy( + &VerifiedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + self_mint: false, + }, + &Brc20RevealBuilder::new().inscription_number(0).build(), + &block, + 0, + &tx, + &ctx, + ); + cache.insert_token_mint( + &VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 21000000.0 - 500.0, // For testing + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + }, + &Brc20RevealBuilder::new().inscription_number(1).build(), + &block, + 1, + &tx, + &ctx, + ); + verify_brc20_operation( + &op, + &reveal, + &block, + &BitcoinNetwork::Mainnet, + &mut cache, + &tx, + &ctx, + ) + } + + #[test_case( + ParsedBrc20Operation::Mint(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "1000.0".to_string(), + }), + Brc20RevealBuilder::new() + .inscription_number(3) + .inscription_id("04b29b646f6389154e4fa0f0761472c27b9f13a482c715d9976edc474c258bc7i0") + .build() + => Ok(VerifiedBrc20Operation::TokenMint(VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 1000.0, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + })); "with mint on existing balance address 1" + )] + #[test_case( + ParsedBrc20Operation::Mint(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "1000.0".to_string(), + }), + Brc20RevealBuilder::new() + .inscription_number(3) + .inscription_id("04b29b646f6389154e4fa0f0761472c27b9f13a482c715d9976edc474c258bc7i0") + .inscriber_address(Some("19aeyQe8hGDoA1MHmmh2oM5Bbgrs9Jx7yZ".to_string())) + .build() + => Ok(VerifiedBrc20Operation::TokenMint(VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 1000.0, + address: "19aeyQe8hGDoA1MHmmh2oM5Bbgrs9Jx7yZ".to_string(), + })); "with mint on existing balance address 2" + )] + #[test_case( + ParsedBrc20Operation::Transfer(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "500.0".to_string(), + }), + Brc20RevealBuilder::new() + .inscription_number(3) + .inscription_id("04b29b646f6389154e4fa0f0761472c27b9f13a482c715d9976edc474c258bc7i0") + .build() + => Ok(VerifiedBrc20Operation::TokenTransfer(VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 500.0, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + })); "with transfer" + )] + #[test_case( + ParsedBrc20Operation::Transfer(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "1000".to_string(), + }), + Brc20RevealBuilder::new() + .inscription_number(3) + .inscription_id("04b29b646f6389154e4fa0f0761472c27b9f13a482c715d9976edc474c258bc7i0") + .build() + => Ok(VerifiedBrc20Operation::TokenTransfer(VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 1000.0, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + })); "with transfer full balance" + )] + #[test_case( + ParsedBrc20Operation::Transfer(ParsedBrc20BalanceData { + tick: "pepe".to_string(), + amt: "5000.0".to_string(), + }), + Brc20RevealBuilder::new() + .inscription_number(3) + .inscription_id("04b29b646f6389154e4fa0f0761472c27b9f13a482c715d9976edc474c258bc7i0") + .build() + => Err("Insufficient balance for pepe transfer, attempting to transfer 5000.0, only 1000 available".to_string()); + "with transfer insufficient balance" + )] + fn test_brc20_verify_for_token_with_mints( + op: ParsedBrc20Operation, + reveal: OrdinalInscriptionRevealData, + ) -> Result { + let ctx = get_test_ctx(); + let mut conn = initialize_brc20_db(None, &ctx); + let tx = conn.transaction().unwrap(); + let block = BlockIdentifier { + index: 835727, + hash: "00000000000000000002d8ba402150b259ddb2b30a1d32ab4a881d4653bceb5b".to_string(), + }; + let mut cache = Brc20MemoryCache::new(10); + cache.insert_token_deploy( + &VerifiedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + self_mint: false, + }, + &Brc20RevealBuilder::new() + .inscription_number(0) + .inscription_id( + "e45957c419f130cd5c88cdac3eb1caf2d118aee20c17b15b74a611be395a065di0", + ) + .build(), + &block, + 0, + &tx, + &ctx, + ); + // Mint from 2 addresses + cache.insert_token_mint( + &VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 1000.0, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + }, + &Brc20RevealBuilder::new() + .inscription_number(1) + .inscription_id( + "269d46f148733ce86153e3ec0e0a3c78780e9b07e90a07e11753f0e934a60724i0", + ) + .build(), + &block, + 1, + &tx, + &ctx, + ); + cache.insert_token_mint( + &VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 1000.0, + address: "19aeyQe8hGDoA1MHmmh2oM5Bbgrs9Jx7yZ".to_string(), + }, + &Brc20RevealBuilder::new() + .inscription_number(2) + .inscription_id( + "704b85a939c34ec9dbbf79c0ffc69ba09566d732dbf1af2c04de65b0697aa1f8i0", + ) + .build(), + &block, + 2, + &tx, + &ctx, + ); + verify_brc20_operation( + &op, + &reveal, + &block, + &BitcoinNetwork::Mainnet, + &mut cache, + &tx, + &ctx, + ) + } + + #[test_case( + Brc20TransferBuilder::new().ordinal_number(5000).build() + => Ok(VerifiedBrc20TransferData { + tick: "pepe".to_string(), + amt: 500.0, + sender_address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + receiver_address: "bc1pls75sfwullhygkmqap344f5cqf97qz95lvle6fvddm0tpz2l5ffslgq3m0".to_string(), + }); + "with transfer" + )] + #[test_case( + Brc20TransferBuilder::new() + .ordinal_number(5000) + .destination(OrdinalInscriptionTransferDestination::SpentInFees) + .build() + => Ok(VerifiedBrc20TransferData { + tick: "pepe".to_string(), + amt: 500.0, + sender_address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + receiver_address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string() + }); + "with transfer spent as fee" + )] + #[test_case( + Brc20TransferBuilder::new() + .ordinal_number(5000) + .destination(OrdinalInscriptionTransferDestination::Burnt("test".to_string())) + .build() + => Ok(VerifiedBrc20TransferData { + tick: "pepe".to_string(), + amt: 500.0, + sender_address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + receiver_address: "".to_string() + }); + "with transfer burnt" + )] + #[test_case( + Brc20TransferBuilder::new().ordinal_number(200).build() + => Err("No BRC-20 transfer in ordinal 200 or transfer already sent".to_string()); + "with transfer non existent" + )] + fn test_brc20_verify_transfer_for_token_with_mint_and_transfer( + transfer: OrdinalInscriptionTransferData, + ) -> Result { + let ctx = get_test_ctx(); + let mut conn = initialize_brc20_db(None, &ctx); + let tx = conn.transaction().unwrap(); + let block = BlockIdentifier { + index: 835727, + hash: "00000000000000000002d8ba402150b259ddb2b30a1d32ab4a881d4653bceb5b".to_string(), + }; + let mut cache = Brc20MemoryCache::new(10); + cache.insert_token_deploy( + &VerifiedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + self_mint: false, + }, + &Brc20RevealBuilder::new() + .inscription_number(0) + .inscription_id( + "e45957c419f130cd5c88cdac3eb1caf2d118aee20c17b15b74a611be395a065di0", + ) + .build(), + &block, + 0, + &tx, + &ctx, + ); + cache.insert_token_mint( + &VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 1000.0, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + }, + &Brc20RevealBuilder::new() + .inscription_number(1) + .inscription_id( + "269d46f148733ce86153e3ec0e0a3c78780e9b07e90a07e11753f0e934a60724i0", + ) + .build(), + &block, + 1, + &tx, + &ctx, + ); + cache.insert_token_transfer( + &VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 500.0, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + }, + &Brc20RevealBuilder::new() + .inscription_number(2) + .ordinal_number(5000) + .inscription_id( + "704b85a939c34ec9dbbf79c0ffc69ba09566d732dbf1af2c04de65b0697aa1f8i0", + ) + .build(), + &block, + 2, + &tx, + &ctx, + ); + verify_brc20_transfer(&transfer, &mut cache, &tx, &ctx) + } + + #[test_case( + Brc20TransferBuilder::new().ordinal_number(5000).build() + => Err("No BRC-20 transfer in ordinal 5000 or transfer already sent".to_string()); + "with transfer already sent" + )] + fn test_brc20_verify_transfer_for_token_with_mint_transfer_and_send( + transfer: OrdinalInscriptionTransferData, + ) -> Result { + let ctx = get_test_ctx(); + let mut conn = initialize_brc20_db(None, &ctx); + let tx = conn.transaction().unwrap(); + let block = BlockIdentifier { + index: 835727, + hash: "00000000000000000002d8ba402150b259ddb2b30a1d32ab4a881d4653bceb5b".to_string(), + }; + let mut cache = Brc20MemoryCache::new(10); + cache.insert_token_deploy( + &VerifiedBrc20TokenDeployData { + tick: "pepe".to_string(), + max: 21000000.0, + lim: 1000.0, + dec: 18, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + self_mint: false, + }, + &Brc20RevealBuilder::new() + .inscription_number(0) + .inscription_id( + "e45957c419f130cd5c88cdac3eb1caf2d118aee20c17b15b74a611be395a065di0", + ) + .build(), + &block, + 0, + &tx, + &ctx, + ); + cache.insert_token_mint( + &VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 1000.0, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + }, + &Brc20RevealBuilder::new() + .inscription_number(1) + .inscription_id( + "269d46f148733ce86153e3ec0e0a3c78780e9b07e90a07e11753f0e934a60724i0", + ) + .build(), + &block, + 1, + &tx, + &ctx, + ); + cache.insert_token_transfer( + &VerifiedBrc20BalanceData { + tick: "pepe".to_string(), + amt: 500.0, + address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + }, + &Brc20RevealBuilder::new() + .inscription_number(2) + .ordinal_number(5000) + .inscription_id( + "704b85a939c34ec9dbbf79c0ffc69ba09566d732dbf1af2c04de65b0697aa1f8i0", + ) + .build(), + &block, + 2, + &tx, + &ctx, + ); + cache.insert_token_transfer_send( + &VerifiedBrc20TransferData { + tick: "pepe".to_string(), + amt: 500.0, + sender_address: "324A7GHA2azecbVBAFy4pzEhcPT1GjbUAp".to_string(), + receiver_address: "bc1pls75sfwullhygkmqap344f5cqf97qz95lvle6fvddm0tpz2l5ffslgq3m0" + .to_string(), + }, + &Brc20TransferBuilder::new().ordinal_number(5000).build(), + &block, + 3, + &tx, + &ctx, + ); + verify_brc20_transfer(&transfer, &mut cache, &tx, &ctx) + } +} diff --git a/components/ordhook-core/src/core/meta_protocols/mod.rs b/components/ordhook-core/src/core/meta_protocols/mod.rs new file mode 100644 index 0000000..ec4a9d2 --- /dev/null +++ b/components/ordhook-core/src/core/meta_protocols/mod.rs @@ -0,0 +1 @@ +pub mod brc20; diff --git a/components/ordhook-core/src/core/mod.rs b/components/ordhook-core/src/core/mod.rs index 9bf1cf5..f62f84c 100644 --- a/components/ordhook-core/src/core/mod.rs +++ b/components/ordhook-core/src/core/mod.rs @@ -1,3 +1,4 @@ +pub mod meta_protocols; pub mod pipeline; pub mod protocol; @@ -13,7 +14,7 @@ use chainhook_sdk::{ }; use crate::{ - config::{Config, LogConfig, ResourcesConfig}, + config::{Config, LogConfig, MetaProtocolsConfig, ResourcesConfig}, db::{find_pinned_block_bytes_at_block_height, open_ordhook_db_conn_rocks_db_loop}, }; @@ -30,6 +31,7 @@ pub struct OrdhookConfig { pub db_path: PathBuf, pub first_inscription_height: u64, pub logs: LogConfig, + pub meta_protocols: MetaProtocolsConfig, } pub fn new_traversals_cache( 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 fe6ebb5..aaa0cfa 100644 --- a/components/ordhook-core/src/core/pipeline/processors/inscription_indexing.rs +++ b/components/ordhook-core/src/core/pipeline/processors/inscription_indexing.rs @@ -1,5 +1,5 @@ use std::{ - collections::BTreeMap, + collections::{BTreeMap, HashMap}, sync::Arc, thread::{sleep, JoinHandle}, time::Duration, @@ -19,10 +19,12 @@ use std::hash::BuildHasherDefault; use crate::{ core::{ + meta_protocols::brc20::{cache::Brc20MemoryCache, db::open_readwrite_brc20_db_conn}, pipeline::processors::block_archiving::store_compacted_blocks, protocol::{ inscription_parsing::{ get_inscriptions_revealed_in_block, get_inscriptions_transferred_in_block, + parse_inscriptions_in_standardized_block, }, inscription_sequencing::{ augment_block_with_ordinals_inscriptions_data_and_write_to_db_tx, @@ -37,6 +39,7 @@ use crate::{ get_any_entry_in_ordinal_activities, open_ordhook_db_conn_rocks_db_loop, open_readonly_ordhook_db_conn, }, + service::write_brc20_block_operations, }; use crate::db::{TransactionBytesCursor, TraversalResult}; @@ -177,9 +180,18 @@ pub fn process_blocks( let mut updated_blocks = vec![]; + let mut brc20_db_conn_rw = match open_readwrite_brc20_db_conn(&ordhook_config.db_path, &ctx) { + Ok(dbs) => dbs, + Err(e) => { + panic!("Unable to open readwrite connection: {e}"); + } + }; + let mut brc20_cache = Brc20MemoryCache::new(ordhook_config.resources.brc20_lru_cache_size); + for _cursor in 0..next_blocks.len() { let inscriptions_db_tx: rusqlite::Transaction<'_> = inscriptions_db_conn_rw.transaction().unwrap(); + let brc20_db_tx = brc20_db_conn_rw.transaction().unwrap(); let mut block = next_blocks.remove(0); @@ -205,6 +217,8 @@ pub fn process_blocks( &mut cache_l1, cache_l2, &inscriptions_db_tx, + Some(&brc20_db_tx), + &mut brc20_cache, ordhook_config, ctx, ); @@ -235,16 +249,21 @@ pub fn process_blocks( ) }); let _ = inscriptions_db_tx.rollback(); + let _ = brc20_db_tx.rollback(); } else { match inscriptions_db_tx.commit() { - Ok(_) => { - // ctx.try_log(|logger| { - // info!( - // logger, - // "Updates saved for block {}", block.block_identifier.index, - // ) - // }); - } + Ok(_) => match brc20_db_tx.commit() { + Ok(_) => {} + Err(_) => { + // delete_data_in_ordhook_db( + // block.block_identifier.index, + // block.block_identifier.index, + // ordhook_config, + // ctx, + // ); + todo!() + } + }, Err(e) => { ctx.try_log(|logger| { error!( @@ -273,9 +292,15 @@ pub fn process_block( cache_l1: &mut BTreeMap<(TransactionIdentifier, usize, u64), TraversalResult>, cache_l2: &Arc>>, inscriptions_db_tx: &Transaction, + brc20_db_tx: Option<&Transaction>, + brc20_cache: &mut Brc20MemoryCache, ordhook_config: &OrdhookConfig, ctx: &Context, ) -> Result<(), String> { + // Parsed BRC20 ops will be deposited here for this block. + let mut brc20_operation_map = HashMap::new(); + parse_inscriptions_in_standardized_block(block, &mut brc20_operation_map, &ctx); + let any_processable_transactions = parallelize_inscription_data_computations( &block, &next_blocks, @@ -306,5 +331,15 @@ pub fn process_block( // Handle transfers let _ = augment_block_with_ordinals_transfer_data(block, inscriptions_db_tx, true, &inner_ctx); + if let Some(brc20_db_tx) = brc20_db_tx { + write_brc20_block_operations( + &block, + &mut brc20_operation_map, + brc20_cache, + &brc20_db_tx, + &ctx, + ); + } + Ok(()) } diff --git a/components/ordhook-core/src/core/pipeline/processors/transfers_recomputing.rs b/components/ordhook-core/src/core/pipeline/processors/transfers_recomputing.rs index 8d5c12e..56e338d 100644 --- a/components/ordhook-core/src/core/pipeline/processors/transfers_recomputing.rs +++ b/components/ordhook-core/src/core/pipeline/processors/transfers_recomputing.rs @@ -74,6 +74,7 @@ pub fn start_transfers_recomputing_processor( block, &inscriptions_db_tx, false, + None, &ctx, ); diff --git a/components/ordhook-core/src/core/protocol/inscription_parsing.rs b/components/ordhook-core/src/core/protocol/inscription_parsing.rs index 01ce655..40cc5ff 100644 --- a/components/ordhook-core/src/core/protocol/inscription_parsing.rs +++ b/components/ordhook-core/src/core/protocol/inscription_parsing.rs @@ -2,15 +2,17 @@ use chainhook_sdk::bitcoincore_rpc_json::bitcoin::Txid; use chainhook_sdk::indexer::bitcoin::BitcoinTransactionFullBreakdown; use chainhook_sdk::indexer::bitcoin::{standardize_bitcoin_block, BitcoinBlockFullBreakdown}; use chainhook_sdk::types::{ - BitcoinBlockData, BitcoinNetwork, BitcoinTransactionData, OrdinalInscriptionCurseType, - OrdinalInscriptionNumber, OrdinalInscriptionRevealData, OrdinalInscriptionTransferData, - OrdinalOperation, + BitcoinBlockData, BitcoinNetwork, BitcoinTransactionData, BlockIdentifier, + OrdinalInscriptionCurseType, OrdinalInscriptionNumber, OrdinalInscriptionRevealData, + OrdinalInscriptionTransferData, OrdinalOperation, }; use chainhook_sdk::utils::Context; use serde_json::json; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use std::str::FromStr; +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; @@ -20,7 +22,7 @@ pub fn parse_inscriptions_from_witness( input_index: usize, witness_bytes: Vec>, txid: &str, -) -> Option> { +) -> Option> { // Efficient debugging: Isolate one specific transaction // if !txid.eq("aa2ab56587c7d6609c95157e6dff37c5c3fa6531702f41229a289a5613887077") { // return None @@ -103,14 +105,17 @@ pub fn parse_inscriptions_from_witness( satpoint_post_inscription: format!(""), curse_type, }; - inscriptions.push(reveal_data); + inscriptions.push((reveal_data, envelope.payload)); } Some(inscriptions) } pub fn parse_inscriptions_from_standardized_tx( - tx: &BitcoinTransactionData, - _ctx: &Context, + tx: &mut BitcoinTransactionData, + block_identifier: &BlockIdentifier, + network: &BitcoinNetwork, + brc20_operation_map: &mut HashMap, + ctx: &Context, ) -> Vec { let mut operations = vec![]; for (input_index, input) in tx.metadata.inputs.iter().enumerate() { @@ -125,8 +130,21 @@ pub fn parse_inscriptions_from_standardized_tx( witness_bytes, tx.transaction_identifier.get_hash_bytes_str(), ) { - for inscription in inscriptions.into_iter() { - operations.push(OrdinalOperation::InscriptionRevealed(inscription)); + for (reveal, inscription) in inscriptions.into_iter() { + if block_identifier.index >= brc20_activation_height(&network) { + match parse_brc20_operation(&inscription) { + Ok(Some(op)) => { + brc20_operation_map.insert(reveal.inscription_id.clone(), op); + } + Ok(None) => {} + Err(e) => { + ctx.try_log(|logger| { + warn!(logger, "Error parsing BRC-20 operation: {}", e) + }); + } + }; + } + operations.push(OrdinalOperation::InscriptionRevealed(reveal)); } } } @@ -148,8 +166,8 @@ pub fn parse_inscriptions_in_raw_tx( if let Some(inscriptions) = parse_inscriptions_from_witness(input_index, witness_bytes, &tx.txid) { - for inscription in inscriptions.into_iter() { - operations.push(OrdinalOperation::InscriptionRevealed(inscription)); + for (reveal, _inscription) in inscriptions.into_iter() { + operations.push(OrdinalOperation::InscriptionRevealed(reveal)); } } } @@ -197,9 +215,19 @@ pub fn parse_inscriptions_and_standardize_block( Ok(block) } -pub fn parse_inscriptions_in_standardized_block(block: &mut BitcoinBlockData, ctx: &Context) { +pub fn parse_inscriptions_in_standardized_block( + block: &mut BitcoinBlockData, + brc20_operation_map: &mut HashMap, + ctx: &Context, +) { for tx in block.transactions.iter_mut() { - tx.metadata.ordinal_operations = parse_inscriptions_from_standardized_tx(tx, ctx); + tx.metadata.ordinal_operations = parse_inscriptions_from_standardized_tx( + tx, + &block.block_identifier, + &block.metadata.network, + brc20_operation_map, + ctx, + ); } } diff --git a/components/ordhook-core/src/core/protocol/inscription_sequencing.rs b/components/ordhook-core/src/core/protocol/inscription_sequencing.rs index 3aa5fe5..aa15374 100644 --- a/components/ordhook-core/src/core/protocol/inscription_sequencing.rs +++ b/components/ordhook-core/src/core/protocol/inscription_sequencing.rs @@ -19,7 +19,12 @@ use fxhash::FxHasher; use rusqlite::{Connection, Transaction}; use crate::{ - core::{resolve_absolute_pointer, OrdhookConfig}, + core::{ + meta_protocols::brc20::db::{ + augment_transaction_with_brc20_operation_data, get_brc20_operations_on_block, + }, + resolve_absolute_pointer, OrdhookConfig, + }, db::{ find_blessed_inscription_with_ordinal_number, find_nth_classic_neg_number_at_block_height, find_nth_classic_pos_number_at_block_height, find_nth_jubilee_number_at_block_height, @@ -311,7 +316,6 @@ pub fn parallelize_inscription_data_computations( let _ = tx.send(None); } - let ctx_moved = inner_ctx.clone(); let _ = hiro_system_kit::thread_named("Garbage collection").spawn(move || { for handle in thread_pool_handles.into_iter() { let _ = handle.join(); @@ -921,6 +925,7 @@ pub fn consolidate_block_with_pre_computed_ordinals_data( block: &mut BitcoinBlockData, inscriptions_db_tx: &Transaction, include_transfers: bool, + brc20_db_conn: Option<&Connection>, ctx: &Context, ) { let network = get_bitcoin_network(&block.metadata.network); @@ -944,6 +949,11 @@ pub fn consolidate_block_with_pre_computed_ordinals_data( } break results; }; + let mut brc20_token_map = HashMap::new(); + let mut brc20_block_ledger_map = match brc20_db_conn { + Some(conn) => get_brc20_operations_on_block(&block.block_identifier, &conn, &ctx), + None => HashMap::new(), + }; for (tx_index, tx) in block.transactions.iter_mut().enumerate() { // Add inscriptions data consolidate_transaction_with_pre_computed_inscription_data( @@ -970,5 +980,14 @@ pub fn consolidate_block_with_pre_computed_ordinals_data( ctx, ); } + if let Some(brc20_db_conn) = brc20_db_conn { + augment_transaction_with_brc20_operation_data( + tx, + &mut brc20_token_map, + &mut brc20_block_ledger_map, + &brc20_db_conn, + &ctx, + ); + } } } diff --git a/components/ordhook-core/src/core/protocol/satoshi_tracking.rs b/components/ordhook-core/src/core/protocol/satoshi_tracking.rs index 5116eb0..638920c 100644 --- a/components/ordhook-core/src/core/protocol/satoshi_tracking.rs +++ b/components/ordhook-core/src/core/protocol/satoshi_tracking.rs @@ -3,7 +3,7 @@ use std::collections::HashSet; use chainhook_sdk::{ bitcoincore_rpc_json::bitcoin::{Address, Network, ScriptBuf}, types::{ - BitcoinBlockData, BitcoinNetwork, BitcoinTransactionData, OrdinalInscriptionTransferData, + BitcoinBlockData, BitcoinTransactionData, OrdinalInscriptionTransferData, OrdinalInscriptionTransferDestination, OrdinalOperation, TransactionIdentifier, }, utils::Context, diff --git a/components/ordhook-core/src/db/mod.rs b/components/ordhook-core/src/db/mod.rs index dcd305c..4d2c117 100644 --- a/components/ordhook-core/src/db/mod.rs +++ b/components/ordhook-core/src/db/mod.rs @@ -1,5 +1,5 @@ use std::{ - collections::{BTreeMap, BTreeSet, HashMap}, + collections::{BTreeMap, HashMap}, io::{Read, Write}, path::PathBuf, thread::sleep, @@ -16,14 +16,18 @@ use chainhook_sdk::{ indexer::bitcoin::BitcoinBlockFullBreakdown, types::{ BitcoinBlockData, BlockIdentifier, OrdinalInscriptionNumber, OrdinalInscriptionRevealData, - OrdinalInscriptionTransferData, TransactionIdentifier, + TransactionIdentifier, }, utils::Context, }; use crate::{ - core::protocol::inscription_parsing::{ - get_inscriptions_revealed_in_block, get_inscriptions_transferred_in_block, + config::Config, + core::{ + meta_protocols::brc20::db::{delete_activity_in_block_range, open_readwrite_brc20_db_conn}, + protocol::inscription_parsing::{ + get_inscriptions_revealed_in_block, get_inscriptions_transferred_in_block, + }, }, ord::sat::Sat, }; @@ -48,13 +52,13 @@ pub fn open_readwrite_ordhook_db_conn( ctx: &Context, ) -> Result { let db_path = get_default_ordhook_db_file_path(&base_dir); - let conn = create_or_open_readwrite_db(&db_path, ctx); + let conn = create_or_open_readwrite_db(Some(&db_path), ctx); Ok(conn) } pub fn initialize_ordhook_db(base_dir: &PathBuf, ctx: &Context) -> Connection { let db_path = get_default_ordhook_db_file_path(&base_dir); - let conn = create_or_open_readwrite_db(&db_path, ctx); + let conn = create_or_open_readwrite_db(Some(&db_path), ctx); // TODO: introduce initial output if let Err(e) = conn.execute( "CREATE TABLE IF NOT EXISTS inscriptions ( @@ -172,29 +176,37 @@ pub fn initialize_ordhook_db(base_dir: &PathBuf, ctx: &Context) -> Connection { conn } -pub fn create_or_open_readwrite_db(db_path: &PathBuf, ctx: &Context) -> Connection { - let open_flags = match std::fs::metadata(&db_path) { - Err(e) => { - if e.kind() == std::io::ErrorKind::NotFound { - // need to create - if let Some(dirp) = PathBuf::from(&db_path).parent() { - std::fs::create_dir_all(dirp).unwrap_or_else(|e| { - ctx.try_log(|logger| error!(logger, "{}", e.to_string())); - }); +pub fn create_or_open_readwrite_db(db_path: Option<&PathBuf>, ctx: &Context) -> Connection { + let open_flags = if let Some(db_path) = db_path { + match std::fs::metadata(&db_path) { + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + // need to create + if let Some(dirp) = PathBuf::from(&db_path).parent() { + std::fs::create_dir_all(dirp).unwrap_or_else(|e| { + ctx.try_log(|logger| error!(logger, "{}", e.to_string())); + }); + } + OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE + } else { + panic!("FATAL: could not stat {}", db_path.display()); } - OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE - } else { - panic!("FATAL: could not stat {}", db_path.display()); + } + Ok(_md) => { + // can just open + OpenFlags::SQLITE_OPEN_READ_WRITE } } - Ok(_md) => { - // can just open - OpenFlags::SQLITE_OPEN_READ_WRITE - } + } else { + OpenFlags::SQLITE_OPEN_READ_WRITE }; + let path = match db_path { + Some(path) => path.to_str().unwrap(), + None => ":memory:", + }; let conn = loop { - match Connection::open_with_flags(&db_path, open_flags) { + match Connection::open_with_flags(&path, open_flags) { Ok(conn) => break conn, Err(e) => { ctx.try_log(|logger| error!(logger, "{}", e.to_string())); @@ -1275,17 +1287,25 @@ pub fn remove_entries_from_locations_at_block_height( pub fn delete_data_in_ordhook_db( start_block: u64, end_block: u64, - blocks_db_rw: &DB, - inscriptions_db_conn_rw: &Connection, + config: &Config, ctx: &Context, ) -> Result<(), String> { + let blocks_db = open_ordhook_db_conn_rocks_db_loop( + true, + &config.expected_cache_path(), + config.resources.ulimit, + config.resources.memory_available, + ctx, + ); + let inscriptions_db_conn_rw = + open_readwrite_ordhook_db_conn(&config.expected_cache_path(), ctx)?; ctx.try_log(|logger| { info!( logger, "Deleting entries from block #{start_block} to block #{end_block}" ) }); - delete_blocks_in_block_range(start_block as u32, end_block as u32, blocks_db_rw, &ctx); + delete_blocks_in_block_range(start_block as u32, end_block as u32, &blocks_db, &ctx); ctx.try_log(|logger| { info!( logger, @@ -1295,9 +1315,19 @@ pub fn delete_data_in_ordhook_db( delete_inscriptions_in_block_range( start_block as u32, end_block as u32, - inscriptions_db_conn_rw, + &inscriptions_db_conn_rw, &ctx, ); + if config.meta_protocols.brc20 { + let conn = open_readwrite_brc20_db_conn(&config.expected_cache_path(), ctx)?; + delete_activity_in_block_range(start_block as u32, end_block as u32, &conn, &ctx); + ctx.try_log(|logger| { + info!( + logger, + "Deleting BRC-20 activity from block #{start_block} to block #{end_block}" + ) + }); + } Ok(()) } diff --git a/components/ordhook-core/src/lib.rs b/components/ordhook-core/src/lib.rs index 7158994..0d7cc4e 100644 --- a/components/ordhook-core/src/lib.rs +++ b/components/ordhook-core/src/lib.rs @@ -7,6 +7,9 @@ extern crate hiro_system_kit; #[macro_use] extern crate serde_derive; +#[macro_use] +extern crate lazy_static; + extern crate serde; pub extern crate chainhook_sdk; @@ -20,3 +23,25 @@ pub mod ord; pub mod scan; pub mod service; pub mod utils; + +use core::meta_protocols::brc20::db::initialize_brc20_db; + +use chainhook_sdk::utils::Context; +use config::Config; +use db::initialize_ordhook_db; +use rusqlite::Connection; + +pub struct DbConnections { + pub ordhook: Connection, + pub brc20: Option, +} + +pub fn initialize_db(config: &Config, ctx: &Context) -> DbConnections { + DbConnections { + ordhook: initialize_ordhook_db(&config.expected_cache_path(), ctx), + brc20: match config.meta_protocols.brc20 { + true => Some(initialize_brc20_db(Some(&config.expected_cache_path()), ctx)), + false => None + }, + } +} diff --git a/components/ordhook-core/src/scan/bitcoin.rs b/components/ordhook-core/src/scan/bitcoin.rs index fec68e2..c9a939d 100644 --- a/components/ordhook-core/src/scan/bitcoin.rs +++ b/components/ordhook-core/src/scan/bitcoin.rs @@ -1,4 +1,6 @@ use crate::config::Config; +use crate::core::meta_protocols::brc20::brc20_activation_height; +use crate::core::meta_protocols::brc20::db::open_readonly_brc20_db_conn; use crate::core::protocol::inscription_parsing::{ get_inscriptions_revealed_in_block, get_inscriptions_transferred_in_block, parse_inscriptions_and_standardize_block, @@ -15,7 +17,9 @@ use chainhook_sdk::chainhooks::bitcoin::{ evaluate_bitcoin_chainhooks_on_chain_event, handle_bitcoin_hook_action, BitcoinChainhookOccurrence, BitcoinTriggerChainhook, }; -use chainhook_sdk::chainhooks::types::BitcoinChainhookSpecification; +use chainhook_sdk::chainhooks::types::{ + BitcoinChainhookSpecification, BitcoinPredicateType, OrdinalOperations, +}; use chainhook_sdk::indexer::bitcoin::{ build_http_client, download_and_parse_block_with_retry, retrieve_block_hash_with_retry, }; @@ -47,7 +51,7 @@ pub async fn scan_bitcoin_chainstate_via_rpc_using_predicate( }; let mut floating_end_block = false; - let mut block_heights_to_scan = if let Some(ref blocks) = predicate_spec.blocks { + let block_heights_to_scan_res = if let Some(ref blocks) = predicate_spec.blocks { BlockHeights::Blocks(blocks.clone()).get_sorted_entries() } else { let start_block = match predicate_spec.start_block { @@ -75,6 +79,9 @@ pub async fn scan_bitcoin_chainstate_via_rpc_using_predicate( BlockHeights::BlockRange(start_block, end_block).get_sorted_entries() }; + let mut block_heights_to_scan = + block_heights_to_scan_res.map_err(|_e| format!("Block start / end block spec invalid"))?; + info!( ctx.expect_logger(), "Starting predicate evaluation on {} Bitcoin blocks", @@ -94,6 +101,21 @@ pub async fn scan_bitcoin_chainstate_via_rpc_using_predicate( while let Some(current_block_height) = block_heights_to_scan.pop_front() { let mut inscriptions_db_conn = open_readonly_ordhook_db_conn(&config.expected_cache_path(), ctx)?; + let brc20_db_conn = match predicate_spec.predicate { + BitcoinPredicateType::OrdinalsProtocol(OrdinalOperations::InscriptionFeed( + ref feed_data, + )) if feed_data.meta_protocols.is_some() => { + if current_block_height >= brc20_activation_height(&bitcoin_config.network) { + Some(open_readonly_brc20_db_conn( + &config.expected_cache_path(), + ctx, + )?) + } else { + None + } + } + _ => None, + }; number_of_blocks_scanned += 1; @@ -133,6 +155,7 @@ pub async fn scan_bitcoin_chainstate_via_rpc_using_predicate( &mut block, &inscriptions_db_tx, true, + brc20_db_conn.as_ref(), &Context::empty(), ); } diff --git a/components/ordhook-core/src/service/mod.rs b/components/ordhook-core/src/service/mod.rs index 4673fac..ea0d5c1 100644 --- a/components/ordhook-core/src/service/mod.rs +++ b/components/ordhook-core/src/service/mod.rs @@ -3,6 +3,13 @@ pub mod observers; mod runloops; use crate::config::{Config, PredicatesApi}; +use crate::core::meta_protocols::brc20::cache::Brc20MemoryCache; +use crate::core::meta_protocols::brc20::db::open_readwrite_brc20_db_conn; +use crate::core::meta_protocols::brc20::parser::ParsedBrc20Operation; +use crate::core::meta_protocols::brc20::verifier::{ + verify_brc20_operation, verify_brc20_transfer, VerifiedBrc20Operation, +}; +use crate::core::meta_protocols::brc20::brc20_activation_height; use crate::core::pipeline::download_and_pipeline_blocks; use crate::core::pipeline::processors::block_archiving::start_block_archiving_processor; use crate::core::pipeline::processors::inscription_indexing::process_block; @@ -10,19 +17,19 @@ use crate::core::pipeline::processors::start_inscription_indexing_processor; use crate::core::pipeline::processors::transfers_recomputing::start_transfers_recomputing_processor; use crate::core::protocol::inscription_parsing::{ get_inscriptions_revealed_in_block, get_inscriptions_transferred_in_block, - parse_inscriptions_in_standardized_block, }; use crate::core::protocol::inscription_sequencing::SequenceCursor; use crate::core::{new_traversals_lazy_cache, should_sync_ordhook_db, should_sync_rocks_db}; use crate::db::{ delete_data_in_ordhook_db, insert_entry_in_blocks, open_ordhook_db_conn_rocks_db_loop, - open_readwrite_ordhook_db_conn, open_readwrite_ordhook_dbs, update_ordinals_db_with_block, - BlockBytesCursor, TransactionBytesCursor, + open_readwrite_ordhook_dbs, update_ordinals_db_with_block, BlockBytesCursor, + TransactionBytesCursor, }; use crate::db::{ find_last_block_inserted, find_missing_blocks, run_compaction, update_sequence_metadata_with_block, }; +use crate::ord::inscription; use crate::scan::bitcoin::process_block_with_predicates; use crate::service::http_api::start_predicate_api_server; use crate::service::observers::{ @@ -40,14 +47,15 @@ use chainhook_sdk::observer::{ start_event_observer, BitcoinBlockDataCached, DataHandlerEvent, EventObserverConfig, HandleBlock, ObserverCommand, ObserverEvent, ObserverSidecar, }; -use chainhook_sdk::types::{BitcoinBlockData, BlockIdentifier}; +use chainhook_sdk::types::{BitcoinBlockData, BlockIdentifier, OrdinalOperation}; use chainhook_sdk::utils::{BlockHeights, Context}; use crossbeam_channel::unbounded; use crossbeam_channel::{select, Sender}; use dashmap::DashMap; use fxhash::FxHasher; +use rusqlite::Transaction; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use std::hash::BuildHasherDefault; use std::sync::mpsc::channel; use std::sync::Arc; @@ -135,6 +143,7 @@ impl Service { observer_command_rx, Some(observer_event_tx), Some(observer_sidecar), + None, inner_ctx, ); @@ -188,6 +197,7 @@ impl Service { observer_command_rx, Some(observer_event_tx), Some(observer_sidecar), + None, inner_ctx, ); @@ -336,7 +346,7 @@ impl Service { &self.ctx, ); } - ObserverEvent::PredicateDeregistered(spec) => { + ObserverEvent::PredicateDeregistered(uuid) => { let observers_db_conn = match open_readwrite_observers_db_conn( &self.config.expected_cache_path(), &self.ctx, @@ -351,7 +361,7 @@ impl Service { continue; } }; - remove_entry_from_observers(&spec.uuid(), &observers_db_conn, &self.ctx); + remove_entry_from_observers(&uuid, &observers_db_conn, &self.ctx); } ObserverEvent::BitcoinPredicateTriggered(data) => { if let Some(ref tip) = data.apply.last() { @@ -511,27 +521,6 @@ impl Service { info!(self.ctx.expect_logger(), "Running database compaction",); run_compaction(&blocks_db_rw, tip); } - - if rebuild_from_scratch { - let blocks_db_rw = open_ordhook_db_conn_rocks_db_loop( - false, - &self.config.expected_cache_path(), - self.config.resources.ulimit, - self.config.resources.memory_available, - &self.ctx, - ); - - let inscriptions_db_conn_rw = - open_readwrite_ordhook_db_conn(&self.config.expected_cache_path(), &self.ctx)?; - - delete_data_in_ordhook_db( - 767430, - 820000, - &blocks_db_rw, - &inscriptions_db_conn_rw, - &self.ctx, - )?; - } } self.update_state(block_post_processor).await } @@ -560,7 +549,9 @@ impl Service { let ordhook_config = self.config.get_ordhook_config(); let first_inscription_height = ordhook_config.first_inscription_height; - let blocks = BlockHeights::BlockRange(start_block, end_block).get_sorted_entries(); + let blocks = BlockHeights::BlockRange(start_block, end_block) + .get_sorted_entries() + .map_err(|_e| format!("Block start / end block spec invalid"))?; download_and_pipeline_blocks( &self.config, blocks.into(), @@ -595,7 +586,9 @@ impl Service { let ordhook_config = self.config.get_ordhook_config(); let first_inscription_height = ordhook_config.first_inscription_height; - let blocks = BlockHeights::BlockRange(start_block, end_block).get_sorted_entries(); + let blocks = BlockHeights::BlockRange(start_block, end_block) + .get_sorted_entries() + .map_err(|_e| format!("Block start / end block spec invalid"))?; download_and_pipeline_blocks( &self.config, blocks.into(), @@ -662,8 +655,7 @@ fn chainhook_sidecar_mutate_ordhook_db(command: HandleBlock, config: &Config, ct if let Err(e) = delete_data_in_ordhook_db( block.block_identifier.index, block.block_identifier.index, - &blocks_db_rw, - &inscriptions_db_conn_rw, + &config, &ctx, ) { ctx.try_log(|logger| { @@ -757,19 +749,29 @@ pub fn chainhook_sidecar_mutate_blocks( ) { Ok(dbs) => dbs, Err(e) => { - ctx.try_log(|logger| error!(logger, "Unable to open readwtite connection: {e}",)); + ctx.try_log(|logger| error!(logger, "Unable to open readwrite connection: {e}",)); return; } }; let inscriptions_db_tx = inscriptions_db_conn_rw.transaction().unwrap(); + let mut brc20_db_conn_rw = + match open_readwrite_brc20_db_conn(&config.expected_cache_path(), &ctx) { + Ok(dbs) => dbs, + Err(e) => { + ctx.try_log(|logger| error!(logger, "Unable to open readwrite connection: {e}",)); + return; + } + }; + let mut brc20_cache = Brc20MemoryCache::new(config.resources.brc20_lru_cache_size); + let brc20_db_tx = brc20_db_conn_rw.transaction().unwrap(); + for block_id_to_rollback in blocks_ids_to_rollback.iter() { if let Err(e) = delete_data_in_ordhook_db( block_id_to_rollback.index, block_id_to_rollback.index, - &blocks_db_rw, - &inscriptions_db_tx, + &config, &ctx, ) { ctx.try_log(|logger| { @@ -814,8 +816,6 @@ pub fn chainhook_sidecar_mutate_blocks( } else { updated_blocks_ids.push(format!("{}", cache.block.block_identifier.index)); - parse_inscriptions_in_standardized_block(&mut cache.block, &ctx); - let mut cache_l1 = BTreeMap::new(); let mut sequence_cursor = SequenceCursor::new(&inscriptions_db_tx); @@ -826,11 +826,13 @@ pub fn chainhook_sidecar_mutate_blocks( &mut cache_l1, &cache_l2, &inscriptions_db_tx, + Some(&brc20_db_tx), + &mut brc20_cache, &ordhook_config, &ctx, ); - let inscriptions_revealed = get_inscriptions_revealed_in_block(&cache.block) + let inscription_numbers = get_inscriptions_revealed_in_block(&cache.block) .iter() .map(|d| d.get_inscription_number().to_string()) .collect::>(); @@ -843,12 +845,144 @@ pub fn chainhook_sidecar_mutate_blocks( logger, "Block #{} processed, mutated and revealed {} inscriptions [{}] and {inscriptions_transferred} transfers", cache.block.block_identifier.index, - inscriptions_revealed.len(), - inscriptions_revealed.join(", ") + inscription_numbers.len(), + inscription_numbers.join(", ") ) }); cache.processed_by_sidecar = true; } } let _ = inscriptions_db_tx.rollback(); + let _ = brc20_db_tx.rollback(); +} + +pub fn write_brc20_block_operations( + block: &BitcoinBlockData, + brc20_operation_map: &mut HashMap, + brc20_cache: &mut Brc20MemoryCache, + db_tx: &Transaction, + ctx: &Context, +) { + if block.block_identifier.index < brc20_activation_height(&block.metadata.network) { + return; + } + for (tx_index, tx) in block.transactions.iter().enumerate() { + for op in tx.metadata.ordinal_operations.iter() { + match op { + OrdinalOperation::InscriptionRevealed(reveal) => { + if let Some(parsed_brc20_operation) = + brc20_operation_map.get(&reveal.inscription_id) + { + match verify_brc20_operation( + parsed_brc20_operation, + reveal, + &block.block_identifier, + &block.metadata.network, + brc20_cache, + &db_tx, + &ctx, + ) { + Ok(op) => { + match op { + VerifiedBrc20Operation::TokenDeploy(token) => { + brc20_cache.insert_token_deploy( + &token, + reveal, + &block.block_identifier, + tx_index as u64, + db_tx, + ctx, + ); + ctx.try_log(|logger| { + info!( + logger, + "BRC-20 deploy {} ({}) at block {}", + token.tick, + token.address, + block.block_identifier.index + ) + }); + } + VerifiedBrc20Operation::TokenMint(balance) => { + brc20_cache.insert_token_mint( + &balance, + reveal, + &block.block_identifier, + tx_index as u64, + db_tx, + ctx, + ); + ctx.try_log(|logger| { + info!( + logger, + "BRC-20 mint {} {} ({}) at block {}", + balance.tick, balance.amt, balance.address, + block.block_identifier.index + ) + }); + } + VerifiedBrc20Operation::TokenTransfer(balance) => { + brc20_cache.insert_token_transfer( + &balance, + reveal, + &block.block_identifier, + tx_index as u64, + db_tx, + ctx, + ); + ctx.try_log(|logger| { + info!( + logger, + "BRC-20 transfer {} {} ({}) at block {}", + balance.tick, balance.amt, balance.address, + block.block_identifier.index + ) + }); + } + VerifiedBrc20Operation::TokenTransferSend(_) => { + unreachable!("BRC-20 token transfer send should never be generated on reveal") + } + } + } + Err(e) => { + ctx.try_log(|logger| { + debug!(logger, "Error validating BRC-20 operation {}", e) + }); + } + } + } else { + brc20_cache.ignore_inscription(reveal.ordinal_number); + } + } + OrdinalOperation::InscriptionTransferred(transfer) => { + match verify_brc20_transfer(transfer, brc20_cache, &db_tx, &ctx) { + Ok(data) => { + brc20_cache.insert_token_transfer_send( + &data, + &transfer, + &block.block_identifier, + tx_index as u64, + db_tx, + ctx, + ); + ctx.try_log(|logger| { + info!( + logger, + "BRC-20 transfer_send {} {} ({} -> {}) at block {}", + data.tick, data.amt, data.sender_address, data.receiver_address, + block.block_identifier.index + ) + }); + } + Err(e) => { + ctx.try_log(|logger| { + debug!(logger, "Error validating BRC-20 transfer {}", e) + }); + } + } + } + } + } + } + brc20_cache.db_cache.flush(db_tx, ctx); } diff --git a/components/ordhook-core/src/service/observers.rs b/components/ordhook-core/src/service/observers.rs index e8ac421..b465a90 100644 --- a/components/ordhook-core/src/service/observers.rs +++ b/components/ordhook-core/src/service/observers.rs @@ -8,6 +8,7 @@ use chainhook_sdk::{ chainhooks::types::{ BitcoinChainhookFullSpecification, BitcoinChainhookNetworkSpecification, BitcoinChainhookSpecification, ChainhookConfig, ChainhookSpecification, + InscriptionFeedData, }, observer::EventObserverConfig, types::BitcoinBlockData, @@ -103,7 +104,7 @@ pub fn open_readwrite_observers_db_conn( ctx: &Context, ) -> Result { let db_path = get_default_observers_db_file_path(&base_dir); - let conn = create_or_open_readwrite_db(&db_path, ctx); + let conn = create_or_open_readwrite_db(Some(&db_path), ctx); Ok(conn) } @@ -120,7 +121,7 @@ pub fn open_readwrite_observers_db_conn_or_panic(base_dir: &PathBuf, ctx: &Conte pub fn initialize_observers_db(base_dir: &PathBuf, ctx: &Context) -> Connection { let db_path = get_default_observers_db_file_path(&base_dir); - let conn = create_or_open_readwrite_db(&db_path, ctx); + let conn = create_or_open_readwrite_db(Some(&db_path), ctx); // TODO: introduce initial output if let Err(e) = conn.execute( "CREATE TABLE IF NOT EXISTS observers ( @@ -266,7 +267,11 @@ pub fn create_and_consolidate_chainhook_config_with_predicates( expired_at: None, expire_after_occurrence: None, predicate: chainhook_sdk::chainhooks::types::BitcoinPredicateType::OrdinalsProtocol( - chainhook_sdk::chainhooks::types::OrdinalOperations::InscriptionFeed, + chainhook_sdk::chainhooks::types::OrdinalOperations::InscriptionFeed( + InscriptionFeedData { + meta_protocols: None, + }, + ), ), action: chainhook_sdk::chainhooks::types::HookAction::Noop, include_proof: false, diff --git a/components/ordhook-sdk-js/src/ordinals_indexer.rs b/components/ordhook-sdk-js/src/ordinals_indexer.rs index 5ec496b..963fad1 100644 --- a/components/ordhook-sdk-js/src/ordinals_indexer.rs +++ b/components/ordhook-sdk-js/src/ordinals_indexer.rs @@ -6,8 +6,7 @@ use napi::threadsafe_function::{ }; use ordhook::chainhook_sdk::chainhooks::bitcoin::BitcoinTransactionPayload; use ordhook::chainhook_sdk::chainhooks::types::{ - BitcoinChainhookFullSpecification, BitcoinChainhookNetworkSpecification, BitcoinPredicateType, - HookAction, OrdinalOperations, + BitcoinChainhookFullSpecification, BitcoinChainhookNetworkSpecification, BitcoinPredicateType, HookAction, InscriptionFeedData, OrdinalOperations }; use ordhook::chainhook_sdk::observer::DataHandlerEvent; use ordhook::chainhook_sdk::utils::{BlockHeights, Context as OrdhookContext}; @@ -184,7 +183,9 @@ impl OrdinalsIndexingRunloop { include_outputs: None, include_witness: None, predicate: BitcoinPredicateType::OrdinalsProtocol( - OrdinalOperations::InscriptionFeed, + OrdinalOperations::InscriptionFeed(InscriptionFeedData { + meta_protocols: None + }), ), action: HookAction::Noop, }, @@ -349,7 +350,7 @@ impl OrdinalsIndexer { #[napi] pub fn replay_block_range(&self, start_block: i64, end_block: i64) { let range = BlockHeights::BlockRange(start_block as u64, end_block as u64); - let blocks = range.get_sorted_entries().into_iter().collect(); + let blocks = range.get_sorted_entries().unwrap().into_iter().collect(); let _ = self .runloop .command_tx diff --git a/dockerfiles/components/ordhook.dockerfile b/dockerfiles/components/ordhook.dockerfile index 68a9bb9..1133c06 100644 --- a/dockerfiles/components/ordhook.dockerfile +++ b/dockerfiles/components/ordhook.dockerfile @@ -8,7 +8,7 @@ WORKDIR /src RUN apt-get update && apt-get install -y ca-certificates pkg-config libssl-dev libclang-11-dev libunwind-dev libunwind8 curl gnupg -RUN rustup update 1.72.0 && rustup default 1.72.0 +RUN rustup update 1.77.1 && rustup default 1.77.1 RUN mkdir /out diff --git a/rust-toolchain b/rust-toolchain index 743f7cd..87d6531 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,2 +1,2 @@ [toolchain] -channel = "1.72.0" +channel = "1.77.1"