github.com/Hnampk/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 }