fix: some fixes for block payee validation and corresponding tests (#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)_
This commit is contained in:
UdjinM6 2023-11-13 19:02:52 +03:00 committed by GitHub
parent c2354fb55f
commit 704c594237
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 154 additions and 207 deletions

View File

@ -261,31 +261,36 @@ bool IsBlockValueValid(const CSporkManager& sporkManager, CGovernanceManager& go
return true; return true;
} }
bool IsBlockPayeeValid(const CSporkManager& sporkManager, CGovernanceManager& governanceManager, bool IsBlockPayeeValid(const CSporkManager& sporkManager, CGovernanceManager& governanceManager, const CMasternodeSync& mn_sync,
const CTransaction& txNew, const CBlockIndex* const pindexPrev, const CAmount blockSubsidy, const CAmount feeReward) const CTransaction& txNew, const CBlockIndex* const pindexPrev, const CAmount blockSubsidy, const CAmount feeReward)
{ {
if(fDisableGovernance) { const int nBlockHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1;
//there is no budget data to use to check anything, let's just accept the longest chain
LogPrint(BCLog::MNPAYMENTS, "%s -- WARNING: Not enough data, skipping block payee checks\n", __func__); // Check for correct masternode payment
return true; if (IsTransactionValid(txNew, pindexPrev, blockSubsidy, feeReward)) {
LogPrint(BCLog::MNPAYMENTS, "%s -- Valid masternode payment at height %d: %s", __func__, nBlockHeight, txNew.ToString()); /* Continued */
} else {
LogPrintf("%s -- ERROR: Invalid masternode payment detected at height %d: %s", __func__, nBlockHeight, txNew.ToString()); /* Continued */
return false;
} }
const int nBlockHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1; if (!mn_sync.IsSynced() || fDisableGovernance) {
// we are still using budgets, but we have no data about them anymore, // governance data is either incomplete or non-existent
// we can only check masternode payments LogPrint(BCLog::MNPAYMENTS, "%s -- WARNING: Not enough data, skipping superblock payee checks\n", __func__);
return true; // not an error
}
const Consensus::Params& consensusParams = Params().GetConsensus(); if (nBlockHeight < Params().GetConsensus().nSuperblockStartBlock) {
// We are still using budgets, but we have no data about them anymore,
if(nBlockHeight < consensusParams.nSuperblockStartBlock) { // we can only check masternode payments.
// NOTE: old budget system is disabled since 12.1 and we should never enter this branch // NOTE: old budget system is disabled since 12.1 and we should never enter this branch
// anymore when sync is finished (on mainnet). We have no old budget data but these blocks // anymore when sync is finished (on mainnet). We have no old budget data but these blocks
// have tons of confirmations and can be safely accepted without payee verification // have tons of confirmations and can be safely accepted without payee verification
LogPrint(BCLog::GOBJECT, "%s -- WARNING: Client synced but old budget system is disabled, accepting any payee\n", __func__); LogPrint(BCLog::GOBJECT, "%s -- WARNING: Client synced but old budget system is disabled, accepting any payee\n", __func__);
return true; return true; // not an error
} }
// superblocks started // superblocks started
// SEE IF THIS IS A VALID SUPERBLOCK
if (AreSuperblocksEnabled(sporkManager)) { if (AreSuperblocksEnabled(sporkManager)) {
if (CSuperblockManager::IsSuperblockTriggered(governanceManager, nBlockHeight)) { if (CSuperblockManager::IsSuperblockTriggered(governanceManager, nBlockHeight)) {
@ -305,16 +310,9 @@ bool IsBlockPayeeValid(const CSporkManager& sporkManager, CGovernanceManager& go
LogPrint(BCLog::GOBJECT, "%s -- Superblocks are disabled, no superblocks allowed\n", __func__); LogPrint(BCLog::GOBJECT, "%s -- Superblocks are disabled, no superblocks allowed\n", __func__);
} }
// Check for correct masternode payment
if(IsTransactionValid(txNew, pindexPrev, blockSubsidy, feeReward)) {
LogPrint(BCLog::MNPAYMENTS, "%s -- Valid masternode payment at height %d: %s", __func__, nBlockHeight, txNew.ToString()); /* Continued */
return true; return true;
} }
LogPrintf("%s -- ERROR: Invalid masternode payment detected at height %d: %s", __func__, nBlockHeight, txNew.ToString()); /* Continued */
return false;
}
void FillBlockPayments(const CSporkManager& sporkManager, CGovernanceManager& governanceManager, void FillBlockPayments(const CSporkManager& sporkManager, CGovernanceManager& governanceManager,
CMutableTransaction& txNew, const CBlockIndex* const pindexPrev, const CAmount blockSubsidy, const CAmount feeReward, CMutableTransaction& txNew, const CBlockIndex* const pindexPrev, const CAmount blockSubsidy, const CAmount feeReward,
std::vector<CTxOut>& voutMasternodePaymentsRet, std::vector<CTxOut>& voutSuperblockPaymentsRet) std::vector<CTxOut>& voutMasternodePaymentsRet, std::vector<CTxOut>& voutSuperblockPaymentsRet)

View File

@ -28,7 +28,7 @@ namespace MasternodePayments
{ {
bool IsBlockValueValid(const CSporkManager& sporkManager, CGovernanceManager& governanceManager, const CMasternodeSync& mn_sync, bool IsBlockValueValid(const CSporkManager& sporkManager, CGovernanceManager& governanceManager, const CMasternodeSync& mn_sync,
const CBlock& block, const int nBlockHeight, const CAmount blockReward, std::string& strErrorRet); const CBlock& block, const int nBlockHeight, const CAmount blockReward, std::string& strErrorRet);
bool IsBlockPayeeValid(const CSporkManager& sporkManager, CGovernanceManager& governanceManager, bool IsBlockPayeeValid(const CSporkManager& sporkManager, CGovernanceManager& governanceManager, const CMasternodeSync& mn_sync,
const CTransaction& txNew, const CBlockIndex* const pindexPrev, const CAmount blockSubsidy, const CAmount feeReward); const CTransaction& txNew, const CBlockIndex* const pindexPrev, const CAmount blockSubsidy, const CAmount feeReward);
void FillBlockPayments(const CSporkManager& sporkManager, CGovernanceManager& governanceManager, void FillBlockPayments(const CSporkManager& sporkManager, CGovernanceManager& governanceManager,
CMutableTransaction& txNew, const CBlockIndex* const pindexPrev, const CAmount blockSubsidy, const CAmount feeReward, CMutableTransaction& txNew, const CBlockIndex* const pindexPrev, const CAmount blockSubsidy, const CAmount feeReward,

View File

@ -2416,7 +2416,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
int64_t nTime5_4 = GetTimeMicros(); nTimeValueValid += nTime5_4 - nTime5_3; int64_t nTime5_4 = GetTimeMicros(); nTimeValueValid += nTime5_4 - nTime5_3;
LogPrint(BCLog::BENCHMARK, " - IsBlockValueValid: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime5_4 - nTime5_3), nTimeValueValid * MICRO, nTimeValueValid * MILLI / nBlocksTotal); LogPrint(BCLog::BENCHMARK, " - IsBlockValueValid: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime5_4 - nTime5_3), nTimeValueValid * MICRO, nTimeValueValid * MILLI / nBlocksTotal);
if (!MasternodePayments::IsBlockPayeeValid(*sporkManager, *governance, *block.vtx[0], pindex->pprev, blockSubsidy, feeReward)) { if (!MasternodePayments::IsBlockPayeeValid(*sporkManager, *governance, *::masternodeSync, *block.vtx[0], pindex->pprev, blockSubsidy, feeReward)) {
// NOTE: Do not punish, the node might be missing governance data // NOTE: Do not punish, the node might be missing governance data
LogPrintf("ERROR: ConnectBlock(DASH): couldn't find masternode or superblock payments\n"); LogPrintf("ERROR: ConnectBlock(DASH): couldn't find masternode or superblock payments\n");
return state.Invalid(BlockValidationResult::BLOCK_RESULT_UNSET, "bad-cb-payee"); return state.Invalid(BlockValidationResult::BLOCK_RESULT_UNSET, "bad-cb-payee");

View File

@ -9,8 +9,8 @@
from decimal import Decimal from decimal import Decimal
from test_framework.blocktools import create_block, create_coinbase, get_masternode_payment from test_framework.blocktools import create_block_with_mnpayments
from test_framework.messages import CCbTx, COIN, CTransaction, FromHex, ToHex, uint256_to_string from test_framework.messages import CTransaction, FromHex, ToHex
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, force_finish_mnsync, p2p_port from test_framework.util import assert_equal, force_finish_mnsync, p2p_port
@ -131,7 +131,7 @@ class DIP3Test(BitcoinTestFramework):
self.assert_mnlist(self.nodes[0], mns_tmp) self.assert_mnlist(self.nodes[0], mns_tmp)
self.log.info("cause a reorg with a double spend and check that mnlists are still correct on all nodes") self.log.info("cause a reorg with a double spend and check that mnlists are still correct on all nodes")
self.mine_double_spend(self.nodes[0], dummy_txins, self.nodes[0].getnewaddress(), use_mnmerkleroot_from_tip=True) self.mine_double_spend(mns, self.nodes[0], dummy_txins, self.nodes[0].getnewaddress())
self.nodes[0].generate(spend_mns_count) self.nodes[0].generate(spend_mns_count)
self.sync_all() self.sync_all()
self.assert_mnlists(mns_tmp) self.assert_mnlists(mns_tmp)
@ -139,7 +139,7 @@ class DIP3Test(BitcoinTestFramework):
self.log.info("test mn payment enforcement with deterministic MNs") self.log.info("test mn payment enforcement with deterministic MNs")
for i in range(20): for i in range(20):
node = self.nodes[i % len(self.nodes)] node = self.nodes[i % len(self.nodes)]
self.test_invalid_mn_payment(node) self.test_invalid_mn_payment(mns, node)
self.nodes[0].generate(1) self.nodes[0].generate(1)
self.sync_all() self.sync_all()
@ -218,6 +218,7 @@ class DIP3Test(BitcoinTestFramework):
mn.idx = idx mn.idx = idx
mn.alias = alias mn.alias = alias
mn.p2p_port = p2p_port(mn.idx) mn.p2p_port = p2p_port(mn.idx)
mn.operator_reward = (mn.idx % self.num_initial_mn)
blsKey = node.bls('generate') blsKey = node.bls('generate')
mn.fundsAddr = node.getnewaddress() mn.fundsAddr = node.getnewaddress()
@ -247,7 +248,7 @@ class DIP3Test(BitcoinTestFramework):
mn.collateral_address = node.getnewaddress() mn.collateral_address = node.getnewaddress()
mn.rewards_address = node.getnewaddress() mn.rewards_address = node.getnewaddress()
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) 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)
mn.collateral_txid = mn.protx_hash mn.collateral_txid = mn.protx_hash
mn.collateral_vout = None mn.collateral_vout = None
@ -263,7 +264,7 @@ class DIP3Test(BitcoinTestFramework):
node.sendtoaddress(mn.fundsAddr, 0.001) node.sendtoaddress(mn.fundsAddr, 0.001)
mn.rewards_address = node.getnewaddress() mn.rewards_address = node.getnewaddress()
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) 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)
node.generate(1) node.generate(1)
def start_mn(self, mn): def start_mn(self, mn):
@ -353,93 +354,15 @@ class DIP3Test(BitcoinTestFramework):
return dummy_txin return dummy_txin
def mine_block(self, node, vtx=None, miner_address=None, mn_payee=None, mn_amount=None, use_mnmerkleroot_from_tip=False, expected_error=None): def mine_block(self, mns, node, vtx=None, mn_payee=None, mn_amount=None, expected_error=None):
if vtx is None: block = create_block_with_mnpayments(mns, node, vtx, mn_payee, mn_amount)
vtx = []
bt = node.getblocktemplate()
height = bt['height']
tip_hash = bt['previousblockhash']
tip_block = node.getblock(tip_hash)
coinbasevalue = bt['coinbasevalue']
if miner_address is None:
miner_address = self.nodes[0].getnewaddress()
if mn_payee is None:
if isinstance(bt['masternode'], list):
mn_payee = bt['masternode'][0]['payee']
else:
mn_payee = bt['masternode']['payee']
# we can't take the masternode payee amount from the template here as we might have additional fees in vtx
# calculate fees that the block template included (we'll have to remove it from the coinbase as we won't
# include the template's transactions
bt_fees = 0
for tx in bt['transactions']:
bt_fees += tx['fee']
new_fees = 0
for tx in vtx:
in_value = 0
out_value = 0
for txin in tx.vin:
txout = node.gettxout(uint256_to_string(txin.prevout.hash), txin.prevout.n, False)
in_value += int(txout['value'] * COIN)
for txout in tx.vout:
out_value += txout.nValue
new_fees += in_value - out_value
# fix fees
coinbasevalue -= bt_fees
coinbasevalue += new_fees
if mn_amount is None:
realloc_info = node.getblockchaininfo()['softforks']['realloc']
realloc_height = 99999999
if realloc_info['active']:
realloc_height = realloc_info['height']
mn_amount = get_masternode_payment(height, coinbasevalue, realloc_height)
miner_amount = coinbasevalue - mn_amount
outputs = {miner_address: str(Decimal(miner_amount) / COIN)}
if mn_amount > 0:
outputs[mn_payee] = str(Decimal(mn_amount) / COIN)
coinbase = FromHex(CTransaction(), node.createrawtransaction([], outputs))
coinbase.vin = create_coinbase(height).vin
# We can't really use this one as it would result in invalid merkle roots for masternode lists
if len(bt['coinbase_payload']) != 0:
cbtx = FromHex(CCbTx(version=1), bt['coinbase_payload'])
if use_mnmerkleroot_from_tip:
if 'cbTx' in tip_block:
cbtx.merkleRootMNList = int(tip_block['cbTx']['merkleRootMNList'], 16)
else:
cbtx.merkleRootMNList = 0
coinbase.nVersion = 3
coinbase.nType = 5 # CbTx
coinbase.vExtraPayload = cbtx.serialize()
coinbase.calc_sha256()
block = create_block(int(tip_hash, 16), coinbase)
block.vtx += vtx
# Add quorum commitments from template
for tx in bt['transactions']:
tx2 = FromHex(CTransaction(), tx['data'])
if tx2.nType == 6:
block.vtx.append(tx2)
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()
result = node.submitblock(ToHex(block)) result = node.submitblock(ToHex(block))
if expected_error is not None and result != expected_error: if expected_error is not None and result != expected_error:
raise AssertionError('mining the block should have failed with error %s, but submitblock returned %s' % (expected_error, result)) raise AssertionError('mining the block should have failed with error %s, but submitblock returned %s' % (expected_error, result))
elif expected_error is None and result is not None: elif expected_error is None and result is not None:
raise AssertionError('submitblock returned %s' % (result)) raise AssertionError('submitblock returned %s' % (result))
def mine_double_spend(self, node, txins, target_address, use_mnmerkleroot_from_tip=False): def mine_double_spend(self, mns, node, txins, target_address):
amount = Decimal(0) amount = Decimal(0)
for txin in txins: for txin in txins:
txout = node.gettxout(txin['txid'], txin['vout'], False) txout = node.gettxout(txin['txid'], txin['vout'], False)
@ -450,12 +373,12 @@ class DIP3Test(BitcoinTestFramework):
rawtx = node.signrawtransactionwithwallet(rawtx)['hex'] rawtx = node.signrawtransactionwithwallet(rawtx)['hex']
tx = FromHex(CTransaction(), rawtx) tx = FromHex(CTransaction(), rawtx)
self.mine_block(node, [tx], use_mnmerkleroot_from_tip=use_mnmerkleroot_from_tip) self.mine_block(mns, node, [tx])
def test_invalid_mn_payment(self, node): def test_invalid_mn_payment(self, mns, node):
mn_payee = self.nodes[0].getnewaddress() mn_payee = self.nodes[0].getnewaddress()
self.mine_block(node, mn_payee=mn_payee, expected_error='bad-cb-payee') self.mine_block(mns, node, mn_payee=mn_payee, expected_error='bad-cb-payee')
self.mine_block(node, mn_amount=1, expected_error='bad-cb-payee') self.mine_block(mns, node, mn_amount=1, expected_error='bad-cb-payee')
if __name__ == '__main__': if __name__ == '__main__':
DIP3Test().main() DIP3Test().main()

View File

@ -10,11 +10,10 @@ Checks conflict handling between ChainLocks and InstantSend
''' '''
from decimal import Decimal
import struct import struct
from test_framework.blocktools import get_masternode_payment, create_coinbase, create_block from test_framework.blocktools import create_block_with_mnpayments
from test_framework.messages import CCbTx, CInv, COIN, CTransaction, FromHex, hash256, msg_clsig, msg_inv, ser_string, ToHex, uint256_from_str, uint256_to_string from test_framework.messages import CInv, CTransaction, FromHex, hash256, msg_clsig, msg_inv, ser_string, ToHex, uint256_from_str
from test_framework.mininode import P2PInterface from test_framework.mininode import P2PInterface
from test_framework.test_framework import DashTestFramework from test_framework.test_framework import DashTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error, hex_str_to_bytes, wait_until from test_framework.util import assert_equal, assert_raises_rpc_error, hex_str_to_bytes, wait_until
@ -112,7 +111,7 @@ class LLMQ_IS_CL_Conflicts(DashTestFramework):
self.wait_for_instantlock(rawtx1_txid, node) self.wait_for_instantlock(rawtx1_txid, node)
self.wait_for_instantlock(rawtx4_txid, node) self.wait_for_instantlock(rawtx4_txid, node)
block = self.create_block(self.nodes[0], [rawtx2_obj]) block = create_block_with_mnpayments(self.mninfo, self.nodes[0], [rawtx2_obj])
if test_block_conflict: if test_block_conflict:
# The block shouldn't be accepted/connected but it should be known to node 0 now # The block shouldn't be accepted/connected but it should be known to node 0 now
submit_result = self.nodes[0].submitblock(ToHex(block)) submit_result = self.nodes[0].submitblock(ToHex(block))
@ -234,7 +233,7 @@ class LLMQ_IS_CL_Conflicts(DashTestFramework):
assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent", self.nodes[0].sendrawtransaction, rawtx2) assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent", self.nodes[0].sendrawtransaction, rawtx2)
# Create the block and the corresponding clsig but do not relay clsig yet # Create the block and the corresponding clsig but do not relay clsig yet
cl_block = self.create_block(self.nodes[0]) cl_block = create_block_with_mnpayments(self.mninfo, self.nodes[0])
cl = self.create_chainlock(self.nodes[0].getblockcount() + 1, cl_block) cl = self.create_chainlock(self.nodes[0].getblockcount() + 1, cl_block)
self.nodes[0].submitblock(ToHex(cl_block)) self.nodes[0].submitblock(ToHex(cl_block))
self.sync_all() self.sync_all()
@ -275,74 +274,6 @@ class LLMQ_IS_CL_Conflicts(DashTestFramework):
# Previous tip should be marked as conflicting now # Previous tip should be marked as conflicting now
assert_equal(node.getchaintips(2)[1]["status"], "conflicting") assert_equal(node.getchaintips(2)[1]["status"], "conflicting")
def create_block(self, node, vtx=None):
if vtx is None:
vtx = []
bt = node.getblocktemplate()
height = bt['height']
tip_hash = bt['previousblockhash']
coinbasevalue = bt['coinbasevalue']
miner_address = node.getnewaddress()
mn_payee = bt['masternode'][0]['payee']
# calculate fees that the block template included (we'll have to remove it from the coinbase as we won't
# include the template's transactions
bt_fees = 0
for tx in bt['transactions']:
bt_fees += tx['fee']
new_fees = 0
for tx in vtx:
in_value = 0
out_value = 0
for txin in tx.vin:
txout = node.gettxout(uint256_to_string(txin.prevout.hash), txin.prevout.n, False)
in_value += int(txout['value'] * COIN)
for txout in tx.vout:
out_value += txout.nValue
new_fees += in_value - out_value
# fix fees
coinbasevalue -= bt_fees
coinbasevalue += new_fees
realloc_info = self.nodes[0].getblockchaininfo()['softforks']['realloc']
realloc_height = 99999999
if realloc_info['active']:
realloc_height = realloc_info['height']
mn_amount = get_masternode_payment(height, coinbasevalue, realloc_height)
miner_amount = coinbasevalue - mn_amount
outputs = {miner_address: str(Decimal(miner_amount) / COIN)}
if mn_amount > 0:
outputs[mn_payee] = str(Decimal(mn_amount) / COIN)
coinbase = FromHex(CTransaction(), node.createrawtransaction([], outputs))
coinbase.vin = create_coinbase(height).vin
# We can't really use this one as it would result in invalid merkle roots for masternode lists
if len(bt['coinbase_payload']) != 0:
cbtx = FromHex(CCbTx(version=1), bt['coinbase_payload'])
coinbase.nVersion = 3
coinbase.nType = 5 # CbTx
coinbase.vExtraPayload = cbtx.serialize()
coinbase.calc_sha256()
block = create_block(int(tip_hash, 16), coinbase, ntime=bt['curtime'], version=bt['version'])
block.vtx += vtx
# Add quorum commitments from template
for tx in bt['transactions']:
tx2 = FromHex(CTransaction(), tx['data'])
if tx2.nType == 6:
block.vtx.append(tx2)
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()
return block
def create_chainlock(self, height, block): def create_chainlock(self, height, block):
request_id_buf = ser_string(b"clsig") + struct.pack("<I", height) request_id_buf = ser_string(b"clsig") + struct.pack("<I", height)
request_id = hash256(request_id_buf)[::-1].hex() request_id = hash256(request_id_buf)[::-1].hex()

View File

@ -4,6 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php. # file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Utilities for manipulating blocks and transactions.""" """Utilities for manipulating blocks and transactions."""
from decimal import Decimal
import unittest import unittest
from .messages import ( from .messages import (
@ -14,6 +15,8 @@ from .messages import (
CTransaction, CTransaction,
CTxIn, CTxIn,
CTxOut, CTxOut,
FromHex,
uint256_to_string,
) )
from .script import CScript, CScriptNum, CScriptOp, OP_TRUE, OP_CHECKSIG from .script import CScript, CScriptNum, CScriptOp, OP_TRUE, OP_CHECKSIG
from .util import assert_equal, hex_str_to_bytes from .util import assert_equal, hex_str_to_bytes
@ -43,6 +46,97 @@ def create_block(hashprev, coinbase, ntime=None, *, version=1):
block.calc_sha256() block.calc_sha256()
return block return block
def create_block_with_mnpayments(mninfo, node, vtx=None, mn_payee=None, mn_amount=None):
if vtx is None:
vtx = []
bt = node.getblocktemplate()
height = bt['height']
tip_hash = bt['previousblockhash']
coinbasevalue = bt['coinbasevalue']
assert len(bt['masternode']) <= 2
if mn_payee is None:
mn_payee = bt['masternode'][0]['payee']
mn_operator_payee = None
if len(bt['masternode']) == 2:
mn_operator_payee = bt['masternode'][1]['payee']
# we can't take the masternode payee amount from the template here as we might have additional fees in vtx
# calculate fees that the block template included (we'll have to remove it from the coinbase as we won't
# include the template's transactions
bt_fees = 0
for tx in bt['transactions']:
bt_fees += tx['fee']
new_fees = 0
for tx in vtx:
in_value = 0
out_value = 0
for txin in tx.vin:
txout = node.gettxout(uint256_to_string(txin.prevout.hash), txin.prevout.n, False)
in_value += int(txout['value'] * COIN)
for txout in tx.vout:
out_value += txout.nValue
new_fees += in_value - out_value
# fix fees
coinbasevalue -= bt_fees
coinbasevalue += new_fees
operator_reward = 0
if mn_operator_payee is not None:
for mn in mninfo:
if mn.rewards_address == mn_payee:
operator_reward = mn.operator_reward
break
assert operator_reward > 0
mn_operator_amount = 0
if mn_amount is None:
v20_info = node.getblockchaininfo()['softforks']['v20']
mn_amount_total = get_masternode_payment(height, coinbasevalue, v20_info['active'])
mn_operator_amount = mn_amount_total * operator_reward // 100
mn_amount = mn_amount_total - mn_operator_amount
miner_amount = coinbasevalue - mn_amount - mn_operator_amount
miner_address = node.get_deterministic_priv_key().address
outputs = {miner_address: str(Decimal(miner_amount) / COIN)}
if mn_amount > 0:
outputs[mn_payee] = str(Decimal(mn_amount) / COIN)
if mn_operator_amount > 0:
outputs[mn_operator_payee] = str(Decimal(mn_operator_amount) / COIN)
coinbase = FromHex(CTransaction(), node.createrawtransaction([], outputs))
coinbase.vin = create_coinbase(height).vin
# We can't really use this one as it would result in invalid merkle roots for masternode lists
if len(bt['coinbase_payload']) != 0:
tip_block = node.getblock(tip_hash)
cbtx = FromHex(CCbTx(version=1), bt['coinbase_payload'])
if 'cbTx' in tip_block:
cbtx.merkleRootMNList = int(tip_block['cbTx']['merkleRootMNList'], 16)
else:
cbtx.merkleRootMNList = 0
coinbase.nVersion = 3
coinbase.nType = 5 # CbTx
coinbase.vExtraPayload = cbtx.serialize()
coinbase.calc_sha256()
block = create_block(int(tip_hash, 16), coinbase, ntime=bt['curtime'], version=bt['version'])
block.vtx += vtx
# Add quorum commitments from template
for tx in bt['transactions']:
tx2 = FromHex(CTransaction(), tx['data'])
if tx2.nType == 6:
block.vtx.append(tx2)
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()
return block
def script_BIP34_coinbase_height(height): def script_BIP34_coinbase_height(height):
if height <= 16: if height <= 16:
res = CScriptOp.encode_op_n(height) res = CScriptOp.encode_op_n(height)
@ -128,11 +222,12 @@ def get_legacy_sigopcount_tx(tx, accurate=True):
return count return count
# Identical to GetMasternodePayment in C++ code # Identical to GetMasternodePayment in C++ code
def get_masternode_payment(nHeight, blockValue, nReallocActivationHeight): def get_masternode_payment(nHeight, blockValue, fV20Active):
ret = int(blockValue / 5) ret = int(blockValue / 5)
nMNPIBlock = 350 nMNPIBlock = 350
nMNPIPeriod = 10 nMNPIPeriod = 10
nReallocActivationHeight = 2500
if nHeight > nMNPIBlock: if nHeight > nMNPIBlock:
ret += int(blockValue / 20) ret += int(blockValue / 20)
@ -165,6 +260,12 @@ def get_masternode_payment(nHeight, blockValue, nReallocActivationHeight):
# Activated but we have to wait for the next cycle to start realocation, nothing to do # Activated but we have to wait for the next cycle to start realocation, nothing to do
return ret return ret
if fV20Active:
# Once MNRewardReallocated activates, block reward is 80% of block subsidy (+ tx fees) since treasury is 20%
# Since the MN reward needs to be equal to 60% of the block subsidy (according to the proposal), MN reward is set to 75% of the block reward.
# Previous reallocation periods are dropped.
return blockValue * 3 // 4
# Periods used to reallocate the masternode reward from 50% to 60% # Periods used to reallocate the masternode reward from 50% to 60%
vecPeriods = [ vecPeriods = [
513, # Period 1: 51.3% 513, # Period 1: 51.3%

View File

@ -999,10 +999,12 @@ MASTERNODE_COLLATERAL = 1000
EVONODE_COLLATERAL = 4000 EVONODE_COLLATERAL = 4000
class MasternodeInfo: class MasternodeInfo:
def __init__(self, proTxHash, ownerAddr, votingAddr, pubKeyOperator, keyOperator, collateral_address, collateral_txid, collateral_vout, addr, evo=False): def __init__(self, proTxHash, ownerAddr, votingAddr, rewards_address, operator_reward, pubKeyOperator, keyOperator, collateral_address, collateral_txid, collateral_vout, addr, evo=False):
self.proTxHash = proTxHash self.proTxHash = proTxHash
self.ownerAddr = ownerAddr self.ownerAddr = ownerAddr
self.votingAddr = votingAddr self.votingAddr = votingAddr
self.rewards_address = rewards_address
self.operator_reward = operator_reward
self.pubKeyOperator = pubKeyOperator self.pubKeyOperator = pubKeyOperator
self.keyOperator = keyOperator self.keyOperator = keyOperator
self.collateral_address = collateral_address self.collateral_address = collateral_address
@ -1234,7 +1236,7 @@ class DashTestFramework(BitcoinTestFramework):
self.sync_all(self.nodes) self.sync_all(self.nodes)
assert_equal(self.nodes[0].getrawtransaction(protx_result, 1, tip)['confirmations'], 1) assert_equal(self.nodes[0].getrawtransaction(protx_result, 1, tip)['confirmations'], 1)
mn_info = MasternodeInfo(protx_result, owner_address, voting_address, bls['public'], bls['secret'], collateral_address, collateral_txid, collateral_vout, ipAndPort, evo) mn_info = MasternodeInfo(protx_result, owner_address, voting_address, reward_address, operatorReward, bls['public'], bls['secret'], collateral_address, collateral_txid, collateral_vout, ipAndPort, evo)
self.mninfo.append(mn_info) self.mninfo.append(mn_info)
mn_type_str = "EvoNode" if evo else "MN" mn_type_str = "EvoNode" if evo else "MN"
@ -1273,20 +1275,18 @@ class DashTestFramework(BitcoinTestFramework):
def prepare_masternodes(self): def prepare_masternodes(self):
self.log.info("Preparing %d masternodes" % self.mn_count) self.log.info("Preparing %d masternodes" % self.mn_count)
rewardsAddr = self.nodes[0].getnewaddress()
for idx in range(0, self.mn_count): for idx in range(0, self.mn_count):
self.prepare_masternode(idx, rewardsAddr, False) self.prepare_masternode(idx)
self.sync_all() self.sync_all()
def prepare_masternode(self, idx, rewardsAddr=None, evo=False): def prepare_masternode(self, idx):
register_fund = (idx % 2) == 0 register_fund = (idx % 2) == 0
bls = self.nodes[0].bls('generate') bls = self.nodes[0].bls('generate')
address = self.nodes[0].getnewaddress() address = self.nodes[0].getnewaddress()
collateral_amount = EVONODE_COLLATERAL if evo else MASTERNODE_COLLATERAL collateral_amount = MASTERNODE_COLLATERAL
txid = None txid = None
txid = self.nodes[0].sendtoaddress(address, collateral_amount) txid = self.nodes[0].sendtoaddress(address, collateral_amount)
collateral_vout = 0 collateral_vout = 0
@ -1302,11 +1302,8 @@ class DashTestFramework(BitcoinTestFramework):
self.nodes[0].sendtoaddress(address, 0.001) self.nodes[0].sendtoaddress(address, 0.001)
ownerAddr = self.nodes[0].getnewaddress() ownerAddr = self.nodes[0].getnewaddress()
# votingAddr = self.nodes[0].getnewaddress()
if rewardsAddr is None:
rewardsAddr = self.nodes[0].getnewaddress() rewardsAddr = self.nodes[0].getnewaddress()
votingAddr = ownerAddr votingAddr = ownerAddr
# rewardsAddr = ownerAddr
port = p2p_port(len(self.nodes) + idx) port = p2p_port(len(self.nodes) + idx)
ipAndPort = '127.0.0.1:%d' % port ipAndPort = '127.0.0.1:%d' % port
@ -1315,7 +1312,6 @@ class DashTestFramework(BitcoinTestFramework):
submit = (idx % 4) < 2 submit = (idx % 4) < 2
if register_fund: if register_fund:
# self.nodes[0].lockunspent(True, [{'txid': txid, 'vout': collateral_vout}])
protx_result = self.nodes[0].protx('register_fund', address, ipAndPort, ownerAddr, bls['public'], votingAddr, operatorReward, rewardsAddr, address, submit) protx_result = self.nodes[0].protx('register_fund', address, ipAndPort, ownerAddr, bls['public'], votingAddr, operatorReward, rewardsAddr, address, submit)
else: else:
self.nodes[0].generate(1) self.nodes[0].generate(1)
@ -1331,11 +1327,9 @@ class DashTestFramework(BitcoinTestFramework):
operatorPayoutAddress = self.nodes[0].getnewaddress() operatorPayoutAddress = self.nodes[0].getnewaddress()
self.nodes[0].protx('update_service', proTxHash, ipAndPort, bls['secret'], operatorPayoutAddress, address) self.nodes[0].protx('update_service', proTxHash, ipAndPort, bls['secret'], operatorPayoutAddress, address)
self.mninfo.append(MasternodeInfo(proTxHash, ownerAddr, votingAddr, bls['public'], bls['secret'], address, txid, collateral_vout, ipAndPort, evo)) self.mninfo.append(MasternodeInfo(proTxHash, ownerAddr, votingAddr, rewardsAddr, operatorReward, bls['public'], bls['secret'], address, txid, collateral_vout, ipAndPort, False))
# self.sync_all()
mn_type_str = "EvoNode" if evo else "MN" self.log.info("Prepared MN %d: collateral_txid=%s, collateral_vout=%d, protxHash=%s" % (idx, txid, collateral_vout, proTxHash))
self.log.info("Prepared %s %d: collateral_txid=%s, collateral_vout=%d, protxHash=%s" % (mn_type_str, idx, txid, collateral_vout, proTxHash))
def remove_masternode(self, idx): def remove_masternode(self, idx):
mn = self.mninfo[idx] mn = self.mninfo[idx]