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  }