mirror of
https://github.com/LBRYFoundation/lbcutil.git
synced 2025-08-23 17:47:30 +00:00
225 lines
5.7 KiB
Go
225 lines
5.7 KiB
Go
// Copyright (c) 2014 Conformal Systems LLC.
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package bloomfilter
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"github.com/conformal/btcscript"
|
|
"github.com/conformal/btcutil"
|
|
"github.com/conformal/btcwire"
|
|
"math"
|
|
"sync"
|
|
)
|
|
|
|
// BloomFilter defines a bitcoin bloomfilter the provides easy manipulation of raw
|
|
// filter data.
|
|
type BloomFilter struct {
|
|
sync.Mutex
|
|
msgFilterLoad *btcwire.MsgFilterLoad
|
|
}
|
|
|
|
// New creates a new Filter instance, mainly to be used by SPV clients. The tweak parameter is
|
|
// a random value added to the seed value. For more information on what values to use for both
|
|
// elements and fprate, please see https://en.wikipedia.org/wiki/Bloom_filter.
|
|
func New(elements, tweak uint32, fprate float64, flags btcwire.BloomUpdateType) *BloomFilter {
|
|
dataLen := uint32(math.Abs(math.Log(fprate)) * float64(elements) / (math.Ln2 * math.Ln2))
|
|
dataLen = min(dataLen, btcwire.MaxFilterLoadFilterSize*8) / 8
|
|
|
|
hashFuncs := min(uint32(float64(dataLen)*8.0/float64(elements)*math.Ln2), btcwire.MaxFilterLoadHashFuncs)
|
|
|
|
data := make([]byte, dataLen)
|
|
msg := btcwire.NewMsgFilterLoad(data, hashFuncs, tweak, flags)
|
|
|
|
return &BloomFilter{
|
|
msgFilterLoad: msg,
|
|
}
|
|
}
|
|
|
|
// Load creates a new BloomFilter instance with the given btcwire.MsgFilterLoad.
|
|
func Load(filter *btcwire.MsgFilterLoad) *BloomFilter {
|
|
return &BloomFilter{
|
|
msgFilterLoad: filter,
|
|
}
|
|
}
|
|
|
|
// Loaded returns true if a filter is loaded, otherwise false.
|
|
func (bf *BloomFilter) IsLoaded() bool {
|
|
bf.Lock()
|
|
defer bf.Unlock()
|
|
|
|
return bf.msgFilterLoad != nil
|
|
}
|
|
|
|
// Unload clears the Filter.
|
|
func (bf *BloomFilter) Unload() {
|
|
bf.Lock()
|
|
defer bf.Unlock()
|
|
|
|
bf.msgFilterLoad = nil
|
|
}
|
|
|
|
func (bf *BloomFilter) contains(data []byte) bool {
|
|
if bf.msgFilterLoad == nil {
|
|
return false
|
|
}
|
|
|
|
for i := uint32(0); i < bf.msgFilterLoad.HashFuncs; i++ {
|
|
idx := bf.hash(i, data)
|
|
if bf.msgFilterLoad.Filter[idx>>3]&(1<<(7&idx)) != 1<<(7&idx) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Contains returns true if the BloomFilter contains the passed byte slice. Otherwise,
|
|
// it returns false.
|
|
func (bf *BloomFilter) Contains(data []byte) bool {
|
|
bf.Lock()
|
|
defer bf.Unlock()
|
|
|
|
return bf.contains(data)
|
|
}
|
|
|
|
func (bf *BloomFilter) containsOutPoint(outpoint *btcwire.OutPoint) bool {
|
|
// Serialize
|
|
var buf [btcwire.HashSize + 4]byte
|
|
copy(buf[:], outpoint.Hash.Bytes())
|
|
binary.LittleEndian.PutUint32(buf[btcwire.HashSize:], outpoint.Index)
|
|
|
|
return bf.contains(buf[:])
|
|
}
|
|
|
|
// ContainsOutPoint returns true if the BloomFilter contains the given
|
|
// btcwire.OutPoint. Otherwise, it returns false.
|
|
func (bf *BloomFilter) ContainsOutPoint(outpoint *btcwire.OutPoint) bool {
|
|
bf.Lock()
|
|
defer bf.Unlock()
|
|
|
|
return bf.containsOutPoint(outpoint)
|
|
}
|
|
|
|
func (bf *BloomFilter) add(data []byte) {
|
|
if bf.msgFilterLoad == nil {
|
|
return
|
|
}
|
|
|
|
for i := uint32(0); i < bf.msgFilterLoad.HashFuncs; i++ {
|
|
idx := bf.hash(i, data)
|
|
bf.msgFilterLoad.Filter[idx>>3] |= (1 << (7 & idx))
|
|
}
|
|
}
|
|
|
|
// Add adds the passed byte slice to the BloomFilter.
|
|
func (bf *BloomFilter) Add(data []byte) {
|
|
bf.Lock()
|
|
defer bf.Unlock()
|
|
|
|
bf.add(data)
|
|
}
|
|
|
|
// AddShaHash adds the passed btcwire.ShaHash to the BloomFilter.
|
|
func (bf *BloomFilter) AddShaHash(sha *btcwire.ShaHash) {
|
|
bf.Lock()
|
|
defer bf.Unlock()
|
|
|
|
bf.add(sha.Bytes())
|
|
}
|
|
|
|
// AddOutPoint adds the passed btcwire.OutPoint to the BloomFilter.
|
|
func (bf *BloomFilter) AddOutPoint(outpoint *btcwire.OutPoint) {
|
|
bf.Lock()
|
|
defer bf.Unlock()
|
|
|
|
bf.addOutPoint(outpoint)
|
|
}
|
|
|
|
func (bf *BloomFilter) addOutPoint(outpoint *btcwire.OutPoint) {
|
|
// Serialize
|
|
var buf [btcwire.HashSize + 4]byte
|
|
copy(buf[:], outpoint.Hash.Bytes())
|
|
binary.LittleEndian.PutUint32(buf[btcwire.HashSize:], outpoint.Index)
|
|
|
|
bf.add(buf[:])
|
|
}
|
|
|
|
func (bf *BloomFilter) matches(tx *btcutil.Tx) bool {
|
|
hash := tx.Sha().Bytes()
|
|
matched := bf.contains(hash)
|
|
|
|
for i, txout := range tx.MsgTx().TxOut {
|
|
pushedData, err := btcscript.PushedData(txout.PkScript)
|
|
if err != nil {
|
|
break
|
|
}
|
|
for _, p := range pushedData {
|
|
if bf.contains(p) {
|
|
switch bf.msgFilterLoad.Flags {
|
|
case btcwire.BloomUpdateAll:
|
|
outpoint := btcwire.NewOutPoint(tx.Sha(), uint32(i))
|
|
bf.addOutPoint(outpoint)
|
|
case btcwire.BloomUpdateP2PubkeyOnly:
|
|
class := btcscript.GetScriptClass(txout.PkScript)
|
|
if class == btcscript.PubKeyTy || class == btcscript.MultiSigTy {
|
|
outpoint := btcwire.NewOutPoint(tx.Sha(), uint32(i))
|
|
bf.addOutPoint(outpoint)
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
if matched {
|
|
return true
|
|
}
|
|
|
|
for _, txin := range tx.MsgTx().TxIn {
|
|
if bf.containsOutPoint(&txin.PreviousOutpoint) {
|
|
return true
|
|
}
|
|
pushedData, err := btcscript.PushedData(txin.SignatureScript)
|
|
if err != nil {
|
|
break
|
|
}
|
|
for _, p := range pushedData {
|
|
if bf.contains(p) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// MatchesTx returns true if the BloomFilter matches data within the passed transaction,
|
|
// otherwise false is returned. If the BloomFilter does match the passed transaction,
|
|
// it will also update the BloomFilter if required.
|
|
func (bf *BloomFilter) MatchesTx(tx *btcutil.Tx) bool {
|
|
bf.Lock()
|
|
defer bf.Unlock()
|
|
|
|
return bf.matches(tx)
|
|
}
|
|
|
|
// MsgFilterLoad returns the underlying btcwire.MsgFilterLoad for the BloomFilter.
|
|
func (bf *BloomFilter) MsgFilterLoad() *btcwire.MsgFilterLoad {
|
|
return bf.msgFilterLoad
|
|
}
|
|
|
|
func (bf *BloomFilter) hash(hashNum uint32, data []byte) uint32 {
|
|
// bitcoind: 0xFBA4C795 chosen as it guarantees a reasonable bit
|
|
// difference between nHashNum values.
|
|
mm := MurmurHash3(hashNum*0xFBA4C795+bf.msgFilterLoad.Tweak, data)
|
|
return mm % (uint32(len(bf.msgFilterLoad.Filter)) * 8)
|
|
}
|
|
|
|
// min is a convenience function to return the minimum value of the two
|
|
// passed uint32 values.
|
|
func min(a, b uint32) uint32 {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|