github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/core/ledger/confighistory/mgr.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package confighistory
     8  
     9  import (
    10  	"fmt"
    11  	"path/filepath"
    12  
    13  	"github.com/golang/protobuf/proto"
    14  	"github.com/hyperledger/fabric-protos-go/common"
    15  	"github.com/hyperledger/fabric-protos-go/ledger/rwset/kvrwset"
    16  	"github.com/hyperledger/fabric-protos-go/peer"
    17  	"github.com/osdi23p228/fabric/common/flogging"
    18  	"github.com/osdi23p228/fabric/common/ledger/snapshot"
    19  	"github.com/osdi23p228/fabric/core/ledger"
    20  	"github.com/pkg/errors"
    21  )
    22  
    23  var (
    24  	logger                 = flogging.MustGetLogger("confighistory")
    25  	importConfigsBatchSize = 1024 * 1024
    26  )
    27  
    28  const (
    29  	collectionConfigNamespace = "lscc" // lscc namespace was introduced in version 1.2 and we continue to use this in order to be compatible with existing data
    30  	snapshotFileFormat        = byte(1)
    31  	snapshotDataFileName      = "confighistory.data"
    32  	snapshotMetadataFileName  = "confighistory.metadata"
    33  )
    34  
    35  // Mgr manages the history of configurations such as chaincode's collection configurations.
    36  // It should be registered as a state listener. The state listener builds the history.
    37  type Mgr struct {
    38  	ccInfoProvider ledger.DeployedChaincodeInfoProvider
    39  	dbProvider     *dbProvider
    40  }
    41  
    42  // NewMgr constructs an instance that implements interface `Mgr`
    43  func NewMgr(dbPath string, ccInfoProvider ledger.DeployedChaincodeInfoProvider) (*Mgr, error) {
    44  	p, err := newDBProvider(dbPath)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	return &Mgr{ccInfoProvider, p}, nil
    49  }
    50  
    51  // Name returns the name of the listener
    52  func (m *Mgr) Name() string {
    53  	return "collection configuration history listener"
    54  }
    55  
    56  func (m *Mgr) Initialize(ledgerID string, qe ledger.SimpleQueryExecutor) error {
    57  	// Noop
    58  	return nil
    59  }
    60  
    61  // InterestedInNamespaces implements function from the interface ledger.StateListener
    62  func (m *Mgr) InterestedInNamespaces() []string {
    63  	return m.ccInfoProvider.Namespaces()
    64  }
    65  
    66  // StateCommitDone implements function from the interface ledger.StateListener
    67  func (m *Mgr) StateCommitDone(ledgerID string) {
    68  	// Noop
    69  }
    70  
    71  // HandleStateUpdates implements function from the interface ledger.StateListener
    72  // In this implementation, the latest collection config package is retrieved via
    73  // ledger.DeployedChaincodeInfoProvider and is persisted as a separate entry in a separate db.
    74  // The composite key for the entry is a tuple of <blockNum, namespace, key>
    75  func (m *Mgr) HandleStateUpdates(trigger *ledger.StateUpdateTrigger) error {
    76  	updatedCCs, err := m.ccInfoProvider.UpdatedChaincodes(extractPublicUpdates(trigger.StateUpdates))
    77  	if err != nil {
    78  		return err
    79  	}
    80  	// updated chaincodes can be empty if the invocation to this function is triggered
    81  	// because of state updates that contains only chaincode approval transaction output
    82  	if len(updatedCCs) == 0 {
    83  		return nil
    84  	}
    85  	updatedCollConfigs := map[string]*peer.CollectionConfigPackage{}
    86  	for _, cc := range updatedCCs {
    87  		ccInfo, err := m.ccInfoProvider.ChaincodeInfo(trigger.LedgerID, cc.Name, trigger.PostCommitQueryExecutor)
    88  		if err != nil {
    89  			return err
    90  		}
    91  
    92  		// DeployedChaincodeInfoProvider implementation in new lifecycle return an empty 'CollectionConfigPackage'
    93  		// (instead of a nil) to indicate the absence of collection config, so check for both conditions
    94  		if ccInfo.ExplicitCollectionConfigPkg == nil || len(ccInfo.ExplicitCollectionConfigPkg.Config) == 0 {
    95  			continue
    96  		}
    97  		updatedCollConfigs[ccInfo.Name] = ccInfo.ExplicitCollectionConfigPkg
    98  	}
    99  	if len(updatedCollConfigs) == 0 {
   100  		return nil
   101  	}
   102  	dbHandle := m.dbProvider.getDB(trigger.LedgerID)
   103  	batch := dbHandle.newBatch()
   104  	err = prepareDBBatch(batch, updatedCollConfigs, trigger.CommittingBlockNum)
   105  	if err != nil {
   106  		return err
   107  	}
   108  	return dbHandle.writeBatch(batch, true)
   109  }
   110  
   111  func (m *Mgr) ImportConfigHistory(ledgerID string, dir string) error {
   112  	db := m.dbProvider.getDB(ledgerID)
   113  	empty, err := db.isEmpty()
   114  	if err != nil {
   115  		return err
   116  	}
   117  	if !empty {
   118  		return errors.New(fmt.Sprintf(
   119  			"config history for ledger [%s] exists. Incremental import is not supported. "+
   120  				"Remove the existing ledger data before retry",
   121  			ledgerID,
   122  		))
   123  	}
   124  
   125  	configMetadata, err := snapshot.OpenFile(filepath.Join(dir, snapshotMetadataFileName), snapshotFileFormat)
   126  	if err != nil {
   127  		return err
   128  	}
   129  	numCollectionConfigs, err := configMetadata.DecodeUVarInt()
   130  	if err != nil {
   131  		return err
   132  	}
   133  	collectionConfigData, err := snapshot.OpenFile(filepath.Join(dir, snapshotDataFileName), snapshotFileFormat)
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	batch := db.NewUpdateBatch()
   139  	currentBatchSize := 0
   140  	for i := uint64(0); i < numCollectionConfigs; i++ {
   141  		key, err := collectionConfigData.DecodeBytes()
   142  		if err != nil {
   143  			return err
   144  		}
   145  		val, err := collectionConfigData.DecodeBytes()
   146  		if err != nil {
   147  			return err
   148  		}
   149  		batch.Put(key, val)
   150  		currentBatchSize += len(key) + len(val)
   151  		if currentBatchSize >= importConfigsBatchSize {
   152  			if err := db.WriteBatch(batch, true); err != nil {
   153  				return err
   154  			}
   155  			batch = db.NewUpdateBatch()
   156  		}
   157  	}
   158  	return db.WriteBatch(batch, true)
   159  }
   160  
   161  // GetRetriever returns an implementation of `ledger.ConfigHistoryRetriever` for the given ledger id.
   162  func (m *Mgr) GetRetriever(ledgerID string, ledgerInfoRetriever LedgerInfoRetriever) *Retriever {
   163  	return &Retriever{
   164  		ledgerInfoRetriever:    ledgerInfoRetriever,
   165  		ledgerID:               ledgerID,
   166  		deployedCCInfoProvider: m.ccInfoProvider,
   167  		dbHandle:               m.dbProvider.getDB(ledgerID),
   168  	}
   169  }
   170  
   171  // Close implements the function in the interface 'Mgr'
   172  func (m *Mgr) Close() {
   173  	m.dbProvider.Close()
   174  }
   175  
   176  type Retriever struct {
   177  	ledgerInfoRetriever    LedgerInfoRetriever
   178  	ledgerID               string
   179  	deployedCCInfoProvider ledger.DeployedChaincodeInfoProvider
   180  	dbHandle               *db
   181  }
   182  
   183  // MostRecentCollectionConfigBelow implements function from the interface ledger.ConfigHistoryRetriever
   184  func (r *Retriever) MostRecentCollectionConfigBelow(blockNum uint64, chaincodeName string) (*ledger.CollectionConfigInfo, error) {
   185  	compositeKV, err := r.dbHandle.mostRecentEntryBelow(blockNum, collectionConfigNamespace, constructCollectionConfigKey(chaincodeName))
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  	implicitColls, err := r.getImplicitCollection(chaincodeName)
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  
   194  	return constructCollectionConfigInfo(compositeKV, implicitColls)
   195  }
   196  
   197  // CollectionConfigAt implements function from the interface ledger.ConfigHistoryRetriever
   198  func (r *Retriever) CollectionConfigAt(blockNum uint64, chaincodeName string) (*ledger.CollectionConfigInfo, error) {
   199  	info, err := r.ledgerInfoRetriever.GetBlockchainInfo()
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  	maxCommittedBlockNum := info.Height - 1
   204  	if maxCommittedBlockNum < blockNum {
   205  		return nil, &ledger.ErrCollectionConfigNotYetAvailable{MaxBlockNumCommitted: maxCommittedBlockNum,
   206  			Msg: fmt.Sprintf("The maximum block number committed [%d] is less than the requested block number [%d]", maxCommittedBlockNum, blockNum)}
   207  	}
   208  
   209  	compositeKV, err := r.dbHandle.entryAt(blockNum, collectionConfigNamespace, constructCollectionConfigKey(chaincodeName))
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  	implicitColls, err := r.getImplicitCollection(chaincodeName)
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  	return constructCollectionConfigInfo(compositeKV, implicitColls)
   218  }
   219  
   220  // ExportConfigHistory exports configuration history from the confighistoryDB to
   221  // a file. Currently, we store only one type of configuration in the db, i.e.,
   222  // private data collection configuration.
   223  // We write the full key and value stored in the database as is to the file.
   224  // Though we could decode the key and write a proto message with exact ns, key,
   225  // block number, and collection config, we store the full key and value to avoid
   226  // unnecessary encoding and decoding of proto messages.
   227  // The key format stored in db is "s" + ns + byte(0) + key + "~collection" + byte(0)
   228  // + blockNum. As we store the key as is, we store 13 extra bytes. For a million
   229  // records, it would add only 12 MB overhead. Note that the protobuf also adds some
   230  // extra bytes. Further, the collection config namespace is not expected to have
   231  // millions of entries.
   232  func (r *Retriever) ExportConfigHistory(dir string, newHashFunc snapshot.NewHashFunc) (map[string][]byte, error) {
   233  	nsItr, err := r.dbHandle.getNamespaceIterator(collectionConfigNamespace)
   234  	if err != nil {
   235  		return nil, err
   236  	}
   237  	defer nsItr.Release()
   238  
   239  	var numCollectionConfigs uint64 = 0
   240  	var dataFileWriter *snapshot.FileWriter
   241  	for nsItr.Next() {
   242  		if err := nsItr.Error(); err != nil {
   243  			return nil, errors.Wrap(err, "internal leveldb error while iterating for collection config history")
   244  		}
   245  		if numCollectionConfigs == 0 { // first iteration, create the data file
   246  			dataFileWriter, err = snapshot.CreateFile(filepath.Join(dir, snapshotDataFileName), snapshotFileFormat, newHashFunc)
   247  			if err != nil {
   248  				return nil, err
   249  			}
   250  			defer dataFileWriter.Close()
   251  		}
   252  		if err := dataFileWriter.EncodeBytes(nsItr.Key()); err != nil {
   253  			return nil, err
   254  		}
   255  		if err := dataFileWriter.EncodeBytes(nsItr.Value()); err != nil {
   256  			return nil, err
   257  		}
   258  		numCollectionConfigs++
   259  	}
   260  
   261  	if dataFileWriter == nil {
   262  		return nil, nil
   263  	}
   264  
   265  	dataHash, err := dataFileWriter.Done()
   266  	if err != nil {
   267  		return nil, err
   268  	}
   269  	metadataFileWriter, err := snapshot.CreateFile(filepath.Join(dir, snapshotMetadataFileName), snapshotFileFormat, newHashFunc)
   270  	if err != nil {
   271  		return nil, err
   272  	}
   273  	defer metadataFileWriter.Close()
   274  	if err = metadataFileWriter.EncodeUVarint(numCollectionConfigs); err != nil {
   275  		return nil, err
   276  	}
   277  	metadataHash, err := metadataFileWriter.Done()
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  
   282  	return map[string][]byte{
   283  		snapshotDataFileName:     dataHash,
   284  		snapshotMetadataFileName: metadataHash,
   285  	}, nil
   286  }
   287  
   288  func (r *Retriever) getImplicitCollection(chaincodeName string) ([]*peer.StaticCollectionConfig, error) {
   289  	qe, err := r.ledgerInfoRetriever.NewQueryExecutor()
   290  	if err != nil {
   291  		return nil, err
   292  	}
   293  	defer qe.Done()
   294  	return r.deployedCCInfoProvider.ImplicitCollections(r.ledgerID, chaincodeName, qe)
   295  }
   296  
   297  func prepareDBBatch(batch *batch, chaincodeCollConfigs map[string]*peer.CollectionConfigPackage, committingBlockNum uint64) error {
   298  	for ccName, collConfig := range chaincodeCollConfigs {
   299  		key := constructCollectionConfigKey(ccName)
   300  		var configBytes []byte
   301  		var err error
   302  		if configBytes, err = proto.Marshal(collConfig); err != nil {
   303  			return errors.WithStack(err)
   304  		}
   305  		batch.add(collectionConfigNamespace, key, committingBlockNum, configBytes)
   306  	}
   307  	return nil
   308  }
   309  
   310  func compositeKVToCollectionConfig(compositeKV *compositeKV) (*ledger.CollectionConfigInfo, error) {
   311  	conf := &peer.CollectionConfigPackage{}
   312  	if err := proto.Unmarshal(compositeKV.value, conf); err != nil {
   313  		return nil, errors.Wrap(err, "error unmarshalling compositeKV to collection config")
   314  	}
   315  	return &ledger.CollectionConfigInfo{
   316  		CollectionConfig:   conf,
   317  		CommittingBlockNum: compositeKV.blockNum,
   318  	}, nil
   319  }
   320  
   321  func constructCollectionConfigKey(chaincodeName string) string {
   322  	return chaincodeName + "~collection" // collection config key as in version 1.2 and we continue to use this in order to be compatible with existing data
   323  }
   324  
   325  func extractPublicUpdates(stateUpdates ledger.StateUpdates) map[string][]*kvrwset.KVWrite {
   326  	m := map[string][]*kvrwset.KVWrite{}
   327  	for ns, updates := range stateUpdates {
   328  		m[ns] = updates.PublicUpdates
   329  	}
   330  	return m
   331  }
   332  
   333  func constructCollectionConfigInfo(
   334  	compositeKV *compositeKV,
   335  	implicitColls []*peer.StaticCollectionConfig,
   336  ) (*ledger.CollectionConfigInfo, error) {
   337  	var collConf *ledger.CollectionConfigInfo
   338  	var err error
   339  
   340  	if compositeKV == nil && len(implicitColls) == 0 {
   341  		return nil, nil
   342  	}
   343  
   344  	collConf = &ledger.CollectionConfigInfo{
   345  		CollectionConfig: &peer.CollectionConfigPackage{},
   346  	}
   347  	if compositeKV != nil {
   348  		if collConf, err = compositeKVToCollectionConfig(compositeKV); err != nil {
   349  			return nil, err
   350  		}
   351  	}
   352  
   353  	for _, implicitColl := range implicitColls {
   354  		cc := &peer.CollectionConfig{}
   355  		cc.Payload = &peer.CollectionConfig_StaticCollectionConfig{StaticCollectionConfig: implicitColl}
   356  		collConf.CollectionConfig.Config = append(
   357  			collConf.CollectionConfig.Config,
   358  			cc,
   359  		)
   360  	}
   361  	return collConf, nil
   362  }
   363  
   364  // LedgerInfoRetriever retrieves the relevant info from ledger
   365  type LedgerInfoRetriever interface {
   366  	GetBlockchainInfo() (*common.BlockchainInfo, error)
   367  	NewQueryExecutor() (ledger.QueryExecutor, error)
   368  }