mirror of
https://github.com/dashpay/dash.git
synced 2024-12-26 04:22:55 +01:00
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:
parent
c2354fb55f
commit
704c594237
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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");
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -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%
|
||||||
|
@ -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]
|
||||||
|
Loading…
Reference in New Issue
Block a user