github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/common/ledger/blkstorage/blockindex.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package blkstorage 8 9 import ( 10 "bytes" 11 "fmt" 12 "path/filepath" 13 "unicode/utf8" 14 15 "github.com/golang/protobuf/proto" 16 "github.com/hyperledger/fabric-protos-go/common" 17 "github.com/hyperledger/fabric-protos-go/peer" 18 "github.com/osdi23p228/fabric/common/ledger/snapshot" 19 "github.com/osdi23p228/fabric/common/ledger/util" 20 "github.com/osdi23p228/fabric/common/ledger/util/leveldbhelper" 21 "github.com/osdi23p228/fabric/internal/pkg/txflags" 22 "github.com/pkg/errors" 23 ) 24 25 const ( 26 blockNumIdxKeyPrefix = 'n' 27 blockHashIdxKeyPrefix = 'h' 28 txIDIdxKeyPrefix = 't' 29 blockNumTranNumIdxKeyPrefix = 'a' 30 indexSavePointKeyStr = "indexCheckpointKey" 31 32 snapshotFileFormat = byte(1) 33 snapshotDataFileName = "txids.data" 34 snapshotMetadataFileName = "txids.metadata" 35 ) 36 37 var ( 38 indexSavePointKey = []byte(indexSavePointKeyStr) 39 errIndexSavePointKeyNotPresent = errors.New("NoBlockIndexed") 40 errNilValue = errors.New("") 41 importTxIDsBatchSize = uint64(1000) // txID is 64 bytes, so batch size roughly translates to 64KB 42 ) 43 44 type blockIdxInfo struct { 45 blockNum uint64 46 blockHash []byte 47 flp *fileLocPointer 48 txOffsets []*txindexInfo 49 metadata *common.BlockMetadata 50 } 51 52 type blockIndex struct { 53 indexItemsMap map[IndexableAttr]bool 54 db *leveldbhelper.DBHandle 55 } 56 57 func newBlockIndex(indexConfig *IndexConfig, db *leveldbhelper.DBHandle) (*blockIndex, error) { 58 indexItems := indexConfig.AttrsToIndex 59 logger.Debugf("newBlockIndex() - indexItems:[%s]", indexItems) 60 indexItemsMap := make(map[IndexableAttr]bool) 61 for _, indexItem := range indexItems { 62 indexItemsMap[indexItem] = true 63 } 64 return &blockIndex{ 65 indexItemsMap: indexItemsMap, 66 db: db, 67 }, nil 68 } 69 70 func (index *blockIndex) getLastBlockIndexed() (uint64, error) { 71 var blockNumBytes []byte 72 var err error 73 if blockNumBytes, err = index.db.Get(indexSavePointKey); err != nil { 74 return 0, err 75 } 76 if blockNumBytes == nil { 77 return 0, errIndexSavePointKeyNotPresent 78 } 79 return decodeBlockNum(blockNumBytes), nil 80 } 81 82 func (index *blockIndex) indexBlock(blockIdxInfo *blockIdxInfo) error { 83 // do not index anything 84 if len(index.indexItemsMap) == 0 { 85 logger.Debug("Not indexing block... as nothing to index") 86 return nil 87 } 88 logger.Debugf("Indexing block [%s]", blockIdxInfo) 89 flp := blockIdxInfo.flp 90 txOffsets := blockIdxInfo.txOffsets 91 blkNum := blockIdxInfo.blockNum 92 blkHash := blockIdxInfo.blockHash 93 txsfltr := txflags.ValidationFlags(blockIdxInfo.metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER]) 94 batch := index.db.NewUpdateBatch() 95 flpBytes, err := flp.marshal() 96 if err != nil { 97 return err 98 } 99 100 //Index1 101 if index.isAttributeIndexed(IndexableAttrBlockHash) { 102 batch.Put(constructBlockHashKey(blkHash), flpBytes) 103 } 104 105 //Index2 106 if index.isAttributeIndexed(IndexableAttrBlockNum) { 107 batch.Put(constructBlockNumKey(blkNum), flpBytes) 108 } 109 110 //Index3 Used to find a transaction by its transaction id 111 if index.isAttributeIndexed(IndexableAttrTxID) { 112 for i, txoffset := range txOffsets { 113 txFlp := newFileLocationPointer(flp.fileSuffixNum, flp.offset, txoffset.loc) 114 logger.Debugf("Adding txLoc [%s] for tx ID: [%s] to txid-index", txFlp, txoffset.txID) 115 txFlpBytes, marshalErr := txFlp.marshal() 116 if marshalErr != nil { 117 return marshalErr 118 } 119 120 indexVal := &TxIDIndexValue{ 121 BlkLocation: flpBytes, 122 TxLocation: txFlpBytes, 123 TxValidationCode: int32(txsfltr.Flag(i)), 124 } 125 indexValBytes, err := proto.Marshal(indexVal) 126 if err != nil { 127 return errors.Wrap(err, "unexpected error while marshaling TxIDIndexValProto message") 128 } 129 batch.Put( 130 constructTxIDKey(txoffset.txID, blkNum, uint64(i)), 131 indexValBytes, 132 ) 133 } 134 } 135 136 //Index4 - Store BlockNumTranNum will be used to query history data 137 if index.isAttributeIndexed(IndexableAttrBlockNumTranNum) { 138 for i, txoffset := range txOffsets { 139 txFlp := newFileLocationPointer(flp.fileSuffixNum, flp.offset, txoffset.loc) 140 logger.Debugf("Adding txLoc [%s] for tx number:[%d] ID: [%s] to blockNumTranNum index", txFlp, i, txoffset.txID) 141 txFlpBytes, marshalErr := txFlp.marshal() 142 if marshalErr != nil { 143 return marshalErr 144 } 145 batch.Put(constructBlockNumTranNumKey(blkNum, uint64(i)), txFlpBytes) 146 } 147 } 148 149 batch.Put(indexSavePointKey, encodeBlockNum(blockIdxInfo.blockNum)) 150 // Setting snyc to true as a precaution, false may be an ok optimization after further testing. 151 if err := index.db.WriteBatch(batch, true); err != nil { 152 return err 153 } 154 return nil 155 } 156 157 func (index *blockIndex) isAttributeIndexed(attribute IndexableAttr) bool { 158 _, ok := index.indexItemsMap[attribute] 159 return ok 160 } 161 162 func (index *blockIndex) getBlockLocByHash(blockHash []byte) (*fileLocPointer, error) { 163 if !index.isAttributeIndexed(IndexableAttrBlockHash) { 164 return nil, ErrAttrNotIndexed 165 } 166 b, err := index.db.Get(constructBlockHashKey(blockHash)) 167 if err != nil { 168 return nil, err 169 } 170 if b == nil { 171 return nil, ErrNotFoundInIndex 172 } 173 blkLoc := &fileLocPointer{} 174 blkLoc.unmarshal(b) 175 return blkLoc, nil 176 } 177 178 func (index *blockIndex) getBlockLocByBlockNum(blockNum uint64) (*fileLocPointer, error) { 179 if !index.isAttributeIndexed(IndexableAttrBlockNum) { 180 return nil, ErrAttrNotIndexed 181 } 182 b, err := index.db.Get(constructBlockNumKey(blockNum)) 183 if err != nil { 184 return nil, err 185 } 186 if b == nil { 187 return nil, ErrNotFoundInIndex 188 } 189 blkLoc := &fileLocPointer{} 190 blkLoc.unmarshal(b) 191 return blkLoc, nil 192 } 193 194 func (index *blockIndex) getTxLoc(txID string) (*fileLocPointer, error) { 195 v, err := index.getTxIDVal(txID) 196 if err != nil { 197 return nil, err 198 } 199 txFLP := &fileLocPointer{} 200 if err = txFLP.unmarshal(v.TxLocation); err != nil { 201 return nil, err 202 } 203 return txFLP, nil 204 } 205 206 func (index *blockIndex) getBlockLocByTxID(txID string) (*fileLocPointer, error) { 207 v, err := index.getTxIDVal(txID) 208 if err != nil { 209 return nil, err 210 } 211 blkFLP := &fileLocPointer{} 212 if err = blkFLP.unmarshal(v.BlkLocation); err != nil { 213 return nil, err 214 } 215 return blkFLP, nil 216 } 217 218 func (index *blockIndex) getTxValidationCodeByTxID(txID string) (peer.TxValidationCode, error) { 219 v, err := index.getTxIDVal(txID) 220 if err != nil { 221 return peer.TxValidationCode(-1), err 222 } 223 return peer.TxValidationCode(v.TxValidationCode), nil 224 } 225 226 func (index *blockIndex) getTxIDVal(txID string) (*TxIDIndexValue, error) { 227 if !index.isAttributeIndexed(IndexableAttrTxID) { 228 return nil, ErrAttrNotIndexed 229 } 230 rangeScan := constructTxIDRangeScan(txID) 231 itr, err := index.db.GetIterator(rangeScan.startKey, rangeScan.stopKey) 232 if err != nil { 233 return nil, errors.WithMessagef(err, "error while trying to retrieve transaction info by TXID [%s]", txID) 234 } 235 defer itr.Release() 236 237 present := itr.Next() 238 if err := itr.Error(); err != nil { 239 return nil, errors.Wrapf(err, "error while trying to retrieve transaction info by TXID [%s]", txID) 240 } 241 if !present { 242 return nil, ErrNotFoundInIndex 243 } 244 valBytes := itr.Value() 245 if len(valBytes) == 0 { 246 return nil, errNilValue 247 } 248 val := &TxIDIndexValue{} 249 if err := proto.Unmarshal(valBytes, val); err != nil { 250 return nil, errors.Wrapf(err, "unexpected error while unmarshaling bytes [%#v] into TxIDIndexValProto", valBytes) 251 } 252 return val, nil 253 } 254 255 func (index *blockIndex) getTXLocByBlockNumTranNum(blockNum uint64, tranNum uint64) (*fileLocPointer, error) { 256 if !index.isAttributeIndexed(IndexableAttrBlockNumTranNum) { 257 return nil, ErrAttrNotIndexed 258 } 259 b, err := index.db.Get(constructBlockNumTranNumKey(blockNum, tranNum)) 260 if err != nil { 261 return nil, err 262 } 263 if b == nil { 264 return nil, ErrNotFoundInIndex 265 } 266 txFLP := &fileLocPointer{} 267 txFLP.unmarshal(b) 268 return txFLP, nil 269 } 270 271 func (index *blockIndex) exportUniqueTxIDs(dir string, newHashFunc snapshot.NewHashFunc) (map[string][]byte, error) { 272 if !index.isAttributeIndexed(IndexableAttrTxID) { 273 return nil, ErrAttrNotIndexed 274 } 275 276 dbItr, err := index.db.GetIterator([]byte{txIDIdxKeyPrefix}, []byte{txIDIdxKeyPrefix + 1}) 277 if err != nil { 278 return nil, err 279 } 280 defer dbItr.Release() 281 282 var previousTxID string 283 var numTxIDs uint64 = 0 284 var dataFile *snapshot.FileWriter 285 for dbItr.Next() { 286 if err := dbItr.Error(); err != nil { 287 return nil, errors.Wrap(err, "internal leveldb error while iterating for txids") 288 } 289 txID, err := retrieveTxID(dbItr.Key()) 290 if err != nil { 291 return nil, err 292 } 293 // duplicate TxID may be present in the index 294 if previousTxID == txID { 295 continue 296 } 297 previousTxID = txID 298 if numTxIDs == 0 { // first iteration, create the data file 299 dataFile, err = snapshot.CreateFile(filepath.Join(dir, snapshotDataFileName), snapshotFileFormat, newHashFunc) 300 if err != nil { 301 return nil, err 302 } 303 defer dataFile.Close() 304 } 305 if err := dataFile.EncodeString(txID); err != nil { 306 return nil, err 307 } 308 numTxIDs++ 309 } 310 311 if dataFile == nil { 312 return nil, nil 313 } 314 315 dataHash, err := dataFile.Done() 316 if err != nil { 317 return nil, err 318 } 319 320 // create the metadata file 321 metadataFile, err := snapshot.CreateFile(filepath.Join(dir, snapshotMetadataFileName), snapshotFileFormat, newHashFunc) 322 if err != nil { 323 return nil, err 324 } 325 defer metadataFile.Close() 326 327 if err = metadataFile.EncodeUVarint(numTxIDs); err != nil { 328 return nil, err 329 } 330 metadataHash, err := metadataFile.Done() 331 if err != nil { 332 return nil, err 333 } 334 335 return map[string][]byte{ 336 snapshotDataFileName: dataHash, 337 snapshotMetadataFileName: metadataHash, 338 }, nil 339 } 340 341 func importTxIDsFromSnapshot( 342 snapshotDir string, 343 lastBlockNumInSnapshot uint64, 344 db *leveldbhelper.DBHandle) error { 345 346 batch := db.NewUpdateBatch() 347 txIDsMetadata, err := snapshot.OpenFile(filepath.Join(snapshotDir, snapshotMetadataFileName), snapshotFileFormat) 348 if err != nil { 349 return err 350 } 351 numTxIDs, err := txIDsMetadata.DecodeUVarInt() 352 if err != nil { 353 return err 354 } 355 txIDsData, err := snapshot.OpenFile(filepath.Join(snapshotDir, snapshotDataFileName), snapshotFileFormat) 356 if err != nil { 357 return err 358 } 359 for i := uint64(0); i < numTxIDs; i++ { 360 txID, err := txIDsData.DecodeString() 361 if err != nil { 362 return err 363 } 364 batch.Put( 365 constructTxIDKey(txID, lastBlockNumInSnapshot, uint64(i)), 366 []byte{}, 367 ) 368 if (i+1)%importTxIDsBatchSize == 0 { 369 if err := db.WriteBatch(batch, true); err != nil { 370 return err 371 } 372 batch = db.NewUpdateBatch() 373 } 374 } 375 batch.Put(indexSavePointKey, encodeBlockNum(lastBlockNumInSnapshot)) 376 if err := db.WriteBatch(batch, true); err != nil { 377 return err 378 } 379 return nil 380 } 381 382 func constructBlockNumKey(blockNum uint64) []byte { 383 blkNumBytes := util.EncodeOrderPreservingVarUint64(blockNum) 384 return append([]byte{blockNumIdxKeyPrefix}, blkNumBytes...) 385 } 386 387 func constructBlockHashKey(blockHash []byte) []byte { 388 return append([]byte{blockHashIdxKeyPrefix}, blockHash...) 389 } 390 391 func constructTxIDKey(txID string, blkNum, txNum uint64) []byte { 392 k := append( 393 []byte{txIDIdxKeyPrefix}, 394 util.EncodeOrderPreservingVarUint64(uint64(len(txID)))..., 395 ) 396 k = append(k, txID...) 397 k = append(k, util.EncodeOrderPreservingVarUint64(blkNum)...) 398 return append(k, util.EncodeOrderPreservingVarUint64(txNum)...) 399 } 400 401 // retrieveTxID takes input an encoded txid key of the format `prefix:len(TxID):TxID:BlkNum:TxNum` 402 // and returns the TxID from this 403 func retrieveTxID(encodedTxIDKey []byte) (string, error) { 404 if len(encodedTxIDKey) == 0 { 405 return "", errors.New("invalid txIDKey - zero-length slice") 406 } 407 if encodedTxIDKey[0] != txIDIdxKeyPrefix { 408 return "", errors.Errorf("invalid txIDKey {%x} - unexpected prefix", encodedTxIDKey) 409 } 410 remainingBytes := encodedTxIDKey[utf8.RuneLen(txIDIdxKeyPrefix):] 411 412 txIDLen, n, err := util.DecodeOrderPreservingVarUint64(remainingBytes) 413 if err != nil { 414 return "", errors.WithMessagef(err, "invalid txIDKey {%x}", encodedTxIDKey) 415 } 416 remainingBytes = remainingBytes[n:] 417 if len(remainingBytes) <= int(txIDLen) { 418 return "", errors.Errorf("invalid txIDKey {%x}, fewer bytes present", encodedTxIDKey) 419 } 420 return string(remainingBytes[:int(txIDLen)]), nil 421 } 422 423 type rangeScan struct { 424 startKey []byte 425 stopKey []byte 426 } 427 428 func constructTxIDRangeScan(txID string) *rangeScan { 429 sk := append( 430 []byte{txIDIdxKeyPrefix}, 431 util.EncodeOrderPreservingVarUint64(uint64(len(txID)))..., 432 ) 433 sk = append(sk, txID...) 434 return &rangeScan{ 435 startKey: sk, 436 stopKey: append(sk, 0xff), 437 } 438 } 439 440 func constructBlockNumTranNumKey(blockNum uint64, txNum uint64) []byte { 441 blkNumBytes := util.EncodeOrderPreservingVarUint64(blockNum) 442 tranNumBytes := util.EncodeOrderPreservingVarUint64(txNum) 443 key := append(blkNumBytes, tranNumBytes...) 444 return append([]byte{blockNumTranNumIdxKeyPrefix}, key...) 445 } 446 447 func encodeBlockNum(blockNum uint64) []byte { 448 return proto.EncodeVarint(blockNum) 449 } 450 451 func decodeBlockNum(blockNumBytes []byte) uint64 { 452 blockNum, _ := proto.DecodeVarint(blockNumBytes) 453 return blockNum 454 } 455 456 type locPointer struct { 457 offset int 458 bytesLength int 459 } 460 461 func (lp *locPointer) String() string { 462 return fmt.Sprintf("offset=%d, bytesLength=%d", 463 lp.offset, lp.bytesLength) 464 } 465 466 // fileLocPointer 467 type fileLocPointer struct { 468 fileSuffixNum int 469 locPointer 470 } 471 472 func newFileLocationPointer(fileSuffixNum int, beginningOffset int, relativeLP *locPointer) *fileLocPointer { 473 flp := &fileLocPointer{fileSuffixNum: fileSuffixNum} 474 flp.offset = beginningOffset + relativeLP.offset 475 flp.bytesLength = relativeLP.bytesLength 476 return flp 477 } 478 479 func (flp *fileLocPointer) marshal() ([]byte, error) { 480 buffer := proto.NewBuffer([]byte{}) 481 e := buffer.EncodeVarint(uint64(flp.fileSuffixNum)) 482 if e != nil { 483 return nil, errors.Wrapf(e, "unexpected error while marshaling fileLocPointer [%s]", flp) 484 } 485 e = buffer.EncodeVarint(uint64(flp.offset)) 486 if e != nil { 487 return nil, errors.Wrapf(e, "unexpected error while marshaling fileLocPointer [%s]", flp) 488 } 489 e = buffer.EncodeVarint(uint64(flp.bytesLength)) 490 if e != nil { 491 return nil, errors.Wrapf(e, "unexpected error while marshaling fileLocPointer [%s]", flp) 492 } 493 return buffer.Bytes(), nil 494 } 495 496 func (flp *fileLocPointer) unmarshal(b []byte) error { 497 buffer := proto.NewBuffer(b) 498 i, e := buffer.DecodeVarint() 499 if e != nil { 500 return errors.Wrapf(e, "unexpected error while unmarshaling bytes [%#v] into fileLocPointer", b) 501 } 502 flp.fileSuffixNum = int(i) 503 504 i, e = buffer.DecodeVarint() 505 if e != nil { 506 return errors.Wrapf(e, "unexpected error while unmarshaling bytes [%#v] into fileLocPointer", b) 507 } 508 flp.offset = int(i) 509 i, e = buffer.DecodeVarint() 510 if e != nil { 511 return errors.Wrapf(e, "unexpected error while unmarshaling bytes [%#v] into fileLocPointer", b) 512 } 513 flp.bytesLength = int(i) 514 return nil 515 } 516 517 func (flp *fileLocPointer) String() string { 518 return fmt.Sprintf("fileSuffixNum=%d, %s", flp.fileSuffixNum, flp.locPointer.String()) 519 } 520 521 func (blockIdxInfo *blockIdxInfo) String() string { 522 var buffer bytes.Buffer 523 for _, txOffset := range blockIdxInfo.txOffsets { 524 buffer.WriteString("txId=") 525 buffer.WriteString(txOffset.txID) 526 buffer.WriteString(" locPointer=") 527 buffer.WriteString(txOffset.loc.String()) 528 buffer.WriteString("\n") 529 } 530 txOffsetsString := buffer.String() 531 532 return fmt.Sprintf("blockNum=%d, blockHash=%#v txOffsets=\n%s", blockIdxInfo.blockNum, blockIdxInfo.blockHash, txOffsetsString) 533 }