diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 693266885c..49fe4e4637 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/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml
index 8f46cb1fe5..537eeef898 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/Cargo.lock b/Cargo.lock
index 4beda90501..8e9829fea6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2355,7 +2355,7 @@ dependencies = [
[[package]]
name = "kaspa-addresses"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"borsh",
"criterion",
@@ -2372,7 +2372,7 @@ dependencies = [
[[package]]
name = "kaspa-addressmanager"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"borsh",
"igd-next",
@@ -2395,14 +2395,14 @@ dependencies = [
[[package]]
name = "kaspa-alloc"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"mimalloc",
]
[[package]]
name = "kaspa-bip32"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"borsh",
"bs58",
@@ -2429,7 +2429,7 @@ dependencies = [
[[package]]
name = "kaspa-cli"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-trait",
"borsh",
@@ -2476,7 +2476,7 @@ dependencies = [
[[package]]
name = "kaspa-connectionmanager"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"duration-string",
"futures-util",
@@ -2493,7 +2493,7 @@ dependencies = [
[[package]]
name = "kaspa-consensus"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"arc-swap",
"async-channel 2.3.1",
@@ -2536,7 +2536,7 @@ dependencies = [
[[package]]
name = "kaspa-consensus-client"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"ahash",
"cfg-if 1.0.0",
@@ -2564,7 +2564,7 @@ dependencies = [
[[package]]
name = "kaspa-consensus-core"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-trait",
"bincode",
@@ -2602,7 +2602,7 @@ dependencies = [
[[package]]
name = "kaspa-consensus-notify"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-channel 2.3.1",
"cfg-if 1.0.0",
@@ -2621,7 +2621,7 @@ dependencies = [
[[package]]
name = "kaspa-consensus-wasm"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"cfg-if 1.0.0",
"faster-hex",
@@ -2645,7 +2645,7 @@ dependencies = [
[[package]]
name = "kaspa-consensusmanager"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"duration-string",
"futures",
@@ -2663,7 +2663,7 @@ dependencies = [
[[package]]
name = "kaspa-core"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"cfg-if 1.0.0",
"ctrlc",
@@ -2681,7 +2681,7 @@ dependencies = [
[[package]]
name = "kaspa-daemon"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-trait",
"borsh",
@@ -2703,7 +2703,7 @@ dependencies = [
[[package]]
name = "kaspa-database"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"bincode",
"enum-primitive-derive",
@@ -2725,7 +2725,7 @@ dependencies = [
[[package]]
name = "kaspa-grpc-client"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-channel 2.3.1",
"async-stream",
@@ -2757,7 +2757,7 @@ dependencies = [
[[package]]
name = "kaspa-grpc-core"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-channel 2.3.1",
"async-stream",
@@ -2786,7 +2786,7 @@ dependencies = [
[[package]]
name = "kaspa-grpc-server"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-channel 2.3.1",
"async-stream",
@@ -2822,7 +2822,7 @@ dependencies = [
[[package]]
name = "kaspa-hashes"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"blake2b_simd",
"borsh",
@@ -2843,7 +2843,7 @@ dependencies = [
[[package]]
name = "kaspa-index-core"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-channel 2.3.1",
"async-trait",
@@ -2862,7 +2862,7 @@ dependencies = [
[[package]]
name = "kaspa-index-processor"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-channel 2.3.1",
"async-trait",
@@ -2890,7 +2890,7 @@ dependencies = [
[[package]]
name = "kaspa-math"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"borsh",
"criterion",
@@ -2911,14 +2911,14 @@ dependencies = [
[[package]]
name = "kaspa-merkle"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"kaspa-hashes",
]
[[package]]
name = "kaspa-metrics-core"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-trait",
"borsh",
@@ -2934,7 +2934,7 @@ dependencies = [
[[package]]
name = "kaspa-mining"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"criterion",
"futures-util",
@@ -2961,7 +2961,7 @@ dependencies = [
[[package]]
name = "kaspa-mining-errors"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"kaspa-consensus-core",
"thiserror",
@@ -2969,7 +2969,7 @@ dependencies = [
[[package]]
name = "kaspa-muhash"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"criterion",
"kaspa-hashes",
@@ -2982,7 +2982,7 @@ dependencies = [
[[package]]
name = "kaspa-notify"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-channel 2.3.1",
"async-trait",
@@ -3018,7 +3018,7 @@ dependencies = [
[[package]]
name = "kaspa-p2p-flows"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-trait",
"chrono",
@@ -3049,7 +3049,7 @@ dependencies = [
[[package]]
name = "kaspa-p2p-lib"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"borsh",
"ctrlc",
@@ -3080,7 +3080,7 @@ dependencies = [
[[package]]
name = "kaspa-perf-monitor"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"kaspa-core",
"log",
@@ -3092,7 +3092,7 @@ dependencies = [
[[package]]
name = "kaspa-pow"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"criterion",
"js-sys",
@@ -3108,7 +3108,7 @@ dependencies = [
[[package]]
name = "kaspa-rpc-core"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-channel 2.3.1",
"async-trait",
@@ -3150,7 +3150,7 @@ dependencies = [
[[package]]
name = "kaspa-rpc-macros"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"convert_case 0.6.0",
"proc-macro-error",
@@ -3162,7 +3162,7 @@ dependencies = [
[[package]]
name = "kaspa-rpc-service"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-trait",
"kaspa-addresses",
@@ -3191,7 +3191,7 @@ dependencies = [
[[package]]
name = "kaspa-testing-integration"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-channel 2.3.1",
"async-trait",
@@ -3251,7 +3251,7 @@ dependencies = [
[[package]]
name = "kaspa-txscript"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"blake2b_simd",
"borsh",
@@ -3283,7 +3283,7 @@ dependencies = [
[[package]]
name = "kaspa-txscript-errors"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"secp256k1",
"thiserror",
@@ -3291,7 +3291,7 @@ dependencies = [
[[package]]
name = "kaspa-utils"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"arc-swap",
"async-channel 2.3.1",
@@ -3327,7 +3327,7 @@ dependencies = [
[[package]]
name = "kaspa-utils-tower"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"bytes",
"cfg-if 1.0.0",
@@ -3343,7 +3343,7 @@ dependencies = [
[[package]]
name = "kaspa-utxoindex"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"futures",
"kaspa-consensus",
@@ -3364,7 +3364,7 @@ dependencies = [
[[package]]
name = "kaspa-wallet"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-std",
"async-trait",
@@ -3376,7 +3376,7 @@ dependencies = [
[[package]]
name = "kaspa-wallet-cli-wasm"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-trait",
"js-sys",
@@ -3390,7 +3390,7 @@ dependencies = [
[[package]]
name = "kaspa-wallet-core"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"aes",
"ahash",
@@ -3471,7 +3471,7 @@ dependencies = [
[[package]]
name = "kaspa-wallet-keys"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-trait",
"borsh",
@@ -3504,7 +3504,7 @@ dependencies = [
[[package]]
name = "kaspa-wallet-macros"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"convert_case 0.5.0",
"proc-macro-error",
@@ -3517,7 +3517,7 @@ dependencies = [
[[package]]
name = "kaspa-wallet-pskt"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"bincode",
"derive_builder",
@@ -3544,7 +3544,7 @@ dependencies = [
[[package]]
name = "kaspa-wasm"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
@@ -3572,7 +3572,7 @@ dependencies = [
[[package]]
name = "kaspa-wasm-core"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"faster-hex",
"hexplay",
@@ -3583,7 +3583,7 @@ dependencies = [
[[package]]
name = "kaspa-wrpc-client"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-std",
"async-trait",
@@ -3619,7 +3619,7 @@ dependencies = [
[[package]]
name = "kaspa-wrpc-example-subscriber"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"ctrlc",
"futures",
@@ -3634,7 +3634,7 @@ dependencies = [
[[package]]
name = "kaspa-wrpc-proxy"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-trait",
"clap 4.5.16",
@@ -3653,7 +3653,7 @@ dependencies = [
[[package]]
name = "kaspa-wrpc-server"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-trait",
"borsh",
@@ -3681,7 +3681,7 @@ dependencies = [
[[package]]
name = "kaspa-wrpc-simple-client-example"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"futures",
"kaspa-rpc-core",
@@ -3691,7 +3691,7 @@ dependencies = [
[[package]]
name = "kaspa-wrpc-wasm"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"ahash",
"async-std",
@@ -3721,7 +3721,7 @@ dependencies = [
[[package]]
name = "kaspad"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-channel 2.3.1",
"cfg-if 1.0.0",
@@ -5145,7 +5145,7 @@ dependencies = [
[[package]]
name = "rothschild"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-channel 2.3.1",
"clap 4.5.16",
@@ -5557,7 +5557,7 @@ dependencies = [
[[package]]
name = "simpa"
-version = "0.14.7"
+version = "0.15.2"
dependencies = [
"async-channel 2.3.1",
"cfg-if 1.0.0",
diff --git a/Cargo.toml b/Cargo.toml
index 1625602d75..8e6da754a6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -63,7 +63,7 @@ members = [
[workspace.package]
rust-version = "1.81.0"
-version = "0.14.7"
+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.14.7", path = "testing/integration" }
-kaspa-addresses = { version = "0.14.7", path = "crypto/addresses" }
-kaspa-addressmanager = { version = "0.14.7", path = "components/addressmanager" }
-kaspa-bip32 = { version = "0.14.7", path = "wallet/bip32" }
-kaspa-cli = { version = "0.14.7", path = "cli" }
-kaspa-connectionmanager = { version = "0.14.7", path = "components/connectionmanager" }
-kaspa-consensus = { version = "0.14.7", path = "consensus" }
-kaspa-consensus-core = { version = "0.14.7", path = "consensus/core" }
-kaspa-consensus-client = { version = "0.14.7", path = "consensus/client" }
-kaspa-consensus-notify = { version = "0.14.7", path = "consensus/notify" }
-kaspa-consensus-wasm = { version = "0.14.7", path = "consensus/wasm" }
-kaspa-consensusmanager = { version = "0.14.7", path = "components/consensusmanager" }
-kaspa-core = { version = "0.14.7", path = "core" }
-kaspa-daemon = { version = "0.14.7", path = "daemon" }
-kaspa-database = { version = "0.14.7", path = "database" }
-kaspa-grpc-client = { version = "0.14.7", path = "rpc/grpc/client" }
-kaspa-grpc-core = { version = "0.14.7", path = "rpc/grpc/core" }
-kaspa-grpc-server = { version = "0.14.7", path = "rpc/grpc/server" }
-kaspa-hashes = { version = "0.14.7", path = "crypto/hashes" }
-kaspa-index-core = { version = "0.14.7", path = "indexes/core" }
-kaspa-index-processor = { version = "0.14.7", path = "indexes/processor" }
-kaspa-math = { version = "0.14.7", path = "math" }
-kaspa-merkle = { version = "0.14.7", path = "crypto/merkle" }
-kaspa-metrics-core = { version = "0.14.7", path = "metrics/core" }
-kaspa-mining = { version = "0.14.7", path = "mining" }
-kaspa-mining-errors = { version = "0.14.7", path = "mining/errors" }
-kaspa-muhash = { version = "0.14.7", path = "crypto/muhash" }
-kaspa-notify = { version = "0.14.7", path = "notify" }
-kaspa-p2p-flows = { version = "0.14.7", path = "protocol/flows" }
-kaspa-p2p-lib = { version = "0.14.7", path = "protocol/p2p" }
-kaspa-perf-monitor = { version = "0.14.7", path = "metrics/perf_monitor" }
-kaspa-pow = { version = "0.14.7", path = "consensus/pow" }
-kaspa-rpc-core = { version = "0.14.7", path = "rpc/core" }
-kaspa-rpc-macros = { version = "0.14.7", path = "rpc/macros" }
-kaspa-rpc-service = { version = "0.14.7", path = "rpc/service" }
-kaspa-txscript = { version = "0.14.7", path = "crypto/txscript" }
-kaspa-txscript-errors = { version = "0.14.7", path = "crypto/txscript/errors" }
-kaspa-utils = { version = "0.14.7", path = "utils" }
-kaspa-utils-tower = { version = "0.14.7", path = "utils/tower" }
-kaspa-utxoindex = { version = "0.14.7", path = "indexes/utxoindex" }
-kaspa-wallet = { version = "0.14.7", path = "wallet/native" }
-kaspa-wallet-cli-wasm = { version = "0.14.7", path = "wallet/wasm" }
-kaspa-wallet-keys = { version = "0.14.7", path = "wallet/keys" }
-kaspa-wallet-pskt = { version = "0.14.7", path = "wallet/pskt" }
-kaspa-wallet-core = { version = "0.14.7", path = "wallet/core" }
-kaspa-wallet-macros = { version = "0.14.7", path = "wallet/macros" }
-kaspa-wasm = { version = "0.14.7", path = "wasm" }
-kaspa-wasm-core = { version = "0.14.7", path = "wasm/core" }
-kaspa-wrpc-client = { version = "0.14.7", path = "rpc/wrpc/client" }
-kaspa-wrpc-proxy = { version = "0.14.7", path = "rpc/wrpc/proxy" }
-kaspa-wrpc-server = { version = "0.14.7", path = "rpc/wrpc/server" }
-kaspa-wrpc-wasm = { version = "0.14.7", path = "rpc/wrpc/wasm" }
-kaspa-wrpc-example-subscriber = { version = "0.14.7", path = "rpc/wrpc/examples/subscriber" }
-kaspad = { version = "0.14.7", path = "kaspad" }
-kaspa-alloc = { version = "0.14.7", 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/README.md b/README.md
index 22b4ef7ca6..ada38c55d7 100644
--- a/README.md
+++ b/README.md
@@ -3,10 +3,12 @@
Welcome to the Rust-based implementation of the Kaspa full-node and its ancillary libraries. The contained node release serves as a drop-in replacement to the established Golang node and to date is the recommended node software for the Kaspa network, introducing developers to the possibilities of Rust in the Kaspa network's context.
-We invite developers and blockchain enthusiasts to collaborate, test, and optimize our Rust implementation. Each line of code here is an opportunity to contribute to the open-source blockchain movement, shaping a platform designed for scalability and speed without compromising on decentralization.
+We invite developers and blockchain enthusiasts to collaborate, test, and optimize our Rust implementation. Each line of code here is an opportunity to contribute to the open-source blockchain movement, shaping a platform designed for scalability and speed without compromising on security and decentralization.
Your feedback, contributions, and issue reports will be integral to evolving this codebase and continuing its maturity as a reliable node in the Kaspa network.
+The default branch of this repository is `master` and new contributions are constantly merged into it. For a stable branch corresponding to the latest stable release please pull and compile the `stable` branch.
+
## Installation
Building on Linux
diff --git a/cli/src/modules/connect.rs b/cli/src/modules/connect.rs
index 024f7e6934..a755915d4f 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/cli/src/modules/rpc.rs b/cli/src/modules/rpc.rs
index f32523c4a3..cf6bc6bd20 100644
--- a/cli/src/modules/rpc.rs
+++ b/cli/src/modules/rpc.rs
@@ -114,17 +114,28 @@ 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 => {
// 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.first().and_then(|x| x.parse::().ok()).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/lib.rs b/components/consensusmanager/src/lib.rs
index 54bdda40b9..6d31653aab 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 81d5891488..8e0c6e9335 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,
@@ -267,8 +268,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 +385,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/client/src/error.rs b/consensus/client/src/error.rs
index e0aab2156c..e632f517d5 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 4402cfb1b5..1577689a67 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 56fd92845e..6f04a73c43 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 736696bfae..a5018199d5 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 eb482eab16..3afae2f78b 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 06be53f6aa..a9b39f5e4f 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 8f335c47d7..17b4a58c80 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 4c8cb83f54..d8bff8aa10 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 a590ab2862..ab78d956be 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 733afd54e9..6c24db634a 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 e35cdb028b..35c7907b29 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 c254aee076..4044dc5701 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 fb6d185f0e..17cc381265 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 4f543d45bc..7e08556fec 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 0a8b3fdb94..bbfc1199d1 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 4833c7659a..365b8404c1 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,
}
@@ -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/core/src/config/bps.rs b/consensus/core/src/config/bps.rs
index c0c52a6dfd..5e98aac5df 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 46ad3f2cea..188b2403b4 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 d5e9abd244..18e52eacbf 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 bad1b679a2..a4dd7dd45b 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/consensus/mod.rs b/consensus/src/consensus/mod.rs
index 8474a6864a..1731729a32 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 dfb7394b80..88fee97bff 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/ghostdag/protocol.rs b/consensus/src/processes/ghostdag/protocol.rs
index 87beeb565d..8dfe4e7937 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 9f8d7fbd09..b910f3ddf1 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/consensus/src/processes/sync/mod.rs b/consensus/src/processes/sync/mod.rs
index 8472229682..3978913bae 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 3ae0aef5d7..23dc5c69f0 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;
-/// 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 4406bd5b6d..ad800d2488 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 61aa877ab8..0e09989055 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 b71935afa2..3b1bf2fe9d 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 8555a02d41..96449dbffe 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 d8030f1f3d..ce9dc72161 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,
@@ -922,7 +863,7 @@ pub fn as_data_size(bytes: f64, si: bool) -> String {
}
/// Format supplied value as a float with 2 decimal places.
-fn format_as_float(f: f64, short: bool) -> String {
+pub fn format_as_float(f: f64, short: bool) -> String {
if short {
if f < 1000.0 {
format_with_precision(f)
diff --git a/metrics/core/src/lib.rs b/metrics/core/src/lib.rs
index b88dcf0946..53519b0f0c 100644
--- a/metrics/core/src/lib.rs
+++ b/metrics/core/src/lib.rs
@@ -74,6 +74,8 @@ impl Metrics {
let interval = interval(Duration::from_secs(1));
pin_mut!(interval);
+ let mut first = true;
+
loop {
select! {
_ = task_ctl_receiver.recv().fuse() => {
@@ -81,17 +83,16 @@ impl Metrics {
},
_ = interval.next().fuse() => {
- // current_metrics_data = MetricsData::new(unixtime_as_millis_f64());
-
if let Some(rpc) = this.rpc() {
- // if let Err(err) = this.sample_metrics(rpc.clone(), &mut current_metrics_data).await {
match this.sample_metrics(rpc.clone()).await {
Ok(incoming_data) => {
let last_metrics_data = current_metrics_data;
current_metrics_data = incoming_data;
this.data.lock().unwrap().replace(current_metrics_data.clone());
- if let Some(sink) = this.sink() {
+ if first {
+ first = false;
+ } else if let Some(sink) = this.sink() {
let snapshot = MetricsSnapshot::from((&last_metrics_data, ¤t_metrics_data));
if let Some(future) = sink(snapshot) {
future.await.ok();
@@ -100,7 +101,6 @@ impl Metrics {
}
Err(err) => {
- // current_metrics_data = last_metrics_data.clone();
log_trace!("Metrics::sample_metrics() error: {}", err);
}
}
@@ -120,87 +120,7 @@ impl Metrics {
Ok(())
}
- // --- samplers
-
async fn sample_metrics(self: &Arc, rpc: Arc) -> Result {
- // let GetMetricsResponse {
- // server_time: _,
- // consensus_metrics,
- // connection_metrics,
- // bandwidth_metrics,
- // process_metrics,
- // storage_metrics,
- // custom_metrics: _,
- // } =
- let response = rpc.get_metrics(true, true, true, true, true, false).await?;
-
- MetricsData::try_from(response)
-
- // if let Some(consensus_metrics) = consensus_metrics {
- // data.node_blocks_submitted_count = consensus_metrics.node_blocks_submitted_count;
- // data.node_headers_processed_count = consensus_metrics.node_headers_processed_count;
- // data.node_dependencies_processed_count = consensus_metrics.node_dependencies_processed_count;
- // data.node_bodies_processed_count = consensus_metrics.node_bodies_processed_count;
- // data.node_transactions_processed_count = consensus_metrics.node_transactions_processed_count;
- // data.node_chain_blocks_processed_count = consensus_metrics.node_chain_blocks_processed_count;
- // data.node_mass_processed_count = consensus_metrics.node_mass_processed_count;
- // // --
- // data.node_database_blocks_count = consensus_metrics.node_database_blocks_count;
- // data.node_database_headers_count = consensus_metrics.node_database_headers_count;
- // data.network_mempool_size = consensus_metrics.network_mempool_size;
- // data.network_tip_hashes_count = consensus_metrics.network_tip_hashes_count;
- // data.network_difficulty = consensus_metrics.network_difficulty;
- // data.network_past_median_time = consensus_metrics.network_past_median_time;
- // data.network_virtual_parent_hashes_count = consensus_metrics.network_virtual_parent_hashes_count;
- // data.network_virtual_daa_score = consensus_metrics.network_virtual_daa_score;
- // }
-
- // if let Some(connection_metrics) = connection_metrics {
- // data.node_borsh_live_connections = connection_metrics.borsh_live_connections;
- // data.node_borsh_connection_attempts = connection_metrics.borsh_connection_attempts;
- // data.node_borsh_handshake_failures = connection_metrics.borsh_handshake_failures;
- // data.node_json_live_connections = connection_metrics.json_live_connections;
- // data.node_json_connection_attempts = connection_metrics.json_connection_attempts;
- // data.node_json_handshake_failures = connection_metrics.json_handshake_failures;
- // data.node_active_peers = connection_metrics.active_peers;
- // }
-
- // if let Some(bandwidth_metrics) = bandwidth_metrics {
- // data.node_borsh_bytes_tx = bandwidth_metrics.borsh_bytes_tx;
- // data.node_borsh_bytes_rx = bandwidth_metrics.borsh_bytes_rx;
- // data.node_json_bytes_tx = bandwidth_metrics.json_bytes_tx;
- // data.node_json_bytes_rx = bandwidth_metrics.json_bytes_rx;
- // data.node_p2p_bytes_tx = bandwidth_metrics.p2p_bytes_tx;
- // data.node_p2p_bytes_rx = bandwidth_metrics.p2p_bytes_rx;
- // data.node_grpc_user_bytes_tx = bandwidth_metrics.grpc_bytes_tx;
- // data.node_grpc_user_bytes_rx = bandwidth_metrics.grpc_bytes_rx;
-
- // data.node_total_bytes_tx = bandwidth_metrics.borsh_bytes_tx
- // + bandwidth_metrics.json_bytes_tx
- // + bandwidth_metrics.p2p_bytes_tx
- // + bandwidth_metrics.grpc_bytes_tx;
-
- // data.node_total_bytes_rx = bandwidth_metrics.borsh_bytes_rx
- // + bandwidth_metrics.json_bytes_rx
- // + bandwidth_metrics.p2p_bytes_rx
- // + bandwidth_metrics.grpc_bytes_rx;
- // }
-
- // if let Some(process_metrics) = process_metrics {
- // data.node_resident_set_size_bytes = process_metrics.resident_set_size;
- // data.node_virtual_memory_size_bytes = process_metrics.virtual_memory_size;
- // data.node_cpu_cores = process_metrics.core_num;
- // data.node_cpu_usage = process_metrics.cpu_usage;
- // data.node_file_handles = process_metrics.fd_num;
- // data.node_disk_io_read_bytes = process_metrics.disk_io_read_bytes;
- // data.node_disk_io_write_bytes = process_metrics.disk_io_write_bytes;
- // data.node_disk_io_read_per_sec = process_metrics.disk_io_read_per_sec;
- // data.node_disk_io_write_per_sec = process_metrics.disk_io_write_per_sec;
- // }
-
- // if let Some(storage_metrics) = storage_metrics {
- // data.node_storage_size_bytes = storage_metrics.storage_size_bytes;
- // }
- // Ok(())
+ MetricsData::try_from(rpc.get_metrics(true, true, true, true, true, false).await?)
}
}
diff --git a/mining/src/feerate/fee_estimation.ipynb b/mining/src/feerate/fee_estimation.ipynb
index 694f47450c..a8b8fbfc89 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": [
"