diff --git a/.gitignore b/.gitignore index cb0a311337..8f1163b82c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ /target /test-times.txt /tmp +_cache \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 05754ec1d4..aaba7763ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -164,6 +164,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + [[package]] name = "async-channel" version = "2.1.1" @@ -172,7 +183,7 @@ checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" dependencies = [ "concurrent-queue", "event-listener 4.0.2", - "event-listener-strategy", + "event-listener-strategy 0.4.0", "futures-core", "pin-project-lite", ] @@ -240,7 +251,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7125e42787d53db9dd54261812ef17e937c95a51e4d291373b670342fa44310c" dependencies = [ "event-listener 4.0.2", - "event-listener-strategy", + "event-listener-strategy 0.4.0", "pin-project-lite", ] @@ -317,6 +328,19 @@ dependencies = [ "utf-8", ] +[[package]] +name = "asynchronous-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", +] + [[package]] name = "atom_syndication" version = "0.12.2" @@ -344,6 +368,18 @@ dependencies = [ "reqwest", ] +[[package]] +name = "auto_impl" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -453,6 +489,18 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bigdecimal" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + [[package]] name = "bigdecimal" version = "0.4.2" @@ -540,7 +588,7 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" dependencies = [ - "async-channel", + "async-channel 2.1.1", "async-lock 3.2.0", "async-task", "fastrand 2.0.1", @@ -595,6 +643,12 @@ dependencies = [ "serde", ] +[[package]] +name = "build_const" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" + [[package]] name = "bumpalo" version = "3.14.0" @@ -809,6 +863,17 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "core_affinity" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622892f5635ce1fc38c8f16dfc938553ed64af482edb5e150bf4caedbfcb2304" +dependencies = [ + "libc", + "num_cpus", + "winapi", +] + [[package]] name = "cpufeatures" version = "0.2.11" @@ -818,6 +883,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" +dependencies = [ + "build_const", +] + [[package]] name = "crc32fast" version = "1.3.2" @@ -863,6 +937,28 @@ dependencies = [ "itertools", ] +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.4" @@ -885,6 +981,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.18" @@ -894,6 +999,12 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -910,7 +1021,7 @@ version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" dependencies = [ - "nix", + "nix 0.27.1", "windows-sys 0.52.0", ] @@ -984,6 +1095,19 @@ dependencies = [ "syn 2.0.46", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if 1.0.0", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core 0.9.9", +] + [[package]] name = "data-encoding" version = "2.5.0" @@ -1131,6 +1255,12 @@ dependencies = [ "syn 2.0.46", ] +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + [[package]] name = "either" version = "1.9.0" @@ -1171,6 +1301,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + [[package]] name = "errno" version = "0.3.8" @@ -1181,6 +1322,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -1198,6 +1349,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "event-listener" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + [[package]] name = "event-listener-strategy" version = "0.4.0" @@ -1208,6 +1370,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "event-listener-strategy" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" +dependencies = [ + "event-listener 5.2.0", + "pin-project-lite", +] + [[package]] name = "executable-path" version = "1.0.0" @@ -1229,6 +1401,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "static_assertions", +] + [[package]] name = "flate2" version = "1.0.28" @@ -1269,6 +1450,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "futures" version = "0.3.30" @@ -1394,6 +1585,19 @@ dependencies = [ "slab", ] +[[package]] +name = "generator" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "windows 0.48.0", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1714,6 +1918,47 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-num-traits" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951641f13f873bff03d4bf19ae8bec531935ac0ac2cc775f84d7edfdcfed3f17" +dependencies = [ + "integer-sqrt", + "num-traits", + "uint", +] + +[[package]] +name = "indexer-sdk" +version = "0.1.0" +source = "git+https://github.com/okx/indexer-sdk.git?rev=ccb97b47a0b7d44a8fa9c3a939e5d8a1e242245a#ccb97b47a0b7d44a8fa9c3a939e5d8a1e242245a" +dependencies = [ + "async-channel 1.9.0", + "async-trait", + "auto_impl", + "bigdecimal 0.3.1", + "chrono", + "crossbeam", + "downcast-rs", + "env_logger", + "hex", + "log", + "log4rs", + "may", + "once_cell", + "ord-bitcoincore-rpc", + "primitive-types", + "rustc-serialize", + "rusty-leveldb", + "serde", + "serde_json", + "thiserror", + "tokio", + "wg", + "zeromq", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1757,6 +2002,21 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "integer-encoding" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" + +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -2034,12 +2294,55 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "may" +version = "0.3.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f1b6d76280e56d1c65b8d0a9a7f05d0b730ff0463110ea82bc999838d6f7369" +dependencies = [ + "core_affinity", + "crossbeam", + "generator", + "lazy_static", + "libc", + "log", + "may_queue", + "miow", + "nix 0.26.4", + "num_cpus", + "parking_lot 0.12.1", + "rustversion", + "smallvec", + "socket2 0.5.5", + "windows-sys 0.42.0", +] + +[[package]] +name = "may_queue" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f6fa598a71c1f6d55b09fdb508d256c301e458ea544457c7f0fa8ee64b0676c" +dependencies = [ + "crossbeam-utils", + "rustversion", + "smallvec", +] + [[package]] name = "memchr" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -2092,6 +2395,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "miow" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ffbca2f655e33c08be35d87278e5b18b89550a37dbd598c20db92f6a471123" +dependencies = [ + "windows-sys 0.42.0", +] + [[package]] name = "mp4" version = "0.14.0" @@ -2151,6 +2463,19 @@ dependencies = [ "unicase", ] +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if 1.0.0", + "libc", + "memoffset", + "pin-utils", +] + [[package]] name = "nix" version = "0.27.1" @@ -2334,12 +2659,13 @@ name = "ord" version = "0.14.1" dependencies = [ "anyhow", + "async-channel 1.9.0", "async-trait", "axum", "axum-server", "base64 0.21.5", "bech32", - "bigdecimal", + "bigdecimal 0.4.2", "bip39", "bitcoin", "boilerplate", @@ -2359,6 +2685,7 @@ dependencies = [ "http 0.2.11", "humantime", "hyper", + "indexer-sdk", "indicatif", "lazy_static", "log", @@ -2628,6 +2955,17 @@ dependencies = [ "yansi", ] +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-num-traits", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -2973,6 +3311,12 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-serialize" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe834bc780604f4674073badbad26d7219cadfb4a2275802db12cbae17498401" + [[package]] name = "rustc_version" version = "0.4.0" @@ -2998,7 +3342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" dependencies = [ "bitflags 1.3.2", - "errno", + "errno 0.3.8", "io-lifetimes", "libc", "linux-raw-sys 0.3.8", @@ -3012,7 +3356,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ "bitflags 2.4.1", - "errno", + "errno 0.3.8", "libc", "linux-raw-sys 0.4.12", "windows-sys 0.52.0", @@ -3115,6 +3459,20 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +[[package]] +name = "rusty-leveldb" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3453f8c92d48f99810f59095444eda0622ae8485ac899ad97a574fd7cb9aa9c4" +dependencies = [ + "crc", + "errno 0.2.8", + "fs2", + "integer-encoding", + "rand", + "snap", +] + [[package]] name = "ryu" version = "1.0.16" @@ -3339,6 +3697,15 @@ dependencies = [ "tzdb", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "slab" version = "0.4.9" @@ -3354,6 +3721,12 @@ version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + [[package]] name = "socket2" version = "0.4.10" @@ -3386,6 +3759,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -3472,7 +3851,7 @@ dependencies = [ "ntapi", "once_cell", "rayon", - "windows", + "windows 0.52.0", ] [[package]] @@ -3631,7 +4010,9 @@ dependencies = [ "libc", "mio", "num_cpus", + "parking_lot 0.12.1", "pin-project-lite", + "signal-hook-registry", "socket2 0.5.5", "tokio-macros", "windows-sys 0.48.0", @@ -3826,6 +4207,18 @@ dependencies = [ "tz-rs", ] +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicase" version = "2.7.0" @@ -3948,6 +4341,15 @@ dependencies = [ "syn 2.0.46", ] +[[package]] +name = "uuid" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +dependencies = [ + "getrandom", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -4073,6 +4475,17 @@ version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" +[[package]] +name = "wg" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f8542ff1b4f572beb2fa9620c0c5fc9abf6b15cfb609bfd87886328cbff4ef" +dependencies = [ + "event-listener 5.2.0", + "event-listener-strategy 0.5.0", + "pin-project-lite", +] + [[package]] name = "winapi" version = "0.3.9" @@ -4104,6 +4517,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows" version = "0.52.0" @@ -4123,6 +4545,21 @@ dependencies = [ "windows-targets 0.52.0", ] +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -4378,3 +4815,30 @@ name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + +[[package]] +name = "zeromq" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2db35fbc7d9082d39a85c9831ec5dc7b7b135038d2f00bb5ff2a4c0275893da1" +dependencies = [ + "async-trait", + "asynchronous-codec", + "bytes", + "crossbeam-queue", + "dashmap", + "futures-channel", + "futures-io", + "futures-task", + "futures-util", + "log", + "num-traits", + "once_cell", + "parking_lot 0.12.1", + "rand", + "regex", + "thiserror", + "tokio", + "tokio-util 0.7.10", + "uuid", +] diff --git a/Cargo.toml b/Cargo.toml index 634ae8db32..d3fea9ff5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,8 @@ log4rs = { version = "1.2.0", features = ["gzip"] } once_cell = "1.19.0" rmp-serde = "1.1.2" rayon = "1.8.0" - +indexer-sdk={git="https://github.com/okx/indexer-sdk.git",rev = "ccb97b47a0b7d44a8fa9c3a939e5d8a1e242245a"} +async-channel = "1.9.0" [dev-dependencies] criterion = "0.5.1" executable-path = "1.0.0" diff --git a/dev/start.sh b/dev/start.sh new file mode 100755 index 0000000000..1d7b410e15 --- /dev/null +++ b/dev/start.sh @@ -0,0 +1 @@ +cargo run -- --data-dir=./_cache --regtest --first-brc20-height=0 --log-level=DEBUG --rpc-url=localhost:28443 --bitcoin-rpc-user=bitcoinrpc --bitcoin-rpc-pass=bitcoinrpc --simulate-zmq-url=tcp://0.0.0.0:29000 --simulate-bitcoin-rpc-url=localhost:28443 --simulate-bitcoin-rpc-pass=bitcoinrpc --simulate-bitcoin-rpc-user=bitcoinrpc --simulate-index=simulate --enable-save-ord-receipts --enable-index-brc20 server --http-port=3888 \ No newline at end of file diff --git a/src/index.rs b/src/index.rs index 689b4a6a3a..bbf2bb5564 100644 --- a/src/index.rs +++ b/src/index.rs @@ -50,6 +50,7 @@ mod reorg; mod rtx; pub(crate) mod updater; +pub mod simulator; #[cfg(test)] pub(crate) mod testing; @@ -101,6 +102,9 @@ define_table! { BRC20_EVENTS, &TxidValue, &[u8] } define_table! { BRC20_TRANSFERABLELOG, &str, &[u8] } define_table! { BRC20_INSCRIBE_TRANSFER, InscriptionIdValue, &[u8] } +// simulate +define_table! { SIMULATE_TRACE_TABLE, &TxidValue, &[u8] } + #[derive(Debug, PartialEq)] pub enum List { Spent, @@ -268,6 +272,7 @@ impl Index { let once = Once::new(); let progress_bar = Mutex::new(None); + #[allow(clippy::blocks_in_conditions)] let database = match Database::builder() .set_cache_size(db_cache_size) .set_repair_callback(move |progress: &mut RepairSession| { @@ -788,11 +793,11 @@ impl Index { Ok(()) } - fn begin_read(&self) -> Result { + pub fn begin_read(&self) -> Result { Ok(rtx::Rtx(self.database.begin_read()?)) } - fn begin_write(&self) -> Result { + pub fn begin_write(&self) -> Result { let mut tx = self.database.begin_write()?; tx.set_durability(self.durability); Ok(tx) diff --git a/src/index/rtx.rs b/src/index/rtx.rs index 0767ec6152..1398709006 100644 --- a/src/index/rtx.rs +++ b/src/index/rtx.rs @@ -1,6 +1,6 @@ use super::*; -pub(crate) struct Rtx<'a>(pub(crate) redb::ReadTransaction<'a>); +pub struct Rtx<'a>(pub(crate) redb::ReadTransaction<'a>); impl Rtx<'_> { pub(crate) fn block_height(&self) -> Result> { diff --git a/src/index/simulator/error.rs b/src/index/simulator/error.rs new file mode 100644 index 0000000000..f9fe30cc3b --- /dev/null +++ b/src/index/simulator/error.rs @@ -0,0 +1,27 @@ +use bitcoin::Txid; +use indexer_sdk::error::IndexerError; +use redb::{CommitError, TableError}; + +#[derive(Debug, thiserror::Error)] +pub enum SimulateError { + #[error("tx not found: {0}")] + TxNotFound(Txid), + + #[error("error: {0}")] + Anyhow(#[from] anyhow::Error), + + #[error("commit failed: {0}")] + CommitError(#[from] CommitError), + + #[error("table failed: {0}")] + TableError(#[from] TableError), + + #[error("indexer failed: {0}")] + IndexerError(#[from] IndexerError), + + #[error("transaction failed: {0}")] + TransactionError(#[from] redb::TransactionError), + + #[error("over flow")] + StackOverFlow, +} diff --git a/src/index/simulator/mod.rs b/src/index/simulator/mod.rs new file mode 100644 index 0000000000..abb4fda4e1 --- /dev/null +++ b/src/index/simulator/mod.rs @@ -0,0 +1,14 @@ +pub mod error; +#[allow(clippy::needless_return)] +#[allow(clippy::map_identity)] +#[allow(clippy::unnecessary_cast)] +#[allow(clippy::clone_on_copy)] +pub mod processor; +#[allow(clippy::needless_return)] +#[allow(clippy::map_identity)] +#[allow(clippy::unnecessary_cast)] +#[allow(clippy::clone_on_copy)] +#[allow(clippy::needless_borrow)] +#[allow(clippy::let_and_return)] +pub mod simulate; +pub mod types; diff --git a/src/index/simulator/processor.rs b/src/index/simulator/processor.rs new file mode 100644 index 0000000000..2a08b6cafc --- /dev/null +++ b/src/index/simulator/processor.rs @@ -0,0 +1,432 @@ +use crate::index::entry::{Entry, SatPointValue}; +use crate::index::{ + InscriptionEntryValue, InscriptionIdValue, OutPointValue, Statistic, TxidValue, + HOME_INSCRIPTIONS, INSCRIPTION_ID_TO_SEQUENCE_NUMBER, OUTPOINT_TO_ENTRY, + SATPOINT_TO_SEQUENCE_NUMBER, SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY, STATISTIC_TO_COUNT, +}; +use crate::okx::datastore::ord::redb::table::get_txout_by_outpoint; +use crate::okx::protocol::simulate::SimulateContext; +use crate::okx::protocol::trace::TraceNode; +use crate::{Index, InscriptionId, SatPoint}; +use anyhow::anyhow; +use bitcoin::{OutPoint, Transaction, TxOut, Txid}; +use indexer_sdk::client::drect::DirectClient; +use indexer_sdk::client::SyncClient; +use indexer_sdk::storage::db::memory::MemoryDB; +use indexer_sdk::storage::db::thread_safe::ThreadSafeDB; +use indexer_sdk::storage::kv::KVStorageProcessor; +use redb::{ + MultimapTable, ReadOnlyTable, ReadableTable, RedbKey, RedbValue, Table, TableDefinition, +}; +use std::cell::RefCell; +use std::collections::HashSet; +use std::marker::PhantomData; +use std::ops::Deref; +use std::rc::Rc; +use std::sync::Arc; + +#[derive(Clone)] +pub struct IndexWrapper { + pub internal: Arc, +} + +impl IndexWrapper { + pub fn use_internal_table( + &self, + table_def: TableDefinition, + f: impl FnOnce(ReadOnlyTable) -> crate::Result, + ) -> crate::Result { + let rtx = self.internal.begin_read()?; + let table = rtx.0.open_table(table_def)?; + f(table) + } + pub fn new(internal: Arc) -> Self { + Self { internal } + } +} + +// could be trait +#[derive(Clone)] +pub struct StorageProcessor<'a, 'db, 'tx> { + pub internal: IndexWrapper, + + pub(super) home_inscriptions: Rc>>, + pub(super) id_to_sequence_number: Rc>>, + pub(super) inscription_number_to_sequence_number: Rc>>, + pub(super) outpoint_to_entry: Rc>>, + pub(super) transaction_id_to_transaction: + Rc>>, + pub(super) sat_to_sequence_number: Rc>>, + pub(super) satpoint_to_sequence_number: + Rc>>, + pub(super) sequence_number_to_children: Rc>>, + pub(super) sequence_number_to_satpoint: Rc>>, + pub(super) sequence_number_to_inscription_entry: + Rc>>, + pub outpoint_to_sat_ranges: Rc>>, + pub sat_to_satpoint: Rc>>, + pub statistic_to_count: Rc>>, + pub trace_table: Rc>>, + pub _marker_a: PhantomData<&'a ()>, + + pub client: Option>>>, + + pub traces: Rc>>, + pub context: SimulateContext<'a, 'db, 'tx>, +} + +unsafe impl<'a, 'db, 'tx> Send for StorageProcessor<'a, 'db, 'tx> {} + +unsafe impl<'a, 'db, 'tx> Sync for StorageProcessor<'a, 'db, 'tx> {} + +impl<'a, 'db, 'tx> StorageProcessor<'a, 'db, 'tx> { + pub fn get_transaction(&self, tx_id: &Txid) -> crate::Result> { + let client = self.client.as_ref().unwrap(); + let ret = client.get_transaction_by_tx_id(*tx_id)?; + Ok(ret) + } + pub fn create_context(&self) -> crate::Result> { + Ok(self.context.clone()) + } + pub fn next_sequence_number(&self) -> crate::Result { + let table = self.sequence_number_to_inscription_entry.borrow(); + let ret: u32 = table + .iter()? + .next_back() + .and_then(|result| result.ok()) + .map(|(number, _id)| number.value() + 1) + .unwrap_or(0); + let v = self + .internal + .use_internal_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY, |table| { + Ok({ + table + .iter()? + .next_back() + .and_then(|result| result.ok()) + .map(|(number, _id)| number.value() + 1) + .unwrap_or(0) + }) + })?; + if ret > v { + return Ok(ret); + } + Ok(v) + } + pub fn id_to_sequence_number_get(&self, x: &InscriptionIdValue) -> crate::Result> { + let ret = self + .internal + .use_internal_table(INSCRIPTION_ID_TO_SEQUENCE_NUMBER, |table| { + let value = table + .get(x) + .map_err(|e| anyhow!("id_to_sequence_number_get error:{}", e))?; + if let Some(value) = value { + return Ok(Some(value.value())); + } + return Ok(None); + })?; + if let Some(ret) = ret { + return Ok(Some(ret)); + } + let table = self.id_to_sequence_number.borrow(); + let v = table.get(x)?; + if let Some(v) = v { + return Ok(Some(v.value())); + } + Ok(None) + } + pub fn sequence_number_to_entry_get( + &self, + initial_inscription_sequence_number: u32, + ) -> crate::Result> { + let ret = self + .internal + .use_internal_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY, |table| { + let ret = table + .get(initial_inscription_sequence_number) + .map_err(move |e| anyhow!("sequence_number_to_entry_get error:{}", e))?; + if let Some(ret) = ret { + return Ok(Some(ret.value())); + } + return Ok(None); + })?; + if let Some(ret) = ret { + return Ok(Some(ret)); + } + let table = self.sequence_number_to_inscription_entry.borrow(); + let value = table.get(initial_inscription_sequence_number)?; + if let Some(v) = value { + return Ok(Some(v.value())); + } + Ok(None) + } + pub fn get_lost_sats(&self) -> crate::Result { + let ret = self + .internal + .use_internal_table(STATISTIC_TO_COUNT, |table| { + Ok({ + table + .get(&Statistic::LostSats.key())? + .map(|lost_sats| lost_sats.value()) + .unwrap_or(0) + }) + })?; + if ret == 0 { + let table = self.statistic_to_count.borrow(); + let ret = table + .get(&Statistic::LostSats.key())? + .map(|lost_sats| lost_sats.value()) + .unwrap_or(0); + return Ok(ret); + } + return Ok(ret); + } + pub fn get_cursed_inscription_count(&self) -> crate::Result { + let ret = self + .internal + .use_internal_table(STATISTIC_TO_COUNT, |table| { + Ok({ + table + .get(&Statistic::CursedInscriptions.key())? + .map(|count| count.value()) + .unwrap_or(0) + }) + })?; + if ret != 0 { + return Ok(ret); + } + let table = self.statistic_to_count.borrow(); + let ret = table + .get(&Statistic::CursedInscriptions.key())? + .map(|count| count.value()) + .unwrap_or(0); + return Ok(ret); + } + pub fn get_blessed_inscription_count(&self) -> crate::Result { + let ret = self + .internal + .use_internal_table(STATISTIC_TO_COUNT, |table| { + Ok({ + table + .get(&Statistic::BlessedInscriptions.key())? + .map(|count| count.value()) + .unwrap_or(0) + }) + })?; + if ret != 0 { + return Ok(ret); + } + let table = self.statistic_to_count.borrow(); + let ret = table + .get(&Statistic::BlessedInscriptions.key())? + .map(|count| count.value()) + .unwrap_or(0); + Ok(ret) + } + pub fn get_unbound_inscriptions(&self) -> crate::Result { + let table = self.statistic_to_count.borrow(); + let ret = table + .get(&Statistic::UnboundInscriptions.key())? + .map(|unbound_inscriptions| unbound_inscriptions.value()) + .unwrap_or(0); + if ret > 0 { + return Ok(ret); + } + let ret = self + .internal + .use_internal_table(STATISTIC_TO_COUNT, |table| { + Ok({ + table + .get(&Statistic::UnboundInscriptions.key())? + .map(|count| count.value()) + .unwrap_or(0) + }) + })?; + Ok(ret) + } + pub fn get_txout_by_outpoint(&self, x: &OutPoint) -> crate::Result> { + let bindind = self.outpoint_to_entry.borrow(); + let ret = get_txout_by_outpoint(bindind.deref(), x)?; + if let Some(ret) = ret { + return Ok(Some(ret)); + } + let ret = self + .internal + .use_internal_table(OUTPOINT_TO_ENTRY, |table| get_txout_by_outpoint(&table, x))?; + Ok(ret) + } + pub fn inscriptions_on_output( + &self, + prev_output: &OutPoint, + ) -> crate::Result> { + let table = self.satpoint_to_sequence_number.borrow(); + let satpoint_to_sequence_number = table.deref(); + + let table = self.sequence_number_to_inscription_entry.borrow(); + let sequence_number_to_entry = table.deref(); + let ret = Index::inscriptions_on_output( + satpoint_to_sequence_number, + sequence_number_to_entry, + prev_output.clone(), + )?; + + let mut set: HashSet<(SatPoint, InscriptionId)> = + ret.into_iter().map(|(k, v)| (k, v)).collect(); + + let rtx = self.internal.internal.begin_read()?; + let satpoint_to_sequence_number = rtx.0.open_multimap_table(SATPOINT_TO_SEQUENCE_NUMBER)?; + let sequence_number_to_entry = rtx.0.open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY)?; + let ret = Index::inscriptions_on_output( + &satpoint_to_sequence_number, + &sequence_number_to_entry, + prev_output.clone(), + )?; + for node in ret { + if set.contains(&node) { + continue; + } + set.insert(node); + } + Ok(set.into_iter().collect()) + } + pub fn home_inscriptions_len(&self) -> u64 { + let table = self.home_inscriptions.borrow(); + let sim = table.len().unwrap(); + let ret = self + .internal + .use_internal_table(HOME_INSCRIPTIONS, |table| { + // TODO + Ok(table.len().unwrap()) + }) + .unwrap(); + return sim + ret; + } + pub fn sequence_number_to_satpoint_insert( + &self, + sequence_number: u32, + sat_point: &SatPointValue, + ) -> crate::Result<()> { + let mut table = self.sequence_number_to_satpoint.borrow_mut(); + table.insert(sequence_number, sat_point)?; + Ok(()) + } + pub fn satpoint_to_sequence_number_insert( + &self, + sat_point: &SatPointValue, + sequence: u32, + ) -> crate::Result<()> { + let mut table = self.sequence_number_to_satpoint.borrow_mut(); + table.insert(sequence, sat_point)?; + Ok(()) + } + pub fn home_inscriptions_pop_first(&self) -> crate::Result<()> { + let mut table = self.home_inscriptions.borrow_mut(); + table.pop_first()?; + Ok(()) + } + pub fn home_inscriptions_insert( + &self, + sequence_number: &u32, + value: InscriptionIdValue, + ) -> crate::Result<()> { + let mut table = self.home_inscriptions.borrow_mut(); + table.insert(sequence_number, value)?; + Ok(()) + } + pub fn id_to_sequence_number_insert( + &self, + value: &InscriptionIdValue, + sequence_number: u32, + ) -> crate::Result<()> { + let mut table = self.id_to_sequence_number.borrow_mut(); + table.insert(value, sequence_number)?; + Ok(()) + } + pub fn sequence_number_to_children_insert( + &self, + parent_sequence_number: u32, + sequence_number: u32, + ) -> crate::Result<()> { + let mut table = self.sequence_number_to_children.borrow_mut(); + table.insert(parent_sequence_number, sequence_number)?; + Ok(()) + } + pub fn sequence_number_to_entry_insert( + &self, + sequence: u32, + value: &InscriptionEntryValue, + ) -> crate::Result<()> { + let mut table = self.sequence_number_to_inscription_entry.borrow_mut(); + table.insert(sequence, value)?; + Ok(()) + } + pub fn sat_to_sequence_number_insert(&self, n: &u64, sequence_number: &u32) -> crate::Result<()> { + let mut table = self.sat_to_sequence_number.borrow_mut(); + table.insert(n, sequence_number)?; + Ok(()) + } + pub fn inscription_number_to_sequence_number_insert( + &self, + inscription_number: i32, + sequence_number: u32, + ) -> crate::Result<()> { + let mut table = self.inscription_number_to_sequence_number.borrow_mut(); + table.insert(inscription_number, sequence_number)?; + Ok(()) + } + pub fn outpoint_to_entry_insert(&self, value: &OutPointValue, entry: &[u8]) -> crate::Result<()> { + let mut table = self.outpoint_to_entry.borrow_mut(); + table.insert(value, entry)?; + Ok(()) + } + pub fn transaction_id_to_transaction_insert( + &self, + tx_id: &TxidValue, + value: &[u8], + ) -> crate::Result<()> { + let mut table = self.transaction_id_to_transaction.borrow_mut(); + table.insert(tx_id, value)?; + Ok(()) + } + pub fn outpoint_to_sat_ranges_insert( + &self, + value: &OutPointValue, + data: &[u8], + ) -> crate::Result<()> { + let mut table = self.outpoint_to_sat_ranges.borrow_mut(); + table.insert(value, data)?; + Ok(()) + } + pub fn outpoint_to_sat_ranges_remove(&self, k: &OutPointValue) -> crate::Result>> { + let mut table = self.outpoint_to_sat_ranges.borrow_mut(); + let ret: Vec = table + .remove(k)? + .map(|ranges| ranges.value().to_vec()) + .unwrap_or_default(); + return Ok(Some(ret)); + } + pub fn satpoint_to_sequence_number_remove_all(&self, v: &SatPointValue) -> crate::Result<()> { + let mut table = self.satpoint_to_sequence_number.borrow_mut(); + table.remove_all(v)?; + Ok(()) + } + pub fn sat_to_satpoint_insert(&self, key: &u64, value: &SatPointValue) -> crate::Result<()> { + let mut table = self.sat_to_satpoint.borrow_mut(); + table.insert(key, value)?; + Ok(()) + } + + pub fn save_traces(&self, tx_id: &Txid) -> crate::Result<()> { + let mut traces = self.traces.borrow_mut(); + let insert_traces = traces.clone(); + traces.clear(); + if insert_traces.is_empty() { + return Ok(()); + } + let mut table = self.trace_table.borrow_mut(); + let key = tx_id.store(); + let value = rmp_serde::to_vec(&insert_traces)?; + table.insert(&key, value.as_slice())?; + Ok(()) + } +} diff --git a/src/index/simulator/simulate.rs b/src/index/simulator/simulate.rs new file mode 100644 index 0000000000..a22099e81a --- /dev/null +++ b/src/index/simulator/simulate.rs @@ -0,0 +1,734 @@ +use crate::height::Height; +use crate::index::entry::Entry; +use crate::index::simulator::error::SimulateError; +use crate::index::simulator::processor::{IndexWrapper, StorageProcessor}; +use crate::index::simulator::types::ExecuteTxResponse; +use crate::index::updater::pending_updater::PendingUpdater; +use crate::index::{ + BlockData, InscriptionIdValue, BRC20_BALANCES, BRC20_EVENTS, BRC20_INSCRIBE_TRANSFER, + BRC20_TOKEN, BRC20_TRANSFERABLELOG, COLLECTIONS_INSCRIPTION_ID_TO_KINDS, + COLLECTIONS_KEY_TO_INSCRIPTION_ID, HOME_INSCRIPTIONS, INSCRIPTION_ID_TO_SEQUENCE_NUMBER, + INSCRIPTION_NUMBER_TO_SEQUENCE_NUMBER, ORD_TX_TO_OPERATIONS, OUTPOINT_TO_ENTRY, + OUTPOINT_TO_SAT_RANGES, SATPOINT_TO_SEQUENCE_NUMBER, SAT_TO_SATPOINT, SAT_TO_SEQUENCE_NUMBER, + SEQUENCE_NUMBER_TO_CHILDREN, SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY, SEQUENCE_NUMBER_TO_SATPOINT, + SIMULATE_TRACE_TABLE, STATISTIC_TO_COUNT, TRANSACTION_ID_TO_TRANSACTION, +}; +use crate::okx::datastore::brc20::redb::table::get_transaction_receipts; +use crate::okx::datastore::brc20::{Brc20Reader, Receipt}; +use crate::okx::datastore::cache::CacheTableIndex; +use crate::okx::datastore::ord::{InscriptionOp, OrdReader}; +use crate::okx::lru::SimpleLru; +use crate::okx::protocol::simulate::SimulateContext; +use crate::okx::protocol::trace::TraceNode; +use crate::okx::protocol::{ProtocolConfig, ProtocolManager}; +use crate::{Index, Options, Sat, SatPoint}; +use anyhow::anyhow; +use bitcoin::{Block, OutPoint, Transaction, TxOut, Txid}; +use indexer_sdk::client::drect::DirectClient; +use indexer_sdk::client::event::ClientEvent; +use indexer_sdk::client::Client; +use indexer_sdk::configuration::base::{IndexerConfiguration, NetConfiguration, ZMQConfiguration}; +use indexer_sdk::factory::common::async_create_and_start_processor; +use indexer_sdk::storage::db::memory::MemoryDB; +use indexer_sdk::storage::db::thread_safe::ThreadSafeDB; +use indexer_sdk::storage::kv::KVStorageProcessor; +use indexer_sdk::wait_exit_signal; +use log::{error, info}; +use redb::{Database, ReadableTable, RedbValue, WriteTransaction}; +use std::cell::RefCell; +use std::collections::{HashMap, HashSet, VecDeque}; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; +use std::path::Path; +use std::rc::Rc; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; +use std::thread; +use tokio::runtime::Runtime; +use tokio::sync::watch; +use tokio::task::JoinHandle; + +pub struct Simulator<'a, 'db, 'tx> { + pub internal_index: IndexWrapper, + pub client: Option>>>, + _marker_a: PhantomData<&'a ()>, + _marker_b: PhantomData<&'db ()>, + _marker_tx: PhantomData<&'tx ()>, +} + +#[derive(Clone)] +pub struct SimulatorServer { + tx_out_cache: Rc>>, + pub internal_index: IndexWrapper, + pub simulate_index: Arc, + pub client: DirectClient>>, +} + +unsafe impl Send for SimulatorServer {} + +unsafe impl Sync for SimulatorServer {} + +impl SimulatorServer { + pub async fn start(&self, rt: Arc, exit: watch::Receiver<()>) -> JoinHandle<()> { + let internal = self.clone(); + rt.spawn(async move { + internal.on_start(exit).await; + }) + } + async fn on_start(self, mut exit: watch::Receiver<()>) { + loop { + tokio::select! { + event=self.get_client_event()=>{ + match event{ + Ok(event) => { + if let Err(e)= self.handle_event(&event).await{ + log::error!("handle event error: {:?}", e); + } + } + Err(e) => { + log::error!("receive event error: {:?}", e); + continue; + } + } + }, + _ = exit.changed() => { + log::info!("simulator receive exit signal"); + break; + } + } + } + } + + fn get_current_height(&self) -> crate::Result { + let height = self + .internal_index + .internal + .block_height()? + .unwrap_or(Height(0)); + Ok(height.0) + } + async fn handle_event(&self, event: &ClientEvent) -> crate::Result<()> { + info!("sim receive event:{:?}", event); + match event { + ClientEvent::Transaction(tx) => { + self.execute_tx(tx, true)?; + } + ClientEvent::GetHeight => { + let height = self.get_current_height()?; + self.client.report_height(height).await?; + } + ClientEvent::TxDroped(tx) => { + let tx = tx.clone().into(); + self.remove_tx_traces(&tx)?; + } + ClientEvent::TxConfirmed(tx) => { + let tx = tx.clone().into(); + self.remove_tx_traces(&tx)?; + } + } + Ok(()) + } + fn remove_tx_traces(&self, txid: &Txid) -> crate::Result<()> { + let wtx = self.simulate_index.begin_write()?; + let commit = self.do_remove_traces(txid, &wtx)?; + if commit { + wtx.commit()?; + } + Ok(()) + } + fn do_remove_traces(&self, txid: &Txid, wtx: &WriteTransaction) -> crate::Result { + let traces_table = wtx.open_table(SIMULATE_TRACE_TABLE)?; + let value = txid.store(); + let traces = traces_table.get(&value)?; + if traces.is_none() { + return Ok(false); + } + + let mut brc20_balances = wtx.open_table(BRC20_BALANCES)?; + let mut brc20_token = wtx.open_table(BRC20_TOKEN)?; + let mut events = wtx.open_table(BRC20_EVENTS)?; + let mut brc20_transferlog = wtx.open_table(BRC20_TRANSFERABLELOG)?; + let mut brc20_inscribe_transfer = wtx.open_table(BRC20_INSCRIBE_TRANSFER)?; + let mut ord_tx_id_to_operations = wtx.open_table(ORD_TX_TO_OPERATIONS)?; + let mut collections_key_to_inscription_id = + wtx.open_table(COLLECTIONS_KEY_TO_INSCRIPTION_ID)?; + let mut collection_inscription_id_to_kinds = + wtx.open_table(COLLECTIONS_INSCRIPTION_ID_TO_KINDS)?; + let binding = traces.unwrap(); + let traces = binding.value(); + let traces: Vec = rmp_serde::from_slice(traces)?; + for node in traces { + let key = node.key; + match node.trace_type { + CacheTableIndex::TXID_TO_INSCRIPTION_RECEIPTS => {} + CacheTableIndex::SEQUENCE_NUMBER_TO_SATPOINT => {} + CacheTableIndex::SAT_TO_SEQUENCE_NUMBER => {} + CacheTableIndex::HOME_INSCRIPTIONS => {} + CacheTableIndex::INSCRIPTION_ID_TO_SEQUENCE_NUMBER => {} + CacheTableIndex::SEQUENCE_NUMBER_TO_CHILDREN => {} + CacheTableIndex::SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY => {} + CacheTableIndex::INSCRIPTION_NUMBER_TO_SEQUENCE_NUMBER => {} + CacheTableIndex::OUTPOINT_TO_ENTRY => {} + CacheTableIndex::BRC20_BALANCES => { + let key = String::from_utf8(key).unwrap(); + let key = key.as_str(); + brc20_balances.remove(key)?; + } + CacheTableIndex::BRC20_TOKEN => { + let key = String::from_utf8(key).unwrap(); + let key = key.as_str(); + brc20_token.remove(key)?; + } + CacheTableIndex::BRC20_EVENTS => { + let key = key.as_slice(); + let key: &Txid = &rmp_serde::from_slice(key).unwrap(); + events.remove(&key.store())?; + } + CacheTableIndex::BRC20_TRANSFERABLELOG => { + let key = String::from_utf8(key).unwrap(); + brc20_transferlog.remove(key.as_str())?; + } + CacheTableIndex::BRC20_INSCRIBE_TRANSFER => { + let key = InscriptionIdValue::from_bytes(key.as_slice()); + brc20_inscribe_transfer.remove(&key)?; + } + CacheTableIndex::ORD_TX_TO_OPERATIONS => { + let key: [u8; 32] = key.as_slice().try_into().unwrap(); + ord_tx_id_to_operations.remove(&key)?; + } + CacheTableIndex::COLLECTIONS_KEY_TO_INSCRIPTION_ID => { + let key = String::from_utf8(key).unwrap(); + collections_key_to_inscription_id.remove(key.as_str())?; + } + CacheTableIndex::COLLECTIONS_INSCRIPTION_ID_TO_KINDS => { + let key = InscriptionIdValue::from_bytes(&key); + collection_inscription_id_to_kinds.remove(&key)?; + } + } + } + Ok(true) + } + async fn get_client_event(&self) -> crate::Result { + let ret = self.client.block_get_event().await?; + Ok(ret) + } + pub fn execute_tx( + &self, + tx: &Transaction, + commit: bool, + ) -> crate::Result { + let wtx = self.simulate_index.begin_write()?; + let traces = Rc::new(RefCell::new(vec![])); + let ret = self.simulate_tx(tx, &wtx, traces)?; + if commit { + wtx.commit()?; + } + + Ok(ret) + } + + pub fn get_receipt(&self, tx_id: Txid) -> Result, anyhow::Error> { + let rx = self.simulate_index.begin_read()?; + let tab = rx.open_table(BRC20_EVENTS)?; + let ret = get_transaction_receipts(&tab, &tx_id)?; + Ok(ret) + } + fn simulate_tx( + &self, + tx: &Transaction, + wtx: &WriteTransaction, + traces: Rc>>, + ) -> crate::Result { + let mut ret = ExecuteTxResponse::default(); + let brc20_receipts = Rc::new(RefCell::new(vec![])); + let ord_operations = Rc::new(RefCell::new(vec![])); + let height = self.get_current_height()?; + let block = self + .internal_index + .internal + .get_block_by_height(height)? + .unwrap(); + let home_inscriptions = wtx.open_table(HOME_INSCRIPTIONS)?; + let inscription_id_to_sequence_number = wtx.open_table(INSCRIPTION_ID_TO_SEQUENCE_NUMBER)?; + let inscription_number_to_sequence_number = + wtx.open_table(INSCRIPTION_NUMBER_TO_SEQUENCE_NUMBER)?; + let sat_to_sequence_number = wtx.open_multimap_table(SAT_TO_SEQUENCE_NUMBER)?; + let satpoint_to_sequence_number = wtx.open_multimap_table(SATPOINT_TO_SEQUENCE_NUMBER)?; + let sequence_number_to_children = wtx.open_multimap_table(SEQUENCE_NUMBER_TO_CHILDREN)?; + let sequence_number_to_inscription_entry = Rc::new(RefCell::new( + wtx.open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY)?, + )); + let sequence_number_to_satpoint = wtx.open_table(SEQUENCE_NUMBER_TO_SATPOINT)?; + let transaction_id_to_transaction = wtx.open_table(TRANSACTION_ID_TO_TRANSACTION)?; + let outpoint_to_entry = Rc::new(RefCell::new(wtx.open_table(OUTPOINT_TO_ENTRY)?)); + let outpoint_to_sat_ranges = wtx.open_table(OUTPOINT_TO_SAT_RANGES)?; + let sat_to_point = wtx.open_table(SAT_TO_SATPOINT)?; + let statis_to_count = wtx.open_table(STATISTIC_TO_COUNT)?; + let traces_table = wtx.open_table(SIMULATE_TRACE_TABLE)?; + + let h = height; + let ts = block.header.time; + let ctx = SimulateContext { + network: self.internal_index.internal.get_chain_network().clone(), + current_height: h, + current_block_time: ts as u32, + internal_index: self.internal_index.clone(), + ORD_TX_TO_OPERATIONS: Rc::new(RefCell::new( + wtx.open_table(crate::index::ORD_TX_TO_OPERATIONS)?, + )), + COLLECTIONS_KEY_TO_INSCRIPTION_ID: Rc::new(RefCell::new( + wtx.open_table(COLLECTIONS_KEY_TO_INSCRIPTION_ID)?, + )), + COLLECTIONS_INSCRIPTION_ID_TO_KINDS: Rc::new(RefCell::new( + wtx.open_table(COLLECTIONS_INSCRIPTION_ID_TO_KINDS)?, + )), + SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY: sequence_number_to_inscription_entry.clone(), + OUTPOINT_TO_ENTRY: outpoint_to_entry.clone(), + BRC20_BALANCES: Rc::new(RefCell::new(wtx.open_table(BRC20_BALANCES)?)), + BRC20_TOKEN: Rc::new(RefCell::new(wtx.open_table(BRC20_TOKEN)?)), + BRC20_EVENTS: Rc::new(RefCell::new(wtx.open_table(BRC20_EVENTS)?)), + BRC20_TRANSFERABLELOG: Rc::new(RefCell::new(wtx.open_table(BRC20_TRANSFERABLELOG)?)), + BRC20_INSCRIBE_TRANSFER: Rc::new(RefCell::new(wtx.open_table(BRC20_INSCRIBE_TRANSFER)?)), + traces: traces.clone(), + brc20_receipts: brc20_receipts.clone(), + ord_operations: ord_operations.clone(), + _marker_a: Default::default(), + }; + + let db_receipts = ctx.get_transaction_receipts(&tx.txid())?; + let operations = ctx.get_transaction_operations(&tx.txid())?; + if !db_receipts.is_empty() || !operations.is_empty() { + ret.ord_operations = operations; + ret.brc20_receipts = db_receipts; + info!("tx:{:?} already simulated", tx.txid()); + return Ok(ret); + } + + let processor = StorageProcessor { + internal: self.internal_index.clone(), + // wtx: &mut wtx, + home_inscriptions: Rc::new(RefCell::new(home_inscriptions)), + id_to_sequence_number: Rc::new(RefCell::new(inscription_id_to_sequence_number)), + inscription_number_to_sequence_number: Rc::new(RefCell::new( + inscription_number_to_sequence_number, + )), + outpoint_to_entry, + transaction_id_to_transaction: Rc::new(RefCell::new(transaction_id_to_transaction)), + sat_to_sequence_number: Rc::new(RefCell::new(sat_to_sequence_number)), + satpoint_to_sequence_number: Rc::new(RefCell::new(satpoint_to_sequence_number)), + sequence_number_to_children: Rc::new(RefCell::new(sequence_number_to_children)), + sequence_number_to_satpoint: Rc::new(RefCell::new(sequence_number_to_satpoint)), + sequence_number_to_inscription_entry, + outpoint_to_sat_ranges: Rc::new(RefCell::new(outpoint_to_sat_ranges)), + sat_to_satpoint: Rc::new(RefCell::new(sat_to_point)), + statistic_to_count: Rc::new(RefCell::new(statis_to_count)), + trace_table: Rc::new(RefCell::new(traces_table)), + _marker_a: Default::default(), + client: Some(self.client.clone()), + traces: traces.clone(), + context: ctx, + }; + + let mut depth = 0; + self.loop_simulate_tx(h, &block, &processor, &tx, &mut depth)?; + + let receipts = brc20_receipts.borrow(); + let receipts = receipts.deref().clone(); + let ord_operations = ord_operations.borrow(); + let ord_operations = ord_operations.deref().clone(); + ret.brc20_receipts = receipts; + ret.ord_operations = ord_operations; + + Ok(ret) + } + pub fn loop_simulate_tx( + &self, + h: u32, + block: &Block, + processor: &StorageProcessor, + tx: &Transaction, + depth: &mut i32, + ) -> crate::Result<(), SimulateError> { + if *depth >= 20 { + error!("simulate tx:{:?} depth:{:?} over 20", tx.txid(), depth); + return Err(SimulateError::StackOverFlow); + } + *depth += 1; + let tx_id = tx.txid(); + let mut need_handle_first = vec![]; + for input in &tx.input { + if input.previous_output.is_null() { + continue; + } + let prev_tx_id = &input.previous_output.txid; + let prev_out = processor.get_txout_by_outpoint(&input.previous_output)?; + if prev_out.is_none() { + need_handle_first.push(prev_tx_id.clone()); + } + } + if need_handle_first.is_empty() { + info!("parent suits is ready,start to simulate tx:{:?}", &tx_id); + } + for (index, parent) in need_handle_first.iter().enumerate() { + let parent_tx = processor.get_transaction(parent)?; + if parent_tx.is_none() { + error!( + "parent tx not exist,tx_hash:{:?},child_hash:{:?}", + *parent, &tx_id + ); + return Err(SimulateError::TxNotFound(parent.clone())); + } + let parent_tx = parent_tx.unwrap(); + info!( + "parent tx :{:?},exist,but not in utxo data,child_hash:{:?},need to simulate parent tx", + &parent, &tx_id + ); + self.loop_simulate_tx(h, block, processor, &parent_tx, depth)?; + if index == need_handle_first.len() - 1 { + info!( + "all parent txs {:?} simulate done,start to simulate child_hash:{:?}", + &need_handle_first, &tx_id + ); + } else { + info!( + "parent tx {:?} simulate done,start to simulate next parent:{:?}", + &parent, + need_handle_first[index + 1] + ); + } + } + self.do_simulate_tx(h, block, processor, &tx)?; + + Ok(()) + } + + pub fn do_simulate_tx( + &self, + h: u32, + block: &Block, + processor: &StorageProcessor, + tx: &Transaction, + ) -> crate::Result<(), SimulateError> { + let mut sim = Simulator { + internal_index: self.internal_index.clone(), + client: None, + _marker_a: Default::default(), + _marker_b: Default::default(), + _marker_tx: Default::default(), + }; + let height = h; + let block = block.clone(); + let mut cache = self.tx_out_cache.borrow_mut(); + let cache = cache.deref_mut(); + + let mut operations: HashMap> = HashMap::new(); + let block = BlockData { + header: block.header, + txdata: vec![(tx.clone(), tx.txid())], + }; + sim.index_block(block.clone(), height, cache, &processor, &mut operations)?; + processor.save_traces(&tx.txid())?; + + Ok(()) + } + + pub fn new( + path: impl AsRef, + internal_index: Arc, + client: DirectClient>>, + ) -> crate::Result { + let simulate_index = Database::create(path)?; + let simulate_index = Arc::new(simulate_index); + Ok(Self { + tx_out_cache: Rc::new(RefCell::new(SimpleLru::new(500))), + internal_index: IndexWrapper::new(internal_index), + simulate_index, + client, + }) + } +} + +impl<'a, 'db, 'tx> Simulator<'a, 'db, 'tx> { + fn index_block( + &mut self, + block: BlockData, + height: u32, + tx_out_cache: &'a mut SimpleLru, + processor: &StorageProcessor<'a, 'db, 'tx>, + operations: &'a mut HashMap>, + ) -> crate::Result<()> { + let mut sat_ranges_written = 0; + let mut outputs_in_block = 0; + + let index_inscriptions = true; + + let fetching_outputs_count = AtomicUsize::new(0); + let total_outputs_count = AtomicUsize::new(0); + let cache_outputs_count = AtomicUsize::new(0); + let miss_outputs_count = AtomicUsize::new(0); + let meet_outputs_count = AtomicUsize::new(0); + if index_inscriptions { + // Send all missing input outpoints to be fetched right away + let txids = block + .txdata + .iter() + .map(|(_, txid)| txid) + .collect::>(); + let mut tx_outs = Vec::new(); + for (tx, _) in block.txdata.iter() { + for input in tx.input.iter() { + total_outputs_count.fetch_add(1, Ordering::Relaxed); + let prev_output = input.previous_output; + if prev_output.is_null() { + continue; + } else if txids.contains(&prev_output.txid) { + meet_outputs_count.fetch_add(1, Ordering::Relaxed); + continue; + } else if tx_out_cache.contains(&prev_output) { + cache_outputs_count.fetch_add(1, Ordering::Relaxed); + continue; + } else if let Some(txout) = processor.get_txout_by_outpoint(&prev_output)? { + miss_outputs_count.fetch_add(1, Ordering::Relaxed); + tx_outs.push((prev_output, Some(txout))); + } else { + fetching_outputs_count.fetch_add(1, Ordering::Relaxed); + tx_outs.push((prev_output, None)); + } + } + } + for (out_point, value) in tx_outs.into_iter() { + if let Some(tx_out) = value { + tx_out_cache.insert(out_point, tx_out); + } else { + let tx = processor.get_transaction(&out_point.txid)?; + if tx.is_none() { + return Err(anyhow!("missing transaction {}", out_point.txid.clone())); + } + let tx = tx.unwrap(); + let out = tx.output[out_point.vout as usize].clone(); + let tx_out = TxOut { + value: out.value, + script_pubkey: out.script_pubkey.clone(), + }; + tx_out_cache.insert(out_point, tx_out); + } + } + } + + let mut lost_sats = processor.get_lost_sats()?; + let cursed_inscription_count = processor.get_cursed_inscription_count()?; + let blessed_inscription_count = processor.get_blessed_inscription_count()?; + let unbound_inscriptions = processor.get_unbound_inscriptions()?; + let next_sequence_number = processor.next_sequence_number()?; + + let mut inscription_updater = PendingUpdater::new( + operations, + blessed_inscription_count, + self.internal_index.internal.options.chain(), + cursed_inscription_count, + height, + self.internal_index.internal.index_transactions, + next_sequence_number, + lost_sats, + block.header.time, + unbound_inscriptions, + tx_out_cache, + processor.clone(), + )?; + + if processor.internal.internal.index_sats { + let mut coinbase_inputs = VecDeque::new(); + + let h = Height(height); + if h.subsidy() > 0 { + let start = h.starting_sat(); + coinbase_inputs.push_front((start.n(), (start + h.subsidy()).n())); + } + + for (tx_offset, (tx, txid)) in block.txdata.iter().enumerate().skip(1) { + log::trace!("Indexing transaction {tx_offset}…"); + + let mut input_sat_ranges = VecDeque::new(); + + self.index_transaction_sats( + tx, + *txid, + &mut input_sat_ranges, + &mut sat_ranges_written, + &mut outputs_in_block, + &mut inscription_updater, + index_inscriptions, + )?; + + coinbase_inputs.extend(input_sat_ranges); + } + + if let Some((tx, txid)) = block.txdata.first() { + self.index_transaction_sats( + tx, + *txid, + &mut coinbase_inputs, + &mut sat_ranges_written, + &mut outputs_in_block, + &mut inscription_updater, + index_inscriptions, + )?; + } + + if !coinbase_inputs.is_empty() { + let mut lost_sat_ranges = processor + .outpoint_to_sat_ranges_remove(&OutPoint::null().store())? + .map(|ranges| ranges.to_vec()) + .unwrap_or_default(); + + for (start, end) in coinbase_inputs { + if !Sat(start).common() { + processor.sat_to_satpoint_insert( + &start, + &SatPoint { + outpoint: OutPoint::null(), + offset: lost_sats, + } + .store(), + )?; + } + + lost_sat_ranges.extend_from_slice(&(start, end).store()); + + lost_sats += end - start; + } + processor + .outpoint_to_sat_ranges_insert(&OutPoint::null().store(), lost_sat_ranges.as_slice())?; + } + } else if index_inscriptions { + for (tx, txid) in block.txdata.iter().skip(1).chain(block.txdata.first()) { + inscription_updater.index_envelopes(tx, *txid, None)?; + } + } + inscription_updater.flush_cache()?; + + let mut context = processor.create_context()?; + let config = ProtocolConfig::new_with_options(&self.internal_index.internal.options); + ProtocolManager::new(config).index_block(&mut context, &block, operations.clone())?; + + Ok(()) + } + + fn index_transaction_sats( + &mut self, + tx: &Transaction, + txid: Txid, + input_sat_ranges: &mut VecDeque<(u64, u64)>, + sat_ranges_written: &mut u64, + outputs_traversed: &mut u64, + inscription_updater: &mut PendingUpdater, + index_inscriptions: bool, + ) -> crate::Result { + if index_inscriptions { + inscription_updater.index_envelopes(tx, txid, Some(input_sat_ranges))?; + } + + for (vout, output) in tx.output.iter().enumerate() { + let outpoint = OutPoint { + vout: vout.try_into().unwrap(), + txid, + }; + let mut sats = Vec::new(); + + let mut remaining = output.value; + while remaining > 0 { + let range = input_sat_ranges + .pop_front() + .ok_or_else(|| anyhow!("insufficient inputs for transaction outputs"))?; + + if !Sat(range.0).common() { + inscription_updater.processor.sat_to_satpoint_insert( + &range.0, + &SatPoint { + outpoint, + offset: 0, + } + .store(), + )?; + } + + let count = range.1 - range.0; + + let assigned = if count > remaining { + let middle = range.0 + remaining; + input_sat_ranges.push_front((middle, range.1)); + (range.0, middle) + } else { + range + }; + + sats.extend_from_slice(&assigned.store()); + + remaining -= assigned.1 - assigned.0; + + *sat_ranges_written += 1; + } + + *outputs_traversed += 1; + } + + Ok(()) + } +} + +pub fn start_simulator(ops: Options, internal: Arc) -> Option { + if !ops.simulate_enable { + return None; + } + + let rt = Arc::new(Runtime::new().unwrap()); + + let ret = rt.block_on(async { do_start_simulator(ops, internal.clone()).await }); + ret +} + +async fn do_start_simulator(ops: Options, internal: Arc) -> Option { + let rt = Arc::new(Runtime::new().unwrap()); + let zmq_url = ops.simulate_zmq_url.clone().unwrap(); + let sim_rpc = ops.simulate_bitcoin_rpc_url.clone().unwrap(); + let sim_user = ops.simulate_bitcoin_rpc_user.clone().unwrap(); + let sim_pass = ops.simulate_bitcoin_rpc_pass.clone().unwrap(); + let notify_rx = ops.rx.clone().unwrap(); + + let config = IndexerConfiguration { + mq: ZMQConfiguration { + zmq_url, + zmq_topic: vec!["sequence".to_string(), "rawblock".to_string()], + }, + net: NetConfiguration { + url: sim_rpc, + username: sim_user, + password: sim_pass, + }, + ..Default::default() + }; + + let (tx, rx) = watch::channel(()); + let (client, mut handlers) = + async_create_and_start_processor(rx.clone(), config.clone(), rt.clone()).await; + + let server = SimulatorServer::new(ops.simulate_index.unwrap(), internal.clone(), client).unwrap(); + let start_server = server.clone(); + handlers.push(start_server.start(rt.clone(), rx.clone()).await); + thread::spawn(move || { + rt.block_on(async { + wait_exit_signal().await.unwrap(); + tx.send(()).unwrap(); + for h in handlers { + let _ = h.await; + } + let ret = notify_rx.recv().await; + if ret.is_ok() { + let ret = ret.unwrap(); + let _ = ret.send(()); + } + }); + }); + + Some(server) +} diff --git a/src/index/simulator/types.rs b/src/index/simulator/types.rs new file mode 100644 index 0000000000..4cfd5c96c8 --- /dev/null +++ b/src/index/simulator/types.rs @@ -0,0 +1,9 @@ +use crate::okx::datastore::brc20::Receipt; +use crate::okx::datastore::ord::InscriptionOp; +use serde::{Deserialize, Serialize}; + +#[derive(Default, Clone, Debug, Serialize, Deserialize)] +pub struct ExecuteTxResponse { + pub brc20_receipts: Vec, + pub ord_operations: Vec, +} diff --git a/src/index/updater.rs b/src/index/updater.rs index 6c4de37267..ec9b726046 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -11,8 +11,10 @@ use { pub(crate) mod inscription_updater; use crate::okx::lru::SimpleLru; +pub mod pending_updater; mod rune_updater; +#[derive(Clone)] pub(crate) struct BlockData { pub(crate) header: Header, pub(crate) txdata: Vec<(Transaction, Txid)>, diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 719d84a4b8..21d91a7ea9 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -2,7 +2,7 @@ use super::*; use crate::okx::datastore::ord::operation::{Action, InscriptionOp}; #[derive(Debug, PartialEq, Copy, Clone)] -enum Curse { +pub enum Curse { DuplicateField, IncompleteField, NotAtOffsetZero, @@ -16,16 +16,16 @@ enum Curse { #[derive(Debug, Clone)] pub(super) struct Flotsam { - txid: Txid, - inscription_id: InscriptionId, - offset: u64, - old_satpoint: SatPoint, - origin: Origin, + pub txid: Txid, + pub inscription_id: InscriptionId, + pub offset: u64, + pub old_satpoint: SatPoint, + pub origin: Origin, } #[allow(clippy::large_enum_variant)] #[derive(Debug, Clone)] -enum Origin { +pub enum Origin { New { cursed: bool, fee: u64, @@ -637,7 +637,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { } else { new_satpoint.store() }; - + let transfer_to_coin_base = new_satpoint.outpoint.txid != flotsam.txid; self .operations .entry(flotsam.txid) @@ -667,6 +667,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { unbound, vindicated, inscription, + transfer_to_coin_base, }, }, old_satpoint: flotsam.old_satpoint, diff --git a/src/index/updater/pending_updater.rs b/src/index/updater/pending_updater.rs new file mode 100644 index 0000000000..9171274b80 --- /dev/null +++ b/src/index/updater/pending_updater.rs @@ -0,0 +1,589 @@ +use crate::index::simulator::processor::StorageProcessor; +use crate::index::updater::inscription_updater::{Flotsam, Origin}; +use { + super::*, + crate::okx::datastore::ord::operation::{Action, InscriptionOp}, +}; + +pub struct PendingUpdater<'a, 'db, 'tx> { + pub processor: StorageProcessor<'a, 'db, 'tx>, + pub(super) operations: &'a mut HashMap>, + pub(super) blessed_inscription_count: u64, + pub(super) chain: Chain, + pub(super) cursed_inscription_count: u64, + pub(super) height: u32, + pub(super) home_inscription_count: u64, + pub(super) index_transactions: bool, + pub(super) next_sequence_number: u32, + pub(super) lost_sats: u64, + pub(super) reward: u64, + pub(super) transaction_buffer: Vec, + pub(super) timestamp: u32, + pub(super) unbound_inscriptions: u64, + pub(super) tx_out_cache: &'a mut SimpleLru, + pub(super) new_outpoints: Vec, +} + +impl<'a, 'db, 'tx> PendingUpdater<'a, 'db, 'tx> { + pub fn new( + operations: &'a mut HashMap>, + blessed_inscription_count: u64, + chain: Chain, + cursed_inscription_count: u64, + height: u32, + index_transactions: bool, + next_sequence_number: u32, + lost_sats: u64, + timestamp: u32, + unbound_inscriptions: u64, + tx_out_cache: &'a mut SimpleLru, + processor: StorageProcessor<'a, 'db, 'tx>, + ) -> Result { + let home_inscriptions_len = processor.home_inscriptions_len(); + Ok(Self { + processor, + operations, + blessed_inscription_count, + chain, + cursed_inscription_count, + height, + home_inscription_count: home_inscriptions_len, + index_transactions, + next_sequence_number, + lost_sats, + reward: Height(height).subsidy(), + transaction_buffer: vec![], + timestamp, + unbound_inscriptions, + tx_out_cache, + new_outpoints: vec![], + }) + } + pub fn index_envelopes( + &mut self, + tx: &Transaction, + txid: Txid, + input_sat_ranges: Option<&VecDeque<(u64, u64)>>, + ) -> Result { + let mut floating_inscriptions = Vec::new(); + let mut id_counter = 0; + let mut inscribed_offsets = BTreeMap::new(); + let jubilant = self.height >= self.chain.jubilee_height(); + let mut total_input_value = 0; + let total_output_value = tx.output.iter().map(|txout| txout.value).sum::(); + + let envelopes = ParsedEnvelope::from_transaction(tx); + let inscriptions = !envelopes.is_empty(); + let mut envelopes = envelopes.into_iter().peekable(); + + for (input_index, tx_in) in tx.input.iter().enumerate() { + // skip subsidy since no inscriptions possible + if tx_in.previous_output.is_null() { + total_input_value += Height(self.height).subsidy(); + continue; + } + + // find existing inscriptions on input (transfers of inscriptions) + for (old_satpoint, inscription_id) in self + .processor + .inscriptions_on_output(&tx_in.previous_output)? + { + let offset = total_input_value + old_satpoint.offset; + floating_inscriptions.push(Flotsam { + txid, + offset, + inscription_id, + old_satpoint, + origin: Origin::Old, + }); + + inscribed_offsets + .entry(offset) + .or_insert((inscription_id, 0)) + .1 += 1; + } + + let offset = total_input_value; + + // multi-level cache for UTXO set to get to the input amount + let current_input_value = if let Some(tx_out) = self.tx_out_cache.get(&tx_in.previous_output) + { + tx_out.value + } else { + panic!("tx_out_cache should have tx_out for input"); + }; + + total_input_value += current_input_value; + + // go through all inscriptions in this input + while let Some(inscription) = envelopes.peek() { + if inscription.input != u32::try_from(input_index).unwrap() { + break; + } + + let inscription_id = InscriptionId { + txid, + index: id_counter, + }; + + let curse = if inscription.payload.unrecognized_even_field { + Some(crate::index::updater::inscription_updater::Curse::UnrecognizedEvenField) + } else if inscription.payload.duplicate_field { + Some(crate::index::updater::inscription_updater::Curse::DuplicateField) + } else if inscription.payload.incomplete_field { + Some(crate::index::updater::inscription_updater::Curse::IncompleteField) + } else if inscription.input != 0 { + Some(crate::index::updater::inscription_updater::Curse::NotInFirstInput) + } else if inscription.offset != 0 { + Some(crate::index::updater::inscription_updater::Curse::NotAtOffsetZero) + } else if inscription.payload.pointer.is_some() { + Some(crate::index::updater::inscription_updater::Curse::Pointer) + } else if inscription.pushnum { + Some(crate::index::updater::inscription_updater::Curse::Pushnum) + } else if inscription.stutter { + Some(crate::index::updater::inscription_updater::Curse::Stutter) + } else if let Some((id, count)) = inscribed_offsets.get(&offset) { + if *count > 1 { + Some(crate::index::updater::inscription_updater::Curse::Reinscription) + } else { + let initial_inscription_sequence_number = self + .processor + .id_to_sequence_number_get(&id.store())? + .unwrap(); + + let entry = InscriptionEntry::load( + self + .processor + .sequence_number_to_entry_get(initial_inscription_sequence_number)? + .unwrap(), + ); + + let initial_inscription_was_cursed_or_vindicated = + entry.inscription_number < 0 || Charm::Vindicated.is_set(entry.charms); + + if initial_inscription_was_cursed_or_vindicated { + None + } else { + Some(crate::index::updater::inscription_updater::Curse::Reinscription) + } + } + } else { + None + }; + + let unbound = current_input_value == 0 + || curse + == Some(crate::index::updater::inscription_updater::Curse::UnrecognizedEvenField) + || inscription.payload.unrecognized_even_field; + + let offset = inscription + .payload + .pointer() + .filter(|&pointer| pointer < total_output_value) + .unwrap_or(offset); + + floating_inscriptions.push(Flotsam { + txid, + inscription_id, + offset, + old_satpoint: SatPoint { + outpoint: tx_in.previous_output, + offset: 0, + }, + origin: Origin::New { + cursed: curse.is_some() && !jubilant, + fee: 0, + hidden: inscription.payload.hidden(), + parent: inscription.payload.parent(), + pointer: inscription.payload.pointer(), + reinscription: inscribed_offsets.get(&offset).is_some(), + unbound, + inscription: inscription.payload.clone(), + vindicated: curse.is_some() && jubilant, + }, + }); + + inscribed_offsets + .entry(offset) + .or_insert((inscription_id, 0)) + .1 += 1; + + envelopes.next(); + id_counter += 1; + } + } + + if self.index_transactions && inscriptions { + tx.consensus_encode(&mut self.transaction_buffer) + .expect("in-memory writers don't error"); + self + .processor + .transaction_id_to_transaction_insert(&txid.store(), self.transaction_buffer.as_slice())?; + + self.transaction_buffer.clear(); + } + + let potential_parents = floating_inscriptions + .iter() + .map(|flotsam| flotsam.inscription_id) + .collect::>(); + + for flotsam in &mut floating_inscriptions { + if let Flotsam { + origin: Origin::New { parent, .. }, + .. + } = flotsam + { + if let Some(purported_parent) = parent { + if !potential_parents.contains(purported_parent) { + *parent = None; + } + } + } + } + + // still have to normalize over inscription size + for flotsam in &mut floating_inscriptions { + if let Flotsam { + origin: Origin::New { ref mut fee, .. }, + .. + } = flotsam + { + *fee = (total_input_value - total_output_value) / u64::from(id_counter); + } + } + + floating_inscriptions.sort_by_key(|flotsam| flotsam.offset); + let mut inscriptions = floating_inscriptions.into_iter().peekable(); + + let mut range_to_vout = BTreeMap::new(); + let mut new_locations = Vec::new(); + let mut output_value = 0; + for (vout, tx_out) in tx.output.iter().enumerate() { + let end = output_value + tx_out.value; + + while let Some(flotsam) = inscriptions.peek() { + if flotsam.offset >= end { + break; + } + + let new_satpoint = SatPoint { + outpoint: OutPoint { + txid, + vout: vout.try_into().unwrap(), + }, + offset: flotsam.offset - output_value, + }; + + new_locations.push((new_satpoint, inscriptions.next().unwrap())); + } + + range_to_vout.insert((output_value, end), vout.try_into().unwrap()); + + output_value = end; + + self.tx_out_cache.insert( + OutPoint { + vout: vout.try_into().unwrap(), + txid, + }, + tx_out.clone(), + ); + } + + for (new_satpoint, mut flotsam) in new_locations.into_iter() { + let new_satpoint = match flotsam.origin { + Origin::New { + pointer: Some(pointer), + .. + } if pointer < output_value => { + match range_to_vout.iter().find_map(|((start, end), vout)| { + (pointer >= *start && pointer < *end).then(|| (vout, pointer - start)) + }) { + Some((vout, offset)) => { + flotsam.offset = pointer; + SatPoint { + outpoint: OutPoint { txid, vout: *vout }, + offset, + } + } + _ => new_satpoint, + } + } + _ => new_satpoint, + }; + + self.update_inscription_location(input_sat_ranges, flotsam, new_satpoint)?; + } + + for flotsam in inscriptions { + let new_satpoint = SatPoint { + outpoint: OutPoint::null(), + offset: u64::MAX, + }; + self.update_inscription_location(input_sat_ranges, flotsam, new_satpoint)?; + } + self.lost_sats += self.reward - output_value; + Ok(()) + } + + // write tx_out to outpoint_to_entry table + pub fn flush_cache(self) -> Result { + let start = Instant::now(); + let persist = self.new_outpoints.len(); + let mut entry = Vec::new(); + for outpoint in self.new_outpoints.into_iter() { + let tx_out = self.tx_out_cache.get(&outpoint).unwrap(); + tx_out.consensus_encode(&mut entry)?; + self + .processor + .outpoint_to_entry_insert(&outpoint.store(), entry.as_slice())?; + entry.clear(); + } + log::info!( + "flush cache, persist:{}, global:{} cost: {}ms", + persist, + self.tx_out_cache.len(), + start.elapsed().as_millis() + ); + Ok(()) + } + + fn calculate_sat( + input_sat_ranges: Option<&VecDeque<(u64, u64)>>, + input_offset: u64, + ) -> Option { + let input_sat_ranges = input_sat_ranges?; + + let mut offset = 0; + for (start, end) in input_sat_ranges { + let size = end - start; + if offset + size > input_offset { + let n = start + input_offset - offset; + return Some(Sat(n)); + } + offset += size; + } + + unreachable!() + } + + fn update_inscription_location( + &mut self, + input_sat_ranges: Option<&VecDeque<(u64, u64)>>, + flotsam: Flotsam, + new_satpoint: SatPoint, + ) -> Result { + let inscription_id = flotsam.inscription_id; + let (unbound, sequence_number) = match flotsam.origin { + Origin::Old => { + self + .processor + .satpoint_to_sequence_number_remove_all(&flotsam.old_satpoint.store())?; + + ( + false, + self + .processor + .id_to_sequence_number_get(&inscription_id.store())? + .unwrap(), + ) + } + Origin::New { + cursed, + fee, + hidden, + parent, + pointer: _, + reinscription, + unbound, + inscription: _, + vindicated, + } => { + let inscription_number = if cursed { + let number: i32 = self.cursed_inscription_count.try_into().unwrap(); + self.cursed_inscription_count += 1; + + // because cursed numbers start at -1 + -(number + 1) + } else { + let number: i32 = self.blessed_inscription_count.try_into().unwrap(); + self.blessed_inscription_count += 1; + + number + }; + + let sequence_number = self.next_sequence_number; + self.next_sequence_number += 1; + + self + .processor + .inscription_number_to_sequence_number_insert(inscription_number, sequence_number)?; + + let sat = if unbound { + None + } else { + Self::calculate_sat(input_sat_ranges, flotsam.offset) + }; + + let mut charms = 0; + + if cursed { + Charm::Cursed.set(&mut charms); + } + + if reinscription { + Charm::Reinscription.set(&mut charms); + } + + if let Some(sat) = sat { + if sat.nineball() { + Charm::Nineball.set(&mut charms); + } + + if sat.coin() { + Charm::Coin.set(&mut charms); + } + + match sat.rarity() { + Rarity::Common | Rarity::Mythic => {} + Rarity::Uncommon => Charm::Uncommon.set(&mut charms), + Rarity::Rare => Charm::Rare.set(&mut charms), + Rarity::Epic => Charm::Epic.set(&mut charms), + Rarity::Legendary => Charm::Legendary.set(&mut charms), + } + } + + if new_satpoint.outpoint == OutPoint::null() { + Charm::Lost.set(&mut charms); + } + + if unbound { + Charm::Unbound.set(&mut charms); + } + + if vindicated { + Charm::Vindicated.set(&mut charms); + } + + if let Some(Sat(n)) = sat { + self + .processor + .sat_to_sequence_number_insert(&n, &sequence_number)?; + } + + let parent = match parent { + Some(parent_id) => { + let parent_sequence_number = self + .processor + .id_to_sequence_number_get(&parent_id.store())? + .unwrap(); + self + .processor + .sequence_number_to_children_insert(parent_sequence_number, sequence_number)?; + + Some(parent_sequence_number) + } + None => None, + }; + + self.processor.sequence_number_to_entry_insert( + sequence_number, + &InscriptionEntry { + charms, + fee, + height: self.height, + id: inscription_id, + inscription_number, + parent, + sat, + sequence_number, + timestamp: self.timestamp, + } + .store(), + )?; + + self + .processor + .id_to_sequence_number_insert(&inscription_id.store(), sequence_number)?; + + if !hidden { + self + .processor + .home_inscriptions_insert(&sequence_number, inscription_id.store())?; + + if self.home_inscription_count == 100 { + self.processor.home_inscriptions_pop_first()?; + } else { + self.home_inscription_count += 1; + } + } + + (unbound, sequence_number) + } + }; + + let satpoint = if unbound { + let new_unbound_satpoint = SatPoint { + outpoint: unbound_outpoint(), + offset: self.unbound_inscriptions, + }; + self.unbound_inscriptions += 1; + new_unbound_satpoint.store() + } else { + new_satpoint.store() + }; + let mut is_transfer_to_coinbase = false; + let point = if new_satpoint.outpoint.is_null() && new_satpoint.offset == u64::MAX { + is_transfer_to_coinbase = true; + None + } else { + Some(new_satpoint) + }; + + self + .operations + .entry(flotsam.txid) + .or_default() + .push(InscriptionOp { + txid: flotsam.txid, + sequence_number, + inscription_number: self + .processor + .sequence_number_to_entry_get(sequence_number)? + .map(|entry| InscriptionEntry::load(entry).inscription_number), + inscription_id: flotsam.inscription_id, + action: match flotsam.origin { + Origin::Old => Action::Transfer, + Origin::New { + cursed, + fee: _, + hidden: _, + parent: _, + pointer: _, + reinscription: _, + unbound, + inscription, + vindicated, + } => Action::New { + cursed, + unbound, + vindicated, + inscription, + transfer_to_coin_base: is_transfer_to_coinbase, + }, + }, + old_satpoint: flotsam.old_satpoint, + new_satpoint: point, + }); + + self + .processor + .satpoint_to_sequence_number_insert(&satpoint, sequence_number)?; + self + .processor + .sequence_number_to_satpoint_insert(sequence_number, &satpoint)?; + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 18a615efb0..f5c2ad6b2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ clippy::cast_sign_loss )] +use log::info; use { self::{ arguments::Arguments, @@ -217,8 +218,9 @@ fn gracefully_shutdown_indexer() { pub fn main() { let args = Arguments::parse(); let log_dir = args.options.log_dir(); + let enable_pending = args.options.simulate_enable; logger::init(args.options.log_level(), log_dir).expect("initialize logger error:"); - + let (tx, rx) = async_channel::bounded(1); ctrlc::set_handler(move || { if SHUTTING_DOWN.fetch_or(true, atomic::Ordering::Relaxed) { process::exit(1); @@ -234,7 +236,9 @@ pub fn main() { }) .expect("Error setting handler"); - match Arguments::parse().run() { + let mut server = Arguments::parse(); + server.options.rx = Some(rx); + match server.run() { Err(err) => { eprintln!("error: {err}"); err @@ -249,6 +253,7 @@ pub fn main() { } gracefully_shutdown_indexer(); + wait_pending_shutdown(enable_pending, tx); process::exit(1); } @@ -256,4 +261,20 @@ pub fn main() { } gracefully_shutdown_indexer(); + wait_pending_shutdown(enable_pending, tx); +} + +fn wait_pending_shutdown( + enable_pending: bool, + tx: async_channel::Sender>, +) { + let (notify_tx, notify_rx) = tokio::sync::oneshot::channel(); + if enable_pending { + info!("pending enbale,begin to send exit signal to pending thread"); + let _ = tx.send_blocking(notify_tx); + let _ = notify_rx.blocking_recv(); + info!("pending thread shutdown successfully"); + } else { + info!("pending disable"); + } } diff --git a/src/okx/datastore/brc20/redb/mod.rs b/src/okx/datastore/brc20/redb/mod.rs index 6421d2b885..6b612bded0 100644 --- a/src/okx/datastore/brc20/redb/mod.rs +++ b/src/okx/datastore/brc20/redb/mod.rs @@ -3,7 +3,11 @@ pub mod table; use super::{LowerTick, ScriptKey, Tick}; use crate::inscriptions::InscriptionId; -fn script_tick_id_key(script: &ScriptKey, tick: &Tick, inscription_id: &InscriptionId) -> String { +pub fn script_tick_id_key( + script: &ScriptKey, + tick: &Tick, + inscription_id: &InscriptionId, +) -> String { format!( "{}_{}_{}", script, @@ -21,7 +25,7 @@ fn max_script_tick_id_key(script: &ScriptKey, tick: &Tick) -> String { format!("{}_{}_g", script, tick.to_lowercase().hex()) } -fn script_tick_key(script: &ScriptKey, tick: &Tick) -> String { +pub fn script_tick_key(script: &ScriptKey, tick: &Tick) -> String { format!("{}_{}", script, tick.to_lowercase().hex()) } diff --git a/src/okx/datastore/brc20/tick.rs b/src/okx/datastore/brc20/tick.rs index ed6533f055..b38680943a 100644 --- a/src/okx/datastore/brc20/tick.rs +++ b/src/okx/datastore/brc20/tick.rs @@ -4,7 +4,7 @@ use std::{fmt::Formatter, str::FromStr}; pub const TICK_BYTE_COUNT: usize = 4; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Tick([u8; TICK_BYTE_COUNT]); impl FromStr for Tick { diff --git a/src/okx/datastore/cache.rs b/src/okx/datastore/cache.rs new file mode 100644 index 0000000000..68331165ea --- /dev/null +++ b/src/okx/datastore/cache.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; + +#[allow(non_camel_case_types)] +#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub enum CacheTableIndex { + TXID_TO_INSCRIPTION_RECEIPTS, + SEQUENCE_NUMBER_TO_SATPOINT, + SAT_TO_SEQUENCE_NUMBER, + HOME_INSCRIPTIONS, + INSCRIPTION_ID_TO_SEQUENCE_NUMBER, + SEQUENCE_NUMBER_TO_CHILDREN, + SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY, + INSCRIPTION_NUMBER_TO_SEQUENCE_NUMBER, + OUTPOINT_TO_ENTRY, + BRC20_BALANCES, + BRC20_TOKEN, + BRC20_EVENTS, + BRC20_TRANSFERABLELOG, + BRC20_INSCRIBE_TRANSFER, + ORD_TX_TO_OPERATIONS, + COLLECTIONS_KEY_TO_INSCRIPTION_ID, + COLLECTIONS_INSCRIPTION_ID_TO_KINDS, +} diff --git a/src/okx/datastore/mod.rs b/src/okx/datastore/mod.rs index d17e469105..8badc6d812 100644 --- a/src/okx/datastore/mod.rs +++ b/src/okx/datastore/mod.rs @@ -1,4 +1,5 @@ pub mod brc20; +pub mod cache; pub mod ord; mod script_key; diff --git a/src/okx/datastore/ord/operation.rs b/src/okx/datastore/ord/operation.rs index 93ce131d3c..7fa855f41c 100644 --- a/src/okx/datastore/ord/operation.rs +++ b/src/okx/datastore/ord/operation.rs @@ -25,6 +25,8 @@ pub enum Action { unbound: bool, inscription: Inscription, #[serde(default)] + transfer_to_coin_base: bool, + #[serde(default)] vindicated: bool, }, Transfer, @@ -79,6 +81,7 @@ mod tests { unbound: true, vindicated: false, inscription: inscription("text/plain;charset=utf-8", "foobar"), + transfer_to_coin_base: false, } ); @@ -115,6 +118,7 @@ mod tests { unbound: true, vindicated: false, inscription: inscription("text/plain;charset=utf-8", "foobar"), + transfer_to_coin_base: false, }, sequence_number: 100, inscription_number: Some(100), diff --git a/src/okx/datastore/ord/redb/table.rs b/src/okx/datastore/ord/redb/table.rs index 740b5c3e91..f5fdbbc69c 100644 --- a/src/okx/datastore/ord/redb/table.rs +++ b/src/okx/datastore/ord/redb/table.rs @@ -128,6 +128,7 @@ mod tests { unbound: false, vindicated: false, inscription: inscription("text/plain;charset=utf-8", "foobar"), + transfer_to_coin_base: false, }, sequence_number: 100, inscription_number: Some(100), diff --git a/src/okx/lru.rs b/src/okx/lru.rs index f2ebac3c89..2c443b289c 100644 --- a/src/okx/lru.rs +++ b/src/okx/lru.rs @@ -8,7 +8,6 @@ pub struct SimpleLru { new_cache: HashMap, old_cache: HashMap, } - impl SimpleLru where K: Eq + Hash, diff --git a/src/okx/protocol/brc20/msg_executor.rs b/src/okx/protocol/brc20/msg_executor.rs index a84d300d0f..487588f241 100644 --- a/src/okx/protocol/brc20/msg_executor.rs +++ b/src/okx/protocol/brc20/msg_executor.rs @@ -3,9 +3,7 @@ use super::{ *, }; -use crate::okx::datastore::brc20::{Brc20Reader, Brc20ReaderWriter}; -use crate::okx::datastore::ord::OrdReader; -use crate::okx::protocol::context::Context; +use crate::okx::protocol::ContextTrait; use crate::{ okx::{ datastore::brc20::{ @@ -34,18 +32,30 @@ pub struct ExecutionMessage { } impl ExecutionMessage { - pub fn from_message(context: &mut Context, msg: &Message, network: Network) -> Result { + pub fn from_message( + context: &mut T, + msg: &Message, + network: Network, + ) -> Result { Ok(Self { txid: msg.txid, inscription_id: msg.inscription_id, - inscription_number: context.get_inscription_number_by_sequence_number(msg.sequence_number)?, + inscription_number: context + .get_inscription_number_by_sequence_number(msg.sequence_number) + .map_err(|e| anyhow!("failed to get inscription number from state! error: {e}"))?, old_satpoint: msg.old_satpoint, new_satpoint: msg .new_satpoint .ok_or(anyhow!("new satpoint cannot be None"))?, - from: context.get_script_key_on_satpoint(&msg.old_satpoint, network)?, + from: context + .get_script_key_on_satpoint(&msg.old_satpoint, network) + .map_err(|e| anyhow!("failed to get script key from state! error: {e}"))?, to: if msg.sat_in_outputs { - Some(context.get_script_key_on_satpoint(msg.new_satpoint.as_ref().unwrap(), network)?) + Some( + context + .get_script_key_on_satpoint(msg.new_satpoint.as_ref().unwrap(), network) + .map_err(|e| anyhow!("failed to get script key from state! error: {e}"))?, + ) } else { None }, @@ -54,7 +64,7 @@ impl ExecutionMessage { } } -pub fn execute(context: &mut Context, msg: &ExecutionMessage) -> Result { +pub fn execute(context: &mut T, msg: &ExecutionMessage) -> Result { log::debug!("BRC20 execute message: {:?}", msg); let event = match &msg.op { Operation::Deploy(deploy) => process_deploy(context, msg, deploy.clone()), @@ -85,8 +95,8 @@ pub fn execute(context: &mut Context, msg: &ExecutionMessage) -> Result Ok(receipt) } -fn process_deploy( - context: &mut Context, +fn process_deploy( + context: &mut T, msg: &ExecutionMessage, deploy: Deploy, ) -> Result { @@ -95,7 +105,10 @@ fn process_deploy( let tick = deploy.tick.parse::()?; - if let Some(stored_tick_info) = context.get_token_info(&tick).map_err(Error::LedgerError)? { + if let Some(stored_tick_info) = context + .get_token_info(&tick) + .map_err(|e| Error::LedgerError(anyhow!("failed to get token info from database: {}", e)))? + { return Err(Error::BRC20Error(BRC20Error::DuplicateTick( stored_tick_info.tick.to_string(), ))); @@ -143,13 +156,13 @@ fn process_deploy( limit_per_mint: limit, minted: 0u128, deploy_by: to_script_key, - deployed_number: context.chain.blockheight, - latest_mint_number: context.chain.blockheight, - deployed_timestamp: context.chain.blocktime, + deployed_number: context.block_height(), + latest_mint_number: context.block_height(), + deployed_timestamp: context.block_time(), }; context .insert_token_info(&tick, &new_info) - .map_err(Error::LedgerError)?; + .map_err(|e| Error::LedgerError(anyhow!("failed to insert token info to database: {}", e)))?; Ok(Event::Deploy(DeployEvent { supply, @@ -159,7 +172,11 @@ fn process_deploy( })) } -fn process_mint(context: &mut Context, msg: &ExecutionMessage, mint: Mint) -> Result { +fn process_mint( + context: &mut T, + msg: &ExecutionMessage, + mint: Mint, +) -> Result { // ignore inscribe inscription to coinbase. let to_script_key = msg.to.clone().ok_or(BRC20Error::InscribeToCoinbase)?; @@ -167,7 +184,7 @@ fn process_mint(context: &mut Context, msg: &ExecutionMessage, mint: Mint) -> Re let token_info = context .get_token_info(&tick) - .map_err(Error::LedgerError)? + .map_err(|e| Error::LedgerError(anyhow!("failed to get token info from database: {}", e)))? .ok_or(BRC20Error::TickNotFound(tick.to_string()))?; let base = BIGDECIMAL_TEN.checked_powu(u64::from(token_info.decimal))?; @@ -214,7 +231,7 @@ fn process_mint(context: &mut Context, msg: &ExecutionMessage, mint: Mint) -> Re // get or initialize user balance. let mut balance = context .get_balance(&to_script_key, &tick) - .map_err(Error::LedgerError)? + .map_err(|e| Error::LedgerError(anyhow!("failed to get balance from database: {}", e)))? .map_or(Balance::new(&tick), |v| v); // add amount to available balance. @@ -225,13 +242,13 @@ fn process_mint(context: &mut Context, msg: &ExecutionMessage, mint: Mint) -> Re // store to database. context .update_token_balance(&to_script_key, balance) - .map_err(Error::LedgerError)?; + .map_err(|e| Error::LedgerError(anyhow!("failed to update balance to database: {}", e)))?; // update token minted. let minted = minted.checked_add(&amt)?.checked_to_u128()?; context - .update_mint_token_info(&tick, minted, context.chain.blockheight) - .map_err(Error::LedgerError)?; + .update_mint_token_info(&tick, minted, context.block_height()) + .map_err(|e| Error::LedgerError(anyhow!("failed to update minted to database: {}", e)))?; Ok(Event::Mint(MintEvent { tick: token_info.tick, @@ -240,8 +257,8 @@ fn process_mint(context: &mut Context, msg: &ExecutionMessage, mint: Mint) -> Re })) } -fn process_inscribe_transfer( - context: &mut Context, +fn process_inscribe_transfer( + context: &mut T, msg: &ExecutionMessage, transfer: Transfer, ) -> Result { @@ -252,7 +269,7 @@ fn process_inscribe_transfer( let token_info = context .get_token_info(&tick) - .map_err(Error::LedgerError)? + .map_err(|e| Error::LedgerError(anyhow!("failed to get token info from database: {}", e)))? .ok_or(BRC20Error::TickNotFound(tick.to_string()))?; let base = BIGDECIMAL_TEN.checked_powu(u64::from(token_info.decimal))?; @@ -274,7 +291,7 @@ fn process_inscribe_transfer( let mut balance = context .get_balance(&to_script_key, &tick) - .map_err(Error::LedgerError)? + .map_err(|e| Error::LedgerError(anyhow!("failed to get balance from database: {}", e)))? .map_or(Balance::new(&tick), |v| v); let overall = Into::::into(balance.overall_balance); @@ -292,7 +309,7 @@ fn process_inscribe_transfer( let amt = amt.checked_to_u128()?; context .update_token_balance(&to_script_key, balance) - .map_err(Error::LedgerError)?; + .map_err(|e| Error::LedgerError(anyhow!("failed to update balance to database: {}", e)))?; let inscription = TransferableLog { inscription_id: msg.inscription_id, @@ -304,7 +321,7 @@ fn process_inscribe_transfer( context .insert_transferable(&inscription.owner, &tick, &inscription) - .map_err(Error::LedgerError)?; + .map_err(|e| Error::LedgerError(anyhow!("failed to insert transferable to database: {}", e)))?; context .insert_inscribe_transfer_inscription( @@ -314,7 +331,12 @@ fn process_inscribe_transfer( amt, }, ) - .map_err(Error::LedgerError)?; + .map_err(|e| { + Error::LedgerError(anyhow!( + "failed to insert inscribe transfer inscription to database: {}", + e + )) + })?; Ok(Event::InscribeTransfer(InscripbeTransferEvent { tick: inscription.tick, @@ -322,10 +344,13 @@ fn process_inscribe_transfer( })) } -fn process_transfer(context: &mut Context, msg: &ExecutionMessage) -> Result { +fn process_transfer( + context: &mut T, + msg: &ExecutionMessage, +) -> Result { let transferable = context .get_transferable_by_id(&msg.from, &msg.inscription_id) - .map_err(Error::LedgerError)? + .map_err(|e| Error::LedgerError(anyhow!("failed to get transferable from database: {}", e)))? .ok_or(BRC20Error::TransferableNotFound(msg.inscription_id))?; let amt = Into::::into(transferable.amount); @@ -340,13 +365,13 @@ fn process_transfer(context: &mut Context, msg: &ExecutionMessage) -> Result::into(from_balance.overall_balance); @@ -360,7 +385,7 @@ fn process_transfer(context: &mut Context, msg: &ExecutionMessage) -> Result Result::into(to_balance.overall_balance); @@ -384,15 +409,25 @@ fn process_transfer(context: &mut Context, msg: &ExecutionMessage) -> Result( @@ -19,7 +17,7 @@ impl Message { op: &InscriptionOp, ) -> Result> where - T: ReadableTable, + T: Brc20ReaderWriter, { log::debug!("BRC20 resolving the message from {:?}", op); let sat_in_outputs = op @@ -34,6 +32,7 @@ impl Message { unbound: false, vindicated: false, inscription: _, + .. } if sat_in_outputs => { match deserialize_brc20_operation( new_inscriptions @@ -47,7 +46,7 @@ impl Message { } // Transfered inscription operation. // Attempt to retrieve the `InscribeTransfer` Inscription information from the data store of BRC20S. - Action::Transfer => match get_inscribe_transfer_inscription(table, &op.inscription_id) { + Action::Transfer => match table.get_inscribe_transfer_inscription(&op.inscription_id) { // Ignore non-first transfer operations. Ok(Some(transfer_info)) if op.inscription_id.txid == op.old_satpoint.outpoint.txid => { Operation::Transfer(Transfer { @@ -80,10 +79,17 @@ impl Message { #[cfg(test)] mod tests { use super::*; - use crate::index::BRC20_INSCRIBE_TRANSFER; + use crate::index::{ + BRC20_BALANCES, BRC20_EVENTS, BRC20_INSCRIBE_TRANSFER, BRC20_TOKEN, BRC20_TRANSFERABLELOG, + COLLECTIONS_INSCRIPTION_ID_TO_KINDS, COLLECTIONS_KEY_TO_INSCRIPTION_ID, ORD_TX_TO_OPERATIONS, + OUTPOINT_TO_ENTRY, SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY, + }; use crate::okx::datastore::brc20::redb::table::insert_inscribe_transfer_inscription; use crate::okx::datastore::brc20::{Tick, TransferInfo}; - use bitcoin::OutPoint; + use crate::okx::lru::SimpleLru; + use crate::okx::protocol::context::Context; + use crate::okx::protocol::BlockContext; + use bitcoin::{Network, OutPoint}; use redb::Database; use std::str::FromStr; use tempfile::NamedTempFile; @@ -105,6 +111,7 @@ mod tests { cursed: false, unbound: false, inscription: inscriptions.first().unwrap().clone(), + transfer_to_coin_base: false, vindicated: false, }, sequence_number: 1, @@ -161,12 +168,36 @@ mod tests { let db_file = NamedTempFile::new().unwrap(); let db = Database::create(db_file.path()).unwrap(); let wtx = db.begin_write().unwrap(); - let table = wtx.open_table(BRC20_INSCRIBE_TRANSFER).unwrap(); - + let context = Context { + chain: BlockContext { + network: Network::Regtest, + blockheight: 0, + blocktime: 0, + }, + tx_out_cache: &mut SimpleLru::new(10), + hit: 0, + miss: 0, + ORD_TX_TO_OPERATIONS: &mut wtx.open_table(ORD_TX_TO_OPERATIONS).unwrap(), + COLLECTIONS_KEY_TO_INSCRIPTION_ID: &mut wtx + .open_table(COLLECTIONS_KEY_TO_INSCRIPTION_ID) + .unwrap(), + COLLECTIONS_INSCRIPTION_ID_TO_KINDS: &mut wtx + .open_table(COLLECTIONS_INSCRIPTION_ID_TO_KINDS) + .unwrap(), + SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY: &mut wtx + .open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY) + .unwrap(), + OUTPOINT_TO_ENTRY: &mut wtx.open_table(OUTPOINT_TO_ENTRY).unwrap(), + BRC20_BALANCES: &mut wtx.open_table(BRC20_BALANCES).unwrap(), + BRC20_TOKEN: &mut wtx.open_table(BRC20_TOKEN).unwrap(), + BRC20_EVENTS: &mut wtx.open_table(BRC20_EVENTS).unwrap(), + BRC20_TRANSFERABLELOG: &mut wtx.open_table(BRC20_TRANSFERABLELOG).unwrap(), + BRC20_INSCRIBE_TRANSFER: &mut wtx.open_table(BRC20_INSCRIBE_TRANSFER).unwrap(), + }; let (inscriptions, op) = create_inscribe_operation( r#"{ "p": "brc-20s","op": "deploy", "tick": "ordi", "max": "1000", "lim": "10" }"#, ); - assert_matches!(Message::resolve(&table, &inscriptions, &op), Ok(None)); + assert_matches!(Message::resolve(&context, &inscriptions, &op), Ok(None)); } #[test] @@ -174,7 +205,32 @@ mod tests { let db_file = NamedTempFile::new().unwrap(); let db = Database::create(db_file.path()).unwrap(); let wtx = db.begin_write().unwrap(); - let table = wtx.open_table(BRC20_INSCRIBE_TRANSFER).unwrap(); + let context = Context { + chain: BlockContext { + network: Network::Regtest, + blockheight: 0, + blocktime: 0, + }, + tx_out_cache: &mut SimpleLru::new(10), + hit: 0, + miss: 0, + ORD_TX_TO_OPERATIONS: &mut wtx.open_table(ORD_TX_TO_OPERATIONS).unwrap(), + COLLECTIONS_KEY_TO_INSCRIPTION_ID: &mut wtx + .open_table(COLLECTIONS_KEY_TO_INSCRIPTION_ID) + .unwrap(), + COLLECTIONS_INSCRIPTION_ID_TO_KINDS: &mut wtx + .open_table(COLLECTIONS_INSCRIPTION_ID_TO_KINDS) + .unwrap(), + SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY: &mut wtx + .open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY) + .unwrap(), + OUTPOINT_TO_ENTRY: &mut wtx.open_table(OUTPOINT_TO_ENTRY).unwrap(), + BRC20_BALANCES: &mut wtx.open_table(BRC20_BALANCES).unwrap(), + BRC20_TOKEN: &mut wtx.open_table(BRC20_TOKEN).unwrap(), + BRC20_EVENTS: &mut wtx.open_table(BRC20_EVENTS).unwrap(), + BRC20_TRANSFERABLELOG: &mut wtx.open_table(BRC20_TRANSFERABLELOG).unwrap(), + BRC20_INSCRIBE_TRANSFER: &mut wtx.open_table(BRC20_INSCRIBE_TRANSFER).unwrap(), + }; let (inscriptions, op) = create_inscribe_operation( r#"{ "p": "brc-20","op": "deploy", "tick": "ordi", "max": "1000", "lim": "10" }"#, @@ -184,32 +240,35 @@ mod tests { cursed: true, unbound: false, inscription: inscriptions.first().unwrap().clone(), + transfer_to_coin_base: false, vindicated: false, }, ..op }; - assert_matches!(Message::resolve(&table, &inscriptions, &op), Ok(None)); + assert_matches!(Message::resolve(&context, &inscriptions, &op), Ok(None)); let op2 = InscriptionOp { action: Action::New { cursed: false, unbound: true, inscription: inscriptions.first().unwrap().clone(), + transfer_to_coin_base: false, vindicated: false, }, ..op }; - assert_matches!(Message::resolve(&table, &inscriptions, &op2), Ok(None)); + assert_matches!(Message::resolve(&context, &inscriptions, &op2), Ok(None)); let op3 = InscriptionOp { action: Action::New { cursed: true, unbound: true, inscription: inscriptions.first().unwrap().clone(), + transfer_to_coin_base: false, vindicated: false, }, ..op }; - assert_matches!(Message::resolve(&table, &inscriptions, &op3), Ok(None)); + assert_matches!(Message::resolve(&context, &inscriptions, &op3), Ok(None)); } #[test] @@ -217,7 +276,32 @@ mod tests { let db_file = NamedTempFile::new().unwrap(); let db = Database::create(db_file.path()).unwrap(); let wtx = db.begin_write().unwrap(); - let table = wtx.open_table(BRC20_INSCRIBE_TRANSFER).unwrap(); + let context = Context { + chain: BlockContext { + network: Network::Regtest, + blockheight: 0, + blocktime: 0, + }, + tx_out_cache: &mut SimpleLru::new(10), + hit: 0, + miss: 0, + ORD_TX_TO_OPERATIONS: &mut wtx.open_table(ORD_TX_TO_OPERATIONS).unwrap(), + COLLECTIONS_KEY_TO_INSCRIPTION_ID: &mut wtx + .open_table(COLLECTIONS_KEY_TO_INSCRIPTION_ID) + .unwrap(), + COLLECTIONS_INSCRIPTION_ID_TO_KINDS: &mut wtx + .open_table(COLLECTIONS_INSCRIPTION_ID_TO_KINDS) + .unwrap(), + SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY: &mut wtx + .open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY) + .unwrap(), + OUTPOINT_TO_ENTRY: &mut wtx.open_table(OUTPOINT_TO_ENTRY).unwrap(), + BRC20_BALANCES: &mut wtx.open_table(BRC20_BALANCES).unwrap(), + BRC20_TOKEN: &mut wtx.open_table(BRC20_TOKEN).unwrap(), + BRC20_EVENTS: &mut wtx.open_table(BRC20_EVENTS).unwrap(), + BRC20_TRANSFERABLELOG: &mut wtx.open_table(BRC20_TRANSFERABLELOG).unwrap(), + BRC20_INSCRIBE_TRANSFER: &mut wtx.open_table(BRC20_INSCRIBE_TRANSFER).unwrap(), + }; let (inscriptions, op) = create_inscribe_operation( r#"{ "p": "brc-20","op": "deploy", "tick": "ordi", "max": "1000", "lim": "10" }"#, @@ -237,7 +321,7 @@ mod tests { sat_in_outputs: true, }; assert_matches!( - Message::resolve(&table, &inscriptions, &op), + Message::resolve(&context, &inscriptions, &op), Ok(Some(_result_msg)) ); } @@ -247,11 +331,36 @@ mod tests { let db_file = NamedTempFile::new().unwrap(); let db = Database::create(db_file.path()).unwrap(); let wtx = db.begin_write().unwrap(); - let table = wtx.open_table(BRC20_INSCRIBE_TRANSFER).unwrap(); + let context = Context { + chain: BlockContext { + network: Network::Regtest, + blockheight: 0, + blocktime: 0, + }, + tx_out_cache: &mut SimpleLru::new(10), + hit: 0, + miss: 0, + ORD_TX_TO_OPERATIONS: &mut wtx.open_table(ORD_TX_TO_OPERATIONS).unwrap(), + COLLECTIONS_KEY_TO_INSCRIPTION_ID: &mut wtx + .open_table(COLLECTIONS_KEY_TO_INSCRIPTION_ID) + .unwrap(), + COLLECTIONS_INSCRIPTION_ID_TO_KINDS: &mut wtx + .open_table(COLLECTIONS_INSCRIPTION_ID_TO_KINDS) + .unwrap(), + SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY: &mut wtx + .open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY) + .unwrap(), + OUTPOINT_TO_ENTRY: &mut wtx.open_table(OUTPOINT_TO_ENTRY).unwrap(), + BRC20_BALANCES: &mut wtx.open_table(BRC20_BALANCES).unwrap(), + BRC20_TOKEN: &mut wtx.open_table(BRC20_TOKEN).unwrap(), + BRC20_EVENTS: &mut wtx.open_table(BRC20_EVENTS).unwrap(), + BRC20_TRANSFERABLELOG: &mut wtx.open_table(BRC20_TRANSFERABLELOG).unwrap(), + BRC20_INSCRIBE_TRANSFER: &mut wtx.open_table(BRC20_INSCRIBE_TRANSFER).unwrap(), + }; // inscribe transfer not found let op = create_transfer_operation(); - assert_matches!(Message::resolve(&table, &[], &op), Ok(None)); + assert_matches!(Message::resolve(&context, &[], &op), Ok(None)); // non-first transfer operations. let op1 = InscriptionOp { @@ -265,7 +374,7 @@ mod tests { }, ..op }; - assert_matches!(Message::resolve(&table, &[], &op1), Ok(None)); + assert_matches!(Message::resolve(&context, &[], &op1), Ok(None)); } #[test] @@ -300,6 +409,33 @@ mod tests { sat_in_outputs: true, }; - assert_matches!(Message::resolve(&table, &[], &op), Ok(Some(_msg))); + let context = Context { + chain: BlockContext { + network: Network::Regtest, + blockheight: 0, + blocktime: 0, + }, + tx_out_cache: &mut SimpleLru::new(10), + hit: 0, + miss: 0, + ORD_TX_TO_OPERATIONS: &mut wtx.open_table(ORD_TX_TO_OPERATIONS).unwrap(), + COLLECTIONS_KEY_TO_INSCRIPTION_ID: &mut wtx + .open_table(COLLECTIONS_KEY_TO_INSCRIPTION_ID) + .unwrap(), + COLLECTIONS_INSCRIPTION_ID_TO_KINDS: &mut wtx + .open_table(COLLECTIONS_INSCRIPTION_ID_TO_KINDS) + .unwrap(), + SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY: &mut wtx + .open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY) + .unwrap(), + OUTPOINT_TO_ENTRY: &mut wtx.open_table(OUTPOINT_TO_ENTRY).unwrap(), + BRC20_BALANCES: &mut wtx.open_table(BRC20_BALANCES).unwrap(), + BRC20_TOKEN: &mut wtx.open_table(BRC20_TOKEN).unwrap(), + BRC20_EVENTS: &mut wtx.open_table(BRC20_EVENTS).unwrap(), + BRC20_TRANSFERABLELOG: &mut wtx.open_table(BRC20_TRANSFERABLELOG).unwrap(), + BRC20_INSCRIBE_TRANSFER: &mut table, + }; + + assert_matches!(Message::resolve(&context, &[], &op), Ok(Some(_msg))); } } diff --git a/src/okx/protocol/brc20/operation/mod.rs b/src/okx/protocol/brc20/operation/mod.rs index da558eaf71..47c98d6dba 100644 --- a/src/okx/protocol/brc20/operation/mod.rs +++ b/src/okx/protocol/brc20/operation/mod.rs @@ -212,7 +212,8 @@ mod tests { cursed: false, unbound: false, vindicated: false, - inscription: inscription.clone() + inscription: inscription.clone(), + transfer_to_coin_base: false }, ) .unwrap(), @@ -235,7 +236,8 @@ mod tests { cursed: false, unbound: false, vindicated: false, - inscription: inscription.clone() + inscription: inscription.clone(), + transfer_to_coin_base: false, }, ) .unwrap(), @@ -256,7 +258,8 @@ mod tests { cursed: false, unbound: false, vindicated: false, - inscription: inscription.clone() + inscription: inscription.clone(), + transfer_to_coin_base: false, }, ) .unwrap(), diff --git a/src/okx/protocol/context.rs b/src/okx/protocol/context.rs index 1d88af5478..8debdd5be9 100644 --- a/src/okx/protocol/context.rs +++ b/src/okx/protocol/context.rs @@ -21,7 +21,7 @@ use crate::okx::datastore::ord::redb::table::{ use crate::okx::datastore::ord::{InscriptionOp, OrdReader, OrdReaderWriter}; use crate::okx::datastore::ScriptKey; use crate::okx::lru::SimpleLru; -use crate::okx::protocol::BlockContext; +use crate::okx::protocol::{BlockContext, ContextTrait}; use crate::SatPoint; use anyhow::anyhow; use bitcoin::{Network, OutPoint, TxOut, Txid}; @@ -271,3 +271,17 @@ impl<'a, 'db, 'txn> Brc20ReaderWriter for Context<'a, 'db, 'txn> { remove_inscribe_transfer_inscription(self.BRC20_INSCRIBE_TRANSFER, inscription_id) } } + +impl<'a, 'db, 'txn> ContextTrait for Context<'a, 'db, 'txn> { + fn block_height(&self) -> u32 { + self.chain.blockheight + } + + fn network(&self) -> Network { + self.chain.network + } + + fn block_time(&self) -> u32 { + self.chain.blocktime + } +} diff --git a/src/okx/protocol/execute_manager.rs b/src/okx/protocol/execute_manager.rs index 70d708e343..d9ba27d0c9 100644 --- a/src/okx/protocol/execute_manager.rs +++ b/src/okx/protocol/execute_manager.rs @@ -1,5 +1,3 @@ -use crate::okx::datastore::brc20::Brc20ReaderWriter; -use crate::okx::protocol::context::Context; use anyhow::anyhow; use bitcoin::Txid; use { @@ -14,14 +12,19 @@ impl CallManager { Self {} } - pub fn execute_message(&self, context: &mut Context, txid: &Txid, msgs: &[Message]) -> Result { + pub fn execute_message( + &self, + context: &mut T, + txid: &Txid, + msgs: &[Message], + ) -> Result { let mut receipts = vec![]; // execute message for msg in msgs { match msg { Message::BRC20(brc_msg) => { let msg = - brc20_proto::ExecutionMessage::from_message(context, brc_msg, context.chain.network)?; + brc20_proto::ExecutionMessage::from_message(context, brc_msg, context.network())?; let receipt = brc20_proto::execute(context, &msg)?; receipts.push(receipt); } diff --git a/src/okx/protocol/mod.rs b/src/okx/protocol/mod.rs index c7dc147bdc..3f42cc2a96 100644 --- a/src/okx/protocol/mod.rs +++ b/src/okx/protocol/mod.rs @@ -5,9 +5,13 @@ pub(crate) mod message; pub(crate) mod ord; pub(crate) mod protocol_manager; pub(crate) mod resolve_manager; +pub mod simulate; +pub mod trace; pub use self::protocol_manager::ProtocolManager; +use crate::okx::datastore::brc20::Brc20ReaderWriter; +use crate::okx::datastore::ord::OrdReaderWriter; use { self::{execute_manager::CallManager, message::Message, resolve_manager::MsgResolveManager}, crate::Options, @@ -20,6 +24,7 @@ pub struct BlockContext { pub blockheight: u32, pub blocktime: u32, } + #[derive(Debug, Clone, Copy)] pub struct ProtocolConfig { first_inscription_height: u32, @@ -42,3 +47,9 @@ impl ProtocolConfig { } } } + +pub trait ContextTrait: Brc20ReaderWriter + OrdReaderWriter { + fn block_height(&self) -> u32; + fn network(&self) -> Network; + fn block_time(&self) -> u32; +} diff --git a/src/okx/protocol/ord/bitmap.rs b/src/okx/protocol/ord/bitmap.rs index a436cab273..a424cec850 100644 --- a/src/okx/protocol/ord/bitmap.rs +++ b/src/okx/protocol/ord/bitmap.rs @@ -1,5 +1,4 @@ -use crate::okx::datastore::ord::{OrdReader, OrdReaderWriter}; -use crate::okx::protocol::context::Context; +use crate::okx::protocol::ContextTrait; use { crate::{ okx::datastore::ord::{ @@ -14,8 +13,8 @@ use { std::collections::HashMap, }; -pub fn index_bitmap( - context: &mut Context, +pub fn index_bitmap( + context: &mut T, operations: &HashMap>, ) -> Result { let mut count = 0; @@ -40,13 +39,22 @@ pub fn index_bitmap( unbound: _, vindicated: _, inscription, + transfer_to_coin_base: _, } => { if let Some((inscription_id, district)) = index_district(context, inscription, op.inscription_id)? { let key = district.to_collection_key(); - context.set_inscription_by_collection_key(&key, &inscription_id)?; - context.set_inscription_attributes(&inscription_id, &[CollectionKind::BitMap])?; + context.set_inscription_by_collection_key(&key, &inscription_id).map_err(|e|{ + anyhow!("failed to set inscription by collection key! key: {key} inscription_id: {inscription_id} error: {e}") + })?; + context + .set_inscription_attributes(&inscription_id, &[CollectionKind::BitMap]) + .map_err(|e| { + anyhow!( + "failed to set inscription attributes! inscription_id: {inscription_id} error: {e}" + ) + })?; count += 1; } @@ -57,14 +65,14 @@ pub fn index_bitmap( Ok(count) } -fn index_district( - context: &mut Context, +fn index_district( + context: &mut T, inscription: Inscription, inscription_id: InscriptionId, ) -> Result> { if let Some(content) = inscription.body() { if let Ok(district) = District::parse(content) { - if district.number > context.chain.blockheight { + if district.number > context.block_height() { return Ok(None); } let collection_key = district.to_collection_key(); diff --git a/src/okx/protocol/protocol_manager.rs b/src/okx/protocol/protocol_manager.rs index b1541088bd..7331895968 100644 --- a/src/okx/protocol/protocol_manager.rs +++ b/src/okx/protocol/protocol_manager.rs @@ -1,5 +1,4 @@ -use crate::okx::datastore::ord::OrdReaderWriter; -use crate::okx::protocol::context::Context; +use anyhow::anyhow; use { super::*, crate::{ @@ -27,9 +26,9 @@ impl ProtocolManager { } } - pub(crate) fn index_block( + pub(crate) fn index_block( &self, - context: &mut Context, + context: &mut T, block: &BlockData, operations: HashMap>, ) -> Result { @@ -54,10 +53,12 @@ impl ProtocolManager { if let Some(tx_operations) = operations.get(txid) { // save all transaction operations to ord database. if self.config.enable_ord_receipts - && context.chain.blockheight >= self.config.first_inscription_height + && context.block_height() >= self.config.first_inscription_height { let start = Instant::now(); - context.save_transaction_operations(txid, tx_operations)?; + context + .save_transaction_operations(txid, tx_operations) + .map_err(|e| anyhow!("failed to save transaction operations! error: {}", e))?; inscriptions_size += tx_operations.len(); cost1 += start.elapsed().as_micros(); } @@ -85,7 +86,7 @@ impl ProtocolManager { log::info!( "Protocol Manager indexed block {} with ord inscriptions {}, messages {}, bitmap {} in {} ms, {}/{}/{}/{}", - context.chain.blockheight, + context.block_height(), inscriptions_size, messages_size, bitmap_count, diff --git a/src/okx/protocol/resolve_manager.rs b/src/okx/protocol/resolve_manager.rs index 2a9d338440..591b1a1593 100644 --- a/src/okx/protocol/resolve_manager.rs +++ b/src/okx/protocol/resolve_manager.rs @@ -1,5 +1,4 @@ use crate::inscriptions::ParsedEnvelope; -use crate::okx::protocol::context::Context; use { super::*, crate::{ @@ -18,9 +17,9 @@ impl MsgResolveManager { Self { config } } - pub fn resolve_message( + pub fn resolve_message( &self, - context: &Context, + context: &T, tx: &Transaction, operations: &[InscriptionOp], ) -> Result> { @@ -50,14 +49,10 @@ impl MsgResolveManager { if self .config .first_brc20_height - .map(|height| context.chain.blockheight >= height) + .map(|height| context.block_height() >= height) .unwrap_or(false) { - if let Some(msg) = brc20::Message::resolve( - context.BRC20_INSCRIBE_TRANSFER, - &new_inscriptions, - operation, - )? { + if let Some(msg) = brc20::Message::resolve(context, &new_inscriptions, operation)? { log::debug!( "BRC20 resolved the message from {:?}, msg {:?}", operation, diff --git a/src/okx/protocol/simulate.rs b/src/okx/protocol/simulate.rs new file mode 100644 index 0000000000..52641999b3 --- /dev/null +++ b/src/okx/protocol/simulate.rs @@ -0,0 +1,554 @@ +use crate::index::entry::Entry; +use crate::index::simulator::processor::IndexWrapper; +use crate::index::{ + InscriptionEntryValue, InscriptionIdValue, OutPointValue, TxidValue, BRC20_BALANCES, + BRC20_EVENTS, BRC20_INSCRIBE_TRANSFER, BRC20_TOKEN, BRC20_TRANSFERABLELOG, + COLLECTIONS_INSCRIPTION_ID_TO_KINDS, COLLECTIONS_KEY_TO_INSCRIPTION_ID, OUTPOINT_TO_ENTRY, + SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY, +}; +use crate::okx::datastore::brc20::redb::table::{ + get_balance, get_balances, get_inscribe_transfer_inscription, get_token_info, get_tokens_info, + get_transaction_receipts, get_transferable, get_transferable_by_id, get_transferable_by_tick, + insert_inscribe_transfer_inscription, insert_token_info, insert_transferable, + remove_inscribe_transfer_inscription, remove_transferable, save_transaction_receipts, + update_token_balance, +}; +use crate::okx::datastore::brc20::redb::{script_tick_id_key, script_tick_key}; +use crate::okx::datastore::brc20::{ + Balance, Brc20Reader, Brc20ReaderWriter, Receipt, Tick, TokenInfo, TransferInfo, TransferableLog, +}; +use crate::okx::datastore::cache::CacheTableIndex; +use crate::okx::datastore::ord::collections::CollectionKind; +use crate::okx::datastore::ord::redb::table::{ + get_collection_inscription_id, get_collections_of_inscription, + get_inscription_number_by_sequence_number, get_transaction_operations, get_txout_by_outpoint, + save_transaction_operations, set_inscription_attributes, set_inscription_by_collection_key, +}; +use crate::okx::datastore::ord::{InscriptionOp, OrdReader, OrdReaderWriter}; +use crate::okx::datastore::ScriptKey; +use crate::okx::protocol::trace::TraceNode; +use crate::okx::protocol::ContextTrait; +use crate::{InscriptionId, SatPoint}; +use anyhow::anyhow; +use bitcoin::{Network, Txid}; +use redb::{ReadOnlyTable, RedbKey, RedbValue, Table, TableDefinition}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; +use std::rc::Rc; + +#[allow(non_snake_case)] +#[derive(Clone)] +pub struct SimulateContext<'a, 'db, 'txn> { + pub network: Network, + pub current_height: u32, + pub current_block_time: u32, + pub internal_index: IndexWrapper, + pub(crate) ORD_TX_TO_OPERATIONS: Rc>>, + pub(crate) COLLECTIONS_KEY_TO_INSCRIPTION_ID: + Rc>>, + pub(crate) COLLECTIONS_INSCRIPTION_ID_TO_KINDS: + Rc>>, + pub(crate) SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY: + Rc>>, + pub(crate) OUTPOINT_TO_ENTRY: + Rc>>, + + // BRC20 tables + pub(crate) BRC20_BALANCES: Rc>>, + pub(crate) BRC20_TOKEN: Rc>>, + pub(crate) BRC20_EVENTS: Rc>>, + pub(crate) BRC20_TRANSFERABLELOG: Rc>>, + pub(crate) BRC20_INSCRIBE_TRANSFER: + Rc>>, + pub traces: Rc>>, + pub brc20_receipts: Rc>>, + pub ord_operations: Rc>>, + pub _marker_a: PhantomData<&'a ()>, +} + +impl<'a, 'db, 'txn> Brc20Reader for SimulateContext<'a, 'db, 'txn> { + type Error = anyhow::Error; + + fn get_balances(&self, script_key: &ScriptKey) -> crate::Result, Self::Error> { + let balances = self.BRC20_BALANCES.borrow(); + let table = balances.deref(); + let simulate = get_balances(table, script_key)?; + let internal = self.use_internal_table(BRC20_BALANCES, |v| get_balances(&v, script_key))?; + let mut simulate_balances: HashMap = simulate + .into_iter() + .map(|v| (v.tick.clone(), v.clone())) + .collect(); + for node in internal { + let v = simulate_balances + .entry(node.tick.clone()) + .or_insert(node.clone()); + v.transferable_balance += node.transferable_balance; + v.overall_balance += node.overall_balance + } + let ret = simulate_balances.into_values().collect(); + Ok(ret) + } + + fn get_balance( + &self, + script_key: &ScriptKey, + tick: &Tick, + ) -> crate::Result, Self::Error> { + let table = self.BRC20_BALANCES.borrow(); + let table = table.deref(); + let ret = get_balance(table, script_key, tick)?; + if let Some(ret) = ret { + return Ok(Some(ret)); + } + self.use_internal_table(BRC20_BALANCES, |table| { + get_balance(&table, script_key, tick) + }) + } + + fn get_token_info(&self, tick: &Tick) -> crate::Result, Self::Error> { + let table = self.BRC20_TOKEN.borrow(); + let table = table.deref(); + let ret = get_token_info(table, tick)?; + if let Some(ret) = ret { + return Ok(Some(ret)); + } + self.use_internal_table(BRC20_TOKEN, |table| get_token_info(&table, tick)) + } + + fn get_tokens_info(&self) -> crate::Result, Self::Error> { + let binding = self.BRC20_TOKEN.borrow(); + let table = binding.deref(); + let ret = get_tokens_info(table)?; + let mut token_map = ret + .into_iter() + .map(|v| (v.tick.clone(), v)) + .collect::>(); + let internal = self.use_internal_table(BRC20_TOKEN, |table| get_tokens_info(&table))?; + for node in internal { + if !token_map.contains_key(&node.tick) { + token_map.insert(node.tick.clone(), node.clone()); + } + } + let ret = token_map.into_values().collect(); + Ok(ret) + } + + fn get_transaction_receipts(&self, txid: &Txid) -> crate::Result, Self::Error> { + let binding = self.BRC20_EVENTS.borrow(); + let table = binding.deref(); + let ret = get_transaction_receipts(table, txid)?; + let mut simulate_receipts = ret + .into_iter() + .map(|v| (v.inscription_id, v)) + .collect::>(); + let internal = + self.use_internal_table(BRC20_EVENTS, |table| get_transaction_receipts(&table, txid))?; + for node in internal { + simulate_receipts + .entry(node.inscription_id) + .or_insert_with(|| node.clone()); + } + let ret = simulate_receipts.into_values().collect(); + Ok(ret) + } + + fn get_transferable( + &self, + script: &ScriptKey, + ) -> crate::Result, Self::Error> { + let binding = self.BRC20_TRANSFERABLELOG.borrow(); + let table = binding.deref(); + let ret = get_transferable(table, script)?; + let mut simulate_transferable = ret + .into_iter() + .map(|v| (v.inscription_id, v)) + .collect::>(); + let internal = self.use_internal_table(BRC20_TRANSFERABLELOG, |table| { + get_transferable(&table, script) + })?; + for node in internal { + simulate_transferable + .entry(node.inscription_id) + .or_insert_with(|| node.clone()); + } + let ret = simulate_transferable.into_values().collect(); + Ok(ret) + } + + fn get_transferable_by_tick( + &self, + script: &ScriptKey, + tick: &Tick, + ) -> crate::Result, Self::Error> { + let binding = self.BRC20_TRANSFERABLELOG.borrow(); + let table = binding.deref(); + let ret = get_transferable_by_tick(table, script, tick)?; + let mut simulate_transferable = ret + .into_iter() + .map(|v| (v.inscription_id, v)) + .collect::>(); + let internal = self.use_internal_table(BRC20_TRANSFERABLELOG, |table| { + get_transferable_by_tick(&table, script, tick) + })?; + for node in internal { + simulate_transferable + .entry(node.inscription_id) + .or_insert_with(|| node.clone()); + } + let ret = simulate_transferable.into_values().collect(); + Ok(ret) + } + + fn get_transferable_by_id( + &self, + script: &ScriptKey, + inscription_id: &InscriptionId, + ) -> crate::Result, Self::Error> { + let binding = self.BRC20_TRANSFERABLELOG.borrow(); + let table = binding.deref(); + let ret = get_transferable_by_id(table, script, inscription_id)?; + if let Some(ret) = ret { + return Ok(Some(ret)); + } + self.use_internal_table(BRC20_TRANSFERABLELOG, |table| { + get_transferable_by_id(&table, script, inscription_id) + }) + } + + fn get_inscribe_transfer_inscription( + &self, + inscription_id: &InscriptionId, + ) -> crate::Result, Self::Error> { + let binding = self.BRC20_INSCRIBE_TRANSFER.borrow(); + let table = binding.deref(); + let ret = get_inscribe_transfer_inscription(table, inscription_id)?; + if let Some(ret) = ret { + return Ok(Some(ret)); + } + self.use_internal_table(BRC20_INSCRIBE_TRANSFER, |table| { + get_inscribe_transfer_inscription(&table, inscription_id) + }) + } +} + +impl<'a, 'db, 'txn> Brc20ReaderWriter for SimulateContext<'a, 'db, 'txn> { + fn update_token_balance( + &mut self, + script_key: &ScriptKey, + new_balance: Balance, + ) -> crate::Result<(), Self::Error> { + let mut traces = self.traces.borrow_mut(); + let binding = script_tick_key(script_key, &new_balance.tick); + let key = binding.as_str(); + let key = key.as_bytes().to_vec(); + traces.push(TraceNode { + trace_type: CacheTableIndex::BRC20_BALANCES, + key, + }); + let mut table = self.BRC20_BALANCES.borrow_mut(); + update_token_balance(&mut table, script_key, new_balance) + } + + fn insert_token_info( + &mut self, + tick: &Tick, + new_info: &TokenInfo, + ) -> crate::Result<(), Self::Error> { + let mut traces = self.traces.borrow_mut(); + let binding = tick.to_lowercase().hex(); + let key = binding.as_str(); + let key = key.as_bytes().to_vec(); + traces.push(TraceNode { + trace_type: CacheTableIndex::BRC20_TOKEN, + key, + }); + + let mut binding = self.BRC20_TOKEN.borrow_mut(); + let table = binding.deref_mut(); + insert_token_info(table, tick, new_info) + } + + fn update_mint_token_info( + &mut self, + tick: &Tick, + minted_amt: u128, + minted_block_number: u32, + ) -> crate::Result<(), Self::Error> { + let info = self.get_token_info(tick)?; + if info.is_none() { + return Err(anyhow!(format!( + "token {:?} not exist", + tick.to_lowercase().to_string() + ))); + } + let mut info = info.unwrap(); + let mut binding = self.BRC20_TOKEN.borrow_mut(); + let table = binding.deref_mut(); + info.minted = minted_amt; + info.latest_mint_number = minted_block_number; + insert_token_info(table, tick, &info) + } + + fn save_transaction_receipts( + &mut self, + txid: &Txid, + receipt: &[Receipt], + ) -> crate::Result<(), Self::Error> { + let mut traces = self.traces.borrow_mut(); + let key = rmp_serde::to_vec(txid).unwrap(); + traces.push(TraceNode { + trace_type: CacheTableIndex::BRC20_EVENTS, + key, + }); + let mut receipts = self.brc20_receipts.borrow_mut(); + receipts.extend_from_slice(receipt); + let mut table = self.BRC20_EVENTS.borrow_mut(); + save_transaction_receipts(&mut table, txid, receipt) + } + + fn insert_transferable( + &mut self, + script: &ScriptKey, + tick: &Tick, + inscription: &TransferableLog, + ) -> crate::Result<(), Self::Error> { + let mut traces = self.traces.borrow_mut(); + let binding = script_tick_id_key(script, tick, &inscription.inscription_id); + let key = binding.as_str(); + let key = key.as_bytes().to_vec(); + traces.push(TraceNode { + trace_type: CacheTableIndex::BRC20_TRANSFERABLELOG, + key, + }); + let mut table = self.BRC20_TRANSFERABLELOG.borrow_mut(); + insert_transferable(&mut table, script, tick, inscription) + } + + fn remove_transferable( + &mut self, + script: &ScriptKey, + tick: &Tick, + inscription_id: &InscriptionId, + ) -> crate::Result<(), Self::Error> { + let mut table = self.BRC20_TRANSFERABLELOG.borrow_mut(); + remove_transferable(&mut table, script, tick, inscription_id) + } + + fn insert_inscribe_transfer_inscription( + &mut self, + inscription_id: &InscriptionId, + transfer_info: TransferInfo, + ) -> crate::Result<(), Self::Error> { + let mut traces = self.traces.borrow_mut(); + let key = &inscription_id.store(); + let key = InscriptionIdValue::as_bytes(key); + traces.push(TraceNode { + trace_type: CacheTableIndex::BRC20_INSCRIBE_TRANSFER, + key, + }); + let mut table = self.BRC20_INSCRIBE_TRANSFER.borrow_mut(); + insert_inscribe_transfer_inscription(&mut table, inscription_id, transfer_info) + } + + fn remove_inscribe_transfer_inscription( + &mut self, + inscription_id: &InscriptionId, + ) -> crate::Result<(), Self::Error> { + let mut table = self.BRC20_INSCRIBE_TRANSFER.borrow_mut(); + remove_inscribe_transfer_inscription(&mut table, inscription_id) + } +} + +impl<'a, 'db, 'txn> OrdReaderWriter for SimulateContext<'a, 'db, 'txn> { + fn save_transaction_operations( + &mut self, + txid: &Txid, + operations: &[InscriptionOp], + ) -> crate::Result<(), Self::Error> { + let mut traces = self.traces.borrow_mut(); + let key = &txid.store(); + let key = TxidValue::as_slice(key).to_vec(); + traces.push(TraceNode { + trace_type: CacheTableIndex::ORD_TX_TO_OPERATIONS, + key, + }); + + let mut ord_traces = self.ord_operations.borrow_mut(); + ord_traces.extend_from_slice(operations); + + let mut table = self.ORD_TX_TO_OPERATIONS.borrow_mut(); + save_transaction_operations(&mut table, txid, operations) + } + + fn set_inscription_by_collection_key( + &mut self, + key: &str, + inscription_id: &InscriptionId, + ) -> crate::Result<(), Self::Error> { + let mut traces = self.traces.borrow_mut(); + let trace_key = key.as_bytes().to_vec(); + traces.push(TraceNode { + trace_type: CacheTableIndex::COLLECTIONS_KEY_TO_INSCRIPTION_ID, + key: trace_key, + }); + let mut table = self.COLLECTIONS_KEY_TO_INSCRIPTION_ID.borrow_mut(); + set_inscription_by_collection_key(&mut table, key, inscription_id) + } + + fn set_inscription_attributes( + &mut self, + inscription_id: &InscriptionId, + kind: &[CollectionKind], + ) -> crate::Result<(), Self::Error> { + let mut traces = self.traces.borrow_mut(); + let key = inscription_id.store(); + let key = InscriptionIdValue::as_bytes(&key); + traces.push(TraceNode { + trace_type: CacheTableIndex::COLLECTIONS_INSCRIPTION_ID_TO_KINDS, + key, + }); + let mut table = self.COLLECTIONS_INSCRIPTION_ID_TO_KINDS.borrow_mut(); + set_inscription_attributes(&mut table, inscription_id, kind) + } +} + +impl<'a, 'db, 'txn> OrdReader for SimulateContext<'a, 'db, 'txn> { + type Error = anyhow::Error; + + fn get_inscription_number_by_sequence_number( + &self, + sequence_number: u32, + ) -> crate::Result { + let binding = self.SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY.borrow(); + let table = binding.deref(); + let ret = get_inscription_number_by_sequence_number(table, sequence_number) + .map_err(|e| anyhow!("failed to get inscription number from state! error: {e}"))?; + if let Some(ret) = ret { + return Ok(ret); + } + self + .use_internal_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY, |table| { + get_inscription_number_by_sequence_number(&table, sequence_number) + }) + .map_err(|e| anyhow!("failed to get inscription number from state! error: {e}"))? + .ok_or(anyhow!( + "failed to get inscription number! error: sequence number {} not found", + sequence_number + )) + } + + fn get_script_key_on_satpoint( + &mut self, + satpoint: &SatPoint, + network: Network, + ) -> crate::Result { + let binding = self.OUTPOINT_TO_ENTRY.borrow(); + let table = binding.deref(); + if let Some(tx_out) = get_txout_by_outpoint(table, &satpoint.outpoint)? { + return Ok(ScriptKey::from_script(&tx_out.script_pubkey, network)); + } else { + let ret = self.use_internal_table(OUTPOINT_TO_ENTRY, |table| { + get_txout_by_outpoint(&table, &satpoint.outpoint) + })?; + if let Some(ret) = ret { + return Ok(ScriptKey::from_script(&ret.script_pubkey, network)); + } + } + Err(anyhow!( + "failed to get tx out! error: outpoint {} not found", + &satpoint.outpoint + )) + } + + fn get_transaction_operations( + &self, + txid: &Txid, + ) -> crate::Result, Self::Error> { + let binding = self.ORD_TX_TO_OPERATIONS.borrow(); + let table = binding.deref(); + let simulate = get_transaction_operations(table, txid)?; + let mut simulate_operations: HashMap = simulate + .into_iter() + .map(|v| (v.inscription_id, v.clone())) + .collect(); + let internal = self.use_internal_table(BRC20_EVENTS, |table| { + get_transaction_operations(&table, txid) + })?; + for node in internal { + if simulate_operations.contains_key(&node.inscription_id) { + continue; + } + simulate_operations.insert(node.inscription_id, node.clone()); + } + let ret = simulate_operations.into_values().collect(); + Ok(ret) + } + + fn get_collections_of_inscription( + &self, + inscription_id: &InscriptionId, + ) -> crate::Result>, Self::Error> { + let binding = self.COLLECTIONS_INSCRIPTION_ID_TO_KINDS.borrow(); + let table = binding.deref(); + let simulate = get_collections_of_inscription(table, inscription_id)?; + let mut simulate = if let Some(ret) = simulate { + ret + } else { + vec![] + }; + + let internal = self.use_internal_table(COLLECTIONS_INSCRIPTION_ID_TO_KINDS, |table| { + get_collections_of_inscription(&table, inscription_id) + })?; + if let Some(internal) = internal { + simulate.extend_from_slice(&internal); + } + if simulate.is_empty() { + return Ok(None); + } + Ok(Some(simulate)) + } + + fn get_collection_inscription_id( + &self, + collection_key: &str, + ) -> crate::Result, Self::Error> { + let binding = self.COLLECTIONS_KEY_TO_INSCRIPTION_ID.borrow(); + let table = binding.deref(); + let ret = get_collection_inscription_id(table, collection_key)?; + if let Some(ret) = ret { + return Ok(Some(ret)); + } + self.use_internal_table(COLLECTIONS_KEY_TO_INSCRIPTION_ID, |table| { + get_collection_inscription_id(&table, collection_key) + }) + } +} + +impl<'a, 'db, 'txn> ContextTrait for SimulateContext<'a, 'db, 'txn> { + fn block_height(&self) -> u32 { + self.current_height + } + + fn network(&self) -> Network { + self.network + } + + fn block_time(&self) -> u32 { + self.current_block_time + } +} + +impl<'a, 'db, 'txn> SimulateContext<'a, 'db, 'txn> { + fn use_internal_table( + &self, + table_def: TableDefinition, + f: impl FnOnce(ReadOnlyTable) -> crate::Result, + ) -> crate::Result { + let rtx = self.internal_index.internal.begin_read()?; + let table = rtx.0.open_table(table_def)?; + f(table) + } +} diff --git a/src/okx/protocol/trace.rs b/src/okx/protocol/trace.rs new file mode 100644 index 0000000000..3f84de9870 --- /dev/null +++ b/src/okx/protocol/trace.rs @@ -0,0 +1,8 @@ +use crate::okx::datastore::cache::CacheTableIndex; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Serialize, Deserialize)] +pub struct TraceNode { + pub trace_type: CacheTableIndex, + pub key: Vec, +} diff --git a/src/options.rs b/src/options.rs index a7854ccfca..7ef0346acb 100644 --- a/src/options.rs +++ b/src/options.rs @@ -89,6 +89,26 @@ pub struct Options { help = "Don't look for BRC20 messages below ." )] pub(crate) first_brc20_height: Option, + + #[arg( + long, + default_value = "false", + help = "simulate tx flag, default is false" + )] + pub(crate) simulate_enable: bool, + #[arg(long, help = "bitcoin zmq url.")] + pub(crate) simulate_zmq_url: Option, + #[arg(long, help = "bitcoin rpc url.")] + pub(crate) simulate_bitcoin_rpc_url: Option, + #[arg(long, help = "bitcoin rpc password .")] + pub(crate) simulate_bitcoin_rpc_pass: Option, + #[arg(long, help = "bitcoin rpc user.")] + pub(crate) simulate_bitcoin_rpc_user: Option, + #[arg(long, help = "Simulate Use index at .")] + pub(crate) simulate_index: Option, + + #[clap(skip)] + pub rx: Option>>, #[clap(long, default_value = "200", help = "DB commit interval.")] pub(crate) commit_height_interval: u64, #[clap( diff --git a/src/sat_point.rs b/src/sat_point.rs index 75c034cf82..52e34d653c 100644 --- a/src/sat_point.rs +++ b/src/sat_point.rs @@ -1,6 +1,6 @@ use super::*; -#[derive(Debug, PartialEq, Copy, Clone, Eq, PartialOrd, Ord, Default)] +#[derive(Debug, PartialEq, Copy, Clone, Eq, PartialOrd, Ord, Default, Hash)] pub struct SatPoint { pub outpoint: OutPoint, pub offset: u64, diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 5a9d45d4c7..81a7b07178 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1,3 +1,7 @@ +use crate::index::simulator::simulate::{start_simulator, SimulatorServer}; +use crate::okx::datastore::brc20::Receipt; +use crate::okx::datastore::ord::InscriptionOp; +use bitcoincore_rpc::Auth; use { self::{ accept_encoding::AcceptEncoding, @@ -184,6 +188,12 @@ pub(crate) struct Server { impl Server { pub(crate) fn run(self, options: Options, index: Arc, handle: Handle) -> SubcommandResult { + let sim_option = options.clone(); + let sim_index = index.clone(); + let simulator_server = thread::spawn(move || start_simulator(sim_option, sim_index)) + .join() + .unwrap(); + Runtime::new()?.block_on(async { let index_clone = index.clone(); @@ -198,6 +208,21 @@ impl Server { }); INDEXER.lock().unwrap().replace(index_thread); + let client = if options.simulate_enable { + Some(Arc::new( + Client::new( + options.simulate_bitcoin_rpc_url.as_ref().unwrap().as_ref(), + Auth::UserPass( + options.simulate_bitcoin_rpc_user.as_ref().unwrap().clone(), + options.simulate_bitcoin_rpc_pass.as_ref().unwrap().clone(), + ), + ) + .unwrap(), + )) + } else { + None + }; + #[derive(OpenApi)] #[openapi( paths( @@ -402,10 +427,18 @@ impl Server { .route("/static/*path", get(Self::static_asset)) .route("/status", get(Self::status)) .route("/tx/:txid", get(Self::transaction)) + .route("/tx/simulate_ord/:txid", get(Self::simulate_ord)) + .route("/tx/simulate_brc20/:txid", get(Self::simulate_brc20)) + .route( + "/tx/multiple_receipt/:txid", + get(Self::confirm_or_pending_receipt), + ) .nest("/api", api_router) .layer(Extension(index)) .layer(Extension(server_config.clone())) .layer(Extension(config)) + .layer(Extension(client)) + .layer(Extension(simulator_server)) .layer(SetResponseHeaderLayer::if_not_present( header::CONTENT_SECURITY_POLICY, HeaderValue::from_static("default-src 'self'"), @@ -1667,6 +1700,69 @@ impl Server { Redirect::to(&destination) } + + async fn simulate_brc20( + Extension(client): Extension>>, + Extension(simulator): Extension>, + Path(tx_id): Path, + ) -> ApiResult> { + if simulator.is_none() { + return Err(ApiError::Internal("simulator not enabled".to_string())); + } + + let tx = client.unwrap().get_raw_transaction(&tx_id, None); + if tx.is_err() { + return Err(ApiError::NotFound("tx not found".to_string())); + } + + match simulator.unwrap().execute_tx(tx.as_ref().unwrap(), false) { + Ok(data) => Ok(Json(ApiResponse::ok(data.brc20_receipts))), + Err(err) => Err(ApiError::Internal(err.to_string())), + } + } + + async fn simulate_ord( + Extension(client): Extension>, + Extension(simulator): Extension>, + Path(tx_id): Path, + ) -> ApiResult> { + if simulator.is_none() { + return Err(ApiError::Internal("simulator not enabled".to_string())); + } + + let tx = client.get_raw_transaction(&tx_id, None); + if tx.is_err() { + return Err(ApiError::NotFound("tx not found".to_string())); + } + + match simulator.unwrap().execute_tx(tx.as_ref().unwrap(), false) { + Ok(data) => Ok(Json(ApiResponse::ok(data.ord_operations))), + Err(err) => Err(ApiError::Internal(err.to_string())), + } + } + + async fn confirm_or_pending_receipt( + Extension(index): Extension>, + Extension(simulator): Extension>, + Path(tx_id): Path, + ) -> ServerResult> { + let pending_receipt = simulator.unwrap().get_receipt(tx_id).unwrap_or(Vec::new()); + let confirm_receipt = index + .brc20_get_tx_events_by_txid(&tx_id) + .unwrap_or(Some(Vec::new())); + let receipt = MultipleReceipt { + confirm: confirm_receipt, + pending: Some(pending_receipt), + }; + + Ok(Json(receipt)) + } +} + +#[derive(Serialize, Deserialize)] +struct MultipleReceipt { + pub confirm: Option>, + pub pending: Option>, } #[cfg(test)] diff --git a/src/subcommand/wallet/balance.rs b/src/subcommand/wallet/balance.rs index 8a95985e44..e911ac1bee 100644 --- a/src/subcommand/wallet/balance.rs +++ b/src/subcommand/wallet/balance.rs @@ -53,23 +53,3 @@ pub(crate) fn run(wallet: String, options: Options) -> SubcommandResult { total: cardinal + ordinal + runic, })) } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn runes_and_runic_fields_are_not_present_if_none() { - assert_eq!( - serde_json::to_string(&Output { - cardinal: 0, - ordinal: 0, - runes: None, - runic: None, - total: 0 - }) - .unwrap(), - r#"{"cardinal":0,"ordinal":0,"total":0}"# - ); - } -} diff --git a/test-bitcoincore-rpc/src/lib.rs b/test-bitcoincore-rpc/src/lib.rs index 2e1276ef5e..4263313822 100644 --- a/test-bitcoincore-rpc/src/lib.rs +++ b/test-bitcoincore-rpc/src/lib.rs @@ -159,7 +159,7 @@ impl From for JsonOutPoint { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] -struct FundRawTransactionOptions { +pub struct FundRawTransactionOptions { #[serde(with = "bitcoin::amount::serde::as_btc::opt")] fee_rate: Option, #[serde(skip_serializing_if = "Option::is_none")]