github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/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/osdi23p228/fabric/common/util" 19 "github.com/osdi23p228/fabric/core/committer" 20 "github.com/osdi23p228/fabric/core/ledger" 21 "github.com/osdi23p228/fabric/gossip/metrics" 22 privdatacommon "github.com/osdi23p228/fabric/gossip/privdata/common" 23 "github.com/osdi23p228/fabric/gossip/util" 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 76 func (*NoOpReconciler) Start() { 77 // do nothing 78 logger.Debug("Private data reconciliation has been disabled") 79 } 80 81 func (*NoOpReconciler) Stop() { 82 // do nothing 83 } 84 85 // NewReconciler creates a new instance of reconciler 86 func NewReconciler(channel string, metrics *metrics.PrivdataMetrics, c committer.Committer, 87 fetcher ReconciliationFetcher, config *PrivdataConfig) *Reconciler { 88 reconcilerLogger := logger.With("channel", channel) 89 reconcilerLogger.Debug("Private data reconciliation is enabled") 90 return &Reconciler{ 91 channel: channel, 92 logger: reconcilerLogger, 93 metrics: metrics, 94 ReconcileSleepInterval: config.ReconcileSleepInterval, 95 ReconcileBatchSize: config.ReconcileBatchSize, 96 Committer: c, 97 ReconciliationFetcher: fetcher, 98 stopChan: make(chan struct{}), 99 } 100 } 101 102 func (r *Reconciler) Stop() { 103 r.stopOnce.Do(func() { 104 close(r.stopChan) 105 }) 106 } 107 108 func (r *Reconciler) Start() { 109 r.startOnce.Do(func() { 110 go r.run() 111 }) 112 } 113 114 func (r *Reconciler) run() { 115 for { 116 select { 117 case <-r.stopChan: 118 return 119 case <-time.After(r.ReconcileSleepInterval): 120 r.logger.Debug("Start reconcile missing private info") 121 if err := r.reconcile(); err != nil { 122 r.logger.Error("Failed to reconcile missing private info, error: ", err.Error()) 123 break 124 } 125 } 126 } 127 } 128 129 // returns the number of items that were reconciled , minBlock, maxBlock (blocks range) and an error 130 func (r *Reconciler) reconcile() error { 131 missingPvtDataTracker, err := r.GetMissingPvtDataTracker() 132 if err != nil { 133 r.logger.Error("reconciliation error when trying to get missingPvtDataTracker:", err) 134 return err 135 } 136 if missingPvtDataTracker == nil { 137 r.logger.Error("got nil as MissingPvtDataTracker, exiting...") 138 return errors.New("got nil as MissingPvtDataTracker, exiting...") 139 } 140 totalReconciled, minBlock, maxBlock := 0, uint64(math.MaxUint64), uint64(0) 141 142 defer r.reportReconciliationDuration(time.Now()) 143 144 for { 145 missingPvtDataInfo, err := missingPvtDataTracker.GetMissingPvtDataInfoForMostRecentBlocks(r.ReconcileBatchSize) 146 if err != nil { 147 r.logger.Error("reconciliation error when trying to get missing pvt data info recent blocks:", err) 148 return err 149 } 150 // if missingPvtDataInfo is nil, len will return 0 151 if len(missingPvtDataInfo) == 0 { 152 if totalReconciled > 0 { 153 r.logger.Infof("Reconciliation cycle finished successfully. reconciled %d private data keys from blocks range [%d - %d]", totalReconciled, minBlock, maxBlock) 154 } else { 155 r.logger.Debug("Reconciliation cycle finished successfully. no items to reconcile") 156 } 157 return nil 158 } 159 160 r.logger.Debug("got from ledger", len(missingPvtDataInfo), "blocks with missing private data, trying to reconcile...") 161 162 dig2collectionCfg, minB, maxB := r.getDig2CollectionConfig(missingPvtDataInfo) 163 fetchedData, err := r.FetchReconciledItems(dig2collectionCfg) 164 if err != nil { 165 r.logger.Error("reconciliation error when trying to fetch missing items from different peers:", err) 166 return err 167 } 168 169 pvtDataToCommit := r.preparePvtDataToCommit(fetchedData.AvailableElements) 170 unreconciled := constructUnreconciledMissingData(dig2collectionCfg, fetchedData.AvailableElements) 171 pvtdataHashMismatch, err := r.CommitPvtDataOfOldBlocks(pvtDataToCommit, unreconciled) 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 r.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 r.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 r.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 } 319 320 func constructUnreconciledMissingData(requestedMissingData privdatacommon.Dig2CollectionConfig, fetchedData []*protosgossip.PvtDataElement) ledger.MissingPvtDataInfo { 321 fetchedDataKeys := make(map[privdatacommon.DigKey]struct{}) 322 for _, pvtData := range fetchedData { 323 key := privdatacommon.DigKey{ 324 TxId: pvtData.Digest.TxId, 325 Namespace: pvtData.Digest.Namespace, 326 Collection: pvtData.Digest.Collection, 327 BlockSeq: pvtData.Digest.BlockSeq, 328 SeqInBlock: pvtData.Digest.SeqInBlock, 329 } 330 fetchedDataKeys[key] = struct{}{} 331 } 332 333 var unreconciledMissingDataInfo ledger.MissingPvtDataInfo 334 for key := range requestedMissingData { 335 if _, ok := fetchedDataKeys[key]; !ok { 336 if unreconciledMissingDataInfo == nil { 337 unreconciledMissingDataInfo = make(ledger.MissingPvtDataInfo) 338 } 339 unreconciledMissingDataInfo.Add(key.BlockSeq, key.SeqInBlock, key.Namespace, key.Collection) 340 } 341 } 342 return unreconciledMissingDataInfo 343 }