From 7d0c424ad801a1af7652ab79aab865837c413521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Musil?= Date: Fri, 1 Sep 2023 12:00:08 +0200 Subject: [PATCH] Ignore Ordinals in Golomb filters (#967) --- bchain/coins/btc/bitcoinlikeparser.go | 1 + bchain/coins/btc/bitcoinrpc.go | 5 +- bchain/golomb.go | 133 ++++++++++++++++++++++++-- bchain/mempool_bitcoin_type.go | 16 ++-- bchain/mempool_bitcoin_type_test.go | 2 +- bchain/types.go | 5 +- common/config.go | 19 ++-- common/internalstate.go | 5 +- db/bulkconnect.go | 2 +- db/rocksdb.go | 22 +++-- server/public.go | 27 ++++-- server/websocket.go | 64 +++++++++++-- server/ws_types.go | 11 ++- static/test-websocket.html | 12 ++- 14 files changed, 267 insertions(+), 57 deletions(-) diff --git a/bchain/coins/btc/bitcoinlikeparser.go b/bchain/coins/btc/bitcoinlikeparser.go index ba99d6fecc..9034dbc1a4 100644 --- a/bchain/coins/btc/bitcoinlikeparser.go +++ b/bchain/coins/btc/bitcoinlikeparser.go @@ -231,6 +231,7 @@ func (p *BitcoinLikeParser) TxFromMsgTx(t *wire.MsgTx, parseAddresses bool) bcha Vout: in.PreviousOutPoint.Index, Sequence: in.Sequence, ScriptSig: s, + Witness: in.Witness, } } vout := make([]bchain.Vout, len(t.TxOut)) diff --git a/bchain/coins/btc/bitcoinrpc.go b/bchain/coins/btc/bitcoinrpc.go index 12013c49ec..f37d37728b 100644 --- a/bchain/coins/btc/bitcoinrpc.go +++ b/bchain/coins/btc/bitcoinrpc.go @@ -34,6 +34,7 @@ type BitcoinRPC struct { RPCMarshaler RPCMarshaler mempoolGolombFilterP uint8 mempoolFilterScripts string + mempoolUseZeroedKey bool } // Configuration represents json config file @@ -63,6 +64,7 @@ type Configuration struct { MinimumCoinbaseConfirmations int `json:"minimumCoinbaseConfirmations,omitempty"` MempoolGolombFilterP uint8 `json:"mempool_golomb_filter_p,omitempty"` MempoolFilterScripts string `json:"mempool_filter_scripts,omitempty"` + MempoolFilterUseZeroedKey bool `json:"mempool_filter_use_zeroed_key,omitempty"` } // NewBitcoinRPC returns new BitcoinRPC instance. @@ -110,6 +112,7 @@ func NewBitcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationT RPCMarshaler: JSONMarshalerV2{}, mempoolGolombFilterP: c.MempoolGolombFilterP, mempoolFilterScripts: c.MempoolFilterScripts, + mempoolUseZeroedKey: c.MempoolFilterUseZeroedKey, } return s, nil @@ -155,7 +158,7 @@ func (b *BitcoinRPC) Initialize() error { // CreateMempool creates mempool if not already created, however does not initialize it func (b *BitcoinRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, error) { 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 } diff --git a/bchain/golomb.go b/bchain/golomb.go index f5e8986015..c0d38e303c 100644 --- a/bchain/golomb.go +++ b/bchain/golomb.go @@ -1,6 +1,7 @@ package bchain import ( + "bytes" "encoding/hex" "github.com/golang/glog" @@ -14,26 +15,33 @@ 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) (*GolombFilter, error) { +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, @@ -41,21 +49,85 @@ func NewGolombFilter(p uint8, filterScripts string, key string) (*GolombFilter, filterData: make([][]byte, 0), uniqueData: make(map[string]struct{}), } - // only taproot and all is supported + // 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) { - if f.filterScriptsType == FilterScriptsTaproot && !ad.IsTaproot() { +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) @@ -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 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 { return nil } - b, _ := hex.DecodeString(f.key) - if len(b) < gcs.KeySize { - 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, *(*[gcs.KeySize]byte)(b[:gcs.KeySize]), f.filterData) + 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 @@ -91,12 +188,30 @@ func (f *GolombFilter) Compute() []byte { 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 } diff --git a/bchain/mempool_bitcoin_type.go b/bchain/mempool_bitcoin_type.go index 5a63e756af..949fa9bf64 100644 --- a/bchain/mempool_bitcoin_type.go +++ b/bchain/mempool_bitcoin_type.go @@ -22,11 +22,12 @@ type MempoolBitcoinType struct { AddrDescForOutpoint AddrDescForOutpointFunc golombFilterP uint8 filterScripts string + useZeroedKey bool } // 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 -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{ BaseMempool: BaseMempool{ chain: chain, @@ -37,6 +38,7 @@ func NewMempoolBitcoinType(chain BlockChain, workers int, subworkers int, golomb chanAddrIndex: make(chan txidio, 1), golombFilterP: golombFilterP, filterScripts: filterScripts, + useZeroedKey: useZeroedKey, } for i := 0; i < workers; i++ { go func(i int) { @@ -97,18 +99,18 @@ func (m *MempoolBitcoinType) getInputAddress(payload *chanInputPayload) *addrInd } -func (m *MempoolBitcoinType) computeGolombFilter(mtx *MempoolTx) string { - gf, _ := NewGolombFilter(m.golombFilterP, m.filterScripts, mtx.Txid) +func (m *MempoolBitcoinType) computeGolombFilter(mtx *MempoolTx, tx *Tx) string { + gf, _ := NewGolombFilter(m.golombFilterP, "", mtx.Txid, m.useZeroedKey) if gf == nil || !gf.Enabled { return "" } for _, vin := range mtx.Vin { - gf.AddAddrDesc(vin.AddrDesc) + gf.AddAddrDesc(vin.AddrDesc, tx) } for _, vout := range mtx.Vout { b, err := hex.DecodeString(vout.ScriptPubKey.Hex) if err == nil { - gf.AddAddrDesc(b) + gf.AddAddrDesc(b, tx) } } fb := gf.Compute() @@ -168,7 +170,7 @@ func (m *MempoolBitcoinType) getTxAddrs(txid string, chanInput chan chanInputPay } var golombFilter string if m.golombFilterP > 0 { - golombFilter = m.computeGolombFilter(mtx) + golombFilter = m.computeGolombFilter(mtx, tx) } if m.OnNewTx != nil { m.OnNewTx(mtx) @@ -249,5 +251,5 @@ func (m *MempoolBitcoinType) GetTxidFilterEntries(filterScripts string, fromTime } } m.mux.Unlock() - return MempoolTxidFilterEntries{entries}, nil + return MempoolTxidFilterEntries{entries, m.useZeroedKey}, nil } diff --git a/bchain/mempool_bitcoin_type_test.go b/bchain/mempool_bitcoin_type_test.go index 9201be4cbe..0417915a0a 100644 --- a/bchain/mempool_bitcoin_type_test.go +++ b/bchain/mempool_bitcoin_type_test.go @@ -18,7 +18,7 @@ func TestMempoolBitcoinType_computeGolombFilter_taproot(t *testing.T) { golombFilterP: 20, filterScripts: "taproot", } - golombFilterM := uint64(1 << uint64(m.golombFilterP)) + golombFilterM := GetGolombParamM(m.golombFilterP) tests := []struct { name string mtx MempoolTx diff --git a/bchain/types.go b/bchain/types.go index 91774948f3..ca75444d6c 100644 --- a/bchain/types.go +++ b/bchain/types.go @@ -57,6 +57,7 @@ type Vin struct { ScriptSig ScriptSig `json:"scriptSig"` Sequence uint32 `json:"sequence"` Addresses []string `json:"addresses"` + Witness [][]byte `json:"witness"` } // ScriptPubKey contains data about output script @@ -273,8 +274,10 @@ type XpubDescriptor struct { type MempoolTxidEntries []MempoolTxidEntry // 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 { - 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 diff --git a/common/config.go b/common/config.go index 0d6d4239e3..a04298055e 100644 --- a/common/config.go +++ b/common/config.go @@ -9,15 +9,16 @@ import ( // Config struct type Config struct { - CoinName string `json:"coin_name"` - CoinShortcut string `json:"coin_shortcut"` - CoinLabel string `json:"coin_label"` - FourByteSignatures string `json:"fourByteSignatures"` - FiatRates string `json:"fiat_rates"` - FiatRatesParams string `json:"fiat_rates_params"` - FiatRatesVsCurrencies string `json:"fiat_rates_vs_currencies"` - BlockGolombFilterP uint8 `json:"block_golomb_filter_p"` - BlockFilterScripts string `json:"block_filter_scripts"` + CoinName string `json:"coin_name"` + CoinShortcut string `json:"coin_shortcut"` + CoinLabel string `json:"coin_label"` + FourByteSignatures string `json:"fourByteSignatures"` + FiatRates string `json:"fiat_rates"` + FiatRatesParams string `json:"fiat_rates_params"` + FiatRatesVsCurrencies string `json:"fiat_rates_vs_currencies"` + BlockGolombFilterP uint8 `json:"block_golomb_filter_p"` + 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 diff --git a/common/internalstate.go b/common/internalstate.go index fb1cec6218..d02da85414 100644 --- a/common/internalstate.go +++ b/common/internalstate.go @@ -94,8 +94,9 @@ type InternalState struct { SortedAddressContracts bool `json:"sortedAddressContracts"` // golomb filter settings - BlockGolombFilterP uint8 `json:"block_golomb_filter_p"` - BlockFilterScripts string `json:"block_filter_scripts"` + BlockGolombFilterP uint8 `json:"block_golomb_filter_p"` + BlockFilterScripts string `json:"block_filter_scripts"` + BlockFilterUseZeroedKey bool `json:"block_filter_use_zeroed_key"` } // StartedSync signals start of synchronization diff --git a/db/bulkconnect.go b/db/bulkconnect.go index 07c7988f92..2a044bc12a 100644 --- a/db/bulkconnect.go +++ b/db/bulkconnect.go @@ -185,7 +185,7 @@ func (b *BulkConnect) storeBulkBlockFilters(wb *grocksdb.WriteBatch) error { func (b *BulkConnect) connectBlockBitcoinType(block *bchain.Block, storeBlockTxs bool) error { 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 { glog.Error("connectBlockBitcoinType golomb filter error ", err) gf = nil diff --git a/db/rocksdb.go b/db/rocksdb.go index 06743c8a1d..9c5a3b2fdf 100644 --- a/db/rocksdb.go +++ b/db/rocksdb.go @@ -349,7 +349,7 @@ func (d *RocksDB) ConnectBlock(block *bchain.Block) error { if chainType == bchain.ChainBitcoinType { txAddressesMap := make(map[string]*TxAddresses) 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 { glog.Error("ConnectBlock golomb filter error ", err) gf = nil @@ -643,7 +643,7 @@ func (d *RocksDB) processAddressesBitcoinType(block *bchain.Block, addresses add continue } if gf != nil { - gf.AddAddrDesc(addrDesc) + gf.AddAddrDesc(addrDesc, tx) } tao.AddrDesc = 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) } if gf != nil { - gf.AddAddrDesc(spentOutput.AddrDesc) + gf.AddAddrDesc(spentOutput.AddrDesc, tx) } tai.AddrDesc = spentOutput.AddrDesc tai.ValueSat = spentOutput.ValueSat @@ -1894,12 +1894,13 @@ func (d *RocksDB) LoadInternalState(config *common.Config) (*common.InternalStat var is *common.InternalState if len(data) == 0 { is = &common.InternalState{ - Coin: config.CoinName, - UtxoChecked: true, - SortedAddressContracts: true, - ExtendedIndex: d.extendedIndex, - BlockGolombFilterP: config.BlockGolombFilterP, - BlockFilterScripts: config.BlockFilterScripts, + Coin: config.CoinName, + UtxoChecked: true, + SortedAddressContracts: true, + ExtendedIndex: d.extendedIndex, + BlockGolombFilterP: config.BlockGolombFilterP, + BlockFilterScripts: config.BlockFilterScripts, + BlockFilterUseZeroedKey: config.BlockFilterUseZeroedKey, } } else { is, err = common.UnpackInternalState(data) @@ -1922,6 +1923,9 @@ func (d *RocksDB) LoadInternalState(config *common.Config) (*common.InternalStat if 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) if err != nil { diff --git a/server/public.go b/server/public.go index 580c681201..d21da713f2 100644 --- a/server/public.go +++ b/server/public.go @@ -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) { // Define return type + type blockFilterResult struct { + BlockHash string `json:"blockHash"` + Filter string `json:"filter"` + } 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 @@ -1247,6 +1254,11 @@ func (s *PublicServer) apiBlockFilters(r *http.Request, apiVersion int) (interfa if ec != nil { 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 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) { - blockFiltersMap := make(map[int]map[string]string) + blockFiltersMap := make(map[int]blockFilterResult) for i := fromHeight; i <= toHeight; i++ { blockHash, err := s.db.GetBlockHash(uint32(i)) if err != nil { @@ -1290,12 +1302,15 @@ func (s *PublicServer) apiBlockFilters(r *http.Request, apiVersion int) (interfa glog.Error(err) return nil, err } - resultMap := make(map[string]string) - resultMap["blockHash"] = blockHash - resultMap["filter"] = blockFilter - blockFiltersMap[i] = resultMap + blockFiltersMap[i] = blockFilterResult{ + BlockHash: blockHash, + Filter: blockFilter, + } } return resBlockFilters{ + ParamP: s.is.BlockGolombFilterP, + ParamM: bchain.GetGolombParamM(s.is.BlockGolombFilterP), + ZeroedKey: s.is.BlockFilterUseZeroedKey, BlockFilters: blockFiltersMap, }, nil } diff --git a/server/websocket.go b/server/websocket.go index 447010f408..753748871d 100644 --- a/server/websocket.go +++ b/server/websocket.go @@ -657,17 +657,67 @@ func (s *WebsocketServer) sendTransaction(tx string) (res resultSendTransaction, return } -func (s *WebsocketServer) getMempoolFilters(r *WsMempoolFiltersReq) (res bchain.MempoolTxidFilterEntries, err error) { - res, err = s.mempool.GetTxidFilterEntries(r.ScriptType, r.FromTimestamp) - return +func (s *WebsocketServer) getMempoolFilters(r *WsMempoolFiltersReq) (res interface{}, err error) { + type resMempoolFilters struct { + 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) { - return s.db.GetBlockFilter(r.BlockHash) +func (s *WebsocketServer) getBlockFilter(r *WsBlockFilterReq) (res interface{}, err error) { + 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) { - return s.api.GetBlockFiltersBatch(r.BlockHash, r.PageSize) +func (s *WebsocketServer) getBlockFiltersBatch(r *WsBlockFiltersBatchReq) (res interface{}, err error) { + 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 { diff --git a/server/ws_types.go b/server/ws_types.go index a71c2df7fb..d91a2a565d 100644 --- a/server/ws_types.go +++ b/server/ws_types.go @@ -79,15 +79,20 @@ type WsTransactionReq struct { type WsMempoolFiltersReq struct { ScriptType string `json:"scriptType"` FromTimestamp uint32 `json:"fromTimestamp"` + ParamM uint64 `json:"M,omitempty"` } type WsBlockFilterReq struct { - BlockHash string `json:"blockHash"` + ScriptType string `json:"scriptType"` + BlockHash string `json:"blockHash"` + ParamM uint64 `json:"M,omitempty"` } type WsBlockFiltersBatchReq struct { - BlockHash string `json:"bestKnownBlockHash"` - PageSize int `json:"pageSize,omitempty"` + ScriptType string `json:"scriptType"` + BlockHash string `json:"bestKnownBlockHash"` + PageSize int `json:"pageSize,omitempty"` + ParamM uint64 `json:"M,omitempty"` } type WsTransactionSpecificReq struct { diff --git a/static/test-websocket.html b/static/test-websocket.html index 07a5ebf855..3972aa539e 100644 --- a/static/test-websocket.html +++ b/static/test-websocket.html @@ -87,7 +87,11 @@ var f = pendingMessages[resp.id]; if (f != undefined) { delete pendingMessages[resp.id]; - f(resp.data); + try { + f(resp.data); + } catch (e) { + alert(`Error: ${e}.\nLook into the console for websocket response.`); + } } else { f = subscriptions[resp.id]; if (f != undefined) { @@ -414,8 +418,10 @@ function getBlockFilter() { const method = 'getBlockFilter'; const blockHash = document.getElementById('getBlockFilterBlockHash').value; + const scriptType = document.getElementById('getBlockFilterBlockHashScriptType').value; const params = { blockHash, + scriptType, }; send(method, params, function (result) { document.getElementById('getBlockFilterResult').innerText = JSON.stringify(result).replace(/,/g, ", "); @@ -426,8 +432,10 @@ const method = 'getBlockFiltersBatch'; const bestKnownBlockHash = document.getElementById('getBlockFiltersBatchBlockHash').value; const pageSize = parseInt(document.getElementById("getBlockFiltersBatchPageSize").value); + const scriptType = document.getElementById('getBlockFiltersBatchScriptType').value; const params = { bestKnownBlockHash, + scriptType, }; if (pageSize) params.pageSize = pageSize; send(method, params, function (result) { @@ -719,6 +727,7 @@

Blockbook Websocket Test Page

+
@@ -732,6 +741,7 @@

Blockbook Websocket Test Page

+