diff --git a/src/init.cpp b/src/init.cpp index 3621cc805b..7aa21f25db 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -875,6 +875,7 @@ static bool AppInitServers() default: return RPCResultCache::RPCCacheMode::None; }}(); GetRPCResultCache().Init(rpcCacheMode); + GetMemoizedResultCache().Init(rpcCacheMode); RPCServer::OnStarted(&OnRPCStarted); RPCServer::OnStopped(&OnRPCStopped); @@ -1480,8 +1481,8 @@ bool AppInitMain(InitInterfaces& interfaces) // -par=-n means "leave n cores free" (number of cores - n - 1 script threads) script_threads += GetNumCores(); // DeFiChain specific: - // Set this to a max value, since most custom TXs don't utilize this unfortunately - // and is just a waste of resources. + // Set this to a max value, since most custom TXs don't utilize this unfortunately + // and is just a waste of resources. script_threads = std::min(script_threads, 4); } diff --git a/src/masternodes/rpc_accounts.cpp b/src/masternodes/rpc_accounts.cpp index 3f33253968..6febb208e5 100644 --- a/src/masternodes/rpc_accounts.cpp +++ b/src/masternodes/rpc_accounts.cpp @@ -5,16 +5,6 @@ #include #include -struct BalanceResults { - CAmount burntDFI{}; - CAmount burntFee{}; - CAmount auctionFee{}; - CBalances burntTokens; - CBalances nonConsortiumTokens; - CBalances dexfeeburn; - CBalances paybackFee; -}; - std::string tokenAmountString(const CTokenAmount &amount, AmountFormat format = AmountFormat::Symbol) { const auto token = pcustomcsview->GetToken(amount.nTokenId); const auto amountString = ValueFromAmount(amount.nValue).getValStr(); @@ -1967,19 +1957,14 @@ UniValue getburninfo(const JSONRPCRequest& request) { }.Check(request); if (auto res = GetRPCResultCache().TryGet(request)) return *res; + auto initialResult = GetMemoizedResultCache().GetOrDefault(request); + auto totalResult = std::get_if(&initialResult.data); - CAmount burntDFI{0}; - CAmount burntFee{0}; - CAmount auctionFee{0}; CAmount dfiPaybackFee{0}; CAmount burnt{0}; - CBalances burntTokens; CBalances consortiumTokens; - CBalances nonConsortiumTokens; - CBalances dexfeeburn; CBalances paybackfees; - CBalances paybackFee; CBalances paybacktokens; CBalances dfi2203Tokens; CBalances dfipaybacktokens; @@ -1988,6 +1973,7 @@ UniValue getburninfo(const JSONRPCRequest& request) { LOCK(cs_main); auto height = ::ChainActive().Height(); + auto hash = ::ChainActive().Tip()->GetBlockHash(); auto fortCanningHeight = Params().GetConsensus().FortCanningHeight; auto burnAddress = Params().GetConsensus().burnAddress; auto view = *pcustomcsview; @@ -2023,109 +2009,133 @@ UniValue getburninfo(const JSONRPCRequest& request) { } } + class WorkerResultPool { + public: + explicit WorkerResultPool(size_t size) { + pool.reserve(size); + for (size_t i = 0; i < size; i++) { + pool.push_back(CGetBurnInfoResult{}); + } + } + + std::shared_ptr Acquire() { + LOCK(syncFlag); + auto res = std::make_shared(pool.back()); + pool.pop_back(); + + return res; + } + + void Release(std::shared_ptr res) { + LOCK(syncFlag); + pool.push_back(*res); + } + + std::vector &GetContainer() { + return pool; + } + + private: + CCriticalSection syncFlag; + std::vector pool; + }; + auto nWorkers = DfTxTaskPool->GetAvailableThreads(); - if (static_cast(height) < nWorkers) { + if (static_cast(height) < nWorkers) { nWorkers = height; } const auto chunks = height / nWorkers; TaskGroup g; - - std::vector> workerResults; - // Note this creates a massive amount of chunks as we go in mem. - // But this is fine for now. Most optimal impl is to return the future val - // and add it on receive. It requires a bit more changes, but for now - // this should do. - // However reserve in one-go to prevent numerous reallocations - workerResults.reserve(chunks + 1); - - for (size_t i = 0; i <= chunks; i++) { - auto result = std::make_shared(); - workerResults.push_back(result); - } + WorkerResultPool resultsPool{nWorkers}; auto &pool = DfTxTaskPool->pool; - auto processedHeight = 0; + auto processedHeight = initialResult.height; auto i = 0; while (processedHeight < height) { - auto startHeight = (chunks * (i + 1)); - auto stopHeight = (chunks * (i)); - auto result = workerResults[i]; + auto startHeight = initialResult.height + (chunks * (i + 1)); + auto stopHeight = initialResult.height + (chunks * (i)); g.AddTask(); - boost::asio::post(pool, [result, startHeight, stopHeight, &g] { - pburnHistoryDB->ForEachAccountHistory([result, stopHeight](const AccountHistoryKey &key, const AccountHistoryValue &value) { + boost::asio::post(pool, [startHeight, stopHeight, &g, &resultsPool] { + auto currentResult = resultsPool.Acquire(); - // Stop on chunk range for worker - if (key.blockHeight <= stopHeight) { - return false; - } + pburnHistoryDB->ForEachAccountHistory( + [currentResult, stopHeight](const AccountHistoryKey &key, const AccountHistoryValue &value) { + // Stop on chunk range for worker + if (key.blockHeight <= stopHeight) { + return false; + } - // UTXO burn - if (value.category == uint8_t(CustomTxType::None)) { - for (auto const & diff : value.diff) { - result->burntDFI += diff.second; + // UTXO burn + if (value.category == uint8_t(CustomTxType::None)) { + for (auto const &diff : value.diff) { + currentResult->burntDFI += diff.second; + } + return true; } - return true; - } - // Fee burn - if (value.category == uint8_t(CustomTxType::CreateMasternode) - || value.category == uint8_t(CustomTxType::CreateToken) - || value.category == uint8_t(CustomTxType::Vault) - || value.category == uint8_t(CustomTxType::CreateCfp) - || value.category == uint8_t(CustomTxType::CreateVoc)) { - for (auto const & diff : value.diff) { - result->burntFee += diff.second; + // Fee burn + if (value.category == uint8_t(CustomTxType::CreateMasternode) || + value.category == uint8_t(CustomTxType::CreateToken) || + value.category == uint8_t(CustomTxType::Vault) || + value.category == uint8_t(CustomTxType::CreateCfp) || + value.category == uint8_t(CustomTxType::CreateVoc)) { + for (auto const &diff : value.diff) { + currentResult->burntFee += diff.second; + } + return true; } - return true; - } - // withdraw burn - if (value.category == uint8_t(CustomTxType::PaybackLoan) - || value.category == uint8_t(CustomTxType::PaybackLoanV2) - || value.category == uint8_t(CustomTxType::PaybackWithCollateral)) { - for (const auto& [id, amount] : value.diff) { - result->paybackFee.Add({id, amount}); + // withdraw burn + if (value.category == uint8_t(CustomTxType::PaybackLoan) || + value.category == uint8_t(CustomTxType::PaybackLoanV2) || + value.category == uint8_t(CustomTxType::PaybackWithCollateral)) { + for (const auto &[id, amount] : value.diff) { + currentResult->paybackFee.Add({id, amount}); + } + return true; } - return true; - } - // auction burn - if (value.category == uint8_t(CustomTxType::AuctionBid)) { - for (auto const & diff : value.diff) { - result->auctionFee += diff.second; + // auction burn + if (value.category == uint8_t(CustomTxType::AuctionBid)) { + for (auto const &diff : value.diff) { + currentResult->auctionFee += diff.second; + } + return true; } - return true; - } - // dex fee burn - if (value.category == uint8_t(CustomTxType::PoolSwap) - || value.category == uint8_t(CustomTxType::PoolSwapV2)) { - for (auto const & diff : value.diff) { - result->dexfeeburn.Add({diff.first, diff.second}); + // dex fee burn + if (value.category == uint8_t(CustomTxType::PoolSwap) || + value.category == uint8_t(CustomTxType::PoolSwapV2)) { + for (auto const &diff : value.diff) { + currentResult->dexfeeburn.Add({diff.first, diff.second}); + } + return true; } - return true; - } - // token burn with burnToken tx - if (value.category == uint8_t(CustomTxType::BurnToken)) - { - for (auto const & diff : value.diff) { - result->nonConsortiumTokens.Add({diff.first, diff.second}); + // token burn with burnToken tx + if (value.category == uint8_t(CustomTxType::BurnToken)) { + for (auto const &diff : value.diff) { + currentResult->nonConsortiumTokens.Add({diff.first, diff.second}); + } + return true; } - return true; - } - // Token burn - for (auto const & diff : value.diff) { - result->burntTokens.Add({diff.first, diff.second}); - } + // Token burn + for (auto const &diff : value.diff) { + currentResult->burntTokens.Add({diff.first, diff.second}); + } - return true; - }, {}, startHeight, std::numeric_limits::max()); + return true; + }, + {}, + startHeight, + std::numeric_limits::max()); + + resultsPool.Release(currentResult); g.RemoveTask(); }); @@ -2136,38 +2146,38 @@ UniValue getburninfo(const JSONRPCRequest& request) { g.WaitForCompletion(); - for (const auto &result : workerResults) { - burntDFI += result->burntDFI; - burntFee += result->burntFee; - auctionFee += result->auctionFee; - burntTokens.AddBalances(result->burntTokens.balances); - nonConsortiumTokens.AddBalances(result->nonConsortiumTokens.balances); - dexfeeburn.AddBalances(result->dexfeeburn.balances); - paybackFee.AddBalances(result->paybackFee.balances); + for (const auto &r : resultsPool.GetContainer()) { + totalResult->burntDFI += r.burntDFI; + totalResult->burntFee += r.burntFee; + totalResult->auctionFee += r.auctionFee; + totalResult->burntTokens.AddBalances(r.burntTokens.balances); + totalResult->nonConsortiumTokens.AddBalances(r.nonConsortiumTokens.balances); + totalResult->dexfeeburn.AddBalances(r.dexfeeburn.balances); + totalResult->paybackFee.AddBalances(r.paybackFee.balances); } CDataStructureV0 liveKey = {AttributeTypes::Live, ParamIDs::Economy, EconomyKeys::ConsortiumMinted}; auto balances = attributes->GetValue(liveKey, CConsortiumGlobalMinted{}); - for (const auto &token : nonConsortiumTokens.balances) { + for (const auto &token : totalResult->nonConsortiumTokens.balances) { TAmounts amount; amount[token.first] = balances[token.first].burnt; consortiumTokens.AddBalances(amount); } - nonConsortiumTokens.SubBalances(consortiumTokens.balances); - burntTokens.AddBalances(nonConsortiumTokens.balances); + totalResult->nonConsortiumTokens.SubBalances(consortiumTokens.balances); + totalResult->burntTokens.AddBalances(totalResult->nonConsortiumTokens.balances); UniValue result(UniValue::VOBJ); result.pushKV("address", ScriptToString(burnAddress)); - result.pushKV("amount", ValueFromAmount(burntDFI)); + result.pushKV("amount", ValueFromAmount(totalResult->burntDFI)); - result.pushKV("tokens", AmountsToJSON(burntTokens.balances)); + result.pushKV("tokens", AmountsToJSON(totalResult->burntTokens.balances)); result.pushKV("consortiumtokens", AmountsToJSON(consortiumTokens.balances)); - result.pushKV("feeburn", ValueFromAmount(burntFee)); - result.pushKV("auctionburn", ValueFromAmount(auctionFee)); - result.pushKV("paybackburn", AmountsToJSON(paybackFee.balances)); - result.pushKV("dexfeetokens", AmountsToJSON(dexfeeburn.balances)); + result.pushKV("feeburn", ValueFromAmount(totalResult->burntFee)); + result.pushKV("auctionburn", ValueFromAmount(totalResult->auctionFee)); + result.pushKV("paybackburn", AmountsToJSON(totalResult->paybackFee.balances)); + result.pushKV("dexfeetokens", AmountsToJSON(totalResult->dexfeeburn.balances)); result.pushKV("dfipaybackfee", ValueFromAmount(dfiPaybackFee)); result.pushKV("dfipaybacktokens", AmountsToJSON(dfipaybacktokens.balances)); @@ -2179,6 +2189,7 @@ UniValue getburninfo(const JSONRPCRequest& request) { result.pushKV("dfip2203", AmountsToJSON(dfi2203Tokens.balances)); result.pushKV("dfip2206f", AmountsToJSON(dfiToDUSDTokens.balances)); + GetMemoizedResultCache().Set(request, {height, hash, *totalResult}); return GetRPCResultCache() .Set(request, result); } diff --git a/src/masternodes/rpc_proposals.cpp b/src/masternodes/rpc_proposals.cpp index e00f25dff1..e08923e84b 100644 --- a/src/masternodes/rpc_proposals.cpp +++ b/src/masternodes/rpc_proposals.cpp @@ -581,6 +581,164 @@ UniValue votegov(const JSONRPCRequest &request) { return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } + +UniValue votegovbatch(const JSONRPCRequest &request) { + auto pwallet = GetWallet(request); + + RPCHelpMan{ + "votegovbatch", + "\nVote for community proposal with multiple masternodes" + HelpRequiringPassphrase(pwallet) + "\n", + { + {"votes", RPCArg::Type::ARR, RPCArg::Optional::NO, "A json array of proposal ID, masternode IDs, operator or owner addresses and vote decision (yes/no/neutral).", + { + {"proposalId", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The proposal txid"}, + {"masternodeId", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The masternode ID, operator or owner address"}, + {"decision", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The vote decision (yes/no/neutral)"}, + }}, + }, + RPCResult{"\"hash\" (string) The hex-encoded hash of broadcasted transaction\n"}, + RPCExamples{HelpExampleCli("votegovbatch", "{{proposalId, masternodeId, yes}...}") + + HelpExampleRpc("votegovbatch", "{{proposalId, masternodeId, yes}...}")}, + } + .Check(request); + + if (pwallet->chain().isInitialBlockDownload()) { + throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot vote while still in Initial Block Download"); + } + pwallet->BlockUntilSyncedToCurrentChain(); + + RPCTypeCheck(request.params, {UniValue::VARR}, false); + + const auto &keys = request.params[0].get_array(); + auto neutralVotesAllowed = gArgs.GetBoolArg("-rpc-governance-accept-neutral", DEFAULT_RPC_GOV_NEUTRAL); + + int targetHeight; + + struct MasternodeMultiVote { + uint256 propId; + uint256 mnId; + CTxDestination dest; + CProposalVoteType type; + }; + + std::vector mnMultiVotes; + { + CCustomCSView view(*pcustomcsview); + + for (size_t i{}; i < keys.size(); ++i) { + + const auto &votes{keys[i].get_array()}; + if (votes.size() != 3) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Incorrect number of items, three expected, proposal ID, masternode ID and vote expected. %d entries provided.", votes.size())); + } + + const auto propId = ParseHashV(votes[0].get_str(), "proposalId"); + const auto prop = view.GetProposal(propId); + if (!prop) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Proposal <%s> does not exist", propId.GetHex())); + } + + if (prop->status != CProposalStatusType::Voting) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + strprintf("Proposal <%s> is not in voting period", propId.GetHex())); + } + + uint256 mnId; + const auto &id = votes[1].get_str(); + + if (id.length() == 64) { + mnId = ParseHashV(id, "masternodeId"); + } else { + const CTxDestination dest = DecodeDestination(id); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + strprintf("The masternode id or address is not valid: %s", id)); + } + CKeyID ckeyId; + if (dest.index() == PKHashType) { + ckeyId = CKeyID(std::get(dest)); + } else if (dest.index() == WitV0KeyHashType) { + ckeyId = CKeyID(std::get(dest)); + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s does not refer to a P2PKH or P2WPKH address", id)); + } + if (auto masterNodeIdByOwner = view.GetMasternodeIdByOwner(ckeyId)) { + mnId = masterNodeIdByOwner.value(); + } else if (auto masterNodeIdByOperator = view.GetMasternodeIdByOperator(ckeyId)) { + mnId = masterNodeIdByOperator.value(); + } + } + + const auto node = view.GetMasternode(mnId); + if (!node) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + strprintf("The masternode does not exist or the address doesn't own a masternode: %s", id)); + } + + auto vote = CProposalVoteType::VoteNeutral; + auto voteStr = ToLower(votes[2].get_str()); + + if (voteStr == "no") { + vote = CProposalVoteType::VoteNo; + } else if (voteStr == "yes") { + vote = CProposalVoteType::VoteYes; + } else if (neutralVotesAllowed && voteStr != "neutral") { + throw JSONRPCError(RPC_INVALID_PARAMETER, "decision supports yes/no/neutral"); + } else if (!neutralVotesAllowed) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Decision supports yes or no. Neutral is currently disabled because of issue https://github.com/DeFiCh/ain/issues/1704"); + } + + mnMultiVotes.push_back({ + propId, + mnId, + node->ownerType == 1 ? CTxDestination(PKHash(node->ownerAuthAddress)) : CTxDestination(WitnessV0KeyHash(node->ownerAuthAddress)), + vote + }); + } + + targetHeight = view.GetLastHeight() + 1; + } + + UniValue ret(UniValue::VARR); + + for (const auto& [propId, mnId, ownerDest, vote] : mnMultiVotes) { + + CProposalVoteMessage msg; + msg.propId = propId; + msg.masternodeId = mnId; + msg.vote = vote; + + // encode + CDataStream metadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION); + metadata << static_cast(CustomTxType::Vote) << msg; + + CScript scriptMeta; + scriptMeta << OP_RETURN << ToByteVector(metadata); + + const auto txVersion = GetTransactionVersion(targetHeight); + CMutableTransaction rawTx(txVersion); + + CTransactionRef optAuthTx; + std::set auths = {GetScriptForDestination(ownerDest)}; + rawTx.vin = GetAuthInputsSmart(pwallet, rawTx.nVersion, auths, false /*needFoundersAuth*/, optAuthTx, {}); + rawTx.vout.emplace_back(0, scriptMeta); + + CCoinControl coinControl; + if (IsValidDestination(ownerDest)) { + coinControl.destChange = ownerDest; + } + + fund(rawTx, pwallet, optAuthTx, &coinControl); + + // check execution + execTestTx(CTransaction(rawTx), targetHeight, optAuthTx); + + ret.push_back(signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex()); + } + + return ret; +} + UniValue listgovproposalvotes(const JSONRPCRequest &request) { auto pwallet = GetWallet(request); RPCHelpMan{ @@ -1192,6 +1350,7 @@ static const CRPCCommand commands[] = { {"proposals", "creategovcfp", &creategovcfp, {"data", "inputs"} }, {"proposals", "creategovvoc", &creategovvoc, {"data", "inputs"} }, {"proposals", "votegov", &votegov, {"proposalId", "masternodeId", "decision", "inputs"}}, + {"proposals", "votegovbatch", &votegovbatch, {"proposalId", "masternodeIds", "decision"}}, {"proposals", "listgovproposalvotes", &listgovproposalvotes, {"proposalId", "masternode", "cycle", "pagination"} }, {"proposals", "getgovproposal", &getgovproposal, {"proposalId"} }, {"proposals", "listgovproposals", &listgovproposals, {"type", "status", "cycle", "pagination"} }, diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index f0b0e8c1c5..0276457c59 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include