diff --git a/Cargo.lock b/Cargo.lock index 8bc4c86ea7..5023bf4187 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -895,16 +895,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if 1.0.0", - "wasm-bindgen", -] - [[package]] name = "constant_time_eq" version = "0.3.1" @@ -2256,16 +2246,17 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "kaspa-addresses" -version = "0.15.4" +version = "0.16.0" dependencies = [ "borsh", "criterion", @@ -2282,7 +2273,7 @@ dependencies = [ [[package]] name = "kaspa-addressmanager" -version = "0.15.4" +version = "0.16.0" dependencies = [ "borsh", "igd-next", @@ -2304,14 +2295,14 @@ dependencies = [ [[package]] name = "kaspa-alloc" -version = "0.15.4" +version = "0.16.0" dependencies = [ "mimalloc", ] [[package]] name = "kaspa-bip32" -version = "0.15.4" +version = "0.16.0" dependencies = [ "borsh", "bs58", @@ -2338,7 +2329,7 @@ dependencies = [ [[package]] name = "kaspa-cli" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-trait", "borsh", @@ -2385,7 +2376,7 @@ dependencies = [ [[package]] name = "kaspa-connectionmanager" -version = "0.15.4" +version = "0.16.0" dependencies = [ "duration-string", "futures-util", @@ -2402,7 +2393,7 @@ dependencies = [ [[package]] name = "kaspa-consensus" -version = "0.15.4" +version = "0.16.0" dependencies = [ "arc-swap", "async-channel 2.3.1", @@ -2446,7 +2437,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-client" -version = "0.15.4" +version = "0.16.0" dependencies = [ "ahash", "cfg-if 1.0.0", @@ -2474,7 +2465,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-core" -version = "0.15.4" +version = "0.16.0" dependencies = [ "arc-swap", "async-trait", @@ -2513,7 +2504,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-notify" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "cfg-if 1.0.0", @@ -2532,7 +2523,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-wasm" -version = "0.15.4" +version = "0.16.0" dependencies = [ "cfg-if 1.0.0", "faster-hex", @@ -2556,12 +2547,13 @@ dependencies = [ [[package]] name = "kaspa-consensusmanager" -version = "0.15.4" +version = "0.16.0" dependencies = [ "duration-string", "futures", "futures-util", "itertools 0.13.0", + "kaspa-addresses", "kaspa-consensus-core", "kaspa-consensus-notify", "kaspa-core", @@ -2574,7 +2566,7 @@ dependencies = [ [[package]] name = "kaspa-core" -version = "0.15.4" +version = "0.16.0" dependencies = [ "cfg-if 1.0.0", "ctrlc", @@ -2592,7 +2584,7 @@ dependencies = [ [[package]] name = "kaspa-daemon" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-trait", "borsh", @@ -2614,7 +2606,7 @@ dependencies = [ [[package]] name = "kaspa-database" -version = "0.15.4" +version = "0.16.0" dependencies = [ "bincode", "enum-primitive-derive", @@ -2636,7 +2628,7 @@ dependencies = [ [[package]] name = "kaspa-grpc-client" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "async-stream", @@ -2668,7 +2660,7 @@ dependencies = [ [[package]] name = "kaspa-grpc-core" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "async-stream", @@ -2676,6 +2668,7 @@ dependencies = [ "faster-hex", "futures", "h2 0.4.6", + "kaspa-addresses", "kaspa-consensus-core", "kaspa-core", "kaspa-notify", @@ -2697,7 +2690,7 @@ dependencies = [ [[package]] name = "kaspa-grpc-server" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "async-stream", @@ -2733,7 +2726,7 @@ dependencies = [ [[package]] name = "kaspa-hashes" -version = "0.15.4" +version = "0.16.0" dependencies = [ "blake2b_simd", "borsh", @@ -2754,7 +2747,7 @@ dependencies = [ [[package]] name = "kaspa-index-core" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "async-trait", @@ -2773,7 +2766,7 @@ dependencies = [ [[package]] name = "kaspa-index-processor" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "async-trait", @@ -2801,7 +2794,7 @@ dependencies = [ [[package]] name = "kaspa-math" -version = "0.15.4" +version = "0.16.0" dependencies = [ "borsh", "criterion", @@ -2822,14 +2815,14 @@ dependencies = [ [[package]] name = "kaspa-merkle" -version = "0.15.4" +version = "0.16.0" dependencies = [ "kaspa-hashes", ] [[package]] name = "kaspa-metrics-core" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-trait", "borsh", @@ -2845,7 +2838,7 @@ dependencies = [ [[package]] name = "kaspa-mining" -version = "0.15.4" +version = "0.16.0" dependencies = [ "criterion", "futures-util", @@ -2872,7 +2865,7 @@ dependencies = [ [[package]] name = "kaspa-mining-errors" -version = "0.15.4" +version = "0.16.0" dependencies = [ "kaspa-consensus-core", "thiserror", @@ -2880,7 +2873,7 @@ dependencies = [ [[package]] name = "kaspa-muhash" -version = "0.15.4" +version = "0.16.0" dependencies = [ "criterion", "kaspa-hashes", @@ -2893,7 +2886,7 @@ dependencies = [ [[package]] name = "kaspa-notify" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "async-trait", @@ -2929,7 +2922,7 @@ dependencies = [ [[package]] name = "kaspa-p2p-flows" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-trait", "chrono", @@ -2960,7 +2953,7 @@ dependencies = [ [[package]] name = "kaspa-p2p-lib" -version = "0.15.4" +version = "0.16.0" dependencies = [ "borsh", "ctrlc", @@ -2991,7 +2984,7 @@ dependencies = [ [[package]] name = "kaspa-perf-monitor" -version = "0.15.4" +version = "0.16.0" dependencies = [ "kaspa-core", "log", @@ -3003,7 +2996,7 @@ dependencies = [ [[package]] name = "kaspa-pow" -version = "0.15.4" +version = "0.16.0" dependencies = [ "criterion", "js-sys", @@ -3019,7 +3012,7 @@ dependencies = [ [[package]] name = "kaspa-rpc-core" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "async-trait", @@ -3061,7 +3054,7 @@ dependencies = [ [[package]] name = "kaspa-rpc-macros" -version = "0.15.4" +version = "0.16.0" dependencies = [ "convert_case 0.6.0", "proc-macro-error", @@ -3073,7 +3066,7 @@ dependencies = [ [[package]] name = "kaspa-rpc-service" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-trait", "kaspa-addresses", @@ -3102,7 +3095,7 @@ dependencies = [ [[package]] name = "kaspa-testing-integration" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "async-trait", @@ -3162,7 +3155,7 @@ dependencies = [ [[package]] name = "kaspa-txscript" -version = "0.15.4" +version = "0.16.0" dependencies = [ "blake2b_simd", "borsh", @@ -3194,7 +3187,7 @@ dependencies = [ [[package]] name = "kaspa-txscript-errors" -version = "0.15.4" +version = "0.16.0" dependencies = [ "secp256k1", "thiserror", @@ -3202,7 +3195,7 @@ dependencies = [ [[package]] name = "kaspa-utils" -version = "0.15.4" +version = "0.16.0" dependencies = [ "arc-swap", "async-channel 2.3.1", @@ -3238,7 +3231,7 @@ dependencies = [ [[package]] name = "kaspa-utils-tower" -version = "0.15.4" +version = "0.16.0" dependencies = [ "bytes", "cfg-if 1.0.0", @@ -3254,7 +3247,7 @@ dependencies = [ [[package]] name = "kaspa-utxoindex" -version = "0.15.4" +version = "0.16.0" dependencies = [ "futures", "kaspa-consensus", @@ -3275,7 +3268,7 @@ dependencies = [ [[package]] name = "kaspa-wallet" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-std", "async-trait", @@ -3287,7 +3280,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-cli-wasm" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-trait", "js-sys", @@ -3301,7 +3294,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-core" -version = "0.15.4" +version = "0.16.0" dependencies = [ "aes", "ahash", @@ -3382,7 +3375,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-keys" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-trait", "borsh", @@ -3415,7 +3408,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-macros" -version = "0.15.4" +version = "0.16.0" dependencies = [ "convert_case 0.5.0", "proc-macro-error", @@ -3428,7 +3421,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-pskt" -version = "0.15.4" +version = "0.16.0" dependencies = [ "bincode", "derive_builder", @@ -3455,7 +3448,7 @@ dependencies = [ [[package]] name = "kaspa-wasm" -version = "0.15.4" +version = "0.16.0" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -3483,7 +3476,7 @@ dependencies = [ [[package]] name = "kaspa-wasm-core" -version = "0.15.4" +version = "0.16.0" dependencies = [ "faster-hex", "hexplay", @@ -3494,7 +3487,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-client" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-std", "async-trait", @@ -3530,7 +3523,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-example-subscriber" -version = "0.15.4" +version = "0.16.0" dependencies = [ "ctrlc", "futures", @@ -3545,7 +3538,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-proxy" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-trait", "clap 4.5.19", @@ -3564,7 +3557,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-server" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-trait", "borsh", @@ -3592,7 +3585,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-simple-client-example" -version = "0.15.4" +version = "0.16.0" dependencies = [ "futures", "kaspa-rpc-core", @@ -3602,7 +3595,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-wasm" -version = "0.15.4" +version = "0.16.0" dependencies = [ "ahash", "async-std", @@ -3632,7 +3625,7 @@ dependencies = [ [[package]] name = "kaspad" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "cfg-if 1.0.0", @@ -4974,7 +4967,7 @@ dependencies = [ [[package]] name = "rothschild" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "clap 4.5.19", @@ -5123,12 +5116,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -5387,7 +5374,7 @@ dependencies = [ [[package]] name = "simpa" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "cfg-if 1.0.0", @@ -6220,12 +6207,13 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if 1.0.0", "once_cell", + "rustversion", "serde", "serde_json", "wasm-bindgen-macro", @@ -6233,13 +6221,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn 2.0.79", @@ -6248,21 +6235,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if 1.0.0", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6270,9 +6258,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -6283,20 +6271,21 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-bindgen-test" -version = "0.3.43" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68497a05fb21143a08a7d24fc81763384a3072ee43c44e86aad1744d6adef9d9" +checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" dependencies = [ - "console_error_panic_hook", "js-sys", "minicov", - "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", @@ -6304,9 +6293,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.43" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8220be1fa9e4c889b30fd207d4906657e7e90b12e0e6b0c8b8d8709f5de021" +checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", @@ -6315,9 +6304,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 4ff4b234ee..7ab0a2579d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ members = [ [workspace.package] rust-version = "1.82.0" -version = "0.15.4" +version = "0.16.0" authors = ["Kaspa developers"] license = "ISC" repository = "https://github.com/kaspanet/rusty-kaspa" @@ -80,61 +80,61 @@ include = [ ] [workspace.dependencies] -# kaspa-testing-integration = { version = "0.15.4", path = "testing/integration" } -kaspa-addresses = { version = "0.15.4", path = "crypto/addresses" } -kaspa-addressmanager = { version = "0.15.4", path = "components/addressmanager" } -kaspa-bip32 = { version = "0.15.4", path = "wallet/bip32" } -kaspa-cli = { version = "0.15.4", path = "cli" } -kaspa-connectionmanager = { version = "0.15.4", path = "components/connectionmanager" } -kaspa-consensus = { version = "0.15.4", path = "consensus" } -kaspa-consensus-core = { version = "0.15.4", path = "consensus/core" } -kaspa-consensus-client = { version = "0.15.4", path = "consensus/client" } -kaspa-consensus-notify = { version = "0.15.4", path = "consensus/notify" } -kaspa-consensus-wasm = { version = "0.15.4", path = "consensus/wasm" } -kaspa-consensusmanager = { version = "0.15.4", path = "components/consensusmanager" } -kaspa-core = { version = "0.15.4", path = "core" } -kaspa-daemon = { version = "0.15.4", path = "daemon" } -kaspa-database = { version = "0.15.4", path = "database" } -kaspa-grpc-client = { version = "0.15.4", path = "rpc/grpc/client" } -kaspa-grpc-core = { version = "0.15.4", path = "rpc/grpc/core" } -kaspa-grpc-server = { version = "0.15.4", path = "rpc/grpc/server" } -kaspa-hashes = { version = "0.15.4", path = "crypto/hashes" } -kaspa-index-core = { version = "0.15.4", path = "indexes/core" } -kaspa-index-processor = { version = "0.15.4", path = "indexes/processor" } -kaspa-math = { version = "0.15.4", path = "math" } -kaspa-merkle = { version = "0.15.4", path = "crypto/merkle" } -kaspa-metrics-core = { version = "0.15.4", path = "metrics/core" } -kaspa-mining = { version = "0.15.4", path = "mining" } -kaspa-mining-errors = { version = "0.15.4", path = "mining/errors" } -kaspa-muhash = { version = "0.15.4", path = "crypto/muhash" } -kaspa-notify = { version = "0.15.4", path = "notify" } -kaspa-p2p-flows = { version = "0.15.4", path = "protocol/flows" } -kaspa-p2p-lib = { version = "0.15.4", path = "protocol/p2p" } -kaspa-perf-monitor = { version = "0.15.4", path = "metrics/perf_monitor" } -kaspa-pow = { version = "0.15.4", path = "consensus/pow" } -kaspa-rpc-core = { version = "0.15.4", path = "rpc/core" } -kaspa-rpc-macros = { version = "0.15.4", path = "rpc/macros" } -kaspa-rpc-service = { version = "0.15.4", path = "rpc/service" } -kaspa-txscript = { version = "0.15.4", path = "crypto/txscript" } -kaspa-txscript-errors = { version = "0.15.4", path = "crypto/txscript/errors" } -kaspa-utils = { version = "0.15.4", path = "utils" } -kaspa-utils-tower = { version = "0.15.4", path = "utils/tower" } -kaspa-utxoindex = { version = "0.15.4", path = "indexes/utxoindex" } -kaspa-wallet = { version = "0.15.4", path = "wallet/native" } -kaspa-wallet-cli-wasm = { version = "0.15.4", path = "wallet/wasm" } -kaspa-wallet-keys = { version = "0.15.4", path = "wallet/keys" } -kaspa-wallet-pskt = { version = "0.15.4", path = "wallet/pskt" } -kaspa-wallet-core = { version = "0.15.4", path = "wallet/core" } -kaspa-wallet-macros = { version = "0.15.4", path = "wallet/macros" } -kaspa-wasm = { version = "0.15.4", path = "wasm" } -kaspa-wasm-core = { version = "0.15.4", path = "wasm/core" } -kaspa-wrpc-client = { version = "0.15.4", path = "rpc/wrpc/client" } -kaspa-wrpc-proxy = { version = "0.15.4", path = "rpc/wrpc/proxy" } -kaspa-wrpc-server = { version = "0.15.4", path = "rpc/wrpc/server" } -kaspa-wrpc-wasm = { version = "0.15.4", path = "rpc/wrpc/wasm" } -kaspa-wrpc-example-subscriber = { version = "0.15.4", path = "rpc/wrpc/examples/subscriber" } -kaspad = { version = "0.15.4", path = "kaspad" } -kaspa-alloc = { version = "0.15.4", path = "utils/alloc" } +# kaspa-testing-integration = { version = "0.16.0", path = "testing/integration" } +kaspa-addresses = { version = "0.16.0", path = "crypto/addresses" } +kaspa-addressmanager = { version = "0.16.0", path = "components/addressmanager" } +kaspa-bip32 = { version = "0.16.0", path = "wallet/bip32" } +kaspa-cli = { version = "0.16.0", path = "cli" } +kaspa-connectionmanager = { version = "0.16.0", path = "components/connectionmanager" } +kaspa-consensus = { version = "0.16.0", path = "consensus" } +kaspa-consensus-core = { version = "0.16.0", path = "consensus/core" } +kaspa-consensus-client = { version = "0.16.0", path = "consensus/client" } +kaspa-consensus-notify = { version = "0.16.0", path = "consensus/notify" } +kaspa-consensus-wasm = { version = "0.16.0", path = "consensus/wasm" } +kaspa-consensusmanager = { version = "0.16.0", path = "components/consensusmanager" } +kaspa-core = { version = "0.16.0", path = "core" } +kaspa-daemon = { version = "0.16.0", path = "daemon" } +kaspa-database = { version = "0.16.0", path = "database" } +kaspa-grpc-client = { version = "0.16.0", path = "rpc/grpc/client" } +kaspa-grpc-core = { version = "0.16.0", path = "rpc/grpc/core" } +kaspa-grpc-server = { version = "0.16.0", path = "rpc/grpc/server" } +kaspa-hashes = { version = "0.16.0", path = "crypto/hashes" } +kaspa-index-core = { version = "0.16.0", path = "indexes/core" } +kaspa-index-processor = { version = "0.16.0", path = "indexes/processor" } +kaspa-math = { version = "0.16.0", path = "math" } +kaspa-merkle = { version = "0.16.0", path = "crypto/merkle" } +kaspa-metrics-core = { version = "0.16.0", path = "metrics/core" } +kaspa-mining = { version = "0.16.0", path = "mining" } +kaspa-mining-errors = { version = "0.16.0", path = "mining/errors" } +kaspa-muhash = { version = "0.16.0", path = "crypto/muhash" } +kaspa-notify = { version = "0.16.0", path = "notify" } +kaspa-p2p-flows = { version = "0.16.0", path = "protocol/flows" } +kaspa-p2p-lib = { version = "0.16.0", path = "protocol/p2p" } +kaspa-perf-monitor = { version = "0.16.0", path = "metrics/perf_monitor" } +kaspa-pow = { version = "0.16.0", path = "consensus/pow" } +kaspa-rpc-core = { version = "0.16.0", path = "rpc/core" } +kaspa-rpc-macros = { version = "0.16.0", path = "rpc/macros" } +kaspa-rpc-service = { version = "0.16.0", path = "rpc/service" } +kaspa-txscript = { version = "0.16.0", path = "crypto/txscript" } +kaspa-txscript-errors = { version = "0.16.0", path = "crypto/txscript/errors" } +kaspa-utils = { version = "0.16.0", path = "utils" } +kaspa-utils-tower = { version = "0.16.0", path = "utils/tower" } +kaspa-utxoindex = { version = "0.16.0", path = "indexes/utxoindex" } +kaspa-wallet = { version = "0.16.0", path = "wallet/native" } +kaspa-wallet-cli-wasm = { version = "0.16.0", path = "wallet/wasm" } +kaspa-wallet-keys = { version = "0.16.0", path = "wallet/keys" } +kaspa-wallet-pskt = { version = "0.16.0", path = "wallet/pskt" } +kaspa-wallet-core = { version = "0.16.0", path = "wallet/core" } +kaspa-wallet-macros = { version = "0.16.0", path = "wallet/macros" } +kaspa-wasm = { version = "0.16.0", path = "wasm" } +kaspa-wasm-core = { version = "0.16.0", path = "wasm/core" } +kaspa-wrpc-client = { version = "0.16.0", path = "rpc/wrpc/client" } +kaspa-wrpc-proxy = { version = "0.16.0", path = "rpc/wrpc/proxy" } +kaspa-wrpc-server = { version = "0.16.0", path = "rpc/wrpc/server" } +kaspa-wrpc-wasm = { version = "0.16.0", path = "rpc/wrpc/wasm" } +kaspa-wrpc-example-subscriber = { version = "0.16.0", path = "rpc/wrpc/examples/subscriber" } +kaspad = { version = "0.16.0", path = "kaspad" } +kaspa-alloc = { version = "0.16.0", path = "utils/alloc" } # external aes = "0.8.3" @@ -256,9 +256,9 @@ tonic = { version = "0.12.3", features = ["tls-webpki-roots", "gzip", "transport tonic-build = { version = "0.12.3", features = ["prost"] } triggered = "0.1.2" uuid = { version = "1.5.0", features = ["v4", "fast-rng", "serde"] } -wasm-bindgen = { version = "0.2.93", features = ["serde-serialize"] } +wasm-bindgen = { version = "0.2.100", features = ["serde-serialize"] } wasm-bindgen-futures = "0.4.43" -wasm-bindgen-test = "0.3.43" +wasm-bindgen-test = "0.3.50" web-sys = "0.3.70" xxhash-rust = { version = "0.8.7", features = ["xxh3"] } zeroize = { version = "1.6.0", default-features = false, features = ["alloc"] } diff --git a/cli/src/modules/guide.rs b/cli/src/modules/guide.rs index 0419f8d2db..4c9ccac1b3 100644 --- a/cli/src/modules/guide.rs +++ b/cli/src/modules/guide.rs @@ -12,10 +12,10 @@ impl Guide { let mut paras = Vec::::new(); let mut para = String::new(); + let regex = Regex::new(r"\s+").unwrap(); for line in lines { if line.trim().is_empty() { if !para.is_empty() { - let regex = Regex::new(r"\s+").unwrap(); let text = regex.replace_all(para.trim(), " "); paras.push(text.to_string()); para.clear(); diff --git a/cli/src/modules/rpc.rs b/cli/src/modules/rpc.rs index cf6bc6bd20..c721ed6223 100644 --- a/cli/src/modules/rpc.rs +++ b/cli/src/modules/rpc.rs @@ -269,6 +269,21 @@ impl Rpc { let result = rpc.get_current_block_color_call(None, GetCurrentBlockColorRequest { hash }).await?; self.println(&ctx, result); } + RpcApiOps::GetUtxoReturnAddress => { + if argv.is_empty() || argv.len() != 2 { + return Err(Error::custom("Please specify a txid and a accepting_block_daa_score")); + } + + let txid = argv.remove(0); + let txid = RpcHash::from_hex(txid.as_str())?; + + let accepting_block_daa_score = argv.remove(0).parse::()?; + + let result = + rpc.get_utxo_return_address_call(None, GetUtxoReturnAddressRequest { txid, accepting_block_daa_score }).await?; + + self.println(&ctx, result); + } _ => { tprintln!(ctx, "rpc method exists but is not supported by the cli: '{op_str}'\r\n"); return Ok(()); diff --git a/components/addressmanager/src/lib.rs b/components/addressmanager/src/lib.rs index 093323e155..da0f7c1ed1 100644 --- a/components/addressmanager/src/lib.rs +++ b/components/addressmanager/src/lib.rs @@ -596,7 +596,7 @@ mod address_store_with_cache { let target_uniform_dist = Uniform::new(1.0, num_of_buckets as f64).unwrap(); let uniform_cdf = |x: f64| target_uniform_dist.cdf(&x); for _ in 0..num_of_trials { - // The weight sampled expected uniform distibution + // The weight sampled expected uniform distribution let prioritized_address_distribution = am .lock() .iterate_prioritized_random_addresses(HashSet::new()) diff --git a/components/consensusmanager/Cargo.toml b/components/consensusmanager/Cargo.toml index 16f6900871..c9f3645572 100644 --- a/components/consensusmanager/Cargo.toml +++ b/components/consensusmanager/Cargo.toml @@ -14,6 +14,7 @@ duration-string.workspace = true futures-util.workspace = true futures.workspace = true itertools.workspace = true +kaspa-addresses.workspace=true kaspa-consensus-core.workspace = true kaspa-consensus-notify.workspace = true kaspa-core.workspace = true diff --git a/components/consensusmanager/src/lib.rs b/components/consensusmanager/src/lib.rs index 6d31653aab..b1c0b8f61b 100644 --- a/components/consensusmanager/src/lib.rs +++ b/components/consensusmanager/src/lib.rs @@ -213,7 +213,7 @@ impl StagingConsensus { // Drop `prev` so that deletion below succeeds drop(prev); // Staging was committed and is now the active consensus so we can delete - // any pervious, now inactive, consensus entries + // any previous, now inactive, consensus entries self.manager.delete_inactive_consensus_entries(); } diff --git a/components/consensusmanager/src/session.rs b/components/consensusmanager/src/session.rs index 8e0c6e9335..c67caf07d4 100644 --- a/components/consensusmanager/src/session.rs +++ b/components/consensusmanager/src/session.rs @@ -12,7 +12,8 @@ use kaspa_consensus_core::{ header::Header, pruning::{PruningPointProof, PruningPointTrustedData, PruningPointsList}, trusted::{ExternalGhostdagData, TrustedBlock}, - tx::{MutableTransaction, Transaction, TransactionOutpoint, UtxoEntry}, + tx::{MutableTransaction, SignableTransaction, Transaction, TransactionOutpoint, UtxoEntry}, + utxo::utxo_inquirer::UtxoInquirerError, BlockHashSet, BlueWorkType, ChainPath, Hash, }; use kaspa_utils::sync::rwlock::*; @@ -313,6 +314,14 @@ impl ConsensusSessionOwned { self.clone().spawn_blocking(|c| c.get_chain_block_samples()).await } + pub async fn async_get_populated_transaction( + &self, + txid: Hash, + accepting_block_daa_score: u64, + ) -> Result { + self.clone().spawn_blocking(move |c| c.get_populated_transaction(txid, accepting_block_daa_score)).await + } + /// Returns the antipast of block `hash` from the POV of `context`, i.e. `antipast(hash) ∩ past(context)`. /// Since this might be an expensive operation for deep blocks, we allow the caller to specify a limit /// `max_traversal_allowed` on the maximum amount of blocks to traverse for obtaining the answer diff --git a/consensus/Cargo.toml b/consensus/Cargo.toml index 443e591c8a..90aad80e1a 100644 --- a/consensus/Cargo.toml +++ b/consensus/Cargo.toml @@ -18,6 +18,7 @@ faster-hex.workspace = true futures-util.workspace = true indexmap.workspace = true itertools.workspace = true +kaspa-addresses.workspace = true kaspa-consensus-core.workspace = true kaspa-consensus-notify.workspace = true kaspa-consensusmanager.workspace = true diff --git a/consensus/core/src/api/mod.rs b/consensus/core/src/api/mod.rs index 7c244b9148..f8df0c0e14 100644 --- a/consensus/core/src/api/mod.rs +++ b/consensus/core/src/api/mod.rs @@ -19,7 +19,8 @@ use crate::{ header::Header, pruning::{PruningPointProof, PruningPointTrustedData, PruningPointsList, PruningProofMetadata}, trusted::{ExternalGhostdagData, TrustedBlock}, - tx::{MutableTransaction, Transaction, TransactionOutpoint, UtxoEntry}, + tx::{MutableTransaction, SignableTransaction, Transaction, TransactionOutpoint, UtxoEntry}, + utxo::utxo_inquirer::UtxoInquirerError, BlockHashSet, BlueWorkType, ChainPath, }; use kaspa_hashes::Hash; @@ -170,6 +171,12 @@ pub trait ConsensusApi: Send + Sync { unimplemented!() } + /// Returns the fully populated transaction with the given txid which was accepted at the provided accepting_block_daa_score. + /// The argument `accepting_block_daa_score` is expected to be the DAA score of the accepting chain block of `txid`. + fn get_populated_transaction(&self, txid: Hash, accepting_block_daa_score: u64) -> Result { + unimplemented!() + } + fn get_virtual_parents(&self) -> BlockHashSet { unimplemented!() } diff --git a/consensus/core/src/tx/script_public_key.rs b/consensus/core/src/tx/script_public_key.rs index b0a4756066..00a0ffe29d 100644 --- a/consensus/core/src/tx/script_public_key.rs +++ b/consensus/core/src/tx/script_public_key.rs @@ -405,7 +405,9 @@ impl TryCastFromJs for ScriptPublicKey { #[cfg(test)] mod tests { use super::*; + #[cfg(target_arch = "wasm32")] use js_sys::Object; + #[cfg(target_arch = "wasm32")] use wasm_bindgen::__rt::IntoJsResult; #[test] @@ -438,10 +440,14 @@ mod tests { assert_eq!(spk, spk2); } + #[cfg(target_arch = "wasm32")] use wasm_bindgen::convert::IntoWasmAbi; + #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::wasm_bindgen_test; + #[cfg(target_arch = "wasm32")] use workflow_wasm::serde::{from_value, to_value}; + #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] pub fn test_wasm_serde_constructor() { let version = 0xc0de; @@ -459,6 +465,7 @@ mod tests { assert_eq!(JsValue::from_str("string"), spk_js.js_typeof()); } + #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] pub fn test_wasm_serde_js_spk_object() { let version = 0xc0de; @@ -478,6 +485,7 @@ mod tests { assert_eq!(spk, actual); } + #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] pub fn test_wasm_serde_spk_object() { let version = 0xc0de; diff --git a/consensus/core/src/utxo/mod.rs b/consensus/core/src/utxo/mod.rs index 42014856f7..697506cb81 100644 --- a/consensus/core/src/utxo/mod.rs +++ b/consensus/core/src/utxo/mod.rs @@ -1,4 +1,5 @@ pub mod utxo_collection; pub mod utxo_diff; pub mod utxo_error; +pub mod utxo_inquirer; pub mod utxo_view; diff --git a/consensus/core/src/utxo/utxo_inquirer.rs b/consensus/core/src/utxo/utxo_inquirer.rs new file mode 100644 index 0000000000..3aa1000295 --- /dev/null +++ b/consensus/core/src/utxo/utxo_inquirer.rs @@ -0,0 +1,38 @@ +use kaspa_hashes::Hash; +use thiserror::Error; + +#[derive(Error, Debug, Clone)] +pub enum UtxoInquirerError { + #[error("Transaction is already pruned")] + AlreadyPruned, + #[error("Transaction return address is coinbase")] + TxFromCoinbase, + #[error("Transaction not found at given accepting daa score")] + NoTxAtScore, + #[error("Transaction was found but not standard")] + NonStandard, + #[error("Did not find compact header for block hash {0} ")] + MissingCompactHeaderForBlockHash(Hash), + #[error("Did not find containing_acceptance for tx {0} ")] + MissingContainingAcceptanceForTx(Hash), + #[error("Did not find block {0} at block tx store")] + MissingBlockFromBlockTxStore(Hash), + #[error("Did not find index {0} in transactions of block {1}")] + MissingTransactionIndexOfBlock(usize, Hash), + #[error("Expected {0} to match {1} when checking block_transaction_store using array index of transaction")] + UnexpectedTransactionMismatch(Hash, Hash), + #[error("Did not find a utxo diff for chain block {0} ")] + MissingUtxoDiffForChainBlock(Hash), + #[error("Transaction {0} acceptance data must also be in the same block in this case")] + MissingOtherTransactionAcceptanceData(Hash), + #[error("Did not find index for hash {0}")] + MissingIndexForHash(Hash), + #[error("Did not find tip data")] + MissingTipData, + #[error("Did not find a hash at index {0} ")] + MissingHashAtIndex(u64), + #[error("Did not find acceptance data for chain block {0}")] + MissingAcceptanceDataForChainBlock(Hash), + #[error("Utxo entry is not filled")] + UnfilledUtxoEntry, +} diff --git a/consensus/src/consensus/mod.rs b/consensus/src/consensus/mod.rs index 99719d4ac2..21e5bf5573 100644 --- a/consensus/src/consensus/mod.rs +++ b/consensus/src/consensus/mod.rs @@ -66,7 +66,8 @@ use kaspa_consensus_core::{ network::NetworkType, pruning::{PruningPointProof, PruningPointTrustedData, PruningPointsList, PruningProofMetadata}, trusted::{ExternalGhostdagData, TrustedBlock}, - tx::{MutableTransaction, Transaction, TransactionOutpoint, UtxoEntry}, + tx::{MutableTransaction, SignableTransaction, Transaction, TransactionOutpoint, UtxoEntry}, + utxo::utxo_inquirer::UtxoInquirerError, BlockHashSet, BlueWorkType, ChainPath, HashMapCustomHasher, }; use kaspa_consensus_notify::root::ConsensusNotificationRoot; @@ -687,6 +688,12 @@ impl ConsensusApi for Consensus { sample_headers } + fn get_populated_transaction(&self, txid: Hash, accepting_block_daa_score: u64) -> Result { + // We need consistency between the pruning_point_store, utxo_diffs_store, block_transactions_store, selected chain and headers store reads + let _guard = self.pruning_lock.blocking_read(); + self.virtual_processor.get_populated_transaction(txid, accepting_block_daa_score, self.get_source()) + } + fn get_virtual_parents(&self) -> BlockHashSet { self.lkg_virtual_state.load().parents.iter().copied().collect() } diff --git a/consensus/src/pipeline/virtual_processor/mod.rs b/consensus/src/pipeline/virtual_processor/mod.rs index a35dec6856..fd3756f3ae 100644 --- a/consensus/src/pipeline/virtual_processor/mod.rs +++ b/consensus/src/pipeline/virtual_processor/mod.rs @@ -1,5 +1,6 @@ pub mod errors; mod processor; +mod utxo_inquirer; mod utxo_validation; pub use processor::*; pub mod test_block_builder; diff --git a/consensus/src/pipeline/virtual_processor/processor.rs b/consensus/src/pipeline/virtual_processor/processor.rs index 914e0a327d..21c347c4df 100644 --- a/consensus/src/pipeline/virtual_processor/processor.rs +++ b/consensus/src/pipeline/virtual_processor/processor.rs @@ -156,7 +156,7 @@ pub struct VirtualStateProcessor { pub(super) block_window_cache_for_past_median_time: Arc, // Pruning lock - pruning_lock: SessionLock, + pub(super) pruning_lock: SessionLock, // Notifier notification_root: Arc, diff --git a/consensus/src/pipeline/virtual_processor/utxo_inquirer.rs b/consensus/src/pipeline/virtual_processor/utxo_inquirer.rs new file mode 100644 index 0000000000..617c1026bd --- /dev/null +++ b/consensus/src/pipeline/virtual_processor/utxo_inquirer.rs @@ -0,0 +1,189 @@ +use std::{cmp, sync::Arc}; + +use kaspa_consensus_core::{ + acceptance_data::AcceptanceData, + tx::{SignableTransaction, Transaction, UtxoEntry}, + utxo::{utxo_diff::ImmutableUtxoDiff, utxo_inquirer::UtxoInquirerError}, +}; +use kaspa_core::{trace, warn}; +use kaspa_hashes::Hash; + +use crate::model::stores::{ + acceptance_data::AcceptanceDataStoreReader, block_transactions::BlockTransactionsStoreReader, headers::HeaderStoreReader, + selected_chain::SelectedChainStoreReader, utxo_diffs::UtxoDiffsStoreReader, +}; + +use super::VirtualStateProcessor; + +impl VirtualStateProcessor { + /// Returns the fully populated transaction with the given txid which was accepted at the provided accepting_block_daa_score. + /// The argument `accepting_block_daa_score` is expected to be the DAA score of the accepting chain block of `txid`. + /// + /// *Assumed to be called under the pruning read lock.* + pub fn get_populated_transaction( + &self, + txid: Hash, + accepting_block_daa_score: u64, + source_hash: Hash, + ) -> Result { + let source_daa_score = self + .headers_store + .get_compact_header_data(source_hash) + .map(|compact_header| compact_header.daa_score) + .map_err(|_| UtxoInquirerError::MissingCompactHeaderForBlockHash(source_hash))?; + + if accepting_block_daa_score < source_daa_score { + // Early exit if target daa score is lower than that of pruning point's daa score: + return Err(UtxoInquirerError::AlreadyPruned); + } + + let (matching_chain_block_hash, acceptance_data) = + self.find_accepting_chain_block_hash_at_daa_score(accepting_block_daa_score, source_hash)?; + + // Expected to never fail, since we found the acceptance data and therefore there must be matching diff + let utxo_diff = self + .utxo_diffs_store + .get(matching_chain_block_hash) + .map_err(|_| UtxoInquirerError::MissingUtxoDiffForChainBlock(matching_chain_block_hash))?; + + let tx = self.find_tx_from_acceptance_data(txid, &acceptance_data)?; + + let mut populated_tx = SignableTransaction::new(tx); + + let removed_diffs = utxo_diff.removed(); + + populated_tx.tx.inputs.iter().enumerate().for_each(|(index, input)| { + let filled_utxo = if let Some(utxo_entry) = removed_diffs.get(&input.previous_outpoint) { + Some(utxo_entry.clone().to_owned()) + } else { + // This handles this rare scenario: + // - UTXO0 is spent by TX1 and creates UTXO1 + // - UTXO1 is spent by TX2 and creates UTXO2 + // - A chain block happens to accept both of these + // In this case, removed_diff wouldn't contain the outpoint of the created-and-immediately-spent UTXO + // so we use the transaction (which also has acceptance data in this block) and look at its outputs + let other_txid = input.previous_outpoint.transaction_id; + let other_tx = self.find_tx_from_acceptance_data(other_txid, &acceptance_data).unwrap(); + let output = &other_tx.outputs[input.previous_outpoint.index as usize]; + let utxo_entry = + UtxoEntry::new(output.value, output.script_public_key.clone(), accepting_block_daa_score, other_tx.is_coinbase()); + Some(utxo_entry) + }; + + populated_tx.entries[index] = filled_utxo; + }); + + Ok(populated_tx) + } + + /// Find the accepting chain block hash at the given DAA score by binary searching + /// through selected chain store using indexes. + /// This method assumes that local caller have acquired the pruning read lock to guarantee + /// consistency between reads on the selected_chain_store and headers_store (as well as + /// other stores outside). If no such lock is acquired, this method tries to find + /// the accepting chain block hash on a best effort basis (may fail if parts of the data + /// are pruned between two sequential calls) + fn find_accepting_chain_block_hash_at_daa_score( + &self, + target_daa_score: u64, + source_hash: Hash, + ) -> Result<(Hash, Arc), UtxoInquirerError> { + let sc_read = self.selected_chain_store.read(); + + let source_index = sc_read.get_by_hash(source_hash).map_err(|_| UtxoInquirerError::MissingIndexForHash(source_hash))?; + let (tip_index, tip_hash) = sc_read.get_tip().map_err(|_| UtxoInquirerError::MissingTipData)?; + let tip_daa_score = self + .headers_store + .get_compact_header_data(tip_hash) + .map(|tip| tip.daa_score) + .map_err(|_| UtxoInquirerError::MissingCompactHeaderForBlockHash(tip_hash))?; + + // For a chain segment it holds that len(segment) <= daa_score(segment end) - daa_score(segment start). This is true + // because each chain block increases the daa score by at least one. Hence we can lower bound our search by high index + // minus the daa score gap as done below + let mut low_index = tip_index.saturating_sub(tip_daa_score.saturating_sub(target_daa_score)).max(source_index); + let mut high_index = tip_index; + + let matching_chain_block_hash = loop { + // Binary search for the chain block that matches the target_daa_score + // 0. Get the mid point index + let mid = low_index + (high_index - low_index) / 2; + + // 1. Get the chain block hash at that index. Error if we cannot find a hash at that index + let hash = sc_read.get_by_index(mid).map_err(|_| { + trace!("Did not find a hash at index {}", mid); + UtxoInquirerError::MissingHashAtIndex(mid) + })?; + + // 2. Get the compact header so we have access to the daa_score. Error if we cannot find the header + let compact_header = self.headers_store.get_compact_header_data(hash).map_err(|_| { + trace!("Did not find a compact header with hash {}", hash); + UtxoInquirerError::MissingCompactHeaderForBlockHash(hash) + })?; + + // 3. Compare block daa score to our target + match compact_header.daa_score.cmp(&target_daa_score) { + cmp::Ordering::Equal => { + // We found the chain block we need + break hash; + } + cmp::Ordering::Greater => { + high_index = mid - 1; + } + cmp::Ordering::Less => { + low_index = mid + 1; + } + } + + if low_index > high_index { + return Err(UtxoInquirerError::NoTxAtScore); + } + }; + + let acceptance_data = self + .acceptance_data_store + .get(matching_chain_block_hash) + .map_err(|_| UtxoInquirerError::MissingAcceptanceDataForChainBlock(matching_chain_block_hash))?; + + Ok((matching_chain_block_hash, acceptance_data)) + } + + /// Finds a transaction's containing block hash and index within block through + /// the accepting block acceptance data + fn find_containing_block_and_index_from_acceptance_data( + &self, + txid: Hash, + acceptance_data: &AcceptanceData, + ) -> Option<(Hash, usize)> { + acceptance_data.iter().find_map(|mbad| { + let tx_arr_index = + mbad.accepted_transactions.iter().find_map(|tx| (tx.transaction_id == txid).then_some(tx.index_within_block as usize)); + tx_arr_index.map(|index| (mbad.block_hash, index)) + }) + } + + /// Finds a transaction through the accepting block acceptance data (and using indexed info therein for + /// finding the tx in the block transactions store) + fn find_tx_from_acceptance_data(&self, txid: Hash, acceptance_data: &AcceptanceData) -> Result { + let (containing_block, index) = self + .find_containing_block_and_index_from_acceptance_data(txid, acceptance_data) + .ok_or(UtxoInquirerError::MissingContainingAcceptanceForTx(txid))?; + + let tx = self + .block_transactions_store + .get(containing_block) + .map_err(|_| UtxoInquirerError::MissingBlockFromBlockTxStore(containing_block)) + .and_then(|block_txs| { + block_txs.get(index).cloned().ok_or(UtxoInquirerError::MissingTransactionIndexOfBlock(index, containing_block)) + })?; + + if tx.id() != txid { + // Should never happen, but do a sanity check. This would mean something went wrong with storing block transactions. + // Sanity check is necessary to guarantee that this function will never give back a wrong address (err on the side of not found) + warn!("Expected {} to match {} when checking block_transaction_store using array index of transaction", tx.id(), txid); + return Err(UtxoInquirerError::UnexpectedTransactionMismatch(tx.id(), txid)); + } + + Ok(tx) + } +} diff --git a/crypto/addresses/src/lib.rs b/crypto/addresses/src/lib.rs index 48c0baf198..aa0d6d8d3f 100644 --- a/crypto/addresses/src/lib.rs +++ b/crypto/addresses/src/lib.rs @@ -638,11 +638,16 @@ mod tests { // cspell:enable } + #[cfg(target_arch = "wasm32")] use js_sys::Object; + #[cfg(target_arch = "wasm32")] use wasm_bindgen::{JsValue, __rt::IntoJsResult}; + #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::wasm_bindgen_test; + #[cfg(target_arch = "wasm32")] use workflow_wasm::{extensions::ObjectExtension, serde::from_value, serde::to_value}; + #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] pub fn test_wasm_serde_constructor() { let str = "kaspa:qpauqsvk7yf9unexwmxsnmg547mhyga37csh0kj53q6xxgl24ydxjsgzthw5j"; @@ -654,6 +659,7 @@ mod tests { assert_eq!(a, from_value(value).unwrap()); } + #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] pub fn test_wasm_js_serde_object() { let expected = Address::constructor("kaspa:qpauqsvk7yf9unexwmxsnmg547mhyga37csh0kj53q6xxgl24ydxjsgzthw5j"); @@ -678,6 +684,7 @@ mod tests { assert_eq!(expected, actual); } + #[cfg(target_arch = "wasm32")] #[wasm_bindgen_test] pub fn test_wasm_serde_object() { use wasm_bindgen::convert::IntoWasmAbi; diff --git a/protocol/p2p/src/core/router.rs b/protocol/p2p/src/core/router.rs index 6cd63bb146..55caf26c43 100644 --- a/protocol/p2p/src/core/router.rs +++ b/protocol/p2p/src/core/router.rs @@ -467,9 +467,6 @@ fn match_for_io_error(err_status: &tonic::Status) -> Option<&std::io::Error> { } } - err = match err.source() { - Some(err) => err, - None => return None, - }; + err = err.source()?; } } diff --git a/rpc/core/src/api/ops.rs b/rpc/core/src/api/ops.rs index 26ca356eb0..4541ddc56d 100644 --- a/rpc/core/src/api/ops.rs +++ b/rpc/core/src/api/ops.rs @@ -136,6 +136,8 @@ pub enum RpcApiOps { GetFeeEstimateExperimental = 148, /// Block color determination by iterating DAG. GetCurrentBlockColor = 149, + /// Get UTXO Return Addresses + GetUtxoReturnAddress = 150, } impl RpcApiOps { diff --git a/rpc/core/src/api/rpc.rs b/rpc/core/src/api/rpc.rs index cadc9e00cd..d9011c167e 100644 --- a/rpc/core/src/api/rpc.rs +++ b/rpc/core/src/api/rpc.rs @@ -438,6 +438,18 @@ pub trait RpcApi: Sync + Send + AnySync { request: GetDaaScoreTimestampEstimateRequest, ) -> RpcResult; + async fn get_utxo_return_address(&self, txid: RpcHash, accepting_block_daa_score: u64) -> RpcResult { + Ok(self + .get_utxo_return_address_call(None, GetUtxoReturnAddressRequest { txid, accepting_block_daa_score }) + .await? + .return_address) + } + async fn get_utxo_return_address_call( + &self, + _connection: Option<&DynRpcConnection>, + request: GetUtxoReturnAddressRequest, + ) -> RpcResult; + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Fee estimation API diff --git a/rpc/core/src/error.rs b/rpc/core/src/error.rs index 0e2bfee225..54763c71ba 100644 --- a/rpc/core/src/error.rs +++ b/rpc/core/src/error.rs @@ -2,7 +2,7 @@ //! [`RpcError`] enum used by RPC primitives. //! -use kaspa_consensus_core::{subnets::SubnetworkConversionError, tx::TransactionId}; +use kaspa_consensus_core::{subnets::SubnetworkConversionError, tx::TransactionId, utxo::utxo_inquirer::UtxoInquirerError}; use kaspa_utils::networking::IpAddress; use std::{net::AddrParseError, num::TryFromIntError}; use thiserror::Error; @@ -134,6 +134,9 @@ pub enum RpcError { #[error(transparent)] ConsensusClient(#[from] kaspa_consensus_client::error::Error), + + #[error("utxo return address could not be found -> {0}")] + UtxoReturnAddressNotFound(UtxoInquirerError), } impl From for RpcError { diff --git a/rpc/core/src/model/message.rs b/rpc/core/src/model/message.rs index ba8d6abf76..cb663c394a 100644 --- a/rpc/core/src/model/message.rs +++ b/rpc/core/src/model/message.rs @@ -2666,6 +2666,69 @@ impl Deserializer for GetCurrentBlockColorResponse { } } +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetUtxoReturnAddressRequest { + pub txid: RpcHash, + pub accepting_block_daa_score: u64, +} + +impl GetUtxoReturnAddressRequest { + pub fn new(txid: RpcHash, accepting_block_daa_score: u64) -> Self { + Self { txid, accepting_block_daa_score } + } +} + +impl Serializer for GetUtxoReturnAddressRequest { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(u16, &1, writer)?; + store!(RpcHash, &self.txid, writer)?; + store!(u64, &self.accepting_block_daa_score, writer)?; + + Ok(()) + } +} + +impl Deserializer for GetUtxoReturnAddressRequest { + fn deserialize(reader: &mut R) -> std::io::Result { + let _version = load!(u16, reader)?; + let txid = load!(RpcHash, reader)?; + let accepting_block_daa_score = load!(u64, reader)?; + + Ok(Self { txid, accepting_block_daa_score }) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetUtxoReturnAddressResponse { + pub return_address: RpcAddress, +} + +impl GetUtxoReturnAddressResponse { + pub fn new(return_address: RpcAddress) -> Self { + Self { return_address } + } +} + +impl Serializer for GetUtxoReturnAddressResponse { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(u16, &1, writer)?; + store!(RpcAddress, &self.return_address, writer)?; + + Ok(()) + } +} + +impl Deserializer for GetUtxoReturnAddressResponse { + fn deserialize(reader: &mut R) -> std::io::Result { + let _version = load!(u16, reader)?; + let return_address = load!(RpcAddress, reader)?; + + Ok(Self { return_address }) + } +} + // ---------------------------------------------------------------------------- // Subscriptions & notifications // ---------------------------------------------------------------------------- diff --git a/rpc/grpc/client/src/lib.rs b/rpc/grpc/client/src/lib.rs index b7e53bb5e1..04e37b5fe6 100644 --- a/rpc/grpc/client/src/lib.rs +++ b/rpc/grpc/client/src/lib.rs @@ -276,6 +276,7 @@ impl RpcApi for GrpcClient { route!(get_fee_estimate_call, GetFeeEstimate); route!(get_fee_estimate_experimental_call, GetFeeEstimateExperimental); route!(get_current_block_color_call, GetCurrentBlockColor); + route!(get_utxo_return_address_call, GetUtxoReturnAddress); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Notification API diff --git a/rpc/grpc/core/Cargo.toml b/rpc/grpc/core/Cargo.toml index 2edc10b600..61734df9e3 100644 --- a/rpc/grpc/core/Cargo.toml +++ b/rpc/grpc/core/Cargo.toml @@ -10,6 +10,7 @@ license.workspace = true repository.workspace = true [dependencies] +kaspa-addresses.workspace = true kaspa-consensus-core.workspace = true kaspa-core.workspace = true kaspa-notify.workspace = true diff --git a/rpc/grpc/core/proto/messages.proto b/rpc/grpc/core/proto/messages.proto index 2d6310d9e3..ccb2798b67 100644 --- a/rpc/grpc/core/proto/messages.proto +++ b/rpc/grpc/core/proto/messages.proto @@ -65,6 +65,7 @@ message KaspadRequest { GetFeeEstimateRequestMessage getFeeEstimateRequest = 1106; GetFeeEstimateExperimentalRequestMessage getFeeEstimateExperimentalRequest = 1108; GetCurrentBlockColorRequestMessage getCurrentBlockColorRequest = 1110; + GetUtxoReturnAddressRequestMessage GetUtxoReturnAddressRequest = 1112; } } @@ -130,6 +131,7 @@ message KaspadResponse { GetFeeEstimateResponseMessage getFeeEstimateResponse = 1107; GetFeeEstimateExperimentalResponseMessage getFeeEstimateExperimentalResponse = 1109; GetCurrentBlockColorResponseMessage getCurrentBlockColorResponse = 1111; + GetUtxoReturnAddressResponseMessage GetUtxoReturnAddressResponse = 1113; } } diff --git a/rpc/grpc/core/proto/rpc.proto b/rpc/grpc/core/proto/rpc.proto index e218681b65..4c36150ad3 100644 --- a/rpc/grpc/core/proto/rpc.proto +++ b/rpc/grpc/core/proto/rpc.proto @@ -904,7 +904,7 @@ message GetDaaScoreTimestampEstimateRequestMessage { repeated uint64 daaScores = 1; } -message GetDaaScoreTimestampEstimateResponseMessage{ +message GetDaaScoreTimestampEstimateResponseMessage { repeated uint64 timestamps = 1; RPCError error = 1000; } @@ -974,3 +974,13 @@ message GetCurrentBlockColorResponseMessage { RPCError error = 1000; } + +message GetUtxoReturnAddressRequestMessage { + string txid = 1; + uint64 accepting_block_daa_score = 2; +} + +message GetUtxoReturnAddressResponseMessage { + string return_address = 1; + RPCError error = 1000; +} diff --git a/rpc/grpc/core/src/convert/kaspad.rs b/rpc/grpc/core/src/convert/kaspad.rs index c3411545cc..7243fd401a 100644 --- a/rpc/grpc/core/src/convert/kaspad.rs +++ b/rpc/grpc/core/src/convert/kaspad.rs @@ -63,6 +63,7 @@ pub mod kaspad_request_convert { impl_into_kaspad_request!(GetFeeEstimate); impl_into_kaspad_request!(GetFeeEstimateExperimental); impl_into_kaspad_request!(GetCurrentBlockColor); + impl_into_kaspad_request!(GetUtxoReturnAddress); impl_into_kaspad_request!(NotifyBlockAdded); impl_into_kaspad_request!(NotifyNewBlockTemplate); @@ -200,6 +201,7 @@ pub mod kaspad_response_convert { impl_into_kaspad_response!(GetFeeEstimate); impl_into_kaspad_response!(GetFeeEstimateExperimental); impl_into_kaspad_response!(GetCurrentBlockColor); + impl_into_kaspad_response!(GetUtxoReturnAddress); impl_into_kaspad_notify_response!(NotifyBlockAdded); impl_into_kaspad_notify_response!(NotifyNewBlockTemplate); diff --git a/rpc/grpc/core/src/convert/message.rs b/rpc/grpc/core/src/convert/message.rs index 67ac60650c..c92e824ed0 100644 --- a/rpc/grpc/core/src/convert/message.rs +++ b/rpc/grpc/core/src/convert/message.rs @@ -19,7 +19,8 @@ //! The SubmitBlockResponse is a notable exception to this general rule. use crate::protowire::{self, submit_block_response_message::RejectReason}; -use kaspa_consensus_core::network::NetworkId; +use kaspa_addresses::Address; +use kaspa_consensus_core::{network::NetworkId, Hash}; use kaspa_core::debug; use kaspa_notify::subscription::Command; use kaspa_rpc_core::{ @@ -430,6 +431,16 @@ from!(item: RpcResult<&kaspa_rpc_core::GetCurrentBlockColorResponse>, protowire: Self { blue: item.blue, error: None } }); +from!(item: &kaspa_rpc_core::GetUtxoReturnAddressRequest, protowire::GetUtxoReturnAddressRequestMessage, { + Self { + txid: item.txid.to_string(), + accepting_block_daa_score: item.accepting_block_daa_score + } +}); +from!(item: RpcResult<&kaspa_rpc_core::GetUtxoReturnAddressResponse>, protowire::GetUtxoReturnAddressResponseMessage, { + Self { return_address: item.return_address.address_to_string(), error: None } +}); + from!(&kaspa_rpc_core::PingRequest, protowire::PingRequestMessage); from!(RpcResult<&kaspa_rpc_core::PingResponse>, protowire::PingResponseMessage); @@ -916,6 +927,15 @@ try_from!(item: &protowire::GetCurrentBlockColorResponseMessage, RpcResult, { + Self { return_address: Address::try_from(item.return_address.clone())? } +}); try_from!(&protowire::PingRequestMessage, kaspa_rpc_core::PingRequest); try_from!(&protowire::PingResponseMessage, RpcResult); diff --git a/rpc/grpc/core/src/ops.rs b/rpc/grpc/core/src/ops.rs index f3bc12c829..223774c74c 100644 --- a/rpc/grpc/core/src/ops.rs +++ b/rpc/grpc/core/src/ops.rs @@ -87,6 +87,7 @@ pub enum KaspadPayloadOps { GetFeeEstimate, GetFeeEstimateExperimental, GetCurrentBlockColor, + GetUtxoReturnAddress, // Subscription commands for starting/stopping notifications NotifyBlockAdded, diff --git a/rpc/grpc/server/src/connection.rs b/rpc/grpc/server/src/connection.rs index 442eb2f423..7613ac58ea 100644 --- a/rpc/grpc/server/src/connection.rs +++ b/rpc/grpc/server/src/connection.rs @@ -374,10 +374,7 @@ fn match_for_io_error(err_status: &tonic::Status) -> Option<&std::io::Error> { } } - err = match err.source() { - Some(err) => err, - None => return None, - }; + err = err.source()?; } } diff --git a/rpc/grpc/server/src/request_handler/factory.rs b/rpc/grpc/server/src/request_handler/factory.rs index b6a5b4476f..9fec86e476 100644 --- a/rpc/grpc/server/src/request_handler/factory.rs +++ b/rpc/grpc/server/src/request_handler/factory.rs @@ -81,6 +81,7 @@ impl Factory { GetFeeEstimate, GetFeeEstimateExperimental, GetCurrentBlockColor, + GetUtxoReturnAddress, NotifyBlockAdded, NotifyNewBlockTemplate, NotifyFinalityConflict, diff --git a/rpc/grpc/server/src/tests/rpc_core_mock.rs b/rpc/grpc/server/src/tests/rpc_core_mock.rs index dd6de46d2a..ada801850e 100644 --- a/rpc/grpc/server/src/tests/rpc_core_mock.rs +++ b/rpc/grpc/server/src/tests/rpc_core_mock.rs @@ -362,6 +362,14 @@ impl RpcApi for RpcCoreMock { Err(RpcError::NotImplemented) } + async fn get_utxo_return_address_call( + &self, + _connection: Option<&DynRpcConnection>, + _request: GetUtxoReturnAddressRequest, + ) -> RpcResult { + Err(RpcError::NotImplemented) + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Notification API diff --git a/rpc/macros/src/wrpc/wasm.rs b/rpc/macros/src/wrpc/wasm.rs index 30af3e74a3..a3c955d102 100644 --- a/rpc/macros/src/wrpc/wasm.rs +++ b/rpc/macros/src/wrpc/wasm.rs @@ -136,6 +136,7 @@ impl Parse for RpcSubscriptions { impl ToTokens for RpcSubscriptions { fn to_tokens(&self, tokens: &mut TokenStream) { let mut targets = Vec::new(); + let regex = Regex::new(r"^Notify").unwrap(); for handler in self.handlers.elems.iter() { let (name, docs) = match handler { @@ -146,7 +147,6 @@ impl ToTokens for RpcSubscriptions { }; let name = format!("Notify{}", name.as_str()); - let regex = Regex::new(r"^Notify").unwrap(); let blank = regex.replace(&name, ""); let subscribe = regex.replace(&name, "Subscribe"); let unsubscribe = regex.replace(&name, "Unsubscribe"); diff --git a/rpc/service/src/service.rs b/rpc/service/src/service.rs index d75ff770b0..c8c40c7707 100644 --- a/rpc/service/src/service.rs +++ b/rpc/service/src/service.rs @@ -7,6 +7,7 @@ use crate::service::NetworkType::{Mainnet, Testnet}; use async_trait::async_trait; use kaspa_consensus_core::api::counters::ProcessingCounters; use kaspa_consensus_core::errors::block::RuleError; +use kaspa_consensus_core::utxo::utxo_inquirer::UtxoInquirerError; use kaspa_consensus_core::{ block::Block, coinbase::MinerData, @@ -794,6 +795,33 @@ NOTE: This error usually indicates an RPC conversion error between the node and } } + async fn get_utxo_return_address_call( + &self, + _connection: Option<&DynRpcConnection>, + request: GetUtxoReturnAddressRequest, + ) -> RpcResult { + let session = self.consensus_manager.consensus().session().await; + + match session.async_get_populated_transaction(request.txid, request.accepting_block_daa_score).await { + Ok(tx) => { + if tx.tx.inputs.is_empty() || tx.entries.is_empty() { + return Err(RpcError::UtxoReturnAddressNotFound(UtxoInquirerError::TxFromCoinbase)); + } + + if let Some(utxo_entry) = &tx.entries[0] { + if let Ok(address) = extract_script_pub_key_address(&utxo_entry.script_public_key, self.config.prefix()) { + Ok(GetUtxoReturnAddressResponse { return_address: address }) + } else { + Err(RpcError::UtxoReturnAddressNotFound(UtxoInquirerError::NonStandard)) + } + } else { + Err(RpcError::UtxoReturnAddressNotFound(UtxoInquirerError::UnfilledUtxoEntry)) + } + } + Err(error) => return Err(RpcError::UtxoReturnAddressNotFound(error)), + } + } + async fn ping_call(&self, _connection: Option<&DynRpcConnection>, _: PingRequest) -> RpcResult { Ok(PingResponse {}) } diff --git a/rpc/wrpc/client/src/client.rs b/rpc/wrpc/client/src/client.rs index 3ac04fa984..f22bcf6255 100644 --- a/rpc/wrpc/client/src/client.rs +++ b/rpc/wrpc/client/src/client.rs @@ -644,6 +644,7 @@ impl RpcApi for KaspaRpcClient { GetSubnetwork, GetSyncStatus, GetSystemInfo, + GetUtxoReturnAddress, GetUtxosByAddresses, GetVirtualChainFromBlock, ResolveFinalityConflict, diff --git a/rpc/wrpc/server/src/router.rs b/rpc/wrpc/server/src/router.rs index 4d0e206259..b4c74a3374 100644 --- a/rpc/wrpc/server/src/router.rs +++ b/rpc/wrpc/server/src/router.rs @@ -47,13 +47,14 @@ impl Router { GetCurrentBlockColor, GetCoinSupply, GetConnectedPeerInfo, + GetDaaScoreTimestampEstimate, + GetUtxoReturnAddress, GetCurrentNetwork, GetDaaScoreTimestampEstimate, GetFeeEstimate, GetFeeEstimateExperimental, GetHeaders, GetInfo, - GetInfo, GetMempoolEntries, GetMempoolEntriesByAddresses, GetMempoolEntry, diff --git a/testing/integration/src/daemon_integration_tests.rs b/testing/integration/src/daemon_integration_tests.rs index 460cf049c3..25471cab13 100644 --- a/testing/integration/src/daemon_integration_tests.rs +++ b/testing/integration/src/daemon_integration_tests.rs @@ -274,7 +274,8 @@ async fn daemon_utxos_propagation_test() { clients.iter().for_each(|x| x.utxos_changed_listener().unwrap().drain()); clients.iter().for_each(|x| x.virtual_daa_score_changed_listener().unwrap().drain()); - // Spend some coins + // Spend some coins - sending funds from miner address to user address + // The transaction here is later used to verify utxo return address RPC const NUMBER_INPUTS: u64 = 2; const NUMBER_OUTPUTS: u64 = 2; const TX_AMOUNT: u64 = SIMNET_PARAMS.pre_deflationary_phase_base_subsidy * (NUMBER_INPUTS * 5 - 1) / 5; @@ -324,6 +325,23 @@ async fn daemon_utxos_propagation_test() { assert_eq!(user_balance, TX_AMOUNT); } + // UTXO Return Address Test + // Mine another block to accept the transactions from the previous block + // The tx above is sending from miner address to user address + mine_block(blank_address.clone(), &rpc_client1, &clients).await; + let new_utxos = rpc_client1.get_utxos_by_addresses(vec![user_address]).await.unwrap(); + let new_utxo = new_utxos + .iter() + .find(|utxo| utxo.outpoint.transaction_id == transaction.id()) + .expect("Did not find a utxo for the tx we just created but expected to"); + + let utxo_return_address = rpc_client1 + .get_utxo_return_address(new_utxo.outpoint.transaction_id, new_utxo.utxo_entry.block_daa_score) + .await + .expect("We just created the tx and utxo here"); + + assert_eq!(miner_address, utxo_return_address); + // Terminate multi-listener clients for x in clients.iter() { x.disconnect().await.unwrap(); diff --git a/testing/integration/src/rpc_tests.rs b/testing/integration/src/rpc_tests.rs index 3c4df601b3..3ca619423a 100644 --- a/testing/integration/src/rpc_tests.rs +++ b/testing/integration/src/rpc_tests.rs @@ -656,6 +656,23 @@ async fn sanity_test() { }) } + KaspadPayloadOps::GetUtxoReturnAddress => { + let rpc_client = client.clone(); + tst!(op, { + let results = rpc_client.get_utxo_return_address(RpcHash::from_bytes([0; 32]), 1000).await; + + assert!(results.is_err_and(|err| { + match err { + kaspa_rpc_core::RpcError::General(msg) => { + info!("Expected error message: {}", msg); + true + } + _ => false, + } + })); + }) + } + KaspadPayloadOps::NotifyBlockAdded => { let rpc_client = client.clone(); let id = listener_id; diff --git a/wallet/bip32/src/prefix.rs b/wallet/bip32/src/prefix.rs index daff8267e7..e391c5eb48 100644 --- a/wallet/bip32/src/prefix.rs +++ b/wallet/bip32/src/prefix.rs @@ -135,7 +135,7 @@ impl Prefix { /// Validate that the given prefix string is well-formed. // TODO(tarcieri): validate the string ends with `prv` or `pub`? pub(crate) const fn validate_str(s: &str) -> crate::error::ResultConst<&str> { - if s.as_bytes().len() != Self::LENGTH { + if s.len() != Self::LENGTH { return Err(crate::error::ErrorImpl::DecodeInvalidLength); } diff --git a/wallet/core/src/tests/rpc_core_mock.rs b/wallet/core/src/tests/rpc_core_mock.rs index 4d10cdd9b1..529fffffe8 100644 --- a/wallet/core/src/tests/rpc_core_mock.rs +++ b/wallet/core/src/tests/rpc_core_mock.rs @@ -379,6 +379,14 @@ impl RpcApi for RpcCoreMock { Err(RpcError::NotImplemented) } + async fn get_utxo_return_address_call( + &self, + _connection: Option<&DynRpcConnection>, + _request: GetUtxoReturnAddressRequest, + ) -> RpcResult { + Err(RpcError::NotImplemented) + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Notification API diff --git a/wasm/examples/nodejs/javascript/transactions/generator.js b/wasm/examples/nodejs/javascript/transactions/generator.js index ca6819f086..fa3f4f3aeb 100644 --- a/wasm/examples/nodejs/javascript/transactions/generator.js +++ b/wasm/examples/nodejs/javascript/transactions/generator.js @@ -67,7 +67,7 @@ const { encoding, networkId, address : destinationAddress } = require("../utils" // to the change address. // // If the requested amount is greater than the Kaspa - // transactoin mass, the Generator will create multiple + // transaction mass, the Generator will create multiple // transactions where each transaction will forward // UTXOs to the change address, until the requested // amount is reached. It will then create a final