github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/gossip/privdata/coordinator.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  	"time"
    11  
    12  	"github.com/hechain20/hechain/common/channelconfig"
    13  	"github.com/hechain20/hechain/core/committer"
    14  	"github.com/hechain20/hechain/core/committer/txvalidator"
    15  	"github.com/hechain20/hechain/core/common/privdata"
    16  	"github.com/hechain20/hechain/core/ledger"
    17  	"github.com/hechain20/hechain/core/ledger/kvledger/txmgmt/rwsetutil"
    18  	"github.com/hechain20/hechain/core/transientstore"
    19  	"github.com/hechain20/hechain/gossip/metrics"
    20  	privdatacommon "github.com/hechain20/hechain/gossip/privdata/common"
    21  	"github.com/hechain20/hechain/gossip/util"
    22  	"github.com/hechain20/hechain/protoutil"
    23  	"github.com/hyperledger/fabric-protos-go/common"
    24  	"github.com/hyperledger/fabric-protos-go/ledger/rwset"
    25  	"github.com/hyperledger/fabric-protos-go/peer"
    26  	protostransientstore "github.com/hyperledger/fabric-protos-go/transientstore"
    27  	"github.com/pkg/errors"
    28  )
    29  
    30  const pullRetrySleepInterval = time.Second
    31  
    32  var logger = util.GetLogger(util.PrivateDataLogger, "")
    33  
    34  //go:generate mockery -dir . -name CollectionStore -case underscore -output mocks/
    35  
    36  // CollectionStore is the local interface used to generate mocks for foreign interface.
    37  type CollectionStore interface {
    38  	privdata.CollectionStore
    39  }
    40  
    41  //go:generate mockery -dir . -name Committer -case underscore -output mocks/
    42  
    43  // Committer is the local interface used to generate mocks for foreign interface.
    44  type Committer interface {
    45  	committer.Committer
    46  }
    47  
    48  // Coordinator orchestrates the flow of the new
    49  // blocks arrival and in flight transient data, responsible
    50  // to complete missing parts of transient data for given block.
    51  type Coordinator interface {
    52  	// StoreBlock deliver new block with underlined private data
    53  	// returns missing transaction ids
    54  	StoreBlock(block *common.Block, data util.PvtDataCollections) error
    55  
    56  	// StorePvtData used to persist private data into transient store
    57  	StorePvtData(txid string, privData *protostransientstore.TxPvtReadWriteSetWithConfigInfo, blckHeight uint64) error
    58  
    59  	// GetPvtDataAndBlockByNum gets block by number and also returns all related private data
    60  	// that requesting peer is eligible for.
    61  	// The order of private data in slice of PvtDataCollections doesn't imply the order of
    62  	// transactions in the block related to these private data, to get the correct placement
    63  	// need to read TxPvtData.SeqInBlock field
    64  	GetPvtDataAndBlockByNum(seqNum uint64, peerAuth protoutil.SignedData) (*common.Block, util.PvtDataCollections, error)
    65  
    66  	// Get recent block sequence number
    67  	LedgerHeight() (uint64, error)
    68  
    69  	// Close coordinator, shuts down coordinator service
    70  	Close()
    71  }
    72  
    73  type dig2sources map[privdatacommon.DigKey][]*peer.Endorsement
    74  
    75  func (d2s dig2sources) keys() []privdatacommon.DigKey {
    76  	res := make([]privdatacommon.DigKey, 0, len(d2s))
    77  	for dig := range d2s {
    78  		res = append(res, dig)
    79  	}
    80  	return res
    81  }
    82  
    83  // Fetcher interface which defines API to fetch missing
    84  // private data elements
    85  type Fetcher interface {
    86  	fetch(dig2src dig2sources) (*privdatacommon.FetchedPvtDataContainer, error)
    87  }
    88  
    89  //go:generate mockery -dir ./ -name CapabilityProvider -case underscore -output mocks/
    90  
    91  // CapabilityProvider contains functions to retrieve capability information for a channel
    92  type CapabilityProvider interface {
    93  	// Capabilities defines the capabilities for the application portion of this channel
    94  	Capabilities() channelconfig.ApplicationCapabilities
    95  }
    96  
    97  // Support encapsulates set of interfaces to
    98  // aggregate required functionality by single struct
    99  type Support struct {
   100  	ChainID string
   101  	privdata.CollectionStore
   102  	txvalidator.Validator
   103  	committer.Committer
   104  	Fetcher
   105  	CapabilityProvider
   106  }
   107  
   108  // CoordinatorConfig encapsulates the config that is passed to a new coordinator
   109  type CoordinatorConfig struct {
   110  	// TransientBlockRetention indicates the number of blocks to retain in the transient store
   111  	// when purging below height on committing every TransientBlockRetention-th block
   112  	TransientBlockRetention uint64
   113  	// PullRetryThreshold indicates the max duration an attempted fetch from a remote peer will retry
   114  	// for before giving up and leaving the private data as missing
   115  	PullRetryThreshold time.Duration
   116  	// SkipPullingInvalidTransactions if true will skip the fetch from remote peer step for transactions
   117  	// marked as invalid
   118  	SkipPullingInvalidTransactions bool
   119  }
   120  
   121  type coordinator struct {
   122  	mspID          string
   123  	selfSignedData protoutil.SignedData
   124  	Support
   125  	store                          *transientstore.Store
   126  	transientBlockRetention        uint64
   127  	logger                         util.Logger
   128  	metrics                        *metrics.PrivdataMetrics
   129  	pullRetryThreshold             time.Duration
   130  	skipPullingInvalidTransactions bool
   131  	idDeserializerFactory          IdentityDeserializerFactory
   132  }
   133  
   134  // NewCoordinator creates a new instance of coordinator
   135  func NewCoordinator(mspID string, support Support, store *transientstore.Store, selfSignedData protoutil.SignedData, metrics *metrics.PrivdataMetrics,
   136  	config CoordinatorConfig, idDeserializerFactory IdentityDeserializerFactory) Coordinator {
   137  	return &coordinator{
   138  		Support:                        support,
   139  		mspID:                          mspID,
   140  		store:                          store,
   141  		selfSignedData:                 selfSignedData,
   142  		transientBlockRetention:        config.TransientBlockRetention,
   143  		logger:                         logger.With("channel", support.ChainID),
   144  		metrics:                        metrics,
   145  		pullRetryThreshold:             config.PullRetryThreshold,
   146  		skipPullingInvalidTransactions: config.SkipPullingInvalidTransactions,
   147  		idDeserializerFactory:          idDeserializerFactory,
   148  	}
   149  }
   150  
   151  // StoreBlock stores block with private data into the ledger
   152  func (c *coordinator) StoreBlock(block *common.Block, privateDataSets util.PvtDataCollections) error {
   153  	if block.Data == nil {
   154  		return errors.New("Block data is empty")
   155  	}
   156  	if block.Header == nil {
   157  		return errors.New("Block header is nil")
   158  	}
   159  
   160  	c.logger.Infof("Received block [%d] from buffer", block.Header.Number)
   161  
   162  	c.logger.Debugf("Validating block [%d]", block.Header.Number)
   163  
   164  	validationStart := time.Now()
   165  	err := c.Validator.Validate(block)
   166  	c.reportValidationDuration(time.Since(validationStart))
   167  	if err != nil {
   168  		c.logger.Errorf("Validation failed: %+v", err)
   169  		return err
   170  	}
   171  
   172  	blockAndPvtData := &ledger.BlockAndPvtData{
   173  		Block:          block,
   174  		PvtData:        make(ledger.TxPvtDataMap),
   175  		MissingPvtData: make(ledger.TxMissingPvtData),
   176  	}
   177  
   178  	exist, err := c.DoesPvtDataInfoExistInLedger(block.Header.Number)
   179  	if err != nil {
   180  		return err
   181  	}
   182  	if exist {
   183  		commitOpts := &ledger.CommitOptions{FetchPvtDataFromLedger: true}
   184  		return c.CommitLegacy(blockAndPvtData, commitOpts)
   185  	}
   186  
   187  	listMissingPrivateDataDurationHistogram := c.metrics.ListMissingPrivateDataDuration.With("channel", c.ChainID)
   188  	fetchDurationHistogram := c.metrics.FetchDuration.With("channel", c.ChainID)
   189  	purgeDurationHistogram := c.metrics.PurgeDuration.With("channel", c.ChainID)
   190  	pdp := &PvtdataProvider{
   191  		mspID:                                   c.mspID,
   192  		selfSignedData:                          c.selfSignedData,
   193  		logger:                                  logger.With("channel", c.ChainID),
   194  		listMissingPrivateDataDurationHistogram: listMissingPrivateDataDurationHistogram,
   195  		fetchDurationHistogram:                  fetchDurationHistogram,
   196  		purgeDurationHistogram:                  purgeDurationHistogram,
   197  		transientStore:                          c.store,
   198  		pullRetryThreshold:                      c.pullRetryThreshold,
   199  		prefetchedPvtdata:                       privateDataSets,
   200  		transientBlockRetention:                 c.transientBlockRetention,
   201  		channelID:                               c.ChainID,
   202  		blockNum:                                block.Header.Number,
   203  		storePvtdataOfInvalidTx:                 c.Support.CapabilityProvider.Capabilities().StorePvtDataOfInvalidTx(),
   204  		skipPullingInvalidTransactions:          c.skipPullingInvalidTransactions,
   205  		fetcher:                                 c.Fetcher,
   206  		idDeserializerFactory:                   c.idDeserializerFactory,
   207  	}
   208  	pvtdataToRetrieve, err := c.getTxPvtdataInfoFromBlock(block)
   209  	if err != nil {
   210  		c.logger.Warningf("Failed to get private data info from block: %s", err)
   211  		return err
   212  	}
   213  
   214  	// Retrieve the private data.
   215  	// RetrievePvtdata checks this peer's eligibility and then retreives from cache, transient store, or from a remote peer.
   216  	retrievedPvtdata, err := pdp.RetrievePvtdata(pvtdataToRetrieve)
   217  	if err != nil {
   218  		c.logger.Warningf("Failed to retrieve pvtdata: %s", err)
   219  		return err
   220  	}
   221  
   222  	blockAndPvtData.PvtData = retrievedPvtdata.blockPvtdata.PvtData
   223  	blockAndPvtData.MissingPvtData = retrievedPvtdata.blockPvtdata.MissingPvtData
   224  
   225  	// commit block and private data
   226  	commitStart := time.Now()
   227  	err = c.CommitLegacy(blockAndPvtData, &ledger.CommitOptions{})
   228  	c.reportCommitDuration(time.Since(commitStart))
   229  	if err != nil {
   230  		return errors.Wrap(err, "commit failed")
   231  	}
   232  
   233  	// Purge transactions
   234  	go retrievedPvtdata.Purge()
   235  
   236  	return nil
   237  }
   238  
   239  // StorePvtData used to persist private data into transient store
   240  func (c *coordinator) StorePvtData(txID string, privData *protostransientstore.TxPvtReadWriteSetWithConfigInfo, blkHeight uint64) error {
   241  	return c.store.Persist(txID, blkHeight, privData)
   242  }
   243  
   244  // GetPvtDataAndBlockByNum gets block by number and also returns all related private data
   245  // that requesting peer is eligible for.
   246  // The order of private data in slice of PvtDataCollections doesn't imply the order of
   247  // transactions in the block related to these private data, to get the correct placement
   248  // need to read TxPvtData.SeqInBlock field
   249  func (c *coordinator) GetPvtDataAndBlockByNum(seqNum uint64, peerAuthInfo protoutil.SignedData) (*common.Block, util.PvtDataCollections, error) {
   250  	blockAndPvtData, err := c.Committer.GetPvtDataAndBlockByNum(seqNum)
   251  	if err != nil {
   252  		return nil, nil, err
   253  	}
   254  
   255  	seqs2Namespaces := aggregatedCollections{}
   256  	for seqInBlock := range blockAndPvtData.Block.Data.Data {
   257  		txPvtDataItem, exists := blockAndPvtData.PvtData[uint64(seqInBlock)]
   258  		if !exists {
   259  			continue
   260  		}
   261  
   262  		// Iterate through the private write sets and include them in response if requesting peer is eligible for it
   263  		for _, ns := range txPvtDataItem.WriteSet.NsPvtRwset {
   264  			for _, col := range ns.CollectionPvtRwset {
   265  				cc := privdata.CollectionCriteria{
   266  					Channel:    c.ChainID,
   267  					Namespace:  ns.Namespace,
   268  					Collection: col.CollectionName,
   269  				}
   270  				sp, err := c.CollectionStore.RetrieveCollectionAccessPolicy(cc)
   271  				if err != nil {
   272  					c.logger.Warningf("Failed obtaining policy for collection criteria [%#v]: %s", cc, err)
   273  					continue
   274  				}
   275  				isAuthorized := sp.AccessFilter()
   276  				if isAuthorized == nil {
   277  					c.logger.Warningf("Failed obtaining filter for collection criteria [%#v]", cc)
   278  					continue
   279  				}
   280  				if !isAuthorized(peerAuthInfo) {
   281  					c.logger.Debugf("Skipping collection criteria [%#v] because peer isn't authorized", cc)
   282  					continue
   283  				}
   284  				seqs2Namespaces.addCollection(uint64(seqInBlock), txPvtDataItem.WriteSet.DataModel, ns.Namespace, col)
   285  			}
   286  		}
   287  	}
   288  
   289  	return blockAndPvtData.Block, seqs2Namespaces.asPrivateData(), nil
   290  }
   291  
   292  // getTxPvtdataInfoFromBlock parses the block transactions and returns the list of private data items in the block.
   293  // Note that this peer's eligibility for the private data is not checked here.
   294  func (c *coordinator) getTxPvtdataInfoFromBlock(block *common.Block) ([]*ledger.TxPvtdataInfo, error) {
   295  	txPvtdataItemsFromBlock := []*ledger.TxPvtdataInfo{}
   296  
   297  	if block.Metadata == nil || len(block.Metadata.Metadata) <= int(common.BlockMetadataIndex_TRANSACTIONS_FILTER) {
   298  		return nil, errors.New("Block.Metadata is nil or Block.Metadata lacks a Tx filter bitmap")
   299  	}
   300  	txsFilter := txValidationFlags(block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER])
   301  	data := block.Data.Data
   302  	if len(txsFilter) != len(block.Data.Data) {
   303  		return nil, errors.Errorf("block data size(%d) is different from Tx filter size(%d)", len(block.Data.Data), len(txsFilter))
   304  	}
   305  
   306  	for seqInBlock, txEnvBytes := range data {
   307  		invalid := txsFilter[seqInBlock] != uint8(peer.TxValidationCode_VALID)
   308  		txInfo, err := getTxInfoFromTransactionBytes(txEnvBytes)
   309  		if err != nil {
   310  			continue
   311  		}
   312  
   313  		colPvtdataInfo := []*ledger.CollectionPvtdataInfo{}
   314  		for _, ns := range txInfo.txRWSet.NsRwSets {
   315  			for _, hashedCollection := range ns.CollHashedRwSets {
   316  				// skip if no writes
   317  				if !containsWrites(txInfo.txID, ns.NameSpace, hashedCollection) {
   318  					continue
   319  				}
   320  				cc := privdata.CollectionCriteria{
   321  					Channel:    txInfo.channelID,
   322  					Namespace:  ns.NameSpace,
   323  					Collection: hashedCollection.CollectionName,
   324  				}
   325  
   326  				colConfig, err := c.CollectionStore.RetrieveCollectionConfig(cc)
   327  				if err != nil {
   328  					c.logger.Warningf("Failed to retrieve collection config for collection criteria [%#v]: %s", cc, err)
   329  					return nil, err
   330  				}
   331  				col := &ledger.CollectionPvtdataInfo{
   332  					Namespace:        ns.NameSpace,
   333  					Collection:       hashedCollection.CollectionName,
   334  					ExpectedHash:     hashedCollection.PvtRwSetHash,
   335  					CollectionConfig: colConfig,
   336  					Endorsers:        txInfo.endorsements,
   337  				}
   338  				colPvtdataInfo = append(colPvtdataInfo, col)
   339  			}
   340  		}
   341  		txPvtdataToRetrieve := &ledger.TxPvtdataInfo{
   342  			TxID:                  txInfo.txID,
   343  			Invalid:               invalid,
   344  			SeqInBlock:            uint64(seqInBlock),
   345  			CollectionPvtdataInfo: colPvtdataInfo,
   346  		}
   347  		txPvtdataItemsFromBlock = append(txPvtdataItemsFromBlock, txPvtdataToRetrieve)
   348  	}
   349  
   350  	return txPvtdataItemsFromBlock, nil
   351  }
   352  
   353  func (c *coordinator) reportValidationDuration(time time.Duration) {
   354  	c.metrics.ValidationDuration.With("channel", c.ChainID).Observe(time.Seconds())
   355  }
   356  
   357  func (c *coordinator) reportCommitDuration(time time.Duration) {
   358  	c.metrics.CommitPrivateDataDuration.With("channel", c.ChainID).Observe(time.Seconds())
   359  }
   360  
   361  type seqAndDataModel struct {
   362  	seq       uint64
   363  	dataModel rwset.TxReadWriteSet_DataModel
   364  }
   365  
   366  // map from seqAndDataModel to:
   367  //     map from namespace to []*rwset.CollectionPvtReadWriteSet
   368  type aggregatedCollections map[seqAndDataModel]map[string][]*rwset.CollectionPvtReadWriteSet
   369  
   370  func (ac aggregatedCollections) addCollection(seqInBlock uint64, dm rwset.TxReadWriteSet_DataModel, namespace string, col *rwset.CollectionPvtReadWriteSet) {
   371  	seq := seqAndDataModel{
   372  		dataModel: dm,
   373  		seq:       seqInBlock,
   374  	}
   375  	if _, exists := ac[seq]; !exists {
   376  		ac[seq] = make(map[string][]*rwset.CollectionPvtReadWriteSet)
   377  	}
   378  	ac[seq][namespace] = append(ac[seq][namespace], col)
   379  }
   380  
   381  func (ac aggregatedCollections) asPrivateData() []*ledger.TxPvtData {
   382  	var data []*ledger.TxPvtData
   383  	for seq, ns := range ac {
   384  		txPrivateData := &ledger.TxPvtData{
   385  			SeqInBlock: seq.seq,
   386  			WriteSet: &rwset.TxPvtReadWriteSet{
   387  				DataModel: seq.dataModel,
   388  			},
   389  		}
   390  		for namespaceName, cols := range ns {
   391  			txPrivateData.WriteSet.NsPvtRwset = append(txPrivateData.WriteSet.NsPvtRwset, &rwset.NsPvtReadWriteSet{
   392  				Namespace:          namespaceName,
   393  				CollectionPvtRwset: cols,
   394  			})
   395  		}
   396  		data = append(data, txPrivateData)
   397  	}
   398  	return data
   399  }
   400  
   401  type txInfo struct {
   402  	channelID    string
   403  	txID         string
   404  	endorsements []*peer.Endorsement
   405  	txRWSet      *rwsetutil.TxRwSet
   406  }
   407  
   408  // getTxInfoFromTransactionBytes parses a transaction and returns info required for private data retrieval
   409  func getTxInfoFromTransactionBytes(envBytes []byte) (*txInfo, error) {
   410  	txInfo := &txInfo{}
   411  	env, err := protoutil.GetEnvelopeFromBlock(envBytes)
   412  	if err != nil {
   413  		logger.Warningf("Invalid envelope: %s", err)
   414  		return nil, err
   415  	}
   416  
   417  	payload, err := protoutil.UnmarshalPayload(env.Payload)
   418  	if err != nil {
   419  		logger.Warningf("Invalid payload: %s", err)
   420  		return nil, err
   421  	}
   422  	if payload.Header == nil {
   423  		err := errors.New("payload header is nil")
   424  		logger.Warningf("Invalid tx: %s", err)
   425  		return nil, err
   426  	}
   427  
   428  	chdr, err := protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader)
   429  	if err != nil {
   430  		logger.Warningf("Invalid channel header: %s", err)
   431  		return nil, err
   432  	}
   433  	txInfo.channelID = chdr.ChannelId
   434  	txInfo.txID = chdr.TxId
   435  
   436  	if chdr.Type != int32(common.HeaderType_ENDORSER_TRANSACTION) {
   437  		err := errors.New("header type is not an endorser transaction")
   438  		logger.Debugf("Invalid transaction type: %s", err)
   439  		return nil, err
   440  	}
   441  
   442  	respPayload, err := protoutil.GetActionFromEnvelope(envBytes)
   443  	if err != nil {
   444  		logger.Warningf("Failed obtaining action from envelope: %s", err)
   445  		return nil, err
   446  	}
   447  
   448  	tx, err := protoutil.UnmarshalTransaction(payload.Data)
   449  	if err != nil {
   450  		logger.Warningf("Invalid transaction in payload data for tx [%s]: %s", chdr.TxId, err)
   451  		return nil, err
   452  	}
   453  
   454  	ccActionPayload, err := protoutil.UnmarshalChaincodeActionPayload(tx.Actions[0].Payload)
   455  	if err != nil {
   456  		logger.Warningf("Invalid chaincode action in payload for tx [%s]: %s", chdr.TxId, err)
   457  		return nil, err
   458  	}
   459  
   460  	if ccActionPayload.Action == nil {
   461  		logger.Warningf("Action in ChaincodeActionPayload for tx [%s] is nil", chdr.TxId)
   462  		return nil, err
   463  	}
   464  	txInfo.endorsements = ccActionPayload.Action.Endorsements
   465  
   466  	txRWSet := &rwsetutil.TxRwSet{}
   467  	if err = txRWSet.FromProtoBytes(respPayload.Results); err != nil {
   468  		logger.Warningf("Failed obtaining TxRwSet from ChaincodeAction's results: %s", err)
   469  		return nil, err
   470  	}
   471  	txInfo.txRWSet = txRWSet
   472  
   473  	return txInfo, nil
   474  }
   475  
   476  // containsWrites checks whether the given CollHashedRwSet contains writes
   477  func containsWrites(txID string, namespace string, colHashedRWSet *rwsetutil.CollHashedRwSet) bool {
   478  	if colHashedRWSet.HashedRwSet == nil {
   479  		logger.Warningf("HashedRWSet of tx [%s], namespace [%s], collection [%s] is nil", txID, namespace, colHashedRWSet.CollectionName)
   480  		return false
   481  	}
   482  	if len(colHashedRWSet.HashedRwSet.HashedWrites) == 0 && len(colHashedRWSet.HashedRwSet.MetadataWrites) == 0 {
   483  		logger.Debugf("HashedRWSet of tx [%s], namespace [%s], collection [%s] doesn't contain writes", txID, namespace, colHashedRWSet.CollectionName)
   484  		return false
   485  	}
   486  	return true
   487  }