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,
Sequence: in.Sequence,
ScriptSig: s,
Witness: in.Witness,
}
}
vout := make([]bchain.Vout, len(t.TxOut))

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {

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) {
// 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
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 @@
</div>
<div class="col-8">
<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 class="row">
@ -732,6 +741,7 @@
<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" 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>