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 }