feat: Add Announcement viewer
Every transaction can have zero or more announcements, which are essentially messages that must be included across all future mergers and updates. Announcements are typically used for transmitting information related to receiving UTXOs, encrypted. However, a new use case is that of *transparent transaction info*, which is an announcement containing the UTXOs and the commitment randomnesses needed to reproduce the transaction's inputs and outputs. With such a transparent transaction info announcement, third parties can transparently audit transactions. This commit adds a page for viewing announcements, and if the announcement can be parsed as a transparent transaction info type announcement then it is rendered as such, complete with native currency amounts and linkable UTXOs (where possible). The path for accessing announcements is any of - `/announcement/digest/<hex-string>/<index>` - `/announcement/tip/<index>` - `/announcement/genesis/<index>` - `/announcement/height/<height>/<index>` - `/announcement/height_or_digest/<height-or-diges>/index/<index>`. The last bullet point exists to support the quick lookup functionality, which is also new. A `AnnouncementSelector` has display and from-string methods relating these path formats to the relevant object. A suite of (prop)tests verifies parsing. Also, this commit enables mocking, which is useful when the neptune-core node the explorer is connected to is outdated, unsynced, or for whatever reason does not serve the desired data. The RPC client call is intercepted, and if it fails and mocking is enabled, an imagined (pseudorandom) resource of the requested type is returned. To enable mocking, compile with the "mock" feature flag and set the "MOCK" environment variable.
This commit is contained in:
parent
8fa76ec8a3
commit
86698687a8
435
Cargo.lock
generated
435
Cargo.lock
generated
@ -168,6 +168,12 @@ version = "1.7.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
|
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayref"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arraystring"
|
name = "arraystring"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@ -178,6 +184,12 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-stream"
|
name = "async-stream"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
@ -361,12 +373,40 @@ dependencies = [
|
|||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.9.1"
|
version = "2.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
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]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
@ -408,21 +448,6 @@ version = "1.3.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659"
|
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]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.31"
|
version = "1.2.31"
|
||||||
@ -494,15 +519,6 @@ dependencies = [
|
|||||||
"strsim",
|
"strsim",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_complete"
|
|
||||||
version = "4.5.55"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a"
|
|
||||||
dependencies = [
|
|
||||||
"clap",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.41"
|
version = "4.5.41"
|
||||||
@ -551,20 +567,6 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"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]]
|
[[package]]
|
||||||
name = "const_format"
|
name = "const_format"
|
||||||
version = "0.2.34"
|
version = "0.2.34"
|
||||||
@ -585,6 +587,12 @@ dependencies = [
|
|||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "constant_time_eq"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@ -644,47 +652,6 @@ version = "0.8.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
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]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
@ -749,6 +716,18 @@ dependencies = [
|
|||||||
"powerfmt",
|
"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]]
|
[[package]]
|
||||||
name = "derive-where"
|
name = "derive-where"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@ -935,12 +914,6 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foldhash"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types"
|
name = "foreign-types"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@ -1146,11 +1119,6 @@ name = "hashbrown"
|
|||||||
version = "0.15.4"
|
version = "0.15.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||||
dependencies = [
|
|
||||||
"allocator-api2",
|
|
||||||
"equivalent",
|
|
||||||
"foldhash",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
@ -1447,12 +1415,6 @@ dependencies = [
|
|||||||
"rayon",
|
"rayon",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "indoc"
|
|
||||||
version = "2.0.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inout"
|
name = "inout"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@ -1462,19 +1424,6 @@ dependencies = [
|
|||||||
"generic-array",
|
"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]]
|
[[package]]
|
||||||
name = "interpolator"
|
name = "interpolator"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@ -1507,15 +1456,6 @@ dependencies = [
|
|||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itertools"
|
|
||||||
version = "0.13.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@ -1618,12 +1558,6 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "linux-raw-sys"
|
|
||||||
version = "0.4.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@ -1652,15 +1586,6 @@ version = "0.4.27"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
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]]
|
[[package]]
|
||||||
name = "manyhow"
|
name = "manyhow"
|
||||||
version = "0.11.4"
|
version = "0.11.4"
|
||||||
@ -1765,18 +1690,6 @@ dependencies = [
|
|||||||
"adler2",
|
"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]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
@ -1784,7 +1697,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
|
||||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
@ -1825,8 +1737,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "neptune-cash"
|
name = "neptune-cash"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e0420017f9e5e1c2e9d255a9bd4da9a78989fe0d8f9be550560859d2735d0756"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aead",
|
"aead",
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
@ -1840,8 +1750,6 @@ dependencies = [
|
|||||||
"bytesize",
|
"bytesize",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
|
||||||
"crossterm 0.27.0",
|
|
||||||
"directories",
|
"directories",
|
||||||
"field_count",
|
"field_count",
|
||||||
"futures",
|
"futures",
|
||||||
@ -1856,7 +1764,6 @@ dependencies = [
|
|||||||
"priority-queue",
|
"priority-queue",
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
"rand_distr",
|
"rand_distr",
|
||||||
"ratatui",
|
|
||||||
"rayon",
|
"rayon",
|
||||||
"readonly",
|
"readonly",
|
||||||
"regex",
|
"regex",
|
||||||
@ -1867,8 +1774,8 @@ dependencies = [
|
|||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha3",
|
"sha3",
|
||||||
"strum 0.27.2",
|
"strum",
|
||||||
"strum_macros 0.27.2",
|
"strum_macros",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"systemstat",
|
"systemstat",
|
||||||
"tarpc",
|
"tarpc",
|
||||||
@ -1882,7 +1789,6 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"tracing-test",
|
"tracing-test",
|
||||||
"unicode-width 0.1.14",
|
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1891,8 +1797,10 @@ name = "neptune-explorer"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"arbitrary",
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"axum",
|
"axum",
|
||||||
|
"blake3",
|
||||||
"boilerplate",
|
"boilerplate",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
@ -1901,10 +1809,14 @@ dependencies = [
|
|||||||
"indexmap 2.10.0",
|
"indexmap 2.10.0",
|
||||||
"lettre",
|
"lettre",
|
||||||
"neptune-cash",
|
"neptune-cash",
|
||||||
|
"proptest",
|
||||||
|
"proptest-arbitrary-interop",
|
||||||
|
"rand 0.9.2",
|
||||||
"readonly",
|
"readonly",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tarpc",
|
"tarpc",
|
||||||
|
"test-strategy",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
@ -2210,12 +2122,6 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"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]]
|
[[package]]
|
||||||
name = "pbkdf2"
|
name = "pbkdf2"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@ -2403,6 +2309,36 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"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]]
|
[[package]]
|
||||||
name = "psm"
|
name = "psm"
|
||||||
version = "0.1.26"
|
version = "0.1.26"
|
||||||
@ -2412,6 +2348,12 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-error"
|
||||||
|
version = "1.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.40"
|
version = "1.0.40"
|
||||||
@ -2525,24 +2467,12 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ratatui"
|
name = "rand_xorshift"
|
||||||
version = "0.29.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
|
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"rand_core 0.9.3",
|
||||||
"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",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2668,19 +2598,6 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
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]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.0.8"
|
version = "1.0.8"
|
||||||
@ -2690,7 +2607,7 @@ dependencies = [
|
|||||||
"bitflags",
|
"bitflags",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.9.4",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2700,6 +2617,18 @@ version = "1.0.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
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]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.20"
|
version = "1.0.20"
|
||||||
@ -2858,28 +2787,6 @@ version = "1.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
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]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.6"
|
version = "1.4.6"
|
||||||
@ -2949,12 +2856,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum"
|
name = "structmeta"
|
||||||
version = "0.26.3"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329"
|
||||||
dependencies = [
|
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]]
|
[[package]]
|
||||||
@ -2963,20 +2884,7 @@ version = "0.27.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
|
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"strum_macros 0.27.2",
|
"strum_macros",
|
||||||
]
|
|
||||||
|
|
||||||
[[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",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3116,7 +3024,7 @@ dependencies = [
|
|||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"strum 0.27.2",
|
"strum",
|
||||||
"tasm-object-derive",
|
"tasm-object-derive",
|
||||||
"triton-vm",
|
"triton-vm",
|
||||||
]
|
]
|
||||||
@ -3141,10 +3049,23 @@ dependencies = [
|
|||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom 0.3.3",
|
"getrandom 0.3.3",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 1.0.8",
|
"rustix",
|
||||||
"windows-sys 0.59.0",
|
"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]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.69"
|
version = "1.0.69"
|
||||||
@ -3293,7 +3214,7 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"io-uring",
|
"io-uring",
|
||||||
"libc",
|
"libc",
|
||||||
"mio 1.0.4",
|
"mio",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
@ -3514,7 +3435,7 @@ checksum = "973a0422b170667a558ae36e21643ba827e76d2c663607087f0797888f4c59ad"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"arbitrary",
|
"arbitrary",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"strum 0.27.2",
|
"strum",
|
||||||
"triton-constraint-circuit",
|
"triton-constraint-circuit",
|
||||||
"triton-isa",
|
"triton-isa",
|
||||||
"twenty-first",
|
"twenty-first",
|
||||||
@ -3530,7 +3451,7 @@ dependencies = [
|
|||||||
"prettyplease",
|
"prettyplease",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strum 0.27.2",
|
"strum",
|
||||||
"syn 2.0.104",
|
"syn 2.0.104",
|
||||||
"triton-air",
|
"triton-air",
|
||||||
"triton-constraint-circuit",
|
"triton-constraint-circuit",
|
||||||
@ -3567,7 +3488,7 @@ dependencies = [
|
|||||||
"nom-language",
|
"nom-language",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
"strum 0.27.2",
|
"strum",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"twenty-first",
|
"twenty-first",
|
||||||
]
|
]
|
||||||
@ -3592,7 +3513,7 @@ dependencies = [
|
|||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
"rayon",
|
"rayon",
|
||||||
"serde",
|
"serde",
|
||||||
"strum 0.27.2",
|
"strum",
|
||||||
"syn 2.0.104",
|
"syn 2.0.104",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"triton-air",
|
"triton-air",
|
||||||
@ -3600,7 +3521,7 @@ dependencies = [
|
|||||||
"triton-constraint-circuit",
|
"triton-constraint-circuit",
|
||||||
"triton-isa",
|
"triton-isa",
|
||||||
"twenty-first",
|
"twenty-first",
|
||||||
"unicode-width 0.2.0",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3636,6 +3557,12 @@ version = "1.18.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unarray"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicase"
|
name = "unicase"
|
||||||
version = "2.8.1"
|
version = "2.8.1"
|
||||||
@ -3657,29 +3584,6 @@ dependencies = [
|
|||||||
"tinyvec",
|
"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]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -3743,6 +3647,15 @@ version = "0.9.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
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]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.1+wasi-snapshot-preview1"
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
|
|||||||
14
Cargo.toml
14
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
|
# not a direct dep. workaround for weird "could not resolve" cargo error
|
||||||
indexmap = "2.7.0"
|
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"]
|
||||||
@ -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.
|
* 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.
|
* 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.
|
* 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
|
## Connecting via Browser
|
||||||
|
|
||||||
Just navigate to http://localhost:3000/
|
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.
|
## SSL/TLS, Nginx, etc.
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use crate::model::app_state::AppStateInner;
|
use crate::model::app_state::AppStateInner;
|
||||||
use html_escaper::Escape;
|
use html_escaper::Escape;
|
||||||
|
|
||||||
#[derive(boilerplate::Boilerplate)]
|
#[derive(Debug, Clone, boilerplate::Boilerplate)]
|
||||||
#[boilerplate(filename = "web/html/components/header.html")]
|
#[boilerplate(filename = "web/html/components/header.html")]
|
||||||
pub struct HeaderHtml<'a> {
|
pub struct HeaderHtml<'a> {
|
||||||
pub state: &'a AppStateInner,
|
pub state: &'a AppStateInner,
|
||||||
|
|||||||
87
src/html/page/announcement.rs
Normal file
87
src/html/page/announcement.rs
Normal file
@ -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<Path<AnnouncementSelector>, PathRejection>,
|
||||||
|
State(state_rw): State<Arc<AppState>>,
|
||||||
|
) -> Result<Html<String>, 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()))
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
pub mod announcement;
|
||||||
pub mod block;
|
pub mod block;
|
||||||
pub mod not_found;
|
pub mod not_found;
|
||||||
pub mod redirect_qs_to_path;
|
pub mod redirect_qs_to_path;
|
||||||
|
|||||||
@ -3,6 +3,7 @@ use axum::routing::get;
|
|||||||
use axum::routing::post;
|
use axum::routing::post;
|
||||||
use axum::routing::Router;
|
use axum::routing::Router;
|
||||||
use neptune_explorer::alert_email;
|
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::block::block_page;
|
||||||
use neptune_explorer::html::page::not_found::not_found_html_fallback;
|
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;
|
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("/", get(root))
|
||||||
.route("/block/*selector", get(block_page))
|
.route("/block/*selector", get(block_page))
|
||||||
.route("/utxo/:value", get(utxo_page))
|
.route("/utxo/:value", get(utxo_page))
|
||||||
|
.route("/announcement/*selector", get(announcement_page))
|
||||||
// -- Rewrite query-strings to path --
|
// -- Rewrite query-strings to path --
|
||||||
.route("/rqs", get(redirect_query_string_to_path))
|
.route("/rqs", get(redirect_query_string_to_path))
|
||||||
// -- Static files --
|
// -- Static files --
|
||||||
|
|||||||
336
src/model/announcement_selector.rs
Normal file
336
src/model/announcement_selector.rs
Normal file
@ -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<Self, Self::Err> {
|
||||||
|
let parts: Vec<&str> = input.split('/').collect();
|
||||||
|
|
||||||
|
let (block_selector, index) = match parts.as_slice() {
|
||||||
|
["tip", index] => {
|
||||||
|
let index = index.parse::<u64>().map_err(Self::Err::TipIndex)?;
|
||||||
|
(BlockSelector::Tip, index)
|
||||||
|
}
|
||||||
|
["genesis", index] => index
|
||||||
|
.parse::<u64>()
|
||||||
|
.map(|i| (BlockSelector::Genesis, i))
|
||||||
|
.map_err(Self::Err::GenesisIndex)?,
|
||||||
|
["height", number, index] => {
|
||||||
|
let height_as_u64 = number.parse::<u64>().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::<u64>()
|
||||||
|
.map_err(|e| Self::Err::DigestIndex(digest, e))?;
|
||||||
|
(BlockSelector::Digest(digest), index)
|
||||||
|
}
|
||||||
|
["height_or_digest", hod, "index", index] => {
|
||||||
|
let parsed_height = hod.parse::<u64>();
|
||||||
|
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::<u64>()
|
||||||
|
.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<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
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<Self> {
|
||||||
|
// 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<usize>) {
|
||||||
|
// 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(_, _))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/model/announcement_type.rs
Normal file
29
src/model/announcement_type.rs
Normal file
@ -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<BFieldElement>),
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@ use neptune_cash::prelude::twenty_first::tip5::Digest;
|
|||||||
use neptune_cash::rpc_auth;
|
use neptune_cash::rpc_auth;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct AppStateInner {
|
pub struct AppStateInner {
|
||||||
pub network: Network,
|
pub network: Network,
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
pub mod announcement_selector;
|
||||||
|
pub mod announcement_type;
|
||||||
pub mod app_state;
|
pub mod app_state;
|
||||||
pub mod block_selector_extended;
|
pub mod block_selector_extended;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
|||||||
@ -6,12 +6,17 @@ use chrono::DateTime;
|
|||||||
use chrono::TimeDelta;
|
use chrono::TimeDelta;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use neptune_cash::api::export::Announcement;
|
||||||
use neptune_cash::config_models::data_directory::DataDirectory;
|
use neptune_cash::config_models::data_directory::DataDirectory;
|
||||||
use neptune_cash::config_models::network::Network;
|
use neptune_cash::config_models::network::Network;
|
||||||
use neptune_cash::models::blockchain::block::block_height::BlockHeight;
|
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_auth;
|
||||||
use neptune_cash::rpc_server::error::RpcError;
|
use neptune_cash::rpc_server::error::RpcError;
|
||||||
use neptune_cash::rpc_server::RPCClient;
|
use neptune_cash::rpc_server::RPCClient;
|
||||||
|
use neptune_cash::rpc_server::RpcResult;
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use tarpc::client;
|
use tarpc::client;
|
||||||
@ -19,6 +24,10 @@ use tarpc::context;
|
|||||||
use tarpc::tokio_serde::formats::Json as RpcJson;
|
use tarpc::tokio_serde::formats::Json as RpcJson;
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
|
#[cfg(feature = "mock")]
|
||||||
|
const MOCK_KEY: &str = "MOCK";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct AuthenticatedClient {
|
pub struct AuthenticatedClient {
|
||||||
pub client: RPCClient,
|
pub client: RPCClient,
|
||||||
pub token: rpc_auth::Token,
|
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<RpcResult<Option<BlockInfo>>, ::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::<Vec<_>>());
|
||||||
|
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<RpcResult<Option<Digest>>, ::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<Result<Option<Vec<Announcement>>, 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::<Vec<_>>());
|
||||||
|
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::<BFieldElement>())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
Announcement::new(message)
|
||||||
|
} else {
|
||||||
|
rng.random::<TransparentTransactionInfo>().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.
|
/// generates RPCClient, for querying neptune-core RPC server.
|
||||||
pub async fn gen_authenticated_rpc_client() -> Result<AuthenticatedClient, anyhow::Error> {
|
pub async fn gen_authenticated_rpc_client() -> Result<AuthenticatedClient, anyhow::Error> {
|
||||||
let client = gen_rpc_client().await?;
|
let client = gen_rpc_client().await?;
|
||||||
|
|||||||
150
templates/web/html/page/announcement.html
Normal file
150
templates/web/html/page/announcement.html
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>{{self.header.state.config.site_name}}: Announcement {{self.block_height}}/{{self.index}}</title>
|
||||||
|
{{html_escaper::Trusted(include_str!( concat!(env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/templates/web/html/components/head.html")))}}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{{Trusted(self.header.to_string())}}
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<h2>Announcement
|
||||||
|
</h2>
|
||||||
|
<h3>
|
||||||
|
Metadata
|
||||||
|
</h3>
|
||||||
|
<table class="striped">
|
||||||
|
<tr>
|
||||||
|
<td>Block Height</td>
|
||||||
|
<td><a href='/block/digest/{{self.block_hash.to_hex()}}'>{{self.block_height}}</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Block Hash</td>
|
||||||
|
<td class="mono"><a
|
||||||
|
href='/block/digest/{{self.block_hash.to_hex()}}'>{{self.block_hash.to_hex()}}</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Index</td>
|
||||||
|
<td>{{self.index}}/{{self.num_announcements}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Type</td>
|
||||||
|
<td>{{self.announcement_type.name()}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<h3>Payload</h3>
|
||||||
|
{% match &self.announcement_type { AnnouncementType::TransparentTxInfo(tx_info) => { %}
|
||||||
|
<details open>
|
||||||
|
<summary>Transparent Transaction Info</summary>
|
||||||
|
<table class="striped">
|
||||||
|
<tr>
|
||||||
|
<th colspan=2 style="font-weight: bold;">inputs</th>
|
||||||
|
</tr>
|
||||||
|
{% for input in tx_info.inputs.iter() { %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<details>
|
||||||
|
<summary>
|
||||||
|
<a
|
||||||
|
href='/utxo/{{input.aocl_leaf_index}}'>{{input.addition_record().canonical_commitment.to_hex()}}</a>
|
||||||
|
</summary>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>UTXO digest:</td>
|
||||||
|
<td class="mono">{{Tip5::hash(&input.utxo).to_hex()}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>sender randomness:</td>
|
||||||
|
<td class="mono">{{input.sender_randomness.to_hex()}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>receiver preimage:</td>
|
||||||
|
<td class="mono">{{input.receiver_preimage.to_hex()}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</details>
|
||||||
|
</td>
|
||||||
|
<td style="text-align: right" class="mono">
|
||||||
|
{{input.utxo.get_native_currency_amount().display_n_decimals(5)}}
|
||||||
|
NPT
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% } %}
|
||||||
|
</table>
|
||||||
|
<table class="striped">
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" style="font-weight: bold;">outputs</th>
|
||||||
|
</tr>
|
||||||
|
{% for output in tx_info.outputs.iter() { %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<details>
|
||||||
|
<summary>
|
||||||
|
{{output.addition_record().canonical_commitment.to_hex()}}
|
||||||
|
</summary>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>UTXO digest:</td>
|
||||||
|
<td class="mono">{{Tip5::hash(&output.utxo).to_hex()}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>sender randomness:</td>
|
||||||
|
<td class="mono">{{output.sender_randomness.to_hex()}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>receiver digest:</td>
|
||||||
|
<td class="mono">{{output.receiver_digest.to_hex()}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</details>
|
||||||
|
</td>
|
||||||
|
<td style="text-align: right" class="mono">
|
||||||
|
{{output.utxo.get_native_currency_amount().display_n_decimals(5)}}
|
||||||
|
NPT
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% } %}
|
||||||
|
</table>
|
||||||
|
</details>
|
||||||
|
{% }, AnnouncementType::Unknown(payload) => { %}
|
||||||
|
<details>
|
||||||
|
<summary>Unknown Type</summary>
|
||||||
|
{% for chunk in payload.encode().chunks(4) { %}
|
||||||
|
<p class="mono">
|
||||||
|
{% for d in chunk { %}
|
||||||
|
{{ format!("{:016x}", d.value()) }}
|
||||||
|
{% } %}
|
||||||
|
</p>
|
||||||
|
{% } %}
|
||||||
|
</details>
|
||||||
|
{% }, } %}
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<p>
|
||||||
|
<a href="/">Home</a>
|
||||||
|
| <a href='/block/genesis'>Genesis</a>
|
||||||
|
| <a href='/block/tip'>Tip</a>
|
||||||
|
{% if self.index == 0 { %}
|
||||||
|
| Previous Announcement
|
||||||
|
{% } else { %}
|
||||||
|
| <a href='/announcement/digest/{{self.block_hash.to_hex()}}/{{self.index - 1}}'>Previous
|
||||||
|
Announcement</a>
|
||||||
|
{% } %}
|
||||||
|
|
||||||
|
{% if self.index+1 >= self.num_announcements { %}
|
||||||
|
| Next Announcement
|
||||||
|
{% } else { %}
|
||||||
|
| <a href='/announcement/digest/{{self.block_hash.to_hex()}}/{{self.index + 1}}'>Next Announcement</a>
|
||||||
|
{% } %}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@ -3,140 +3,164 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>{{self.header.state.config.site_name}}: Block Height {{self.block_info.height}}</title>
|
<title>{{self.header.state.config.site_name}}: Block Height {{self.block_info.height}}</title>
|
||||||
{{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")))}}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
{{Trusted(self.header.to_string())}}
|
{{Trusted(self.header.to_string())}}
|
||||||
|
|
||||||
<main class="container">
|
<main class="container">
|
||||||
|
|
||||||
<article>
|
<article>
|
||||||
<h2>Block height: {{self.block_info.height}}</h2>
|
<h2>Block height: {{self.block_info.height}}</h2>
|
||||||
|
|
||||||
<!-- special_block_notice -->
|
<!-- special_block_notice -->
|
||||||
%% if self.block_info.is_genesis {
|
%% if self.block_info.is_genesis {
|
||||||
<p>This is the genesis block</p>
|
<p>This is the genesis block</p>
|
||||||
%% }
|
%% }
|
||||||
%% if self.block_info.is_tip {
|
%% if self.block_info.is_tip {
|
||||||
<p>This is the latest block (tip)</p>
|
<p>This is the latest block (tip)</p>
|
||||||
%% }
|
%% }
|
||||||
|
|
||||||
<table class="striped">
|
<table class="striped">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Digest</td>
|
<td>Digest</td>
|
||||||
<td class="mono">{{self.block_info.digest.to_hex()}}</td>
|
<td class="mono">{{self.block_info.digest.to_hex()}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Created</td>
|
<td>Created</td>
|
||||||
<td>{{self.block_info.timestamp.standard_format()}}</td>
|
<td>{{self.block_info.timestamp.standard_format()}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Size
|
<td>Size
|
||||||
<span class="tooltip">ⓘ
|
<span class="tooltip">ⓘ
|
||||||
<span class="tooltiptext">Unit: number of BFieldElements. One BFieldElement consists of 8 bytes.</span>
|
<span class="tooltiptext">Unit: number of BFieldElements. One BFieldElement consists of 8
|
||||||
</span>
|
bytes.</span>
|
||||||
</td>
|
</span>
|
||||||
<td>{{self.block_info.size}}</td>
|
</td>
|
||||||
</tr>
|
<td>{{self.block_info.size}}</td>
|
||||||
<tr>
|
</tr>
|
||||||
<td>Inputs</td>
|
<tr>
|
||||||
<td>{{self.block_info.num_inputs}}<br /></td>
|
<td>Inputs</td>
|
||||||
</tr>
|
<td>{{self.block_info.num_inputs}}<br /></td>
|
||||||
<tr>
|
</tr>
|
||||||
<td>Outputs</td>
|
<tr>
|
||||||
<td>{{self.block_info.num_outputs}}</td>
|
<td>Outputs</td>
|
||||||
</tr>
|
<td>{{self.block_info.num_outputs}}</td>
|
||||||
<tr>
|
</tr>
|
||||||
<td>Difficulty</td>
|
<tr>
|
||||||
<td>{{self.block_info.difficulty}}</td>
|
<td>Announcements
|
||||||
</tr>
|
<span class="tooltip">ⓘ
|
||||||
<tr>
|
<span class="tooltiptext">
|
||||||
<td>Cumulative Proof-Of-Work
|
Data broadcast as part of the transaction.
|
||||||
<span class="tooltip">ⓘ
|
</span>
|
||||||
<span class="tooltiptext">estimated total # of hashes performed by miners from genesis block to this block.</span>
|
</span>
|
||||||
</span>
|
</td>
|
||||||
</td>
|
<td>
|
||||||
<td>{{self.block_info.cumulative_proof_of_work}}</td>
|
{% if self.block_info.num_announcements > 0 { %}
|
||||||
</tr>
|
<a
|
||||||
<tr>
|
href='/announcement/digest/{{self.block_info.digest.to_hex()}}/0'>{{self.block_info.num_announcements}}</a>
|
||||||
<td>Coinbase
|
{% } else { %}
|
||||||
<span class="tooltip">ⓘ
|
0
|
||||||
<span class="tooltiptext">Total block reward amount paid to the miner(s) that found this block.</span>
|
{% } %}
|
||||||
</span>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
<td>{{self.block_info.coinbase_amount}}</td>
|
<tr>
|
||||||
</tr>
|
<td>Difficulty</td>
|
||||||
%% if self.block_info.coinbase_amount != self.block_info.expected_coinbase_amount() {
|
<td>{{self.block_info.difficulty}}</td>
|
||||||
<tr>
|
</tr>
|
||||||
<td>Expected Coinbase
|
<tr>
|
||||||
<span class="tooltip">ⓘ
|
<td>Cumulative Proof-Of-Work
|
||||||
<span class="tooltiptext">Expected (maximum) block reward amount paid to the miner(s) that find a block at this block-height.</span>
|
<span class="tooltip">ⓘ
|
||||||
</span>
|
<span class="tooltiptext">estimated total # of hashes performed by miners from genesis block
|
||||||
</td>
|
to this block.</span>
|
||||||
<td>{{self.block_info.expected_coinbase_amount()}}</td>
|
</span>
|
||||||
</tr>
|
</td>
|
||||||
%% }
|
<td>{{self.block_info.cumulative_proof_of_work}}</td>
|
||||||
<tr>
|
</tr>
|
||||||
<td>Fee</td>
|
<tr>
|
||||||
<td>{{self.block_info.fee}}</td>
|
<td>Coinbase
|
||||||
</tr>
|
<span class="tooltip">ⓘ
|
||||||
<tr>
|
<span class="tooltiptext">Total block reward amount paid to the miner(s) that found this
|
||||||
<td>Canonical
|
block.</span>
|
||||||
<span class="tooltip">ⓘ
|
</span>
|
||||||
<span class="tooltiptext">
|
</td>
|
||||||
The canonical blockchain is the chain with the most accumulated proof-of-work and is considered the
|
<td>{{self.block_info.coinbase_amount}}</td>
|
||||||
official record of transaction history.
|
</tr>
|
||||||
</span>
|
%% if self.block_info.coinbase_amount != self.block_info.expected_coinbase_amount() {
|
||||||
</span>
|
<tr>
|
||||||
</td>
|
<td>Expected Coinbase
|
||||||
<td>
|
<span class="tooltip">ⓘ
|
||||||
%% if self.block_info.is_canonical {
|
<span class="tooltiptext">Expected (maximum) block reward amount paid to the miner(s) that
|
||||||
Yes. This block is in the canonical blockchain.
|
find a block at this block-height.</span>
|
||||||
%% } else {
|
</span>
|
||||||
No. This block is not in the canonical blockchain.
|
</td>
|
||||||
|
<td>{{self.block_info.expected_coinbase_amount()}}</td>
|
||||||
|
</tr>
|
||||||
%% }
|
%% }
|
||||||
</td>
|
<tr>
|
||||||
</tr>
|
<td>Fee</td>
|
||||||
<tr>
|
<td>{{self.block_info.fee}}</td>
|
||||||
<td>Sibling Blocks
|
</tr>
|
||||||
<span class="tooltip">ⓘ
|
<tr>
|
||||||
<span class="tooltiptext">
|
<td>Canonical
|
||||||
Blocks that exist at the same height as this block. Only one sibling can be in the canonical blockchain.
|
<span class="tooltip">ⓘ
|
||||||
</span>
|
<span class="tooltiptext">
|
||||||
</span>
|
The canonical blockchain is the chain with the most accumulated proof-of-work and is
|
||||||
</td>
|
considered the
|
||||||
<td class="mono">
|
official record of transaction history.
|
||||||
%% for sibling_digest in self.block_info.sibling_blocks.iter().map(|d| d.to_hex()) {
|
</span>
|
||||||
<a href='/block/digest/{{sibling_digest}}'>{{sibling_digest}}</a><br/>
|
</span>
|
||||||
%% }
|
</td>
|
||||||
</td>
|
<td>
|
||||||
</tr>
|
%% if self.block_info.is_canonical {
|
||||||
|
Yes. This block is in the canonical blockchain.
|
||||||
|
%% } else {
|
||||||
|
No. This block is not in the canonical blockchain.
|
||||||
|
%% }
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Sibling Blocks
|
||||||
|
<span class="tooltip">ⓘ
|
||||||
|
<span class="tooltiptext">
|
||||||
|
Blocks that exist at the same height as this block. Only one sibling can be in the
|
||||||
|
canonical blockchain.
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="mono">
|
||||||
|
%% for sibling_digest in self.block_info.sibling_blocks.iter().map(|d| d.to_hex()) {
|
||||||
|
<a href='/block/digest/{{sibling_digest}}'>{{sibling_digest}}</a><br />
|
||||||
|
%% }
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article>
|
<article>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a href="/">Home</a>
|
<a href="/">Home</a>
|
||||||
| <a href='/block/genesis'>Genesis</a>
|
| <a href='/block/genesis'>Genesis</a>
|
||||||
| <a href='/block/tip'>Tip</a>
|
| <a href='/block/tip'>Tip</a>
|
||||||
%% if self.block_info.is_genesis {
|
%% if self.block_info.is_genesis {
|
||||||
| Previous Block
|
| Previous Block
|
||||||
%% } else {
|
%% } else {
|
||||||
| <a href='/block/height/{{self.block_info.height.previous().unwrap()}}'>Previous Block</a>
|
| <a href='/block/height/{{self.block_info.height.previous().unwrap()}}'>Previous Block</a>
|
||||||
%% }
|
%% }
|
||||||
|
|
||||||
%% if self.block_info.is_tip {
|
%% if self.block_info.is_tip {
|
||||||
| Next Block
|
| Next Block
|
||||||
%% } else {
|
%% } else {
|
||||||
| <a href='/block/height/{{self.block_info.height.next()}}'>Next Block</a>
|
| <a href='/block/height/{{self.block_info.height.next()}}'>Next Block</a>
|
||||||
%% }
|
%% }
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@ -1,113 +1,152 @@
|
|||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>{{self.state.config.site_name}}: (network: {{self.state.network}})</title>
|
<title>{{self.state.config.site_name}}: (network: {{self.state.network}})</title>
|
||||||
{{ 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"))) }}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<header class="container">
|
<header class="container">
|
||||||
<h1>
|
<h1>
|
||||||
<img src="/image/neptune-logo-circle-small.png" align="right"/>
|
<img src="/image/neptune-logo-circle-small.png" align="right" />
|
||||||
{{self.state.config.site_name}} (network: {{self.state.network}})
|
{{self.state.config.site_name}} (network: {{self.state.network}})
|
||||||
</h1>
|
</h1>
|
||||||
The blockchain tip is at height: {{self.tip_height}}
|
The blockchain tip is at height: {{self.tip_height}}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="container">
|
<main class="container">
|
||||||
|
|
||||||
<article>
|
<article>
|
||||||
<details open>
|
<details open>
|
||||||
<summary>
|
<summary>
|
||||||
Block Lookup
|
Block Lookup
|
||||||
</summary>
|
</summary>
|
||||||
<form action="/rqs" method="get">
|
<form action="/rqs" method="get">
|
||||||
<input type="hidden" name="block" value="" />
|
<input type="hidden" name="block" value="" />
|
||||||
<input type="hidden" name="_ig" value="l"/>
|
<input type="hidden" name="_ig" value="l" />
|
||||||
<span class="tooltip">ⓘ
|
<span class="tooltip">ⓘ
|
||||||
<span class="tooltiptext">
|
<span class="tooltiptext">
|
||||||
Provide a numeric block height or hexadecimal digest identifier to lookup any block in the Neptune blockchain.
|
Provide a numeric block height or hexadecimal digest identifier to lookup any block in the
|
||||||
</span>
|
Neptune blockchain.
|
||||||
</span>
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
Block height or digest:
|
Block height or digest:
|
||||||
<input type="text" size="80" name="height_or_digest" class="mono"/>
|
<input type="text" size="80" name="height_or_digest" class="mono" />
|
||||||
<input type="submit" name="l" value="Lookup Block"/>
|
<input type="submit" name="l" value="Lookup Block" />
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
Quick Lookup:
|
Quick Lookup:
|
||||||
<a href="/block/genesis">Genesis Block</a> |
|
<a href="/block/genesis">Genesis Block</a> |
|
||||||
<a href="/block/tip">Tip</a><br/>
|
<a href="/block/tip">Tip</a><br />
|
||||||
</details>
|
</details>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article>
|
<article>
|
||||||
<details open>
|
<details open>
|
||||||
<summary>UTXO Lookup</summary>
|
<summary>UTXO Lookup</summary>
|
||||||
<form action="/rqs" method="get">
|
<form action="/rqs" method="get">
|
||||||
<input type="hidden" name="_ig" value="l" />
|
<input type="hidden" name="_ig" value="l" />
|
||||||
<span class="tooltip">ⓘ
|
<span class="tooltip">ⓘ
|
||||||
<span class="tooltiptext">
|
<span class="tooltiptext">
|
||||||
An Unspent Transaction Output (UTXO) index can be found in the output of <i>neptune-cli wallet-status</i>. Look for the field: <b>aocl_leaf_index</b>
|
An Unspent Transaction Output (UTXO) index can be found in the output of <i>neptune-cli
|
||||||
</span>
|
wallet-status</i>. Look for the field: <b>aocl_leaf_index</b>
|
||||||
</span>
|
</span>
|
||||||
UTXO index:
|
</span>
|
||||||
<input type="text" size="10" name="utxo" />
|
UTXO index:
|
||||||
<input type="submit" name="l" value="Lookup Utxo" />
|
<input type="text" size="10" name="utxo" />
|
||||||
</form>
|
<input type="submit" name="l" value="Lookup Utxo" />
|
||||||
</details>
|
</form>
|
||||||
</article>
|
</details>
|
||||||
|
</article>
|
||||||
|
|
||||||
<article>
|
<article>
|
||||||
<details>
|
<details open>
|
||||||
<summary>REST RPCs</summary>
|
<summary>Announcement Lookup</summary>
|
||||||
<section>
|
<form action="/rqs" method="get">
|
||||||
RPC endpoints are available for automating block explorer queries:
|
<input type="hidden" name="announcement" value="" />
|
||||||
</section>
|
<input type="hidden" name="_ig" value="l" />
|
||||||
|
<span class="tooltip">ⓘ
|
||||||
|
<span class="tooltiptext">
|
||||||
|
A numeric block height or hexadecimal digest to identify the block in which the announcement
|
||||||
|
lives.
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
Block height or digest:
|
||||||
|
<input type="text" size="80" name="height_or_digest" class="mono" />
|
||||||
|
|
||||||
<details>
|
<span class="tooltip">ⓘ
|
||||||
<summary>/block_info</summary>
|
<span class="tooltiptext">
|
||||||
<div class="indent">
|
The index of the announcement within the block (as a block can have many announcements).
|
||||||
<h4>Examples</h4>
|
</span>
|
||||||
|
</span>
|
||||||
|
Announcement index:
|
||||||
|
<input type="text" size="10" name="index" />
|
||||||
|
<input type="submit" name="l" value="Lookup Announcement" />
|
||||||
|
</form>
|
||||||
|
</details>
|
||||||
|
</article>
|
||||||
|
|
||||||
<ul>
|
<article>
|
||||||
<li><a href="/rpc/block_info/genesis">/rpc/block_info/genesis</a></li>
|
<details>
|
||||||
<li><a href="/rpc/block_info/tip">/rpc/block_info/tip</a></li>
|
<summary>REST RPCs</summary>
|
||||||
<li><a href="/rpc/block_info/height/2">/rpc/block_info/height/2</a></li>
|
<section>
|
||||||
<li><a href="/rpc/block_info/digest/{{self.state.genesis_digest.to_hex()}}">/rpc/block_info/digest/{{self.state.genesis_digest.to_hex()}}</a></li>
|
RPC endpoints are available for automating block explorer queries:
|
||||||
<li><a href="/rpc/block_info/height_or_digest/1">/rpc/block_info/height_or_digest/1</a></li>
|
</section>
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>/block_digest</summary>
|
<summary>/block_info</summary>
|
||||||
<div class="indent">
|
<div class="indent">
|
||||||
<h4>Examples</h4>
|
<h4>Examples</h4>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/rpc/block_digest/genesis">/rpc/block_digest/genesis</a></li>
|
<li><a href="/rpc/block_info/genesis">/rpc/block_info/genesis</a></li>
|
||||||
<li><a href="/rpc/block_digest/tip">/rpc/block_digest/tip</a></li>
|
<li><a href="/rpc/block_info/tip">/rpc/block_info/tip</a></li>
|
||||||
<li><a href="/rpc/block_digest/height/2">/rpc/block_digest/height/2</a></li>
|
<li><a href="/rpc/block_info/height/2">/rpc/block_info/height/2</a></li>
|
||||||
<li><a href="/rpc/block_digest/digest/{{self.state.genesis_digest.to_hex()}}">/rpc/block_digest/digest/{{self.state.genesis_digest.to_hex()}}</a></li>
|
<li><a
|
||||||
<li><a href="/rpc/block_digest/height_or_digest/{{self.state.genesis_digest.to_hex()}}">/rpc/block_digest/height_or_digest/{{self.state.genesis_digest.to_hex()}}</a></li>
|
href="/rpc/block_info/digest/{{self.state.genesis_digest.to_hex()}}">/rpc/block_info/digest/{{self.state.genesis_digest.to_hex()}}</a>
|
||||||
</ul>
|
</li>
|
||||||
</div>
|
<li><a href="/rpc/block_info/height_or_digest/1">/rpc/block_info/height_or_digest/1</a></li>
|
||||||
</details>
|
</ul>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>/utxo_digest</summary>
|
<summary>/block_digest</summary>
|
||||||
<div class="indent">
|
<div class="indent">
|
||||||
<h4>Examples</h4>
|
<h4>Examples</h4>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/rpc/utxo_digest/2">/rpc/utxo_digest/2</a><br/></li>
|
<li><a href="/rpc/block_digest/genesis">/rpc/block_digest/genesis</a></li>
|
||||||
</ul>
|
<li><a href="/rpc/block_digest/tip">/rpc/block_digest/tip</a></li>
|
||||||
</div>
|
<li><a href="/rpc/block_digest/height/2">/rpc/block_digest/height/2</a></li>
|
||||||
</details>
|
<li><a
|
||||||
|
href="/rpc/block_digest/digest/{{self.state.genesis_digest.to_hex()}}">/rpc/block_digest/digest/{{self.state.genesis_digest.to_hex()}}</a>
|
||||||
|
</li>
|
||||||
|
<li><a
|
||||||
|
href="/rpc/block_digest/height_or_digest/{{self.state.genesis_digest.to_hex()}}">/rpc/block_digest/height_or_digest/{{self.state.genesis_digest.to_hex()}}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
</details>
|
<details>
|
||||||
</article>
|
<summary>/utxo_digest</summary>
|
||||||
|
<div class="indent">
|
||||||
|
<h4>Examples</h4>
|
||||||
|
|
||||||
</main>
|
<ul>
|
||||||
|
<li><a href="/rpc/utxo_digest/2">/rpc/utxo_digest/2</a><br /></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user