lbcutil/bloomfilter/filter.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
}