Skip to content

Commit

Permalink
[Test] Add test coverage for invalid budget finalization.
Browse files Browse the repository at this point in the history
  • Loading branch information
furszy committed Jun 22, 2021
1 parent d39a531 commit 18a5bc5
Show file tree
Hide file tree
Showing 3 changed files with 261 additions and 0 deletions.
88 changes: 88 additions & 0 deletions src/rpc/budget.cpp
Expand Up @@ -770,6 +770,93 @@ UniValue mnfinalbudgetsuggest(const JSONRPCRequest& request)
return (budgetHash.IsNull()) ? NullUniValue : budgetHash.ToString();
}

UniValue createrawmnfinalbudget(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() > 4)
throw std::runtime_error(
"createrawmnfinalbudget\n"
"\nTry to submit the raw budget finalization\n"
"returns the budget hash if it was broadcasted sucessfully"
"\nArguments:\n"
"1. \"budgetname\" (string, required) finalization name\n"
"2. \"blockstart\" (numeric, required) superblock height\n"
"3. \"proposals\" (string, required) A json array of json objects\n"
" [\n"
" {\n"
" \"proposalid\":\"id\", (string, required) The proposal id\n"
" \"payee\":n, (hex, required) The payee script\n"
" \"amount\":n (numeric, optional) The payee amount\n"
" }\n"
" ,...\n"
" ]\n"
"4. \"feetxid\" (string, optional) the transaction fee hash\n"
""
"\nResult:\n"
"{\n"
"\"result\" (string) Budget suggest broadcast or error\n"
"\"id\" (string) id of the fee tx or the finalized budget\n"
"}\n"
); // future: add examples.

if (!Params().IsRegTestNet()) {
throw JSONRPCError(RPC_MISC_ERROR, "command available only for RegTest network");
}

// future: add inputs error checking..
std::string budName = request.params[0].get_str();
int nBlockStart = request.params[1].get_int();
std::vector<CTxBudgetPayment> vecTxBudgetPayments;
UniValue budgetVec = request.params[2].get_array();
for (unsigned int idx = 0; idx < budgetVec.size(); idx++) {
const UniValue& prop = budgetVec[idx].get_obj();
uint256 propId = ParseHashO(prop, "proposalid");
std::vector<unsigned char> scriptData(ParseHexO(prop, "payee"));
CScript payee = CScript(scriptData.begin(), scriptData.end());
CAmount amount = AmountFromValue(find_value(prop, "amount"));
vecTxBudgetPayments.emplace_back(propId, payee, amount);
}

Optional<uint256> txFeeId = nullopt;
if (request.params.size() > 3) {
txFeeId = ParseHashV(request.params[3], "parameter 4");
}

if (!txFeeId) {
CFinalizedBudget tempBudget(budName, nBlockStart, vecTxBudgetPayments, UINT256_ZERO);
const uint256& budgetHash = tempBudget.GetHash();

// create fee tx
CTransactionRef wtx;
CReserveKey keyChange(vpwallets[0]);
if (!vpwallets[0]->CreateBudgetFeeTX(wtx, budgetHash, keyChange, true)) {
throw std::runtime_error("Can't make collateral transaction");
}
// Send the tx to the network
const CWallet::CommitResult& res = vpwallets[0]->CommitTransaction(wtx, keyChange, g_connman.get());
UniValue ret(UniValue::VOBJ);
if (res.status == CWallet::CommitStatus::OK) {
ret.pushKV("result", "tx_fee_sent");
ret.pushKV("id", wtx->GetHash().ToString());
} else {
ret.pushKV("result", "error");
}
return ret;
}

UniValue ret(UniValue::VOBJ);
// Collateral tx already exists, see if it's mature enough.
CFinalizedBudget fb(budName, nBlockStart, vecTxBudgetPayments, *txFeeId);
if (g_budgetman.AddFinalizedBudget(fb)) {
fb.Relay();
ret.pushKV("result", "fin_budget_sent");
ret.pushKV("id", fb.GetHash().ToString());
} else {
// future: add proper error
ret.pushKV("result", "error");
}
return ret;
}

UniValue mnfinalbudget(const JSONRPCRequest& request)
{
std::string strCommand;
Expand Down Expand Up @@ -880,6 +967,7 @@ static const CRPCCommand commands[] =

/* Not shown in help */
{ "hidden", "mnfinalbudgetsuggest", &mnfinalbudgetsuggest, true, {} },
{ "hidden", "createrawmnfinalbudget", &createrawmnfinalbudget, true, {"budgetname", "blockstart", "proposals", "feetxid"} },

};

Expand Down
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Expand Up @@ -149,6 +149,7 @@
'tiertwo_governance_reorg.py', # ~ 361 sec
'tiertwo_masternode_activation.py', # ~ 352 sec
'tiertwo_masternode_ping.py', # ~ 293 sec
'tiertwo_governance_invalid_budget.py',
'tiertwo_reorg_mempool.py', # ~ 107 sec
]

Expand Down
172 changes: 172 additions & 0 deletions test/functional/tiertwo_governance_invalid_budget.py
@@ -0,0 +1,172 @@
#!/usr/bin/env python3
# Copyright (c) 2021 The PIVX developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://www.opensource.org/licenses/mit-license.php.

from test_framework.test_framework import PivxTestFramework
from test_framework.util import (
assert_equal,
p2p_port,
)

import os
import time

class GovernanceInvalidBudgetTest(PivxTestFramework):

def set_test_params(self):
self.setup_clean_chain = True
# 4 nodes:
# - 1 miner/mncontroller
# - 2 remote mns
# - 1 other node to stake a forked chain
self.num_nodes = 4
self.extra_args = [["-sporkkey=932HEevBSujW2ud7RfB1YF91AFygbBRQj3de3LyaCRqNzKKgWXi"],
[],
["-listen", "-externalip=127.0.0.1"],
["-listen", "-externalip=127.0.0.1"],
]
self.enable_mocktime()

self.minerAPos = 0
self.minerBPos = 1
self.remoteOnePos = 1
self.remoteTwoPos = 2

self.masternodeOneAlias = "mnOne"
self.masternodeTwoAlias = "mntwo"

self.mnOnePrivkey = "9247iC59poZmqBYt9iDh9wDam6v9S1rW5XekjLGyPnDhrDkP4AK"
self.mnTwoPrivkey = "92Hkebp3RHdDidGZ7ARgS4orxJAGyFUPDXNqtsYsiwho1HGVRbF"

def run_test(self):
self.minerA = self.nodes[self.minerAPos] # also controller of mn1 and mn2
self.minerB = self.nodes[self.minerBPos]
self.mn1 = self.nodes[self.remoteOnePos]
self.mn2 = self.nodes[self.remoteTwoPos]
self.setupContext()

# Create a proposal and vote on it
next_superblock = self.minerA.getnextsuperblock()
payee = self.minerA.getnewaddress()
self.log.info("Creating a proposal to be paid at block %d" % next_superblock)
proposalFeeTxId = self.minerA.preparebudget("test1", "https://test1.org", 2,
next_superblock, payee, 300)
self.stake_and_ping(self.minerAPos, 3, [self.mn1, self.mn2])
proposalHash = self.minerA.submitbudget("test1", "https://test1.org", 2,
next_superblock, payee, 300, proposalFeeTxId)
time.sleep(1)
self.stake_and_ping(self.minerAPos, 7, [self.mn1, self.mn2])
self.log.info("Vote for the proposal and check projection...")
self.minerA.mnbudgetvote("alias", proposalHash, "yes", self.masternodeOneAlias)
self.minerA.mnbudgetvote("alias", proposalHash, "yes", self.masternodeTwoAlias)
time.sleep(1)
self.stake_and_ping(self.minerAPos, 1, [self.mn1, self.mn2])
projection = self.minerB.getbudgetprojection()[0]
assert_equal(projection["Name"], "test1")
assert_equal(projection["Hash"], proposalHash)
assert_equal(projection["Yeas"], 2)

# Create invalid finalized budget and vote on it
self.log.info("Creating invalid budget finalization...")
self.stake_and_ping(self.minerAPos, 5, [self.mn1, self.mn2])

budgetname = "invalid finalization"
blockstart = self.minerA.getnextsuperblock()
proposals = []
badPropId = "aa0061d705de36385c37701e7632408bd9d2876626b1299a17f7dc818c0ad285"
badPropPayee = "8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"
badPropAmount = 500
proposals.append({"proposalid": badPropId, "payee": badPropPayee, "amount": badPropAmount})
res = self.minerA.createrawmnfinalbudget(budgetname, blockstart, proposals)
assert(res["result"] == "tx_fee_sent")
feeBudgetId = res["id"]
time.sleep(1)
self.stake_and_ping(self.minerAPos, 4, [self.mn1, self.mn2])
res = self.minerA.createrawmnfinalbudget(budgetname, blockstart, proposals, feeBudgetId)
assert(res["result"] == "fin_budget_sent")
budgetFinHash = res["id"]
assert (budgetFinHash != "")
time.sleep(1)

self.log.info("Voting for invalid budget finalization...")
self.minerA.mnfinalbudget("vote-many", budgetFinHash)
self.stake_and_ping(self.minerAPos, 2, [self.mn1, self.mn2])
budFin = self.minerB.mnfinalbudget("show")
budget = budFin[next(iter(budFin))]
assert_equal(budget["VoteCount"], 2)

# Stake up until the block before the superblock.
skip_blocks = next_superblock - self.minerA.getblockcount() - 1
self.stake_and_ping(self.minerAPos, skip_blocks, [self.mn1, self.mn2])

# mine the superblock and check payment, it must not pay to the invalid finalization.
self.log.info("Checking superblock...")
self.stake_and_ping(self.nodes.index(self.minerA), 1, [])
assert_equal(self.minerA.getblockcount(), next_superblock)
coinstake = self.minerA.getrawtransaction(self.minerA.getblock(self.minerA.getbestblockhash())["tx"][1], True)
budget_payment_out = coinstake["vout"][-1]
assert(budget_payment_out["scriptPubKey"]["hex"] != badPropPayee)
assert(budget_payment_out["value"] != badPropAmount)

self.log.info("All good.")

def send_3_pings(self, mn_list):
self.advance_mocktime(30)
self.send_pings(mn_list)
self.stake_and_ping(self.minerAPos, 1, mn_list)
self.advance_mocktime(30)
self.send_pings(mn_list)
time.sleep(2)

def setupContext(self):
# First mine 250 PoW blocks (50 with minerB, 200 with minerA)
self.log.info("Generating 259 blocks...")
for _ in range(2):
for _ in range(25):
self.mocktime = self.generate_pow(self.minerBPos, self.mocktime)
self.sync_blocks()
for _ in range(100):
self.mocktime = self.generate_pow(self.minerAPos, self.mocktime)
self.sync_blocks()
# Then stake 9 blocks with minerA
self.stake_and_ping(self.minerAPos, 9, [])
for n in self.nodes:
assert_equal(n.getblockcount(), 259)

# Setup Masternodes
self.log.info("Masternodes setup...")
ownerdir = os.path.join(self.options.tmpdir, "node%d" % self.minerAPos, "regtest")
self.mnOneCollateral = self.setupMasternode(self.minerA, self.minerA, self.masternodeOneAlias,
ownerdir, self.remoteOnePos, self.mnOnePrivkey)
self.mnTwoCollateral = self.setupMasternode(self.minerA, self.minerA, self.masternodeTwoAlias,
ownerdir, self.remoteTwoPos, self.mnTwoPrivkey)

# Activate masternodes
self.log.info("Masternodes activation...")
self.stake_and_ping(self.minerAPos, 1, [])
time.sleep(3)
self.advance_mocktime(10)
remoteOnePort = p2p_port(self.remoteOnePos)
remoteTwoPort = p2p_port(self.remoteTwoPos)
self.mn1.initmasternode(self.mnOnePrivkey, "127.0.0.1:"+str(remoteOnePort))
self.mn2.initmasternode(self.mnTwoPrivkey, "127.0.0.1:"+str(remoteTwoPort))
self.stake_and_ping(self.minerAPos, 1, [])
self.wait_until_mnsync_finished()
self.controller_start_masternode(self.minerA, self.masternodeOneAlias)
self.controller_start_masternode(self.minerA, self.masternodeTwoAlias)
self.wait_until_mn_preenabled(self.mnOneCollateral.hash, 40)
self.wait_until_mn_preenabled(self.mnOneCollateral.hash, 40)
self.send_3_pings([self.mn1, self.mn2])
self.wait_until_mn_enabled(self.mnOneCollateral.hash, 120, [self.mn1, self.mn2])
self.wait_until_mn_enabled(self.mnOneCollateral.hash, 120, [self.mn1, self.mn2])

# activate sporks
self.log.info("Masternodes enabled. Activating sporks.")
self.activate_spork(self.minerAPos, "SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT")
self.activate_spork(self.minerAPos, "SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT")
self.activate_spork(self.minerAPos, "SPORK_13_ENABLE_SUPERBLOCKS")


if __name__ == '__main__':
GovernanceInvalidBudgetTest().main()

0 comments on commit 18a5bc5

Please sign in to comment.