rpc: Add masternode payments (#3863)

* rpc: Implement `masternode payments`

Returns an array of deterministic masternodes and their payments for a specific block

* tests: Add rpc_masternode.py

* Apply review suggestions

* Add amounts calculated per masternode and per block

* Tweak help string

* Update src/rpc/masternode.cpp

Co-authored-by: dustinface <35775977+xdustinface@users.noreply.github.com>

* rpc: Check against vector size instead of decrementing a counter

* rpc: Use `std::vector::begin()` instead of `std::begin(std::vector)`

* Drop set_dash_dip8_activation in rpc_masternode.py

* Apply suggestions from code review

Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com>

Co-authored-by: dustinface <35775977+xdustinface@users.noreply.github.com>
Co-authored-by: xdustinface <xdustinfacex@gmail.com>
Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com>
This commit is contained in:
UdjinM6 2020-12-15 05:15:09 +03:00 committed by GitHub
parent 0c28bf4ab8
commit 0a8664fd33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 198 additions and 0 deletions

View File

@ -368,6 +368,146 @@ UniValue masternode_winners(const JSONRPCRequest& request)
return obj;
}
void masternode_payments_help()
{
throw std::runtime_error(
"masternode payments ( \"blockhash\" count )\n"
"\nReturns an array of deterministic masternodes and their payments for the specified block\n"
"\nArguments:\n"
"1. \"blockhash\" (string, optional, default=tip) The hash of the starting block\n"
"2. count (numeric, optional, default=1) The number of blocks to return.\n"
" Will return <count> previous blocks if <count> is negative.\n"
" Both 1 and -1 correspond to the chain tip.\n"
"\nResult:\n"
" [ (array) Blocks\n"
" {\n"
" \"height\" : n, (numeric) The height of the block\n"
" \"blockhash\" : \"hash\", (string) The hash of the block\n"
" \"amount\": n (numeric) Amount received in this block by all masternodes\n"
" \"masternodes\": [ (array) Masternodes that received payments in this block\n"
" {\n"
" \"proTxHash\": \"xxxx\", (string) The hash of the corresponding ProRegTx\n"
" \"amount\": n (numeric) Amount received by this masternode\n"
" \"payees\": [ (array) Payees who received a share of this payment\n"
" {\n"
" \"address\" : \"xxx\", (string) Payee address\n"
" \"script\" : \"xxx\", (string) Payee scriptPubKey\n"
" \"amount\": n (numeric) Amount received by this payee\n"
" },...\n"
" ]\n"
" },...\n"
" ]\n"
" },...\n"
" ]\n"
);
}
UniValue masternode_payments(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() > 3) {
masternode_payments_help();
}
CBlockIndex* pindex{nullptr};
if (request.params[1].isNull()) {
LOCK(cs_main);
pindex = chainActive.Tip();
} else {
LOCK(cs_main);
uint256 blockHash = ParseHashV(request.params[1], "blockhash");
auto it = mapBlockIndex.find(blockHash);
if (it == mapBlockIndex.end()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
pindex = it->second;
}
int64_t nCount = request.params.size() > 2 ? ParseInt64V(request.params[2], "count") : 1;
// A temporary vector which is used to sort results properly (there is no "reverse" in/for UniValue)
std::vector<UniValue> vecPayments;
while (vecPayments.size() < std::abs(nCount) != 0 && pindex != nullptr) {
CBlock block;
if (!ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
}
// Note: we have to actually calculate block reward from scratch instead of simply querying coinbase vout
// because miners might collect less coins than they potentially could and this would break our calculations.
CAmount nBlockFees{0};
for (const auto& tx : block.vtx) {
if (tx->IsCoinBase()) {
continue;
}
CAmount nValueIn{0};
for (const auto txin : tx->vin) {
CTransactionRef txPrev;
uint256 blockHashTmp;
GetTransaction(txin.prevout.hash, txPrev, Params().GetConsensus(), blockHashTmp);
nValueIn += txPrev->vout[txin.prevout.n].nValue;
}
nBlockFees += nValueIn - tx->GetValueOut();
}
std::vector<CTxOut> voutMasternodePayments, voutDummy;
CMutableTransaction dummyTx;
CAmount blockReward = nBlockFees + GetBlockSubsidy(pindex->pprev->nBits, pindex->pprev->nHeight, Params().GetConsensus());
FillBlockPayments(dummyTx, pindex->nHeight, blockReward, voutMasternodePayments, voutDummy);
UniValue blockObj(UniValue::VOBJ);
CAmount payedPerBlock{0};
UniValue masternodeArr(UniValue::VARR);
UniValue protxObj(UniValue::VOBJ);
UniValue payeesArr(UniValue::VARR);
CAmount payedPerMasternode{0};
for (const auto& txout : voutMasternodePayments) {
UniValue obj(UniValue::VOBJ);
CTxDestination dest;
ExtractDestination(txout.scriptPubKey, dest);
obj.pushKV("address", EncodeDestination(dest));
obj.pushKV("script", HexStr(txout.scriptPubKey));
obj.pushKV("amount", txout.nValue);
payedPerMasternode += txout.nValue;
payeesArr.push_back(obj);
}
const auto dmnPayee = deterministicMNManager->GetListForBlock(pindex).GetMNPayee();
protxObj.pushKV("proTxHash", dmnPayee == nullptr ? "" : dmnPayee->proTxHash.ToString());
protxObj.pushKV("amount", payedPerMasternode);
protxObj.pushKV("payees", payeesArr);
payedPerBlock += payedPerMasternode;
masternodeArr.push_back(protxObj);
blockObj.pushKV("height", pindex->nHeight);
blockObj.pushKV("blockhash", pindex->GetBlockHash().ToString());
blockObj.pushKV("amount", payedPerBlock);
blockObj.pushKV("masternodes", masternodeArr);
vecPayments.push_back(blockObj);
if (nCount > 0) {
LOCK(cs_main);
pindex = chainActive.Next(pindex);
} else {
pindex = pindex->pprev;
}
}
if (nCount < 0) {
std::reverse(vecPayments.begin(), vecPayments.end());
}
UniValue paymentsArr(UniValue::VARR);
for (const auto& payment : vecPayments) {
paymentsArr.push_back(payment);
}
return paymentsArr;
}
[[ noreturn ]] void masternode_help()
{
@ -384,6 +524,7 @@ UniValue masternode_winners(const JSONRPCRequest& request)
#endif // ENABLE_WALLET
" status - Print masternode status information\n"
" list - Print list of all known masternodes (see masternodelist for more info)\n"
" payments - Return information about masternode payments in a mined block\n"
" winner - Print info on next masternode winner to vote for\n"
" winners - Print list of masternode winners\n"
);
@ -416,6 +557,8 @@ UniValue masternode(const JSONRPCRequest& request)
#endif // ENABLE_WALLET
} else if (strCommand == "status") {
return masternode_status(request);
} else if (strCommand == "payments") {
return masternode_payments(request);
} else if (strCommand == "winners") {
return masternode_winners(request);
} else {

View File

@ -0,0 +1,54 @@
#!/usr/bin/env python3
# Copyright (c) 2020 The Dash Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
from test_framework.test_framework import DashTestFramework
from test_framework.util import assert_equal
'''
rpc_masternode.py
Test "masternode" rpc subcommands
'''
class RPCMasternodeTest(DashTestFramework):
def set_test_params(self):
self.set_dash_test_params(4, 3, fast_dip3_enforcement=True)
def run_test(self):
self.log.info("test that results from `winners` and `payments` RPCs match")
bi = self.nodes[0].getblockchaininfo()
height = bi["blocks"]
blockhash = bi["bestblockhash"]
winners_payee = self.nodes[0].masternode("winners")[str(height)]
payments = self.nodes[0].masternode("payments", blockhash)
assert_equal(len(payments), 1)
payments_block = payments[0]
payments_payee = payments_block["masternodes"][0]["payees"][0]["address"]
assert_equal(payments_block["height"], height)
assert_equal(payments_block["blockhash"], blockhash)
assert_equal(winners_payee, payments_payee)
self.log.info("test various `payments` RPC options")
payments1 = self.nodes[0].masternode("payments", blockhash, -1)
assert_equal(payments, payments1)
payments2_1 = self.nodes[0].masternode("payments", blockhash, 2)
# using chaintip as a start block should return 1 block only
assert_equal(len(payments2_1), 1)
assert_equal(payments[0], payments2_1[0])
payments2_2 = self.nodes[0].masternode("payments", blockhash, -2)
# using chaintip as a start block should return 2 blocks now, with the tip being the last one
assert_equal(len(payments2_2), 2)
assert_equal(payments[0], payments2_2[-1])
self.log.info("test that `masternode payments` results at chaintip match `getblocktemplate` results for that block")
gbt_masternode = self.nodes[0].getblocktemplate()["masternode"]
self.nodes[0].generate(1)
payments_masternode = self.nodes[0].masternode("payments")[0]["masternodes"][0]
assert_equal(gbt_masternode[0]["payee"], payments_masternode["payees"][0]["address"])
assert_equal(gbt_masternode[0]["script"], payments_masternode["payees"][0]["script"])
assert_equal(gbt_masternode[0]["amount"], payments_masternode["payees"][0]["amount"])
if __name__ == '__main__':
RPCMasternodeTest().main()

View File

@ -155,6 +155,7 @@ BASE_SCRIPTS= [
'p2p_unrequested_blocks.py', # NOTE: needs dash_hash to pass
'feature_shutdown.py',
'rpc_privatesend.py',
'rpc_masternode.py',
'p2p_fingerprint.py',
'rpc_platform_filter.py',
'feature_dip0020_activation.py',