mirror of
https://github.com/trezor/blockbook.git
synced 2026-02-20 00:51:39 +01:00
Ignore Ordinals in Golomb filters (#967)
This commit is contained in:
@@ -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))
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
133
bchain/golomb.go
133
bchain/golomb.go
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user