Skip to content

Commit

Permalink
Apply Bloom filters to DIP2 transactions extra payload (#2786)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
gladcow authored and UdjinM6 committed Mar 21, 2019
1 parent a1e4ac2 commit 658ce9e
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 29 deletions.
143 changes: 114 additions & 29 deletions src/bloom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
#include "bloom.h"

#include "primitives/transaction.h"
#include "evo/specialtx.h"
#include "evo/providertx.h"
#include "evo/cbtx.h"
#include "llmq/quorums_commitment.h"
#include "hash.h"
#include "script/script.h"
#include "script/standard.h"
Expand Down Expand Up @@ -113,6 +117,12 @@ bool CBloomFilter::contains(const uint256& hash) const
return contains(data);
}

bool CBloomFilter::contains(const uint160& hash) const
{
std::vector<unsigned char> data(hash.begin(), hash.end());
return contains(data);
}

void CBloomFilter::clear()
{
vData.assign(vData.size(),0);
Expand All @@ -131,6 +141,96 @@ bool CBloomFilter::IsWithinSizeConstraints() const
return vData.size() <= MAX_BLOOM_FILTER_SIZE && nHashFuncs <= MAX_HASH_FUNCS;
}

// Match if the filter contains any arbitrary script data element in script
bool CBloomFilter::CheckScript(const CScript &script) const
{
CScript::const_iterator pc = script.begin();
std::vector<unsigned char> data;
while (pc < script.end()) {
opcodetype opcode;
if (!script.GetOp(pc, opcode, data))
break;
if (data.size() != 0 && contains(data))
return true;
}
return false;
}

// If the transaction is a special transaction that has a registration
// transaction hash, test the registration transaction hash.
// If the transaction is a special transaction with any public keys or any
// public key hashes test them.
// If the transaction is a special transaction with payout addresses test
// the hash160 of those addresses.
// Filter is updated only if it has BLOOM_UPDATE_ALL flag to be able to have
// simple SPV wallets that doesn't work with DIP2 transactions (multicoin
// wallets, etc.)
bool CBloomFilter::CheckSpecialTransactionMatchesAndUpdate(const CTransaction &tx)
{
if(tx.nVersion != 3 || tx.nType == TRANSACTION_NORMAL) {
return false; // it is not a special transaction
}
switch(tx.nType) {
case(TRANSACTION_PROVIDER_REGISTER): {
CProRegTx proTx;
if (GetTxPayload(tx, proTx)) {
if(contains(proTx.collateralOutpoint) ||
contains(proTx.keyIDOwner) ||
contains(proTx.keyIDVoting) ||
CheckScript(proTx.scriptPayout)) {
if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_ALL)
insert(tx.GetHash());
return true;
}
}
return false;
}
case(TRANSACTION_PROVIDER_UPDATE_SERVICE): {
CProUpServTx proTx;
if (GetTxPayload(tx, proTx)) {
if(contains(proTx.proTxHash)) {
return true;
}
if(CheckScript(proTx.scriptOperatorPayout)) {
if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_ALL)
insert(proTx.proTxHash);
return true;
}
}
return false;
}
case(TRANSACTION_PROVIDER_UPDATE_REGISTRAR): {
CProUpRegTx proTx;
if (GetTxPayload(tx, proTx)) {
if(contains(proTx.proTxHash))
return true;
if(contains(proTx.keyIDVoting) ||
CheckScript(proTx.scriptPayout)) {
if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_ALL)
insert(proTx.proTxHash);
return true;
}
}
return false;
}
case(TRANSACTION_PROVIDER_UPDATE_REVOKE): {
CProUpRevTx proTx;
if (GetTxPayload(tx, proTx)) {
if(contains(proTx.proTxHash))
return true;
}
return false;
}
case(TRANSACTION_COINBASE):
case(TRANSACTION_QUORUM_COMMITMENT):
// No aditional checks for this transaction types
return false;
}

LogPrintf("Unknown special transaction type in Bloom filter check.");
return false;
}

bool CBloomFilter::IsRelevantAndUpdate(const CTransaction& tx)
{
bool fFound = false;
Expand All @@ -144,34 +244,27 @@ bool CBloomFilter::IsRelevantAndUpdate(const CTransaction& tx)
if (contains(hash))
fFound = true;

// Check additional matches for special transactions
fFound = fFound || CheckSpecialTransactionMatchesAndUpdate(tx);

for (unsigned int i = 0; i < tx.vout.size(); i++)
{
const CTxOut& txout = tx.vout[i];
// Match if the filter contains any arbitrary script data element in any scriptPubKey in tx
// If this matches, also add the specific output that was matched.
// This means clients don't have to update the filter themselves when a new relevant tx
// is discovered in order to find spending transactions, which avoids round-tripping and race conditions.
CScript::const_iterator pc = txout.scriptPubKey.begin();
std::vector<unsigned char> data;
while (pc < txout.scriptPubKey.end())
{
opcodetype opcode;
if (!txout.scriptPubKey.GetOp(pc, opcode, data))
break;
if (data.size() != 0 && contains(data))
if(CheckScript(txout.scriptPubKey)) {
fFound = true;
if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_ALL)
insert(COutPoint(hash, i));
else if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_P2PUBKEY_ONLY)
{
fFound = true;
if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_ALL)
txnouttype type;
std::vector<std::vector<unsigned char> > vSolutions;
if (Solver(txout.scriptPubKey, type, vSolutions) &&
(type == TX_PUBKEY || type == TX_MULTISIG))
insert(COutPoint(hash, i));
else if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_P2PUBKEY_ONLY)
{
txnouttype type;
std::vector<std::vector<unsigned char> > vSolutions;
if (Solver(txout.scriptPubKey, type, vSolutions) &&
(type == TX_PUBKEY || type == TX_MULTISIG))
insert(COutPoint(hash, i));
}
break;
}
}
}
Expand All @@ -186,16 +279,8 @@ bool CBloomFilter::IsRelevantAndUpdate(const CTransaction& tx)
return true;

// Match if the filter contains any arbitrary script data element in any scriptSig in tx
CScript::const_iterator pc = txin.scriptSig.begin();
std::vector<unsigned char> data;
while (pc < txin.scriptSig.end())
{
opcodetype opcode;
if (!txin.scriptSig.GetOp(pc, opcode, data))
break;
if (data.size() != 0 && contains(data))
return true;
}
if(CheckScript(txin.scriptSig))
return true;
}

return false;
Expand Down
7 changes: 7 additions & 0 deletions src/bloom.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
#include <vector>

class COutPoint;
class CScript;
class CTransaction;
class uint256;
class uint160;

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

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

void clear();
void reset(unsigned int nNewTweak);
Expand Down
Loading

0 comments on commit 658ce9e

Please sign in to comment.