Line data Source code
1 : // Copyright (c) 2021-2025 The Dash Core developers
2 : // Distributed under the MIT software license, see the accompanying
3 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 :
5 : #include <test/util/setup_common.h>
6 :
7 : #include <bls/bls.h>
8 : #include <evo/deterministicmns.h>
9 : #include <evo/providertx.h>
10 : #include <evo/specialtx.h>
11 : #include <masternode/payments.h>
12 : #include <util/helpers.h>
13 : #include <util/std23.h>
14 :
15 : #include <chainparams.h>
16 : #include <deploymentstatus.h>
17 : #include <node/miner.h>
18 : #include <node/transaction.h>
19 : #include <script/interpreter.h>
20 : #include <script/sign.h>
21 : #include <script/signingprovider.h>
22 : #include <script/standard.h>
23 : #include <validation.h>
24 :
25 : #include <boost/test/unit_test.hpp>
26 :
27 : #include <map>
28 : #include <vector>
29 :
30 : using node::BlockAssembler;
31 : using node::GetTransaction;
32 :
33 : using SimpleUTXOMap = std::map<COutPoint, std::pair<int, CAmount>>;
34 :
35 : struct TestChainBRRBeforeActivationSetup : public TestChainSetup
36 : {
37 : // Force fast DIP3 activation
38 1 : TestChainBRRBeforeActivationSetup() :
39 1 : TestChainSetup(497, CBaseChainParams::REGTEST,
40 1 : {"-dip3params=30:50", "-testactivationheight=brr@1000", "-testactivationheight=v20@1200", "-testactivationheight=mn_rr@2200"})
41 : {
42 1 : }
43 : };
44 :
45 1 : static SimpleUTXOMap BuildSimpleUtxoMap(const std::vector<CTransactionRef>& txs)
46 : {
47 1 : SimpleUTXOMap utxos;
48 1492 : for (auto [i, tx] : std23::views::enumerate(txs)) {
49 994 : for (auto [j, output] : std23::views::enumerate(tx->vout)) {
50 497 : utxos.try_emplace(COutPoint(tx->GetHash(), j), std::make_pair((int)i + 1, output.nValue));
51 : }
52 : }
53 1 : return utxos;
54 1 : }
55 :
56 1 : static std::vector<COutPoint> SelectUTXOs(const CChain& active_chain, SimpleUTXOMap& utoxs, CAmount amount, CAmount& changeRet)
57 : {
58 1 : changeRet = 0;
59 :
60 1 : std::vector<COutPoint> selectedUtxos;
61 1 : CAmount selectedAmount = 0;
62 3 : while (!utoxs.empty()) {
63 3 : bool found = false;
64 3 : for (auto it = utoxs.begin(); it != utoxs.end(); ++it) {
65 3 : if (active_chain.Height() - it->second.first < 101) {
66 0 : continue;
67 : }
68 :
69 3 : found = true;
70 3 : selectedAmount += it->second.second;
71 3 : selectedUtxos.emplace_back(it->first);
72 3 : utoxs.erase(it);
73 3 : break;
74 : }
75 3 : BOOST_REQUIRE(found);
76 3 : if (selectedAmount >= amount) {
77 1 : changeRet = selectedAmount - amount;
78 1 : break;
79 : }
80 : }
81 :
82 1 : return selectedUtxos;
83 1 : }
84 :
85 1 : static void FundTransaction(const CChain& active_chain, CMutableTransaction& tx, SimpleUTXOMap& utoxs, const CScript& scriptPayout, CAmount amount)
86 : {
87 : CAmount change;
88 1 : auto inputs = SelectUTXOs(active_chain, utoxs, amount, change);
89 4 : for (const auto& input : inputs) {
90 3 : tx.vin.emplace_back(input);
91 : }
92 1 : tx.vout.emplace_back(amount, scriptPayout);
93 1 : if (change != 0) {
94 1 : tx.vout.emplace_back(change, scriptPayout);
95 1 : }
96 1 : }
97 :
98 1 : static void SignTransaction(const CTxMemPool& mempool, CMutableTransaction& tx, const CKey& coinbaseKey)
99 : {
100 1 : FillableSigningProvider tempKeystore;
101 1 : tempKeystore.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
102 :
103 7 : for (auto [i, input] : std23::views::enumerate(tx.vin)) {
104 3 : uint256 hashBlock;
105 6 : CTransactionRef txFrom = GetTransaction(/*block_index=*/nullptr, &mempool, input.prevout.hash,
106 3 : Params().GetConsensus(), hashBlock);
107 3 : BOOST_REQUIRE(txFrom);
108 3 : BOOST_REQUIRE(SignSignature(tempKeystore, *txFrom, tx, i, SIGHASH_ALL));
109 3 : }
110 1 : }
111 :
112 1 : static CMutableTransaction CreateProRegTx(const CChain& active_chain, const CTxMemPool& mempool, SimpleUTXOMap& utxos, int port, const CScript& scriptPayout, const CKey& coinbaseKey, CKey& ownerKeyRet, CBLSSecretKey& operatorKeyRet)
113 : {
114 1 : ownerKeyRet.MakeNewKey(true);
115 1 : operatorKeyRet.MakeNewKey();
116 :
117 1 : CProRegTx proTx;
118 1 : proTx.nVersion = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false);
119 1 : proTx.netInfo = NetInfoInterface::MakeNetInfo(proTx.nVersion);
120 1 : proTx.collateralOutpoint.n = 0;
121 1 : BOOST_CHECK_EQUAL(proTx.netInfo->AddEntry(NetInfoPurpose::CORE_P2P, strprintf("1.1.1.1:%d", port)),
122 : NetInfoStatus::Success);
123 1 : proTx.keyIDOwner = ownerKeyRet.GetPubKey().GetID();
124 1 : proTx.pubKeyOperator.Set(operatorKeyRet.GetPublicKey(), bls::bls_legacy_scheme.load());
125 1 : proTx.keyIDVoting = ownerKeyRet.GetPubKey().GetID();
126 1 : proTx.scriptPayout = scriptPayout;
127 :
128 1 : CMutableTransaction tx;
129 1 : tx.nVersion = 3;
130 1 : tx.nType = TRANSACTION_PROVIDER_REGISTER;
131 1 : FundTransaction(active_chain, tx, utxos, scriptPayout, dmn_types::Regular.collat_amount);
132 1 : proTx.inputsHash = CalcTxInputsHash(CTransaction(tx));
133 1 : SetTxPayload(tx, proTx);
134 1 : SignTransaction(mempool, tx, coinbaseKey);
135 :
136 1 : return tx;
137 1 : }
138 :
139 1 : static CScript GenerateRandomAddress()
140 : {
141 1 : CKey key;
142 1 : key.MakeNewKey(false);
143 1 : return GetScriptForDestination(PKHash(key.GetPubKey()));
144 1 : }
145 :
146 146 : BOOST_AUTO_TEST_SUITE(block_reward_reallocation_tests)
147 :
148 148 : BOOST_FIXTURE_TEST_CASE(block_reward_reallocation, TestChainBRRBeforeActivationSetup)
149 : {
150 1 : auto& dmnman = *Assert(m_node.dmnman);
151 1 : const auto& consensus_params = Params().GetConsensus();
152 :
153 1 : CScript coinbasePubKey = GetScriptForRawPubKey(coinbaseKey.GetPubKey());
154 :
155 2 : BOOST_REQUIRE(DeploymentDIP0003Enforced(WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Height()),
156 : consensus_params));
157 :
158 : // Register one MN
159 1 : CKey ownerKey;
160 1 : CBLSSecretKey operatorKey;
161 1 : auto utxos = BuildSimpleUtxoMap(m_coinbase_txns);
162 1 : auto tx = CreateProRegTx(m_node.chainman->ActiveChain(), *m_node.mempool, utxos, 1, GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey);
163 :
164 1 : CreateAndProcessBlock({tx}, coinbasePubKey);
165 :
166 : {
167 1 : LOCK(cs_main);
168 1 : const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()};
169 1 : dmnman.UpdatedBlockTip(tip);
170 :
171 1 : BOOST_REQUIRE(dmnman.GetListAtChainTip().HasMN(tx.GetHash()));
172 :
173 1 : BOOST_CHECK_EQUAL(tip->nHeight, 498);
174 1 : BOOST_CHECK(tip->nHeight < Params().GetConsensus().BRRHeight);
175 1 : }
176 :
177 1 : CreateAndProcessBlock({}, coinbasePubKey);
178 :
179 : {
180 1 : LOCK(cs_main);
181 1 : const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()};
182 1 : BOOST_CHECK_EQUAL(tip->nHeight, 499);
183 1 : dmnman.UpdatedBlockTip(tip);
184 1 : BOOST_REQUIRE(dmnman.GetListAtChainTip().HasMN(tx.GetHash()));
185 1 : BOOST_CHECK(tip->nHeight < Params().GetConsensus().BRRHeight);
186 : // Creating blocks by different ways
187 1 : const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey);
188 1 : }
189 500 : for ([[maybe_unused]] auto _ : util::irange(499)) {
190 499 : CreateAndProcessBlock({}, coinbasePubKey);
191 499 : LOCK(cs_main);
192 499 : dmnman.UpdatedBlockTip(m_node.chainman->ActiveChain().Tip());
193 499 : }
194 1 : BOOST_CHECK(m_node.chainman->ActiveChain().Height() < Params().GetConsensus().BRRHeight);
195 1 : CreateAndProcessBlock({}, coinbasePubKey);
196 :
197 : {
198 : // Advance to ACTIVE at height = (BRRHeight - 1)
199 1 : LOCK(cs_main);
200 1 : const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()};
201 1 : BOOST_CHECK_EQUAL(tip->nHeight, Params().GetConsensus().BRRHeight - 1);
202 1 : dmnman.UpdatedBlockTip(tip);
203 1 : BOOST_REQUIRE(dmnman.GetListAtChainTip().HasMN(tx.GetHash()));
204 1 : }
205 :
206 : {
207 : // Reward split should stay ~50/50 before the first superblock after activation.
208 : // This applies even if reallocation was activated right at superblock height like it does here.
209 : // next block should be signaling by default
210 1 : LOCK(cs_main);
211 1 : const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()};
212 1 : const bool isV20Active{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_V20)};
213 1 : dmnman.UpdatedBlockTip(tip);
214 1 : BOOST_REQUIRE(dmnman.GetListAtChainTip().HasMN(tx.GetHash()));
215 1 : const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active);
216 1 : const CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, isV20Active);
217 1 : const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey);
218 1 : BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[0].nValue, masternode_payment);
219 1 : }
220 :
221 20 : for ([[maybe_unused]] auto _ : util::irange(consensus_params.nSuperblockCycle - 1)) {
222 19 : CreateAndProcessBlock({}, coinbasePubKey);
223 : }
224 :
225 : {
226 1 : LOCK(cs_main);
227 1 : const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()};
228 1 : const bool isV20Active{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_V20)};
229 1 : const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active);
230 1 : const CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, isV20Active);
231 1 : const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey);
232 1 : BOOST_CHECK_EQUAL(pblocktemplate->block.vtx[0]->GetValueOut(), 28847249686);
233 1 : BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[0].nValue, masternode_payment);
234 1 : BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[0].nValue, 14423624841); // 0.4999999999
235 1 : }
236 :
237 : // Reallocation should kick-in with the superblock after 19 adjustments, 3 superblocks long each
238 20 : for ([[maybe_unused]] auto i : util::irange(19)) {
239 76 : for ([[maybe_unused]] auto j : util::irange(3)) {
240 1197 : for ([[maybe_unused]] auto k : util::irange(consensus_params.nSuperblockCycle)) {
241 1140 : CreateAndProcessBlock({}, coinbasePubKey);
242 : }
243 57 : LOCK(cs_main);
244 57 : const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()};
245 57 : const bool isV20Active{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_V20)};
246 57 : const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active);
247 57 : const CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, isV20Active);
248 57 : const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey);
249 57 : BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[0].nValue, masternode_payment);
250 57 : }
251 : }
252 1 : BOOST_CHECK(DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), consensus_params, Consensus::DEPLOYMENT_V20));
253 : // Allocation of block subsidy is 60% MN, 20% miners and 20% treasury
254 : {
255 : // Reward split should reach ~75/25 after reallocation is done
256 1 : LOCK(cs_main);
257 1 : const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()};
258 1 : const bool isV20Active{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_V20)};
259 1 : const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active);
260 1 : const CAmount block_subsidy_sb = GetSuperblockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active);
261 1 : CAmount block_subsidy_potential = block_subsidy + block_subsidy_sb;
262 1 : BOOST_CHECK_EQUAL(block_subsidy_potential, 177167660);
263 1 : CAmount expected_block_reward = block_subsidy_potential - block_subsidy_potential / 5;
264 :
265 1 : const CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, isV20Active);
266 1 : const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey);
267 1 : BOOST_CHECK_EQUAL(pblocktemplate->block.vtx[0]->GetValueOut(), expected_block_reward);
268 1 : BOOST_CHECK_EQUAL(pblocktemplate->block.vtx[0]->GetValueOut(), 141734128);
269 1 : BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[0].nValue, masternode_payment);
270 1 : BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[0].nValue, 106300596); // 0.75
271 1 : }
272 1 : BOOST_CHECK(!DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), consensus_params, Consensus::DEPLOYMENT_MN_RR));
273 :
274 : // Reward split should stay ~75/25 after reallocation is done,
275 : // check 10 next superblocks
276 11 : for ([[maybe_unused]] auto i : util::irange(10)) {
277 210 : for ([[maybe_unused]] auto k : util::irange(consensus_params.nSuperblockCycle)) {
278 200 : CreateAndProcessBlock({}, coinbasePubKey);
279 : }
280 10 : LOCK(cs_main);
281 10 : const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()};
282 10 : const bool isV20Active{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_V20)};
283 10 : const bool isMNRewardReallocated{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_MN_RR)};
284 10 : const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active);
285 10 : CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, isV20Active);
286 10 : const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey);
287 :
288 10 : if (isMNRewardReallocated) {
289 8 : const CAmount platform_payment = PlatformShare(masternode_payment);
290 8 : masternode_payment -= platform_payment;
291 8 : }
292 10 : size_t payment_index = isMNRewardReallocated ? 1 : 0;
293 :
294 10 : BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[payment_index].nValue, masternode_payment);
295 10 : }
296 :
297 1 : BOOST_CHECK(DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), consensus_params, Consensus::DEPLOYMENT_MN_RR));
298 : { // At this moment Masternode reward should be reallocated to platform
299 : // Allocation of block subsidy is 60% MN, 20% miners and 20% treasury
300 1 : LOCK(cs_main);
301 1 : const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()};
302 1 : const bool isV20Active{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_V20)};
303 1 : const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active);
304 1 : const CAmount block_subsidy_sb = GetSuperblockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active);
305 1 : CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, isV20Active);
306 1 : const CAmount platform_payment = PlatformShare(masternode_payment);
307 1 : masternode_payment -= platform_payment;
308 1 : const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey);
309 :
310 1 : CAmount block_subsidy_potential = block_subsidy + block_subsidy_sb;
311 1 : BOOST_CHECK_EQUAL(tip->nHeight, 2358);
312 1 : BOOST_CHECK_EQUAL(block_subsidy_potential, 164512828);
313 : // Treasury is 20% since MNRewardReallocation
314 1 : CAmount expected_block_reward = block_subsidy_potential - block_subsidy_potential / 5;
315 : // Since MNRewardReallocation, MN reward share is 75% of the block reward
316 1 : CAmount expected_masternode_reward = expected_block_reward * 3 / 4;
317 1 : CAmount expected_mn_platform_payment = PlatformShare(expected_masternode_reward);
318 1 : CAmount expected_mn_core_payment = expected_masternode_reward - expected_mn_platform_payment;
319 :
320 1 : BOOST_CHECK_EQUAL(pblocktemplate->block.vtx[0]->GetValueOut(), expected_block_reward);
321 1 : BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[1].nValue, masternode_payment);
322 1 : BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[1].nValue, expected_mn_core_payment);
323 1 : }
324 1 : }
325 :
326 146 : BOOST_AUTO_TEST_SUITE_END()
|