From 613d082b0c2e51247819d932af7ddb5ebd5aa460 Mon Sep 17 00:00:00 2001 From: Michael Sutton Date: Sun, 15 Sep 2024 02:06:13 +0300 Subject: [PATCH 01/19] Fix new gRPC methods to use camel case (non-breaking change) (#560) --- rpc/grpc/core/proto/rpc.proto | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/rpc/grpc/core/proto/rpc.proto b/rpc/grpc/core/proto/rpc.proto index 7a38f8852..8c3e7b3b2 100644 --- a/rpc/grpc/core/proto/rpc.proto +++ b/rpc/grpc/core/proto/rpc.proto @@ -5,7 +5,6 @@ // 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; @@ -897,7 +896,7 @@ message GetSyncStatusResponseMessage{ } message GetDaaScoreTimestampEstimateRequestMessage { - repeated uint64 daa_scores = 1; + repeated uint64 daaScores = 1; } message GetDaaScoreTimestampEstimateResponseMessage{ @@ -908,7 +907,7 @@ message GetDaaScoreTimestampEstimateResponseMessage{ message RpcFeerateBucket { // Fee/mass of a transaction in `sompi/gram` units double feerate = 1; - double estimated_seconds = 2; + double estimatedSeconds = 2; } // Data required for making fee estimates. @@ -918,28 +917,28 @@ message RpcFeerateBucket { // taking the transaction mass and multiplying it by feerate: `fee = feerate * mass(tx)` message RpcFeeEstimate { // Top-priority feerate bucket. Provides an estimation of the feerate required for sub-second DAG inclusion. - RpcFeerateBucket priority_bucket = 1; + RpcFeerateBucket priorityBucket = 1; // A vector of *normal* priority feerate values. The first value of this vector is guaranteed to exist and // provide an estimation for sub-*minute* DAG inclusion. All other values will have shorter estimation - // times than all `low_bucket` values. Therefor by chaining `[priority] | normal | low` and interpolating + // times than all `lowBucket` values. Therefor by chaining `[priority] | normal | low` and interpolating // between them, one can compose a complete feerate function on the client side. The API makes an effort // to sample enough "interesting" points on the feerate-to-time curve, so that the interpolation is meaningful. - repeated RpcFeerateBucket normal_buckets = 2; + repeated RpcFeerateBucket normalBuckets = 2; // A vector of *low* priority feerate values. The first value of this vector is guaranteed to // exist and provide an estimation for sub-*hour* DAG inclusion. - repeated RpcFeerateBucket low_buckets = 3; + repeated RpcFeerateBucket lowBuckets = 3; } message RpcFeeEstimateVerboseExperimentalData { - uint64 mempool_ready_transactions_count = 1; - uint64 mempool_ready_transactions_total_mass = 2; - uint64 network_mass_per_second = 3; + uint64 mempoolReadyTransactionsCount = 1; + uint64 mempoolReadyTransactionsTotalMass = 2; + uint64 networkMassPerSecond = 3; - double next_block_template_feerate_min = 11; - double next_block_template_feerate_median = 12; - double next_block_template_feerate_max = 13; + double nextBlockTemplateFeerateMin = 11; + double nextBlockTemplateFeerateMedian = 12; + double nextBlockTemplateFeerateMax = 13; } message GetFeeEstimateRequestMessage { From b14537fac0c69505e64743dba8270c06fa1f11f5 Mon Sep 17 00:00:00 2001 From: D-Stacks <78099568+D-Stacks@users.noreply.github.com> Date: Thu, 19 Sep 2024 22:16:54 +0200 Subject: [PATCH 02/19] `virtual chain from block` batching. (#454) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * expose vspc_from_block batching possibilities to rpc. * fmt * limit by merged blocks, set source as def. start. * small clean-up * fmt * actually bound by num of merged blocks, in include transactions case. * fmt * update. * update_2 * new_high = high * remove high hash in consensus api.. as it is not required. * fmt * make proto comment more accurate. * fix tests, and lints, add to ser / der correctly. * change two freq warns to debug * remove option, default to pp. not source. * fix integration test, some Option left. * bump version: ´0.15.1 => 0.15.2` * remove "optional" startHash * add to cli rpc.rs * remove comment. * edit comment in .proto referencing def. startHash behavior. * only batch added chain blocks, not removed, add check if source is a chain ancestor of high. * remove dangling code in comment * remove error from some prev. commit. * Optionalize limts. --------- Co-authored-by: Michael Sutton --- Cargo.lock | 116 +++++++++--------- Cargo.toml | 112 ++++++++--------- cli/src/modules/rpc.rs | 18 ++- components/consensusmanager/src/session.rs | 16 ++- consensus/core/src/api/mod.rs | 13 +- consensus/src/consensus/mod.rs | 50 ++++++-- .../pipeline/virtual_processor/processor.rs | 2 +- consensus/src/processes/sync/mod.rs | 2 +- consensus/src/processes/traversal_manager.rs | 19 ++- mining/src/mempool/remove_transaction.rs | 6 +- rpc/grpc/core/proto/rpc.proto | 9 +- rpc/service/src/converter/consensus.rs | 3 +- rpc/service/src/service.rs | 21 +++- .../src/daemon_integration_tests.rs | 8 +- 14 files changed, 247 insertions(+), 148 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f23aca0f..1296a8922 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2363,7 +2363,7 @@ dependencies = [ [[package]] name = "kaspa-addresses" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "criterion", @@ -2380,7 +2380,7 @@ dependencies = [ [[package]] name = "kaspa-addressmanager" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "igd-next", @@ -2403,14 +2403,14 @@ dependencies = [ [[package]] name = "kaspa-alloc" -version = "0.15.1" +version = "0.15.2" dependencies = [ "mimalloc", ] [[package]] name = "kaspa-bip32" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "bs58", @@ -2437,7 +2437,7 @@ dependencies = [ [[package]] name = "kaspa-cli" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-trait", "borsh", @@ -2484,7 +2484,7 @@ dependencies = [ [[package]] name = "kaspa-connectionmanager" -version = "0.15.1" +version = "0.15.2" dependencies = [ "duration-string", "futures-util", @@ -2501,7 +2501,7 @@ dependencies = [ [[package]] name = "kaspa-consensus" -version = "0.15.1" +version = "0.15.2" dependencies = [ "arc-swap", "async-channel 2.3.1", @@ -2544,7 +2544,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-client" -version = "0.15.1" +version = "0.15.2" dependencies = [ "ahash", "cfg-if 1.0.0", @@ -2572,7 +2572,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-core" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-trait", "bincode", @@ -2610,7 +2610,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-notify" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-channel 2.3.1", "cfg-if 1.0.0", @@ -2629,7 +2629,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-wasm" -version = "0.15.1" +version = "0.15.2" dependencies = [ "cfg-if 1.0.0", "faster-hex", @@ -2653,7 +2653,7 @@ dependencies = [ [[package]] name = "kaspa-consensusmanager" -version = "0.15.1" +version = "0.15.2" dependencies = [ "duration-string", "futures", @@ -2671,7 +2671,7 @@ dependencies = [ [[package]] name = "kaspa-core" -version = "0.15.1" +version = "0.15.2" dependencies = [ "cfg-if 1.0.0", "ctrlc", @@ -2689,7 +2689,7 @@ dependencies = [ [[package]] name = "kaspa-daemon" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-trait", "borsh", @@ -2711,7 +2711,7 @@ dependencies = [ [[package]] name = "kaspa-database" -version = "0.15.1" +version = "0.15.2" dependencies = [ "bincode", "enum-primitive-derive", @@ -2733,7 +2733,7 @@ dependencies = [ [[package]] name = "kaspa-grpc-client" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-channel 2.3.1", "async-stream", @@ -2765,7 +2765,7 @@ dependencies = [ [[package]] name = "kaspa-grpc-core" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-channel 2.3.1", "async-stream", @@ -2794,7 +2794,7 @@ dependencies = [ [[package]] name = "kaspa-grpc-server" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-channel 2.3.1", "async-stream", @@ -2830,7 +2830,7 @@ dependencies = [ [[package]] name = "kaspa-hashes" -version = "0.15.1" +version = "0.15.2" dependencies = [ "blake2b_simd", "borsh", @@ -2851,7 +2851,7 @@ dependencies = [ [[package]] name = "kaspa-index-core" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-channel 2.3.1", "async-trait", @@ -2870,7 +2870,7 @@ dependencies = [ [[package]] name = "kaspa-index-processor" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-channel 2.3.1", "async-trait", @@ -2898,7 +2898,7 @@ dependencies = [ [[package]] name = "kaspa-math" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "criterion", @@ -2919,14 +2919,14 @@ dependencies = [ [[package]] name = "kaspa-merkle" -version = "0.15.1" +version = "0.15.2" dependencies = [ "kaspa-hashes", ] [[package]] name = "kaspa-metrics-core" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-trait", "borsh", @@ -2942,7 +2942,7 @@ dependencies = [ [[package]] name = "kaspa-mining" -version = "0.15.1" +version = "0.15.2" dependencies = [ "criterion", "futures-util", @@ -2969,7 +2969,7 @@ dependencies = [ [[package]] name = "kaspa-mining-errors" -version = "0.15.1" +version = "0.15.2" dependencies = [ "kaspa-consensus-core", "thiserror", @@ -2977,7 +2977,7 @@ dependencies = [ [[package]] name = "kaspa-muhash" -version = "0.15.1" +version = "0.15.2" dependencies = [ "criterion", "kaspa-hashes", @@ -2990,7 +2990,7 @@ dependencies = [ [[package]] name = "kaspa-notify" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-channel 2.3.1", "async-trait", @@ -3026,7 +3026,7 @@ dependencies = [ [[package]] name = "kaspa-p2p-flows" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-trait", "chrono", @@ -3057,7 +3057,7 @@ dependencies = [ [[package]] name = "kaspa-p2p-lib" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "ctrlc", @@ -3088,7 +3088,7 @@ dependencies = [ [[package]] name = "kaspa-perf-monitor" -version = "0.15.1" +version = "0.15.2" dependencies = [ "kaspa-core", "log", @@ -3100,7 +3100,7 @@ dependencies = [ [[package]] name = "kaspa-pow" -version = "0.15.1" +version = "0.15.2" dependencies = [ "criterion", "js-sys", @@ -3116,7 +3116,7 @@ dependencies = [ [[package]] name = "kaspa-rpc-core" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-channel 2.3.1", "async-trait", @@ -3158,7 +3158,7 @@ dependencies = [ [[package]] name = "kaspa-rpc-macros" -version = "0.15.1" +version = "0.15.2" dependencies = [ "convert_case 0.6.0", "proc-macro-error", @@ -3170,7 +3170,7 @@ dependencies = [ [[package]] name = "kaspa-rpc-service" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-trait", "kaspa-addresses", @@ -3199,7 +3199,7 @@ dependencies = [ [[package]] name = "kaspa-testing-integration" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-channel 2.3.1", "async-trait", @@ -3259,7 +3259,7 @@ dependencies = [ [[package]] name = "kaspa-txscript" -version = "0.15.1" +version = "0.15.2" dependencies = [ "blake2b_simd", "borsh", @@ -3291,7 +3291,7 @@ dependencies = [ [[package]] name = "kaspa-txscript-errors" -version = "0.15.1" +version = "0.15.2" dependencies = [ "secp256k1", "thiserror", @@ -3299,7 +3299,7 @@ dependencies = [ [[package]] name = "kaspa-utils" -version = "0.15.1" +version = "0.15.2" dependencies = [ "arc-swap", "async-channel 2.3.1", @@ -3335,7 +3335,7 @@ dependencies = [ [[package]] name = "kaspa-utils-tower" -version = "0.15.1" +version = "0.15.2" dependencies = [ "cfg-if 1.0.0", "futures", @@ -3349,7 +3349,7 @@ dependencies = [ [[package]] name = "kaspa-utxoindex" -version = "0.15.1" +version = "0.15.2" dependencies = [ "futures", "kaspa-consensus", @@ -3370,7 +3370,7 @@ dependencies = [ [[package]] name = "kaspa-wallet" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-std", "async-trait", @@ -3382,7 +3382,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-cli-wasm" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-trait", "js-sys", @@ -3396,7 +3396,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-core" -version = "0.15.1" +version = "0.15.2" dependencies = [ "aes", "ahash", @@ -3477,7 +3477,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-keys" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-trait", "borsh", @@ -3510,7 +3510,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-macros" -version = "0.15.1" +version = "0.15.2" dependencies = [ "convert_case 0.5.0", "proc-macro-error", @@ -3523,7 +3523,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-pskt" -version = "0.15.1" +version = "0.15.2" dependencies = [ "bincode", "derive_builder", @@ -3550,7 +3550,7 @@ dependencies = [ [[package]] name = "kaspa-wasm" -version = "0.15.1" +version = "0.15.2" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -3578,7 +3578,7 @@ dependencies = [ [[package]] name = "kaspa-wasm-core" -version = "0.15.1" +version = "0.15.2" dependencies = [ "faster-hex", "hexplay", @@ -3589,7 +3589,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-client" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-std", "async-trait", @@ -3625,7 +3625,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-example-subscriber" -version = "0.15.1" +version = "0.15.2" dependencies = [ "ctrlc", "futures", @@ -3640,7 +3640,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-proxy" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-trait", "clap 4.5.16", @@ -3659,7 +3659,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-server" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-trait", "borsh", @@ -3687,7 +3687,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-simple-client-example" -version = "0.15.1" +version = "0.15.2" dependencies = [ "futures", "kaspa-rpc-core", @@ -3697,7 +3697,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-wasm" -version = "0.15.1" +version = "0.15.2" dependencies = [ "ahash", "async-std", @@ -3727,7 +3727,7 @@ dependencies = [ [[package]] name = "kaspad" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-channel 2.3.1", "cfg-if 1.0.0", @@ -5151,7 +5151,7 @@ dependencies = [ [[package]] name = "rothschild" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-channel 2.3.1", "clap 4.5.16", @@ -5603,7 +5603,7 @@ dependencies = [ [[package]] name = "simpa" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-channel 2.3.1", "cfg-if 1.0.0", diff --git a/Cargo.toml b/Cargo.toml index eaf07936e..37acfb172 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ members = [ [workspace.package] rust-version = "1.81.0" -version = "0.15.1" +version = "0.15.2" 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.1", path = "testing/integration" } -kaspa-addresses = { version = "0.15.1", path = "crypto/addresses" } -kaspa-addressmanager = { version = "0.15.1", path = "components/addressmanager" } -kaspa-bip32 = { version = "0.15.1", path = "wallet/bip32" } -kaspa-cli = { version = "0.15.1", path = "cli" } -kaspa-connectionmanager = { version = "0.15.1", path = "components/connectionmanager" } -kaspa-consensus = { version = "0.15.1", path = "consensus" } -kaspa-consensus-core = { version = "0.15.1", path = "consensus/core" } -kaspa-consensus-client = { version = "0.15.1", path = "consensus/client" } -kaspa-consensus-notify = { version = "0.15.1", path = "consensus/notify" } -kaspa-consensus-wasm = { version = "0.15.1", path = "consensus/wasm" } -kaspa-consensusmanager = { version = "0.15.1", path = "components/consensusmanager" } -kaspa-core = { version = "0.15.1", path = "core" } -kaspa-daemon = { version = "0.15.1", path = "daemon" } -kaspa-database = { version = "0.15.1", path = "database" } -kaspa-grpc-client = { version = "0.15.1", path = "rpc/grpc/client" } -kaspa-grpc-core = { version = "0.15.1", path = "rpc/grpc/core" } -kaspa-grpc-server = { version = "0.15.1", path = "rpc/grpc/server" } -kaspa-hashes = { version = "0.15.1", path = "crypto/hashes" } -kaspa-index-core = { version = "0.15.1", path = "indexes/core" } -kaspa-index-processor = { version = "0.15.1", path = "indexes/processor" } -kaspa-math = { version = "0.15.1", path = "math" } -kaspa-merkle = { version = "0.15.1", path = "crypto/merkle" } -kaspa-metrics-core = { version = "0.15.1", path = "metrics/core" } -kaspa-mining = { version = "0.15.1", path = "mining" } -kaspa-mining-errors = { version = "0.15.1", path = "mining/errors" } -kaspa-muhash = { version = "0.15.1", path = "crypto/muhash" } -kaspa-notify = { version = "0.15.1", path = "notify" } -kaspa-p2p-flows = { version = "0.15.1", path = "protocol/flows" } -kaspa-p2p-lib = { version = "0.15.1", path = "protocol/p2p" } -kaspa-perf-monitor = { version = "0.15.1", path = "metrics/perf_monitor" } -kaspa-pow = { version = "0.15.1", path = "consensus/pow" } -kaspa-rpc-core = { version = "0.15.1", path = "rpc/core" } -kaspa-rpc-macros = { version = "0.15.1", path = "rpc/macros" } -kaspa-rpc-service = { version = "0.15.1", path = "rpc/service" } -kaspa-txscript = { version = "0.15.1", path = "crypto/txscript" } -kaspa-txscript-errors = { version = "0.15.1", path = "crypto/txscript/errors" } -kaspa-utils = { version = "0.15.1", path = "utils" } -kaspa-utils-tower = { version = "0.15.1", path = "utils/tower" } -kaspa-utxoindex = { version = "0.15.1", path = "indexes/utxoindex" } -kaspa-wallet = { version = "0.15.1", path = "wallet/native" } -kaspa-wallet-cli-wasm = { version = "0.15.1", path = "wallet/wasm" } -kaspa-wallet-keys = { version = "0.15.1", path = "wallet/keys" } -kaspa-wallet-pskt = { version = "0.15.1", path = "wallet/pskt" } -kaspa-wallet-core = { version = "0.15.1", path = "wallet/core" } -kaspa-wallet-macros = { version = "0.15.1", path = "wallet/macros" } -kaspa-wasm = { version = "0.15.1", path = "wasm" } -kaspa-wasm-core = { version = "0.15.1", path = "wasm/core" } -kaspa-wrpc-client = { version = "0.15.1", path = "rpc/wrpc/client" } -kaspa-wrpc-proxy = { version = "0.15.1", path = "rpc/wrpc/proxy" } -kaspa-wrpc-server = { version = "0.15.1", path = "rpc/wrpc/server" } -kaspa-wrpc-wasm = { version = "0.15.1", path = "rpc/wrpc/wasm" } -kaspa-wrpc-example-subscriber = { version = "0.15.1", path = "rpc/wrpc/examples/subscriber" } -kaspad = { version = "0.15.1", path = "kaspad" } -kaspa-alloc = { version = "0.15.1", path = "utils/alloc" } +# kaspa-testing-integration = { version = "0.15.2", path = "testing/integration" } +kaspa-addresses = { version = "0.15.2", path = "crypto/addresses" } +kaspa-addressmanager = { version = "0.15.2", path = "components/addressmanager" } +kaspa-bip32 = { version = "0.15.2", path = "wallet/bip32" } +kaspa-cli = { version = "0.15.2", path = "cli" } +kaspa-connectionmanager = { version = "0.15.2", path = "components/connectionmanager" } +kaspa-consensus = { version = "0.15.2", path = "consensus" } +kaspa-consensus-core = { version = "0.15.2", path = "consensus/core" } +kaspa-consensus-client = { version = "0.15.2", path = "consensus/client" } +kaspa-consensus-notify = { version = "0.15.2", path = "consensus/notify" } +kaspa-consensus-wasm = { version = "0.15.2", path = "consensus/wasm" } +kaspa-consensusmanager = { version = "0.15.2", path = "components/consensusmanager" } +kaspa-core = { version = "0.15.2", path = "core" } +kaspa-daemon = { version = "0.15.2", path = "daemon" } +kaspa-database = { version = "0.15.2", path = "database" } +kaspa-grpc-client = { version = "0.15.2", path = "rpc/grpc/client" } +kaspa-grpc-core = { version = "0.15.2", path = "rpc/grpc/core" } +kaspa-grpc-server = { version = "0.15.2", path = "rpc/grpc/server" } +kaspa-hashes = { version = "0.15.2", path = "crypto/hashes" } +kaspa-index-core = { version = "0.15.2", path = "indexes/core" } +kaspa-index-processor = { version = "0.15.2", path = "indexes/processor" } +kaspa-math = { version = "0.15.2", path = "math" } +kaspa-merkle = { version = "0.15.2", path = "crypto/merkle" } +kaspa-metrics-core = { version = "0.15.2", path = "metrics/core" } +kaspa-mining = { version = "0.15.2", path = "mining" } +kaspa-mining-errors = { version = "0.15.2", path = "mining/errors" } +kaspa-muhash = { version = "0.15.2", path = "crypto/muhash" } +kaspa-notify = { version = "0.15.2", path = "notify" } +kaspa-p2p-flows = { version = "0.15.2", path = "protocol/flows" } +kaspa-p2p-lib = { version = "0.15.2", path = "protocol/p2p" } +kaspa-perf-monitor = { version = "0.15.2", path = "metrics/perf_monitor" } +kaspa-pow = { version = "0.15.2", path = "consensus/pow" } +kaspa-rpc-core = { version = "0.15.2", path = "rpc/core" } +kaspa-rpc-macros = { version = "0.15.2", path = "rpc/macros" } +kaspa-rpc-service = { version = "0.15.2", path = "rpc/service" } +kaspa-txscript = { version = "0.15.2", path = "crypto/txscript" } +kaspa-txscript-errors = { version = "0.15.2", path = "crypto/txscript/errors" } +kaspa-utils = { version = "0.15.2", path = "utils" } +kaspa-utils-tower = { version = "0.15.2", path = "utils/tower" } +kaspa-utxoindex = { version = "0.15.2", path = "indexes/utxoindex" } +kaspa-wallet = { version = "0.15.2", path = "wallet/native" } +kaspa-wallet-cli-wasm = { version = "0.15.2", path = "wallet/wasm" } +kaspa-wallet-keys = { version = "0.15.2", path = "wallet/keys" } +kaspa-wallet-pskt = { version = "0.15.2", path = "wallet/pskt" } +kaspa-wallet-core = { version = "0.15.2", path = "wallet/core" } +kaspa-wallet-macros = { version = "0.15.2", path = "wallet/macros" } +kaspa-wasm = { version = "0.15.2", path = "wasm" } +kaspa-wasm-core = { version = "0.15.2", path = "wasm/core" } +kaspa-wrpc-client = { version = "0.15.2", path = "rpc/wrpc/client" } +kaspa-wrpc-proxy = { version = "0.15.2", path = "rpc/wrpc/proxy" } +kaspa-wrpc-server = { version = "0.15.2", path = "rpc/wrpc/server" } +kaspa-wrpc-wasm = { version = "0.15.2", path = "rpc/wrpc/wasm" } +kaspa-wrpc-example-subscriber = { version = "0.15.2", path = "rpc/wrpc/examples/subscriber" } +kaspad = { version = "0.15.2", path = "kaspad" } +kaspa-alloc = { version = "0.15.2", path = "utils/alloc" } # external aes = "0.8.3" diff --git a/cli/src/modules/rpc.rs b/cli/src/modules/rpc.rs index f32523c4a..f96cb5b61 100644 --- a/cli/src/modules/rpc.rs +++ b/cli/src/modules/rpc.rs @@ -121,10 +121,20 @@ impl Rpc { // let result = rpc.get_subnetwork_call(GetSubnetworkRequest { }).await?; // self.println(&ctx, result); // } - // RpcApiOps::GetVirtualChainFromBlock => { - // let result = rpc.get_virtual_chain_from_block_call(GetVirtualChainFromBlockRequest { }).await?; - // self.println(&ctx, result); - // } + RpcApiOps::GetVirtualChainFromBlock => { + if argv.is_empty() { + return Err(Error::custom("Missing startHash argument")); + }; + let start_hash = RpcHash::from_hex(argv.remove(0).as_str())?; + let include_accepted_transaction_ids = argv.remove(0).parse::().unwrap_or_default(); + let result = rpc + .get_virtual_chain_from_block_call( + None, + GetVirtualChainFromBlockRequest { start_hash, include_accepted_transaction_ids }, + ) + .await?; + self.println(&ctx, result); + } // RpcApiOps::GetBlocks => { // let result = rpc.get_blocks_call(GetBlocksRequest { }).await?; // self.println(&ctx, result); diff --git a/components/consensusmanager/src/session.rs b/components/consensusmanager/src/session.rs index 81d589148..2643739ee 100644 --- a/components/consensusmanager/src/session.rs +++ b/components/consensusmanager/src/session.rs @@ -267,8 +267,12 @@ impl ConsensusSessionOwned { self.clone().spawn_blocking(|c| c.is_nearly_synced()).await } - pub async fn async_get_virtual_chain_from_block(&self, hash: Hash) -> ConsensusResult { - self.clone().spawn_blocking(move |c| c.get_virtual_chain_from_block(hash)).await + pub async fn async_get_virtual_chain_from_block( + &self, + low: Hash, + chain_path_added_limit: Option, + ) -> ConsensusResult { + self.clone().spawn_blocking(move |c| c.get_virtual_chain_from_block(low, chain_path_added_limit)).await } pub async fn async_get_virtual_utxos( @@ -380,8 +384,12 @@ impl ConsensusSessionOwned { /// Returns acceptance data for a set of blocks belonging to the selected parent chain. /// /// See `self::get_virtual_chain` - pub async fn async_get_blocks_acceptance_data(&self, hashes: Vec) -> ConsensusResult>> { - self.clone().spawn_blocking(move |c| c.get_blocks_acceptance_data(&hashes)).await + pub async fn async_get_blocks_acceptance_data( + &self, + hashes: Vec, + merged_blocks_limit: Option, + ) -> ConsensusResult>> { + self.clone().spawn_blocking(move |c| c.get_blocks_acceptance_data(&hashes, merged_blocks_limit)).await } pub async fn async_is_chain_block(&self, hash: Hash) -> ConsensusResult { diff --git a/consensus/core/src/api/mod.rs b/consensus/core/src/api/mod.rs index 4833c7659..91165b73d 100644 --- a/consensus/core/src/api/mod.rs +++ b/consensus/core/src/api/mod.rs @@ -157,7 +157,12 @@ pub trait ConsensusApi: Send + Sync { unimplemented!() } - fn get_virtual_chain_from_block(&self, hash: Hash) -> ConsensusResult { + /// Gets the virtual chain paths from `low` to the `sink` hash, or until `chain_path_added_limit` is reached + /// + /// Note: + /// 1) `chain_path_added_limit` will populate removed fully, and then the added chain path, up to `chain_path_added_limit` amount of hashes. + /// 1.1) use `None to impose no limit with optimized backward chain iteration, for better performance in cases where batching is not required. + fn get_virtual_chain_from_block(&self, low: Hash, chain_path_added_limit: Option) -> ConsensusResult { unimplemented!() } @@ -297,7 +302,11 @@ pub trait ConsensusApi: Send + Sync { /// Returns acceptance data for a set of blocks belonging to the selected parent chain. /// /// See `self::get_virtual_chain` - fn get_blocks_acceptance_data(&self, hashes: &[Hash]) -> ConsensusResult>> { + fn get_blocks_acceptance_data( + &self, + hashes: &[Hash], + merged_blocks_limit: Option, + ) -> ConsensusResult>> { unimplemented!() } diff --git a/consensus/src/consensus/mod.rs b/consensus/src/consensus/mod.rs index 8474a6864..1731729a3 100644 --- a/consensus/src/consensus/mod.rs +++ b/consensus/src/consensus/mod.rs @@ -607,14 +607,26 @@ impl ConsensusApi for Consensus { self.config.is_nearly_synced(compact.timestamp, compact.daa_score) } - fn get_virtual_chain_from_block(&self, hash: Hash) -> ConsensusResult { - // Calculate chain changes between the given hash and the - // sink. Note that we explicitly don't + fn get_virtual_chain_from_block(&self, low: Hash, chain_path_added_limit: Option) -> ConsensusResult { + // Calculate chain changes between the given `low` and the current sink hash (up to `limit` amount of block hashes). + // Note: + // 1) that we explicitly don't // do the calculation against the virtual itself so that we // won't later need to remove it from the result. + // 2) supplying `None` as `chain_path_added_limit` will result in the full chain path, with optimized performance. let _guard = self.pruning_lock.blocking_read(); - self.validate_block_exists(hash)?; - Ok(self.services.dag_traversal_manager.calculate_chain_path(hash, self.get_sink())) + + // Verify that the block exists + self.validate_block_exists(low)?; + + // Verify that source is on chain(block) + self.services + .reachability_service + .is_chain_ancestor_of(self.get_source(), low) + .then_some(()) + .ok_or(ConsensusError::General("the queried hash does not have source on its chain"))?; + + Ok(self.services.dag_traversal_manager.calculate_chain_path(low, self.get_sink(), chain_path_added_limit)) } /// Returns a Vec of header samples since genesis @@ -914,11 +926,35 @@ impl ConsensusApi for Consensus { self.acceptance_data_store.get(hash).unwrap_option().ok_or(ConsensusError::MissingData(hash)) } - fn get_blocks_acceptance_data(&self, hashes: &[Hash]) -> ConsensusResult>> { + fn get_blocks_acceptance_data( + &self, + hashes: &[Hash], + merged_blocks_limit: Option, + ) -> ConsensusResult>> { + // Note: merged_blocks_limit will limit after the sum of merged blocks is breached along the supplied hash's acceptance data + // and not limit the acceptance data within a queried hash. i.e. It has mergeset_size_limit granularity, this is to guarantee full acceptance data coverage. + if merged_blocks_limit.is_none() { + return hashes + .iter() + .copied() + .map(|hash| self.acceptance_data_store.get(hash).unwrap_option().ok_or(ConsensusError::MissingData(hash))) + .collect::>>(); + } + let merged_blocks_limit = merged_blocks_limit.unwrap(); // we handle `is_none`, so may unwrap. + let mut num_of_merged_blocks = 0usize; + hashes .iter() .copied() - .map(|hash| self.acceptance_data_store.get(hash).unwrap_option().ok_or(ConsensusError::MissingData(hash))) + .map_while(|hash| { + let entry = self.acceptance_data_store.get(hash).unwrap_option().ok_or(ConsensusError::MissingData(hash)); + num_of_merged_blocks += entry.as_ref().map_or(0, |entry| entry.len()); + if num_of_merged_blocks > merged_blocks_limit { + None + } else { + Some(entry) + } + }) .collect::>>() } diff --git a/consensus/src/pipeline/virtual_processor/processor.rs b/consensus/src/pipeline/virtual_processor/processor.rs index dfb7394b8..88fee97bf 100644 --- a/consensus/src/pipeline/virtual_processor/processor.rs +++ b/consensus/src/pipeline/virtual_processor/processor.rs @@ -290,7 +290,7 @@ impl VirtualStateProcessor { assert_eq!(virtual_ghostdag_data.selected_parent, new_sink); let sink_multiset = self.utxo_multisets_store.get(new_sink).unwrap(); - let chain_path = self.dag_traversal_manager.calculate_chain_path(prev_sink, new_sink); + let chain_path = self.dag_traversal_manager.calculate_chain_path(prev_sink, new_sink, None); let new_virtual_state = self .calculate_and_commit_virtual_state( virtual_read, diff --git a/consensus/src/processes/sync/mod.rs b/consensus/src/processes/sync/mod.rs index 847222968..3978913ba 100644 --- a/consensus/src/processes/sync/mod.rs +++ b/consensus/src/processes/sync/mod.rs @@ -111,7 +111,7 @@ impl< (blocks, highest_reached) } - fn find_highest_common_chain_block(&self, low: Hash, high: Hash) -> Hash { + pub fn find_highest_common_chain_block(&self, low: Hash, high: Hash) -> Hash { self.reachability_service .default_backward_chain_iterator(low) .find(|candidate| self.reachability_service.is_chain_ancestor_of(*candidate, high)) diff --git a/consensus/src/processes/traversal_manager.rs b/consensus/src/processes/traversal_manager.rs index 3ae0aef5d..23dc5c69f 100644 --- a/consensus/src/processes/traversal_manager.rs +++ b/consensus/src/processes/traversal_manager.rs @@ -31,7 +31,7 @@ impl ChainPath { + pub fn calculate_chain_path(&self, from: Hash, to: Hash, chain_path_added_limit: Option) -> ChainPath { let mut removed = Vec::new(); let mut common_ancestor = from; for current in self.reachability_service.default_backward_chain_iterator(from) { @@ -42,9 +42,20 @@ impl {} TxRemovalReason::DoubleSpend => match removed_transactions.len() { 0 => {} - 1 => warn!("Removed transaction ({}) {}{}", reason, removed_transactions[0], extra_info), - n => warn!( + 1 => debug!("Removed transaction ({}) {}{}", reason, removed_transactions[0], extra_info), + n => debug!( "Removed {} transactions ({}): {}{}", n, reason, diff --git a/rpc/grpc/core/proto/rpc.proto b/rpc/grpc/core/proto/rpc.proto index 8c3e7b3b2..e218681b6 100644 --- a/rpc/grpc/core/proto/rpc.proto +++ b/rpc/grpc/core/proto/rpc.proto @@ -374,8 +374,13 @@ message GetSubnetworkResponseMessage{ RPCError error = 1000; } -// GetVirtualChainFromBlockRequestMessage requests the virtual selected -// parent chain from some startHash to this kaspad's current virtual +/// GetVirtualChainFromBlockRequestMessage requests the virtual selected +/// parent chain from some startHash to this kaspad's current virtual +/// Note: +/// this call batches the response to: +/// a. the network's `mergeset size limit * 10` amount of added chain blocks, if `includeAcceptedTransactionIds = false` +/// b. or `mergeset size limit * 10` amount of merged blocks, if `includeAcceptedTransactionIds = true` +/// c. it does not batch the removed chain blocks, only the added ones. message GetVirtualChainFromBlockRequestMessage{ string startHash = 1; bool includeAcceptedTransactionIds = 2; diff --git a/rpc/service/src/converter/consensus.rs b/rpc/service/src/converter/consensus.rs index a5e5d3b51..c744300e5 100644 --- a/rpc/service/src/converter/consensus.rs +++ b/rpc/service/src/converter/consensus.rs @@ -162,8 +162,9 @@ impl ConsensusConverter { &self, consensus: &ConsensusProxy, chain_path: &ChainPath, + merged_blocks_limit: Option, ) -> RpcResult> { - let acceptance_data = consensus.async_get_blocks_acceptance_data(chain_path.added.clone()).await.unwrap(); + let acceptance_data = consensus.async_get_blocks_acceptance_data(chain_path.added.clone(), merged_blocks_limit).await.unwrap(); Ok(chain_path .added .iter() diff --git a/rpc/service/src/service.rs b/rpc/service/src/service.rs index 2c22fd6bb..bdc2a9541 100644 --- a/rpc/service/src/service.rs +++ b/rpc/service/src/service.rs @@ -539,7 +539,7 @@ NOTE: This error usually indicates an RPC conversion error between the node and ) -> RpcResult { let allow_orphan = self.config.unsafe_rpc && request.allow_orphan; if !self.config.unsafe_rpc && request.allow_orphan { - warn!("SubmitTransaction RPC command called with AllowOrphan enabled while node in safe RPC mode -- switching to ForbidOrphan."); + debug!("SubmitTransaction RPC command called with AllowOrphan enabled while node in safe RPC mode -- switching to ForbidOrphan."); } let transaction: Transaction = request.transaction.try_into()?; @@ -609,13 +609,26 @@ NOTE: This error usually indicates an RPC conversion error between the node and request: GetVirtualChainFromBlockRequest, ) -> RpcResult { let session = self.consensus_manager.consensus().session().await; - let virtual_chain = session.async_get_virtual_chain_from_block(request.start_hash).await?; + + // batch_size is set to 10 times the mergeset_size_limit. + // this means batch_size is 2480 on 10 bps, and 1800 on mainnet. + // this bounds by number of merged blocks, if include_accepted_transactions = true + // else it returns the batch_size amount on pure chain blocks. + // Note: batch_size does not bound removed chain blocks, only added chain blocks. + let batch_size = (self.config.mergeset_size_limit * 10) as usize; + let mut virtual_chain_batch = session.async_get_virtual_chain_from_block(request.start_hash, Some(batch_size)).await?; let accepted_transaction_ids = if request.include_accepted_transaction_ids { - self.consensus_converter.get_virtual_chain_accepted_transaction_ids(&session, &virtual_chain).await? + let accepted_transaction_ids = self + .consensus_converter + .get_virtual_chain_accepted_transaction_ids(&session, &virtual_chain_batch, Some(batch_size)) + .await?; + // bound added to the length of the accepted transaction ids, which is bounded by merged blocks + virtual_chain_batch.added = virtual_chain_batch.added[..accepted_transaction_ids.len()].to_vec(); + accepted_transaction_ids } else { vec![] }; - Ok(GetVirtualChainFromBlockResponse::new(virtual_chain.removed, virtual_chain.added, accepted_transaction_ids)) + Ok(GetVirtualChainFromBlockResponse::new(virtual_chain_batch.removed, virtual_chain_batch.added, accepted_transaction_ids)) } async fn get_block_count_call( diff --git a/testing/integration/src/daemon_integration_tests.rs b/testing/integration/src/daemon_integration_tests.rs index a92320294..460cf049c 100644 --- a/testing/integration/src/daemon_integration_tests.rs +++ b/testing/integration/src/daemon_integration_tests.rs @@ -106,7 +106,13 @@ async fn daemon_mining_test() { assert_eq!(dag_info.sink, last_block_hash.unwrap()); // Check that acceptance data contains the expected coinbase tx ids - let vc = rpc_client2.get_virtual_chain_from_block(kaspa_consensus::params::SIMNET_GENESIS.hash, true).await.unwrap(); + let vc = rpc_client2 + .get_virtual_chain_from_block( + kaspa_consensus::params::SIMNET_GENESIS.hash, // + true, + ) + .await + .unwrap(); assert_eq!(vc.removed_chain_block_hashes.len(), 0); assert_eq!(vc.added_chain_block_hashes.len(), 10); assert_eq!(vc.accepted_transaction_ids.len(), 10); From 4d03153f9cb3d7e6674ac7c28c1956c0f4b75a03 Mon Sep 17 00:00:00 2001 From: Michael Sutton Date: Fri, 20 Sep 2024 01:58:28 +0300 Subject: [PATCH 03/19] A few CLI rpc query fixes (#563) --- cli/src/modules/rpc.rs | 5 +++-- rpc/core/src/model/tx.rs | 34 +++++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/cli/src/modules/rpc.rs b/cli/src/modules/rpc.rs index f96cb5b61..cf6bc6bd2 100644 --- a/cli/src/modules/rpc.rs +++ b/cli/src/modules/rpc.rs @@ -114,7 +114,8 @@ impl Rpc { } let hash = argv.remove(0); let hash = RpcHash::from_hex(hash.as_str())?; - let result = rpc.get_block_call(None, GetBlockRequest { hash, include_transactions: true }).await?; + let include_transactions = argv.first().and_then(|x| x.parse::().ok()).unwrap_or(true); + let result = rpc.get_block_call(None, GetBlockRequest { hash, include_transactions }).await?; self.println(&ctx, result); } // RpcApiOps::GetSubnetwork => { @@ -126,7 +127,7 @@ impl Rpc { return Err(Error::custom("Missing startHash argument")); }; let start_hash = RpcHash::from_hex(argv.remove(0).as_str())?; - let include_accepted_transaction_ids = argv.remove(0).parse::().unwrap_or_default(); + let include_accepted_transaction_ids = argv.first().and_then(|x| x.parse::().ok()).unwrap_or_default(); let result = rpc .get_virtual_chain_from_block_call( None, diff --git a/rpc/core/src/model/tx.rs b/rpc/core/src/model/tx.rs index c2b91f187..0c17e26f5 100644 --- a/rpc/core/src/model/tx.rs +++ b/rpc/core/src/model/tx.rs @@ -4,7 +4,7 @@ use kaspa_consensus_core::tx::{ ScriptPublicKey, ScriptVec, TransactionId, TransactionIndexType, TransactionInput, TransactionOutpoint, TransactionOutput, UtxoEntry, }; -use kaspa_utils::serde_bytes_fixed_ref; +use kaspa_utils::{hex::ToHex, serde_bytes_fixed_ref}; use serde::{Deserialize, Serialize}; use workflow_serializer::prelude::*; @@ -131,7 +131,7 @@ impl Deserializer for RpcTransactionOutpoint { } /// Represents a Kaspa transaction input -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RpcTransactionInput { pub previous_outpoint: RpcTransactionOutpoint, @@ -142,6 +142,18 @@ pub struct RpcTransactionInput { pub verbose_data: Option, } +impl std::fmt::Debug for RpcTransactionInput { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RpcTransactionInput") + .field("previous_outpoint", &self.previous_outpoint) + .field("signature_script", &self.signature_script.to_hex()) + .field("sequence", &self.sequence) + .field("sig_op_count", &self.sig_op_count) + .field("verbose_data", &self.verbose_data) + .finish() + } +} + impl From for RpcTransactionInput { fn from(input: TransactionInput) -> Self { Self { @@ -277,7 +289,7 @@ impl Deserializer for RpcTransactionOutputVerboseData { } /// Represents a Kaspa transaction -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RpcTransaction { pub version: u16, @@ -292,6 +304,22 @@ pub struct RpcTransaction { pub verbose_data: Option, } +impl std::fmt::Debug for RpcTransaction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RpcTransaction") + .field("version", &self.version) + .field("lock_time", &self.lock_time) + .field("subnetwork_id", &self.subnetwork_id) + .field("gas", &self.gas) + .field("payload", &self.payload.to_hex()) + .field("mass", &self.mass) + .field("inputs", &self.inputs) // Inputs and outputs are placed purposely at the end for better debug visibility + .field("outputs", &self.outputs) + .field("verbose_data", &self.verbose_data) + .finish() + } +} + impl Serializer for RpcTransaction { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { store!(u16, &1, writer)?; From 9fae376500c3b7bde4ac0d0f03f15d47a4d6f12c Mon Sep 17 00:00:00 2001 From: Michael Sutton Date: Fri, 20 Sep 2024 13:31:55 +0300 Subject: [PATCH 04/19] Deploy linux binary without musl in its name + various minor miscellaneous things towards v0.15.2 (#564) * remove musl from linux binary name * remove simpa from win and osx builds in order to be consistent with linux build * safe eqv optimization: use inplace truncate (tested) --- .github/workflows/deploy.yaml | 8 ++------ rpc/service/src/service.rs | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 8f46cb1fe..537eeef89 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -65,8 +65,8 @@ jobs: cp target/x86_64-unknown-linux-musl/release/kaspad bin/ cp target/x86_64-unknown-linux-musl/release/rothschild bin/ cp target/x86_64-unknown-linux-musl/release/kaspa-wallet bin/ - archive="bin/rusty-kaspa-${{ github.event.release.tag_name }}-linux-musl-amd64.zip" - asset_name="rusty-kaspa-${{ github.event.release.tag_name }}-linux-musl-amd64.zip" + archive="bin/rusty-kaspa-${{ github.event.release.tag_name }}-linux-amd64.zip" + asset_name="rusty-kaspa-${{ github.event.release.tag_name }}-linux-amd64.zip" zip -r "${archive}" ./bin/* echo "archive=${archive}" >> $GITHUB_ENV echo "asset_name=${asset_name}" >> $GITHUB_ENV @@ -76,12 +76,10 @@ jobs: shell: bash run: | cargo build --bin kaspad --release - cargo build --bin simpa --release cargo build --bin rothschild --release cargo build --bin kaspa-wallet --release mkdir bin || true cp target/release/kaspad.exe bin/ - cp target/release/simpa.exe bin/ cp target/release/rothschild.exe bin/ cp target/release/kaspa-wallet.exe bin/ archive="bin/rusty-kaspa-${{ github.event.release.tag_name }}-win64.zip" @@ -94,12 +92,10 @@ jobs: if: runner.os == 'macOS' run: | cargo build --bin kaspad --release - cargo build --bin simpa --release cargo build --bin rothschild --release cargo build --bin kaspa-wallet --release mkdir bin || true cp target/release/kaspad bin/ - cp target/release/simpa bin/ cp target/release/rothschild bin/ cp target/release/kaspa-wallet bin/ archive="bin/rusty-kaspa-${{ github.event.release.tag_name }}-osx.zip" diff --git a/rpc/service/src/service.rs b/rpc/service/src/service.rs index bdc2a9541..d75ff770b 100644 --- a/rpc/service/src/service.rs +++ b/rpc/service/src/service.rs @@ -623,7 +623,7 @@ NOTE: This error usually indicates an RPC conversion error between the node and .get_virtual_chain_accepted_transaction_ids(&session, &virtual_chain_batch, Some(batch_size)) .await?; // bound added to the length of the accepted transaction ids, which is bounded by merged blocks - virtual_chain_batch.added = virtual_chain_batch.added[..accepted_transaction_ids.len()].to_vec(); + virtual_chain_batch.added.truncate(accepted_transaction_ids.len()); accepted_transaction_ids } else { vec![] From d66cbe3300bb54adfbbf38327881b20b2909d3ba Mon Sep 17 00:00:00 2001 From: demisrael <81626907+demisrael@users.noreply.github.com> Date: Mon, 23 Sep 2024 08:02:58 +0300 Subject: [PATCH 05/19] rothschild: donate funds to external address with custom priority fee (#482) * rothschild: donate funds to external address Signed-off-by: Dmitry Perchanov * rothschild: Append priority fee to txs. Signed-off-by: Dmitry Perchanov * rothschild: add option to choose and randomize fee Signed-off-by: Dmitry Perchanov * rothschild: address clippy formatting issues Signed-off-by: Dmitry Perchanov --------- Signed-off-by: Dmitry Perchanov Signed-off-by: Dmitry Perchanov Co-authored-by: coderofstuff <114628839+coderofstuff@users.noreply.github.com> Co-authored-by: Dmitry Perchanov --- rothschild/src/main.rs | 76 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 6 deletions(-) diff --git a/rothschild/src/main.rs b/rothschild/src/main.rs index d303f1da0..35d08493b 100644 --- a/rothschild/src/main.rs +++ b/rothschild/src/main.rs @@ -17,7 +17,10 @@ use kaspa_rpc_core::{api::rpc::RpcApi, notify::mode::NotificationMode, RpcUtxoEn use kaspa_txscript::pay_to_address_script; use parking_lot::Mutex; use rayon::prelude::*; -use secp256k1::{rand::thread_rng, Keypair}; +use secp256k1::{ + rand::{thread_rng, Rng}, + Keypair, +}; use tokio::time::{interval, MissedTickBehavior}; const DEFAULT_SEND_AMOUNT: u64 = 10 * SOMPI_PER_KASPA; @@ -40,6 +43,9 @@ pub struct Args { pub rpc_server: String, pub threads: u8, pub unleashed: bool, + pub addr: Option, + pub priority_fee: u64, + pub randomize_fee: bool, } impl Args { @@ -51,6 +57,9 @@ impl Args { rpc_server: m.get_one::("rpcserver").cloned().unwrap_or("localhost:16210".to_owned()), threads: m.get_one::("threads").cloned().unwrap(), unleashed: m.get_one::("unleashed").cloned().unwrap_or(false), + addr: m.get_one::("addr").cloned(), + priority_fee: m.get_one::("priority-fee").cloned().unwrap_or(0), + randomize_fee: m.get_one::("randomize-fee").cloned().unwrap_or(false), } } } @@ -85,6 +94,25 @@ pub fn cli() -> Command { .help("The number of threads to use for TX generation. Set to 0 to use 1 thread per core. Default is 2."), ) .arg(Arg::new("unleashed").long("unleashed").action(ArgAction::SetTrue).hide(true).help("Allow higher TPS")) + .arg(Arg::new("addr").long("to-addr").short('a').value_name("addr").help("address to send to")) + .arg( + Arg::new("priority-fee") + .long("priority-fee") + .short('f') + .value_name("priority-fee") + .default_value("0") + .value_parser(clap::value_parser!(u64)) + .help("Transaction priority fee"), + ) + .arg( + Arg::new("randomize-fee") + .long("randomize-fee") + .short('r') + .value_name("randomize-fee") + .action(ArgAction::SetTrue) + .default_value("false") + .help("Randomize transaction priority fee"), + ) } async fn new_rpc_client(subscription_context: &SubscriptionContext, address: &str) -> GrpcClient { @@ -111,6 +139,11 @@ struct ClientPoolArg { utxos_len: usize, } +struct TxsFeeConfig { + priority_fee: u64, + randomize_fee: bool, +} + #[tokio::main] async fn main() { kaspa_core::log::init_logger(None, ""); @@ -150,9 +183,31 @@ async fn main() { let kaspa_addr = Address::new(ADDRESS_PREFIX, ADDRESS_VERSION, &schnorr_key.x_only_public_key().0.serialize()); + let kaspa_to_addr = args.addr.as_ref().map_or_else(|| kaspa_addr.clone(), |addr_str| Address::try_from(addr_str.clone()).unwrap()); + + let fee_config = TxsFeeConfig { priority_fee: args.priority_fee, randomize_fee: args.randomize_fee }; + rayon::ThreadPoolBuilder::new().num_threads(args.threads as usize).build_global().unwrap(); - info!("Using Rothschild with private key {} and address {}", schnorr_key.display_secret(), String::from(&kaspa_addr)); + let mut log_message = format!( + "Using Rothschild with:\n\ + \tprivate key: {}\n\ + \tfrom address: {}", + schnorr_key.display_secret(), + String::from(&kaspa_addr) + ); + if args.addr.is_some() { + log_message.push_str(&format!("\n\tto address: {}", String::from(&kaspa_to_addr))); + } + if args.priority_fee != 0 { + log_message.push_str(&format!( + "\n\tpriority fee: {} SOMPS {}", + fee_config.priority_fee, + if fee_config.randomize_fee { "[randomize]" } else { "" } + )); + } + info!("{}", log_message); + let info = rpc_client.get_block_dag_info().await.unwrap(); let coinbase_maturity = match info.network.suffix { Some(11) => TESTNET11_PARAMS.coinbase_maturity, @@ -249,13 +304,14 @@ async fn main() { let has_funds = maybe_send_tx( txs_to_send, &tx_sender, - kaspa_addr.clone(), + kaspa_to_addr.clone(), &mut utxos, &mut pending, schnorr_key, stats.clone(), maximize_inputs, &mut next_available_utxo_index, + &fee_config, ) .await; if !has_funds { @@ -369,6 +425,7 @@ async fn maybe_send_tx( stats: Arc>, maximize_inputs: bool, next_available_utxo_index: &mut usize, + fee_config: &TxsFeeConfig, ) -> bool { let num_outs = if maximize_inputs { 1 } else { 2 }; @@ -377,7 +434,7 @@ async fn maybe_send_tx( let selected_utxos_groups = (0..txs_to_send) .map(|_| { let (selected_utxos, selected_amount) = - select_utxos(utxos, DEFAULT_SEND_AMOUNT, num_outs, maximize_inputs, next_available_utxo_index); + select_utxos(utxos, DEFAULT_SEND_AMOUNT, num_outs, maximize_inputs, next_available_utxo_index, fee_config); if selected_amount == 0 { return None; } @@ -473,10 +530,12 @@ fn select_utxos( num_outs: u64, maximize_utxos: bool, next_available_utxo_index: &mut usize, + fee_config: &TxsFeeConfig, ) -> (Vec<(TransactionOutpoint, UtxoEntry)>, u64) { const MAX_UTXOS: usize = 84; let mut selected_amount: u64 = 0; let mut selected = Vec::new(); + let mut rng = thread_rng(); while next_available_utxo_index < &mut utxos.len() { let (outpoint, entry) = utxos[*next_available_utxo_index].clone(); @@ -484,11 +543,16 @@ fn select_utxos( selected.push((outpoint, entry)); let fee = required_fee(selected.len(), num_outs); + let priority_fee = if fee_config.randomize_fee && fee_config.priority_fee > 0 { + rng.gen_range(0..fee_config.priority_fee) + } else { + fee_config.priority_fee + }; *next_available_utxo_index += 1; - if selected_amount >= min_amount + fee && (!maximize_utxos || selected.len() == MAX_UTXOS) { - return (selected, selected_amount - fee); + if selected_amount >= min_amount + fee + priority_fee && (!maximize_utxos || selected.len() == MAX_UTXOS) { + return (selected, selected_amount - fee - priority_fee); } if selected.len() > MAX_UTXOS { From 4bfa392922fff59c1e248b7b141d07b35f3294bc Mon Sep 17 00:00:00 2001 From: Maxim <59533214+biryukovmaxim@users.noreply.github.com> Date: Tue, 24 Sep 2024 21:45:43 +0400 Subject: [PATCH 06/19] fix wrong combiner condition (#567) --- wallet/pskt/src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/pskt/src/utils.rs b/wallet/pskt/src/utils.rs index 28b7959ed..cab211951 100644 --- a/wallet/pskt/src/utils.rs +++ b/wallet/pskt/src/utils.rs @@ -6,7 +6,7 @@ where V: Eq + Clone, K: Ord + Clone, { - if lhs.len() > rhs.len() { + if lhs.len() >= rhs.len() { if let Some((field, rhs, lhs)) = rhs.iter().map(|(k, v)| (k, v, lhs.get(k))).find(|(_, v, rhs_v)| rhs_v.is_some_and(|rv| rv != *v)) { From 200b8ea63a6786d784713de092810a6854b81880 Mon Sep 17 00:00:00 2001 From: aspect Date: Fri, 27 Sep 2024 02:09:57 +0300 Subject: [PATCH 07/19] fix wRPC json notification format (#571) --- rpc/wrpc/server/src/connection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/wrpc/server/src/connection.rs b/rpc/wrpc/server/src/connection.rs index 97a4e8adc..e118d161d 100644 --- a/rpc/wrpc/server/src/connection.rs +++ b/rpc/wrpc/server/src/connection.rs @@ -134,7 +134,7 @@ impl Connection { { match encoding { Encoding::Borsh => workflow_rpc::server::protocol::borsh::create_serialized_notification_message(op, msg), - Encoding::SerdeJson => workflow_rpc::server::protocol::borsh::create_serialized_notification_message(op, msg), + Encoding::SerdeJson => workflow_rpc::server::protocol::serde_json::create_serialized_notification_message(op, msg), } } } From 035a394d144bac7f142c9556f65da4612c24a8fe Mon Sep 17 00:00:00 2001 From: aspect Date: Fri, 27 Sep 2024 05:59:59 +0300 Subject: [PATCH 08/19] Documentation updates (#570) * docs * Export ConsensusSessionOwned * add CI pass to run `cargo doc` * module rust docs * lints * fix typos * replace glob import terminology with "re-exports" * cleanup --- .github/workflows/ci.yaml | 3 + cli/src/modules/connect.rs | 4 +- components/consensusmanager/src/lib.rs | 3 +- components/consensusmanager/src/session.rs | 9 +-- consensus/client/src/error.rs | 2 + consensus/client/src/hash.rs | 7 +++ consensus/client/src/header.rs | 11 ++++ consensus/client/src/input.rs | 13 ++++ consensus/client/src/lib.rs | 14 +++++ consensus/client/src/outpoint.rs | 48 +++++++++----- consensus/client/src/output.rs | 13 ++++ consensus/client/src/result.rs | 2 + consensus/client/src/serializable/mod.rs | 22 +++++++ consensus/client/src/serializable/numeric.rs | 8 ++- consensus/client/src/serializable/string.rs | 5 +- consensus/client/src/sign.rs | 4 ++ consensus/client/src/transaction.rs | 7 +++ consensus/client/src/utils.rs | 6 ++ consensus/client/src/utxo.rs | 19 ++++++ consensus/core/src/api/mod.rs | 2 +- consensus/core/src/config/bps.rs | 2 +- consensus/core/src/lib.rs | 6 ++ consensus/core/src/network.rs | 13 ++++ consensus/core/src/tx.rs | 9 +++ consensus/src/processes/ghostdag/protocol.rs | 2 +- .../src/processes/reachability/interval.rs | 4 +- crypto/addresses/src/lib.rs | 24 ++++++- crypto/txscript/src/opcodes/mod.rs | 2 +- indexes/utxoindex/src/core/errors.rs | 4 +- indexes/utxoindex/src/index.rs | 5 +- indexes/utxoindex/src/update_container.rs | 4 +- metrics/core/src/data.rs | 59 ----------------- mining/src/feerate/fee_estimation.ipynb | 10 +-- .../src/mempool/model/frontier/search_tree.rs | 5 +- notify/src/address/tracker.rs | 4 +- notify/src/notifier.rs | 4 +- .../flows/src/flowcontext/transactions.rs | 2 +- rpc/core/src/api/connection.rs | 4 ++ rpc/core/src/api/ctl.rs | 4 ++ rpc/core/src/api/mod.rs | 4 ++ rpc/core/src/api/notifications.rs | 4 ++ rpc/core/src/api/ops.rs | 4 ++ rpc/core/src/api/rpc.rs | 8 ++- rpc/core/src/convert/block.rs | 2 + rpc/core/src/convert/mod.rs | 4 ++ rpc/core/src/convert/notification.rs | 2 + rpc/core/src/convert/scope.rs | 2 + rpc/core/src/convert/tx.rs | 2 + rpc/core/src/convert/utxo.rs | 2 + rpc/core/src/error.rs | 4 ++ rpc/core/src/lib.rs | 14 +++++ rpc/core/src/model/mod.rs | 3 + rpc/core/src/notify/mod.rs | 4 ++ rpc/core/src/wasm/convert.rs | 4 ++ rpc/core/src/wasm/message.rs | 4 ++ rpc/core/src/wasm/mod.rs | 2 + rpc/grpc/client/src/lib.rs | 2 +- rpc/grpc/core/src/convert/message.rs | 2 +- rpc/wrpc/client/src/client.rs | 13 ++-- rpc/wrpc/client/src/error.rs | 2 + rpc/wrpc/client/src/lib.rs | 16 +++++ rpc/wrpc/client/src/node.rs | 2 + rpc/wrpc/client/src/parse.rs | 2 + rpc/wrpc/client/src/prelude.rs | 2 + rpc/wrpc/client/src/resolver.rs | 25 +++++++- rpc/wrpc/client/src/result.rs | 2 + rpc/wrpc/wasm/src/client.rs | 9 ++- rpc/wrpc/wasm/src/lib.rs | 4 ++ rpc/wrpc/wasm/src/notify.rs | 4 ++ rpc/wrpc/wasm/src/resolver.rs | 4 ++ simpa/src/main.rs | 2 +- utils/src/lib.rs | 6 ++ utils/src/option.rs | 2 +- wallet/bip32/src/address_type.rs | 5 ++ wallet/bip32/src/lib.rs | 2 + wallet/bip32/src/private_key.rs | 1 + wallet/bip32/src/public_key.rs | 2 +- wallet/core/src/account/descriptor.rs | 5 ++ wallet/core/src/account/kind.rs | 5 ++ wallet/core/src/account/pskb.rs | 5 ++ wallet/core/src/api/message.rs | 21 ++++--- wallet/core/src/api/mod.rs | 2 + wallet/core/src/api/traits.rs | 26 +++++++- wallet/core/src/api/transport.rs | 4 +- wallet/core/src/compat/mod.rs | 4 ++ wallet/core/src/cryptobox.rs | 13 +++- wallet/core/src/events.rs | 6 ++ wallet/core/src/factory.rs | 3 + wallet/core/src/lib.rs | 63 +++++++------------ wallet/core/src/message.rs | 1 + wallet/core/src/metrics.rs | 29 +++------ wallet/core/src/prelude.rs | 3 +- wallet/core/src/rpc.rs | 8 ++- wallet/core/src/settings.rs | 5 ++ wallet/core/src/tx/payment.rs | 10 +++ wallet/core/src/utxo/processor.rs | 4 +- wallet/core/src/wallet/api.rs | 10 +-- wallet/core/src/wallet/args.rs | 1 - wallet/core/src/wallet/maps.rs | 1 + wallet/core/src/wallet/mod.rs | 17 +++-- wallet/core/src/wasm/cryptobox.rs | 2 +- wallet/core/src/wasm/signer.rs | 4 +- wallet/keys/src/derivation/gen0/mod.rs | 2 +- wallet/keys/src/derivation/gen1/mod.rs | 3 +- wallet/keys/src/derivation/mod.rs | 4 ++ wallet/keys/src/derivation_path.rs | 6 ++ wallet/keys/src/keypair.rs | 8 ++- wallet/keys/src/lib.rs | 7 +++ wallet/keys/src/prelude.rs | 4 ++ wallet/keys/src/privatekey.rs | 6 +- wallet/keys/src/privkeygen.rs | 4 ++ wallet/keys/src/pubkeygen.rs | 4 ++ wallet/keys/src/publickey.rs | 4 +- wallet/keys/src/secret.rs | 4 +- wallet/keys/src/types.rs | 2 +- wallet/keys/src/xprv.rs | 4 ++ wallet/keys/src/xpub.rs | 4 ++ wallet/pskt/src/bundle.rs | 5 ++ wallet/pskt/src/convert.rs | 6 ++ wallet/pskt/src/error.rs | 2 + wallet/pskt/src/global.rs | 2 + wallet/pskt/src/input.rs | 2 + wallet/pskt/src/output.rs | 2 + wallet/pskt/src/pskt.rs | 21 +++++++ wallet/pskt/src/role.rs | 2 + wallet/pskt/src/utils.rs | 2 + wasm/src/lib.rs | 31 ++++++--- 127 files changed, 701 insertions(+), 239 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 693266885..49fe4e463 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -126,6 +126,9 @@ jobs: - name: Run cargo doc tests with features=no-asm on kaspa-hashes run: cargo test --doc --release -p kaspa-hashes --features=no-asm + - name: Run cargo doc + run: cargo doc --release --no-deps --workspace + # test-release: # name: Test Suite Release # runs-on: ${{ matrix.os }} diff --git a/cli/src/modules/connect.rs b/cli/src/modules/connect.rs index 024f7e693..a755915d4 100644 --- a/cli/src/modules/connect.rs +++ b/cli/src/modules/connect.rs @@ -14,11 +14,11 @@ impl Connect { let (is_public, url) = match arg_or_server_address.as_deref() { Some("public") => { tprintln!(ctx, "Connecting to a public node"); - (true, Resolver::default().fetch(WrpcEncoding::Borsh, network_id).await.map_err(|e| e.to_string())?.url) + (true, Resolver::default().get_url(WrpcEncoding::Borsh, network_id).await.map_err(|e| e.to_string())?) } None => { tprintln!(ctx, "No server set, connecting to a public node"); - (true, Resolver::default().fetch(WrpcEncoding::Borsh, network_id).await.map_err(|e| e.to_string())?.url) + (true, Resolver::default().get_url(WrpcEncoding::Borsh, network_id).await.map_err(|e| e.to_string())?) } Some(url) => { (false, wrpc_client.parse_url_with_network_type(url.to_string(), network_id.into()).map_err(|e| e.to_string())?) diff --git a/components/consensusmanager/src/lib.rs b/components/consensusmanager/src/lib.rs index 54bdda40b..6d31653aa 100644 --- a/components/consensusmanager/src/lib.rs +++ b/components/consensusmanager/src/lib.rs @@ -9,7 +9,8 @@ mod session; pub use batch::BlockProcessingBatch; pub use session::{ - spawn_blocking, ConsensusInstance, ConsensusProxy, ConsensusSessionBlocking, SessionLock, SessionReadGuard, SessionWriteGuard, + spawn_blocking, ConsensusInstance, ConsensusProxy, ConsensusSessionBlocking, ConsensusSessionOwned, SessionLock, SessionReadGuard, + SessionWriteGuard, }; /// Consensus controller trait. Includes methods required to start/stop/control consensus, but which should not diff --git a/components/consensusmanager/src/session.rs b/components/consensusmanager/src/session.rs index 2643739ee..8e0c6e933 100644 --- a/components/consensusmanager/src/session.rs +++ b/components/consensusmanager/src/session.rs @@ -91,7 +91,7 @@ impl ConsensusInstance { /// Returns an unguarded *blocking* consensus session. There's no guarantee that data will not be pruned between /// two sequential consensus calls. This session doesn't hold the consensus pruning lock, so it should - /// be preferred upon [`session_blocking`] when data consistency is not important. + /// be preferred upon [`session_blocking()`](Self::session_blocking) when data consistency is not important. pub fn unguarded_session_blocking(&self) -> ConsensusSessionBlocking<'static> { ConsensusSessionBlocking::new_without_session_guard(self.consensus.clone()) } @@ -100,7 +100,7 @@ impl ConsensusInstance { /// that consensus state is consistent between operations, that is, no pruning was performed between the calls. /// The returned object is an *owned* consensus session type which can be cloned and shared across threads. /// The sharing ability is useful for spawning blocking operations on a different thread using the same - /// session object, see [`ConsensusSessionOwned::spawn_blocking`]. The caller is responsible to make sure + /// session object, see [`ConsensusSessionOwned::spawn_blocking()`](ConsensusSessionOwned::spawn_blocking). The caller is responsible to make sure /// that the overall lifetime of this session is not too long (~2 seconds max) pub async fn session(&self) -> ConsensusSessionOwned { let g = self.session_lock.read_owned().await; @@ -109,7 +109,7 @@ impl ConsensusInstance { /// Returns an unguarded consensus session. There's no guarantee that data will not be pruned between /// two sequential consensus calls. This session doesn't hold the consensus pruning lock, so it should - /// be preferred upon [`session`] when data consistency is not important. + /// be preferred upon [`session()`](Self::session) when data consistency is not important. pub fn unguarded_session(&self) -> ConsensusSessionOwned { ConsensusSessionOwned::new_without_session_guard(self.consensus.clone()) } @@ -139,7 +139,8 @@ impl Deref for ConsensusSessionBlocking<'_> { } /// An *owned* consensus session type which can be cloned and shared across threads. -/// See method `spawn_blocking` within for context on the usefulness of this type +/// See method `spawn_blocking` within for context on the usefulness of this type. +/// Please note - you must use [`ConsensusProxy`] type alias instead of this struct. #[derive(Clone)] pub struct ConsensusSessionOwned { _session_guard: Option, diff --git a/consensus/client/src/error.rs b/consensus/client/src/error.rs index e0aab2156..e632f517d 100644 --- a/consensus/client/src/error.rs +++ b/consensus/client/src/error.rs @@ -1,3 +1,5 @@ +//! The [`Error`](enum@Error) enum used by this crate + use thiserror::Error; use wasm_bindgen::{JsError, JsValue}; use workflow_wasm::jserror::JsErrorData; diff --git a/consensus/client/src/hash.rs b/consensus/client/src/hash.rs index 4402cfb1b..1577689a6 100644 --- a/consensus/client/src/hash.rs +++ b/consensus/client/src/hash.rs @@ -1,3 +1,10 @@ +//! +//! WASM bindings for transaction hashers: [`TransactionSigningHash`](native::TransactionSigningHash) +//! and [`TransactionSigningHashECDSA`](native::TransactionSigningHashECDSA). +//! + +#![allow(non_snake_case)] + use crate::imports::*; use crate::result::Result; use kaspa_hashes as native; diff --git a/consensus/client/src/header.rs b/consensus/client/src/header.rs index 56fd92845..6f04a73c4 100644 --- a/consensus/client/src/header.rs +++ b/consensus/client/src/header.rs @@ -1,3 +1,9 @@ +//! +//! Implementation of the Block [`Header`] struct. +//! + +#![allow(non_snake_case)] + use crate::error::Error; use js_sys::{Array, Object}; use kaspa_consensus_core::hashing; @@ -59,10 +65,15 @@ export interface IRawHeader { #[wasm_bindgen] extern "C" { + /// WASM (TypeScript) type definition for the Header-like struct: `Header | IHeader | IRawHeader`. + /// + /// @category Consensus #[wasm_bindgen(typescript_type = "Header | IHeader | IRawHeader")] pub type HeaderT; } +/// Kaspa Block Header +/// /// @category Consensus #[derive(Clone, Debug, Serialize, Deserialize, CastFromJs)] #[serde(rename_all = "camelCase")] diff --git a/consensus/client/src/input.rs b/consensus/client/src/input.rs index 736696bfa..a5018199d 100644 --- a/consensus/client/src/input.rs +++ b/consensus/client/src/input.rs @@ -1,3 +1,9 @@ +//! +//! Implementation of the client-side [`TransactionInput`] struct used by the client-side [`Transaction`] struct. +//! + +#![allow(non_snake_case)] + use crate::imports::*; use crate::result::Result; use crate::TransactionOutpoint; @@ -33,14 +39,21 @@ export interface ITransactionInputVerboseData { } #[wasm_bindgen] extern "C" { + /// WASM (TypeScript) type representing `ITransactionInput | TransactionInput` + /// @category Consensus #[wasm_bindgen(typescript_type = "ITransactionInput | TransactionInput")] pub type TransactionInputT; + /// WASM (TypeScript) type representing `ITransactionInput[] | TransactionInput[]` + /// @category Consensus #[wasm_bindgen(typescript_type = "(ITransactionInput | TransactionInput)[]")] pub type TransactionInputArrayAsArgT; + /// WASM (TypeScript) type representing `TransactionInput[]` + /// @category Consensus #[wasm_bindgen(typescript_type = "TransactionInput[]")] pub type TransactionInputArrayAsResultT; } +/// Inner type used by [`TransactionInput`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TransactionInputInner { diff --git a/consensus/client/src/lib.rs b/consensus/client/src/lib.rs index eb482eab1..3afae2f78 100644 --- a/consensus/client/src/lib.rs +++ b/consensus/client/src/lib.rs @@ -1,3 +1,17 @@ +//! +//! # Client-side consensus primitives. +//! +//! This crate offers client-side primitives mirroring the consensus layer of the Kaspa p2p node. +//! It declares structs such as [`Transaction`], [`TransactionInput`], [`TransactionOutput`], +//! [`TransactionOutpoint`], [`UtxoEntry`], and [`UtxoEntryReference`] +//! that are used by the Wallet subsystem as well as WASM bindings. +//! +//! Unlike raw consensus primitives (used for high-performance DAG processing) the primitives +//! offered in this crate are designed to be used in client-side applications. Their internal +//! data is typically wrapped into `Arc>`, allowing for easy sharing between +//! async / threaded environments and WASM bindings. +//! + pub mod error; mod imports; mod input; diff --git a/consensus/client/src/outpoint.rs b/consensus/client/src/outpoint.rs index 06be53f6a..a9b39f5e4 100644 --- a/consensus/client/src/outpoint.rs +++ b/consensus/client/src/outpoint.rs @@ -1,3 +1,11 @@ +//! +//! Implementation of the client-side [`TransactionOutpoint`] used by the [`TransactionInput`] struct. +//! + +#![allow(non_snake_case)] + +use cfg_if::cfg_if; + use crate::imports::*; use crate::result::Result; @@ -14,6 +22,7 @@ export interface ITransactionOutpoint { } "#; +/// Inner type used by [`TransactionOutpoint`] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Ord, PartialOrd)] #[serde(rename_all = "camelCase")] pub struct TransactionOutpointInner { @@ -110,26 +119,31 @@ impl TransactionOutpoint { } } -#[cfg_attr(feature = "wasm32-sdk", wasm_bindgen)] -impl TransactionOutpoint { - #[cfg_attr(feature = "wasm32-sdk", wasm_bindgen(constructor))] - pub fn ctor(transaction_id: TransactionId, index: u32) -> TransactionOutpoint { - Self { inner: Arc::new(TransactionOutpointInner { transaction_id, index }) } - } +cfg_if! { + if #[cfg(feature = "wasm32-sdk")] { - #[cfg_attr(feature = "wasm32-sdk", wasm_bindgen(js_name = "getId"))] - pub fn id_string(&self) -> String { - format!("{}-{}", self.get_transaction_id_as_string(), self.get_index()) - } + #[wasm_bindgen] + impl TransactionOutpoint { + #[wasm_bindgen(constructor)] + pub fn ctor(transaction_id: TransactionId, index: u32) -> TransactionOutpoint { + Self { inner: Arc::new(TransactionOutpointInner { transaction_id, index }) } + } - #[cfg_attr(feature = "wasm32-sdk", wasm_bindgen(getter, js_name = transactionId))] - pub fn get_transaction_id_as_string(&self) -> String { - self.inner().transaction_id.to_string() - } + #[wasm_bindgen(js_name = "getId")] + pub fn id_string(&self) -> String { + format!("{}-{}", self.get_transaction_id_as_string(), self.get_index()) + } - #[cfg_attr(feature = "wasm32-sdk", wasm_bindgen(getter, js_name = index))] - pub fn get_index(&self) -> TransactionIndexType { - self.inner().index + #[wasm_bindgen(getter, js_name = transactionId)] + pub fn get_transaction_id_as_string(&self) -> String { + self.inner().transaction_id.to_string() + } + + #[wasm_bindgen(getter, js_name = index)] + pub fn get_index(&self) -> TransactionIndexType { + self.inner().index + } + } } } diff --git a/consensus/client/src/output.rs b/consensus/client/src/output.rs index 8f335c47d..17b4a58c8 100644 --- a/consensus/client/src/output.rs +++ b/consensus/client/src/output.rs @@ -1,3 +1,9 @@ +//! +//! Implementation of the client-side [`TransactionOutput`] used by the [`Transaction`] struct. +//! + +#![allow(non_snake_case)] + use crate::imports::*; #[wasm_bindgen(typescript_custom_section)] @@ -28,14 +34,21 @@ export interface ITransactionOutputVerboseData { #[wasm_bindgen] extern "C" { + /// WASM (TypeScript) type representing `ITransactionOutput | TransactionOutput` + /// @category Consensus #[wasm_bindgen(typescript_type = "ITransactionOutput | TransactionOutput")] pub type TransactionOutputT; + /// WASM (TypeScript) type representing `ITransactionOutput[] | TransactionOutput[]` + /// @category Consensus #[wasm_bindgen(typescript_type = "(ITransactionOutput | TransactionOutput)[]")] pub type TransactionOutputArrayAsArgT; + /// WASM (TypeScript) type representing `TransactionOutput[]` + /// @category Consensus #[wasm_bindgen(typescript_type = "TransactionOutput[]")] pub type TransactionOutputArrayAsResultT; } +/// Inner type used by [`TransactionOutput`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TransactionOutputInner { diff --git a/consensus/client/src/result.rs b/consensus/client/src/result.rs index 4c8cb83f5..d8bff8aa1 100644 --- a/consensus/client/src/result.rs +++ b/consensus/client/src/result.rs @@ -1 +1,3 @@ +//! [`Result`] type alias that is bound to the [`Error`](super::error::Error) type from this crate. + pub type Result = std::result::Result; diff --git a/consensus/client/src/serializable/mod.rs b/consensus/client/src/serializable/mod.rs index a590ab286..ab78d956b 100644 --- a/consensus/client/src/serializable/mod.rs +++ b/consensus/client/src/serializable/mod.rs @@ -1,3 +1,24 @@ +//! +//! # Standardized JSON serialization and deserialization of Kaspa transactions. +//! +//! This module provides standardized JSON serialization and deserialization of +//! Kaspa transactions. There are two sub-modules: `numeric` and `string`. +//! +//! The `numeric` module provides serialization and deserialization of transactions +//! with all large integer values as `bigint` types in WASM or numerical values that +//! exceed the largest integer that can be represented by the JavaScript `number` type. +//! +//! The `string` module provides serialization and deserialization of transactions +//! with all large integer values as `string` types. This allows deserialization +//! via JSON in JavaScript environments and later conversion to `bigint` types. +//! +//! These data structures can be used for manual transport of transactions using JSON. +//! For more advanced use cases, please refer to `PSKT` in the [`kaspa_wallet_pskt`](https://docs.rs/kaspa_wallet_pskt) +//! crate. +//! + +#![allow(non_snake_case)] + pub mod numeric; pub mod string; @@ -80,6 +101,7 @@ export interface ISerializableTransaction { #[wasm_bindgen] extern "C" { + /// WASM (TypeScript) representation of the `ISerializableTransaction` interface. #[wasm_bindgen(extends = js_sys::Array, typescript_type = "ISerializableTransaction")] pub type SerializableTransactionT; } diff --git a/consensus/client/src/serializable/numeric.rs b/consensus/client/src/serializable/numeric.rs index 733afd54e..6c24db634 100644 --- a/consensus/client/src/serializable/numeric.rs +++ b/consensus/client/src/serializable/numeric.rs @@ -1,4 +1,10 @@ -//! This module implements the primitives for external transaction signing. +//! +//! This module implements transaction-related primitives for JSON serialization +//! where all large integer values (`u64`) are serialized to JSON using `serde` and +//! can exceed the largest integer value representable by the JavaScript `number` type. +//! (i.e. transactions serialized using this module can not be deserialized in JavaScript +//! but may be deserialized in other JSON-capable environments that support large integers) +//! use crate::error::Error; use crate::imports::*; diff --git a/consensus/client/src/serializable/string.rs b/consensus/client/src/serializable/string.rs index e35cdb028..35c7907b2 100644 --- a/consensus/client/src/serializable/string.rs +++ b/consensus/client/src/serializable/string.rs @@ -1,4 +1,7 @@ -//! This module implements the primitives for external transaction signing. +//! +//! This module implements transaction-related primitives for JSON serialization +//! where all large integer values (`u64`) are serialized to and from JSON as strings. +//! use crate::imports::*; use crate::result::Result; diff --git a/consensus/client/src/sign.rs b/consensus/client/src/sign.rs index c254aee07..4044dc570 100644 --- a/consensus/client/src/sign.rs +++ b/consensus/client/src/sign.rs @@ -1,3 +1,7 @@ +//! +//! Utilities for signing transactions. +//! + use crate::transaction::Transaction; use core::iter::once; use itertools::Itertools; diff --git a/consensus/client/src/transaction.rs b/consensus/client/src/transaction.rs index fb6d185f0..17cc38126 100644 --- a/consensus/client/src/transaction.rs +++ b/consensus/client/src/transaction.rs @@ -1,3 +1,7 @@ +//! +//! Declares the client-side [`Transaction`] type, which represents a Kaspa transaction. +//! + #![allow(non_snake_case)] use crate::imports::*; @@ -53,10 +57,13 @@ export interface ITransactionVerboseData { #[wasm_bindgen] extern "C" { + /// WASM (TypeScript) type representing `ITransaction | Transaction` + /// @category Consensus #[wasm_bindgen(typescript_type = "ITransaction | Transaction")] pub type TransactionT; } +/// Inner type used by [`Transaction`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TransactionInner { diff --git a/consensus/client/src/utils.rs b/consensus/client/src/utils.rs index 4f543d45b..7e08556fe 100644 --- a/consensus/client/src/utils.rs +++ b/consensus/client/src/utils.rs @@ -1,3 +1,9 @@ +//! +//! Client-side utility functions and their WASM bindings. +//! + +#![allow(non_snake_case)] + use crate::imports::*; use crate::result::Result; use kaspa_addresses::*; diff --git a/consensus/client/src/utxo.rs b/consensus/client/src/utxo.rs index 0a8b3fdb9..bbfc1199d 100644 --- a/consensus/client/src/utxo.rs +++ b/consensus/client/src/utxo.rs @@ -1,3 +1,13 @@ +//! +//! # UTXO client-side data structures. +//! +//! This module provides client-side data structures for UTXO management. +//! In particular, the [`UtxoEntry`] and [`UtxoEntryReference`] structs +//! are used to represent UTXO entries in the wallet subsystem and WASM bindings. +//! + +#![allow(non_snake_case)] + use crate::imports::*; use crate::outpoint::{TransactionOutpoint, TransactionOutpointInner}; use crate::result::Result; @@ -29,16 +39,22 @@ export interface IUtxoEntry { #[wasm_bindgen] extern "C" { + /// WASM type representing an array of [`UtxoEntryReference`] objects (i.e. `UtxoEntryReference[]`) #[wasm_bindgen(extends = Array, typescript_type = "UtxoEntryReference[]")] pub type UtxoEntryReferenceArrayT; + /// WASM type representing a UTXO entry interface (a UTXO-like object) #[wasm_bindgen(typescript_type = "IUtxoEntry")] pub type IUtxoEntry; + /// WASM type representing an array of UTXO entries (i.e. `IUtxoEntry[]`) #[wasm_bindgen(typescript_type = "IUtxoEntry[]")] pub type IUtxoEntryArray; } +/// A UTXO entry Id is a unique identifier for a UTXO entry defined by the `txid+output_index`. pub type UtxoEntryId = TransactionOutpointInner; +/// [`UtxoEntry`] struct represents a client-side UTXO entry. +/// /// @category Wallet SDK #[derive(Clone, Debug, Serialize, Deserialize, CastFromJs)] #[serde(rename_all = "camelCase")] @@ -119,6 +135,8 @@ impl From<&UtxoEntry> for cctx::UtxoEntry { } } +/// [`Arc`] reference to a [`UtxoEntry`] used by the wallet subsystems. +/// /// @category Wallet SDK #[derive(Clone, Debug, Serialize, Deserialize, CastFromJs)] #[wasm_bindgen(inspectable)] @@ -251,6 +269,7 @@ impl PartialOrd for UtxoEntryReference { } } +/// An extension trait to convert a JS value into a vec of UTXO entry references. pub trait TryIntoUtxoEntryReferences { fn try_into_utxo_entry_references(&self) -> Result>; } diff --git a/consensus/core/src/api/mod.rs b/consensus/core/src/api/mod.rs index 91165b73d..365b8404c 100644 --- a/consensus/core/src/api/mod.rs +++ b/consensus/core/src/api/mod.rs @@ -39,7 +39,7 @@ pub struct BlockValidationFutures { /// A future triggered when DAG state which included this block has been processed by the virtual processor /// (exceptions are header-only blocks and trusted blocks which have the future completed before virtual - /// processing along with the [`block_task`]) + /// processing along with the `block_task`) pub virtual_state_task: BlockValidationFuture, } diff --git a/consensus/core/src/config/bps.rs b/consensus/core/src/config/bps.rs index c0c52a6df..5e98aac5d 100644 --- a/consensus/core/src/config/bps.rs +++ b/consensus/core/src/config/bps.rs @@ -33,7 +33,7 @@ impl Bps { } /// Returns the GHOSTDAG K value which was pre-computed for this BPS - /// (see [`calculate_ghostdag_k`] and [`gen_ghostdag_table`] for the full calculation) + /// (see [`calculate_ghostdag_k`] and `gen_ghostdag_table` for the full calculation) #[rustfmt::skip] pub const fn ghostdag_k() -> KType { match BPS { diff --git a/consensus/core/src/lib.rs b/consensus/core/src/lib.rs index 46ad3f2ce..188b2403b 100644 --- a/consensus/core/src/lib.rs +++ b/consensus/core/src/lib.rs @@ -1,3 +1,9 @@ +//! +//! # Consensus Core +//! +//! This crate implements primitives used in the Kaspa node consensus processing. +//! + extern crate alloc; extern crate core; extern crate self as consensus_core; diff --git a/consensus/core/src/network.rs b/consensus/core/src/network.rs index d5e9abd24..18e52eacb 100644 --- a/consensus/core/src/network.rs +++ b/consensus/core/src/network.rs @@ -1,3 +1,16 @@ +//! +//! # Network Types +//! +//! This module implements [`NetworkType`] (such as `mainnet`, `testnet`, `devnet`, and `simnet`) +//! and [`NetworkId`] that combines a network type with an optional numerical suffix. +//! +//! The suffix is used to differentiate between multiple networks of the same type and is used +//! explicitly with `testnet` networks, allowing declaration of testnet versions such as +//! `testnet-10`, `testnet-11`, etc. +//! + +#![allow(non_snake_case)] + use borsh::{BorshDeserialize, BorshSerialize}; use kaspa_addresses::Prefix; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; diff --git a/consensus/core/src/tx.rs b/consensus/core/src/tx.rs index bad1b679a..a4dd7dd45 100644 --- a/consensus/core/src/tx.rs +++ b/consensus/core/src/tx.rs @@ -1,3 +1,11 @@ +//! +//! # Transaction +//! +//! This module implements consensus [`Transaction`] structure and related types. +//! + +#![allow(non_snake_case)] + mod script_public_key; use borsh::{BorshDeserialize, BorshSerialize}; @@ -25,6 +33,7 @@ use crate::{ /// COINBASE_TRANSACTION_INDEX is the index of the coinbase transaction in every block pub const COINBASE_TRANSACTION_INDEX: usize = 0; +/// A 32-byte Kaspa transaction identifier. pub type TransactionId = kaspa_hashes::Hash; /// Holds details about an individual transaction output in a utxo diff --git a/consensus/src/processes/ghostdag/protocol.rs b/consensus/src/processes/ghostdag/protocol.rs index 87beeb565..8dfe4e793 100644 --- a/consensus/src/processes/ghostdag/protocol.rs +++ b/consensus/src/processes/ghostdag/protocol.rs @@ -91,7 +91,7 @@ impl pub fn ghostdag(&self, parents: &[Hash]) -> GhostdagData { assert!(!parents.is_empty(), "genesis must be added via a call to init"); diff --git a/consensus/src/processes/reachability/interval.rs b/consensus/src/processes/reachability/interval.rs index 9f8d7fbd0..b910f3ddf 100644 --- a/consensus/src/processes/reachability/interval.rs +++ b/consensus/src/processes/reachability/interval.rs @@ -89,7 +89,7 @@ impl Interval { } /// Splits this interval to exactly |sizes| parts where - /// |part_i| = sizes[i]. This method expects sum(sizes) to be exactly + /// |part_i| = sizes\[i\]. This method expects sum(sizes) to be exactly /// equal to the interval's size. pub fn split_exact(&self, sizes: &[u64]) -> Vec { assert_eq!(sizes.iter().sum::(), self.size(), "sum of sizes must be equal to the interval's size"); @@ -107,7 +107,7 @@ impl Interval { /// Splits this interval to |sizes| parts /// by the allocation rule described below. This method expects sum(sizes) /// to be smaller or equal to the interval's size. Every part_i is - /// allocated at least sizes[i] capacity. The remaining budget is + /// allocated at least sizes\[i\] capacity. The remaining budget is /// split by an exponentially biased rule described below. /// /// This rule follows the GHOSTDAG protocol behavior where the child diff --git a/crypto/addresses/src/lib.rs b/crypto/addresses/src/lib.rs index 8aca86386..8e3ea385a 100644 --- a/crypto/addresses/src/lib.rs +++ b/crypto/addresses/src/lib.rs @@ -1,3 +1,11 @@ +//! +//! Kaspa [`Address`] implementation. +//! +//! In it's string form, the Kaspa [`Address`] is represented by a `bech32`-encoded +//! address string combined with a network type. The `bech32` string encoding is +//! comprised of a public key, the public key version and the resulting checksum. +//! + use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use smallvec::SmallVec; @@ -11,6 +19,7 @@ use workflow_wasm::{ mod bech32; +/// Error type produced by [`Address`] operations. #[derive(Error, PartialEq, Eq, Debug, Clone)] pub enum AddressError { #[error("The address has an invalid prefix {0}")] @@ -190,7 +199,8 @@ pub const PAYLOAD_VECTOR_SIZE: usize = 36; /// Used as the underlying type for address payload, optimized for the largest version length (33). pub type PayloadVec = SmallVec<[u8; PAYLOAD_VECTOR_SIZE]>; -/// Kaspa `Address` struct that serializes to and from an address format string: `kaspa:qz0s...t8cv`. +/// Kaspa [`Address`] struct that serializes to and from an address format string: `kaspa:qz0s...t8cv`. +/// /// @category Address #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash, CastFromJs)] #[wasm_bindgen(inspectable)] @@ -516,12 +526,24 @@ impl TryCastFromJs for Address { #[wasm_bindgen] extern "C" { + /// WASM (TypeScript) type representing an Address-like object: `Address | string`. + /// + /// @category Address #[wasm_bindgen(extends = js_sys::Array, typescript_type = "Address | string")] pub type AddressT; + /// WASM (TypeScript) type representing an array of Address-like objects: `(Address | string)[]`. + /// + /// @category Address #[wasm_bindgen(extends = js_sys::Array, typescript_type = "(Address | string)[]")] pub type AddressOrStringArrayT; + /// WASM (TypeScript) type representing an array of [`Address`] objects: `Address[]`. + /// + /// @category Address #[wasm_bindgen(extends = js_sys::Array, typescript_type = "Address[]")] pub type AddressArrayT; + /// WASM (TypeScript) type representing an [`Address`] or an undefined value: `Address | undefined`. + /// + /// @category Address #[wasm_bindgen(typescript_type = "Address | undefined")] pub type AddressOrUndefinedT; } diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 4406bd5b6..ad800d248 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -2747,7 +2747,7 @@ mod test { (1u64, vec![], false), // Case 1: 0 = locktime < txLockTime (0x800000, vec![0x7f, 0, 0], false), // Case 2: 0 < locktime < txLockTime (0x800000, vec![0x7f, 0, 0, 0, 0, 0, 0, 0, 0], true), // Case 3: locktime too big - (LOCK_TIME_THRESHOLD * 2, vec![0x7f, 0, 0, 0], true), // Case 4: lock times are inconsistant + (LOCK_TIME_THRESHOLD * 2, vec![0x7f, 0, 0, 0], true), // Case 4: lock times are inconsistent ] { let mut tx = base_tx.clone(); tx.0.lock_time = tx_lock_time; diff --git a/indexes/utxoindex/src/core/errors.rs b/indexes/utxoindex/src/core/errors.rs index 61aa877ab..0e0998905 100644 --- a/indexes/utxoindex/src/core/errors.rs +++ b/indexes/utxoindex/src/core/errors.rs @@ -4,7 +4,7 @@ use thiserror::Error; use crate::IDENT; use kaspa_database::prelude::StoreError; -/// Errors originating from the [`UtxoIndex`]. +/// Errors originating from the [`UtxoIndex`](crate::UtxoIndex). #[derive(Error, Debug)] pub enum UtxoIndexError { #[error("[{IDENT}]: {0}")] @@ -14,5 +14,5 @@ pub enum UtxoIndexError { DBResetError(#[from] io::Error), } -/// Results originating from the [`UtxoIndex`]. +/// Results originating from the [`UtxoIndex`](crate::UtxoIndex). pub type UtxoIndexResult = Result; diff --git a/indexes/utxoindex/src/index.rs b/indexes/utxoindex/src/index.rs index b71935afa..3b1bf2fe9 100644 --- a/indexes/utxoindex/src/index.rs +++ b/indexes/utxoindex/src/index.rs @@ -21,7 +21,8 @@ use std::{ const RESYNC_CHUNK_SIZE: usize = 2048; //Increased from 1k (used in go-kaspad), for quicker resets, while still having a low memory footprint. -/// UtxoIndex indexes [`CompactUtxoEntryCollections`] by [`ScriptPublicKey`], commits them to its owns store, and emits changes. +/// UtxoIndex indexes `CompactUtxoEntryCollections` by [`ScriptPublicKey`](kaspa_consensus_core::tx::ScriptPublicKey), +/// commits them to its owns store, and emits changes. /// Note: The UtxoIndex struct by itself is not thread save, only correct usage of the supplied RwLock via `new` makes it so. /// please follow guidelines found in the comments under `utxoindex::core::api::UtxoIndexApi` for proper thread safety. pub struct UtxoIndex { @@ -131,7 +132,7 @@ impl UtxoIndexApi for UtxoIndex { /// Deletes and reinstates the utxoindex database, syncing it from scratch via the consensus database. /// /// **Notes:** - /// 1) There is an implicit expectation that the consensus store must have [VirtualParent] tips. i.e. consensus database must be initiated. + /// 1) There is an implicit expectation that the consensus store must have VirtualParent tips. i.e. consensus database must be initiated. /// 2) resyncing while consensus notifies of utxo differences, may result in a corrupted db. fn resync(&mut self) -> UtxoIndexResult<()> { info!("Resyncing the utxoindex..."); diff --git a/indexes/utxoindex/src/update_container.rs b/indexes/utxoindex/src/update_container.rs index 8555a02d4..96449dbff 100644 --- a/indexes/utxoindex/src/update_container.rs +++ b/indexes/utxoindex/src/update_container.rs @@ -25,7 +25,7 @@ impl UtxoIndexChanges { } } - /// Add a [`UtxoDiff`] the the [`UtxoIndexChanges`] struct. + /// Add a [`UtxoDiff`] the [`UtxoIndexChanges`] struct. pub fn update_utxo_diff(&mut self, utxo_diff: UtxoDiff) { let (to_add, mut to_remove) = (utxo_diff.add, utxo_diff.remove); @@ -53,7 +53,7 @@ impl UtxoIndexChanges { } } - /// Add a [`Vec<(TransactionOutpoint, UtxoEntry)>`] the the [`UtxoIndexChanges`] struct + /// Add a [`Vec<(TransactionOutpoint, UtxoEntry)>`] the [`UtxoIndexChanges`] struct /// /// Note: This is meant to be used when resyncing. pub fn add_utxos_from_vector(&mut self, utxo_vector: Vec<(TransactionOutpoint, UtxoEntry)>) { diff --git a/metrics/core/src/data.rs b/metrics/core/src/data.rs index d47941aa7..ce9dc7216 100644 --- a/metrics/core/src/data.rs +++ b/metrics/core/src/data.rs @@ -252,65 +252,6 @@ pub enum Metric { } impl Metric { - // TODO - this will be refactored at a later date - // as this requires changes and testing in /kos - // pub fn group(&self) -> &'static str { - // match self { - // Metric::NodeCpuUsage - // | Metric::NodeResidentSetSizeBytes - // | Metric::NodeVirtualMemorySizeBytes - // | Metric::NodeFileHandlesCount - // | Metric::NodeDiskIoReadBytes - // | Metric::NodeDiskIoWriteBytes - // | Metric::NodeDiskIoReadPerSec - // | Metric::NodeDiskIoWritePerSec - // | Metric::NodeBorshLiveConnections - // | Metric::NodeBorshConnectionAttempts - // | Metric::NodeBorshHandshakeFailures - // | Metric::NodeJsonLiveConnections - // | Metric::NodeJsonConnectionAttempts - // | Metric::NodeJsonHandshakeFailures - // | Metric::NodeBorshBytesTx - // | Metric::NodeBorshBytesRx - // | Metric::NodeJsonBytesTx - // | Metric::NodeJsonBytesRx - // | Metric::NodeP2pBytesTx - // | Metric::NodeP2pBytesRx - // | Metric::NodeGrpcUserBytesTx - // | Metric::NodeGrpcUserBytesRx - // | Metric::NodeTotalBytesTx - // | Metric::NodeTotalBytesRx - // | Metric::NodeBorshBytesTxPerSecond - // | Metric::NodeBorshBytesRxPerSecond - // | Metric::NodeJsonBytesTxPerSecond - // | Metric::NodeJsonBytesRxPerSecond - // | Metric::NodeP2pBytesTxPerSecond - // | Metric::NodeP2pBytesRxPerSecond - // | Metric::NodeGrpcUserBytesTxPerSecond - // | Metric::NodeGrpcUserBytesRxPerSecond - // | Metric::NodeTotalBytesTxPerSecond - // | Metric::NodeTotalBytesRxPerSecond - // | Metric::NodeActivePeers => "system", - // // -- - // Metric::NodeBlocksSubmittedCount - // | Metric::NodeHeadersProcessedCount - // | Metric::NodeDependenciesProcessedCount - // | Metric::NodeBodiesProcessedCount - // | Metric::NodeTransactionsProcessedCount - // | Metric::NodeChainBlocksProcessedCount - // | Metric::NodeMassProcessedCount - // | Metric::NodeDatabaseBlocksCount - // | Metric::NodeDatabaseHeadersCount - // | Metric::NetworkMempoolSize - // | Metric::NetworkTransactionsPerSecond - // | Metric::NetworkTipHashesCount - // | Metric::NetworkDifficulty - // | Metric::NetworkPastMedianTime - // | Metric::NetworkVirtualParentHashesCount - // | Metric::NetworkVirtualDaaScore => "kaspa", - // } - // } - pub fn is_key_performance_metric(&self) -> bool { matches!( self, diff --git a/mining/src/feerate/fee_estimation.ipynb b/mining/src/feerate/fee_estimation.ipynb index 694f47450..a8b8fbfc8 100644 --- a/mining/src/feerate/fee_estimation.ipynb +++ b/mining/src/feerate/fee_estimation.ipynb @@ -252,7 +252,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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", "text/plain": [ "
" ] @@ -303,7 +303,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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", "text/plain": [ "
" ] @@ -355,7 +355,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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", "text/plain": [ "
" ] @@ -396,7 +396,7 @@ "source": [ "### Outliers: solution\n", "\n", - "Compute the estimator conditioned on the event the the top most transaction captures the first slot. This decreases `total_weight` on the one hand (thus increasing `p`), while increasing `inclusion_interval` on the other, by capturing a block slot. If this estimator gives lower prediction times we switch to it, and then repeat the process with the next highest transaction. The process convegres when the estimator is no longer improving or if all block slots are captured. " + "Compute the estimator conditioned on the event the top most transaction captures the first slot. This decreases `total_weight` on the one hand (thus increasing `p`), while increasing `inclusion_interval` on the other, by capturing a block slot. If this estimator gives lower prediction times we switch to it, and then repeat the process with the next highest transaction. The process converges when the estimator is no longer improving or if all block slots are captured. " ] }, { @@ -406,7 +406,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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", "text/plain": [ "
" ] diff --git a/mining/src/mempool/model/frontier/search_tree.rs b/mining/src/mempool/model/frontier/search_tree.rs index fc18b2118..edf34c271 100644 --- a/mining/src/mempool/model/frontier/search_tree.rs +++ b/mining/src/mempool/model/frontier/search_tree.rs @@ -157,8 +157,9 @@ type InnerTree = BPlusTree>; /// is recomputed from subtree weights for each item insertion/removal /// /// Computing the prefix weight is a crucial operation if the tree is used for random sampling and -/// the tree is highly imbalanced in terms of weight variance. See [`Frontier::sample_inplace`] for -/// more details. +/// the tree is highly imbalanced in terms of weight variance. +/// See [`Frontier::sample_inplace()`](crate::mempool::model::frontier::Frontier::sample_inplace) +/// for more details. pub struct SearchTree { tree: InnerTree, } diff --git a/notify/src/address/tracker.rs b/notify/src/address/tracker.rs index f103b163c..a2b1c64dd 100644 --- a/notify/src/address/tracker.rs +++ b/notify/src/address/tracker.rs @@ -384,11 +384,11 @@ impl Inner { } } -/// Tracker of a set of [`Address`](kaspa_addresses::Address), indexing and counting registrations +/// Tracker of a set of [`Address`], indexing and counting registrations /// /// #### Implementation design /// -/// Each [`Address`](kaspa_addresses::Address) is stored internally as a [`ScriptPubKey`](kaspa_consensus_core::tx::ScriptPublicKey). +/// Each [`Address`] is stored internally as a [`ScriptPubKey`](kaspa_consensus_core::tx::ScriptPublicKey). /// This prevents inter-network duplication and optimizes UTXOs filtering efficiency. /// /// But consequently the address network prefix gets lost and must be globally provided when querying for addresses by indexes. diff --git a/notify/src/notifier.rs b/notify/src/notifier.rs index 220fd261b..6927ea1a1 100644 --- a/notify/src/notifier.rs +++ b/notify/src/notifier.rs @@ -75,8 +75,8 @@ pub type DynNotify = Arc>; /// /// - a vector of [`DynCollector`] /// - a vector of [`Subscriber`] -/// - a pool of [`Broadcaster`] -/// - a map of [`Listener`] +/// - a pool of `Broadcaster` +/// - a map of `Listener` /// /// Collectors and subscribers form the scaffold. They are provided to the ctor, are immutable and share its /// lifespan. Both do materialize a connection to the notifier _parents_, collectors for incoming notifications diff --git a/protocol/flows/src/flowcontext/transactions.rs b/protocol/flows/src/flowcontext/transactions.rs index d3112f0af..110b378b7 100644 --- a/protocol/flows/src/flowcontext/transactions.rs +++ b/protocol/flows/src/flowcontext/transactions.rs @@ -73,7 +73,7 @@ impl TransactionsSpread { /// within transaction Inv messages. /// /// The broadcast itself may happen only during a subsequent call to this function since it is done at most - /// every [`BROADCAST_INTERVAL`] milliseconds or when the queue length is larger than the Inv message + /// every `BROADCAST_INTERVAL` milliseconds or when the queue length is larger than the Inv message /// capacity. /// /// _GO-KASPAD: EnqueueTransactionIDsForPropagation_ diff --git a/rpc/core/src/api/connection.rs b/rpc/core/src/api/connection.rs index 5b4254288..fba2aa71a 100644 --- a/rpc/core/src/api/connection.rs +++ b/rpc/core/src/api/connection.rs @@ -1,3 +1,7 @@ +//! +//! Generic connection trait representing a connection to a client (where available). +//! + use std::sync::Arc; pub trait RpcConnection: Send + Sync { diff --git a/rpc/core/src/api/ctl.rs b/rpc/core/src/api/ctl.rs index 49241e7d9..d7705127b 100644 --- a/rpc/core/src/api/ctl.rs +++ b/rpc/core/src/api/ctl.rs @@ -1,3 +1,7 @@ +//! +//! Client-side RPC helper for handling connection and disconnection events. +//! + use crate::error::RpcResult; use std::sync::{Arc, Mutex}; use workflow_core::channel::Multiplexer; diff --git a/rpc/core/src/api/mod.rs b/rpc/core/src/api/mod.rs index 1373bd6e0..a75056a84 100644 --- a/rpc/core/src/api/mod.rs +++ b/rpc/core/src/api/mod.rs @@ -1,3 +1,7 @@ +//! +//! API module for the RPC server. Implements core RPC primitives. +//! + pub mod connection; pub mod ctl; pub mod notifications; diff --git a/rpc/core/src/api/notifications.rs b/rpc/core/src/api/notifications.rs index e07a7c4d9..503af0de8 100644 --- a/rpc/core/src/api/notifications.rs +++ b/rpc/core/src/api/notifications.rs @@ -1,3 +1,7 @@ +//! +//! RPC notifications that can be sent to clients. +//! + use crate::model::message::*; use derive_more::Display; use kaspa_notify::{ diff --git a/rpc/core/src/api/ops.rs b/rpc/core/src/api/ops.rs index 822798a1d..26ca356eb 100644 --- a/rpc/core/src/api/ops.rs +++ b/rpc/core/src/api/ops.rs @@ -1,3 +1,7 @@ +//! +//! RPC Operations used to identify RPC methods during transport and in various RPC-related macros. +//! + use borsh::{BorshDeserialize, BorshSerialize}; use kaspa_notify::events::EventType; use serde::{Deserialize, Serialize}; diff --git a/rpc/core/src/api/rpc.rs b/rpc/core/src/api/rpc.rs index 85713e547..cadc9e00c 100644 --- a/rpc/core/src/api/rpc.rs +++ b/rpc/core/src/api/rpc.rs @@ -1,8 +1,10 @@ -//! The client API +//! +//! The main [`RpcApi`] trait that defines all RPC methods available in the Rusty Kaspa p2p node. //! //! 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 +//! All data provided by the RPC server can be trusted by the client +//! No data submitted by the client to the node can be trusted +//! use crate::api::connection::DynRpcConnection; use crate::{model::*, notify::connection::ChannelConnection, RpcResult}; diff --git a/rpc/core/src/convert/block.rs b/rpc/core/src/convert/block.rs index 8888fe2bb..8cb0ab01e 100644 --- a/rpc/core/src/convert/block.rs +++ b/rpc/core/src/convert/block.rs @@ -1,3 +1,5 @@ +//! Conversion of Block related types + use std::sync::Arc; use crate::{RpcBlock, RpcError, RpcRawBlock, RpcResult, RpcTransaction}; diff --git a/rpc/core/src/convert/mod.rs b/rpc/core/src/convert/mod.rs index dee1988d5..bc5c0e64b 100644 --- a/rpc/core/src/convert/mod.rs +++ b/rpc/core/src/convert/mod.rs @@ -1,3 +1,7 @@ +//! +//! Data conversion utilities and structs for the RPC layer. +//! + pub mod block; pub mod notification; pub mod scope; diff --git a/rpc/core/src/convert/notification.rs b/rpc/core/src/convert/notification.rs index 362dd5ed9..6251cc1cd 100644 --- a/rpc/core/src/convert/notification.rs +++ b/rpc/core/src/convert/notification.rs @@ -1,3 +1,5 @@ +//! Conversion of Notification related types + use crate::{ convert::utxo::utxo_set_into_rpc, BlockAddedNotification, FinalityConflictNotification, FinalityConflictResolvedNotification, NewBlockTemplateNotification, Notification, PruningPointUtxoSetOverrideNotification, RpcAcceptedTransactionIds, diff --git a/rpc/core/src/convert/scope.rs b/rpc/core/src/convert/scope.rs index e38f09a1f..6d94de326 100644 --- a/rpc/core/src/convert/scope.rs +++ b/rpc/core/src/convert/scope.rs @@ -1,3 +1,5 @@ +//! Conversion of Notification Scope related types + use crate::{ NotifyBlockAddedRequest, NotifyFinalityConflictRequest, NotifyNewBlockTemplateRequest, NotifyPruningPointUtxoSetOverrideRequest, NotifySinkBlueScoreChangedRequest, NotifyUtxosChangedRequest, NotifyVirtualChainChangedRequest, diff --git a/rpc/core/src/convert/tx.rs b/rpc/core/src/convert/tx.rs index 20d41d674..9b69ca168 100644 --- a/rpc/core/src/convert/tx.rs +++ b/rpc/core/src/convert/tx.rs @@ -1,3 +1,5 @@ +//! Conversion of Transaction related types + use crate::{RpcError, RpcResult, RpcTransaction, RpcTransactionInput, RpcTransactionOutput}; use kaspa_consensus_core::tx::{Transaction, TransactionInput, TransactionOutput}; diff --git a/rpc/core/src/convert/utxo.rs b/rpc/core/src/convert/utxo.rs index a0376580d..5fc09f690 100644 --- a/rpc/core/src/convert/utxo.rs +++ b/rpc/core/src/convert/utxo.rs @@ -1,3 +1,5 @@ +//! Conversion functions for UTXO related types. + use crate::RpcUtxoEntry; use crate::RpcUtxosByAddressesEntry; use kaspa_addresses::Prefix; diff --git a/rpc/core/src/error.rs b/rpc/core/src/error.rs index 235ea639e..0e2bfee22 100644 --- a/rpc/core/src/error.rs +++ b/rpc/core/src/error.rs @@ -1,3 +1,7 @@ +//! +//! [`RpcError`] enum used by RPC primitives. +//! + use kaspa_consensus_core::{subnets::SubnetworkConversionError, tx::TransactionId}; use kaspa_utils::networking::IpAddress; use std::{net::AddrParseError, num::TryFromIntError}; diff --git a/rpc/core/src/lib.rs b/rpc/core/src/lib.rs index 66e4ece3a..a2ece77d4 100644 --- a/rpc/core/src/lib.rs +++ b/rpc/core/src/lib.rs @@ -1,3 +1,16 @@ +//! # RPC Core +//! +//! This crate provides foundational primitives used in Rusty Kaspa node RPC subsystem. +//! These include the main [`RpcApi`](api::rpc::RpcApi) trait, [`RpcApiOps`](crate::api::ops::RpcApiOps) +//! enum used in RPC method dispatching, and various data structures used in RPC method arguments. +//! +//! This crate acts as a foundation for [`kaspa_grpc_client`](https://docs.rs/kaspa_grpc_client) and +//! [`kaspa_wrpc_client`](https://docs.rs/kaspa_wrpc_client) crates, which provide gRPC and WebSocket +//! RPC client implementations. This crate is also used by WASM bindings to provide [WASM RpcClient +//! implementation](https://docs.rs/kaspa-wrpc-client/latest/kaspa_wrpc_client/wasm/struct.RpcClient.html) +//! (based on wRPC). +//! + // This attribute is required by BorshSerialize/Deserialize #![recursion_limit = "256"] @@ -9,6 +22,7 @@ pub mod notify; pub mod wasm; pub mod prelude { + //! Re-exports of the most commonly used types and traits in this crate. pub use super::api::notifications::*; pub use super::model::script_class::*; pub use super::model::*; diff --git a/rpc/core/src/model/mod.rs b/rpc/core/src/model/mod.rs index beef03257..a7c255624 100644 --- a/rpc/core/src/model/mod.rs +++ b/rpc/core/src/model/mod.rs @@ -1,3 +1,6 @@ +//! This module contains RPC-specific data structures +//! used in RPC methods. + pub mod address; pub mod block; pub mod blue_work; diff --git a/rpc/core/src/notify/mod.rs b/rpc/core/src/notify/mod.rs index 088483e8f..e6dc1be06 100644 --- a/rpc/core/src/notify/mod.rs +++ b/rpc/core/src/notify/mod.rs @@ -1,3 +1,7 @@ +//! +//! Notification structures used by the RPC subsystem. +//! + pub mod channel; pub mod collector; pub mod connection; diff --git a/rpc/core/src/wasm/convert.rs b/rpc/core/src/wasm/convert.rs index 7bc68171b..319f74bf0 100644 --- a/rpc/core/src/wasm/convert.rs +++ b/rpc/core/src/wasm/convert.rs @@ -1,3 +1,7 @@ +//! +//! WASM specific conversion functions +//! + use crate::model::*; use kaspa_consensus_client::*; use std::sync::Arc; diff --git a/rpc/core/src/wasm/message.rs b/rpc/core/src/wasm/message.rs index 4e330c347..85c085702 100644 --- a/rpc/core/src/wasm/message.rs +++ b/rpc/core/src/wasm/message.rs @@ -1,3 +1,7 @@ +//! +//! WASM interfaces and conversion to and from RPC messages. +//! + #![allow(non_snake_case)] use crate::error::RpcError as Error; use crate::error::RpcResult as Result; diff --git a/rpc/core/src/wasm/mod.rs b/rpc/core/src/wasm/mod.rs index 6552baa42..e3bcdc024 100644 --- a/rpc/core/src/wasm/mod.rs +++ b/rpc/core/src/wasm/mod.rs @@ -1,3 +1,5 @@ +//! WASM related conversions + pub mod convert; cfg_if::cfg_if! { diff --git a/rpc/grpc/client/src/lib.rs b/rpc/grpc/client/src/lib.rs index 74db82c4e..00dadee23 100644 --- a/rpc/grpc/client/src/lib.rs +++ b/rpc/grpc/client/src/lib.rs @@ -102,7 +102,7 @@ impl GrpcClient { /// `url`: the server to connect to /// /// `subscription_context`: it is advised to provide a clone of the same instance if multiple clients dealing with - /// [`UtxosChangedNotifications`] are connected concurrently in order to optimize the memory footprint. + /// `UtxosChangedNotifications` are connected concurrently in order to optimize the memory footprint. /// /// `reconnect`: features an automatic reconnection to the server, reactivating all subscriptions on success. /// diff --git a/rpc/grpc/core/src/convert/message.rs b/rpc/grpc/core/src/convert/message.rs index c0e75cf03..67ac60650 100644 --- a/rpc/grpc/core/src/convert/message.rs +++ b/rpc/grpc/core/src/convert/message.rs @@ -3,7 +3,7 @@ //! Response payloads in protowire do always contain an error field and generally a set of //! fields providing the requested data. //! -//! Responses in rpc core are expressed as RpcResult, where Xxx is the called +//! Responses in rpc core are expressed as `RpcResult`, where `Xxx` is the called //! RPC method. //! //! The general conversion convention from protowire to rpc core is to consider the error diff --git a/rpc/wrpc/client/src/client.rs b/rpc/wrpc/client/src/client.rs index 71147fd67..3ac04fa98 100644 --- a/rpc/wrpc/client/src/client.rs +++ b/rpc/wrpc/client/src/client.rs @@ -1,3 +1,5 @@ +//! Kaspa wRPC client implementation. + use crate::imports::*; use crate::parse::parse_host; use crate::{error::Error, node::NodeDescriptor}; @@ -246,14 +248,17 @@ impl RpcResolver for Inner { const WRPC_CLIENT: &str = "wrpc-client"; -/// [`KaspaRpcClient`] allows connection to the Kaspa wRPC Server via -/// binary Borsh or JSON protocols. +/// # [`KaspaRpcClient`] connects to Kaspa wRPC endpoint via binary Borsh or JSON protocols. /// /// RpcClient has two ways to interface with the underlying RPC subsystem: /// [`Interface`] that has a [`notification()`](Interface::notification) /// method to register closures that will be invoked on server-side -/// notifications and the [`RpcClient::call`] method that allows async -/// method invocation server-side. +/// notifications and the [`RpcClient::call`] method that allows server-side +/// async method invocation. +/// +/// The node address can be supplied via a URL or a [`Resolver`] that +/// can be used to resolve a public node address dynamically. [`Resolver`] can also +/// be configured to operate against custom node clusters. /// #[derive(Clone)] pub struct KaspaRpcClient { diff --git a/rpc/wrpc/client/src/error.rs b/rpc/wrpc/client/src/error.rs index 781455ddd..657027ed0 100644 --- a/rpc/wrpc/client/src/error.rs +++ b/rpc/wrpc/client/src/error.rs @@ -1,3 +1,5 @@ +//! [`Error`](enum@Error) variants for the wRPC client library. + use thiserror::Error; use wasm_bindgen::JsError; use wasm_bindgen::JsValue; diff --git a/rpc/wrpc/client/src/lib.rs b/rpc/wrpc/client/src/lib.rs index b3f26c425..ac004eccb 100644 --- a/rpc/wrpc/client/src/lib.rs +++ b/rpc/wrpc/client/src/lib.rs @@ -1,3 +1,19 @@ +//! +//! # wRPC Client for Rusty Kaspa p2p Node +//! +//! This crate provides a WebSocket RPC client for Rusty Kaspa p2p node. It is based on the +//! [wRPC](https://docs.rs/workflow-rpc) crate that offers WebSocket RPC implementation +//! for Rust based on Borsh and Serde JSON serialization. wRPC is a lightweight RPC framework +//! meant to function as an IPC (Inter-Process Communication) mechanism for Rust applications. +//! +//! Rust examples on using wRPC client can be found in the +//! [examples](https://github.com/kaspanet/rusty-kaspa/tree/master/rpc/wrpc/examples) folder. +//! +//! WASM bindings for wRPC client can be found in the [`kaspa-wrpc-wasm`](https://docs.rs/kaspa-wrpc-wasm) crate. +//! +//! The main struct managing Kaspa RPC client connections is the [`KaspaRpcClient`]. +//! + pub mod client; pub mod error; mod imports; diff --git a/rpc/wrpc/client/src/node.rs b/rpc/wrpc/client/src/node.rs index ca7e19c87..f775bfd18 100644 --- a/rpc/wrpc/client/src/node.rs +++ b/rpc/wrpc/client/src/node.rs @@ -1,3 +1,5 @@ +//! Node connection endpoint as provided by the [`Resolver`]. + use crate::imports::*; /// diff --git a/rpc/wrpc/client/src/parse.rs b/rpc/wrpc/client/src/parse.rs index 35db2c768..5a497c507 100644 --- a/rpc/wrpc/client/src/parse.rs +++ b/rpc/wrpc/client/src/parse.rs @@ -1,3 +1,5 @@ +//! wRPC URL parsing and validation utilities. + use std::fmt::Display; use std::net::{Ipv4Addr, Ipv6Addr}; use std::num::ParseIntError; diff --git a/rpc/wrpc/client/src/prelude.rs b/rpc/wrpc/client/src/prelude.rs index 6a410b723..a4598e537 100644 --- a/rpc/wrpc/client/src/prelude.rs +++ b/rpc/wrpc/client/src/prelude.rs @@ -1,3 +1,5 @@ +//! Re-exports of the most commonly used types and traits. + pub use crate::client::{ConnectOptions, ConnectStrategy}; pub use crate::{KaspaRpcClient, Resolver, WrpcEncoding}; pub use kaspa_consensus_core::network::{NetworkId, NetworkType}; diff --git a/rpc/wrpc/client/src/resolver.rs b/rpc/wrpc/client/src/resolver.rs index 8dcb19447..170fe12dd 100644 --- a/rpc/wrpc/client/src/resolver.rs +++ b/rpc/wrpc/client/src/resolver.rs @@ -1,3 +1,7 @@ +//! +//! Module implementing [`Resolver`] client for obtaining public Kaspa wRPC endpoints. +//! + use std::sync::OnceLock; use crate::error::Error; @@ -78,7 +82,12 @@ impl Inner { } /// -/// Resolver is a client for obtaining public Kaspa wRPC endpoints. +/// # Resolver - a client for obtaining public Kaspa wRPC endpoints. +/// +/// This client operates against [Kaspa Resolver](https://github.com/aspectron/kaspa-resolver) service +/// that provides load-balancing and failover capabilities for Kaspa wRPC endpoints. The default +/// configuration allows access to public Kaspa nodes, while custom configurations can be supplied +/// if you are running your own custom Kaspa node cluster. /// #[derive(Debug, Clone)] pub struct Resolver { @@ -92,10 +101,15 @@ impl Default for Resolver { } impl Resolver { + /// Create a new [`Resolver`] client with the specified list of resolver URLs and an optional `tls` flag. + /// The `tls` flag can be used to enforce secure connection to the node. pub fn new(urls: Option>>, tls: bool) -> Self { Self { inner: Arc::new(Inner::new(urls, tls)) } } + /// Obtain a list of URLs in the resolver client. (This function + /// returns `None` if the resolver is configured to use public + /// node endpoints.) pub fn urls(&self) -> Option>> { if self.inner.public { None @@ -104,11 +118,12 @@ impl Resolver { } } + /// Obtain the `tls` flag in the resolver client. pub fn tls(&self) -> bool { self.inner.tls } - pub fn tls_as_str(&self) -> &'static str { + fn tls_as_str(&self) -> &'static str { if self.inner.tls { "tls" } else { @@ -140,6 +155,7 @@ impl Resolver { format!("{url}/v{CURRENT_VERSION}/kaspa/{network_id}/{tls}/wrpc/{encoding}") } + // query a single resolver service async fn fetch_node_info(&self, url: &str, encoding: Encoding, network_id: NetworkId) -> Result { let url = self.make_url(url, encoding, network_id); let node = @@ -147,7 +163,8 @@ impl Resolver { Ok(node) } - pub async fn fetch(&self, encoding: Encoding, network_id: NetworkId) -> Result { + // query multiple resolver services in random order + async fn fetch(&self, encoding: Encoding, network_id: NetworkId) -> Result { let mut urls = self.inner.urls.clone(); urls.shuffle(&mut thread_rng()); @@ -161,10 +178,12 @@ impl Resolver { Err(Error::Custom(format!("Failed to connect: {:?}", errors))) } + /// Obtain a Kaspa p2p [`NodeDescriptor`] from the resolver based on the supplied [`Encoding`] and [`NetworkId`]. pub async fn get_node(&self, encoding: Encoding, network_id: NetworkId) -> Result { self.fetch(encoding, network_id).await } + /// Returns a Kaspa wRPC URL from the resolver based on the supplied [`Encoding`] and [`NetworkId`]. pub async fn get_url(&self, encoding: Encoding, network_id: NetworkId) -> Result { let nodes = self.fetch(encoding, network_id).await?; Ok(nodes.url.clone()) diff --git a/rpc/wrpc/client/src/result.rs b/rpc/wrpc/client/src/result.rs index 32f663388..8427fd12f 100644 --- a/rpc/wrpc/client/src/result.rs +++ b/rpc/wrpc/client/src/result.rs @@ -1 +1,3 @@ +//! The [`Result`] type alias bound to the [`Error`](super::error::Error) enum used in this crate. + pub type Result = std::result::Result; diff --git a/rpc/wrpc/wasm/src/client.rs b/rpc/wrpc/wasm/src/client.rs index 81487172f..5982a2425 100644 --- a/rpc/wrpc/wasm/src/client.rs +++ b/rpc/wrpc/wasm/src/client.rs @@ -1,3 +1,10 @@ +//! +//! # WASM bindings for the [Kaspa p2p Node RPC client](KaspaRpcClient). +//! +//! This module provides a WASM interface for the Kaspa p2p Node RPC client +//! - [`RpcClient`]. +//! + #![allow(non_snake_case)] use crate::imports::*; @@ -130,7 +137,7 @@ impl TryFrom for NotificationEvent { } } -pub struct Inner { +pub(crate) struct Inner { client: Arc, resolver: Option, notification_task: AtomicBool, diff --git a/rpc/wrpc/wasm/src/lib.rs b/rpc/wrpc/wasm/src/lib.rs index e80b3baac..61d0de19b 100644 --- a/rpc/wrpc/wasm/src/lib.rs +++ b/rpc/wrpc/wasm/src/lib.rs @@ -1,3 +1,7 @@ +//! +//! WASM bindings for the [Rusty Kaspa p2p Node wRPC Client](kaspa-wrpc-client) +//! + #![allow(unused_imports)] use cfg_if::cfg_if; diff --git a/rpc/wrpc/wasm/src/notify.rs b/rpc/wrpc/wasm/src/notify.rs index 23781e314..c586f24dc 100644 --- a/rpc/wrpc/wasm/src/notify.rs +++ b/rpc/wrpc/wasm/src/notify.rs @@ -1,3 +1,7 @@ +//! Notification types and interfaces for wRPC events. + +#![allow(non_snake_case)] + use crate::imports::*; use kaspa_rpc_macros::declare_typescript_wasm_interface as declare; diff --git a/rpc/wrpc/wasm/src/resolver.rs b/rpc/wrpc/wasm/src/resolver.rs index 2ffc7ea56..7abfdb688 100644 --- a/rpc/wrpc/wasm/src/resolver.rs +++ b/rpc/wrpc/wasm/src/resolver.rs @@ -1,3 +1,7 @@ +//! [`Resolver`](NativeResolver) bindings for obtaining public Kaspa wRPC URL endpoints. + +#![allow(non_snake_case)] + use crate::client::{RpcClient, RpcConfig}; use crate::imports::*; use js_sys::Array; diff --git a/simpa/src/main.rs b/simpa/src/main.rs index 1d14a3c68..c66656be3 100644 --- a/simpa/src/main.rs +++ b/simpa/src/main.rs @@ -83,7 +83,7 @@ struct Args { ram_scale: f64, /// Logging level for all subsystems {off, error, warn, info, debug, trace} - /// -- You may also specify =,=,... to set the log level for individual subsystems + /// -- You may also specify `=,=,...` to set the log level for individual subsystems #[arg(long = "loglevel", default_value = format!("info,{}=trace", env!("CARGO_PKG_NAME")))] log_level: String, diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 4e57548e7..3d1bb5438 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -1,3 +1,9 @@ +//! +//! # Kaspa Utilities +//! +//! General purpose utilities and various type extensions used across the Rusty Kaspa codebase. +//! + pub mod any; pub mod arc; pub mod binary_heap; diff --git a/utils/src/option.rs b/utils/src/option.rs index ff4779dc1..3e619f46f 100644 --- a/utils/src/option.rs +++ b/utils/src/option.rs @@ -1,5 +1,5 @@ pub trait OptionExtensions { - /// Substitute for unstable [Option::is_non_or] + /// Substitute for unstable [`Option::is_none_or`] fn is_none_or_ex(&self, f: impl FnOnce(&T) -> bool) -> bool; } diff --git a/wallet/bip32/src/address_type.rs b/wallet/bip32/src/address_type.rs index 63ea00361..3aecfdb75 100644 --- a/wallet/bip32/src/address_type.rs +++ b/wallet/bip32/src/address_type.rs @@ -1,5 +1,10 @@ +//! +//! Address type (`Receive` or `Change`) used in HD wallet address derivation. +//! + use std::fmt; +/// Address type used in HD wallet address derivation. pub enum AddressType { Receive = 0, Change, diff --git a/wallet/bip32/src/lib.rs b/wallet/bip32/src/lib.rs index a406067f6..1926728c4 100644 --- a/wallet/bip32/src/lib.rs +++ b/wallet/bip32/src/lib.rs @@ -32,6 +32,8 @@ pub use xkey::ExtendedKey; pub use xprivate_key::ExtendedPrivateKey; pub use xpublic_key::ExtendedPublicKey; +/// Extension for [`secp256k1::SecretKey`] that provides access +/// to [`secp256k1::PublicKey`] and the public key string representation. pub trait SecretKeyExt { fn get_public_key(&self) -> secp256k1::PublicKey; fn as_str(&self, attrs: ExtendedKeyAttrs, prefix: Prefix) -> Zeroizing; diff --git a/wallet/bip32/src/private_key.rs b/wallet/bip32/src/private_key.rs index 0d4769ee4..d5dbc3d14 100644 --- a/wallet/bip32/src/private_key.rs +++ b/wallet/bip32/src/private_key.rs @@ -4,6 +4,7 @@ use crate::Result; pub use secp256k1::SecretKey; use secp256k1::{scalar::Scalar, Secp256k1, SignOnly}; +/// Trait for private key types which can be derived using BIP32. pub trait PrivateKey: Sized { /// Public key type which corresponds to this private key. type PublicKey: PublicKey; diff --git a/wallet/bip32/src/public_key.rs b/wallet/bip32/src/public_key.rs index 28a121811..56fb17de5 100644 --- a/wallet/bip32/src/public_key.rs +++ b/wallet/bip32/src/public_key.rs @@ -3,7 +3,7 @@ use ripemd::{Digest, Ripemd160}; use secp256k1::{scalar::Scalar, Secp256k1, VerifyOnly}; use sha2::Sha256; -/// Trait for key types which can be derived using BIP32. +/// Trait for public key types which can be derived using BIP32. pub trait PublicKey: Sized { /// Initialize this key from bytes. fn from_bytes(bytes: PublicKeyBytes) -> Result; diff --git a/wallet/core/src/account/descriptor.rs b/wallet/core/src/account/descriptor.rs index c549b739d..c3bf97cc1 100644 --- a/wallet/core/src/account/descriptor.rs +++ b/wallet/core/src/account/descriptor.rs @@ -11,6 +11,11 @@ use kaspa_wallet_macros::declare_typescript_wasm_interface as declare; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +/// +/// Structure that represents a wallet account. This structure contains +/// properties that are common to all wallet accounts as well as +/// account-specific properties stored in a BTreeMap by each account. +/// /// @category Wallet API #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct AccountDescriptor { diff --git a/wallet/core/src/account/kind.rs b/wallet/core/src/account/kind.rs index 20e863d77..511c1f4ed 100644 --- a/wallet/core/src/account/kind.rs +++ b/wallet/core/src/account/kind.rs @@ -8,6 +8,11 @@ use std::hash::Hash; use std::str::FromStr; use workflow_wasm::convert::CastFromJs; +/// +/// Account kind is a string signature that represents an account type. +/// Account kind is used to identify the account type during +/// serialization, deserialization and various API calls. +/// /// @category Wallet SDK #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash, CastFromJs)] #[wasm_bindgen] diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index 8fc46088b..e71d7e479 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -1,3 +1,8 @@ +//! +//! Tools for interfacing wallet accounts with PSKBs. +//! (Partial Signed Kaspa Transaction Bundles). +//! + pub use crate::error::Error; use crate::imports::*; use crate::tx::PaymentOutputs; diff --git a/wallet/core/src/api/message.rs b/wallet/core/src/api/message.rs index 3b96abd1a..e27cb2b29 100644 --- a/wallet/core/src/api/message.rs +++ b/wallet/core/src/api/message.rs @@ -118,15 +118,18 @@ pub struct RetainContextRequest { #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[serde(rename_all = "camelCase")] -pub struct RetainContextResponse { - // pub name : String, - // pub data: Option>>, - // pub is_connected: bool, - // pub is_synced: bool, - // pub is_open: bool, - // pub url: Option, - // pub is_wrpc_client: bool, - // pub network_id: Option, +pub struct RetainContextResponse {} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetContextRequest { + pub name: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetContextResponse { + pub data: Option>, } #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] diff --git a/wallet/core/src/api/mod.rs b/wallet/core/src/api/mod.rs index f0963d610..979ef1c72 100644 --- a/wallet/core/src/api/mod.rs +++ b/wallet/core/src/api/mod.rs @@ -1,4 +1,6 @@ //! +//! # Wallet API +//! //! Wallet API module that provides a unified interface for all wallet operations. //! diff --git a/wallet/core/src/api/traits.rs b/wallet/core/src/api/traits.rs index 08ebd65f6..357665e77 100644 --- a/wallet/core/src/api/traits.rs +++ b/wallet/core/src/api/traits.rs @@ -21,13 +21,29 @@ pub trait WalletApi: Send + Sync + AnySync { async fn register_notifications(self: Arc, channel: Receiver) -> Result; async fn unregister_notifications(self: Arc, channel_id: u64) -> Result<()>; + /// Wrapper around [`retain_context_call()`](Self::retain_context_call). async fn retain_context(self: Arc, name: &str, data: Option>) -> Result<()> { self.retain_context_call(RetainContextRequest { name: name.to_string(), data }).await?; Ok(()) } + /// Obtain earlier retained context data using the context `name` as a key. + async fn get_context(self: Arc, name: &str) -> Result>> { + Ok(self.get_context_call(GetContextRequest { name: name.to_string() }).await?.data) + } + + /// Allows user to store string key-associated context data in the wallet subsystem runtime. + /// The context data persists only during the wallet instance runtime. + /// This can be useful if you have a front-end that connects to a + /// persistent wallet instance operating in the backend (such as a browser + /// extension popup connecting to the background page) and you need to store + /// any type of runtime data in the backend (but are limited to using only + /// the wallet interface). async fn retain_context_call(self: Arc, request: RetainContextRequest) -> Result; + /// Obtain context data stored using [`retain_context()`](Self::retain_context). + async fn get_context_call(self: Arc, request: GetContextRequest) -> Result; + /// Wrapper around [`get_status_call()`](Self::get_status_call). async fn get_status(self: Arc, name: Option<&str>) -> Result { Ok(self.get_status_call(GetStatusRequest { name: name.map(String::from) }).await?) @@ -42,7 +58,7 @@ pub trait WalletApi: Send + Sync + AnySync { /// - `is_wrpc_client` - whether the wallet is connected to a node via wRPC async fn get_status_call(self: Arc, request: GetStatusRequest) -> Result; - /// Synchronous connect call (blocking, single attempt, requires sync). + /// Synchronous connect call (blocking, single attempt, requires node sync). async fn connect(self: Arc, url: Option, network_id: &NetworkId) -> Result<()> { let retry_on_error = false; let block_async_connect = true; @@ -55,6 +71,7 @@ pub trait WalletApi: Send + Sync + AnySync { /// comprised of the `url` and a `network_id`. async fn connect_call(self: Arc, request: ConnectRequest) -> Result; + /// Request the wallet RPC subsystem to disconnect from the node. async fn disconnect(self: Arc) -> Result<()> { self.disconnect_call(DisconnectRequest {}).await?; Ok(()) @@ -80,6 +97,7 @@ pub trait WalletApi: Send + Sync + AnySync { /// Ping the wallet service. Accepts an optional `u64` value that is returned in the response. async fn ping_call(self: Arc, request: PingRequest) -> Result; + /// Wrapper around [`batch_call()`](Self::batch_call). async fn batch(self: Arc) -> Result<()> { self.batch_call(BatchRequest {}).await?; Ok(()) @@ -94,6 +112,7 @@ pub trait WalletApi: Send + Sync + AnySync { /// async fn batch_call(self: Arc, request: BatchRequest) -> Result; + /// Wrapper around [`flush_call()`](Self::flush_call). async fn flush(self: Arc, wallet_secret: Secret) -> Result<()> { self.flush_call(FlushRequest { wallet_secret }).await?; Ok(()) @@ -268,6 +287,7 @@ pub trait WalletApi: Send + Sync + AnySync { /// around this call. async fn accounts_rename_call(self: Arc, request: AccountsRenameRequest) -> Result; + /// Wrapper around [`accounts_select_call()`](Self::accounts_select_call) async fn accounts_select(self: Arc, account_id: Option) -> Result<()> { self.accounts_select_call(AccountsSelectRequest { account_id }).await?; Ok(()) @@ -404,6 +424,7 @@ pub trait WalletApi: Send + Sync + AnySync { async fn accounts_estimate_call(self: Arc, request: AccountsEstimateRequest) -> Result; /// Get a range of transaction records for a specific account id. + /// Wrapper around [`transactions_data_get_call()`](Self::transactions_data_get_call). async fn transactions_data_get_range( self: Arc, account_id: AccountId, @@ -413,8 +434,8 @@ pub trait WalletApi: Send + Sync + AnySync { self.transactions_data_get_call(TransactionsDataGetRequest::with_range(account_id, network_id, range)).await } + /// Get a range of transaction records for a specific account id. async fn transactions_data_get_call(self: Arc, request: TransactionsDataGetRequest) -> Result; - // async fn transaction_get_call(self: Arc, request: TransactionGetRequest) -> Result; /// Replaces the note of a transaction with a new note. Note is meant /// to explicitly store a user-supplied string. The note is treated @@ -439,6 +460,7 @@ pub trait WalletApi: Send + Sync + AnySync { request: TransactionsReplaceMetadataRequest, ) -> Result; + // TODO async fn address_book_enumerate_call( self: Arc, request: AddressBookEnumerateRequest, diff --git a/wallet/core/src/api/transport.rs b/wallet/core/src/api/transport.rs index 4de2d7824..c9e5f6de6 100644 --- a/wallet/core/src/api/transport.rs +++ b/wallet/core/src/api/transport.rs @@ -71,6 +71,7 @@ impl WalletApi for WalletClient { Disconnect, ChangeNetworkId, RetainContext, + GetContext, Batch, Flush, WalletEnumerate, @@ -118,7 +119,7 @@ pub trait EventHandler: Send + Sync { /// [`WalletServer`] is a server-side transport interface that declares /// API methods that can be invoked via Borsh or Serde messages containing -/// serializations created using the [`Transport`] interface. The [`WalletServer`] +/// serializations created using the [`Codec`] interface. The [`WalletServer`] /// is a counter-part to [`WalletClient`]. pub struct WalletServer { // pub wallet_api: Arc, @@ -147,6 +148,7 @@ impl WalletServer { Disconnect, ChangeNetworkId, RetainContext, + GetContext, Batch, Flush, WalletEnumerate, diff --git a/wallet/core/src/compat/mod.rs b/wallet/core/src/compat/mod.rs index 79c8e11dd..093b8845c 100644 --- a/wallet/core/src/compat/mod.rs +++ b/wallet/core/src/compat/mod.rs @@ -1,3 +1,7 @@ +//! +//! Compatibility layer for legacy wallets. +//! + pub mod gen0; pub use gen0::*; pub mod gen1; diff --git a/wallet/core/src/cryptobox.rs b/wallet/core/src/cryptobox.rs index fa9b188f3..83845eee9 100644 --- a/wallet/core/src/cryptobox.rs +++ b/wallet/core/src/cryptobox.rs @@ -1,3 +1,7 @@ +//! +//! Re-export of the `crypto_box` crate that can be used to encrypt and decrypt messages. +//! + use crate::imports::*; use crypto_box::{ aead::{Aead, AeadCore, OsRng}, @@ -5,8 +9,13 @@ use crypto_box::{ }; pub use crypto_box::{PublicKey, SecretKey}; -// https://docs.rs/crypto_box/0.9.1/crypto_box/ - +/// +/// Primitives for encrypting and decrypting messages using the `crypto_box` crate. +/// This exists primarily for the purposes of [WASM bindings](crate::wasm::cryptobox::CryptoBox) +/// to allow access to the `crypto_box` encryption functionality from within web wallets. +/// +/// +/// pub struct CryptoBox { public_key: PublicKey, codec: ChaChaBox, diff --git a/wallet/core/src/events.rs b/wallet/core/src/events.rs index 63d7d5bca..37816d8b2 100644 --- a/wallet/core/src/events.rs +++ b/wallet/core/src/events.rs @@ -245,6 +245,12 @@ impl Events { } } +/// +/// Event kind representing [`Events`] variant. +/// Used primarily by WASM bindings to identify event types +/// by their string representation. Can be obtained from the +/// event via [`Events::kind()`]. +/// #[derive(Clone, Copy, Debug, Serialize, Eq, PartialEq, Hash)] #[serde(rename_all = "kebab-case")] pub enum EventKind { diff --git a/wallet/core/src/factory.rs b/wallet/core/src/factory.rs index 515093fdc..178e331e5 100644 --- a/wallet/core/src/factory.rs +++ b/wallet/core/src/factory.rs @@ -6,6 +6,7 @@ use crate::imports::*; use crate::result::Result; use std::sync::OnceLock; +/// Wallet account loading factory. #[async_trait] pub trait Factory { fn name(&self) -> String; @@ -22,6 +23,7 @@ type FactoryMap = AHashMap static EXTERNAL: OnceLock> = OnceLock::new(); static INITIALIZED: AtomicBool = AtomicBool::new(false); +/// Global factory registry accessor. pub fn factories() -> &'static FactoryMap { static FACTORIES: OnceLock = OnceLock::new(); FACTORIES.get_or_init(|| { @@ -41,6 +43,7 @@ pub fn factories() -> &'static FactoryMap { }) } +/// Registers a new global account factory. pub fn register(kind: AccountKind, factory: Arc) { if INITIALIZED.load(Ordering::Relaxed) { panic!("Factory registrations must occur before the framework initialization"); diff --git a/wallet/core/src/lib.rs b/wallet/core/src/lib.rs index bca89d4e7..09cc3ca7f 100644 --- a/wallet/core/src/lib.rs +++ b/wallet/core/src/lib.rs @@ -1,16 +1,21 @@ //! -//! Kaspa Wallet Core - Multi-platform Rust framework for Kaspa Wallet. +//! # Kaspa Wallet Core +//! +//! Multi-platform Rust framework for Kaspa Wallet. //! //! This framework provides a series of APIs and primitives //! to simplify building applications that interface with //! the Kaspa p2p network. //! -//! Included are low-level primitives +//! For key generation and derivation, please see the +//! [`kaspa_wallet_keys`] crate. +//! +//! This crate included are low-level primitives //! such as [`UtxoProcessor`](crate::utxo::UtxoProcessor) //! and [`UtxoContext`](crate::utxo::UtxoContext) that provide //! various levels of automation as well as higher-level //! APIs such as [`Wallet`](crate::wallet::Wallet), -//! [`Account`](crate::account::Account) (managed via +//! [`Account`](crate::account::Account) (managed via the //! [`WalletApi`](crate::api::WalletApi) trait) //! that offer a fully-featured wallet implementation //! backed by a multi-platform data storage layer capable of @@ -28,15 +33,28 @@ //! to satisfy the requested amount exceeds the maximum //! allowed transaction mass. //! +//! Key generation and derivation is available in the +//! [`kaspa_wallet_keys`] crate. +//! //! The framework can operate -//! within native Rust applications as well as within the NodeJS -//! and browser environments via WASM32. +//! within native Rust applications as well as within NodeJS, Bun +//! and browser environments via the WASM32 SDK. //! -//! For JavaScript / TypeScript environments, there are two +//! WASM32 SDK documentation is available at: +//! +//! +//! For NodeJS JavaScript and TypeScript environments, there are two //! available NPM modules: //! - //! - //! +//! NOTE: for security reasons (to mitigate potential upstream vendor +//! attacks) it is always recommended to build WASM SDK from source or +//! download pre-built redistributables. +//! +//! Latest development builds of the WASM32 SDK can be found at: +//! +//! //! The `kaspa-wasm` module is a pure WASM32 module that includes //! the entire wallet framework, but does not support RPC due to an absence //! of a native WebSocket in NodeJs environment, while @@ -54,36 +72,6 @@ extern crate alloc; extern crate self as kaspa_wallet_core; -// use cfg_if::cfg_if; - -// cfg_if! { -// if #[cfg(feature = "wasm32-core")] { -// // pub mod wasm; -// // pub use wasm::*; - -// pub mod account; -// pub mod api; -// pub mod compat; -// pub mod derivation; -// pub mod deterministic; -// pub mod encryption; -// pub mod error; -// pub mod events; -// pub mod factory; -// mod imports; -// pub mod message; -// pub mod prelude; -// pub mod result; -// pub mod rpc; -// pub mod serializer; -// pub mod settings; -// pub mod storage; -// pub mod tx; -// pub mod utils; -// pub mod utxo; -// pub mod wallet; - -// } else if #[cfg(any(feature = "wasm32-sdk", not(target_arch = "wasm32")))] { pub mod account; pub mod api; pub mod compat; @@ -107,9 +95,6 @@ pub mod tx; pub mod utils; pub mod utxo; pub mod wallet; -// } - -// } #[cfg(any(feature = "wasm32-sdk", feature = "wasm32-core"))] pub mod wasm; diff --git a/wallet/core/src/message.rs b/wallet/core/src/message.rs index 160c8f040..01dc78676 100644 --- a/wallet/core/src/message.rs +++ b/wallet/core/src/message.rs @@ -5,6 +5,7 @@ use kaspa_hashes::{Hash, PersonalMessageSigningHash}; use secp256k1::{Error, XOnlyPublicKey}; +/// A personal message (text) that can be signed. #[derive(Clone)] pub struct PersonalMessage<'a>(pub &'a str); diff --git a/wallet/core/src/metrics.rs b/wallet/core/src/metrics.rs index 87a3f9913..b0edb1f88 100644 --- a/wallet/core/src/metrics.rs +++ b/wallet/core/src/metrics.rs @@ -1,6 +1,13 @@ +//! +//! Primitives for network metrics. +//! + use crate::imports::*; -// use kaspa_metrics_core::MetricsSnapshot; +/// Metrics posted by the wallet subsystem. +/// See [`UtxoProcessor::start_metrics`] to enable metrics processing. +/// This struct contains mempool size that can be used to estimate +/// current network congestion. #[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[serde(tag = "type", content = "data")] #[serde(rename_all = "kebab-case")] @@ -8,37 +15,19 @@ pub enum MetricsUpdate { WalletMetrics { #[serde(rename = "mempoolSize")] mempool_size: u64, - #[serde(rename = "nodePeers")] - node_peers: u32, - #[serde(rename = "networkTPS")] - network_tps: f64, }, - // NodeMetrics { - // snapshot : Box - // } } +/// [`MetricsUpdate`] variant identifier. #[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub enum MetricsUpdateKind { WalletMetrics, - // NodeMetrics } impl MetricsUpdate { pub fn kind(&self) -> MetricsUpdateKind { match self { MetricsUpdate::WalletMetrics { .. } => MetricsUpdateKind::WalletMetrics, - // MetricsUpdate::NodeMetrics { .. } => MetricsUpdateKind::NodeMetrics } } } - -// impl MetricsUpdate { -// pub fn wallet_metrics(mempool_size: u64, peers: usize) -> Self { -// MetricsUpdate::WalletMetrics { mempool_size, peers } -// } - -// pub fn node_metrics(snapshot: MetricsSnapshot) -> Self { -// MetricsUpdate::NodeMetrics(Box::new(snapshot)) -// } -// } diff --git a/wallet/core/src/prelude.rs b/wallet/core/src/prelude.rs index 0ca019434..741ea0b1e 100644 --- a/wallet/core/src/prelude.rs +++ b/wallet/core/src/prelude.rs @@ -1,6 +1,5 @@ //! -//! A module which is typically glob imported. -//! Contains most commonly used imports. +//! Re-exports of the most commonly used types and traits in this crate. //! pub use crate::account::descriptor::AccountDescriptor; diff --git a/wallet/core/src/rpc.rs b/wallet/core/src/rpc.rs index 999e09e30..b75f4b5b2 100644 --- a/wallet/core/src/rpc.rs +++ b/wallet/core/src/rpc.rs @@ -6,13 +6,17 @@ use std::sync::Arc; pub use kaspa_rpc_core::api::ctl::RpcCtl; pub use kaspa_rpc_core::api::rpc::RpcApi; -pub type DynRpcApi = dyn RpcApi; -pub type NotificationChannel = kaspa_utils::channel::Channel; pub use kaspa_rpc_core::notify::mode::NotificationMode; pub use kaspa_wrpc_client::client::{ConnectOptions, ConnectStrategy}; pub use kaspa_wrpc_client::Resolver; pub use kaspa_wrpc_client::WrpcEncoding; +/// Type alias for [`dyn RpcApi`](RpcApi). +pub type DynRpcApi = dyn RpcApi; +/// Type alias for a concrete [`Channel`](kaspa_utils::channel::Channel) +/// used for handling RPC [`Notification`](kaspa_rpc_core::Notification) events. +pub type NotificationChannel = kaspa_utils::channel::Channel; + /// RPC adaptor class that holds the [`RpcApi`] /// and [`RpcCtl`] instances. #[derive(Clone)] diff --git a/wallet/core/src/settings.rs b/wallet/core/src/settings.rs index 35fde4486..f861c5a13 100644 --- a/wallet/core/src/settings.rs +++ b/wallet/core/src/settings.rs @@ -13,6 +13,7 @@ use std::path::PathBuf; use workflow_core::enums::Describe; use workflow_store::fs; +/// Wallet settings enumeration. #[derive(Describe, Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq, Ord, PartialOrd)] #[serde(rename_all = "lowercase")] pub enum WalletSettings { @@ -36,6 +37,8 @@ pub trait DefaultSettings: Sized { async fn defaults() -> Vec<(Self, Value)>; } +/// Platform neutral settings store (stores the settings K:V map +/// in a file or the browser `localStorage`). #[derive(Debug, Clone)] pub struct SettingsStore where @@ -170,10 +173,12 @@ where } } +/// Returns the wallet data storage folder `~/.kaspa`. pub fn application_folder() -> Result { Ok(fs::resolve_path(storage::local::default_storage_folder())?) } +/// If missing, creates the wallet data storage folder `~/.kaspa`. pub async fn ensure_application_folder() -> Result<()> { let path = application_folder()?; log_info!("Creating application folder: `{}`", path.display()); diff --git a/wallet/core/src/tx/payment.rs b/wallet/core/src/tx/payment.rs index e28c75a22..c164e0d78 100644 --- a/wallet/core/src/tx/payment.rs +++ b/wallet/core/src/tx/payment.rs @@ -30,12 +30,19 @@ export interface IPaymentOutput { #[wasm_bindgen] extern "C" { + /// WASM (TypeScript) type representing a single payment output (`IPaymentOutput`). + /// @category Wallet SDK #[wasm_bindgen(typescript_type = "IPaymentOutput")] pub type IPaymentOutput; + /// WASM (TypeScript) type representing multiple payment outputs (`IPaymentOutput[]`). + /// @category Wallet SDK #[wasm_bindgen(typescript_type = "IPaymentOutput[]")] pub type IPaymentOutputArray; } +/// A Rust data structure representing a payment destination. +/// A payment destination is used to signal Generator where to send the funds. +/// The destination can be a change address or a set of [`PaymentOutput`]. #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub enum PaymentDestination { Change, @@ -51,6 +58,9 @@ impl PaymentDestination { } } +/// A Rust data structure representing a single payment +/// output containing a destination address and amount. +/// /// @category Wallet SDK #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, CastFromJs)] #[wasm_bindgen(inspectable)] diff --git a/wallet/core/src/utxo/processor.rs b/wallet/core/src/utxo/processor.rs index b72b9784a..f6480f333 100644 --- a/wallet/core/src/utxo/processor.rs +++ b/wallet/core/src/utxo/processor.rs @@ -604,9 +604,7 @@ impl UtxoProcessor { match kind { MetricsUpdateKind::WalletMetrics => { let mempool_size = snapshot.get(&Metric::NetworkMempoolSize) as u64; - let node_peers = snapshot.get(&Metric::NodeActivePeers) as u32; - let network_tps = snapshot.get(&Metric::NetworkTransactionsPerSecond); - let metrics = MetricsUpdate::WalletMetrics { mempool_size, node_peers, network_tps }; + let metrics = MetricsUpdate::WalletMetrics { mempool_size }; self.try_notify(Events::Metrics { network_id: self.network_id()?, metrics })?; } } diff --git a/wallet/core/src/wallet/api.rs b/wallet/core/src/wallet/api.rs index adeb00075..93becef42 100644 --- a/wallet/core/src/wallet/api.rs +++ b/wallet/core/src/wallet/api.rs @@ -1,5 +1,5 @@ //! -//! [`WalletApi`] trait implementation for [`Wallet`]. +//! [`WalletApi`] trait implementation for the [`Wallet`] struct. //! use crate::api::{message::*, traits::WalletApi}; @@ -62,15 +62,17 @@ impl WalletApi for super::Wallet { if let Some(data) = data { self.inner.retained_contexts.lock().unwrap().insert(name, Arc::new(data)); - Ok(RetainContextResponse {}) } else { self.inner.retained_contexts.lock().unwrap().remove(&name); - // let data = self.inner.retained_contexts.lock().unwrap().get(&name).cloned(); Ok(RetainContextResponse {}) } + } - // self.retain_context(retain); + async fn get_context_call(self: Arc, request: GetContextRequest) -> Result { + let GetContextRequest { name } = request; + let data = self.inner.retained_contexts.lock().unwrap().get(&name).map(|data| (**data).clone()); + Ok(GetContextResponse { data }) } // ------------------------------------------------------------------------------------- diff --git a/wallet/core/src/wallet/args.rs b/wallet/core/src/wallet/args.rs index a5fa378bb..f0168f740 100644 --- a/wallet/core/src/wallet/args.rs +++ b/wallet/core/src/wallet/args.rs @@ -3,7 +3,6 @@ //! use crate::imports::*; -// use crate::secret::Secret; use crate::storage::interface::CreateArgs; use crate::storage::{Hint, PrvKeyDataId}; use borsh::{BorshDeserialize, BorshSerialize}; diff --git a/wallet/core/src/wallet/maps.rs b/wallet/core/src/wallet/maps.rs index 430f54e56..232e6fbeb 100644 --- a/wallet/core/src/wallet/maps.rs +++ b/wallet/core/src/wallet/maps.rs @@ -5,6 +5,7 @@ use crate::imports::*; +/// A thread-safe map of [`AccountId`] to [`Account`] instances. #[derive(Default, Clone)] pub struct ActiveAccountMap(Arc>>>); diff --git a/wallet/core/src/wallet/mod.rs b/wallet/core/src/wallet/mod.rs index d5f4dfadd..d7c9b6c76 100644 --- a/wallet/core/src/wallet/mod.rs +++ b/wallet/core/src/wallet/mod.rs @@ -1,6 +1,14 @@ //! -//! Kaspa wallet runtime implementation. +//! # Kaspa wallet runtime implementation. //! +//! This module contains a Rust implementation of the Kaspa wallet that +//! can be used in native Rust as well as WASM32 (Browser, NodeJs, Bun) +//! environments. +//! +//! This wallet is not meant to be used directly, but rather through the +//! use of the [`WalletApi`] trait. +//! + pub mod api; pub mod args; pub mod maps; @@ -81,7 +89,8 @@ pub enum WalletBusMessage { Discovery { record: TransactionRecord }, } -pub struct Inner { +/// Internal wallet state. +struct Inner { active_accounts: ActiveAccountMap, legacy_accounts: ActiveAccountMap, listener_id: Mutex>, @@ -189,10 +198,6 @@ impl Wallet { self } - pub fn inner(&self) -> &Arc { - &self.inner - } - // // Mutex used to protect concurrent access to accounts // at the wallet api level. This is a global lock that diff --git a/wallet/core/src/wasm/cryptobox.rs b/wallet/core/src/wasm/cryptobox.rs index 118020fe4..957d4fc35 100644 --- a/wallet/core/src/wasm/cryptobox.rs +++ b/wallet/core/src/wasm/cryptobox.rs @@ -106,7 +106,7 @@ impl std::ops::Deref for CryptoBoxPublicKey { /// /// CryptoBox allows for encrypting and decrypting messages using the `crypto_box` crate. /// -/// https://docs.rs/crypto_box/0.9.1/crypto_box/ +/// /// /// @category Wallet SDK /// diff --git a/wallet/core/src/wasm/signer.rs b/wallet/core/src/wasm/signer.rs index e00729ef5..157f06d90 100644 --- a/wallet/core/src/wasm/signer.rs +++ b/wallet/core/src/wasm/signer.rs @@ -50,7 +50,7 @@ pub fn js_sign_transaction(tx: &Transaction, signer: &PrivateKeyArrayT, verify_s } } -pub fn sign_transaction<'a>(tx: &'a Transaction, private_keys: &[[u8; 32]], verify_sig: bool) -> Result<&'a Transaction> { +fn sign_transaction<'a>(tx: &'a Transaction, private_keys: &[[u8; 32]], verify_sig: bool) -> Result<&'a Transaction> { let tx = sign(tx, private_keys)?; if verify_sig { let (cctx, utxos) = tx.tx_and_utxos()?; @@ -97,7 +97,7 @@ pub fn sign_script_hash(script_hash: JsValue, privkey: &PrivateKey) -> Result Result> { +fn sign_hash(sig_hash: Hash, privkey: &[u8; 32]) -> Result> { let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice())?; let schnorr_key = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, privkey)?; let sig: [u8; 64] = *schnorr_key.sign_schnorr(msg).as_ref(); diff --git a/wallet/keys/src/derivation/gen0/mod.rs b/wallet/keys/src/derivation/gen0/mod.rs index d25032626..0de5da286 100644 --- a/wallet/keys/src/derivation/gen0/mod.rs +++ b/wallet/keys/src/derivation/gen0/mod.rs @@ -1,4 +1,4 @@ -//! Derivation management for legacy account derivation scheme `972` +//! Derivation management for legacy account derivation scheme based on `'972` derivation path (deprecated). mod hd; pub use hd::{PubkeyDerivationManagerV0, WalletDerivationManagerV0}; diff --git a/wallet/keys/src/derivation/gen1/mod.rs b/wallet/keys/src/derivation/gen1/mod.rs index 1822c4d7a..5ec859b59 100644 --- a/wallet/keys/src/derivation/gen1/mod.rs +++ b/wallet/keys/src/derivation/gen1/mod.rs @@ -1,3 +1,4 @@ -/// Derivation management for the Kaspa standard derivation scheme `111111'` +//! Derivation management for the Kaspa standard derivation scheme `'111111'` + mod hd; pub use hd::{PubkeyDerivationManager, WalletDerivationManager}; diff --git a/wallet/keys/src/derivation/mod.rs b/wallet/keys/src/derivation/mod.rs index a63201194..cfa80b4f7 100644 --- a/wallet/keys/src/derivation/mod.rs +++ b/wallet/keys/src/derivation/mod.rs @@ -1,3 +1,7 @@ +//! +//! Derivation utilities used by the integrated Kaspa Wallet API. +//! + pub mod gen0; pub mod gen1; pub mod traits; diff --git a/wallet/keys/src/derivation_path.rs b/wallet/keys/src/derivation_path.rs index df220ee44..a5389ca37 100644 --- a/wallet/keys/src/derivation_path.rs +++ b/wallet/keys/src/derivation_path.rs @@ -1,7 +1,13 @@ +//! +//! Implementation of the [`DerivationPath`] manager for arbitrary derivation paths. +//! + use crate::imports::*; use workflow_wasm::prelude::*; +/// /// Key derivation path +/// /// @category Wallet SDK #[derive(Clone, CastFromJs)] #[wasm_bindgen] diff --git a/wallet/keys/src/keypair.rs b/wallet/keys/src/keypair.rs index f4b39f3d3..2cc3d5760 100644 --- a/wallet/keys/src/keypair.rs +++ b/wallet/keys/src/keypair.rs @@ -2,6 +2,8 @@ //! [`keypair`](mod@self) module encapsulates [`Keypair`] and [`PrivateKey`]. //! The [`Keypair`] provides access to the secret and public keys. //! +//! # JavaScript Example +//! //! ```javascript //! //! let keypair = Keypair.random(); @@ -56,7 +58,8 @@ impl Keypair { } /// Get the [`Address`] of this Keypair's [`PublicKey`]. - /// Receives a [`NetworkType`] to determine the prefix of the address. + /// Receives a [`NetworkType`](kaspa_consensus_core::network::NetworkType) + /// to determine the prefix of the address. /// JavaScript: `let address = keypair.toAddress(NetworkType.MAINNET);`. #[wasm_bindgen(js_name = toAddress)] // pub fn to_address(&self, network_type: NetworkType) -> Result
{ @@ -67,7 +70,8 @@ impl Keypair { } /// Get `ECDSA` [`Address`] of this Keypair's [`PublicKey`]. - /// Receives a [`NetworkType`] to determine the prefix of the address. + /// Receives a [`NetworkType`](kaspa_consensus_core::network::NetworkType) + /// to determine the prefix of the address. /// JavaScript: `let address = keypair.toAddress(NetworkType.MAINNET);`. #[wasm_bindgen(js_name = toAddressECDSA)] pub fn to_address_ecdsa(&self, network: &NetworkTypeT) -> Result
{ diff --git a/wallet/keys/src/lib.rs b/wallet/keys/src/lib.rs index bec8747d0..86984e36a 100644 --- a/wallet/keys/src/lib.rs +++ b/wallet/keys/src/lib.rs @@ -1,3 +1,10 @@ +//! +//! # Kaspa Wallet Keys +//! +//! This crate provides tools for creating and managing Kaspa wallet keys. +//! This includes extended key generation and derivation. +//! + pub mod derivation; pub mod derivation_path; pub mod error; diff --git a/wallet/keys/src/prelude.rs b/wallet/keys/src/prelude.rs index 1aed7c535..5d3af82dd 100644 --- a/wallet/keys/src/prelude.rs +++ b/wallet/keys/src/prelude.rs @@ -1,3 +1,7 @@ +//! +//! Re-exports of the most commonly used types and traits in this crate. +//! + pub use crate::derivation_path::*; pub use crate::keypair::*; pub use crate::privatekey::*; diff --git a/wallet/keys/src/privatekey.rs b/wallet/keys/src/privatekey.rs index 84e2d2e3b..554bdf36e 100644 --- a/wallet/keys/src/privatekey.rs +++ b/wallet/keys/src/privatekey.rs @@ -68,7 +68,8 @@ impl PrivateKey { } /// Get the [`Address`] of the PublicKey generated from this PrivateKey. - /// Receives a [`NetworkType`] to determine the prefix of the address. + /// Receives a [`NetworkType`](kaspa_consensus_core::network::NetworkType) + /// to determine the prefix of the address. /// JavaScript: `let address = privateKey.toAddress(NetworkType.MAINNET);`. #[wasm_bindgen(js_name = toAddress)] pub fn to_address(&self, network: &NetworkTypeT) -> Result
{ @@ -80,7 +81,8 @@ impl PrivateKey { } /// Get `ECDSA` [`Address`] of the PublicKey generated from this PrivateKey. - /// Receives a [`NetworkType`] to determine the prefix of the address. + /// Receives a [`NetworkType`](kaspa_consensus_core::network::NetworkType) + /// to determine the prefix of the address. /// JavaScript: `let address = privateKey.toAddress(NetworkType.MAINNET);`. #[wasm_bindgen(js_name = toAddressECDSA)] pub fn to_address_ecdsa(&self, network: &NetworkTypeT) -> Result
{ diff --git a/wallet/keys/src/privkeygen.rs b/wallet/keys/src/privkeygen.rs index ff2f3bd8f..474dec8ec 100644 --- a/wallet/keys/src/privkeygen.rs +++ b/wallet/keys/src/privkeygen.rs @@ -1,3 +1,7 @@ +//! +//! [`PrivateKeyGenerator`] helper for generating private key derivations from an extended private key (XPrv). +//! + use crate::derivation::gen1::WalletDerivationManager; use crate::imports::*; diff --git a/wallet/keys/src/pubkeygen.rs b/wallet/keys/src/pubkeygen.rs index c05ae844f..a61eeb5ad 100644 --- a/wallet/keys/src/pubkeygen.rs +++ b/wallet/keys/src/pubkeygen.rs @@ -1,3 +1,7 @@ +//! +//! [`PublicKeyGenerator`] helper for generating public key derivations from an extended public key (XPub). +//! + use crate::derivation::gen1::WalletDerivationManager; use crate::derivation::traits::WalletDerivationManagerTrait; use crate::imports::*; diff --git a/wallet/keys/src/publickey.rs b/wallet/keys/src/publickey.rs index d63eca491..235eb8080 100644 --- a/wallet/keys/src/publickey.rs +++ b/wallet/keys/src/publickey.rs @@ -1,6 +1,6 @@ //! -//! [`keypair`](mod@self) module encapsulates [`Keypair`] and [`PrivateKey`]. -//! The [`Keypair`] provides access to the secret and public keys. +//! [`keypair`](mod@self) module encapsulates [`Keypair`](crate::keypair::Keypair) and [`PrivateKey`]. +//! The [`Keypair`](crate::keypair::Keypair) provides access to the secret and public keys. //! //! ```javascript //! diff --git a/wallet/keys/src/secret.rs b/wallet/keys/src/secret.rs index 99d94f5be..d934ce4ec 100644 --- a/wallet/keys/src/secret.rs +++ b/wallet/keys/src/secret.rs @@ -1,10 +1,10 @@ //! -//! Secret container for sensitive data. Performs zeroization on drop. +//! Secret container for sensitive data. Performs data erasure (zeroization) on drop. //! use crate::imports::*; -/// Secret container for sensitive data. Performs memory zeroization on drop. +/// Secret container for sensitive data. Performs memory erasure (zeroization) on drop. #[derive(Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct Secret(Vec); diff --git a/wallet/keys/src/types.rs b/wallet/keys/src/types.rs index c006d2872..11c567834 100644 --- a/wallet/keys/src/types.rs +++ b/wallet/keys/src/types.rs @@ -1,5 +1,5 @@ //! -//! Type aliases used by the wallet framework. +//! Key-related type aliases used by the wallet framework. //! use std::sync::Arc; diff --git a/wallet/keys/src/xprv.rs b/wallet/keys/src/xprv.rs index 3e120841b..c19e0b9cc 100644 --- a/wallet/keys/src/xprv.rs +++ b/wallet/keys/src/xprv.rs @@ -1,3 +1,7 @@ +//! +//! Extended private key ([`XPrv`]). +//! + use kaspa_bip32::{ChainCode, KeyFingerprint}; use crate::imports::*; diff --git a/wallet/keys/src/xpub.rs b/wallet/keys/src/xpub.rs index 551881d8e..8706f3fc9 100644 --- a/wallet/keys/src/xpub.rs +++ b/wallet/keys/src/xpub.rs @@ -1,3 +1,7 @@ +//! +//! Extended public key ([`XPub`]). +//! + use kaspa_bip32::{ChainCode, KeyFingerprint, Prefix}; use std::{fmt, str::FromStr}; diff --git a/wallet/pskt/src/bundle.rs b/wallet/pskt/src/bundle.rs index 6e8dc8350..6c926c666 100644 --- a/wallet/pskt/src/bundle.rs +++ b/wallet/pskt/src/bundle.rs @@ -13,6 +13,11 @@ use kaspa_txscript::{extract_script_pub_key_address, pay_to_address_script, pay_ use serde::{Deserialize, Serialize}; use std::ops::Deref; +/// +/// Bundle is a [`PSKT`] bundle - a sequence of PSKT transactions +/// meant for batch processing and transport as a +/// single serialized payload. +/// #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Bundle(pub Vec); diff --git a/wallet/pskt/src/convert.rs b/wallet/pskt/src/convert.rs index 18acf94ed..a3956c3bb 100644 --- a/wallet/pskt/src/convert.rs +++ b/wallet/pskt/src/convert.rs @@ -1,3 +1,9 @@ +//! +//! Conversion functions for converting between +//! the [`kaspa_consensus_client`], [`kaspa_consensus_core`] +//! and [`kaspa_wallet_pskt`](crate) types. +//! + use crate::error::Error; use crate::input::{Input, InputBuilder}; use crate::output::{Output, OutputBuilder}; diff --git a/wallet/pskt/src/error.rs b/wallet/pskt/src/error.rs index 11303ae4a..f3fd83570 100644 --- a/wallet/pskt/src/error.rs +++ b/wallet/pskt/src/error.rs @@ -1,3 +1,5 @@ +//! Error types for the PSKT crate. + use kaspa_txscript_errors::TxScriptError; use crate::input::InputBuilderError; diff --git a/wallet/pskt/src/global.rs b/wallet/pskt/src/global.rs index b79798776..ad98f11d3 100644 --- a/wallet/pskt/src/global.rs +++ b/wallet/pskt/src/global.rs @@ -1,3 +1,5 @@ +//! Global PSKT data. + use crate::pskt::{KeySource, Version}; use crate::utils::combine_if_no_conflicts; use derive_builder::Builder; diff --git a/wallet/pskt/src/input.rs b/wallet/pskt/src/input.rs index c99ae2542..8d01a7d48 100644 --- a/wallet/pskt/src/input.rs +++ b/wallet/pskt/src/input.rs @@ -1,3 +1,5 @@ +//! PSKT input structure. + use crate::pskt::{KeySource, PartialSigs}; use crate::utils::{combine_if_no_conflicts, Error as CombineMapErr}; use derive_builder::Builder; diff --git a/wallet/pskt/src/output.rs b/wallet/pskt/src/output.rs index e873ce4a6..36b09edae 100644 --- a/wallet/pskt/src/output.rs +++ b/wallet/pskt/src/output.rs @@ -1,3 +1,5 @@ +//! PSKT output structure. + use crate::pskt::KeySource; use crate::utils::combine_if_no_conflicts; use derive_builder::Builder; diff --git a/wallet/pskt/src/pskt.rs b/wallet/pskt/src/pskt.rs index 245609803..73f87a628 100644 --- a/wallet/pskt/src/pskt.rs +++ b/wallet/pskt/src/pskt.rs @@ -1,3 +1,7 @@ +//! +//! Partially Signed Kaspa Transaction (PSKT) +//! + use kaspa_bip32::{secp256k1, DerivationPath, KeyFingerprint}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -76,6 +80,23 @@ impl Signature { } } +/// +/// A Partially Signed Kaspa Transaction (PSKT) is a standardized format +/// that allows multiple participants to collaborate in creating and signing +/// a Kaspa transaction. PSKT enables the exchange of incomplete transaction +/// data between different wallets or entities, allowing each participant +/// to add their signature or inputs in stages. This facilitates more complex +/// transaction workflows, such as multi-signature setups or hardware wallet +/// interactions, by ensuring that sensitive data remains secure while +/// enabling cooperation across different devices or platforms without +/// exposing private keys. +/// +/// Please note that due to transaction mass limits and potential of +/// a wallet aggregating large UTXO sets, the PSKT [`Bundle`](crate::bundle::Bundle) primitive +/// is used to represent a collection of PSKTs and should be used for +/// PSKT serialization and transport. PSKT is an internal implementation +/// primitive that represents each transaction in the bundle. +/// #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PSKT { diff --git a/wallet/pskt/src/role.rs b/wallet/pskt/src/role.rs index 84f55bb04..2d6daa47d 100644 --- a/wallet/pskt/src/role.rs +++ b/wallet/pskt/src/role.rs @@ -1,3 +1,5 @@ +//! PSKT roles. + /// Initializes the PSKT with 0 inputs and 0 outputs. /// Reference: [BIP-370: Creator](https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki#creator) pub enum Creator {} diff --git a/wallet/pskt/src/utils.rs b/wallet/pskt/src/utils.rs index cab211951..357b61bc7 100644 --- a/wallet/pskt/src/utils.rs +++ b/wallet/pskt/src/utils.rs @@ -1,3 +1,5 @@ +//! Utility functions for the PSKT module. + use std::collections::BTreeMap; // todo optimize without cloning diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index 77c5e16ea..d8b0f06a9 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -1,5 +1,5 @@ /*! -# `rusty-kaspa WASM32 bindings` +# Rusty Kaspa WASM32 bindings [github](https://github.com/kaspanet/rusty-kaspa/tree/master/wasm) [crates.io](https://crates.io/crates/kaspa-wasm) @@ -13,9 +13,9 @@ codebase within JavaScript environments such as Node.js and Web Browsers. ## Documentation -- [**integrating with Kaspa** guide](https://kaspa.aspectron.org/) -- [**Rustdoc** documentation](https://docs.rs/kaspa-wasm/latest/kaspa-wasm) -- [**JSDoc** documentation](https://kaspa.aspectron.org/jsdoc/) +- [**Integrating with Kaspa** guide](https://kaspa.aspectron.org/) +- [Rust SDK documentation (**Rustdoc**)](https://docs.rs/kaspa-wasm/) +- [TypeScript documentation (**JSDoc**)](https://kaspa.aspectron.org/docs/) Please note that while WASM directly binds JavaScript and Rust resources, their names on JavaScript side are different from their name in Rust as they conform to the 'camelCase' convention in JavaScript and @@ -25,9 +25,10 @@ to the 'snake_case' convention in Rust. The APIs are currently separated into the following groups (this will be expanded in the future): -- **Transaction API** — Bindings for primitives related to transactions. -- **RPC API** — [RPC interface bindings](rpc) for the Kaspa node using WebSocket (wRPC) connections. -- **Wallet API** — API for async core wallet processing tasks. +- **Consensus Client API** — Bindings for primitives related to transactions. +- **RPC API** — [RPC interface bindings](kaspa_wrpc_wasm::client) for the Kaspa node using WebSocket (wRPC) connections. +- **Wallet SDK** — API for async core wallet processing tasks. +- **Wallet API** — A rust implementation of the fully-featured wallet usable in the native Rust, Browser or NodeJs and Bun environments. ## NPM Modules @@ -43,6 +44,9 @@ of a native WebSocket in NodeJs environment, while the `kaspa` module includes `websocket` package dependency simulating the W3C WebSocket and due to this supports RPC. +NOTE: for security reasons it is always recommended to build WASM SDK from source or +download pre-built redistributables from releases or development builds. + ## Examples JavaScript examples for using this framework can be found at: @@ -54,8 +58,19 @@ For pre-built browser-compatible WASM32 redistributables of this framework please see the releases section of the Rusty Kaspa repository at . +## Development Builds + +The latest development builds from . +Development builds typically contain fixes and improvements that are not yet available in +stable releases. Additional information can be found at +. + ## Using RPC +No special handling is required to use the RPC client +in **Browser** or **Bun** environments due to the fact that +these environments provide native WebSocket support. + **NODEJS:** If you are building from source, to use WASM RPC client in the NodeJS environment, you need to introduce a global W3C WebSocket object before loading the WASM32 library (to simulate the browser behavior). @@ -123,7 +138,7 @@ const rpc = new RpcClient({ })(); ``` -For more details, please follow the [**integrating with Kaspa**](https://kaspa.aspectron.org/) guide. +For more details, please follow the [**Integrating with Kaspa**](https://kaspa.aspectron.org/) guide. */ From 180114e6a7839eec287687a3d5debcf0da593057 Mon Sep 17 00:00:00 2001 From: aspect Date: Sun, 29 Sep 2024 17:14:16 +0300 Subject: [PATCH 09/19] fix wasm rpc method types for methods without mandatory arguments (#572) --- rpc/wrpc/wasm/src/client.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rpc/wrpc/wasm/src/client.rs b/rpc/wrpc/wasm/src/client.rs index 5982a2425..ccd9cb284 100644 --- a/rpc/wrpc/wasm/src/client.rs +++ b/rpc/wrpc/wasm/src/client.rs @@ -976,6 +976,11 @@ build_wrpc_wasm_bindgen_interface!( /// Obtains basic information about the synchronization status of the Kaspa node. /// Returned information: Syncing status. GetSyncStatus, + /// Feerate estimates + GetFeeEstimate, + /// Retrieves the current network configuration. + /// Returned information: Current network configuration. + GetCurrentNetwork, ], [ // functions with `request` argument @@ -1010,13 +1015,8 @@ build_wrpc_wasm_bindgen_interface!( /// score timestamp estimate. /// Returned information: DAA score timestamp estimate. GetDaaScoreTimestampEstimate, - /// Feerate estimates - GetFeeEstimate, /// Feerate estimates (experimental) GetFeeEstimateExperimental, - /// Retrieves the current network configuration. - /// Returned information: Current network configuration. - GetCurrentNetwork, /// Retrieves block headers from the Kaspa BlockDAG. /// Returned information: List of block headers. GetHeaders, From 3bc2844ee36eb1dd4af8a342ba56a2663b7a5c25 Mon Sep 17 00:00:00 2001 From: aspect Date: Sun, 29 Sep 2024 18:59:54 +0300 Subject: [PATCH 10/19] cleanup legacy bip39 cfg that interferes with docs.rs builds (#573) --- wallet/bip32/src/mnemonic/mod.rs | 1 - wallet/bip32/src/mnemonic/phrase.rs | 2 -- wallet/bip32/src/mnemonic/seed.rs | 1 - 3 files changed, 4 deletions(-) diff --git a/wallet/bip32/src/mnemonic/mod.rs b/wallet/bip32/src/mnemonic/mod.rs index 611d88b05..89262b7e2 100644 --- a/wallet/bip32/src/mnemonic/mod.rs +++ b/wallet/bip32/src/mnemonic/mod.rs @@ -7,7 +7,6 @@ mod bits; mod language; mod phrase; -//#[cfg(feature = "bip39")] pub(crate) mod seed; pub use self::{language::Language, phrase::Mnemonic, phrase::WordCount}; diff --git a/wallet/bip32/src/mnemonic/phrase.rs b/wallet/bip32/src/mnemonic/phrase.rs index 95fd92189..eaa7e7096 100644 --- a/wallet/bip32/src/mnemonic/phrase.rs +++ b/wallet/bip32/src/mnemonic/phrase.rs @@ -229,8 +229,6 @@ impl Mnemonic { } /// Convert this mnemonic phrase into the BIP39 seed value. - //#[cfg(feature = "bip39")] - //#[cfg_attr(docsrs, doc(cfg(feature = "bip39")))] pub fn to_seed(&self, password: &str) -> Seed { let salt = Zeroizing::new(format!("mnemonic{password}")); let mut seed = [0u8; Seed::SIZE]; diff --git a/wallet/bip32/src/mnemonic/seed.rs b/wallet/bip32/src/mnemonic/seed.rs index a4c4025c6..7fd57f19a 100644 --- a/wallet/bip32/src/mnemonic/seed.rs +++ b/wallet/bip32/src/mnemonic/seed.rs @@ -4,7 +4,6 @@ use zeroize::Zeroize; /// BIP39 seeds. // TODO(tarcieri): support for 32-byte seeds -#[cfg_attr(docsrs, doc(cfg(feature = "bip39")))] pub struct Seed(pub(crate) [u8; Seed::SIZE]); impl Seed { From 66959d4a72abc150454da12179c0bef553a8bdd7 Mon Sep 17 00:00:00 2001 From: Maxim <59533214+biryukovmaxim@users.noreply.github.com> Date: Mon, 30 Sep 2024 20:43:35 +0400 Subject: [PATCH 11/19] Bump tonic and prost versions, adapt middlewares (#553) * bump tonic, prost versions update middlewares * use unbounded channel * change log level to trace * use bounded channel * reuse counts bytes body to measure bytes body * remove unneeded clone --- Cargo.lock | 245 ++++++++------------ Cargo.toml | 15 +- protocol/p2p/src/core/connection_handler.rs | 9 +- rpc/grpc/client/src/lib.rs | 7 +- rpc/grpc/server/src/connection_handler.rs | 4 +- utils/tower/Cargo.toml | 6 +- utils/tower/src/middleware.rs | 77 ++---- 7 files changed, 129 insertions(+), 234 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1296a8922..8e9829fea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -435,18 +435,17 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "axum" -version = "0.6.20" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" dependencies = [ "async-trait", "axum-core", - "bitflags 1.3.2", "bytes", "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.30", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", "itoa", "matchit", "memchr", @@ -455,25 +454,28 @@ dependencies = [ "pin-project-lite", "rustversion", "serde", - "sync_wrapper 0.1.2", - "tower", + "sync_wrapper 1.0.1", + "tower 0.4.13", "tower-layer", "tower-service", ] [[package]] name = "axum-core" -version = "0.3.4" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" dependencies = [ "async-trait", "bytes", "futures-util", - "http 0.2.12", - "http-body 0.4.6", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", "mime", + "pin-project-lite", "rustversion", + "sync_wrapper 0.1.2", "tower-layer", "tower-service", ] @@ -493,12 +495,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -2008,12 +2004,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "http-range-header" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" - [[package]] name = "httparse" version = "1.9.4" @@ -2069,6 +2059,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -2086,24 +2077,25 @@ dependencies = [ "http 1.1.0", "hyper 1.4.1", "hyper-util", - "rustls 0.23.12", + "rustls", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls", "tower-service", - "webpki-roots 0.26.5", + "webpki-roots", ] [[package]] name = "hyper-timeout" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" dependencies = [ - "hyper 0.14.30", + "hyper 1.4.1", + "hyper-util", "pin-project-lite", "tokio", - "tokio-io-timeout", + "tower-service", ] [[package]] @@ -2121,7 +2113,7 @@ dependencies = [ "pin-project-lite", "socket2 0.5.7", "tokio", - "tower", + "tower 0.4.13", "tower-service", "tracing", ] @@ -2755,7 +2747,7 @@ dependencies = [ "prost", "rand 0.8.5", "regex", - "rustls 0.23.12", + "rustls", "thiserror", "tokio", "tokio-stream", @@ -2819,7 +2811,7 @@ dependencies = [ "paste", "prost", "rand 0.8.5", - "rustls 0.23.12", + "rustls", "thiserror", "tokio", "tokio-stream", @@ -3337,13 +3329,15 @@ dependencies = [ name = "kaspa-utils-tower" version = "0.15.2" dependencies = [ + "bytes", "cfg-if 1.0.0", "futures", - "hyper 0.14.30", + "http-body 1.0.1", + "http-body-util", "log", "pin-project-lite", "tokio", - "tower", + "tower 0.5.1", "tower-http", ] @@ -3404,7 +3398,7 @@ dependencies = [ "async-channel 2.3.1", "async-std", "async-trait", - "base64 0.22.1", + "base64", "borsh", "cfb-mode", "cfg-if 1.0.0", @@ -3606,7 +3600,7 @@ dependencies = [ "paste", "rand 0.8.5", "regex", - "rustls 0.23.12", + "rustls", "serde", "serde-wasm-bindgen", "serde_json", @@ -3675,7 +3669,7 @@ dependencies = [ "log", "num_cpus", "paste", - "rustls 0.23.12", + "rustls", "serde", "thiserror", "tokio", @@ -4788,9 +4782,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.6" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" dependencies = [ "bytes", "prost-derive", @@ -4798,13 +4792,13 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.12.6" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" dependencies = [ "bytes", "heck", - "itertools 0.12.1", + "itertools 0.13.0", "log", "multimap", "once_cell", @@ -4819,12 +4813,12 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.6" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.13.0", "proc-macro2", "quote", "syn 2.0.75", @@ -4832,9 +4826,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.12.6" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +checksum = "60caa6738c7369b940c3d49246a8d1749323674c65cb13010134f5c9bad5b519" dependencies = [ "prost", ] @@ -4850,7 +4844,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.0.0", - "rustls 0.23.12", + "rustls", "socket2 0.5.7", "thiserror", "tokio", @@ -4867,7 +4861,7 @@ dependencies = [ "rand 0.8.5", "ring", "rustc-hash 2.0.0", - "rustls 0.23.12", + "rustls", "slab", "thiserror", "tinyvec", @@ -5067,7 +5061,7 @@ version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-core", @@ -5087,8 +5081,8 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.12", - "rustls-pemfile 2.1.3", + "rustls", + "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", @@ -5096,13 +5090,13 @@ dependencies = [ "sync_wrapper 1.0.1", "system-configuration", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.26.5", + "webpki-roots", "windows-registry", ] @@ -5227,48 +5221,28 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - [[package]] name = "rustls" version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ + "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.6", + "rustls-webpki", "subtle", "zeroize", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - [[package]] name = "rustls-pemfile" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ - "base64 0.22.1", + "base64", "rustls-pki-types", ] @@ -5278,16 +5252,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "rustls-webpki" version = "0.102.6" @@ -5341,16 +5305,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "secp256k1" version = "0.29.0" @@ -5486,7 +5440,7 @@ version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" dependencies = [ - "base64 0.22.1", + "base64", "chrono", "hex", "indexmap 1.9.3", @@ -5985,16 +5939,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[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]] name = "tokio-macros" version = "2.4.0" @@ -6006,23 +5950,13 @@ dependencies = [ "syn 2.0.75", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.12", + "rustls", "rustls-pki-types", "tokio", ] @@ -6046,12 +5980,12 @@ checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" dependencies = [ "futures-util", "log", - "rustls 0.23.12", + "rustls", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls", "tungstenite", - "webpki-roots 0.26.5", + "webpki-roots", ] [[package]] @@ -6114,41 +6048,43 @@ dependencies = [ [[package]] name = "tonic" -version = "0.10.2" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" +checksum = "c6f6ba989e4b2c58ae83d862d3a3e27690b6e3ae630d0deb59f3697f32aa88ad" dependencies = [ "async-stream", "async-trait", "axum", - "base64 0.21.7", + "base64", "bytes", "flate2", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.30", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", "hyper-timeout", + "hyper-util", "percent-encoding", "pin-project", "prost", - "rustls 0.21.12", - "rustls-pemfile 1.0.4", + "rustls-pemfile", + "socket2 0.5.7", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", "tokio-stream", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", - "webpki-roots 0.25.4", + "webpki-roots", ] [[package]] name = "tonic-build" -version = "0.10.2" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d021fc044c18582b9a2408cd0dd05b1596e3ecdb5c4df822bb0183545683889" +checksum = "fe4ee8877250136bd7e3d2331632810a4df4ea5e004656990d8d66d2f5ee8a67" dependencies = [ "prettyplease", "proc-macro2", @@ -6177,19 +6113,27 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "tower-layer", + "tower-service", +] + [[package]] name = "tower-http" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "bitflags 2.6.0", "bytes", - "futures-core", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "http-range-header", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", "pin-project-lite", "tower-layer", "tower-service", @@ -6213,7 +6157,6 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -6264,7 +6207,7 @@ dependencies = [ "httparse", "log", "rand 0.8.5", - "rustls 0.23.12", + "rustls", "rustls-pki-types", "sha1", "thiserror", @@ -6581,12 +6524,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - [[package]] name = "webpki-roots" version = "0.26.5" @@ -7156,7 +7093,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d161c4b844eee479f81306f2526266f9608a663e0a679d9fc0572ee15c144e06" dependencies = [ "async-std", - "base64 0.22.1", + "base64", "cfg-if 1.0.0", "chrome-sys", "faster-hex", diff --git a/Cargo.toml b/Cargo.toml index 37acfb172..8e6da754a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -150,6 +150,7 @@ bincode = { version = "1.3.3", default-features = false } blake2b_simd = "1.0.2" borsh = { version = "1.5.1", features = ["derive", "rc"] } bs58 = { version = "0.5.0", features = ["check"], default-features = false } +bytes = "1.7.1" cc = "1.0.83" cfb-mode = "0.8.2" cfg-if = "1.0.0" @@ -188,6 +189,8 @@ hex-literal = "0.4.1" hexplay = "0.3.0" hmac = { version = "0.12.1", default-features = false } home = "0.5.5" +http-body = "1.0.1" +http-body-util = "0.1.2" igd-next = { version = "0.14.2", features = ["aio_tokio"] } indexmap = "2.1.0" intertrait = "0.2.2" @@ -211,8 +214,7 @@ parking_lot = "0.12.1" paste = "1.0.14" pbkdf2 = "0.12.2" portable-atomic = { version = "1.5.1", features = ["float"] } -prost = "0.12.1" -# prost = "0.13.1" +prost = "0.13.2" rand = "0.8.5" rand_chacha = "0.3.1" rand_core = { version = "0.6.4", features = ["std"] } @@ -251,8 +253,8 @@ thiserror = "1.0.50" tokio = { version = "1.33.0", features = ["sync", "rt-multi-thread"] } tokio-stream = "0.1.14" toml = "0.8.8" -tonic = { version = "0.10.2", features = ["tls-webpki-roots", "gzip", "transport"] } -tonic-build = { version = "0.10.2", features = ["prost"] } +tonic = { version = "0.12.2", features = ["tls-webpki-roots", "gzip", "transport"] } +tonic-build = { version = "0.12.2", 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"] } @@ -262,12 +264,11 @@ web-sys = "0.3.70" xxhash-rust = { version = "0.8.7", features = ["xxh3"] } zeroize = { version = "1.6.0", default-features = false, features = ["alloc"] } pin-project-lite = "0.2.13" -tower-http = { version = "0.4.4", features = [ +tower-http = { version = "0.5.2", features = [ "map-response-body", "map-request-body", ] } -tower = "0.4.7" -hyper = "0.14.27" +tower = "0.5.1" chrono = "0.4.31" indexed_db_futures = "0.5.0" # workflow dependencies that are not a part of core libraries diff --git a/protocol/p2p/src/core/connection_handler.rs b/protocol/p2p/src/core/connection_handler.rs index a8ec431e4..54d387043 100644 --- a/protocol/p2p/src/core/connection_handler.rs +++ b/protocol/p2p/src/core/connection_handler.rs @@ -9,7 +9,7 @@ use kaspa_core::{debug, info}; use kaspa_utils::networking::NetAddress; use kaspa_utils_tower::{ counters::TowerConnectionCounters, - middleware::{measure_request_body_size_layer, CountBytesBody, MapResponseBodyLayer, ServiceBuilder}, + middleware::{BodyExt, CountBytesBody, MapRequestBodyLayer, MapResponseBodyLayer, ServiceBuilder}, }; use std::net::ToSocketAddrs; use std::pin::Pin; @@ -20,7 +20,6 @@ use tokio::sync::mpsc::{channel as mpsc_channel, Sender as MpscSender}; use tokio::sync::oneshot::{channel as oneshot_channel, Sender as OneshotSender}; use tokio_stream::wrappers::ReceiverStream; use tokio_stream::StreamExt; -use tonic::codegen::Body; use tonic::transport::{Error as TonicError, Server as TonicServer}; use tonic::{Request, Response, Status as TonicStatus, Streaming}; @@ -80,7 +79,7 @@ impl ConnectionHandler { // TODO: check whether we should set tcp_keepalive let serve_result = TonicServer::builder() - .layer(measure_request_body_size_layer(bytes_rx, |b| b)) + .layer(MapRequestBodyLayer::new(move |body| CountBytesBody::new(body, bytes_rx.clone()).boxed_unsync())) .layer(MapResponseBodyLayer::new(move |body| CountBytesBody::new(body, bytes_tx.clone()))) .add_service(proto_server) .serve_with_shutdown(serve_address.into(), termination_receiver.map(drop)) @@ -110,9 +109,7 @@ impl ConnectionHandler { let channel = ServiceBuilder::new() .layer(MapResponseBodyLayer::new(move |body| CountBytesBody::new(body, self.counters.bytes_rx.clone()))) - .layer(measure_request_body_size_layer(self.counters.bytes_tx.clone(), |body| { - body.map_err(|e| tonic::Status::from_error(Box::new(e))).boxed_unsync() - })) + .layer(MapRequestBodyLayer::new(move |body| CountBytesBody::new(body, self.counters.bytes_tx.clone()).boxed_unsync())) .service(channel); let mut client = ProtoP2pClient::new(channel) diff --git a/rpc/grpc/client/src/lib.rs b/rpc/grpc/client/src/lib.rs index 00dadee23..b7e53bb5e 100644 --- a/rpc/grpc/client/src/lib.rs +++ b/rpc/grpc/client/src/lib.rs @@ -38,7 +38,7 @@ use kaspa_rpc_core::{ use kaspa_utils::{channel::Channel, triggers::DuplexTrigger}; use kaspa_utils_tower::{ counters::TowerConnectionCounters, - middleware::{measure_request_body_size_layer, CountBytesBody, MapResponseBodyLayer, ServiceBuilder}, + middleware::{BodyExt, CountBytesBody, MapRequestBodyLayer, MapResponseBodyLayer, ServiceBuilder}, }; use regex::Regex; use std::{ @@ -50,7 +50,6 @@ use std::{ }; use tokio::sync::Mutex; use tonic::codec::CompressionEncoding; -use tonic::codegen::Body; use tonic::Streaming; mod connection_event; @@ -544,9 +543,7 @@ impl Inner { let bytes_tx = &counters.bytes_tx; let channel = ServiceBuilder::new() .layer(MapResponseBodyLayer::new(move |body| CountBytesBody::new(body, bytes_rx.clone()))) - .layer(measure_request_body_size_layer(bytes_tx.clone(), |body| { - body.map_err(|e| tonic::Status::from_error(Box::new(e))).boxed_unsync() - })) + .layer(MapRequestBodyLayer::new(move |body| CountBytesBody::new(body, bytes_tx.clone()).boxed_unsync())) .service(channel); // Build the gRPC client with an interceptor setting the request timeout diff --git a/rpc/grpc/server/src/connection_handler.rs b/rpc/grpc/server/src/connection_handler.rs index d581ea441..fd13cf9bb 100644 --- a/rpc/grpc/server/src/connection_handler.rs +++ b/rpc/grpc/server/src/connection_handler.rs @@ -29,7 +29,7 @@ use kaspa_rpc_core::{ use kaspa_utils::networking::NetAddress; use kaspa_utils_tower::{ counters::TowerConnectionCounters, - middleware::{measure_request_body_size_layer, CountBytesBody, MapResponseBodyLayer}, + middleware::{BodyExt, CountBytesBody, MapRequestBodyLayer, MapResponseBodyLayer}, }; use std::fmt::Debug; use std::{ @@ -144,7 +144,7 @@ impl ConnectionHandler { let serve_result = TonicServer::builder() // .http2_keepalive_interval(Some(GRPC_KEEP_ALIVE_PING_INTERVAL)) // .http2_keepalive_timeout(Some(GRPC_KEEP_ALIVE_PING_TIMEOUT)) - .layer(measure_request_body_size_layer(bytes_rx, |b| b)) + .layer(MapRequestBodyLayer::new(move |body| CountBytesBody::new(body, bytes_rx.clone()).boxed_unsync())) .layer(MapResponseBodyLayer::new(move |body| CountBytesBody::new(body, bytes_tx.clone()))) .add_service(protowire_server) .serve_with_shutdown( diff --git a/utils/tower/Cargo.toml b/utils/tower/Cargo.toml index 2a2f5f796..010f8843b 100644 --- a/utils/tower/Cargo.toml +++ b/utils/tower/Cargo.toml @@ -14,9 +14,11 @@ cfg-if.workspace = true log.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] +bytes.workspace = true futures.workspace = true -hyper.workspace = true +http-body.workspace = true +http-body-util.workspace = true pin-project-lite.workspace = true tokio.workspace = true tower-http.workspace = true -tower.workspace = true \ No newline at end of file +tower.workspace = true diff --git a/utils/tower/src/middleware.rs b/utils/tower/src/middleware.rs index 727d8ca47..8d0fa77c3 100644 --- a/utils/tower/src/middleware.rs +++ b/utils/tower/src/middleware.rs @@ -1,9 +1,6 @@ -use futures::ready; -use hyper::{ - body::{Bytes, HttpBody, SizeHint}, - HeaderMap, -}; -use log::*; +use bytes::Bytes; +use http_body::{Body, Frame, SizeHint}; +use log::trace; use pin_project_lite::pin_project; use std::{ pin::Pin, @@ -11,11 +8,12 @@ use std::{ atomic::{AtomicUsize, Ordering}, Arc, }, - task::{Context, Poll}, + task::{ready, Context, Poll}, }; + +pub use http_body_util::BodyExt; pub use tower::ServiceBuilder; -pub use tower_http::map_request_body::MapRequestBodyLayer; -pub use tower_http::map_response_body::MapResponseBodyLayer; +pub use tower_http::{map_request_body::MapRequestBodyLayer, map_response_body::MapResponseBodyLayer}; pin_project! { pub struct CountBytesBody { @@ -31,32 +29,29 @@ impl CountBytesBody { } } -impl HttpBody for CountBytesBody +impl Body for CountBytesBody where - B: HttpBody + Default, + B: Body + Default, { type Data = B::Data; type Error = B::Error; - fn poll_data(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>> { + fn poll_frame(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll, Self::Error>>> { let this = self.project(); - let counter: Arc = this.counter.clone(); - match ready!(this.inner.poll_data(cx)) { - Some(Ok(chunk)) => { - debug!("[SIZE MW] response body chunk size = {}", chunk.len()); - let _previous = counter.fetch_add(chunk.len(), Ordering::Relaxed); - debug!("[SIZE MW] total count: {}", _previous); + match ready!(this.inner.poll_frame(cx)) { + Some(Ok(frame)) => { + if let Some(chunk) = frame.data_ref() { + trace!("[SIZE MW] body chunk size = {}", chunk.len()); + let _previous = this.counter.fetch_add(chunk.len(), Ordering::Relaxed); + trace!("[SIZE MW] total count: {}", _previous); + } - Poll::Ready(Some(Ok(chunk))) + Poll::Ready(Some(Ok(frame))) } x => Poll::Ready(x), } } - fn poll_trailers(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll, Self::Error>> { - self.project().inner.poll_trailers(cx) - } - fn is_end_stream(&self) -> bool { self.inner.is_end_stream() } @@ -68,43 +63,9 @@ where impl Default for CountBytesBody where - B: HttpBody + Default, + B: Body + Default, { fn default() -> Self { Self { inner: Default::default(), counter: Default::default() } } } - -pub fn measure_request_body_size_layer( - bytes_sent_counter: Arc, - f: F, -) -> MapRequestBodyLayer B2 + Clone> -where - B1: HttpBody + Unpin + Send + 'static, - ::Error: Send, - F: Fn(hyper::body::Body) -> B2 + Clone, -{ - MapRequestBodyLayer::new(move |mut body: B1| { - let (mut tx, new_body) = hyper::Body::channel(); - let bytes_sent_counter = bytes_sent_counter.clone(); - tokio::spawn(async move { - while let Some(Ok(chunk)) = body.data().await { - debug!("[SIZE MW] request body chunk size = {}", chunk.len()); - let _previous = bytes_sent_counter.fetch_add(chunk.len(), Ordering::Relaxed); - debug!("[SIZE MW] total count: {}", _previous); - if let Err(_err) = tx.send_data(chunk).await { - // error can occurs only if the channel is already closed - debug!("[SIZE MW] error sending data: {}", _err) - } - } - - if let Ok(Some(trailers)) = body.trailers().await { - if let Err(_err) = tx.send_trailers(trailers).await { - // error can occurs only if the channel is already closed - debug!("[SIZE MW] error sending trailers: {}", _err) - } - } - }); - f(new_body) - }) -} From 2b0f3ab57c09d58082560ba81447c52630506b51 Mon Sep 17 00:00:00 2001 From: George Bogodukhov Date: Thu, 3 Oct 2024 16:36:45 +1000 Subject: [PATCH 12/19] Fix README.md layout and add linting section (#488) --- README.md | 88 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index ada38c55d..8749c49e9 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,15 @@ The default branch of this repository is `master` and new contributions are cons ## Installation
Building on Linux - + 1. Install general prerequisites ```bash - sudo apt install curl git build-essential libssl-dev pkg-config + sudo apt install curl git build-essential libssl-dev pkg-config ``` 2. Install Protobuf (required for gRPC) - + ```bash sudo apt install protobuf-compiler libprotobuf-dev #Required for gRPC ``` @@ -36,8 +36,8 @@ The default branch of this repository is `master` and new contributions are cons llvm python3-clang ``` 3. Install the [rust toolchain](https://rustup.rs/) - - If you already have rust installed, update it by running: `rustup update` + + If you already have rust installed, update it by running: `rustup update` 4. Install wasm-pack ```bash cargo install wasm-pack @@ -45,7 +45,7 @@ The default branch of this repository is `master` and new contributions are cons 4. Install wasm32 target ```bash rustup target add wasm32-unknown-unknown - ``` + ``` 5. Clone the repo ```bash git clone https://github.com/kaspanet/rusty-kaspa @@ -55,7 +55,7 @@ The default branch of this repository is `master` and new contributions are cons -
+
Building on Windows @@ -63,18 +63,18 @@ The default branch of this repository is `master` and new contributions are cons 2. Install [Protocol Buffers](https://github.com/protocolbuffers/protobuf/releases/download/v21.10/protoc-21.10-win64.zip) and add the `bin` directory to your `Path` - + 3. Install [LLVM-15.0.6-win64.exe](https://github.com/llvm/llvm-project/releases/download/llvmorg-15.0.6/LLVM-15.0.6-win64.exe) Add the `bin` directory of the LLVM installation (`C:\Program Files\LLVM\bin`) to PATH - + set `LIBCLANG_PATH` environment variable to point to the `bin` directory as well **IMPORTANT:** Due to C++ dependency configuration issues, LLVM `AR` installation on Windows may not function correctly when switching between WASM and native C++ code compilation (native `RocksDB+secp256k1` vs WASM32 builds of `secp256k1`). Unfortunately, manually setting `AR` environment variable also confuses C++ build toolchain (it should not be set for native but should be set for WASM32 targets). Currently, the best way to address this, is as follows: after installing LLVM on Windows, go to the target `bin` installation directory and copy or rename `LLVM_AR.exe` to `AR.exe`. - + 4. Install the [rust toolchain](https://rustup.rs/) - - If you already have rust installed, update it by running: `rustup update` + + If you already have rust installed, update it by running: `rustup update` 5. Install wasm-pack ```bash cargo install wasm-pack @@ -82,16 +82,16 @@ The default branch of this repository is `master` and new contributions are cons 6. Install wasm32 target ```bash rustup target add wasm32-unknown-unknown - ``` + ``` 7. Clone the repo ```bash git clone https://github.com/kaspanet/rusty-kaspa cd rusty-kaspa ``` -
+
-
+
Building on Mac OS @@ -99,8 +99,8 @@ The default branch of this repository is `master` and new contributions are cons ```bash brew install protobuf ``` - 2. Install llvm. - + 2. Install llvm. + The default XCode installation of `llvm` does not support WASM build targets. To build WASM on MacOS you need to install `llvm` from homebrew (at the time of writing, the llvm version for MacOS is 16.0.1). ```bash @@ -133,8 +133,8 @@ To build WASM on MacOS you need to install `llvm` from homebrew (at the time of source ~/.zshrc ``` 3. Install the [rust toolchain](https://rustup.rs/) - - If you already have rust installed, update it by running: `rustup update` + + If you already have rust installed, update it by running: `rustup update` 4. Install wasm-pack ```bash cargo install wasm-pack @@ -142,14 +142,14 @@ To build WASM on MacOS you need to install `llvm` from homebrew (at the time of 4. Install wasm32 target ```bash rustup target add wasm32-unknown-unknown - ``` + ``` 5. Clone the repo ```bash git clone https://github.com/kaspanet/rusty-kaspa cd rusty-kaspa ``` -
+
@@ -235,7 +235,7 @@ cargo run --release --bin kaspad -- --testnet ``` **Testnet 11** - + For participation in the 10BPS test network (TN11), see the following detailed [guide](docs/testnet11.md).
@@ -249,7 +249,7 @@ cargo run --release --bin kaspad -- --configfile /path/to/configfile.toml # or cargo run --release --bin kaspad -- -C /path/to/configfile.toml ``` - - The config file should be a list of \ = \ separated by newlines. + - The config file should be a list of \ = \ separated by newlines. - Whitespace around the `=` is fine, `arg=value` and `arg = value` are both parsed correctly. - Values with special characters like `.` or `=` will require quoting the value i.e \ = "\". - Arguments with multiple values should be surrounded with brackets like `addpeer = ["10.0.0.1", "1.2.3.4"]`. @@ -297,17 +297,17 @@ wRPC **Sidenote:** Rusty Kaspa integrates an optional wRPC - subsystem. wRPC is a high-performance, platform-neutral, Rust-centric, WebSocket-framed RPC + subsystem. wRPC is a high-performance, platform-neutral, Rust-centric, WebSocket-framed RPC implementation that can use [Borsh](https://borsh.io/) and JSON protocol encoding. - JSON protocol messaging - is similar to JSON-RPC 1.0, but differs from the specification due to server-side + JSON protocol messaging + is similar to JSON-RPC 1.0, but differs from the specification due to server-side notifications. [Borsh](https://borsh.io/) encoding is meant for inter-process communication. When using [Borsh](https://borsh.io/) - both client and server should be built from the same codebase. + both client and server should be built from the same codebase. - JSON protocol is based on + JSON protocol is based on Kaspa data structures and is data-structure-version agnostic. You can connect to the JSON endpoint using any WebSocket library. Built-in RPC clients for JavaScript and TypeScript capable of running in web browsers and Node.js are available as a part of @@ -316,27 +316,23 @@ wRPC
- -
- - ## Benchmarking & Testing -
+
Simulation framework (Simpa) Logging in `kaspad` and `simpa` can be [filtered](https://docs.rs/env_logger/0.10.0/env_logger/#filtering-results) by either: -The current codebase supports a full in-process network simulation, building an actual DAG over virtual time with virtual delay and benchmarking validation time (following the simulation generation). +The current codebase supports a full in-process network simulation, building an actual DAG over virtual time with virtual delay and benchmarking validation time (following the simulation generation). To see the available commands -```bash +```bash cargo run --release --bin simpa -- --help -``` +``` -The following command will run a simulation to produce 1000 blocks with communication delay of 2 seconds and 8 BPS (blocks per second) while attempting to fill each block with up to 200 transactions. +The following command will run a simulation to produce 1000 blocks with communication delay of 2 seconds and 8 BPS (blocks per second) while attempting to fill each block with up to 200 transactions. ```bash cargo run --release --bin simpa -- -t=200 -d=2 -b=8 -n=1000 @@ -347,7 +343,7 @@ cargo run --release --bin simpa -- -t=200 -d=2 -b=8 -n=1000 -
+
Heap Profiling @@ -362,7 +358,7 @@ It will produce `{bin-name}-heap.json` file in the root of the workdir, that can
-
+
Tests @@ -384,12 +380,21 @@ cd rusty-kaspa cargo nextest run --release ``` +
+
+ +Lints + +```bash +cd rusty-kaspa +./check +```
-
+
Benchmarks @@ -400,7 +405,7 @@ cargo bench
-
+
Logging @@ -415,4 +420,3 @@ Logging in `kaspad` and `simpa` can be [filtered](https://docs.rs/env_logger/0.1 In this command we set the `loglevel` to `INFO`.
- From b37f0305401b16e80fea74e6d3b1a2cbb7ac5c44 Mon Sep 17 00:00:00 2001 From: Michael Sutton Date: Sun, 6 Oct 2024 14:55:24 +0300 Subject: [PATCH 13/19] Bump tonic version (#579) --- Cargo.lock | 13 +++++++------ Cargo.toml | 4 ++-- protocol/p2p/build.rs | 2 +- rpc/grpc/core/build.rs | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e9829fea..b5e4bc913 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5963,9 +5963,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -6048,9 +6048,9 @@ dependencies = [ [[package]] name = "tonic" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f6ba989e4b2c58ae83d862d3a3e27690b6e3ae630d0deb59f3697f32aa88ad" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ "async-stream", "async-trait", @@ -6082,13 +6082,14 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4ee8877250136bd7e3d2331632810a4df4ea5e004656990d8d66d2f5ee8a67" +checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" dependencies = [ "prettyplease", "proc-macro2", "prost-build", + "prost-types", "quote", "syn 2.0.75", ] diff --git a/Cargo.toml b/Cargo.toml index 8e6da754a..b99276945 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -253,8 +253,8 @@ thiserror = "1.0.50" tokio = { version = "1.33.0", features = ["sync", "rt-multi-thread"] } tokio-stream = "0.1.14" toml = "0.8.8" -tonic = { version = "0.12.2", features = ["tls-webpki-roots", "gzip", "transport"] } -tonic-build = { version = "0.12.2", features = ["prost"] } +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"] } diff --git a/protocol/p2p/build.rs b/protocol/p2p/build.rs index b41fe87f5..b4aea6939 100644 --- a/protocol/p2p/build.rs +++ b/protocol/p2p/build.rs @@ -5,7 +5,7 @@ fn main() { tonic_build::configure() .build_server(true) .build_client(true) - .compile(&proto_files[0..1], dirs) + .compile_protos(&proto_files[0..1], dirs) .unwrap_or_else(|e| panic!("protobuf compilation failed, error: {e}")); // recompile protobufs only if any of the proto files changes. for file in proto_files { diff --git a/rpc/grpc/core/build.rs b/rpc/grpc/core/build.rs index fdf54486d..b3a0614ea 100644 --- a/rpc/grpc/core/build.rs +++ b/rpc/grpc/core/build.rs @@ -10,7 +10,7 @@ fn main() { // uncomment this line and reflect the change in src/lib.rs //.out_dir("./src") - .compile(&protowire_files[0..1], dirs) + .compile_protos(&protowire_files[0..1], dirs) .unwrap_or_else(|e| panic!("protobuf compile error: {e}")); // recompile protobufs only if any of the proto files changes. From 1378e7b6015fcbcc098ec03103f6ce02273adefc Mon Sep 17 00:00:00 2001 From: D-Stacks <78099568+D-Stacks@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:41:32 +0200 Subject: [PATCH 14/19] replace statrs and statest deps & upgrade some deps. (#425) * replace statrs and statest deps. * remove todo in toml.cargo and fmt & lints. * do a run of `cargo audit fix` for some miscellaneous reports. * use maintained alt ks crate. * add cargo.lock. * update * use new command * newline * refresh cargo lock with a few more version updates * fix minor readme glitches --------- Co-authored-by: Michael Sutton --- Cargo.lock | 1167 +++++++++++--------------- Cargo.toml | 3 +- README.md | 5 +- components/addressmanager/Cargo.toml | 3 +- components/addressmanager/src/lib.rs | 17 +- 5 files changed, 488 insertions(+), 707 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b5e4bc913..3449a43da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,24 +11,18 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" @@ -42,7 +36,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", - "generic-array 0.14.7", + "generic-array", ] [[package]] @@ -63,7 +57,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if 1.0.0", - "getrandom 0.2.15", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -78,17 +72,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "alga" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f823d037a7ec6ea2197046bafd4ae150e6bc36f9ca347404f46a46823fa84f2" -dependencies = [ - "approx", - "num-complex 0.2.4", - "num-traits", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -170,18 +153,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" - -[[package]] -name = "approx" -version = "0.3.2" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" -dependencies = [ - "num-traits", -] +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "arc-swap" @@ -203,9 +177,9 @@ dependencies = [ [[package]] name = "arrayref" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -248,14 +222,14 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.1.0", - "futures-lite 2.3.0", + "fastrand", + "futures-lite", "slab", ] @@ -267,61 +241,32 @@ checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ "async-channel 2.3.1", "async-executor", - "async-io 2.3.4", - "async-lock 3.4.0", + "async-io", + "async-lock", "blocking", - "futures-lite 2.3.0", + "futures-lite", "once_cell", ] -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "cfg-if 1.0.0", - "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.27", - "slab", - "socket2 0.4.10", - "waker-fn", -] - [[package]] name = "async-io" version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" dependencies = [ - "async-lock 3.4.0", + "async-lock", "cfg-if 1.0.0", "concurrent-queue", "futures-io", - "futures-lite 2.3.0", + "futures-lite", "parking", - "polling 3.7.3", - "rustix 0.38.34", + "polling", + "rustix", "slab", "tracing", "windows-sys 0.59.0", ] -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", -] - [[package]] name = "async-lock" version = "3.4.0" @@ -335,20 +280,20 @@ dependencies = [ [[package]] name = "async-std" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" dependencies = [ "async-attributes", "async-channel 1.9.0", "async-global-executor", - "async-io 1.13.0", - "async-lock 2.8.0", + "async-io", + "async-lock", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", - "futures-lite 1.13.0", + "futures-lite", "gloo-timers", "kv-log-macro", "log", @@ -362,9 +307,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", @@ -373,13 +318,13 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -390,13 +335,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -429,15 +374,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" dependencies = [ "async-trait", "axum-core", @@ -455,16 +400,16 @@ dependencies = [ "rustversion", "serde", "sync_wrapper 1.0.1", - "tower 0.4.13", + "tower 0.5.1", "tower-layer", "tower-service", ] [[package]] name = "axum-core" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ "async-trait", "bytes", @@ -475,24 +420,24 @@ dependencies = [ "mime", "pin-project-lite", "rustversion", - "sync_wrapper 0.1.2", + "sync_wrapper 1.0.1", "tower-layer", "tower-service", ] [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if 1.0.0", "libc", - "miniz_oxide 0.7.4", + "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -556,7 +501,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -597,7 +542,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array 0.14.7", + "generic-array", ] [[package]] @@ -609,7 +554,7 @@ dependencies = [ "async-channel 2.3.1", "async-task", "futures-io", - "futures-lite 2.3.0", + "futures-lite", "piper", ] @@ -633,7 +578,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", "syn_derive", ] @@ -661,9 +606,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "bzip2-sys" @@ -716,9 +661,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.13" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" dependencies = [ "jobserver", "libc", @@ -884,9 +829,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.16" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" dependencies = [ "clap_builder", "clap_derive", @@ -894,9 +839,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" dependencies = [ "anstream", "anstyle", @@ -906,14 +851,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -962,9 +907,9 @@ dependencies = [ [[package]] name = "constant_time_eq" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "convert_case" @@ -1005,9 +950,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -1030,7 +975,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.5.16", + "clap 4.5.19", "criterion-plot", "is-terminal", "itertools 0.10.5", @@ -1126,8 +1071,8 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.7", - "rand_core 0.6.4", + "generic-array", + "rand_core", "typenum", ] @@ -1155,7 +1100,7 @@ dependencies = [ "aead", "chacha20", "cipher", - "generic-array 0.14.7", + "generic-array", "poly1305", "salsa20", "subtle", @@ -1195,7 +1140,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -1219,7 +1164,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -1230,14 +1175,14 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] name = "dashmap" -version = "6.0.1" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -1262,7 +1207,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -1288,33 +1233,33 @@ dependencies = [ [[package]] name = "derive_builder" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" +checksum = "cd33f37ee6a119146a1781d3356a7c26028f83d779b2e04ecd45fdc75c76877b" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" +checksum = "7431fa049613920234f22c47fdc33e6cf3ee83067091ea4277a3f8c4587aae38" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] name = "derive_builder_macro" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" +checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc" dependencies = [ "derive_builder_core", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -1327,7 +1272,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -1390,6 +1335,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "downcast" version = "0.11.0" @@ -1449,7 +1400,7 @@ checksum = "ba7795da175654fe16979af73f81f26a8ea27638d8d9823d317016888a63dc4c" dependencies = [ "num-traits", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -1526,7 +1477,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -1540,18 +1491,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fiat-crypto" @@ -1561,9 +1503,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "filetime" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if 1.0.0", "libc", @@ -1579,21 +1521,21 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "fixedstr" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e049f021908beff8f8c430a99f5c136d3be69f1667346e581f446b173bc012" +checksum = "60aba7afd9b1b9e1950c2b7e8bcac3cc44a273c62a02717dedca2d0a1aee694d" dependencies = [ "serde", ] [[package]] name = "flate2" -version = "1.0.32" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c0596c1eac1f9e04ed902702e9878208b336edc9d6fddc8a48387349bab3666" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", - "miniz_oxide 0.8.0", + "miniz_oxide", ] [[package]] @@ -1613,9 +1555,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1628,9 +1570,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1638,15 +1580,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1655,24 +1597,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" @@ -1680,7 +1607,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ - "fastrand 2.1.0", + "fastrand", "futures-core", "futures-io", "parking", @@ -1689,32 +1616,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1728,15 +1655,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generic-array" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309" -dependencies = [ - "typenum", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1748,17 +1666,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.15" @@ -1768,15 +1675,15 @@ dependencies = [ "cfg-if 1.0.0", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" @@ -1786,9 +1693,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "gloo-timers" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ "futures-channel", "futures-core", @@ -1808,7 +1715,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.4.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -1827,7 +1734,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.4.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -1859,6 +1766,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1868,6 +1784,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "heapless" version = "0.8.0" @@ -2006,9 +1928,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -2039,7 +1961,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.7", + "socket2", "tokio", "tower-service", "tracing", @@ -2069,9 +1991,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", @@ -2100,9 +2022,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", @@ -2111,18 +2033,17 @@ dependencies = [ "http-body 1.0.1", "hyper 1.4.1", "pin-project-lite", - "socket2 0.5.7", + "socket2", "tokio", - "tower 0.4.13", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2170,7 +2091,7 @@ dependencies = [ "http 0.2.12", "hyper 0.14.30", "log", - "rand 0.8.5", + "rand", "tokio", "url", "xmltree", @@ -2206,12 +2127,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.0", "serde", ] @@ -2221,7 +2142,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "generic-array 0.14.7", + "generic-array", ] [[package]] @@ -2259,22 +2180,11 @@ dependencies = [ "uuid 0.8.2", ] -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is-terminal" @@ -2384,11 +2294,10 @@ dependencies = [ "local-ip-address", "log", "parking_lot", - "rand 0.8.5", + "rand", "rocksdb", + "rv", "serde", - "statest", - "statrs", "thiserror", "tokio", ] @@ -2407,15 +2316,15 @@ dependencies = [ "borsh", "bs58", "faster-hex", - "getrandom 0.2.15", + "getrandom", "hmac", "js-sys", "kaspa-consensus-core", "kaspa-utils", "once_cell", "pbkdf2", - "rand 0.8.5", - "rand_core 0.6.4", + "rand", + "rand_core", "ripemd", "secp256k1", "serde", @@ -2487,7 +2396,7 @@ dependencies = [ "kaspa-utils", "log", "parking_lot", - "rand 0.8.5", + "rand", "tokio", ] @@ -2503,7 +2412,7 @@ dependencies = [ "faster-hex", "flate2", "futures-util", - "indexmap 2.4.0", + "indexmap 2.6.0", "itertools 0.13.0", "kaspa-consensus-core", "kaspa-consensus-notify", @@ -2522,8 +2431,8 @@ dependencies = [ "log", "once_cell", "parking_lot", - "rand 0.8.5", - "rand_distr 0.4.3", + "rand", + "rand_distr", "rayon", "rocksdb", "secp256k1", @@ -2551,7 +2460,7 @@ dependencies = [ "kaspa-txscript", "kaspa-utils", "kaspa-wasm-core", - "rand 0.8.5", + "rand", "secp256k1", "serde", "serde-wasm-bindgen", @@ -2573,7 +2482,7 @@ dependencies = [ "criterion", "faster-hex", "futures-util", - "getrandom 0.2.15", + "getrandom", "itertools 0.13.0", "js-sys", "kaspa-addresses", @@ -2584,7 +2493,7 @@ dependencies = [ "kaspa-muhash", "kaspa-txscript-errors", "kaspa-utils", - "rand 0.8.5", + "rand", "secp256k1", "serde", "serde-wasm-bindgen", @@ -2632,7 +2541,7 @@ dependencies = [ "kaspa-hashes", "kaspa-txscript", "kaspa-utils", - "rand 0.8.5", + "rand", "secp256k1", "serde", "serde-wasm-bindgen", @@ -2657,7 +2566,7 @@ dependencies = [ "kaspa-utils", "log", "parking_lot", - "rand 0.8.5", + "rand", "tokio", ] @@ -2708,14 +2617,14 @@ dependencies = [ "bincode", "enum-primitive-derive", "faster-hex", - "indexmap 2.4.0", + "indexmap 2.6.0", "itertools 0.13.0", "kaspa-hashes", "kaspa-utils", "num-traits", "num_cpus", "parking_lot", - "rand 0.8.5", + "rand", "rocksdb", "serde", "smallvec", @@ -2745,7 +2654,7 @@ dependencies = [ "parking_lot", "paste", "prost", - "rand 0.8.5", + "rand", "regex", "rustls", "thiserror", @@ -2773,7 +2682,7 @@ dependencies = [ "log", "paste", "prost", - "rand 0.8.5", + "rand", "regex", "thiserror", "tokio", @@ -2810,7 +2719,7 @@ dependencies = [ "parking_lot", "paste", "prost", - "rand 0.8.5", + "rand", "rustls", "thiserror", "tokio", @@ -2833,7 +2742,7 @@ dependencies = [ "kaspa-utils", "keccak", "once_cell", - "rand 0.8.5", + "rand", "serde", "sha2", "sha3", @@ -2882,7 +2791,7 @@ dependencies = [ "log", "parking_lot", "paste", - "rand 0.8.5", + "rand", "thiserror", "tokio", "triggered", @@ -2899,7 +2808,7 @@ dependencies = [ "kaspa-utils", "malachite-base", "malachite-nz", - "rand_chacha 0.3.1", + "rand_chacha", "serde", "serde-wasm-bindgen", "thiserror", @@ -2950,7 +2859,7 @@ dependencies = [ "kaspa-utils", "log", "parking_lot", - "rand 0.8.5", + "rand", "secp256k1", "serde", "smallvec", @@ -2974,8 +2883,8 @@ dependencies = [ "criterion", "kaspa-hashes", "kaspa-math", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand", + "rand_chacha", "rayon", "serde", ] @@ -2991,7 +2900,7 @@ dependencies = [ "derive_more", "futures", "futures-util", - "indexmap 2.4.0", + "indexmap 2.6.0", "itertools 0.13.0", "kaspa-addresses", "kaspa-alloc", @@ -3005,7 +2914,7 @@ dependencies = [ "log", "parking_lot", "paste", - "rand 0.8.5", + "rand", "serde", "thiserror", "tokio", @@ -3023,7 +2932,7 @@ dependencies = [ "async-trait", "chrono", "futures", - "indexmap 2.4.0", + "indexmap 2.6.0", "itertools 0.13.0", "kaspa-addressmanager", "kaspa-connectionmanager", @@ -3040,7 +2949,7 @@ dependencies = [ "kaspa-utils-tower", "log", "parking_lot", - "rand 0.8.5", + "rand", "thiserror", "tokio", "tokio-stream", @@ -3067,7 +2976,7 @@ dependencies = [ "log", "parking_lot", "prost", - "rand 0.8.5", + "rand", "seqlock", "serde", "thiserror", @@ -3135,7 +3044,7 @@ dependencies = [ "kaspa-utils", "log", "paste", - "rand 0.8.5", + "rand", "serde", "serde-wasm-bindgen", "serde_json", @@ -3197,14 +3106,14 @@ dependencies = [ "async-trait", "bincode", "chrono", - "clap 4.5.16", + "clap 4.5.19", "criterion", "crossbeam-channel", "dhat", "faster-hex", "flate2", "futures-util", - "indexmap 2.4.0", + "indexmap 2.6.0", "itertools 0.13.0", "kaspa-addresses", "kaspa-alloc", @@ -3235,8 +3144,8 @@ dependencies = [ "kaspad", "log", "parking_lot", - "rand 0.8.5", - "rand_distr 0.4.3", + "rand", + "rand_distr", "rayon", "rocksdb", "secp256k1", @@ -3259,7 +3168,7 @@ dependencies = [ "criterion", "hex", "hexplay", - "indexmap 2.4.0", + "indexmap 2.6.0", "itertools 0.13.0", "kaspa-addresses", "kaspa-consensus-core", @@ -3269,7 +3178,7 @@ dependencies = [ "kaspa-wasm-core", "log", "parking_lot", - "rand 0.8.5", + "rand", "secp256k1", "serde", "serde-wasm-bindgen", @@ -3311,7 +3220,7 @@ dependencies = [ "num_cpus", "once_cell", "parking_lot", - "rand 0.8.5", + "rand", "rlimit", "serde", "serde_json", @@ -3356,7 +3265,7 @@ dependencies = [ "kaspa-utils", "log", "parking_lot", - "rand 0.8.5", + "rand", "rocksdb", "serde", "thiserror", @@ -3441,7 +3350,7 @@ dependencies = [ "md-5", "pad", "pbkdf2", - "rand 0.8.5", + "rand", "regex", "ripemd", "secp256k1", @@ -3486,7 +3395,7 @@ dependencies = [ "kaspa-txscript-errors", "kaspa-utils", "kaspa-wasm-core", - "rand 0.8.5", + "rand", "ripemd", "secp256k1", "serde", @@ -3598,7 +3507,7 @@ dependencies = [ "kaspa-rpc-core", "kaspa-rpc-macros", "paste", - "rand 0.8.5", + "rand", "regex", "rustls", "serde", @@ -3637,7 +3546,7 @@ name = "kaspa-wrpc-proxy" version = "0.15.2" dependencies = [ "async-trait", - "clap 4.5.16", + "clap 4.5.19", "kaspa-consensus-core", "kaspa-grpc-client", "kaspa-rpc-core", @@ -3725,7 +3634,7 @@ version = "0.15.2" dependencies = [ "async-channel 2.3.1", "cfg-if 1.0.0", - "clap 4.5.16", + "clap 4.5.19", "dhat", "dirs", "futures-util", @@ -3754,7 +3663,7 @@ dependencies = [ "kaspa-wrpc-server", "log", "num_cpus", - "rand 0.8.5", + "rand", "rayon", "serde", "serde_with", @@ -3797,9 +3706,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libloading" @@ -3856,9 +3765,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.19" +version = "1.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc53a7799a7496ebc9fd29f31f7df80e83c9bda5299768af5f9e59eeea74647" +checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" dependencies = [ "cc", "pkg-config", @@ -3885,12 +3794,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -3899,14 +3802,14 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "local-ip-address" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136ef34e18462b17bf39a7826f8f3bbc223341f8e83822beb8b77db9a3d49696" +checksum = "3669cf5561f8d27e8fc84cc15e58350e70f557d4d65f70e3154e54cd2f8e1782" dependencies = [ "libc", "neli", "thiserror", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -3953,7 +3856,7 @@ dependencies = [ "log-mdc", "once_cell", "parking_lot", - "rand 0.8.5", + "rand", "serde", "serde-value", "serde_json", @@ -3964,11 +3867,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "lru" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e7d46de488603ffdd5f30afbc64fbba2378214a2c3a2fb83abf3d33126df17" +dependencies = [ + "hashbrown 0.13.2", +] + [[package]] name = "lz4-sys" -version = "1.10.0" +version = "1.11.1+lz4-1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109de74d5d2353660401699a4174a4ff23fcc649caf553df71933c7fb45ad868" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" dependencies = [ "cc", "libc", @@ -4013,7 +3925,7 @@ dependencies = [ "cfg-if 1.0.0", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -4024,7 +3936,7 @@ checksum = "13198c120864097a565ccb3ff947672d969932b7975ebd4085732c9f09435e55" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -4037,14 +3949,14 @@ dependencies = [ "macroific_core", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] name = "malachite-base" -version = "0.4.15" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5f8d7930df6fcb9c86761ca0999ba484d7b6469c81cee4a7d38da5386440f96" +checksum = "46059721011b0458b7bd6d9179be5d0b60294281c23320c207adceaecc54d13b" dependencies = [ "hashbrown 0.14.5", "itertools 0.11.0", @@ -4054,9 +3966,9 @@ dependencies = [ [[package]] name = "malachite-nz" -version = "0.4.15" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa263ca62420c1f65cf6758f55c979a49ad83169f332e602b1890f1e1277a429" +checksum = "1503b27e825cabd1c3d0ff1e95a39fb2ec9eab6fd3da6cfa41aec7091d273e78" dependencies = [ "itertools 0.11.0", "libm", @@ -4080,11 +3992,15 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "matrixmultiply" -version = "0.2.4" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916806ba0031cd542105d916a97c8572e1fa6dd79c9c51e7eb43a09ec2dd84c1" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" dependencies = [ + "autocfg", + "num_cpus", + "once_cell", "rawpointer", + "thread-tree", ] [[package]] @@ -4143,15 +4059,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.8.0" @@ -4175,7 +4082,7 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] @@ -4187,7 +4094,7 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.52.0", ] @@ -4197,44 +4104,13 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" -[[package]] -name = "nalgebra" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abb021006c01b126a936a8dd1351e0720d83995f4fc942d0d426c654f990745" -dependencies = [ - "alga", - "approx", - "generic-array 0.13.3", - "matrixmultiply", - "num-complex 0.2.4", - "num-rational 0.2.4", - "num-traits", - "rand 0.7.3", - "rand_distr 0.2.2", - "typenum", -] - [[package]] name = "nanoid" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" dependencies = [ - "rand 0.8.5", -] - -[[package]] -name = "ndarray" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac06db03ec2f46ee0ecdca1a1c34a99c0d188a0d83439b84bf0cb4b386e4ab09" -dependencies = [ - "matrixmultiply", - "num-complex 0.2.4", - "num-integer", - "num-traits", - "rawpointer", + "rand", ] [[package]] @@ -4326,10 +4202,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", - "num-complex 0.4.6", + "num-complex", "num-integer", "num-iter", - "num-rational 0.4.2", + "num-rational", "num-traits", ] @@ -4343,16 +4219,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-complex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" -dependencies = [ - "autocfg", - "num-traits", -] - [[package]] name = "num-complex" version = "0.4.6" @@ -4388,17 +4254,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" version = "0.4.2" @@ -4460,18 +4315,18 @@ dependencies = [ [[package]] name = "object" -version = "0.36.3" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oorandom" @@ -4491,6 +4346,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "order-stat" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa535d5117d3661134dbf1719b6f0ffe06f2375843b13935db186cd094105eb" + [[package]] name = "ordered-float" version = "2.10.1" @@ -4521,9 +4382,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" @@ -4565,7 +4426,7 @@ checksum = "70df726c43c645ef1dde24c7ae14692036ebe5457c92c5f0ec4cfceb99634ff6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -4575,7 +4436,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -4607,6 +4468,30 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "peroxide" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703b5fbdc1f9018a66e2db8758633cec31d39ad3127bfd38c9b6ad510637519c" +dependencies = [ + "matrixmultiply", + "order-stat", + "peroxide-ad", + "puruspe", + "rand", + "rand_distr", +] + +[[package]] +name = "peroxide-ad" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6fba8ff3f40b67996f7c745f699babaa3e57ef5c8178ec999daf7eedc51dc8c" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "petgraph" version = "0.6.5" @@ -4614,27 +4499,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.4.0", + "indexmap 2.6.0", ] [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -4656,31 +4541,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.1.0", + "fastrand", "futures-io", ] [[package]] name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - -[[package]] -name = "polling" -version = "2.8.0" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if 1.0.0", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "polling" @@ -4692,7 +4561,7 @@ dependencies = [ "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.34", + "rustix", "tracing", "windows-sys 0.59.0", ] @@ -4710,9 +4579,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "powerfmt" @@ -4731,21 +4600,21 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.21.1", + "toml_edit", ] [[package]] @@ -4782,9 +4651,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" +checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" dependencies = [ "bytes", "prost-derive", @@ -4792,9 +4661,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" +checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15" dependencies = [ "bytes", "heck", @@ -4807,32 +4676,38 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.75", + "syn 2.0.79", "tempfile", ] [[package]] name = "prost-derive" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" +checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" dependencies = [ "anyhow", "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] name = "prost-types" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60caa6738c7369b940c3d49246a8d1749323674c65cb13010134f5c9bad5b519" +checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670" dependencies = [ "prost", ] +[[package]] +name = "puruspe" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3804877ffeba468c806c2ad9057bbbae92e4b2c410c2f108baaa0042f241fa4c" + [[package]] name = "quinn" version = "0.11.5" @@ -4845,7 +4720,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.0.0", "rustls", - "socket2 0.5.7", + "socket2", "thiserror", "tokio", "tracing", @@ -4858,7 +4733,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" dependencies = [ "bytes", - "rand 0.8.5", + "rand", "ring", "rustc-hash 2.0.0", "rustls", @@ -4876,33 +4751,20 @@ checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" dependencies = [ "libc", "once_cell", - "socket2 0.5.7", + "socket2", "tracing", "windows-sys 0.59.0", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 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" @@ -4910,18 +4772,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "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", + "rand_chacha", + "rand_core", ] [[package]] @@ -4931,16 +4783,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "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", + "rand_core", ] [[package]] @@ -4949,16 +4792,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", -] - -[[package]] -name = "rand_distr" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2" -dependencies = [ - "rand 0.7.3", + "getrandom", ] [[package]] @@ -4968,16 +4802,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", - "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", + "rand", ] [[package]] @@ -5008,9 +4833,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] @@ -5021,16 +4846,16 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.15", + "getrandom", "libredox", "thiserror", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -5040,9 +4865,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -5051,15 +4876,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64", "bytes", @@ -5108,7 +4933,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if 1.0.0", - "getrandom 0.2.15", + "getrandom", "libc", "spin", "untrusted", @@ -5126,9 +4951,9 @@ dependencies = [ [[package]] name = "rlimit" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3560f70f30a0f16d11d01ed078a07740fe6b489667abc7c7b029155d9f21c3d8" +checksum = "7043b63bd0cd1aaa628e476b80e6d4023a3b50eb32789f2728908107bd0c793a" dependencies = [ "libc", ] @@ -5148,7 +4973,7 @@ name = "rothschild" version = "0.15.2" dependencies = [ "async-channel 2.3.1", - "clap 4.5.16", + "clap 4.5.19", "criterion", "faster-hex", "itertools 0.13.0", @@ -5187,45 +5012,31 @@ checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" -version = "0.37.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustix" -version = "0.38.34" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys 0.4.14", + "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" dependencies = [ "log", "once_cell", @@ -5238,25 +5049,24 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" -version = "0.102.6" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -5269,6 +5079,22 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +[[package]] +name = "rv" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07e0a3b756794c7ea2f05d93760ffb946ff4f94b255d92444d94c19fd71f4ab" +dependencies = [ + "doc-comment", + "lru", + "num", + "num-traits", + "peroxide", + "rand", + "rand_distr", + "special", +] + [[package]] name = "ryu" version = "1.0.18" @@ -5307,20 +5133,20 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "secp256k1" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "rand 0.8.5", + "rand", "secp256k1-sys", "serde", ] [[package]] name = "secp256k1-sys" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1433bd67156263443f14d603720b082dd3121779323fce20cba2aa07b874bc1b" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" dependencies = [ "cc", ] @@ -5351,9 +5177,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] @@ -5381,20 +5207,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -5410,14 +5236,14 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -5436,15 +5262,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.9.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.4.0", + "indexmap 2.6.0", "serde", "serde_derive", "serde_json", @@ -5454,14 +5280,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.9.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -5470,7 +5296,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.6.0", "itoa", "ryu", "serde", @@ -5561,11 +5387,11 @@ version = "0.15.2" dependencies = [ "async-channel 2.3.1", "cfg-if 1.0.0", - "clap 4.5.16", + "clap 4.5.19", "dhat", "futures", "futures-util", - "indexmap 2.4.0", + "indexmap 2.6.0", "itertools 0.13.0", "kaspa-alloc", "kaspa-consensus", @@ -5578,8 +5404,8 @@ dependencies = [ "kaspa-utils", "log", "num_cpus", - "rand 0.8.5", - "rand_distr 0.4.3", + "rand", + "rand_distr", "rayon", "secp256k1", "tokio", @@ -5619,16 +5445,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.5.7" @@ -5645,6 +5461,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd62203d74a728ae353b4d716fc2a80e8da881dfdf8bbc0c012d877a58c4030" +[[package]] +name = "special" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89cf0d71ae639fdd8097350bfac415a41aabf1d5ddd356295fdc95f09760382" +dependencies = [ + "libm", +] + [[package]] name = "spin" version = "0.9.8" @@ -5657,27 +5482,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "statest" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04ed65138bd1680f47e4d980ac7d3cf5e827fa99c2fa6683e640094a494602b4" -dependencies = [ - "ndarray", - "num-traits", - "statrs", -] - -[[package]] -name = "statrs" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e34b58a8f9b7462b6922e0b4e3c83d1b3c2075f7f996a56d6c66afa81590064" -dependencies = [ - "nalgebra", - "rand 0.7.3", -] - [[package]] name = "strsim" version = "0.8.0" @@ -5715,9 +5519,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.75" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -5733,7 +5537,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -5753,9 +5557,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.31.2" +version = "0.31.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4115055da5f572fff541dd0c4e61b0262977f453cc9fe04be83aba25a89bdab" +checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" dependencies = [ "core-foundation-sys", "libc", @@ -5788,14 +5592,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if 1.0.0", - "fastrand 2.1.0", + "fastrand", "once_cell", - "rustix 0.38.34", + "rustix", "windows-sys 0.59.0", ] @@ -5830,22 +5634,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -5864,6 +5668,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "thread-tree" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbd370cb847953a25954d9f63e14824a36113f8c72eecf6eccef5dc4b45d630" +dependencies = [ + "crossbeam-channel", +] + [[package]] name = "time" version = "0.3.36" @@ -5924,9 +5737,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.3" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", @@ -5934,7 +5747,7 @@ dependencies = [ "mio 1.0.2", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.7", + "socket2", "tokio-macros", "windows-sys 0.52.0", ] @@ -5947,7 +5760,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -5990,9 +5803,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -6010,7 +5823,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.20", + "toml_edit", ] [[package]] @@ -6024,26 +5837,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap 2.4.0", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.20" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.18", + "winnow", ] [[package]] @@ -6069,7 +5871,7 @@ dependencies = [ "pin-project", "prost", "rustls-pemfile", - "socket2 0.5.7", + "socket2", "tokio", "tokio-rustls", "tokio-stream", @@ -6091,7 +5893,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -6105,7 +5907,7 @@ dependencies = [ "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand 0.8.5", + "rand", "slab", "tokio", "tokio-util", @@ -6120,6 +5922,10 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 0.1.2", "tower-layer", "tower-service", ] @@ -6171,7 +5977,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -6207,7 +6013,7 @@ dependencies = [ "http 1.1.0", "httparse", "log", - "rand 0.8.5", + "rand", "rustls", "rustls-pki-types", "sha1", @@ -6232,15 +6038,15 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-linebreak" @@ -6250,24 +6056,24 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "universal-hash" @@ -6329,7 +6135,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.15", + "getrandom", ] [[package]] @@ -6338,8 +6144,8 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ - "getrandom 0.2.15", - "rand 0.8.5", + "getrandom", + "rand", "serde", "wasm-bindgen", ] @@ -6383,12 +6189,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "waker-fn" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" - [[package]] name = "walkdir" version = "2.5.0" @@ -6408,12 +6208,6 @@ dependencies = [ "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" @@ -6444,7 +6238,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", "wasm-bindgen-shared", ] @@ -6478,7 +6272,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6512,7 +6306,7 @@ checksum = "4b8220be1fa9e4c889b30fd207d4906657e7e90b12e0e6b0c8b8d8709f5de021" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -6527,9 +6321,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.5" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" dependencies = [ "rustls-pki-types", ] @@ -6543,7 +6337,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.34", + "rustix", ] [[package]] @@ -6616,7 +6410,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -6627,7 +6421,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] @@ -6819,18 +6613,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -6865,10 +6650,10 @@ dependencies = [ "dirs", "faster-hex", "futures", - "getrandom 0.2.15", + "getrandom", "instant", "js-sys", - "rand 0.8.5", + "rand", "rlimit", "serde", "serde-wasm-bindgen", @@ -6996,7 +6781,7 @@ dependencies = [ "futures", "js-sys", "nw-sys", - "rand 0.8.5", + "rand", "serde", "serde-wasm-bindgen", "thiserror", @@ -7046,9 +6831,9 @@ dependencies = [ "downcast-rs", "futures", "futures-util", - "getrandom 0.2.15", + "getrandom", "manual_future", - "rand 0.8.5", + "rand", "serde", "serde_json", "thiserror", @@ -7254,9 +7039,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "539a77ee7c0de333dcc6da69b177380a0b81e0dacfa4f7344c465a36871ee601" +checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26" [[package]] name = "xmltree" @@ -7291,7 +7076,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.79", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b99276945..dd5eb3132 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -224,6 +224,7 @@ regex = "1.10.2" ripemd = { version = "0.1.3", default-features = false } rlimit = "0.10.1" rocksdb = "0.22.0" +rv = "0.16.4" secp256k1 = { version = "0.29.0", features = [ "global-context", "rand-std", @@ -243,8 +244,6 @@ sha3 = "0.10.8" slugify-rs = "0.0.3" smallvec = { version = "1.11.1", features = ["serde"] } sorted-insert = "0.2.3" -statest = "0.2.2" -statrs = "0.13.0" # TODO "0.16.0" subtle = { version = "2.5.0", default-features = false } sysinfo = "0.31.2" tempfile = "3.8.1" diff --git a/README.md b/README.md index 8749c49e9..d9066efa8 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,8 @@ To build WASM on MacOS you need to install `llvm` from homebrew (at the time of Kaspa CLI + Wallet -`kaspa-cli` crate provides cli-driven RPC interface to the node and a + +`kaspa-cli` crate provides a cli-driven RPC interface to the node and a terminal interface to the Rusty Kaspa Wallet runtime. These wallets are compatible with WASM SDK Wallet API and Kaspa NG projects. @@ -323,8 +324,6 @@ wRPC Simulation framework (Simpa) -Logging in `kaspad` and `simpa` can be [filtered](https://docs.rs/env_logger/0.10.0/env_logger/#filtering-results) by either: - The current codebase supports a full in-process network simulation, building an actual DAG over virtual time with virtual delay and benchmarking validation time (following the simulation generation). To see the available commands diff --git a/components/addressmanager/Cargo.toml b/components/addressmanager/Cargo.toml index e4398dc4e..ef735b19c 100644 --- a/components/addressmanager/Cargo.toml +++ b/components/addressmanager/Cargo.toml @@ -27,5 +27,4 @@ thiserror.workspace = true tokio.workspace = true [dev-dependencies] -statrs.workspace = true -statest.workspace = true +rv.workspace = true diff --git a/components/addressmanager/src/lib.rs b/components/addressmanager/src/lib.rs index 85f9acb3e..093323e15 100644 --- a/components/addressmanager/src/lib.rs +++ b/components/addressmanager/src/lib.rs @@ -520,8 +520,7 @@ mod address_store_with_cache { use kaspa_database::create_temp_db; use kaspa_database::prelude::ConnBuilder; use kaspa_utils::networking::IpAddress; - use statest::ks::KSTest; - use statrs::distribution::Uniform; + use rv::{dist::Uniform, misc::ks_test as one_way_ks_test, traits::Cdf}; use std::net::{IpAddr, Ipv6Addr}; #[test] @@ -591,10 +590,11 @@ mod address_store_with_cache { assert!(num_of_buckets >= 12); // Run multiple Kolmogorov–Smirnov tests to offset random noise of the random weighted iterator - let num_of_trials = 512; + let num_of_trials = 2048; // Number of trials to run the test, chosen to reduce random noise. let mut cul_p = 0.; // The target uniform distribution - let target_u_dist = Uniform::new(0.0, (num_of_buckets) as f64).unwrap(); + 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 let prioritized_address_distribution = am @@ -603,13 +603,12 @@ mod address_store_with_cache { .take(num_of_buckets) .map(|addr| addr.prefix_bucket().as_u64() as f64) .collect_vec(); - - let ks_test = KSTest::new(prioritized_address_distribution.as_slice()); - cul_p += ks_test.ks1(&target_u_dist).0; + cul_p += one_way_ks_test(prioritized_address_distribution.as_slice(), uniform_cdf).1; } // Normalize and adjust p to test for uniformity, over average of all trials. - let adjusted_p = (0.5 - cul_p / num_of_trials as f64).abs(); + // we do this to reduce the effect of random noise failing this test. + let adjusted_p = ((cul_p / num_of_trials as f64) - 0.5).abs(); // Define the significance threshold. let significance = 0.10; @@ -619,7 +618,7 @@ mod address_store_with_cache { adjusted_p, significance ); - assert!(adjusted_p <= significance) + assert!(adjusted_p <= significance); } } } From 1274e9c1de6778aa4038e354412410a26f7bc6a4 Mon Sep 17 00:00:00 2001 From: Maxim <59533214+biryukovmaxim@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:00:20 +0400 Subject: [PATCH 15/19] enhance tx inputs processing (#495) * sighash reused trait * benches are implemented * use cache per iteration per function * fix par versions * fix benches * use upgreadable read * use concurrent cache * use hashcache * dont apply cache * rollback rwlock and indexmap. * remove scc * apply par iter to `check_scripts` * refactor check_scripts fn, fix tests * fix clippy * add bench with custom threadpool * style: fmt * suppress warnings * Merge branch 'master' into bcm-parallel-processing * renames + map err * reuse code * bench: avoid exposing cache map + iter pools in powers of 2 * simplify check_sig_op_counts * use thread pool also if a single input 1. to avoid confusion 2. since tokio blocking threads are not meant to be used for processing anyway * remove todo * clear cache instead of recreate * use and_then (so map_err can be called in a single location) * extend check scripts tests for better coverage of the par_iter case --------- Co-authored-by: Michael Sutton --- Cargo.lock | 2 + consensus/Cargo.toml | 5 + consensus/benches/check_scripts.rs | 126 ++++++++++++ consensus/client/src/sign.rs | 6 +- consensus/client/src/signing.rs | 10 +- consensus/core/Cargo.toml | 1 + consensus/core/src/hashing/sighash.rs | 193 +++++++++++++----- consensus/core/src/sign.rs | 22 +- .../pipeline/virtual_processor/processor.rs | 5 +- .../transaction_validator_populated.rs | 140 +++++++++++-- consensus/wasm/src/utils.rs | 6 +- crypto/txscript/src/caches.rs | 4 + crypto/txscript/src/lib.rs | 59 +++--- crypto/txscript/src/opcodes/macros.rs | 22 +- crypto/txscript/src/opcodes/mod.rs | 70 ++++--- crypto/txscript/src/standard/multisig.rs | 10 +- .../src/mempool/check_transaction_standard.rs | 9 +- wallet/core/src/account/pskb.rs | 8 +- wallet/pskt/examples/multisig.rs | 8 +- wallet/pskt/src/pskt.rs | 7 +- 20 files changed, 538 insertions(+), 175 deletions(-) create mode 100644 consensus/benches/check_scripts.rs diff --git a/Cargo.lock b/Cargo.lock index 3449a43da..a0db54630 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2414,6 +2414,7 @@ dependencies = [ "futures-util", "indexmap 2.6.0", "itertools 0.13.0", + "kaspa-addresses", "kaspa-consensus-core", "kaspa-consensus-notify", "kaspa-consensusmanager", @@ -2475,6 +2476,7 @@ dependencies = [ name = "kaspa-consensus-core" version = "0.15.2" dependencies = [ + "arc-swap", "async-trait", "bincode", "borsh", diff --git a/consensus/Cargo.toml b/consensus/Cargo.toml index 3f4a1b456..b9a183ea8 100644 --- a/consensus/Cargo.toml +++ b/consensus/Cargo.toml @@ -54,11 +54,16 @@ serde_json.workspace = true flate2.workspace = true rand_distr.workspace = true kaspa-txscript-errors.workspace = true +kaspa-addresses.workspace = true [[bench]] name = "hash_benchmarks" harness = false +[[bench]] +name = "check_scripts" +harness = false + [features] html_reports = [] devnet-prealloc = ["kaspa-consensus-core/devnet-prealloc"] diff --git a/consensus/benches/check_scripts.rs b/consensus/benches/check_scripts.rs new file mode 100644 index 000000000..d65ac6362 --- /dev/null +++ b/consensus/benches/check_scripts.rs @@ -0,0 +1,126 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion, SamplingMode}; +use kaspa_addresses::{Address, Prefix, Version}; +use kaspa_consensus::processes::transaction_validator::transaction_validator_populated::{ + check_scripts_par_iter, check_scripts_par_iter_pool, check_scripts_sequential, +}; +use kaspa_consensus_core::hashing::sighash::{calc_schnorr_signature_hash, SigHashReusedValuesUnsync}; +use kaspa_consensus_core::hashing::sighash_type::SIG_HASH_ALL; +use kaspa_consensus_core::subnets::SubnetworkId; +use kaspa_consensus_core::tx::{MutableTransaction, Transaction, TransactionInput, TransactionOutpoint, UtxoEntry}; +use kaspa_txscript::caches::Cache; +use kaspa_txscript::pay_to_address_script; +use rand::{thread_rng, Rng}; +use secp256k1::Keypair; +use std::thread::available_parallelism; + +// You may need to add more detailed mocks depending on your actual code. +fn mock_tx(inputs_count: usize, non_uniq_signatures: usize) -> (Transaction, Vec) { + let reused_values = SigHashReusedValuesUnsync::new(); + let dummy_prev_out = TransactionOutpoint::new(kaspa_hashes::Hash::from_u64_word(1), 1); + let mut tx = Transaction::new( + 0, + vec![], + vec![], + 0, + SubnetworkId::from_bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 0, + vec![], + ); + let mut utxos = vec![]; + let mut kps = vec![]; + for _ in 0..inputs_count - non_uniq_signatures { + let kp = Keypair::new(secp256k1::SECP256K1, &mut thread_rng()); + tx.inputs.push(TransactionInput { previous_outpoint: dummy_prev_out, signature_script: vec![], sequence: 0, sig_op_count: 1 }); + let address = Address::new(Prefix::Mainnet, Version::PubKey, &kp.x_only_public_key().0.serialize()); + utxos.push(UtxoEntry { + amount: thread_rng().gen::() as u64, + script_public_key: pay_to_address_script(&address), + block_daa_score: 333, + is_coinbase: false, + }); + kps.push(kp); + } + for _ in 0..non_uniq_signatures { + let kp = kps.last().unwrap(); + tx.inputs.push(TransactionInput { previous_outpoint: dummy_prev_out, signature_script: vec![], sequence: 0, sig_op_count: 1 }); + let address = Address::new(Prefix::Mainnet, Version::PubKey, &kp.x_only_public_key().0.serialize()); + utxos.push(UtxoEntry { + amount: thread_rng().gen::() as u64, + script_public_key: pay_to_address_script(&address), + block_daa_score: 444, + is_coinbase: false, + }); + } + for (i, kp) in kps.iter().enumerate().take(inputs_count - non_uniq_signatures) { + let mut_tx = MutableTransaction::with_entries(&tx, utxos.clone()); + let sig_hash = calc_schnorr_signature_hash(&mut_tx.as_verifiable(), i, SIG_HASH_ALL, &reused_values); + let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); + let sig: [u8; 64] = *kp.sign_schnorr(msg).as_ref(); + // This represents OP_DATA_65 (since signature length is 64 bytes and SIGHASH_TYPE is one byte) + tx.inputs[i].signature_script = std::iter::once(65u8).chain(sig).chain([SIG_HASH_ALL.to_u8()]).collect(); + } + let length = tx.inputs.len(); + for i in (inputs_count - non_uniq_signatures)..length { + let kp = kps.last().unwrap(); + let mut_tx = MutableTransaction::with_entries(&tx, utxos.clone()); + let sig_hash = calc_schnorr_signature_hash(&mut_tx.as_verifiable(), i, SIG_HASH_ALL, &reused_values); + let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); + let sig: [u8; 64] = *kp.sign_schnorr(msg).as_ref(); + // This represents OP_DATA_65 (since signature length is 64 bytes and SIGHASH_TYPE is one byte) + tx.inputs[i].signature_script = std::iter::once(65u8).chain(sig).chain([SIG_HASH_ALL.to_u8()]).collect(); + } + (tx, utxos) +} + +fn benchmark_check_scripts(c: &mut Criterion) { + for inputs_count in [100, 50, 25, 10, 5, 2] { + for non_uniq_signatures in [0, inputs_count / 2] { + let (tx, utxos) = mock_tx(inputs_count, non_uniq_signatures); + let mut group = c.benchmark_group(format!("inputs: {inputs_count}, non uniq: {non_uniq_signatures}")); + group.sampling_mode(SamplingMode::Flat); + + group.bench_function("single_thread", |b| { + let tx = MutableTransaction::with_entries(&tx, utxos.clone()); + let cache = Cache::new(inputs_count as u64); + b.iter(|| { + cache.clear(); + check_scripts_sequential(black_box(&cache), black_box(&tx.as_verifiable())).unwrap(); + }) + }); + + group.bench_function("rayon par iter", |b| { + let tx = MutableTransaction::with_entries(tx.clone(), utxos.clone()); + let cache = Cache::new(inputs_count as u64); + b.iter(|| { + cache.clear(); + check_scripts_par_iter(black_box(&cache), black_box(&tx.as_verifiable())).unwrap(); + }) + }); + + // Iterate powers of two up to available parallelism + for i in (1..=(available_parallelism().unwrap().get() as f64).log2().ceil() as u32).map(|x| 2u32.pow(x) as usize) { + if inputs_count >= i { + group.bench_function(format!("rayon, custom thread pool, thread count {i}"), |b| { + let tx = MutableTransaction::with_entries(tx.clone(), utxos.clone()); + // Create a custom thread pool with the specified number of threads + let pool = rayon::ThreadPoolBuilder::new().num_threads(i).build().unwrap(); + let cache = Cache::new(inputs_count as u64); + b.iter(|| { + cache.clear(); + check_scripts_par_iter_pool(black_box(&cache), black_box(&tx.as_verifiable()), black_box(&pool)).unwrap(); + }) + }); + } + } + } + } +} + +criterion_group! { + name = benches; + // This can be any expression that returns a `Criterion` object. + config = Criterion::default().with_output_color(true).measurement_time(std::time::Duration::new(20, 0)); + targets = benchmark_check_scripts +} + +criterion_main!(benches); diff --git a/consensus/client/src/sign.rs b/consensus/client/src/sign.rs index 4044dc570..18ff3c849 100644 --- a/consensus/client/src/sign.rs +++ b/consensus/client/src/sign.rs @@ -7,7 +7,7 @@ use core::iter::once; use itertools::Itertools; use kaspa_consensus_core::{ hashing::{ - sighash::{calc_schnorr_signature_hash, SigHashReusedValues}, + sighash::{calc_schnorr_signature_hash, SigHashReusedValuesUnsync}, sighash_type::SIG_HASH_ALL, }, tx::PopulatedTransaction, @@ -44,7 +44,7 @@ pub fn sign_with_multiple_v3<'a>(tx: &'a Transaction, privkeys: &[[u8; 32]]) -> map.insert(script_pub_key_script, schnorr_key); } - let mut reused_values = SigHashReusedValues::new(); + let reused_values = SigHashReusedValuesUnsync::new(); let mut additional_signatures_required = false; { let input_len = tx.inner().inputs.len(); @@ -59,7 +59,7 @@ pub fn sign_with_multiple_v3<'a>(tx: &'a Transaction, privkeys: &[[u8; 32]]) -> }; let script = script_pub_key.script(); if let Some(schnorr_key) = map.get(script) { - let sig_hash = calc_schnorr_signature_hash(&populated_transaction, i, SIG_HASH_ALL, &mut reused_values); + let sig_hash = calc_schnorr_signature_hash(&populated_transaction, i, SIG_HASH_ALL, &reused_values); let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); let sig: [u8; 64] = *schnorr_key.sign_schnorr(msg).as_ref(); // This represents OP_DATA_65 (since signature length is 64 bytes and SIGHASH_TYPE is one byte) diff --git a/consensus/client/src/signing.rs b/consensus/client/src/signing.rs index ef993d011..f7fe8cee6 100644 --- a/consensus/client/src/signing.rs +++ b/consensus/client/src/signing.rs @@ -75,7 +75,7 @@ impl SigHashCache { } } - pub fn sig_op_counts_hash(&mut self, tx: &Transaction, hash_type: SigHashType, reused_values: &mut SigHashReusedValues) -> Hash { + pub fn sig_op_counts_hash(&mut self, tx: &Transaction, hash_type: SigHashType, reused_values: &SigHashReusedValues) -> Hash { if hash_type.is_sighash_anyone_can_pay() { return ZERO_HASH; } @@ -185,16 +185,16 @@ pub fn calc_schnorr_signature_hash( let mut hasher = TransactionSigningHash::new(); hasher .write_u16(tx.version) - .update(previous_outputs_hash(&tx, hash_type, &mut reused_values)) - .update(sequences_hash(&tx, hash_type, &mut reused_values)) - .update(sig_op_counts_hash(&tx, hash_type, &mut reused_values)); + .update(previous_outputs_hash(&tx, hash_type, &reused_values)) + .update(sequences_hash(&tx, hash_type, &reused_values)) + .update(sig_op_counts_hash(&tx, hash_type, &reused_values)); hash_outpoint(&mut hasher, input.previous_outpoint); hash_script_public_key(&mut hasher, &utxo.script_public_key); hasher .write_u64(utxo.amount) .write_u64(input.sequence) .write_u8(input.sig_op_count) - .update(outputs_hash(&tx, hash_type, &mut reused_values, input_index)) + .update(outputs_hash(&tx, hash_type, &reused_values, input_index)) .write_u64(tx.lock_time) .update(&tx.subnetwork_id) .write_u64(tx.gas) diff --git a/consensus/core/Cargo.toml b/consensus/core/Cargo.toml index 44dbedd38..228b4ac11 100644 --- a/consensus/core/Cargo.toml +++ b/consensus/core/Cargo.toml @@ -15,6 +15,7 @@ wasm32-sdk = [] default = [] [dependencies] +arc-swap.workspace = true async-trait.workspace = true borsh.workspace = true cfg-if.workspace = true diff --git a/consensus/core/src/hashing/sighash.rs b/consensus/core/src/hashing/sighash.rs index c1b6133e8..e6c7ad4dd 100644 --- a/consensus/core/src/hashing/sighash.rs +++ b/consensus/core/src/hashing/sighash.rs @@ -1,4 +1,7 @@ +use arc_swap::ArcSwapOption; use kaspa_hashes::{Hash, Hasher, HasherBase, TransactionSigningHash, TransactionSigningHashECDSA, ZERO_HASH}; +use std::cell::Cell; +use std::sync::Arc; use crate::{ subnets::SUBNETWORK_ID_NATIVE, @@ -11,72 +14,172 @@ use super::{sighash_type::SigHashType, HasherExtensions}; /// the same for all transaction inputs. /// Reuse of such values prevents the quadratic hashing problem. #[derive(Default)] -pub struct SigHashReusedValues { - previous_outputs_hash: Option, - sequences_hash: Option, - sig_op_counts_hash: Option, - outputs_hash: Option, +pub struct SigHashReusedValuesUnsync { + previous_outputs_hash: Cell>, + sequences_hash: Cell>, + sig_op_counts_hash: Cell>, + outputs_hash: Cell>, } -impl SigHashReusedValues { +impl SigHashReusedValuesUnsync { pub fn new() -> Self { - Self { previous_outputs_hash: None, sequences_hash: None, sig_op_counts_hash: None, outputs_hash: None } + Self::default() } } -pub fn previous_outputs_hash(tx: &Transaction, hash_type: SigHashType, reused_values: &mut SigHashReusedValues) -> Hash { +#[derive(Default)] +pub struct SigHashReusedValuesSync { + previous_outputs_hash: ArcSwapOption, + sequences_hash: ArcSwapOption, + sig_op_counts_hash: ArcSwapOption, + outputs_hash: ArcSwapOption, +} + +impl SigHashReusedValuesSync { + pub fn new() -> Self { + Self::default() + } +} + +pub trait SigHashReusedValues { + fn previous_outputs_hash(&self, set: impl Fn() -> Hash) -> Hash; + fn sequences_hash(&self, set: impl Fn() -> Hash) -> Hash; + fn sig_op_counts_hash(&self, set: impl Fn() -> Hash) -> Hash; + fn outputs_hash(&self, set: impl Fn() -> Hash) -> Hash; +} + +impl SigHashReusedValues for Arc { + fn previous_outputs_hash(&self, set: impl Fn() -> Hash) -> Hash { + self.as_ref().previous_outputs_hash(set) + } + + fn sequences_hash(&self, set: impl Fn() -> Hash) -> Hash { + self.as_ref().sequences_hash(set) + } + + fn sig_op_counts_hash(&self, set: impl Fn() -> Hash) -> Hash { + self.as_ref().sig_op_counts_hash(set) + } + + fn outputs_hash(&self, set: impl Fn() -> Hash) -> Hash { + self.as_ref().outputs_hash(set) + } +} + +impl SigHashReusedValues for SigHashReusedValuesUnsync { + fn previous_outputs_hash(&self, set: impl Fn() -> Hash) -> Hash { + self.previous_outputs_hash.get().unwrap_or_else(|| { + let hash = set(); + self.previous_outputs_hash.set(Some(hash)); + hash + }) + } + + fn sequences_hash(&self, set: impl Fn() -> Hash) -> Hash { + self.sequences_hash.get().unwrap_or_else(|| { + let hash = set(); + self.sequences_hash.set(Some(hash)); + hash + }) + } + + fn sig_op_counts_hash(&self, set: impl Fn() -> Hash) -> Hash { + self.sig_op_counts_hash.get().unwrap_or_else(|| { + let hash = set(); + self.sig_op_counts_hash.set(Some(hash)); + hash + }) + } + + fn outputs_hash(&self, set: impl Fn() -> Hash) -> Hash { + self.outputs_hash.get().unwrap_or_else(|| { + let hash = set(); + self.outputs_hash.set(Some(hash)); + hash + }) + } +} + +impl SigHashReusedValues for SigHashReusedValuesSync { + fn previous_outputs_hash(&self, set: impl Fn() -> Hash) -> Hash { + if let Some(value) = self.previous_outputs_hash.load().as_ref() { + return **value; + } + let hash = set(); + self.previous_outputs_hash.rcu(|_| Arc::new(hash)); + hash + } + + fn sequences_hash(&self, set: impl Fn() -> Hash) -> Hash { + if let Some(value) = self.sequences_hash.load().as_ref() { + return **value; + } + let hash = set(); + self.sequences_hash.rcu(|_| Arc::new(hash)); + hash + } + + fn sig_op_counts_hash(&self, set: impl Fn() -> Hash) -> Hash { + if let Some(value) = self.sig_op_counts_hash.load().as_ref() { + return **value; + } + let hash = set(); + self.sig_op_counts_hash.rcu(|_| Arc::new(hash)); + hash + } + + fn outputs_hash(&self, set: impl Fn() -> Hash) -> Hash { + if let Some(value) = self.outputs_hash.load().as_ref() { + return **value; + } + let hash = set(); + self.outputs_hash.rcu(|_| Arc::new(hash)); + hash + } +} + +pub fn previous_outputs_hash(tx: &Transaction, hash_type: SigHashType, reused_values: &impl SigHashReusedValues) -> Hash { if hash_type.is_sighash_anyone_can_pay() { return ZERO_HASH; } - - if let Some(previous_outputs_hash) = reused_values.previous_outputs_hash { - previous_outputs_hash - } else { + let hash = || { let mut hasher = TransactionSigningHash::new(); for input in tx.inputs.iter() { hasher.update(input.previous_outpoint.transaction_id.as_bytes()); hasher.write_u32(input.previous_outpoint.index); } - let previous_outputs_hash = hasher.finalize(); - reused_values.previous_outputs_hash = Some(previous_outputs_hash); - previous_outputs_hash - } + hasher.finalize() + }; + reused_values.previous_outputs_hash(hash) } -pub fn sequences_hash(tx: &Transaction, hash_type: SigHashType, reused_values: &mut SigHashReusedValues) -> Hash { +pub fn sequences_hash(tx: &Transaction, hash_type: SigHashType, reused_values: &impl SigHashReusedValues) -> Hash { if hash_type.is_sighash_single() || hash_type.is_sighash_anyone_can_pay() || hash_type.is_sighash_none() { return ZERO_HASH; } - - if let Some(sequences_hash) = reused_values.sequences_hash { - sequences_hash - } else { + let hash = || { let mut hasher = TransactionSigningHash::new(); for input in tx.inputs.iter() { hasher.write_u64(input.sequence); } - let sequence_hash = hasher.finalize(); - reused_values.sequences_hash = Some(sequence_hash); - sequence_hash - } + hasher.finalize() + }; + reused_values.sequences_hash(hash) } -pub fn sig_op_counts_hash(tx: &Transaction, hash_type: SigHashType, reused_values: &mut SigHashReusedValues) -> Hash { +pub fn sig_op_counts_hash(tx: &Transaction, hash_type: SigHashType, reused_values: &impl SigHashReusedValues) -> Hash { if hash_type.is_sighash_anyone_can_pay() { return ZERO_HASH; } - if let Some(sig_op_counts_hash) = reused_values.sig_op_counts_hash { - sig_op_counts_hash - } else { + let hash = || { let mut hasher = TransactionSigningHash::new(); for input in tx.inputs.iter() { hasher.write_u8(input.sig_op_count); } - let sig_op_counts_hash = hasher.finalize(); - reused_values.sig_op_counts_hash = Some(sig_op_counts_hash); - sig_op_counts_hash - } + hasher.finalize() + }; + reused_values.sig_op_counts_hash(hash) } pub fn payload_hash(tx: &Transaction) -> Hash { @@ -92,7 +195,7 @@ pub fn payload_hash(tx: &Transaction) -> Hash { hasher.finalize() } -pub fn outputs_hash(tx: &Transaction, hash_type: SigHashType, reused_values: &mut SigHashReusedValues, input_index: usize) -> Hash { +pub fn outputs_hash(tx: &Transaction, hash_type: SigHashType, reused_values: &impl SigHashReusedValues, input_index: usize) -> Hash { if hash_type.is_sighash_none() { return ZERO_HASH; } @@ -107,19 +210,15 @@ pub fn outputs_hash(tx: &Transaction, hash_type: SigHashType, reused_values: &mu hash_output(&mut hasher, &tx.outputs[input_index]); return hasher.finalize(); } - - // Otherwise, return hash of all outputs. Re-use hash if available. - if let Some(outputs_hash) = reused_values.outputs_hash { - outputs_hash - } else { + let hash = || { let mut hasher = TransactionSigningHash::new(); for output in tx.outputs.iter() { hash_output(&mut hasher, output); } - let outputs_hash = hasher.finalize(); - reused_values.outputs_hash = Some(outputs_hash); - outputs_hash - } + hasher.finalize() + }; + // Otherwise, return hash of all outputs. Re-use hash if available. + reused_values.outputs_hash(hash) } pub fn hash_outpoint(hasher: &mut impl Hasher, outpoint: TransactionOutpoint) { @@ -141,7 +240,7 @@ pub fn calc_schnorr_signature_hash( verifiable_tx: &impl VerifiableTransaction, input_index: usize, hash_type: SigHashType, - reused_values: &mut SigHashReusedValues, + reused_values: &impl SigHashReusedValues, ) -> Hash { let input = verifiable_tx.populated_input(input_index); let tx = verifiable_tx.tx(); @@ -170,7 +269,7 @@ pub fn calc_ecdsa_signature_hash( tx: &impl VerifiableTransaction, input_index: usize, hash_type: SigHashType, - reused_values: &mut SigHashReusedValues, + reused_values: &impl SigHashReusedValues, ) -> Hash { let hash = calc_schnorr_signature_hash(tx, input_index, hash_type, reused_values); let mut hasher = TransactionSigningHashECDSA::new(); @@ -573,9 +672,9 @@ mod tests { } } let populated_tx = PopulatedTransaction::new(&tx, entries); - let mut reused_values = SigHashReusedValues::new(); + let reused_values = SigHashReusedValuesUnsync::new(); assert_eq!( - calc_schnorr_signature_hash(&populated_tx, test.input_index, test.hash_type, &mut reused_values).to_string(), + calc_schnorr_signature_hash(&populated_tx, test.input_index, test.hash_type, &reused_values).to_string(), test.expected_hash, "test {} failed", test.name diff --git a/consensus/core/src/sign.rs b/consensus/core/src/sign.rs index a40b949e3..1a87d03f1 100644 --- a/consensus/core/src/sign.rs +++ b/consensus/core/src/sign.rs @@ -1,6 +1,6 @@ use crate::{ hashing::{ - sighash::{calc_schnorr_signature_hash, SigHashReusedValues}, + sighash::{calc_schnorr_signature_hash, SigHashReusedValuesUnsync}, sighash_type::{SigHashType, SIG_HASH_ALL}, }, tx::{SignableTransaction, VerifiableTransaction}, @@ -84,9 +84,9 @@ pub fn sign(mut signable_tx: SignableTransaction, schnorr_key: secp256k1::Keypai signable_tx.tx.inputs[i].sig_op_count = 1; } - let mut reused_values = SigHashReusedValues::new(); + let reused_values = SigHashReusedValuesUnsync::new(); for i in 0..signable_tx.tx.inputs.len() { - let sig_hash = calc_schnorr_signature_hash(&signable_tx.as_verifiable(), i, SIG_HASH_ALL, &mut reused_values); + let sig_hash = calc_schnorr_signature_hash(&signable_tx.as_verifiable(), i, SIG_HASH_ALL, &reused_values); let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); let sig: [u8; 64] = *schnorr_key.sign_schnorr(msg).as_ref(); // This represents OP_DATA_65 (since signature length is 64 bytes and SIGHASH_TYPE is one byte) @@ -106,11 +106,11 @@ pub fn sign_with_multiple(mut mutable_tx: SignableTransaction, privkeys: Vec<[u8 mutable_tx.tx.inputs[i].sig_op_count = 1; } - let mut reused_values = SigHashReusedValues::new(); + let reused_values = SigHashReusedValuesUnsync::new(); for i in 0..mutable_tx.tx.inputs.len() { let script = mutable_tx.entries[i].as_ref().unwrap().script_public_key.script(); if let Some(schnorr_key) = map.get(script) { - let sig_hash = calc_schnorr_signature_hash(&mutable_tx.as_verifiable(), i, SIG_HASH_ALL, &mut reused_values); + let sig_hash = calc_schnorr_signature_hash(&mutable_tx.as_verifiable(), i, SIG_HASH_ALL, &reused_values); let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); let sig: [u8; 64] = *schnorr_key.sign_schnorr(msg).as_ref(); // This represents OP_DATA_65 (since signature length is 64 bytes and SIGHASH_TYPE is one byte) @@ -132,12 +132,12 @@ pub fn sign_with_multiple_v2(mut mutable_tx: SignableTransaction, privkeys: &[[u map.insert(script_pub_key_script, schnorr_key); } - let mut reused_values = SigHashReusedValues::new(); + let reused_values = SigHashReusedValuesUnsync::new(); let mut additional_signatures_required = false; for i in 0..mutable_tx.tx.inputs.len() { let script = mutable_tx.entries[i].as_ref().unwrap().script_public_key.script(); if let Some(schnorr_key) = map.get(script) { - let sig_hash = calc_schnorr_signature_hash(&mutable_tx.as_verifiable(), i, SIG_HASH_ALL, &mut reused_values); + let sig_hash = calc_schnorr_signature_hash(&mutable_tx.as_verifiable(), i, SIG_HASH_ALL, &reused_values); let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); let sig: [u8; 64] = *schnorr_key.sign_schnorr(msg).as_ref(); // This represents OP_DATA_65 (since signature length is 64 bytes and SIGHASH_TYPE is one byte) @@ -155,9 +155,9 @@ pub fn sign_with_multiple_v2(mut mutable_tx: SignableTransaction, privkeys: &[[u /// Sign a transaction input with a sighash_type using schnorr pub fn sign_input(tx: &impl VerifiableTransaction, input_index: usize, private_key: &[u8; 32], hash_type: SigHashType) -> Vec { - let mut reused_values = SigHashReusedValues::new(); + let reused_values = SigHashReusedValuesUnsync::new(); - let hash = calc_schnorr_signature_hash(tx, input_index, hash_type, &mut reused_values); + let hash = calc_schnorr_signature_hash(tx, input_index, hash_type, &reused_values); let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice()).unwrap(); let schnorr_key = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, private_key).unwrap(); let sig: [u8; 64] = *schnorr_key.sign_schnorr(msg).as_ref(); @@ -167,7 +167,7 @@ pub fn sign_input(tx: &impl VerifiableTransaction, input_index: usize, private_k } pub fn verify(tx: &impl VerifiableTransaction) -> Result<(), Error> { - let mut reused_values = SigHashReusedValues::new(); + let reused_values = SigHashReusedValuesUnsync::new(); for (i, (input, entry)) in tx.populated_inputs().enumerate() { if input.signature_script.is_empty() { return Err(Error::Message(format!("Signature is empty for input: {i}"))); @@ -175,7 +175,7 @@ pub fn verify(tx: &impl VerifiableTransaction) -> Result<(), Error> { let pk = &entry.script_public_key.script()[1..33]; let pk = secp256k1::XOnlyPublicKey::from_slice(pk)?; let sig = secp256k1::schnorr::Signature::from_slice(&input.signature_script[1..65])?; - let sig_hash = calc_schnorr_signature_hash(tx, i, SIG_HASH_ALL, &mut reused_values); + let sig_hash = calc_schnorr_signature_hash(tx, i, SIG_HASH_ALL, &reused_values); let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice())?; sig.verify(&msg, &pk)?; } diff --git a/consensus/src/pipeline/virtual_processor/processor.rs b/consensus/src/pipeline/virtual_processor/processor.rs index 88fee97bf..9af6879c7 100644 --- a/consensus/src/pipeline/virtual_processor/processor.rs +++ b/consensus/src/pipeline/virtual_processor/processor.rs @@ -778,7 +778,10 @@ impl VirtualStateProcessor { let virtual_utxo_view = &virtual_read.utxo_set; let virtual_daa_score = virtual_state.daa_score; let virtual_past_median_time = virtual_state.past_median_time; - self.validate_mempool_transaction_impl(mutable_tx, virtual_utxo_view, virtual_daa_score, virtual_past_median_time, args) + // Run within the thread pool since par_iter might be internally applied to inputs + self.thread_pool.install(|| { + self.validate_mempool_transaction_impl(mutable_tx, virtual_utxo_view, virtual_daa_score, virtual_past_median_time, args) + }) } pub fn validate_mempool_transactions_in_parallel( diff --git a/consensus/src/processes/transaction_validator/transaction_validator_populated.rs b/consensus/src/processes/transaction_validator/transaction_validator_populated.rs index 4a8733d2b..dbf1aa37e 100644 --- a/consensus/src/processes/transaction_validator/transaction_validator_populated.rs +++ b/consensus/src/processes/transaction_validator/transaction_validator_populated.rs @@ -1,18 +1,24 @@ use crate::constants::{MAX_SOMPI, SEQUENCE_LOCK_TIME_DISABLED, SEQUENCE_LOCK_TIME_MASK}; use kaspa_consensus_core::{ - hashing::sighash::SigHashReusedValues, + hashing::sighash::{SigHashReusedValuesSync, SigHashReusedValuesUnsync}, mass::Kip9Version, tx::{TransactionInput, VerifiableTransaction}, }; use kaspa_core::warn; -use kaspa_txscript::{get_sig_op_count, TxScriptEngine}; +use kaspa_txscript::{caches::Cache, get_sig_op_count, SigCacheKey, TxScriptEngine}; use kaspa_txscript_errors::TxScriptError; +use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use rayon::ThreadPool; +use std::marker::Sync; use super::{ errors::{TxResult, TxRuleError}, TransactionValidator, }; +/// The threshold above which we apply parallelism to input script processing +const CHECK_SCRIPTS_PARALLELISM_THRESHOLD: usize = 1; + #[derive(Clone, Copy, PartialEq, Eq)] pub enum TxValidationFlags { /// Perform full validation including script verification @@ -29,7 +35,7 @@ pub enum TxValidationFlags { impl TransactionValidator { pub fn validate_populated_transaction_and_get_fee( &self, - tx: &impl VerifiableTransaction, + tx: &(impl VerifiableTransaction + Sync), pov_daa_score: u64, flags: TxValidationFlags, mass_and_feerate_threshold: Option<(u64, f64)>, @@ -48,8 +54,8 @@ impl TransactionValidator { } Self::check_sequence_lock(tx, pov_daa_score)?; - // The following call is not a consensus check (it could not be one in the first place since it uses floating number) - // but rather a mempool Replace by Fee validation rule. It was placed here purposely for avoiding unneeded script checks. + // The following call is not a consensus check (it could not be one in the first place since it uses a floating number) + // but rather a mempool Replace by Fee validation rule. It is placed here purposely for avoiding unneeded script checks. Self::check_feerate_threshold(fee, mass_and_feerate_threshold)?; match flags { @@ -158,7 +164,7 @@ impl TransactionValidator { fn check_sig_op_counts(tx: &T) -> TxResult<()> { for (i, (input, entry)) in tx.populated_inputs().enumerate() { - let calculated = get_sig_op_count::(&input.signature_script, &entry.script_public_key); + let calculated = get_sig_op_count::(&input.signature_script, &entry.script_public_key); if calculated != input.sig_op_count as u64 { return Err(TxRuleError::WrongSigOpCount(i, input.sig_op_count as u64, calculated)); } @@ -166,16 +172,45 @@ impl TransactionValidator { Ok(()) } - pub fn check_scripts(&self, tx: &impl VerifiableTransaction) -> TxResult<()> { - let mut reused_values = SigHashReusedValues::new(); - for (i, (input, entry)) in tx.populated_inputs().enumerate() { - let mut engine = TxScriptEngine::from_transaction_input(tx, input, i, entry, &mut reused_values, &self.sig_cache) - .map_err(|err| map_script_err(err, input))?; - engine.execute().map_err(|err| map_script_err(err, input))?; - } + pub fn check_scripts(&self, tx: &(impl VerifiableTransaction + Sync)) -> TxResult<()> { + check_scripts(&self.sig_cache, tx) + } +} - Ok(()) +pub fn check_scripts(sig_cache: &Cache, tx: &(impl VerifiableTransaction + Sync)) -> TxResult<()> { + if tx.inputs().len() > CHECK_SCRIPTS_PARALLELISM_THRESHOLD { + check_scripts_par_iter(sig_cache, tx) + } else { + check_scripts_sequential(sig_cache, tx) + } +} + +pub fn check_scripts_sequential(sig_cache: &Cache, tx: &impl VerifiableTransaction) -> TxResult<()> { + let reused_values = SigHashReusedValuesUnsync::new(); + for (i, (input, entry)) in tx.populated_inputs().enumerate() { + TxScriptEngine::from_transaction_input(tx, input, i, entry, &reused_values, sig_cache) + .and_then(|mut e| e.execute()) + .map_err(|err| map_script_err(err, input))?; } + Ok(()) +} + +pub fn check_scripts_par_iter(sig_cache: &Cache, tx: &(impl VerifiableTransaction + Sync)) -> TxResult<()> { + let reused_values = SigHashReusedValuesSync::new(); + (0..tx.inputs().len()).into_par_iter().try_for_each(|idx| { + let (input, utxo) = tx.populated_input(idx); + TxScriptEngine::from_transaction_input(tx, input, idx, utxo, &reused_values, sig_cache) + .and_then(|mut e| e.execute()) + .map_err(|err| map_script_err(err, input)) + }) +} + +pub fn check_scripts_par_iter_pool( + sig_cache: &Cache, + tx: &(impl VerifiableTransaction + Sync), + pool: &ThreadPool, +) -> TxResult<()> { + pool.install(|| check_scripts_par_iter(sig_cache, tx)) } fn map_script_err(script_err: TxScriptError, input: &TransactionInput) -> TxRuleError { @@ -189,6 +224,7 @@ fn map_script_err(script_err: TxScriptError, input: &TransactionInput) -> TxRule #[cfg(test)] mod tests { use super::super::errors::TxRuleError; + use super::CHECK_SCRIPTS_PARALLELISM_THRESHOLD; use core::str::FromStr; use itertools::Itertools; use kaspa_consensus_core::sign::sign; @@ -202,6 +238,15 @@ mod tests { use crate::{params::MAINNET_PARAMS, processes::transaction_validator::TransactionValidator}; + /// Helper function to duplicate the last input + fn duplicate_input(tx: &Transaction, entries: &[UtxoEntry]) -> (Transaction, Vec) { + let mut tx2 = tx.clone(); + let mut entries2 = entries.to_owned(); + tx2.inputs.push(tx2.inputs.last().unwrap().clone()); + entries2.push(entries2.last().unwrap().clone()); + (tx2, entries2) + } + #[test] fn check_signature_test() { let mut params = MAINNET_PARAMS.clone(); @@ -261,6 +306,14 @@ mod tests { ); tv.check_scripts(&populated_tx).expect("Signature check failed"); + + // Test a tx with 2 inputs to cover parallelism split points in inner script checking code + let (tx2, entries2) = duplicate_input(&tx, &populated_tx.entries); + // Duplicated sigs should fail due to wrong sighash + assert_eq!( + tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2)), + Err(TxRuleError::SignatureInvalid(TxScriptError::EvalFalse)) + ); } #[test] @@ -322,7 +375,18 @@ mod tests { }], ); - assert!(tv.check_scripts(&populated_tx).is_err(), "Failing Signature Test Failed"); + assert!(tv.check_scripts(&populated_tx).is_err(), "Expecting signature check to fail"); + + // Test a tx with 2 inputs to cover parallelism split points in inner script checking code + let (tx2, entries2) = duplicate_input(&tx, &populated_tx.entries); + tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2)).expect_err("Expecting signature check to fail"); + + // Verify we are correctly testing the parallelism case (applied here as sanity for all tests) + assert!( + tx2.inputs.len() > CHECK_SCRIPTS_PARALLELISM_THRESHOLD, + "The script tests must cover the case of a tx with inputs.len() > {}", + CHECK_SCRIPTS_PARALLELISM_THRESHOLD + ); } #[test] @@ -385,6 +449,14 @@ mod tests { }], ); tv.check_scripts(&populated_tx).expect("Signature check failed"); + + // Test a tx with 2 inputs to cover parallelism split points in inner script checking code + let (tx2, entries2) = duplicate_input(&tx, &populated_tx.entries); + // Duplicated sigs should fail due to wrong sighash + assert_eq!( + tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2)), + Err(TxRuleError::SignatureInvalid(TxScriptError::NullFail)) + ); } #[test] @@ -447,7 +519,14 @@ mod tests { }], ); - assert!(tv.check_scripts(&populated_tx) == Err(TxRuleError::SignatureInvalid(TxScriptError::NullFail))); + assert_eq!(tv.check_scripts(&populated_tx), Err(TxRuleError::SignatureInvalid(TxScriptError::NullFail))); + + // Test a tx with 2 inputs to cover parallelism split points in inner script checking code + let (tx2, entries2) = duplicate_input(&tx, &populated_tx.entries); + assert_eq!( + tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2)), + Err(TxRuleError::SignatureInvalid(TxScriptError::NullFail)) + ); } #[test] @@ -510,7 +589,14 @@ mod tests { }], ); - assert!(tv.check_scripts(&populated_tx) == Err(TxRuleError::SignatureInvalid(TxScriptError::NullFail))); + assert_eq!(tv.check_scripts(&populated_tx), Err(TxRuleError::SignatureInvalid(TxScriptError::NullFail))); + + // Test a tx with 2 inputs to cover parallelism split points in inner script checking code + let (tx2, entries2) = duplicate_input(&tx, &populated_tx.entries); + assert_eq!( + tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2)), + Err(TxRuleError::SignatureInvalid(TxScriptError::NullFail)) + ); } #[test] @@ -573,8 +659,14 @@ mod tests { }], ); - let result = tv.check_scripts(&populated_tx); - assert!(result == Err(TxRuleError::SignatureInvalid(TxScriptError::EvalFalse))); + assert_eq!(tv.check_scripts(&populated_tx), Err(TxRuleError::SignatureInvalid(TxScriptError::EvalFalse))); + + // Test a tx with 2 inputs to cover parallelism split points in inner script checking code + let (tx2, entries2) = duplicate_input(&tx, &populated_tx.entries); + assert_eq!( + tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2)), + Err(TxRuleError::SignatureInvalid(TxScriptError::EvalFalse)) + ); } #[test] @@ -628,8 +720,14 @@ mod tests { }], ); - let result = tv.check_scripts(&populated_tx); - assert!(result == Err(TxRuleError::SignatureInvalid(TxScriptError::SignatureScriptNotPushOnly))); + assert_eq!(tv.check_scripts(&populated_tx), Err(TxRuleError::SignatureInvalid(TxScriptError::SignatureScriptNotPushOnly))); + + // Test a tx with 2 inputs to cover parallelism split points in inner script checking code + let (tx2, entries2) = duplicate_input(&tx, &populated_tx.entries); + assert_eq!( + tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2)), + Err(TxRuleError::SignatureInvalid(TxScriptError::SignatureScriptNotPushOnly)) + ); } #[test] diff --git a/consensus/wasm/src/utils.rs b/consensus/wasm/src/utils.rs index 0139b573f..b70664e1e 100644 --- a/consensus/wasm/src/utils.rs +++ b/consensus/wasm/src/utils.rs @@ -1,5 +1,5 @@ use crate::result::Result; -use kaspa_consensus_core::hashing::sighash::{calc_schnorr_signature_hash, SigHashReusedValues}; +use kaspa_consensus_core::hashing::sighash::{calc_schnorr_signature_hash, SigHashReusedValuesUnsync}; use kaspa_consensus_core::hashing::sighash_type::SIG_HASH_ALL; use kaspa_consensus_core::tx; @@ -9,9 +9,9 @@ pub fn script_hashes(mut mutable_tx: tx::SignableTransaction) -> Result Option { self.map.read().get(key).cloned().inspect(|_data| { self.counters.get_counts.fetch_add(1, Ordering::Relaxed); diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index b145fb90e..f17796272 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -45,6 +45,8 @@ pub const MAX_PUB_KEYS_PER_MUTLTISIG: i32 = 20; // Note that this includes OP_RESERVED which counts as a push operation. pub const NO_COST_OPCODE: u8 = 0x60; +type DynOpcodeImplementation = Box>; + #[derive(Clone, Hash, PartialEq, Eq)] enum Signature { Secp256k1(secp256k1::schnorr::Signature), @@ -70,15 +72,14 @@ enum ScriptSource<'a, T: VerifiableTransaction> { StandAloneScripts(Vec<&'a [u8]>), } -pub struct TxScriptEngine<'a, T: VerifiableTransaction> { +pub struct TxScriptEngine<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> { dstack: Stack, astack: Stack, script_source: ScriptSource<'a, T>, // Outer caches for quicker calculation - // TODO:: make it compatible with threading - reused_values: &'a mut SigHashReusedValues, + reused_values: &'a Reused, sig_cache: &'a Cache, cond_stack: Vec, // Following if stacks, and whether it is running @@ -86,30 +87,35 @@ pub struct TxScriptEngine<'a, T: VerifiableTransaction> { num_ops: i32, } -fn parse_script( +fn parse_script( script: &[u8], -) -> impl Iterator>, TxScriptError>> + '_ { +) -> impl Iterator, TxScriptError>> + '_ { script.iter().batching(|it| deserialize_next_opcode(it)) } -pub fn get_sig_op_count(signature_script: &[u8], prev_script_public_key: &ScriptPublicKey) -> u64 { +pub fn get_sig_op_count( + signature_script: &[u8], + prev_script_public_key: &ScriptPublicKey, +) -> u64 { let is_p2sh = ScriptClass::is_pay_to_script_hash(prev_script_public_key.script()); - let script_pub_key_ops = parse_script::(prev_script_public_key.script()).collect_vec(); + let script_pub_key_ops = parse_script::(prev_script_public_key.script()).collect_vec(); if !is_p2sh { return get_sig_op_count_by_opcodes(&script_pub_key_ops); } - let signature_script_ops = parse_script::(signature_script).collect_vec(); + let signature_script_ops = parse_script::(signature_script).collect_vec(); if signature_script_ops.is_empty() || signature_script_ops.iter().any(|op| op.is_err() || !op.as_ref().unwrap().is_push_opcode()) { return 0; } let p2sh_script = signature_script_ops.last().expect("checked if empty above").as_ref().expect("checked if err above").get_data(); - let p2sh_ops = parse_script::(p2sh_script).collect_vec(); + let p2sh_ops = parse_script::(p2sh_script).collect_vec(); get_sig_op_count_by_opcodes(&p2sh_ops) } -fn get_sig_op_count_by_opcodes(opcodes: &[Result>, TxScriptError>]) -> u64 { +fn get_sig_op_count_by_opcodes( + opcodes: &[Result, TxScriptError>], +) -> u64 { // TODO: Check for overflows let mut num_sigs: u64 = 0; for (i, op) in opcodes.iter().enumerate() { @@ -142,12 +148,12 @@ fn get_sig_op_count_by_opcodes(opcodes: &[Result(script: &[u8]) -> bool { - parse_script::(script).enumerate().any(|(index, op)| op.is_err() || (index == 0 && op.unwrap().value() == OpReturn)) +pub fn is_unspendable(script: &[u8]) -> bool { + parse_script::(script).enumerate().any(|(index, op)| op.is_err() || (index == 0 && op.unwrap().value() == OpReturn)) } -impl<'a, T: VerifiableTransaction> TxScriptEngine<'a, T> { - pub fn new(reused_values: &'a mut SigHashReusedValues, sig_cache: &'a Cache) -> Self { +impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<'a, T, Reused> { + pub fn new(reused_values: &'a Reused, sig_cache: &'a Cache) -> Self { Self { dstack: vec![], astack: vec![], @@ -164,7 +170,7 @@ impl<'a, T: VerifiableTransaction> TxScriptEngine<'a, T> { input: &'a TransactionInput, input_idx: usize, utxo_entry: &'a UtxoEntry, - reused_values: &'a mut SigHashReusedValues, + reused_values: &'a Reused, sig_cache: &'a Cache, ) -> Result { let script_public_key = utxo_entry.script_public_key.script(); @@ -185,7 +191,7 @@ impl<'a, T: VerifiableTransaction> TxScriptEngine<'a, T> { } } - pub fn from_script(script: &'a [u8], reused_values: &'a mut SigHashReusedValues, sig_cache: &'a Cache) -> Self { + pub fn from_script(script: &'a [u8], reused_values: &'a Reused, sig_cache: &'a Cache) -> Self { Self { dstack: Default::default(), astack: Default::default(), @@ -202,7 +208,7 @@ impl<'a, T: VerifiableTransaction> TxScriptEngine<'a, T> { return self.cond_stack.is_empty() || *self.cond_stack.last().expect("Checked not empty") == OpCond::True; } - fn execute_opcode(&mut self, opcode: Box>) -> Result<(), TxScriptError> { + fn execute_opcode(&mut self, opcode: DynOpcodeImplementation) -> Result<(), TxScriptError> { // Different from kaspad: Illegal and disabled opcode are checked on execute instead // Note that this includes OP_RESERVED which counts as a push operation. if !opcode.is_push_opcode() { @@ -512,6 +518,7 @@ mod tests { use crate::opcodes::codes::{OpBlake2b, OpCheckSig, OpData1, OpData2, OpData32, OpDup, OpEqual, OpPushData1, OpTrue}; use super::*; + use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync; use kaspa_consensus_core::tx::{ PopulatedTransaction, ScriptPublicKey, Transaction, TransactionId, TransactionOutpoint, TransactionOutput, }; @@ -542,7 +549,7 @@ mod tests { fn run_test_script_cases(test_cases: Vec) { let sig_cache = Cache::new(10_000); - let mut reused_values = SigHashReusedValues::new(); + let reused_values = SigHashReusedValuesUnsync::new(); for test in test_cases { // Ensure encapsulation of variables (no leaking between tests) @@ -565,7 +572,7 @@ mod tests { let populated_tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - let mut vm = TxScriptEngine::from_transaction_input(&populated_tx, &input, 0, &utxo_entry, &mut reused_values, &sig_cache) + let mut vm = TxScriptEngine::from_transaction_input(&populated_tx, &input, 0, &utxo_entry, &reused_values, &sig_cache) .expect("Script creation failed"); assert_eq!(vm.execute(), test.expected_result); } @@ -783,7 +790,7 @@ mod tests { ]; for test in test_cases { - let check = TxScriptEngine::::check_pub_key_encoding(test.key); + let check = TxScriptEngine::::check_pub_key_encoding(test.key); if test.is_valid { assert_eq!( check, @@ -880,7 +887,10 @@ mod tests { for test in tests { assert_eq!( - get_sig_op_count::(test.signature_script, &test.prev_script_public_key), + get_sig_op_count::( + test.signature_script, + &test.prev_script_public_key + ), test.expected_sig_ops, "failed for '{}'", test.name @@ -909,7 +919,7 @@ mod tests { for test in tests { assert_eq!( - is_unspendable::(test.script_public_key), + is_unspendable::(test.script_public_key), test.expected, "failed for '{}'", test.name @@ -929,6 +939,7 @@ mod bitcoind_tests { use super::*; use crate::script_builder::ScriptBuilderError; use kaspa_consensus_core::constants::MAX_TX_IN_SEQUENCE_NUM; + use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync; use kaspa_consensus_core::tx::{ PopulatedTransaction, ScriptPublicKey, Transaction, TransactionId, TransactionOutpoint, TransactionOutput, }; @@ -1019,13 +1030,13 @@ mod bitcoind_tests { // Run transaction let sig_cache = Cache::new(10_000); - let mut reused_values = SigHashReusedValues::new(); + let reused_values = SigHashReusedValuesUnsync::new(); let mut vm = TxScriptEngine::from_transaction_input( &populated_tx, &populated_tx.tx().inputs[0], 0, &populated_tx.entries[0], - &mut reused_values, + &reused_values, &sig_cache, ) .map_err(UnifiedError::TxScriptError)?; diff --git a/crypto/txscript/src/opcodes/macros.rs b/crypto/txscript/src/opcodes/macros.rs index b3db98829..c4d161d40 100644 --- a/crypto/txscript/src/opcodes/macros.rs +++ b/crypto/txscript/src/opcodes/macros.rs @@ -6,9 +6,9 @@ macro_rules! opcode_serde { [[self.value()].as_slice(), length.to_le_bytes().as_slice(), self.data.as_slice()].concat() } - fn deserialize<'i, I: Iterator, T: VerifiableTransaction>( + fn deserialize<'i, I: Iterator, T: VerifiableTransaction, Reused: SigHashReusedValues>( it: &mut I, - ) -> Result>, TxScriptError> { + ) -> Result>, TxScriptError> { match it.take(size_of::<$type>()).copied().collect::>().try_into() { Ok(bytes) => { let length = <$type>::from_le_bytes(bytes) as usize; @@ -32,9 +32,9 @@ macro_rules! opcode_serde { [[self.value()].as_slice(), self.data.clone().as_slice()].concat() } - fn deserialize<'i, I: Iterator, T: VerifiableTransaction>( + fn deserialize<'i, I: Iterator, T: VerifiableTransaction, Reused: SigHashReusedValues>( it: &mut I, - ) -> Result>, TxScriptError> { + ) -> Result>, TxScriptError> { // Static length includes the opcode itself let data: Vec = it.take($length - 1).copied().collect(); Self::new(data) @@ -44,7 +44,7 @@ macro_rules! opcode_serde { macro_rules! opcode_init { ($type:ty) => { - fn new(data: Vec) -> Result>, TxScriptError> { + fn new(data: Vec) -> Result>, TxScriptError> { if data.len() > <$type>::MAX as usize { return Err(TxScriptError::MalformedPush(<$type>::MAX as usize, data.len())); } @@ -52,7 +52,7 @@ macro_rules! opcode_init { } }; ($length: literal) => { - fn new(data: Vec) -> Result>, TxScriptError> { + fn new(data: Vec) -> Result>, TxScriptError> { if data.len() != $length - 1 { return Err(TxScriptError::MalformedPush($length - 1, data.len())); } @@ -69,20 +69,20 @@ macro_rules! opcode_impl { opcode_serde!($length); } - impl OpCodeExecution for $name { - fn empty() -> Result>, TxScriptError> { + impl OpCodeExecution for $name { + fn empty() -> Result>, TxScriptError> { Self::new(vec![]) } opcode_init!($length); #[allow(unused_variables)] - fn execute(&$self, $vm: &mut TxScriptEngine) -> OpCodeResult { + fn execute(&$self, $vm: &mut TxScriptEngine) -> OpCodeResult { $code } } - impl OpCodeImplementation for $name {} + impl OpCodeImplementation for $name {} } } @@ -111,7 +111,7 @@ macro_rules! opcode_list { )? )* - pub fn deserialize_next_opcode<'i, I: Iterator, T: VerifiableTransaction>(it: &mut I) -> Option>, TxScriptError>> { + pub fn deserialize_next_opcode<'i, I: Iterator, T: VerifiableTransaction, Reused: SigHashReusedValues>(it: &mut I) -> Option>, TxScriptError>> { match it.next() { Some(opcode_num) => match opcode_num { $( diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index ad800d248..f2a92fa0b 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -8,6 +8,7 @@ use crate::{ }; use blake2b_simd::Params; use core::cmp::{max, min}; +use kaspa_consensus_core::hashing::sighash::SigHashReusedValues; use kaspa_consensus_core::hashing::sighash_type::SigHashType; use kaspa_consensus_core::tx::VerifiableTransaction; use sha2::{Digest, Sha256}; @@ -73,28 +74,31 @@ pub trait OpCodeMetadata: Debug { } } -pub trait OpCodeExecution { - fn empty() -> Result>, TxScriptError> +pub trait OpCodeExecution { + fn empty() -> Result>, TxScriptError> where Self: Sized; #[allow(clippy::new_ret_no_self)] - fn new(data: Vec) -> Result>, TxScriptError> + fn new(data: Vec) -> Result>, TxScriptError> where Self: Sized; - fn execute(&self, vm: &mut TxScriptEngine) -> OpCodeResult; + fn execute(&self, vm: &mut TxScriptEngine) -> OpCodeResult; } pub trait OpcodeSerialization { fn serialize(&self) -> Vec; - fn deserialize<'i, I: Iterator, T: VerifiableTransaction>( + fn deserialize<'i, I: Iterator, T: VerifiableTransaction, Reused: SigHashReusedValues>( it: &mut I, - ) -> Result>, TxScriptError> + ) -> Result>, TxScriptError> where Self: Sized; } -pub trait OpCodeImplementation: OpCodeExecution + OpCodeMetadata + OpcodeSerialization {} +pub trait OpCodeImplementation: + OpCodeExecution + OpCodeMetadata + OpcodeSerialization +{ +} impl OpCodeMetadata for OpCode { fn value(&self) -> u8 { @@ -193,13 +197,19 @@ impl OpCodeMetadata for OpCode { // Helpers for some opcodes with shared data #[inline] -fn push_data(data: Vec, vm: &mut TxScriptEngine) -> OpCodeResult { +fn push_data( + data: Vec, + vm: &mut TxScriptEngine, +) -> OpCodeResult { vm.dstack.push(data); Ok(()) } #[inline] -fn push_number(number: i64, vm: &mut TxScriptEngine) -> OpCodeResult { +fn push_number( + number: i64, + vm: &mut TxScriptEngine, +) -> OpCodeResult { vm.dstack.push_item(number); Ok(()) } @@ -958,7 +968,7 @@ opcode_list! { // converts an opcode from the list of Op0 to Op16 to its associated value #[allow(clippy::borrowed_box)] -pub fn to_small_int(opcode: &Box>) -> u8 { +pub fn to_small_int(opcode: &Box>) -> u8 { let value = opcode.value(); if value == codes::OpFalse { return 0; @@ -976,7 +986,7 @@ mod test { use crate::{opcodes, pay_to_address_script, TxScriptEngine, TxScriptError, LOCK_TIME_THRESHOLD}; use kaspa_addresses::{Address, Prefix, Version}; use kaspa_consensus_core::constants::{SOMPI_PER_KASPA, TX_VERSION}; - use kaspa_consensus_core::hashing::sighash::SigHashReusedValues; + use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync; use kaspa_consensus_core::subnets::SUBNETWORK_ID_NATIVE; use kaspa_consensus_core::tx::{ PopulatedTransaction, ScriptPublicKey, Transaction, TransactionInput, TransactionOutpoint, TransactionOutput, UtxoEntry, @@ -985,21 +995,21 @@ mod test { struct TestCase<'a> { init: Stack, - code: Box>>, + code: Box, SigHashReusedValuesUnsync>>, dstack: Stack, } struct ErrorTestCase<'a> { init: Stack, - code: Box>>, + code: Box, SigHashReusedValuesUnsync>>, error: TxScriptError, } fn run_success_test_cases(tests: Vec) { let cache = Cache::new(10_000); - let mut reused_values = SigHashReusedValues::new(); + let reused_values = SigHashReusedValuesUnsync::new(); for TestCase { init, code, dstack } in tests { - let mut vm = TxScriptEngine::new(&mut reused_values, &cache); + let mut vm = TxScriptEngine::new(&reused_values, &cache); vm.dstack = init; code.execute(&mut vm).unwrap_or_else(|_| panic!("Opcode {} should not fail", code.value())); assert_eq!(*vm.dstack, dstack, "OpCode {} Pushed wrong value", code.value()); @@ -1008,9 +1018,9 @@ mod test { fn run_error_test_cases(tests: Vec) { let cache = Cache::new(10_000); - let mut reused_values = SigHashReusedValues::new(); + let reused_values = SigHashReusedValuesUnsync::new(); for ErrorTestCase { init, code, error } in tests { - let mut vm = TxScriptEngine::new(&mut reused_values, &cache); + let mut vm = TxScriptEngine::new(&reused_values, &cache); vm.dstack.clone_from(&init); assert_eq!( code.execute(&mut vm) @@ -1025,7 +1035,7 @@ mod test { #[test] fn test_opcode_disabled() { - let tests: Vec>> = vec![ + let tests: Vec>> = vec![ opcodes::OpCat::empty().expect("Should accept empty"), opcodes::OpSubStr::empty().expect("Should accept empty"), opcodes::OpLeft::empty().expect("Should accept empty"), @@ -1044,8 +1054,8 @@ mod test { ]; let cache = Cache::new(10_000); - let mut reused_values = SigHashReusedValues::new(); - let mut vm = TxScriptEngine::new(&mut reused_values, &cache); + let reused_values = SigHashReusedValuesUnsync::new(); + let mut vm = TxScriptEngine::new(&reused_values, &cache); for pop in tests { match pop.execute(&mut vm) { @@ -1057,7 +1067,7 @@ mod test { #[test] fn test_opcode_reserved() { - let tests: Vec>> = vec![ + let tests: Vec>> = vec![ opcodes::OpReserved::empty().expect("Should accept empty"), opcodes::OpVer::empty().expect("Should accept empty"), opcodes::OpVerIf::empty().expect("Should accept empty"), @@ -1067,8 +1077,8 @@ mod test { ]; let cache = Cache::new(10_000); - let mut reused_values = SigHashReusedValues::new(); - let mut vm = TxScriptEngine::new(&mut reused_values, &cache); + let reused_values = SigHashReusedValuesUnsync::new(); + let mut vm = TxScriptEngine::new(&reused_values, &cache); for pop in tests { match pop.execute(&mut vm) { @@ -1080,7 +1090,7 @@ mod test { #[test] fn test_opcode_invalid() { - let tests: Vec>> = vec![ + let tests: Vec>> = vec![ opcodes::OpUnknown166::empty().expect("Should accept empty"), opcodes::OpUnknown167::empty().expect("Should accept empty"), opcodes::OpUnknown178::empty().expect("Should accept empty"), @@ -1158,8 +1168,8 @@ mod test { ]; let cache = Cache::new(10_000); - let mut reused_values = SigHashReusedValues::new(); - let mut vm = TxScriptEngine::new(&mut reused_values, &cache); + let reused_values = SigHashReusedValuesUnsync::new(); + let mut vm = TxScriptEngine::new(&reused_values, &cache); for pop in tests { match pop.execute(&mut vm) { @@ -2739,7 +2749,7 @@ mod test { let (base_tx, input, utxo_entry) = make_mock_transaction(1); let sig_cache = Cache::new(10_000); - let mut reused_values = SigHashReusedValues::new(); + let reused_values = SigHashReusedValuesUnsync::new(); let code = opcodes::OpCheckLockTimeVerify::empty().expect("Should accept empty"); @@ -2751,7 +2761,7 @@ mod test { ] { let mut tx = base_tx.clone(); tx.0.lock_time = tx_lock_time; - let mut vm = TxScriptEngine::from_transaction_input(&tx, &input, 0, &utxo_entry, &mut reused_values, &sig_cache) + let mut vm = TxScriptEngine::from_transaction_input(&tx, &input, 0, &utxo_entry, &reused_values, &sig_cache) .expect("Shouldn't fail"); vm.dstack = vec![lock_time.clone()]; match code.execute(&mut vm) { @@ -2781,7 +2791,7 @@ mod test { let (tx, base_input, utxo_entry) = make_mock_transaction(1); let sig_cache = Cache::new(10_000); - let mut reused_values = SigHashReusedValues::new(); + let reused_values = SigHashReusedValuesUnsync::new(); let code = opcodes::OpCheckSequenceVerify::empty().expect("Should accept empty"); @@ -2794,7 +2804,7 @@ mod test { ] { let mut input = base_input.clone(); input.sequence = tx_sequence; - let mut vm = TxScriptEngine::from_transaction_input(&tx, &input, 0, &utxo_entry, &mut reused_values, &sig_cache) + let mut vm = TxScriptEngine::from_transaction_input(&tx, &input, 0, &utxo_entry, &reused_values, &sig_cache) .expect("Shouldn't fail"); vm.dstack = vec![sequence.clone()]; match code.execute(&mut vm) { diff --git a/crypto/txscript/src/standard/multisig.rs b/crypto/txscript/src/standard/multisig.rs index 79c74c7b3..cbd9dbe6d 100644 --- a/crypto/txscript/src/standard/multisig.rs +++ b/crypto/txscript/src/standard/multisig.rs @@ -74,7 +74,7 @@ mod tests { use core::str::FromStr; use kaspa_consensus_core::{ hashing::{ - sighash::{calc_ecdsa_signature_hash, calc_schnorr_signature_hash, SigHashReusedValues}, + sighash::{calc_ecdsa_signature_hash, calc_schnorr_signature_hash, SigHashReusedValuesUnsync}, sighash_type::SIG_HASH_ALL, }, subnets::SubnetworkId, @@ -154,11 +154,11 @@ mod tests { }]; let mut tx = MutableTransaction::with_entries(tx, entries); - let mut reused_values = SigHashReusedValues::new(); + let reused_values = SigHashReusedValuesUnsync::new(); let sig_hash = if !is_ecdsa { - calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &mut reused_values) + calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &reused_values) } else { - calc_ecdsa_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &mut reused_values) + calc_ecdsa_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &reused_values) }; let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); let signatures: Vec<_> = inputs @@ -184,7 +184,7 @@ mod tests { let (input, entry) = tx.populated_inputs().next().unwrap(); let cache = Cache::new(10_000); - let mut engine = TxScriptEngine::from_transaction_input(&tx, input, 0, entry, &mut reused_values, &cache).unwrap(); + let mut engine = TxScriptEngine::from_transaction_input(&tx, input, 0, entry, &reused_values, &cache).unwrap(); assert_eq!(engine.execute().is_ok(), is_ok); } #[test] diff --git a/mining/src/mempool/check_transaction_standard.rs b/mining/src/mempool/check_transaction_standard.rs index e759a9e50..060677a1e 100644 --- a/mining/src/mempool/check_transaction_standard.rs +++ b/mining/src/mempool/check_transaction_standard.rs @@ -2,6 +2,7 @@ use crate::mempool::{ errors::{NonStandardError, NonStandardResult}, Mempool, }; +use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync; use kaspa_consensus_core::{ constants::{MAX_SCRIPT_PUBLIC_KEY_VERSION, MAX_SOMPI}, mass, @@ -114,7 +115,7 @@ impl Mempool { /// It is exposed by [MiningManager] for use by transaction generators and wallets. pub(crate) fn is_transaction_output_dust(&self, transaction_output: &TransactionOutput) -> bool { // Unspendable outputs are considered dust. - if is_unspendable::(transaction_output.script_public_key.script()) { + if is_unspendable::(transaction_output.script_public_key.script()) { return true; } @@ -175,7 +176,6 @@ impl Mempool { if contextual_mass > MAXIMUM_STANDARD_TRANSACTION_MASS { return Err(NonStandardError::RejectContextualMass(transaction_id, contextual_mass, MAXIMUM_STANDARD_TRANSACTION_MASS)); } - for (i, input) in transaction.tx.inputs.iter().enumerate() { // It is safe to elide existence and index checks here since // they have already been checked prior to calling this @@ -188,7 +188,10 @@ impl Mempool { ScriptClass::PubKey => {} ScriptClass::PubKeyECDSA => {} ScriptClass::ScriptHash => { - get_sig_op_count::(&input.signature_script, &entry.script_public_key); + get_sig_op_count::( + &input.signature_script, + &entry.script_public_key, + ); let num_sig_ops = 1; if num_sig_ops > MAX_STANDARD_P2SH_SIG_OPS { return Err(NonStandardError::RejectSignatureCount(transaction_id, i, num_sig_ops, MAX_STANDARD_P2SH_SIG_OPS)); diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index e71d7e479..fad6bdb4a 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -9,7 +9,7 @@ use crate::tx::PaymentOutputs; use futures::stream; use kaspa_bip32::{DerivationPath, KeyFingerprint, PrivateKey}; use kaspa_consensus_client::UtxoEntry as ClientUTXO; -use kaspa_consensus_core::hashing::sighash::{calc_schnorr_signature_hash, SigHashReusedValues}; +use kaspa_consensus_core::hashing::sighash::{calc_schnorr_signature_hash, SigHashReusedValuesUnsync}; use kaspa_consensus_core::tx::VerifiableTransaction; use kaspa_consensus_core::tx::{TransactionInput, UtxoEntry}; use kaspa_txscript::extract_script_pub_key_address; @@ -160,7 +160,7 @@ pub async fn pskb_signer_for_address( key_fingerprint: KeyFingerprint, ) -> Result { let mut signed_bundle = Bundle::new(); - let mut reused_values = SigHashReusedValues::new(); + let reused_values = SigHashReusedValuesUnsync::new(); // If set, sign-for address is used for signing. // Else, all addresses from inputs are. @@ -186,7 +186,7 @@ pub async fn pskb_signer_for_address( for pskt_inner in bundle.iter().cloned() { let pskt: PSKT = PSKT::from(pskt_inner); - let mut sign = |signer_pskt: PSKT| { + let sign = |signer_pskt: PSKT| { signer_pskt .pass_signature_sync(|tx, sighash| -> Result, String> { tx.tx @@ -194,7 +194,7 @@ pub async fn pskb_signer_for_address( .iter() .enumerate() .map(|(idx, _input)| { - let hash = calc_schnorr_signature_hash(&tx.as_verifiable(), idx, sighash[idx], &mut reused_values); + let hash = calc_schnorr_signature_hash(&tx.as_verifiable(), idx, sighash[idx], &reused_values); let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice()).unwrap(); // When address represents a locked UTXO, no private key is available. diff --git a/wallet/pskt/examples/multisig.rs b/wallet/pskt/examples/multisig.rs index fb011402f..7a9ca190e 100644 --- a/wallet/pskt/examples/multisig.rs +++ b/wallet/pskt/examples/multisig.rs @@ -1,5 +1,5 @@ use kaspa_consensus_core::{ - hashing::sighash::{calc_schnorr_signature_hash, SigHashReusedValues}, + hashing::sighash::{calc_schnorr_signature_hash, SigHashReusedValuesUnsync}, tx::{TransactionId, TransactionOutpoint, UtxoEntry}, }; use kaspa_txscript::{multisig_redeem_script, opcodes::codes::OpData65, pay_to_script_hash_script, script_builder::ScriptBuilder}; @@ -51,8 +51,8 @@ fn main() { println!("Serialized after setting sequence: {}", ser_updated); let signer_pskt: PSKT = serde_json::from_str(&ser_updated).expect("Failed to deserialize"); - let mut reused_values = SigHashReusedValues::new(); - let mut sign = |signer_pskt: PSKT, kp: &Keypair| { + let reused_values = SigHashReusedValuesUnsync::new(); + let sign = |signer_pskt: PSKT, kp: &Keypair| { signer_pskt .pass_signature_sync(|tx, sighash| -> Result, String> { let tx = dbg!(tx); @@ -61,7 +61,7 @@ fn main() { .iter() .enumerate() .map(|(idx, _input)| { - let hash = calc_schnorr_signature_hash(&tx.as_verifiable(), idx, sighash[idx], &mut reused_values); + let hash = calc_schnorr_signature_hash(&tx.as_verifiable(), idx, sighash[idx], &reused_values); let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice()).unwrap(); Ok(SignInputOk { signature: Signature::Schnorr(kp.sign_schnorr(msg)), diff --git a/wallet/pskt/src/pskt.rs b/wallet/pskt/src/pskt.rs index 73f87a628..93c16ccc8 100644 --- a/wallet/pskt/src/pskt.rs +++ b/wallet/pskt/src/pskt.rs @@ -3,6 +3,7 @@ //! use kaspa_bip32::{secp256k1, DerivationPath, KeyFingerprint}; +use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use std::{collections::BTreeMap, fmt::Display, fmt::Formatter, future::Future, marker::PhantomData, ops::Deref}; @@ -14,7 +15,7 @@ pub use crate::output::{Output, OutputBuilder}; pub use crate::role::{Combiner, Constructor, Creator, Extractor, Finalizer, Signer, Updater}; use kaspa_consensus_core::tx::UtxoEntry; use kaspa_consensus_core::{ - hashing::{sighash::SigHashReusedValues, sighash_type::SigHashType}, + hashing::sighash_type::SigHashType, subnets::SUBNETWORK_ID_NATIVE, tx::{MutableTransaction, SignableTransaction, Transaction, TransactionId, TransactionInput, TransactionOutput}, }; @@ -432,10 +433,10 @@ impl PSKT { { let tx = tx.as_verifiable(); let cache = Cache::new(10_000); - let mut reused_values = SigHashReusedValues::new(); + let reused_values = SigHashReusedValuesUnsync::new(); tx.populated_inputs().enumerate().try_for_each(|(idx, (input, entry))| { - TxScriptEngine::from_transaction_input(&tx, input, idx, entry, &mut reused_values, &cache)?.execute()?; + TxScriptEngine::from_transaction_input(&tx, input, idx, entry, &reused_values, &cache)?.execute()?; >::Ok(()) })?; } From c59a0d1e7d54b732d5626fb0ef56fcb76eb98c62 Mon Sep 17 00:00:00 2001 From: coderofstuff <114628839+coderofstuff@users.noreply.github.com> Date: Sat, 12 Oct 2024 12:32:55 -0600 Subject: [PATCH 16/19] Parallelize MuHash calculations (#575) * Parallelize MuHash calculations MuHash calculations are additive and can be done in chunks then later combined * Reimplement validate tx with muhash as a separate fn * Use smallvec for muhash parallel Co-authored-by: Michael Sutton * Add independent rayon order test * Filter some data * Use tuple_windows for test iter --------- Co-authored-by: Michael Sutton --- consensus/src/consensus/mod.rs | 19 +++- .../virtual_processor/utxo_validation.rs | 96 ++++++++++++++++++- 2 files changed, 110 insertions(+), 5 deletions(-) diff --git a/consensus/src/consensus/mod.rs b/consensus/src/consensus/mod.rs index 1731729a3..fd352fe28 100644 --- a/consensus/src/consensus/mod.rs +++ b/consensus/src/consensus/mod.rs @@ -81,6 +81,7 @@ use kaspa_database::prelude::StoreResultExtensions; use kaspa_hashes::Hash; use kaspa_muhash::MuHash; use kaspa_txscript::caches::TxScriptCacheCounters; +use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use std::{ cmp::Reverse, @@ -771,9 +772,21 @@ impl ConsensusApi for Consensus { fn append_imported_pruning_point_utxos(&self, utxoset_chunk: &[(TransactionOutpoint, UtxoEntry)], current_multiset: &mut MuHash) { let mut pruning_utxoset_write = self.pruning_utxoset_stores.write(); pruning_utxoset_write.utxo_set.write_many(utxoset_chunk).unwrap(); - for (outpoint, entry) in utxoset_chunk { - current_multiset.add_utxo(outpoint, entry); - } + + // Parallelize processing + let inner_multiset = utxoset_chunk + .par_iter() + .map(|(outpoint, entry)| { + let mut inner_multiset = MuHash::new(); + inner_multiset.add_utxo(outpoint, entry); + inner_multiset + }) + .reduce(MuHash::new, |mut a, b| { + a.combine(&b); + a + }); + + current_multiset.combine(&inner_multiset); } fn import_pruning_point_utxo_set(&self, new_pruning_point: Hash, imported_utxo_multiset: MuHash) -> PruningImportResult<()> { diff --git a/consensus/src/pipeline/virtual_processor/utxo_validation.rs b/consensus/src/pipeline/virtual_processor/utxo_validation.rs index 306f81446..454722dca 100644 --- a/consensus/src/pipeline/virtual_processor/utxo_validation.rs +++ b/consensus/src/pipeline/virtual_processor/utxo_validation.rs @@ -31,6 +31,7 @@ use kaspa_muhash::MuHash; use kaspa_utils::refs::Refs; use rayon::prelude::*; +use smallvec::{smallvec, SmallVec}; use std::{iter::once, ops::Deref}; /// A context for processing the UTXO state of a block with respect to its selected parent. @@ -95,12 +96,14 @@ impl VirtualStateProcessor { // No need to fully validate selected parent transactions since selected parent txs were already validated // as part of selected parent UTXO state verification with the exact same UTXO context. let validation_flags = if is_selected_parent { TxValidationFlags::SkipScriptChecks } else { TxValidationFlags::Full }; - let validated_transactions = self.validate_transactions_in_parallel(&txs, &composed_view, pov_daa_score, validation_flags); + let (validated_transactions, inner_multiset) = + self.validate_transactions_with_muhash_in_parallel(&txs, &composed_view, pov_daa_score, validation_flags); + + ctx.multiset_hash.combine(&inner_multiset); let mut block_fee = 0u64; for (validated_tx, _) in validated_transactions.iter() { ctx.mergeset_diff.add_transaction(validated_tx, pov_daa_score).unwrap(); - ctx.multiset_hash.add_transaction(validated_tx, pov_daa_score); ctx.accepted_tx_ids.push(validated_tx.id()); block_fee += validated_tx.calculated_fee; } @@ -229,6 +232,38 @@ impl VirtualStateProcessor { }) } + /// Same as validate_transactions_in_parallel except during the iteration this will also + /// calculate the muhash in parallel for valid transactions + pub(crate) fn validate_transactions_with_muhash_in_parallel<'a, V: UtxoView + Sync>( + &self, + txs: &'a Vec, + utxo_view: &V, + pov_daa_score: u64, + flags: TxValidationFlags, + ) -> (SmallVec<[(ValidatedTransaction<'a>, u32); 2]>, MuHash) { + self.thread_pool.install(|| { + txs + .par_iter() // We can do this in parallel without complications since block body validation already ensured + // that all txs within each block are independent + .enumerate() + .skip(1) // Skip the coinbase tx. + .filter_map(|(i, tx)| self.validate_transaction_in_utxo_context(tx, &utxo_view, pov_daa_score, flags).ok().map(|vtx| { + let mut mh = MuHash::new(); + mh.add_transaction(&vtx, pov_daa_score); + (smallvec![(vtx, i as u32)], mh) + } + )) + .reduce( + || (smallvec![], MuHash::new()), + |mut a, mut b| { + a.0.append(&mut b.0); + a.1.combine(&b.1); + a + }, + ) + }) + } + /// Attempts to populate the transaction with UTXO entries and performs all utxo-related tx validations pub(super) fn validate_transaction_in_utxo_context<'a>( &self, @@ -318,3 +353,60 @@ impl VirtualStateProcessor { Ok(()) } } + +#[cfg(test)] +mod tests { + use itertools::Itertools; + + use super::*; + + #[test] + fn test_rayon_reduce_retains_order() { + // this is an independent test to replicate the behavior of + // validate_txs_in_parallel and validate_txs_with_muhash_in_parallel + // and assert that the order of data is retained when doing par_iter + let data: Vec = (1..=1000).collect(); + + let collected: Vec = data + .par_iter() + .filter_map(|a| { + let chance: f64 = rand::random(); + if chance < 0.05 { + return None; + } + Some(*a) + }) + .collect(); + + println!("collected len: {}", collected.len()); + + collected.iter().tuple_windows().for_each(|(prev, curr)| { + // Data was originally sorted, so we check if they remain sorted after filtering + assert!(prev < curr, "expected {} < {} if original sort was preserved", prev, curr); + }); + + let reduced: SmallVec<[u16; 2]> = data + .par_iter() + .filter_map(|a: &u16| { + let chance: f64 = rand::random(); + if chance < 0.05 { + return None; + } + Some(smallvec![*a]) + }) + .reduce( + || smallvec![], + |mut arr, mut curr_data| { + arr.append(&mut curr_data); + arr + }, + ); + + println!("reduced len: {}", reduced.len()); + + reduced.iter().tuple_windows().for_each(|(prev, curr)| { + // Data was originally sorted, so we check if they remain sorted after filtering + assert!(prev < curr, "expected {} < {} if original sort was preserved", prev, curr); + }); + } +} From 0df2de50c442304724a6031bb0a48193c57a95ab Mon Sep 17 00:00:00 2001 From: Michael Sutton Date: Sun, 13 Oct 2024 20:54:13 +0300 Subject: [PATCH 17/19] Muhash parallel reduce -- optimize U3072 mul when LHS = one (#581) * semantic: add `from` ext methods * muhash from txs benchmark * optimization: in u3072 mul test if lhs is one * extract `parallelism_in_power_steps` * comment --- consensus/Cargo.toml | 2 +- consensus/benches/check_scripts.rs | 4 +- consensus/benches/hash_benchmarks.rs | 15 ----- consensus/benches/parallel_muhash.rs | 66 +++++++++++++++++++ consensus/core/src/muhash.rs | 14 ++++ consensus/src/consensus/mod.rs | 10 +-- .../virtual_processor/utxo_validation.rs | 3 +- crypto/muhash/src/u3072.rs | 10 +++ utils/src/iter.rs | 6 ++ 9 files changed, 102 insertions(+), 28 deletions(-) delete mode 100644 consensus/benches/hash_benchmarks.rs create mode 100644 consensus/benches/parallel_muhash.rs diff --git a/consensus/Cargo.toml b/consensus/Cargo.toml index b9a183ea8..443e591c8 100644 --- a/consensus/Cargo.toml +++ b/consensus/Cargo.toml @@ -57,7 +57,7 @@ kaspa-txscript-errors.workspace = true kaspa-addresses.workspace = true [[bench]] -name = "hash_benchmarks" +name = "parallel_muhash" harness = false [[bench]] diff --git a/consensus/benches/check_scripts.rs b/consensus/benches/check_scripts.rs index d65ac6362..4a596da1b 100644 --- a/consensus/benches/check_scripts.rs +++ b/consensus/benches/check_scripts.rs @@ -9,9 +9,9 @@ use kaspa_consensus_core::subnets::SubnetworkId; use kaspa_consensus_core::tx::{MutableTransaction, Transaction, TransactionInput, TransactionOutpoint, UtxoEntry}; use kaspa_txscript::caches::Cache; use kaspa_txscript::pay_to_address_script; +use kaspa_utils::iter::parallelism_in_power_steps; use rand::{thread_rng, Rng}; use secp256k1::Keypair; -use std::thread::available_parallelism; // You may need to add more detailed mocks depending on your actual code. fn mock_tx(inputs_count: usize, non_uniq_signatures: usize) -> (Transaction, Vec) { @@ -98,7 +98,7 @@ fn benchmark_check_scripts(c: &mut Criterion) { }); // Iterate powers of two up to available parallelism - for i in (1..=(available_parallelism().unwrap().get() as f64).log2().ceil() as u32).map(|x| 2u32.pow(x) as usize) { + for i in parallelism_in_power_steps() { if inputs_count >= i { group.bench_function(format!("rayon, custom thread pool, thread count {i}"), |b| { let tx = MutableTransaction::with_entries(tx.clone(), utxos.clone()); diff --git a/consensus/benches/hash_benchmarks.rs b/consensus/benches/hash_benchmarks.rs deleted file mode 100644 index 8ba6836b8..000000000 --- a/consensus/benches/hash_benchmarks.rs +++ /dev/null @@ -1,15 +0,0 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use std::str::FromStr; - -use kaspa_hashes::Hash; - -/// Placeholder for actual benchmarks -pub fn hash_benchmark(c: &mut Criterion) { - c.bench_function("Hash::from_str", |b| { - let hash_str = "8e40af02265360d59f4ecf9ae9ebf8f00a3118408f5a9cdcbcc9c0f93642f3af"; - b.iter(|| Hash::from_str(black_box(hash_str))) - }); -} - -criterion_group!(benches, hash_benchmark); -criterion_main!(benches); diff --git a/consensus/benches/parallel_muhash.rs b/consensus/benches/parallel_muhash.rs new file mode 100644 index 000000000..99ab5b6c3 --- /dev/null +++ b/consensus/benches/parallel_muhash.rs @@ -0,0 +1,66 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use itertools::Itertools; +use kaspa_consensus_core::{ + muhash::MuHashExtensions, + subnets::SUBNETWORK_ID_NATIVE, + tx::{ScriptPublicKey, SignableTransaction, Transaction, TransactionInput, TransactionOutpoint, TransactionOutput, UtxoEntry}, +}; +use kaspa_hashes::TransactionID; +use kaspa_muhash::MuHash; +use kaspa_utils::iter::parallelism_in_power_steps; +use rayon::prelude::*; + +fn generate_transaction(ins: usize, outs: usize, randomness: u64) -> SignableTransaction { + let mut tx = Transaction::new(0, vec![], vec![], 0, SUBNETWORK_ID_NATIVE, 0, vec![]); + let mut entries = vec![]; + for i in 0..ins { + let mut hasher = TransactionID::new(); + hasher.write(i.to_le_bytes()); + hasher.write(randomness.to_le_bytes()); + let input = TransactionInput::new(TransactionOutpoint::new(hasher.finalize(), 0), vec![10; 66], 0, 1); + let entry = UtxoEntry::new(22222222, ScriptPublicKey::from_vec(0, vec![99; 34]), 23456, false); + tx.inputs.push(input); + entries.push(entry); + } + for _ in 0..outs { + let output = TransactionOutput::new(23456, ScriptPublicKey::from_vec(0, vec![101; 34])); + tx.outputs.push(output); + } + tx.finalize(); + SignableTransaction::with_entries(tx, entries) +} + +pub fn parallel_muhash_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("muhash txs"); + let txs = (0..256).map(|i| generate_transaction(2, 2, i)).collect_vec(); + group.bench_function("seq", |b| { + b.iter(|| { + let mut mh = MuHash::new(); + for tx in txs.iter() { + mh.add_transaction(&tx.as_verifiable(), 222); + } + black_box(mh) + }) + }); + + for threads in parallelism_in_power_steps() { + group.bench_function(format!("par {threads}"), |b| { + let pool = rayon::ThreadPoolBuilder::new().num_threads(threads).build().unwrap(); + b.iter(|| { + pool.install(|| { + let mh = + txs.par_iter().map(|tx| MuHash::from_transaction(&tx.as_verifiable(), 222)).reduce(MuHash::new, |mut a, b| { + a.combine(&b); + a + }); + black_box(mh) + }) + }) + }); + } + + group.finish(); +} + +criterion_group!(benches, parallel_muhash_benchmark); +criterion_main!(benches); diff --git a/consensus/core/src/muhash.rs b/consensus/core/src/muhash.rs index 3782f855c..0286596eb 100644 --- a/consensus/core/src/muhash.rs +++ b/consensus/core/src/muhash.rs @@ -8,6 +8,8 @@ use kaspa_muhash::MuHash; pub trait MuHashExtensions { fn add_transaction(&mut self, tx: &impl VerifiableTransaction, block_daa_score: u64); fn add_utxo(&mut self, outpoint: &TransactionOutpoint, entry: &UtxoEntry); + fn from_transaction(tx: &impl VerifiableTransaction, block_daa_score: u64) -> Self; + fn from_utxo(outpoint: &TransactionOutpoint, entry: &UtxoEntry) -> Self; } impl MuHashExtensions for MuHash { @@ -30,6 +32,18 @@ impl MuHashExtensions for MuHash { write_utxo(&mut writer, entry, outpoint); writer.finalize(); } + + fn from_transaction(tx: &impl VerifiableTransaction, block_daa_score: u64) -> Self { + let mut mh = Self::new(); + mh.add_transaction(tx, block_daa_score); + mh + } + + fn from_utxo(outpoint: &TransactionOutpoint, entry: &UtxoEntry) -> Self { + let mut mh = Self::new(); + mh.add_utxo(outpoint, entry); + mh + } } fn write_utxo(writer: &mut impl HasherBase, entry: &UtxoEntry, outpoint: &TransactionOutpoint) { diff --git a/consensus/src/consensus/mod.rs b/consensus/src/consensus/mod.rs index fd352fe28..d9e4ac7d1 100644 --- a/consensus/src/consensus/mod.rs +++ b/consensus/src/consensus/mod.rs @@ -774,14 +774,8 @@ impl ConsensusApi for Consensus { pruning_utxoset_write.utxo_set.write_many(utxoset_chunk).unwrap(); // Parallelize processing - let inner_multiset = utxoset_chunk - .par_iter() - .map(|(outpoint, entry)| { - let mut inner_multiset = MuHash::new(); - inner_multiset.add_utxo(outpoint, entry); - inner_multiset - }) - .reduce(MuHash::new, |mut a, b| { + let inner_multiset = + utxoset_chunk.par_iter().map(|(outpoint, entry)| MuHash::from_utxo(outpoint, entry)).reduce(MuHash::new, |mut a, b| { a.combine(&b); a }); diff --git a/consensus/src/pipeline/virtual_processor/utxo_validation.rs b/consensus/src/pipeline/virtual_processor/utxo_validation.rs index 454722dca..651688818 100644 --- a/consensus/src/pipeline/virtual_processor/utxo_validation.rs +++ b/consensus/src/pipeline/virtual_processor/utxo_validation.rs @@ -248,8 +248,7 @@ impl VirtualStateProcessor { .enumerate() .skip(1) // Skip the coinbase tx. .filter_map(|(i, tx)| self.validate_transaction_in_utxo_context(tx, &utxo_view, pov_daa_score, flags).ok().map(|vtx| { - let mut mh = MuHash::new(); - mh.add_transaction(&vtx, pov_daa_score); + let mh = MuHash::from_transaction(&vtx, pov_daa_score); (smallvec![(vtx, i as u32)], mh) } )) diff --git a/crypto/muhash/src/u3072.rs b/crypto/muhash/src/u3072.rs index 82021eb88..fae82b8cf 100644 --- a/crypto/muhash/src/u3072.rs +++ b/crypto/muhash/src/u3072.rs @@ -88,6 +88,16 @@ impl U3072 { } fn mul(&mut self, other: &U3072) { + /* + Optimization: short-circuit when LHS is one + - This case is especially frequent during parallel reduce operation where the identity (one) is used for each sub-computation (at the LHS) + - If self ≠ one, the comparison should exit early, otherwise if they are equal -- we gain much more than we lose + - Benchmarks show that general performance remains the same while parallel reduction gains ~35% + */ + if *self == Self::one() { + *self = *other; + return; + } let (mut carry_low, mut carry_high, mut carry_highest) = (0, 0, 0); let mut tmp = Self::one(); diff --git a/utils/src/iter.rs b/utils/src/iter.rs index 58a61d770..3c4c98c64 100644 --- a/utils/src/iter.rs +++ b/utils/src/iter.rs @@ -48,3 +48,9 @@ where self.inner.clone().fmt(f) } } + +/// Returns an iterator over powers of two up to (the rounded up) available parallelism: `2, 4, 8, ..., 2^(available_parallelism.log2().ceil())`, +/// i.e., for `std::thread::available_parallelism = 15` the function will return `2, 4, 8, 16` +pub fn parallelism_in_power_steps() -> impl Iterator { + (1..=(std::thread::available_parallelism().unwrap().get() as f64).log2().ceil() as u32).map(|x| 2usize.pow(x)) +} From a40efbb6900333f072043f30bf9c3de2c34ec143 Mon Sep 17 00:00:00 2001 From: Michael Sutton Date: Tue, 22 Oct 2024 12:40:20 +0300 Subject: [PATCH 18/19] Rust 1.82 fixes + mempool std sig op count check (#583) * rust 1.82 fixes * sig op count std check --- cli/src/cli.rs | 4 ++-- crypto/txscript/src/lib.rs | 1 + mining/errors/src/mempool.rs | 2 +- mining/src/mempool/check_transaction_standard.rs | 5 ++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 5ca1997ea..a32956740 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -1016,7 +1016,7 @@ mod panic_handler { fn stack(error: &Error) -> String; } - pub fn process(info: &std::panic::PanicInfo) -> String { + pub fn process(info: &std::panic::PanicHookInfo) -> String { let mut msg = info.to_string(); // Add the error stack to our message. @@ -1053,7 +1053,7 @@ mod panic_handler { impl KaspaCli { pub fn init_panic_hook(self: &Arc) { let this = self.clone(); - let handler = move |info: &std::panic::PanicInfo| { + let handler = move |info: &std::panic::PanicHookInfo| { let msg = panic_handler::process(info); this.term().writeln(msg.crlf()); panic_handler::console_error(msg); diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index f17796272..5fed84328 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -93,6 +93,7 @@ fn parse_script( script.iter().batching(|it| deserialize_next_opcode(it)) } +#[must_use] pub fn get_sig_op_count( signature_script: &[u8], prev_script_public_key: &ScriptPublicKey, diff --git a/mining/errors/src/mempool.rs b/mining/errors/src/mempool.rs index 319aaa484..12416be67 100644 --- a/mining/errors/src/mempool.rs +++ b/mining/errors/src/mempool.rs @@ -131,7 +131,7 @@ pub enum NonStandardError { RejectInsufficientFee(TransactionId, u64, u64), #[error("transaction input #{1} has {2} signature operations which is more than the allowed max amount of {3}")] - RejectSignatureCount(TransactionId, usize, u8, u8), + RejectSignatureCount(TransactionId, usize, u64, u8), } impl NonStandardError { diff --git a/mining/src/mempool/check_transaction_standard.rs b/mining/src/mempool/check_transaction_standard.rs index 060677a1e..ef4d3fb9e 100644 --- a/mining/src/mempool/check_transaction_standard.rs +++ b/mining/src/mempool/check_transaction_standard.rs @@ -188,12 +188,11 @@ impl Mempool { ScriptClass::PubKey => {} ScriptClass::PubKeyECDSA => {} ScriptClass::ScriptHash => { - get_sig_op_count::( + let num_sig_ops = get_sig_op_count::( &input.signature_script, &entry.script_public_key, ); - let num_sig_ops = 1; - if num_sig_ops > MAX_STANDARD_P2SH_SIG_OPS { + if num_sig_ops > MAX_STANDARD_P2SH_SIG_OPS as u64 { return Err(NonStandardError::RejectSignatureCount(transaction_id, i, num_sig_ops, MAX_STANDARD_P2SH_SIG_OPS)); } } From aac16a9244e558f719e37663e587838664248466 Mon Sep 17 00:00:00 2001 From: Romain Billot Date: Tue, 22 Oct 2024 11:41:22 +0200 Subject: [PATCH 19/19] typo(cli/utils): kaspa wording (#582) Co-authored-by: Michael Sutton --- cli/src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/utils.rs b/cli/src/utils.rs index 3e1d0ddb1..e52581b4c 100644 --- a/cli/src/utils.rs +++ b/cli/src/utils.rs @@ -8,7 +8,7 @@ pub fn try_parse_required_nonzero_kaspa_as_sompi_u64(kasp let sompi_amount = kaspa_amount .to_string() .parse::() - .map_err(|_| Error::custom(format!("Supplied Kasapa amount is not valid: '{kaspa_amount}'")))? + .map_err(|_| Error::custom(format!("Supplied Kaspa amount is not valid: '{kaspa_amount}'")))? * SOMPI_PER_KASPA as f64; if sompi_amount < 0.0 { Err(Error::custom("Supplied Kaspa amount is not valid: '{kaspa_amount}'"))