LCOV - code coverage report
Current view: top level - src/test - blockfilter_tests.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 262 266 98.5 %
Date: 2026-06-25 07:23:43 Functions: 24 24 100.0 %

          Line data    Source code
       1             : // Copyright (c) 2018-2020 The Bitcoin 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/data/blockfilters.json.h>
       6             : #include <test/util/setup_common.h>
       7             : 
       8             : #include <blockfilter.h>
       9             : #include <core_io.h>
      10             : #include <evo/assetlocktx.h>
      11             : #include <evo/netinfo.h>
      12             : #include <evo/providertx.h>
      13             : #include <evo/specialtx.h>
      14             : #include <netaddress.h>
      15             : #include <netbase.h>
      16             : #include <serialize.h>
      17             : #include <span.h>
      18             : #include <streams.h>
      19             : #include <univalue.h>
      20             : #include <util/strencodings.h>
      21             : 
      22             : #include <boost/test/unit_test.hpp>
      23             : 
      24         146 : BOOST_AUTO_TEST_SUITE(blockfilter_tests)
      25             : 
      26         148 : BOOST_AUTO_TEST_CASE(gcsfilter_test)
      27             : {
      28           1 :     GCSFilter::ElementSet included_elements, excluded_elements;
      29         101 :     for (int i = 0; i < 100; ++i) {
      30         100 :         GCSFilter::Element element1(32);
      31         100 :         element1[0] = i;
      32         100 :         included_elements.insert(std::move(element1));
      33             : 
      34         100 :         GCSFilter::Element element2(32);
      35         100 :         element2[1] = i;
      36         100 :         excluded_elements.insert(std::move(element2));
      37         100 :     }
      38             : 
      39           1 :     GCSFilter filter({0, 0, 10, 1 << 10}, included_elements);
      40         101 :     for (const auto& element : included_elements) {
      41         100 :         BOOST_CHECK(filter.Match(element));
      42             : 
      43         100 :         auto insertion = excluded_elements.insert(element);
      44         100 :         BOOST_CHECK(filter.MatchAny(excluded_elements));
      45         100 :         excluded_elements.erase(insertion.first);
      46             :     }
      47           1 : }
      48             : 
      49         148 : BOOST_AUTO_TEST_CASE(gcsfilter_default_constructor)
      50             : {
      51           1 :     GCSFilter filter;
      52           1 :     BOOST_CHECK_EQUAL(filter.GetN(), 0U);
      53           1 :     BOOST_CHECK_EQUAL(filter.GetEncoded().size(), 1U);
      54             : 
      55           1 :     const GCSFilter::Params& params = filter.GetParams();
      56           1 :     BOOST_CHECK_EQUAL(params.m_siphash_k0, 0U);
      57           1 :     BOOST_CHECK_EQUAL(params.m_siphash_k1, 0U);
      58           1 :     BOOST_CHECK_EQUAL(params.m_P, 0);
      59           1 :     BOOST_CHECK_EQUAL(params.m_M, 1U);
      60           1 : }
      61             : 
      62         148 : BOOST_AUTO_TEST_CASE(blockfilter_basic_test)
      63             : {
      64           8 :     CScript included_scripts[5], excluded_scripts[4];
      65             : 
      66             :     // First two are outputs on a single transaction.
      67           1 :     included_scripts[0] << std::vector<unsigned char>(0, 65) << OP_CHECKSIG;
      68           1 :     included_scripts[1] << OP_DUP << OP_HASH160 << std::vector<unsigned char>(1, 20) << OP_EQUALVERIFY << OP_CHECKSIG;
      69             : 
      70             :     // Third is an output on in a second transaction.
      71           1 :     included_scripts[2] << OP_1 << std::vector<unsigned char>(2, 33) << OP_1 << OP_CHECKMULTISIG;
      72             : 
      73             :     // Last two are spent by a single transaction.
      74           1 :     included_scripts[3] << OP_0 << std::vector<unsigned char>(3, 32);
      75           1 :     included_scripts[4] << OP_4 << OP_ADD << OP_8 << OP_EQUAL;
      76             : 
      77             :     // OP_RETURN output is an output on the second transaction.
      78           1 :     excluded_scripts[0] << OP_RETURN << std::vector<unsigned char>(4, 40);
      79             : 
      80             :     // This script is not related to the block at all.
      81           1 :     excluded_scripts[1] << std::vector<unsigned char>(5, 33) << OP_CHECKSIG;
      82             : 
      83             :     // OP_RETURN is non-standard since it's not followed by a data push, but is still excluded from
      84             :     // filter.
      85           1 :     excluded_scripts[2] << OP_RETURN << OP_4 << OP_ADD << OP_8 << OP_EQUAL;
      86             : 
      87           1 :     CMutableTransaction tx_1;
      88           1 :     tx_1.vout.emplace_back(100, included_scripts[0]);
      89           1 :     tx_1.vout.emplace_back(200, included_scripts[1]);
      90           1 :     tx_1.vout.emplace_back(0, excluded_scripts[0]);
      91             : 
      92           1 :     CMutableTransaction tx_2;
      93           1 :     tx_2.vout.emplace_back(300, included_scripts[2]);
      94           1 :     tx_2.vout.emplace_back(0, excluded_scripts[2]);
      95           1 :     tx_2.vout.emplace_back(400, excluded_scripts[3]); // Script is empty
      96             : 
      97           1 :     CBlock block;
      98           1 :     block.vtx.push_back(MakeTransactionRef(tx_1));
      99           1 :     block.vtx.push_back(MakeTransactionRef(tx_2));
     100             : 
     101           1 :     CBlockUndo block_undo;
     102           1 :     block_undo.vtxundo.emplace_back();
     103           1 :     block_undo.vtxundo.back().vprevout.emplace_back(CTxOut(500, included_scripts[3]), 1000, true);
     104           1 :     block_undo.vtxundo.back().vprevout.emplace_back(CTxOut(600, included_scripts[4]), 10000, false);
     105           1 :     block_undo.vtxundo.back().vprevout.emplace_back(CTxOut(700, excluded_scripts[3]), 100000, false);
     106             : 
     107           1 :     BlockFilter block_filter(BlockFilterType::BASIC_FILTER, block, block_undo);
     108           1 :     const GCSFilter& filter = block_filter.GetFilter();
     109             : 
     110           6 :     for (const CScript& script : included_scripts) {
     111           5 :         BOOST_CHECK(filter.Match(GCSFilter::Element(script.begin(), script.end())));
     112             :     }
     113           5 :     for (const CScript& script : excluded_scripts) {
     114           4 :         BOOST_CHECK(!filter.Match(GCSFilter::Element(script.begin(), script.end())));
     115             :     }
     116             : 
     117             :     // Test serialization/unserialization.
     118           1 :     BlockFilter block_filter2;
     119             : 
     120           1 :     CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
     121           1 :     stream << block_filter;
     122           1 :     stream >> block_filter2;
     123             : 
     124           1 :     BOOST_CHECK_EQUAL(block_filter.GetFilterType(), block_filter2.GetFilterType());
     125           1 :     BOOST_CHECK_EQUAL(block_filter.GetBlockHash(), block_filter2.GetBlockHash());
     126           1 :     BOOST_CHECK(block_filter.GetEncodedFilter() == block_filter2.GetEncodedFilter());
     127             : 
     128           1 :     BlockFilter default_ctor_block_filter_1;
     129           1 :     BlockFilter default_ctor_block_filter_2;
     130           1 :     BOOST_CHECK_EQUAL(default_ctor_block_filter_1.GetFilterType(), default_ctor_block_filter_2.GetFilterType());
     131           1 :     BOOST_CHECK_EQUAL(default_ctor_block_filter_1.GetBlockHash(), default_ctor_block_filter_2.GetBlockHash());
     132           1 :     BOOST_CHECK(default_ctor_block_filter_1.GetEncodedFilter() == default_ctor_block_filter_2.GetEncodedFilter());
     133           1 : }
     134             : 
     135         148 : BOOST_AUTO_TEST_CASE(blockfilters_json_test)
     136             : {
     137           1 :     UniValue json;
     138           1 :     std::string json_data(json_tests::blockfilters,
     139             :                           json_tests::blockfilters + sizeof(json_tests::blockfilters));
     140           1 :     if (!json.read(json_data) || !json.isArray()) {
     141           0 :         BOOST_ERROR("Parse error.");
     142           0 :         return;
     143             :     }
     144             : 
     145           1 :     const UniValue& tests = json.get_array();
     146           9 :     for (unsigned int i = 0; i < tests.size(); i++) {
     147           8 :         const UniValue& test = tests[i];
     148           8 :         std::string strTest = test.write();
     149             : 
     150           8 :         if (test.size() == 1) {
     151           1 :             continue;
     152           7 :         } else if (test.size() < 7) {
     153           0 :             BOOST_ERROR("Bad test: " << strTest);
     154           0 :             continue;
     155             :         }
     156             : 
     157           7 :         unsigned int pos = 0;
     158           7 :         /*int block_height =*/ test[pos++].getInt<int>();
     159           7 :         uint256 block_hash;
     160           7 :         BOOST_CHECK(ParseHashStr(test[pos++].get_str(), block_hash));
     161             : 
     162           7 :         CBlock block;
     163           7 :         BOOST_REQUIRE(DecodeHexBlk(block, test[pos++].get_str()));
     164             : 
     165           7 :         CBlockUndo block_undo;
     166           7 :         block_undo.vtxundo.emplace_back();
     167           7 :         CTxUndo& tx_undo = block_undo.vtxundo.back();
     168           7 :         const UniValue& prev_scripts = test[pos++].get_array();
     169          31 :         for (unsigned int ii = 0; ii < prev_scripts.size(); ii++) {
     170          24 :             std::vector<unsigned char> raw_script = ParseHex(prev_scripts[ii].get_str());
     171          24 :             CTxOut txout(0, CScript(raw_script.begin(), raw_script.end()));
     172          24 :             tx_undo.vprevout.emplace_back(txout, 0, false);
     173          24 :         }
     174             : 
     175           7 :         uint256 prev_filter_header_basic;
     176           7 :         BOOST_CHECK(ParseHashStr(test[pos++].get_str(), prev_filter_header_basic));
     177           7 :         std::vector<unsigned char> filter_basic = ParseHex(test[pos++].get_str());
     178           7 :         uint256 filter_header_basic;
     179           7 :         BOOST_CHECK(ParseHashStr(test[pos++].get_str(), filter_header_basic));
     180             : 
     181           7 :         BlockFilter computed_filter_basic(BlockFilterType::BASIC_FILTER, block, block_undo);
     182           7 :         BOOST_CHECK(computed_filter_basic.GetFilter().GetEncoded() == filter_basic);
     183             : 
     184           7 :         uint256 computed_header_basic = computed_filter_basic.ComputeHeader(prev_filter_header_basic);
     185           7 :         BOOST_CHECK(computed_header_basic == filter_header_basic);
     186           8 :     }
     187           1 : }
     188             : 
     189         148 : BOOST_AUTO_TEST_CASE(blockfilter_type_names)
     190             : {
     191           1 :     BOOST_CHECK_EQUAL(BlockFilterTypeName(BlockFilterType::BASIC_FILTER), "basic");
     192           1 :     BOOST_CHECK_EQUAL(BlockFilterTypeName(static_cast<BlockFilterType>(255)), "");
     193             : 
     194             :     BlockFilterType filter_type;
     195           1 :     BOOST_CHECK(BlockFilterTypeByName("basic", filter_type));
     196           1 :     BOOST_CHECK_EQUAL(filter_type, BlockFilterType::BASIC_FILTER);
     197             : 
     198           1 :     BOOST_CHECK(!BlockFilterTypeByName("unknown", filter_type));
     199           1 : }
     200             : 
     201         148 : BOOST_AUTO_TEST_CASE(blockfilter_special_transactions_test)
     202             : {
     203             :     // Test that special transaction fields are properly included in block filters
     204             :     // This ensures parity with bloom filter functionality
     205             : 
     206             :     // Test 1: ProRegTx (Provider Registration Transaction)
     207           1 :     CMutableTransaction protx;
     208           1 :     protx.nVersion = 3;
     209           1 :     protx.nType = TRANSACTION_PROVIDER_REGISTER;
     210             : 
     211           1 :     CProRegTx proRegPayload;
     212           1 :     proRegPayload.nVersion = 1;
     213           1 :     proRegPayload.nType = MnType::Regular;
     214           1 :     proRegPayload.nMode = 0;
     215           1 :     proRegPayload.collateralOutpoint = COutPoint(uint256S("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), 0);
     216             : 
     217             :     // Set up network info (required for serialization)
     218           1 :     proRegPayload.netInfo = NetInfoInterface::MakeNetInfo(1);  // Version 1
     219             : 
     220           1 :     proRegPayload.keyIDOwner = CKeyID(uint160(ParseHex("1111111111111111111111111111111111111111")));
     221           1 :     proRegPayload.keyIDVoting = CKeyID(uint160(ParseHex("2222222222222222222222222222222222222222")));
     222             : 
     223             :     // Generate a valid BLS key
     224           1 :     CBLSSecretKey blsKey;
     225           1 :     blsKey.MakeNewKey();
     226           1 :     proRegPayload.pubKeyOperator.Set(blsKey.GetPublicKey(), false);
     227           1 :     proRegPayload.inputsHash = uint256();  // Empty inputs hash
     228             : 
     229             :     // Create payout script
     230           1 :     CScript payoutScript = CScript() << OP_DUP << OP_HASH160 << ToByteVector(ParseHex("3333333333333333333333333333333333333333")) << OP_EQUALVERIFY << OP_CHECKSIG;
     231           1 :     proRegPayload.scriptPayout = payoutScript;
     232             : 
     233           1 :     SetTxPayload(protx, proRegPayload);
     234             : 
     235             :     // Test 2: ProUpServTx (Provider Update Service Transaction)
     236           1 :     CMutableTransaction proupservtx;
     237           1 :     proupservtx.nVersion = 3;
     238           1 :     proupservtx.nType = TRANSACTION_PROVIDER_UPDATE_SERVICE;
     239             : 
     240           1 :     CProUpServTx proUpServPayload;
     241           1 :     proUpServPayload.nVersion = 1;
     242           1 :     proUpServPayload.proTxHash = uint256S("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
     243           1 :     proUpServPayload.netInfo = NetInfoInterface::MakeNetInfo(1);
     244           1 :     CScript operatorPayoutScript = CScript() << OP_DUP << OP_HASH160 << ToByteVector(ParseHex("5555555555555555555555555555555555555555")) << OP_EQUALVERIFY << OP_CHECKSIG;
     245           1 :     proUpServPayload.scriptOperatorPayout = operatorPayoutScript;
     246           1 :     proUpServPayload.inputsHash = uint256();
     247             : 
     248           1 :     SetTxPayload(proupservtx, proUpServPayload);
     249             : 
     250             :     // Test 3: ProUpRegTx (Provider Update Registrar Transaction)
     251           1 :     CMutableTransaction proupregtx;
     252           1 :     proupregtx.nVersion = 3;
     253           1 :     proupregtx.nType = TRANSACTION_PROVIDER_UPDATE_REGISTRAR;
     254             : 
     255           1 :     CProUpRegTx proUpRegPayload;
     256           1 :     proUpRegPayload.nVersion = 1;
     257           1 :     proUpRegPayload.proTxHash = uint256S("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
     258           1 :     proUpRegPayload.nMode = 0;
     259           1 :     proUpRegPayload.pubKeyOperator.Set(blsKey.GetPublicKey(), false);
     260           1 :     proUpRegPayload.keyIDVoting = CKeyID(uint160(ParseHex("6666666666666666666666666666666666666666")));
     261           1 :     CScript newPayoutScript = CScript() << OP_DUP << OP_HASH160 << ToByteVector(ParseHex("7777777777777777777777777777777777777777")) << OP_EQUALVERIFY << OP_CHECKSIG;
     262           1 :     proUpRegPayload.scriptPayout = newPayoutScript;
     263           1 :     proUpRegPayload.inputsHash = uint256();
     264             : 
     265           1 :     SetTxPayload(proupregtx, proUpRegPayload);
     266             : 
     267             :     // Test 4: ProUpRevTx (Provider Update Revoke Transaction)
     268           1 :     CMutableTransaction prouprevtx;
     269           1 :     prouprevtx.nVersion = 3;
     270           1 :     prouprevtx.nType = TRANSACTION_PROVIDER_UPDATE_REVOKE;
     271             : 
     272           1 :     CProUpRevTx proUpRevPayload;
     273           1 :     proUpRevPayload.nVersion = 1;
     274           1 :     proUpRevPayload.proTxHash = uint256S("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc");
     275           1 :     proUpRevPayload.nReason = 1;
     276           1 :     proUpRevPayload.inputsHash = uint256();
     277             : 
     278           1 :     SetTxPayload(prouprevtx, proUpRevPayload);
     279             : 
     280             :     // Test 5: AssetLockTx
     281           1 :     CMutableTransaction assetlockTx;
     282           1 :     assetlockTx.nVersion = 3;
     283           1 :     assetlockTx.nType = TRANSACTION_ASSET_LOCK;
     284             : 
     285           1 :     CAssetLockPayload assetLockPayload;
     286           1 :     CScript creditScript = CScript() << OP_DUP << OP_HASH160 << ToByteVector(ParseHex("4444444444444444444444444444444444444444")) << OP_EQUALVERIFY << OP_CHECKSIG;
     287           1 :     CTxOut creditOut(1000, creditScript);
     288           1 :     std::vector<CTxOut> creditOutputs = {creditOut};
     289           1 :     assetLockPayload = CAssetLockPayload(creditOutputs);
     290             : 
     291           1 :     SetTxPayload(assetlockTx, assetLockPayload);
     292             : 
     293             :     // Create block with all special transactions
     294           1 :     CBlock block;
     295           1 :     block.vtx.push_back(MakeTransactionRef(protx));
     296           1 :     block.vtx.push_back(MakeTransactionRef(proupservtx));
     297           1 :     block.vtx.push_back(MakeTransactionRef(proupregtx));
     298           1 :     block.vtx.push_back(MakeTransactionRef(prouprevtx));
     299           1 :     block.vtx.push_back(MakeTransactionRef(assetlockTx));
     300             : 
     301             :     // Create minimal block undo
     302           1 :     CBlockUndo block_undo;
     303           6 :     for (size_t i = 0; i < block.vtx.size(); i++) {
     304           5 :         block_undo.vtxundo.emplace_back();
     305           5 :     }
     306             : 
     307             :     // Create block filter
     308           1 :     BlockFilter block_filter(BlockFilterType::BASIC_FILTER, block, block_undo);
     309           1 :     const GCSFilter& filter = block_filter.GetFilter();
     310             : 
     311             :     // Test ProRegTx fields
     312             :     // collateralOutpoint
     313           1 :     CDataStream outpointStream(SER_NETWORK, PROTOCOL_VERSION);
     314           1 :     outpointStream << proRegPayload.collateralOutpoint;
     315           1 :     auto outpointSpan = MakeUCharSpan(outpointStream);
     316           1 :     BOOST_CHECK(filter.Match(GCSFilter::Element(outpointSpan.begin(), outpointSpan.end())));
     317             : 
     318             :     // keyIDOwner
     319           1 :     BOOST_CHECK(filter.Match(GCSFilter::Element(proRegPayload.keyIDOwner.begin(), proRegPayload.keyIDOwner.end())));
     320             : 
     321             :     // keyIDVoting
     322           1 :     BOOST_CHECK(filter.Match(GCSFilter::Element(proRegPayload.keyIDVoting.begin(), proRegPayload.keyIDVoting.end())));
     323             : 
     324             :     // scriptPayout
     325           1 :     BOOST_CHECK(filter.Match(GCSFilter::Element(payoutScript.begin(), payoutScript.end())));
     326             : 
     327             :     // Test ProUpServTx fields
     328             :     // proTxHash
     329           1 :     BOOST_CHECK(filter.Match(GCSFilter::Element(proUpServPayload.proTxHash.begin(), proUpServPayload.proTxHash.end())));
     330             : 
     331             :     // scriptOperatorPayout
     332           1 :     BOOST_CHECK(filter.Match(GCSFilter::Element(operatorPayoutScript.begin(), operatorPayoutScript.end())));
     333             : 
     334             :     // Test ProUpRegTx fields
     335             :     // proTxHash
     336           1 :     BOOST_CHECK(filter.Match(GCSFilter::Element(proUpRegPayload.proTxHash.begin(), proUpRegPayload.proTxHash.end())));
     337             : 
     338             :     // keyIDVoting
     339           1 :     BOOST_CHECK(filter.Match(GCSFilter::Element(proUpRegPayload.keyIDVoting.begin(), proUpRegPayload.keyIDVoting.end())));
     340             : 
     341             :     // scriptPayout
     342           1 :     BOOST_CHECK(filter.Match(GCSFilter::Element(newPayoutScript.begin(), newPayoutScript.end())));
     343             : 
     344             :     // Test ProUpRevTx fields
     345             :     // proTxHash
     346           1 :     BOOST_CHECK(filter.Match(GCSFilter::Element(proUpRevPayload.proTxHash.begin(), proUpRevPayload.proTxHash.end())));
     347             : 
     348             :     // Test AssetLock creditOutputs script
     349           1 :     BOOST_CHECK(filter.Match(GCSFilter::Element(creditScript.begin(), creditScript.end())));
     350             : 
     351             :     // Check that unrelated data is not in the filter
     352           1 :     CKeyID unrelatedKey(uint160(ParseHex("9999999999999999999999999999999999999999")));
     353           1 :     BOOST_CHECK(!filter.Match(GCSFilter::Element(unrelatedKey.begin(), unrelatedKey.end())));
     354           1 : }
     355             : 
     356         148 : BOOST_AUTO_TEST_CASE(blockfilter_special_transactions_edge_cases)
     357             : {
     358             :     // Test edge cases for special transaction filtering
     359             : 
     360             :     // Test 1: Empty scripts should be handled gracefully
     361           1 :     CMutableTransaction protx_empty;
     362           1 :     protx_empty.nVersion = 3;
     363           1 :     protx_empty.nType = TRANSACTION_PROVIDER_REGISTER;
     364             : 
     365           1 :     CProRegTx proRegPayload_empty;
     366           1 :     proRegPayload_empty.nVersion = 1;
     367           1 :     proRegPayload_empty.nType = MnType::Regular;
     368           1 :     proRegPayload_empty.nMode = 0;
     369           1 :     proRegPayload_empty.collateralOutpoint = COutPoint(uint256S("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), 0);
     370           1 :     proRegPayload_empty.netInfo = NetInfoInterface::MakeNetInfo(1);
     371           1 :     proRegPayload_empty.keyIDOwner = CKeyID(uint160(ParseHex("1111111111111111111111111111111111111111")));
     372           1 :     proRegPayload_empty.keyIDVoting = CKeyID(uint160(ParseHex("2222222222222222222222222222222222222222")));
     373             : 
     374           1 :     CBLSSecretKey blsKey;
     375           1 :     blsKey.MakeNewKey();
     376           1 :     proRegPayload_empty.pubKeyOperator.Set(blsKey.GetPublicKey(), false);
     377           1 :     proRegPayload_empty.inputsHash = uint256();
     378             : 
     379             :     // Empty payout script
     380           1 :     proRegPayload_empty.scriptPayout = CScript();
     381             : 
     382           1 :     SetTxPayload(protx_empty, proRegPayload_empty);
     383             : 
     384             :     // Test 2: AssetLock with OP_RETURN output (should be excluded)
     385           1 :     CMutableTransaction assetlock_opreturn;
     386           1 :     assetlock_opreturn.nVersion = 3;
     387           1 :     assetlock_opreturn.nType = TRANSACTION_ASSET_LOCK;
     388             : 
     389           1 :     CAssetLockPayload assetLockPayload_opreturn;
     390           1 :     CScript opReturnScript = CScript() << OP_RETURN << ToByteVector(ParseHex("deadbeef"));
     391           1 :     CScript normalScript = CScript() << OP_DUP << OP_HASH160 << ToByteVector(ParseHex("8888888888888888888888888888888888888888")) << OP_EQUALVERIFY << OP_CHECKSIG;
     392             : 
     393           1 :     CTxOut opReturnOut(0, opReturnScript);
     394           1 :     CTxOut normalOut(1000, normalScript);
     395           1 :     std::vector<CTxOut> mixedOutputs = {opReturnOut, normalOut};
     396           1 :     assetLockPayload_opreturn = CAssetLockPayload(mixedOutputs);
     397             : 
     398           1 :     SetTxPayload(assetlock_opreturn, assetLockPayload_opreturn);
     399             : 
     400             :     // Test 3: ProUpServTx with empty operator payout script
     401           1 :     CMutableTransaction proupserv_empty;
     402           1 :     proupserv_empty.nVersion = 3;
     403           1 :     proupserv_empty.nType = TRANSACTION_PROVIDER_UPDATE_SERVICE;
     404             : 
     405           1 :     CProUpServTx proUpServPayload_empty;
     406           1 :     proUpServPayload_empty.nVersion = 1;
     407           1 :     proUpServPayload_empty.proTxHash = uint256S("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd");
     408           1 :     proUpServPayload_empty.netInfo = NetInfoInterface::MakeNetInfo(1);
     409           1 :     proUpServPayload_empty.scriptOperatorPayout = CScript();  // Empty script
     410           1 :     proUpServPayload_empty.inputsHash = uint256();
     411             : 
     412           1 :     SetTxPayload(proupserv_empty, proUpServPayload_empty);
     413             : 
     414             :     // Create block with edge case transactions
     415           1 :     CBlock block;
     416           1 :     block.vtx.push_back(MakeTransactionRef(protx_empty));
     417           1 :     block.vtx.push_back(MakeTransactionRef(assetlock_opreturn));
     418           1 :     block.vtx.push_back(MakeTransactionRef(proupserv_empty));
     419             : 
     420             :     // Create minimal block undo
     421           1 :     CBlockUndo block_undo;
     422           4 :     for (size_t i = 0; i < block.vtx.size(); i++) {
     423           3 :         block_undo.vtxundo.emplace_back();
     424           3 :     }
     425             : 
     426             :     // Create block filter - should not crash with edge cases
     427           1 :     BlockFilter block_filter(BlockFilterType::BASIC_FILTER, block, block_undo);
     428           1 :     const GCSFilter& filter = block_filter.GetFilter();
     429             : 
     430             :     // Test that non-empty fields are still included
     431           1 :     BOOST_CHECK(filter.Match(GCSFilter::Element(proRegPayload_empty.keyIDOwner.begin(),
     432             :                                                 proRegPayload_empty.keyIDOwner.end())));
     433             : 
     434             :     // Test that OP_RETURN script is NOT in filter
     435           1 :     BOOST_CHECK(!filter.Match(GCSFilter::Element(opReturnScript.begin(), opReturnScript.end())));
     436             : 
     437             :     // Test that normal script from AssetLock IS in filter
     438           1 :     BOOST_CHECK(filter.Match(GCSFilter::Element(normalScript.begin(), normalScript.end())));
     439             : 
     440             :     // Test that ProUpServTx hash is still included even with empty payout script
     441           1 :     BOOST_CHECK(filter.Match(GCSFilter::Element(proUpServPayload_empty.proTxHash.begin(),
     442             :                                                proUpServPayload_empty.proTxHash.end())));
     443           1 : }
     444             : 
     445         146 : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.16