Line data Source code
1 : // Copyright (c) 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 <evo/specialtx_filter.h> 6 : 7 : #include <evo/assetlocktx.h> 8 : #include <evo/providertx.h> 9 : #include <evo/specialtx.h> 10 : #include <primitives/transaction.h> 11 : #include <script/script.h> 12 : #include <span.h> 13 : #include <streams.h> 14 : 15 : /** 16 : * Rationale for Special Transaction Field Extraction: 17 : * 18 : * This implementation extracts specific fields from Dash special transactions 19 : * to maintain parity with the bloom filter implementation (CBloomFilter::CheckSpecialTransactionMatchesAndUpdate). 20 : * 21 : * The fields extracted are those that SPV clients might need to detect: 22 : * - Owner/Voting keys: To track masternode ownership and voting rights 23 : * - Payout scripts: To detect payments to specific addresses 24 : * - ProTx hashes: To track masternode lifecycle and updates 25 : * - Collateral outpoints: To track masternode collateral 26 : * - Credit outputs: To track platform-related transactions 27 : * 28 : * Each transaction type has different fields based on its purpose: 29 : * - ProRegTx: All identity and payout fields (initial registration) 30 : * - ProUpServTx: ProTx hash and operator payout (service updates) 31 : * - ProUpRegTx: ProTx hash, voting key, and payout (ownership updates) 32 : * - ProUpRevTx: ProTx hash only (revocation tracking) 33 : * - AssetLockTx: Credit output scripts (platform credits) 34 : */ 35 : // Helper function to add a script to the filter if it's not empty 36 7 : static void AddScriptElement(const CScript& script, const std::function<void(Span<const unsigned char>)>& addElement) 37 : { 38 7 : if (!script.empty()) { 39 5 : addElement(MakeUCharSpan(script)); 40 5 : } 41 7 : } 42 : 43 : // Helper function to add a hash/key to the filter 44 : template <typename T> 45 9 : static void AddHashElement(const T& hash, const std::function<void(Span<const unsigned char>)>& addElement) 46 : { 47 9 : addElement(MakeUCharSpan(hash)); 48 9 : } 49 : 50 : // NOTE(maintenance): Keep this in sync with 51 : // CBloomFilter::CheckSpecialTransactionMatchesAndUpdate in 52 : // src/common/bloom.cpp. If you add or remove fields for a special 53 : // transaction type here, update the bloom filter routine accordingly 54 : // (and vice versa) to avoid compact-filter vs bloom-filter divergence. 55 250 : void ExtractSpecialTxFilterElements(const CTransaction& tx, const std::function<void(Span<const unsigned char>)>& addElement) 56 : { 57 250 : if (!tx.HasExtraPayloadField()) { 58 242 : return; // not a special transaction 59 : } 60 : 61 8 : switch (tx.nType) { 62 : case TRANSACTION_PROVIDER_REGISTER: { 63 4 : if (const auto opt_proTx = GetTxPayload<CProRegTx>(tx)) { 64 : // Add collateral outpoint 65 2 : CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); 66 2 : stream << opt_proTx->collateralOutpoint; 67 2 : addElement(MakeUCharSpan(stream)); 68 : 69 : // Add owner key ID 70 2 : AddHashElement(opt_proTx->keyIDOwner, addElement); 71 : 72 : // Add voting key ID 73 2 : AddHashElement(opt_proTx->keyIDVoting, addElement); 74 : 75 : // Add payout script 76 2 : AddScriptElement(opt_proTx->scriptPayout, addElement); 77 2 : } 78 2 : break; 79 : } 80 : case TRANSACTION_PROVIDER_UPDATE_SERVICE: { 81 4 : if (const auto opt_proTx = GetTxPayload<CProUpServTx>(tx)) { 82 : // Add ProTx hash 83 2 : AddHashElement(opt_proTx->proTxHash, addElement); 84 : 85 : // Add operator payout script 86 2 : AddScriptElement(opt_proTx->scriptOperatorPayout, addElement); 87 2 : } 88 2 : break; 89 : } 90 : case TRANSACTION_PROVIDER_UPDATE_REGISTRAR: { 91 2 : if (const auto opt_proTx = GetTxPayload<CProUpRegTx>(tx)) { 92 : // Add ProTx hash 93 1 : AddHashElement(opt_proTx->proTxHash, addElement); 94 : 95 : // Add voting key ID 96 1 : AddHashElement(opt_proTx->keyIDVoting, addElement); 97 : 98 : // Add payout script 99 1 : AddScriptElement(opt_proTx->scriptPayout, addElement); 100 1 : } 101 1 : break; 102 : } 103 : case TRANSACTION_PROVIDER_UPDATE_REVOKE: { 104 2 : if (const auto opt_proTx = GetTxPayload<CProUpRevTx>(tx)) { 105 : // Add ProTx hash 106 1 : AddHashElement(opt_proTx->proTxHash, addElement); 107 1 : } 108 1 : break; 109 : } 110 : case TRANSACTION_ASSET_LOCK: { 111 : // Asset Lock transactions have special outputs (creditOutputs) that should be included 112 4 : if (const auto opt_assetlockTx = GetTxPayload<CAssetLockPayload>(tx)) { 113 2 : const auto& extraOuts = opt_assetlockTx->getCreditOutputs(); 114 5 : for (const CTxOut& txout : extraOuts) { 115 3 : const CScript& script = txout.scriptPubKey; 116 : // Exclude OP_RETURN outputs as they are not spendable 117 3 : if (!script.empty() && script[0] != OP_RETURN) { 118 2 : AddScriptElement(script, addElement); 119 2 : } 120 : } 121 2 : } 122 2 : break; 123 : } 124 : case TRANSACTION_ASSET_UNLOCK: 125 : case TRANSACTION_COINBASE: 126 : case TRANSACTION_QUORUM_COMMITMENT: 127 : case TRANSACTION_MNHF_SIGNAL: 128 : // No additional special fields needed for these transaction types 129 : // Their standard outputs are already included in the base filter 130 0 : break; 131 : } // no default case, so the compiler can warn about missing cases 132 250 : }