diff --git a/Cargo.lock b/Cargo.lock index 3fcfc30..da15942 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -37,12 +37,33 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "async-trait" version = "0.1.79" @@ -51,7 +72,7 @@ checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.106", ] [[package]] @@ -60,6 +81,73 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "axum-macros", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tower 0.5.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 1.0.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "backtrace" version = "0.3.71" @@ -75,12 +163,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "base64" version = "0.21.7" @@ -89,9 +171,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bech32" @@ -99,15 +181,6 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" -[[package]] -name = "beef" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" -dependencies = [ - "serde", -] - [[package]] name = "bit-vec" version = "0.6.3" @@ -142,15 +215,6 @@ dependencies = [ "cty", ] -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -187,7 +251,7 @@ version = "12.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "142316461ed3a3dfcba10417317472da5bfd0461e4d276bf7c07b330766d9490" dependencies = [ - "digest 0.10.7", + "digest", "either", "futures", "hex", @@ -202,7 +266,7 @@ dependencies = [ "sha2", "ssri", "tempfile", - "thiserror", + "thiserror 1.0.58", "tokio", "tokio-stream", "walkdir", @@ -226,6 +290,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "ckb-chain-spec" version = "0.116.1" @@ -275,7 +354,7 @@ dependencies = [ "lazy_static", "rand 0.7.3", "secp256k1", - "thiserror", + "thiserror 1.0.58", ] [[package]] @@ -298,7 +377,7 @@ dependencies = [ "anyhow", "ckb-occupied-capacity", "derive_more", - "thiserror", + "thiserror 1.0.58", ] [[package]] @@ -320,7 +399,7 @@ dependencies = [ "ckb_schemars", "faster-hex", "serde", - "thiserror", + "thiserror 1.0.58", ] [[package]] @@ -527,7 +606,7 @@ dependencies = [ "serde_json", "sha3", "sparse-merkle-tree", - "thiserror", + "thiserror 1.0.58", "tokio", "tokio-util", ] @@ -739,22 +818,13 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", + "block-buffer", "crypto-common", ] @@ -762,6 +832,9 @@ dependencies = [ name = "dob-decoder-server" version = "0.1.0" dependencies = [ + "axum", + "base64 0.22.1", + "chrono", "ckb-hash", "ckb-jsonrpc-types", "ckb-sdk", @@ -771,15 +844,21 @@ dependencies = [ "hex", "jsonrpc-core", "jsonrpsee", + "lazy-regex", "lazy_static", + "lyon", "reqwest 0.12.4", "serde", "serde_json", "spore-types", - "thiserror", + "svg", + "thiserror 1.0.58", "tokio", "toml 0.8.12", + "tower 0.5.2", + "tower-http", "tracing-subscriber", + "ttf-parser", ] [[package]] @@ -837,6 +916,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "euclid" +version = "0.22.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +dependencies = [ + "num-traits", +] + [[package]] name = "faster-hex" version = "0.6.1" @@ -859,6 +947,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + [[package]] name = "fnv" version = "1.0.7" @@ -945,7 +1039,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.106", ] [[package]] @@ -1010,6 +1104,18 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "r-efi", + "wasi 0.14.4+wasi-0.2.4", +] + [[package]] name = "gimli" version = "0.28.1" @@ -1111,15 +1217,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "hermit-abi" -version = "0.3.9" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" @@ -1185,9 +1285,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -1212,7 +1312,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.5.6", "tokio", "tower-service", "tracing", @@ -1221,9 +1321,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.3.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", @@ -1232,6 +1332,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -1239,6 +1340,23 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.6.0", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -1260,7 +1378,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.3.1", + "hyper 1.6.0", "hyper-util", "native-tls", "tokio", @@ -1279,15 +1397,39 @@ dependencies = [ "futures-util", "http 1.1.0", "http-body 1.0.0", - "hyper 1.3.1", + "hyper 1.6.0", "pin-project-lite", - "socket2", + "socket2 0.5.6", "tokio", - "tower", + "tower 0.4.13", "tower-service", "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.61.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.5.0" @@ -1329,6 +1471,17 @@ dependencies = [ "hashbrown 0.14.3", ] +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags 2.5.0", + "cfg-if 1.0.0", + "libc", +] + [[package]] name = "ipnet" version = "2.9.0" @@ -1367,9 +1520,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.22.3" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cdbb7cb6f3ba28f5b212dd250ab4483105efc3e381f5c8bb90340f14f0a2cc3" +checksum = "3f3f48dc3e6b8bd21e15436c1ddd0bc22a6a54e8ec46fedd6adf3425f396ec6a" dependencies = [ "jsonrpsee-core", "jsonrpsee-proc-macros", @@ -1381,48 +1534,54 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.22.3" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71962a1c49af43adf81d337e4ebc93f3c915faf6eccaa14d74e255107dfd7723" +checksum = "316c96719901f05d1137f19ba598b5fe9c9bc39f4335f67f6be8613921946480" dependencies = [ - "anyhow", "async-trait", - "beef", + "bytes", "futures-util", - "hyper 0.14.28", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", "jsonrpsee-types", "parking_lot", - "rand 0.8.5", + "pin-project", + "rand 0.9.2", "rustc-hash", "serde", "serde_json", - "thiserror", + "thiserror 2.0.16", "tokio", + "tower 0.5.2", "tracing", ] [[package]] name = "jsonrpsee-proc-macros" -version = "0.22.3" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7c2416c400c94b2e864603c51a5bbd5b103386da1f5e58cbf01e7bb3ef0833" +checksum = "2da3f8ab5ce1bb124b6d082e62dffe997578ceaf0aeb9f3174a214589dc00f07" dependencies = [ "heck", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.106", ] [[package]] name = "jsonrpsee-server" -version = "0.22.3" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4882e640e70c2553e3d9487e6f4dddd5fd11918f25e40fa45218f9fe29ed2152" +checksum = "4c51b7c290bb68ce3af2d029648148403863b982f138484a73f02a9dd52dbd7f" dependencies = [ "futures-util", - "http 0.2.12", - "hyper 0.14.28", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.6.0", + "hyper-util", "jsonrpsee-core", "jsonrpsee-types", "pin-project", @@ -1430,25 +1589,24 @@ dependencies = [ "serde", "serde_json", "soketto", - "thiserror", + "thiserror 2.0.16", "tokio", "tokio-stream", "tokio-util", - "tower", + "tower 0.5.2", "tracing", ] [[package]] name = "jsonrpsee-types" -version = "0.22.3" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e53c72de6cd2ad6ac1aa6e848206ef8b736f92ed02354959130373dfa5b3cbd" +checksum = "bc88ff4688e43cc3fa9883a8a95c6fa27aa2e76c96e610b737b6554d650d7fd5" dependencies = [ - "anyhow", - "beef", + "http 1.1.0", "serde", "serde_json", - "thiserror", + "thiserror 2.0.16", ] [[package]] @@ -1460,6 +1618,29 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "lazy-regex" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60c7310b93682b36b98fa7ea4de998d3463ccbebd94d935d6b48ba5b6ffa7126" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba01db5ef81e17eb10a5e0f2109d1b3a3e29bac3070fdbd7d156bf7dbd206a1" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.106", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1468,9 +1649,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "linux-raw-sys" @@ -1503,6 +1690,58 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "lyon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7f9cda98b5430809e63ca5197b06c7d191bf7e26dfc467d5a3f0290e2a74f" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f13c9be19d257c7d37e70608ed858e8eab4b2afcea2e3c9a622e892acbf43c08" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8af69edc087272df438b3ee436c4bb6d7c04aa8af665cfd398feae627dbd8570" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0047f508cd7a85ad6bad9518f68cce7b1bf6b943fb71f6da0ee3bc1e8cb75f25" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579d42360a4b09846eff2feef28f538696c7d6c7439bfa65874ff3cbe0951b2c" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + [[package]] name = "matchers" version = "0.1.0" @@ -1512,6 +1751,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.2" @@ -1544,7 +1789,7 @@ checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" dependencies = [ "miette-derive", "once_cell", - "thiserror", + "thiserror 1.0.58", "unicode-width", ] @@ -1556,7 +1801,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.106", ] [[package]] @@ -1576,13 +1821,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -1625,13 +1870,13 @@ dependencies = [ ] [[package]] -name = "num_cpus" -version = "1.16.0" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "hermit-abi", - "libc", + "autocfg", + "libm", ] [[package]] @@ -1665,7 +1910,7 @@ dependencies = [ "numext-constructor", "rand 0.7.3", "serde", - "thiserror", + "thiserror 1.0.58", ] [[package]] @@ -1691,15 +1936,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" @@ -1724,7 +1963,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.106", ] [[package]] @@ -1841,7 +2080,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.106", ] [[package]] @@ -1909,9 +2148,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -1925,6 +2164,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.7.3" @@ -1950,6 +2195,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -1970,6 +2225,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -1988,6 +2253,15 @@ dependencies = [ "getrandom 0.2.12", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -2028,14 +2302,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -2049,13 +2323,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax 0.8.5", ] [[package]] @@ -2066,9 +2340,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" @@ -2098,7 +2372,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 0.1.2", "system-configuration", "tokio", "tokio-native-tls", @@ -2116,7 +2390,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", @@ -2125,7 +2399,8 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "http-body-util", - "hyper 1.3.1", + "hyper 1.6.0", + "hyper-rustls", "hyper-tls 0.6.0", "hyper-util", "ipnet", @@ -2136,22 +2411,41 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls", "rustls-pemfile 2.1.2", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 0.1.2", "system-configuration", "tokio", "tokio-native-tls", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", "winreg 0.52.0", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if 1.0.0", + "getrandom 0.2.12", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "route-recognizer" version = "0.3.1" @@ -2166,9 +2460,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" @@ -2192,6 +2486,20 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -2207,7 +2515,7 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "rustls-pki-types", ] @@ -2217,6 +2525,23 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" +[[package]] +name = "rustls-webpki" +version = "0.102.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" version = "1.0.17" @@ -2331,7 +2656,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.106", ] [[package]] @@ -2347,15 +2672,26 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.5" @@ -2377,19 +2713,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha-1" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha-1" version = "0.10.1" @@ -2398,7 +2721,7 @@ checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.7", + "digest", ] [[package]] @@ -2409,7 +2732,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.7", + "digest", ] [[package]] @@ -2420,7 +2743,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.7", + "digest", ] [[package]] @@ -2429,7 +2752,7 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest 0.10.7", + "digest", "keccak", ] @@ -2482,20 +2805,30 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "soketto" -version = "0.7.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" +checksum = "2e859df029d160cb88608f5d7df7fb4753fd20fdfb4de5644f3d8b8440841721" dependencies = [ - "base64 0.13.1", + "base64 0.22.1", "bytes", "futures", - "http 0.2.12", + "http 1.1.0", "httparse", "log", "rand 0.8.5", - "sha-1 0.9.8", + "sha1", ] [[package]] @@ -2509,6 +2842,12 @@ dependencies = [ "cfg-if 0.1.10", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spore-types" version = "0.1.0" @@ -2524,16 +2863,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7a2b3c2bc9693bcb40870c4e9b5bf0d79f9cb46273321bf855ec513e919082" dependencies = [ "base64 0.21.7", - "digest 0.10.7", + "digest", "hex", "miette", "serde", - "sha-1 0.10.1", + "sha-1", "sha2", - "thiserror", + "thiserror 1.0.58", "xxhash-rust", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "svg" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683eed9bd9a2b078f92f87d166db38292e8114ab16d4cf23787ad4eecd1bb6e5" + [[package]] name = "syn" version = "1.0.109" @@ -2547,9 +2898,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.57" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -2562,6 +2913,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + [[package]] name = "system-configuration" version = "0.5.1" @@ -2601,7 +2958,16 @@ version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.58", +] + +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl 2.0.16", ] [[package]] @@ -2612,7 +2978,18 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.106", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] @@ -2642,31 +3019,32 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", - "num_cpus", "pin-project-lite", "signal-hook-registry", - "socket2", + "slab", + "socket2 0.6.0", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.106", ] [[package]] @@ -2679,6 +3057,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.15" @@ -2776,17 +3165,47 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 1.0.2", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.5.0", + "bytes", + "http 1.1.0", + "pin-project-lite", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -2808,7 +3227,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.106", ] [[package]] @@ -2856,6 +3275,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ttf-parser" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a" + [[package]] name = "typenum" version = "1.17.0" @@ -2889,6 +3314,12 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.0" @@ -2949,6 +3380,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.4+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a5f4a424faf49c3c2c344f166f0662341d470ea185e939657aaff130f0ec4a" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.92" @@ -2970,7 +3410,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.106", "wasm-bindgen-shared", ] @@ -3004,7 +3444,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3025,6 +3465,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi" version = "0.3.9" @@ -3062,8 +3511,8 @@ version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" dependencies = [ - "windows-core", - "windows-targets 0.52.4", + "windows-core 0.54.0", + "windows-targets 0.52.6", ] [[package]] @@ -3072,17 +3521,76 @@ version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" dependencies = [ - "windows-result", - "windows-targets 0.52.4", + "windows-result 0.1.0", + "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result 0.3.4", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-result" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd19df78e5168dfb0aedc343d1d1b8d422ab2db6756d2dc3fef75035402a3f64" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", ] [[package]] @@ -3100,7 +3608,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -3120,17 +3637,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -3141,9 +3659,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -3153,9 +3671,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -3165,9 +3683,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -3177,9 +3701,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -3189,9 +3713,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -3201,9 +3725,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -3213,9 +3737,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -3255,8 +3779,20 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" + [[package]] name = "xxhash-rust" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927da81e25be1e1a2901d59b81b37dd2efd1fc9c9345a55007f09bf5a2d3ee03" + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 2edf7a2..f5cb883 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +base64 = "0.22.1" ckb-sdk = "3.2.0" ckb-types = "0.116.1" ckb-jsonrpc-types = "0.116.1" @@ -13,21 +14,29 @@ ckb-hash = "0.116.1" thiserror = "1.0" serde_json = "1.0" hex = "0.4.3" -reqwest = { version = "0.12.4", features = ["json"] } +svg = "0.15" +ttf-parser = "0.24" +lyon = "1.0" +reqwest = { version = "0.12.4", features = ["json", "rustls-tls", "trust-dns"] } jsonrpc-core = "18.0" serde = { version = "1.0", features = ["serde_derive"] } futures = "0.3" -lazy_static = { version = "1.4" } +lazy_static = "1.4" +lazy-regex = "3.1.0" +chrono = { version = "0.4", features = ["serde"] } ckb-vm = { version = "0.24", features = ["asm"] } +axum = { version = "0.7", features = ["macros"] } spore-types = { git = "https://github.com/sporeprotocol/spore-contract", rev = "81315ca" } -jsonrpsee = { version = "0.22.3", features = ["server", "macros"], optional = true } +jsonrpsee = { version = "0.26", features = ["server", "macros"], optional = true } +tower-http = { version = "0.6", features = ["cors"], optional = true } +tower = { version = "0.5", optional = true } toml = { version = "0.8.2", optional = true } tokio = { version = "1.37", features = ["rt", "signal"], optional = true } tracing-subscriber = { version = "0.3.18", features = ["fmt", "env-filter"], optional = true } [features] default = ["standalone_server", "render_debug"] -standalone_server = ["jsonrpsee", "toml", "tokio", "tracing-subscriber"] +standalone_server = ["jsonrpsee", "toml", "tokio", "tracing-subscriber", "tower-http", "tower"] render_debug = [] diff --git a/README.md b/README.md index fb2b04a..aa3486f 100644 --- a/README.md +++ b/README.md @@ -40,13 +40,57 @@ $ RUST_LOG=dob_decoder_server=debug cargo run And then, try it out: +**Get protocol versions:** + +```bash +$ curl -H 'content-type: application/json' -d '{ + "id": 1, + "jsonrpc": "2.0", + "method": "dob_protocol_version", + "params": [] +}' http://localhost:8090 +``` + +**Decode a spore ID:** + ```bash $ echo '{ "id": 2, "jsonrpc": "2.0", "method": "dob_decode", "params": [ - "" + "4f7fb83a65dae9b95c21e55d5776a84f17bb6377681befeedb20a077ce1d8aad" + ] +}' \ +| curl -H 'content-type: application/json' -d @- \ +http://localhost:8090 +``` + +**Decode and extract SVG from another example spore ID on mainnet:** + +```bash +$ echo '{ + "id": 3, + "jsonrpc": "2.0", + "method": "dob_decode_svg", + "params": [ + "0x577bf0de0dcffe2811fa827480a700bc800c8e1e9606615b1484baeea2cba830" + ] +}' \ +| curl -H 'content-type: application/json' -d @- \ +http://localhost:8090 +``` + +**Extract image from a btcfs or ipfs path:** + +```bash +$ echo '{ + "id": 4, + "jsonrpc": "2.0", + "method": "dob_extract_image_from_fsuri", + "params": [ + "btcfs://5895004e95c8a4b80f05f5314d310067a703134515d82effc2ec6eba0dda3fc9i0", + "base64" ] }' \ | curl -H 'content-type: application/json' -d @- \ diff --git a/settings.mainnet.toml b/settings.mainnet.toml index fce017f..6360d57 100644 --- a/settings.mainnet.toml +++ b/settings.mainnet.toml @@ -7,6 +7,9 @@ protocol_versions = [ # connect to the RPC of CKB node ckb_rpc = "https://mainnet.ckb.dev/" +# connect to the image fetcher service +image_fetcher_url = { btcfs = "https://mempool.space/api/tx/", ipfs = "https://ipfs.io/ipfs/" } + # address that rpc server running at in case of standalone server mode rpc_server_address = "0.0.0.0:8090" @@ -19,6 +22,9 @@ dobs_cache_directory = "cache/dobs" # expiration time indicator for cleaning whole dobs cache, zero means never clean dobs_cache_expiration_sec = 300 +# expiration time in minutes dedicated for type_id and type_script decoder cache, zero means always update +decoders_cache_expiration_minutes = 1440 + # all deployed on-chain Spore contracts binary hash (order from new to old) # refer to: https://github.com/sporeprotocol/spore-contract/blob/master/docs/VERSIONS.md [[available_spores]] diff --git a/settings.toml b/settings.toml index 028c208..8d20400 100644 --- a/settings.toml +++ b/settings.toml @@ -7,11 +7,17 @@ protocol_versions = [ # connect to the RPC of CKB node ckb_rpc = "https://testnet.ckbapp.dev/" +# connect to the image fetcher service +image_fetcher_url = { btcfs = "https://mempool.space/api/tx/", ipfs = "https://ipfs.io/ipfs/" } + # address that rpc server running at in case of standalone server mode rpc_server_address = "0.0.0.0:8090" +# address that restful api server running at +restful_server_address = "0.0.0.0:8091" + # directory that stores decoders on hard-disk, including on-chain and off-chain binary files -decoders_cache_directory = "cache/decoders" +decoders_cache_directory = "cache/decoders/testnet" # directory that stores DOBs rendering results on hard-disk dobs_cache_directory = "cache/dobs" @@ -19,6 +25,9 @@ dobs_cache_directory = "cache/dobs" # expiration time indicator for cleaning whole dobs cache, zero means never clean dobs_cache_expiration_sec = 300 +# expiration time in minutes dedicated for type_id and type_script decoder cache, zero means always update +decoders_cache_expiration_minutes = 1440 + # all deployed on-chain Spore contracts binary hash (order from new to old) # refer to: https://github.com/sporeprotocol/spore-contract/blob/master/docs/VERSIONS.md [[available_spores]] diff --git a/src/client.rs b/src/client.rs index 1cd26e7..9159b2f 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::future::Future; use std::pin::Pin; use std::sync::atomic::{AtomicU64, Ordering}; @@ -9,7 +10,9 @@ use ckb_jsonrpc_types::{ use ckb_sdk::rpc::ckb_indexer::{Cell, Order, Pagination, SearchKey, Tx}; use ckb_types::H256; use jsonrpc_core::futures::FutureExt; +use lazy_regex::{regex, regex_replace_all}; use reqwest::{Client, Url}; +use serde_json::Value; use crate::types::Error; @@ -150,3 +153,166 @@ impl RpcClient { .boxed() } } + +#[derive(Clone)] +pub struct ImageFetchClient { + base_url: HashMap, +} + +impl ImageFetchClient { + pub fn new(base_url: &HashMap) -> Self { + Self { + base_url: base_url.clone(), + } + } + + pub async fn fetch_images(&self, images_uri: &[String]) -> Result>, Error> { + let mut requests = vec![]; + for uri in images_uri { + match uri.try_into()? { + URI::BTCFS(tx_hash, index) => { + let url = self + .base_url + .get("btcfs") + .ok_or(Error::FsuriNotFoundInConfig)? + .join(&tx_hash) + .expect("image url"); + requests.push(parse_image_from_btcfs(url, index).boxed()); + } + URI::IPFS(cid) => { + let url = self + .base_url + .get("ipfs") + .ok_or(Error::FsuriNotFoundInConfig)? + .join(&cid) + .expect("image url"); + requests.push( + async move { + let image = reqwest::get(url.clone()) + .await + .map_err(|e| Error::FetchFromIpfsError(e.to_string()))? + .bytes() + .await + .map_err(|e| Error::FetchFromIpfsError(e.to_string()))? + .to_vec(); + Ok(image) + } + .boxed(), + ); + } + } + } + let mut images = vec![]; + let responses = futures::future::join_all(requests).await; + for response in responses { + images.push(response?); + } + Ok(images) + } +} + +#[allow(clippy::upper_case_acronyms)] +enum URI { + BTCFS(String, usize), + IPFS(String), +} + +impl TryFrom<&String> for URI { + type Error = Error; + + fn try_from(uri: &String) -> Result { + if let Some(body) = uri.strip_prefix("btcfs://") { + let parts: Vec<&str> = body.split('i').collect::>(); + if parts.len() != 2 { + return Err(Error::InvalidOnchainFsuriFormat); + } + let tx_hash = parts[0].to_string(); + let index = parts[1] + .parse() + .map_err(|_| Error::InvalidOnchainFsuriFormat)?; + Ok(URI::BTCFS(tx_hash, index)) + } else if let Some(body) = uri.strip_prefix("ipfs://") { + let hash = body.to_string(); + Ok(URI::IPFS(hash)) + } else { + Err(Error::InvalidOnchainFsuriFormat) + } + } +} + +async fn parse_image_from_btcfs(url: Url, index: usize) -> Result, Error> { + // parse btc transaction + let btc_tx = reqwest::get(url) + .await + .map_err(|e| Error::FetchFromBtcNodeError(e.to_string()))? + .json::() + .await + .map_err(|e| Error::FetchFromBtcNodeError(e.to_string()))?; + let vin = btc_tx + .get("vin") + .ok_or(Error::InvalidBtcTransactionFormat( + "vin not found".to_string(), + ))? + .as_array() + .ok_or(Error::InvalidBtcTransactionFormat( + "vin not an array".to_string(), + ))? + .first() + .ok_or(Error::InvalidBtcTransactionFormat( + "vin is empty".to_string(), + ))?; + let witness = vin + .get("inner_witnessscript_asm") + .ok_or(Error::InvalidBtcTransactionFormat( + "inner_witnessscript_asm not found".to_string(), + ))? + .as_str() + .ok_or(Error::InvalidBtcTransactionFormat( + "inner_witnessscript_asm not a string".to_string(), + ))? + .to_owned(); + + // parse inscription body + let mut images = vec![]; + let mut witness_view = witness.as_str(); + + // flexible header pattern, e.g. OP_IF OP_PUSHBYTES_3 444f42 OP_PUSHBYTES_1 01 OP_PUSHBYTES_9 696d6167652f706e67 OP_0 OP_PUSHDATA2 + // note: arbitrary part is the specification of image type + let header_pattern = regex!( + r#"OP_IF\s+OP_PUSHBYTES_3\s+444f42\s+OP_PUSHBYTES_1\s+01\s+OP_PUSHBYTES_(\d+)\s+([0-9a-fA-F]+)\s+OP_0\s+OP_PUSHDATA2\s+"# + ); + + while let (Some(start), Some(end)) = (witness_view.find("OP_IF"), witness_view.find("OP_ENDIF")) + { + if start >= end { + return Err(Error::InvalidInscriptionFormat( + "bad start and end position".to_string(), + )); + } + let inscription = &witness_view[start..end + "OP_ENDIF".len()]; + + if let Some(captures) = header_pattern.captures(inscription) { + let matched_header = &captures[0]; + let base_removed = inscription.replace(matched_header, ""); + let hexed = regex_replace_all!(r#"\s?OP\_\w+\s?"#, &base_removed, ""); + let image = hex::decode(hexed.as_bytes()) + .map_err(|_| Error::InvalidInscriptionContentHexFormat)?; + images.push(image); + } else { + return Err(Error::InvalidInscriptionFormat( + "HEADER pattern not found".to_string(), + )); + } + + witness_view = &witness_view[end + "OP_ENDIF".len()..]; + } + if images.is_empty() { + return Err(Error::EmptyInscriptionContent); + } + + let image = images + .get(index) + .cloned() + .ok_or(Error::ExceededInscriptionIndex)?; + Ok(image) +} diff --git a/src/decoder/helpers.rs b/src/decoder/helpers.rs index 4755b1f..1748523 100644 --- a/src/decoder/helpers.rs +++ b/src/decoder/helpers.rs @@ -32,6 +32,26 @@ fn build_type_script_search_option(type_script: Script) -> CellQueryOptions { CellQueryOptions::new_type(type_script) } +fn file_older_than_minutes(file_path: &PathBuf, minutes: u64) -> bool { + if minutes == 0 { + return true; + } + let metadata = match std::fs::metadata(file_path) { + Ok(m) => m, + Err(_) => return true, // File doesn't exist or we can't access it, so it's "old". + }; + + let file_time = match metadata.modified().or_else(|_| metadata.created()) { + Ok(time) => time, + Err(_) => return true, // Can't get a timestamp, assume it's old. + }; + + match file_time.elapsed() { + Ok(elapsed) => elapsed.as_secs() >= minutes * 60, + Err(_) => true, // System clock is earlier than file time, assume it's old to be safe. + } +} + fn build_batch_search_options( type_args: &[u8; 32], available_script_ids: &[ScriptId], @@ -80,7 +100,9 @@ pub fn decode_spore_content(content: &[u8]) -> Result<(Value, String), Error> { return Ok((serde_json::Value::String(dna.clone()), dna)); } - let value: Value = serde_json::from_slice(content).map_err(|_| Error::DOBContentUnexpected)?; + // if content is not a valid JSON, treat it as a DNA string + let value: Value = serde_json::from_slice(content) + .unwrap_or(Value::String(String::from_utf8_lossy(content).to_string())); let dna = match &value { serde_json::Value::String(_) => &value, serde_json::Value::Array(array) => array.first().ok_or(Error::DOBContentUnexpected)?, @@ -267,7 +289,7 @@ pub async fn parse_decoder_path( DecoderLocationType::TypeId => { let hash = decoder.hash.as_ref().ok_or(Error::DecoderHashNotFound)?; decoder_path.push(format!("type_id_{}.bin", hex::encode(hash))); - if !decoder_path.exists() { + if file_older_than_minutes(&decoder_path, settings.decoders_cache_expiration_minutes) { let decoder_search_option = build_type_id_search_option(hash.clone().into()); let decoder_binary = fetch_decoder_binary(rpc, decoder_search_option).await?; std::fs::write(decoder_path.clone(), decoder_binary) @@ -284,7 +306,7 @@ pub async fn parse_decoder_path( "type_script_{}.bin", hex::encode(script.calc_script_hash().raw_data()) )); - if !decoder_path.exists() { + if file_older_than_minutes(&decoder_path, settings.decoders_cache_expiration_minutes) { let decoder_search_option = build_type_script_search_option(script); let decoder_binary = fetch_decoder_binary(rpc, decoder_search_option).await?; std::fs::write(decoder_path.clone(), decoder_binary) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index ee0ab0a..1ef83f9 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -11,6 +11,7 @@ use crate::{ pub(crate) mod helpers; use helpers::*; +#[derive(Clone)] pub struct DOBDecoder { rpc: RpcClient, settings: Settings, diff --git a/src/main.rs b/src/main.rs index 375225f..0862097 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ use std::fs; -use jsonrpsee::{server::ServerBuilder, tracing}; +use jsonrpsee::{server::Server, tracing}; use server::DecoderRpcServer; +use tower_http::cors::{Any, CorsLayer}; use tracing_subscriber::EnvFilter; mod client; @@ -25,21 +26,69 @@ async fn main() { "server settings: {}", serde_json::to_string_pretty(&settings).unwrap() ); + + tracing::info!("ensuring cache directories exist"); + fs::create_dir_all(&settings.decoders_cache_directory) + .expect("failed to create decoders cache directory"); + fs::create_dir_all(&settings.dobs_cache_directory) + .expect("failed to create DOBs cache directory"); + tracing::info!( + "decoders cache directory: {:?}", + settings.decoders_cache_directory + ); + tracing::info!("DOBs cache directory: {:?}", settings.dobs_cache_directory); + let rpc_server_address = settings.rpc_server_address.clone(); + let restful_server_address = settings.restful_server_address.clone(); let cache_expiration = settings.dobs_cache_expiration_sec; let decoder = decoder::DOBDecoder::new(settings); - tracing::info!("running decoder server at {}", rpc_server_address); - let http_server = ServerBuilder::new() - .http_only() - .build(rpc_server_address) + // Create the decoder server instance + let decoder_server = server::DecoderStandaloneServer::new(decoder, cache_expiration); + let cors = CorsLayer::new() + .allow_origin(Any) + .allow_methods(Any) + .allow_headers(Any); + + // Start JSON-RPC server + tracing::info!("running JSON-RPC decoder server at {}", rpc_server_address); + let http_middleware = tower::ServiceBuilder::new().layer(cors.clone()); + let http_server = Server::builder() + .set_http_middleware(http_middleware) + .build(rpc_server_address.clone()) .await .expect("build http_server"); - let rpc_methods = server::DecoderStandaloneServer::new(decoder, cache_expiration); - let handler = http_server.start(rpc_methods.into_rpc()); + let rpc_handler = http_server.start(decoder_server.clone().into_rpc()); + + // Start RESTful API server + let restful_handle = if let Some(restful_server_address) = restful_server_address { + tracing::info!("running RESTful API server at {}", restful_server_address); + + let app = server::DecoderStandaloneServer::create_restful_routes() + .with_state(decoder_server) + .layer(cors); + + let restful_listener = tokio::net::TcpListener::bind(&restful_server_address) + .await + .expect("Failed to bind RESTful server"); + + let restful_handle = tokio::spawn(async move { + axum::serve(restful_listener, app) + .await + .expect("RESTful server failed"); + }); + + Some(restful_handle) + } else { + None + }; tokio::signal::ctrl_c().await.unwrap(); - tracing::info!("stopping decoder server"); - handler.stop().unwrap(); + tracing::info!("stopping both servers"); + + rpc_handler.stop().unwrap(); + if let Some(restful_handle) = restful_handle { + restful_handle.abort(); + } } diff --git a/src/server.rs b/src/server/core.rs similarity index 61% rename from src/server.rs rename to src/server/core.rs index fdbc727..861f29c 100644 --- a/src/server.rs +++ b/src/server/core.rs @@ -1,18 +1,21 @@ -use std::fs; use std::path::PathBuf; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use base64::{engine::general_purpose::STANDARD, Engine}; use jsonrpsee::core::async_trait; use jsonrpsee::{proc_macros::rpc, tracing, types::error::ErrorObjectOwned}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; -use crate::decoder::helpers::{decode_cluster_data, decode_spore_data}; -use crate::decoder::DOBDecoder; +use crate::client::ImageFetchClient; +use crate::decoder::{ + helpers::{decode_cluster_data, decode_spore_data}, + DOBDecoder, +}; +use crate::server::utils::{read_dob_from_cache, trim_0x, write_dob_to_cache}; use crate::types::Error; // decoding result contains rendered result from native decoder and DNA string for optional use -#[derive(Serialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct ServerDecodeResult { render_output: String, dob_content: Value, @@ -38,41 +41,72 @@ trait DecoderRpc { spore_data: String, cluster_data: String, ) -> Result; + + #[method(name = "dob_extract_image_from_fsuri")] + async fn extract_image_from_fsuri( + &self, + fsuri: String, + encode_type: Option, + ) -> Result; } +#[async_trait] +impl DecoderRpcServer for DecoderStandaloneServer { + async fn protocol_versions(&self) -> Vec { + self.service_protocol_versions().await + } + + // decode DNA in particular spore DOB cell + async fn decode(&self, hexed_spore_id: String) -> Result { + self.service_decode(hexed_spore_id).await + } + + // decode DNA from a set + async fn batch_decode( + &self, + hexed_spore_ids: Vec, + ) -> Result, ErrorObjectOwned> { + self.service_batch_decode(hexed_spore_ids).await + } + + // decode directly from spore and cluster data + async fn raw_decode( + &self, + hexed_spore_data: String, + hexed_cluster_data: String, + ) -> Result { + self.service_raw_decode(hexed_spore_data, hexed_cluster_data) + .await + } + + async fn extract_image_from_fsuri( + &self, + fsuri: String, + encode_type: Option, + ) -> Result { + self.service_extract_image_from_fsuri(fsuri, encode_type) + .await + } +} + +#[derive(Clone)] pub struct DecoderStandaloneServer { decoder: DOBDecoder, cache_expiration: u64, + image_fetcher: ImageFetchClient, } impl DecoderStandaloneServer { pub fn new(decoder: DOBDecoder, cache_expiration: u64) -> Self { Self { + image_fetcher: ImageFetchClient::new(&decoder.setting().image_fetcher_url), decoder, cache_expiration, } } - async fn cache_decode( - &self, - spore_id: [u8; 32], - cache_path: PathBuf, - ) -> Result<(String, Value), Error> { - let (content, dna, metadata) = self.decoder.fetch_decode_ingredients(spore_id).await?; - let render_output = self.decoder.decode_dna(&dna, metadata).await?; - write_dob_to_cache(&render_output, &content, cache_path, self.cache_expiration)?; - Ok((render_output, content)) - } -} - -#[async_trait] -impl DecoderRpcServer for DecoderStandaloneServer { - async fn protocol_versions(&self) -> Vec { - self.decoder.protocol_versions() - } - - // decode DNA in particular spore DOB cell - async fn decode(&self, hexed_spore_id: String) -> Result { + /// Abstracted service method for decoding DOB to JSON + pub async fn service_decode(&self, hexed_spore_id: String) -> Result { tracing::info!("decoding spore_id {hexed_spore_id}"); let spore_id: [u8; 32] = hex::decode(trim_0x(&hexed_spore_id)) .map_err(|_| Error::HexedSporeIdParseError)? @@ -95,14 +129,14 @@ impl DecoderRpcServer for DecoderStandaloneServer { Ok(result) } - // decode DNA from a set - async fn batch_decode( + /// Abstracted service method for batch decoding + pub async fn service_batch_decode( &self, hexed_spore_ids: Vec, ) -> Result, ErrorObjectOwned> { let mut await_results = Vec::new(); for hexed_spore_id in hexed_spore_ids { - await_results.push(self.decode(hexed_spore_id)); + await_results.push(self.service_decode(hexed_spore_id)); } let results = futures::future::join_all(await_results) .await @@ -115,8 +149,8 @@ impl DecoderRpcServer for DecoderStandaloneServer { Ok(results) } - // decode directly from spore and cluster data - async fn raw_decode( + /// Abstracted service method for raw decoding + pub async fn service_raw_decode( &self, hexed_spore_data: String, hexed_cluster_data: String, @@ -136,67 +170,43 @@ impl DecoderRpcServer for DecoderStandaloneServer { tracing::info!("raw, result: {result}"); Ok(result) } -} -fn trim_0x(hexed: &str) -> &str { - hexed.trim_start_matches("0x") -} - -fn read_dob_from_cache( - cache_path: PathBuf, - mut expiration: u64, -) -> Result, Error> { - if !cache_path.exists() { - return Ok(None); - } - let file_content = fs::read_to_string(&cache_path) - .map_err(|_| Error::DOBRenderCacheNotFound(cache_path.clone()))?; - let mut lines = file_content.split('\n'); - let (Some(result), Some(content), timestamp) = (lines.next(), lines.next(), lines.next()) - else { - return Err(Error::DOBRenderCacheModified(cache_path)); - }; - if let Some(value) = timestamp { - if !value.is_empty() { - expiration = value - .parse::() - .map_err(|_| Error::DOBRenderCacheModified(cache_path.clone()))?; - } - } - match serde_json::from_str(content) { - Ok(content) => { - if expiration > 0 && now()? > Duration::from_secs(expiration) { - Ok(None) - } else { - Ok(Some((result.to_string(), content))) - } + /// Abstracted service method for image extraction + pub async fn service_extract_image_from_fsuri( + &self, + fsuri: String, + encode_type: Option, + ) -> Result { + let raw_images = self.image_fetcher.fetch_images(&[fsuri]).await?; + let image = raw_images.first().ok_or(Error::NoImageFound)?; + + match encode_type.as_deref() { + Some("hex") | None => Ok(hex::encode(image)), + Some("base64") => Ok(STANDARD.encode(image)), + unknown => Err(ErrorObjectOwned::owned::( + -1, + format!( + "Unknown encode type: {}. Supported types: 'base64', 'hex' (default)", + unknown.unwrap_or("unknown") + ), + None, + )), } - Err(_) => Err(Error::DOBRenderCacheModified(cache_path)), } -} -fn write_dob_to_cache( - render_result: &str, - dob_content: &Value, - cache_path: PathBuf, - cache_expiration: u64, -) -> Result<(), Error> { - let expiration_timestamp = if cache_expiration > 0 { - now()? - .checked_add(Duration::from_secs(cache_expiration)) - .ok_or(Error::SystemTimeError)? - .as_secs() - } else { - 0 // zero means always read from cache - }; - let json_dob_content = serde_json::to_string(dob_content).unwrap(); - let file_content = format!("{render_result}\n{json_dob_content}\n{expiration_timestamp}"); - fs::write(&cache_path, file_content).map_err(|_| Error::DOBRenderCacheNotFound(cache_path))?; - Ok(()) -} + /// Abstracted service method for protocol versions + pub async fn service_protocol_versions(&self) -> Vec { + self.decoder.protocol_versions() + } -fn now() -> Result { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .map_err(|_| Error::SystemTimeError) + async fn cache_decode( + &self, + spore_id: [u8; 32], + cache_path: PathBuf, + ) -> Result<(String, Value), Error> { + let (content, dna, metadata) = self.decoder.fetch_decode_ingredients(spore_id).await?; + let render_output = self.decoder.decode_dna(&dna, metadata).await?; + write_dob_to_cache(&render_output, &content, cache_path, self.cache_expiration)?; + Ok((render_output, content)) + } } diff --git a/src/server/jsonrpc.rs b/src/server/jsonrpc.rs new file mode 100644 index 0000000..aca101f --- /dev/null +++ b/src/server/jsonrpc.rs @@ -0,0 +1,81 @@ +use jsonrpsee::core::async_trait; +use jsonrpsee::{proc_macros::rpc, types::error::ErrorObjectOwned}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use crate::server::DecoderStandaloneServer; + +// decoding result contains rendered result from native decoder and DNA string for optional use +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ServerDecodeResult { + render_output: String, + dob_content: Value, +} + +#[rpc(server)] +trait DecoderRpc { + #[method(name = "dob_protocol_version")] + async fn protocol_versions(&self) -> Vec; + + #[method(name = "dob_decode")] + async fn decode(&self, hexed_spore_id: String) -> Result; + + #[method(name = "dob_batch_decode")] + async fn batch_decode( + &self, + hexed_spore_ids: Vec, + ) -> Result, ErrorObjectOwned>; + + #[method(name = "dob_raw_decode")] + async fn raw_decode( + &self, + spore_data: String, + cluster_data: String, + ) -> Result; + + #[method(name = "dob_extract_image")] + async fn extract_image( + &self, + fsuri: String, + encode_type: Option, + ) -> Result; +} + +#[async_trait] +impl DecoderRpcServer for DecoderStandaloneServer { + async fn protocol_versions(&self) -> Vec { + self.service_protocol_versions().await + } + + // decode DNA in particular spore DOB cell + async fn decode(&self, hexed_spore_id: String) -> Result { + self.service_decode(hexed_spore_id).await + } + + // decode DNA from a set + async fn batch_decode( + &self, + hexed_spore_ids: Vec, + ) -> Result, ErrorObjectOwned> { + self.service_batch_decode(hexed_spore_ids).await + } + + // decode directly from spore and cluster data + async fn raw_decode( + &self, + hexed_spore_data: String, + hexed_cluster_data: String, + ) -> Result { + self.service_raw_decode(hexed_spore_data, hexed_cluster_data) + .await + } + + async fn extract_image( + &self, + fsuri: String, + encode_type: Option, + ) -> Result { + self.service_extract_image_from_fsuri(fsuri, encode_type) + .await + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs new file mode 100644 index 0000000..8db9c4e --- /dev/null +++ b/src/server/mod.rs @@ -0,0 +1,7 @@ +mod core; +mod jsonrpc; +mod restful; +mod utils; + +pub use core::DecoderStandaloneServer; +pub use jsonrpc::DecoderRpcServer; diff --git a/src/server/restful.rs b/src/server/restful.rs new file mode 100644 index 0000000..c936fef --- /dev/null +++ b/src/server/restful.rs @@ -0,0 +1,119 @@ +use axum::{ + extract::{Path, Query, State}, + http::StatusCode, + response::{Html, IntoResponse}, + routing::get, + Router, +}; +use jsonrpsee::tracing; +use serde::Deserialize; + +use crate::server::DecoderStandaloneServer; + +#[derive(Deserialize)] +struct ExtractImageQuery { + uri: String, + encode: Option, +} + +// RESTful API implementation +impl DecoderStandaloneServer { + /// Create RESTful API routes + pub fn create_restful_routes() -> Router { + Router::new() + .route("/dob_decode/:spore_id", get(handle_dob_decode)) + .route("/dob_batch_decode/:spore_ids", get(handle_dob_batch_decode)) + .route("/dob_extract_image", get(handle_extract_image_from_fsuri)) + .route("/protocol_versions", get(handle_protocol_versions)) + } +} + +/// Handle dob_decode RESTful endpoint +async fn handle_dob_decode( + Path(spore_id): Path, + State(server): State, +) -> impl IntoResponse { + tracing::info!("RESTful API: decoding spore_id {}", spore_id); + + match server.service_decode(spore_id).await { + Ok(result) => { + tracing::info!("RESTful API: decode successful"); + (StatusCode::OK, Html(result)) + } + Err(error) => { + tracing::error!("RESTful API: decode failed: {}", error); + ( + StatusCode::BAD_REQUEST, + Html(format!("Error: {}", error.message())), + ) + } + } +} + +/// Handle dob_batch_decode RESTful endpoint +async fn handle_dob_batch_decode( + Path(spore_ids): Path, + State(server): State, +) -> impl IntoResponse { + tracing::info!("RESTful API: batch decoding spore_ids: {}", spore_ids); + + // Parse comma-separated spore IDs + let spore_id_list: Vec = spore_ids.split(',').map(|s| s.trim().to_string()).collect(); + + match server.service_batch_decode(spore_id_list).await { + Ok(results) => { + tracing::info!("RESTful API: batch decode successful"); + let json_result = serde_json::to_string(&results).unwrap_or_else(|_| "[]".to_string()); + (StatusCode::OK, Html(json_result)) + } + Err(error) => { + tracing::error!("RESTful API: batch decode failed: {}", error); + ( + StatusCode::BAD_REQUEST, + Html(format!("Error: {}", error.message())), + ) + } + } +} + +/// Handle dob_extract_image_from_fsuri RESTful endpoint +async fn handle_extract_image_from_fsuri( + Query(query): Query, + State(server): State, +) -> impl IntoResponse { + tracing::info!( + "RESTful API: extracting image from fsuri: {} with encode: {:?}", + query.uri, + query.encode + ); + + match server + .service_extract_image_from_fsuri(query.uri, query.encode) + .await + { + Ok(result) => { + tracing::info!("RESTful API: image extraction successful"); + (StatusCode::OK, Html(result)) + } + Err(error) => { + tracing::error!("RESTful API: image extraction failed: {}", error); + ( + StatusCode::BAD_REQUEST, + Html(format!("Error: {}", error.message())), + ) + } + } +} + +/// Handle protocol_versions RESTful endpoint +async fn handle_protocol_versions( + State(server): State, +) -> impl IntoResponse { + tracing::info!("RESTful API: getting protocol versions"); + + let versions = server.service_protocol_versions().await; + let json_result = serde_json::to_string(&versions).unwrap_or_else(|_| "[]".to_string()); + + tracing::info!("RESTful API: protocol versions retrieved successfully"); + (StatusCode::OK, Html(json_result)) +} diff --git a/src/server/utils.rs b/src/server/utils.rs new file mode 100644 index 0000000..9e692f1 --- /dev/null +++ b/src/server/utils.rs @@ -0,0 +1,69 @@ +use serde_json::Value; +use std::fs; +use std::path::PathBuf; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use crate::types::Error; + +pub fn trim_0x(hexed: &str) -> &str { + hexed.trim_start_matches("0x") +} + +pub fn read_dob_from_cache( + cache_path: PathBuf, + mut expiration: u64, +) -> Result, Error> { + if !cache_path.exists() { + return Ok(None); + } + let file_content = fs::read_to_string(&cache_path) + .map_err(|_| Error::DOBRenderCacheNotFound(cache_path.clone()))?; + let mut lines = file_content.split('\n'); + let (Some(result), Some(content), timestamp) = (lines.next(), lines.next(), lines.next()) + else { + return Err(Error::DOBRenderCacheModified(cache_path)); + }; + if let Some(value) = timestamp { + if !value.is_empty() { + expiration = value + .parse::() + .map_err(|_| Error::DOBRenderCacheModified(cache_path.clone()))?; + } + } + match serde_json::from_str(content) { + Ok(content) => { + if expiration > 0 && now()? > Duration::from_secs(expiration) { + Ok(None) + } else { + Ok(Some((result.to_string(), content))) + } + } + Err(_) => Err(Error::DOBRenderCacheModified(cache_path)), + } +} + +pub fn write_dob_to_cache( + render_result: &str, + dob_content: &Value, + cache_path: PathBuf, + cache_expiration: u64, +) -> Result<(), Error> { + let expiration_timestamp = if cache_expiration > 0 { + now()? + .checked_add(Duration::from_secs(cache_expiration)) + .ok_or(Error::SystemTimeError)? + .as_secs() + } else { + 0 // zero means always read from cache + }; + let json_dob_content = serde_json::to_string(dob_content).unwrap(); + let file_content = format!("{render_result}\n{json_dob_content}\n{expiration_timestamp}"); + fs::write(&cache_path, file_content).map_err(|_| Error::DOBRenderCacheNotFound(cache_path))?; + Ok(()) +} + +pub fn now() -> Result { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|_| Error::SystemTimeError) +} diff --git a/src/tests/dob0/decoder.rs b/src/tests/dob0/decoder.rs index 98c3755..7c0a294 100644 --- a/src/tests/dob0/decoder.rs +++ b/src/tests/dob0/decoder.rs @@ -2,7 +2,7 @@ use ckb_types::{h256, H256}; use serde_json::{json, Value}; use crate::decoder::DOBDecoder; -use crate::tests::prepare_settings; +use crate::tests::{prepare_settings, SettingType}; use crate::types::{ ClusterDescriptionField, DOBClusterFormat, DOBClusterFormatV0, DOBDecoderFormat, DecoderLocationType, @@ -84,7 +84,7 @@ fn generate_example_dob_ingredients(onchain_decoder: bool) -> (Value, ClusterDes #[tokio::test] async fn test_fetch_and_decode_unicorn_dna() { - let settings = prepare_settings("text/plain"); + let settings = prepare_settings(SettingType::Testnet, vec!["text/plain"]); let decoder = DOBDecoder::new(settings); let (_, dna, dob_metadata) = decoder .fetch_decode_ingredients(UNICORN_SPORE_ID.into()) @@ -115,7 +115,7 @@ fn test_unicorn_json_serde() { #[tokio::test] async fn test_fetch_and_decode_example_dna() { - let settings = prepare_settings("text/plain"); + let settings = prepare_settings(SettingType::Testnet, vec!["text/plain"]); let decoder = DOBDecoder::new(settings); let (_, dna, dob_metadata) = decoder .fetch_decode_ingredients(EXAMPLE_SPORE_ID.into()) diff --git a/src/tests/dob0/legacy_decoder.rs b/src/tests/dob0/legacy_decoder.rs index 2e96379..d7f70cc 100644 --- a/src/tests/dob0/legacy_decoder.rs +++ b/src/tests/dob0/legacy_decoder.rs @@ -1,7 +1,7 @@ use ckb_types::{h256, H256}; use crate::decoder::{helpers::decode_spore_content, DOBDecoder}; -use crate::tests::prepare_settings; +use crate::tests::{prepare_settings, SettingType}; use crate::types::{ ClusterDescriptionField, DOBClusterFormat, DOBClusterFormatV0, DOBDecoderFormat, DecoderLocationType, @@ -79,11 +79,11 @@ fn generate_unicorn_dob_ingredients(onchain_decoder: bool) -> (Value, ClusterDes } async fn decode_unicorn_dna(onchain_decoder: bool) -> String { - let settings = prepare_settings("text/plain"); + let settings = prepare_settings(SettingType::Testnet, vec!["text/plain"]); let decoder = DOBDecoder::new(settings); let (unicorn_content, unicorn_metadata) = generate_unicorn_dob_ingredients(onchain_decoder); decoder - .decode_dna(&unicorn_content["dna"].as_str().unwrap(), unicorn_metadata) + .decode_dna(unicorn_content["dna"].as_str().unwrap(), unicorn_metadata) .await .expect("decode") } @@ -99,7 +99,7 @@ async fn test_decode_unicorn_dna() { #[tokio::test] async fn test_fetch_and_decode_nervape_dna() { - let settings = prepare_settings("text/plain"); + let settings = prepare_settings(SettingType::Testnet, vec!["text/plain"]); let decoder = DOBDecoder::new(settings); let (_, dna, dob_metadata) = decoder .fetch_decode_ingredients(NERVAPE_SPORE_ID.into()) @@ -116,7 +116,7 @@ async fn test_fetch_and_decode_nervape_dna() { #[tokio::test] #[should_panic = "fetch: DOBVersionUnexpected"] async fn test_fetch_onchain_dob_failed() { - let settings = prepare_settings("dob/0"); + let settings = prepare_settings(SettingType::Testnet, vec!["dob/0"]); DOBDecoder::new(settings) .fetch_decode_ingredients(NERVAPE_SPORE_ID.into()) .await @@ -164,8 +164,8 @@ fn test_decode_multiple_spore_data() { .into_iter() .enumerate() .for_each(|(i, spore_data)| { - let (_, v) = - decode_spore_content(spore_data.as_bytes()).expect(&format!("assert type index {i}")); + let (_, v) = decode_spore_content(spore_data.as_bytes()) + .unwrap_or_else(|_| panic!("assert type index {i}")); assert_eq!(v, dna, "object type comparison failed"); }); } diff --git a/src/tests/dob1/decoder.rs b/src/tests/dob1/decoder.rs index 84e5b98..2b9622e 100644 --- a/src/tests/dob1/decoder.rs +++ b/src/tests/dob1/decoder.rs @@ -3,7 +3,7 @@ use serde_json::{json, Value}; use crate::{ decoder::DOBDecoder, - tests::prepare_settings, + tests::{prepare_settings, SettingType}, types::{ ClusterDescriptionField, DOBClusterFormat, DOBClusterFormatV0, DOBClusterFormatV1, DOBDecoderFormat, DecoderLocationType, @@ -60,7 +60,7 @@ fn test_print_dob1_ingreidents() { #[tokio::test] async fn test_dob1_basic_decode() { - let settings = prepare_settings("dob/1"); + let settings = prepare_settings(SettingType::Testnet, vec![]); let (content, dob_metadata) = generate_dob1_ingredients(); let decoder = DOBDecoder::new(settings); let dna = content.get("dna").unwrap().as_str().unwrap(); diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 0616f35..78b0eff 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,64 +1,31 @@ -use ckb_types::h256; +use std::fs; -use crate::types::{HashType, OnchainDecoderDeployment, ScriptId, Settings}; +use crate::types::Settings; mod dob0; mod dob1; -fn prepare_settings(version: &str) -> Settings { - Settings { - ckb_rpc: "https://testnet.ckbapp.dev/".to_string(), - protocol_versions: vec![version.to_string()], - decoders_cache_directory: "cache/decoders".parse().unwrap(), - dobs_cache_directory: "cache/dobs".parse().unwrap(), - available_spores: vec![ - ScriptId { - code_hash: h256!( - "0x685a60219309029d01310311dba953d67029170ca4848a4ff638e57002130a0d" - ), - hash_type: HashType::Data1, - }, - ScriptId { - code_hash: h256!( - "0x5e063b4c0e7abeaa6a428df3b693521a3050934cf3b0ae97a800d1bc31449398" - ), - hash_type: HashType::Data1, - }, - ], - available_clusters: vec![ - ScriptId { - code_hash: h256!( - "0x0bbe768b519d8ea7b96d58f1182eb7e6ef96c541fbd9526975077ee09f049058" - ), - hash_type: HashType::Data1, - }, - ScriptId { - code_hash: h256!( - "0x7366a61534fa7c7e6225ecc0d828ea3b5366adec2b58206f2ee84995fe030075" - ), - hash_type: HashType::Data1, - }, - ], - onchain_decoder_deployment: vec![ - OnchainDecoderDeployment { - code_hash: h256!( - "0xb82abd59ade361a014f0abb692f71b0feb880693c3ccb95b9137b73551d872ce" - ), - tx_hash: h256!( - "0xb2497dc3e616055125ef8276be7ee21986d2cd4b2ce90992725386cabcb6ea7f" - ), - out_index: 0, - }, - OnchainDecoderDeployment { - code_hash: h256!( - "0x32f29aba4b17f3d05bec8cec55d50ef86766fd0bf82fdedaa14269f344d3784a" - ), - tx_hash: h256!( - "0x987cf95d129a2dcc2cdf7bd387c1bd888fa407e3c5a3d511fd80c80dcf6c6b67" - ), - out_index: 0, - }, - ], - ..Default::default() - } +#[allow(dead_code)] +pub enum SettingType { + Mainnet, + Testnet, +} + +const TESTNET_SETTINGS_FILE: &str = "./settings.toml"; +const MAINNET_SETTINGS_FILE: &str = "./settings.mainnet.toml"; + +fn prepare_settings(setting_type: SettingType, extra_version: Vec<&str>) -> Settings { + let settings_file = match setting_type { + SettingType::Mainnet => { + fs::read_to_string(MAINNET_SETTINGS_FILE).expect("read settings.mainnet.toml") + } + SettingType::Testnet => { + fs::read_to_string(TESTNET_SETTINGS_FILE).expect("read settings.toml") + } + }; + let mut settings: Settings = toml::from_str(&settings_file).unwrap(); + settings + .protocol_versions + .extend(extra_version.iter().map(|v| v.to_string())); + settings } diff --git a/src/types.rs b/src/types.rs index fb72861..bf3c82d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,8 +1,9 @@ -use std::path::PathBuf; +use std::{collections::HashMap, path::PathBuf}; use ckb_jsonrpc_types::Script; use ckb_types::{core::ScriptHashType, H256}; -use serde::{ser::SerializeMap, Deserialize}; +use reqwest::Url; +use serde::{ser::SerializeMap, Deserialize, Deserializer, Serializer}; use serde_json::Value; #[cfg(feature = "standalone_server")] @@ -75,6 +76,30 @@ pub enum Error { DecoderScriptNotFound, #[error("decoders configured in cluster are empty, please check your cluster config")] DecoderChainIsEmpty, + #[error("BTC node responsed badly with error: {0}")] + FetchFromBtcNodeError(String), + #[error("BTC transaction format has broken: {0}")] + InvalidBtcTransactionFormat(String), + #[error("Inscription format broken: {0}")] + InvalidInscriptionFormat(String), + #[error("Inscription content must be hex format")] + InvalidInscriptionContentHexFormat, + #[error("Inscription content must be filled")] + EmptyInscriptionContent, + #[error("Inscription index flag exceeded")] + ExceededInscriptionIndex, + #[error("fs header like 'btcfs://' and 'ckbfs://' are not contained")] + InvalidOnchainFsuriFormat, + #[error("fs header like 'btcfs://' and 'ckbfs://' are not configured in config file")] + FsuriNotFoundInConfig, + #[error("IPFS Gateway responsed badly with error: {0}")] + FetchFromIpfsError(String), + #[error("HTTP(S) responsed badly with error: {0}")] + FetchFromHttpError(String), + #[error("No image found")] + NoImageFound, + #[error("DOB render output is not in format of DOB protocol")] + DOBRenderOutputInvalid, } pub enum Dob<'a> { @@ -94,6 +119,7 @@ impl From for ErrorObjectOwned { #[derive(Deserialize)] #[cfg_attr(test, derive(serde::Serialize, PartialEq, Debug))] pub struct ClusterDescriptionField { + #[allow(dead_code)] pub description: String, pub dob: DOBClusterFormat, } @@ -209,6 +235,7 @@ pub struct DOBDecoderFormat { // asscoiate `code_hash` of decoder binary with its onchain deployment information #[cfg_attr(feature = "standalone_server", derive(Serialize, Deserialize))] #[cfg_attr(test, derive(Default))] +#[derive(Clone)] pub struct OnchainDecoderDeployment { pub code_hash: H256, pub tx_hash: H256, @@ -217,6 +244,7 @@ pub struct OnchainDecoderDeployment { #[cfg_attr(feature = "standalone_server", derive(Serialize, Deserialize))] #[cfg_attr(test, derive(Default))] +#[derive(Clone)] pub enum HashType { #[serde(rename(serialize = "data", deserialize = "data"))] #[cfg_attr(test, default)] @@ -242,6 +270,7 @@ impl From<&HashType> for ScriptHashType { #[cfg_attr(feature = "standalone_server", derive(Serialize, Deserialize))] #[cfg_attr(test, derive(Default))] +#[derive(Clone)] pub struct ScriptId { pub code_hash: H256, pub hash_type: HashType, @@ -250,13 +279,24 @@ pub struct ScriptId { // standalone server settings in TOML format #[cfg_attr(feature = "standalone_server", derive(Serialize, Deserialize))] #[cfg_attr(test, derive(Default))] +#[derive(Clone)] pub struct Settings { pub protocol_versions: Vec, pub ckb_rpc: String, + #[cfg_attr( + feature = "standalone_server", + serde( + serialize_with = "serialize_fetcher", + deserialize_with = "deserialize_fetcher" + ) + )] + pub image_fetcher_url: HashMap, pub rpc_server_address: String, + pub restful_server_address: Option, pub decoders_cache_directory: PathBuf, pub dobs_cache_directory: PathBuf, pub dobs_cache_expiration_sec: u64, + pub decoders_cache_expiration_minutes: u64, pub onchain_decoder_deployment: Vec, pub available_spores: Vec, pub available_clusters: Vec, @@ -275,6 +315,7 @@ pub struct ParsedTrait { pub value: Value, } +#[cfg(feature = "standalone_server")] impl Serialize for ParsedTrait { fn serialize(&self, serializer: S) -> Result where @@ -286,6 +327,7 @@ impl Serialize for ParsedTrait { } } +#[cfg(feature = "standalone_server")] impl<'de> Deserialize<'de> for ParsedTrait { fn deserialize(deserializer: D) -> Result where @@ -303,3 +345,29 @@ impl<'de> Deserialize<'de> for ParsedTrait { .unwrap_or_else(|| Err(serde::de::Error::custom("invalid ParsedTrait"))) } } + +#[cfg(feature = "standalone_server")] +fn serialize_fetcher(urls: &HashMap, serializer: S) -> Result +where + S: Serializer, +{ + let mut map = serializer.serialize_map(Some(urls.len()))?; + for (key, url) in urls { + map.serialize_entry(key, &url.as_str())?; + } + map.end() +} + +#[cfg(feature = "standalone_server")] +fn deserialize_fetcher<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let map: HashMap = HashMap::deserialize(deserializer)?; + let mut urls = HashMap::new(); + for (key, url_str) in map { + let url = Url::parse(&url_str).map_err(serde::de::Error::custom)?; + urls.insert(key, url); + } + Ok(urls) +}