Skip to content

Commit 704c594

Browse files
authored
fix: some fixes for block payee validation and corresponding tests (dashpay#5684)
## Issue being fixed or feature implemented 1. we _should not_ skip masternode payments checks below nSuperblockStartBlock or when governance is disabled 2. we _should_ skip superblock payee checks while we aren't synced yet (should help recovering from missed triggers) ## What was done? pls see individual commits. ## How Has This Been Tested? run tests, sync w/ and w/out `--disablegovernance`, reindexed on testnet ## Breaking Changes n/a ## Checklist: - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_
1 parent c2354fb commit 704c594

File tree

7 files changed

+154
-207
lines changed

7 files changed

+154
-207
lines changed

src/masternode/payments.cpp

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -261,35 +261,40 @@ bool IsBlockValueValid(const CSporkManager& sporkManager, CGovernanceManager& go
261261
return true;
262262
}
263263

264-
bool IsBlockPayeeValid(const CSporkManager& sporkManager, CGovernanceManager& governanceManager,
264+
bool IsBlockPayeeValid(const CSporkManager& sporkManager, CGovernanceManager& governanceManager, const CMasternodeSync& mn_sync,
265265
const CTransaction& txNew, const CBlockIndex* const pindexPrev, const CAmount blockSubsidy, const CAmount feeReward)
266266
{
267-
if(fDisableGovernance) {
268-
//there is no budget data to use to check anything, let's just accept the longest chain
269-
LogPrint(BCLog::MNPAYMENTS, "%s -- WARNING: Not enough data, skipping block payee checks\n", __func__);
270-
return true;
271-
}
272-
273267
const int nBlockHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1;
274-
// we are still using budgets, but we have no data about them anymore,
275-
// we can only check masternode payments
276268

277-
const Consensus::Params& consensusParams = Params().GetConsensus();
269+
// Check for correct masternode payment
270+
if (IsTransactionValid(txNew, pindexPrev, blockSubsidy, feeReward)) {
271+
LogPrint(BCLog::MNPAYMENTS, "%s -- Valid masternode payment at height %d: %s", __func__, nBlockHeight, txNew.ToString()); /* Continued */
272+
} else {
273+
LogPrintf("%s -- ERROR: Invalid masternode payment detected at height %d: %s", __func__, nBlockHeight, txNew.ToString()); /* Continued */
274+
return false;
275+
}
278276

279-
if(nBlockHeight < consensusParams.nSuperblockStartBlock) {
277+
if (!mn_sync.IsSynced() || fDisableGovernance) {
278+
// governance data is either incomplete or non-existent
279+
LogPrint(BCLog::MNPAYMENTS, "%s -- WARNING: Not enough data, skipping superblock payee checks\n", __func__);
280+
return true; // not an error
281+
}
282+
283+
if (nBlockHeight < Params().GetConsensus().nSuperblockStartBlock) {
284+
// We are still using budgets, but we have no data about them anymore,
285+
// we can only check masternode payments.
280286
// NOTE: old budget system is disabled since 12.1 and we should never enter this branch
281287
// anymore when sync is finished (on mainnet). We have no old budget data but these blocks
282288
// have tons of confirmations and can be safely accepted without payee verification
283289
LogPrint(BCLog::GOBJECT, "%s -- WARNING: Client synced but old budget system is disabled, accepting any payee\n", __func__);
284-
return true;
290+
return true; // not an error
285291
}
286292

287293
// superblocks started
288-
// SEE IF THIS IS A VALID SUPERBLOCK
289294

290-
if(AreSuperblocksEnabled(sporkManager)) {
291-
if(CSuperblockManager::IsSuperblockTriggered(governanceManager, nBlockHeight)) {
292-
if(CSuperblockManager::IsValid(governanceManager, txNew, nBlockHeight, blockSubsidy + feeReward)) {
295+
if (AreSuperblocksEnabled(sporkManager)) {
296+
if (CSuperblockManager::IsSuperblockTriggered(governanceManager, nBlockHeight)) {
297+
if (CSuperblockManager::IsValid(governanceManager, txNew, nBlockHeight, blockSubsidy + feeReward)) {
293298
LogPrint(BCLog::GOBJECT, "%s -- Valid superblock at height %d: %s", __func__, nBlockHeight, txNew.ToString()); /* Continued */
294299
// continue validation, should also pay MN
295300
} else {
@@ -305,14 +310,7 @@ bool IsBlockPayeeValid(const CSporkManager& sporkManager, CGovernanceManager& go
305310
LogPrint(BCLog::GOBJECT, "%s -- Superblocks are disabled, no superblocks allowed\n", __func__);
306311
}
307312

308-
// Check for correct masternode payment
309-
if(IsTransactionValid(txNew, pindexPrev, blockSubsidy, feeReward)) {
310-
LogPrint(BCLog::MNPAYMENTS, "%s -- Valid masternode payment at height %d: %s", __func__, nBlockHeight, txNew.ToString()); /* Continued */
311-
return true;
312-
}
313-
314-
LogPrintf("%s -- ERROR: Invalid masternode payment detected at height %d: %s", __func__, nBlockHeight, txNew.ToString()); /* Continued */
315-
return false;
313+
return true;
316314
}
317315

318316
void FillBlockPayments(const CSporkManager& sporkManager, CGovernanceManager& governanceManager,

src/masternode/payments.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace MasternodePayments
2828
{
2929
bool IsBlockValueValid(const CSporkManager& sporkManager, CGovernanceManager& governanceManager, const CMasternodeSync& mn_sync,
3030
const CBlock& block, const int nBlockHeight, const CAmount blockReward, std::string& strErrorRet);
31-
bool IsBlockPayeeValid(const CSporkManager& sporkManager, CGovernanceManager& governanceManager,
31+
bool IsBlockPayeeValid(const CSporkManager& sporkManager, CGovernanceManager& governanceManager, const CMasternodeSync& mn_sync,
3232
const CTransaction& txNew, const CBlockIndex* const pindexPrev, const CAmount blockSubsidy, const CAmount feeReward);
3333
void FillBlockPayments(const CSporkManager& sporkManager, CGovernanceManager& governanceManager,
3434
CMutableTransaction& txNew, const CBlockIndex* const pindexPrev, const CAmount blockSubsidy, const CAmount feeReward,

src/validation.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2416,7 +2416,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
24162416
int64_t nTime5_4 = GetTimeMicros(); nTimeValueValid += nTime5_4 - nTime5_3;
24172417
LogPrint(BCLog::BENCHMARK, " - IsBlockValueValid: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime5_4 - nTime5_3), nTimeValueValid * MICRO, nTimeValueValid * MILLI / nBlocksTotal);
24182418

2419-
if (!MasternodePayments::IsBlockPayeeValid(*sporkManager, *governance, *block.vtx[0], pindex->pprev, blockSubsidy, feeReward)) {
2419+
if (!MasternodePayments::IsBlockPayeeValid(*sporkManager, *governance, *::masternodeSync, *block.vtx[0], pindex->pprev, blockSubsidy, feeReward)) {
24202420
// NOTE: Do not punish, the node might be missing governance data
24212421
LogPrintf("ERROR: ConnectBlock(DASH): couldn't find masternode or superblock payments\n");
24222422
return state.Invalid(BlockValidationResult::BLOCK_RESULT_UNSET, "bad-cb-payee");

test/functional/feature_dip3_deterministicmns.py

Lines changed: 14 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99

1010
from decimal import Decimal
1111

12-
from test_framework.blocktools import create_block, create_coinbase, get_masternode_payment
13-
from test_framework.messages import CCbTx, COIN, CTransaction, FromHex, ToHex, uint256_to_string
12+
from test_framework.blocktools import create_block_with_mnpayments
13+
from test_framework.messages import CTransaction, FromHex, ToHex
1414
from test_framework.test_framework import BitcoinTestFramework
1515
from test_framework.util import assert_equal, force_finish_mnsync, p2p_port
1616

@@ -131,15 +131,15 @@ def run_test(self):
131131
self.assert_mnlist(self.nodes[0], mns_tmp)
132132

133133
self.log.info("cause a reorg with a double spend and check that mnlists are still correct on all nodes")
134-
self.mine_double_spend(self.nodes[0], dummy_txins, self.nodes[0].getnewaddress(), use_mnmerkleroot_from_tip=True)
134+
self.mine_double_spend(mns, self.nodes[0], dummy_txins, self.nodes[0].getnewaddress())
135135
self.nodes[0].generate(spend_mns_count)
136136
self.sync_all()
137137
self.assert_mnlists(mns_tmp)
138138

139139
self.log.info("test mn payment enforcement with deterministic MNs")
140140
for i in range(20):
141141
node = self.nodes[i % len(self.nodes)]
142-
self.test_invalid_mn_payment(node)
142+
self.test_invalid_mn_payment(mns, node)
143143
self.nodes[0].generate(1)
144144
self.sync_all()
145145

@@ -218,6 +218,7 @@ def prepare_mn(self, node, idx, alias):
218218
mn.idx = idx
219219
mn.alias = alias
220220
mn.p2p_port = p2p_port(mn.idx)
221+
mn.operator_reward = (mn.idx % self.num_initial_mn)
221222

222223
blsKey = node.bls('generate')
223224
mn.fundsAddr = node.getnewaddress()
@@ -247,7 +248,7 @@ def register_fund_mn(self, node, mn):
247248
mn.collateral_address = node.getnewaddress()
248249
mn.rewards_address = node.getnewaddress()
249250

250-
mn.protx_hash = node.protx('register_fund', mn.collateral_address, '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, 0, mn.rewards_address, mn.fundsAddr)
251+
mn.protx_hash = node.protx('register_fund', mn.collateral_address, '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, mn.operator_reward, mn.rewards_address, mn.fundsAddr)
251252
mn.collateral_txid = mn.protx_hash
252253
mn.collateral_vout = None
253254

@@ -263,7 +264,7 @@ def register_mn(self, node, mn):
263264
node.sendtoaddress(mn.fundsAddr, 0.001)
264265
mn.rewards_address = node.getnewaddress()
265266

266-
mn.protx_hash = node.protx('register', mn.collateral_txid, mn.collateral_vout, '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, 0, mn.rewards_address, mn.fundsAddr)
267+
mn.protx_hash = node.protx('register', mn.collateral_txid, mn.collateral_vout, '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, mn.operator_reward, mn.rewards_address, mn.fundsAddr)
267268
node.generate(1)
268269

269270
def start_mn(self, mn):
@@ -353,93 +354,15 @@ def spend_input(self, txid, vout, amount, with_dummy_input_output=False):
353354

354355
return dummy_txin
355356

356-
def mine_block(self, node, vtx=None, miner_address=None, mn_payee=None, mn_amount=None, use_mnmerkleroot_from_tip=False, expected_error=None):
357-
if vtx is None:
358-
vtx = []
359-
bt = node.getblocktemplate()
360-
height = bt['height']
361-
tip_hash = bt['previousblockhash']
362-
363-
tip_block = node.getblock(tip_hash)
364-
365-
coinbasevalue = bt['coinbasevalue']
366-
if miner_address is None:
367-
miner_address = self.nodes[0].getnewaddress()
368-
if mn_payee is None:
369-
if isinstance(bt['masternode'], list):
370-
mn_payee = bt['masternode'][0]['payee']
371-
else:
372-
mn_payee = bt['masternode']['payee']
373-
# we can't take the masternode payee amount from the template here as we might have additional fees in vtx
374-
375-
# calculate fees that the block template included (we'll have to remove it from the coinbase as we won't
376-
# include the template's transactions
377-
bt_fees = 0
378-
for tx in bt['transactions']:
379-
bt_fees += tx['fee']
380-
381-
new_fees = 0
382-
for tx in vtx:
383-
in_value = 0
384-
out_value = 0
385-
for txin in tx.vin:
386-
txout = node.gettxout(uint256_to_string(txin.prevout.hash), txin.prevout.n, False)
387-
in_value += int(txout['value'] * COIN)
388-
for txout in tx.vout:
389-
out_value += txout.nValue
390-
new_fees += in_value - out_value
391-
392-
# fix fees
393-
coinbasevalue -= bt_fees
394-
coinbasevalue += new_fees
395-
396-
if mn_amount is None:
397-
realloc_info = node.getblockchaininfo()['softforks']['realloc']
398-
realloc_height = 99999999
399-
if realloc_info['active']:
400-
realloc_height = realloc_info['height']
401-
mn_amount = get_masternode_payment(height, coinbasevalue, realloc_height)
402-
miner_amount = coinbasevalue - mn_amount
403-
404-
outputs = {miner_address: str(Decimal(miner_amount) / COIN)}
405-
if mn_amount > 0:
406-
outputs[mn_payee] = str(Decimal(mn_amount) / COIN)
407-
408-
coinbase = FromHex(CTransaction(), node.createrawtransaction([], outputs))
409-
coinbase.vin = create_coinbase(height).vin
410-
411-
# We can't really use this one as it would result in invalid merkle roots for masternode lists
412-
if len(bt['coinbase_payload']) != 0:
413-
cbtx = FromHex(CCbTx(version=1), bt['coinbase_payload'])
414-
if use_mnmerkleroot_from_tip:
415-
if 'cbTx' in tip_block:
416-
cbtx.merkleRootMNList = int(tip_block['cbTx']['merkleRootMNList'], 16)
417-
else:
418-
cbtx.merkleRootMNList = 0
419-
coinbase.nVersion = 3
420-
coinbase.nType = 5 # CbTx
421-
coinbase.vExtraPayload = cbtx.serialize()
422-
423-
coinbase.calc_sha256()
424-
425-
block = create_block(int(tip_hash, 16), coinbase)
426-
block.vtx += vtx
427-
428-
# Add quorum commitments from template
429-
for tx in bt['transactions']:
430-
tx2 = FromHex(CTransaction(), tx['data'])
431-
if tx2.nType == 6:
432-
block.vtx.append(tx2)
433-
434-
block.hashMerkleRoot = block.calc_merkle_root()
435-
block.solve()
357+
def mine_block(self, mns, node, vtx=None, mn_payee=None, mn_amount=None, expected_error=None):
358+
block = create_block_with_mnpayments(mns, node, vtx, mn_payee, mn_amount)
436359
result = node.submitblock(ToHex(block))
437360
if expected_error is not None and result != expected_error:
438361
raise AssertionError('mining the block should have failed with error %s, but submitblock returned %s' % (expected_error, result))
439362
elif expected_error is None and result is not None:
440363
raise AssertionError('submitblock returned %s' % (result))
441364

442-
def mine_double_spend(self, node, txins, target_address, use_mnmerkleroot_from_tip=False):
365+
def mine_double_spend(self, mns, node, txins, target_address):
443366
amount = Decimal(0)
444367
for txin in txins:
445368
txout = node.gettxout(txin['txid'], txin['vout'], False)
@@ -450,12 +373,12 @@ def mine_double_spend(self, node, txins, target_address, use_mnmerkleroot_from_t
450373
rawtx = node.signrawtransactionwithwallet(rawtx)['hex']
451374
tx = FromHex(CTransaction(), rawtx)
452375

453-
self.mine_block(node, [tx], use_mnmerkleroot_from_tip=use_mnmerkleroot_from_tip)
376+
self.mine_block(mns, node, [tx])
454377

455-
def test_invalid_mn_payment(self, node):
378+
def test_invalid_mn_payment(self, mns, node):
456379
mn_payee = self.nodes[0].getnewaddress()
457-
self.mine_block(node, mn_payee=mn_payee, expected_error='bad-cb-payee')
458-
self.mine_block(node, mn_amount=1, expected_error='bad-cb-payee')
380+
self.mine_block(mns, node, mn_payee=mn_payee, expected_error='bad-cb-payee')
381+
self.mine_block(mns, node, mn_amount=1, expected_error='bad-cb-payee')
459382

460383
if __name__ == '__main__':
461384
DIP3Test().main()

test/functional/feature_llmq_is_cl_conflicts.py

Lines changed: 4 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@
1010
1111
'''
1212

13-
from decimal import Decimal
1413
import struct
1514

16-
from test_framework.blocktools import get_masternode_payment, create_coinbase, create_block
17-
from test_framework.messages import CCbTx, CInv, COIN, CTransaction, FromHex, hash256, msg_clsig, msg_inv, ser_string, ToHex, uint256_from_str, uint256_to_string
15+
from test_framework.blocktools import create_block_with_mnpayments
16+
from test_framework.messages import CInv, CTransaction, FromHex, hash256, msg_clsig, msg_inv, ser_string, ToHex, uint256_from_str
1817
from test_framework.mininode import P2PInterface
1918
from test_framework.test_framework import DashTestFramework
2019
from test_framework.util import assert_equal, assert_raises_rpc_error, hex_str_to_bytes, wait_until
@@ -112,7 +111,7 @@ def test_chainlock_overrides_islock(self, test_block_conflict, mine_confllicting
112111
self.wait_for_instantlock(rawtx1_txid, node)
113112
self.wait_for_instantlock(rawtx4_txid, node)
114113

115-
block = self.create_block(self.nodes[0], [rawtx2_obj])
114+
block = create_block_with_mnpayments(self.mninfo, self.nodes[0], [rawtx2_obj])
116115
if test_block_conflict:
117116
# The block shouldn't be accepted/connected but it should be known to node 0 now
118117
submit_result = self.nodes[0].submitblock(ToHex(block))
@@ -234,7 +233,7 @@ def test_chainlock_overrides_islock_overrides_nonchainlock(self, deterministic):
234233
assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent", self.nodes[0].sendrawtransaction, rawtx2)
235234

236235
# Create the block and the corresponding clsig but do not relay clsig yet
237-
cl_block = self.create_block(self.nodes[0])
236+
cl_block = create_block_with_mnpayments(self.mninfo, self.nodes[0])
238237
cl = self.create_chainlock(self.nodes[0].getblockcount() + 1, cl_block)
239238
self.nodes[0].submitblock(ToHex(cl_block))
240239
self.sync_all()
@@ -275,74 +274,6 @@ def test_chainlock_overrides_islock_overrides_nonchainlock(self, deterministic):
275274
# Previous tip should be marked as conflicting now
276275
assert_equal(node.getchaintips(2)[1]["status"], "conflicting")
277276

278-
def create_block(self, node, vtx=None):
279-
if vtx is None:
280-
vtx = []
281-
bt = node.getblocktemplate()
282-
height = bt['height']
283-
tip_hash = bt['previousblockhash']
284-
285-
coinbasevalue = bt['coinbasevalue']
286-
miner_address = node.getnewaddress()
287-
mn_payee = bt['masternode'][0]['payee']
288-
289-
# calculate fees that the block template included (we'll have to remove it from the coinbase as we won't
290-
# include the template's transactions
291-
bt_fees = 0
292-
for tx in bt['transactions']:
293-
bt_fees += tx['fee']
294-
295-
new_fees = 0
296-
for tx in vtx:
297-
in_value = 0
298-
out_value = 0
299-
for txin in tx.vin:
300-
txout = node.gettxout(uint256_to_string(txin.prevout.hash), txin.prevout.n, False)
301-
in_value += int(txout['value'] * COIN)
302-
for txout in tx.vout:
303-
out_value += txout.nValue
304-
new_fees += in_value - out_value
305-
306-
# fix fees
307-
coinbasevalue -= bt_fees
308-
coinbasevalue += new_fees
309-
310-
realloc_info = self.nodes[0].getblockchaininfo()['softforks']['realloc']
311-
realloc_height = 99999999
312-
if realloc_info['active']:
313-
realloc_height = realloc_info['height']
314-
mn_amount = get_masternode_payment(height, coinbasevalue, realloc_height)
315-
miner_amount = coinbasevalue - mn_amount
316-
317-
outputs = {miner_address: str(Decimal(miner_amount) / COIN)}
318-
if mn_amount > 0:
319-
outputs[mn_payee] = str(Decimal(mn_amount) / COIN)
320-
321-
coinbase = FromHex(CTransaction(), node.createrawtransaction([], outputs))
322-
coinbase.vin = create_coinbase(height).vin
323-
324-
# We can't really use this one as it would result in invalid merkle roots for masternode lists
325-
if len(bt['coinbase_payload']) != 0:
326-
cbtx = FromHex(CCbTx(version=1), bt['coinbase_payload'])
327-
coinbase.nVersion = 3
328-
coinbase.nType = 5 # CbTx
329-
coinbase.vExtraPayload = cbtx.serialize()
330-
331-
coinbase.calc_sha256()
332-
333-
block = create_block(int(tip_hash, 16), coinbase, ntime=bt['curtime'], version=bt['version'])
334-
block.vtx += vtx
335-
336-
# Add quorum commitments from template
337-
for tx in bt['transactions']:
338-
tx2 = FromHex(CTransaction(), tx['data'])
339-
if tx2.nType == 6:
340-
block.vtx.append(tx2)
341-
342-
block.hashMerkleRoot = block.calc_merkle_root()
343-
block.solve()
344-
return block
345-
346277
def create_chainlock(self, height, block):
347278
request_id_buf = ser_string(b"clsig") + struct.pack("<I", height)
348279
request_id = hash256(request_id_buf)[::-1].hex()

0 commit comments

Comments
 (0)