Skip to content

Commit 658ce9e

Browse files
gladcowUdjinM6
gladcow
authored andcommitted
Apply Bloom filters to DIP2 transactions extra payload (#2786)
* check matches for special transactions additional data * additional method to check matches for CKeyID * remove code duplication * unit tests for bloom filters for DIP2 txes * automatically update filters if special transaction matches * unit tests for filter updates * Error in comment Co-Authored-By: gladcow <sergey@dash.org> * use switch instead of if-chain * fix version check * remove code duplication * add negative tests in unit tests
1 parent a1e4ac2 commit 658ce9e

File tree

3 files changed

+221
-29
lines changed

3 files changed

+221
-29
lines changed

src/bloom.cpp

+114-29
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
#include "bloom.h"
66

77
#include "primitives/transaction.h"
8+
#include "evo/specialtx.h"
9+
#include "evo/providertx.h"
10+
#include "evo/cbtx.h"
11+
#include "llmq/quorums_commitment.h"
812
#include "hash.h"
913
#include "script/script.h"
1014
#include "script/standard.h"
@@ -113,6 +117,12 @@ bool CBloomFilter::contains(const uint256& hash) const
113117
return contains(data);
114118
}
115119

120+
bool CBloomFilter::contains(const uint160& hash) const
121+
{
122+
std::vector<unsigned char> data(hash.begin(), hash.end());
123+
return contains(data);
124+
}
125+
116126
void CBloomFilter::clear()
117127
{
118128
vData.assign(vData.size(),0);
@@ -131,6 +141,96 @@ bool CBloomFilter::IsWithinSizeConstraints() const
131141
return vData.size() <= MAX_BLOOM_FILTER_SIZE && nHashFuncs <= MAX_HASH_FUNCS;
132142
}
133143

144+
// Match if the filter contains any arbitrary script data element in script
145+
bool CBloomFilter::CheckScript(const CScript &script) const
146+
{
147+
CScript::const_iterator pc = script.begin();
148+
std::vector<unsigned char> data;
149+
while (pc < script.end()) {
150+
opcodetype opcode;
151+
if (!script.GetOp(pc, opcode, data))
152+
break;
153+
if (data.size() != 0 && contains(data))
154+
return true;
155+
}
156+
return false;
157+
}
158+
159+
// If the transaction is a special transaction that has a registration
160+
// transaction hash, test the registration transaction hash.
161+
// If the transaction is a special transaction with any public keys or any
162+
// public key hashes test them.
163+
// If the transaction is a special transaction with payout addresses test
164+
// the hash160 of those addresses.
165+
// Filter is updated only if it has BLOOM_UPDATE_ALL flag to be able to have
166+
// simple SPV wallets that doesn't work with DIP2 transactions (multicoin
167+
// wallets, etc.)
168+
bool CBloomFilter::CheckSpecialTransactionMatchesAndUpdate(const CTransaction &tx)
169+
{
170+
if(tx.nVersion != 3 || tx.nType == TRANSACTION_NORMAL) {
171+
return false; // it is not a special transaction
172+
}
173+
switch(tx.nType) {
174+
case(TRANSACTION_PROVIDER_REGISTER): {
175+
CProRegTx proTx;
176+
if (GetTxPayload(tx, proTx)) {
177+
if(contains(proTx.collateralOutpoint) ||
178+
contains(proTx.keyIDOwner) ||
179+
contains(proTx.keyIDVoting) ||
180+
CheckScript(proTx.scriptPayout)) {
181+
if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_ALL)
182+
insert(tx.GetHash());
183+
return true;
184+
}
185+
}
186+
return false;
187+
}
188+
case(TRANSACTION_PROVIDER_UPDATE_SERVICE): {
189+
CProUpServTx proTx;
190+
if (GetTxPayload(tx, proTx)) {
191+
if(contains(proTx.proTxHash)) {
192+
return true;
193+
}
194+
if(CheckScript(proTx.scriptOperatorPayout)) {
195+
if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_ALL)
196+
insert(proTx.proTxHash);
197+
return true;
198+
}
199+
}
200+
return false;
201+
}
202+
case(TRANSACTION_PROVIDER_UPDATE_REGISTRAR): {
203+
CProUpRegTx proTx;
204+
if (GetTxPayload(tx, proTx)) {
205+
if(contains(proTx.proTxHash))
206+
return true;
207+
if(contains(proTx.keyIDVoting) ||
208+
CheckScript(proTx.scriptPayout)) {
209+
if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_ALL)
210+
insert(proTx.proTxHash);
211+
return true;
212+
}
213+
}
214+
return false;
215+
}
216+
case(TRANSACTION_PROVIDER_UPDATE_REVOKE): {
217+
CProUpRevTx proTx;
218+
if (GetTxPayload(tx, proTx)) {
219+
if(contains(proTx.proTxHash))
220+
return true;
221+
}
222+
return false;
223+
}
224+
case(TRANSACTION_COINBASE):
225+
case(TRANSACTION_QUORUM_COMMITMENT):
226+
// No aditional checks for this transaction types
227+
return false;
228+
}
229+
230+
LogPrintf("Unknown special transaction type in Bloom filter check.");
231+
return false;
232+
}
233+
134234
bool CBloomFilter::IsRelevantAndUpdate(const CTransaction& tx)
135235
{
136236
bool fFound = false;
@@ -144,34 +244,27 @@ bool CBloomFilter::IsRelevantAndUpdate(const CTransaction& tx)
144244
if (contains(hash))
145245
fFound = true;
146246

247+
// Check additional matches for special transactions
248+
fFound = fFound || CheckSpecialTransactionMatchesAndUpdate(tx);
249+
147250
for (unsigned int i = 0; i < tx.vout.size(); i++)
148251
{
149252
const CTxOut& txout = tx.vout[i];
150253
// Match if the filter contains any arbitrary script data element in any scriptPubKey in tx
151254
// If this matches, also add the specific output that was matched.
152255
// This means clients don't have to update the filter themselves when a new relevant tx
153256
// is discovered in order to find spending transactions, which avoids round-tripping and race conditions.
154-
CScript::const_iterator pc = txout.scriptPubKey.begin();
155-
std::vector<unsigned char> data;
156-
while (pc < txout.scriptPubKey.end())
157-
{
158-
opcodetype opcode;
159-
if (!txout.scriptPubKey.GetOp(pc, opcode, data))
160-
break;
161-
if (data.size() != 0 && contains(data))
257+
if(CheckScript(txout.scriptPubKey)) {
258+
fFound = true;
259+
if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_ALL)
260+
insert(COutPoint(hash, i));
261+
else if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_P2PUBKEY_ONLY)
162262
{
163-
fFound = true;
164-
if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_ALL)
263+
txnouttype type;
264+
std::vector<std::vector<unsigned char> > vSolutions;
265+
if (Solver(txout.scriptPubKey, type, vSolutions) &&
266+
(type == TX_PUBKEY || type == TX_MULTISIG))
165267
insert(COutPoint(hash, i));
166-
else if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_P2PUBKEY_ONLY)
167-
{
168-
txnouttype type;
169-
std::vector<std::vector<unsigned char> > vSolutions;
170-
if (Solver(txout.scriptPubKey, type, vSolutions) &&
171-
(type == TX_PUBKEY || type == TX_MULTISIG))
172-
insert(COutPoint(hash, i));
173-
}
174-
break;
175268
}
176269
}
177270
}
@@ -186,16 +279,8 @@ bool CBloomFilter::IsRelevantAndUpdate(const CTransaction& tx)
186279
return true;
187280

188281
// Match if the filter contains any arbitrary script data element in any scriptSig in tx
189-
CScript::const_iterator pc = txin.scriptSig.begin();
190-
std::vector<unsigned char> data;
191-
while (pc < txin.scriptSig.end())
192-
{
193-
opcodetype opcode;
194-
if (!txin.scriptSig.GetOp(pc, opcode, data))
195-
break;
196-
if (data.size() != 0 && contains(data))
197-
return true;
198-
}
282+
if(CheckScript(txin.scriptSig))
283+
return true;
199284
}
200285

201286
return false;

src/bloom.h

+7
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
#include <vector>
1111

1212
class COutPoint;
13+
class CScript;
1314
class CTransaction;
1415
class uint256;
16+
class uint160;
1517

1618
//! 20,000 items with fp rate < 0.1% or 10,000 items and <0.0001%
1719
static const unsigned int MAX_BLOOM_FILTER_SIZE = 36000; // bytes
@@ -57,6 +59,10 @@ class CBloomFilter
5759
CBloomFilter(unsigned int nElements, double nFPRate, unsigned int nTweak);
5860
friend class CRollingBloomFilter;
5961

62+
// Check matches for arbitrary script data elements
63+
bool CheckScript(const CScript& script) const;
64+
// Check additional matches for special transactions
65+
bool CheckSpecialTransactionMatchesAndUpdate(const CTransaction& tx);
6066
public:
6167
/**
6268
* Creates a new bloom filter which will provide the given fp rate when filled with the given number of elements
@@ -87,6 +93,7 @@ class CBloomFilter
8793
bool contains(const std::vector<unsigned char>& vKey) const;
8894
bool contains(const COutPoint& outpoint) const;
8995
bool contains(const uint256& hash) const;
96+
bool contains(const uint160& hash) const;
9097

9198
void clear();
9299
void reset(unsigned int nNewTweak);

0 commit comments

Comments
 (0)