Ignore Ordinals in Golomb filters (#967)

This commit is contained in:
Jiří Musil
2023-09-01 12:00:08 +02:00
committed by Martin Boehm
parent a1a17b4331
commit 7d0c424ad8
14 changed files with 267 additions and 57 deletions

View File

@@ -231,6 +231,7 @@ func (p *BitcoinLikeParser) TxFromMsgTx(t *wire.MsgTx, parseAddresses bool) bcha
Vout: in.PreviousOutPoint.Index, Vout: in.PreviousOutPoint.Index,
Sequence: in.Sequence, Sequence: in.Sequence,
ScriptSig: s, ScriptSig: s,
Witness: in.Witness,
} }
} }
vout := make([]bchain.Vout, len(t.TxOut)) vout := make([]bchain.Vout, len(t.TxOut))

View File

@@ -34,6 +34,7 @@ type BitcoinRPC struct {
RPCMarshaler RPCMarshaler RPCMarshaler RPCMarshaler
mempoolGolombFilterP uint8 mempoolGolombFilterP uint8
mempoolFilterScripts string mempoolFilterScripts string
mempoolUseZeroedKey bool
} }
// Configuration represents json config file // Configuration represents json config file
@@ -63,6 +64,7 @@ type Configuration struct {
MinimumCoinbaseConfirmations int `json:"minimumCoinbaseConfirmations,omitempty"` MinimumCoinbaseConfirmations int `json:"minimumCoinbaseConfirmations,omitempty"`
MempoolGolombFilterP uint8 `json:"mempool_golomb_filter_p,omitempty"` MempoolGolombFilterP uint8 `json:"mempool_golomb_filter_p,omitempty"`
MempoolFilterScripts string `json:"mempool_filter_scripts,omitempty"` MempoolFilterScripts string `json:"mempool_filter_scripts,omitempty"`
MempoolFilterUseZeroedKey bool `json:"mempool_filter_use_zeroed_key,omitempty"`
} }
// NewBitcoinRPC returns new BitcoinRPC instance. // NewBitcoinRPC returns new BitcoinRPC instance.
@@ -110,6 +112,7 @@ func NewBitcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationT
RPCMarshaler: JSONMarshalerV2{}, RPCMarshaler: JSONMarshalerV2{},
mempoolGolombFilterP: c.MempoolGolombFilterP, mempoolGolombFilterP: c.MempoolGolombFilterP,
mempoolFilterScripts: c.MempoolFilterScripts, mempoolFilterScripts: c.MempoolFilterScripts,
mempoolUseZeroedKey: c.MempoolFilterUseZeroedKey,
} }
return s, nil return s, nil
@@ -155,7 +158,7 @@ func (b *BitcoinRPC) Initialize() error {
// CreateMempool creates mempool if not already created, however does not initialize it // CreateMempool creates mempool if not already created, however does not initialize it
func (b *BitcoinRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, error) { func (b *BitcoinRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, error) {
if b.Mempool == nil { if b.Mempool == nil {
b.Mempool = bchain.NewMempoolBitcoinType(chain, b.ChainConfig.MempoolWorkers, b.ChainConfig.MempoolSubWorkers, b.mempoolGolombFilterP, b.mempoolFilterScripts) b.Mempool = bchain.NewMempoolBitcoinType(chain, b.ChainConfig.MempoolWorkers, b.ChainConfig.MempoolSubWorkers, b.mempoolGolombFilterP, b.mempoolFilterScripts, b.mempoolUseZeroedKey)
} }
return b.Mempool, nil return b.Mempool, nil
} }

View File

@@ -1,6 +1,7 @@
package bchain package bchain
import ( import (
"bytes"
"encoding/hex" "encoding/hex"
"github.com/golang/glog" "github.com/golang/glog"
@@ -14,26 +15,33 @@ const (
FilterScriptsInvalid = FilterScriptsType(iota) FilterScriptsInvalid = FilterScriptsType(iota)
FilterScriptsAll FilterScriptsAll
FilterScriptsTaproot FilterScriptsTaproot
FilterScriptsTaprootNoOrdinals
) )
// GolombFilter is computing golomb filter of address descriptors // GolombFilter is computing golomb filter of address descriptors
type GolombFilter struct { type GolombFilter struct {
Enabled bool Enabled bool
UseZeroedKey bool
p uint8 p uint8
key string key string
filterScripts string filterScripts string
filterScriptsType FilterScriptsType filterScriptsType FilterScriptsType
filterData [][]byte filterData [][]byte
uniqueData map[string]struct{} 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 // NewGolombFilter initializes the GolombFilter handler
func NewGolombFilter(p uint8, filterScripts string, key string) (*GolombFilter, error) { func NewGolombFilter(p uint8, filterScripts string, key string, useZeroedKey bool) (*GolombFilter, error) {
if p == 0 { if p == 0 {
return &GolombFilter{Enabled: false}, nil return &GolombFilter{Enabled: false}, nil
} }
gf := GolombFilter{ gf := GolombFilter{
Enabled: true, Enabled: true,
UseZeroedKey: useZeroedKey,
p: p, p: p,
key: key, key: key,
filterScripts: filterScripts, filterScripts: filterScripts,
@@ -41,21 +49,85 @@ func NewGolombFilter(p uint8, filterScripts string, key string) (*GolombFilter,
filterData: make([][]byte, 0), filterData: make([][]byte, 0),
uniqueData: make(map[string]struct{}), uniqueData: make(map[string]struct{}),
} }
// only taproot and all is supported // reject invalid filterScripts
if gf.filterScriptsType == FilterScriptsInvalid { if gf.filterScriptsType == FilterScriptsInvalid {
return nil, errors.Errorf("Invalid/unsupported filterScripts parameter %s", filterScripts) 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 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 // AddAddrDesc adds taproot address descriptor to the data for the filter
func (f *GolombFilter) AddAddrDesc(ad AddressDescriptor) { func (f *GolombFilter) AddAddrDesc(ad AddressDescriptor, tx *Tx) {
if f.filterScriptsType == FilterScriptsTaproot && !ad.IsTaproot() { if f.ignoreNonTaproot() && !ad.IsTaproot() {
return
}
if f.ignoreOrdinals() && tx != nil && txContainsOrdinal(tx) {
f.markTxAndParentsAsOrdinals(tx)
return return
} }
if len(ad) == 0 { if len(ad) == 0 {
return 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) s := string(ad)
if _, found := f.uniqueData[s]; !found { if _, found := f.uniqueData[s]; !found {
f.filterData = append(f.filterData, ad) f.filterData = append(f.filterData, ad)
@@ -63,20 +135,45 @@ func (f *GolombFilter) AddAddrDesc(ad AddressDescriptor) {
} }
} }
// 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 // Compute computes golomb filter from the data
func (f *GolombFilter) Compute() []byte { func (f *GolombFilter) Compute() []byte {
m := uint64(1 << uint64(f.p)) 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 { if len(f.filterData) == 0 {
return nil return nil
} }
b, _ := hex.DecodeString(f.key) // Used key is possibly just zeroes, otherwise get it from the supplied key
if len(b) < gcs.KeySize { var key [gcs.KeySize]byte
return nil 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, *(*[gcs.KeySize]byte)(b[:gcs.KeySize]), f.filterData) filter, err := gcs.BuildGCSFilter(f.p, m, key, f.filterData)
if err != nil { if err != nil {
glog.Error("Cannot create golomb filter for ", f.key, ", ", err) glog.Error("Cannot create golomb filter for ", f.key, ", ", err)
return nil return nil
@@ -91,12 +188,30 @@ func (f *GolombFilter) Compute() []byte {
return fb 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 { func filterScriptsToScriptsType(filterScripts string) FilterScriptsType {
switch filterScripts { switch filterScripts {
case "": case "":
return FilterScriptsAll return FilterScriptsAll
case "taproot": case "taproot":
return FilterScriptsTaproot return FilterScriptsTaproot
case "taproot-noordinals":
return FilterScriptsTaprootNoOrdinals
} }
return FilterScriptsInvalid return FilterScriptsInvalid
} }

View File

@@ -22,11 +22,12 @@ type MempoolBitcoinType struct {
AddrDescForOutpoint AddrDescForOutpointFunc AddrDescForOutpoint AddrDescForOutpointFunc
golombFilterP uint8 golombFilterP uint8
filterScripts string filterScripts string
useZeroedKey bool
} }
// NewMempoolBitcoinType creates new mempool handler. // NewMempoolBitcoinType creates new mempool handler.
// For now there is no cleanup of sync routines, the expectation is that the mempool is created only once per process // For now there is no cleanup of sync routines, the expectation is that the mempool is created only once per process
func NewMempoolBitcoinType(chain BlockChain, workers int, subworkers int, golombFilterP uint8, filterScripts string) *MempoolBitcoinType { func NewMempoolBitcoinType(chain BlockChain, workers int, subworkers int, golombFilterP uint8, filterScripts string, useZeroedKey bool) *MempoolBitcoinType {
m := &MempoolBitcoinType{ m := &MempoolBitcoinType{
BaseMempool: BaseMempool{ BaseMempool: BaseMempool{
chain: chain, chain: chain,
@@ -37,6 +38,7 @@ func NewMempoolBitcoinType(chain BlockChain, workers int, subworkers int, golomb
chanAddrIndex: make(chan txidio, 1), chanAddrIndex: make(chan txidio, 1),
golombFilterP: golombFilterP, golombFilterP: golombFilterP,
filterScripts: filterScripts, filterScripts: filterScripts,
useZeroedKey: useZeroedKey,
} }
for i := 0; i < workers; i++ { for i := 0; i < workers; i++ {
go func(i int) { go func(i int) {
@@ -97,18 +99,18 @@ func (m *MempoolBitcoinType) getInputAddress(payload *chanInputPayload) *addrInd
} }
func (m *MempoolBitcoinType) computeGolombFilter(mtx *MempoolTx) string { func (m *MempoolBitcoinType) computeGolombFilter(mtx *MempoolTx, tx *Tx) string {
gf, _ := NewGolombFilter(m.golombFilterP, m.filterScripts, mtx.Txid) gf, _ := NewGolombFilter(m.golombFilterP, "", mtx.Txid, m.useZeroedKey)
if gf == nil || !gf.Enabled { if gf == nil || !gf.Enabled {
return "" return ""
} }
for _, vin := range mtx.Vin { for _, vin := range mtx.Vin {
gf.AddAddrDesc(vin.AddrDesc) gf.AddAddrDesc(vin.AddrDesc, tx)
} }
for _, vout := range mtx.Vout { for _, vout := range mtx.Vout {
b, err := hex.DecodeString(vout.ScriptPubKey.Hex) b, err := hex.DecodeString(vout.ScriptPubKey.Hex)
if err == nil { if err == nil {
gf.AddAddrDesc(b) gf.AddAddrDesc(b, tx)
} }
} }
fb := gf.Compute() fb := gf.Compute()
@@ -168,7 +170,7 @@ func (m *MempoolBitcoinType) getTxAddrs(txid string, chanInput chan chanInputPay
} }
var golombFilter string var golombFilter string
if m.golombFilterP > 0 { if m.golombFilterP > 0 {
golombFilter = m.computeGolombFilter(mtx) golombFilter = m.computeGolombFilter(mtx, tx)
} }
if m.OnNewTx != nil { if m.OnNewTx != nil {
m.OnNewTx(mtx) m.OnNewTx(mtx)
@@ -249,5 +251,5 @@ func (m *MempoolBitcoinType) GetTxidFilterEntries(filterScripts string, fromTime
} }
} }
m.mux.Unlock() m.mux.Unlock()
return MempoolTxidFilterEntries{entries}, nil return MempoolTxidFilterEntries{entries, m.useZeroedKey}, nil
} }

View File

@@ -18,7 +18,7 @@ func TestMempoolBitcoinType_computeGolombFilter_taproot(t *testing.T) {
golombFilterP: 20, golombFilterP: 20,
filterScripts: "taproot", filterScripts: "taproot",
} }
golombFilterM := uint64(1 << uint64(m.golombFilterP)) golombFilterM := GetGolombParamM(m.golombFilterP)
tests := []struct { tests := []struct {
name string name string
mtx MempoolTx mtx MempoolTx

View File

@@ -57,6 +57,7 @@ type Vin struct {
ScriptSig ScriptSig `json:"scriptSig"` ScriptSig ScriptSig `json:"scriptSig"`
Sequence uint32 `json:"sequence"` Sequence uint32 `json:"sequence"`
Addresses []string `json:"addresses"` Addresses []string `json:"addresses"`
Witness [][]byte `json:"witness"`
} }
// ScriptPubKey contains data about output script // ScriptPubKey contains data about output script
@@ -273,8 +274,10 @@ type XpubDescriptor struct {
type MempoolTxidEntries []MempoolTxidEntry type MempoolTxidEntries []MempoolTxidEntry
// MempoolTxidFilterEntries is a map of txids to mempool golomb filters // MempoolTxidFilterEntries is a map of txids to mempool golomb filters
// Also contains a flag whether constant zeroed key was used when calculating the filters
type MempoolTxidFilterEntries struct { type MempoolTxidFilterEntries struct {
Entries map[string]string `json:"entries,omitempty"` Entries map[string]string `json:"entries,omitempty"`
UsedZeroedKey bool `json:"usedZeroedKey,omitempty"`
} }
// OnNewBlockFunc is used to send notification about a new block // OnNewBlockFunc is used to send notification about a new block

View File

@@ -9,15 +9,16 @@ import (
// Config struct // Config struct
type Config struct { type Config struct {
CoinName string `json:"coin_name"` CoinName string `json:"coin_name"`
CoinShortcut string `json:"coin_shortcut"` CoinShortcut string `json:"coin_shortcut"`
CoinLabel string `json:"coin_label"` CoinLabel string `json:"coin_label"`
FourByteSignatures string `json:"fourByteSignatures"` FourByteSignatures string `json:"fourByteSignatures"`
FiatRates string `json:"fiat_rates"` FiatRates string `json:"fiat_rates"`
FiatRatesParams string `json:"fiat_rates_params"` FiatRatesParams string `json:"fiat_rates_params"`
FiatRatesVsCurrencies string `json:"fiat_rates_vs_currencies"` FiatRatesVsCurrencies string `json:"fiat_rates_vs_currencies"`
BlockGolombFilterP uint8 `json:"block_golomb_filter_p"` BlockGolombFilterP uint8 `json:"block_golomb_filter_p"`
BlockFilterScripts string `json:"block_filter_scripts"` BlockFilterScripts string `json:"block_filter_scripts"`
BlockFilterUseZeroedKey bool `json:"block_filter_use_zeroed_key"`
} }
// GetConfig loads and parses the config file and returns Config struct // GetConfig loads and parses the config file and returns Config struct

View File

@@ -94,8 +94,9 @@ type InternalState struct {
SortedAddressContracts bool `json:"sortedAddressContracts"` SortedAddressContracts bool `json:"sortedAddressContracts"`
// golomb filter settings // golomb filter settings
BlockGolombFilterP uint8 `json:"block_golomb_filter_p"` BlockGolombFilterP uint8 `json:"block_golomb_filter_p"`
BlockFilterScripts string `json:"block_filter_scripts"` BlockFilterScripts string `json:"block_filter_scripts"`
BlockFilterUseZeroedKey bool `json:"block_filter_use_zeroed_key"`
} }
// StartedSync signals start of synchronization // StartedSync signals start of synchronization

View File

@@ -185,7 +185,7 @@ func (b *BulkConnect) storeBulkBlockFilters(wb *grocksdb.WriteBatch) error {
func (b *BulkConnect) connectBlockBitcoinType(block *bchain.Block, storeBlockTxs bool) error { func (b *BulkConnect) connectBlockBitcoinType(block *bchain.Block, storeBlockTxs bool) error {
addresses := make(addressesMap) addresses := make(addressesMap)
gf, err := bchain.NewGolombFilter(b.d.is.BlockGolombFilterP, b.d.is.BlockFilterScripts, block.BlockHeader.Hash) gf, err := bchain.NewGolombFilter(b.d.is.BlockGolombFilterP, b.d.is.BlockFilterScripts, block.BlockHeader.Hash, b.d.is.BlockFilterUseZeroedKey)
if err != nil { if err != nil {
glog.Error("connectBlockBitcoinType golomb filter error ", err) glog.Error("connectBlockBitcoinType golomb filter error ", err)
gf = nil gf = nil

View File

@@ -349,7 +349,7 @@ func (d *RocksDB) ConnectBlock(block *bchain.Block) error {
if chainType == bchain.ChainBitcoinType { if chainType == bchain.ChainBitcoinType {
txAddressesMap := make(map[string]*TxAddresses) txAddressesMap := make(map[string]*TxAddresses)
balances := make(map[string]*AddrBalance) balances := make(map[string]*AddrBalance)
gf, err := bchain.NewGolombFilter(d.is.BlockGolombFilterP, d.is.BlockFilterScripts, block.BlockHeader.Hash) gf, err := bchain.NewGolombFilter(d.is.BlockGolombFilterP, d.is.BlockFilterScripts, block.BlockHeader.Hash, d.is.BlockFilterUseZeroedKey)
if err != nil { if err != nil {
glog.Error("ConnectBlock golomb filter error ", err) glog.Error("ConnectBlock golomb filter error ", err)
gf = nil gf = nil
@@ -643,7 +643,7 @@ func (d *RocksDB) processAddressesBitcoinType(block *bchain.Block, addresses add
continue continue
} }
if gf != nil { if gf != nil {
gf.AddAddrDesc(addrDesc) gf.AddAddrDesc(addrDesc, tx)
} }
tao.AddrDesc = addrDesc tao.AddrDesc = addrDesc
if d.chainParser.IsAddrDescIndexable(addrDesc) { if d.chainParser.IsAddrDescIndexable(addrDesc) {
@@ -720,7 +720,7 @@ func (d *RocksDB) processAddressesBitcoinType(block *bchain.Block, addresses add
glog.Warningf("rocksdb: height %d, tx %v, input tx %v vout %v is double spend", block.Height, tx.Txid, input.Txid, input.Vout) glog.Warningf("rocksdb: height %d, tx %v, input tx %v vout %v is double spend", block.Height, tx.Txid, input.Txid, input.Vout)
} }
if gf != nil { if gf != nil {
gf.AddAddrDesc(spentOutput.AddrDesc) gf.AddAddrDesc(spentOutput.AddrDesc, tx)
} }
tai.AddrDesc = spentOutput.AddrDesc tai.AddrDesc = spentOutput.AddrDesc
tai.ValueSat = spentOutput.ValueSat tai.ValueSat = spentOutput.ValueSat
@@ -1894,12 +1894,13 @@ func (d *RocksDB) LoadInternalState(config *common.Config) (*common.InternalStat
var is *common.InternalState var is *common.InternalState
if len(data) == 0 { if len(data) == 0 {
is = &common.InternalState{ is = &common.InternalState{
Coin: config.CoinName, Coin: config.CoinName,
UtxoChecked: true, UtxoChecked: true,
SortedAddressContracts: true, SortedAddressContracts: true,
ExtendedIndex: d.extendedIndex, ExtendedIndex: d.extendedIndex,
BlockGolombFilterP: config.BlockGolombFilterP, BlockGolombFilterP: config.BlockGolombFilterP,
BlockFilterScripts: config.BlockFilterScripts, BlockFilterScripts: config.BlockFilterScripts,
BlockFilterUseZeroedKey: config.BlockFilterUseZeroedKey,
} }
} else { } else {
is, err = common.UnpackInternalState(data) is, err = common.UnpackInternalState(data)
@@ -1922,6 +1923,9 @@ func (d *RocksDB) LoadInternalState(config *common.Config) (*common.InternalStat
if is.BlockFilterScripts != config.BlockFilterScripts { if is.BlockFilterScripts != config.BlockFilterScripts {
return nil, errors.Errorf("BlockFilterScripts does not match. DB BlockFilterScripts %v, config BlockFilterScripts %v", is.BlockFilterScripts, config.BlockFilterScripts) return nil, errors.Errorf("BlockFilterScripts does not match. DB BlockFilterScripts %v, config BlockFilterScripts %v", is.BlockFilterScripts, config.BlockFilterScripts)
} }
if is.BlockFilterUseZeroedKey != config.BlockFilterUseZeroedKey {
return nil, errors.Errorf("BlockFilterUseZeroedKey does not match. DB BlockFilterUseZeroedKey %v, config BlockFilterUseZeroedKey %v", is.BlockFilterUseZeroedKey, config.BlockFilterUseZeroedKey)
}
} }
nc, err := d.checkColumns(is) nc, err := d.checkColumns(is)
if err != nil { if err != nil {

View File

@@ -1230,8 +1230,15 @@ func (s *PublicServer) apiBlockIndex(r *http.Request, apiVersion int) (interface
func (s *PublicServer) apiBlockFilters(r *http.Request, apiVersion int) (interface{}, error) { func (s *PublicServer) apiBlockFilters(r *http.Request, apiVersion int) (interface{}, error) {
// Define return type // Define return type
type blockFilterResult struct {
BlockHash string `json:"blockHash"`
Filter string `json:"filter"`
}
type resBlockFilters struct { type resBlockFilters struct {
BlockFilters map[int]map[string]string `json:"blockFilters"` ParamP uint8 `json:"P"`
ParamM uint64 `json:"M"`
ZeroedKey bool `json:"zeroedKey"`
BlockFilters map[int]blockFilterResult `json:"blockFilters"`
} }
// Parse parameters // Parse parameters
@@ -1247,6 +1254,11 @@ func (s *PublicServer) apiBlockFilters(r *http.Request, apiVersion int) (interfa
if ec != nil { if ec != nil {
to = 0 to = 0
} }
scriptType := r.URL.Query().Get("scriptType")
if scriptType != s.is.BlockFilterScripts {
return nil, api.NewAPIError(fmt.Sprintf("Invalid scriptType %s. Use %s", scriptType, s.is.BlockFilterScripts), true)
}
// NOTE: technically, we are also accepting "m: uint64" param, but we do not use it currently
// Sanity checks // Sanity checks
if lastN == 0 && from == 0 && to == 0 { if lastN == 0 && from == 0 && to == 0 {
@@ -1278,7 +1290,7 @@ func (s *PublicServer) apiBlockFilters(r *http.Request, apiVersion int) (interfa
} }
handleBlockFiltersResultFromTo := func(fromHeight int, toHeight int) (interface{}, error) { handleBlockFiltersResultFromTo := func(fromHeight int, toHeight int) (interface{}, error) {
blockFiltersMap := make(map[int]map[string]string) blockFiltersMap := make(map[int]blockFilterResult)
for i := fromHeight; i <= toHeight; i++ { for i := fromHeight; i <= toHeight; i++ {
blockHash, err := s.db.GetBlockHash(uint32(i)) blockHash, err := s.db.GetBlockHash(uint32(i))
if err != nil { if err != nil {
@@ -1290,12 +1302,15 @@ func (s *PublicServer) apiBlockFilters(r *http.Request, apiVersion int) (interfa
glog.Error(err) glog.Error(err)
return nil, err return nil, err
} }
resultMap := make(map[string]string) blockFiltersMap[i] = blockFilterResult{
resultMap["blockHash"] = blockHash BlockHash: blockHash,
resultMap["filter"] = blockFilter Filter: blockFilter,
blockFiltersMap[i] = resultMap }
} }
return resBlockFilters{ return resBlockFilters{
ParamP: s.is.BlockGolombFilterP,
ParamM: bchain.GetGolombParamM(s.is.BlockGolombFilterP),
ZeroedKey: s.is.BlockFilterUseZeroedKey,
BlockFilters: blockFiltersMap, BlockFilters: blockFiltersMap,
}, nil }, nil
} }

View File

@@ -657,17 +657,67 @@ func (s *WebsocketServer) sendTransaction(tx string) (res resultSendTransaction,
return return
} }
func (s *WebsocketServer) getMempoolFilters(r *WsMempoolFiltersReq) (res bchain.MempoolTxidFilterEntries, err error) { func (s *WebsocketServer) getMempoolFilters(r *WsMempoolFiltersReq) (res interface{}, err error) {
res, err = s.mempool.GetTxidFilterEntries(r.ScriptType, r.FromTimestamp) type resMempoolFilters struct {
return ParamP uint8 `json:"P"`
ParamM uint64 `json:"M"`
ZeroedKey bool `json:"zeroedKey"`
Entries map[string]string `json:"entries"`
}
filterEntries, err := s.mempool.GetTxidFilterEntries(r.ScriptType, r.FromTimestamp)
if err != nil {
return nil, err
}
return resMempoolFilters{
ParamP: s.is.BlockGolombFilterP,
ParamM: bchain.GetGolombParamM(s.is.BlockGolombFilterP),
ZeroedKey: filterEntries.UsedZeroedKey,
Entries: filterEntries.Entries,
}, nil
} }
func (s *WebsocketServer) getBlockFilter(r *WsBlockFilterReq) (res string, err error) { func (s *WebsocketServer) getBlockFilter(r *WsBlockFilterReq) (res interface{}, err error) {
return s.db.GetBlockFilter(r.BlockHash) type resBlockFilter struct {
ParamP uint8 `json:"P"`
ParamM uint64 `json:"M"`
ZeroedKey bool `json:"zeroedKey"`
BlockFilter string `json:"blockFilter"`
}
if s.is.BlockFilterScripts != r.ScriptType {
return nil, errors.Errorf("Unsupported script type %s", r.ScriptType)
}
blockFilter, err := s.db.GetBlockFilter(r.BlockHash)
if err != nil {
return nil, err
}
return resBlockFilter{
ParamP: s.is.BlockGolombFilterP,
ParamM: bchain.GetGolombParamM(s.is.BlockGolombFilterP),
ZeroedKey: s.is.BlockFilterUseZeroedKey,
BlockFilter: blockFilter,
}, nil
} }
func (s *WebsocketServer) getBlockFiltersBatch(r *WsBlockFiltersBatchReq) (res []string, err error) { func (s *WebsocketServer) getBlockFiltersBatch(r *WsBlockFiltersBatchReq) (res interface{}, err error) {
return s.api.GetBlockFiltersBatch(r.BlockHash, r.PageSize) type resBlockFiltersBatch struct {
ParamP uint8 `json:"P"`
ParamM uint64 `json:"M"`
ZeroedKey bool `json:"zeroedKey"`
BlockFiltersBatch []string `json:"blockFiltersBatch"`
}
if s.is.BlockFilterScripts != r.ScriptType {
return nil, errors.Errorf("Unsupported script type %s", r.ScriptType)
}
blockFiltersBatch, err := s.api.GetBlockFiltersBatch(r.BlockHash, r.PageSize)
if err != nil {
return nil, err
}
return resBlockFiltersBatch{
ParamP: s.is.BlockGolombFilterP,
ParamM: bchain.GetGolombParamM(s.is.BlockGolombFilterP),
ZeroedKey: s.is.BlockFilterUseZeroedKey,
BlockFiltersBatch: blockFiltersBatch,
}, nil
} }
type subscriptionResponse struct { type subscriptionResponse struct {

View File

@@ -79,15 +79,20 @@ type WsTransactionReq struct {
type WsMempoolFiltersReq struct { type WsMempoolFiltersReq struct {
ScriptType string `json:"scriptType"` ScriptType string `json:"scriptType"`
FromTimestamp uint32 `json:"fromTimestamp"` FromTimestamp uint32 `json:"fromTimestamp"`
ParamM uint64 `json:"M,omitempty"`
} }
type WsBlockFilterReq struct { type WsBlockFilterReq struct {
BlockHash string `json:"blockHash"` ScriptType string `json:"scriptType"`
BlockHash string `json:"blockHash"`
ParamM uint64 `json:"M,omitempty"`
} }
type WsBlockFiltersBatchReq struct { type WsBlockFiltersBatchReq struct {
BlockHash string `json:"bestKnownBlockHash"` ScriptType string `json:"scriptType"`
PageSize int `json:"pageSize,omitempty"` BlockHash string `json:"bestKnownBlockHash"`
PageSize int `json:"pageSize,omitempty"`
ParamM uint64 `json:"M,omitempty"`
} }
type WsTransactionSpecificReq struct { type WsTransactionSpecificReq struct {

View File

@@ -87,7 +87,11 @@
var f = pendingMessages[resp.id]; var f = pendingMessages[resp.id];
if (f != undefined) { if (f != undefined) {
delete pendingMessages[resp.id]; delete pendingMessages[resp.id];
f(resp.data); try {
f(resp.data);
} catch (e) {
alert(`Error: ${e}.\nLook into the console for websocket response.`);
}
} else { } else {
f = subscriptions[resp.id]; f = subscriptions[resp.id];
if (f != undefined) { if (f != undefined) {
@@ -414,8 +418,10 @@
function getBlockFilter() { function getBlockFilter() {
const method = 'getBlockFilter'; const method = 'getBlockFilter';
const blockHash = document.getElementById('getBlockFilterBlockHash').value; const blockHash = document.getElementById('getBlockFilterBlockHash').value;
const scriptType = document.getElementById('getBlockFilterBlockHashScriptType').value;
const params = { const params = {
blockHash, blockHash,
scriptType,
}; };
send(method, params, function (result) { send(method, params, function (result) {
document.getElementById('getBlockFilterResult').innerText = JSON.stringify(result).replace(/,/g, ", "); document.getElementById('getBlockFilterResult').innerText = JSON.stringify(result).replace(/,/g, ", ");
@@ -426,8 +432,10 @@
const method = 'getBlockFiltersBatch'; const method = 'getBlockFiltersBatch';
const bestKnownBlockHash = document.getElementById('getBlockFiltersBatchBlockHash').value; const bestKnownBlockHash = document.getElementById('getBlockFiltersBatchBlockHash').value;
const pageSize = parseInt(document.getElementById("getBlockFiltersBatchPageSize").value); const pageSize = parseInt(document.getElementById("getBlockFiltersBatchPageSize").value);
const scriptType = document.getElementById('getBlockFiltersBatchScriptType').value;
const params = { const params = {
bestKnownBlockHash, bestKnownBlockHash,
scriptType,
}; };
if (pageSize) params.pageSize = pageSize; if (pageSize) params.pageSize = pageSize;
send(method, params, function (result) { send(method, params, function (result) {
@@ -719,6 +727,7 @@
</div> </div>
<div class="col-8"> <div class="col-8">
<input type="text" class="form-control" id="getBlockFilterBlockHash" value="000000000000001cb4edd91be03b6775abd351fb51b1fbb0871fc1451454f362" placeholder="block hash"> <input type="text" class="form-control" id="getBlockFilterBlockHash" value="000000000000001cb4edd91be03b6775abd351fb51b1fbb0871fc1451454f362" placeholder="block hash">
<input type="text" class="form-control" id="getBlockFilterBlockHashScriptType" value="taproot-noordinals" placeholder="filter script">
</div> </div>
</div> </div>
<div class="row"> <div class="row">
@@ -732,6 +741,7 @@
<div class="row" style="margin: 0;"> <div class="row" style="margin: 0;">
<input type="text" class="form-control" id="getBlockFiltersBatchBlockHash" style="width: 80%; margin-right: 5px;" value="000000000000001cb4edd91be03b6775abd351fb51b1fbb0871fc1451454f362" placeholder="best known block hash"> <input type="text" class="form-control" id="getBlockFiltersBatchBlockHash" style="width: 80%; margin-right: 5px;" value="000000000000001cb4edd91be03b6775abd351fb51b1fbb0871fc1451454f362" placeholder="best known block hash">
<input type="text" class="form-control" placeholder="page size" style="width: 15%;" id="getBlockFiltersBatchPageSize" value=""> <input type="text" class="form-control" placeholder="page size" style="width: 15%;" id="getBlockFiltersBatchPageSize" value="">
<input type="text" class="form-control" id="getBlockFiltersBatchScriptType" value="taproot-noordinals" placeholder="filter script">
</div> </div>
</div> </div>
</div> </div>