github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/gossip/privdata/reconcile.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 "encoding/hex" 11 "fmt" 12 "math" 13 "sync" 14 "time" 15 16 commonutil "github.com/hechain20/hechain/common/util" 17 "github.com/hechain20/hechain/core/committer" 18 "github.com/hechain20/hechain/core/ledger" 19 "github.com/hechain20/hechain/gossip/metrics" 20 privdatacommon "github.com/hechain20/hechain/gossip/privdata/common" 21 "github.com/hechain20/hechain/gossip/util" 22 protosgossip "github.com/hyperledger/fabric-protos-go/gossip" 23 "github.com/hyperledger/fabric-protos-go/peer" 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 func (*NoOpReconciler) Start() { 76 // do nothing 77 logger.Debug("Private data reconciliation has been disabled") 78 } 79 80 func (*NoOpReconciler) Stop() { 81 // do nothing 82 } 83 84 // NewReconciler creates a new instance of reconciler 85 func NewReconciler(channel string, metrics *metrics.PrivdataMetrics, c committer.Committer, 86 fetcher ReconciliationFetcher, config *PrivdataConfig) *Reconciler { 87 reconcilerLogger := logger.With("channel", channel) 88 reconcilerLogger.Debug("Private data reconciliation is enabled") 89 return &Reconciler{ 90 channel: channel, 91 logger: reconcilerLogger, 92 metrics: metrics, 93 ReconcileSleepInterval: config.ReconcileSleepInterval, 94 ReconcileBatchSize: config.ReconcileBatchSize, 95 Committer: c, 96 ReconciliationFetcher: fetcher, 97 stopChan: make(chan struct{}), 98 } 99 } 100 101 func (r *Reconciler) Stop() { 102 r.stopOnce.Do(func() { 103 close(r.stopChan) 104 }) 105 } 106 107 func (r *Reconciler) Start() { 108 r.startOnce.Do(func() { 109 go r.run() 110 }) 111 } 112 113 func (r *Reconciler) run() { 114 for { 115 select { 116 case <-r.stopChan: 117 return 118 case <-time.After(r.ReconcileSleepInterval): 119 r.logger.Debug("Start reconcile missing private info") 120 if err := r.reconcile(); err != nil { 121 r.logger.Error("Failed to reconcile missing private info, error: ", err.Error()) 122 } 123 } 124 } 125 } 126 127 // returns the number of items that were reconciled , minBlock, maxBlock (blocks range) and an error 128 func (r *Reconciler) reconcile() error { 129 missingPvtDataTracker, err := r.GetMissingPvtDataTracker() 130 if err != nil { 131 r.logger.Error("reconciliation error when trying to get missingPvtDataTracker:", err) 132 return err 133 } 134 if missingPvtDataTracker == nil { 135 r.logger.Error("got nil as MissingPvtDataTracker, exiting...") 136 return errors.New("got nil as MissingPvtDataTracker, exiting...") 137 } 138 totalReconciled, minBlock, maxBlock := 0, uint64(math.MaxUint64), uint64(0) 139 140 defer r.reportReconciliationDuration(time.Now()) 141 142 for { 143 missingPvtDataInfo, err := missingPvtDataTracker.GetMissingPvtDataInfoForMostRecentBlocks(r.ReconcileBatchSize) 144 if err != nil { 145 r.logger.Error("reconciliation error when trying to get missing pvt data info recent blocks:", err) 146 return err 147 } 148 // if missingPvtDataInfo is nil, len will return 0 149 if len(missingPvtDataInfo) == 0 { 150 if totalReconciled > 0 { 151 r.logger.Infof("Reconciliation cycle finished successfully. reconciled %d private data keys from blocks range [%d - %d]", totalReconciled, minBlock, maxBlock) 152 } else { 153 r.logger.Debug("Reconciliation cycle finished successfully. no items to reconcile") 154 } 155 return nil 156 } 157 158 r.logger.Debug("got from ledger", len(missingPvtDataInfo), "blocks with missing private data, trying to reconcile...") 159 160 dig2collectionCfg, minB, maxB := r.getDig2CollectionConfig(missingPvtDataInfo) 161 fetchedData, err := r.FetchReconciledItems(dig2collectionCfg) 162 if err != nil { 163 r.logger.Error("reconciliation error when trying to fetch missing items from different peers:", err) 164 return err 165 } 166 167 pvtDataToCommit := r.preparePvtDataToCommit(fetchedData.AvailableElements) 168 unreconciled := constructUnreconciledMissingData(dig2collectionCfg, fetchedData.AvailableElements) 169 pvtdataHashMismatch, err := r.CommitPvtDataOfOldBlocks(pvtDataToCommit, unreconciled) 170 if err != nil { 171 return errors.Wrap(err, "failed to commit private data") 172 } 173 r.logMismatched(pvtdataHashMismatch) 174 if minB < minBlock { 175 minBlock = minB 176 } 177 if maxB > maxBlock { 178 maxBlock = maxB 179 } 180 totalReconciled += len(fetchedData.AvailableElements) 181 } 182 } 183 184 func (r *Reconciler) reportReconciliationDuration(startTime time.Time) { 185 r.metrics.ReconciliationDuration.With("channel", r.channel).Observe(time.Since(startTime).Seconds()) 186 } 187 188 type collectionConfigKey struct { 189 chaincodeName, collectionName string 190 blockNum uint64 191 } 192 193 func (r *Reconciler) getDig2CollectionConfig(missingPvtDataInfo ledger.MissingPvtDataInfo) (privdatacommon.Dig2CollectionConfig, uint64, uint64) { 194 var minBlock, maxBlock uint64 195 minBlock = math.MaxUint64 196 maxBlock = 0 197 collectionConfigCache := make(map[collectionConfigKey]*peer.StaticCollectionConfig) 198 dig2collectionCfg := make(map[privdatacommon.DigKey]*peer.StaticCollectionConfig) 199 for blockNum, blockPvtDataInfo := range missingPvtDataInfo { 200 if blockNum < minBlock { 201 minBlock = blockNum 202 } 203 if blockNum > maxBlock { 204 maxBlock = blockNum 205 } 206 for seqInBlock, collectionPvtDataInfo := range blockPvtDataInfo { 207 for _, pvtDataInfo := range collectionPvtDataInfo { 208 collConfigKey := collectionConfigKey{ 209 chaincodeName: pvtDataInfo.Namespace, 210 collectionName: pvtDataInfo.Collection, 211 blockNum: blockNum, 212 } 213 if _, exists := collectionConfigCache[collConfigKey]; !exists { 214 collectionConfig, err := r.getMostRecentCollectionConfig(pvtDataInfo.Namespace, pvtDataInfo.Collection, blockNum) 215 if err != nil { 216 r.logger.Debug(err) 217 continue 218 } 219 collectionConfigCache[collConfigKey] = collectionConfig 220 } 221 digKey := privdatacommon.DigKey{ 222 SeqInBlock: seqInBlock, 223 Collection: pvtDataInfo.Collection, 224 Namespace: pvtDataInfo.Namespace, 225 BlockSeq: blockNum, 226 } 227 dig2collectionCfg[digKey] = collectionConfigCache[collConfigKey] 228 } 229 } 230 } 231 return dig2collectionCfg, minBlock, maxBlock 232 } 233 234 func (r *Reconciler) getMostRecentCollectionConfig(chaincodeName string, collectionName string, blockNum uint64) (*peer.StaticCollectionConfig, error) { 235 configHistoryRetriever, err := r.GetConfigHistoryRetriever() 236 if err != nil { 237 return nil, errors.Wrap(err, "configHistoryRetriever is not available") 238 } 239 240 configInfo, err := configHistoryRetriever.MostRecentCollectionConfigBelow(blockNum, chaincodeName) 241 if err != nil { 242 return nil, errors.New(fmt.Sprintf("cannot find recent collection config update below block sequence = %d for chaincode %s", blockNum, chaincodeName)) 243 } 244 if configInfo == nil { 245 return nil, errors.New(fmt.Sprintf("no collection config update below block sequence = %d for chaincode %s is available", blockNum, chaincodeName)) 246 } 247 248 collectionConfig := extractCollectionConfig(configInfo.CollectionConfig, collectionName) 249 if collectionConfig == nil { 250 return nil, errors.New(fmt.Sprintf("no collection config was found for collection %s for chaincode %s", collectionName, chaincodeName)) 251 } 252 253 staticCollectionConfig, wasCastingSuccessful := collectionConfig.Payload.(*peer.CollectionConfig_StaticCollectionConfig) 254 if !wasCastingSuccessful { 255 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)) 256 } 257 return staticCollectionConfig.StaticCollectionConfig, nil 258 } 259 260 func (r *Reconciler) preparePvtDataToCommit(elements []*protosgossip.PvtDataElement) []*ledger.ReconciledPvtdata { 261 rwSetByBlockByKeys := r.groupRwsetByBlock(elements) 262 263 // populate the private RWSets passed to the ledger 264 var pvtDataToCommit []*ledger.ReconciledPvtdata 265 266 for blockNum, rwSetKeys := range rwSetByBlockByKeys { 267 blockPvtData := &ledger.ReconciledPvtdata{ 268 BlockNum: blockNum, 269 WriteSets: make(map[uint64]*ledger.TxPvtData), 270 } 271 for seqInBlock, nsRWS := range rwSetKeys.bySeqsInBlock() { 272 rwsets := nsRWS.toRWSet() 273 r.logger.Debugf("Preparing to commit [%d] private write set, missed from transaction index [%d] of block number [%d]", len(rwsets.NsPvtRwset), seqInBlock, blockNum) 274 blockPvtData.WriteSets[seqInBlock] = &ledger.TxPvtData{ 275 SeqInBlock: seqInBlock, 276 WriteSet: rwsets, 277 } 278 } 279 pvtDataToCommit = append(pvtDataToCommit, blockPvtData) 280 } 281 return pvtDataToCommit 282 } 283 284 func (r *Reconciler) logMismatched(pvtdataMismatched []*ledger.PvtdataHashMismatch) { 285 if len(pvtdataMismatched) > 0 { 286 for _, hashMismatch := range pvtdataMismatched { 287 r.logger.Warningf("failed to reconcile pvtdata chaincode %s, collection %s, block num %d, tx num %d due to hash mismatch or partially available bootKVs", 288 hashMismatch.Namespace, hashMismatch.Collection, hashMismatch.BlockNum, hashMismatch.TxNum) 289 } 290 } 291 } 292 293 // return a mapping from block num to rwsetByKeys 294 func (r *Reconciler) groupRwsetByBlock(elements []*protosgossip.PvtDataElement) map[uint64]rwsetByKeys { 295 rwSetByBlockByKeys := make(map[uint64]rwsetByKeys) // map from block num to rwsetByKeys 296 297 // Iterate over data fetched from peers 298 for _, element := range elements { 299 dig := element.Digest 300 if _, exists := rwSetByBlockByKeys[dig.BlockSeq]; !exists { 301 rwSetByBlockByKeys[dig.BlockSeq] = make(map[rwSetKey][]byte) 302 } 303 for _, rws := range element.Payload { 304 hash := hex.EncodeToString(commonutil.ComputeSHA256(rws)) 305 key := rwSetKey{ 306 txID: dig.TxId, 307 namespace: dig.Namespace, 308 collection: dig.Collection, 309 seqInBlock: dig.SeqInBlock, 310 hash: hash, 311 } 312 rwSetByBlockByKeys[dig.BlockSeq][key] = rws 313 } 314 } 315 return rwSetByBlockByKeys 316 } 317 318 func constructUnreconciledMissingData(requestedMissingData privdatacommon.Dig2CollectionConfig, fetchedData []*protosgossip.PvtDataElement) ledger.MissingPvtDataInfo { 319 fetchedDataKeys := make(map[privdatacommon.DigKey]struct{}) 320 for _, pvtData := range fetchedData { 321 key := privdatacommon.DigKey{ 322 TxId: pvtData.Digest.TxId, 323 Namespace: pvtData.Digest.Namespace, 324 Collection: pvtData.Digest.Collection, 325 BlockSeq: pvtData.Digest.BlockSeq, 326 SeqInBlock: pvtData.Digest.SeqInBlock, 327 } 328 fetchedDataKeys[key] = struct{}{} 329 } 330 331 var unreconciledMissingDataInfo ledger.MissingPvtDataInfo 332 for key := range requestedMissingData { 333 if _, ok := fetchedDataKeys[key]; !ok { 334 if unreconciledMissingDataInfo == nil { 335 unreconciledMissingDataInfo = make(ledger.MissingPvtDataInfo) 336 } 337 unreconciledMissingDataInfo.Add(key.BlockSeq, key.SeqInBlock, key.Namespace, key.Collection) 338 } 339 } 340 return unreconciledMissingDataInfo 341 }