diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6be3e3bda..6fed256af 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -10,6 +10,9 @@ jobs: - name: Checkout sources uses: actions/checkout@v2 + - name: Install protoc + run: sudo apt install -y protobuf-compiler + - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: @@ -50,6 +53,9 @@ jobs: if: runner.os == 'Windows' run: git config --global core.autocrlf false + - name: Install protoc + run: sudo apt install -y protobuf-compiler + - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: @@ -96,6 +102,9 @@ jobs: # if: runner.os == 'Windows' # run: git config --global core.autocrlf false + # - name: Install protoc + # run: sudo apt install -y protobuf-compiler + # - name: Install stable toolchain # uses: actions-rs/toolchain@v1 # with: @@ -133,6 +142,9 @@ jobs: - name: Checkout sources uses: actions/checkout@v2 + - name: Install protoc + run: sudo apt install -y protobuf-compiler + - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: diff --git a/.rustfmt.toml b/.rustfmt.toml index 036fe91b0..50c6d244b 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -2,5 +2,5 @@ max_width = 135 use_field_init_shorthand = true use_try_shorthand = true use_small_heuristics = "Max" -newline_style = "unix" +newline_style = "auto" edition = "2021" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 6c7e6db58..cea3fe794 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,12 +15,41 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.7", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf6ccdb167abbf410dcb915cabd428929d7f6a04980b54a11f26a39f1c7f7107" +dependencies = [ + "cfg-if", + "getrandom 0.2.7", + "once_cell", + "version_check", +] + [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "anyhow" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" + [[package]] name = "arrayref" version = "0.3.6" @@ -42,6 +71,157 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "async-channel" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" +dependencies = [ + "concurrent-queue 1.2.4", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue 2.0.0", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8121296a9f05be7f34aa4196b1747243b3b62e048bb7906f644f3fbfc490cf7" +dependencies = [ + "async-lock", + "autocfg", + "concurrent-queue 1.2.4", + "futures-lite", + "libc", + "log", + "parking", + "polling", + "slab", + "socket2", + "waker-fn", + "winapi", +] + +[[package]] +name = "async-lock" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +dependencies = [ + "event-listener", + "futures-lite", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-attributes", + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-task" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" + +[[package]] +name = "async-trait" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" + [[package]] name = "atty" version = "0.2.14" @@ -59,6 +239,63 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa 1.0.3", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "bincode" version = "1.3.3" @@ -93,6 +330,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + [[package]] name = "blake2b_simd" version = "1.0.0" @@ -104,6 +350,30 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "blake3" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" +dependencies = [ + "arrayref", + "arrayvec 0.7.2", + "cc", + "cfg-if", + "constant_time_eq", + "digest 0.10.5", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "block-buffer" version = "0.10.3" @@ -113,12 +383,125 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "blocking" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" +dependencies = [ + "async-channel", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "once_cell", +] + +[[package]] +name = "borsh" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" +dependencies = [ + "borsh-derive", + "hashbrown 0.11.2", +] + +[[package]] +name = "borsh-derive" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate", + "proc-macro2", + "syn", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + [[package]] name = "bumpalo" version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +[[package]] +name = "bv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" +dependencies = [ + "feature-probe", + "serde", +] + +[[package]] +name = "bytemuck" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fe233b960f12f8007e3db2d136e3cb1c291bfd7396e384ee76025fc1a3932b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + [[package]] name = "bzip2-sys" version = "0.1.11+1.0.8" @@ -130,6 +513,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" + [[package]] name = "cast" version = "0.3.0" @@ -219,6 +608,24 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "concurrent-queue" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "concurrent-queue" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "consensus" version = "0.1.0" @@ -240,7 +647,7 @@ dependencies = [ "muhash", "parking_lot", "pow", - "rand", + "rand 0.8.5", "rand_distr", "rayon", "rocksdb", @@ -257,6 +664,7 @@ dependencies = [ name = "consensus-core" version = "0.1.0" dependencies = [ + "borsh", "faster-hex", "hashes", "math", @@ -268,31 +676,63 @@ dependencies = [ ] [[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "cpufeatures" -version = "0.2.5" +name = "console_error_panic_hook" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ - "libc", + "cfg-if", + "wasm-bindgen", ] [[package]] -name = "crc32fast" -version = "1.3.2" +name = "console_log" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "501a375961cef1a0d44767200e66e4a559283097e91d0730b1d75dfb2f8a1494" dependencies = [ - "cfg-if", + "log", + "web-sys", ] [[package]] -name = "criterion" +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" @@ -370,6 +810,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -380,6 +826,26 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "ctrlc" version = "3.2.3" @@ -390,14 +856,51 @@ dependencies = [ "winapi", ] +[[package]] +name = "curve25519-dalek" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ - "block-buffer", + "block-buffer 0.10.3", "crypto-common", + "subtle", ] [[package]] @@ -406,6 +909,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "faster-hex" version = "0.6.1" @@ -421,6 +930,18 @@ dependencies = [ "instant", ] +[[package]] +name = "feature-probe" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.24" @@ -431,28 +952,114 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" -version = "0.3.24" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-executor" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" [[package]] name = "futures-task" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-util" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -461,10 +1068,24 @@ version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ + "serde", "typenum", "version_check", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + [[package]] name = "getrandom" version = "0.2.7" @@ -472,8 +1093,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -482,34 +1105,84 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "gloo-timers" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash 0.7.6", +] + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.6", +] [[package]] name = "hashes" version = "0.1.0" dependencies = [ "blake2b_simd", + "borsh", "cc", "criterion", "faster-hex", "keccak", "once_cell", - "rand", + "rand 0.8.5", "serde", - "sha2", + "sha2 0.10.6", "sha3", ] +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -519,6 +1192,119 @@ dependencies = [ "libc", ] +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.3", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 1.0.3", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core 0.6.4", + "rand_xoshiro", + "rayon", + "serde", + "sized-chunks", + "typenum", + "version_check", +] + [[package]] name = "indexmap" version = "1.9.1" @@ -526,7 +1312,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] @@ -536,6 +1322,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -591,6 +1380,15 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "kaspa-core" version = "0.1.0" @@ -603,6 +1401,10 @@ dependencies = [ [[package]] name = "kaspa-utils" version = "0.1.0" +dependencies = [ + "async-std", + "triggered", +] [[package]] name = "kaspa-wallet" @@ -627,7 +1429,7 @@ dependencies = [ "hashes", "kaspa-core", "num-format", - "rand", + "rand 0.8.5", "rand_distr", "rayon", "tempfile", @@ -641,6 +1443,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -691,7 +1502,55 @@ dependencies = [ ] [[package]] -name = "libz-sys" +name = "libsecp256k1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d220bc1feda2ac231cb78c3d26f27676b8cf82c96971f7aeef3d0cf2797c73" +dependencies = [ + "arrayref", + "base64 0.12.3", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libz-sys" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" @@ -738,15 +1597,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", + "value-bag", +] + +[[package]] +name = "manual_future" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943968aefb9b0fdf36cccc03f6cd9d6698b23574ab49eccc185ae6c5cb6ad43e" +dependencies = [ + "futures", ] +[[package]] +name = "matchit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" + [[package]] name = "math" version = "0.1.0" dependencies = [ + "borsh", "criterion", "faster-hex", - "rand_chacha", + "rand_chacha 0.3.1", "serde", ] @@ -756,6 +1632,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memmap2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -772,6 +1657,12 @@ dependencies = [ "hashes", ] +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -787,6 +1678,18 @@ dependencies = [ "adler", ] +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.42.0", +] + [[package]] name = "muhash" version = "0.1.0" @@ -794,12 +1697,18 @@ dependencies = [ "criterion", "hashes", "math", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "rayon", "serde", ] +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + [[package]] name = "nix" version = "0.25.0" @@ -828,6 +1737,17 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "num-format" version = "0.4.0" @@ -870,12 +1790,24 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "os_str_bytes" version = "6.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + [[package]] name = "parking_lot" version = "0.12.1" @@ -896,7 +1828,36 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.36.1", +] + +[[package]] +name = "parse-variants" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb0dafde854639f86e6ddd8af43386cec411b205c440e622e11b87c9a30e0421" +dependencies = [ + "parse-variants-derive", +] + +[[package]] +name = "parse-variants-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cea6089666c59f944aaf46cab9d2ae1bff85e01c42c49a29e78ece5d39b5abd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pbkdf2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +dependencies = [ + "crypto-mac", ] [[package]] @@ -905,6 +1866,42 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "petgraph" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -923,6 +1920,20 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +[[package]] +name = "polling" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4609a838d88b73d8238967b60dd115cc08d38e2bbaf51ee1e4b695f89122e2" +dependencies = [ + "autocfg", + "cfg-if", + "libc", + "log", + "wepoll-ffi", + "winapi", +] + [[package]] name = "pow" version = "0.1.0" @@ -939,6 +1950,25 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "prettyplease" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c142c0e46b57171fe0c528bee8c5b7569e80f0c17e377cd0e30ea57dbc11bb51" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + [[package]] name = "proc-macro2" version = "1.0.43" @@ -948,6 +1978,61 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0841812012b2d4a6145fae9a6af1534873c32aa67fff26bd09f8fa42c83f95a" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8b442418ea0822409d9e7d047cbf1e7e9e1760b172bf9982cf29d517c93511" +dependencies = [ + "bytes", + "heck", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "164ae68b6587001ca506d3bf7f1000bfa248d0e1217b618108fba4ec1d0cc306" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747761bc3dc48f9a34553bf65605cf6cb6288ba219f3450b4275dbd81539551a" +dependencies = [ + "bytes", + "prost", +] + [[package]] name = "quote" version = "1.0.21" @@ -957,6 +2042,19 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + [[package]] name = "rand" version = "0.8.5" @@ -964,8 +2062,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] @@ -975,7 +2083,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", ] [[package]] @@ -984,7 +2101,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.7", ] [[package]] @@ -994,7 +2111,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", - "rand", + "rand 0.8.5", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", ] [[package]] @@ -1064,12 +2199,71 @@ dependencies = [ "librocksdb-sys", ] +[[package]] +name = "rpc-core" +version = "0.1.0" +dependencies = [ + "ahash 0.8.2", + "async-std", + "async-trait", + "borsh", + "consensus-core", + "derive_more", + "faster-hex", + "futures", + "hashes", + "kaspa-core", + "kaspa-utils", + "math", + "rand 0.8.5", + "serde", + "thiserror", + "triggered", + "workflow-core", +] + +[[package]] +name = "rpc-grpc" +version = "0.1.0" +dependencies = [ + "async-std", + "async-trait", + "faster-hex", + "futures", + "h2", + "kaspa-core", + "kaspa-utils", + "prost", + "rpc-core", + "thiserror", + "tokio", + "tokio-stream", + "tonic", + "tonic-build", + "triggered", +] + [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" + [[package]] name = "ryu" version = "1.0.11" @@ -1097,7 +2291,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff55dc09d460954e9ef2fa8a7ced735a964be9981fd50e870b2b3b0705e14964" dependencies = [ - "rand", + "rand 0.8.5", "secp256k1-sys", ] @@ -1110,6 +2304,12 @@ dependencies = [ "cc", ] +[[package]] +name = "semver" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" + [[package]] name = "serde" version = "1.0.145" @@ -1119,6 +2319,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.145" @@ -1141,6 +2350,19 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha2" version = "0.10.6" @@ -1149,7 +2371,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.5", ] [[package]] @@ -1158,7 +2380,7 @@ version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2904bea16a1ae962b483322a1c7b81d976029203aea1f461e51cd7705db7ba9" dependencies = [ - "digest", + "digest 0.10.5", "keccak", ] @@ -1169,14 +2391,157 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] -name = "smallvec" -version = "1.10.0" +name = "sized-chunks" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" dependencies = [ - "serde", + "bitmaps", + "typenum", ] +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "solana-frozen-abi" +version = "1.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9d5107e663df4a87c658ee764e9f0e4d15adf8bc1d1c9088b45ed8eaaf4958" +dependencies = [ + "ahash 0.7.6", + "blake3", + "block-buffer 0.9.0", + "bs58", + "bv", + "byteorder", + "cc", + "either", + "generic-array", + "getrandom 0.1.16", + "hashbrown 0.12.3", + "im", + "lazy_static", + "log", + "memmap2", + "once_cell", + "rand_core 0.6.4", + "rustc_version", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "sha2 0.10.6", + "solana-frozen-abi-macro", + "subtle", + "thiserror", +] + +[[package]] +name = "solana-frozen-abi-macro" +version = "1.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e4600fe5ae28cec848debc4ea3b41f34d9d8fd088aca209fbb1e8205489d08d" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "solana-program" +version = "1.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512475cccb7e13f96ba76ed091b2d79a8431a485c73be728cd2235f9adba5a4e" +dependencies = [ + "base64 0.13.1", + "bincode", + "bitflags", + "blake3", + "borsh", + "borsh-derive", + "bs58", + "bv", + "bytemuck", + "cc", + "console_error_panic_hook", + "console_log", + "curve25519-dalek", + "getrandom 0.2.7", + "itertools", + "js-sys", + "lazy_static", + "libc", + "libsecp256k1", + "log", + "memoffset", + "num-derive", + "num-traits", + "parking_lot", + "rand 0.7.3", + "rand_chacha 0.2.2", + "rustc_version", + "rustversion", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "sha2 0.10.6", + "sha3", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-sdk-macro", + "thiserror", + "tiny-bip39", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "solana-sdk-macro" +version = "1.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08cc4804804ecb9eb07a16c7ff2d4a770fe0533298f36f867a5efc2e3284745" +dependencies = [ + "bs58", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + [[package]] name = "syn" version = "1.0.100" @@ -1188,6 +2553,24 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + [[package]] name = "tempfile" version = "3.3.0" @@ -1228,6 +2611,25 @@ dependencies = [ "syn", ] +[[package]] +name = "tiny-bip39" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +dependencies = [ + "anyhow", + "hmac", + "once_cell", + "pbkdf2", + "rand 0.7.3", + "rustc-hash", + "sha2 0.9.9", + "thiserror", + "unicode-normalization", + "wasm-bindgen", + "zeroize", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -1238,6 +2640,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + [[package]] name = "tokio" version = "1.21.1" @@ -1245,10 +2662,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0020c875007ad96677dcc890298f4b942882c5d4eb7cc8f439fc3bf813dc9c95" dependencies = [ "autocfg", + "bytes", + "libc", + "memchr", + "mio", "num_cpus", "once_cell", "pin-project-lite", + "socket2", "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", ] [[package]] @@ -1262,6 +2695,192 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-stream" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tonic" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55b9af819e54b8f33d453655bef9b9acc171568fb49523078d0cc4e7484200ec" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.13.1", + "bytes", + "flate2", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "prost-derive", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tonic-build" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c6fd7c2581e36d63388a9e04c350c21beb7a8b059580b2e93993c526899ddc" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "triggered" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce148eae0d1a376c1b94ae651fc3261d9cb8294788b962b7382066376503a2d1" + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + [[package]] name = "typenum" version = "1.15.0" @@ -1274,13 +2893,38 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "uuid" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom", + "getrandom 0.2.7", +] + +[[package]] +name = "value-bag" +version = "1.0.0-alpha.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" +dependencies = [ + "ctor", + "version_check", ] [[package]] @@ -1295,6 +2939,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.2" @@ -1306,6 +2956,22 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1339,6 +3005,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.83" @@ -1368,6 +3046,36 @@ version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + +[[package]] +name = "which" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1405,43 +3113,172 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", ] +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + [[package]] name = "windows_i686_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + [[package]] name = "windows_i686_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "workflow-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad499a77165d951e2c45bb603a248282f7a87fa584331a69d9d5b539bf92f60a" +dependencies = [ + "async-std", + "borsh", + "bs58", + "cfg-if", + "futures", + "instant", + "manual_future", + "rand 0.7.3", + "serde", + "solana-program", + "thiserror", + "tokio", + "triggered", + "wasm-bindgen", + "workflow-core-macros", +] + +[[package]] +name = "workflow-core-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff71b48e62ee96756e65b4a0de3ca722accbc35a3209bbca6f80e6f210ee59e" +dependencies = [ + "convert_case 0.5.0", + "parse-variants", + "proc-macro2", + "quote", + "sha2 0.10.6", + "syn", + "workflow-macro-tools", +] + +[[package]] +name = "workflow-macro-tools" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33bfaec4d4c858470b43ee5bbc8a80a06af52a9ab961ca9a0cf0dfa6e6c4db3" +dependencies = [ + "convert_case 0.5.0", + "parse-variants", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zstd-sys" version = "2.0.1+zstd.1.5.2" diff --git a/Cargo.toml b/Cargo.toml index 607e0e5cf..6b5c5e70b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,8 @@ members = [ "crypto/muhash", "crypto/addresses", "crypto/merkle", + "rpc/core", + "rpc/grpc", ] [workspace.package] @@ -35,6 +37,8 @@ addresses = { path = "crypto/addresses" } merkle = { path = "crypto/merkle" } pow = { path = "consensus/pow" } kaspa-utils = { path = "utils" } +rpc-core = { path = "rpc/core" } +rpc-grpc = { path = "rpc/grpc" } thiserror = "1" faster-hex = "0.6" @@ -53,3 +57,6 @@ wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } criterion = { version = "0.4", default-features = false } indexmap = "1.9.1" smallvec = { version = "1.10.0", features = ["serde"] } +borsh = "0.9.3" +async-std = { version = "1.12.0", features = ['attributes'] } +derive_more = { version = "0.99" } diff --git a/consensus/core/Cargo.toml b/consensus/core/Cargo.toml index d444ed0d2..323e264a6 100644 --- a/consensus/core/Cargo.toml +++ b/consensus/core/Cargo.toml @@ -15,3 +15,4 @@ thiserror.workspace = true serde.workspace = true faster-hex.workspace = true smallvec.workspace = true +borsh.workspace = true \ No newline at end of file diff --git a/consensus/core/src/lib.rs b/consensus/core/src/lib.rs index 4fa5bdd53..4077120de 100644 --- a/consensus/core/src/lib.rs +++ b/consensus/core/src/lib.rs @@ -9,6 +9,7 @@ pub mod hashing; pub mod header; pub mod merkle; pub mod muhash; +pub mod notify; pub mod subnets; pub mod tx; pub mod utxo; diff --git a/consensus/core/src/notify/mod.rs b/consensus/core/src/notify/mod.rs new file mode 100644 index 000000000..8a84776ff --- /dev/null +++ b/consensus/core/src/notify/mod.rs @@ -0,0 +1,11 @@ +use crate::block::Block; + +#[derive(Debug, Clone)] +pub enum Notification { + BlockAdded(BlockAddedNotification), +} + +#[derive(Debug, Clone)] +pub struct BlockAddedNotification { + pub block: Block, +} diff --git a/consensus/core/src/subnets.rs b/consensus/core/src/subnets.rs index 23cfeb139..f8d7a0dfa 100644 --- a/consensus/core/src/subnets.rs +++ b/consensus/core/src/subnets.rs @@ -1,12 +1,14 @@ -use std::str::FromStr; +use std::fmt::{Debug, Display, Formatter}; +use std::str::{self, FromStr}; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; /// The size of the array used to store subnetwork IDs. pub const SUBNETWORK_ID_SIZE: usize = 20; /// The domain representation of a Subnetwork ID -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct SubnetworkId([u8; SUBNETWORK_ID_SIZE]); impl AsRef<[u8]> for SubnetworkId { @@ -41,6 +43,15 @@ impl SubnetworkId { } } +impl Display for SubnetworkId { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut hex = [0u8; SUBNETWORK_ID_SIZE * 2]; + faster_hex::hex_encode(&self.0, &mut hex).expect("The output is exactly twice the size of the input"); + f.write_str(str::from_utf8(&hex).expect("hex is always valid UTF-8")) + } +} + impl FromStr for SubnetworkId { type Err = faster_hex::Error; diff --git a/crypto/hashes/Cargo.toml b/crypto/hashes/Cargo.toml index a7c735bc6..3bac06020 100644 --- a/crypto/hashes/Cargo.toml +++ b/crypto/hashes/Cargo.toml @@ -10,6 +10,7 @@ license.workspace = true no-asm = ["keccak"] [dependencies] +borsh.workspace = true faster-hex.workspace = true serde.workspace = true blake2b_simd = "1" diff --git a/crypto/hashes/src/lib.rs b/crypto/hashes/src/lib.rs index 5be42e1aa..ea2f9a283 100644 --- a/crypto/hashes/src/lib.rs +++ b/crypto/hashes/src/lib.rs @@ -1,6 +1,7 @@ mod hashers; mod pow_hashers; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; use std::fmt::{Debug, Display, Formatter}; use std::hash::{Hash as StdHash, Hasher as StdHasher}; @@ -11,7 +12,7 @@ pub const HASH_SIZE: usize = 32; pub use hashers::*; // TODO: Check if we use hash more as an array of u64 or of bytes and change the default accordingly -#[derive(Eq, Clone, Copy, Default, PartialOrd, Ord, Serialize, Deserialize)] +#[derive(Eq, Clone, Copy, Default, PartialOrd, Ord, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct Hash([u8; HASH_SIZE]); impl Hash { diff --git a/math/Cargo.toml b/math/Cargo.toml index c9191719f..6de2e0945 100644 --- a/math/Cargo.toml +++ b/math/Cargo.toml @@ -10,6 +10,7 @@ license.workspace = true [dependencies] serde.workspace = true faster-hex.workspace = true +borsh.workspace = true [dev-dependencies] rand_chacha.workspace = true diff --git a/math/src/lib.rs b/math/src/lib.rs index 3ada42af5..2787a7b50 100644 --- a/math/src/lib.rs +++ b/math/src/lib.rs @@ -1,6 +1,8 @@ +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; + pub mod int; pub mod uint; -construct_uint!(Uint192, 3); +construct_uint!(Uint192, 3, BorshSerialize, BorshDeserialize, BorshSchema); construct_uint!(Uint256, 4); construct_uint!(Uint320, 5); construct_uint!(Uint3072, 48); diff --git a/math/src/uint.rs b/math/src/uint.rs index 1c3751188..910daa04c 100644 --- a/math/src/uint.rs +++ b/math/src/uint.rs @@ -5,9 +5,9 @@ pub use serde; #[macro_export] macro_rules! construct_uint { - ($name:ident, $n_words:literal) => { + ($name:ident, $n_words:literal $(, $derive_trait:ty)*) => { /// Little-endian large integer type - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug$(, $derive_trait )*)] pub struct $name(pub [u64; $n_words]); #[allow(unused)] impl $name { @@ -202,6 +202,18 @@ macro_rules! construct_uint { Self(out) } + /// Creates big integer value from a byte slice using + /// big-endian encoding + #[inline(always)] + pub fn from_be_bytes(bytes: [u8; Self::BYTES]) -> $name { + let mut out = [0u64; Self::LIMBS]; + // This should optimize to basically a transmute. + out.iter_mut() + .zip(bytes.chunks_exact(8).rev()) + .for_each(|(word, bytes)| *word = u64::from_be_bytes(bytes.try_into().unwrap())); + Self(out) + } + /// Convert's the Uint into little endian byte array #[inline(always)] pub fn to_le_bytes(self) -> [u8; Self::BYTES] { diff --git a/rpc/core/Cargo.toml b/rpc/core/Cargo.toml new file mode 100644 index 000000000..b2b29ff7e --- /dev/null +++ b/rpc/core/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "rpc-core" +version.workspace = true +edition.workspace = true +authors.workspace = true +include.workspace = true +license.workspace = true + +[dependencies] +consensus-core.workspace = true +hashes.workspace = true +math.workspace = true +kaspa-core.workspace = true +kaspa-utils.workspace = true +faster-hex.workspace = true +serde.workspace = true +derive_more.workspace = true +thiserror.workspace = true +borsh.workspace = true +async-std.workspace = true +async-trait = "0.1.57" +ahash = "0.8.0" +futures = { version = "0.3" } +rand = "0.8" +triggered = "0.1" +workflow-core = "0.1.0" diff --git a/rpc/core/src/api/mod.rs b/rpc/core/src/api/mod.rs new file mode 100644 index 000000000..2a74e294d --- /dev/null +++ b/rpc/core/src/api/mod.rs @@ -0,0 +1,3 @@ +pub mod notifications; +pub mod ops; +pub mod rpc; diff --git a/rpc/core/src/api/notifications.rs b/rpc/core/src/api/notifications.rs new file mode 100644 index 000000000..1191683dc --- /dev/null +++ b/rpc/core/src/api/notifications.rs @@ -0,0 +1,83 @@ +use crate::model::message::*; +use crate::stubs::*; +use async_std::channel::{Receiver, Sender}; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use serde::{Deserialize, Serialize}; +use std::fmt::Display; +use std::sync::Arc; + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +pub enum NotificationType { + BlockAdded, + VirtualSelectedParentChainChanged, + FinalityConflicts, + FinalityConflictResolved, + UtxosChanged(Vec), + VirtualSelectedParentBlueScoreChanged, + VirtualDaaScoreChanged, + PruningPointUTXOSetOverride, + NewBlockTemplate, +} + +impl From<&Notification> for NotificationType { + fn from(item: &Notification) -> Self { + match item { + Notification::BlockAdded(_) => NotificationType::BlockAdded, + Notification::VirtualSelectedParentChainChanged(_) => NotificationType::VirtualSelectedParentChainChanged, + Notification::FinalityConflict(_) => NotificationType::FinalityConflicts, + Notification::FinalityConflictResolved(_) => NotificationType::FinalityConflictResolved, + Notification::UtxosChanged(_) => NotificationType::UtxosChanged(vec![]), + Notification::VirtualSelectedParentBlueScoreChanged(_) => NotificationType::VirtualSelectedParentBlueScoreChanged, + Notification::VirtualDaaScoreChanged(_) => NotificationType::VirtualDaaScoreChanged, + Notification::PruningPointUTXOSetOverride(_) => NotificationType::PruningPointUTXOSetOverride, + Notification::NewBlockTemplate(_) => NotificationType::NewBlockTemplate, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[allow(clippy::large_enum_variant)] +pub enum Notification { + BlockAdded(BlockAddedNotification), + VirtualSelectedParentChainChanged(VirtualSelectedParentChainChangedNotification), + FinalityConflict(FinalityConflictNotification), + FinalityConflictResolved(FinalityConflictResolvedNotification), + UtxosChanged(UtxosChangedNotification), + VirtualSelectedParentBlueScoreChanged(VirtualSelectedParentBlueScoreChangedNotification), + VirtualDaaScoreChanged(VirtualDaaScoreChangedNotification), + PruningPointUTXOSetOverride(PruningPointUTXOSetOverrideNotification), + NewBlockTemplate(NewBlockTemplateNotification), +} + +impl Display for Notification { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Notification::BlockAdded(ref notification) => { + write!(f, "Notification BlockAdded with hash {}", notification.block.verbose_data.hash) + } + _ => write!(f, "Notification type not implemented yet"), + // Notification::VirtualSelectedParentChainChanged(_) => todo!(), + // Notification::FinalityConflict(_) => todo!(), + // Notification::FinalityConflictResolved(_) => todo!(), + // Notification::UtxosChanged(_) => todo!(), + // Notification::VirtualSelectedParentBlueScoreChanged(_) => todo!(), + // Notification::VirtualDaaScoreChanged(_) => todo!(), + // Notification::PruningPointUTXOSetOverride(_) => todo!(), + // Notification::NewBlockTemplate(_) => todo!(), + } + } +} + +pub type NotificationSender = Sender>; +pub type NotificationReceiver = Receiver>; + +pub enum NotificationHandle { + Existing(u64), + New(NotificationSender), +} + +impl AsRef for Notification { + fn as_ref(&self) -> &Self { + self + } +} diff --git a/rpc/core/src/api/ops.rs b/rpc/core/src/api/ops.rs new file mode 100644 index 000000000..58b893a32 --- /dev/null +++ b/rpc/core/src/api/ops.rs @@ -0,0 +1,82 @@ +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq)] +#[repr(u32)] +pub enum RpcApiOps { + Ping = 0, + GetCurrentNetwork, + SubmitBlock, + GetBlockTemplate, + GetPeerAddresses, + GetSelectedTipHash, + GetMempoolEntry, + GetMempoolEntries, + GetConnectedPeerInfo, + AddPeer, + SubmitTransaction, + GetBlock, + GetSubnetwork, + GetVirtualSelectedParentChainFromBlock, + GetBlocks, + GetBlockCount, + GetBlockDagInfo, + ResolveFinalityConflict, + Shutdown, + GetHeaders, + GetUtxosByAddresses, + GetBalanceByAddress, + GetBalancesByAddresses, + GetVirtualSelectedParentBlueScore, + Ban, + Unban, + GetInfo, + EstimateNetworkHashesPerSecond, + GetMempoolEntriesByAddresses, + GetCoinSupply, + + // Subscription commands for starting/stopping notifications + NotifyBlockAdded, + + // Server to client notification + Notification, +} + +impl From for u32 { + fn from(item: RpcApiOps) -> Self { + item as u32 + } +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +pub enum SubscribeCommand { + Start = 0, + Stop = 1, +} + +impl From for i32 { + fn from(item: SubscribeCommand) -> Self { + item as i32 + } +} + +impl From for SubscribeCommand { + // We make this conversion infallible by falling back to Start from any unexpected value. + fn from(item: i32) -> Self { + if item == 1 { + SubscribeCommand::Stop + } else { + SubscribeCommand::Start + } + } +} + +#[cfg(test)] +mod tests { + use super::RpcApiOps; + + #[test] + fn test_rpc_api_ops_convert() { + assert_eq!(0_u32, u32::from(RpcApiOps::Ping)); + } +} diff --git a/rpc/core/src/api/rpc.rs b/rpc/core/src/api/rpc.rs new file mode 100644 index 000000000..6377774d9 --- /dev/null +++ b/rpc/core/src/api/rpc.rs @@ -0,0 +1,175 @@ +//! The client API +//! +//! Rpc = External RPC Service +//! All data provided by the RCP server can be trusted by the client +//! No data submitted by the client to the server can be trusted + +use crate::{ + model::*, + notify::{ + channel::NotificationChannel, + listener::{ListenerID, ListenerReceiverSide}, + }, + NotificationType, RpcResult, +}; +use async_trait::async_trait; + +#[async_trait] +pub trait RpcApi: Sync + Send { + // async fn ping( + // &self, + // msg : String + // ) -> RpcResult; + + // async fn get_current_network( + // &self + // ) -> RpcResult; + + // async fn submit_block( + // &self, + // block: RpcBlock, + // allow_non_daa_blocks : bool + // ) -> RpcResult; + + // async fn get_block_template( + // &self, + // req: GetBlockTemplateRequest + // ) -> RpcResult; + + // async fn get_peer_addresses( + // &self + // ) -> RpcResult; + + // async fn get_selected_tip_hash( + // &self + // ) -> RpcResult; + + // async fn get_mempool_entry( + // &self, + // req: GetMempoolEntryRequest + // ) -> RpcResult; + + // async fn get_mempool_entries( + // &self, + // include_orphan_pool: bool, + // filter_transaction_pool: bool + // ) -> RpcResult; + + // async fn get_connected_peer_info( + // &self + // ) -> RpcResult; + + // async fn add_peer( + // &self, + // req: AddPeerRequest + // ) -> RpcResult; + + // async fn submit_transaction( + // &self, + // transaction: RpcTransaction, + // allow_orphan: bool, + // ) -> RpcResult; + + async fn get_block(&self, req: GetBlockRequest) -> RpcResult; + + // async fn get_subnetwork( + // &self, + // req: GetSubnetworkRequest + // ) -> RpcResult; + + // async fn get_virtual_selected_parent_chain_from_block( + // &self, + // req: GetVirtualSelectedParentChainFromBlockRequest + // ) -> RpcResult; + + // async fn get_blocks( + // &self, + // req: GetBlocksRequest + // ) -> RpcResult; + + // async fn get_block_count( + // &self, + // req: GetBlockCountRequest + // ) -> RpcResult; + + // async fn get_block_dag_info( + // &self, + // req: GetBlockDagInfoRequest + // ) -> RpcResult; + + // async fn resolve_finality_conflict( + // &self, + // req: ResolveFinalityConflictRequest + // ) -> RpcResult; + + // async fn shutdown( + // &self + // ) -> RpcResult<()>; + + // async fn get_headers( + // &self, + // req: GetHeadersRequest + // ) -> RpcResult; + + // async fn get_utxos_by_address( + // &self, + // addresses : Vec
+ // ) -> RpcResult; + + // async fn get_balance_by_address( + // &self, + // address : Address + // ) -> RpcResult; + + // async fn get_balances_by_addresses( + // &self, + // addresses : Vec
+ // ) -> RpcResult>; + + // async fn get_virtual_selected_parent_blue_score( + // &self + // ) -> RpcResult; + + // async fn ban( + // &self, + // req: BanRequest + // ) -> RpcResult; + + // async fn unban( + // &self, + // req: UnbanRequest + // ) -> RpcResult; + + async fn get_info(&self, req: GetInfoRequest) -> RpcResult; + + // async fn estimate_network_hashes_per_second( + // &self, + // req: EstimateNetworkHashesPerSecondRequest + // ) -> RpcResult; + + // async fn get_mempool_entries_by_addresses( + // &self, + // req: GetMempoolEntriesByAddressesRequest + // ) -> RpcResult; + + // async fn get_coin_supply( + // &self + // ) -> RpcResult; + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Notification API + + /// Register a new listener and returns an id and a channel receiver. + fn register_new_listener(&self, channel: Option) -> ListenerReceiverSide; + + /// Unregister an existing listener. + /// + /// Stop all notifications for this listener and drop its channel. + async fn unregister_listener(&self, id: ListenerID) -> RpcResult<()>; + + /// Start sending notifications of some type to a listener. + async fn start_notify(&self, id: ListenerID, notification_type: NotificationType) -> RpcResult<()>; + + /// Stop sending notifications of some type to a listener. + async fn stop_notify(&self, id: ListenerID, notification_type: NotificationType) -> RpcResult<()>; +} diff --git a/rpc/core/src/convert/block.rs b/rpc/core/src/convert/block.rs new file mode 100644 index 000000000..16c8267d7 --- /dev/null +++ b/rpc/core/src/convert/block.rs @@ -0,0 +1,50 @@ +use std::sync::Arc; + +use crate::{RpcBlock, RpcBlockVerboseData, RpcError, RpcResult}; +use consensus_core::block::Block; + +// ---------------------------------------------------------------------------- +// consensus_core to rpc_core +// ---------------------------------------------------------------------------- + +impl From<&Block> for RpcBlock { + fn from(item: &Block) -> Self { + Self { header: (&*item.header).into(), transactions: vec![], verbose_data: item.into() } + } +} + +impl From<&Block> for RpcBlockVerboseData { + fn from(item: &Block) -> Self { + // TODO: Fill all fields with real values. + // see kaspad\app\rpc\rpccontext\verbosedata.go PopulateBlockWithVerboseData + Self { + hash: item.hash(), + difficulty: 0.into(), + selected_parent_hash: 0.into(), + transaction_ids: vec![], + is_header_only: true, + blue_score: 0u64, + children_hashes: vec![], + merge_set_blues_hashes: vec![], + merge_set_reds_hashes: vec![], + is_chain_block: false, + } + } +} + +// ---------------------------------------------------------------------------- +// rpc_core to consensus_core +// ---------------------------------------------------------------------------- + +impl TryFrom<&RpcBlock> for Block { + type Error = RpcError; + fn try_from(item: &RpcBlock) -> RpcResult { + Ok(Self { + header: Arc::new((&item.header).try_into()?), + + // TODO: Implement converters for all tx structs and fill transactions + // with real values. + transactions: Arc::new(vec![]), // FIXME + }) + } +} diff --git a/rpc/core/src/convert/header.rs b/rpc/core/src/convert/header.rs new file mode 100644 index 000000000..c5f57c089 --- /dev/null +++ b/rpc/core/src/convert/header.rs @@ -0,0 +1,53 @@ +use crate::{RpcBlockHeader, RpcBlockLevelParents, RpcError, RpcResult}; +use consensus_core::header::Header; + +// ---------------------------------------------------------------------------- +// consensus_core to rpc_core +// ---------------------------------------------------------------------------- + +impl From<&Header> for RpcBlockHeader { + fn from(item: &Header) -> Self { + Self { + version: item.version.into(), + parents: item.parents_by_level.iter().map(|x| RpcBlockLevelParents { parent_hashes: x.clone() }).collect(), + hash_merkle_root: item.hash_merkle_root, + accepted_id_merkle_root: item.accepted_id_merkle_root, + utxo_commitment: item.utxo_commitment, + timestamp: item.timestamp.try_into().expect("time stamp is convertible from u64 to i64"), + bits: item.bits, + nonce: item.nonce, + daa_score: item.daa_score, + blue_work: item.blue_work.into(), + pruning_point: item.pruning_point, + blue_score: item.blue_score, + } + } +} + +// ---------------------------------------------------------------------------- +// rpc_core to consensus_core +// ---------------------------------------------------------------------------- + +impl TryFrom<&RpcBlockHeader> for Header { + type Error = RpcError; + fn try_from(item: &RpcBlockHeader) -> RpcResult { + let mut header = Self::new( + item.version.try_into()?, + vec![], + item.hash_merkle_root, + item.timestamp.try_into()?, + item.bits, + item.nonce, + item.daa_score, + item.blue_work.into(), + item.blue_score, + ); + header.parents_by_level = item.parents.iter().map(|x| x.parent_hashes.clone()).collect(); + header.accepted_id_merkle_root = item.accepted_id_merkle_root; + header.utxo_commitment = item.utxo_commitment; + header.pruning_point = item.pruning_point; + header.finalize(); + + Ok(header) + } +} diff --git a/rpc/core/src/convert/mod.rs b/rpc/core/src/convert/mod.rs new file mode 100644 index 000000000..db9dcc041 --- /dev/null +++ b/rpc/core/src/convert/mod.rs @@ -0,0 +1,3 @@ +pub mod block; +pub mod header; +pub mod notification; diff --git a/rpc/core/src/convert/notification.rs b/rpc/core/src/convert/notification.rs new file mode 100644 index 000000000..7e031ed89 --- /dev/null +++ b/rpc/core/src/convert/notification.rs @@ -0,0 +1,40 @@ +use std::sync::Arc; + +use crate::{notify::collector::ArcConvert, BlockAddedNotification, Notification}; +use consensus_core::notify as consensus_notify; + +// ---------------------------------------------------------------------------- +// consensus_core to rpc_core +// ---------------------------------------------------------------------------- + +impl From<&consensus_notify::Notification> for Notification { + fn from(item: &consensus_notify::Notification) -> Self { + match item { + consensus_notify::Notification::BlockAdded(msg) => Notification::BlockAdded(msg.into()), + } + } +} + +impl From<&consensus_notify::BlockAddedNotification> for BlockAddedNotification { + fn from(item: &consensus_notify::BlockAddedNotification) -> Self { + Self { block: (&item.block).into() } + } +} + +/// Pseudo conversion from Arc to Arc. +/// This is basically a clone() op. +impl From> for Arc { + fn from(item: ArcConvert) -> Self { + (*item).clone() + } +} + +impl From> for Arc { + fn from(item: ArcConvert) -> Self { + Arc::new((&**item).into()) + } +} + +// ---------------------------------------------------------------------------- +// rpc_core to consensus_core +// ---------------------------------------------------------------------------- diff --git a/rpc/core/src/errors.rs b/rpc/core/src/errors.rs new file mode 100644 index 000000000..8486b46b4 --- /dev/null +++ b/rpc/core/src/errors.rs @@ -0,0 +1,46 @@ +use std::num::TryFromIntError; +use thiserror::Error; + +#[derive(Clone, Debug, Error)] +pub enum RpcError { + #[error("Not implemented")] + NotImplemented, + + #[error("Integer downsize conversion error {0}")] + IntConversionError(#[from] TryFromIntError), + + #[error("Hex parsing error: {0}")] + HexParsingError(#[from] faster_hex::Error), + + #[error("Blue work parsing error {0}")] + RpcBlueWorkTypeParseError(std::num::ParseIntError), + + #[error("Integer parsing error: {0}")] + ParseIntError(#[from] std::num::ParseIntError), + + #[error("Invalid script class: {0}")] + InvalidRpcScriptClass(String), + + #[error("Missing required field {0}.{1}")] + MissingRpcFieldError(String, String), + + #[error("Feature not supported")] + UnsupportedFeature, + + #[error("{0}")] + General(String), +} + +impl From for RpcError { + fn from(value: String) -> Self { + RpcError::General(value) + } +} + +impl From<&str> for RpcError { + fn from(value: &str) -> Self { + RpcError::General(value.to_string()) + } +} + +pub type RpcResult = std::result::Result; diff --git a/rpc/core/src/lib.rs b/rpc/core/src/lib.rs new file mode 100644 index 000000000..f721794b3 --- /dev/null +++ b/rpc/core/src/lib.rs @@ -0,0 +1,36 @@ +// This attribute is required by BorshSerialize/Deserialize +#![recursion_limit = "256"] + +pub mod api; +pub mod convert; +pub mod errors; +pub mod model; +pub mod notify; +pub mod server; +pub mod stubs; + +pub mod prelude { + pub use super::api::notifications::*; + pub use super::model::block::*; + pub use super::model::blue_work::*; + pub use super::model::hash::*; + pub use super::model::header::*; + pub use super::model::hex_data::*; + pub use super::model::message::*; + pub use super::model::script_class::*; + pub use super::model::subnets::*; + pub use super::model::tx::*; +} + +pub use api::notifications::*; +pub use convert::*; +pub use errors::*; +pub use model::block::*; +pub use model::blue_work::*; +pub use model::hash::*; +pub use model::header::*; +pub use model::hex_data::*; +pub use model::message::*; +pub use model::script_class::*; +pub use model::subnets::*; +pub use model::tx::*; diff --git a/rpc/core/src/model/block.rs b/rpc/core/src/model/block.rs new file mode 100644 index 000000000..6085a2420 --- /dev/null +++ b/rpc/core/src/model/block.rs @@ -0,0 +1,27 @@ +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use crate::prelude::{RpcBlockHeader, RpcHash, RpcTransaction}; + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct RpcBlock { + pub header: RpcBlockHeader, + pub transactions: Vec, + pub verbose_data: RpcBlockVerboseData, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct RpcBlockVerboseData { + pub hash: RpcHash, + pub difficulty: f64, + pub selected_parent_hash: RpcHash, + pub transaction_ids: Vec, + pub is_header_only: bool, + pub blue_score: u64, + pub children_hashes: Vec, + pub merge_set_blues_hashes: Vec, + pub merge_set_reds_hashes: Vec, + pub is_chain_block: bool, +} diff --git a/rpc/core/src/model/blue_work.rs b/rpc/core/src/model/blue_work.rs new file mode 100644 index 000000000..c843a164e --- /dev/null +++ b/rpc/core/src/model/blue_work.rs @@ -0,0 +1,118 @@ +extern crate derive_more; + +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use consensus_core::BlueWorkType; +use serde::{Deserialize, Serialize}; +use std::convert::TryFrom; +use std::fmt::{Debug, Display, Formatter}; +use std::str::{self, FromStr}; + +use crate::RpcError; + +/// A RPC-dedicated representation of BlueWorkType in which string representation is always hexadecimal. +#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase", try_from = "String", into = "String")] +pub struct RpcBlueWorkType(BlueWorkType); + +impl Display for RpcBlueWorkType { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{0:x}", self.0)) + } +} + +impl From for RpcBlueWorkType { + fn from(item: BlueWorkType) -> RpcBlueWorkType { + RpcBlueWorkType(item) + } +} + +impl From for BlueWorkType { + fn from(item: RpcBlueWorkType) -> BlueWorkType { + item.0 + } +} + +impl From for String { + fn from(item: RpcBlueWorkType) -> String { + item.to_string() + } +} + +impl FromStr for RpcBlueWorkType { + type Err = RpcError; + + fn from_str(s: &str) -> Result { + // TODO: replace the whole impl with math::Uint192::from_hex when available + const HEX_LEN: usize = math::Uint192::BYTES * 2; + let mut hex = [0u8; HEX_LEN]; + if s.len() > HEX_LEN { + return Err(RpcError::HexParsingError(faster_hex::Error::InvalidLength(s.len()))); + } + hex.iter_mut().rev().zip(s.bytes().rev()).for_each(|(target, source)| *target = source); + if s.len() < HEX_LEN { + hex[0..HEX_LEN - s.len()].iter_mut().for_each(|x| *x = b'0'); + } + let mut bytes = [0u8; math::Uint192::BYTES]; + faster_hex::hex_decode(&hex, &mut bytes)?; + Ok(RpcBlueWorkType(BlueWorkType::from_be_bytes(bytes))) + } +} + +impl TryFrom<&str> for RpcBlueWorkType { + type Error = RpcError; + fn try_from(value: &str) -> Result { + value.parse() + } +} + +impl TryFrom for RpcBlueWorkType { + type Error = RpcError; + fn try_from(value: String) -> Result { + value.parse() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_rpc_blue_work() { + const HEX_STR: &str = "1234567890abcdef1234567890abc"; + const HEX_BIN: u128 = 0x1234567890abcdef1234567890abc; + + let bw = BlueWorkType::from_u128(123456789012345678901234567890123456789); + let rbw: RpcBlueWorkType = bw.into(); + assert_eq!(bw, BlueWorkType::from(rbw)); + + let b1 = RpcBlueWorkType::try_from(HEX_STR).unwrap(); + assert_eq!(b1.to_string(), HEX_STR); + assert_eq!(b1, RpcBlueWorkType(math::Uint192::from_u128(HEX_BIN))); + + let rbw2 = rbw; + assert_eq!(rbw, rbw2); + } + + #[test] + fn test_rpc_blue_work_try_from() { + const HEX_STR: &str = "d529a12"; + let b2 = BlueWorkType::from_u128(u128::from_str_radix(HEX_STR, 16).unwrap()); + + assert!(RpcBlueWorkType::try_from(HEX_STR).is_ok()); + assert!(RpcBlueWorkType::try_from("not a number").is_err()); + + assert_eq!(RpcBlueWorkType(b2), RpcBlueWorkType::from_str(HEX_STR).unwrap()); + } + + #[test] + fn test_rpc_blue_work_from_str() { + const HEX_STR: &str = "40a593f53f695ba413"; + let b2 = BlueWorkType::from_u128(u128::from_str_radix(HEX_STR, 16).unwrap()); + + assert!(RpcBlueWorkType::from_str(HEX_STR).is_ok()); + assert!(RpcBlueWorkType::from_str("not a number").is_err()); + + assert_eq!(RpcBlueWorkType(b2), RpcBlueWorkType::from_str(HEX_STR).unwrap()); + } +} diff --git a/rpc/core/src/model/hash.rs b/rpc/core/src/model/hash.rs new file mode 100644 index 000000000..f663ebb3e --- /dev/null +++ b/rpc/core/src/model/hash.rs @@ -0,0 +1 @@ +pub type RpcHash = hashes::Hash; diff --git a/rpc/core/src/model/header.rs b/rpc/core/src/model/header.rs new file mode 100644 index 000000000..01700a9bf --- /dev/null +++ b/rpc/core/src/model/header.rs @@ -0,0 +1,26 @@ +use crate::{prelude::RpcHash, RpcBlueWorkType}; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct RpcBlockHeader { + pub version: u32, + pub parents: Vec, + pub hash_merkle_root: RpcHash, + pub accepted_id_merkle_root: RpcHash, + pub utxo_commitment: RpcHash, + pub timestamp: i64, + pub bits: u32, + pub nonce: u64, + pub daa_score: u64, + pub blue_work: RpcBlueWorkType, + pub pruning_point: RpcHash, + pub blue_score: u64, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct RpcBlockLevelParents { + pub parent_hashes: Vec, +} diff --git a/rpc/core/src/model/hex_data.rs b/rpc/core/src/model/hex_data.rs new file mode 100644 index 000000000..b51eeb834 --- /dev/null +++ b/rpc/core/src/model/hex_data.rs @@ -0,0 +1,107 @@ +extern crate derive_more; + +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use serde::{Deserialize, Serialize}; +use std::convert::TryFrom; +use std::fmt; +use std::str::{self, FromStr}; + +use crate::errors; + +// Represents binary data stringifyed in hexadecimal form +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase", try_from = "String", into = "String")] +pub struct RpcHexData(Vec); + +impl fmt::Display for RpcHexData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // an empty vector is allowed + if self.0.is_empty() { + return f.write_str(""); + } + + let mut hex = vec![0u8; self.0.len() * 2]; + faster_hex::hex_encode(&self.0, hex.as_mut_slice()).expect("The output is exactly twice the size of the input"); + f.write_str(str::from_utf8(&hex).expect("hex is always valid UTF-8")) + } +} + +impl From<&Vec> for RpcHexData { + fn from(item: &Vec) -> RpcHexData { + RpcHexData(item.clone()) + } +} + +impl From for String { + fn from(item: RpcHexData) -> String { + item.to_string() + } +} + +impl FromStr for RpcHexData { + type Err = errors::RpcError; + + fn from_str(s: &str) -> Result { + // an empty string is allowed + if s.is_empty() { + return Ok(RpcHexData(vec![])); + } + + let mut bytes = vec![0u8; s.len() / 2]; + faster_hex::hex_decode(s.as_bytes(), bytes.as_mut_slice())?; + Ok(RpcHexData(bytes)) + } +} + +impl TryFrom<&str> for RpcHexData { + type Error = errors::RpcError; + fn try_from(value: &str) -> Result { + value.parse() + } +} + +impl TryFrom for RpcHexData { + type Error = errors::RpcError; + fn try_from(value: String) -> Result { + value.parse() + } +} + +impl AsRef> for RpcHexData { + fn as_ref(&self) -> &Vec { + &self.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_rpc_script_key() { + let raw: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; + let skey: RpcHexData = RpcHexData::from(&raw); + assert_eq!(raw, *skey.as_ref()); + assert_eq!(RpcHexData::from(&vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]), skey); + + assert!(RpcHexData::from_str("123456789012345678901234567890123456789").is_err()); + assert!(RpcHexData::from_str("1234567890123456789012345678901234567890").is_ok()); + assert!(RpcHexData::from_str("not a number").is_err()); + + assert!(RpcHexData::try_from("123456789012345678901234567890123456789".to_string()).is_err()); + assert!(RpcHexData::try_from("1234567890123456789012345678901234567890".to_string()).is_ok()); + assert!(RpcHexData::try_from("not a number".to_string()).is_err()); + + assert!(RpcHexData::try_from("10").is_ok()); + assert!(RpcHexData::try_from("aaFF").is_ok()); + assert!(RpcHexData::try_from("not a number").is_err()); + + let skey2 = skey.clone(); + assert_eq!(skey, skey2); + + let code = "fedcba9876543210"; + let key = RpcHexData::try_from(code).unwrap(); + assert_eq!(key.to_string().to_lowercase(), code); + assert_eq!(key.as_ref().len(), code.len() / 2); + } +} diff --git a/rpc/core/src/model/message.rs b/rpc/core/src/model/message.rs new file mode 100644 index 000000000..7aa98a9a5 --- /dev/null +++ b/rpc/core/src/model/message.rs @@ -0,0 +1,62 @@ +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use crate::{api::ops::SubscribeCommand, RpcBlock, RpcHash}; + +/// GetBlockRequest requests information about a specific block +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct GetBlockRequest { + /// The hash of the requested block + pub hash: RpcHash, + + /// Whether to include transaction data in the response + pub include_transactions: bool, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct GetBlockResponse { + pub block: RpcBlock, + // According to app\rpc\rpchandlers\get_block.go + // block and error as mutually exclusive +} + +/// NotifyBlockAddedRequest registers this connection for blockAdded notifications. +/// +/// See: [`BlockAddedNotification`] +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct NotifyBlockAddedRequest { + pub command: SubscribeCommand, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct NotifyBlockAddedResponse {} + +/// BlockAddedNotification is sent whenever a blocks has been added (NOT accepted) +/// into the DAG. +/// +/// See: [`NotifyBlockAddedRequest`] +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct BlockAddedNotification { + pub block: RpcBlock, +} + +/// GetInfoRequest returns info about the node. +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct GetInfoRequest {} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct GetInfoResponse { + pub p2p_id: String, + pub mempool_size: u64, + pub server_version: String, // FIXME ? + pub is_utxo_indexed: bool, + pub is_synced: bool, + pub has_notify_command: bool, +} diff --git a/rpc/core/src/model/mod.rs b/rpc/core/src/model/mod.rs new file mode 100644 index 000000000..fb6c20a9f --- /dev/null +++ b/rpc/core/src/model/mod.rs @@ -0,0 +1,18 @@ +pub mod block; +pub mod blue_work; +pub mod hash; +pub mod header; +pub mod hex_data; +pub mod message; +pub mod script_class; +pub mod subnets; +pub mod tx; + +pub use block::*; +pub use blue_work::*; +pub use hash::*; +pub use header::*; +pub use hex_data::*; +pub use message::*; +pub use subnets::*; +pub use tx::*; diff --git a/rpc/core/src/model/script_class.rs b/rpc/core/src/model/script_class.rs new file mode 100644 index 000000000..aca0793d8 --- /dev/null +++ b/rpc/core/src/model/script_class.rs @@ -0,0 +1,69 @@ +use crate::RpcError; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use serde::{Deserialize, Serialize}; +use std::{ + fmt::{Display, Formatter}, + str::FromStr, +}; + +// TODO: in the future it should be a newtype of ScriptClass, that will be probably a type +// associated with the script engine +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +pub enum RpcScriptClass { + /// None of the recognized forms. + NonStandardTy = 0, + + /// Pay to pubkey. + PubKeyTy = 1, + + /// Pay to pubkey ECDSA. + PubKeyECDSATy = 2, + + /// Pay to script hash. + ScriptHashTy = 3, +} + +const NON_STANDARD_TY: &str = "nonstandard"; +const PUB_KEY_TY: &str = "pubkey"; +const PUB_KEY_ECDSA_TY: &str = "pubkeyecdsa"; +const SCRIPT_HASH_TY: &str = "scripthash"; + +impl RpcScriptClass { + fn as_str(&self) -> &'static str { + match self { + RpcScriptClass::NonStandardTy => NON_STANDARD_TY, + RpcScriptClass::PubKeyTy => PUB_KEY_TY, + RpcScriptClass::PubKeyECDSATy => PUB_KEY_ECDSA_TY, + RpcScriptClass::ScriptHashTy => SCRIPT_HASH_TY, + } + } +} + +impl Display for RpcScriptClass { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} + +impl FromStr for RpcScriptClass { + type Err = RpcError; + + fn from_str(script_class: &str) -> Result { + match script_class { + NON_STANDARD_TY => Ok(RpcScriptClass::NonStandardTy), + PUB_KEY_TY => Ok(RpcScriptClass::PubKeyTy), + PUB_KEY_ECDSA_TY => Ok(RpcScriptClass::PubKeyECDSATy), + SCRIPT_HASH_TY => Ok(RpcScriptClass::ScriptHashTy), + + _ => Err(RpcError::InvalidRpcScriptClass(script_class.to_string())), + } + } +} + +impl TryFrom<&str> for RpcScriptClass { + type Error = RpcError; + + fn try_from(script_class: &str) -> Result { + script_class.parse() + } +} diff --git a/rpc/core/src/model/subnets.rs b/rpc/core/src/model/subnets.rs new file mode 100644 index 000000000..494cf9c22 --- /dev/null +++ b/rpc/core/src/model/subnets.rs @@ -0,0 +1 @@ +pub type RpcSubnetworkId = consensus_core::subnets::SubnetworkId; diff --git a/rpc/core/src/model/tx.rs b/rpc/core/src/model/tx.rs new file mode 100644 index 000000000..ce0fae341 --- /dev/null +++ b/rpc/core/src/model/tx.rs @@ -0,0 +1,84 @@ +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use consensus_core::tx::TransactionId; +use serde::{Deserialize, Serialize}; + +use crate::prelude::{RpcHash, RpcHexData, RpcScriptClass, RpcSubnetworkId}; + +pub type RpcTransactionId = TransactionId; + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct RpcTransaction { + pub version: u32, + pub inputs: Vec, + pub outputs: Vec, + pub lock_time: u64, + pub subnetwork_id: RpcSubnetworkId, + pub gas: u64, + pub payload: RpcHexData, + pub verbose_data: RpcTransactionVerboseData, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct RpcTransactionInput { + pub previous_outpoint: RpcOutpoint, + pub signature_script: RpcHexData, + pub sequence: u64, + pub sig_op_count: u32, + pub verbose_data: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct RpcTransactionOutput { + pub amount: u64, + pub script_public_key: RpcScriptPublicKey, + pub verbose_data: RpcTransactionOutputVerboseData, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct RpcOutpoint { + pub transaction_id: RpcTransactionId, + pub index: u32, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct RpcUtxoEntry { + pub amount: u64, + pub script_public_key: RpcScriptPublicKey, + pub block_daa_score: u64, + pub is_coinbase: bool, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct RpcScriptPublicKey { + pub script_public_key: RpcHexData, + pub version: u16, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct RpcTransactionVerboseData { + pub transaction_id: RpcTransactionId, + pub hash: RpcHash, + pub mass: u64, + pub block_hash: RpcHash, + pub block_time: u64, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct RpcTransactionInputVerboseData {} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +pub struct RpcTransactionOutputVerboseData { + pub script_public_key_type: RpcScriptClass, + + // TODO: change the type of this field for a better binary representation + pub script_public_key_address: String, +} diff --git a/rpc/core/src/notify/channel.rs b/rpc/core/src/notify/channel.rs new file mode 100644 index 000000000..90bd658eb --- /dev/null +++ b/rpc/core/src/notify/channel.rs @@ -0,0 +1,5 @@ +use crate::Notification; +use kaspa_utils::channel::Channel; +use std::sync::Arc; + +pub type NotificationChannel = Channel>; diff --git a/rpc/core/src/notify/collector.rs b/rpc/core/src/notify/collector.rs new file mode 100644 index 000000000..9d3a49bce --- /dev/null +++ b/rpc/core/src/notify/collector.rs @@ -0,0 +1,147 @@ +use async_std::channel::{Receiver, Sender}; +use async_std::stream::StreamExt; +use async_trait::async_trait; +use core::fmt::Debug; +use futures::{ + future::FutureExt, // for `.fuse()` + pin_mut, + select, +}; +use kaspa_core::trace; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +extern crate derive_more; +use crate::notify::{collector, errors::Error, notifier::Notifier, result::Result}; +use crate::Notification; +use derive_more::Deref; +use kaspa_utils::channel::Channel; +use kaspa_utils::triggers::DuplexTrigger; + +pub type CollectorNotificationChannel = Channel>; +pub type CollectorNotificationSender = Sender>; +pub type CollectorNotificationReceiver = Receiver>; + +/// A notification collector, acting as a notification source for a [`Notifier`]. +/// +/// A [`Collector`] is responsible for collecting notifications of +/// a specific form from a specific source, convert them if necessary +/// into [`Notification`]s and forward them to the [Notifier] provided +/// to `Collector::start`. +#[async_trait] +pub trait Collector: Send + Sync + Debug { + /// Start collecting notifications for `nofifier` + fn start(self: Arc, notifier: Arc); + /// Stop collecting notifications + async fn stop(self: Arc) -> Result<()>; +} + +pub type DynCollector = Arc; + +/// A newtype allowing conversion from Arc to Arc. +/// See [`super::collector::CollectorFrom`] +#[derive(Clone, Debug, Deref)] +pub struct ArcConvert(Arc); + +impl From> for ArcConvert { + fn from(item: Arc) -> Self { + ArcConvert(item) + } +} + +/// A notification [`Collector`] that receives [`T`] from a channel, +/// converts it into a [`Notification`] and sends it to a its +/// [`Notifier`]. +#[derive(Debug)] +pub struct CollectorFrom +where + T: Send + Sync + 'static + Sized, +{ + recv_channel: CollectorNotificationReceiver, + + /// Has this collector been started? + is_started: Arc, + + collect_shutdown: Arc, +} + +impl CollectorFrom +where + T: Send + Sync + 'static + Sized + Debug, + ArcConvert: Into>, +{ + pub fn new(recv_channel: CollectorNotificationReceiver) -> Self { + Self { recv_channel, collect_shutdown: Arc::new(DuplexTrigger::new()), is_started: Arc::new(AtomicBool::new(false)) } + } + + fn spawn_collecting_task(self: Arc, notifier: Arc) { + // The task can only be spawned once + if self.is_started.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_err() { + return; + } + let collect_shutdown = self.collect_shutdown.clone(); + let recv_channel = self.recv_channel.clone(); + + workflow_core::task::spawn(async move { + trace!("[Collector] collecting_task start"); + + let shutdown = collect_shutdown.request.listener.clone().fuse(); + pin_mut!(shutdown); + + let notifications = recv_channel.fuse(); + pin_mut!(notifications); + + loop { + select! { + _ = shutdown => { break; } + notification = notifications.next().fuse() => { + match notification { + Some(msg) => { + let rpc_notification: Arc = ArcConvert::from(msg.clone()).into(); + match notifier.clone().notify(rpc_notification) { + Ok(_) => (), + Err(err) => { + trace!("[Collector] notification sender error: {:?}", err); + }, + } + }, + None => { + trace!("[Collector] notifications returned None. This should never happen"); + } + } + } + } + } + collect_shutdown.response.trigger.trigger(); + trace!("[Collector] collecting_task end"); + }); + } + + async fn stop_collecting_task(self: Arc) -> Result<()> { + if self.is_started.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst).is_err() { + return Err(Error::AlreadyStoppedError); + } + self.collect_shutdown.request.trigger.trigger(); + self.collect_shutdown.response.listener.clone().await; + Ok(()) + } +} + +#[async_trait] +impl collector::Collector for CollectorFrom +where + T: Send + Sync + 'static + Sized + Debug, + ArcConvert: Into>, +{ + fn start(self: Arc, notifier: Arc) { + self.spawn_collecting_task(notifier); + } + + async fn stop(self: Arc) -> Result<()> { + self.stop_collecting_task().await + } +} + +/// A rpc_core notification collector providing a simple pass-through. +/// No conversion occurs since both source and target data are of +/// type [`Notification`]. +pub type RpcCoreCollector = CollectorFrom; diff --git a/rpc/core/src/notify/errors.rs b/rpc/core/src/notify/errors.rs new file mode 100644 index 000000000..31febb287 --- /dev/null +++ b/rpc/core/src/notify/errors.rs @@ -0,0 +1,49 @@ +use crate::RpcError; +use thiserror::Error; + +pub type BoxedStdError = Box<(dyn std::error::Error + Sync + std::marker::Send + 'static)>; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Error: {0}")] + General(String), + + #[error("Notification: channel receive error")] + ChannelRecvError, + + #[error("Notification: channel send error")] + ChannelSendError, + + #[error("object already stopped")] + AlreadyStoppedError, +} + +impl From for RpcError { + fn from(value: Error) -> Self { + RpcError::General(value.to_string()) + } +} + +impl From for Error { + fn from(err: BoxedStdError) -> Self { + Error::General(err.to_string()) + } +} + +impl From> for Error { + fn from(_: async_std::channel::SendError) -> Self { + Error::ChannelSendError + } +} + +impl From> for Error { + fn from(_: async_std::channel::TrySendError) -> Self { + Error::ChannelSendError + } +} + +impl From for Error { + fn from(_: async_std::channel::RecvError) -> Self { + Error::ChannelRecvError + } +} diff --git a/rpc/core/src/notify/events.rs b/rpc/core/src/notify/events.rs new file mode 100644 index 000000000..88122fb34 --- /dev/null +++ b/rpc/core/src/notify/events.rs @@ -0,0 +1,103 @@ +use std::ops::{Index, IndexMut}; + +use crate::{Notification, NotificationType}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum EventType { + BlockAdded = 0, + VirtualSelectedParentChainChanged, + FinalityConflicts, + FinalityConflictResolved, + UtxosChanged, + VirtualSelectedParentBlueScoreChanged, + VirtualDaaScoreChanged, + PruningPointUTXOSetOverride, + NewBlockTemplate, +} + +// TODO: write a macro or use an external crate to get this +pub(crate) const EVENT_COUNT: usize = 9; + +// TODO: write a macro or use an external crate to get this +pub const EVENT_TYPE_ARRAY: [EventType; EVENT_COUNT] = [ + EventType::BlockAdded, + EventType::VirtualSelectedParentChainChanged, + EventType::FinalityConflicts, + EventType::FinalityConflictResolved, + EventType::UtxosChanged, + EventType::VirtualSelectedParentBlueScoreChanged, + EventType::VirtualDaaScoreChanged, + EventType::PruningPointUTXOSetOverride, + EventType::NewBlockTemplate, +]; + +// TODO: write a macro to get this +impl From for NotificationType { + fn from(item: EventType) -> Self { + match item { + EventType::BlockAdded => NotificationType::BlockAdded, + EventType::VirtualSelectedParentChainChanged => NotificationType::VirtualSelectedParentChainChanged, + EventType::FinalityConflicts => NotificationType::FinalityConflicts, + EventType::FinalityConflictResolved => NotificationType::FinalityConflictResolved, + EventType::UtxosChanged => NotificationType::UtxosChanged(vec![]), + EventType::VirtualSelectedParentBlueScoreChanged => NotificationType::VirtualSelectedParentBlueScoreChanged, + EventType::VirtualDaaScoreChanged => NotificationType::VirtualDaaScoreChanged, + EventType::PruningPointUTXOSetOverride => NotificationType::PruningPointUTXOSetOverride, + EventType::NewBlockTemplate => NotificationType::NewBlockTemplate, + } + } +} + +// TODO: write a macro to get this +impl From<&Notification> for EventType { + fn from(item: &Notification) -> Self { + match item { + Notification::BlockAdded(_) => EventType::BlockAdded, + Notification::VirtualSelectedParentChainChanged(_) => EventType::VirtualSelectedParentChainChanged, + Notification::FinalityConflict(_) => EventType::FinalityConflicts, + Notification::FinalityConflictResolved(_) => EventType::FinalityConflictResolved, + Notification::UtxosChanged(_) => EventType::UtxosChanged, + Notification::VirtualSelectedParentBlueScoreChanged(_) => EventType::VirtualSelectedParentBlueScoreChanged, + Notification::VirtualDaaScoreChanged(_) => EventType::VirtualDaaScoreChanged, + Notification::PruningPointUTXOSetOverride(_) => EventType::PruningPointUTXOSetOverride, + Notification::NewBlockTemplate(_) => EventType::NewBlockTemplate, + } + } +} + +// TODO: write a macro to get this +impl From<&NotificationType> for EventType { + fn from(item: &NotificationType) -> Self { + match item { + NotificationType::BlockAdded => EventType::BlockAdded, + NotificationType::VirtualSelectedParentChainChanged => EventType::VirtualSelectedParentChainChanged, + NotificationType::FinalityConflicts => EventType::FinalityConflicts, + NotificationType::FinalityConflictResolved => EventType::FinalityConflictResolved, + NotificationType::UtxosChanged(_) => EventType::UtxosChanged, + NotificationType::VirtualSelectedParentBlueScoreChanged => EventType::VirtualSelectedParentBlueScoreChanged, + NotificationType::VirtualDaaScoreChanged => EventType::VirtualDaaScoreChanged, + NotificationType::PruningPointUTXOSetOverride => EventType::PruningPointUTXOSetOverride, + NotificationType::NewBlockTemplate => EventType::NewBlockTemplate, + } + } +} + +/// Generic array with [`EventType`] strongly-typed index +#[derive(Default, Clone, Copy, Debug)] +pub(crate) struct EventArray([T; EVENT_COUNT]); + +impl Index for EventArray { + type Output = T; + + fn index(&self, index: EventType) -> &Self::Output { + let idx = index as usize; + &self.0[idx] + } +} + +impl IndexMut for EventArray { + fn index_mut(&mut self, index: EventType) -> &mut Self::Output { + let idx = index as usize; + &mut self.0[idx] + } +} diff --git a/rpc/core/src/notify/listener.rs b/rpc/core/src/notify/listener.rs new file mode 100644 index 000000000..809c0f591 --- /dev/null +++ b/rpc/core/src/notify/listener.rs @@ -0,0 +1,176 @@ +use std::fmt::Debug; +use std::sync::Arc; + +use crate::notify::{ + channel::NotificationChannel, + events::{EventArray, EventType}, + result::Result, + utxo_address_set::RpcUtxoAddressSet, +}; +use crate::stubs::RpcUtxoAddress; +use crate::{Notification, NotificationReceiver, NotificationSender, NotificationType}; + +pub type ListenerID = u64; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ListenerUtxoNotificationFilterSetting { + /// Send all changed UTXO events, whatever the address + All, + + /// Send all changed UTXO events filtered by the address + FilteredByAddress, +} + +/// A listener of [`super::notifier::Notifier`] notifications. +/// +/// ### Implementation details +/// +/// This struct is not async protected against mutations. +/// It is the responsability of code using a [Listener] to guard memory +/// before calling toggle. +/// +/// Any ListenerSenderSide derived from a [Listener] should also be rebuilt +/// upon relevant mutation by a call to toggle. +#[derive(Debug)] +pub(crate) struct Listener { + id: u64, + channel: NotificationChannel, + active_event: EventArray, + utxo_addresses: RpcUtxoAddressSet, +} + +impl Listener { + pub(crate) fn new(id: ListenerID, channel: Option) -> Listener { + let channel = channel.unwrap_or_default(); + Self { id, channel, active_event: EventArray::default(), utxo_addresses: RpcUtxoAddressSet::new() } + } + + pub(crate) fn id(&self) -> ListenerID { + self.id + } + + /// Has registered for [`EventType`] notifications? + pub(crate) fn has(&self, event: EventType) -> bool { + self.active_event[event] + } + + fn toggle_utxo_addresses(&mut self, utxo_addresses: &[RpcUtxoAddress]) -> bool { + let utxo_addresses = RpcUtxoAddressSet::from_iter(utxo_addresses.iter().cloned()); + if utxo_addresses != self.utxo_addresses { + self.utxo_addresses = utxo_addresses; + return true; + } + false + } + + /// Toggle registration for [`NotificationType`] notifications. + /// Return true if any change occured in the registration state. + pub(crate) fn toggle(&mut self, notification_type: NotificationType, active: bool) -> bool { + let mut changed = false; + let event: EventType = (¬ification_type).into(); + + if self.active_event[event] != active { + self.active_event[event] = active; + changed = true; + } + + if let NotificationType::UtxosChanged(ref utxo_addresses) = notification_type { + changed = self.toggle_utxo_addresses(utxo_addresses); + } + changed + } + + pub(crate) fn close(&mut self) { + if !self.is_closed() { + self.channel.close(); + } + } + + pub(crate) fn is_closed(&self) -> bool { + self.channel.is_closed() + } +} + +/// Contains the receiver side of a listener +#[derive(Debug)] +pub struct ListenerReceiverSide { + pub id: ListenerID, + pub recv_channel: NotificationReceiver, +} + +impl From<&Listener> for ListenerReceiverSide { + fn from(item: &Listener) -> Self { + Self { id: item.id(), recv_channel: item.channel.receiver() } + } +} + +#[derive(Debug)] +/// Contains the sender side of a listener +pub(crate) struct ListenerSenderSide { + send_channel: NotificationSender, + filter: Box, +} + +impl ListenerSenderSide { + pub(crate) fn new(listener: &Listener, sending_changed_utxos: ListenerUtxoNotificationFilterSetting, event: EventType) -> Self { + match event { + EventType::UtxosChanged if sending_changed_utxos == ListenerUtxoNotificationFilterSetting::FilteredByAddress => Self { + send_channel: listener.channel.sender(), + filter: Box::new(FilterUtxoAddress { utxos_addresses: listener.utxo_addresses.clone() }), + }, + _ => Self { send_channel: listener.channel.sender(), filter: Box::new(Unfiltered {}) }, + } + } + + /// Try to send a notification. + /// + /// If the notification does not meet requirements (see [`Notification::UtxosChanged`]) returns `Ok(false)`, + /// otherwise returns `Ok(true)`. + pub(crate) fn try_send(&self, notification: Arc) -> Result { + if self.filter.matches(notification.clone()) { + match self.send_channel.try_send(notification) { + Ok(_) => { + return Ok(true); + } + Err(err) => { + return Err(err.into()); + } + } + } + Ok(false) + } + + pub(crate) fn is_closed(&self) -> bool { + self.send_channel.is_closed() + } +} + +trait InnerFilter { + fn matches(&self, notification: Arc) -> bool; +} + +trait Filter: InnerFilter + Debug {} + +#[derive(Clone, Debug)] +struct Unfiltered; +impl InnerFilter for Unfiltered { + fn matches(&self, _: Arc) -> bool { + true + } +} +impl Filter for Unfiltered {} + +#[derive(Clone, Debug)] +struct FilterUtxoAddress { + utxos_addresses: RpcUtxoAddressSet, +} + +impl InnerFilter for FilterUtxoAddress { + fn matches(&self, notification: Arc) -> bool { + if let Notification::UtxosChanged(ref notification) = *notification { + return self.utxos_addresses.contains(¬ification.utxo_address); + } + false + } +} +impl Filter for FilterUtxoAddress {} diff --git a/rpc/core/src/notify/message.rs b/rpc/core/src/notify/message.rs new file mode 100644 index 000000000..130e63342 --- /dev/null +++ b/rpc/core/src/notify/message.rs @@ -0,0 +1,18 @@ +use super::listener::{ListenerID, ListenerSenderSide}; +use crate::{Notification, NotificationType}; +use std::sync::Arc; + +#[derive(Clone, Debug)] +pub(crate) enum DispatchMessage { + Send(Arc), + AddListener(ListenerID, Arc), + RemoveListener(ListenerID), + Shutdown, +} + +#[derive(Clone, Debug)] +pub(crate) enum SubscribeMessage { + StartEvent(NotificationType), + StopEvent(NotificationType), + Shutdown, +} diff --git a/rpc/core/src/notify/mod.rs b/rpc/core/src/notify/mod.rs new file mode 100644 index 000000000..fd68bb7fe --- /dev/null +++ b/rpc/core/src/notify/mod.rs @@ -0,0 +1,10 @@ +pub mod channel; +pub mod collector; +pub mod errors; +pub mod events; +pub mod listener; +pub mod message; +pub mod notifier; +pub mod result; +pub mod subscriber; +pub mod utxo_address_set; diff --git a/rpc/core/src/notify/notifier.rs b/rpc/core/src/notify/notifier.rs new file mode 100644 index 000000000..363610992 --- /dev/null +++ b/rpc/core/src/notify/notifier.rs @@ -0,0 +1,370 @@ +use super::{ + channel::NotificationChannel, + collector::DynCollector, + errors::Error, + events::{EventArray, EventType, EVENT_TYPE_ARRAY}, + listener::{Listener, ListenerID, ListenerReceiverSide, ListenerSenderSide, ListenerUtxoNotificationFilterSetting}, + message::{DispatchMessage, SubscribeMessage}, + result::Result, + subscriber::{Subscriber, SubscriptionManager}, +}; +use crate::{api::ops::SubscribeCommand, Notification, NotificationType, RpcResult}; +use ahash::AHashMap; +use async_std::channel::{Receiver, Sender}; +use async_trait::async_trait; +use kaspa_core::trace; +use kaspa_utils::channel::Channel; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, +}; + +/// A notification sender +/// +/// Manages a collection of [`Listener`] and, for each one, a set of events to be notified. +/// Actually notify the listeners of incoming events. +#[derive(Debug)] +pub struct Notifier { + inner: Arc, +} + +impl Notifier { + pub fn new( + collector: Option, + subscriber: Option, + sending_changed_utxos: ListenerUtxoNotificationFilterSetting, + ) -> Self { + Self { inner: Arc::new(Inner::new(collector, subscriber, sending_changed_utxos)) } + } + + pub fn start(self: Arc) { + self.inner.clone().start(self.clone()); + } + + pub fn register_new_listener(&self, channel: Option) -> ListenerReceiverSide { + self.inner.clone().register_new_listener(channel) + } + + pub fn unregister_listener(&self, id: ListenerID) -> Result<()> { + self.inner.clone().unregister_listener(id) + } + + pub fn execute_subscribe_command( + self: Arc, + id: ListenerID, + notification_type: NotificationType, + command: SubscribeCommand, + ) -> Result<()> { + self.inner.clone().execute_subscribe_command(id, notification_type, command) + } + + pub fn start_notify(&self, id: ListenerID, notification_type: NotificationType) -> Result<()> { + trace!("[Notifier] start sending to listener {0} notifications of type {1:?}", id, notification_type); + self.inner.clone().start_notify(id, notification_type) + } + + pub fn notify(self: Arc, notification: Arc) -> Result<()> { + self.inner.clone().notify(notification) + } + + pub fn stop_notify(&self, id: ListenerID, notification_type: NotificationType) -> Result<()> { + trace!("[Notifier] stop sending to listener {0} notifications of type {1:?}", id, notification_type); + self.inner.clone().stop_notify(id, notification_type) + } + + pub async fn stop(&self) -> Result<()> { + self.inner.clone().stop().await + } +} + +#[async_trait] +impl SubscriptionManager for Notifier { + async fn start_notify(self: Arc, id: ListenerID, notification_type: NotificationType) -> RpcResult<()> { + trace!("[Notifier] as subscription manager start sending to listener {0} notifications of type {1:?}", id, notification_type); + self.inner.clone().start_notify(id, notification_type)?; + Ok(()) + } + + async fn stop_notify(self: Arc, id: ListenerID, notification_type: NotificationType) -> RpcResult<()> { + trace!("[Notifier] as subscription manager stop sending to listener {0} notifications of type {1:?}", id, notification_type); + self.inner.clone().stop_notify(id, notification_type)?; + Ok(()) + } +} + +#[derive(Debug)] +struct Inner { + /// Map of registered listeners + listeners: Arc>>, + + /// Has this notifier been started? + is_started: Arc, + + /// Dispatcher channels by event type + dispatcher_channel: EventArray>, + dispatcher_shutdown_listener: Arc>>>, + + // Collector & Subscriber + collector: Arc>, + subscriber: Arc>>, + + /// How to handle UtxoChanged notifications + sending_changed_utxos: ListenerUtxoNotificationFilterSetting, +} + +impl Inner { + fn new( + collector: Option, + subscriber: Option, + sending_changed_utxos: ListenerUtxoNotificationFilterSetting, + ) -> Self { + let subscriber = subscriber.map(Arc::new); + Self { + listeners: Arc::new(Mutex::new(AHashMap::new())), + is_started: Arc::new(AtomicBool::new(false)), + dispatcher_channel: EventArray::default(), + dispatcher_shutdown_listener: Arc::new(Mutex::new(EventArray::default())), + collector: Arc::new(collector), + subscriber: Arc::new(subscriber), + sending_changed_utxos, + } + } + + fn start(self: Arc, notifier: Arc) { + if self.is_started.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_ok() { + if let Some(ref subscriber) = self.subscriber.clone().as_ref() { + subscriber.clone().start(); + } + for event in EVENT_TYPE_ARRAY.into_iter() { + let (shutdown_trigger, shutdown_listener) = triggered::trigger(); + let mut dispatcher_shutdown_listener = self.dispatcher_shutdown_listener.lock().unwrap(); + dispatcher_shutdown_listener[event] = Some(shutdown_listener); + self.spawn_dispatcher_task(event, shutdown_trigger, self.dispatcher_channel[event].receiver()); + } + if let Some(ref collector) = self.collector.clone().as_ref() { + collector.clone().start(notifier); + } + } + } + + /// Launch a dispatcher task for an event type. + /// + /// ### Implementation note + /// + /// The separation by event type allows to keep an internal map + /// with all listeners willing to receive notification of the + /// corresponding type. The dispatcher receives and executes messages + /// instructing to modify the map. This happens without blocking + /// the whole notifier. + fn spawn_dispatcher_task(&self, event: EventType, shutdown_trigger: triggered::Trigger, dispatch_rx: Receiver) { + // Feedback + let send_subscriber = self.subscriber.clone().as_ref().as_ref().map(|x| x.sender()); + let has_subscriber = self.subscriber.clone().as_ref().is_some(); + + let sending_changed_utxos = self.sending_changed_utxos; + + // This holds the map of all active listeners for the event type + let mut listeners: AHashMap> = AHashMap::new(); + + // TODO: feed the listeners map with pre-existing self.listeners having event active + // This is necessary for the correct handling of repeating start/stop cycles. + + workflow_core::task::spawn(async move { + trace!("[Notifier] dispatch_task spawned"); + + fn send_subscribe_message(send_subscriber: Sender, message: SubscribeMessage) { + trace!("[Notifier] dispatch_task send subscribe message: {:?}", message); + match send_subscriber.try_send(message) { + Ok(_) => {} + Err(err) => { + trace!("[Notifier] sending subscribe message error: {:?}", err); + } + } + } + + // We will send subscribe messages for all dispatch messages if event is a filtered UtxosChanged. + // Otherwise, subscribe message is only sent when needed by the execution of the dispatche message. + let report_all_changes = + event == EventType::UtxosChanged && sending_changed_utxos == ListenerUtxoNotificationFilterSetting::FilteredByAddress; + + let mut need_subscribe: bool = false; + loop { + // If needed, send subscribe message based on listeners map being empty or not + if need_subscribe && has_subscriber { + if listeners.len() > 0 { + // TODO: handle actual utxo addresse set + + send_subscribe_message(send_subscriber.as_ref().unwrap().clone(), SubscribeMessage::StartEvent(event.into())); + } else { + send_subscribe_message(send_subscriber.as_ref().unwrap().clone(), SubscribeMessage::StopEvent(event.into())); + } + } + let dispatch = dispatch_rx.recv().await.unwrap(); + + match dispatch { + DispatchMessage::Send(ref notification) => { + // Create a store for closed listeners to be removed from the map + let mut purge: Vec = Vec::new(); + + // Broadcast the notification to all listeners + for (id, listener) in listeners.iter() { + match listener.try_send(notification.clone()) { + Ok(_) => {} + Err(_) => { + if listener.is_closed() { + purge.push(*id); + } + } + } + } + + // Feedback needed if purge will empty listeners or if reporting any change + need_subscribe = (!purge.is_empty() && (purge.len() == listeners.len())) || report_all_changes; + + // Remove closed listeners + for id in purge { + listeners.remove(&id); + } + } + + DispatchMessage::AddListener(id, listener) => { + // Subscription needed if a first listener is added or if reporting any change + need_subscribe = listeners.len() == 0 || report_all_changes; + + // We don't care whether this is an insertion or a replacement + listeners.insert(id, listener.clone()); + } + + DispatchMessage::RemoveListener(id) => { + listeners.remove(&id); + + // Feedback needed if no more listeners are present or if reporting any change + need_subscribe = listeners.len() == 0 || report_all_changes; + } + + DispatchMessage::Shutdown => { + break; + } + } + } + shutdown_trigger.trigger(); + }); + } + + fn register_new_listener(self: Arc, channel: Option) -> ListenerReceiverSide { + let mut listeners = self.listeners.lock().unwrap(); + loop { + let id = u64::from_le_bytes(rand::random::<[u8; 8]>()); + + // This is very unlikely to happen but still, check for duplicates + if !listeners.contains_key(&id) { + let listener = Listener::new(id, channel); + let registration: ListenerReceiverSide = (&listener).into(); + listeners.insert(id, listener); + return registration; + } + } + } + + fn unregister_listener(self: Arc, id: ListenerID) -> Result<()> { + let mut listeners = self.listeners.lock().unwrap(); + if let Some(mut listener) = listeners.remove(&id) { + drop(listeners); + let active_events: Vec = EVENT_TYPE_ARRAY.into_iter().filter(|event| listener.has(*event)).collect(); + for event in active_events.iter() { + self.clone().stop_notify(listener.id(), (*event).into())?; + } + listener.close(); + } + Ok(()) + } + + pub fn execute_subscribe_command( + self: Arc, + id: ListenerID, + notification_type: NotificationType, + command: SubscribeCommand, + ) -> Result<()> { + match command { + SubscribeCommand::Start => self.start_notify(id, notification_type), + SubscribeCommand::Stop => self.stop_notify(id, notification_type), + } + } + + fn start_notify(self: Arc, id: ListenerID, notification_type: NotificationType) -> Result<()> { + let event: EventType = (¬ification_type).into(); + let mut listeners = self.listeners.lock().unwrap(); + if let Some(listener) = listeners.get_mut(&id) { + trace!("[Notifier] start notify to {0} about {1:?}", id, notification_type); + + // Any mutation in the listener will trigger a dispatch of a brand new ListenerSenderSide + // eventually creating or replacing this listener in the matching dispatcher. + + if listener.toggle(notification_type, true) { + let listener_sender_side = ListenerSenderSide::new(listener, self.sending_changed_utxos, event); + let msg = DispatchMessage::AddListener(listener.id(), Arc::new(listener_sender_side)); + self.clone().try_send_dispatch(event, msg)?; + } + } + Ok(()) + } + + fn notify(self: Arc, notification: Arc) -> Result<()> { + let event: EventType = notification.as_ref().into(); + let msg = DispatchMessage::Send(notification); + self.try_send_dispatch(event, msg)?; + Ok(()) + } + + fn stop_notify(self: Arc, id: ListenerID, notification_type: NotificationType) -> Result<()> { + let event: EventType = (¬ification_type).into(); + let mut listeners = self.listeners.lock().unwrap(); + if let Some(listener) = listeners.get_mut(&id) { + trace!("[Notifier] stop notify to {0} about {1:?}", id, notification_type); + + if listener.toggle(notification_type, false) { + let msg = DispatchMessage::RemoveListener(listener.id()); + self.clone().try_send_dispatch(event, msg)?; + } + } + Ok(()) + } + + fn try_send_dispatch(self: Arc, event: EventType, msg: DispatchMessage) -> Result<()> { + self.dispatcher_channel[event].sender().try_send(msg)?; + Ok(()) + } + + async fn stop_dispatcher_task(self: Arc) -> Result<()> { + let mut result: Result<()> = Ok(()); + for event in EVENT_TYPE_ARRAY.into_iter() { + match self.clone().try_send_dispatch(event, DispatchMessage::Shutdown) { + Ok(_) => { + let shutdown_listener: triggered::Listener; + { + let mut dispatcher_shutdown_listener = self.dispatcher_shutdown_listener.lock().unwrap(); + shutdown_listener = dispatcher_shutdown_listener[event].take().unwrap(); + } + shutdown_listener.await; + } + Err(err) => result = Err(err), + } + } + result + } + + async fn stop(self: Arc) -> Result<()> { + if self.is_started.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst).is_ok() { + if let Some(ref collector) = self.collector.clone().as_ref() { + collector.clone().stop().await?; + } + self.clone().stop_dispatcher_task().await?; + if let Some(ref subscriber) = self.subscriber.clone().as_ref() { + subscriber.clone().stop().await?; + } + } else { + return Err(Error::AlreadyStoppedError); + } + Ok(()) + } +} diff --git a/rpc/core/src/notify/result.rs b/rpc/core/src/notify/result.rs new file mode 100644 index 000000000..7d7d5ed05 --- /dev/null +++ b/rpc/core/src/notify/result.rs @@ -0,0 +1,2 @@ +use super::errors::Error; +pub type Result = std::result::Result; diff --git a/rpc/core/src/notify/subscriber.rs b/rpc/core/src/notify/subscriber.rs new file mode 100644 index 000000000..b9703643b --- /dev/null +++ b/rpc/core/src/notify/subscriber.rs @@ -0,0 +1,140 @@ +use async_std::channel::{Receiver, Sender}; +use async_trait::async_trait; +use core::fmt::Debug; +use kaspa_core::trace; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, +}; +extern crate derive_more; +use super::{errors::Error, listener::ListenerID, message::SubscribeMessage, result::Result}; +use crate::{api::ops::SubscribeCommand, NotificationType, RpcResult}; +use kaspa_utils::channel::Channel; + +/// A manager of subscriptions to notifications for registered listeners +#[async_trait] +pub trait SubscriptionManager: Send + Sync + Debug { + async fn start_notify(self: Arc, id: ListenerID, notification_type: NotificationType) -> RpcResult<()>; + async fn stop_notify(self: Arc, id: ListenerID, notification_type: NotificationType) -> RpcResult<()>; + + async fn execute_notify_command( + self: Arc, + id: ListenerID, + notification_type: NotificationType, + command: SubscribeCommand, + ) -> RpcResult<()> { + match command { + SubscribeCommand::Start => self.start_notify(id, notification_type).await, + SubscribeCommand::Stop => self.stop_notify(id, notification_type).await, + } + } +} + +pub type DynSubscriptionManager = Arc; + +/// A subscriber handling subscription messages executing them into a [SubscriptionManager]. +#[derive(Debug)] +pub struct Subscriber { + /// Subscription manager + subscription_manager: DynSubscriptionManager, + listener_id: ListenerID, + + /// Has this subscriber been started? + is_started: Arc, + + /// Feedback channel + subscribe_channel: Channel, + subscribe_shutdown_listener: Arc>>, +} + +impl Subscriber { + pub fn new(subscription_manager: DynSubscriptionManager, listener_id: ListenerID) -> Self { + Self { + subscription_manager, + listener_id, + subscribe_channel: Channel::default(), + subscribe_shutdown_listener: Arc::new(Mutex::new(None)), + is_started: Arc::new(AtomicBool::default()), + } + } + + pub(crate) fn sender(&self) -> Sender { + self.subscribe_channel.sender() + } + + pub fn start(self: Arc) { + let (shutdown_trigger, shutdown_listener) = triggered::trigger(); + let mut subscribe_shutdown_listener = self.subscribe_shutdown_listener.lock().unwrap(); + *subscribe_shutdown_listener = Some(shutdown_listener); + self.spawn_subscription_receiver_task(shutdown_trigger, self.subscribe_channel.receiver()); + } + + /// Launch the subscription receiver + fn spawn_subscription_receiver_task(&self, shutdown_trigger: triggered::Trigger, subscribe_rx: Receiver) { + // The task can only be spawned once + if self.is_started.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_err() { + return; + } + let subscription_manager = self.subscription_manager.clone(); + let listener_id = self.listener_id; + + workflow_core::task::spawn(async move { + loop { + let subscribe = subscribe_rx.recv().await.unwrap(); + + match subscribe { + SubscribeMessage::StartEvent(ref notification_type) => { + match subscription_manager.clone().start_notify(listener_id, notification_type.clone()).await { + Ok(_) => (), + Err(err) => { + trace!("[Reporter] start notify error: {:?}", err); + } + } + } + + SubscribeMessage::StopEvent(ref notification_type) => { + match subscription_manager.clone().stop_notify(listener_id, notification_type.clone()).await { + Ok(_) => (), + Err(err) => { + trace!("[Reporter] start notify error: {:?}", err); + } + } + } + + SubscribeMessage::Shutdown => { + break; + } + } + } + shutdown_trigger.trigger(); + }); + } + + fn try_send_subscribe(self: Arc, msg: SubscribeMessage) -> Result<()> { + self.subscribe_channel.sender().try_send(msg)?; + Ok(()) + } + + async fn stop_subscription_receiver_task(self: Arc) -> Result<()> { + if self.is_started.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst).is_err() { + return Err(Error::AlreadyStoppedError); + } + let mut result: Result<()> = Ok(()); + match self.clone().try_send_subscribe(SubscribeMessage::Shutdown) { + Ok(_) => { + let shutdown_listener: triggered::Listener; + { + let mut subscribe_shutdown_listener = self.subscribe_shutdown_listener.lock().unwrap(); + shutdown_listener = subscribe_shutdown_listener.take().unwrap(); + } + shutdown_listener.await; + } + Err(err) => result = Err(err), + } + result + } + + pub async fn stop(self: Arc) -> Result<()> { + self.stop_subscription_receiver_task().await + } +} diff --git a/rpc/core/src/notify/utxo_address_set.rs b/rpc/core/src/notify/utxo_address_set.rs new file mode 100644 index 000000000..aadd708ce --- /dev/null +++ b/rpc/core/src/notify/utxo_address_set.rs @@ -0,0 +1,4 @@ +use crate::stubs::RpcUtxoAddress; +use ahash::AHashSet; + +pub type RpcUtxoAddressSet = AHashSet; diff --git a/rpc/core/src/server/collector.rs b/rpc/core/src/server/collector.rs new file mode 100644 index 000000000..58b027d06 --- /dev/null +++ b/rpc/core/src/server/collector.rs @@ -0,0 +1,11 @@ +use crate::notify::collector::CollectorFrom; +use async_std::channel::{Receiver, Sender}; +use consensus_core::notify::Notification as ConsensusNotification; +use kaspa_utils::channel::Channel; +use std::sync::Arc; + +pub(crate) type ConsensusCollector = CollectorFrom; + +pub type ConsensusNotificationChannel = Channel>; +pub type ConsensusNotificationSender = Sender>; +pub type ConsensusNotificationReceiver = Receiver>; diff --git a/rpc/core/src/server/mod.rs b/rpc/core/src/server/mod.rs new file mode 100644 index 000000000..a9fef4229 --- /dev/null +++ b/rpc/core/src/server/mod.rs @@ -0,0 +1,2 @@ +pub mod collector; +pub mod service; diff --git a/rpc/core/src/server/service.rs b/rpc/core/src/server/service.rs new file mode 100644 index 000000000..25bd3c0c8 --- /dev/null +++ b/rpc/core/src/server/service.rs @@ -0,0 +1,158 @@ +//! Core server implementation for ClientAPI + +use super::collector::{ConsensusCollector, ConsensusNotificationReceiver}; +use crate::{ + api::rpc, + model::*, + notify::{ + channel::NotificationChannel, + listener::{ListenerID, ListenerReceiverSide, ListenerUtxoNotificationFilterSetting}, + notifier::Notifier, + }, + NotificationType, RpcError, RpcResult, +}; +use async_trait::async_trait; +use hashes::Hash; +use std::{ + str::FromStr, + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, + vec, +}; + +/// A service implementing the Rpc API at rpc_core level. +/// +/// Collects notifications from the consensus and forwards them to +/// actual protocol-featured services. Thanks to the subscribtion pattern, +/// notifications are sent to the registered services only if the actually +/// need them. +/// +/// ### Implementation notes +/// +/// This was designed to have a unique instance in the whole application, +/// though multiple instances could coexist safely. +/// +/// Any lower-level service providing an actual protocol, like gPRC should +/// register into this instance in order to get notifications. The data flow +/// from this instance to registered services and backwards should occur +/// by adding respectively to the registered service a Collector and a +/// Subscriber. +#[derive(Debug)] +pub struct RpcCoreService { + notifier: Arc, +} + +impl RpcCoreService { + pub fn new(consensus_recv: ConsensusNotificationReceiver) -> Arc { + // TODO: the channel receiver should be obtained by registering to a consensus notification service + + let collector = Arc::new(ConsensusCollector::new(consensus_recv)); + + // TODO: Some consensus-compatible subscriber could be provided here + let notifier = Arc::new(Notifier::new(Some(collector), None, ListenerUtxoNotificationFilterSetting::All)); + + Arc::new(Self { notifier }) + } + + pub fn start(&self) { + self.notifier.clone().start(); + } + + pub async fn stop(&self) -> RpcResult<()> { + self.notifier.clone().stop().await?; + Ok(()) + } + + pub fn notifier(&self) -> Arc { + self.notifier.clone() + } +} + +#[async_trait] +impl rpc::RpcApi for RpcCoreService { + async fn get_block(&self, req: GetBlockRequest) -> RpcResult { + // TODO: Remove the following test when consensus is used to fetch data + + // This is a test to simulate a consensus error + if req.hash.as_bytes()[0] == 0 { + return Err(RpcError::General(format!("Block {0} not found", req.hash))); + } + + // TODO: query info from consensus and use it to build the response + Ok(GetBlockResponse { block: create_dummy_rpc_block() }) + } + + async fn get_info(&self, _req: GetInfoRequest) -> RpcResult { + // TODO: query info from consensus and use it to build the response + Ok(GetInfoResponse { + p2p_id: "test".to_string(), + mempool_size: 1, + server_version: "0.12.8".to_string(), + is_utxo_indexed: false, + is_synced: false, + has_notify_command: true, + }) + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Notification API + + /// Register a new listener and returns an id and a channel receiver. + fn register_new_listener(&self, channel: Option) -> ListenerReceiverSide { + self.notifier.register_new_listener(channel) + } + + /// Unregister an existing listener. + /// + /// Stop all notifications for this listener and drop its channel. + async fn unregister_listener(&self, id: ListenerID) -> RpcResult<()> { + self.notifier.unregister_listener(id)?; + Ok(()) + } + + /// Start sending notifications of some type to a listener. + async fn start_notify(&self, id: ListenerID, notification_type: NotificationType) -> RpcResult<()> { + self.notifier.start_notify(id, notification_type)?; + Ok(()) + } + + /// Stop sending notifications of some type to a listener. + async fn stop_notify(&self, id: ListenerID, notification_type: NotificationType) -> RpcResult<()> { + self.notifier.stop_notify(id, notification_type)?; + Ok(()) + } +} + +// TODO: Remove the following function when consensus is used to fetch data +fn create_dummy_rpc_block() -> RpcBlock { + let sel_parent_hash = Hash::from_str("5963be67f12da63004ce1baceebd7733c4fb601b07e9b0cfb447a3c5f4f3c4f0").unwrap(); + RpcBlock { + header: RpcBlockHeader { + version: 1, + parents: vec![], + hash_merkle_root: Hash::from_str("4b5a041951c4668ecc190c6961f66e54c1ce10866bef1cf1308e46d66adab270").unwrap(), + accepted_id_merkle_root: Hash::from_str("1a1310d49d20eab15bf62c106714bdc81e946d761701e81fabf7f35e8c47b479").unwrap(), + utxo_commitment: Hash::from_str("e7cdeaa3a8966f3fff04e967ed2481615c76b7240917c5d372ee4ed353a5cc15").unwrap(), + timestamp: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as i64, + bits: 1, + nonce: 1234, + daa_score: 123456, + blue_work: RpcBlueWorkType::from_str("1234567890abcdef").unwrap(), + pruning_point: Hash::from_str("7190c08d42a0f7994b183b52e7ef2f99bac0b91ef9023511cadf4da3a2184b16").unwrap(), + blue_score: 12345678901, + }, + transactions: vec![], + verbose_data: RpcBlockVerboseData { + hash: Hash::from_str("8270e63a0295d7257785b9c9b76c9a2efb7fb8d6ac0473a1bff1571c5030e995").unwrap(), + difficulty: 5678.0, + selected_parent_hash: sel_parent_hash, + transaction_ids: vec![], + is_header_only: true, + blue_score: 98765, + children_hashes: vec![], + merge_set_blues_hashes: vec![], + merge_set_reds_hashes: vec![], + is_chain_block: true, + }, + } +} diff --git a/rpc/core/src/stubs.rs b/rpc/core/src/stubs.rs new file mode 100644 index 000000000..711d72ea8 --- /dev/null +++ b/rpc/core/src/stubs.rs @@ -0,0 +1,43 @@ +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use crate::RpcHexData; + +#[derive(Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +pub enum NetworkType { + Mainnet, + Testnet, + Simnet, + Devnet, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct Address; + +pub type RpcUtxoAddress = RpcHexData; + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct UtxosChangedNotification { + pub utxo_address: RpcUtxoAddress, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct VirtualSelectedParentChainChangedNotification; + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct FinalityConflictNotification; + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct FinalityConflictResolvedNotification; + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct VirtualSelectedParentBlueScoreChangedNotification; + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct VirtualDaaScoreChangedNotification; + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct PruningPointUTXOSetOverrideNotification; + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct NewBlockTemplateNotification; diff --git a/rpc/grpc/Cargo.toml b/rpc/grpc/Cargo.toml new file mode 100644 index 000000000..081a3f9bd --- /dev/null +++ b/rpc/grpc/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "rpc-grpc" +version.workspace = true +edition.workspace = true +authors.workspace = true +include.workspace = true +license.workspace = true + +[dependencies] +thiserror.workspace = true +rpc-core.workspace = true +kaspa-utils.workspace = true +kaspa-core.workspace = true +faster-hex.workspace = true +async-std.workspace = true +async-trait = "0.1.57" +futures = { version = "0.3" } +tonic = { version = "0.8", features = ["gzip"] } +prost = { version = "0.11" } +h2 = "0.3" +tokio = { version = "1.0", features = ["rt-multi-thread", "macros", "sync", "time"] } +tokio-stream = "0.1" +triggered = "0.1" + +[build-dependencies] +tonic-build = { version = "0.8" } diff --git a/rpc/grpc/build.rs b/rpc/grpc/build.rs new file mode 100644 index 000000000..e6c565480 --- /dev/null +++ b/rpc/grpc/build.rs @@ -0,0 +1,11 @@ +fn main() { + let proto_file1 = "./proto/messages.proto"; + let proto_file2 = "./proto/kaspadrpc.proto"; + + println!("cargo:rerun-if-changed={}, {}", proto_file1, proto_file2); + + tonic_build::configure() + .build_server(true) + .compile(&[proto_file1], &["./proto/", "."]) + .unwrap_or_else(|e| panic!("protobuf compile error: {}", e)); +} diff --git a/rpc/grpc/proto/messages.proto b/rpc/grpc/proto/messages.proto new file mode 100644 index 000000000..637df58c7 --- /dev/null +++ b/rpc/grpc/proto/messages.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; +package protowire; + +import "rpc.proto"; + +message KaspadRequest { + oneof payload { + GetCurrentNetworkRequestMessage getCurrentNetworkRequest = 1001; + NotifyBlockAddedRequestMessage notifyBlockAddedRequest = 1007; + GetBlockRequestMessage getBlockRequest = 1025; + GetInfoRequestMessage getInfoRequest = 1063; + } +} + +message KaspadResponse { + oneof payload { + GetCurrentNetworkResponseMessage getCurrentNetworkResponse = 1002; + NotifyBlockAddedResponseMessage notifyBlockAddedResponse = 1008; + BlockAddedNotificationMessage blockAddedNotification = 1009; + GetBlockResponseMessage getBlockResponse = 1026; + GetInfoResponseMessage getInfoResponse = 1064; + } +} + +service RPC { + rpc MessageStream (stream KaspadRequest) returns (stream KaspadResponse) {} +} diff --git a/rpc/grpc/proto/rpc.proto b/rpc/grpc/proto/rpc.proto new file mode 100644 index 000000000..894c03616 --- /dev/null +++ b/rpc/grpc/proto/rpc.proto @@ -0,0 +1,745 @@ +// RPC-related types. Request messages, response messages, and dependant types. +// +// Clients are expected to build RequestMessages and wrap them in KaspadMessage. (see messages.proto) +// +// Having received a RequestMessage, (wrapped in a KaspadMessage) the RPC server will respond with a +// ResponseMessage (likewise wrapped in a KaspadMessage) respective to the original RequestMessage. +// +// **IMPORTANT:** This API is a work in progress and is subject to break between versions. +// +syntax = "proto3"; +package protowire; + +// RPCError represents a generic non-internal error. +// +// Receivers of any ResponseMessage are expected to check whether its error field is not null. +message RPCError{ + string message = 1; +} + +message RpcBlock { + RpcBlockHeader header = 1; + repeated RpcTransaction transactions = 2; + RpcBlockVerboseData verboseData = 3; +} + +message RpcBlockHeader { + uint32 version = 1; + repeated RpcBlockLevelParents parents = 12; + string hashMerkleRoot = 3; + string acceptedIdMerkleRoot = 4; + string utxoCommitment = 5; + int64 timestamp = 6; + uint32 bits = 7; + uint64 nonce = 8; + uint64 daaScore = 9; + string blueWork = 10; + string pruningPoint = 14; + uint64 blueScore = 13; +} + +message RpcBlockLevelParents { + repeated string parentHashes = 1; +} + +message RpcBlockVerboseData{ + string hash = 1; + double difficulty = 11; + string selectedParentHash = 13; + repeated string transactionIds = 14; + bool isHeaderOnly = 15; + uint64 blueScore = 16; + repeated string childrenHashes = 17; + repeated string mergeSetBluesHashes = 18; + repeated string mergeSetRedsHashes = 19; + bool isChainBlock = 20; +} + +message RpcTransaction { + uint32 version = 1; + repeated RpcTransactionInput inputs = 2; + repeated RpcTransactionOutput outputs = 3; + uint64 lockTime = 4; + string subnetworkId = 5; + uint64 gas = 6; + string payload = 8; + RpcTransactionVerboseData verboseData = 9; +} + +message RpcTransactionInput { + RpcOutpoint previousOutpoint = 1; + string signatureScript = 2; + uint64 sequence = 3; + uint32 sigOpCount = 5; + RpcTransactionInputVerboseData verboseData = 4; +} + +message RpcScriptPublicKey { + uint32 version = 1; + string scriptPublicKey = 2; +} + +message RpcTransactionOutput { + uint64 amount = 1; + RpcScriptPublicKey scriptPublicKey = 2; + RpcTransactionOutputVerboseData verboseData = 3; +} + +message RpcOutpoint { + string transactionId = 1; + uint32 index = 2; +} + +message RpcUtxoEntry { + uint64 amount = 1; + RpcScriptPublicKey scriptPublicKey = 2; + uint64 blockDaaScore = 3; + bool isCoinbase = 4; +} + +message RpcTransactionVerboseData{ + string transactionId = 1; + string hash = 2; + uint64 mass = 4; + string blockHash = 12; + uint64 blockTime = 14; +} + +message RpcTransactionInputVerboseData{ +} + +message RpcTransactionOutputVerboseData{ + string scriptPublicKeyType = 5; + string scriptPublicKeyAddress = 6; +} + +enum RpcNotifyCommand { + NOTIFY_START = 0; + NOTIFY_STOP = 1; +} + +// GetCurrentNetworkRequestMessage requests the network kaspad is currently running against. +// +// Possible networks are: Mainnet, Testnet, Simnet, Devnet +message GetCurrentNetworkRequestMessage{ +} + +message GetCurrentNetworkResponseMessage{ + string currentNetwork = 1; + RPCError error = 1000; +} + +// SubmitBlockRequestMessage requests to submit a block into the DAG. +// Blocks are generally expected to have been generated using the getBlockTemplate call. +// +// See: GetBlockTemplateRequestMessage +message SubmitBlockRequestMessage{ + RpcBlock block = 2; + bool allowNonDAABlocks = 3; +} + +message SubmitBlockResponseMessage{ + enum RejectReason { + NONE = 0; + BLOCK_INVALID = 1; + IS_IN_IBD = 2; + } + RejectReason rejectReason = 1; + RPCError error = 1000; +} + +// GetBlockTemplateRequestMessage requests a current block template. +// Callers are expected to solve the block template and submit it using the submitBlock call +// +// See: SubmitBlockRequestMessage +message GetBlockTemplateRequestMessage{ + // Which kaspa address should the coinbase block reward transaction pay into + string payAddress = 1; + string extraData = 2; +} + +message GetBlockTemplateResponseMessage{ + RpcBlock block = 3; + + // Whether kaspad thinks that it's synced. + // Callers are discouraged (but not forbidden) from solving blocks when kaspad is not synced. + // That is because when kaspad isn't in sync with the rest of the network there's a high + // chance the block will never be accepted, thus the solving effort would have been wasted. + bool isSynced = 2; + + RPCError error = 1000; +} + +// NotifyBlockAddedRequestMessage registers this connection for blockAdded notifications. +// +// See: BlockAddedNotificationMessage +message NotifyBlockAddedRequestMessage{ + RpcNotifyCommand command = 101; +} + +message NotifyBlockAddedResponseMessage{ + RPCError error = 1000; +} + +// BlockAddedNotificationMessage is sent whenever a blocks has been added (NOT accepted) +// into the DAG. +// +// See: NotifyBlockAddedRequestMessage +message BlockAddedNotificationMessage{ + RpcBlock block = 3; +} + +// GetPeerAddressesRequestMessage requests the list of known kaspad addresses in the +// current network. (mainnet, testnet, etc.) +message GetPeerAddressesRequestMessage{ +} + +message GetPeerAddressesResponseMessage{ + repeated GetPeerAddressesKnownAddressMessage addresses = 1; + repeated GetPeerAddressesKnownAddressMessage bannedAddresses = 2; + RPCError error = 1000; +} + +message GetPeerAddressesKnownAddressMessage { + string Addr = 1; +} + +// GetSelectedTipHashRequestMessage requests the hash of the current virtual's +// selected parent. +message GetSelectedTipHashRequestMessage{ +} + +message GetSelectedTipHashResponseMessage{ + string selectedTipHash = 1; + RPCError error = 1000; +} + +// GetMempoolEntryRequestMessage requests information about a specific transaction +// in the mempool. +message GetMempoolEntryRequestMessage{ + // The transaction's TransactionID. + string txId = 1; + bool includeOrphanPool = 2; + bool filterTransactionPool = 3; +} + +message GetMempoolEntryResponseMessage{ + MempoolEntry entry = 1; + + RPCError error = 1000; +} + +// GetMempoolEntriesRequestMessage requests information about all the transactions +// currently in the mempool. +message GetMempoolEntriesRequestMessage{ + bool includeOrphanPool = 1; + bool filterTransactionPool = 2; +} + +message GetMempoolEntriesResponseMessage{ + repeated MempoolEntry entries = 1; + + RPCError error = 1000; +} + +message MempoolEntry{ + uint64 fee = 1; + RpcTransaction transaction = 3; + bool isOrphan = 4; +} + +// GetConnectedPeerInfoRequestMessage requests information about all the p2p peers +// currently connected to this kaspad. +message GetConnectedPeerInfoRequestMessage{ +} + +message GetConnectedPeerInfoResponseMessage{ + repeated GetConnectedPeerInfoMessage infos = 1; + RPCError error = 1000; +} + +message GetConnectedPeerInfoMessage{ + string id = 1; + string address = 2; + + // How long did the last ping/pong exchange take + int64 lastPingDuration = 3; + + // Whether this kaspad initiated the connection + bool isOutbound = 6; + int64 timeOffset = 7; + string userAgent = 8; + + // The protocol version that this peer claims to support + uint32 advertisedProtocolVersion = 9; + + // The timestamp of when this peer connected to this kaspad + int64 timeConnected = 10; + + // Whether this peer is the IBD peer (if IBD is running) + bool isIbdPeer = 11; +} + +// AddPeerRequestMessage adds a peer to kaspad's outgoing connection list. +// This will, in most cases, result in kaspad connecting to said peer. +message AddPeerRequestMessage{ + string address = 1; + + // Whether to keep attempting to connect to this peer after disconnection + bool isPermanent = 2; +} + +message AddPeerResponseMessage{ + RPCError error = 1000; +} + +// SubmitTransactionRequestMessage submits a transaction to the mempool +message SubmitTransactionRequestMessage{ + RpcTransaction transaction = 1; + bool allowOrphan = 2; +} + +message SubmitTransactionResponseMessage{ + // The transaction ID of the submitted transaction + string transactionId = 1; + + RPCError error = 1000; +} + +// NotifyVirtualSelectedParentChainChangedRequestMessage registers this connection for virtualSelectedParentChainChanged notifications. +// +// See: VirtualSelectedParentChainChangedNotificationMessage +message NotifyVirtualSelectedParentChainChangedRequestMessage{ + bool includeAcceptedTransactionIds = 1; + RpcNotifyCommand command = 101; +} + +message NotifyVirtualSelectedParentChainChangedResponseMessage{ + RPCError error = 1000; +} + +// VirtualSelectedParentChainChangedNotificationMessage is sent whenever the DAG's selected parent +// chain had changed. +// +// See: NotifyVirtualSelectedParentChainChangedRequestMessage +message VirtualSelectedParentChainChangedNotificationMessage{ + // The chain blocks that were removed, in high-to-low order + repeated string removedChainBlockHashes = 1; + + // The chain blocks that were added, in low-to-high order + repeated string addedChainBlockHashes = 3; + + // Will be filled only if `includeAcceptedTransactionIds = true` in the notify request. + repeated AcceptedTransactionIds acceptedTransactionIds = 2; +} + +// GetBlockRequestMessage requests information about a specific block +message GetBlockRequestMessage{ + // The hash of the requested block + string hash = 1; + + // Whether to include transaction data in the response + bool includeTransactions = 3; +} + +message GetBlockResponseMessage{ + RpcBlock block = 3; + RPCError error = 1000; +} + +// GetSubnetworkRequestMessage requests information about a specific subnetwork +// +// Currently unimplemented +message GetSubnetworkRequestMessage{ + string subnetworkId = 1; +} + +message GetSubnetworkResponseMessage{ + uint64 gasLimit = 1; + RPCError error = 1000; +} + +// GetVirtualSelectedParentChainFromBlockRequestMessage requests the virtual selected +// parent chain from some startHash to this kaspad's current virtual +message GetVirtualSelectedParentChainFromBlockRequestMessage{ + string startHash = 1; + bool includeAcceptedTransactionIds = 2; +} + +message AcceptedTransactionIds{ + string acceptingBlockHash = 1; + repeated string acceptedTransactionIds = 2; +} + +message GetVirtualSelectedParentChainFromBlockResponseMessage{ + // The chain blocks that were removed, in high-to-low order + repeated string removedChainBlockHashes = 1; + + // The chain blocks that were added, in low-to-high order + repeated string addedChainBlockHashes = 3; + + // The transactions accepted by each block in addedChainBlockHashes. + // Will be filled only if `includeAcceptedTransactionIds = true` in the request. + repeated AcceptedTransactionIds acceptedTransactionIds = 2; + + RPCError error = 1000; +} + +// GetBlocksRequestMessage requests blocks between a certain block lowHash up to this +// kaspad's current virtual. +message GetBlocksRequestMessage{ + string lowHash = 1; + bool includeBlocks = 2; + bool includeTransactions = 3; +} + +message GetBlocksResponseMessage{ + repeated string blockHashes = 4; + repeated RpcBlock blocks = 3; + RPCError error = 1000; +} + +// GetBlockCountRequestMessage requests the current number of blocks in this kaspad. +// Note that this number may decrease as pruning occurs. +message GetBlockCountRequestMessage{ +} + +message GetBlockCountResponseMessage{ + uint64 blockCount = 1; + uint64 headerCount = 2; + RPCError error = 1000; +} + +// GetBlockDagInfoRequestMessage requests general information about the current state +// of this kaspad's DAG. +message GetBlockDagInfoRequestMessage{ +} + +message GetBlockDagInfoResponseMessage{ + string networkName = 1; + uint64 blockCount = 2; + uint64 headerCount = 3; + repeated string tipHashes = 4; + double difficulty = 5; + int64 pastMedianTime = 6; + repeated string virtualParentHashes = 7; + string pruningPointHash = 8; + uint64 virtualDaaScore = 9; + RPCError error = 1000; +} + +message ResolveFinalityConflictRequestMessage{ + string finalityBlockHash = 1; +} + +message ResolveFinalityConflictResponseMessage{ + RPCError error = 1000; +} + +message NotifyFinalityConflictsRequestMessage{ + RpcNotifyCommand command = 101; +} + +message NotifyFinalityConflictsResponseMessage{ + RPCError error = 1000; +} + +message FinalityConflictNotificationMessage{ + string violatingBlockHash = 1; +} + +message FinalityConflictResolvedNotificationMessage{ + string finalityBlockHash = 1; +} + +// ShutDownRequestMessage shuts down this kaspad. +message ShutDownRequestMessage{ +} + +message ShutDownResponseMessage{ + RPCError error = 1000; +} + +// GetHeadersRequestMessage requests headers between the given startHash and the +// current virtual, up to the given limit. +message GetHeadersRequestMessage{ + string startHash = 1; + uint64 limit = 2; + bool isAscending = 3; +} + +message GetHeadersResponseMessage{ + repeated string headers = 1; + RPCError error = 1000; +} + +// NotifyUtxosChangedRequestMessage registers this connection for utxoChanged notifications +// for the given addresses. +// +// This call is only available when this kaspad was started with `--utxoindex` +// +// See: UtxosChangedNotificationMessage +message NotifyUtxosChangedRequestMessage { + // UTXOs addresses to start/stop getting notified about + // Leave empty to start/stop all updates + repeated string addresses = 1; + RpcNotifyCommand command = 101; +} + +message NotifyUtxosChangedResponseMessage { + RPCError error = 1000; +} + +// UtxosChangedNotificationMessage is sent whenever the UTXO index had been updated. +// +// See: NotifyUtxosChangedRequestMessage +message UtxosChangedNotificationMessage { + repeated UtxosByAddressesEntry added = 1; + repeated UtxosByAddressesEntry removed = 2; +} + +message UtxosByAddressesEntry { + string address = 1; + RpcOutpoint outpoint = 2; + RpcUtxoEntry utxoEntry = 3; +} + +// StopNotifyingUtxosChangedRequestMessage unregisters this connection for utxoChanged notifications +// for the given addresses. +// +// This call is only available when this kaspad was started with `--utxoindex` +// +// See: UtxosChangedNotificationMessage +// +// This message only exists for backward compatibility reason with kaspad and is deprecated. +// Use instead UtxosChangedNotificationMessage with command = NOTIFY_STOP. +message StopNotifyingUtxosChangedRequestMessage { + repeated string addresses = 1; +} + +message StopNotifyingUtxosChangedResponseMessage { + RPCError error = 1000; +} + +// GetUtxosByAddressesRequestMessage requests all current UTXOs for the given kaspad addresses +// +// This call is only available when this kaspad was started with `--utxoindex` +message GetUtxosByAddressesRequestMessage { + repeated string addresses = 1; +} + +message GetUtxosByAddressesResponseMessage { + repeated UtxosByAddressesEntry entries = 1; + + RPCError error = 1000; +} + +// GetBalanceByAddressRequest returns the total balance in unspent transactions towards a given address +// +// This call is only available when this kaspad was started with `--utxoindex` +message GetBalanceByAddressRequestMessage { + string address = 1; +} + +message GetBalanceByAddressResponseMessage { + uint64 balance = 1; + + RPCError error = 1000; +} + +message GetBalancesByAddressesRequestMessage { + repeated string addresses = 1; +} + +message BalancesByAddressEntry{ + string address = 1; + uint64 balance = 2; + + RPCError error = 1000; +} + +message GetBalancesByAddressesResponseMessage { + repeated BalancesByAddressEntry entries = 1; + + RPCError error = 1000; +} + +// GetVirtualSelectedParentBlueScoreRequestMessage requests the blue score of the current selected parent +// of the virtual block. +message GetVirtualSelectedParentBlueScoreRequestMessage { +} + +message GetVirtualSelectedParentBlueScoreResponseMessage { + uint64 blueScore = 1; + + RPCError error = 1000; +} + +// NotifyVirtualSelectedParentBlueScoreChangedRequestMessage registers this connection for +// virtualSelectedParentBlueScoreChanged notifications. +// +// See: VirtualSelectedParentBlueScoreChangedNotificationMessage +message NotifyVirtualSelectedParentBlueScoreChangedRequestMessage { +} + +message NotifyVirtualSelectedParentBlueScoreChangedResponseMessage { + RPCError error = 1000; +} + +// VirtualSelectedParentBlueScoreChangedNotificationMessage is sent whenever the blue score +// of the virtual's selected parent changes. +// +// See NotifyVirtualSelectedParentBlueScoreChangedRequestMessage +message VirtualSelectedParentBlueScoreChangedNotificationMessage { + uint64 virtualSelectedParentBlueScore = 1; +} + +// NotifyVirtualDaaScoreChangedRequestMessage registers this connection for +// virtualDaaScoreChanged notifications. +// +// See: VirtualDaaScoreChangedNotificationMessage +message NotifyVirtualDaaScoreChangedRequestMessage { + RpcNotifyCommand command = 101; +} + +message NotifyVirtualDaaScoreChangedResponseMessage { + RPCError error = 1000; +} + +// VirtualDaaScoreChangedNotificationMessage is sent whenever the DAA score +// of the virtual changes. +// +// See NotifyVirtualDaaScoreChangedRequestMessage +message VirtualDaaScoreChangedNotificationMessage { + uint64 virtualDaaScore = 1; +} + +// NotifyPruningPointUTXOSetOverrideRequestMessage registers this connection for +// pruning point UTXO set override notifications. +// +// This call is only available when this kaspad was started with `--utxoindex` +// +// See: NotifyPruningPointUTXOSetOverrideResponseMessage +message NotifyPruningPointUTXOSetOverrideRequestMessage { + RpcNotifyCommand command = 101; +} + + +message NotifyPruningPointUTXOSetOverrideResponseMessage { + RPCError error = 1000; +} + +// PruningPointUTXOSetOverrideNotificationMessage is sent whenever the UTXO index +// resets due to pruning point change via IBD. +// +// See NotifyPruningPointUTXOSetOverrideRequestMessage +message PruningPointUTXOSetOverrideNotificationMessage { +} + +// StopNotifyingPruningPointUTXOSetOverrideRequestMessage unregisters this connection for +// pruning point UTXO set override notifications. +// +// This call is only available when this kaspad was started with `--utxoindex` +// +// See: PruningPointUTXOSetOverrideNotificationMessage +// +// This message only exists for backward compatibility reason with kaspad and is deprecated. +// Use instead NotifyPruningPointUTXOSetOverrideRequestMessage with command = NOTIFY_STOP. +message StopNotifyingPruningPointUTXOSetOverrideRequestMessage { +} + +message StopNotifyingPruningPointUTXOSetOverrideResponseMessage { + RPCError error = 1000; +} + +// BanRequestMessage bans the given ip. +message BanRequestMessage{ + string ip = 1; +} + +message BanResponseMessage{ + RPCError error = 1000; +} + +// UnbanRequestMessage unbans the given ip. +message UnbanRequestMessage{ + string ip = 1; +} + +message UnbanResponseMessage{ + RPCError error = 1000; +} + +// GetInfoRequestMessage returns info about the node. +message GetInfoRequestMessage{ +} + +message GetInfoResponseMessage{ + string p2pId = 1; + uint64 mempoolSize = 2; + string serverVersion = 3; + bool isUtxoIndexed = 4; + bool isSynced = 5; + bool hasNotifyCommand = 101; + RPCError error = 1000; + + //bool hasRequestResponseId = 102; +} + +message EstimateNetworkHashesPerSecondRequestMessage{ + uint32 windowSize = 1; + string startHash = 2; +} + +message EstimateNetworkHashesPerSecondResponseMessage{ + uint64 networkHashesPerSecond = 1; + RPCError error = 1000; +} + +// NotifyNewBlockTemplateRequestMessage registers this connection for +// NewBlockTemplate notifications. +// +// See: NewBlockTemplateNotificationMessage +message NotifyNewBlockTemplateRequestMessage { + RpcNotifyCommand command = 101; +} + +message NotifyNewBlockTemplateResponseMessage { + RPCError error = 1000; +} + +// NewBlockTemplateNotificationMessage is sent whenever a new updated block template is +// available for miners. +// +// See NotifyNewBlockTemplateRequestMessage +message NewBlockTemplateNotificationMessage { + RpcNotifyCommand command = 101; +} + +message MempoolEntryByAddress{ + string address = 1; + repeated MempoolEntry sending = 2; + repeated MempoolEntry receiving = 3; +} + +message GetMempoolEntriesByAddressesRequestMessage{ + repeated string addresses = 1; + bool includeOrphanPool = 2; + bool filterTransactionPool = 3; +} + +message GetMempoolEntriesByAddressesResponseMessage{ + repeated MempoolEntryByAddress entries = 1; + + RPCError error = 1000; +} + +message GetCoinSupplyRequestMessage{ +} + +message GetCoinSupplyResponseMessage{ + uint64 maxSompi = 1; // note: this is a hard coded maxSupply, actual maxSupply is expected to deviate by upto -5%, but cannot be measured exactly. + uint64 circulatingSompi = 2; + + RPCError error = 1000; +} diff --git a/rpc/grpc/src/client/errors.rs b/rpc/grpc/src/client/errors.rs new file mode 100644 index 000000000..83e0a1114 --- /dev/null +++ b/rpc/grpc/src/client/errors.rs @@ -0,0 +1,59 @@ +use rpc_core::RpcError; +use thiserror::Error; + +pub type BoxedStdError = Box<(dyn std::error::Error + Sync + std::marker::Send + 'static)>; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Error: {0}")] + String(String), + + #[error("gRPC client error {0}")] + TonicStatus(#[from] tonic::Status), + + /// RPC call timeout + #[error("RPC request timeout")] + Timeout, + + #[error("Endpoint connection error: {0}")] + EndpointConnectionError(#[from] tonic::transport::Error), + + #[error("Notify error: {0}")] + NotifyError(#[from] rpc_core::notify::errors::Error), + + #[error("RPC: channel receive error")] + ChannelRecvError, + + #[error("RPC: channel send error")] + ChannelSendError, + + #[error("Missing request payload")] + MissingRequestPayload, + + #[error("Missing response payload")] + MissingResponsePayload, +} + +impl From for RpcError { + fn from(value: Error) -> Self { + RpcError::General(value.to_string()) + } +} + +impl From for Error { + fn from(err: BoxedStdError) -> Self { + Error::String(err.to_string()) + } +} + +impl From> for Error { + fn from(_: tokio::sync::mpsc::error::SendError) -> Self { + Error::ChannelSendError + } +} + +impl From for Error { + fn from(_: tokio::sync::oneshot::error::RecvError) -> Self { + Error::ChannelRecvError + } +} diff --git a/rpc/grpc/src/client/mod.rs b/rpc/grpc/src/client/mod.rs new file mode 100644 index 000000000..8bbb88d5c --- /dev/null +++ b/rpc/grpc/src/client/mod.rs @@ -0,0 +1,101 @@ +use async_trait::async_trait; +use std::sync::Arc; + +use self::resolver::Resolver; +use self::result::Result; +use rpc_core::{ + api::ops::RpcApiOps, + api::rpc::RpcApi, + notify::{ + channel::NotificationChannel, + collector::RpcCoreCollector, + listener::{ListenerID, ListenerReceiverSide, ListenerUtxoNotificationFilterSetting}, + notifier::Notifier, + subscriber::Subscriber, + }, + GetBlockRequest, GetBlockResponse, GetInfoRequest, GetInfoResponse, NotificationType, RpcError, RpcResult, +}; + +mod errors; +mod resolver; +mod result; + +pub struct RpcApiGrpc { + inner: Arc, + notifier: Arc, +} + +impl RpcApiGrpc { + pub async fn connect(address: String) -> Result { + let notify_channel = NotificationChannel::default(); + let inner = Resolver::connect(address, notify_channel.sender()).await?; + let collector = Arc::new(RpcCoreCollector::new(notify_channel.receiver())); + let subscriber = Subscriber::new(inner.clone(), 0); + + let notifier = + Arc::new(Notifier::new(Some(collector), Some(subscriber), ListenerUtxoNotificationFilterSetting::FilteredByAddress)); + + Ok(Self { inner, notifier }) + } + + pub async fn start(&self) { + self.notifier.clone().start(); + } + + pub async fn stop(&self) -> Result<()> { + self.notifier.clone().stop().await?; + Ok(()) + } + + pub fn handle_stop_notify(&self) -> bool { + self.inner.handle_stop_notify() + } + + pub async fn shutdown(&mut self) -> Result<()> { + self.inner.clone().shutdown().await?; + Ok(()) + } +} + +#[async_trait] +impl RpcApi for RpcApiGrpc { + async fn get_block(&self, request: GetBlockRequest) -> RpcResult { + self.inner.clone().call(RpcApiOps::GetBlock, request).await?.as_ref().try_into() + } + + async fn get_info(&self, request: GetInfoRequest) -> RpcResult { + self.inner.clone().call(RpcApiOps::GetInfo, request).await?.as_ref().try_into() + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Notification API + + /// Register a new listener and returns an id and a channel receiver. + fn register_new_listener(&self, channel: Option) -> ListenerReceiverSide { + self.notifier.register_new_listener(channel) + } + + /// Unregister an existing listener. + /// + /// Stop all notifications for this listener and drop its channel. + async fn unregister_listener(&self, id: ListenerID) -> RpcResult<()> { + self.notifier.unregister_listener(id)?; + Ok(()) + } + + /// Start sending notifications of some type to a listener. + async fn start_notify(&self, id: ListenerID, notification_type: NotificationType) -> RpcResult<()> { + self.notifier.start_notify(id, notification_type)?; + Ok(()) + } + + /// Stop sending notifications of some type to a listener. + async fn stop_notify(&self, id: ListenerID, notification_type: NotificationType) -> RpcResult<()> { + if self.handle_stop_notify() { + self.notifier.stop_notify(id, notification_type)?; + Ok(()) + } else { + Err(RpcError::UnsupportedFeature) + } + } +} diff --git a/rpc/grpc/src/client/resolver/matcher.rs b/rpc/grpc/src/client/resolver/matcher.rs new file mode 100644 index 000000000..5801c018e --- /dev/null +++ b/rpc/grpc/src/client/resolver/matcher.rs @@ -0,0 +1,52 @@ +use crate::protowire::{ + self, kaspad_request, kaspad_response, GetBlockRequestMessage, GetBlockResponseMessage, KaspadRequest, KaspadResponse, +}; + +pub(crate) trait Matcher { + fn is_matching(&self, response: T) -> bool; +} + +impl Matcher<&GetBlockResponseMessage> for GetBlockRequestMessage { + fn is_matching(&self, response: &protowire::GetBlockResponseMessage) -> bool { + if let Some(block) = response.block.as_ref() { + if let Some(verbose_data) = block.verbose_data.as_ref() { + return verbose_data.hash == self.hash; + } + } else if let Some(error) = response.error.as_ref() { + // the response error message should contain the requested hash + return error.message.contains(self.hash.as_str()); + } + false + } +} + +impl Matcher<&kaspad_response::Payload> for GetBlockRequestMessage { + fn is_matching(&self, response: &kaspad_response::Payload) -> bool { + if let kaspad_response::Payload::GetBlockResponse(ref response) = response { + return self.is_matching(response); + } + false + } +} + +impl Matcher<&kaspad_response::Payload> for kaspad_request::Payload { + fn is_matching(&self, response: &kaspad_response::Payload) -> bool { + match self { + kaspad_request::Payload::GetBlockRequest(ref request) => request.is_matching(response), + kaspad_request::Payload::GetCurrentNetworkRequest(_) => true, + kaspad_request::Payload::NotifyBlockAddedRequest(_) => true, + kaspad_request::Payload::GetInfoRequest(_) => true, + } + } +} + +impl Matcher<&KaspadResponse> for KaspadRequest { + fn is_matching(&self, response: &KaspadResponse) -> bool { + if let Some(ref response) = response.payload { + if let Some(ref request) = self.payload { + return request.is_matching(response); + } + } + false + } +} diff --git a/rpc/grpc/src/client/resolver/mod.rs b/rpc/grpc/src/client/resolver/mod.rs new file mode 100644 index 000000000..3d89010dc --- /dev/null +++ b/rpc/grpc/src/client/resolver/mod.rs @@ -0,0 +1,396 @@ +use super::{errors::Error, result::Result}; +use crate::protowire::{kaspad_request, rpc_client::RpcClient, GetInfoRequestMessage, KaspadRequest, KaspadResponse}; +use async_trait::async_trait; +use futures::{ + future::FutureExt, // for `.fuse()` + pin_mut, + select, +}; +use kaspa_core::trace; +use kaspa_utils::triggers::DuplexTrigger; +use rpc_core::{ + api::ops::{RpcApiOps, SubscribeCommand}, + notify::{events::EventType, listener::ListenerID, subscriber::SubscriptionManager}, + GetInfoResponse, Notification, NotificationSender, NotificationType, RpcResult, +}; +use std::{ + collections::VecDeque, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, + }, + time::{Duration, Instant}, +}; +use tokio::sync::{ + mpsc::{self, Sender}, + oneshot, +}; +use tokio_stream::wrappers::ReceiverStream; +use tonic::Streaming; +use tonic::{codec::CompressionEncoding, transport::Endpoint}; + +use matcher::*; +mod matcher; + +pub const CONNECT_TIMEOUT_DURATION: u64 = 20_000; +pub const KEEP_ALIVE_DURATION: u64 = 5_000; +pub const REQUEST_TIMEOUT_DURATION: u64 = 5_000; +pub const TIMEOUT_MONITORING_INTERVAL: u64 = 1_000; + +pub type SenderResponse = tokio::sync::oneshot::Sender>; + +#[derive(Debug)] +struct Pending { + timestamp: Instant, + op: RpcApiOps, + request: KaspadRequest, + sender: SenderResponse, +} + +impl Pending { + fn new(op: RpcApiOps, request: KaspadRequest, sender: SenderResponse) -> Self { + Self { timestamp: Instant::now(), op, request, sender } + } + + fn is_matching(&self, response: &KaspadResponse, response_op: RpcApiOps) -> bool { + self.op == response_op && self.request.is_matching(response) + } +} + +/// A struct to handle messages flowing to (requests) and from (responses) a protowire server. +/// Incoming responses are associated to pending requests based on their matching operation +/// type and, for some operations like [`ClientApiOps::GetBlock`], on their properties. +/// +/// Data flow: +/// ``` +/// // KaspadRequest -> request_send -> stream -> KaspadResponse +/// ``` +/// +/// Execution flow: +/// ``` +/// // | call ---------------------------------------------------->| +/// // | response_receiver_task ->| +/// ``` +/// +/// +/// #### Further development +/// +/// TODO: +/// +/// Carry any subscribe call result up to the initial RpcApiGrpc::start_notify execution. +/// For now, RpcApiGrpc::start_notify only gets a result reflecting the call to +/// Notifier::try_send_dispatch. This is not complete. +/// +/// Investigate a possible bottleneck in handle_response with the processing of pendings. +/// If this is the case, some concurrent alternative should be considered. +/// +/// Design/flow: +/// +/// Currently call is blocking until response_receiver_task or timeout_task do solve the pending. +/// So actual concurrency must happen higher in the code. +/// Is there a better way to handle the flow? +/// +#[derive(Debug)] +pub(super) struct Resolver { + handle_stop_notify: bool, + + // Pushing incoming notifications forward + notify_send: NotificationSender, + + // Sending to server + request_send: Sender, + pending_calls: Arc>>, + + // Receiving from server + receiver_is_running: AtomicBool, + receiver_shutdown: DuplexTrigger, + + // Pending timeout cleaning task + timeout_is_running: AtomicBool, + timeout_shutdown: DuplexTrigger, + timeout_timer_interval: u64, + timeout_duration: u64, +} + +impl Resolver { + pub(super) fn new(handle_stop_notify: bool, notify_send: NotificationSender, request_send: Sender) -> Self { + Self { + handle_stop_notify, + notify_send, + request_send, + pending_calls: Arc::new(Mutex::new(VecDeque::new())), + receiver_is_running: AtomicBool::new(false), + receiver_shutdown: DuplexTrigger::new(), + timeout_is_running: AtomicBool::new(false), + timeout_shutdown: DuplexTrigger::new(), + timeout_duration: REQUEST_TIMEOUT_DURATION, + timeout_timer_interval: TIMEOUT_MONITORING_INTERVAL, + } + } + + pub(crate) async fn connect(address: String, notify_send: NotificationSender) -> Result> { + let channel = Endpoint::from_shared(address.clone())? + .timeout(tokio::time::Duration::from_millis(REQUEST_TIMEOUT_DURATION)) + .connect_timeout(tokio::time::Duration::from_millis(CONNECT_TIMEOUT_DURATION)) + .tcp_keepalive(Some(tokio::time::Duration::from_millis(KEEP_ALIVE_DURATION))) + .connect() + .await?; + + let mut client = + RpcClient::new(channel).send_compressed(CompressionEncoding::Gzip).accept_compressed(CompressionEncoding::Gzip); + + // External channel + let (request_send, request_recv) = mpsc::channel(16); + + // Force the opening of the stream when connected to a go kaspad server. + // This is also needed for querying server capabilities. + request_send.send(GetInfoRequestMessage {}.into()).await?; + + // Actual KaspadRequest to KaspadResponse stream + let mut stream: Streaming = client.message_stream(ReceiverStream::new(request_recv)).await?.into_inner(); + + // Collect server capabilities as stated in GetInfoResponse + let mut handle_stop_notify = false; + match stream.message().await? { + Some(ref msg) => { + trace!("GetInfo got response {:?}", msg); + let response: RpcResult = msg.try_into(); + if let Ok(response) = response { + handle_stop_notify = response.has_notify_command; + } + } + None => { + return Err(Error::String("gRPC stream was closed by the server".to_string())); + } + } + + // create the resolver + let resolver = Arc::new(Resolver::new(handle_stop_notify, notify_send, request_send)); + + // Start the request timeout cleaner + resolver.clone().spawn_request_timeout_monitor(); + + // Start the response receiving task + resolver.clone().spawn_response_receiver_task(stream); + + Ok(resolver) + } + + pub(crate) fn handle_stop_notify(&self) -> bool { + self.handle_stop_notify + } + + pub(crate) async fn call(&self, op: RpcApiOps, request: impl Into) -> Result { + let request: KaspadRequest = request.into(); + trace!("resolver call: {:?}", request); + if request.payload.is_some() { + let (sender, receiver) = oneshot::channel::>(); + + { + let pending = Pending::new(op, request.clone(), sender); + + let mut pending_calls = self.pending_calls.lock().unwrap(); + pending_calls.push_back(pending); + drop(pending_calls); + } + + self.request_send.send(request).await.map_err(|_| Error::ChannelRecvError)?; + + receiver.await? + } else { + Err(Error::MissingRequestPayload) + } + } + + /// Launch a task that periodically checks pending requests and deletes those that have + /// waited longer than a predefined delay. + fn spawn_request_timeout_monitor(self: Arc) { + self.timeout_is_running.store(true, Ordering::SeqCst); + + tokio::spawn(async move { + let shutdown = self.timeout_shutdown.request.listener.clone().fuse(); + pin_mut!(shutdown); + + loop { + let timeout_timer_interval = Duration::from_millis(self.timeout_timer_interval); + let delay = tokio::time::sleep(timeout_timer_interval).fuse(); + pin_mut!(delay); + + select! { + _ = shutdown => { break; }, + _ = delay => { + trace!("[Resolver] running timeout task"); + let mut pending_calls = self.pending_calls.lock().unwrap(); + let timeout = Duration::from_millis(self.timeout_duration); + + let mut index: usize = 0; + loop { + if index >= pending_calls.len() { + break; + } + let pending = pending_calls.get(index).unwrap(); + if pending.timestamp.elapsed() > timeout { + let pending = pending_calls.remove(index).unwrap(); + match pending.sender.send(Err(Error::Timeout)) { + Ok(_) => {}, + Err(err) => { + trace!("[Resolver] the timeout monitor failed to send a timeout error: {:?}", err); + }, + } + } else { + // The call to pending_calls.remove moves whichever end is closer to the + // removal point. So to prevent skipping items, we only increment index when + // no removal occurs. + index += 1; + } + } + }, + } + } + + trace!("[Resolver] terminating timeout task"); + self.timeout_is_running.store(false, Ordering::SeqCst); + self.timeout_shutdown.response.trigger.trigger(); + }); + } + + /// Launch a task receiving and handling response messages sent by the server. + fn spawn_response_receiver_task(self: Arc, mut stream: Streaming) { + self.receiver_is_running.store(true, Ordering::SeqCst); + + tokio::spawn(async move { + loop { + trace!("[Resolver] response receiver loop"); + + let shutdown = self.receiver_shutdown.request.listener.clone(); + pin_mut!(shutdown); + + tokio::select! { + _ = shutdown => { break; } + message = stream.message() => { + match message { + Ok(msg) => { + match msg { + Some(response) => { + self.handle_response(response); + }, + None =>{ + trace!("[Resolver] the incoming stream of the response receiver is closed"); + + // This event makes the whole object unable to work anymore. + // This should be reported to the owner of this Resolver. + // + // Some automatical reconnection mecanism could also be investigated. + break; + } + } + }, + Err(err) => { + trace!("[Resolver] the response receiver gets an error from the server: {:?}", err); + } + } + } + } + } + + trace!("[Resolver] terminating response receiver"); + self.receiver_is_running.store(false, Ordering::SeqCst); + self.receiver_shutdown.response.trigger.trigger(); + }); + } + + fn handle_response(&self, response: KaspadResponse) { + if response.is_notification() { + trace!("[Resolver] handle_response received a notification"); + match Notification::try_from(&response) { + Ok(notification) => { + let event: EventType = (¬ification).into(); + trace!("[Resolver] handle_response received notification: {:?}", event); + + // Here we ignore any returned error + match self.notify_send.try_send(Arc::new(notification)) { + Ok(_) => {} + Err(err) => { + trace!("[Resolver] error while trying to send a notification to the notifier: {:?}", err); + } + } + } + Err(err) => { + trace!("[Resolver] handle_response error converting reponse into notification: {:?}", err); + } + } + } else if response.payload.is_some() { + let response_op: RpcApiOps = response.payload.as_ref().unwrap().into(); + trace!("[Resolver] handle_response type: {:?}", response_op); + let mut pending_calls = self.pending_calls.lock().unwrap(); + let mut pending: Option = None; + if pending_calls.front().is_some() { + if pending_calls.front().unwrap().is_matching(&response, response_op.clone()) { + pending = pending_calls.pop_front(); + } else { + let pending_slice = pending_calls.make_contiguous(); + // Iterate the queue front to back, so older pendings first + for i in 0..pending_slice.len() { + if pending_calls.get(i).unwrap().is_matching(&response, response_op.clone()) { + pending = pending_calls.remove(i); + break; + } + } + } + } + drop(pending_calls); + if let Some(pending) = pending { + trace!("[Resolver] handle_response matching request found: {:?}", pending.request); + match pending.sender.send(Ok(response)) { + Ok(_) => {} + Err(err) => { + trace!("[Resolver] handle_response failed to send the response of a pending: {:?}", err); + } + } + } + } + } + + pub async fn shutdown(&self) -> Result<()> { + self.stop_timeout_monitor().await?; + self.stop_response_receiver_task().await?; + Ok(()) + } + + async fn stop_response_receiver_task(&self) -> Result<()> { + if self.receiver_is_running.load(Ordering::SeqCst) { + self.receiver_shutdown.request.trigger.trigger(); + self.receiver_shutdown.response.listener.clone().await; + } + Ok(()) + } + + async fn stop_timeout_monitor(&self) -> Result<()> { + if self.timeout_is_running.load(Ordering::SeqCst) { + self.timeout_shutdown.request.trigger.trigger(); + self.timeout_shutdown.response.listener.clone().await; + } + Ok(()) + } +} + +#[async_trait] +impl SubscriptionManager for Resolver { + async fn start_notify(self: Arc, _: ListenerID, notification_type: NotificationType) -> RpcResult<()> { + trace!("[Resolver] start_notify: {:?}", notification_type); + let request = kaspad_request::Payload::from_notification_type(¬ification_type, SubscribeCommand::Start); + self.clone().call((&request).into(), request).await?; + Ok(()) + } + + async fn stop_notify(self: Arc, _: ListenerID, notification_type: NotificationType) -> RpcResult<()> { + if self.handle_stop_notify { + trace!("[Resolver] stop_notify: {:?}", notification_type); + let request = kaspad_request::Payload::from_notification_type(¬ification_type, SubscribeCommand::Stop); + self.clone().call((&request).into(), request).await?; + } else { + trace!("[Resolver] stop_notify ignored because not supported by the server: {:?}", notification_type); + } + Ok(()) + } +} diff --git a/rpc/grpc/src/client/result.rs b/rpc/grpc/src/client/result.rs new file mode 100644 index 000000000..7d7d5ed05 --- /dev/null +++ b/rpc/grpc/src/client/result.rs @@ -0,0 +1,2 @@ +use super::errors::Error; +pub type Result = std::result::Result; diff --git a/rpc/grpc/src/convert/block.rs b/rpc/grpc/src/convert/block.rs new file mode 100644 index 000000000..a49beb097 --- /dev/null +++ b/rpc/grpc/src/convert/block.rs @@ -0,0 +1,95 @@ +use crate::protowire; +use rpc_core::{RpcError, RpcHash, RpcResult}; +use std::str::FromStr; + +// ---------------------------------------------------------------------------- +// rpc_core to protowire +// ---------------------------------------------------------------------------- + +impl From<&rpc_core::RpcBlock> for protowire::RpcBlock { + fn from(item: &rpc_core::RpcBlock) -> Self { + Self { + header: Some(protowire::RpcBlockHeader::from(&item.header)), + transactions: item.transactions.iter().map(protowire::RpcTransaction::from).collect(), + verbose_data: Some(protowire::RpcBlockVerboseData::from(&item.verbose_data)), + } + } +} + +impl From<&rpc_core::RpcBlockVerboseData> for protowire::RpcBlockVerboseData { + fn from(item: &rpc_core::RpcBlockVerboseData) -> Self { + Self { + hash: item.hash.to_string(), + difficulty: item.difficulty, + selected_parent_hash: item.selected_parent_hash.to_string(), + transaction_ids: item.transaction_ids.iter().map(|x| x.to_string()).collect(), + is_header_only: item.is_header_only, + blue_score: item.blue_score, + children_hashes: item.children_hashes.iter().map(|x| x.to_string()).collect(), + merge_set_blues_hashes: item.merge_set_blues_hashes.iter().map(|x| x.to_string()).collect(), + merge_set_reds_hashes: item.merge_set_reds_hashes.iter().map(|x| x.to_string()).collect(), + is_chain_block: item.is_chain_block, + } + } +} + +// ---------------------------------------------------------------------------- +// protowire to rpc_core +// ---------------------------------------------------------------------------- + +impl TryFrom<&protowire::RpcBlock> for rpc_core::RpcBlock { + type Error = RpcError; + fn try_from(item: &protowire::RpcBlock) -> RpcResult { + Ok(Self { + header: item + .header + .as_ref() + .ok_or_else(|| RpcError::MissingRpcFieldError("RpcBlock".to_string(), "header".to_string()))? + .try_into()?, + transactions: item + .transactions + .iter() + .map(rpc_core::RpcTransaction::try_from) + .collect::>>()?, + verbose_data: item + .verbose_data + .as_ref() + .ok_or_else(|| RpcError::MissingRpcFieldError("RpcBlock".to_string(), "verbose_data".to_string()))? + .try_into()?, + }) + } +} + +impl TryFrom<&protowire::RpcBlockVerboseData> for rpc_core::RpcBlockVerboseData { + type Error = RpcError; + fn try_from(item: &protowire::RpcBlockVerboseData) -> RpcResult { + Ok(Self { + hash: RpcHash::from_str(&item.hash)?, + difficulty: item.difficulty, + selected_parent_hash: RpcHash::from_str(&item.selected_parent_hash)?, + transaction_ids: item + .transaction_ids + .iter() + .map(|x| RpcHash::from_str(x)) + .collect::, faster_hex::Error>>()?, + is_header_only: item.is_header_only, + blue_score: item.blue_score, + children_hashes: item + .children_hashes + .iter() + .map(|x| RpcHash::from_str(x)) + .collect::, faster_hex::Error>>()?, + merge_set_blues_hashes: item + .merge_set_blues_hashes + .iter() + .map(|x| RpcHash::from_str(x)) + .collect::, faster_hex::Error>>()?, + merge_set_reds_hashes: item + .merge_set_reds_hashes + .iter() + .map(|x| RpcHash::from_str(x)) + .collect::, faster_hex::Error>>()?, + is_chain_block: item.is_chain_block, + }) + } +} diff --git a/rpc/grpc/src/convert/error.rs b/rpc/grpc/src/convert/error.rs new file mode 100644 index 000000000..a324fc8bd --- /dev/null +++ b/rpc/grpc/src/convert/error.rs @@ -0,0 +1,27 @@ +use crate::protowire; + +// ---------------------------------------------------------------------------- +// rpc_core to protowire +// ---------------------------------------------------------------------------- + +impl From for protowire::RpcError { + fn from(item: rpc_core::RpcError) -> Self { + Self { message: item.to_string() } + } +} + +impl From<&rpc_core::RpcError> for protowire::RpcError { + fn from(item: &rpc_core::RpcError) -> Self { + Self { message: item.to_string() } + } +} + +// ---------------------------------------------------------------------------- +// protowire to rpc_core +// ---------------------------------------------------------------------------- + +impl From<&protowire::RpcError> for rpc_core::RpcError { + fn from(item: &protowire::RpcError) -> Self { + rpc_core::RpcError::from(item.message.to_string()) + } +} diff --git a/rpc/grpc/src/convert/header.rs b/rpc/grpc/src/convert/header.rs new file mode 100644 index 000000000..4fcddbfaf --- /dev/null +++ b/rpc/grpc/src/convert/header.rs @@ -0,0 +1,73 @@ +use crate::protowire; +use rpc_core::{RpcError, RpcHash, RpcResult}; +use std::str::FromStr; + +// ---------------------------------------------------------------------------- +// rpc_core to protowire +// ---------------------------------------------------------------------------- + +impl From<&rpc_core::RpcBlockHeader> for protowire::RpcBlockHeader { + fn from(item: &rpc_core::RpcBlockHeader) -> Self { + Self { + version: item.version, + parents: item.parents.iter().map(protowire::RpcBlockLevelParents::from).collect(), + hash_merkle_root: item.hash_merkle_root.to_string(), + accepted_id_merkle_root: item.accepted_id_merkle_root.to_string(), + utxo_commitment: item.utxo_commitment.to_string(), + timestamp: item.timestamp, + bits: item.bits, + nonce: item.nonce, + daa_score: item.daa_score, + blue_work: item.blue_work.to_string(), + pruning_point: item.pruning_point.to_string(), + blue_score: item.blue_score, + } + } +} + +impl From<&rpc_core::RpcBlockLevelParents> for protowire::RpcBlockLevelParents { + fn from(item: &rpc_core::RpcBlockLevelParents) -> Self { + Self { parent_hashes: item.parent_hashes.iter().map(|x| x.to_string()).collect() } + } +} + +// ---------------------------------------------------------------------------- +// protowire to rpc_core +// ---------------------------------------------------------------------------- + +impl TryFrom<&protowire::RpcBlockHeader> for rpc_core::RpcBlockHeader { + type Error = RpcError; + fn try_from(item: &protowire::RpcBlockHeader) -> RpcResult { + Ok(Self { + version: item.version, + parents: item + .parents + .iter() + .map(rpc_core::RpcBlockLevelParents::try_from) + .collect::>>()?, + hash_merkle_root: RpcHash::from_str(&item.hash_merkle_root)?, + accepted_id_merkle_root: RpcHash::from_str(&item.accepted_id_merkle_root)?, + utxo_commitment: RpcHash::from_str(&item.utxo_commitment)?, + timestamp: item.timestamp, + bits: item.bits, + nonce: item.nonce, + daa_score: item.daa_score, + blue_work: rpc_core::RpcBlueWorkType::from_str(&item.blue_work)?, + pruning_point: RpcHash::from_str(&item.pruning_point)?, + blue_score: item.blue_score, + }) + } +} + +impl TryFrom<&protowire::RpcBlockLevelParents> for rpc_core::RpcBlockLevelParents { + type Error = RpcError; + fn try_from(item: &protowire::RpcBlockLevelParents) -> RpcResult { + Ok(Self { + parent_hashes: item + .parent_hashes + .iter() + .map(|x| RpcHash::from_str(x)) + .collect::, faster_hex::Error>>()?, + }) + } +} diff --git a/rpc/grpc/src/convert/kaspad.rs b/rpc/grpc/src/convert/kaspad.rs new file mode 100644 index 000000000..37cc3e9d6 --- /dev/null +++ b/rpc/grpc/src/convert/kaspad.rs @@ -0,0 +1,258 @@ +use crate::protowire::{kaspad_request, kaspad_response, KaspadRequest, KaspadResponse}; +use rpc_core::api::ops::RpcApiOps; + +impl From<&kaspad_request::Payload> for RpcApiOps { + fn from(item: &kaspad_request::Payload) -> Self { + match item { + kaspad_request::Payload::GetCurrentNetworkRequest(_) => RpcApiOps::GetCurrentNetwork, + kaspad_request::Payload::GetBlockRequest(_) => RpcApiOps::GetBlock, + kaspad_request::Payload::GetInfoRequest(_) => RpcApiOps::GetInfo, + + // Subscription commands for starting/stopping notifications + kaspad_request::Payload::NotifyBlockAddedRequest(_) => RpcApiOps::NotifyBlockAdded, + } + } +} + +impl From<&kaspad_response::Payload> for RpcApiOps { + fn from(item: &kaspad_response::Payload) -> Self { + match item { + kaspad_response::Payload::GetCurrentNetworkResponse(_) => RpcApiOps::GetCurrentNetwork, + kaspad_response::Payload::GetBlockResponse(_) => RpcApiOps::GetBlock, + kaspad_response::Payload::GetInfoResponse(_) => RpcApiOps::GetInfo, + + // Subscription commands for starting/stopping notifications + kaspad_response::Payload::NotifyBlockAddedResponse(_) => RpcApiOps::NotifyBlockAdded, + + // Notifications + kaspad_response::Payload::BlockAddedNotification(_) => RpcApiOps::Notification, + } + } +} + +impl From for KaspadRequest { + fn from(item: kaspad_request::Payload) -> Self { + KaspadRequest { payload: Some(item) } + } +} + +impl AsRef for KaspadRequest { + fn as_ref(&self) -> &Self { + self + } +} + +impl AsRef for KaspadResponse { + fn as_ref(&self) -> &Self { + self + } +} + +pub mod kaspad_request_convert { + use crate::protowire::*; + use rpc_core::{RpcError, RpcResult}; + + impl_into_kaspad_request!(rpc_core::GetBlockRequest, GetBlockRequestMessage, GetBlockRequest); + impl_into_kaspad_request!(rpc_core::NotifyBlockAddedRequest, NotifyBlockAddedRequestMessage, NotifyBlockAddedRequest); + impl_into_kaspad_request!(rpc_core::GetInfoRequest, GetInfoRequestMessage, GetInfoRequest); + + macro_rules! impl_into_kaspad_request { + ($($core_struct:ident)::+, $($protowire_struct:ident)::+, $($variant:ident)::+) => { + + // ---------------------------------------------------------------------------- + // rpc_core to protowire + // ---------------------------------------------------------------------------- + + impl From<&$($core_struct)::+> for kaspad_request::Payload { + fn from(item: &$($core_struct)::+) -> Self { + Self::$($variant)::+(item.into()) + } + } + + impl From<&$($core_struct)::+> for KaspadRequest { + fn from(item: &$($core_struct)::+) -> Self { + Self { + payload: Some(item.into()) + } + } + } + + impl From<$($core_struct)::+> for kaspad_request::Payload { + fn from(item: $($core_struct)::+) -> Self { + Self::$($variant)::+((&item).into()) + } + } + + impl From<$($core_struct)::+> for KaspadRequest { + fn from(item: $($core_struct)::+) -> Self { + Self { + payload: Some((&item).into()) + } + } + } + + // ---------------------------------------------------------------------------- + // protowire to rpc_core + // ---------------------------------------------------------------------------- + + impl TryFrom<&kaspad_request::Payload> for $($core_struct)::+ { + type Error = RpcError; + fn try_from(item: &kaspad_request::Payload) -> RpcResult { + if let kaspad_request::Payload::$($variant)::+(request) = item { + request.try_into() + } else { + Err(RpcError::MissingRpcFieldError("Payload".to_string(), stringify!($($variant)::+).to_string())) + } + } + } + + impl TryFrom<&KaspadRequest> for $($core_struct)::+ { + type Error = RpcError; + fn try_from(item: &KaspadRequest) -> RpcResult { + item.payload + .as_ref() + .ok_or(RpcError::MissingRpcFieldError("KaspaRequest".to_string(), "Payload".to_string()))? + .try_into() + } + } + + impl From<$($protowire_struct)::+> for KaspadRequest { + fn from(item: $($protowire_struct)::+) -> Self { + Self { payload: Some(kaspad_request::Payload::$($variant)::+(item)) } + } + } + + impl From<$($protowire_struct)::+> for kaspad_request::Payload { + fn from(item: $($protowire_struct)::+) -> Self { + kaspad_request::Payload::$($variant)::+(item) + } + } + + }; + } + use impl_into_kaspad_request; +} + +pub mod kaspad_response_convert { + use crate::protowire::*; + use rpc_core::{RpcError, RpcResult}; + + impl_into_kaspad_response!(rpc_core::GetBlockResponse, GetBlockResponseMessage, GetBlockResponse); + impl_into_kaspad_response!(rpc_core::GetInfoResponse, GetInfoResponseMessage, GetInfoResponse); + + impl_into_kaspad_response!(rpc_core::NotifyBlockAddedResponse, NotifyBlockAddedResponseMessage, NotifyBlockAddedResponse); + impl_into_kaspad_notify_response!(rpc_core::NotifyBlockAddedResponse, NotifyBlockAddedResponseMessage, NotifyBlockAddedResponse); + + macro_rules! impl_into_kaspad_response { + ($($core_struct:ident)::+, $($protowire_struct:ident)::+, $($variant:ident)::+) => { + + // ---------------------------------------------------------------------------- + // rpc_core to protowire + // ---------------------------------------------------------------------------- + + impl From> for kaspad_response::Payload { + fn from(item: RpcResult<&$($core_struct)::+>) -> Self { + kaspad_response::Payload::$($variant)::+(item.into()) + } + } + + impl From> for KaspadResponse { + fn from(item: RpcResult<&$($core_struct)::+>) -> Self { + Self { + payload: Some(item.into()) + } + } + } + + impl From> for kaspad_response::Payload { + fn from(item: RpcResult<$($core_struct)::+>) -> Self { + kaspad_response::Payload::$($variant)::+(item.into()) + } + } + + impl From> for KaspadResponse { + fn from(item: RpcResult<$($core_struct)::+>) -> Self { + Self { + payload: Some(item.into()) + } + } + } + + impl From> for $($protowire_struct)::+ { + fn from(item: RpcResult<$($core_struct)::+>) -> Self { + item.as_ref().map_err(|x| (*x).clone()).into() + } + } + + impl From for $($protowire_struct)::+ { + fn from(item: RpcError) -> Self { + let x: RpcResult<&$($core_struct)::+> = Err(item); + x.into() + } + } + + impl From<$($protowire_struct)::+> for kaspad_response::Payload { + fn from(item: $($protowire_struct)::+) -> Self { + kaspad_response::Payload::$($variant)::+(item) + } + } + + impl From<$($protowire_struct)::+> for KaspadResponse { + fn from(item: $($protowire_struct)::+) -> Self { + Self { + payload: Some(kaspad_response::Payload::$($variant)::+(item)) + } + } + } + + // ---------------------------------------------------------------------------- + // protowire to rpc_core + // ---------------------------------------------------------------------------- + + impl TryFrom<&kaspad_response::Payload> for $($core_struct)::+ { + type Error = RpcError; + fn try_from(item: &kaspad_response::Payload) -> RpcResult { + if let kaspad_response::Payload::$($variant)::+(response) = item { + response.try_into() + } else { + Err(RpcError::MissingRpcFieldError("Payload".to_string(), stringify!($($variant)::+).to_string())) + } + } + } + + impl TryFrom<&KaspadResponse> for $($core_struct)::+ { + type Error = RpcError; + fn try_from(item: &KaspadResponse) -> RpcResult { + item.payload + .as_ref() + .ok_or(RpcError::MissingRpcFieldError("KaspaResponse".to_string(), "Payload".to_string()))? + .try_into() + } + } + + }; + } + use impl_into_kaspad_response; + + macro_rules! impl_into_kaspad_notify_response { + ($($core_struct:ident)::+, $($protowire_struct:ident)::+, $($variant:ident)::+) => { + + // ---------------------------------------------------------------------------- + // rpc_core to protowire + // ---------------------------------------------------------------------------- + + impl From> for $($protowire_struct)::+ + where + T: Into, + { + fn from(item: Result<(), T>) -> Self { + item + .map(|_| $($core_struct)::+{}) + .map_err(|err| err.into()).into() + } + } + + }; + } + use impl_into_kaspad_notify_response; +} diff --git a/rpc/grpc/src/convert/message.rs b/rpc/grpc/src/convert/message.rs new file mode 100644 index 000000000..123c5dbf7 --- /dev/null +++ b/rpc/grpc/src/convert/message.rs @@ -0,0 +1,134 @@ +use crate::protowire; +use rpc_core::{RpcError, RpcHash, RpcResult}; +use std::str::FromStr; + +// ---------------------------------------------------------------------------- +// rpc_core to protowire +// ---------------------------------------------------------------------------- + +impl From<&rpc_core::GetBlockRequest> for protowire::GetBlockRequestMessage { + fn from(item: &rpc_core::GetBlockRequest) -> Self { + Self { hash: item.hash.to_string(), include_transactions: item.include_transactions } + } +} + +impl From> for protowire::GetBlockResponseMessage { + fn from(item: RpcResult<&rpc_core::GetBlockResponse>) -> Self { + Self { + block: item.as_ref().map(|x| protowire::RpcBlock::from(&x.block)).ok(), + error: item.map_err(protowire::RpcError::from).err(), + } + } +} + +impl From<&rpc_core::NotifyBlockAddedRequest> for protowire::NotifyBlockAddedRequestMessage { + fn from(item: &rpc_core::NotifyBlockAddedRequest) -> Self { + Self { command: item.command.into() } + } +} + +impl From> for protowire::NotifyBlockAddedResponseMessage { + fn from(item: RpcResult<&rpc_core::NotifyBlockAddedResponse>) -> Self { + Self { error: item.map_err(protowire::RpcError::from).err() } + } +} + +impl From<&rpc_core::GetInfoRequest> for protowire::GetInfoRequestMessage { + fn from(_item: &rpc_core::GetInfoRequest) -> Self { + Self {} + } +} + +impl From> for protowire::GetInfoResponseMessage { + fn from(item: RpcResult<&rpc_core::GetInfoResponse>) -> Self { + match item { + Ok(response) => Self { + p2p_id: response.p2p_id.clone(), + mempool_size: response.mempool_size, + server_version: response.server_version.clone(), + is_utxo_indexed: response.is_utxo_indexed, + is_synced: response.is_synced, + has_notify_command: response.has_notify_command, + error: None, + }, + Err(err) => Self { + p2p_id: String::default(), + mempool_size: 0, + server_version: String::default(), + is_utxo_indexed: false, + is_synced: false, + has_notify_command: false, + error: Some(err.into()), + }, + } + } +} + +// ---------------------------------------------------------------------------- +// protowire to rpc_core +// ---------------------------------------------------------------------------- + +impl TryFrom<&protowire::GetBlockRequestMessage> for rpc_core::GetBlockRequest { + type Error = RpcError; + fn try_from(item: &protowire::GetBlockRequestMessage) -> RpcResult { + Ok(Self { hash: RpcHash::from_str(&item.hash)?, include_transactions: item.include_transactions }) + } +} + +impl TryFrom<&protowire::GetBlockResponseMessage> for rpc_core::GetBlockResponse { + type Error = RpcError; + fn try_from(item: &protowire::GetBlockResponseMessage) -> RpcResult { + item.block + .as_ref() + .map_or_else( + || { + item.error + .as_ref() + .map_or(Err(RpcError::MissingRpcFieldError("GetBlockResponseMessage".to_string(), "error".to_string())), |x| { + Err(x.into()) + }) + }, + rpc_core::RpcBlock::try_from, + ) + .map(|x| rpc_core::GetBlockResponse { block: x }) + } +} + +impl TryFrom<&protowire::NotifyBlockAddedRequestMessage> for rpc_core::NotifyBlockAddedRequest { + type Error = RpcError; + fn try_from(item: &protowire::NotifyBlockAddedRequestMessage) -> RpcResult { + Ok(Self { command: item.command.into() }) + } +} + +impl TryFrom<&protowire::NotifyBlockAddedResponseMessage> for rpc_core::NotifyBlockAddedResponse { + type Error = RpcError; + fn try_from(item: &protowire::NotifyBlockAddedResponseMessage) -> RpcResult { + item.error.as_ref().map_or(Ok(rpc_core::NotifyBlockAddedResponse {}), |x| Err(x.into())) + } +} + +impl TryFrom<&protowire::GetInfoRequestMessage> for rpc_core::GetInfoRequest { + type Error = RpcError; + fn try_from(_item: &protowire::GetInfoRequestMessage) -> RpcResult { + Ok(Self {}) + } +} + +impl TryFrom<&protowire::GetInfoResponseMessage> for rpc_core::GetInfoResponse { + type Error = RpcError; + fn try_from(item: &protowire::GetInfoResponseMessage) -> RpcResult { + if let Some(err) = item.error.as_ref() { + Err(err.into()) + } else { + Ok(Self { + p2p_id: item.p2p_id.clone(), + mempool_size: item.mempool_size, + server_version: item.server_version.clone(), + is_utxo_indexed: item.is_utxo_indexed, + is_synced: item.is_synced, + has_notify_command: item.has_notify_command, + }) + } + } +} diff --git a/rpc/grpc/src/convert/mod.rs b/rpc/grpc/src/convert/mod.rs new file mode 100644 index 000000000..3cb553593 --- /dev/null +++ b/rpc/grpc/src/convert/mod.rs @@ -0,0 +1,7 @@ +pub mod block; +pub mod error; +pub mod header; +pub mod kaspad; +pub mod message; +pub mod notification; +pub mod tx; diff --git a/rpc/grpc/src/convert/notification.rs b/rpc/grpc/src/convert/notification.rs new file mode 100644 index 000000000..cd87004c7 --- /dev/null +++ b/rpc/grpc/src/convert/notification.rs @@ -0,0 +1,88 @@ +use rpc_core::{Notification, RpcError, RpcResult}; + +use crate::protowire::{kaspad_response::Payload, BlockAddedNotificationMessage, KaspadResponse, RpcNotifyCommand}; + +// ---------------------------------------------------------------------------- +// rpc_core to protowire +// ---------------------------------------------------------------------------- + +impl From<&rpc_core::Notification> for KaspadResponse { + fn from(item: &rpc_core::Notification) -> Self { + Self { payload: Some(item.into()) } + } +} + +impl From<&rpc_core::Notification> for Payload { + fn from(item: &rpc_core::Notification) -> Self { + match item { + Notification::BlockAdded(ref notif) => Payload::BlockAddedNotification(notif.into()), + Notification::VirtualSelectedParentChainChanged(_) => todo!(), + Notification::FinalityConflict(_) => todo!(), + Notification::FinalityConflictResolved(_) => todo!(), + Notification::UtxosChanged(_) => todo!(), + Notification::VirtualSelectedParentBlueScoreChanged(_) => todo!(), + Notification::VirtualDaaScoreChanged(_) => todo!(), + Notification::PruningPointUTXOSetOverride(_) => todo!(), + Notification::NewBlockTemplate(_) => todo!(), + } + } +} + +impl From<&rpc_core::BlockAddedNotification> for BlockAddedNotificationMessage { + fn from(item: &rpc_core::BlockAddedNotification) -> Self { + Self { block: Some((&item.block).into()) } + } +} + +impl From for RpcNotifyCommand { + fn from(item: rpc_core::api::ops::SubscribeCommand) -> Self { + match item { + rpc_core::api::ops::SubscribeCommand::Start => RpcNotifyCommand::NotifyStart, + rpc_core::api::ops::SubscribeCommand::Stop => RpcNotifyCommand::NotifyStop, + } + } +} + +// ---------------------------------------------------------------------------- +// protowire to rpc_core +// ---------------------------------------------------------------------------- + +impl TryFrom<&KaspadResponse> for rpc_core::Notification { + type Error = RpcError; + fn try_from(item: &KaspadResponse) -> Result { + match item.payload { + Some(ref payload) => Ok(payload.try_into()?), + None => Err(RpcError::MissingRpcFieldError("KaspadResponse".to_string(), "payload".to_string())), + } + } +} + +impl TryFrom<&Payload> for rpc_core::Notification { + type Error = RpcError; + fn try_from(item: &Payload) -> Result { + match item { + Payload::BlockAddedNotification(ref notif) => Ok(Notification::BlockAdded(notif.try_into()?)), + _ => Err(RpcError::NotImplemented), + } + } +} + +impl TryFrom<&BlockAddedNotificationMessage> for rpc_core::BlockAddedNotification { + type Error = RpcError; + fn try_from(item: &BlockAddedNotificationMessage) -> RpcResult { + if let Some(ref block) = item.block { + Ok(Self { block: block.try_into()? }) + } else { + Err(RpcError::MissingRpcFieldError("BlockAddedNotificationMessage".to_string(), "block".to_string())) + } + } +} + +impl From for rpc_core::api::ops::SubscribeCommand { + fn from(item: RpcNotifyCommand) -> Self { + match item { + RpcNotifyCommand::NotifyStart => rpc_core::api::ops::SubscribeCommand::Start, + RpcNotifyCommand::NotifyStop => rpc_core::api::ops::SubscribeCommand::Stop, + } + } +} diff --git a/rpc/grpc/src/convert/tx.rs b/rpc/grpc/src/convert/tx.rs new file mode 100644 index 000000000..c9c80c33b --- /dev/null +++ b/rpc/grpc/src/convert/tx.rs @@ -0,0 +1,222 @@ +use crate::protowire; +use rpc_core::{RpcError, RpcHash, RpcHexData, RpcResult}; +use std::str::FromStr; + +// ---------------------------------------------------------------------------- +// rpc_core to protowire +// ---------------------------------------------------------------------------- + +impl From<&rpc_core::RpcTransaction> for protowire::RpcTransaction { + fn from(item: &rpc_core::RpcTransaction) -> Self { + Self { + version: item.version, + inputs: item.inputs.iter().map(protowire::RpcTransactionInput::from).collect(), + outputs: item.outputs.iter().map(protowire::RpcTransactionOutput::from).collect(), + lock_time: item.lock_time, + subnetwork_id: item.subnetwork_id.to_string(), + gas: item.gas, + payload: item.payload.to_string(), + verbose_data: Some((&item.verbose_data).into()), + } + } +} + +impl From<&rpc_core::RpcTransactionInput> for protowire::RpcTransactionInput { + fn from(item: &rpc_core::RpcTransactionInput) -> Self { + Self { + previous_outpoint: Some((&item.previous_outpoint).into()), + signature_script: item.signature_script.to_string(), + sequence: item.sequence, + sig_op_count: item.sig_op_count, + verbose_data: item.verbose_data.as_ref().map(|x| x.into()), + } + } +} + +impl From<&rpc_core::RpcTransactionOutput> for protowire::RpcTransactionOutput { + fn from(item: &rpc_core::RpcTransactionOutput) -> Self { + Self { + amount: item.amount, + script_public_key: Some((&item.script_public_key).into()), + verbose_data: Some((&item.verbose_data).into()), + } + } +} + +impl From<&rpc_core::RpcOutpoint> for protowire::RpcOutpoint { + fn from(item: &rpc_core::RpcOutpoint) -> Self { + Self { transaction_id: item.transaction_id.to_string(), index: item.index } + } +} + +impl From<&rpc_core::RpcUtxoEntry> for protowire::RpcUtxoEntry { + fn from(item: &rpc_core::RpcUtxoEntry) -> Self { + Self { + amount: item.amount, + script_public_key: Some((&item.script_public_key).into()), + block_daa_score: item.block_daa_score, + is_coinbase: item.is_coinbase, + } + } +} + +impl From<&rpc_core::RpcScriptPublicKey> for protowire::RpcScriptPublicKey { + fn from(item: &rpc_core::RpcScriptPublicKey) -> Self { + Self { version: item.version.into(), script_public_key: item.script_public_key.to_string() } + } +} + +impl From<&rpc_core::RpcTransactionVerboseData> for protowire::RpcTransactionVerboseData { + fn from(item: &rpc_core::RpcTransactionVerboseData) -> Self { + Self { + transaction_id: item.transaction_id.to_string(), + hash: item.hash.to_string(), + mass: item.mass, + block_hash: item.block_hash.to_string(), + block_time: item.block_time, + } + } +} + +impl From<&rpc_core::RpcTransactionInputVerboseData> for protowire::RpcTransactionInputVerboseData { + fn from(_item: &rpc_core::RpcTransactionInputVerboseData) -> Self { + Self {} + } +} + +impl From<&rpc_core::RpcTransactionOutputVerboseData> for protowire::RpcTransactionOutputVerboseData { + fn from(item: &rpc_core::RpcTransactionOutputVerboseData) -> Self { + Self { + script_public_key_type: item.script_public_key_type.to_string(), + script_public_key_address: item.script_public_key_address.clone(), + } + } +} + +// ---------------------------------------------------------------------------- +// protowire to rpc_core +// ---------------------------------------------------------------------------- + +impl TryFrom<&protowire::RpcTransaction> for rpc_core::RpcTransaction { + type Error = RpcError; + fn try_from(item: &protowire::RpcTransaction) -> RpcResult { + Ok(Self { + version: item.version, + inputs: item + .inputs + .iter() + .map(rpc_core::RpcTransactionInput::try_from) + .collect::>>()?, + outputs: item + .outputs + .iter() + .map(rpc_core::RpcTransactionOutput::try_from) + .collect::>>()?, + lock_time: item.lock_time, + subnetwork_id: rpc_core::RpcSubnetworkId::from_str(&item.subnetwork_id)?, + gas: item.gas, + payload: RpcHexData::from_str(&item.payload)?, + verbose_data: item + .verbose_data + .as_ref() + .ok_or_else(|| RpcError::MissingRpcFieldError("RpcTransaction".to_string(), "verbose_data".to_string()))? + .try_into()?, + }) + } +} + +impl TryFrom<&protowire::RpcTransactionInput> for rpc_core::RpcTransactionInput { + type Error = RpcError; + fn try_from(item: &protowire::RpcTransactionInput) -> RpcResult { + Ok(Self { + previous_outpoint: item + .previous_outpoint + .as_ref() + .ok_or_else(|| RpcError::MissingRpcFieldError("RpcTransactionInput".to_string(), "previous_outpoint".to_string()))? + .try_into()?, + signature_script: RpcHexData::from_str(&item.signature_script)?, + sequence: item.sequence, + sig_op_count: item.sig_op_count, + verbose_data: item.verbose_data.as_ref().map(rpc_core::RpcTransactionInputVerboseData::try_from).transpose()?, + }) + } +} + +impl TryFrom<&protowire::RpcTransactionOutput> for rpc_core::RpcTransactionOutput { + type Error = RpcError; + fn try_from(item: &protowire::RpcTransactionOutput) -> RpcResult { + Ok(Self { + amount: item.amount, + script_public_key: item + .script_public_key + .as_ref() + .ok_or_else(|| RpcError::MissingRpcFieldError("RpcTransactionOutput".to_string(), "script_public_key".to_string()))? + .try_into()?, + verbose_data: item + .verbose_data + .as_ref() + .ok_or_else(|| RpcError::MissingRpcFieldError("RpcTransactionOutput".to_string(), "verbose_data".to_string()))? + .try_into()?, + }) + } +} + +impl TryFrom<&protowire::RpcOutpoint> for rpc_core::RpcOutpoint { + type Error = RpcError; + fn try_from(item: &protowire::RpcOutpoint) -> RpcResult { + Ok(Self { transaction_id: RpcHash::from_str(&item.transaction_id)?, index: item.index }) + } +} + +impl TryFrom<&protowire::RpcUtxoEntry> for rpc_core::RpcUtxoEntry { + type Error = RpcError; + fn try_from(item: &protowire::RpcUtxoEntry) -> RpcResult { + Ok(Self { + amount: item.amount, + script_public_key: item + .script_public_key + .as_ref() + .ok_or_else(|| RpcError::MissingRpcFieldError("RpcTransactionOutput".to_string(), "script_public_key".to_string()))? + .try_into()?, + block_daa_score: item.block_daa_score, + is_coinbase: item.is_coinbase, + }) + } +} + +impl TryFrom<&protowire::RpcScriptPublicKey> for rpc_core::RpcScriptPublicKey { + type Error = RpcError; + fn try_from(item: &protowire::RpcScriptPublicKey) -> RpcResult { + Ok(Self { version: u16::try_from(item.version)?, script_public_key: item.script_public_key.as_str().try_into()? }) + } +} + +impl TryFrom<&protowire::RpcTransactionVerboseData> for rpc_core::RpcTransactionVerboseData { + type Error = RpcError; + fn try_from(item: &protowire::RpcTransactionVerboseData) -> RpcResult { + Ok(Self { + transaction_id: RpcHash::from_str(&item.transaction_id)?, + hash: RpcHash::from_str(&item.hash)?, + mass: item.mass, + block_hash: RpcHash::from_str(&item.block_hash)?, + block_time: item.block_time, + }) + } +} + +impl TryFrom<&protowire::RpcTransactionInputVerboseData> for rpc_core::RpcTransactionInputVerboseData { + type Error = RpcError; + fn try_from(_item: &protowire::RpcTransactionInputVerboseData) -> RpcResult { + Ok(Self {}) + } +} + +impl TryFrom<&protowire::RpcTransactionOutputVerboseData> for rpc_core::RpcTransactionOutputVerboseData { + type Error = RpcError; + fn try_from(item: &protowire::RpcTransactionOutputVerboseData) -> RpcResult { + Ok(Self { + script_public_key_type: item.script_public_key_type.as_str().try_into()?, + script_public_key_address: item.script_public_key_address.clone(), + }) + } +} diff --git a/rpc/grpc/src/ext/kaspad.rs b/rpc/grpc/src/ext/kaspad.rs new file mode 100644 index 000000000..a1f6b98c9 --- /dev/null +++ b/rpc/grpc/src/ext/kaspad.rs @@ -0,0 +1,51 @@ +use rpc_core::{api::ops::SubscribeCommand, NotificationType}; + +use crate::protowire::{kaspad_request, kaspad_response, KaspadRequest, KaspadResponse, NotifyBlockAddedRequestMessage}; + +impl KaspadRequest { + pub fn from_notification_type(notification_type: &NotificationType, command: SubscribeCommand) -> Self { + KaspadRequest { payload: Some(kaspad_request::Payload::from_notification_type(notification_type, command)) } + } +} + +impl kaspad_request::Payload { + pub fn from_notification_type(notification_type: &NotificationType, command: SubscribeCommand) -> Self { + match notification_type { + NotificationType::BlockAdded => { + kaspad_request::Payload::NotifyBlockAddedRequest(NotifyBlockAddedRequestMessage { command: command.into() }) + } + + // TODO: implement all other notifications + _ => { + kaspad_request::Payload::NotifyBlockAddedRequest(NotifyBlockAddedRequestMessage { command: command.into() }) + } + // NotificationType::VirtualSelectedParentChainChanged => todo!(), + // NotificationType::FinalityConflicts => todo!(), + // NotificationType::FinalityConflictResolved => todo!(), + // NotificationType::UtxosChanged(_) => todo!(), + // NotificationType::VirtualSelectedParentBlueScoreChanged => todo!(), + // NotificationType::VirtualDaaScoreChanged => todo!(), + // NotificationType::PruningPointUTXOSetOverride => todo!(), + // NotificationType::NewBlockTemplate => todo!(), + } + } +} + +impl KaspadResponse { + pub fn is_notification(&self) -> bool { + match self.payload { + Some(ref payload) => payload.is_notification(), + None => false, + } + } +} + +#[allow(clippy::match_like_matches_macro)] +impl kaspad_response::Payload { + pub fn is_notification(&self) -> bool { + match self { + kaspad_response::Payload::BlockAddedNotification(_) => true, + _ => false, + } + } +} diff --git a/rpc/grpc/src/ext/mod.rs b/rpc/grpc/src/ext/mod.rs new file mode 100644 index 000000000..6565c3ced --- /dev/null +++ b/rpc/grpc/src/ext/mod.rs @@ -0,0 +1 @@ +pub mod kaspad; diff --git a/rpc/grpc/src/lib.rs b/rpc/grpc/src/lib.rs new file mode 100644 index 000000000..613e78f4f --- /dev/null +++ b/rpc/grpc/src/lib.rs @@ -0,0 +1,10 @@ +#[allow(clippy::derive_partial_eq_without_eq)] +pub mod protowire { + tonic::include_proto!("protowire"); +} + +pub mod client; +pub mod server; + +pub mod convert; +pub mod ext; diff --git a/rpc/grpc/src/server/connection.rs b/rpc/grpc/src/server/connection.rs new file mode 100644 index 000000000..987064e8f --- /dev/null +++ b/rpc/grpc/src/server/connection.rs @@ -0,0 +1,128 @@ +use crate::{protowire::KaspadResponse, server::StatusResult}; +use futures::pin_mut; +use kaspa_core::trace; +use kaspa_utils::triggers::DuplexTrigger; +use rpc_core::notify::{ + listener::{ListenerID, ListenerReceiverSide}, + notifier::Notifier, +}; +use std::{ + collections::HashMap, + net::SocketAddr, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; +use tokio::sync::mpsc; + +pub type GrpcSender = mpsc::Sender>; + +pub(crate) struct GrpcConnection { + address: SocketAddr, + sender: GrpcSender, + notify_listener: ListenerReceiverSide, + collect_shutdown: Arc, + collect_is_running: Arc, +} + +impl GrpcConnection { + pub(crate) fn new(address: SocketAddr, sender: GrpcSender, notify_listener: ListenerReceiverSide) -> Self { + Self { + address, + sender, + notify_listener, + collect_shutdown: Arc::new(DuplexTrigger::new()), + collect_is_running: Arc::new(AtomicBool::new(false)), + } + } + + pub(crate) fn start(self: Arc) { + self.spawn_collecting_task(); + } + + async fn stop(self: Arc) { + self.stop_collect().await + } + + fn spawn_collecting_task(&self) { + let listener_id = self.notify_listener.id; + let sender = self.sender.clone(); + let collect_shutdown = self.collect_shutdown.clone(); + let collect_is_running = self.collect_is_running.clone(); + let recv_channel = self.notify_listener.recv_channel.clone(); + collect_is_running.store(true, Ordering::SeqCst); + + tokio::task::spawn(async move { + trace!("[GrpcConnection] collect_task listener id {0}: start", listener_id); + loop { + let shutdown = collect_shutdown.request.listener.clone(); + pin_mut!(shutdown); + + tokio::select! { + _ = shutdown => { break; } + notification = recv_channel.recv() => { + match notification { + Ok(notification) => { + trace!("[GrpcConnection] collect_task listener id {0}: notification", listener_id); + match sender.send(Ok((&*notification).into())).await { + Ok(_) => (), + Err(err) => { + + // TODO: we need to decide here if we close connection immediately, or wait for TTL to close it + + trace!("[Connection] notification sender error: {:?}", err); + }, + } + }, + Err(err) => { + trace!("[Connection] notification receiver error: {:?}", err); + } + } + } + } + } + collect_is_running.store(false, Ordering::SeqCst); + collect_shutdown.response.trigger.trigger(); + trace!("[GrpcConnection] collect_task listener id {0}: stop", listener_id); + }); + } + + async fn stop_collect(&self) { + if self.collect_is_running.load(Ordering::SeqCst) { + self.collect_shutdown.request.trigger.trigger(); + self.collect_shutdown.response.listener.clone().await; + } + } +} + +pub(crate) struct GrpcConnectionManager { + connections: HashMap>, + notifier: Arc, +} + +impl GrpcConnectionManager { + pub fn new(notifier: Arc) -> Self { + Self { connections: HashMap::new(), notifier } + } + + pub(crate) async fn register(&mut self, address: SocketAddr, sender: GrpcSender) -> ListenerID { + let notify_listener = self.notifier.clone().register_new_listener(None); + let connection = Arc::new(GrpcConnection::new(address, sender, notify_listener)); + trace!("registering a new gRPC connection from: {0} with listener id {1}", connection.address, connection.notify_listener.id); + + // A pre-existing connection with same address is ignored here + // TODO: see if some close pattern can be applied to the replaced connection + self.connections.insert(address, connection.clone()); + connection.clone().start(); + connection.notify_listener.id + } + + pub(crate) async fn unregister(&mut self, address: SocketAddr) { + if let Some(connection) = self.connections.remove(&address) { + trace!("dismiss a gRPC connection from: {}", connection.address); + //connection.sender.closed().await; + connection.stop().await; + } + } +} diff --git a/rpc/grpc/src/server/mod.rs b/rpc/grpc/src/server/mod.rs new file mode 100644 index 000000000..9154fd45e --- /dev/null +++ b/rpc/grpc/src/server/mod.rs @@ -0,0 +1,26 @@ +use crate::protowire::rpc_server::RpcServer; +use kaspa_core::trace; +use rpc_core::server::service::RpcCoreService; +use std::net::SocketAddr; +use std::sync::Arc; +use tokio::task::JoinHandle; +use tonic::codec::CompressionEncoding; +use tonic::transport::{Error, Server}; + +pub mod connection; +pub mod service; + +pub type StatusResult = Result; + +// TODO: use ctrl-c signaling infrastructure of kaspa-core + +pub fn run_server(address: SocketAddr, core_service: Arc) -> JoinHandle> { + trace!("KaspadRPCServer listening on: {}", address); + + let grpc_service = service::RpcService::new(core_service); + grpc_service.start(); + + let svc = RpcServer::new(grpc_service).send_compressed(CompressionEncoding::Gzip).accept_compressed(CompressionEncoding::Gzip); + + tokio::spawn(async move { Server::builder().add_service(svc).serve(address).await }) +} diff --git a/rpc/grpc/src/server/service.rs b/rpc/grpc/src/server/service.rs new file mode 100644 index 000000000..da8b27699 --- /dev/null +++ b/rpc/grpc/src/server/service.rs @@ -0,0 +1,245 @@ +use super::connection::{GrpcConnectionManager, GrpcSender}; +use crate::protowire::{ + kaspad_request::Payload, rpc_server::Rpc, GetBlockResponseMessage, GetInfoResponseMessage, KaspadRequest, KaspadResponse, + NotifyBlockAddedResponseMessage, +}; +use crate::server::StatusResult; +use futures::Stream; +use kaspa_core::trace; +use rpc_core::notify::channel::NotificationChannel; +use rpc_core::notify::listener::{ListenerID, ListenerReceiverSide, ListenerUtxoNotificationFilterSetting}; +use rpc_core::notify::subscriber::DynSubscriptionManager; +use rpc_core::notify::subscriber::Subscriber; +use rpc_core::RpcResult; +use rpc_core::{ + api::rpc::RpcApi, + notify::{collector::RpcCoreCollector, events::EVENT_TYPE_ARRAY, notifier::Notifier}, + server::service::RpcCoreService, +}; +use std::{io::ErrorKind, net::SocketAddr, pin::Pin, sync::Arc}; +use tokio::sync::{mpsc, RwLock}; +use tonic::{Request, Response}; + +/// A protowire RPC service. +/// +/// Relay requests to a central core service that queries the consensus. +/// +/// Registers into a central core service in order to receive consensus notifications and +/// send those forward to the registered clients. +/// +/// +/// ### Implementation notes +/// +/// The service is a listener of the provided core service. The registration happens in the constructor, +/// giving it the lifetime of the overall service. +/// +/// As a corollary, the unregistration should occur just before the object is dropped by calling finalize. +/// +/// #### Lifetime and usage +/// +/// - new -> Self +/// - start +/// - register_connection +/// - unregister_connection +/// - stop +/// - finalize +/// +/// _Object is ready for being dropped. Any further usage of it is undefined behaviour._ +/// +/// #### Further development +/// +/// TODO: implement a queue of requests and a pool of workers preparing and sending back the reponses. +pub struct RpcService { + core_service: Arc, + core_channel: NotificationChannel, + core_listener: Arc, + connection_manager: Arc>, + notifier: Arc, +} + +impl RpcService { + pub fn new(core_service: Arc) -> Self { + // Prepare core objects + let core_channel = NotificationChannel::default(); + let core_listener = Arc::new(core_service.register_new_listener(Some(core_channel.clone()))); + + // Prepare internals + let collector = Arc::new(RpcCoreCollector::new(core_channel.receiver())); + let subscription_manager: DynSubscriptionManager = core_service.notifier(); + let subscriber = Subscriber::new(subscription_manager, core_listener.id); + let notifier = + Arc::new(Notifier::new(Some(collector), Some(subscriber), ListenerUtxoNotificationFilterSetting::FilteredByAddress)); + let connection_manager = Arc::new(RwLock::new(GrpcConnectionManager::new(notifier.clone()))); + + Self { core_service, core_channel, core_listener, connection_manager, notifier } + } + + pub fn start(&self) { + // Start the internal notifier + self.notifier.clone().start(); + } + + pub async fn register_connection(&self, address: SocketAddr, sender: GrpcSender) -> ListenerID { + self.connection_manager.write().await.register(address, sender).await + } + + pub async fn unregister_connection(&self, address: SocketAddr) { + self.connection_manager.write().await.unregister(address).await; + } + + pub async fn stop(&self) -> RpcResult<()> { + // Unsubscribe from all notification types + let listener_id = self.core_listener.id; + for event in EVENT_TYPE_ARRAY.into_iter() { + self.core_service.stop_notify(listener_id, event.into()).await?; + } + + // Stop the internal notifier + self.notifier.clone().stop().await?; + + Ok(()) + } + + // TODO: implement a proper server shutdown actually calling finalize. + pub async fn finalize(&self) -> RpcResult<()> { + self.core_service.unregister_listener(self.core_listener.id).await?; + self.core_channel.receiver().close(); + Ok(()) + } +} + +#[tonic::async_trait] +impl Rpc for RpcService { + type MessageStreamStream = Pin> + Send + Sync + 'static>>; + + async fn message_stream( + &self, + request: Request>, + ) -> Result, tonic::Status> { + let remote_addr = request.remote_addr().ok_or_else(|| { + tonic::Status::new(tonic::Code::InvalidArgument, "Incoming connection opening request has no remote address".to_string()) + })?; + + trace!("MessageStream from {:?}", remote_addr); + + // External sender and reciever + let (send_channel, mut recv_channel) = mpsc::channel::>(128); + let listener_id = self.register_connection(remote_addr, send_channel.clone()).await; + + // Internal related sender and reciever + let (stream_tx, stream_rx) = mpsc::channel::>(10); + + // KaspadResponse forwarder + let connection_manager = self.connection_manager.clone(); + tokio::spawn(async move { + while let Some(msg) = recv_channel.recv().await { + match stream_tx.send(msg).await { + Ok(_) => {} + Err(_) => { + // If sending failed, then remove the connection from connection manager + trace!("[Remote] stream tx sending error. Remote {:?}", &remote_addr); + connection_manager.write().await.unregister(remote_addr).await; + } + } + } + }); + + // Request handler + let core_service = self.core_service.clone(); + let connection_manager = self.connection_manager.clone(); + let notifier = self.notifier.clone(); + let mut stream: tonic::Streaming = request.into_inner(); + tokio::spawn(async move { + loop { + match stream.message().await { + Ok(Some(request)) => { + trace!("Request is {:?}", request); + let response: KaspadResponse = match request.payload { + Some(Payload::GetBlockRequest(ref request)) => match request.try_into() { + Ok(request) => core_service.get_block(request).await.into(), + Err(err) => GetBlockResponseMessage::from(err).into(), + }, + + Some(Payload::GetInfoRequest(ref request)) => match request.try_into() { + Ok(request) => core_service.get_info(request).await.into(), + Err(err) => GetInfoResponseMessage::from(err).into(), + }, + + Some(Payload::NotifyBlockAddedRequest(ref request)) => NotifyBlockAddedResponseMessage::from({ + let request = rpc_core::NotifyBlockAddedRequest::try_from(request).unwrap(); + notifier.clone().execute_subscribe_command( + listener_id, + rpc_core::NotificationType::BlockAdded, + request.command, + ) + }) + .into(), + + // TODO: This must be replaced by actual handling of all request variants + _ => GetBlockResponseMessage::from(rpc_core::RpcError::General( + "Server-side API Not implemented".to_string(), + )) + .into(), + }; + + match send_channel.send(Ok(response)).await { + Ok(_) => {} + Err(err) => { + trace!("tx send error: {:?}", err); + } + } + } + Ok(None) => { + trace!("Request handler stream {0} got Ok(None). Connection terminated by the server", remote_addr); + break; + } + + Err(err) => { + if let Some(io_err) = match_for_io_error(&err) { + if io_err.kind() == ErrorKind::BrokenPipe { + // here you can handle special case when client + // disconnected in unexpected way + eprintln!("\tRequest handler stream {0} error: client disconnected, broken pipe", remote_addr); + break; + } + } + + match send_channel.send(Err(err)).await { + Ok(_) => (), + Err(_err) => break, // response was droped + } + } + } + } + trace!("Request handler {0} terminated", remote_addr); + connection_manager.write().await.unregister(remote_addr).await; + }); + + // Return connection stream + + Ok(Response::new(Box::pin(tokio_stream::wrappers::ReceiverStream::new(stream_rx)))) + } +} + +fn match_for_io_error(err_status: &tonic::Status) -> Option<&std::io::Error> { + let mut err: &(dyn std::error::Error + 'static) = err_status; + + loop { + if let Some(io_err) = err.downcast_ref::() { + return Some(io_err); + } + + // h2::Error do not expose std::io::Error with `source()` + // https://github.com/hyperium/h2/pull/462 + if let Some(h2_err) = err.downcast_ref::() { + if let Some(io_err) = h2_err.get_io() { + return Some(io_err); + } + } + + err = match err.source() { + Some(err) => err, + None => return None, + }; + } +} diff --git a/utils/Cargo.toml b/utils/Cargo.toml index db60b6fe1..8366a22ff 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -7,3 +7,5 @@ include.workspace = true license.workspace = true [dependencies] +async-std.workspace = true +triggered = "0.1" diff --git a/utils/src/channel.rs b/utils/src/channel.rs new file mode 100644 index 000000000..e60dd0981 --- /dev/null +++ b/utils/src/channel.rs @@ -0,0 +1,38 @@ +use async_std::channel::{unbounded, Receiver, Sender}; + +/// Multiple producers multiple consumers channel +#[derive(Clone, Debug)] +pub struct Channel { + sender: Sender, + receiver: Receiver, +} + +impl Channel { + pub fn new(channel: (Sender, Receiver)) -> Channel { + Self { sender: channel.0, receiver: channel.1 } + } + + pub fn sender(&self) -> Sender { + self.sender.clone() + } + + pub fn receiver(&self) -> Receiver { + self.receiver.clone() + } + + pub fn close(&self) { + self.receiver.close(); + } + + pub fn is_closed(&self) -> bool { + self.receiver.is_closed() + } +} + +/// Default for a [`Channel`] is unbounded +impl Default for Channel { + fn default() -> Self { + let ch = unbounded(); + Self { sender: ch.0, receiver: ch.1 } + } +} diff --git a/utils/src/lib.rs b/utils/src/lib.rs index ecd13bde9..bc7952093 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -1,2 +1,4 @@ pub mod arc; +pub mod channel; pub mod option; +pub mod triggers; diff --git a/utils/src/triggers.rs b/utils/src/triggers.rs new file mode 100644 index 000000000..be6e97d2f --- /dev/null +++ b/utils/src/triggers.rs @@ -0,0 +1,35 @@ +pub use triggered::{Listener, Trigger}; + +/// Wrapper containing a single Trigger instance +#[derive(Debug, Clone)] +pub struct SingleTrigger { + pub trigger: Trigger, + pub listener: Listener, +} + +impl SingleTrigger { + pub fn new() -> SingleTrigger { + let (trigger, listener) = triggered::trigger(); + SingleTrigger { trigger, listener } + } +} + +impl Default for SingleTrigger { + fn default() -> Self { + Self::new() + } +} + +/// Bi-directional trigger meant to function in +/// request/response fashion +#[derive(Debug, Clone, Default)] +pub struct DuplexTrigger { + pub request: SingleTrigger, + pub response: SingleTrigger, +} + +impl DuplexTrigger { + pub fn new() -> DuplexTrigger { + DuplexTrigger { request: SingleTrigger::new(), response: SingleTrigger::new() } + } +}