Generate and store filter headers

This commit is contained in:
pedro martelletto 2017-02-01 12:12:30 +00:00 committed by Olaoluwa Osuntokun
parent b53c42f5dc
commit e620538343

View file

@ -5,11 +5,13 @@
package indexers package indexers
import ( import (
"errors"
"github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/database" "github.com/btcsuite/btcd/database"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/gcs/builder" "github.com/btcsuite/btcutil/gcs/builder"
"github.com/btcsuite/fastsha256"
) )
const ( const (
@ -35,55 +37,48 @@ var (
cfExtendedHeaderKey = []byte("cf1headerbyhashidx") cfExtendedHeaderKey = []byte("cf1headerbyhashidx")
) )
// dbFetchBasicEntry() retrieves a block's basic filter. An entry's absence is // dbFetchFilter() retrieves a block's basic or extended filter. A filter's
// not considered an error. The filter is returned serialized.
func dbFetchBasicEntry(dbTx database.Tx, h *chainhash.Hash) ([]byte, error) {
idx := dbTx.Metadata().Bucket(cfBasicIndexKey)
return idx.Get(h[:]), nil
}
// dbFetchExtendedEntry() retrieves a block's extended filter. An entry's
// absence is not considered an error. The filter is returned serialized.
func dbFetchExtendedEntry(dbTx database.Tx, h *chainhash.Hash) ([]byte, error) {
idx := dbTx.Metadata().Bucket(cfExtendedIndexKey)
return idx.Get(h[:]), nil
}
// dbFetchBasicHeader() retrieves a block's basic filter header. A filter's
// absence is not considered an error. // absence is not considered an error.
func dbFetchBasicHeader(dbTx database.Tx, h *chainhash.Hash) ([]byte, error) { func dbFetchFilter(dbTx database.Tx, key []byte, h *chainhash.Hash) ([]byte, error) {
idx := dbTx.Metadata().Bucket(cfBasicHeaderKey) idx := dbTx.Metadata().Bucket(key)
return idx.Get(h[:]), nil return idx.Get(h[:]), nil
} }
// dbFetchExtendedHeader() retrieves a block's extended filter header. // dbFetchFilterHeader() retrieves a block's basic or extended filter header.
// A filter's absence is not considered an error. // A filter's absence is not considered an error.
func dbFetchExtendedHeader(dbTx database.Tx, h*chainhash.Hash) ([]byte, error) { func dbFetchFilterHeader(dbTx database.Tx, key []byte, h *chainhash.Hash) ([]byte, error) {
idx := dbTx.Metadata().Bucket(cfExtendedHeaderKey) idx := dbTx.Metadata().Bucket(key)
return idx.Get(h[:]), nil fh := idx.Get(h[:])
if len(fh) != fastsha256.Size {
return nil, errors.New("invalid filter header length")
}
return fh, nil
} }
// dbStoreBasicEntry() stores a block's basic filter. // dbStoreFilter() stores a block's basic or extended filter.
func dbStoreBasicEntry(dbTx database.Tx, h *chainhash.Hash, f []byte) error { func dbStoreFilter(dbTx database.Tx, key []byte, h *chainhash.Hash, f []byte) error {
idx := dbTx.Metadata().Bucket(cfBasicIndexKey) idx := dbTx.Metadata().Bucket(key)
return idx.Put(h[:], f) return idx.Put(h[:], f)
} }
// dbStoreBasicEntry() stores a block's extended filter. // dbStoreFilterHeader() stores a block's basic or extended filter header.
func dbStoreExtendedEntry(dbTx database.Tx, h *chainhash.Hash, f []byte) error { func dbStoreFilterHeader(dbTx database.Tx, key []byte, h *chainhash.Hash, fh []byte) error {
idx := dbTx.Metadata().Bucket(cfExtendedIndexKey) if len(fh) != fastsha256.Size {
return idx.Put(h[:], f) return errors.New("invalid filter header length")
}
idx := dbTx.Metadata().Bucket(key)
return idx.Put(h[:], fh)
} }
// dbDeleteBasicEntry() deletes a block's basic filter. // dbDeleteFilter() deletes a filter's basic or extended filter.
func dbDeleteBasicEntry(dbTx database.Tx, h *chainhash.Hash) error { func dbDeleteFilter(dbTx database.Tx, key []byte, h *chainhash.Hash) error {
idx := dbTx.Metadata().Bucket(cfBasicIndexKey) idx := dbTx.Metadata().Bucket(key)
return idx.Delete(h[:]) return idx.Delete(h[:])
} }
// dbDeleteExtendedEntry() deletes a block's extended filter. // dbDeleteFilterHeader() deletes a filter's basic or extended filter header.
func dbDeleteExtendedEntry(dbTx database.Tx, h *chainhash.Hash) error { func dbDeleteFilterHeader(dbTx database.Tx, key []byte, h *chainhash.Hash) error {
idx := dbTx.Metadata().Bucket(cfExtendedIndexKey) idx := dbTx.Metadata().Bucket(key)
return idx.Delete(h[:]) return idx.Delete(h[:])
} }
@ -113,8 +108,8 @@ func (idx *CfIndex) Name() string {
return cfIndexName return cfIndexName
} }
// Create is invoked when the indexer manager determines the index needs to be // Create() is invoked when the indexer manager determines the index needs to
// created for the first time. It creates buckets for the two hash-based cf // be created for the first time. It creates buckets for the two hash-based cf
// indexes (simple, extended). // indexes (simple, extended).
func (idx *CfIndex) Create(dbTx database.Tx) error { func (idx *CfIndex) Create(dbTx database.Tx) error {
meta := dbTx.Metadata() meta := dbTx.Metadata()
@ -134,8 +129,8 @@ func (idx *CfIndex) Create(dbTx database.Tx) error {
return err return err
} }
// makeBasicFilter() builds a block's basic filter, which consists of all // makeBasicFilterForBlock() builds a block's basic filter, which consists of
// outpoints and pkscript data pushes referenced by transactions within the // all outpoints and pkscript data pushes referenced by transactions within the
// block. // block.
func makeBasicFilterForBlock(block *btcutil.Block) ([]byte, error) { func makeBasicFilterForBlock(block *btcutil.Block) ([]byte, error) {
b := builder.WithKeyHash(block.Hash()) b := builder.WithKeyHash(block.Hash())
@ -158,8 +153,8 @@ func makeBasicFilterForBlock(block *btcutil.Block) ([]byte, error) {
return f.Bytes(), nil return f.Bytes(), nil
} }
// makeExtendedFilter() builds a block's extended filter, which consists of // makeExtendedFilterForBlock() builds a block's extended filter, which consists
// all tx hashes and sigscript data pushes contained in the block. // of all tx hashes and sigscript data pushes contained in the block.
func makeExtendedFilterForBlock(block *btcutil.Block) ([]byte, error) { func makeExtendedFilterForBlock(block *btcutil.Block) ([]byte, error) {
b := builder.WithKeyHash(block.Hash()) b := builder.WithKeyHash(block.Hash())
_, err := b.Key() _, err := b.Key()
@ -179,7 +174,45 @@ func makeExtendedFilterForBlock(block *btcutil.Block) ([]byte, error) {
return f.Bytes(), nil return f.Bytes(), nil
} }
// ConnectBlock is invoked by the index manager when a new block has been // makeHeaderForFilter() implements the chaining logic between filters, where
// a filter's header is defined as sha256(sha256(filter) + previousFilterHeader).
func makeHeaderForFilter(f, pfh []byte) []byte {
fhash := fastsha256.Sum256(f)
chain := make([]byte, 0, 2*fastsha256.Size)
chain = append(chain, fhash[:]...)
chain = append(chain, pfh...)
fh := fastsha256.Sum256(chain)
return fh[:]
}
// storeFilter() stores a given filter, and performs the steps needed to
// generate the filter's header.
func storeFilter(dbTx database.Tx, block *btcutil.Block, f []byte, extended bool) error {
// Figure out which buckets to use.
fkey := cfBasicIndexKey
hkey := cfBasicHeaderKey
if extended {
fkey = cfExtendedIndexKey
hkey = cfExtendedHeaderKey
}
// Start by storing the filter.
h := block.Hash()
err := dbStoreFilter(dbTx, fkey, h, f)
if err != nil {
return err
}
// Then fetch the previous block's filter header.
ph := &block.MsgBlock().Header.PrevBlock
pfh, err := dbFetchFilterHeader(dbTx, hkey, ph)
if err != nil {
return err
}
// Construct the new block's filter header, and store it.
fh := makeHeaderForFilter(f, pfh)
return dbStoreFilterHeader(dbTx, cfBasicHeaderKey, h, fh)
}
// ConnectBlock() is invoked by the index manager when a new block has been
// connected to the main chain. This indexer adds a hash-to-cf mapping for // connected to the main chain. This indexer adds a hash-to-cf mapping for
// every passed block. This is part of the Indexer interface. // every passed block. This is part of the Indexer interface.
func (idx *CfIndex) ConnectBlock(dbTx database.Tx, block *btcutil.Block, func (idx *CfIndex) ConnectBlock(dbTx database.Tx, block *btcutil.Block,
@ -188,7 +221,7 @@ func (idx *CfIndex) ConnectBlock(dbTx database.Tx, block *btcutil.Block,
if err != nil { if err != nil {
return err return err
} }
err = dbStoreBasicEntry(dbTx, block.Hash(), f) err = storeFilter(dbTx, block, f, false)
if err != nil { if err != nil {
return err return err
} }
@ -196,30 +229,30 @@ func (idx *CfIndex) ConnectBlock(dbTx database.Tx, block *btcutil.Block,
if err != nil { if err != nil {
return err return err
} }
return dbStoreExtendedEntry(dbTx, block.Hash(), f) return storeFilter(dbTx, block, f, true)
} }
// DisconnectBlock is invoked by the index manager when a block has been // DisconnectBlock() is invoked by the index manager when a block has been
// disconnected from the main chain. This indexer removes the hash-to-cf // disconnected from the main chain. This indexer removes the hash-to-cf
// mapping for every passed block. This is part of the Indexer interface. // mapping for every passed block. This is part of the Indexer interface.
func (idx *CfIndex) DisconnectBlock(dbTx database.Tx, block *btcutil.Block, func (idx *CfIndex) DisconnectBlock(dbTx database.Tx, block *btcutil.Block,
view *blockchain.UtxoViewpoint) error { view *blockchain.UtxoViewpoint) error {
err := dbDeleteBasicEntry(dbTx, block.Hash()) err := dbDeleteFilter(dbTx, cfBasicIndexKey, block.Hash())
if err != nil { if err != nil {
return err return err
} }
return dbDeleteExtendedEntry(dbTx, block.Hash()) return dbDeleteFilter(dbTx, cfExtendedIndexKey, block.Hash())
} }
func (idx *CfIndex) FilterByBlockHash(hash *chainhash.Hash, extended bool) ([]byte, error) { func (idx *CfIndex) FilterByBlockHash(hash *chainhash.Hash, extended bool) ([]byte, error) {
var filterBytes []byte var filterBytes []byte
err := idx.db.View(func(dbTx database.Tx) error { err := idx.db.View(func(dbTx database.Tx) error {
var err error var err error
key := cfBasicIndexKey
if extended { if extended {
filterBytes, err = dbFetchExtendedEntry(dbTx, hash) key = cfExtendedIndexKey
} else {
filterBytes, err = dbFetchBasicEntry(dbTx, hash)
} }
filterBytes, err = dbFetchFilter(dbTx, key, hash)
return err return err
}) })
return filterBytes, err return filterBytes, err