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 }