github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/ledger/pvtdatastorage/snapshot_data_importer.go (about) 1 /* 2 Copyright hechain. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package pvtdatastorage 8 9 import ( 10 "bytes" 11 "io/ioutil" 12 "math" 13 "os" 14 15 "github.com/hechain20/hechain/common/ledger/util" 16 "github.com/hechain20/hechain/common/ledger/util/leveldbhelper" 17 "github.com/hechain20/hechain/core/chaincode/implicitcollection" 18 "github.com/hechain20/hechain/core/ledger" 19 "github.com/hechain20/hechain/core/ledger/confighistory" 20 "github.com/hechain20/hechain/core/ledger/internal/version" 21 "github.com/hechain20/hechain/core/ledger/pvtdatapolicy" 22 "github.com/pkg/errors" 23 "github.com/willf/bitset" 24 ) 25 26 var ( 27 // batch used for sorting the data 28 maxSnapshotRowSortBatchSize = 1 * 1024 * 1024 29 // batch used for writing the final data, 30 // (64 bytes for a single kvhash + additional KVs and encoding overheads) * 10000 will roughly lead to batch size between 1MB and 2MB 31 maxBatchLenForSnapshotImport = 10000 32 ) 33 34 type SnapshotDataImporter struct { 35 namespacesVisited map[string]struct{} 36 eligibilityAndBTLCache *eligibilityAndBTLCache 37 38 rowsSorter *snapshotRowsSorter 39 db *leveldbhelper.DBHandle 40 } 41 42 func newSnapshotDataImporter( 43 ledgerID string, 44 dbHandle *leveldbhelper.DBHandle, 45 membershipProvider ledger.MembershipInfoProvider, 46 configHistoryRetriever *confighistory.Retriever, 47 tempDirRoot string, 48 ) (*SnapshotDataImporter, error) { 49 rowsSorter, err := newSnapshotRowsSorter(tempDirRoot) 50 if err != nil { 51 return nil, err 52 } 53 return &SnapshotDataImporter{ 54 namespacesVisited: map[string]struct{}{}, 55 eligibilityAndBTLCache: newEligibilityAndBTLCache(ledgerID, membershipProvider, configHistoryRetriever), 56 rowsSorter: rowsSorter, 57 db: dbHandle, 58 }, nil 59 } 60 61 func (i *SnapshotDataImporter) ConsumeSnapshotData( 62 namespace, collection string, 63 keyHash, valueHash []byte, 64 version *version.Height, 65 ) error { 66 return i.rowsSorter.add( 67 &snapshotRow{ 68 version: version, 69 ns: namespace, 70 coll: collection, 71 keyHash: keyHash, 72 valueHash: valueHash, 73 }, 74 ) 75 } 76 77 func (i *SnapshotDataImporter) Done() error { 78 defer i.rowsSorter.cleanup() 79 if err := i.rowsSorter.addingDone(); err != nil { 80 return err 81 } 82 iter, err := i.rowsSorter.iterator() 83 if err != nil { 84 return err 85 } 86 defer iter.close() 87 88 dbUpdates := newDBUpdates() 89 currentBlockNum := uint64(0) 90 91 for { 92 row, err := iter.next() 93 if err != nil { 94 return err 95 } 96 if row == nil { 97 // iterator exhausted. Commit all pending writes 98 return dbUpdates.commitToDB(i.db) 99 } 100 101 namespace := row.ns 102 collection := row.coll 103 blkNum := row.version.BlockNum 104 txNum := row.version.TxNum 105 keyHash := row.keyHash 106 valueHash := row.valueHash 107 108 if blkNum != currentBlockNum { 109 currentBlockNum = blkNum 110 // commit is to be invoked only on block boundaries because we write data for one block only once 111 if dbUpdates.numKVHashesEntries() >= maxBatchLenForSnapshotImport { 112 if err := dbUpdates.commitToDB(i.db); err != nil { 113 return err 114 } 115 dbUpdates = newDBUpdates() 116 } 117 } 118 119 if _, ok := i.namespacesVisited[namespace]; !ok { 120 if err := i.eligibilityAndBTLCache.loadDataFor(namespace); err != nil { 121 return err 122 } 123 i.namespacesVisited[namespace] = struct{}{} 124 } 125 126 isEligible, err := i.eligibilityAndBTLCache.isEligibile(namespace, collection, blkNum) 127 if err != nil { 128 return err 129 } 130 131 if isEligible { 132 dbUpdates.upsertElgMissingDataEntry(namespace, collection, blkNum, txNum) 133 } else { 134 dbUpdates.upsertInelgMissingDataEntry(namespace, collection, blkNum, txNum) 135 } 136 137 dbUpdates.upsertBootKVHashes(namespace, collection, blkNum, txNum, keyHash, valueHash) 138 139 if hasExpiry, expiringBlk := i.eligibilityAndBTLCache.hasExpiry(namespace, collection, blkNum); hasExpiry { 140 dbUpdates.upsertExpiryEntry(expiringBlk, blkNum, namespace, collection, txNum) 141 } 142 } 143 } 144 145 type nsColl struct { 146 ns, coll string 147 } 148 149 type eligibility struct { 150 configBlockNum uint64 151 isEligible bool 152 } 153 154 type eligibilityAndBTLCache struct { 155 ledgerID string 156 membershipProvider ledger.MembershipInfoProvider 157 configHistoryRetriever *confighistory.Retriever 158 159 eligibilityHistory map[nsColl][]*eligibility 160 btl map[nsColl]uint64 161 } 162 163 func newEligibilityAndBTLCache( 164 ledgerID string, 165 membershipProvider ledger.MembershipInfoProvider, 166 configHistoryRetriever *confighistory.Retriever) *eligibilityAndBTLCache { 167 return &eligibilityAndBTLCache{ 168 ledgerID: ledgerID, 169 membershipProvider: membershipProvider, 170 configHistoryRetriever: configHistoryRetriever, 171 eligibilityHistory: map[nsColl][]*eligibility{}, 172 btl: map[nsColl]uint64{}, 173 } 174 } 175 176 func (i *eligibilityAndBTLCache) loadDataFor(namespace string) error { 177 var queryBlkNum uint64 = math.MaxUint64 178 for { 179 configInfo, err := i.configHistoryRetriever.MostRecentCollectionConfigBelow(queryBlkNum, namespace) 180 if err != nil || configInfo == nil { 181 return err 182 } 183 184 committingBlkNum := configInfo.CommittingBlockNum 185 collections := configInfo.CollectionConfig.GetConfig() 186 187 for _, collection := range collections { 188 staticCollection := collection.GetStaticCollectionConfig() 189 eligible, err := i.membershipProvider.AmMemberOf(i.ledgerID, staticCollection.MemberOrgsPolicy) 190 if err != nil { 191 return err 192 } 193 key := nsColl{ 194 ns: namespace, 195 coll: staticCollection.Name, 196 } 197 i.eligibilityHistory[key] = append(i.eligibilityHistory[key], 198 &eligibility{ 199 configBlockNum: committingBlkNum, 200 isEligible: eligible, 201 }, 202 ) 203 if staticCollection.BlockToLive > 0 { 204 i.btl[key] = staticCollection.BlockToLive 205 } 206 } 207 queryBlkNum = committingBlkNum 208 } 209 } 210 211 func (i *eligibilityAndBTLCache) isEligibile(namespace, collection string, dataBlockNum uint64) (bool, error) { 212 if implicitcollection.IsImplicitCollection(collection) { 213 return collection == i.membershipProvider.MyImplicitCollectionName(), nil 214 } 215 216 key := nsColl{ 217 ns: namespace, 218 coll: collection, 219 } 220 history := i.eligibilityHistory[key] 221 222 if len(history) == 0 { 223 return false, 224 errors.Errorf( 225 "unexpected error - no collection config history for <namespace=%s, collection=%s>", 226 namespace, collection, 227 ) 228 } 229 230 if dataBlockNum <= history[len(history)-1].configBlockNum { 231 return false, 232 errors.Errorf( 233 "unexpected error - no collection config found below block number [%d] for <namespace=%s, collection=%s>", 234 dataBlockNum, namespace, collection, 235 ) 236 } 237 238 for _, h := range history { 239 if h.configBlockNum >= dataBlockNum { 240 if h.isEligible { 241 return true, nil 242 } 243 continue 244 } 245 return h.isEligible, nil 246 } 247 248 return false, errors.Errorf("unexpected code path - potential bug") 249 } 250 251 func (i *eligibilityAndBTLCache) hasExpiry(namespace, collection string, committingBlk uint64) (bool, uint64) { 252 var expiringBlk uint64 = math.MaxUint64 253 btl, ok := i.btl[nsColl{ 254 ns: namespace, 255 coll: collection, 256 }] 257 if ok { 258 expiringBlk = pvtdatapolicy.ComputeExpiringBlock(namespace, collection, committingBlk, btl) 259 } 260 return expiringBlk < math.MaxUint64, expiringBlk 261 } 262 263 type dbUpdates struct { 264 elgMissingDataEntries map[missingDataKey]*bitset.BitSet 265 inelgMissingDataEntries map[missingDataKey]*bitset.BitSet 266 bootKVHashes map[bootKVHashesKey]*BootKVHashes 267 expiryEntries map[expiryKey]*ExpiryData 268 } 269 270 func newDBUpdates() *dbUpdates { 271 return &dbUpdates{ 272 elgMissingDataEntries: map[missingDataKey]*bitset.BitSet{}, 273 inelgMissingDataEntries: map[missingDataKey]*bitset.BitSet{}, 274 bootKVHashes: map[bootKVHashesKey]*BootKVHashes{}, 275 expiryEntries: map[expiryKey]*ExpiryData{}, 276 } 277 } 278 279 func (u *dbUpdates) upsertElgMissingDataEntry(ns, coll string, blkNum, txNum uint64) { 280 key := missingDataKey{ 281 nsCollBlk{ 282 ns: ns, 283 coll: coll, 284 blkNum: blkNum, 285 }, 286 } 287 missingData, ok := u.elgMissingDataEntries[key] 288 if !ok { 289 missingData = &bitset.BitSet{} 290 u.elgMissingDataEntries[key] = missingData 291 } 292 missingData.Set(uint(txNum)) 293 } 294 295 func (u *dbUpdates) upsertInelgMissingDataEntry(ns, coll string, blkNum, txNum uint64) { 296 key := missingDataKey{ 297 nsCollBlk{ 298 ns: ns, 299 coll: coll, 300 blkNum: blkNum, 301 }, 302 } 303 missingData, ok := u.inelgMissingDataEntries[key] 304 if !ok { 305 missingData = &bitset.BitSet{} 306 u.inelgMissingDataEntries[key] = missingData 307 } 308 missingData.Set(uint(txNum)) 309 } 310 311 func (u *dbUpdates) upsertBootKVHashes(ns, coll string, blkNum, txNum uint64, keyHash, valueHash []byte) { 312 key := bootKVHashesKey{ 313 blkNum: blkNum, 314 txNum: txNum, 315 ns: ns, 316 coll: coll, 317 } 318 bootKVHashes, ok := u.bootKVHashes[key] 319 if !ok { 320 bootKVHashes = &BootKVHashes{} 321 u.bootKVHashes[key] = bootKVHashes 322 } 323 bootKVHashes.List = append(bootKVHashes.List, 324 &BootKVHash{ 325 KeyHash: keyHash, 326 ValueHash: valueHash, 327 }, 328 ) 329 } 330 331 func (u *dbUpdates) upsertExpiryEntry(expiringBlk, committingBlk uint64, namespace, collection string, txNum uint64) { 332 key := expiryKey{ 333 expiringBlk: expiringBlk, 334 committingBlk: committingBlk, 335 } 336 expiryData, ok := u.expiryEntries[key] 337 if !ok { 338 expiryData = newExpiryData() 339 u.expiryEntries[key] = expiryData 340 } 341 expiryData.addMissingData(namespace, collection) 342 expiryData.addBootKVHash(namespace, collection, txNum) 343 } 344 345 func (u *dbUpdates) numKVHashesEntries() int { 346 return len(u.bootKVHashes) 347 } 348 349 func (u *dbUpdates) commitToDB(db *leveldbhelper.DBHandle) error { 350 batch := db.NewUpdateBatch() 351 for k, v := range u.elgMissingDataEntries { 352 encKey := encodeElgPrioMissingDataKey(&k) 353 encVal, err := encodeMissingDataValue(v) 354 if err != nil { 355 return err 356 } 357 batch.Put(encKey, encVal) 358 } 359 360 for k, v := range u.inelgMissingDataEntries { 361 encKey := encodeInelgMissingDataKey(&k) 362 encVal, err := encodeMissingDataValue(v) 363 if err != nil { 364 return err 365 } 366 batch.Put(encKey, encVal) 367 } 368 369 for k, v := range u.bootKVHashes { 370 encKey := encodeBootKVHashesKey(&k) 371 encVal, err := encodeBootKVHashesVal(v) 372 if err != nil { 373 return err 374 } 375 batch.Put(encKey, encVal) 376 } 377 378 for k, v := range u.expiryEntries { 379 encKey := encodeExpiryKey(&k) 380 encVal, err := encodeExpiryValue(v) 381 if err != nil { 382 return err 383 } 384 batch.Put(encKey, encVal) 385 } 386 return db.WriteBatch(batch, true) 387 } 388 389 type snapshotRowsSorter struct { 390 tempDir string 391 dbProvider *leveldbhelper.Provider 392 db *leveldbhelper.DBHandle 393 batch *leveldbhelper.UpdateBatch 394 batchSize int 395 } 396 397 func newSnapshotRowsSorter(tempDirRoot string) (*snapshotRowsSorter, error) { 398 tempDir, err := ioutil.TempDir(tempDirRoot, "pvtdatastore-snapshotdatainporter-") 399 if err != nil { 400 return nil, errors.Wrap(err, "error while creating temp dir for sorting rows") 401 } 402 dbProvider, err := leveldbhelper.NewProvider(&leveldbhelper.Conf{ 403 DBPath: tempDir, 404 }) 405 if err != nil { 406 return nil, err 407 } 408 db := dbProvider.GetDBHandle("") 409 batch := db.NewUpdateBatch() 410 return &snapshotRowsSorter{ 411 tempDir: tempDir, 412 dbProvider: dbProvider, 413 db: db, 414 batch: batch, 415 }, nil 416 } 417 418 func (s *snapshotRowsSorter) add(k *snapshotRow) error { 419 encKey := encodeSnapshotRowForSorting(k) 420 s.batch.Put(encKey, []byte{}) 421 s.batchSize += len(encKey) 422 423 if s.batchSize >= maxSnapshotRowSortBatchSize { 424 if err := s.db.WriteBatch(s.batch, false); err != nil { 425 return err 426 } 427 s.batch.Reset() 428 s.batchSize = 0 429 } 430 return nil 431 } 432 433 func (s *snapshotRowsSorter) addingDone() error { 434 return s.db.WriteBatch(s.batch, false) 435 } 436 437 func (s *snapshotRowsSorter) iterator() (*sortedSnapshotRowsIterator, error) { 438 dbIter, err := s.db.GetIterator(nil, nil) 439 if err != nil { 440 return nil, err 441 } 442 return &sortedSnapshotRowsIterator{ 443 dbIter: dbIter, 444 }, nil 445 } 446 447 func (s *snapshotRowsSorter) cleanup() { 448 s.dbProvider.Close() 449 if err := os.RemoveAll(s.tempDir); err != nil { 450 logger.Errorw("Error while deleting temp dir [%s]", s.tempDir) 451 } 452 } 453 454 type sortedSnapshotRowsIterator struct { 455 dbIter *leveldbhelper.Iterator 456 } 457 458 func (i *sortedSnapshotRowsIterator) next() (*snapshotRow, error) { 459 hasMore := i.dbIter.Next() 460 if err := i.dbIter.Error(); err != nil { 461 return nil, err 462 } 463 if !hasMore { 464 return nil, nil 465 } 466 encKey := i.dbIter.Key() 467 encKeyCopy := make([]byte, len(encKey)) 468 copy(encKeyCopy, encKey) 469 row, err := decodeSnapshotRowFromSortEncoding(encKeyCopy) 470 if err != nil { 471 return nil, err 472 } 473 return row, nil 474 } 475 476 func (i *sortedSnapshotRowsIterator) close() { 477 i.dbIter.Release() 478 } 479 480 type snapshotRow struct { 481 version *version.Height 482 ns, coll string 483 keyHash, valueHash []byte 484 } 485 486 func encodeSnapshotRowForSorting(k *snapshotRow) []byte { 487 encKey := k.version.ToBytes() 488 encKey = append(encKey, []byte(k.ns)...) 489 encKey = append(encKey, nilByte) 490 encKey = append(encKey, []byte(k.coll)...) 491 encKey = append(encKey, nilByte) 492 encKey = append(encKey, util.EncodeOrderPreservingVarUint64(uint64(len(k.keyHash)))...) 493 encKey = append(encKey, k.keyHash...) 494 encKey = append(encKey, k.valueHash...) 495 return encKey 496 } 497 498 func decodeSnapshotRowFromSortEncoding(encKey []byte) (*snapshotRow, error) { 499 version, bytesConsumed, err := version.NewHeightFromBytes(encKey) 500 if err != nil { 501 return nil, err 502 } 503 504 remainingBytes := encKey[bytesConsumed:] 505 nsCollKVHash := bytes.SplitN(remainingBytes, []byte{nilByte}, 3) 506 ns := nsCollKVHash[0] 507 coll := nsCollKVHash[1] 508 kvHashes := nsCollKVHash[2] 509 510 keyHashLen, bytesConsumed, err := util.DecodeOrderPreservingVarUint64(kvHashes) 511 if err != nil { 512 return nil, err 513 } 514 keyHash := kvHashes[bytesConsumed : bytesConsumed+int(keyHashLen)] 515 valueHash := kvHashes[bytesConsumed+int(keyHashLen):] 516 return &snapshotRow{ 517 version: version, 518 ns: string(ns), 519 coll: string(coll), 520 keyHash: keyHash, 521 valueHash: valueHash, 522 }, nil 523 }