diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c9aa2a2673..9b1e99a1cb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -44,7 +44,9 @@ jobs: # Clean unnecessary files to save disk space - name: clean unnecessary files to save space run: | - docker rmi `docker images -q` + if [ "$(docker images -q)" ]; then + docker rmi $(docker images -q) + fi sudo rm -rf /usr/share/dotnet /etc/mysql /etc/php /etc/sudo apt/sources.list.d sudo apt -y autoremove --purge sudo apt -y autoclean diff --git a/Cargo.lock b/Cargo.lock index 8bc4c86ea7..b47619c304 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2265,7 +2265,7 @@ dependencies = [ [[package]] name = "kaspa-addresses" -version = "0.15.4" +version = "0.16.0" dependencies = [ "borsh", "criterion", @@ -2282,7 +2282,7 @@ dependencies = [ [[package]] name = "kaspa-addressmanager" -version = "0.15.4" +version = "0.16.0" dependencies = [ "borsh", "igd-next", @@ -2304,14 +2304,14 @@ dependencies = [ [[package]] name = "kaspa-alloc" -version = "0.15.4" +version = "0.16.0" dependencies = [ "mimalloc", ] [[package]] name = "kaspa-bip32" -version = "0.15.4" +version = "0.16.0" dependencies = [ "borsh", "bs58", @@ -2338,7 +2338,7 @@ dependencies = [ [[package]] name = "kaspa-cli" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-trait", "borsh", @@ -2385,7 +2385,7 @@ dependencies = [ [[package]] name = "kaspa-connectionmanager" -version = "0.15.4" +version = "0.16.0" dependencies = [ "duration-string", "futures-util", @@ -2402,7 +2402,7 @@ dependencies = [ [[package]] name = "kaspa-consensus" -version = "0.15.4" +version = "0.16.0" dependencies = [ "arc-swap", "async-channel 2.3.1", @@ -2446,7 +2446,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-client" -version = "0.15.4" +version = "0.16.0" dependencies = [ "ahash", "cfg-if 1.0.0", @@ -2474,7 +2474,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-core" -version = "0.15.4" +version = "0.16.0" dependencies = [ "arc-swap", "async-trait", @@ -2513,7 +2513,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-notify" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "cfg-if 1.0.0", @@ -2532,7 +2532,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-wasm" -version = "0.15.4" +version = "0.16.0" dependencies = [ "cfg-if 1.0.0", "faster-hex", @@ -2556,12 +2556,13 @@ dependencies = [ [[package]] name = "kaspa-consensusmanager" -version = "0.15.4" +version = "0.16.0" dependencies = [ "duration-string", "futures", "futures-util", "itertools 0.13.0", + "kaspa-addresses", "kaspa-consensus-core", "kaspa-consensus-notify", "kaspa-core", @@ -2574,7 +2575,7 @@ dependencies = [ [[package]] name = "kaspa-core" -version = "0.15.4" +version = "0.16.0" dependencies = [ "cfg-if 1.0.0", "ctrlc", @@ -2592,7 +2593,7 @@ dependencies = [ [[package]] name = "kaspa-daemon" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-trait", "borsh", @@ -2614,7 +2615,7 @@ dependencies = [ [[package]] name = "kaspa-database" -version = "0.15.4" +version = "0.16.0" dependencies = [ "bincode", "enum-primitive-derive", @@ -2636,7 +2637,7 @@ dependencies = [ [[package]] name = "kaspa-grpc-client" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "async-stream", @@ -2668,7 +2669,7 @@ dependencies = [ [[package]] name = "kaspa-grpc-core" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "async-stream", @@ -2676,6 +2677,7 @@ dependencies = [ "faster-hex", "futures", "h2 0.4.6", + "kaspa-addresses", "kaspa-consensus-core", "kaspa-core", "kaspa-notify", @@ -2697,7 +2699,7 @@ dependencies = [ [[package]] name = "kaspa-grpc-server" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "async-stream", @@ -2733,7 +2735,7 @@ dependencies = [ [[package]] name = "kaspa-hashes" -version = "0.15.4" +version = "0.16.0" dependencies = [ "blake2b_simd", "borsh", @@ -2754,7 +2756,7 @@ dependencies = [ [[package]] name = "kaspa-index-core" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "async-trait", @@ -2773,7 +2775,7 @@ dependencies = [ [[package]] name = "kaspa-index-processor" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "async-trait", @@ -2801,7 +2803,7 @@ dependencies = [ [[package]] name = "kaspa-math" -version = "0.15.4" +version = "0.16.0" dependencies = [ "borsh", "criterion", @@ -2822,14 +2824,14 @@ dependencies = [ [[package]] name = "kaspa-merkle" -version = "0.15.4" +version = "0.16.0" dependencies = [ "kaspa-hashes", ] [[package]] name = "kaspa-metrics-core" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-trait", "borsh", @@ -2845,7 +2847,7 @@ dependencies = [ [[package]] name = "kaspa-mining" -version = "0.15.4" +version = "0.16.0" dependencies = [ "criterion", "futures-util", @@ -2872,7 +2874,7 @@ dependencies = [ [[package]] name = "kaspa-mining-errors" -version = "0.15.4" +version = "0.16.0" dependencies = [ "kaspa-consensus-core", "thiserror", @@ -2880,7 +2882,7 @@ dependencies = [ [[package]] name = "kaspa-muhash" -version = "0.15.4" +version = "0.16.0" dependencies = [ "criterion", "kaspa-hashes", @@ -2893,7 +2895,7 @@ dependencies = [ [[package]] name = "kaspa-notify" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "async-trait", @@ -2929,7 +2931,7 @@ dependencies = [ [[package]] name = "kaspa-p2p-flows" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-trait", "chrono", @@ -2960,7 +2962,7 @@ dependencies = [ [[package]] name = "kaspa-p2p-lib" -version = "0.15.4" +version = "0.16.0" dependencies = [ "borsh", "ctrlc", @@ -2991,7 +2993,7 @@ dependencies = [ [[package]] name = "kaspa-perf-monitor" -version = "0.15.4" +version = "0.16.0" dependencies = [ "kaspa-core", "log", @@ -3003,7 +3005,7 @@ dependencies = [ [[package]] name = "kaspa-pow" -version = "0.15.4" +version = "0.16.0" dependencies = [ "criterion", "js-sys", @@ -3019,7 +3021,7 @@ dependencies = [ [[package]] name = "kaspa-rpc-core" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "async-trait", @@ -3061,7 +3063,7 @@ dependencies = [ [[package]] name = "kaspa-rpc-macros" -version = "0.15.4" +version = "0.16.0" dependencies = [ "convert_case 0.6.0", "proc-macro-error", @@ -3073,7 +3075,7 @@ dependencies = [ [[package]] name = "kaspa-rpc-service" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-trait", "kaspa-addresses", @@ -3102,7 +3104,7 @@ dependencies = [ [[package]] name = "kaspa-testing-integration" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "async-trait", @@ -3162,7 +3164,7 @@ dependencies = [ [[package]] name = "kaspa-txscript" -version = "0.15.4" +version = "0.16.0" dependencies = [ "blake2b_simd", "borsh", @@ -3194,7 +3196,7 @@ dependencies = [ [[package]] name = "kaspa-txscript-errors" -version = "0.15.4" +version = "0.16.0" dependencies = [ "secp256k1", "thiserror", @@ -3202,7 +3204,7 @@ dependencies = [ [[package]] name = "kaspa-utils" -version = "0.15.4" +version = "0.16.0" dependencies = [ "arc-swap", "async-channel 2.3.1", @@ -3238,7 +3240,7 @@ dependencies = [ [[package]] name = "kaspa-utils-tower" -version = "0.15.4" +version = "0.16.0" dependencies = [ "bytes", "cfg-if 1.0.0", @@ -3254,7 +3256,7 @@ dependencies = [ [[package]] name = "kaspa-utxoindex" -version = "0.15.4" +version = "0.16.0" dependencies = [ "futures", "kaspa-consensus", @@ -3275,7 +3277,7 @@ dependencies = [ [[package]] name = "kaspa-wallet" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-std", "async-trait", @@ -3287,7 +3289,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-cli-wasm" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-trait", "js-sys", @@ -3301,7 +3303,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-core" -version = "0.15.4" +version = "0.16.0" dependencies = [ "aes", "ahash", @@ -3382,7 +3384,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-keys" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-trait", "borsh", @@ -3415,7 +3417,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-macros" -version = "0.15.4" +version = "0.16.0" dependencies = [ "convert_case 0.5.0", "proc-macro-error", @@ -3428,7 +3430,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-pskt" -version = "0.15.4" +version = "0.16.0" dependencies = [ "bincode", "derive_builder", @@ -3455,7 +3457,7 @@ dependencies = [ [[package]] name = "kaspa-wasm" -version = "0.15.4" +version = "0.16.0" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -3483,7 +3485,7 @@ dependencies = [ [[package]] name = "kaspa-wasm-core" -version = "0.15.4" +version = "0.16.0" dependencies = [ "faster-hex", "hexplay", @@ -3494,7 +3496,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-client" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-std", "async-trait", @@ -3530,7 +3532,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-example-subscriber" -version = "0.15.4" +version = "0.16.0" dependencies = [ "ctrlc", "futures", @@ -3545,7 +3547,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-proxy" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-trait", "clap 4.5.19", @@ -3564,7 +3566,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-server" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-trait", "borsh", @@ -3592,7 +3594,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-simple-client-example" -version = "0.15.4" +version = "0.16.0" dependencies = [ "futures", "kaspa-rpc-core", @@ -3602,7 +3604,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-wasm" -version = "0.15.4" +version = "0.16.0" dependencies = [ "ahash", "async-std", @@ -3632,7 +3634,7 @@ dependencies = [ [[package]] name = "kaspad" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "cfg-if 1.0.0", @@ -4974,7 +4976,7 @@ dependencies = [ [[package]] name = "rothschild" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "clap 4.5.19", @@ -5387,7 +5389,7 @@ dependencies = [ [[package]] name = "simpa" -version = "0.15.4" +version = "0.16.0" dependencies = [ "async-channel 2.3.1", "cfg-if 1.0.0", diff --git a/Cargo.toml b/Cargo.toml index 4ff4b234ee..e9d123f6b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ members = [ [workspace.package] rust-version = "1.82.0" -version = "0.15.4" +version = "0.16.0" authors = ["Kaspa developers"] license = "ISC" repository = "https://github.com/kaspanet/rusty-kaspa" @@ -80,61 +80,61 @@ include = [ ] [workspace.dependencies] -# kaspa-testing-integration = { version = "0.15.4", path = "testing/integration" } -kaspa-addresses = { version = "0.15.4", path = "crypto/addresses" } -kaspa-addressmanager = { version = "0.15.4", path = "components/addressmanager" } -kaspa-bip32 = { version = "0.15.4", path = "wallet/bip32" } -kaspa-cli = { version = "0.15.4", path = "cli" } -kaspa-connectionmanager = { version = "0.15.4", path = "components/connectionmanager" } -kaspa-consensus = { version = "0.15.4", path = "consensus" } -kaspa-consensus-core = { version = "0.15.4", path = "consensus/core" } -kaspa-consensus-client = { version = "0.15.4", path = "consensus/client" } -kaspa-consensus-notify = { version = "0.15.4", path = "consensus/notify" } -kaspa-consensus-wasm = { version = "0.15.4", path = "consensus/wasm" } -kaspa-consensusmanager = { version = "0.15.4", path = "components/consensusmanager" } -kaspa-core = { version = "0.15.4", path = "core" } -kaspa-daemon = { version = "0.15.4", path = "daemon" } -kaspa-database = { version = "0.15.4", path = "database" } -kaspa-grpc-client = { version = "0.15.4", path = "rpc/grpc/client" } -kaspa-grpc-core = { version = "0.15.4", path = "rpc/grpc/core" } -kaspa-grpc-server = { version = "0.15.4", path = "rpc/grpc/server" } -kaspa-hashes = { version = "0.15.4", path = "crypto/hashes" } -kaspa-index-core = { version = "0.15.4", path = "indexes/core" } -kaspa-index-processor = { version = "0.15.4", path = "indexes/processor" } -kaspa-math = { version = "0.15.4", path = "math" } -kaspa-merkle = { version = "0.15.4", path = "crypto/merkle" } -kaspa-metrics-core = { version = "0.15.4", path = "metrics/core" } -kaspa-mining = { version = "0.15.4", path = "mining" } -kaspa-mining-errors = { version = "0.15.4", path = "mining/errors" } -kaspa-muhash = { version = "0.15.4", path = "crypto/muhash" } -kaspa-notify = { version = "0.15.4", path = "notify" } -kaspa-p2p-flows = { version = "0.15.4", path = "protocol/flows" } -kaspa-p2p-lib = { version = "0.15.4", path = "protocol/p2p" } -kaspa-perf-monitor = { version = "0.15.4", path = "metrics/perf_monitor" } -kaspa-pow = { version = "0.15.4", path = "consensus/pow" } -kaspa-rpc-core = { version = "0.15.4", path = "rpc/core" } -kaspa-rpc-macros = { version = "0.15.4", path = "rpc/macros" } -kaspa-rpc-service = { version = "0.15.4", path = "rpc/service" } -kaspa-txscript = { version = "0.15.4", path = "crypto/txscript" } -kaspa-txscript-errors = { version = "0.15.4", path = "crypto/txscript/errors" } -kaspa-utils = { version = "0.15.4", path = "utils" } -kaspa-utils-tower = { version = "0.15.4", path = "utils/tower" } -kaspa-utxoindex = { version = "0.15.4", path = "indexes/utxoindex" } -kaspa-wallet = { version = "0.15.4", path = "wallet/native" } -kaspa-wallet-cli-wasm = { version = "0.15.4", path = "wallet/wasm" } -kaspa-wallet-keys = { version = "0.15.4", path = "wallet/keys" } -kaspa-wallet-pskt = { version = "0.15.4", path = "wallet/pskt" } -kaspa-wallet-core = { version = "0.15.4", path = "wallet/core" } -kaspa-wallet-macros = { version = "0.15.4", path = "wallet/macros" } -kaspa-wasm = { version = "0.15.4", path = "wasm" } -kaspa-wasm-core = { version = "0.15.4", path = "wasm/core" } -kaspa-wrpc-client = { version = "0.15.4", path = "rpc/wrpc/client" } -kaspa-wrpc-proxy = { version = "0.15.4", path = "rpc/wrpc/proxy" } -kaspa-wrpc-server = { version = "0.15.4", path = "rpc/wrpc/server" } -kaspa-wrpc-wasm = { version = "0.15.4", path = "rpc/wrpc/wasm" } -kaspa-wrpc-example-subscriber = { version = "0.15.4", path = "rpc/wrpc/examples/subscriber" } -kaspad = { version = "0.15.4", path = "kaspad" } -kaspa-alloc = { version = "0.15.4", path = "utils/alloc" } +# kaspa-testing-integration = { version = "0.16.0", path = "testing/integration" } +kaspa-addresses = { version = "0.16.0", path = "crypto/addresses" } +kaspa-addressmanager = { version = "0.16.0", path = "components/addressmanager" } +kaspa-bip32 = { version = "0.16.0", path = "wallet/bip32" } +kaspa-cli = { version = "0.16.0", path = "cli" } +kaspa-connectionmanager = { version = "0.16.0", path = "components/connectionmanager" } +kaspa-consensus = { version = "0.16.0", path = "consensus" } +kaspa-consensus-core = { version = "0.16.0", path = "consensus/core" } +kaspa-consensus-client = { version = "0.16.0", path = "consensus/client" } +kaspa-consensus-notify = { version = "0.16.0", path = "consensus/notify" } +kaspa-consensus-wasm = { version = "0.16.0", path = "consensus/wasm" } +kaspa-consensusmanager = { version = "0.16.0", path = "components/consensusmanager" } +kaspa-core = { version = "0.16.0", path = "core" } +kaspa-daemon = { version = "0.16.0", path = "daemon" } +kaspa-database = { version = "0.16.0", path = "database" } +kaspa-grpc-client = { version = "0.16.0", path = "rpc/grpc/client" } +kaspa-grpc-core = { version = "0.16.0", path = "rpc/grpc/core" } +kaspa-grpc-server = { version = "0.16.0", path = "rpc/grpc/server" } +kaspa-hashes = { version = "0.16.0", path = "crypto/hashes" } +kaspa-index-core = { version = "0.16.0", path = "indexes/core" } +kaspa-index-processor = { version = "0.16.0", path = "indexes/processor" } +kaspa-math = { version = "0.16.0", path = "math" } +kaspa-merkle = { version = "0.16.0", path = "crypto/merkle" } +kaspa-metrics-core = { version = "0.16.0", path = "metrics/core" } +kaspa-mining = { version = "0.16.0", path = "mining" } +kaspa-mining-errors = { version = "0.16.0", path = "mining/errors" } +kaspa-muhash = { version = "0.16.0", path = "crypto/muhash" } +kaspa-notify = { version = "0.16.0", path = "notify" } +kaspa-p2p-flows = { version = "0.16.0", path = "protocol/flows" } +kaspa-p2p-lib = { version = "0.16.0", path = "protocol/p2p" } +kaspa-perf-monitor = { version = "0.16.0", path = "metrics/perf_monitor" } +kaspa-pow = { version = "0.16.0", path = "consensus/pow" } +kaspa-rpc-core = { version = "0.16.0", path = "rpc/core" } +kaspa-rpc-macros = { version = "0.16.0", path = "rpc/macros" } +kaspa-rpc-service = { version = "0.16.0", path = "rpc/service" } +kaspa-txscript = { version = "0.16.0", path = "crypto/txscript" } +kaspa-txscript-errors = { version = "0.16.0", path = "crypto/txscript/errors" } +kaspa-utils = { version = "0.16.0", path = "utils" } +kaspa-utils-tower = { version = "0.16.0", path = "utils/tower" } +kaspa-utxoindex = { version = "0.16.0", path = "indexes/utxoindex" } +kaspa-wallet = { version = "0.16.0", path = "wallet/native" } +kaspa-wallet-cli-wasm = { version = "0.16.0", path = "wallet/wasm" } +kaspa-wallet-keys = { version = "0.16.0", path = "wallet/keys" } +kaspa-wallet-pskt = { version = "0.16.0", path = "wallet/pskt" } +kaspa-wallet-core = { version = "0.16.0", path = "wallet/core" } +kaspa-wallet-macros = { version = "0.16.0", path = "wallet/macros" } +kaspa-wasm = { version = "0.16.0", path = "wasm" } +kaspa-wasm-core = { version = "0.16.0", path = "wasm/core" } +kaspa-wrpc-client = { version = "0.16.0", path = "rpc/wrpc/client" } +kaspa-wrpc-proxy = { version = "0.16.0", path = "rpc/wrpc/proxy" } +kaspa-wrpc-server = { version = "0.16.0", path = "rpc/wrpc/server" } +kaspa-wrpc-wasm = { version = "0.16.0", path = "rpc/wrpc/wasm" } +kaspa-wrpc-example-subscriber = { version = "0.16.0", path = "rpc/wrpc/examples/subscriber" } +kaspad = { version = "0.16.0", path = "kaspad" } +kaspa-alloc = { version = "0.16.0", path = "utils/alloc" } # external aes = "0.8.3" diff --git a/cli/src/modules/rpc.rs b/cli/src/modules/rpc.rs index cf6bc6bd20..c721ed6223 100644 --- a/cli/src/modules/rpc.rs +++ b/cli/src/modules/rpc.rs @@ -269,6 +269,21 @@ impl Rpc { let result = rpc.get_current_block_color_call(None, GetCurrentBlockColorRequest { hash }).await?; self.println(&ctx, result); } + RpcApiOps::GetUtxoReturnAddress => { + if argv.is_empty() || argv.len() != 2 { + return Err(Error::custom("Please specify a txid and a accepting_block_daa_score")); + } + + let txid = argv.remove(0); + let txid = RpcHash::from_hex(txid.as_str())?; + + let accepting_block_daa_score = argv.remove(0).parse::()?; + + let result = + rpc.get_utxo_return_address_call(None, GetUtxoReturnAddressRequest { txid, accepting_block_daa_score }).await?; + + self.println(&ctx, result); + } _ => { tprintln!(ctx, "rpc method exists but is not supported by the cli: '{op_str}'\r\n"); return Ok(()); diff --git a/components/addressmanager/src/lib.rs b/components/addressmanager/src/lib.rs index 093323e155..da0f7c1ed1 100644 --- a/components/addressmanager/src/lib.rs +++ b/components/addressmanager/src/lib.rs @@ -596,7 +596,7 @@ mod address_store_with_cache { let target_uniform_dist = Uniform::new(1.0, num_of_buckets as f64).unwrap(); let uniform_cdf = |x: f64| target_uniform_dist.cdf(&x); for _ in 0..num_of_trials { - // The weight sampled expected uniform distibution + // The weight sampled expected uniform distribution let prioritized_address_distribution = am .lock() .iterate_prioritized_random_addresses(HashSet::new()) diff --git a/components/consensusmanager/Cargo.toml b/components/consensusmanager/Cargo.toml index 16f6900871..c9f3645572 100644 --- a/components/consensusmanager/Cargo.toml +++ b/components/consensusmanager/Cargo.toml @@ -14,6 +14,7 @@ duration-string.workspace = true futures-util.workspace = true futures.workspace = true itertools.workspace = true +kaspa-addresses.workspace=true kaspa-consensus-core.workspace = true kaspa-consensus-notify.workspace = true kaspa-core.workspace = true diff --git a/components/consensusmanager/src/lib.rs b/components/consensusmanager/src/lib.rs index 6d31653aab..b1c0b8f61b 100644 --- a/components/consensusmanager/src/lib.rs +++ b/components/consensusmanager/src/lib.rs @@ -213,7 +213,7 @@ impl StagingConsensus { // Drop `prev` so that deletion below succeeds drop(prev); // Staging was committed and is now the active consensus so we can delete - // any pervious, now inactive, consensus entries + // any previous, now inactive, consensus entries self.manager.delete_inactive_consensus_entries(); } diff --git a/components/consensusmanager/src/session.rs b/components/consensusmanager/src/session.rs index edd9e5a966..a711f0f05b 100644 --- a/components/consensusmanager/src/session.rs +++ b/components/consensusmanager/src/session.rs @@ -12,7 +12,8 @@ use kaspa_consensus_core::{ header::Header, pruning::{PruningPointProof, PruningPointTrustedData, PruningPointsList}, trusted::{ExternalGhostdagData, TrustedBlock}, - tx::{MutableTransaction, Transaction, TransactionOutpoint, UtxoEntry}, + tx::{MutableTransaction, SignableTransaction, Transaction, TransactionOutpoint, UtxoEntry}, + utxo::utxo_inquirer::UtxoInquirerError, BlockHashSet, BlueWorkType, ChainPath, Hash, }; use kaspa_utils::sync::rwlock::*; @@ -313,6 +314,14 @@ impl ConsensusSessionOwned { self.clone().spawn_blocking(|c| c.get_chain_block_samples()).await } + pub async fn async_get_populated_transaction( + &self, + txid: Hash, + accepting_block_daa_score: u64, + ) -> Result { + self.clone().spawn_blocking(move |c| c.get_populated_transaction(txid, accepting_block_daa_score)).await + } + /// Returns the antipast of block `hash` from the POV of `context`, i.e. `antipast(hash) ∩ past(context)`. /// Since this might be an expensive operation for deep blocks, we allow the caller to specify a limit /// `max_traversal_allowed` on the maximum amount of blocks to traverse for obtaining the answer diff --git a/consensus/Cargo.toml b/consensus/Cargo.toml index 443e591c8a..90aad80e1a 100644 --- a/consensus/Cargo.toml +++ b/consensus/Cargo.toml @@ -18,6 +18,7 @@ faster-hex.workspace = true futures-util.workspace = true indexmap.workspace = true itertools.workspace = true +kaspa-addresses.workspace = true kaspa-consensus-core.workspace = true kaspa-consensus-notify.workspace = true kaspa-consensusmanager.workspace = true diff --git a/consensus/core/src/api/mod.rs b/consensus/core/src/api/mod.rs index da550afd11..b96edb117a 100644 --- a/consensus/core/src/api/mod.rs +++ b/consensus/core/src/api/mod.rs @@ -15,7 +15,8 @@ use crate::{ header::Header, pruning::{PruningPointProof, PruningPointTrustedData, PruningPointsList, PruningProofMetadata}, trusted::{ExternalGhostdagData, TrustedBlock}, - tx::{MutableTransaction, Transaction, TransactionOutpoint, UtxoEntry}, + tx::{MutableTransaction, SignableTransaction, Transaction, TransactionOutpoint, UtxoEntry}, + utxo::utxo_inquirer::UtxoInquirerError, BlockHashSet, BlueWorkType, ChainPath, }; use consensus_core::header::CompactHeaderData; @@ -170,6 +171,12 @@ pub trait ConsensusApi: Send + Sync { unimplemented!() } + /// Returns the fully populated transaction with the given txid which was accepted at the provided accepting_block_daa_score. + /// The argument `accepting_block_daa_score` is expected to be the DAA score of the accepting chain block of `txid`. + fn get_populated_transaction(&self, txid: Hash, accepting_block_daa_score: u64) -> Result { + unimplemented!() + } + fn get_virtual_parents(&self) -> BlockHashSet { unimplemented!() } diff --git a/consensus/core/src/utxo/mod.rs b/consensus/core/src/utxo/mod.rs index 42014856f7..697506cb81 100644 --- a/consensus/core/src/utxo/mod.rs +++ b/consensus/core/src/utxo/mod.rs @@ -1,4 +1,5 @@ pub mod utxo_collection; pub mod utxo_diff; pub mod utxo_error; +pub mod utxo_inquirer; pub mod utxo_view; diff --git a/consensus/core/src/utxo/utxo_inquirer.rs b/consensus/core/src/utxo/utxo_inquirer.rs new file mode 100644 index 0000000000..3aa1000295 --- /dev/null +++ b/consensus/core/src/utxo/utxo_inquirer.rs @@ -0,0 +1,38 @@ +use kaspa_hashes::Hash; +use thiserror::Error; + +#[derive(Error, Debug, Clone)] +pub enum UtxoInquirerError { + #[error("Transaction is already pruned")] + AlreadyPruned, + #[error("Transaction return address is coinbase")] + TxFromCoinbase, + #[error("Transaction not found at given accepting daa score")] + NoTxAtScore, + #[error("Transaction was found but not standard")] + NonStandard, + #[error("Did not find compact header for block hash {0} ")] + MissingCompactHeaderForBlockHash(Hash), + #[error("Did not find containing_acceptance for tx {0} ")] + MissingContainingAcceptanceForTx(Hash), + #[error("Did not find block {0} at block tx store")] + MissingBlockFromBlockTxStore(Hash), + #[error("Did not find index {0} in transactions of block {1}")] + MissingTransactionIndexOfBlock(usize, Hash), + #[error("Expected {0} to match {1} when checking block_transaction_store using array index of transaction")] + UnexpectedTransactionMismatch(Hash, Hash), + #[error("Did not find a utxo diff for chain block {0} ")] + MissingUtxoDiffForChainBlock(Hash), + #[error("Transaction {0} acceptance data must also be in the same block in this case")] + MissingOtherTransactionAcceptanceData(Hash), + #[error("Did not find index for hash {0}")] + MissingIndexForHash(Hash), + #[error("Did not find tip data")] + MissingTipData, + #[error("Did not find a hash at index {0} ")] + MissingHashAtIndex(u64), + #[error("Did not find acceptance data for chain block {0}")] + MissingAcceptanceDataForChainBlock(Hash), + #[error("Utxo entry is not filled")] + UnfilledUtxoEntry, +} diff --git a/consensus/src/consensus/mod.rs b/consensus/src/consensus/mod.rs index f2c0d8ff81..19d9bab70a 100644 --- a/consensus/src/consensus/mod.rs +++ b/consensus/src/consensus/mod.rs @@ -66,7 +66,8 @@ use kaspa_consensus_core::{ network::NetworkType, pruning::{PruningPointProof, PruningPointTrustedData, PruningPointsList, PruningProofMetadata}, trusted::{ExternalGhostdagData, TrustedBlock}, - tx::{MutableTransaction, Transaction, TransactionOutpoint, UtxoEntry}, + tx::{MutableTransaction, SignableTransaction, Transaction, TransactionOutpoint, UtxoEntry}, + utxo::utxo_inquirer::UtxoInquirerError, BlockHashSet, BlueWorkType, ChainPath, HashMapCustomHasher, }; use kaspa_consensus_notify::root::ConsensusNotificationRoot; @@ -687,6 +688,12 @@ impl ConsensusApi for Consensus { sample_headers } + fn get_populated_transaction(&self, txid: Hash, accepting_block_daa_score: u64) -> Result { + // We need consistency between the pruning_point_store, utxo_diffs_store, block_transactions_store, selected chain and headers store reads + let _guard = self.pruning_lock.blocking_read(); + self.virtual_processor.get_populated_transaction(txid, accepting_block_daa_score, self.get_source()) + } + fn get_virtual_parents(&self) -> BlockHashSet { self.lkg_virtual_state.load().parents.iter().copied().collect() } diff --git a/consensus/src/pipeline/virtual_processor/mod.rs b/consensus/src/pipeline/virtual_processor/mod.rs index a35dec6856..fd3756f3ae 100644 --- a/consensus/src/pipeline/virtual_processor/mod.rs +++ b/consensus/src/pipeline/virtual_processor/mod.rs @@ -1,5 +1,6 @@ pub mod errors; mod processor; +mod utxo_inquirer; mod utxo_validation; pub use processor::*; pub mod test_block_builder; diff --git a/consensus/src/pipeline/virtual_processor/processor.rs b/consensus/src/pipeline/virtual_processor/processor.rs index 4feb817557..af9940b289 100644 --- a/consensus/src/pipeline/virtual_processor/processor.rs +++ b/consensus/src/pipeline/virtual_processor/processor.rs @@ -157,7 +157,7 @@ pub struct VirtualStateProcessor { pub(super) block_window_cache_for_past_median_time: Arc, // Pruning lock - pruning_lock: SessionLock, + pub(super) pruning_lock: SessionLock, // Notifier notification_root: Arc, diff --git a/consensus/src/pipeline/virtual_processor/utxo_inquirer.rs b/consensus/src/pipeline/virtual_processor/utxo_inquirer.rs new file mode 100644 index 0000000000..617c1026bd --- /dev/null +++ b/consensus/src/pipeline/virtual_processor/utxo_inquirer.rs @@ -0,0 +1,189 @@ +use std::{cmp, sync::Arc}; + +use kaspa_consensus_core::{ + acceptance_data::AcceptanceData, + tx::{SignableTransaction, Transaction, UtxoEntry}, + utxo::{utxo_diff::ImmutableUtxoDiff, utxo_inquirer::UtxoInquirerError}, +}; +use kaspa_core::{trace, warn}; +use kaspa_hashes::Hash; + +use crate::model::stores::{ + acceptance_data::AcceptanceDataStoreReader, block_transactions::BlockTransactionsStoreReader, headers::HeaderStoreReader, + selected_chain::SelectedChainStoreReader, utxo_diffs::UtxoDiffsStoreReader, +}; + +use super::VirtualStateProcessor; + +impl VirtualStateProcessor { + /// Returns the fully populated transaction with the given txid which was accepted at the provided accepting_block_daa_score. + /// The argument `accepting_block_daa_score` is expected to be the DAA score of the accepting chain block of `txid`. + /// + /// *Assumed to be called under the pruning read lock.* + pub fn get_populated_transaction( + &self, + txid: Hash, + accepting_block_daa_score: u64, + source_hash: Hash, + ) -> Result { + let source_daa_score = self + .headers_store + .get_compact_header_data(source_hash) + .map(|compact_header| compact_header.daa_score) + .map_err(|_| UtxoInquirerError::MissingCompactHeaderForBlockHash(source_hash))?; + + if accepting_block_daa_score < source_daa_score { + // Early exit if target daa score is lower than that of pruning point's daa score: + return Err(UtxoInquirerError::AlreadyPruned); + } + + let (matching_chain_block_hash, acceptance_data) = + self.find_accepting_chain_block_hash_at_daa_score(accepting_block_daa_score, source_hash)?; + + // Expected to never fail, since we found the acceptance data and therefore there must be matching diff + let utxo_diff = self + .utxo_diffs_store + .get(matching_chain_block_hash) + .map_err(|_| UtxoInquirerError::MissingUtxoDiffForChainBlock(matching_chain_block_hash))?; + + let tx = self.find_tx_from_acceptance_data(txid, &acceptance_data)?; + + let mut populated_tx = SignableTransaction::new(tx); + + let removed_diffs = utxo_diff.removed(); + + populated_tx.tx.inputs.iter().enumerate().for_each(|(index, input)| { + let filled_utxo = if let Some(utxo_entry) = removed_diffs.get(&input.previous_outpoint) { + Some(utxo_entry.clone().to_owned()) + } else { + // This handles this rare scenario: + // - UTXO0 is spent by TX1 and creates UTXO1 + // - UTXO1 is spent by TX2 and creates UTXO2 + // - A chain block happens to accept both of these + // In this case, removed_diff wouldn't contain the outpoint of the created-and-immediately-spent UTXO + // so we use the transaction (which also has acceptance data in this block) and look at its outputs + let other_txid = input.previous_outpoint.transaction_id; + let other_tx = self.find_tx_from_acceptance_data(other_txid, &acceptance_data).unwrap(); + let output = &other_tx.outputs[input.previous_outpoint.index as usize]; + let utxo_entry = + UtxoEntry::new(output.value, output.script_public_key.clone(), accepting_block_daa_score, other_tx.is_coinbase()); + Some(utxo_entry) + }; + + populated_tx.entries[index] = filled_utxo; + }); + + Ok(populated_tx) + } + + /// Find the accepting chain block hash at the given DAA score by binary searching + /// through selected chain store using indexes. + /// This method assumes that local caller have acquired the pruning read lock to guarantee + /// consistency between reads on the selected_chain_store and headers_store (as well as + /// other stores outside). If no such lock is acquired, this method tries to find + /// the accepting chain block hash on a best effort basis (may fail if parts of the data + /// are pruned between two sequential calls) + fn find_accepting_chain_block_hash_at_daa_score( + &self, + target_daa_score: u64, + source_hash: Hash, + ) -> Result<(Hash, Arc), UtxoInquirerError> { + let sc_read = self.selected_chain_store.read(); + + let source_index = sc_read.get_by_hash(source_hash).map_err(|_| UtxoInquirerError::MissingIndexForHash(source_hash))?; + let (tip_index, tip_hash) = sc_read.get_tip().map_err(|_| UtxoInquirerError::MissingTipData)?; + let tip_daa_score = self + .headers_store + .get_compact_header_data(tip_hash) + .map(|tip| tip.daa_score) + .map_err(|_| UtxoInquirerError::MissingCompactHeaderForBlockHash(tip_hash))?; + + // For a chain segment it holds that len(segment) <= daa_score(segment end) - daa_score(segment start). This is true + // because each chain block increases the daa score by at least one. Hence we can lower bound our search by high index + // minus the daa score gap as done below + let mut low_index = tip_index.saturating_sub(tip_daa_score.saturating_sub(target_daa_score)).max(source_index); + let mut high_index = tip_index; + + let matching_chain_block_hash = loop { + // Binary search for the chain block that matches the target_daa_score + // 0. Get the mid point index + let mid = low_index + (high_index - low_index) / 2; + + // 1. Get the chain block hash at that index. Error if we cannot find a hash at that index + let hash = sc_read.get_by_index(mid).map_err(|_| { + trace!("Did not find a hash at index {}", mid); + UtxoInquirerError::MissingHashAtIndex(mid) + })?; + + // 2. Get the compact header so we have access to the daa_score. Error if we cannot find the header + let compact_header = self.headers_store.get_compact_header_data(hash).map_err(|_| { + trace!("Did not find a compact header with hash {}", hash); + UtxoInquirerError::MissingCompactHeaderForBlockHash(hash) + })?; + + // 3. Compare block daa score to our target + match compact_header.daa_score.cmp(&target_daa_score) { + cmp::Ordering::Equal => { + // We found the chain block we need + break hash; + } + cmp::Ordering::Greater => { + high_index = mid - 1; + } + cmp::Ordering::Less => { + low_index = mid + 1; + } + } + + if low_index > high_index { + return Err(UtxoInquirerError::NoTxAtScore); + } + }; + + let acceptance_data = self + .acceptance_data_store + .get(matching_chain_block_hash) + .map_err(|_| UtxoInquirerError::MissingAcceptanceDataForChainBlock(matching_chain_block_hash))?; + + Ok((matching_chain_block_hash, acceptance_data)) + } + + /// Finds a transaction's containing block hash and index within block through + /// the accepting block acceptance data + fn find_containing_block_and_index_from_acceptance_data( + &self, + txid: Hash, + acceptance_data: &AcceptanceData, + ) -> Option<(Hash, usize)> { + acceptance_data.iter().find_map(|mbad| { + let tx_arr_index = + mbad.accepted_transactions.iter().find_map(|tx| (tx.transaction_id == txid).then_some(tx.index_within_block as usize)); + tx_arr_index.map(|index| (mbad.block_hash, index)) + }) + } + + /// Finds a transaction through the accepting block acceptance data (and using indexed info therein for + /// finding the tx in the block transactions store) + fn find_tx_from_acceptance_data(&self, txid: Hash, acceptance_data: &AcceptanceData) -> Result { + let (containing_block, index) = self + .find_containing_block_and_index_from_acceptance_data(txid, acceptance_data) + .ok_or(UtxoInquirerError::MissingContainingAcceptanceForTx(txid))?; + + let tx = self + .block_transactions_store + .get(containing_block) + .map_err(|_| UtxoInquirerError::MissingBlockFromBlockTxStore(containing_block)) + .and_then(|block_txs| { + block_txs.get(index).cloned().ok_or(UtxoInquirerError::MissingTransactionIndexOfBlock(index, containing_block)) + })?; + + if tx.id() != txid { + // Should never happen, but do a sanity check. This would mean something went wrong with storing block transactions. + // Sanity check is necessary to guarantee that this function will never give back a wrong address (err on the side of not found) + warn!("Expected {} to match {} when checking block_transaction_store using array index of transaction", tx.id(), txid); + return Err(UtxoInquirerError::UnexpectedTransactionMismatch(tx.id(), txid)); + } + + Ok(tx) + } +} diff --git a/musl-toolchain/build.sh b/musl-toolchain/build.sh index b32314082b..1e114a9c5b 100755 --- a/musl-toolchain/build.sh +++ b/musl-toolchain/build.sh @@ -17,7 +17,7 @@ source preset.sh if [ ! -d "$HOME/x-tools" ] || [ ! -f "$PRESET_HASH_FILE" ] || [ "$(cat $PRESET_HASH_FILE)" != "$CURRENT_PRESET_HASH" ]; then # Install dependencies sudo apt-get update - sudo apt-get install -y autoconf automake libtool libtool-bin unzip help2man python3.10-dev gperf bison flex texinfo gawk libncurses5-dev + sudo apt-get install -y autoconf automake libtool libtool-bin unzip help2man python3-dev gperf bison flex texinfo gawk libncurses5-dev # Clone crosstool-ng git clone https://github.com/crosstool-ng/crosstool-ng diff --git a/rpc/core/src/api/ops.rs b/rpc/core/src/api/ops.rs index 26ca356eb0..4541ddc56d 100644 --- a/rpc/core/src/api/ops.rs +++ b/rpc/core/src/api/ops.rs @@ -136,6 +136,8 @@ pub enum RpcApiOps { GetFeeEstimateExperimental = 148, /// Block color determination by iterating DAG. GetCurrentBlockColor = 149, + /// Get UTXO Return Addresses + GetUtxoReturnAddress = 150, } impl RpcApiOps { diff --git a/rpc/core/src/api/rpc.rs b/rpc/core/src/api/rpc.rs index cadc9e00cd..d9011c167e 100644 --- a/rpc/core/src/api/rpc.rs +++ b/rpc/core/src/api/rpc.rs @@ -438,6 +438,18 @@ pub trait RpcApi: Sync + Send + AnySync { request: GetDaaScoreTimestampEstimateRequest, ) -> RpcResult; + async fn get_utxo_return_address(&self, txid: RpcHash, accepting_block_daa_score: u64) -> RpcResult { + Ok(self + .get_utxo_return_address_call(None, GetUtxoReturnAddressRequest { txid, accepting_block_daa_score }) + .await? + .return_address) + } + async fn get_utxo_return_address_call( + &self, + _connection: Option<&DynRpcConnection>, + request: GetUtxoReturnAddressRequest, + ) -> RpcResult; + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Fee estimation API diff --git a/rpc/core/src/error.rs b/rpc/core/src/error.rs index 0e2bfee225..54763c71ba 100644 --- a/rpc/core/src/error.rs +++ b/rpc/core/src/error.rs @@ -2,7 +2,7 @@ //! [`RpcError`] enum used by RPC primitives. //! -use kaspa_consensus_core::{subnets::SubnetworkConversionError, tx::TransactionId}; +use kaspa_consensus_core::{subnets::SubnetworkConversionError, tx::TransactionId, utxo::utxo_inquirer::UtxoInquirerError}; use kaspa_utils::networking::IpAddress; use std::{net::AddrParseError, num::TryFromIntError}; use thiserror::Error; @@ -134,6 +134,9 @@ pub enum RpcError { #[error(transparent)] ConsensusClient(#[from] kaspa_consensus_client::error::Error), + + #[error("utxo return address could not be found -> {0}")] + UtxoReturnAddressNotFound(UtxoInquirerError), } impl From for RpcError { diff --git a/rpc/core/src/model/message.rs b/rpc/core/src/model/message.rs index 904a50c96a..ebe70a91bf 100644 --- a/rpc/core/src/model/message.rs +++ b/rpc/core/src/model/message.rs @@ -2670,6 +2670,69 @@ impl Deserializer for GetCurrentBlockColorResponse { } } +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetUtxoReturnAddressRequest { + pub txid: RpcHash, + pub accepting_block_daa_score: u64, +} + +impl GetUtxoReturnAddressRequest { + pub fn new(txid: RpcHash, accepting_block_daa_score: u64) -> Self { + Self { txid, accepting_block_daa_score } + } +} + +impl Serializer for GetUtxoReturnAddressRequest { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(u16, &1, writer)?; + store!(RpcHash, &self.txid, writer)?; + store!(u64, &self.accepting_block_daa_score, writer)?; + + Ok(()) + } +} + +impl Deserializer for GetUtxoReturnAddressRequest { + fn deserialize(reader: &mut R) -> std::io::Result { + let _version = load!(u16, reader)?; + let txid = load!(RpcHash, reader)?; + let accepting_block_daa_score = load!(u64, reader)?; + + Ok(Self { txid, accepting_block_daa_score }) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetUtxoReturnAddressResponse { + pub return_address: RpcAddress, +} + +impl GetUtxoReturnAddressResponse { + pub fn new(return_address: RpcAddress) -> Self { + Self { return_address } + } +} + +impl Serializer for GetUtxoReturnAddressResponse { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(u16, &1, writer)?; + store!(RpcAddress, &self.return_address, writer)?; + + Ok(()) + } +} + +impl Deserializer for GetUtxoReturnAddressResponse { + fn deserialize(reader: &mut R) -> std::io::Result { + let _version = load!(u16, reader)?; + let return_address = load!(RpcAddress, reader)?; + + Ok(Self { return_address }) + } +} + // ---------------------------------------------------------------------------- // Subscriptions & notifications // ---------------------------------------------------------------------------- diff --git a/rpc/grpc/client/src/lib.rs b/rpc/grpc/client/src/lib.rs index b7e53bb5e1..04e37b5fe6 100644 --- a/rpc/grpc/client/src/lib.rs +++ b/rpc/grpc/client/src/lib.rs @@ -276,6 +276,7 @@ impl RpcApi for GrpcClient { route!(get_fee_estimate_call, GetFeeEstimate); route!(get_fee_estimate_experimental_call, GetFeeEstimateExperimental); route!(get_current_block_color_call, GetCurrentBlockColor); + route!(get_utxo_return_address_call, GetUtxoReturnAddress); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Notification API diff --git a/rpc/grpc/core/Cargo.toml b/rpc/grpc/core/Cargo.toml index 2edc10b600..61734df9e3 100644 --- a/rpc/grpc/core/Cargo.toml +++ b/rpc/grpc/core/Cargo.toml @@ -10,6 +10,7 @@ license.workspace = true repository.workspace = true [dependencies] +kaspa-addresses.workspace = true kaspa-consensus-core.workspace = true kaspa-core.workspace = true kaspa-notify.workspace = true diff --git a/rpc/grpc/core/proto/messages.proto b/rpc/grpc/core/proto/messages.proto index 2d6310d9e3..ccb2798b67 100644 --- a/rpc/grpc/core/proto/messages.proto +++ b/rpc/grpc/core/proto/messages.proto @@ -65,6 +65,7 @@ message KaspadRequest { GetFeeEstimateRequestMessage getFeeEstimateRequest = 1106; GetFeeEstimateExperimentalRequestMessage getFeeEstimateExperimentalRequest = 1108; GetCurrentBlockColorRequestMessage getCurrentBlockColorRequest = 1110; + GetUtxoReturnAddressRequestMessage GetUtxoReturnAddressRequest = 1112; } } @@ -130,6 +131,7 @@ message KaspadResponse { GetFeeEstimateResponseMessage getFeeEstimateResponse = 1107; GetFeeEstimateExperimentalResponseMessage getFeeEstimateExperimentalResponse = 1109; GetCurrentBlockColorResponseMessage getCurrentBlockColorResponse = 1111; + GetUtxoReturnAddressResponseMessage GetUtxoReturnAddressResponse = 1113; } } diff --git a/rpc/grpc/core/proto/rpc.proto b/rpc/grpc/core/proto/rpc.proto index 5716f9e8c6..97a85eb62d 100644 --- a/rpc/grpc/core/proto/rpc.proto +++ b/rpc/grpc/core/proto/rpc.proto @@ -910,7 +910,7 @@ message GetDaaScoreTimestampEstimateRequestMessage { repeated uint64 daaScores = 1; } -message GetDaaScoreTimestampEstimateResponseMessage{ +message GetDaaScoreTimestampEstimateResponseMessage { repeated uint64 timestamps = 1; RPCError error = 1000; } @@ -980,3 +980,13 @@ message GetCurrentBlockColorResponseMessage { RPCError error = 1000; } + +message GetUtxoReturnAddressRequestMessage { + string txid = 1; + uint64 accepting_block_daa_score = 2; +} + +message GetUtxoReturnAddressResponseMessage { + string return_address = 1; + RPCError error = 1000; +} diff --git a/rpc/grpc/core/src/convert/kaspad.rs b/rpc/grpc/core/src/convert/kaspad.rs index c3411545cc..7243fd401a 100644 --- a/rpc/grpc/core/src/convert/kaspad.rs +++ b/rpc/grpc/core/src/convert/kaspad.rs @@ -63,6 +63,7 @@ pub mod kaspad_request_convert { impl_into_kaspad_request!(GetFeeEstimate); impl_into_kaspad_request!(GetFeeEstimateExperimental); impl_into_kaspad_request!(GetCurrentBlockColor); + impl_into_kaspad_request!(GetUtxoReturnAddress); impl_into_kaspad_request!(NotifyBlockAdded); impl_into_kaspad_request!(NotifyNewBlockTemplate); @@ -200,6 +201,7 @@ pub mod kaspad_response_convert { impl_into_kaspad_response!(GetFeeEstimate); impl_into_kaspad_response!(GetFeeEstimateExperimental); impl_into_kaspad_response!(GetCurrentBlockColor); + impl_into_kaspad_response!(GetUtxoReturnAddress); impl_into_kaspad_notify_response!(NotifyBlockAdded); impl_into_kaspad_notify_response!(NotifyNewBlockTemplate); diff --git a/rpc/grpc/core/src/convert/message.rs b/rpc/grpc/core/src/convert/message.rs index 770c6a50de..0f09afe27d 100644 --- a/rpc/grpc/core/src/convert/message.rs +++ b/rpc/grpc/core/src/convert/message.rs @@ -19,7 +19,8 @@ //! The SubmitBlockResponse is a notable exception to this general rule. use crate::protowire::{self, submit_block_response_message::RejectReason}; -use kaspa_consensus_core::network::NetworkId; +use kaspa_addresses::Address; +use kaspa_consensus_core::{network::NetworkId, Hash}; use kaspa_core::debug; use kaspa_notify::subscription::Command; use kaspa_rpc_core::{ @@ -430,6 +431,16 @@ from!(item: RpcResult<&kaspa_rpc_core::GetCurrentBlockColorResponse>, protowire: Self { blue: item.blue, error: None } }); +from!(item: &kaspa_rpc_core::GetUtxoReturnAddressRequest, protowire::GetUtxoReturnAddressRequestMessage, { + Self { + txid: item.txid.to_string(), + accepting_block_daa_score: item.accepting_block_daa_score + } +}); +from!(item: RpcResult<&kaspa_rpc_core::GetUtxoReturnAddressResponse>, protowire::GetUtxoReturnAddressResponseMessage, { + Self { return_address: item.return_address.address_to_string(), error: None } +}); + from!(&kaspa_rpc_core::PingRequest, protowire::PingRequestMessage); from!(RpcResult<&kaspa_rpc_core::PingResponse>, protowire::PingResponseMessage); @@ -916,6 +927,15 @@ try_from!(item: &protowire::GetCurrentBlockColorResponseMessage, RpcResult, { + Self { return_address: Address::try_from(item.return_address.clone())? } +}); try_from!(&protowire::PingRequestMessage, kaspa_rpc_core::PingRequest); try_from!(&protowire::PingResponseMessage, RpcResult); diff --git a/rpc/grpc/core/src/ops.rs b/rpc/grpc/core/src/ops.rs index f3bc12c829..223774c74c 100644 --- a/rpc/grpc/core/src/ops.rs +++ b/rpc/grpc/core/src/ops.rs @@ -87,6 +87,7 @@ pub enum KaspadPayloadOps { GetFeeEstimate, GetFeeEstimateExperimental, GetCurrentBlockColor, + GetUtxoReturnAddress, // Subscription commands for starting/stopping notifications NotifyBlockAdded, diff --git a/rpc/grpc/server/src/request_handler/factory.rs b/rpc/grpc/server/src/request_handler/factory.rs index b6a5b4476f..9fec86e476 100644 --- a/rpc/grpc/server/src/request_handler/factory.rs +++ b/rpc/grpc/server/src/request_handler/factory.rs @@ -81,6 +81,7 @@ impl Factory { GetFeeEstimate, GetFeeEstimateExperimental, GetCurrentBlockColor, + GetUtxoReturnAddress, NotifyBlockAdded, NotifyNewBlockTemplate, NotifyFinalityConflict, diff --git a/rpc/grpc/server/src/tests/rpc_core_mock.rs b/rpc/grpc/server/src/tests/rpc_core_mock.rs index dd6de46d2a..ada801850e 100644 --- a/rpc/grpc/server/src/tests/rpc_core_mock.rs +++ b/rpc/grpc/server/src/tests/rpc_core_mock.rs @@ -362,6 +362,14 @@ impl RpcApi for RpcCoreMock { Err(RpcError::NotImplemented) } + async fn get_utxo_return_address_call( + &self, + _connection: Option<&DynRpcConnection>, + _request: GetUtxoReturnAddressRequest, + ) -> RpcResult { + Err(RpcError::NotImplemented) + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Notification API diff --git a/rpc/service/src/service.rs b/rpc/service/src/service.rs index d75ff770b0..c8c40c7707 100644 --- a/rpc/service/src/service.rs +++ b/rpc/service/src/service.rs @@ -7,6 +7,7 @@ use crate::service::NetworkType::{Mainnet, Testnet}; use async_trait::async_trait; use kaspa_consensus_core::api::counters::ProcessingCounters; use kaspa_consensus_core::errors::block::RuleError; +use kaspa_consensus_core::utxo::utxo_inquirer::UtxoInquirerError; use kaspa_consensus_core::{ block::Block, coinbase::MinerData, @@ -794,6 +795,33 @@ NOTE: This error usually indicates an RPC conversion error between the node and } } + async fn get_utxo_return_address_call( + &self, + _connection: Option<&DynRpcConnection>, + request: GetUtxoReturnAddressRequest, + ) -> RpcResult { + let session = self.consensus_manager.consensus().session().await; + + match session.async_get_populated_transaction(request.txid, request.accepting_block_daa_score).await { + Ok(tx) => { + if tx.tx.inputs.is_empty() || tx.entries.is_empty() { + return Err(RpcError::UtxoReturnAddressNotFound(UtxoInquirerError::TxFromCoinbase)); + } + + if let Some(utxo_entry) = &tx.entries[0] { + if let Ok(address) = extract_script_pub_key_address(&utxo_entry.script_public_key, self.config.prefix()) { + Ok(GetUtxoReturnAddressResponse { return_address: address }) + } else { + Err(RpcError::UtxoReturnAddressNotFound(UtxoInquirerError::NonStandard)) + } + } else { + Err(RpcError::UtxoReturnAddressNotFound(UtxoInquirerError::UnfilledUtxoEntry)) + } + } + Err(error) => return Err(RpcError::UtxoReturnAddressNotFound(error)), + } + } + async fn ping_call(&self, _connection: Option<&DynRpcConnection>, _: PingRequest) -> RpcResult { Ok(PingResponse {}) } diff --git a/rpc/wrpc/client/src/client.rs b/rpc/wrpc/client/src/client.rs index 3ac04fa984..f22bcf6255 100644 --- a/rpc/wrpc/client/src/client.rs +++ b/rpc/wrpc/client/src/client.rs @@ -644,6 +644,7 @@ impl RpcApi for KaspaRpcClient { GetSubnetwork, GetSyncStatus, GetSystemInfo, + GetUtxoReturnAddress, GetUtxosByAddresses, GetVirtualChainFromBlock, ResolveFinalityConflict, diff --git a/rpc/wrpc/server/src/router.rs b/rpc/wrpc/server/src/router.rs index 4d0e206259..b4c74a3374 100644 --- a/rpc/wrpc/server/src/router.rs +++ b/rpc/wrpc/server/src/router.rs @@ -47,13 +47,14 @@ impl Router { GetCurrentBlockColor, GetCoinSupply, GetConnectedPeerInfo, + GetDaaScoreTimestampEstimate, + GetUtxoReturnAddress, GetCurrentNetwork, GetDaaScoreTimestampEstimate, GetFeeEstimate, GetFeeEstimateExperimental, GetHeaders, GetInfo, - GetInfo, GetMempoolEntries, GetMempoolEntriesByAddresses, GetMempoolEntry, diff --git a/testing/integration/src/daemon_integration_tests.rs b/testing/integration/src/daemon_integration_tests.rs index 29415531c3..738747ea24 100644 --- a/testing/integration/src/daemon_integration_tests.rs +++ b/testing/integration/src/daemon_integration_tests.rs @@ -274,7 +274,8 @@ async fn daemon_utxos_propagation_test() { clients.iter().for_each(|x| x.utxos_changed_listener().unwrap().drain()); clients.iter().for_each(|x| x.virtual_daa_score_changed_listener().unwrap().drain()); - // Spend some coins + // Spend some coins - sending funds from miner address to user address + // The transaction here is later used to verify utxo return address RPC const NUMBER_INPUTS: u64 = 2; const NUMBER_OUTPUTS: u64 = 2; const TX_AMOUNT: u64 = SIMNET_PARAMS.pre_deflationary_phase_base_subsidy * (NUMBER_INPUTS * 5 - 1) / 5; @@ -324,6 +325,23 @@ async fn daemon_utxos_propagation_test() { assert_eq!(user_balance, TX_AMOUNT); } + // UTXO Return Address Test + // Mine another block to accept the transactions from the previous block + // The tx above is sending from miner address to user address + mine_block(blank_address.clone(), &rpc_client1, &clients).await; + let new_utxos = rpc_client1.get_utxos_by_addresses(vec![user_address]).await.unwrap(); + let new_utxo = new_utxos + .iter() + .find(|utxo| utxo.outpoint.transaction_id == transaction.id()) + .expect("Did not find a utxo for the tx we just created but expected to"); + + let utxo_return_address = rpc_client1 + .get_utxo_return_address(new_utxo.outpoint.transaction_id, new_utxo.utxo_entry.block_daa_score) + .await + .expect("We just created the tx and utxo here"); + + assert_eq!(miner_address, utxo_return_address); + // Terminate multi-listener clients for x in clients.iter() { x.disconnect().await.unwrap(); diff --git a/testing/integration/src/rpc_tests.rs b/testing/integration/src/rpc_tests.rs index 3c4df601b3..3ca619423a 100644 --- a/testing/integration/src/rpc_tests.rs +++ b/testing/integration/src/rpc_tests.rs @@ -656,6 +656,23 @@ async fn sanity_test() { }) } + KaspadPayloadOps::GetUtxoReturnAddress => { + let rpc_client = client.clone(); + tst!(op, { + let results = rpc_client.get_utxo_return_address(RpcHash::from_bytes([0; 32]), 1000).await; + + assert!(results.is_err_and(|err| { + match err { + kaspa_rpc_core::RpcError::General(msg) => { + info!("Expected error message: {}", msg); + true + } + _ => false, + } + })); + }) + } + KaspadPayloadOps::NotifyBlockAdded => { let rpc_client = client.clone(); let id = listener_id; diff --git a/wallet/core/src/tests/rpc_core_mock.rs b/wallet/core/src/tests/rpc_core_mock.rs index 4d10cdd9b1..529fffffe8 100644 --- a/wallet/core/src/tests/rpc_core_mock.rs +++ b/wallet/core/src/tests/rpc_core_mock.rs @@ -379,6 +379,14 @@ impl RpcApi for RpcCoreMock { Err(RpcError::NotImplemented) } + async fn get_utxo_return_address_call( + &self, + _connection: Option<&DynRpcConnection>, + _request: GetUtxoReturnAddressRequest, + ) -> RpcResult { + Err(RpcError::NotImplemented) + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Notification API diff --git a/wasm/examples/nodejs/javascript/transactions/generator.js b/wasm/examples/nodejs/javascript/transactions/generator.js index ca6819f086..fa3f4f3aeb 100644 --- a/wasm/examples/nodejs/javascript/transactions/generator.js +++ b/wasm/examples/nodejs/javascript/transactions/generator.js @@ -67,7 +67,7 @@ const { encoding, networkId, address : destinationAddress } = require("../utils" // to the change address. // // If the requested amount is greater than the Kaspa - // transactoin mass, the Generator will create multiple + // transaction mass, the Generator will create multiple // transactions where each transaction will forward // UTXOs to the change address, until the requested // amount is reached. It will then create a final