diff --git a/Cargo.lock b/Cargo.lock index a657408..e0de3e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -168,6 +168,12 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arraystring" version = "0.3.0" @@ -178,6 +184,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "async-stream" version = "0.3.6" @@ -361,12 +373,40 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -408,21 +448,6 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - -[[package]] -name = "castaway" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" -dependencies = [ - "rustversion", -] - [[package]] name = "cc" version = "1.2.31" @@ -494,15 +519,6 @@ dependencies = [ "strsim", ] -[[package]] -name = "clap_complete" -version = "4.5.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a" -dependencies = [ - "clap", -] - [[package]] name = "clap_derive" version = "4.5.41" @@ -551,20 +567,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "compact_str" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "rustversion", - "ryu", - "static_assertions", -] - [[package]] name = "const_format" version = "0.2.34" @@ -585,6 +587,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "core-foundation" version = "0.9.4" @@ -644,47 +652,6 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" -[[package]] -name = "crossterm" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" -dependencies = [ - "bitflags", - "crossterm_winapi", - "libc", - "mio 0.8.11", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" -dependencies = [ - "bitflags", - "crossterm_winapi", - "mio 1.0.4", - "parking_lot", - "rustix 0.38.44", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -dependencies = [ - "winapi", -] - [[package]] name = "crypto-common" version = "0.1.6" @@ -749,6 +716,18 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive-ex" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bba95f299f6b9cd47f68a847eca2ae9060a2713af532dc35c342065544845407" +dependencies = [ + "proc-macro2", + "quote", + "structmeta", + "syn 2.0.104", +] + [[package]] name = "derive-where" version = "1.5.0" @@ -935,12 +914,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foreign-types" version = "0.3.2" @@ -1146,11 +1119,6 @@ name = "hashbrown" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] [[package]] name = "heck" @@ -1447,12 +1415,6 @@ dependencies = [ "rayon", ] -[[package]] -name = "indoc" -version = "2.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" - [[package]] name = "inout" version = "0.1.4" @@ -1462,19 +1424,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "instability" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" -dependencies = [ - "darling", - "indoc", - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "interpolator" version = "0.5.0" @@ -1507,15 +1456,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.14.0" @@ -1618,12 +1558,6 @@ dependencies = [ "libc", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -1652,15 +1586,6 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" -[[package]] -name = "lru" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -dependencies = [ - "hashbrown 0.15.4", -] - [[package]] name = "manyhow" version = "0.11.4" @@ -1765,18 +1690,6 @@ dependencies = [ "adler2", ] -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "log", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.48.0", -] - [[package]] name = "mio" version = "1.0.4" @@ -1784,7 +1697,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "log", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -1825,8 +1737,6 @@ dependencies = [ [[package]] name = "neptune-cash" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0420017f9e5e1c2e9d255a9bd4da9a78989fe0d8f9be550560859d2735d0756" dependencies = [ "aead", "aes-gcm", @@ -1840,8 +1750,6 @@ dependencies = [ "bytesize", "chrono", "clap", - "clap_complete", - "crossterm 0.27.0", "directories", "field_count", "futures", @@ -1856,7 +1764,6 @@ dependencies = [ "priority-queue", "rand 0.9.2", "rand_distr", - "ratatui", "rayon", "readonly", "regex", @@ -1867,8 +1774,8 @@ dependencies = [ "serde_derive", "serde_json", "sha3", - "strum 0.27.2", - "strum_macros 0.27.2", + "strum", + "strum_macros", "sysinfo", "systemstat", "tarpc", @@ -1882,7 +1789,6 @@ dependencies = [ "tracing", "tracing-subscriber", "tracing-test", - "unicode-width 0.1.14", "zeroize", ] @@ -1891,8 +1797,10 @@ name = "neptune-explorer" version = "0.1.0" dependencies = [ "anyhow", + "arbitrary", "arc-swap", "axum", + "blake3", "boilerplate", "chrono", "clap", @@ -1901,10 +1809,14 @@ dependencies = [ "indexmap 2.10.0", "lettre", "neptune-cash", + "proptest", + "proptest-arbitrary-interop", + "rand 0.9.2", "readonly", "serde", "serde_json", "tarpc", + "test-strategy", "thiserror 1.0.69", "tokio", "tower-http", @@ -2210,12 +2122,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pbkdf2" version = "0.11.0" @@ -2403,6 +2309,36 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "lazy_static", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax 0.8.5", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "proptest-arbitrary-interop" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1981e49bd2432249da8b0e11e5557099a8e74690d6b94e721f7dc0bb7f3555f" +dependencies = [ + "arbitrary", + "proptest", +] + [[package]] name = "psm" version = "0.1.26" @@ -2412,6 +2348,12 @@ dependencies = [ "cc", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.40" @@ -2525,24 +2467,12 @@ dependencies = [ ] [[package]] -name = "ratatui" -version = "0.29.0" +name = "rand_xorshift" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "bitflags", - "cassowary", - "compact_str", - "crossterm 0.28.1", - "indoc", - "instability", - "itertools 0.13.0", - "lru", - "paste", - "strum 0.26.3", - "unicode-segmentation", - "unicode-truncate", - "unicode-width 0.2.0", + "rand_core 0.9.3", ] [[package]] @@ -2668,19 +2598,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - [[package]] name = "rustix" version = "1.0.8" @@ -2690,7 +2607,7 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys 0.9.4", + "linux-raw-sys", "windows-sys 0.60.2", ] @@ -2700,6 +2617,18 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.20" @@ -2858,28 +2787,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "signal-hook" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" -dependencies = [ - "libc", - "mio 0.8.11", - "mio 1.0.4", - "signal-hook", -] - [[package]] name = "signal-hook-registry" version = "1.4.6" @@ -2949,12 +2856,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "strum" -version = "0.26.3" +name = "structmeta" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" dependencies = [ - "strum_macros 0.26.4", + "proc-macro2", + "quote", + "structmeta-derive", + "syn 2.0.104", +] + +[[package]] +name = "structmeta-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] @@ -2963,20 +2884,7 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros 0.27.2", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.104", + "strum_macros", ] [[package]] @@ -3116,7 +3024,7 @@ dependencies = [ "rand 0.9.2", "serde", "serde_json", - "strum 0.27.2", + "strum", "tasm-object-derive", "triton-vm", ] @@ -3141,10 +3049,23 @@ dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.8", + "rustix", "windows-sys 0.59.0", ] +[[package]] +name = "test-strategy" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b12f9683de37f9980e485167ee624bfaa0b6b04da661e98e25ef9c2669bc1b" +dependencies = [ + "derive-ex", + "proc-macro2", + "quote", + "structmeta", + "syn 2.0.104", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -3293,7 +3214,7 @@ dependencies = [ "bytes", "io-uring", "libc", - "mio 1.0.4", + "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -3514,7 +3435,7 @@ checksum = "973a0422b170667a558ae36e21643ba827e76d2c663607087f0797888f4c59ad" dependencies = [ "arbitrary", "itertools 0.14.0", - "strum 0.27.2", + "strum", "triton-constraint-circuit", "triton-isa", "twenty-first", @@ -3530,7 +3451,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "strum 0.27.2", + "strum", "syn 2.0.104", "triton-air", "triton-constraint-circuit", @@ -3567,7 +3488,7 @@ dependencies = [ "nom-language", "num-traits", "serde", - "strum 0.27.2", + "strum", "thiserror 2.0.12", "twenty-first", ] @@ -3592,7 +3513,7 @@ dependencies = [ "rand 0.9.2", "rayon", "serde", - "strum 0.27.2", + "strum", "syn 2.0.104", "thiserror 2.0.12", "triton-air", @@ -3600,7 +3521,7 @@ dependencies = [ "triton-constraint-circuit", "triton-isa", "twenty-first", - "unicode-width 0.2.0", + "unicode-width", ] [[package]] @@ -3636,6 +3557,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicase" version = "2.8.1" @@ -3657,29 +3584,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-truncate" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" -dependencies = [ - "itertools 0.13.0", - "unicode-segmentation", - "unicode-width 0.1.14", -] - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - [[package]] name = "unicode-width" version = "0.2.0" @@ -3743,6 +3647,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index f1d1efb..0032c16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,3 +38,17 @@ derive_more = { version = "1.0.0", features = ["display"] } # not a direct dep. workaround for weird "could not resolve" cargo error indexmap = "2.7.0" +blake3 = {version = "1.8.2", optional = true} +rand = {version = "0.9.2", optional = true} + +#[dev-dependencies] +test-strategy = "0.4.3" +proptest = "1.7.0" +arbitrary = "1.4.1" +proptest-arbitrary-interop = "0.1.0" + +[patch.crates-io] +neptune-cash = {path = "../neptune-core/neptune-core/" } + +[features] +mock = ["dep:blake3", "dep:rand"] \ No newline at end of file diff --git a/README.md b/README.md index c366e43..2d7bf6b 100644 --- a/README.md +++ b/README.md @@ -41,13 +41,18 @@ Notes: * The block-explorer automatically uses the same network (mainnet, testnet, etc) as the neptune-core instance it is connected to, and the network is displayed in the web interface. * If neptune-core RPC server is running on a non-standard port, you can provide it with the `--neptune-rpc-port` flag. * neptune-explorer listens for http requests on port 3000 by default. This can be changed with the `--listen-port` flag. -* Site name can be specified with the `--site-name` flag. +* Site name must be specified with the `--site-name` flag. ## Connecting via Browser Just navigate to http://localhost:3000/ +## Mocking + +When connected to an out-of-date or unsynced neptune-core node, it might be a good idea to turn on mocking so that whenever a resource is unavailable, a random one is generated and returned. To do this, compile with the feature flag "mock" and make sure that the "MOCK" environment variable is set. + +In one command: `MOCK=1 cargo run --features "mock" -- --site-name testname` ## SSL/TLS, Nginx, etc. diff --git a/src/html/component/header.rs b/src/html/component/header.rs index 02fee1a..b340b79 100644 --- a/src/html/component/header.rs +++ b/src/html/component/header.rs @@ -1,7 +1,7 @@ use crate::model::app_state::AppStateInner; use html_escaper::Escape; -#[derive(boilerplate::Boilerplate)] +#[derive(Debug, Clone, boilerplate::Boilerplate)] #[boilerplate(filename = "web/html/components/header.html")] pub struct HeaderHtml<'a> { pub state: &'a AppStateInner, diff --git a/src/html/page/announcement.rs b/src/html/page/announcement.rs new file mode 100644 index 0000000..0077faf --- /dev/null +++ b/src/html/page/announcement.rs @@ -0,0 +1,87 @@ +use crate::html::component::header::HeaderHtml; +use crate::html::page::not_found::not_found_html_response; +use crate::http_util::rpc_method_err; +use crate::model::announcement_selector::AnnouncementSelector; +use crate::model::announcement_type::AnnouncementType; +use crate::model::app_state::AppState; +use axum::extract::rejection::PathRejection; +use axum::extract::Path; +use axum::extract::State; +use axum::response::Html; +use axum::response::Response; +use html_escaper::Escape; +use html_escaper::Trusted; +use neptune_cash::api::export::BlockHeight; +use neptune_cash::prelude::tasm_lib::prelude::Digest; +use neptune_cash::prelude::triton_vm::prelude::BFieldCodec; +use neptune_cash::prelude::twenty_first::tip5::Tip5; +use std::sync::Arc; +use tarpc::context; + +#[axum::debug_handler] +pub async fn announcement_page( + maybe_path: Result, PathRejection>, + State(state_rw): State>, +) -> Result, Response> { + #[derive(Debug, Clone, boilerplate::Boilerplate)] + #[boilerplate(filename = "web/html/page/announcement.html")] + pub struct AnnouncementHtmlPage<'a> { + header: HeaderHtml<'a>, + index: usize, + num_announcements: usize, + block_hash: Digest, + block_height: BlockHeight, + announcement_type: AnnouncementType, + } + + let state = &state_rw.load(); + + let Path(AnnouncementSelector { + block_selector, + index, + }) = maybe_path.map_err(|e| not_found_html_response(state, Some(e.to_string())))?; + + let block_info = state + .rpc_client + .block_info(context::current(), state.token(), block_selector) + .await + .map_err(|e| not_found_html_response(state, Some(e.to_string())))? + .map_err(rpc_method_err)? + .ok_or(not_found_html_response( + state, + Some("The requested block does not exist".to_string()), + ))?; + let block_hash = block_info.digest; + let block_height = block_info.height; + + let announcements = state + .rpc_client + .announcements_in_block(context::current(), state.token(), block_selector) + .await + .map_err(|e| not_found_html_response(state, Some(e.to_string())))? + .map_err(rpc_method_err)? + .expect( + "block guaranteed to exist because we got here; getting its announcements should work", + ); + let num_announcements = announcements.len(); + let announcement = announcements + .get(index) + .ok_or(not_found_html_response( + state, + Some("The requested announcement does not exist".to_string()), + ))? + .clone(); + let announcement_type = AnnouncementType::parse(announcement); + + let header = HeaderHtml { state }; + + let utxo_page = AnnouncementHtmlPage { + index, + header, + block_hash, + block_height, + num_announcements, + announcement_type, + }; + Ok(Html(utxo_page.to_string())) +} diff --git a/src/html/page/mod.rs b/src/html/page/mod.rs index 9384efe..7742e65 100644 --- a/src/html/page/mod.rs +++ b/src/html/page/mod.rs @@ -1,3 +1,4 @@ +pub mod announcement; pub mod block; pub mod not_found; pub mod redirect_qs_to_path; diff --git a/src/main.rs b/src/main.rs index dbe186e..7feaa66 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use axum::routing::get; use axum::routing::post; use axum::routing::Router; use neptune_explorer::alert_email; +use neptune_explorer::html::page::announcement::announcement_page; use neptune_explorer::html::page::block::block_page; use neptune_explorer::html::page::not_found::not_found_html_fallback; use neptune_explorer::html::page::redirect_qs_to_path::redirect_query_string_to_path; @@ -62,6 +63,7 @@ pub fn setup_routes(app_state: AppState) -> Router { .route("/", get(root)) .route("/block/*selector", get(block_page)) .route("/utxo/:value", get(utxo_page)) + .route("/announcement/*selector", get(announcement_page)) // -- Rewrite query-strings to path -- .route("/rqs", get(redirect_query_string_to_path)) // -- Static files -- diff --git a/src/model/announcement_selector.rs b/src/model/announcement_selector.rs new file mode 100644 index 0000000..278b400 --- /dev/null +++ b/src/model/announcement_selector.rs @@ -0,0 +1,336 @@ +use neptune_cash::api::export::BlockHeight; +use neptune_cash::models::blockchain::block::block_selector::BlockSelector; +use neptune_cash::prelude::tasm_lib::prelude::Digest; +use serde::de::Error; +use serde::Deserialize; +use serde::Deserializer; +use std::fmt::Display; +use std::str::FromStr; + +/// newtype for `BlockSelector` that provides ability to parse `height_or_digest/value`. +/// +/// This is useful for HTML form(s) that allow user to enter either height or +/// digest into the same text input field. +/// +/// In particular it is necessary to support javascript-free website with such +/// an html form. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AnnouncementSelector { + pub block_selector: BlockSelector, + pub index: usize, +} + +#[derive(Debug, Clone, thiserror::Error)] +pub enum AnnouncementSelectorParseError { + #[error("too many or too few parts in announcement path")] + NumberOfParts, + #[error("error parsing index for announcement in tip: {0}")] + TipIndex(std::num::ParseIntError), + #[error("error parsing index for announcement in genesis block: {0}")] + GenesisIndex(std::num::ParseIntError), + #[error("error parsing block height: {0}")] + BlockHeight(std::num::ParseIntError), + #[error("error parsing index for announcement in block {0}: {1}")] + HeightIndex(BlockHeight, std::num::ParseIntError), + #[error("error parsing digest: {0}")] + BlockDigest(neptune_cash::prelude::twenty_first::error::TryFromHexDigestError), + #[error("error parsing index for announcement in block {0}: {1}")] + DigestIndex(Digest, std::num::ParseIntError), + #[error("error parsing block-height-or-digest: {0} / {1}")] + HeightNorDigest( + std::num::ParseIntError, + neptune_cash::prelude::twenty_first::error::TryFromHexDigestError, + ), + #[error("error parsing index for block-height-or-digest {0}: {1}")] + HeightOrDigestIndex(BlockSelector, std::num::ParseIntError), + #[error("invalid keyword {0} or {1}")] + InvalidKeyword(String, String), + #[error("invalid prefix: {0}")] + InvalidPrefix(String), +} + +impl FromStr for AnnouncementSelector { + type Err = AnnouncementSelectorParseError; + + fn from_str(input: &str) -> Result { + let parts: Vec<&str> = input.split('/').collect(); + + let (block_selector, index) = match parts.as_slice() { + ["tip", index] => { + let index = index.parse::().map_err(Self::Err::TipIndex)?; + (BlockSelector::Tip, index) + } + ["genesis", index] => index + .parse::() + .map(|i| (BlockSelector::Genesis, i)) + .map_err(Self::Err::GenesisIndex)?, + ["height", number, index] => { + let height_as_u64 = number.parse::().map_err(Self::Err::BlockHeight)?; + let block_height = BlockHeight::from(height_as_u64); + ( + BlockSelector::Height(block_height), + index + .parse() + .map_err(|e| Self::Err::HeightIndex(block_height, e))?, + ) + } + ["digest", hash, index] => { + let digest = Digest::try_from_hex(hash).map_err(Self::Err::BlockDigest)?; + let index = index + .parse::() + .map_err(|e| Self::Err::DigestIndex(digest, e))?; + (BlockSelector::Digest(digest), index) + } + ["height_or_digest", hod, "index", index] => { + let parsed_height = hod.parse::(); + let parsed_digest = Digest::try_from_hex(hod); + + let block_selector = match (parsed_height, parsed_digest) { + (Ok(_), Ok(digest)) => { + // unreachable? Not in theory ... + BlockSelector::Digest(digest) + } + (Ok(h), Err(_)) => BlockSelector::Height(BlockHeight::from(h)), + (Err(_), Ok(digest)) => BlockSelector::Digest(digest), + (Err(pie), Err(hde)) => { + return Err(Self::Err::HeightNorDigest(pie, hde)); + } + }; + + let index = index + .parse::() + .map_err(|e| Self::Err::HeightOrDigestIndex(block_selector, e))?; + (block_selector, index) + } + [prefix, _, keyword, _] => { + return Err(Self::Err::InvalidKeyword( + prefix.to_string(), + keyword.to_string(), + )) + } + [prefix, _, _] | [prefix, _] => { + return Err(Self::Err::InvalidPrefix(prefix.to_string())) + } + &[] | &[_] | &[_, _, _, _, _, ..] => return Err(Self::Err::NumberOfParts), + }; + + Ok(AnnouncementSelector { + block_selector, + index: index as usize, + }) + } +} + +impl Display for AnnouncementSelector { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.block_selector { + BlockSelector::Digest(digest) => write!(f, "digest/{digest:x}/{}", self.index), + BlockSelector::Height(block_height) => { + write!(f, "height/{}/{}", block_height, self.index) + } + BlockSelector::Genesis => write!(f, "genesis/{}", self.index), + BlockSelector::Tip => write!(f, "tip/{}", self.index), + } + } +} + +// note: axum uses serde Deserialize for Path elements. +impl<'de> Deserialize<'de> for AnnouncementSelector { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Self::from_str(&s).map_err(D::Error::custom) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use arbitrary::{Arbitrary, Unstructured}; + use proptest::string::string_regex; + use proptest::{prop_assert, prop_assert_eq}; + use proptest_arbitrary_interop::arb; + use std::str::FromStr; + use test_strategy::proptest; + + impl<'a> Arbitrary<'a> for AnnouncementSelector { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + // Pick one of the variants randomly + let variant = u.int_in_range(0..=3)?; + + let selector = match variant { + 0 => { + // Digest selector + let digest = Digest::arbitrary(u)?; + let index = u64::arbitrary(u)? as usize; + AnnouncementSelector { + block_selector: BlockSelector::Digest(digest), + index, + } + } + 1 => { + // Height selector + let height_u64 = u64::arbitrary(u)?; + let bh = BlockHeight::from(height_u64); + let index = u64::arbitrary(u)? as usize; + AnnouncementSelector { + block_selector: BlockSelector::Height(bh), + index, + } + } + 2 => { + // Genesis selector + let index = u64::arbitrary(u)? as usize; + AnnouncementSelector { + block_selector: BlockSelector::Genesis, + index, + } + } + 3 => { + // Tip selector + let index = u64::arbitrary(u)? as usize; + AnnouncementSelector { + block_selector: BlockSelector::Tip, + index, + } + } + _ => unreachable!(), + }; + + Ok(selector) + } + + fn size_hint(_depth: usize) -> (usize, Option) { + // Not very precise, but enough for fuzzing/proptesting + (1, None) + } + } + + #[proptest] + fn display_roundtrip(#[strategy(arb())] announcement_selector: AnnouncementSelector) { + let as_string = announcement_selector.to_string(); + let parsed = AnnouncementSelector::from_str(&as_string).unwrap(); + prop_assert_eq!(announcement_selector, parsed.clone()); + + let as_string_again = parsed.to_string(); + prop_assert_eq!(as_string, as_string_again); + } + + #[proptest] + fn parse_height_or_digest_digest( + #[strategy(arb())] digest: Digest, + #[strategy(0usize..20)] index: usize, + ) { + let str = format!("height_or_digest/{digest:x}/index/{index}"); + AnnouncementSelector::from_str(&str).unwrap(); // no crash + } + + #[proptest] + fn parse_height_or_digest_height( + #[strategy(arb())] block_height: u64, + #[strategy(0usize..20)] index: usize, + ) { + let str = format!("height_or_digest/{block_height}/index/{index}"); + AnnouncementSelector::from_str(&str).unwrap(); // no crash + } + + #[proptest] + fn parse_invalid_number_of_parts(s: String) { + // Strings with fewer than 2 parts OR more than 3 parts should fail + let parts: Vec<&str> = s.split('/').collect(); + if !(2..=4).contains(&parts.len()) { + let res = AnnouncementSelector::from_str(&s); + prop_assert!(matches!( + res, + Err(AnnouncementSelectorParseError::NumberOfParts) + )); + } + } + + #[proptest] + fn parse_invalid_tip_index(#[strategy(string_regex("tip/[a-z]+").unwrap())] s: String) { + let res = AnnouncementSelector::from_str(&s); + prop_assert!(matches!( + res, + Err(AnnouncementSelectorParseError::TipIndex(_)) + )); + } + + #[proptest] + fn parse_invalid_genesis_index(#[strategy(string_regex("genesis/[a-z]+").unwrap())] s: String) { + let res = AnnouncementSelector::from_str(&s); + prop_assert!(matches!( + res, + Err(AnnouncementSelectorParseError::GenesisIndex(_)) + )); + } + + #[proptest] + fn parse_invalid_height_number( + #[strategy(string_regex("height/[a-z]+/0").unwrap())] s: String, + ) { + let res = AnnouncementSelector::from_str(&s); + prop_assert!(matches!( + res, + Err(AnnouncementSelectorParseError::BlockHeight(_)) + )); + } + + #[proptest] + fn parse_invalid_height_index( + #[strategy(string_regex("height/42/[a-z]+").unwrap())] s: String, + ) { + let res = AnnouncementSelector::from_str(&s); + if let Err(AnnouncementSelectorParseError::HeightIndex(_, _)) = res { + // OK + } else { + panic!("Expected HeightIndex error, got {res:?}"); + } + } + + #[proptest] + fn parse_invalid_digest_hex( + #[strategy(string_regex("digest/z[0-9a-f]{79}/0").unwrap())] s: String, + ) { + let res = AnnouncementSelector::from_str(&s); + prop_assert!(matches!( + res, + Err(AnnouncementSelectorParseError::BlockDigest(_)) + )); + } + + #[proptest] + fn parse_invalid_digest_length_too_short( + #[strategy(string_regex("digest/[0-9a-f]{0,79}/0").unwrap())] s: String, + ) { + let res = AnnouncementSelector::from_str(&s); + prop_assert!(matches!( + res, + Err(AnnouncementSelectorParseError::BlockDigest(_)) + )); + } + + #[proptest] + fn parse_invalid_digest_length_too_long( + #[strategy(string_regex("digest/[0-9a-f]{81,200}/0").unwrap())] s: String, + ) { + let res = AnnouncementSelector::from_str(&s); + prop_assert!(matches!( + res, + Err(AnnouncementSelectorParseError::BlockDigest(_)) + )); + } + + #[proptest] + fn parse_invalid_digest_index(#[strategy(arb())] digest: Digest) { + let s = format!("digest/{digest:x}/notanumber"); + let res = AnnouncementSelector::from_str(&s); + prop_assert!(matches!( + res, + Err(AnnouncementSelectorParseError::DigestIndex(_, _)) + )); + } +} diff --git a/src/model/announcement_type.rs b/src/model/announcement_type.rs new file mode 100644 index 0000000..e6dab29 --- /dev/null +++ b/src/model/announcement_type.rs @@ -0,0 +1,29 @@ +use neptune_cash::api::export::Announcement; +use neptune_cash::api::export::TransparentTransactionInfo; +use neptune_cash::prelude::triton_vm::prelude::BFieldElement; + +#[derive(Debug, Clone)] +pub enum AnnouncementType { + Unknown(Vec), + TransparentTxInfo(TransparentTransactionInfo), +} + +impl AnnouncementType { + pub fn parse(announcement: Announcement) -> Self { + if let Ok(transparent_transaction_info) = + TransparentTransactionInfo::try_from_announcement(&announcement) + { + Self::TransparentTxInfo(transparent_transaction_info) + } else { + Self::Unknown(announcement.message) + } + } + + pub fn name(&self) -> String { + match self { + AnnouncementType::Unknown(_) => "unknown", + AnnouncementType::TransparentTxInfo(_) => "transparent transaction info", + } + .to_string() + } +} diff --git a/src/model/app_state.rs b/src/model/app_state.rs index 67190c9..a5f5855 100644 --- a/src/model/app_state.rs +++ b/src/model/app_state.rs @@ -9,6 +9,7 @@ use neptune_cash::prelude::twenty_first::tip5::Digest; use neptune_cash::rpc_auth; use std::sync::Arc; +#[derive(Debug, Clone)] pub struct AppStateInner { pub network: Network, pub config: Config, diff --git a/src/model/mod.rs b/src/model/mod.rs index a3c8ed6..8f7447a 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,3 +1,5 @@ +pub mod announcement_selector; +pub mod announcement_type; pub mod app_state; pub mod block_selector_extended; pub mod config; diff --git a/src/neptune_rpc.rs b/src/neptune_rpc.rs index aa58bff..c6f187b 100644 --- a/src/neptune_rpc.rs +++ b/src/neptune_rpc.rs @@ -6,12 +6,17 @@ use chrono::DateTime; use chrono::TimeDelta; use chrono::Utc; use clap::Parser; +use neptune_cash::api::export::Announcement; use neptune_cash::config_models::data_directory::DataDirectory; use neptune_cash::config_models::network::Network; use neptune_cash::models::blockchain::block::block_height::BlockHeight; +use neptune_cash::models::blockchain::block::block_info::BlockInfo; +use neptune_cash::models::blockchain::block::block_selector::BlockSelector; +use neptune_cash::prelude::tasm_lib::prelude::Digest; use neptune_cash::rpc_auth; use neptune_cash::rpc_server::error::RpcError; use neptune_cash::rpc_server::RPCClient; +use neptune_cash::rpc_server::RpcResult; use std::net::Ipv4Addr; use std::net::SocketAddr; use tarpc::client; @@ -19,6 +24,10 @@ use tarpc::context; use tarpc::tokio_serde::formats::Json as RpcJson; use tracing::{debug, info, warn}; +#[cfg(feature = "mock")] +const MOCK_KEY: &str = "MOCK"; + +#[derive(Debug, Clone)] pub struct AuthenticatedClient { pub client: RPCClient, pub token: rpc_auth::Token, @@ -33,6 +42,122 @@ impl std::ops::Deref for AuthenticatedClient { } } +impl AuthenticatedClient { + /// Intercept and relay call to [`RPCClient::block_info`] + pub async fn block_info( + &self, + ctx: ::tarpc::context::Context, + token: rpc_auth::Token, + block_selector: BlockSelector, + ) -> ::core::result::Result>, ::tarpc::client::RpcError> { + let rpc_result = self.client.block_info(ctx, token, block_selector).await; + + // if the RPC call was successful, return that + if let Ok(Ok(Some(_))) = rpc_result { + return rpc_result; + } + + // if MOCK environment variable is set and feature is enabled, + // imagine some mock block info + #[cfg(feature = "mock")] + if std::env::var(MOCK_KEY).is_ok() { + use blake3::Hasher; + use rand::rngs::StdRng; + use rand::Rng; + use rand::SeedableRng; + tracing::warn!("RPC query failed and MOCK flag set, so returning an imagined block"); + let mut hasher = Hasher::new(); + hasher.update(&block_selector.to_string().bytes().collect::>()); + let mut rng = StdRng::from_seed(*hasher.finalize().as_bytes()); + let mut block_info: BlockInfo = rng.random(); + match block_selector { + BlockSelector::Digest(digest) => { + block_info.digest = digest; + } + BlockSelector::Height(height) => { + block_info.height = height; + } + _ => {} + }; + return Ok(Ok(Some(block_info))); + } + + // otherwise, return the original error + rpc_result + } + + /// Intercept and relay call to [`RPCClient::utxo_digest`] + pub async fn utxo_digest( + &self, + ctx: ::tarpc::context::Context, + token: rpc_auth::Token, + leaf_index: u64, + ) -> ::core::result::Result>, ::tarpc::client::RpcError> { + self.client.utxo_digest(ctx, token, leaf_index).await + } + + /// Intercept and relay call to [`RPCClient::announcements_in_block`] + pub async fn announcements_in_block( + &self, + ctx: ::tarpc::context::Context, + token: rpc_auth::Token, + block_selector: BlockSelector, + ) -> Result>, RpcError>, ::tarpc::client::RpcError> { + let rpc_result = self + .client + .announcements_in_block(ctx, token, block_selector) + .await; + + // if the RPC call was successful, return that + if let Ok(Ok(Some(_))) = rpc_result { + return rpc_result; + } + + // if MOCK environment variable is set and feature is enabled, + // imagine some mock block info + #[cfg(feature = "mock")] + if std::env::var(MOCK_KEY).is_ok() { + use blake3::Hasher; + use neptune_cash::api::export::TransparentTransactionInfo; + use neptune_cash::prelude::triton_vm::prelude::BFieldElement; + use rand::rngs::StdRng; + use rand::Rng; + use rand::SeedableRng; + tracing::warn!("RPC query failed and MOCK flag set, so returning an imagined block"); + let mut hasher = Hasher::new(); + hasher.update(&block_selector.to_string().bytes().collect::>()); + let mut rng = StdRng::from_seed(*hasher.finalize().as_bytes()); + + // make sure the number of announcements matches with the block + let block_info = self + .block_info(ctx, token, block_selector) + .await + .unwrap() + .unwrap() + .unwrap(); + let num_announcements = block_info.num_announcements; + + let mut announcements = vec![]; + for _ in 0..num_announcements { + let announcement = if rng.random_bool(0.5_f64) { + let message = (0..rng.random_range(0..256)) + .map(|_| rng.random::()) + .collect::>(); + Announcement::new(message) + } else { + rng.random::().to_announcement() + }; + announcements.push(announcement); + } + + return Ok(Ok(Some(announcements))); + } + + // otherwise, return the original error + rpc_result + } +} + /// generates RPCClient, for querying neptune-core RPC server. pub async fn gen_authenticated_rpc_client() -> Result { let client = gen_rpc_client().await?; diff --git a/templates/web/html/page/announcement.html b/templates/web/html/page/announcement.html new file mode 100644 index 0000000..9b20743 --- /dev/null +++ b/templates/web/html/page/announcement.html @@ -0,0 +1,150 @@ + + + + {{self.header.state.config.site_name}}: Announcement {{self.block_height}}/{{self.index}} + {{html_escaper::Trusted(include_str!( concat!(env!("CARGO_MANIFEST_DIR"), + "/templates/web/html/components/head.html")))}} + + + + {{Trusted(self.header.to_string())}} + +
+ +
+

Announcement +

+

+ Metadata +

+ + + + + + + + + + + + + + + + + +
Block Height{{self.block_height}}
Block Hash{{self.block_hash.to_hex()}}
Index{{self.index}}/{{self.num_announcements}}
Type{{self.announcement_type.name()}}
+

Payload

+ {% match &self.announcement_type { AnnouncementType::TransparentTxInfo(tx_info) => { %} +
+ Transparent Transaction Info + + + + + {% for input in tx_info.inputs.iter() { %} + + + + + {% } %} +
inputs
+
+ + {{input.addition_record().canonical_commitment.to_hex()}} + + + + + + + + + + + + + + +
UTXO digest:{{Tip5::hash(&input.utxo).to_hex()}}
sender randomness:{{input.sender_randomness.to_hex()}}
receiver preimage:{{input.receiver_preimage.to_hex()}}
+
+
+ {{input.utxo.get_native_currency_amount().display_n_decimals(5)}} + NPT +
+ + + + + {% for output in tx_info.outputs.iter() { %} + + + + + {% } %} +
outputs
+
+ + {{output.addition_record().canonical_commitment.to_hex()}} + + + + + + + + + + + + + + +
UTXO digest:{{Tip5::hash(&output.utxo).to_hex()}}
sender randomness:{{output.sender_randomness.to_hex()}}
receiver digest:{{output.receiver_digest.to_hex()}}
+
+
+ {{output.utxo.get_native_currency_amount().display_n_decimals(5)}} + NPT +
+
+ {% }, AnnouncementType::Unknown(payload) => { %} +
+ Unknown Type + {% for chunk in payload.encode().chunks(4) { %} +

+ {% for d in chunk { %} + {{ format!("{:016x}", d.value()) }} + {% } %} +

+ {% } %} +
+ {% }, } %} +
+ + + +
+ + + \ No newline at end of file diff --git a/templates/web/html/page/block_info.html b/templates/web/html/page/block_info.html index 831944b..53f1df2 100644 --- a/templates/web/html/page/block_info.html +++ b/templates/web/html/page/block_info.html @@ -3,140 +3,164 @@ {{self.header.state.config.site_name}}: Block Height {{self.block_info.height}} -{{html_escaper::Trusted(include_str!(concat!(env!("CARGO_MANIFEST_DIR"),"/templates/web/html/components/head.html")))}} + {{html_escaper::Trusted(include_str!(concat!(env!("CARGO_MANIFEST_DIR"),"/templates/web/html/components/head.html")))}} -{{Trusted(self.header.to_string())}} + {{Trusted(self.header.to_string())}} -
+
-
-

Block height: {{self.block_info.height}}

+
+

Block height: {{self.block_info.height}}

- -%% if self.block_info.is_genesis { -

This is the genesis block

-%% } -%% if self.block_info.is_tip { -

This is the latest block (tip)

-%% } + + %% if self.block_info.is_genesis { +

This is the genesis block

+ %% } + %% if self.block_info.is_tip { +

This is the latest block (tip)

+ %% } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -%% if self.block_info.coinbase_amount != self.block_info.expected_coinbase_amount() { - - - - -%% } - - - - - - -
Digest{{self.block_info.digest.to_hex()}}
Created{{self.block_info.timestamp.standard_format()}}
Size - ⓘ - Unit: number of BFieldElements. One BFieldElement consists of 8 bytes. - - {{self.block_info.size}}
Inputs{{self.block_info.num_inputs}}
Outputs{{self.block_info.num_outputs}}
Difficulty{{self.block_info.difficulty}}
Cumulative Proof-Of-Work - ⓘ - estimated total # of hashes performed by miners from genesis block to this block. - - {{self.block_info.cumulative_proof_of_work}}
Coinbase - ⓘ - Total block reward amount paid to the miner(s) that found this block. - - {{self.block_info.coinbase_amount}}
Expected Coinbase - ⓘ - Expected (maximum) block reward amount paid to the miner(s) that find a block at this block-height. - - {{self.block_info.expected_coinbase_amount()}}
Fee{{self.block_info.fee}}
Canonical - ⓘ - - The canonical blockchain is the chain with the most accumulated proof-of-work and is considered the - official record of transaction history. - - - - %% if self.block_info.is_canonical { - Yes. This block is in the canonical blockchain. - %% } else { - No. This block is not in the canonical blockchain. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %% if self.block_info.coinbase_amount != self.block_info.expected_coinbase_amount() { + + + + %% } - - - - - - + + + + + + + + + + + + -
Digest{{self.block_info.digest.to_hex()}}
Created{{self.block_info.timestamp.standard_format()}}
Size + ⓘ + Unit: number of BFieldElements. One BFieldElement consists of 8 + bytes. + + {{self.block_info.size}}
Inputs{{self.block_info.num_inputs}}
Outputs{{self.block_info.num_outputs}}
Announcements + ⓘ + + Data broadcast as part of the transaction. + + + + {% if self.block_info.num_announcements > 0 { %} + {{self.block_info.num_announcements}} + {% } else { %} + 0 + {% } %} +
Difficulty{{self.block_info.difficulty}}
Cumulative Proof-Of-Work + ⓘ + estimated total # of hashes performed by miners from genesis block + to this block. + + {{self.block_info.cumulative_proof_of_work}}
Coinbase + ⓘ + Total block reward amount paid to the miner(s) that found this + block. + + {{self.block_info.coinbase_amount}}
Expected Coinbase + ⓘ + Expected (maximum) block reward amount paid to the miner(s) that + find a block at this block-height. + + {{self.block_info.expected_coinbase_amount()}}
Sibling Blocks - ⓘ - - Blocks that exist at the same height as this block. Only one sibling can be in the canonical blockchain. - - - -%% for sibling_digest in self.block_info.sibling_blocks.iter().map(|d| d.to_hex()) { -{{sibling_digest}}
-%% } -
Fee{{self.block_info.fee}}
Canonical + ⓘ + + The canonical blockchain is the chain with the most accumulated proof-of-work and is + considered the + official record of transaction history. + + + + %% if self.block_info.is_canonical { + Yes. This block is in the canonical blockchain. + %% } else { + No. This block is not in the canonical blockchain. + %% } +
Sibling Blocks + ⓘ + + Blocks that exist at the same height as this block. Only one sibling can be in the + canonical blockchain. + + + + %% for sibling_digest in self.block_info.sibling_blocks.iter().map(|d| d.to_hex()) { + {{sibling_digest}}
+ %% } +
+
-
+
-
+
-

- Home - | Genesis - | Tip -%% if self.block_info.is_genesis { - | Previous Block -%% } else { - | Previous Block -%% } +

+ Home + | Genesis + | Tip + %% if self.block_info.is_genesis { + | Previous Block + %% } else { + | Previous Block + %% } -%% if self.block_info.is_tip { - | Next Block -%% } else { - | Next Block -%% } -

+ %% if self.block_info.is_tip { + | Next Block + %% } else { + | Next Block + %% } +

-
-
+ +
- + + \ No newline at end of file diff --git a/templates/web/html/page/root.html b/templates/web/html/page/root.html index dd64f38..a4f3827 100644 --- a/templates/web/html/page/root.html +++ b/templates/web/html/page/root.html @@ -1,113 +1,152 @@ + {{self.state.config.site_name}}: (network: {{self.state.network}}) -{{ html_escaper::Trusted(include_str!( concat!(env!("CARGO_MANIFEST_DIR"), "/templates/web/html/components/head.html"))) }} + {{ html_escaper::Trusted(include_str!( concat!(env!("CARGO_MANIFEST_DIR"), + "/templates/web/html/components/head.html"))) }} + -
-

- - {{self.state.config.site_name}} (network: {{self.state.network}}) -

-The blockchain tip is at height: {{self.tip_height}} -
+
+

+ + {{self.state.config.site_name}} (network: {{self.state.network}}) +

+ The blockchain tip is at height: {{self.tip_height}} +
-
+
-
-
- - Block Lookup - -
- - -ⓘ - - Provide a numeric block height or hexadecimal digest identifier to lookup any block in the Neptune blockchain. - - +
+
+ + Block Lookup + + + + + ⓘ + + Provide a numeric block height or hexadecimal digest identifier to lookup any block in the + Neptune blockchain. + + -Block height or digest: - - - + Block height or digest: + + + -Quick Lookup: - Genesis Block | - Tip
-
-
+ Quick Lookup: + Genesis Block | + Tip
+
+
-
-
-UTXO Lookup -
- - ⓘ - - An Unspent Transaction Output (UTXO) index can be found in the output of neptune-cli wallet-status. Look for the field: aocl_leaf_index - - - UTXO index: - - -
-
-
+
+
+ UTXO Lookup +
+ + ⓘ + + An Unspent Transaction Output (UTXO) index can be found in the output of neptune-cli + wallet-status. Look for the field: aocl_leaf_index + + + UTXO index: + + +
+
+
-
-
-REST RPCs -
-RPC endpoints are available for automating block explorer queries: -
+
+
+ Announcement Lookup +
+ + + ⓘ + + A numeric block height or hexadecimal digest to identify the block in which the announcement + lives. + + + Block height or digest: + -
-/block_info -
-

Examples

+ ⓘ + + The index of the announcement within the block (as a block can have many announcements). + + + Announcement index: + + + +
+
- - -
+
+
+ REST RPCs +
+ RPC endpoints are available for automating block explorer queries: +
-
-/block_digest - +
-
-/utxo_digest - +
-
-
+
+ /utxo_digest +
+

Examples

-
+ + + + + + + +
- + + \ No newline at end of file