github.com/true-sqn/fabric@v2.1.1+incompatible/gossip/privdata/reconcile.go (about)

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