bless
This commit is contained in:
parent
e680744944
commit
a882d6684e
401
Cargo.lock
generated
401
Cargo.lock
generated
@ -17,6 +17,12 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
@ -136,7 +142,9 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"crossterm 0.29.0",
|
||||
"hex",
|
||||
"ratatui",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
@ -144,6 +152,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"uuid",
|
||||
"vlogger",
|
||||
"warp",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
@ -161,6 +170,21 @@ version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[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.34"
|
||||
@ -236,6 +260,29 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[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 = "convert_case"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
@ -251,6 +298,49 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"rustix 0.38.44",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"derive_more",
|
||||
"document-features",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"rustix 1.0.8",
|
||||
"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"
|
||||
@ -261,12 +351,68 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
|
||||
dependencies = [
|
||||
"derive_more-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more-impl"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
@ -277,18 +423,49 @@ dependencies = [
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
|
||||
dependencies = [
|
||||
"litrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
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 = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
@ -391,6 +568,11 @@ name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "headers"
|
||||
@ -536,6 +718,12 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.11.0"
|
||||
@ -546,6 +734,25 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
|
||||
|
||||
[[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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.7.10"
|
||||
@ -563,6 +770,15 @@ version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
@ -585,6 +801,24 @@ version = "0.2.175"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
|
||||
[[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"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||
|
||||
[[package]]
|
||||
name = "litrs"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.13"
|
||||
@ -601,6 +835,15 @@ 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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
@ -639,6 +882,7 @@ 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",
|
||||
]
|
||||
@ -696,6 +940,12 @@ 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 = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
@ -796,6 +1046,27 @@ dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cassowary",
|
||||
"compact_str",
|
||||
"crossterm 0.28.1",
|
||||
"indoc",
|
||||
"instability",
|
||||
"itertools",
|
||||
"lru",
|
||||
"paste",
|
||||
"strum",
|
||||
"unicode-segmentation",
|
||||
"unicode-truncate",
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.17"
|
||||
@ -811,6 +1082,32 @@ version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
|
||||
[[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"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.4",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
@ -907,6 +1204,27 @@ 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",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.6"
|
||||
@ -938,12 +1256,40 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
dependencies = [
|
||||
"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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
@ -1092,6 +1438,35 @@ version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[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",
|
||||
"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"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
@ -1122,6 +1497,10 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "vlogger"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "warp"
|
||||
version = "0.4.2"
|
||||
@ -1235,6 +1614,28 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.2"
|
||||
|
||||
@ -17,3 +17,6 @@ uuid = { version = "1.18.0", features = ["v4", "serde"] }
|
||||
warp = { version = "0.4.2", features = ["server", "websocket"] }
|
||||
wasm-bindgen = "0.2.100"
|
||||
web-sys = { version = "0.3.77", features = ["WebSocket"] }
|
||||
vlogger = { path = "./lib/logger-rs" }
|
||||
ratatui = "0.29.0"
|
||||
crossterm = "0.29.0"
|
||||
|
||||
1
lib/.gitignore
vendored
Normal file
1
lib/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
||||
7
lib/logger-rs/Cargo.lock
generated
Normal file
7
lib/logger-rs/Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "vlogger"
|
||||
version = "0.1.0"
|
||||
6
lib/logger-rs/Cargo.toml
Normal file
6
lib/logger-rs/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "vlogger"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
11
lib/logger-rs/README.md
Normal file
11
lib/logger-rs/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Simple Logger
|
||||
|
||||
A lightweight logging utility for Rust with both printing and string formatting capabilities.
|
||||
|
||||
## Features
|
||||
|
||||
- **Two macros**: `log!` for immediate printing, `msg!` for string formatting
|
||||
- **Color support**: ANSI color codes for terminal output
|
||||
- **Multiple log levels**: INFO, DEBUG, WARNING, ERROR, FATAL
|
||||
- **No external dependencies**: Uses only standard library
|
||||
- **File/line info**: DEBUG, WARNING, ERROR, and FATAL levels include source location
|
||||
173
lib/logger-rs/src/lib.rs
Normal file
173
lib/logger-rs/src/lib.rs
Normal file
@ -0,0 +1,173 @@
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
pub const INFO: usize = 0;
|
||||
pub const DEBUG: usize = 1;
|
||||
pub const WARNING: usize = 2;
|
||||
pub const ERROR: usize = 3;
|
||||
pub const FATAL: usize = 4;
|
||||
|
||||
pub const LOG_LEVEL: [&str; 5] = [
|
||||
"INFO",
|
||||
"DEBUG",
|
||||
"WARNING",
|
||||
"ERROR",
|
||||
"FATAL",
|
||||
];
|
||||
|
||||
pub fn colored(text: &str, color: &str) -> String {
|
||||
// For terminals: use ANSI escape codes
|
||||
let ansi_code = match color {
|
||||
"black" => "30",
|
||||
"red" => "31",
|
||||
"green" => "32",
|
||||
"yellow" => "33",
|
||||
"blue" => "34",
|
||||
"purple" => "35",
|
||||
"cyan" => "36",
|
||||
"white" => "37",
|
||||
_ => "0", // default no color
|
||||
};
|
||||
format!("\x1b[{ansi_code}m{text}\x1b[0m")
|
||||
}
|
||||
|
||||
pub fn current_timestamp() -> String {
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default();
|
||||
|
||||
let total_seconds = now.as_secs();
|
||||
let seconds_in_day = total_seconds % 86400; // Seconds within current day
|
||||
|
||||
let hours = seconds_in_day / 3600;
|
||||
let minutes = (seconds_in_day % 3600) / 60;
|
||||
let seconds = seconds_in_day % 60;
|
||||
|
||||
format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
|
||||
}
|
||||
|
||||
/// Creates a formatted log message string and returns it
|
||||
///
|
||||
/// log_levels are:
|
||||
/// - INFO
|
||||
/// - DEBUG
|
||||
/// - WARNING
|
||||
/// - ERROR
|
||||
/// - FATAL
|
||||
///
|
||||
/// Returns a formatted string ready for display or further processing.
|
||||
#[macro_export]
|
||||
macro_rules! msg {
|
||||
($level:expr, $($arg:tt)*) => {{
|
||||
let formatted_msg = format!($($arg)*);
|
||||
match $level {
|
||||
$crate::INFO => {
|
||||
format!(
|
||||
"[{}] {} | {}\n",
|
||||
$crate::colored($crate::LOG_LEVEL[$level], "green"),
|
||||
$crate::current_timestamp(),
|
||||
formatted_msg
|
||||
)
|
||||
},
|
||||
$crate::DEBUG => {
|
||||
format!(
|
||||
"[{}] [{}:{}] {} | {}\n",
|
||||
$crate::LOG_LEVEL[$level],
|
||||
file!(),
|
||||
line!(),
|
||||
$crate::current_timestamp(),
|
||||
formatted_msg
|
||||
)
|
||||
},
|
||||
$crate::WARNING => {
|
||||
format!(
|
||||
"[{}] {} | {}\n",
|
||||
$crate::colored($crate::LOG_LEVEL[$level], "yellow"),
|
||||
$crate::current_timestamp(),
|
||||
formatted_msg
|
||||
)
|
||||
},
|
||||
$crate::ERROR => {
|
||||
format!(
|
||||
"[{}] {} | {}\n",
|
||||
$crate::colored($crate::LOG_LEVEL[$level], "red"),
|
||||
$crate::current_timestamp(),
|
||||
formatted_msg
|
||||
)
|
||||
},
|
||||
$crate::FATAL => {
|
||||
format!(
|
||||
"[{}] [{}:{}] {} | {}\n",
|
||||
$crate::colored($crate::LOG_LEVEL[$level], "red"),
|
||||
file!(),
|
||||
line!(),
|
||||
$crate::current_timestamp(),
|
||||
formatted_msg
|
||||
)
|
||||
},
|
||||
_ => {
|
||||
format!(
|
||||
"[{}] {} {}",
|
||||
$crate::LOG_LEVEL[$crate::ERROR],
|
||||
"logging error: log_level value invalid:",
|
||||
$level
|
||||
)
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Creates a formatted log message and prints it to stdout
|
||||
///
|
||||
/// log_levels are:
|
||||
/// - INFO
|
||||
/// - DEBUG
|
||||
/// - WARNING
|
||||
/// - ERROR
|
||||
/// - FATAL
|
||||
///
|
||||
/// FATAL will cause the thread to panic and stop execution.
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
($level:expr, $($arg:tt)*) => {{
|
||||
let log_message = $crate::msg!($level, $($arg)*);
|
||||
println!("{}", log_message);
|
||||
|
||||
// Handle FATAL level panic
|
||||
if $level == $crate::FATAL {
|
||||
panic!("Fatal error encountered: {}", format!($($arg)*));
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_msg_macro() {
|
||||
let info_msg = msg!(INFO, "Test info message");
|
||||
assert!(info_msg.contains("INFO"));
|
||||
assert!(info_msg.contains("Test info message"));
|
||||
|
||||
let error_msg = msg!(ERROR, "Test error: {}", 42);
|
||||
assert!(error_msg.contains("ERROR"));
|
||||
assert!(error_msg.contains("Test error: 42"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_colored() {
|
||||
let red_text = colored("error", "red");
|
||||
assert!(red_text.contains("\x1b[31m"));
|
||||
assert!(red_text.contains("error"));
|
||||
assert!(red_text.contains("\x1b[0m"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timestamp() {
|
||||
let ts = current_timestamp();
|
||||
assert!(ts.contains(":"));
|
||||
// Should be in HH:MM:SS format
|
||||
assert_eq!(ts.len(), 8);
|
||||
}
|
||||
}
|
||||
|
||||
47
src/args.rs
47
src/args.rs
@ -4,51 +4,26 @@ use crate::core;
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
#[command(subcommand)]
|
||||
command: Commands
|
||||
/// Provide address on which node will listen
|
||||
#[arg(short = 'a', long)]
|
||||
pub addr: Option<String>,
|
||||
|
||||
/// Provide File with current chain
|
||||
#[arg(short = 'f', long)]
|
||||
pub seed_file: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum Commands {
|
||||
/// Transaction Options (add, ...)
|
||||
#[command(short_flag = 't')]
|
||||
Tx {
|
||||
#[command(subcommand)]
|
||||
tx_command: TxCmd
|
||||
},
|
||||
|
||||
/// Show accounts and balances
|
||||
#[command(short_flag = 'l')]
|
||||
List,
|
||||
|
||||
/// Start as seed node
|
||||
#[command(short_flag = 's')]
|
||||
Seed {
|
||||
#[arg(short = 'a')]
|
||||
addr: String
|
||||
},
|
||||
|
||||
/// listen on addr
|
||||
#[command(short_flag = 'r')]
|
||||
Run {
|
||||
#[arg(short = 'a')]
|
||||
addr: String
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum TxCmd {
|
||||
/// Add a new transaction to the DB
|
||||
#[command(short_flag = 'a')]
|
||||
Add(core::Tx)
|
||||
}
|
||||
|
||||
impl Args {
|
||||
pub fn get_commands(&self) -> &Commands {
|
||||
&self.command
|
||||
}
|
||||
/// Add a new transaction to the DB
|
||||
#[command(short_flag = 'a')]
|
||||
Add(core::Tx)
|
||||
}
|
||||
|
||||
pub fn get_args() -> Args {
|
||||
Args::parse()
|
||||
Args::parse()
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use crate::core;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, Default)]
|
||||
pub struct BlockHeader {
|
||||
pub previous_hash: String,
|
||||
pub timestamp: u64,
|
||||
@ -9,7 +9,7 @@ pub struct BlockHeader {
|
||||
pub nonce: u32
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, Default)]
|
||||
pub struct Block {
|
||||
pub head: BlockHeader,
|
||||
pub tx: Vec<core::Tx>
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
use sha2::Digest;
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::core::block;
|
||||
use crate::log::*;
|
||||
use vlogger::*;
|
||||
|
||||
use crate::core;
|
||||
use crate::error::{ BlockchainError, TxError };
|
||||
@ -18,31 +17,13 @@ pub enum ValidationError {
|
||||
InvalidPreviousBlockHash
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
|
||||
pub struct Genesis {
|
||||
pub genesis_time: String,
|
||||
pub chain_id: String,
|
||||
pub balances: std::collections::HashMap<Account, u32>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Blockchain {
|
||||
genesis: Genesis,
|
||||
balances: std::collections::HashMap<Account, u32>,
|
||||
blocks: Vec<core::Block>,
|
||||
tx_mempool: Vec<core::Tx>,
|
||||
}
|
||||
|
||||
impl Genesis {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
genesis_time: String::new(),
|
||||
chain_id: String::new(),
|
||||
balances: std::collections::HashMap::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Blockchain {
|
||||
pub fn open_account(&mut self, tx: core::Tx) -> Result<(), BlockchainError> {
|
||||
@ -185,9 +166,8 @@ impl Blockchain {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new(balances: HashMap<Account, u32>, blocks: Vec<core::Block>, tx_mempool: Vec<core::Tx>, genesis: Genesis) -> Blockchain {
|
||||
pub fn new(balances: HashMap<Account, u32>, blocks: Vec<core::Block>, tx_mempool: Vec<core::Tx>) -> Blockchain {
|
||||
return Self {
|
||||
genesis,
|
||||
balances,
|
||||
blocks,
|
||||
tx_mempool
|
||||
@ -224,10 +204,6 @@ impl Blockchain {
|
||||
&self.blocks
|
||||
}
|
||||
|
||||
pub fn genesis(&self) -> &Genesis {
|
||||
&self.genesis
|
||||
}
|
||||
|
||||
pub fn add_block(&mut self, block: core::Block) {
|
||||
match self.validate_block(&block) {
|
||||
Ok(()) => self.blocks.push(block),
|
||||
@ -266,10 +242,9 @@ impl Blockchain {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn from_genesis(genesis: Genesis, blocks: Vec<core::Block>) -> Result<Blockchain, ValidationError> {
|
||||
pub fn build(blocks: Vec<core::Block>) -> Result<Blockchain, ValidationError> {
|
||||
log!(INFO, "Starting Chain Build from Genesis");
|
||||
let chain = Blockchain {
|
||||
genesis,
|
||||
blocks,
|
||||
balances: HashMap::new(),
|
||||
tx_mempool: vec![]
|
||||
|
||||
115
src/log.rs
115
src/log.rs
@ -1,115 +0,0 @@
|
||||
pub const INFO: usize = 0;
|
||||
pub const DEBUG: usize = 1;
|
||||
pub const WARNING: usize = 2;
|
||||
pub const ERROR: usize = 3;
|
||||
pub const FATAL: usize = 4;
|
||||
|
||||
pub const LOG_LEVEL: [&str; 5] = [
|
||||
"INFO",
|
||||
"DEBUG",
|
||||
"WARNING",
|
||||
"ERROR",
|
||||
"FATAL",
|
||||
];
|
||||
|
||||
use chrono::Utc;
|
||||
|
||||
pub fn colored(text: &str, color: &str) -> String {
|
||||
// For terminals: use ANSI escape codes
|
||||
let ansi_code = match color {
|
||||
"black" => "30",
|
||||
"red" => "31",
|
||||
"green" => "32",
|
||||
"yellow" => "33",
|
||||
"blue" => "34",
|
||||
"purple" => "35",
|
||||
"cyan" => "36",
|
||||
"white" => "37",
|
||||
_ => "0", // default no color
|
||||
};
|
||||
format!("\x1b[{ansi_code}m{text}\x1b[0m")
|
||||
}
|
||||
|
||||
pub fn current_timestamp() -> String {
|
||||
Utc::now().format("%Y-%m-%d@%H:%M:%S").to_string()
|
||||
}
|
||||
|
||||
/// You can use this macro to create error messages that will show in the console
|
||||
/// if run on the client or in the terminal if run on the server
|
||||
///
|
||||
/// log_levels are :
|
||||
/// - INFO
|
||||
/// - DEBUG
|
||||
/// - WARNING
|
||||
/// - ERROR
|
||||
/// - FATAL
|
||||
///
|
||||
/// important: FATAL will cause the thread to panic and stop execution
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
($level:expr, $($arg:tt)*) => {{
|
||||
let formatted_msg = format!($($arg)*);
|
||||
match $level {
|
||||
$crate::log::INFO => {
|
||||
println!(
|
||||
"[{}] {} | {}",
|
||||
$crate::log::colored($crate::log::LOG_LEVEL[$level], "green"),
|
||||
$crate::log::current_timestamp(),
|
||||
formatted_msg
|
||||
);
|
||||
},
|
||||
$crate::log::DEBUG => {
|
||||
println!(
|
||||
"[{}] [{}:{}] {} | {}",
|
||||
$crate::log::LOG_LEVEL[$level],
|
||||
file!(),
|
||||
line!(),
|
||||
$crate::log::current_timestamp(),
|
||||
formatted_msg
|
||||
);
|
||||
},
|
||||
$crate::log::WARNING => {
|
||||
println!(
|
||||
"[{}] [{}:{}] {} | {}",
|
||||
$crate::log::colored($crate::log::LOG_LEVEL[$level], "yellow"),
|
||||
file!(),
|
||||
line!(),
|
||||
$crate::log::current_timestamp(),
|
||||
formatted_msg
|
||||
);
|
||||
},
|
||||
$crate::log::ERROR => {
|
||||
println!(
|
||||
"[{}] [{}:{}] {} | {}",
|
||||
$crate::log::colored($crate::log::LOG_LEVEL[$level], "red"),
|
||||
file!(),
|
||||
line!(),
|
||||
$crate::log::current_timestamp(),
|
||||
formatted_msg
|
||||
);
|
||||
},
|
||||
$crate::log::FATAL => {
|
||||
println!(
|
||||
"[{}] [{}:{}] {} | {}",
|
||||
$crate::log::colored($crate::log::LOG_LEVEL[$level], "red"),
|
||||
file!(),
|
||||
line!(),
|
||||
$crate::log::current_timestamp(),
|
||||
formatted_msg
|
||||
);
|
||||
if $level == $crate::log::FATAL {
|
||||
panic!("Fatal error encountered: {}", formatted_msg);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
println!(
|
||||
"[{}] {} {}",
|
||||
$crate::log::LOG_LEVEL[$crate::log::ERROR],
|
||||
"loggin error: log_level value invalid:",
|
||||
$level
|
||||
);
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
33
src/main.rs
33
src/main.rs
@ -1,15 +1,13 @@
|
||||
use error::{ BlockchainError, handle_error };
|
||||
|
||||
#[macro_use]
|
||||
pub mod log;
|
||||
pub mod error;
|
||||
pub mod args;
|
||||
pub mod core;
|
||||
pub mod native_node;
|
||||
pub mod seeds_constants;
|
||||
pub mod watcher;
|
||||
|
||||
use crate::native_node::node::NativeNode;
|
||||
use crate::args::{get_args, TxCmd, Commands};
|
||||
use crate::{args::get_args, watcher::watcher::Watcher};
|
||||
|
||||
const SEED_ADDR: &str = "127.0.0.1:8333";
|
||||
|
||||
@ -26,24 +24,13 @@ async fn main() {
|
||||
|
||||
let args = get_args();
|
||||
|
||||
match args.get_commands() {
|
||||
Commands::Tx{ tx_command } => {
|
||||
match tx_command {
|
||||
TxCmd::Add(tx) => {
|
||||
add_transaction(tx.clone()).unwrap_or_else(|e| handle_error(e))
|
||||
}
|
||||
}
|
||||
},
|
||||
Commands::List => {
|
||||
list_accounts()
|
||||
}
|
||||
Commands::Seed { addr: _ } => {
|
||||
NativeNode::seed(SEED_ADDR.to_string()).run_native().await;
|
||||
}
|
||||
Commands::Run{ addr } => {
|
||||
dbg!(&addr);
|
||||
NativeNode::bootstrap(addr).await.unwrap().run_native().await;
|
||||
}
|
||||
let mut watcher = Watcher::build().file(args.seed_file).addr(args.addr).start();
|
||||
|
||||
loop {
|
||||
if !watcher.poll().await.is_ok() {
|
||||
break ;
|
||||
}
|
||||
}
|
||||
println!("Hello, world!");
|
||||
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::native_node::{message, node};
|
||||
use crate::log::*;
|
||||
use crate::native_node::{node};
|
||||
use vlogger::*;
|
||||
use tokio::sync::mpsc;
|
||||
use crate::core;
|
||||
use std::io::{self, Write};
|
||||
@ -22,51 +22,6 @@ impl node::NativeNode {
|
||||
let args = &parts[1..];
|
||||
|
||||
match command {
|
||||
"id" => {
|
||||
command_sender.send(node::NodeCommand::DebugShowId).await.unwrap();
|
||||
},
|
||||
"tx" => {
|
||||
if args.len() != 4 {
|
||||
log!(ERROR, "Invalid arg count! Expected {}, got {}", 4, args.len());
|
||||
continue;
|
||||
}
|
||||
let from = args[0];
|
||||
let to = args[1];
|
||||
let value = args[2].parse::<u32>().unwrap();
|
||||
let data = args[3];
|
||||
|
||||
let tx = core::Tx::new(
|
||||
from.to_string(),
|
||||
to.to_string(),
|
||||
value,
|
||||
data.to_string()
|
||||
);
|
||||
|
||||
let cmd = node::NodeCommand::Transaction { tx };
|
||||
|
||||
command_sender.send(cmd).await.unwrap();
|
||||
},
|
||||
"block" => {
|
||||
let cmd = node::NodeCommand::CreateBlock;
|
||||
command_sender.send(cmd).await.unwrap();
|
||||
},
|
||||
"list" => {
|
||||
if args.len() != 1 {
|
||||
log!(ERROR, "{command}: Invalid arg! (blocks, peers)");
|
||||
continue;
|
||||
}
|
||||
match args[0] {
|
||||
"blocks" => command_sender.send(node::NodeCommand::DebugListBlocks).await.unwrap(),
|
||||
"peers" => command_sender.send(node::NodeCommand::DebugListPeers).await.unwrap(),
|
||||
_ => log!(ERROR, "Unkown arg: {}", args[0]),
|
||||
}
|
||||
},
|
||||
"dump_blocks" => {
|
||||
command_sender.send(node::NodeCommand::DebugDumpBlocks).await.unwrap();
|
||||
},
|
||||
"connect" => {
|
||||
command_sender.send(node::NodeCommand::ConnectToSeeds).await.unwrap();
|
||||
}
|
||||
_ => {
|
||||
log!(ERROR, "Unkown command {command}");
|
||||
continue;
|
||||
|
||||
@ -4,7 +4,7 @@ use crate::native_node::node;
|
||||
use crate::native_node::error;
|
||||
use crate::core;
|
||||
|
||||
use crate::log::*;
|
||||
use vlogger::*;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||
pub enum ProtocolMessage {
|
||||
@ -13,7 +13,6 @@ pub enum ProtocolMessage {
|
||||
version: String
|
||||
},
|
||||
BootstrapResponse {
|
||||
genesis: core::Genesis,
|
||||
blocks: Vec<core::Block>
|
||||
},
|
||||
GetPeersRequest {
|
||||
@ -78,7 +77,6 @@ impl node::NativeNode {
|
||||
log!(INFO, "Received BootstrapRequest from {peer_id}");
|
||||
let peer = &self.tcp_peers[&peer_id];
|
||||
let resp = ProtocolMessage::BootstrapResponse {
|
||||
genesis: self.chain.genesis().clone(),
|
||||
blocks: self.chain.blocks().to_vec()
|
||||
};
|
||||
peer.sender.send(resp).await.unwrap();
|
||||
|
||||
@ -2,7 +2,7 @@ use crate::native_node::{message, node};
|
||||
|
||||
use crate::seeds_constants::SEED_NODES;
|
||||
|
||||
use crate::log::*;
|
||||
use vlogger::*;
|
||||
use tokio::select;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
|
||||
@ -1,275 +1,237 @@
|
||||
use crate::core::{self, ValidationError};
|
||||
use crate::core::{self, Blockchain, ValidationError};
|
||||
use crate::native_node::message::{self, ProtocolMessage};
|
||||
|
||||
use crate::seeds_constants::SEED_NODES;
|
||||
use crate::watcher::executor::ExecutorCommand;
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use std::collections::HashMap;
|
||||
use crate::log::*;
|
||||
use vlogger::*;
|
||||
use tokio::sync::mpsc;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct TcpPeer {
|
||||
pub id: Uuid,
|
||||
pub addr: String,
|
||||
pub sender: tokio::sync::mpsc::Sender<ProtocolMessage>
|
||||
pub id: Uuid,
|
||||
pub addr: String,
|
||||
pub sender: tokio::sync::mpsc::Sender<ProtocolMessage>
|
||||
}
|
||||
|
||||
pub struct NativeNode {
|
||||
pub id: Uuid,
|
||||
pub addr: String,
|
||||
pub tcp_peers: HashMap<Uuid, TcpPeer>,
|
||||
pub ws: Vec<web_sys::WebSocket>,
|
||||
pub chain: core::Blockchain,
|
||||
pub db_file: std::fs::File
|
||||
pub id: Uuid,
|
||||
pub addr: String,
|
||||
pub tcp_peers: HashMap<Uuid, TcpPeer>,
|
||||
pub chain: core::Blockchain,
|
||||
exec_tx: mpsc::Sender<ExecutorCommand>,
|
||||
rx: mpsc::Receiver<NodeCommand>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NodeCommand {
|
||||
AddPeer { peer_id: Uuid, addr: String, sender: tokio::sync::mpsc::Sender<ProtocolMessage> },
|
||||
RemovePeer { peer_id: Uuid },
|
||||
ProcessMessage { peer_id: Uuid, message: ProtocolMessage },
|
||||
Transaction { tx: core::Tx },
|
||||
CreateBlock,
|
||||
DebugListBlocks,
|
||||
DebugListPeers,
|
||||
DebugShowId,
|
||||
DebugDumpBlocks,
|
||||
ConnectToSeeds
|
||||
AddPeer { peer_id: Uuid, addr: String, sender: tokio::sync::mpsc::Sender<ProtocolMessage> },
|
||||
RemovePeer { peer_id: Uuid },
|
||||
ProcessMessage { peer_id: Uuid, message: ProtocolMessage },
|
||||
Transaction { tx: core::Tx },
|
||||
CreateBlock,
|
||||
DebugListBlocks,
|
||||
DebugListPeers,
|
||||
DebugShowId,
|
||||
DebugDumpBlocks,
|
||||
ConnectToSeeds
|
||||
}
|
||||
|
||||
|
||||
impl NativeNode {
|
||||
|
||||
pub fn peer_addresses(&self) -> Vec<String> {
|
||||
let mut addr: Vec<String> = self.tcp_peers.iter().map(|p| p.1.addr.to_string()).collect();
|
||||
addr.push(self.addr.clone());
|
||||
addr
|
||||
pub fn peer_addresses(&self) -> Vec<String> {
|
||||
let mut addr: Vec<String> = self.tcp_peers.iter().map(|p| p.1.addr.to_string()).collect();
|
||||
addr.push(self.addr.clone());
|
||||
addr
|
||||
}
|
||||
|
||||
pub fn list_peers(&self) {
|
||||
println!("Peer List\n-----------");
|
||||
for (i, p) in self.tcp_peers.iter().enumerate() {
|
||||
println!("Peer #{i}: {}", p.1.id)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list_peers(&self) {
|
||||
println!("Peer List\n-----------");
|
||||
for (i, p) in self.tcp_peers.iter().enumerate() {
|
||||
println!("Peer #{i}: {}", p.1.id)
|
||||
}
|
||||
pub fn show_id(&self) {
|
||||
println!("Node Id: {}", self.id)
|
||||
}
|
||||
|
||||
fn remove_tcp_peer(&mut self, peer_id: Uuid) {
|
||||
log!(INFO, "Removing Peer {peer_id}");
|
||||
self.tcp_peers.remove_entry(&peer_id);
|
||||
}
|
||||
|
||||
fn add_tcp_peer(&mut self, id: Uuid, addr: String, sender: tokio::sync::mpsc::Sender<ProtocolMessage>) {
|
||||
let peer = TcpPeer {
|
||||
id: id,
|
||||
addr,
|
||||
sender
|
||||
};
|
||||
|
||||
log!(INFO, "Adding Peer {}", peer.id);
|
||||
|
||||
self.tcp_peers.insert(id, peer);
|
||||
}
|
||||
|
||||
pub fn new_with_id(
|
||||
id: uuid::Uuid,
|
||||
exec_tx: mpsc::Sender<ExecutorCommand>,
|
||||
rx: mpsc::Receiver<NodeCommand>
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
tcp_peers: HashMap::new(),
|
||||
chain: Default::default(),
|
||||
addr: String::new(),
|
||||
exec_tx,
|
||||
rx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show_id(&self) {
|
||||
println!("Node Id: {}", self.id)
|
||||
}
|
||||
|
||||
fn remove_tcp_peer(&mut self, peer_id: Uuid) {
|
||||
log!(INFO, "Removing Peer {peer_id}");
|
||||
self.tcp_peers.remove_entry(&peer_id);
|
||||
}
|
||||
|
||||
fn add_tcp_peer(&mut self, id: Uuid, addr: String, sender: tokio::sync::mpsc::Sender<ProtocolMessage>) {
|
||||
let peer = TcpPeer {
|
||||
id: id,
|
||||
addr,
|
||||
sender
|
||||
};
|
||||
|
||||
log!(INFO, "Adding Peer {}", peer.id);
|
||||
|
||||
self.tcp_peers.insert(id, peer);
|
||||
}
|
||||
|
||||
|
||||
fn persist(&mut self) {
|
||||
for t in self.chain.blocks() {
|
||||
let json = serde_json::to_string(&t).unwrap();
|
||||
self.db_file.write(json.as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_id(id: uuid::Uuid, chain: core::Blockchain, db_file: std::fs::File, addr: String) -> Self {
|
||||
Self {
|
||||
id,
|
||||
tcp_peers: HashMap::new(),
|
||||
ws: Vec::new(),
|
||||
chain,
|
||||
addr,
|
||||
db_file
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(chain: core::Blockchain, db_file: std::fs::File, addr: String) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
tcp_peers: HashMap::new(),
|
||||
ws: Vec::new(),
|
||||
chain,
|
||||
addr,
|
||||
db_file
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_handshake(id: uuid::Uuid, stream: &mut tokio::net::TcpStream) -> Result<uuid::Uuid, ValidationError> {
|
||||
let handshake = ProtocolMessage::Handshake { node_id: id.clone(), version: "".to_string() };
|
||||
NativeNode::send_message(stream, &handshake).await.unwrap();
|
||||
if let Ok(response) = NativeNode::receive_message(stream).await {
|
||||
match response {
|
||||
message::ProtocolMessage::Handshake { node_id, version: _ } => {
|
||||
Ok(node_id)
|
||||
},
|
||||
_ => {
|
||||
log!(ERROR, "Invalid response on Handshake");
|
||||
Err(ValidationError::InvalidBlockHash)
|
||||
}
|
||||
}
|
||||
pub fn new(
|
||||
addr: Option<String>,
|
||||
blocks: Option<Vec<core::Block>>,
|
||||
exec_tx: mpsc::Sender<ExecutorCommand>,
|
||||
rx: mpsc::Receiver<NodeCommand>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
tcp_peers: HashMap::new(),
|
||||
chain: {
|
||||
if blocks.is_some() {
|
||||
Blockchain::build(blocks.unwrap()).unwrap_or(Default::default())
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
},
|
||||
addr: if addr.is_some() { addr.unwrap() } else { String::new() },
|
||||
exec_tx,
|
||||
rx
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_handshake(id: uuid::Uuid, stream: &mut tokio::net::TcpStream) -> Result<uuid::Uuid, ValidationError> {
|
||||
let handshake = ProtocolMessage::Handshake { node_id: id.clone(), version: "".to_string() };
|
||||
NativeNode::send_message(stream, &handshake).await.unwrap();
|
||||
if let Ok(response) = NativeNode::receive_message(stream).await {
|
||||
match response {
|
||||
message::ProtocolMessage::Handshake { node_id, version: _ } => {
|
||||
Ok(node_id)
|
||||
},
|
||||
_ => {
|
||||
log!(ERROR, "Invalid response on Handshake");
|
||||
Err(ValidationError::InvalidBlockHash)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(ValidationError::InvalidBlockHash)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn bootstrap(&mut self) -> Result<(), ValidationError> {
|
||||
log!(INFO, "Running As Native Node");
|
||||
|
||||
let mut stream = tokio::net::TcpStream::connect(SEED_NODES[0]).await.unwrap();
|
||||
|
||||
let id = uuid::Uuid::new_v4();
|
||||
|
||||
if let Ok(_) = NativeNode::send_handshake(id, &mut stream).await {
|
||||
let message = message::ProtocolMessage::BootstrapRequest { node_id: id.clone(), version: "".to_string() };
|
||||
NativeNode::send_message(&mut stream, &message).await.unwrap();
|
||||
log!(INFO, "Sent BootstrapRequest to seed");
|
||||
if let Ok(response) = NativeNode::receive_message(&mut stream).await {
|
||||
match response {
|
||||
ProtocolMessage::BootstrapResponse { blocks } => {
|
||||
log!(INFO, "Received BootstrapResponse from seed");
|
||||
self.chain = core::Blockchain::build(blocks).unwrap();
|
||||
Ok(())
|
||||
},
|
||||
_ => {
|
||||
log!(ERROR, "Invalid Response from BootstrapRequest: {:?}", &response);
|
||||
Err(ValidationError::InvalidBlockHash)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(ValidationError::InvalidBlockHash)
|
||||
}
|
||||
} else {
|
||||
Err(ValidationError::InvalidBlockHash)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn bootstrap(addr: &str) -> Result<Self, ValidationError> {
|
||||
log!(INFO, "Running As Native Node");
|
||||
pub async fn broadcast_transaction(&self, tx: &core::Tx) {
|
||||
for (id, peer) in &self.tcp_peers {
|
||||
let message = ProtocolMessage::Transaction{peer_id: self.id, tx: tx.clone()};
|
||||
peer.sender.send(message).await.unwrap();
|
||||
log!(DEBUG, "Send Transaction message to {id}");
|
||||
}
|
||||
}
|
||||
|
||||
let mut stream = tokio::net::TcpStream::connect(SEED_NODES[0]).await.unwrap();
|
||||
pub async fn broadcast_block(&self, block: &core::Block) {
|
||||
for (id, peer) in &self.tcp_peers {
|
||||
let message = ProtocolMessage::Block{
|
||||
peer_id: self.id,
|
||||
height: self.chain.blocks().len() as u64,
|
||||
block: block.clone()
|
||||
};
|
||||
peer.sender.send(message).await.unwrap();
|
||||
log!(DEBUG, "Send Block message to {id}");
|
||||
}
|
||||
}
|
||||
|
||||
let id = uuid::Uuid::new_v4();
|
||||
pub async fn run_native(&mut self) {
|
||||
let tcp_listner = tokio::net::TcpListener::bind(&self.addr).await.unwrap();
|
||||
|
||||
if let Ok(_) = NativeNode::send_handshake(id, &mut stream).await {
|
||||
let message = message::ProtocolMessage::BootstrapRequest { node_id: id.clone(), version: "".to_string() };
|
||||
NativeNode::send_message(&mut stream, &message).await.unwrap();
|
||||
log!(INFO, "Sent BootstrapRequest to seed");
|
||||
if let Ok(response) = NativeNode::receive_message(&mut stream).await {
|
||||
match response {
|
||||
ProtocolMessage::BootstrapResponse { genesis, blocks } => {
|
||||
log!(INFO, "Received BootstrapResponse from seed");
|
||||
let chain = core::Blockchain::from_genesis(genesis, blocks)?;
|
||||
let node = Self::new_with_id(id, chain, std::fs::File::open("./database/tx.db").unwrap(), addr.to_string());
|
||||
Ok(node)
|
||||
},
|
||||
_ => {
|
||||
log!(ERROR, "Invalid Response from BootstrapRequest: {:?}", &response);
|
||||
Err(ValidationError::InvalidBlockHash)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(ValidationError::InvalidBlockHash)
|
||||
}
|
||||
} else {
|
||||
Err(ValidationError::InvalidBlockHash)
|
||||
let (channel_write, mut channel_read) = mpsc::channel::<NodeCommand>(100);
|
||||
|
||||
let id = self.id.clone();
|
||||
tokio::spawn({
|
||||
let c = channel_write.clone();
|
||||
async move {
|
||||
NativeNode::accept_connections(tcp_listner, c, id).await;
|
||||
}});
|
||||
|
||||
while let Some(command) = channel_read.recv().await {
|
||||
match command {
|
||||
NodeCommand::ConnectToSeeds => {
|
||||
self.connect_to_seeds(channel_write.clone()).await;
|
||||
},
|
||||
NodeCommand::AddPeer { peer_id, addr, sender } => {
|
||||
self.add_tcp_peer(peer_id, addr, sender);
|
||||
},
|
||||
NodeCommand::RemovePeer { peer_id } => {
|
||||
self.remove_tcp_peer(peer_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn seed(addr: String) -> Self {
|
||||
log!(INFO, "Running As Seed Node");
|
||||
let cwd = std::env::current_dir().unwrap();
|
||||
let mut genpath = std::path::PathBuf::from(&cwd);
|
||||
genpath.push("database");
|
||||
genpath.push("genesis.json");
|
||||
let mut gen_file = std::fs::File::open(genpath).unwrap();
|
||||
|
||||
let mut buf = String::new();
|
||||
gen_file.read_to_string(&mut buf).unwrap();
|
||||
|
||||
let mut db_file: std::fs::File = {
|
||||
let mut db_path = std::path::PathBuf::from(&cwd);
|
||||
db_path.push("database");
|
||||
db_path.push("tx.db");
|
||||
|
||||
std::fs::OpenOptions::new().read(true).write(true).create(true).open(&db_path).unwrap()
|
||||
};
|
||||
|
||||
let genesis = serde_json::from_str::<core::Genesis>(&buf).unwrap();
|
||||
|
||||
buf.clear();
|
||||
db_file.read_to_string(&mut buf).unwrap();
|
||||
|
||||
let buf = buf.trim();
|
||||
|
||||
log!(DEBUG, "Buf content: {:#?}", buf);
|
||||
let blocks = if !buf.is_empty() {
|
||||
serde_json::from_str::<Vec<core::Block>>(&buf).unwrap()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let chain = core::Blockchain::from_genesis(genesis, blocks).unwrap();
|
||||
|
||||
Self::new(chain, db_file, addr)
|
||||
}
|
||||
|
||||
pub async fn broadcast_transaction(&self, tx: &core::Tx) {
|
||||
for (id, peer) in &self.tcp_peers {
|
||||
let message = ProtocolMessage::Transaction{peer_id: self.id, tx: tx.clone()};
|
||||
peer.sender.send(message).await.unwrap();
|
||||
log!(DEBUG, "Send Transaction message to {id}");
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn broadcast_block(&self, block: &core::Block) {
|
||||
for (id, peer) in &self.tcp_peers {
|
||||
let message = ProtocolMessage::Block{
|
||||
peer_id: self.id,
|
||||
height: self.chain.blocks().len() as u64,
|
||||
block: block.clone()
|
||||
};
|
||||
peer.sender.send(message).await.unwrap();
|
||||
log!(DEBUG, "Send Block message to {id}");
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_native(&mut self) {
|
||||
let tcp_listner = tokio::net::TcpListener::bind(&self.addr).await.unwrap();
|
||||
|
||||
let (channel_write, mut channel_read) = mpsc::channel::<NodeCommand>(100);
|
||||
|
||||
let id = self.id.clone();
|
||||
tokio::spawn({
|
||||
let c = channel_write.clone();
|
||||
async move {
|
||||
NativeNode::accept_connections(tcp_listner, c, id).await;
|
||||
}});
|
||||
|
||||
tokio::spawn({
|
||||
let c = channel_write.clone();
|
||||
async move {
|
||||
NativeNode::cli(c).await;
|
||||
}
|
||||
});
|
||||
|
||||
while let Some(command) = channel_read.recv().await {
|
||||
match command {
|
||||
NodeCommand::ConnectToSeeds => {
|
||||
self.connect_to_seeds(channel_write.clone()).await;
|
||||
},
|
||||
NodeCommand::AddPeer { peer_id, addr, sender } => {
|
||||
self.add_tcp_peer(peer_id, addr, sender);
|
||||
},
|
||||
NodeCommand::RemovePeer { peer_id } => {
|
||||
self.remove_tcp_peer(peer_id);
|
||||
}
|
||||
NodeCommand::ProcessMessage { peer_id, message } => {
|
||||
self.process_message(peer_id, &message).await;
|
||||
},
|
||||
NodeCommand::Transaction { tx } => {
|
||||
self.chain.apply(&tx).unwrap();
|
||||
self.broadcast_transaction(&tx).await;
|
||||
},
|
||||
NodeCommand::CreateBlock => {
|
||||
log!(INFO, "Received CreateBlock Command");
|
||||
let block = self.chain.create_block();
|
||||
self.broadcast_block(&block).await;
|
||||
},
|
||||
NodeCommand::DebugListBlocks => {
|
||||
log!(INFO, "Received DebugListBlocks command");
|
||||
self.chain.print_blocks();
|
||||
},
|
||||
NodeCommand::DebugListPeers => {
|
||||
log!(INFO, "Received DebugListPeers command");
|
||||
self.list_peers();
|
||||
},
|
||||
NodeCommand::DebugShowId => {
|
||||
log!(INFO, "Received DebugListBlocks command");
|
||||
self.show_id();
|
||||
},
|
||||
NodeCommand::DebugDumpBlocks => {
|
||||
self.chain.dump_blocks(&mut self.db_file);
|
||||
}
|
||||
}
|
||||
NodeCommand::ProcessMessage { peer_id, message } => {
|
||||
self.process_message(peer_id, &message).await;
|
||||
},
|
||||
NodeCommand::Transaction { tx } => {
|
||||
self.chain.apply(&tx).unwrap();
|
||||
self.broadcast_transaction(&tx).await;
|
||||
},
|
||||
NodeCommand::CreateBlock => {
|
||||
log!(INFO, "Received CreateBlock Command");
|
||||
let block = self.chain.create_block();
|
||||
self.broadcast_block(&block).await;
|
||||
},
|
||||
NodeCommand::DebugListBlocks => {
|
||||
log!(INFO, "Received DebugListBlocks command");
|
||||
self.chain.print_blocks();
|
||||
},
|
||||
NodeCommand::DebugListPeers => {
|
||||
log!(INFO, "Received DebugListPeers command");
|
||||
self.list_peers();
|
||||
},
|
||||
NodeCommand::DebugShowId => {
|
||||
log!(INFO, "Received DebugListBlocks command");
|
||||
self.show_id();
|
||||
},
|
||||
NodeCommand::DebugDumpBlocks => {
|
||||
// self.chain.dump_blocks(&mut self.db_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
8
src/watcher.rs
Normal file
8
src/watcher.rs
Normal file
@ -0,0 +1,8 @@
|
||||
pub mod executor;
|
||||
pub mod parser;
|
||||
pub mod renderer;
|
||||
pub mod watcher;
|
||||
|
||||
pub use executor::*;
|
||||
pub use parser::*;
|
||||
pub use renderer::*;
|
||||
109
src/watcher/executor.rs
Normal file
109
src/watcher/executor.rs
Normal file
@ -0,0 +1,109 @@
|
||||
use crate::{native_node::node::NodeCommand, watcher::renderer::*};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
pub enum ExecutorCommand {
|
||||
NodeResponse(String),
|
||||
Echo(Vec<String>),
|
||||
InvalidCommand(String),
|
||||
Node(NodeCommand),
|
||||
Clear(RenderPane),
|
||||
Exit
|
||||
}
|
||||
|
||||
pub struct Executor {
|
||||
render_tx: mpsc::Sender<RenderCommand>,
|
||||
node_tx: mpsc::Sender<NodeCommand>,
|
||||
rx: mpsc::Receiver<ExecutorCommand>,
|
||||
exit: bool
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
pub fn new(render_tx: mpsc::Sender<RenderCommand>, node_tx: mpsc::Sender<NodeCommand>, rx: mpsc::Receiver<ExecutorCommand>) -> Self {
|
||||
Self {
|
||||
render_tx,
|
||||
node_tx,
|
||||
rx,
|
||||
exit: false
|
||||
}
|
||||
}
|
||||
|
||||
fn exit(&mut self) {
|
||||
self.exit = true
|
||||
}
|
||||
|
||||
async fn listen(&mut self) {
|
||||
if let Some(cmd) = self.rx.recv().await {
|
||||
let _ = self.execute(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_node_cmd(&self, cmd: NodeCommand) {
|
||||
self.node_tx.send(cmd).await.unwrap()
|
||||
}
|
||||
|
||||
async fn handle_node_cmd(&self, cmd: NodeCommand) {
|
||||
self.send_node_cmd(cmd).await;
|
||||
|
||||
// match cmd {
|
||||
// NodeCommand::AddPeer { peer_id, addr, sender } => {},
|
||||
// NodeCommand::RemovePeer { peer_id } => {},
|
||||
// NodeCommand::ProcessMessage { peer_id, message } => {},
|
||||
// NodeCommand::Transaction { tx } => {},
|
||||
// NodeCommand::CreateBlock => {},
|
||||
// NodeCommand::DebugListBlocks => {},
|
||||
// NodeCommand::DebugListPeers => {},
|
||||
// NodeCommand::DebugShowId => {},
|
||||
// NodeCommand::DebugDumpBlocks => {},
|
||||
// NodeCommand::ConnectToSeeds => {}
|
||||
// }
|
||||
}
|
||||
|
||||
fn render_string(&self, str: String) {
|
||||
let rd_cmd = RenderCommand::RenderStringToPane{
|
||||
str,
|
||||
pane: RenderPane::CliOutput
|
||||
};
|
||||
let _ = self.render_tx.send(rd_cmd);
|
||||
}
|
||||
|
||||
fn echo(&self, s: Vec<String>) {
|
||||
let mut str = s.join(" ");
|
||||
str.push_str("\n");
|
||||
let rd_cmd = RenderCommand::RenderStringToPane{
|
||||
str,
|
||||
pane: RenderPane::CliOutput
|
||||
};
|
||||
let _ = self.render_tx.send(rd_cmd);
|
||||
}
|
||||
|
||||
fn clear(&self, p: RenderPane) {
|
||||
let rd_cmd = RenderCommand::ClearPane(p);
|
||||
let _ = self.render_tx.send(rd_cmd);
|
||||
}
|
||||
|
||||
fn invalid_command(&self, str: String) {
|
||||
let rd_cmd = RenderCommand::RenderStringToPane{
|
||||
str,
|
||||
pane: RenderPane::CliOutput
|
||||
};
|
||||
let _ = self.render_tx.send(rd_cmd);
|
||||
}
|
||||
|
||||
async fn execute(&mut self, cmd: ExecutorCommand) {
|
||||
match cmd {
|
||||
ExecutorCommand::NodeResponse(resp) => self.render_string(resp),
|
||||
ExecutorCommand::Node(n) => self.handle_node_cmd(n).await,
|
||||
ExecutorCommand::Clear(p) => self.clear(p),
|
||||
ExecutorCommand::Echo(s) => self.echo(s),
|
||||
ExecutorCommand::InvalidCommand(str) => self.invalid_command(str),
|
||||
ExecutorCommand::Exit => self.exit(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) {
|
||||
while !self.exit {
|
||||
self.listen().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
144
src/watcher/parser.rs
Normal file
144
src/watcher/parser.rs
Normal file
@ -0,0 +1,144 @@
|
||||
use crate::native_node::node::NodeCommand;
|
||||
use crate::watcher::executor::{ExecutorCommand};
|
||||
use vlogger::*;
|
||||
|
||||
use crate::core;
|
||||
|
||||
use tokio::time::{timeout, Duration};
|
||||
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::watcher::renderer::RenderPane;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Parser {
|
||||
rx: mpsc::Receiver<ParserCommand>,
|
||||
exec_tx: mpsc::Sender<ExecutorCommand>,
|
||||
exit: bool
|
||||
}
|
||||
|
||||
pub enum ParserCommand {
|
||||
ParseCmdString(String),
|
||||
Exit
|
||||
}
|
||||
|
||||
const CMD_ECHO: &str = "echo";
|
||||
const CMD_CLEAR: &str = "clear";
|
||||
const CMD_NODE: &str = "node";
|
||||
|
||||
impl Parser {
|
||||
pub fn new(
|
||||
rx: mpsc::Receiver<ParserCommand>,
|
||||
exec_tx: mpsc::Sender<ExecutorCommand>
|
||||
) -> Self {
|
||||
Self {
|
||||
rx,
|
||||
exec_tx,
|
||||
exit: false
|
||||
}
|
||||
}
|
||||
|
||||
fn exit(&mut self) {
|
||||
self.exit = true;
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) {
|
||||
while !self.exit {
|
||||
self.listen().await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn listen(&mut self) {
|
||||
if let Ok(Some(mes)) = timeout(Duration::from_millis(400), self.rx.recv()).await {
|
||||
match mes {
|
||||
ParserCommand::ParseCmdString(s) => {
|
||||
let s_split: Vec<&str> = s.split(" ").collect();
|
||||
|
||||
if s_split.len() != 0 {
|
||||
let cmd = &s_split[0];
|
||||
let args = &s_split[1..];
|
||||
let exec_cmd = match *cmd {
|
||||
CMD_NODE => {
|
||||
if !args.is_empty() {
|
||||
match args[0] {
|
||||
"id" => {
|
||||
ExecutorCommand::Node(NodeCommand::DebugShowId)
|
||||
},
|
||||
"tx" => {
|
||||
if args.len() != 4 {
|
||||
log!(ERROR, "Invalid arg count! Expected {}, got {}", 4, args.len());
|
||||
}
|
||||
let from = args[0];
|
||||
let to = args[1];
|
||||
let value = args[2].parse::<u32>().unwrap();
|
||||
let data = args[3];
|
||||
|
||||
let tx = core::Tx::new(
|
||||
from.to_string(),
|
||||
to.to_string(),
|
||||
value,
|
||||
data.to_string()
|
||||
);
|
||||
|
||||
ExecutorCommand::Node(NodeCommand::Transaction { tx })
|
||||
},
|
||||
"block" => {
|
||||
ExecutorCommand::Node(NodeCommand::CreateBlock)
|
||||
},
|
||||
"list" => {
|
||||
if args.len() != 1 {
|
||||
log!(ERROR, "{cmd}: Invalid arg! (blocks, peers)");
|
||||
}
|
||||
match args[0] {
|
||||
"blocks" => ExecutorCommand::Node(NodeCommand::DebugListBlocks),
|
||||
"peers" => ExecutorCommand::Node(NodeCommand::DebugListPeers),
|
||||
_ => ExecutorCommand::InvalidCommand(msg!(ERROR, "Unkown arg: {}", args[0])),
|
||||
}
|
||||
},
|
||||
"dump_blocks" => {
|
||||
ExecutorCommand::Node(NodeCommand::DebugDumpBlocks)
|
||||
},
|
||||
"connect" => {
|
||||
ExecutorCommand::Node(NodeCommand::ConnectToSeeds)
|
||||
}
|
||||
_ => {
|
||||
ExecutorCommand::InvalidCommand(msg!(ERROR, "node: unknown argument {}", args[0]))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ExecutorCommand::InvalidCommand(msg!(ERROR, "node: expected arguments"))
|
||||
}
|
||||
}
|
||||
CMD_ECHO => {
|
||||
if args.is_empty() {
|
||||
ExecutorCommand::InvalidCommand(msg!(ERROR, "print expects args"))
|
||||
} else {
|
||||
ExecutorCommand::Echo(args.iter().map(|a| a.to_string()).collect())
|
||||
}
|
||||
}
|
||||
CMD_CLEAR => {
|
||||
if args.is_empty() {
|
||||
ExecutorCommand::Clear(RenderPane::All)
|
||||
} else if args[0] == "in" {
|
||||
ExecutorCommand::Clear(RenderPane::CliInput)
|
||||
} else if args[0] == "out" {
|
||||
ExecutorCommand::Clear(RenderPane::CliOutput)
|
||||
} else {
|
||||
ExecutorCommand::InvalidCommand(msg!(ERROR, "clear: Unknown arg {}", args[0]))
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
ExecutorCommand::InvalidCommand(msg!(ERROR, "Unknown Command {cmd}"))
|
||||
}
|
||||
};
|
||||
let _ = self.exec_tx.send(exec_cmd);
|
||||
}
|
||||
}
|
||||
ParserCommand::Exit => {
|
||||
self.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
210
src/watcher/renderer.rs
Normal file
210
src/watcher/renderer.rs
Normal file
@ -0,0 +1,210 @@
|
||||
use crossterm::event::KeyCode;
|
||||
use ratatui::prelude::*;
|
||||
use ratatui::widgets::Wrap;
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
symbols::border,
|
||||
widgets::{Block, Paragraph, Widget},
|
||||
DefaultTerminal, Frame,
|
||||
|
||||
};
|
||||
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::{timeout, Duration};
|
||||
use std::io;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Renderer {
|
||||
buffer: String,
|
||||
exit: bool,
|
||||
rx: mpsc::Receiver<RenderCommand>,
|
||||
layout: RenderLayout
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Pane {
|
||||
title: Option<String>,
|
||||
target: RenderPane,
|
||||
layout_index: u8,
|
||||
buffer: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum RenderPane {
|
||||
All,
|
||||
CliInput,
|
||||
CliOutput
|
||||
}
|
||||
|
||||
pub enum RenderCommand {
|
||||
RenderStringToPane{
|
||||
str: String,
|
||||
pane: RenderPane
|
||||
},
|
||||
RenderInput(KeyCode),
|
||||
ChangeLayout(RenderLayoutKind),
|
||||
ClearPane(RenderPane),
|
||||
Exit,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RenderLayoutKind {
|
||||
Cli,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RenderLayout {
|
||||
kind: RenderLayoutKind,
|
||||
panes: Vec<Pane>,
|
||||
}
|
||||
|
||||
impl RenderLayoutKind {
|
||||
pub fn rects(&self, area: Rect) -> std::rc::Rc<[Rect]> {
|
||||
match self {
|
||||
Self::Cli => {
|
||||
Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(vec![
|
||||
Constraint::Percentage(30),
|
||||
Constraint::Percentage(70)
|
||||
])
|
||||
.split(area)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate(&self) -> RenderLayout {
|
||||
RenderLayout {
|
||||
kind: self.clone(),
|
||||
panes: vec![
|
||||
Pane {
|
||||
title: Some(" Input Pane ".to_string()),
|
||||
target: RenderPane::CliInput,
|
||||
layout_index: 0,
|
||||
buffer: String::with_capacity(CLI_INPUT_BUFFE_SIZE) + "> ",
|
||||
},
|
||||
Pane {
|
||||
title: Some(" Output Pane ".to_string()),
|
||||
target: RenderPane::CliOutput,
|
||||
layout_index: 1,
|
||||
buffer: String::with_capacity(CLI_OUTPUT_BUFFE_SIZE),
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CLI_INPUT_BUFFE_SIZE: usize = 4096;
|
||||
const CLI_OUTPUT_BUFFE_SIZE: usize = 4096;
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Renderer {
|
||||
pub fn new(rx: mpsc::Receiver<RenderCommand>, layout: RenderLayoutKind) -> Self {
|
||||
Self {
|
||||
buffer: String::new(),
|
||||
rx,
|
||||
exit: false,
|
||||
layout: layout.generate()
|
||||
}
|
||||
}
|
||||
pub async fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> {
|
||||
while !self.exit {
|
||||
terminal.draw(|frame| self.draw(frame))?;
|
||||
self.listen().await?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw(&self, frame: &mut Frame) {
|
||||
frame.render_widget(self, frame.area());
|
||||
}
|
||||
|
||||
fn exit(&mut self) {
|
||||
self.exit = true;
|
||||
}
|
||||
|
||||
fn buffer_extend<S: AsRef<str>>(&mut self, input: S) {
|
||||
self.buffer.push_str(input.as_ref());
|
||||
}
|
||||
|
||||
fn input_pane(&mut self) -> Option<&mut Pane> {
|
||||
self.layout.panes.iter_mut().find(|p| p.target == RenderPane::CliInput)
|
||||
}
|
||||
|
||||
async fn listen(&mut self) -> io::Result<()> {
|
||||
if let Ok(Some(mes)) = timeout(Duration::from_millis(400), self.rx.recv()).await {
|
||||
match mes {
|
||||
RenderCommand::RenderInput(k) => {
|
||||
if let Some(p) = self.input_pane() {
|
||||
match k {
|
||||
KeyCode::Char(c) => {
|
||||
p.buffer.push(c);
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
if !p.buffer.ends_with("> ") {
|
||||
p.buffer.pop();
|
||||
}
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
p.buffer.push_str("\n> ");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
},
|
||||
RenderCommand::RenderStringToPane{ str, pane } => {
|
||||
if let Some(p) = self.layout.panes.iter_mut().find(|p| p.target == pane) {
|
||||
p.buffer.push_str(&str);
|
||||
}
|
||||
}
|
||||
RenderCommand::Exit => {
|
||||
self.exit();
|
||||
}
|
||||
RenderCommand::ChangeLayout(l) => {
|
||||
match l {
|
||||
RenderLayoutKind::Cli => {
|
||||
self.layout = l.generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
RenderCommand::ClearPane(pane) => {
|
||||
if matches!(pane, RenderPane::All) {
|
||||
for p in self.layout.panes.iter_mut() {
|
||||
p.buffer.clear();
|
||||
}
|
||||
} else if let Some(p) = self.layout.panes.iter_mut().find(|p| p.target == pane) {
|
||||
p.buffer.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &Renderer {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
|
||||
let layout = self.layout.kind.rects(area);
|
||||
|
||||
for p in self.layout.panes.iter() {
|
||||
let block = Block::bordered()
|
||||
.title({
|
||||
if let Some(t) = &p.title {
|
||||
t.clone()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
})
|
||||
.border_set(border::THICK);
|
||||
|
||||
Paragraph::new(p.buffer.clone())
|
||||
.wrap(Wrap::default())
|
||||
.left_aligned()
|
||||
.block(block)
|
||||
.render(layout[p.layout_index as usize], buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
141
src/watcher/watcher.rs
Normal file
141
src/watcher/watcher.rs
Normal file
@ -0,0 +1,141 @@
|
||||
use crate::watcher::*;
|
||||
|
||||
use crossterm::{event::{self, Event, KeyCode, KeyEventKind}};
|
||||
use tokio::sync::mpsc;
|
||||
use std::io::{self};
|
||||
|
||||
use crate::{native_node::node::{NativeNode, NodeCommand}};
|
||||
|
||||
pub struct Watcher {
|
||||
render_tx: mpsc::Sender<RenderCommand>,
|
||||
parser_tx: mpsc::Sender<ParserCommand>,
|
||||
node_tx: mpsc::Sender<NodeCommand>,
|
||||
exec_tx: mpsc::Sender<ExecutorCommand>,
|
||||
cmd_buffer: String,
|
||||
handles: Vec<tokio::task::JoinHandle<()>>
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WatcherBuilder {
|
||||
addr: Option<String>,
|
||||
seed_file: Option<String>
|
||||
}
|
||||
|
||||
impl Watcher {
|
||||
pub fn build() -> WatcherBuilder {
|
||||
WatcherBuilder::new()
|
||||
}
|
||||
|
||||
pub async fn exit(self) {
|
||||
ratatui::restore();
|
||||
// for (i, handle) in self.handles.into_iter().enumerate() {
|
||||
// let _ = handle.await;
|
||||
// println!("Joined thread #{i}")
|
||||
// }
|
||||
}
|
||||
|
||||
pub async fn poll(&mut self) -> io::Result<bool> {
|
||||
match event::read()? {
|
||||
Event::Key(k) if k.kind == KeyEventKind::Press => {
|
||||
match k.code {
|
||||
KeyCode::Char(c) => {
|
||||
self.cmd_buffer.push(c);
|
||||
let message = RenderCommand::RenderInput(k.code);
|
||||
let _ = self.render_tx.send(message);
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
self.cmd_buffer.pop();
|
||||
let message = RenderCommand::RenderInput(k.code);
|
||||
let _ = self.render_tx.send(message);
|
||||
},
|
||||
KeyCode::Enter => {
|
||||
let rd_mes = RenderCommand::RenderInput(k.code);
|
||||
let pr_mes = ParserCommand::ParseCmdString(self.cmd_buffer.clone());
|
||||
let _ = self.render_tx.send(rd_mes);
|
||||
let _ = self.parser_tx.send(pr_mes);
|
||||
self.cmd_buffer.clear();
|
||||
}
|
||||
KeyCode::Esc => {
|
||||
let rd_mes = RenderCommand::Exit;
|
||||
let pr_mes = ParserCommand::Exit;
|
||||
let exec_mes = ExecutorCommand::Exit;
|
||||
let _ = self.render_tx.send(rd_mes);
|
||||
let _ = self.parser_tx.send(pr_mes);
|
||||
let _ = self.exec_tx.send(exec_mes);
|
||||
return Ok(false);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl WatcherBuilder {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn addr(mut self, addr: Option<String>) -> Self {
|
||||
self.addr = addr;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn file(mut self, seed_file: Option<String>) -> Self {
|
||||
self.seed_file = seed_file;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn start(self) -> Watcher {
|
||||
let (render_tx, render_rx) = mpsc::channel::<RenderCommand>(100);
|
||||
let (parser_tx, parser_rx) = mpsc::channel::<ParserCommand>(100);
|
||||
let (exec_tx, exec_rx) = mpsc::channel::<ExecutorCommand>(100);
|
||||
let (node_tx, node_rx) = mpsc::channel::<NodeCommand>(100);
|
||||
|
||||
let mut terminal = ratatui::init();
|
||||
let render_handle = tokio::spawn({
|
||||
async move {
|
||||
let _ = Renderer::new(render_rx, RenderLayoutKind::Cli).run(&mut terminal).await;
|
||||
}
|
||||
});
|
||||
|
||||
let parser_handle = tokio::spawn({
|
||||
let exec_tx = exec_tx.clone();
|
||||
async move {
|
||||
let _ = Parser::new(parser_rx, exec_tx).run().await;
|
||||
}
|
||||
});
|
||||
|
||||
let executor_handle = tokio::spawn({
|
||||
let rend_tx = render_tx.clone();
|
||||
let node_tx = node_tx.clone();
|
||||
async move {
|
||||
let _ = Executor::new(rend_tx, node_tx, exec_rx).run().await;
|
||||
}
|
||||
});
|
||||
|
||||
let blocks = self.seed_file
|
||||
.as_ref()
|
||||
.and_then(|path| std::fs::read_to_string(path).ok())
|
||||
.and_then(|content| serde_json::from_str(&content).ok());
|
||||
|
||||
|
||||
let node_handle = tokio::spawn({
|
||||
let exec_tx = exec_tx.clone();
|
||||
async move {
|
||||
let _ = NativeNode::new(self.addr.clone(), blocks, exec_tx, node_rx);
|
||||
}
|
||||
});
|
||||
|
||||
Watcher {
|
||||
render_tx,
|
||||
node_tx,
|
||||
parser_tx,
|
||||
exec_tx,
|
||||
cmd_buffer: String::new(),
|
||||
handles: vec![parser_handle, render_handle, executor_handle, node_handle]
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user