From 96ae2d61ca5a392cb476da4c7f6ab1f638839a7f Mon Sep 17 00:00:00 2001 From: Duke Date: Mon, 4 Sep 2023 08:22:12 -0400 Subject: [PATCH 1/7] z_getstats RPC that calculates various stats about ztxs in a block range --- src/wallet/rpcwallet.cpp | 144 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index cdf16c695..b10650137 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3636,6 +3636,149 @@ UniValue z_listsentbyaddress(const UniValue& params, bool fHelp,const CPubKey&) return ret; } +UniValue z_getstats(const UniValue& params, bool fHelp, const CPubKey& mypk) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() < 1 || params.size() > 2) + throw runtime_error( + "z_getstats\n" + "\nReturns statistics about ztxs in block height or block height range\n" + "\nArguments:\n" + "1. \"height\" (number, required) The block height\n" + "1. \"end_height\" (number, optional) The ending block height\n" + "\nResult:\n" + "\njson\n" + "\nExamples:\n" + + HelpExampleCli("z_getstats 123", "456") + + HelpExampleRpc("z_getstats 123", "456") + ); + + LOCK2(cs_main, pwalletMain->cs_wallet); + std::string strHeight = params[0].get_str(); + int nHeight = -1; + try { + nHeight = std::stoi(strHeight); + } catch (const std::exception &e) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block height parameter"); + } + + if (nHeight < 0 || nHeight > chainActive.Height()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range"); + } + auto strHash = chainActive[nHeight]->GetBlockHash().GetHex(); + uint256 hash(uint256S(strHash)); + + if (mapBlockIndex.count(hash) == 0) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + + CBlock block; + CBlockIndex* pblockindex = mapBlockIndex[hash]; + + if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0) + throw JSONRPCError(RPC_INTERNAL_ERROR, "Block not available (pruned data)"); + + if(!ReadBlockFromDisk(block, pblockindex,1)) + throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); + + int total_ztxs, total_zins, total_zouts = 0; + int largest_zins, largest_zouts = 0; + + // given a single block height, we calculate stats for that height + if (params.size() == 1) { + BOOST_FOREACH(const CTransaction&tx, block.vtx) + { + int num_zins, num_zouts = 0; + // ignore coinbase txs which have no zins or zouts + if(!tx.IsCoinBase()) { + num_zouts = tx.vShieldedOutput.size(); + num_zins = tx.vShieldedSpend.size(); + // tx must have some zins and zouts to count towards our stats, + // which ignores shielding coinbase txs, which have only transparent inputs + // This mostly will only count "z2z" txs but also counts (z,t)=>z and z=>(z,t) + // which are possible but unlikely, since RPCs will not create them + if(num_zins > 0 && num_zouts > 0) { + total_ztxs++; + total_zins += num_zins; + total_zouts += num_zouts; + if (num_zins > largest_zins) { + largest_zins = num_zins; + } + if (num_zouts > largest_zouts) { + largest_zouts = num_zouts; + } + } + } + } + } else { + // given two blocks, we calculate delta for that range + std::string strHeight2 = params[1].get_str(); + int nHeight2 = -1; + try { + nHeight2 = std::stoi(strHeight2); + } catch (const std::exception &e) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid ending block height parameter"); + } + + if (nHeight2 <= nHeight) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Ending block height must be larger than starting height"); + } + + if (nHeight2 < 0 || nHeight2 > chainActive.Height()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Ending block height out of range"); + } + + // get the delta for every block in the range + for(int currentHeight = nHeight; currentHeight <= nHeight2; currentHeight++) { + auto strHash = chainActive[currentHeight]->GetBlockHash().GetHex(); + uint256 hash(uint256S(strHash)); + + if (mapBlockIndex.count(hash) == 0) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + + CBlock block; + CBlockIndex* pblockindex = mapBlockIndex[hash]; + + if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0) + throw JSONRPCError(RPC_INTERNAL_ERROR, "Block not available (pruned data)"); + + if(!ReadBlockFromDisk(block, pblockindex,1)) + throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); + + BOOST_FOREACH(const CTransaction&tx, block.vtx) + { + int num_zins, num_zouts = 0; + // ignore coinbase txs which have no zins or zouts + if(!tx.IsCoinBase()) { + num_zouts = tx.vShieldedOutput.size(); + num_zins = tx.vShieldedSpend.size(); + if(num_zins > 0 && num_zouts > 0) { + total_ztxs++; + total_zins += num_zins; + total_zouts += num_zouts; + } + if (num_zins > largest_zins) { + largest_zins = num_zins; + } + if (num_zouts > largest_zouts) { + largest_zouts = num_zouts; + } + } + } + } + } + UniValue ret(UniValue::VOBJ); + double avg_zins = total_ztxs > 0 ? total_zins / total_ztxs : 0.0; + double avg_zouts = total_ztxs > 0 ? total_zouts / total_ztxs : 0.0; + ret.pushKV("total_ztxs", total_ztxs); + ret.pushKV("avg_zins", avg_zins); + ret.pushKV("avg_zouts", avg_zouts); + ret.pushKV("largest_zins", largest_zins); + ret.pushKV("largest_zouts", largest_zouts); + return ret; +} + UniValue z_anonsetblockdelta(const UniValue& params, bool fHelp, const CPubKey& mypk) { if (!EnsureWalletIsAvailable(fHelp)) @@ -8489,6 +8632,7 @@ static const CRPCCommand commands[] = { "wallet", "z_listunspent", &z_listunspent, false }, { "wallet", "z_getbalance", &z_getbalance, false }, { "wallet", "z_getbalances", &z_getbalances, false }, + { "wallet", "z_getstats", &z_getstats, true }, { "wallet", "z_anonsettxdelta", &z_anonsettxdelta, true }, { "wallet", "z_anonsetblockdelta", &z_anonsetblockdelta, true }, { "wallet", "z_gettotalbalance", &z_gettotalbalance, false }, From 0f4956dcd54d715e4881db74bfa51a092193b7b6 Mon Sep 17 00:00:00 2001 From: Duke Date: Mon, 4 Sep 2023 08:50:05 -0400 Subject: [PATCH 2/7] Initialize variables in z_getstats correctly --- src/wallet/rpcwallet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index b10650137..01c379ecd 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3682,8 +3682,8 @@ UniValue z_getstats(const UniValue& params, bool fHelp, const CPubKey& mypk) if(!ReadBlockFromDisk(block, pblockindex,1)) throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); - int total_ztxs, total_zins, total_zouts = 0; - int largest_zins, largest_zouts = 0; + int total_ztxs = 0, total_zins = 0, total_zouts = 0; + int largest_zins = 0, largest_zouts = 0; // given a single block height, we calculate stats for that height if (params.size() == 1) { From 8eaba566fdf4732bdddfd42eac2ebb56e9dbba14 Mon Sep 17 00:00:00 2001 From: Duke Date: Mon, 4 Sep 2023 08:57:23 -0400 Subject: [PATCH 3/7] Force avg zins/zouts to be a double --- src/wallet/rpcwallet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 01c379ecd..7c52b744e 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3769,8 +3769,8 @@ UniValue z_getstats(const UniValue& params, bool fHelp, const CPubKey& mypk) } } UniValue ret(UniValue::VOBJ); - double avg_zins = total_ztxs > 0 ? total_zins / total_ztxs : 0.0; - double avg_zouts = total_ztxs > 0 ? total_zouts / total_ztxs : 0.0; + double avg_zins = total_ztxs > 0 ? (double) total_zins / total_ztxs : 0.0; + double avg_zouts = total_ztxs > 0 ? (double) total_zouts / total_ztxs : 0.0; ret.pushKV("total_ztxs", total_ztxs); ret.pushKV("avg_zins", avg_zins); ret.pushKV("avg_zouts", avg_zouts); From 7ea88bb303372eb91bebb0da908ce6ac0c484de0 Mon Sep 17 00:00:00 2001 From: Duke Date: Mon, 4 Sep 2023 09:03:06 -0400 Subject: [PATCH 4/7] Return total zins+zouts in json --- src/wallet/rpcwallet.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 7c52b744e..d88a7a4d0 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3772,6 +3772,8 @@ UniValue z_getstats(const UniValue& params, bool fHelp, const CPubKey& mypk) double avg_zins = total_ztxs > 0 ? (double) total_zins / total_ztxs : 0.0; double avg_zouts = total_ztxs > 0 ? (double) total_zouts / total_ztxs : 0.0; ret.pushKV("total_ztxs", total_ztxs); + ret.pushKV("total_zins", total_zins); + ret.pushKV("total_zouts", total_zouts); ret.pushKV("avg_zins", avg_zins); ret.pushKV("avg_zouts", avg_zouts); ret.pushKV("largest_zins", largest_zins); From ff7a5970325e1f0417a28dcfdd1257703608c5cf Mon Sep 17 00:00:00 2001 From: Duke Date: Tue, 5 Sep 2023 00:35:11 -0400 Subject: [PATCH 5/7] Lots of more data for z_getstats --- src/wallet/rpcwallet.cpp | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index d88a7a4d0..90997e6ce 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3683,7 +3683,9 @@ UniValue z_getstats(const UniValue& params, bool fHelp, const CPubKey& mypk) throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); int total_ztxs = 0, total_zins = 0, total_zouts = 0; + int total_ztxs_10_or_more_zins = 0, total_ztxs_10_or_more_zouts = 0; int largest_zins = 0, largest_zouts = 0; + std::string largest_zins_txid = "", largest_zouts_txid = ""; // given a single block height, we calculate stats for that height if (params.size() == 1) { @@ -3695,24 +3697,33 @@ UniValue z_getstats(const UniValue& params, bool fHelp, const CPubKey& mypk) num_zouts = tx.vShieldedOutput.size(); num_zins = tx.vShieldedSpend.size(); // tx must have some zins and zouts to count towards our stats, - // which ignores shielding coinbase txs, which have only transparent inputs + // which ignores shielding coinbase txs, which have only transparent inputs. // This mostly will only count "z2z" txs but also counts (z,t)=>z and z=>(z,t) - // which are possible but unlikely, since RPCs will not create them + // which are possible but unlikely, since RPCs cannot currently create (z,t)=>z txs + // and z=>(z,t) are disallowed when ac_private=1 if(num_zins > 0 && num_zouts > 0) { total_ztxs++; total_zins += num_zins; total_zouts += num_zouts; if (num_zins > largest_zins) { largest_zins = num_zins; + largest_zins_txid = tx.GetHash().ToString(); } if (num_zouts > largest_zouts) { largest_zouts = num_zouts; + largest_zouts_txid = tx.GetHash().ToString(); + } + if (num_zins >= 10) { + total_ztxs_10_or_more_zins++; + } + if (num_zouts >= 10) { + total_ztxs_10_or_more_zouts++; } } } } } else { - // given two blocks, we calculate delta for that range + // given two blocks, we calculate stats for that range std::string strHeight2 = params[1].get_str(); int nHeight2 = -1; try { @@ -3729,7 +3740,7 @@ UniValue z_getstats(const UniValue& params, bool fHelp, const CPubKey& mypk) throw JSONRPCError(RPC_INVALID_PARAMETER, "Ending block height out of range"); } - // get the delta for every block in the range + // get the stats for every block in the range for(int currentHeight = nHeight; currentHeight <= nHeight2; currentHeight++) { auto strHash = chainActive[currentHeight]->GetBlockHash().GetHex(); uint256 hash(uint256S(strHash)); @@ -3760,9 +3771,17 @@ UniValue z_getstats(const UniValue& params, bool fHelp, const CPubKey& mypk) } if (num_zins > largest_zins) { largest_zins = num_zins; + largest_zins_txid = tx.GetHash().ToString(); } if (num_zouts > largest_zouts) { largest_zouts = num_zouts; + largest_zouts_txid = tx.GetHash().ToString(); + } + if (num_zins >= 10) { + total_ztxs_10_or_more_zins++; + } + if (num_zouts >= 10) { + total_ztxs_10_or_more_zouts++; } } } @@ -3774,10 +3793,14 @@ UniValue z_getstats(const UniValue& params, bool fHelp, const CPubKey& mypk) ret.pushKV("total_ztxs", total_ztxs); ret.pushKV("total_zins", total_zins); ret.pushKV("total_zouts", total_zouts); + ret.pushKV("total_ztxs_10_or_more_zins", total_ztxs_10_or_more_zins); + ret.pushKV("total_ztxs_10_or_more_zouts", total_ztxs_10_or_more_zouts); ret.pushKV("avg_zins", avg_zins); ret.pushKV("avg_zouts", avg_zouts); ret.pushKV("largest_zins", largest_zins); + ret.pushKV("largest_zins_txid", largest_zins_txid); ret.pushKV("largest_zouts", largest_zouts); + ret.pushKV("largest_zouts_txid", largest_zouts_txid); return ret; } From 4aca3493e39910d5e75bf84f5455a16303180ea2 Mon Sep 17 00:00:00 2001 From: Duke Date: Tue, 5 Sep 2023 11:30:27 -0400 Subject: [PATCH 6/7] Even more zstats Example data for the entire history of the current HUSH mainnet : ./src/hush-cli z_getstats 1 1487622 { "total_ztxs": 414962, "total_zins": 798083, "total_zouts": 3312131, "total_ztxs_10_or_more_zins": 6789, "total_ztxs_25_or_more_zins": 1779, "total_ztxs_50_or_more_zins": 688, "total_ztxs_100_or_more_zins": 174, "total_ztxs_10_or_more_zouts": 2855, "total_ztxs_25_or_more_zouts": 394, "total_ztxs_50_or_more_zouts": 314, "total_ztxs_100_or_more_zouts": 208, "avg_zins": 1.923267672702561, "avg_zouts": 7.981769415030774, "largest_zins": 517, "largest_zins_txid": "69f126edd5a0189fbbe84b0824eb48e16eddf180e7d5d4f34c4296d0f868ac7f", "largest_zouts": 210, "largest_zouts_txid": "2a3155f73fab9191978e77e03be8ec7167372c4549113a6eb3f8a9d343f749ba" } --- src/wallet/rpcwallet.cpp | 47 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 90997e6ce..0be79dd3d 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3684,6 +3684,9 @@ UniValue z_getstats(const UniValue& params, bool fHelp, const CPubKey& mypk) int total_ztxs = 0, total_zins = 0, total_zouts = 0; int total_ztxs_10_or_more_zins = 0, total_ztxs_10_or_more_zouts = 0; + int total_ztxs_25_or_more_zins = 0, total_ztxs_25_or_more_zouts = 0; + int total_ztxs_50_or_more_zins = 0, total_ztxs_50_or_more_zouts = 0; + int total_ztxs_100_or_more_zins = 0, total_ztxs_100_or_more_zouts = 0; int largest_zins = 0, largest_zouts = 0; std::string largest_zins_txid = "", largest_zouts_txid = ""; @@ -3715,10 +3718,30 @@ UniValue z_getstats(const UniValue& params, bool fHelp, const CPubKey& mypk) } if (num_zins >= 10) { total_ztxs_10_or_more_zins++; + if (num_zins >= 25) { + total_ztxs_25_or_more_zins++; + if (num_zins >= 50) { + total_ztxs_50_or_more_zins++; + if (num_zins >= 100) { + total_ztxs_100_or_more_zins++; + } + } + } } if (num_zouts >= 10) { total_ztxs_10_or_more_zouts++; + if (num_zouts >= 25) { + total_ztxs_25_or_more_zouts++; + if (num_zouts >= 50) { + total_ztxs_50_or_more_zouts++; + if (num_zouts >= 100) { + total_ztxs_100_or_more_zouts++; + } + } + } } + + } } } @@ -3779,9 +3802,27 @@ UniValue z_getstats(const UniValue& params, bool fHelp, const CPubKey& mypk) } if (num_zins >= 10) { total_ztxs_10_or_more_zins++; + if (num_zins >= 25) { + total_ztxs_25_or_more_zins++; + if (num_zins >= 50) { + total_ztxs_50_or_more_zins++; + if (num_zins >= 100) { + total_ztxs_100_or_more_zins++; + } + } + } } if (num_zouts >= 10) { total_ztxs_10_or_more_zouts++; + if (num_zouts >= 25) { + total_ztxs_25_or_more_zouts++; + if (num_zouts >= 50) { + total_ztxs_50_or_more_zouts++; + if (num_zouts >= 100) { + total_ztxs_100_or_more_zouts++; + } + } + } } } } @@ -3794,7 +3835,13 @@ UniValue z_getstats(const UniValue& params, bool fHelp, const CPubKey& mypk) ret.pushKV("total_zins", total_zins); ret.pushKV("total_zouts", total_zouts); ret.pushKV("total_ztxs_10_or_more_zins", total_ztxs_10_or_more_zins); + ret.pushKV("total_ztxs_25_or_more_zins", total_ztxs_25_or_more_zins); + ret.pushKV("total_ztxs_50_or_more_zins", total_ztxs_50_or_more_zins); + ret.pushKV("total_ztxs_100_or_more_zins", total_ztxs_100_or_more_zins); ret.pushKV("total_ztxs_10_or_more_zouts", total_ztxs_10_or_more_zouts); + ret.pushKV("total_ztxs_25_or_more_zouts", total_ztxs_25_or_more_zouts); + ret.pushKV("total_ztxs_50_or_more_zouts", total_ztxs_50_or_more_zouts); + ret.pushKV("total_ztxs_100_or_more_zouts", total_ztxs_100_or_more_zouts); ret.pushKV("avg_zins", avg_zins); ret.pushKV("avg_zouts", avg_zouts); ret.pushKV("largest_zins", largest_zins); From 7c45e66fbeefa354c4169ce230eb061b92c3adbe Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 8 Sep 2023 08:28:14 -0400 Subject: [PATCH 7/7] Also return start and ending height in z_getstats json --- src/wallet/rpcwallet.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 0be79dd3d..8467c0453 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3689,6 +3689,8 @@ UniValue z_getstats(const UniValue& params, bool fHelp, const CPubKey& mypk) int total_ztxs_100_or_more_zins = 0, total_ztxs_100_or_more_zouts = 0; int largest_zins = 0, largest_zouts = 0; std::string largest_zins_txid = "", largest_zouts_txid = ""; + UniValue ret(UniValue::VOBJ); + ret.pushKV("start_height", nHeight); // given a single block height, we calculate stats for that height if (params.size() == 1) { @@ -3763,6 +3765,8 @@ UniValue z_getstats(const UniValue& params, bool fHelp, const CPubKey& mypk) throw JSONRPCError(RPC_INVALID_PARAMETER, "Ending block height out of range"); } + ret.pushKV("end_height", nHeight2); + // get the stats for every block in the range for(int currentHeight = nHeight; currentHeight <= nHeight2; currentHeight++) { auto strHash = chainActive[currentHeight]->GetBlockHash().GetHex(); @@ -3828,7 +3832,6 @@ UniValue z_getstats(const UniValue& params, bool fHelp, const CPubKey& mypk) } } } - UniValue ret(UniValue::VOBJ); double avg_zins = total_ztxs > 0 ? (double) total_zins / total_ztxs : 0.0; double avg_zouts = total_ztxs > 0 ? (double) total_zouts / total_ztxs : 0.0; ret.pushKV("total_ztxs", total_ztxs);