2121#include " primitives/transaction.h"
2222#include " script/sign.h"
2323#include " spork.h"
24+ #include " util/blocksutil.h"
2425#include " validation.h"
2526#include " validationinterface.h"
2627
@@ -304,7 +305,7 @@ static bool IsMNPayeeInBlock(const CBlock& block, const CScript& expected)
304305 return false ;
305306}
306307
307- static void CheckPayments (std::map<uint256, int > mp, size_t mapSize, int minCount)
308+ static void CheckPayments (const std::map<uint256, int >& mp, size_t mapSize, int minCount)
308309{
309310 BOOST_CHECK_EQUAL (mp.size (), mapSize);
310311 for (const auto & it : mp) {
@@ -954,6 +955,18 @@ static llmq::CFinalCommitment CreateFinalCommitment(std::vector<CBLSPublicKey>&
954955 return qfl;
955956}
956957
958+ CMutableTransaction CreateNullQfcTx (const uint256& quorumHash, int nHeight)
959+ {
960+ llmq::LLMQCommPL pl;
961+ pl.commitment = llmq::CFinalCommitment (Params ().GetConsensus ().llmqs .at (Consensus::LLMQ_TEST), quorumHash);
962+ pl.nHeight = nHeight;
963+ CMutableTransaction tx;
964+ tx.nVersion = CTransaction::TxVersion::SAPLING;
965+ tx.nType = CTransaction::TxType::LLMQCOMM;
966+ SetTxPayload (tx, pl);
967+ return tx;
968+ }
969+
957970CService ip (uint32_t i)
958971{
959972 struct in_addr s;
@@ -972,7 +985,8 @@ static void ProcessQuorum(llmq::CQuorumBlockProcessor* processor, const llmq::CF
972985
973986static NodeId id = 0 ;
974987
975- BOOST_FIXTURE_TEST_CASE (dkg_pose, TestChain400Setup)
988+ // future: split dkg_pose from qfc_invalid_paths test coverage.
989+ BOOST_FIXTURE_TEST_CASE (dkg_pose_and_qfc_invalid_paths, TestChain400Setup)
976990{
977991 auto utxos = BuildSimpleUtxoMap (coinbaseTxns);
978992
@@ -1056,13 +1070,82 @@ BOOST_FIXTURE_TEST_CASE(dkg_pose, TestChain400Setup)
10561070 ProcessQuorum (llmq::quorumBlockProcessor.get (), qfc, &dummyNode);
10571071 BOOST_CHECK (llmq::quorumBlockProcessor->HasMinableCommitment (::SerializeHash (qfc)));
10581072
1059- // mine final commitment ( at block 450)
1060- for (size_t i = 0 ; i < 23 ; i++) {
1073+ // Generate blocks up to be able to mine a null qfc at block 450
1074+ for (size_t i = 0 ; i < 21 ; i++) {
10611075 CreateAndProcessBlock ({}, coinbaseKey);
10621076 chainTip = chainActive.Tip ();
10631077 BOOST_CHECK_EQUAL (chainTip->nHeight , ++nHeight);
10641078 }
1065- BOOST_CHECK_EQUAL (nHeight, 450 );
1079+ BOOST_CHECK_EQUAL (nHeight, 448 );
1080+
1081+ // Coverage for the following qfc paths:
1082+ // 1) Mine a qfc with an invalid height, which should end up being rejected.
1083+ // 2) Mine a null qfc before the mining phase, which should end up being rejected.
1084+ // 3) Mine two qfc in the same block, which should end up being rejected.
1085+ // 4) Mine block without qfc during the mining phase, which should end up being rejected.
1086+ // 5) Mine two blocks with a null qfc.
1087+ // 6) Try to relay the valid qfc to the mempool, which should end up being rejected.
1088+ // 7) Mine a qfc with an invalid quorum hash, which should end up being rejected.
1089+ // 8) Mine the final valid qfc in a block.
1090+ // 9) Mine a null qfc after mining a valid qfc, which should end up being rejected.
1091+
1092+ // 1) Mine a null qfc before the mining phase, which should end up being rejected.
1093+ CMutableTransaction nullQfcTx = CreateNullQfcTx (quorumHash, nHeight);
1094+ CScript coinsbaseScript = CScript () << ToByteVector (coinbaseKey.GetPubKey ()) << OP_CHECKSIG;
1095+ auto pblock_invalid = std::make_shared<CBlock>(CreateBlock ({nullQfcTx}, coinsbaseScript, true , false , false ));
1096+ ProcessBlockAndCheckRejectionReason (pblock_invalid, " bad-qc-height" , nHeight);
1097+
1098+ // 2) Mine a null qfc before the mining phase, which should end up being rejected.
1099+ nullQfcTx = CreateNullQfcTx (quorumHash, nHeight + 1 );
1100+ pblock_invalid = std::make_shared<CBlock>(
1101+ CreateBlock ({nullQfcTx}, coinsbaseScript, true , false , false ));
1102+ ProcessBlockAndCheckRejectionReason (pblock_invalid, " bad-qc-not-allowed" , nHeight);
1103+
1104+ // One more block, 449.
1105+ CreateAndProcessBlock ({}, coinbaseKey);
1106+ chainTip = chainActive.Tip ();
1107+ BOOST_CHECK_EQUAL (chainTip->nHeight , ++nHeight);
1108+
1109+ // 3) Mine two qfc in the same block, which should end up on a rejection. (one null, one valid)
1110+ nullQfcTx = CreateNullQfcTx (quorumHash, nHeight + 1 );
1111+ pblock_invalid = std::make_shared<CBlock>(
1112+ CreateBlock ({nullQfcTx}, coinsbaseScript, true , false , true ));
1113+ ProcessBlockAndCheckRejectionReason (pblock_invalid, " bad-qc-dup" , nHeight);
1114+
1115+ // 4) Mine block without qfc during the mining phase, which should end up being rejected.
1116+ pblock_invalid = std::make_shared<CBlock>(CreateBlock ({}, coinsbaseScript, true , false , false ));
1117+ ProcessBlockAndCheckRejectionReason (pblock_invalid, " bad-qc-missing" , nHeight);
1118+
1119+ // 5) Mine two blocks with a null qfc.
1120+ for (int i = 0 ; i < 2 ; i++) {
1121+ const auto & block = CreateBlock ({nullQfcTx}, coinsbaseScript, true , false , false );
1122+ ProcessNewBlock (std::make_shared<const CBlock>(block), nullptr );
1123+ chainTip = chainActive.Tip ();
1124+ BOOST_CHECK_EQUAL (chainTip->nHeight , ++nHeight);
1125+ nullQfcTx = CreateNullQfcTx (quorumHash, nHeight + 1 );
1126+ }
1127+ BOOST_CHECK_EQUAL (nHeight, 451 );
1128+
1129+ // 6) Try to relay the valid qfc to the mempool, which should end up on a rejection.
1130+ CTransactionRef qcTx;
1131+ BOOST_CHECK (llmq::quorumBlockProcessor->GetMinableCommitmentTx (Consensus::LLMQ_TEST, nHeight + 1 , qcTx));
1132+ CValidationState mempoolState;
1133+ BOOST_CHECK (!AcceptToMemoryPool (mempool, mempoolState, qcTx, true , nullptr ));
1134+
1135+ // 7) Mine a qfc with an invalid quorum hash, which should end up being rejected.
1136+ nullQfcTx = CreateNullQfcTx (chainTip->GetBlockHash (), nHeight + 1 );
1137+ pblock_invalid = std::make_shared<CBlock>(CreateBlock ({nullQfcTx}, coinsbaseScript, true , false , false ));
1138+ ProcessBlockAndCheckRejectionReason (pblock_invalid, " bad-qc-block" , nHeight);
1139+
1140+ // 8) Mine the final valid qfc in a block.
1141+ CreateAndProcessBlock ({}, coinbaseKey);
1142+ chainTip = chainActive.Tip ();
1143+ BOOST_CHECK_EQUAL (chainTip->nHeight , ++nHeight);
1144+
1145+ // 9) Mine a null qfc after mining a valid qfc, which should end up being rejected.
1146+ nullQfcTx = CreateNullQfcTx (chainTip->GetBlockHash (), nHeight + 1 );
1147+ pblock_invalid = std::make_shared<CBlock>(CreateBlock ({nullQfcTx}, coinsbaseScript, true , false , false ));
1148+ ProcessBlockAndCheckRejectionReason (pblock_invalid, " bad-qc-not-allowed" , nHeight);
10661149
10671150 // final commitment has been mined
10681151 llmq::CFinalCommitment ret;
@@ -1086,7 +1169,7 @@ BOOST_FIXTURE_TEST_CASE(dkg_pose, TestChain400Setup)
10861169 BOOST_CHECK_EQUAL (punished_mn->pdmnState ->nPoSePenalty , 65 );
10871170
10881171 // New DKG starts at block 480. Mine till block 481 and create another valid 2-of-3 commitment
1089- for (size_t i = 0 ; i < 30 ; i++) {
1172+ for (size_t i = 0 ; i < 28 ; i++) {
10901173 CreateAndProcessBlock ({}, coinbaseKey);
10911174 chainTip = chainActive.Tip ();
10921175 BOOST_CHECK_EQUAL (chainTip->nHeight , ++nHeight);
0 commit comments