blockbook/bchain/golomb.go
2023-11-08 10:21:18 +01:00

218 lines
5.6 KiB
Go

package bchain
import (
"bytes"
"encoding/hex"
"github.com/golang/glog"
"github.com/juju/errors"
"github.com/martinboehm/btcutil/gcs"
)
type FilterScriptsType int
const (
FilterScriptsInvalid = FilterScriptsType(iota)
FilterScriptsAll
FilterScriptsTaproot
FilterScriptsTaprootNoOrdinals
)
// GolombFilter is computing golomb filter of address descriptors
type GolombFilter struct {
Enabled bool
UseZeroedKey bool
p uint8
key string
filterScripts string
filterScriptsType FilterScriptsType
filterData [][]byte
uniqueData map[string]struct{}
// All the unique txids that contain ordinal data
ordinalTxIds map[string]struct{}
// Mapping of txid to address descriptors - only used in case of taproot-noordinals
allAddressDescriptors map[string][]AddressDescriptor
}
// NewGolombFilter initializes the GolombFilter handler
func NewGolombFilter(p uint8, filterScripts string, key string, useZeroedKey bool) (*GolombFilter, error) {
if p == 0 {
return &GolombFilter{Enabled: false}, nil
}
gf := GolombFilter{
Enabled: true,
UseZeroedKey: useZeroedKey,
p: p,
key: key,
filterScripts: filterScripts,
filterScriptsType: filterScriptsToScriptsType(filterScripts),
filterData: make([][]byte, 0),
uniqueData: make(map[string]struct{}),
}
// reject invalid filterScripts
if gf.filterScriptsType == FilterScriptsInvalid {
return nil, errors.Errorf("Invalid/unsupported filterScripts parameter %s", filterScripts)
}
// set ordinal-related fields if needed
if gf.ignoreOrdinals() {
gf.ordinalTxIds = make(map[string]struct{})
gf.allAddressDescriptors = make(map[string][]AddressDescriptor)
}
return &gf, nil
}
// Gets the M parameter that we are using for the filter
// Currently it relies on P parameter, but that can change
func GetGolombParamM(p uint8) uint64 {
return uint64(1 << uint64(p))
}
// Checks whether this input contains ordinal data
func isInputOrdinal(vin Vin) bool {
byte_pattern := []byte{
0x00, // OP_0, OP_FALSE
0x63, // OP_IF
0x03, // OP_PUSHBYTES_3
0x6f, // "o"
0x72, // "r"
0x64, // "d"
0x01, // OP_PUSHBYTES_1
}
// Witness needs to have at least 3 items and the second one needs to contain certain pattern
return len(vin.Witness) > 2 && bytes.Contains(vin.Witness[1], byte_pattern)
}
// Whether a transaction contains any ordinal data
func txContainsOrdinal(tx *Tx) bool {
for _, vin := range tx.Vin {
if isInputOrdinal(vin) {
return true
}
}
return false
}
// Saving all the ordinal-related txIds so we can later ignore their address descriptors
func (f *GolombFilter) markTxAndParentsAsOrdinals(tx *Tx) {
f.ordinalTxIds[tx.Txid] = struct{}{}
for _, vin := range tx.Vin {
f.ordinalTxIds[vin.Txid] = struct{}{}
}
}
// Adding a new address descriptor mapped to a txid
func (f *GolombFilter) addTxIdMapping(ad AddressDescriptor, tx *Tx) {
f.allAddressDescriptors[tx.Txid] = append(f.allAddressDescriptors[tx.Txid], ad)
}
// AddAddrDesc adds taproot address descriptor to the data for the filter
func (f *GolombFilter) AddAddrDesc(ad AddressDescriptor, tx *Tx) {
if f.ignoreNonTaproot() && !ad.IsTaproot() {
return
}
if f.ignoreOrdinals() && tx != nil && txContainsOrdinal(tx) {
f.markTxAndParentsAsOrdinals(tx)
return
}
if len(ad) == 0 {
return
}
// When ignoring ordinals, we need to save all the address descriptors before
// filtering out the "invalid" ones.
if f.ignoreOrdinals() && tx != nil {
f.addTxIdMapping(ad, tx)
return
}
f.includeAddrDesc(ad)
}
// Private function to be called with descriptors that were already validated
func (f *GolombFilter) includeAddrDesc(ad AddressDescriptor) {
s := string(ad)
if _, found := f.uniqueData[s]; !found {
f.filterData = append(f.filterData, ad)
f.uniqueData[s] = struct{}{}
}
}
// Including all the address descriptors from non-ordinal transactions
func (f *GolombFilter) includeAllAddressDescriptorsOrdinals() {
for txid, ads := range f.allAddressDescriptors {
// Ignoring the txids that contain ordinal data
if _, found := f.ordinalTxIds[txid]; found {
continue
}
for _, ad := range ads {
f.includeAddrDesc(ad)
}
}
}
// Compute computes golomb filter from the data
func (f *GolombFilter) Compute() []byte {
m := GetGolombParamM(f.p)
// In case of ignoring the ordinals, we still need to assemble the filter data
if f.ignoreOrdinals() {
f.includeAllAddressDescriptorsOrdinals()
}
if len(f.filterData) == 0 {
return nil
}
// Used key is possibly just zeroes, otherwise get it from the supplied key
var key [gcs.KeySize]byte
if f.UseZeroedKey {
key = [gcs.KeySize]byte{}
} else {
b, _ := hex.DecodeString(f.key)
if len(b) < gcs.KeySize {
return nil
}
copy(key[:], b[:gcs.KeySize])
}
filter, err := gcs.BuildGCSFilter(f.p, m, key, f.filterData)
if err != nil {
glog.Error("Cannot create golomb filter for ", f.key, ", ", err)
return nil
}
fb, err := filter.NBytes()
if err != nil {
glog.Error("Error getting NBytes from golomb filter for ", f.key, ", ", err)
return nil
}
return fb
}
func (f *GolombFilter) ignoreNonTaproot() bool {
switch f.filterScriptsType {
case FilterScriptsTaproot, FilterScriptsTaprootNoOrdinals:
return true
}
return false
}
func (f *GolombFilter) ignoreOrdinals() bool {
switch f.filterScriptsType {
case FilterScriptsTaprootNoOrdinals:
return true
}
return false
}
func filterScriptsToScriptsType(filterScripts string) FilterScriptsType {
switch filterScripts {
case "":
return FilterScriptsAll
case "taproot":
return FilterScriptsTaproot
case "taproot-noordinals":
return FilterScriptsTaprootNoOrdinals
}
return FilterScriptsInvalid
}