Line data Source code
1 : // Copyright (c) 2018-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 <chainparams.h>
8 : #include <consensus/validation.h>
9 : #include <deploymentstatus.h>
10 : #include <evo/deterministicmns.h>
11 : #include <evo/providertx.h>
12 : #include <evo/simplifiedmns.h>
13 : #include <evo/specialtx.h>
14 : #include <evo/specialtxman.h>
15 : #include <llmq/context.h>
16 : #include <messagesigner.h>
17 : #include <node/transaction.h>
18 : #include <policy/policy.h>
19 : #include <script/interpreter.h>
20 : #include <script/sign.h>
21 : #include <script/signingprovider.h>
22 : #include <script/standard.h>
23 : #include <test/util/txmempool.h>
24 : #include <txmempool.h>
25 : #include <validation.h>
26 :
27 : #include <boost/test/unit_test.hpp>
28 :
29 : #include <vector>
30 :
31 : using node::GetTransaction;
32 :
33 : using SimpleUTXOMap = std::map<COutPoint, std::pair<int, CAmount>>;
34 :
35 9 : static SimpleUTXOMap BuildSimpleUtxoMap(const std::vector<CTransactionRef>& txs)
36 : {
37 9 : SimpleUTXOMap utxos;
38 4135 : for (size_t i = 0; i < txs.size(); i++) {
39 4126 : auto& tx = txs[i];
40 8252 : for (size_t j = 0; j < tx->vout.size(); j++) {
41 4126 : utxos.emplace(COutPoint(tx->GetHash(), j), std::make_pair((int)i + 1, tx->vout[j].nValue));
42 4126 : }
43 4126 : }
44 9 : return utxos;
45 9 : }
46 :
47 54 : static std::vector<COutPoint> SelectUTXOs(const CChain& active_chain, SimpleUTXOMap& utoxs, CAmount amount, CAmount& changeRet)
48 : {
49 54 : changeRet = 0;
50 :
51 54 : std::vector<COutPoint> selectedUtxos;
52 54 : CAmount selectedAmount = 0;
53 125 : while (!utoxs.empty()) {
54 124 : bool found = false;
55 685 : for (auto it = utoxs.begin(); it != utoxs.end(); ++it) {
56 685 : if (active_chain.Height() - it->second.first < 101) {
57 561 : continue;
58 : }
59 :
60 124 : found = true;
61 124 : selectedAmount += it->second.second;
62 124 : selectedUtxos.emplace_back(it->first);
63 124 : utoxs.erase(it);
64 124 : break;
65 : }
66 124 : BOOST_REQUIRE(found);
67 124 : if (selectedAmount >= amount) {
68 53 : changeRet = selectedAmount - amount;
69 53 : break;
70 : }
71 : }
72 :
73 54 : return selectedUtxos;
74 54 : }
75 :
76 54 : static void FundTransaction(const CChain& active_chain, CMutableTransaction& tx, SimpleUTXOMap& utoxs, const CScript& scriptPayout, CAmount amount, const CKey& coinbaseKey)
77 : {
78 : CAmount change;
79 54 : auto inputs = SelectUTXOs(active_chain, utoxs, amount, change);
80 178 : for (size_t i = 0; i < inputs.size(); i++) {
81 124 : tx.vin.emplace_back(CTxIn(inputs[i]));
82 124 : }
83 54 : tx.vout.emplace_back(CTxOut(amount, scriptPayout));
84 54 : if (change != 0) {
85 39 : tx.vout.emplace_back(CTxOut(change, scriptPayout));
86 39 : }
87 54 : }
88 :
89 56 : static void SignTransaction(const CTxMemPool& mempool, CMutableTransaction& tx, const CKey& coinbaseKey)
90 : {
91 56 : FillableSigningProvider tempKeystore;
92 56 : tempKeystore.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
93 :
94 186 : for (size_t i = 0; i < tx.vin.size(); i++) {
95 130 : uint256 hashBlock;
96 130 : CTransactionRef txFrom = GetTransaction(/*block_index=*/nullptr, &mempool, tx.vin[i].prevout.hash,
97 130 : Params().GetConsensus(), hashBlock);
98 130 : BOOST_REQUIRE(txFrom);
99 130 : BOOST_REQUIRE(SignSignature(tempKeystore, *txFrom, tx, i, SIGHASH_ALL));
100 130 : }
101 56 : }
102 :
103 34 : static CMutableTransaction CreateProRegTx(const CChain& active_chain, const CTxMemPool& mempool, SimpleUTXOMap& utxos, int port, const CScript& scriptPayout, const CKey& coinbaseKey, CKey& ownerKeyRet, CBLSSecretKey& operatorKeyRet)
104 : {
105 34 : ownerKeyRet.MakeNewKey(true);
106 34 : operatorKeyRet.MakeNewKey();
107 :
108 34 : CProRegTx proTx;
109 34 : proTx.nVersion = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false);
110 34 : proTx.netInfo = NetInfoInterface::MakeNetInfo(proTx.nVersion);
111 34 : proTx.collateralOutpoint.n = 0;
112 34 : BOOST_CHECK_EQUAL(proTx.netInfo->AddEntry(NetInfoPurpose::CORE_P2P, strprintf("1.1.1.1:%d", port)),
113 : NetInfoStatus::Success);
114 34 : proTx.keyIDOwner = ownerKeyRet.GetPubKey().GetID();
115 34 : proTx.pubKeyOperator.Set(operatorKeyRet.GetPublicKey(), bls::bls_legacy_scheme.load());
116 34 : proTx.keyIDVoting = ownerKeyRet.GetPubKey().GetID();
117 34 : proTx.scriptPayout = scriptPayout;
118 :
119 34 : CMutableTransaction tx;
120 34 : tx.nVersion = 3;
121 34 : tx.nType = TRANSACTION_PROVIDER_REGISTER;
122 34 : FundTransaction(active_chain, tx, utxos, scriptPayout, dmn_types::Regular.collat_amount, coinbaseKey);
123 34 : proTx.inputsHash = CalcTxInputsHash(CTransaction(tx));
124 34 : SetTxPayload(tx, proTx);
125 34 : SignTransaction(mempool, tx, coinbaseKey);
126 :
127 34 : return tx;
128 34 : }
129 :
130 6 : static CMutableTransaction CreateProUpServTx(const CChain& active_chain, const CTxMemPool& mempool, SimpleUTXOMap& utxos, const uint256& proTxHash, const CBLSSecretKey& operatorKey, int port, const CScript& scriptOperatorPayout, const CKey& coinbaseKey)
131 : {
132 6 : CProUpServTx proTx;
133 6 : proTx.nVersion = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false);
134 6 : proTx.netInfo = NetInfoInterface::MakeNetInfo(proTx.nVersion);
135 6 : proTx.proTxHash = proTxHash;
136 6 : BOOST_CHECK_EQUAL(proTx.netInfo->AddEntry(NetInfoPurpose::CORE_P2P, strprintf("1.1.1.1:%d", port)),
137 : NetInfoStatus::Success);
138 6 : proTx.scriptOperatorPayout = scriptOperatorPayout;
139 :
140 6 : CMutableTransaction tx;
141 6 : tx.nVersion = 3;
142 6 : tx.nType = TRANSACTION_PROVIDER_UPDATE_SERVICE;
143 6 : FundTransaction(active_chain, tx, utxos, GetScriptForDestination(PKHash(coinbaseKey.GetPubKey())), 1 * COIN, coinbaseKey);
144 6 : proTx.inputsHash = CalcTxInputsHash(CTransaction(tx));
145 6 : proTx.sig = operatorKey.Sign(::SerializeHash(proTx), bls::bls_legacy_scheme);
146 6 : SetTxPayload(tx, proTx);
147 6 : SignTransaction(mempool, tx, coinbaseKey);
148 :
149 6 : return tx;
150 6 : }
151 :
152 3 : static CMutableTransaction CreateProUpRegTx(const CChain& active_chain, const CTxMemPool& mempool, SimpleUTXOMap& utxos, const uint256& proTxHash, const CKey& mnKey, const CBLSPublicKey& pubKeyOperator, const CKeyID& keyIDVoting, const CScript& scriptPayout, const CKey& coinbaseKey)
153 : {
154 3 : CProUpRegTx proTx;
155 3 : proTx.nVersion = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false);
156 3 : proTx.proTxHash = proTxHash;
157 3 : proTx.pubKeyOperator.Set(pubKeyOperator, bls::bls_legacy_scheme.load());
158 3 : proTx.keyIDVoting = keyIDVoting;
159 3 : proTx.scriptPayout = scriptPayout;
160 :
161 3 : CMutableTransaction tx;
162 3 : tx.nVersion = 3;
163 3 : tx.nType = TRANSACTION_PROVIDER_UPDATE_REGISTRAR;
164 3 : FundTransaction(active_chain, tx, utxos, GetScriptForDestination(PKHash(coinbaseKey.GetPubKey())), 1 * COIN, coinbaseKey);
165 3 : proTx.inputsHash = CalcTxInputsHash(CTransaction(tx));
166 3 : CHashSigner::SignHash(::SerializeHash(proTx), mnKey, proTx.vchSig);
167 3 : SetTxPayload(tx, proTx);
168 3 : SignTransaction(mempool, tx, coinbaseKey);
169 :
170 3 : return tx;
171 3 : }
172 :
173 3 : static CMutableTransaction CreateProUpRevTx(const CChain& active_chain, const CTxMemPool& mempool, SimpleUTXOMap& utxos, const uint256& proTxHash, const CBLSSecretKey& operatorKey, const CKey& coinbaseKey)
174 : {
175 3 : CProUpRevTx proTx;
176 3 : proTx.nVersion = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false);
177 3 : proTx.proTxHash = proTxHash;
178 :
179 3 : CMutableTransaction tx;
180 3 : tx.nVersion = 3;
181 3 : tx.nType = TRANSACTION_PROVIDER_UPDATE_REVOKE;
182 3 : FundTransaction(active_chain, tx, utxos, GetScriptForDestination(PKHash(coinbaseKey.GetPubKey())), 1 * COIN, coinbaseKey);
183 3 : proTx.inputsHash = CalcTxInputsHash(CTransaction(tx));
184 3 : proTx.sig = operatorKey.Sign(::SerializeHash(proTx), bls::bls_legacy_scheme);
185 3 : SetTxPayload(tx, proTx);
186 3 : SignTransaction(mempool, tx, coinbaseKey);
187 :
188 3 : return tx;
189 3 : }
190 :
191 : template<typename ProTx>
192 14 : static CMutableTransaction MalleateProTxPayout(const CMutableTransaction& tx)
193 : {
194 14 : auto opt_protx = GetTxPayload<ProTx>(tx);
195 14 : BOOST_REQUIRE(opt_protx.has_value());
196 14 : auto& protx = *opt_protx;
197 :
198 14 : CKey key;
199 14 : key.MakeNewKey(false);
200 14 : protx.scriptPayout = GetScriptForDestination(PKHash(key.GetPubKey()));
201 :
202 14 : CMutableTransaction tx2 = tx;
203 14 : SetTxPayload(tx2, protx);
204 :
205 14 : return tx2;
206 14 : }
207 :
208 32 : static CScript GenerateRandomAddress()
209 : {
210 32 : CKey key;
211 32 : key.MakeNewKey(false);
212 32 : return GetScriptForDestination(PKHash(key.GetPubKey()));
213 32 : }
214 :
215 120 : static CDeterministicMNCPtr FindPayoutDmn(CDeterministicMNManager& dmnman, const CBlock& block)
216 : {
217 120 : auto dmnList = dmnman.GetListAtChainTip();
218 :
219 360 : for (const auto& txout : block.vtx[0]->vout) {
220 360 : CDeterministicMNCPtr found;
221 4560 : dmnList.ForEachMNShared(/*onlyValid=*/true, [&](const auto& dmn) {
222 4200 : if (found == nullptr && txout.scriptPubKey == dmn->pdmnState->scriptPayout) {
223 120 : found = dmn;
224 120 : }
225 4200 : });
226 360 : if (found != nullptr) {
227 120 : return found;
228 : }
229 360 : }
230 0 : return nullptr;
231 120 : }
232 :
233 28 : static bool CheckTransactionSignature(const CTxMemPool& mempool, const CMutableTransaction& tx)
234 : {
235 61 : for (unsigned int i = 0; i < tx.vin.size(); i++) {
236 47 : const auto& txin = tx.vin[i];
237 47 : uint256 hashBlock;
238 94 : CTransactionRef txFrom = GetTransaction(/*block_index=*/nullptr, &mempool, txin.prevout.hash,
239 47 : Params().GetConsensus(), hashBlock);
240 47 : BOOST_REQUIRE(txFrom);
241 :
242 47 : CAmount amount = txFrom->vout[txin.prevout.n].nValue;
243 47 : if (!VerifyScript(txin.scriptSig, txFrom->vout[txin.prevout.n].scriptPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker(&tx, i, amount, MissingDataBehavior::ASSERT_FAIL))) {
244 14 : return false;
245 : }
246 47 : }
247 14 : return true;
248 28 : }
249 :
250 1 : void FuncDIP3Activation(TestChainSetup& setup)
251 : {
252 1 : auto& chainman = *Assert(setup.m_node.chainman.get());
253 1 : auto& dmnman = *Assert(setup.m_node.dmnman);
254 :
255 1 : auto utxos = BuildSimpleUtxoMap(setup.m_coinbase_txns);
256 1 : CKey ownerKey;
257 1 : CBLSSecretKey operatorKey;
258 1 : CTxDestination payoutDest = DecodeDestination("yRq1Ky1AfFmf597rnotj7QRxsDUKePVWNF");
259 1 : auto tx = CreateProRegTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, 1, GetScriptForDestination(payoutDest), setup.coinbaseKey, ownerKey, operatorKey);
260 1 : std::vector<CMutableTransaction> txns = {tx};
261 :
262 1 : const CScript coinbase_pk = GetScriptForRawPubKey(setup.coinbaseKey.GetPubKey());
263 1 : int nHeight = chainman.ActiveChain().Height();
264 :
265 : // We start one block before DIP3 activation, so mining a block with a DIP3 transaction should fail
266 1 : auto block = std::make_shared<CBlock>(setup.CreateBlock(txns, coinbase_pk, chainman.ActiveChainstate()));
267 1 : chainman.ProcessNewBlock(block, true, nullptr);
268 1 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight);
269 1 : BOOST_REQUIRE(block->GetHash() != chainman.ActiveChain().Tip()->GetBlockHash());
270 1 : BOOST_REQUIRE(!dmnman.GetListAtChainTip().HasMN(tx.GetHash()));
271 :
272 : // This block should activate DIP3
273 1 : setup.CreateAndProcessBlock({}, coinbase_pk);
274 1 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1);
275 : // Mining a block with a DIP3 transaction should succeed now
276 1 : block = std::make_shared<CBlock>(setup.CreateBlock(txns, coinbase_pk, chainman.ActiveChainstate()));
277 1 : BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr));
278 1 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
279 1 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 2);
280 1 : BOOST_CHECK_EQUAL(block->GetHash(), chainman.ActiveChain().Tip()->GetBlockHash());
281 1 : BOOST_REQUIRE(dmnman.GetListAtChainTip().HasMN(tx.GetHash()));
282 1 : };
283 :
284 1 : void FuncV19Activation(TestChainSetup& setup)
285 : {
286 1 : auto& chainman = *Assert(setup.m_node.chainman.get());
287 1 : auto& dmnman = *Assert(setup.m_node.dmnman);
288 :
289 1 : BOOST_REQUIRE(!DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman.GetConsensus(), Consensus::DEPLOYMENT_V19));
290 :
291 : // create
292 1 : auto utxos = BuildSimpleUtxoMap(setup.m_coinbase_txns);
293 1 : CKey owner_key;
294 1 : CBLSSecretKey operator_key;
295 1 : CKey collateral_key;
296 1 : collateral_key.MakeNewKey(false);
297 1 : auto collateralScript = GetScriptForDestination(PKHash(collateral_key.GetPubKey()));
298 1 : auto tx_reg = CreateProRegTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, 1, collateralScript, setup.coinbaseKey, owner_key, operator_key);
299 1 : auto tx_reg_hash = tx_reg.GetHash();
300 :
301 1 : const CScript coinbase_pk = GetScriptForRawPubKey(setup.coinbaseKey.GetPubKey());
302 1 : int nHeight = chainman.ActiveChain().Height();
303 :
304 1 : auto block = std::make_shared<CBlock>(setup.CreateBlock({tx_reg}, coinbase_pk, chainman.ActiveChainstate()));
305 1 : BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr));
306 1 : BOOST_REQUIRE(!DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman.GetConsensus(), Consensus::DEPLOYMENT_V19));
307 1 : ++nHeight;
308 1 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight);
309 1 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
310 1 : dmnman.DoMaintenance();
311 1 : auto tip_list = dmnman.GetListAtChainTip();
312 1 : BOOST_REQUIRE(tip_list.HasMN(tx_reg_hash));
313 1 : auto pindex_create = chainman.ActiveChain().Tip();
314 1 : auto base_list = dmnman.GetListForBlock(pindex_create);
315 1 : std::vector<CDeterministicMNListDiff> diffs;
316 :
317 : // update
318 1 : CBLSSecretKey operator_key_new;
319 1 : operator_key_new.MakeNewKey();
320 1 : auto tx_upreg = CreateProUpRegTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, tx_reg_hash, owner_key, operator_key_new.GetPublicKey(), owner_key.GetPubKey().GetID(), collateralScript, setup.coinbaseKey);
321 :
322 1 : block = std::make_shared<CBlock>(setup.CreateBlock({tx_upreg}, coinbase_pk, chainman.ActiveChainstate()));
323 1 : BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr));
324 1 : BOOST_REQUIRE(!DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman.GetConsensus(), Consensus::DEPLOYMENT_V19));
325 1 : ++nHeight;
326 1 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight);
327 1 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
328 1 : dmnman.DoMaintenance();
329 1 : tip_list = dmnman.GetListAtChainTip();
330 1 : BOOST_REQUIRE(tip_list.HasMN(tx_reg_hash));
331 1 : diffs.push_back(base_list.BuildDiff(tip_list));
332 :
333 : // spend
334 1 : CMutableTransaction tx_spend;
335 1 : COutPoint collateralOutpoint(tx_reg_hash, 0);
336 1 : tx_spend.vin.emplace_back(collateralOutpoint);
337 1 : tx_spend.vout.emplace_back(999.99 * COIN, collateralScript);
338 :
339 1 : FillableSigningProvider signing_provider;
340 1 : signing_provider.AddKeyPubKey(collateral_key, collateral_key.GetPubKey());
341 1 : BOOST_REQUIRE(SignSignature(signing_provider, CTransaction(tx_reg), tx_spend, 0, SIGHASH_ALL));
342 1 : block = std::make_shared<CBlock>(setup.CreateBlock({tx_spend}, coinbase_pk, chainman.ActiveChainstate()));
343 1 : BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr));
344 1 : BOOST_REQUIRE(!DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman.GetConsensus(), Consensus::DEPLOYMENT_V19));
345 1 : ++nHeight;
346 1 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight);
347 1 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
348 1 : dmnman.DoMaintenance();
349 1 : diffs.push_back(tip_list.BuildDiff(dmnman.GetListAtChainTip()));
350 1 : tip_list = dmnman.GetListAtChainTip();
351 1 : BOOST_REQUIRE(!tip_list.HasMN(tx_reg_hash));
352 1 : BOOST_REQUIRE(dmnman.GetListForBlock(pindex_create).HasMN(tx_reg_hash));
353 :
354 : // mine another block so that it's not the last one before V19
355 1 : setup.CreateAndProcessBlock({}, coinbase_pk);
356 1 : BOOST_REQUIRE(!DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman.GetConsensus(), Consensus::DEPLOYMENT_V19));
357 1 : ++nHeight;
358 1 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight);
359 1 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
360 1 : dmnman.DoMaintenance();
361 1 : diffs.push_back(tip_list.BuildDiff(dmnman.GetListAtChainTip()));
362 1 : tip_list = dmnman.GetListAtChainTip();
363 1 : BOOST_REQUIRE(!tip_list.HasMN(tx_reg_hash));
364 1 : BOOST_REQUIRE(dmnman.GetListForBlock(pindex_create).HasMN(tx_reg_hash));
365 :
366 : // this block should activate V19
367 1 : setup.CreateAndProcessBlock({}, coinbase_pk);
368 1 : BOOST_REQUIRE(DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman.GetConsensus(), Consensus::DEPLOYMENT_V19));
369 1 : ++nHeight;
370 1 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight);
371 1 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
372 1 : dmnman.DoMaintenance();
373 1 : diffs.push_back(tip_list.BuildDiff(dmnman.GetListAtChainTip()));
374 1 : tip_list = dmnman.GetListAtChainTip();
375 1 : BOOST_REQUIRE(!tip_list.HasMN(tx_reg_hash));
376 1 : BOOST_REQUIRE(dmnman.GetListForBlock(pindex_create).HasMN(tx_reg_hash));
377 :
378 : // check mn list/diff
379 1 : CDeterministicMNListDiff dummy_diff = base_list.BuildDiff(tip_list);
380 1 : CDeterministicMNList dummy_list{base_list};
381 1 : dummy_list.ApplyDiff(chainman.ActiveChain().Tip(), dummy_diff);
382 : // Lists should match
383 1 : BOOST_REQUIRE(dummy_list == tip_list);
384 :
385 : // mine 10 more blocks
386 11 : for (int i = 0; i < 10; ++i)
387 : {
388 10 : setup.CreateAndProcessBlock({}, coinbase_pk);
389 10 : BOOST_REQUIRE(
390 : DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman.GetConsensus(), Consensus::DEPLOYMENT_V19));
391 10 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1 + i);
392 10 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
393 10 : dmnman.DoMaintenance();
394 10 : diffs.push_back(tip_list.BuildDiff(dmnman.GetListAtChainTip()));
395 10 : tip_list = dmnman.GetListAtChainTip();
396 10 : BOOST_REQUIRE(!tip_list.HasMN(tx_reg_hash));
397 10 : BOOST_REQUIRE(dmnman.GetListForBlock(pindex_create).HasMN(tx_reg_hash));
398 10 : }
399 :
400 : // check mn list/diff
401 1 : const CBlockIndex* v19_index = chainman.ActiveChain().Tip()->GetAncestor(Params().GetConsensus().V19Height);
402 1 : auto v19_list = dmnman.GetListForBlock(v19_index);
403 1 : dummy_diff = v19_list.BuildDiff(tip_list);
404 1 : dummy_list = v19_list;
405 1 : dummy_list.ApplyDiff(chainman.ActiveChain().Tip(), dummy_diff);
406 1 : BOOST_REQUIRE(dummy_list == tip_list);
407 :
408 : // NOTE: this fails on v19/v19.1 with errors like:
409 : // "RemoveMN: Can't delete a masternode ... with a pubKeyOperator=..."
410 1 : dummy_diff = base_list.BuildDiff(tip_list);
411 1 : dummy_list = base_list;
412 1 : dummy_list.ApplyDiff(chainman.ActiveChain().Tip(), dummy_diff);
413 1 : BOOST_REQUIRE(dummy_list == tip_list);
414 :
415 1 : dummy_list = base_list;
416 15 : for (const auto& diff : diffs) {
417 14 : dummy_list.ApplyDiff(chainman.ActiveChain().Tip(), diff);
418 : }
419 1 : BOOST_REQUIRE(dummy_list == tip_list);
420 1 : };
421 :
422 2 : void FuncDIP3Protx(TestChainSetup& setup)
423 : {
424 2 : auto& chainman = *Assert(setup.m_node.chainman.get());
425 2 : auto& dmnman = *Assert(setup.m_node.dmnman);
426 :
427 2 : auto utxos = BuildSimpleUtxoMap(setup.m_coinbase_txns);
428 :
429 2 : const CScript coinbase_pk = GetScriptForRawPubKey(setup.coinbaseKey.GetPubKey());
430 2 : int nHeight = chainman.ActiveChain().Height();
431 2 : int port = 1;
432 :
433 2 : std::vector<uint256> dmnHashes;
434 2 : std::map<uint256, CKey> ownerKeys;
435 2 : std::map<uint256, CBLSSecretKey> operatorKeys;
436 :
437 : // register one MN per block
438 14 : for (size_t i = 0; i < 6; i++) {
439 12 : CKey ownerKey;
440 12 : CBLSSecretKey operatorKey;
441 12 : auto tx = CreateProRegTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, port++, GenerateRandomAddress(), setup.coinbaseKey, ownerKey, operatorKey);
442 12 : dmnHashes.emplace_back(tx.GetHash());
443 12 : ownerKeys.emplace(tx.GetHash(), ownerKey);
444 12 : operatorKeys.emplace(tx.GetHash(), operatorKey);
445 :
446 : // also verify that payloads are not malleable after they have been signed
447 : // the form of ProRegTx we use here is one with a collateral included, so there is no signature inside the
448 : // payload itself. This means, we need to rely on script verification, which takes the hash of the extra payload
449 : // into account
450 12 : auto tx2 = MalleateProTxPayout<CProRegTx>(tx);
451 12 : TxValidationState dummy_state;
452 : // Technically, the payload is still valid...
453 : {
454 12 : LOCK(cs_main);
455 12 : BOOST_REQUIRE(CheckProRegTx(CTransaction(tx), chainman.ActiveChain().Tip(), dmnman,
456 : chainman.ActiveChainstate().CoinsTip(), chainman, dummy_state, true));
457 12 : BOOST_REQUIRE(CheckProRegTx(CTransaction(tx2), chainman.ActiveChain().Tip(), dmnman,
458 : chainman.ActiveChainstate().CoinsTip(), chainman, dummy_state, true));
459 12 : }
460 : // But the signature should not verify anymore
461 12 : BOOST_REQUIRE(CheckTransactionSignature(*(setup.m_node.mempool), tx));
462 12 : BOOST_REQUIRE(!CheckTransactionSignature(*(setup.m_node.mempool), tx2));
463 :
464 12 : setup.CreateAndProcessBlock({tx}, coinbase_pk);
465 12 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
466 :
467 12 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1);
468 12 : BOOST_REQUIRE(dmnman.GetListAtChainTip().HasMN(tx.GetHash()));
469 :
470 12 : nHeight++;
471 12 : }
472 :
473 2 : int DIP0003EnforcementHeightBackup = Params().GetConsensus().DIP0003EnforcementHeight;
474 2 : const_cast<Consensus::Params&>(Params().GetConsensus()).DIP0003EnforcementHeight = chainman.ActiveChain().Height() + 1;
475 2 : setup.CreateAndProcessBlock({}, coinbase_pk);
476 2 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
477 2 : nHeight++;
478 :
479 : // check MN reward payments
480 42 : for (size_t i = 0; i < 20; i++) {
481 40 : auto dmnExpectedPayee = dmnman.GetListAtChainTip().GetMNPayee(chainman.ActiveChain().Tip());
482 40 : BOOST_ASSERT(dmnExpectedPayee);
483 :
484 40 : CBlock block = setup.CreateAndProcessBlock({}, coinbase_pk);
485 40 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
486 40 : BOOST_REQUIRE(!block.vtx.empty());
487 :
488 40 : auto dmnPayout = FindPayoutDmn(dmnman, block);
489 40 : BOOST_REQUIRE(dmnPayout != nullptr);
490 40 : BOOST_CHECK_EQUAL(dmnPayout->proTxHash.ToString(), dmnExpectedPayee->proTxHash.ToString());
491 :
492 40 : nHeight++;
493 40 : }
494 :
495 : // register multiple MNs per block
496 8 : for (size_t i = 0; i < 3; i++) {
497 6 : std::vector<CMutableTransaction> txns;
498 24 : for (size_t j = 0; j < 3; j++) {
499 18 : CKey ownerKey;
500 18 : CBLSSecretKey operatorKey;
501 18 : auto tx = CreateProRegTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, port++, GenerateRandomAddress(), setup.coinbaseKey, ownerKey, operatorKey);
502 18 : dmnHashes.emplace_back(tx.GetHash());
503 18 : ownerKeys.emplace(tx.GetHash(), ownerKey);
504 18 : operatorKeys.emplace(tx.GetHash(), operatorKey);
505 18 : txns.emplace_back(tx);
506 18 : }
507 6 : setup.CreateAndProcessBlock(txns, coinbase_pk);
508 6 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
509 6 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1);
510 :
511 24 : for (size_t j = 0; j < 3; j++) {
512 18 : BOOST_REQUIRE(dmnman.GetListAtChainTip().HasMN(txns[j].GetHash()));
513 18 : }
514 :
515 6 : nHeight++;
516 6 : }
517 :
518 : // test ProUpServTx
519 2 : auto tx = CreateProUpServTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, dmnHashes[0], operatorKeys[dmnHashes[0]], 1000, CScript(), setup.coinbaseKey);
520 2 : setup.CreateAndProcessBlock({tx}, coinbase_pk);
521 2 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
522 2 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1);
523 2 : nHeight++;
524 :
525 2 : auto dmn = dmnman.GetListAtChainTip().GetMN(dmnHashes[0]);
526 2 : BOOST_REQUIRE(dmn != nullptr && dmn->pdmnState->netInfo->GetPrimary().GetPort() == 1000);
527 :
528 : // test ProUpRevTx
529 2 : tx = CreateProUpRevTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, dmnHashes[0], operatorKeys[dmnHashes[0]], setup.coinbaseKey);
530 2 : setup.CreateAndProcessBlock({tx}, coinbase_pk);
531 2 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
532 2 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1);
533 2 : nHeight++;
534 :
535 2 : dmn = dmnman.GetListAtChainTip().GetMN(dmnHashes[0]);
536 2 : BOOST_REQUIRE(dmn != nullptr && dmn->pdmnState->GetBannedHeight() == nHeight);
537 :
538 : // test that the revoked MN does not get paid anymore
539 42 : for (size_t i = 0; i < 20; i++) {
540 40 : auto dmnExpectedPayee = dmnman.GetListAtChainTip().GetMNPayee(chainman.ActiveChain().Tip());
541 80 : BOOST_REQUIRE(dmnExpectedPayee && dmnExpectedPayee->proTxHash != dmnHashes[0]);
542 :
543 40 : CBlock block = setup.CreateAndProcessBlock({}, coinbase_pk);
544 40 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
545 40 : BOOST_REQUIRE(!block.vtx.empty());
546 :
547 40 : auto dmnPayout = FindPayoutDmn(dmnman, block);
548 40 : BOOST_REQUIRE(dmnPayout != nullptr);
549 40 : BOOST_CHECK_EQUAL(dmnPayout->proTxHash.ToString(), dmnExpectedPayee->proTxHash.ToString());
550 :
551 40 : nHeight++;
552 40 : }
553 :
554 : // test reviving the MN
555 2 : CBLSSecretKey newOperatorKey;
556 2 : newOperatorKey.MakeNewKey();
557 2 : dmn = dmnman.GetListAtChainTip().GetMN(dmnHashes[0]);
558 2 : tx = CreateProUpRegTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, dmnHashes[0], ownerKeys[dmnHashes[0]], newOperatorKey.GetPublicKey(), ownerKeys[dmnHashes[0]].GetPubKey().GetID(), dmn->pdmnState->scriptPayout, setup.coinbaseKey);
559 : // check malleability protection again, but this time by also relying on the signature inside the ProUpRegTx
560 2 : auto tx2 = MalleateProTxPayout<CProUpRegTx>(tx);
561 2 : TxValidationState dummy_state;
562 : {
563 2 : LOCK(cs_main);
564 2 : BOOST_REQUIRE(CheckProUpRegTx(CTransaction(tx), chainman.ActiveChain().Tip(), dmnman,
565 : chainman.ActiveChainstate().CoinsTip(), chainman, dummy_state, true));
566 2 : BOOST_REQUIRE(!CheckProUpRegTx(CTransaction(tx2), chainman.ActiveChain().Tip(), dmnman,
567 : chainman.ActiveChainstate().CoinsTip(), chainman, dummy_state, true));
568 2 : }
569 2 : BOOST_REQUIRE(CheckTransactionSignature(*(setup.m_node.mempool), tx));
570 2 : BOOST_REQUIRE(!CheckTransactionSignature(*(setup.m_node.mempool), tx2));
571 : // now process the block
572 2 : setup.CreateAndProcessBlock({tx}, coinbase_pk);
573 2 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
574 2 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1);
575 2 : nHeight++;
576 :
577 2 : tx = CreateProUpServTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, dmnHashes[0], newOperatorKey, 100, CScript(), setup.coinbaseKey);
578 2 : setup.CreateAndProcessBlock({tx}, coinbase_pk);
579 2 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
580 2 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1);
581 2 : nHeight++;
582 :
583 2 : dmn = dmnman.GetListAtChainTip().GetMN(dmnHashes[0]);
584 2 : BOOST_REQUIRE(dmn != nullptr && dmn->pdmnState->netInfo->GetPrimary().GetPort() == 100);
585 2 : BOOST_REQUIRE(dmn != nullptr && !dmn->pdmnState->IsBanned());
586 :
587 : // test that the revived MN gets payments again
588 2 : bool foundRevived = false;
589 42 : for (size_t i = 0; i < 20; i++) {
590 40 : auto dmnExpectedPayee = dmnman.GetListAtChainTip().GetMNPayee(chainman.ActiveChain().Tip());
591 40 : BOOST_ASSERT(dmnExpectedPayee);
592 40 : if (dmnExpectedPayee->proTxHash == dmnHashes[0]) {
593 2 : foundRevived = true;
594 2 : }
595 :
596 40 : CBlock block = setup.CreateAndProcessBlock({}, coinbase_pk);
597 40 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
598 40 : BOOST_REQUIRE(!block.vtx.empty());
599 :
600 40 : auto dmnPayout = FindPayoutDmn(dmnman, block);
601 40 : BOOST_REQUIRE(dmnPayout != nullptr);
602 40 : BOOST_CHECK_EQUAL(dmnPayout->proTxHash.ToString(), dmnExpectedPayee->proTxHash.ToString());
603 :
604 40 : nHeight++;
605 40 : }
606 2 : BOOST_REQUIRE(foundRevived);
607 :
608 2 : const_cast<Consensus::Params&>(Params().GetConsensus()).DIP0003EnforcementHeight = DIP0003EnforcementHeightBackup;
609 2 : }
610 :
611 2 : void FuncTestMempoolReorg(TestChainSetup& setup)
612 : {
613 2 : auto& chainman = *Assert(setup.m_node.chainman.get());
614 :
615 2 : const CScript coinbase_pk = GetScriptForRawPubKey(setup.coinbaseKey.GetPubKey());
616 2 : int nHeight = chainman.ActiveChain().Height();
617 2 : auto utxos = BuildSimpleUtxoMap(setup.m_coinbase_txns);
618 :
619 2 : CKey ownerKey;
620 2 : CKey payoutKey;
621 2 : CKey collateralKey;
622 2 : CBLSSecretKey operatorKey;
623 :
624 2 : ownerKey.MakeNewKey(true);
625 2 : payoutKey.MakeNewKey(true);
626 2 : collateralKey.MakeNewKey(true);
627 2 : operatorKey.MakeNewKey();
628 :
629 2 : auto scriptPayout = GetScriptForDestination(PKHash(payoutKey.GetPubKey()));
630 2 : auto scriptCollateral = GetScriptForDestination(PKHash(collateralKey.GetPubKey()));
631 :
632 : // Create a MN with an external collateral
633 2 : CMutableTransaction tx_collateral;
634 2 : FundTransaction(chainman.ActiveChain(), tx_collateral, utxos, scriptCollateral, dmn_types::Regular.collat_amount, setup.coinbaseKey);
635 2 : SignTransaction(*(setup.m_node.mempool), tx_collateral, setup.coinbaseKey);
636 :
637 2 : auto block = std::make_shared<CBlock>(setup.CreateBlock({tx_collateral}, coinbase_pk, chainman.ActiveChainstate()));
638 2 : BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr));
639 2 : setup.m_node.dmnman->UpdatedBlockTip(chainman.ActiveChain().Tip());
640 2 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1);
641 2 : BOOST_CHECK_EQUAL(block->GetHash(), chainman.ActiveChain().Tip()->GetBlockHash());
642 :
643 2 : CProRegTx payload;
644 2 : payload.nVersion = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false);
645 2 : payload.netInfo = NetInfoInterface::MakeNetInfo(payload.nVersion);
646 2 : BOOST_CHECK_EQUAL(payload.netInfo->AddEntry(NetInfoPurpose::CORE_P2P, "1.1.1.1:1"), NetInfoStatus::Success);
647 2 : payload.keyIDOwner = ownerKey.GetPubKey().GetID();
648 2 : payload.pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load());
649 2 : payload.keyIDVoting = ownerKey.GetPubKey().GetID();
650 2 : payload.scriptPayout = scriptPayout;
651 :
652 2 : for (size_t i = 0; i < tx_collateral.vout.size(); ++i) {
653 2 : if (tx_collateral.vout[i].nValue == dmn_types::Regular.collat_amount) {
654 2 : payload.collateralOutpoint = COutPoint(tx_collateral.GetHash(), i);
655 2 : break;
656 : }
657 0 : }
658 :
659 2 : CMutableTransaction tx_reg;
660 2 : tx_reg.nVersion = 3;
661 2 : tx_reg.nType = TRANSACTION_PROVIDER_REGISTER;
662 2 : FundTransaction(chainman.ActiveChain(), tx_reg, utxos, scriptPayout, dmn_types::Regular.collat_amount, setup.coinbaseKey);
663 2 : payload.inputsHash = CalcTxInputsHash(CTransaction(tx_reg));
664 2 : CMessageSigner::SignMessage(payload.MakeSignString(), payload.vchSig, collateralKey);
665 2 : SetTxPayload(tx_reg, payload);
666 2 : SignTransaction(*(setup.m_node.mempool), tx_reg, setup.coinbaseKey);
667 :
668 2 : CTxMemPool testPool;
669 2 : if (setup.m_node.dmnman) {
670 2 : testPool.ConnectManagers(setup.m_node.dmnman.get(), setup.m_node.llmq_ctx->isman.get());
671 2 : }
672 2 : TestMemPoolEntryHelper entry;
673 2 : LOCK2(cs_main, testPool.cs);
674 :
675 : // Create ProUpServ and test block reorg which double-spend ProRegTx
676 2 : auto tx_up_serv = CreateProUpServTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, tx_reg.GetHash(), operatorKey, 2, CScript(), setup.coinbaseKey);
677 2 : testPool.addUnchecked(entry.FromTx(tx_up_serv));
678 : // A disconnected block would insert ProRegTx back into mempool
679 2 : testPool.addUnchecked(entry.FromTx(tx_reg));
680 2 : BOOST_CHECK_EQUAL(testPool.size(), 2U);
681 :
682 : // Create a tx that will double-spend ProRegTx
683 2 : CMutableTransaction tx_reg_ds;
684 2 : tx_reg_ds.vin = tx_reg.vin;
685 2 : tx_reg_ds.vout.emplace_back(0, CScript() << OP_RETURN);
686 2 : SignTransaction(*(setup.m_node.mempool), tx_reg_ds, setup.coinbaseKey);
687 :
688 : // Check mempool as if a new block with tx_reg_ds was connected instead of the old one with tx_reg
689 2 : std::vector<CTransactionRef> block_reorg;
690 2 : block_reorg.emplace_back(std::make_shared<CTransaction>(tx_reg_ds));
691 2 : testPool.removeForBlock(block_reorg, nHeight + 2);
692 2 : BOOST_CHECK_EQUAL(testPool.size(), 0U);
693 2 : }
694 :
695 2 : void FuncTestMempoolDualProregtx(TestChainSetup& setup)
696 : {
697 2 : auto& chainman = *Assert(setup.m_node.chainman.get());
698 :
699 2 : auto utxos = BuildSimpleUtxoMap(setup.m_coinbase_txns);
700 :
701 : // Create a MN
702 2 : CKey ownerKey1;
703 2 : CBLSSecretKey operatorKey1;
704 2 : auto tx_reg1 = CreateProRegTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, 1, GenerateRandomAddress(), setup.coinbaseKey, ownerKey1, operatorKey1);
705 :
706 : // Create a MN with an external collateral that references tx_reg1
707 2 : CKey ownerKey;
708 2 : CKey payoutKey;
709 2 : CKey collateralKey;
710 2 : CBLSSecretKey operatorKey;
711 :
712 2 : ownerKey.MakeNewKey(true);
713 2 : payoutKey.MakeNewKey(true);
714 2 : collateralKey.MakeNewKey(true);
715 2 : operatorKey.MakeNewKey();
716 :
717 2 : auto scriptPayout = GetScriptForDestination(PKHash(payoutKey.GetPubKey()));
718 :
719 2 : CProRegTx payload;
720 2 : payload.nVersion = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false);
721 2 : payload.netInfo = NetInfoInterface::MakeNetInfo(payload.nVersion);
722 2 : BOOST_CHECK_EQUAL(payload.netInfo->AddEntry(NetInfoPurpose::CORE_P2P, "1.1.1.1:2"), NetInfoStatus::Success);
723 2 : payload.keyIDOwner = ownerKey.GetPubKey().GetID();
724 2 : payload.pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load());
725 2 : payload.keyIDVoting = ownerKey.GetPubKey().GetID();
726 2 : payload.scriptPayout = scriptPayout;
727 :
728 2 : for (size_t i = 0; i < tx_reg1.vout.size(); ++i) {
729 2 : if (tx_reg1.vout[i].nValue == dmn_types::Regular.collat_amount) {
730 2 : payload.collateralOutpoint = COutPoint(tx_reg1.GetHash(), i);
731 2 : break;
732 : }
733 0 : }
734 :
735 2 : CMutableTransaction tx_reg2;
736 2 : tx_reg2.nVersion = 3;
737 2 : tx_reg2.nType = TRANSACTION_PROVIDER_REGISTER;
738 2 : FundTransaction(chainman.ActiveChain(), tx_reg2, utxos, scriptPayout, dmn_types::Regular.collat_amount, setup.coinbaseKey);
739 2 : payload.inputsHash = CalcTxInputsHash(CTransaction(tx_reg2));
740 2 : CMessageSigner::SignMessage(payload.MakeSignString(), payload.vchSig, collateralKey);
741 2 : SetTxPayload(tx_reg2, payload);
742 2 : SignTransaction(*(setup.m_node.mempool), tx_reg2, setup.coinbaseKey);
743 :
744 2 : CTxMemPool testPool;
745 2 : if (setup.m_node.dmnman) {
746 2 : testPool.ConnectManagers(setup.m_node.dmnman.get(), setup.m_node.llmq_ctx->isman.get());
747 2 : }
748 2 : TestMemPoolEntryHelper entry;
749 2 : LOCK2(cs_main, testPool.cs);
750 :
751 2 : testPool.addUnchecked(entry.FromTx(tx_reg1));
752 2 : BOOST_CHECK_EQUAL(testPool.size(), 1U);
753 2 : BOOST_CHECK(testPool.existsProviderTxConflict(CTransaction(tx_reg2)));
754 2 : }
755 :
756 1 : void FuncVerifyDB(TestChainSetup& setup)
757 : {
758 1 : auto& chainman = *Assert(setup.m_node.chainman.get());
759 1 : auto& dmnman = *Assert(setup.m_node.dmnman);
760 :
761 1 : const CScript coinbase_pk = GetScriptForRawPubKey(setup.coinbaseKey.GetPubKey());
762 1 : int nHeight = chainman.ActiveChain().Height();
763 1 : auto utxos = BuildSimpleUtxoMap(setup.m_coinbase_txns);
764 :
765 1 : CKey ownerKey;
766 1 : CKey payoutKey;
767 1 : CKey collateralKey;
768 1 : CBLSSecretKey operatorKey;
769 :
770 1 : ownerKey.MakeNewKey(true);
771 1 : payoutKey.MakeNewKey(true);
772 1 : collateralKey.MakeNewKey(true);
773 1 : operatorKey.MakeNewKey();
774 :
775 1 : auto scriptPayout = GetScriptForDestination(PKHash(payoutKey.GetPubKey()));
776 1 : auto scriptCollateral = GetScriptForDestination(PKHash(collateralKey.GetPubKey()));
777 :
778 : // Create a MN with an external collateral
779 1 : CMutableTransaction tx_collateral;
780 1 : FundTransaction(chainman.ActiveChain(), tx_collateral, utxos, scriptCollateral, dmn_types::Regular.collat_amount, setup.coinbaseKey);
781 1 : SignTransaction(*(setup.m_node.mempool), tx_collateral, setup.coinbaseKey);
782 :
783 1 : auto block = std::make_shared<CBlock>(setup.CreateBlock({tx_collateral}, coinbase_pk, chainman.ActiveChainstate()));
784 1 : BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr));
785 1 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
786 1 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1);
787 1 : BOOST_CHECK_EQUAL(block->GetHash(), chainman.ActiveChain().Tip()->GetBlockHash());
788 :
789 1 : CProRegTx payload;
790 1 : payload.nVersion = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false);
791 1 : payload.netInfo = NetInfoInterface::MakeNetInfo(payload.nVersion);
792 1 : BOOST_CHECK_EQUAL(payload.netInfo->AddEntry(NetInfoPurpose::CORE_P2P, "1.1.1.1:1"), NetInfoStatus::Success);
793 1 : payload.keyIDOwner = ownerKey.GetPubKey().GetID();
794 1 : payload.pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load());
795 1 : payload.keyIDVoting = ownerKey.GetPubKey().GetID();
796 1 : payload.scriptPayout = scriptPayout;
797 :
798 1 : for (size_t i = 0; i < tx_collateral.vout.size(); ++i) {
799 1 : if (tx_collateral.vout[i].nValue == dmn_types::Regular.collat_amount) {
800 1 : payload.collateralOutpoint = COutPoint(tx_collateral.GetHash(), i);
801 1 : break;
802 : }
803 0 : }
804 :
805 1 : CMutableTransaction tx_reg;
806 1 : tx_reg.nVersion = 3;
807 1 : tx_reg.nType = TRANSACTION_PROVIDER_REGISTER;
808 1 : FundTransaction(chainman.ActiveChain(), tx_reg, utxos, scriptPayout, dmn_types::Regular.collat_amount, setup.coinbaseKey);
809 1 : payload.inputsHash = CalcTxInputsHash(CTransaction(tx_reg));
810 1 : CMessageSigner::SignMessage(payload.MakeSignString(), payload.vchSig, collateralKey);
811 1 : SetTxPayload(tx_reg, payload);
812 1 : SignTransaction(*(setup.m_node.mempool), tx_reg, setup.coinbaseKey);
813 :
814 1 : auto tx_reg_hash = tx_reg.GetHash();
815 :
816 1 : block = std::make_shared<CBlock>(setup.CreateBlock({tx_reg}, coinbase_pk, chainman.ActiveChainstate()));
817 1 : BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr));
818 1 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
819 1 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 2);
820 1 : BOOST_CHECK_EQUAL(block->GetHash(), chainman.ActiveChain().Tip()->GetBlockHash());
821 1 : BOOST_REQUIRE(dmnman.GetListAtChainTip().HasMN(tx_reg_hash));
822 :
823 : // Now spend the collateral while updating the same MN
824 1 : SimpleUTXOMap collateral_utxos;
825 1 : collateral_utxos.emplace(payload.collateralOutpoint, std::make_pair(1, 1000));
826 1 : auto proUpRevTx = CreateProUpRevTx(chainman.ActiveChain(), *(setup.m_node.mempool), collateral_utxos, tx_reg_hash, operatorKey, collateralKey);
827 :
828 1 : block = std::make_shared<CBlock>(setup.CreateBlock({proUpRevTx}, coinbase_pk, chainman.ActiveChainstate()));
829 1 : BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr));
830 1 : dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
831 1 : BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 3);
832 1 : BOOST_CHECK_EQUAL(block->GetHash(), chainman.ActiveChain().Tip()->GetBlockHash());
833 1 : BOOST_REQUIRE(!dmnman.GetListAtChainTip().HasMN(tx_reg_hash));
834 :
835 : // Verify db consistency
836 1 : LOCK(cs_main);
837 1 : BOOST_REQUIRE(CVerifyDB().VerifyDB(chainman.ActiveChainstate(), Params().GetConsensus(),
838 : chainman.ActiveChainstate().CoinsTip(), *(setup.m_node.evodb), 4, 2));
839 1 : }
840 :
841 2 : static CDeterministicMNCPtr create_mock_mn(uint64_t internal_id)
842 : {
843 : // Create a mock MN
844 2 : CKey ownerKey;
845 2 : ownerKey.MakeNewKey(true);
846 2 : CBLSSecretKey operatorKey;
847 2 : operatorKey.MakeNewKey();
848 :
849 2 : auto dmnState = std::make_shared<CDeterministicMNState>();
850 2 : dmnState->confirmedHash = GetRandHash();
851 2 : dmnState->keyIDOwner = ownerKey.GetPubKey().GetID();
852 2 : dmnState->pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load());
853 2 : dmnState->keyIDVoting = ownerKey.GetPubKey().GetID();
854 2 : dmnState->netInfo = NetInfoInterface::MakeNetInfo(
855 2 : ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false));
856 2 : BOOST_CHECK_EQUAL(dmnState->netInfo->AddEntry(NetInfoPurpose::CORE_P2P, "1.1.1.1:1"), NetInfoStatus::Success);
857 :
858 2 : auto dmn = std::make_shared<CDeterministicMN>(internal_id, MnType::Regular);
859 2 : dmn->proTxHash = GetRandHash();
860 2 : dmn->collateralOutpoint = COutPoint(GetRandHash(), 0);
861 2 : dmn->nOperatorReward = 0;
862 2 : dmn->pdmnState = dmnState;
863 :
864 2 : return dmn;
865 2 : }
866 :
867 2 : static void SmlCache(TestChainSetup& setup)
868 : {
869 2 : BOOST_CHECK(setup.m_node.dmnman != nullptr);
870 :
871 : // Create empty list and verify SML cache
872 2 : CDeterministicMNList emptyList(uint256(), 0, 0);
873 2 : auto sml_empty = emptyList.to_sml();
874 :
875 : // Should return the same cached object
876 2 : BOOST_CHECK(sml_empty == emptyList.to_sml());
877 :
878 : // Should contain empty list
879 2 : BOOST_CHECK_EQUAL(sml_empty->mnList.size(), 0);
880 :
881 : // Copy list should return the same cached object
882 2 : CDeterministicMNList mn_list_1(emptyList);
883 2 : BOOST_CHECK(sml_empty == mn_list_1.to_sml());
884 :
885 2 : CDeterministicMNList mn_list_2;
886 : // Assigning list should return the same cached object
887 2 : mn_list_2 = emptyList;
888 2 : BOOST_CHECK(sml_empty == mn_list_2.to_sml());
889 :
890 2 : auto dmn = create_mock_mn(1);
891 :
892 : // Add MN - should invalidate cache
893 2 : mn_list_1.AddMN(dmn, true);
894 2 : auto sml_add = mn_list_1.to_sml();
895 :
896 : // Cache should be invalidated, so different pointer but equal content after regeneration
897 2 : BOOST_CHECK(sml_empty != sml_add); // Different pointer (cache invalidated)
898 :
899 2 : BOOST_CHECK_EQUAL(sml_add->mnList.size(), 1); // Should contain the added MN
900 :
901 : {
902 : // Remove MN - should invalidate cache
903 2 : CDeterministicMNList mn_list(mn_list_1);
904 2 : BOOST_CHECK(mn_list_1.to_sml() == mn_list.to_sml());
905 :
906 2 : mn_list.RemoveMN(dmn->proTxHash);
907 2 : auto sml_remove = mn_list.to_sml();
908 :
909 : // Cache should be invalidated
910 2 : BOOST_CHECK(sml_remove != sml_add);
911 2 : BOOST_CHECK(sml_remove != sml_empty);
912 2 : BOOST_CHECK_EQUAL(sml_remove->mnList.size(), 0); // Should be empty after removal
913 2 : }
914 :
915 : // Start with a list containing one MN mn_list_1
916 : // Test 1: Update with same SML entry data - cache should NOT be invalidated
917 2 : auto unchangedState = std::make_shared<CDeterministicMNState>(*dmn->pdmnState);
918 2 : unchangedState->nPoSePenalty += 10;
919 2 : mn_list_1.UpdateMN(*dmn, unchangedState);
920 :
921 : // Cache should NOT be invalidated since SML entry didn't change
922 2 : BOOST_CHECK(sml_add == mn_list_1.to_sml()); // Same pointer (cache preserved)
923 :
924 : // Test 2: Update with different SML entry data - cache SHOULD be invalidated
925 2 : auto changedState = std::make_shared<CDeterministicMNState>(*unchangedState);
926 2 : changedState->pubKeyOperator.Set(CBLSPublicKey{}, bls::bls_legacy_scheme.load());
927 2 : mn_list_1.UpdateMN(*dmn, changedState);
928 :
929 : // Cache should be invalidated since SML entry changed
930 2 : BOOST_CHECK(sml_add != mn_list_1.to_sml());
931 2 : BOOST_CHECK_EQUAL(mn_list_1.to_sml()->mnList.size(), 1); // Still one MN but with updated data
932 2 : }
933 :
934 146 : BOOST_AUTO_TEST_SUITE(evo_dip3_activation_tests)
935 :
936 : struct TestChainDIP3BeforeActivationSetup : public TestChainSetup {
937 7 : TestChainDIP3BeforeActivationSetup() :
938 6 : TestChainSetup(430)
939 1 : {
940 7 : }
941 : };
942 :
943 : struct TestChainDIP3Setup : public TestChainDIP3BeforeActivationSetup {
944 10 : TestChainDIP3Setup()
945 5 : {
946 : // Activate DIP3 here
947 5 : CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
948 10 : }
949 : };
950 :
951 : struct TestChainV19BeforeActivationSetup : public TestChainSetup {
952 : TestChainV19BeforeActivationSetup();
953 : };
954 :
955 : struct TestChainV19Setup : public TestChainV19BeforeActivationSetup {
956 8 : TestChainV19Setup()
957 4 : {
958 4 : const CScript coinbase_pk = GetScriptForRawPubKey(coinbaseKey.GetPubKey());
959 : // Activate V19
960 24 : for (int i = 0; i < 5; ++i) {
961 20 : CreateAndProcessBlock({}, coinbase_pk);
962 20 : }
963 4 : bool v19_just_activated{
964 4 : DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), m_node.chainman->GetConsensus(), Consensus::DEPLOYMENT_V19) &&
965 4 : !DeploymentActiveAt(*m_node.chainman->ActiveChain().Tip(), m_node.chainman->GetConsensus(), Consensus::DEPLOYMENT_V19)};
966 4 : assert(v19_just_activated);
967 8 : }
968 : };
969 :
970 : // 5 blocks earlier
971 6 : TestChainV19BeforeActivationSetup::TestChainV19BeforeActivationSetup() :
972 5 : TestChainSetup(494, CBaseChainParams::REGTEST, {"-testactivationheight=v19@500", "-testactivationheight=v20@500", "-testactivationheight=mn_rr@500"})
973 1 : {
974 5 : bool v19_active{DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), m_node.chainman->GetConsensus(),
975 : Consensus::DEPLOYMENT_V19)};
976 5 : assert(!v19_active);
977 6 : }
978 :
979 : // DIP3 can only be activated with legacy scheme (v19 is activated later)
980 148 : BOOST_AUTO_TEST_CASE(dip3_activation_legacy)
981 : {
982 1 : TestChainDIP3BeforeActivationSetup setup;
983 1 : FuncDIP3Activation(setup);
984 1 : }
985 :
986 : // V19 can only be activated with legacy scheme
987 148 : BOOST_AUTO_TEST_CASE(v19_activation_legacy)
988 : {
989 1 : TestChainV19BeforeActivationSetup setup;
990 1 : FuncV19Activation(setup);
991 1 : }
992 :
993 148 : BOOST_AUTO_TEST_CASE(dip3_protx_legacy)
994 : {
995 1 : TestChainDIP3Setup setup;
996 1 : FuncDIP3Protx(setup);
997 1 : }
998 :
999 148 : BOOST_AUTO_TEST_CASE(dip3_protx_basic)
1000 : {
1001 1 : TestChainV19Setup setup;
1002 1 : FuncDIP3Protx(setup);
1003 1 : }
1004 :
1005 148 : BOOST_AUTO_TEST_CASE(test_mempool_reorg_legacy)
1006 : {
1007 1 : TestChainDIP3Setup setup;
1008 1 : FuncTestMempoolReorg(setup);
1009 1 : }
1010 :
1011 148 : BOOST_AUTO_TEST_CASE(test_mempool_reorg_basic)
1012 : {
1013 1 : TestChainV19Setup setup;
1014 1 : FuncTestMempoolReorg(setup);
1015 1 : }
1016 :
1017 148 : BOOST_AUTO_TEST_CASE(test_mempool_dual_proregtx_legacy)
1018 : {
1019 1 : TestChainDIP3Setup setup;
1020 1 : FuncTestMempoolDualProregtx(setup);
1021 1 : }
1022 :
1023 148 : BOOST_AUTO_TEST_CASE(test_mempool_dual_proregtx_basic)
1024 : {
1025 1 : TestChainV19Setup setup;
1026 1 : FuncTestMempoolDualProregtx(setup);
1027 1 : }
1028 :
1029 : //This one can be started only with legacy scheme, since inside undo block will switch it back to legacy resulting into an inconsistency
1030 148 : BOOST_AUTO_TEST_CASE(verify_db_legacy)
1031 : {
1032 1 : TestChainDIP3Setup setup;
1033 1 : FuncVerifyDB(setup);
1034 1 : }
1035 :
1036 148 : BOOST_AUTO_TEST_CASE(test_sml_cache_legacy)
1037 : {
1038 1 : TestChainDIP3Setup setup;
1039 1 : SmlCache(setup);
1040 1 : }
1041 :
1042 148 : BOOST_AUTO_TEST_CASE(test_sml_cache_basic)
1043 : {
1044 1 : TestChainV19Setup setup;
1045 1 : SmlCache(setup);
1046 1 : }
1047 :
1048 148 : BOOST_AUTO_TEST_CASE(field_bit_migration_validation)
1049 : {
1050 : // Test individual field mappings for ALL 19 fields
1051 : struct FieldMapping {
1052 : uint32_t legacyBit;
1053 : uint32_t newBit;
1054 : std::string name;
1055 : };
1056 :
1057 1 : std::vector<FieldMapping> mappings = {
1058 1 : {0x0001, CDeterministicMNStateDiff::Field_nRegisteredHeight, "nRegisteredHeight"},
1059 1 : {0x0002, CDeterministicMNStateDiff::Field_nLastPaidHeight, "nLastPaidHeight"},
1060 1 : {0x0004, CDeterministicMNStateDiff::Field_nPoSePenalty, "nPoSePenalty"},
1061 1 : {0x0008, CDeterministicMNStateDiff::Field_nPoSeRevivedHeight, "nPoSeRevivedHeight"},
1062 1 : {0x0010, CDeterministicMNStateDiff::Field_nPoSeBanHeight, "nPoSeBanHeight"},
1063 1 : {0x0020, CDeterministicMNStateDiff::Field_nRevocationReason, "nRevocationReason"},
1064 1 : {0x0040, CDeterministicMNStateDiff::Field_confirmedHash, "confirmedHash"},
1065 1 : {0x0080, CDeterministicMNStateDiff::Field_confirmedHashWithProRegTxHash, "confirmedHashWithProRegTxHash"},
1066 1 : {0x0100, CDeterministicMNStateDiff::Field_keyIDOwner, "keyIDOwner"},
1067 1 : {0x0200, CDeterministicMNStateDiff::Field_pubKeyOperator, "pubKeyOperator"},
1068 1 : {0x0400, CDeterministicMNStateDiff::Field_keyIDVoting, "keyIDVoting"},
1069 1 : {0x0800, CDeterministicMNStateDiff::Field_netInfo, "netInfo"},
1070 1 : {0x1000, CDeterministicMNStateDiff::Field_scriptPayout, "scriptPayout"},
1071 1 : {0x2000, CDeterministicMNStateDiff::Field_scriptOperatorPayout, "scriptOperatorPayout"},
1072 1 : {0x4000, CDeterministicMNStateDiff::Field_nConsecutivePayments, "nConsecutivePayments"},
1073 1 : {0x8000, CDeterministicMNStateDiff::Field_platformNodeID, "platformNodeID"},
1074 1 : {0x10000, CDeterministicMNStateDiff::Field_platformP2PPort, "platformP2PPort"},
1075 1 : {0x20000, CDeterministicMNStateDiff::Field_platformHTTPPort, "platformHTTPPort"},
1076 1 : {0x40000, CDeterministicMNStateDiff::Field_nVersion, "nVersion"},
1077 : };
1078 :
1079 : // Verify each field mapping is correct
1080 20 : for (const auto& mapping : mappings) {
1081 : // Test individual field conversion
1082 19 : CDeterministicMNStateDiffLegacy legacyDiff;
1083 19 : legacyDiff.fields |= mapping.legacyBit;
1084 : // Convert to new format
1085 19 : auto newDiff = legacyDiff.ToNewFormat();
1086 19 : BOOST_CHECK_MESSAGE(newDiff.fields == mapping.newBit, strprintf("Field %s: legacy 0x%x should convert to 0x%x",
1087 : mapping.name, mapping.legacyBit, mapping.newBit));
1088 19 : }
1089 :
1090 : // Test complex multi-field scenarios
1091 1 : uint32_t complexLegacyFields = 0x0200 | // Legacy Field_pubKeyOperator
1092 : 0x0800 | // Legacy Field_netInfo
1093 : 0x1000 | // Legacy Field_scriptPayout
1094 : 0x40000; // Legacy Field_nVersion
1095 :
1096 1 : uint32_t expectedNewFields = CDeterministicMNStateDiff::Field_nVersion | // 0x0001
1097 : CDeterministicMNStateDiff::Field_pubKeyOperator | // 0x0400 (was 0x0200)
1098 : CDeterministicMNStateDiff::Field_netInfo | // 0x1000 (was 0x0800)
1099 : CDeterministicMNStateDiff::Field_scriptPayout; // 0x2000 (was 0x1000)
1100 :
1101 1 : CDeterministicMNStateDiffLegacy legacyDiff;
1102 1 : legacyDiff.fields |= complexLegacyFields;
1103 : // Convert to new format
1104 1 : auto newDiff = legacyDiff.ToNewFormat();
1105 1 : BOOST_CHECK_EQUAL(newDiff.fields, expectedNewFields);
1106 :
1107 : // Verify no bit conflicts exist in new field layout
1108 1 : std::set<uint32_t> usedBits;
1109 20 : for (const auto& mapping : mappings) {
1110 19 : BOOST_CHECK_MESSAGE(usedBits.find(mapping.newBit) == usedBits.end(),
1111 : strprintf("Duplicate bit 0x%x found for field %s", mapping.newBit, mapping.name));
1112 19 : usedBits.insert(mapping.newBit);
1113 : }
1114 :
1115 : // Verify all 19 fields have unique bit assignments
1116 1 : BOOST_CHECK_EQUAL(usedBits.size(), 19);
1117 1 : }
1118 :
1119 148 : BOOST_AUTO_TEST_CASE(migration_logic_validation)
1120 : {
1121 : // Test the database migration logic for nVersion-first format conversion.
1122 : // Migration logic is handled at CDeterministicMNListDiff level
1123 : // using CDeterministicMNStateDiffLegacy for legacy format deserialization.
1124 :
1125 : // Create sample legacy format state diff
1126 1 : CDeterministicMNStateDiffLegacy legacyDiff;
1127 1 : legacyDiff.fields = 0x40000 | 0x0200 | 0x0800 | 0x0010; // Legacy: nVersion, pubKeyOperator, netInfo, nPoSeBanHeight
1128 1 : legacyDiff.state.nVersion = ProTxVersion::BasicBLS;
1129 1 : CBLSSecretKey sk;
1130 1 : sk.MakeNewKey();
1131 1 : legacyDiff.state.pubKeyOperator.Set(sk.GetPublicKey(), false);
1132 1 : BOOST_CHECK(!legacyDiff.state.pubKeyOperator.IsLegacy());
1133 1 : legacyDiff.state.netInfo = NetInfoInterface::MakeNetInfo(ProTxVersion::BasicBLS);
1134 1 : BOOST_CHECK(!legacyDiff.state.IsBanned());
1135 1 : legacyDiff.state.BanIfNotBanned(2367316);
1136 1 : BOOST_CHECK(legacyDiff.state.IsBanned());
1137 1 : BOOST_CHECK_EQUAL(legacyDiff.state.GetBannedHeight(), 2367316);
1138 :
1139 :
1140 : // Test legacy class conversion (this would normally be done by CDeterministicMNListDiff)
1141 1 : CDataStream ss(SER_DISK, CLIENT_VERSION);
1142 1 : ss << legacyDiff;
1143 :
1144 1 : CDeterministicMNStateDiffLegacy legacyDeserializer(deserialize, ss);
1145 1 : CDeterministicMNStateDiff convertedDiff = legacyDeserializer.ToNewFormat();
1146 1 : BOOST_CHECK(!legacyDiff.state.pubKeyOperator.IsLegacy());
1147 1 : BOOST_CHECK(convertedDiff.state.pubKeyOperator.IsLegacy());
1148 1 : convertedDiff.state.pubKeyOperator.SetLegacy(false);
1149 1 : BOOST_CHECK(!convertedDiff.state.pubKeyOperator.IsLegacy());
1150 :
1151 : // Verify conversion worked correctly
1152 1 : uint32_t expectedNewFields = CDeterministicMNStateDiff::Field_nVersion | // 0x0001
1153 : CDeterministicMNStateDiff::Field_nPoSeBanHeight | // 0x0020
1154 : CDeterministicMNStateDiff::Field_pubKeyOperator | // 0x0400
1155 : CDeterministicMNStateDiff::Field_netInfo; // 0x1000
1156 :
1157 1 : BOOST_CHECK_EQUAL(convertedDiff.fields, expectedNewFields);
1158 1 : BOOST_CHECK_EQUAL(convertedDiff.state.nVersion, legacyDiff.state.nVersion);
1159 1 : BOOST_CHECK_EQUAL(convertedDiff.state.GetBannedHeight(), legacyDiff.state.GetBannedHeight());
1160 1 : BOOST_CHECK(convertedDiff.state.pubKeyOperator.Get() == legacyDiff.state.pubKeyOperator.Get());
1161 1 : BOOST_CHECK_EQUAL(convertedDiff.state.pubKeyOperator.ToString(), legacyDiff.state.pubKeyOperator.ToString());
1162 1 : }
1163 :
1164 146 : BOOST_AUTO_TEST_SUITE_END()
|