github.com/decred/dcrd/blockchain@v1.2.1/indexers/cfindex.go (about)

     1  // Copyright (c) 2017 The btcsuite developers
     2  // Copyright (c) 2018 The Decred developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package indexers
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  
    12  	"github.com/decred/dcrd/blockchain"
    13  	"github.com/decred/dcrd/chaincfg"
    14  	"github.com/decred/dcrd/chaincfg/chainhash"
    15  	"github.com/decred/dcrd/database"
    16  	"github.com/decred/dcrd/dcrutil"
    17  	"github.com/decred/dcrd/gcs"
    18  	"github.com/decred/dcrd/gcs/blockcf"
    19  	"github.com/decred/dcrd/wire"
    20  )
    21  
    22  const (
    23  	// cfIndexName is the human-readable name for the index.
    24  	cfIndexName = "committed filter index"
    25  
    26  	// cfIndexVersion is the current version of the committed filter index.
    27  	cfIndexVersion = 2
    28  )
    29  
    30  // Committed filters come in two flavors: basic and extended. They are
    31  // generated and dropped in pairs, and both are indexed by a block's hash.
    32  // Besides holding different content, they also live in different buckets.
    33  var (
    34  	// cfIndexParentBucketKey is the name of the parent bucket used to house
    35  	// the index. The rest of the buckets live below this bucket.
    36  	cfIndexParentBucketKey = []byte("cfindexparentbucket")
    37  
    38  	// cfIndexKeys is an array of db bucket names used to house indexes of
    39  	// block hashes to cfilters.
    40  	cfIndexKeys = [][]byte{
    41  		[]byte("cf0byhashidx"),
    42  		[]byte("cf1byhashidx"),
    43  	}
    44  
    45  	// cfHeaderKeys is an array of db bucket names used to house indexes of
    46  	// block hashes to cf headers.
    47  	cfHeaderKeys = [][]byte{
    48  		[]byte("cf0headerbyhashidx"),
    49  		[]byte("cf1headerbyhashidx"),
    50  	}
    51  
    52  	maxFilterType = uint8(len(cfHeaderKeys) - 1)
    53  )
    54  
    55  // dbFetchFilter retrieves a block's basic or extended filter. A filter's
    56  // absence is not considered an error.
    57  func dbFetchFilter(dbTx database.Tx, key []byte, h *chainhash.Hash) []byte {
    58  	idx := dbTx.Metadata().Bucket(cfIndexParentBucketKey).Bucket(key)
    59  	return idx.Get(h[:])
    60  }
    61  
    62  // dbFetchFilterHeader retrieves a block's basic or extended filter header.
    63  // A filter's absence is not considered an error.
    64  func dbFetchFilterHeader(dbTx database.Tx, key []byte, h *chainhash.Hash) ([]byte, error) {
    65  	idx := dbTx.Metadata().Bucket(cfIndexParentBucketKey).Bucket(key)
    66  
    67  	fh := idx.Get(h[:])
    68  	if fh == nil {
    69  		return make([]byte, chainhash.HashSize), nil
    70  	}
    71  	if len(fh) != chainhash.HashSize {
    72  		return nil, fmt.Errorf("invalid filter header length %v", len(fh))
    73  	}
    74  
    75  	return fh, nil
    76  }
    77  
    78  // dbStoreFilter stores a block's basic or extended filter.
    79  func dbStoreFilter(dbTx database.Tx, key []byte, h *chainhash.Hash, f []byte) error {
    80  	idx := dbTx.Metadata().Bucket(cfIndexParentBucketKey).Bucket(key)
    81  	return idx.Put(h[:], f)
    82  }
    83  
    84  // dbStoreFilterHeader stores a block's basic or extended filter header.
    85  func dbStoreFilterHeader(dbTx database.Tx, key []byte, h *chainhash.Hash, fh []byte) error {
    86  	if len(fh) != chainhash.HashSize {
    87  		return fmt.Errorf("invalid filter header length %v", len(fh))
    88  	}
    89  	idx := dbTx.Metadata().Bucket(cfIndexParentBucketKey).Bucket(key)
    90  	return idx.Put(h[:], fh)
    91  }
    92  
    93  // dbDeleteFilter deletes a filter's basic or extended filter.
    94  func dbDeleteFilter(dbTx database.Tx, key []byte, h *chainhash.Hash) error {
    95  	idx := dbTx.Metadata().Bucket(cfIndexParentBucketKey).Bucket(key)
    96  	return idx.Delete(h[:])
    97  }
    98  
    99  // dbDeleteFilterHeader deletes a filter's basic or extended filter header.
   100  func dbDeleteFilterHeader(dbTx database.Tx, key []byte, h *chainhash.Hash) error {
   101  	idx := dbTx.Metadata().Bucket(cfIndexParentBucketKey).Bucket(key)
   102  	return idx.Delete(h[:])
   103  }
   104  
   105  // CFIndex implements a committed filter (cf) by hash index.
   106  type CFIndex struct {
   107  	db          database.DB
   108  	chainParams *chaincfg.Params
   109  }
   110  
   111  // Ensure the CFIndex type implements the Indexer interface.
   112  var _ Indexer = (*CFIndex)(nil)
   113  
   114  // Init initializes the hash-based cf index. This is part of the Indexer
   115  // interface.
   116  func (idx *CFIndex) Init() error {
   117  	return nil // Nothing to do.
   118  }
   119  
   120  // Key returns the database key to use for the index as a byte slice. This is
   121  // part of the Indexer interface.
   122  func (idx *CFIndex) Key() []byte {
   123  	return cfIndexParentBucketKey
   124  }
   125  
   126  // Name returns the human-readable name of the index. This is part of the
   127  // Indexer interface.
   128  func (idx *CFIndex) Name() string {
   129  	return cfIndexName
   130  }
   131  
   132  // Version returns the current version of the index.
   133  //
   134  // This is part of the Indexer interface.
   135  func (idx *CFIndex) Version() uint32 {
   136  	return cfIndexVersion
   137  }
   138  
   139  // Create is invoked when the indexer manager determines the index needs to
   140  // be created for the first time. It creates buckets for the two hash-based cf
   141  // indexes (simple, extended).
   142  func (idx *CFIndex) Create(dbTx database.Tx) error {
   143  	meta := dbTx.Metadata()
   144  
   145  	cfIndexParentBucket, err := meta.CreateBucket(cfIndexParentBucketKey)
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	for _, bucketName := range cfIndexKeys {
   151  		_, err = cfIndexParentBucket.CreateBucket(bucketName)
   152  		if err != nil {
   153  			return err
   154  		}
   155  	}
   156  
   157  	for _, bucketName := range cfHeaderKeys {
   158  		_, err = cfIndexParentBucket.CreateBucket(bucketName)
   159  		if err != nil {
   160  			return err
   161  		}
   162  	}
   163  
   164  	firstHeader := make([]byte, chainhash.HashSize)
   165  	err = dbStoreFilterHeader(dbTx, cfHeaderKeys[wire.GCSFilterRegular],
   166  		&idx.chainParams.GenesisBlock.Header.PrevBlock, firstHeader)
   167  	if err != nil {
   168  		return err
   169  	}
   170  
   171  	return dbStoreFilterHeader(dbTx, cfHeaderKeys[wire.GCSFilterExtended],
   172  		&idx.chainParams.GenesisBlock.Header.PrevBlock, firstHeader)
   173  }
   174  
   175  // storeFilter stores a given filter, and performs the steps needed to
   176  // generate the filter's header.
   177  func storeFilter(dbTx database.Tx, block *dcrutil.Block, f *gcs.Filter, filterType wire.FilterType) error {
   178  	if uint8(filterType) > maxFilterType {
   179  		return errors.New("unsupported filter type")
   180  	}
   181  
   182  	// Figure out which buckets to use.
   183  	fkey := cfIndexKeys[filterType]
   184  	hkey := cfHeaderKeys[filterType]
   185  
   186  	// Start by storing the filter.
   187  	h := block.Hash()
   188  	var basicFilterBytes []byte
   189  	if f != nil {
   190  		basicFilterBytes = f.NBytes()
   191  	}
   192  	err := dbStoreFilter(dbTx, fkey, h, basicFilterBytes)
   193  	if err != nil {
   194  		return err
   195  	}
   196  
   197  	// Then fetch the previous block's filter header.
   198  	ph := &block.MsgBlock().Header.PrevBlock
   199  	pfh, err := dbFetchFilterHeader(dbTx, hkey, ph)
   200  	if err != nil {
   201  		return err
   202  	}
   203  
   204  	// Construct the new block's filter header, and store it.
   205  	prevHeader, err := chainhash.NewHash(pfh)
   206  	if err != nil {
   207  		return err
   208  	}
   209  	fh := gcs.MakeHeaderForFilter(f, prevHeader)
   210  	return dbStoreFilterHeader(dbTx, hkey, h, fh[:])
   211  }
   212  
   213  // ConnectBlock is invoked by the index manager when a new block has been
   214  // connected to the main chain. This indexer adds a hash-to-cf mapping for
   215  // every passed block. This is part of the Indexer interface.
   216  func (idx *CFIndex) ConnectBlock(dbTx database.Tx, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) error {
   217  	f, err := blockcf.Regular(block.MsgBlock())
   218  	if err != nil && err != gcs.ErrNoData {
   219  		return err
   220  	}
   221  
   222  	err = storeFilter(dbTx, block, f, wire.GCSFilterRegular)
   223  	if err != nil {
   224  		return err
   225  	}
   226  
   227  	f, err = blockcf.Extended(block.MsgBlock())
   228  	if err != nil && err != gcs.ErrNoData {
   229  		return err
   230  	}
   231  
   232  	return storeFilter(dbTx, block, f, wire.GCSFilterExtended)
   233  }
   234  
   235  // DisconnectBlock is invoked by the index manager when a block has been
   236  // disconnected from the main chain.  This indexer removes the hash-to-cf
   237  // mapping for every passed block. This is part of the Indexer interface.
   238  func (idx *CFIndex) DisconnectBlock(dbTx database.Tx, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) error {
   239  	for _, key := range cfIndexKeys {
   240  		err := dbDeleteFilter(dbTx, key, block.Hash())
   241  		if err != nil {
   242  			return err
   243  		}
   244  	}
   245  
   246  	for _, key := range cfHeaderKeys {
   247  		err := dbDeleteFilterHeader(dbTx, key, block.Hash())
   248  		if err != nil {
   249  			return err
   250  		}
   251  	}
   252  
   253  	return nil
   254  }
   255  
   256  // FilterByBlockHash returns the serialized contents of a block's basic or
   257  // extended committed filter.
   258  func (idx *CFIndex) FilterByBlockHash(h *chainhash.Hash, filterType wire.FilterType) ([]byte, error) {
   259  	if uint8(filterType) > maxFilterType {
   260  		return nil, errors.New("unsupported filter type")
   261  	}
   262  
   263  	var f []byte
   264  	err := idx.db.View(func(dbTx database.Tx) error {
   265  		f = dbFetchFilter(dbTx, cfIndexKeys[filterType], h)
   266  		return nil
   267  	})
   268  	return f, err
   269  }
   270  
   271  // FilterHeaderByBlockHash returns the serialized contents of a block's basic
   272  // or extended committed filter header.
   273  func (idx *CFIndex) FilterHeaderByBlockHash(h *chainhash.Hash, filterType wire.FilterType) ([]byte, error) {
   274  	if uint8(filterType) > maxFilterType {
   275  		return nil, errors.New("unsupported filter type")
   276  	}
   277  
   278  	var fh []byte
   279  	err := idx.db.View(func(dbTx database.Tx) error {
   280  		var err error
   281  		fh, err = dbFetchFilterHeader(dbTx,
   282  			cfHeaderKeys[filterType], h)
   283  		return err
   284  	})
   285  	return fh, err
   286  }
   287  
   288  // NewCfIndex returns a new instance of an indexer that is used to create a
   289  // mapping of the hashes of all blocks in the blockchain to their respective
   290  // committed filters.
   291  //
   292  // It implements the Indexer interface which plugs into the IndexManager that
   293  // in turn is used by the blockchain package. This allows the index to be
   294  // seamlessly maintained along with the chain.
   295  func NewCfIndex(db database.DB, chainParams *chaincfg.Params) *CFIndex {
   296  	return &CFIndex{db: db, chainParams: chainParams}
   297  }
   298  
   299  // DropCfIndex drops the CF index from the provided database if exists.
   300  func DropCfIndex(db database.DB, interrupt <-chan struct{}) error {
   301  	return dropIndexMetadata(db, cfIndexParentBucketKey, cfIndexName)
   302  }