From 7413fecb7ba188608360b48a6c24df23545668f7 Mon Sep 17 00:00:00 2001 From: surangap Date: Wed, 31 Mar 2021 18:19:02 +0800 Subject: [PATCH] Feature/getmininginfo (#297) * Added getmininginfo RPC method * Updated the help text and formated the response. * Added lastBlockCreationAttemptTs member variable to CMasternode class * Added "lastblockcreationattempt" key-value to getmininginfo RPC response * Added mapMNLastBlockCreationAttemptTs map to store last block creation attempt timestamp by masternodes * Added functional test GetMiningInfoRPCTest * Added a deprication notice for getmintinginfo * Updated code with a critical section optimization. --- src/miner.cpp | 6 +++ src/miner.h | 5 ++ src/rpc/mining.cpp | 74 +++++++++++++++++++++++++++- test/functional/rpc_getmininginfo.py | 56 +++++++++++++++++++++ test/functional/test_runner.py | 1 + 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100755 test/functional/rpc_getmininginfo.py diff --git a/src/miner.cpp b/src/miner.cpp index c28d2bd8c3..889ab09d65 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -121,6 +121,12 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc if (!nodePtr || !nodePtr->IsActive()) return nullptr; + // update last block creation attempt ts for the master node + { + CLockFreeGuard lock(pos::cs_MNLastBlockCreationAttemptTs); + pos::mapMNLastBlockCreationAttemptTs[myIDs->second] = GetTime(); + } + CBlockIndex* pindexPrev = ::ChainActive().Tip(); assert(pindexPrev != nullptr); nHeight = pindexPrev->nHeight + 1; diff --git a/src/miner.h b/src/miner.h index faeb4260fe..98338fe18e 100644 --- a/src/miner.h +++ b/src/miner.h @@ -246,6 +246,11 @@ namespace pos { template bool withSearchInterval(F&& f); }; + + // Map to store [master node id : last block creation attempt timestamp] for local master nodes + static std::map mapMNLastBlockCreationAttemptTs; + static std::atomic_bool cs_MNLastBlockCreationAttemptTs(false); + } #endif // DEFI_MINER_H diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 2694c2b5ec..12ae5664da 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -217,10 +217,11 @@ static UniValue generatetoaddress(const JSONRPCRequest& request) return generateBlocks(coinbase_script, minterKey, myIDs->first, nGenerate, nMaxTries); } +/// @deprecated version of getmininginfo. prefer using getmininginfo static UniValue getmintinginfo(const JSONRPCRequest& request) { RPCHelpMan{"getmintinginfo", - "\nReturns a json object containing mining-related information.", + "\nDEPRECATED. Prefer using getmininginfo.\nReturns a json object containing mining-related information.", {}, RPCResult{ "{\n" @@ -267,6 +268,76 @@ static UniValue getmintinginfo(const JSONRPCRequest& request) return obj; } +// Returns the mining information of all local masternodes +static UniValue getmininginfo(const JSONRPCRequest& request) +{ + RPCHelpMan{"getmininginfo", + "\nReturns a json object containing mining-related information for all local masternodes", + {}, + RPCResult{ + "{\n" + " \"blocks\": nnn, (numeric) The current block\n" + " \"currentblockweight\": nnn, (numeric, optional) The block weight of the last assembled block (only present if a block was ever assembled)\n" + " \"currentblocktx\": nnn, (numeric, optional) The number of block transactions of the last assembled block (only present if a block was ever assembled)\n" + " \"difficulty\": xxx.xxxxx (numeric) The current difficulty\n" + " \"networkhashps\": nnn, (numeric) The network hashes per second\n" + " \"pooledtx\": n (numeric) The size of the mempool\n" + " \"chain\": \"xxxx\", (string) current network name as defined in BIP70 (main, test, regtest)\n" + " \"isoperator\": true|false (boolean) Local master nodes are available or not \n" + " \"masternodes\": [] (array) an array of objects which includes each master node information\n" + " \"warnings\": \"...\" (string) any network and blockchain warnings\n" + "}\n" + }, + RPCExamples{ + HelpExampleCli("getmininginfo", "") + + HelpExampleRpc("getmininginfo", "") + }, + }.Check(request); + + LOCK(cs_main); + + UniValue obj(UniValue::VOBJ); + obj.pushKV("blocks", (int)::ChainActive().Height()); + if (BlockAssembler::m_last_block_weight) obj.pushKV("currentblockweight", *BlockAssembler::m_last_block_weight); + if (BlockAssembler::m_last_block_num_txs) obj.pushKV("currentblocktx", *BlockAssembler::m_last_block_num_txs); + obj.pushKV("difficulty", (double)GetDifficulty(::ChainActive().Tip())); + obj.pushKV("networkhashps", getnetworkhashps(request)); + obj.pushKV("pooledtx", (uint64_t)mempool.size()); + obj.pushKV("chain", Params().NetworkIDString()); + + // get all masternode operators + auto mnIds = pcustomcsview->GetOperatorsMulti(); + obj.pushKV("isoperator", !mnIds.empty()); + + UniValue mnArr(UniValue::UniValue::VARR); // masternodes array + for (const auto& mnId : mnIds) { + UniValue subObj(UniValue::VOBJ); + + subObj.pushKV("masternodeid", mnId.second.GetHex()); + CMasternode const & node = *pcustomcsview->GetMasternode(mnId.second); + auto state = node.GetState(); + subObj.pushKV("masternodeoperator", node.operatorAuthAddress.GetHex());// NOTE(sp) : Should this also be encoded? not the HEX + subObj.pushKV("masternodestate", CMasternode::GetHumanReadableState(state)); + auto generate = node.IsActive() && gArgs.GetBoolArg("-gen", DEFAULT_GENERATE); + subObj.pushKV("generate", generate); + subObj.pushKV("mintedblocks", (uint64_t)node.mintedBlocks); + + if (!generate) { + subObj.pushKV("lastblockcreationattempt", "0"); + } else { + // get the last block creation attempt by the master node + CLockFreeGuard lock(pos::cs_MNLastBlockCreationAttemptTs); + auto lastBlockCreationAttemptTs = pos::mapMNLastBlockCreationAttemptTs[mnId.second]; + subObj.pushKV("lastblockcreationattempt", (lastBlockCreationAttemptTs != 0) ? FormatISO8601DateTime(lastBlockCreationAttemptTs) : "0"); + } + + mnArr.push_back(subObj); + } + + obj.pushKV("masternodes", mnArr); + obj.pushKV("warnings", GetWarnings("statusbar")); + return obj; +} // NOTE: Unlike wallet RPC (which use DFI values), mining RPCs follow GBT (BIP 22) in using satoshi amounts static UniValue prioritisetransaction(const JSONRPCRequest& request) @@ -1011,6 +1082,7 @@ static const CRPCCommand commands[] = { "mining", "getblocktemplate", &getblocktemplate, {"template_request"} }, { "mining", "submitblock", &submitblock, {"hexdata","dummy"} }, { "mining", "submitheader", &submitheader, {"hexdata"} }, + { "mining", "getmininginfo", &getmininginfo, {} }, { "generating", "generatetoaddress", &generatetoaddress, {"nblocks","address","maxtries"} }, diff --git a/test/functional/rpc_getmininginfo.py b/test/functional/rpc_getmininginfo.py new file mode 100755 index 0000000000..40f1bdd22b --- /dev/null +++ b/test/functional/rpc_getmininginfo.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021 The DeFi Blockchain developers +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +from test_framework.test_framework import DefiTestFramework +from test_framework.util import ( + assert_equal, + connect_nodes_bi, +) + +class GetMiningInfoRPCTest(DefiTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.setup_clean_chain = True + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + node0_keys = self.nodes[0].get_genesis_keys() + node1_keys = self.nodes[1].get_genesis_keys() + + self.log.info("Import private keys...") + # node[0] has only one master node + self.nodes[0].importprivkey(node0_keys.operatorPrivKey) + + # node[1] has two master nodes + self.nodes[1].importprivkey(node0_keys.operatorPrivKey) + self.nodes[1].importprivkey(node1_keys.operatorPrivKey) + + operators = [node0_keys.operatorAuthAddress, node1_keys.operatorAuthAddress] + + self.log.info("Restart nodes...") + self.restart_node(0, ['-gen', '-masternode_operator=' + operators[0]]) + self.restart_node(1, ['-gen', '-rewardaddress=' + operators[1]] + + ['-masternode_operator=' + x for x in operators]) + + connect_nodes_bi(self.nodes, 0, 1) + + self.log.info("Mining blocks ...") + self.nodes[0].generate(10) + self.sync_all() + self.nodes[1].generate(10) + self.sync_all() + + # getmininginfo() on node[0], should only return one master node in the response array + resp0 = self.nodes[0].getmininginfo() + assert_equal(len(resp0['masternodes']), 1) + + # getmininginfo() on node[1], should return two master nodes in the response array + resp1 = self.nodes[1].getmininginfo() + assert_equal(len(resp1['masternodes']), 2) + +if __name__ == '__main__': + GetMiningInfoRPCTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 03626a6427..22218d2873 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -244,6 +244,7 @@ 'rpc_help.py', 'feature_help.py', 'feature_shutdown.py', + 'rpc_getmininginfo.py', # Don't append tests at the end to avoid merge conflicts # Put them in a random line within the section that fits their approximate run-time ]