github.com/palcoin-project/palcd@v1.0.0/blockchain/indexers/cfindex.go (about) 1 // Copyright (c) 2017 The btcsuite developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package indexers 6 7 import ( 8 "errors" 9 10 "github.com/palcoin-project/palcd/blockchain" 11 "github.com/palcoin-project/palcd/chaincfg" 12 "github.com/palcoin-project/palcd/chaincfg/chainhash" 13 "github.com/palcoin-project/palcd/database" 14 "github.com/palcoin-project/palcd/wire" 15 "github.com/palcoin-project/palcutil" 16 "github.com/palcoin-project/palcutil/gcs" 17 "github.com/palcoin-project/palcutil/gcs/builder" 18 ) 19 20 const ( 21 // cfIndexName is the human-readable name for the index. 22 cfIndexName = "committed filter index" 23 ) 24 25 // Committed filters come in one flavor currently: basic. They are generated 26 // and dropped in pairs, and both are indexed by a block's hash. Besides 27 // holding different content, they also live in different buckets. 28 var ( 29 // cfIndexParentBucketKey is the name of the parent bucket used to 30 // house the index. The rest of the buckets live below this bucket. 31 cfIndexParentBucketKey = []byte("cfindexparentbucket") 32 33 // cfIndexKeys is an array of db bucket names used to house indexes of 34 // block hashes to cfilters. 35 cfIndexKeys = [][]byte{ 36 []byte("cf0byhashidx"), 37 } 38 39 // cfHeaderKeys is an array of db bucket names used to house indexes of 40 // block hashes to cf headers. 41 cfHeaderKeys = [][]byte{ 42 []byte("cf0headerbyhashidx"), 43 } 44 45 // cfHashKeys is an array of db bucket names used to house indexes of 46 // block hashes to cf hashes. 47 cfHashKeys = [][]byte{ 48 []byte("cf0hashbyhashidx"), 49 } 50 51 maxFilterType = uint8(len(cfHeaderKeys) - 1) 52 53 // zeroHash is the chainhash.Hash value of all zero bytes, defined here 54 // for convenience. 55 zeroHash chainhash.Hash 56 ) 57 58 // dbFetchFilterIdxEntry retrieves a data blob from the filter index database. 59 // An entry's absence is not considered an error. 60 func dbFetchFilterIdxEntry(dbTx database.Tx, key []byte, h *chainhash.Hash) ([]byte, error) { 61 idx := dbTx.Metadata().Bucket(cfIndexParentBucketKey).Bucket(key) 62 return idx.Get(h[:]), nil 63 } 64 65 // dbStoreFilterIdxEntry stores a data blob in the filter index database. 66 func dbStoreFilterIdxEntry(dbTx database.Tx, key []byte, h *chainhash.Hash, f []byte) error { 67 idx := dbTx.Metadata().Bucket(cfIndexParentBucketKey).Bucket(key) 68 return idx.Put(h[:], f) 69 } 70 71 // dbDeleteFilterIdxEntry deletes a data blob from the filter index database. 72 func dbDeleteFilterIdxEntry(dbTx database.Tx, key []byte, h *chainhash.Hash) error { 73 idx := dbTx.Metadata().Bucket(cfIndexParentBucketKey).Bucket(key) 74 return idx.Delete(h[:]) 75 } 76 77 // CfIndex implements a committed filter (cf) by hash index. 78 type CfIndex struct { 79 db database.DB 80 chainParams *chaincfg.Params 81 } 82 83 // Ensure the CfIndex type implements the Indexer interface. 84 var _ Indexer = (*CfIndex)(nil) 85 86 // Ensure the CfIndex type implements the NeedsInputser interface. 87 var _ NeedsInputser = (*CfIndex)(nil) 88 89 // NeedsInputs signals that the index requires the referenced inputs in order 90 // to properly create the index. 91 // 92 // This implements the NeedsInputser interface. 93 func (idx *CfIndex) NeedsInputs() bool { 94 return true 95 } 96 97 // Init initializes the hash-based cf index. This is part of the Indexer 98 // interface. 99 func (idx *CfIndex) Init() error { 100 return nil // Nothing to do. 101 } 102 103 // Key returns the database key to use for the index as a byte slice. This is 104 // part of the Indexer interface. 105 func (idx *CfIndex) Key() []byte { 106 return cfIndexParentBucketKey 107 } 108 109 // Name returns the human-readable name of the index. This is part of the 110 // Indexer interface. 111 func (idx *CfIndex) Name() string { 112 return cfIndexName 113 } 114 115 // Create is invoked when the indexer manager determines the index needs to 116 // be created for the first time. It creates buckets for the two hash-based cf 117 // indexes (regular only currently). 118 func (idx *CfIndex) Create(dbTx database.Tx) error { 119 meta := dbTx.Metadata() 120 121 cfIndexParentBucket, err := meta.CreateBucket(cfIndexParentBucketKey) 122 if err != nil { 123 return err 124 } 125 126 for _, bucketName := range cfIndexKeys { 127 _, err = cfIndexParentBucket.CreateBucket(bucketName) 128 if err != nil { 129 return err 130 } 131 } 132 133 for _, bucketName := range cfHeaderKeys { 134 _, err = cfIndexParentBucket.CreateBucket(bucketName) 135 if err != nil { 136 return err 137 } 138 } 139 140 for _, bucketName := range cfHashKeys { 141 _, err = cfIndexParentBucket.CreateBucket(bucketName) 142 if err != nil { 143 return err 144 } 145 } 146 147 return nil 148 } 149 150 // storeFilter stores a given filter, and performs the steps needed to 151 // generate the filter's header. 152 func storeFilter(dbTx database.Tx, block *palcutil.Block, f *gcs.Filter, 153 filterType wire.FilterType) error { 154 if uint8(filterType) > maxFilterType { 155 return errors.New("unsupported filter type") 156 } 157 158 // Figure out which buckets to use. 159 fkey := cfIndexKeys[filterType] 160 hkey := cfHeaderKeys[filterType] 161 hashkey := cfHashKeys[filterType] 162 163 // Start by storing the filter. 164 h := block.Hash() 165 filterBytes, err := f.NBytes() 166 if err != nil { 167 return err 168 } 169 err = dbStoreFilterIdxEntry(dbTx, fkey, h, filterBytes) 170 if err != nil { 171 return err 172 } 173 174 // Next store the filter hash. 175 filterHash, err := builder.GetFilterHash(f) 176 if err != nil { 177 return err 178 } 179 err = dbStoreFilterIdxEntry(dbTx, hashkey, h, filterHash[:]) 180 if err != nil { 181 return err 182 } 183 184 // Then fetch the previous block's filter header. 185 var prevHeader *chainhash.Hash 186 ph := &block.MsgBlock().Header.PrevBlock 187 if ph.IsEqual(&zeroHash) { 188 prevHeader = &zeroHash 189 } else { 190 pfh, err := dbFetchFilterIdxEntry(dbTx, hkey, ph) 191 if err != nil { 192 return err 193 } 194 195 // Construct the new block's filter header, and store it. 196 prevHeader, err = chainhash.NewHash(pfh) 197 if err != nil { 198 return err 199 } 200 } 201 202 fh, err := builder.MakeHeaderForFilter(f, *prevHeader) 203 if err != nil { 204 return err 205 } 206 return dbStoreFilterIdxEntry(dbTx, hkey, h, fh[:]) 207 } 208 209 // ConnectBlock is invoked by the index manager when a new block has been 210 // connected to the main chain. This indexer adds a hash-to-cf mapping for 211 // every passed block. This is part of the Indexer interface. 212 func (idx *CfIndex) ConnectBlock(dbTx database.Tx, block *palcutil.Block, 213 stxos []blockchain.SpentTxOut) error { 214 215 prevScripts := make([][]byte, len(stxos)) 216 for i, stxo := range stxos { 217 prevScripts[i] = stxo.PkScript 218 } 219 220 f, err := builder.BuildBasicFilter(block.MsgBlock(), prevScripts) 221 if err != nil { 222 return err 223 } 224 225 return storeFilter(dbTx, block, f, wire.GCSFilterRegular) 226 } 227 228 // DisconnectBlock is invoked by the index manager when a block has been 229 // disconnected from the main chain. This indexer removes the hash-to-cf 230 // mapping for every passed block. This is part of the Indexer interface. 231 func (idx *CfIndex) DisconnectBlock(dbTx database.Tx, block *palcutil.Block, 232 _ []blockchain.SpentTxOut) error { 233 234 for _, key := range cfIndexKeys { 235 err := dbDeleteFilterIdxEntry(dbTx, key, block.Hash()) 236 if err != nil { 237 return err 238 } 239 } 240 241 for _, key := range cfHeaderKeys { 242 err := dbDeleteFilterIdxEntry(dbTx, key, block.Hash()) 243 if err != nil { 244 return err 245 } 246 } 247 248 for _, key := range cfHashKeys { 249 err := dbDeleteFilterIdxEntry(dbTx, key, block.Hash()) 250 if err != nil { 251 return err 252 } 253 } 254 255 return nil 256 } 257 258 // entryByBlockHash fetches a filter index entry of a particular type 259 // (eg. filter, filter header, etc) for a filter type and block hash. 260 func (idx *CfIndex) entryByBlockHash(filterTypeKeys [][]byte, 261 filterType wire.FilterType, h *chainhash.Hash) ([]byte, error) { 262 263 if uint8(filterType) > maxFilterType { 264 return nil, errors.New("unsupported filter type") 265 } 266 key := filterTypeKeys[filterType] 267 268 var entry []byte 269 err := idx.db.View(func(dbTx database.Tx) error { 270 var err error 271 entry, err = dbFetchFilterIdxEntry(dbTx, key, h) 272 return err 273 }) 274 return entry, err 275 } 276 277 // entriesByBlockHashes batch fetches a filter index entry of a particular type 278 // (eg. filter, filter header, etc) for a filter type and slice of block hashes. 279 func (idx *CfIndex) entriesByBlockHashes(filterTypeKeys [][]byte, 280 filterType wire.FilterType, blockHashes []*chainhash.Hash) ([][]byte, error) { 281 282 if uint8(filterType) > maxFilterType { 283 return nil, errors.New("unsupported filter type") 284 } 285 key := filterTypeKeys[filterType] 286 287 entries := make([][]byte, 0, len(blockHashes)) 288 err := idx.db.View(func(dbTx database.Tx) error { 289 for _, blockHash := range blockHashes { 290 entry, err := dbFetchFilterIdxEntry(dbTx, key, blockHash) 291 if err != nil { 292 return err 293 } 294 entries = append(entries, entry) 295 } 296 return nil 297 }) 298 return entries, err 299 } 300 301 // FilterByBlockHash returns the serialized contents of a block's basic or 302 // committed filter. 303 func (idx *CfIndex) FilterByBlockHash(h *chainhash.Hash, 304 filterType wire.FilterType) ([]byte, error) { 305 return idx.entryByBlockHash(cfIndexKeys, filterType, h) 306 } 307 308 // FiltersByBlockHashes returns the serialized contents of a block's basic or 309 // committed filter for a set of blocks by hash. 310 func (idx *CfIndex) FiltersByBlockHashes(blockHashes []*chainhash.Hash, 311 filterType wire.FilterType) ([][]byte, error) { 312 return idx.entriesByBlockHashes(cfIndexKeys, filterType, blockHashes) 313 } 314 315 // FilterHeaderByBlockHash returns the serialized contents of a block's basic 316 // committed filter header. 317 func (idx *CfIndex) FilterHeaderByBlockHash(h *chainhash.Hash, 318 filterType wire.FilterType) ([]byte, error) { 319 return idx.entryByBlockHash(cfHeaderKeys, filterType, h) 320 } 321 322 // FilterHeadersByBlockHashes returns the serialized contents of a block's 323 // basic committed filter header for a set of blocks by hash. 324 func (idx *CfIndex) FilterHeadersByBlockHashes(blockHashes []*chainhash.Hash, 325 filterType wire.FilterType) ([][]byte, error) { 326 return idx.entriesByBlockHashes(cfHeaderKeys, filterType, blockHashes) 327 } 328 329 // FilterHashByBlockHash returns the serialized contents of a block's basic 330 // committed filter hash. 331 func (idx *CfIndex) FilterHashByBlockHash(h *chainhash.Hash, 332 filterType wire.FilterType) ([]byte, error) { 333 return idx.entryByBlockHash(cfHashKeys, filterType, h) 334 } 335 336 // FilterHashesByBlockHashes returns the serialized contents of a block's basic 337 // committed filter hash for a set of blocks by hash. 338 func (idx *CfIndex) FilterHashesByBlockHashes(blockHashes []*chainhash.Hash, 339 filterType wire.FilterType) ([][]byte, error) { 340 return idx.entriesByBlockHashes(cfHashKeys, filterType, blockHashes) 341 } 342 343 // NewCfIndex returns a new instance of an indexer that is used to create a 344 // mapping of the hashes of all blocks in the blockchain to their respective 345 // committed filters. 346 // 347 // It implements the Indexer interface which plugs into the IndexManager that 348 // in turn is used by the blockchain package. This allows the index to be 349 // seamlessly maintained along with the chain. 350 func NewCfIndex(db database.DB, chainParams *chaincfg.Params) *CfIndex { 351 return &CfIndex{db: db, chainParams: chainParams} 352 } 353 354 // DropCfIndex drops the CF index from the provided database if exists. 355 func DropCfIndex(db database.DB, interrupt <-chan struct{}) error { 356 return dropIndex(db, cfIndexParentBucketKey, cfIndexName, interrupt) 357 }