github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/gossip/privdata/reconcile.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package privdata
     8  
     9  import (
    10  	"encoding/hex"
    11  	"fmt"
    12  	"math"
    13  	"sync"
    14  	"time"
    15  
    16  	commonutil "github.com/hechain20/hechain/common/util"
    17  	"github.com/hechain20/hechain/core/committer"
    18  	"github.com/hechain20/hechain/core/ledger"
    19  	"github.com/hechain20/hechain/gossip/metrics"
    20  	privdatacommon "github.com/hechain20/hechain/gossip/privdata/common"
    21  	"github.com/hechain20/hechain/gossip/util"
    22  	protosgossip "github.com/hyperledger/fabric-protos-go/gossip"
    23  	"github.com/hyperledger/fabric-protos-go/peer"
    24  	"github.com/pkg/errors"
    25  )
    26  
    27  //go:generate mockery -dir . -name ReconciliationFetcher -case underscore -output mocks/
    28  
    29  //go:generate mockery -dir . -name MissingPvtDataTracker -case underscore -output mocks/
    30  
    31  // MissingPvtDataTracker is the local interface used to generate mocks for foreign interface.
    32  type MissingPvtDataTracker interface {
    33  	ledger.MissingPvtDataTracker
    34  }
    35  
    36  //go:generate mockery -dir . -name ConfigHistoryRetriever -case underscore -output mocks/
    37  
    38  // ConfigHistoryRetriever is the local interface used to generate mocks for foreign interface.
    39  type ConfigHistoryRetriever interface {
    40  	ledger.ConfigHistoryRetriever
    41  }
    42  
    43  // ReconciliationFetcher interface which defines API to fetch
    44  // private data elements that have to be reconciled.
    45  type ReconciliationFetcher interface {
    46  	FetchReconciledItems(dig2collectionConfig privdatacommon.Dig2CollectionConfig) (*privdatacommon.FetchedPvtDataContainer, error)
    47  }
    48  
    49  // PvtDataReconciler completes missing parts of private data that weren't available during commit time.
    50  // this is done by getting from the ledger a list of missing private data and pulling it from the other peers.
    51  type PvtDataReconciler interface {
    52  	// Start function start the reconciler based on a scheduler, as was configured in reconciler creation
    53  	Start()
    54  	// Stop function stops reconciler
    55  	Stop()
    56  }
    57  
    58  type Reconciler struct {
    59  	channel                string
    60  	logger                 util.Logger
    61  	metrics                *metrics.PrivdataMetrics
    62  	ReconcileSleepInterval time.Duration
    63  	ReconcileBatchSize     int
    64  	stopChan               chan struct{}
    65  	startOnce              sync.Once
    66  	stopOnce               sync.Once
    67  	ReconciliationFetcher
    68  	committer.Committer
    69  }
    70  
    71  // NoOpReconciler non functional reconciler to be used
    72  // in case reconciliation has been disabled
    73  type NoOpReconciler struct{}
    74  
    75  func (*NoOpReconciler) Start() {
    76  	// do nothing
    77  	logger.Debug("Private data reconciliation has been disabled")
    78  }
    79  
    80  func (*NoOpReconciler) Stop() {
    81  	// do nothing
    82  }
    83  
    84  // NewReconciler creates a new instance of reconciler
    85  func NewReconciler(channel string, metrics *metrics.PrivdataMetrics, c committer.Committer,
    86  	fetcher ReconciliationFetcher, config *PrivdataConfig) *Reconciler {
    87  	reconcilerLogger := logger.With("channel", channel)
    88  	reconcilerLogger.Debug("Private data reconciliation is enabled")
    89  	return &Reconciler{
    90  		channel:                channel,
    91  		logger:                 reconcilerLogger,
    92  		metrics:                metrics,
    93  		ReconcileSleepInterval: config.ReconcileSleepInterval,
    94  		ReconcileBatchSize:     config.ReconcileBatchSize,
    95  		Committer:              c,
    96  		ReconciliationFetcher:  fetcher,
    97  		stopChan:               make(chan struct{}),
    98  	}
    99  }
   100  
   101  func (r *Reconciler) Stop() {
   102  	r.stopOnce.Do(func() {
   103  		close(r.stopChan)
   104  	})
   105  }
   106  
   107  func (r *Reconciler) Start() {
   108  	r.startOnce.Do(func() {
   109  		go r.run()
   110  	})
   111  }
   112  
   113  func (r *Reconciler) run() {
   114  	for {
   115  		select {
   116  		case <-r.stopChan:
   117  			return
   118  		case <-time.After(r.ReconcileSleepInterval):
   119  			r.logger.Debug("Start reconcile missing private info")
   120  			if err := r.reconcile(); err != nil {
   121  				r.logger.Error("Failed to reconcile missing private info, error: ", err.Error())
   122  			}
   123  		}
   124  	}
   125  }
   126  
   127  // returns the number of items that were reconciled , minBlock, maxBlock (blocks range) and an error
   128  func (r *Reconciler) reconcile() error {
   129  	missingPvtDataTracker, err := r.GetMissingPvtDataTracker()
   130  	if err != nil {
   131  		r.logger.Error("reconciliation error when trying to get missingPvtDataTracker:", err)
   132  		return err
   133  	}
   134  	if missingPvtDataTracker == nil {
   135  		r.logger.Error("got nil as MissingPvtDataTracker, exiting...")
   136  		return errors.New("got nil as MissingPvtDataTracker, exiting...")
   137  	}
   138  	totalReconciled, minBlock, maxBlock := 0, uint64(math.MaxUint64), uint64(0)
   139  
   140  	defer r.reportReconciliationDuration(time.Now())
   141  
   142  	for {
   143  		missingPvtDataInfo, err := missingPvtDataTracker.GetMissingPvtDataInfoForMostRecentBlocks(r.ReconcileBatchSize)
   144  		if err != nil {
   145  			r.logger.Error("reconciliation error when trying to get missing pvt data info recent blocks:", err)
   146  			return err
   147  		}
   148  		// if missingPvtDataInfo is nil, len will return 0
   149  		if len(missingPvtDataInfo) == 0 {
   150  			if totalReconciled > 0 {
   151  				r.logger.Infof("Reconciliation cycle finished successfully. reconciled %d private data keys from blocks range [%d - %d]", totalReconciled, minBlock, maxBlock)
   152  			} else {
   153  				r.logger.Debug("Reconciliation cycle finished successfully. no items to reconcile")
   154  			}
   155  			return nil
   156  		}
   157  
   158  		r.logger.Debug("got from ledger", len(missingPvtDataInfo), "blocks with missing private data, trying to reconcile...")
   159  
   160  		dig2collectionCfg, minB, maxB := r.getDig2CollectionConfig(missingPvtDataInfo)
   161  		fetchedData, err := r.FetchReconciledItems(dig2collectionCfg)
   162  		if err != nil {
   163  			r.logger.Error("reconciliation error when trying to fetch missing items from different peers:", err)
   164  			return err
   165  		}
   166  
   167  		pvtDataToCommit := r.preparePvtDataToCommit(fetchedData.AvailableElements)
   168  		unreconciled := constructUnreconciledMissingData(dig2collectionCfg, fetchedData.AvailableElements)
   169  		pvtdataHashMismatch, err := r.CommitPvtDataOfOldBlocks(pvtDataToCommit, unreconciled)
   170  		if err != nil {
   171  			return errors.Wrap(err, "failed to commit private data")
   172  		}
   173  		r.logMismatched(pvtdataHashMismatch)
   174  		if minB < minBlock {
   175  			minBlock = minB
   176  		}
   177  		if maxB > maxBlock {
   178  			maxBlock = maxB
   179  		}
   180  		totalReconciled += len(fetchedData.AvailableElements)
   181  	}
   182  }
   183  
   184  func (r *Reconciler) reportReconciliationDuration(startTime time.Time) {
   185  	r.metrics.ReconciliationDuration.With("channel", r.channel).Observe(time.Since(startTime).Seconds())
   186  }
   187  
   188  type collectionConfigKey struct {
   189  	chaincodeName, collectionName string
   190  	blockNum                      uint64
   191  }
   192  
   193  func (r *Reconciler) getDig2CollectionConfig(missingPvtDataInfo ledger.MissingPvtDataInfo) (privdatacommon.Dig2CollectionConfig, uint64, uint64) {
   194  	var minBlock, maxBlock uint64
   195  	minBlock = math.MaxUint64
   196  	maxBlock = 0
   197  	collectionConfigCache := make(map[collectionConfigKey]*peer.StaticCollectionConfig)
   198  	dig2collectionCfg := make(map[privdatacommon.DigKey]*peer.StaticCollectionConfig)
   199  	for blockNum, blockPvtDataInfo := range missingPvtDataInfo {
   200  		if blockNum < minBlock {
   201  			minBlock = blockNum
   202  		}
   203  		if blockNum > maxBlock {
   204  			maxBlock = blockNum
   205  		}
   206  		for seqInBlock, collectionPvtDataInfo := range blockPvtDataInfo {
   207  			for _, pvtDataInfo := range collectionPvtDataInfo {
   208  				collConfigKey := collectionConfigKey{
   209  					chaincodeName:  pvtDataInfo.Namespace,
   210  					collectionName: pvtDataInfo.Collection,
   211  					blockNum:       blockNum,
   212  				}
   213  				if _, exists := collectionConfigCache[collConfigKey]; !exists {
   214  					collectionConfig, err := r.getMostRecentCollectionConfig(pvtDataInfo.Namespace, pvtDataInfo.Collection, blockNum)
   215  					if err != nil {
   216  						r.logger.Debug(err)
   217  						continue
   218  					}
   219  					collectionConfigCache[collConfigKey] = collectionConfig
   220  				}
   221  				digKey := privdatacommon.DigKey{
   222  					SeqInBlock: seqInBlock,
   223  					Collection: pvtDataInfo.Collection,
   224  					Namespace:  pvtDataInfo.Namespace,
   225  					BlockSeq:   blockNum,
   226  				}
   227  				dig2collectionCfg[digKey] = collectionConfigCache[collConfigKey]
   228  			}
   229  		}
   230  	}
   231  	return dig2collectionCfg, minBlock, maxBlock
   232  }
   233  
   234  func (r *Reconciler) getMostRecentCollectionConfig(chaincodeName string, collectionName string, blockNum uint64) (*peer.StaticCollectionConfig, error) {
   235  	configHistoryRetriever, err := r.GetConfigHistoryRetriever()
   236  	if err != nil {
   237  		return nil, errors.Wrap(err, "configHistoryRetriever is not available")
   238  	}
   239  
   240  	configInfo, err := configHistoryRetriever.MostRecentCollectionConfigBelow(blockNum, chaincodeName)
   241  	if err != nil {
   242  		return nil, errors.New(fmt.Sprintf("cannot find recent collection config update below block sequence = %d for chaincode %s", blockNum, chaincodeName))
   243  	}
   244  	if configInfo == nil {
   245  		return nil, errors.New(fmt.Sprintf("no collection config update below block sequence = %d for chaincode %s is available", blockNum, chaincodeName))
   246  	}
   247  
   248  	collectionConfig := extractCollectionConfig(configInfo.CollectionConfig, collectionName)
   249  	if collectionConfig == nil {
   250  		return nil, errors.New(fmt.Sprintf("no collection config was found for collection %s for chaincode %s", collectionName, chaincodeName))
   251  	}
   252  
   253  	staticCollectionConfig, wasCastingSuccessful := collectionConfig.Payload.(*peer.CollectionConfig_StaticCollectionConfig)
   254  	if !wasCastingSuccessful {
   255  		return nil, errors.New(fmt.Sprintf("expected collection config of type CollectionConfig_StaticCollectionConfig for collection %s for chaincode %s, while got different config type...", collectionName, chaincodeName))
   256  	}
   257  	return staticCollectionConfig.StaticCollectionConfig, nil
   258  }
   259  
   260  func (r *Reconciler) preparePvtDataToCommit(elements []*protosgossip.PvtDataElement) []*ledger.ReconciledPvtdata {
   261  	rwSetByBlockByKeys := r.groupRwsetByBlock(elements)
   262  
   263  	// populate the private RWSets passed to the ledger
   264  	var pvtDataToCommit []*ledger.ReconciledPvtdata
   265  
   266  	for blockNum, rwSetKeys := range rwSetByBlockByKeys {
   267  		blockPvtData := &ledger.ReconciledPvtdata{
   268  			BlockNum:  blockNum,
   269  			WriteSets: make(map[uint64]*ledger.TxPvtData),
   270  		}
   271  		for seqInBlock, nsRWS := range rwSetKeys.bySeqsInBlock() {
   272  			rwsets := nsRWS.toRWSet()
   273  			r.logger.Debugf("Preparing to commit [%d] private write set, missed from transaction index [%d] of block number [%d]", len(rwsets.NsPvtRwset), seqInBlock, blockNum)
   274  			blockPvtData.WriteSets[seqInBlock] = &ledger.TxPvtData{
   275  				SeqInBlock: seqInBlock,
   276  				WriteSet:   rwsets,
   277  			}
   278  		}
   279  		pvtDataToCommit = append(pvtDataToCommit, blockPvtData)
   280  	}
   281  	return pvtDataToCommit
   282  }
   283  
   284  func (r *Reconciler) logMismatched(pvtdataMismatched []*ledger.PvtdataHashMismatch) {
   285  	if len(pvtdataMismatched) > 0 {
   286  		for _, hashMismatch := range pvtdataMismatched {
   287  			r.logger.Warningf("failed to reconcile pvtdata chaincode %s, collection %s, block num %d, tx num %d due to hash mismatch or partially available bootKVs",
   288  				hashMismatch.Namespace, hashMismatch.Collection, hashMismatch.BlockNum, hashMismatch.TxNum)
   289  		}
   290  	}
   291  }
   292  
   293  // return a mapping from block num to rwsetByKeys
   294  func (r *Reconciler) groupRwsetByBlock(elements []*protosgossip.PvtDataElement) map[uint64]rwsetByKeys {
   295  	rwSetByBlockByKeys := make(map[uint64]rwsetByKeys) // map from block num to rwsetByKeys
   296  
   297  	// Iterate over data fetched from peers
   298  	for _, element := range elements {
   299  		dig := element.Digest
   300  		if _, exists := rwSetByBlockByKeys[dig.BlockSeq]; !exists {
   301  			rwSetByBlockByKeys[dig.BlockSeq] = make(map[rwSetKey][]byte)
   302  		}
   303  		for _, rws := range element.Payload {
   304  			hash := hex.EncodeToString(commonutil.ComputeSHA256(rws))
   305  			key := rwSetKey{
   306  				txID:       dig.TxId,
   307  				namespace:  dig.Namespace,
   308  				collection: dig.Collection,
   309  				seqInBlock: dig.SeqInBlock,
   310  				hash:       hash,
   311  			}
   312  			rwSetByBlockByKeys[dig.BlockSeq][key] = rws
   313  		}
   314  	}
   315  	return rwSetByBlockByKeys
   316  }
   317  
   318  func constructUnreconciledMissingData(requestedMissingData privdatacommon.Dig2CollectionConfig, fetchedData []*protosgossip.PvtDataElement) ledger.MissingPvtDataInfo {
   319  	fetchedDataKeys := make(map[privdatacommon.DigKey]struct{})
   320  	for _, pvtData := range fetchedData {
   321  		key := privdatacommon.DigKey{
   322  			TxId:       pvtData.Digest.TxId,
   323  			Namespace:  pvtData.Digest.Namespace,
   324  			Collection: pvtData.Digest.Collection,
   325  			BlockSeq:   pvtData.Digest.BlockSeq,
   326  			SeqInBlock: pvtData.Digest.SeqInBlock,
   327  		}
   328  		fetchedDataKeys[key] = struct{}{}
   329  	}
   330  
   331  	var unreconciledMissingDataInfo ledger.MissingPvtDataInfo
   332  	for key := range requestedMissingData {
   333  		if _, ok := fetchedDataKeys[key]; !ok {
   334  			if unreconciledMissingDataInfo == nil {
   335  				unreconciledMissingDataInfo = make(ledger.MissingPvtDataInfo)
   336  			}
   337  			unreconciledMissingDataInfo.Add(key.BlockSeq, key.SeqInBlock, key.Namespace, key.Collection)
   338  		}
   339  	}
   340  	return unreconciledMissingDataInfo
   341  }