github.com/kaituanwang/hyperledger@v2.0.1+incompatible/core/committer/txvalidator/v14/validator.go (about) 1 /* 2 Copyright IBM Corp. 2016 All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package txvalidator 8 9 import ( 10 "context" 11 "fmt" 12 "time" 13 14 "github.com/golang/protobuf/proto" 15 "github.com/hyperledger/fabric-protos-go/common" 16 mspprotos "github.com/hyperledger/fabric-protos-go/msp" 17 "github.com/hyperledger/fabric-protos-go/peer" 18 "github.com/hyperledger/fabric/bccsp" 19 "github.com/hyperledger/fabric/common/channelconfig" 20 "github.com/hyperledger/fabric/common/configtx" 21 commonerrors "github.com/hyperledger/fabric/common/errors" 22 "github.com/hyperledger/fabric/common/flogging" 23 "github.com/hyperledger/fabric/core/committer/txvalidator/plugin" 24 "github.com/hyperledger/fabric/core/common/sysccprovider" 25 "github.com/hyperledger/fabric/core/common/validation" 26 "github.com/hyperledger/fabric/core/ledger" 27 ledgerUtil "github.com/hyperledger/fabric/core/ledger/util" 28 "github.com/hyperledger/fabric/msp" 29 "github.com/hyperledger/fabric/protoutil" 30 "github.com/pkg/errors" 31 ) 32 33 // Semaphore provides to the validator means for synchronisation 34 type Semaphore interface { 35 // Acquire implements semaphore-like acquire semantics 36 Acquire(ctx context.Context) error 37 38 // Release implements semaphore-like release semantics 39 Release() 40 } 41 42 // ChannelResources provides access to channel artefacts or 43 // functions to interact with them 44 type ChannelResources interface { 45 // Ledger returns the ledger associated with this validator 46 Ledger() ledger.PeerLedger 47 48 // MSPManager returns the MSP manager for this channel 49 MSPManager() msp.MSPManager 50 51 // Apply attempts to apply a configtx to become the new config 52 Apply(configtx *common.ConfigEnvelope) error 53 54 // GetMSPIDs returns the IDs for the application MSPs 55 // that have been defined in the channel 56 GetMSPIDs() []string 57 58 // Capabilities defines the capabilities for the application portion of this channel 59 Capabilities() channelconfig.ApplicationCapabilities 60 } 61 62 // private interface to decouple tx validator 63 // and vscc execution, in order to increase 64 // testability of TxValidator 65 type vsccValidator interface { 66 VSCCValidateTx(seq int, payload *common.Payload, envBytes []byte, block *common.Block) (error, peer.TxValidationCode) 67 } 68 69 // implementation of Validator interface, keeps 70 // reference to the ledger to enable tx simulation 71 // and execution of vscc 72 type TxValidator struct { 73 ChannelID string 74 Semaphore Semaphore 75 ChannelResources ChannelResources 76 Vscc vsccValidator 77 CryptoProvider bccsp.BCCSP 78 } 79 80 var logger = flogging.MustGetLogger("committer.txvalidator") 81 82 type blockValidationRequest struct { 83 block *common.Block 84 d []byte 85 tIdx int 86 } 87 88 type blockValidationResult struct { 89 tIdx int 90 validationCode peer.TxValidationCode 91 txsChaincodeName *sysccprovider.ChaincodeInstance 92 txsUpgradedChaincode *sysccprovider.ChaincodeInstance 93 err error 94 txid string 95 } 96 97 // NewTxValidator creates new transactions validator 98 func NewTxValidator(channelID string, sem Semaphore, cr ChannelResources, pm plugin.Mapper, cryptoProvider bccsp.BCCSP) *TxValidator { 99 // Encapsulates interface implementation 100 pluginValidator := NewPluginValidator(pm, cr.Ledger(), &dynamicDeserializer{cr: cr}, &dynamicCapabilities{cr: cr}) 101 return &TxValidator{ 102 ChannelID: channelID, 103 Semaphore: sem, 104 ChannelResources: cr, 105 Vscc: newVSCCValidator(channelID, cr, pluginValidator), 106 CryptoProvider: cryptoProvider, 107 } 108 } 109 110 func (v *TxValidator) chainExists(chain string) bool { 111 // TODO: implement this function! 112 return true 113 } 114 115 // Validate performs the validation of a block. The validation 116 // of each transaction in the block is performed in parallel. 117 // The approach is as follows: the committer thread starts the 118 // tx validation function in a goroutine (using a semaphore to cap 119 // the number of concurrent validating goroutines). The committer 120 // thread then reads results of validation (in orderer of completion 121 // of the goroutines) from the results channel. The goroutines 122 // perform the validation of the txs in the block and enqueue the 123 // validation result in the results channel. A few note-worthy facts: 124 // 1) to keep the approach simple, the committer thread enqueues 125 // all transactions in the block and then moves on to reading the 126 // results. 127 // 2) for parallel validation to work, it is important that the 128 // validation function does not change the state of the system. 129 // Otherwise the order in which validation is perform matters 130 // and we have to resort to sequential validation (or some locking). 131 // This is currently true, because the only function that affects 132 // state is when a config transaction is received, but they are 133 // guaranteed to be alone in the block. If/when this assumption 134 // is violated, this code must be changed. 135 func (v *TxValidator) Validate(block *common.Block) error { 136 var err error 137 var errPos int 138 139 startValidation := time.Now() // timer to log Validate block duration 140 logger.Debugf("[%s] START Block Validation for block [%d]", v.ChannelID, block.Header.Number) 141 142 // Initialize trans as not_validated here, then set invalidation reason code upon invalidation below 143 txsfltr := ledgerUtil.NewTxValidationFlags(len(block.Data.Data)) 144 // txsChaincodeNames records all the invoked chaincodes by tx in a block 145 txsChaincodeNames := make(map[int]*sysccprovider.ChaincodeInstance) 146 // upgradedChaincodes records all the chaincodes that are upgraded in a block 147 txsUpgradedChaincodes := make(map[int]*sysccprovider.ChaincodeInstance) 148 // array of txids 149 txidArray := make([]string, len(block.Data.Data)) 150 151 results := make(chan *blockValidationResult) 152 go func() { 153 for tIdx, d := range block.Data.Data { 154 // ensure that we don't have too many concurrent validation workers 155 v.Semaphore.Acquire(context.Background()) 156 157 go func(index int, data []byte) { 158 defer v.Semaphore.Release() 159 160 v.validateTx(&blockValidationRequest{ 161 d: data, 162 block: block, 163 tIdx: index, 164 }, results) 165 }(tIdx, d) 166 } 167 }() 168 169 logger.Debugf("expecting %d block validation responses", len(block.Data.Data)) 170 171 // now we read responses in the order in which they come back 172 for i := 0; i < len(block.Data.Data); i++ { 173 res := <-results 174 175 if res.err != nil { 176 // if there is an error, we buffer its value, wait for 177 // all workers to complete validation and then return 178 // the error from the first tx in this block that returned an error 179 logger.Debugf("got terminal error %s for idx %d", res.err, res.tIdx) 180 181 if err == nil || res.tIdx < errPos { 182 err = res.err 183 errPos = res.tIdx 184 } 185 } else { 186 // if there was no error, we set the txsfltr and we set the 187 // txsChaincodeNames and txsUpgradedChaincodes maps 188 logger.Debugf("got result for idx %d, code %d", res.tIdx, res.validationCode) 189 190 txsfltr.SetFlag(res.tIdx, res.validationCode) 191 192 if res.validationCode == peer.TxValidationCode_VALID { 193 if res.txsChaincodeName != nil { 194 txsChaincodeNames[res.tIdx] = res.txsChaincodeName 195 } 196 if res.txsUpgradedChaincode != nil { 197 txsUpgradedChaincodes[res.tIdx] = res.txsUpgradedChaincode 198 } 199 txidArray[res.tIdx] = res.txid 200 } 201 } 202 } 203 204 // if we're here, all workers have completed the validation. 205 // If there was an error we return the error from the first 206 // tx in this block that returned an error 207 if err != nil { 208 return err 209 } 210 211 // if we operate with this capability, we mark invalid any transaction that has a txid 212 // which is equal to that of a previous tx in this block 213 if v.ChannelResources.Capabilities().ForbidDuplicateTXIdInBlock() { 214 markTXIdDuplicates(txidArray, txsfltr) 215 } 216 217 // if we're here, all workers have completed validation and 218 // no error was reported; we set the tx filter and return 219 // success 220 v.invalidTXsForUpgradeCC(txsChaincodeNames, txsUpgradedChaincodes, txsfltr) 221 222 // make sure no transaction has skipped validation 223 err = v.allValidated(txsfltr, block) 224 if err != nil { 225 return err 226 } 227 228 // Initialize metadata structure 229 protoutil.InitBlockMetadata(block) 230 231 block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] = txsfltr 232 233 elapsedValidation := time.Since(startValidation) / time.Millisecond // duration in ms 234 logger.Infof("[%s] Validated block [%d] in %dms", v.ChannelID, block.Header.Number, elapsedValidation) 235 236 return nil 237 } 238 239 // allValidated returns error if some of the validation flags have not been set 240 // during validation 241 func (v *TxValidator) allValidated(txsfltr ledgerUtil.TxValidationFlags, block *common.Block) error { 242 for id, f := range txsfltr { 243 if peer.TxValidationCode(f) == peer.TxValidationCode_NOT_VALIDATED { 244 return errors.Errorf("transaction %d in block %d has skipped validation", id, block.Header.Number) 245 } 246 } 247 248 return nil 249 } 250 251 func markTXIdDuplicates(txids []string, txsfltr ledgerUtil.TxValidationFlags) { 252 txidMap := make(map[string]struct{}) 253 254 for id, txid := range txids { 255 if txid == "" { 256 continue 257 } 258 259 _, in := txidMap[txid] 260 if in { 261 logger.Error("Duplicate txid", txid, "found, skipping") 262 txsfltr.SetFlag(id, peer.TxValidationCode_DUPLICATE_TXID) 263 } else { 264 txidMap[txid] = struct{}{} 265 } 266 } 267 } 268 269 func (v *TxValidator) validateTx(req *blockValidationRequest, results chan<- *blockValidationResult) { 270 block := req.block 271 d := req.d 272 tIdx := req.tIdx 273 txID := "" 274 275 if d == nil { 276 results <- &blockValidationResult{ 277 tIdx: tIdx, 278 } 279 return 280 } 281 282 if env, err := protoutil.GetEnvelopeFromBlock(d); err != nil { 283 logger.Warningf("Error getting tx from block: %+v", err) 284 results <- &blockValidationResult{ 285 tIdx: tIdx, 286 validationCode: peer.TxValidationCode_INVALID_OTHER_REASON, 287 } 288 return 289 } else if env != nil { 290 // validate the transaction: here we check that the transaction 291 // is properly formed, properly signed and that the security 292 // chain binding proposal to endorsements to tx holds. We do 293 // NOT check the validity of endorsements, though. That's a 294 // job for VSCC below 295 logger.Debugf("[%s] validateTx starts for block %p env %p txn %d", v.ChannelID, block, env, tIdx) 296 defer logger.Debugf("[%s] validateTx completes for block %p env %p txn %d", v.ChannelID, block, env, tIdx) 297 var payload *common.Payload 298 var err error 299 var txResult peer.TxValidationCode 300 var txsChaincodeName *sysccprovider.ChaincodeInstance 301 var txsUpgradedChaincode *sysccprovider.ChaincodeInstance 302 303 if payload, txResult = validation.ValidateTransaction(env, v.CryptoProvider); txResult != peer.TxValidationCode_VALID { 304 logger.Errorf("Invalid transaction with index %d", tIdx) 305 results <- &blockValidationResult{ 306 tIdx: tIdx, 307 validationCode: txResult, 308 } 309 return 310 } 311 312 chdr, err := protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader) 313 if err != nil { 314 logger.Warningf("Could not unmarshal channel header, err %s, skipping", err) 315 results <- &blockValidationResult{ 316 tIdx: tIdx, 317 validationCode: peer.TxValidationCode_INVALID_OTHER_REASON, 318 } 319 return 320 } 321 322 channel := chdr.ChannelId 323 logger.Debugf("Transaction is for channel %s", channel) 324 325 if !v.chainExists(channel) { 326 logger.Errorf("Dropping transaction for non-existent channel %s", channel) 327 results <- &blockValidationResult{ 328 tIdx: tIdx, 329 validationCode: peer.TxValidationCode_TARGET_CHAIN_NOT_FOUND, 330 } 331 return 332 } 333 334 if common.HeaderType(chdr.Type) == common.HeaderType_ENDORSER_TRANSACTION { 335 336 txID = chdr.TxId 337 338 // Check duplicate transactions 339 erroneousResultEntry := v.checkTxIdDupsLedger(tIdx, chdr, v.ChannelResources.Ledger()) 340 if erroneousResultEntry != nil { 341 results <- erroneousResultEntry 342 return 343 } 344 345 // Validate tx with vscc and policy 346 logger.Debug("Validating transaction vscc tx validate") 347 err, cde := v.Vscc.VSCCValidateTx(tIdx, payload, d, block) 348 if err != nil { 349 logger.Errorf("VSCCValidateTx for transaction txId = %s returned error: %s", txID, err) 350 switch err.(type) { 351 case *commonerrors.VSCCExecutionFailureError: 352 results <- &blockValidationResult{ 353 tIdx: tIdx, 354 err: err, 355 } 356 return 357 case *commonerrors.VSCCInfoLookupFailureError: 358 results <- &blockValidationResult{ 359 tIdx: tIdx, 360 err: err, 361 } 362 return 363 default: 364 results <- &blockValidationResult{ 365 tIdx: tIdx, 366 validationCode: cde, 367 } 368 return 369 } 370 } 371 372 invokeCC, upgradeCC, err := v.getTxCCInstance(payload) 373 if err != nil { 374 logger.Errorf("Get chaincode instance from transaction txId = %s returned error: %+v", txID, err) 375 results <- &blockValidationResult{ 376 tIdx: tIdx, 377 validationCode: peer.TxValidationCode_INVALID_OTHER_REASON, 378 } 379 return 380 } 381 txsChaincodeName = invokeCC 382 if upgradeCC != nil { 383 logger.Infof("Find chaincode upgrade transaction for chaincode %s on channel %s with new version %s", upgradeCC.ChaincodeName, upgradeCC.ChannelID, upgradeCC.ChaincodeVersion) 384 txsUpgradedChaincode = upgradeCC 385 } 386 } else if common.HeaderType(chdr.Type) == common.HeaderType_CONFIG { 387 configEnvelope, err := configtx.UnmarshalConfigEnvelope(payload.Data) 388 if err != nil { 389 err = errors.WithMessage(err, "error unmarshalling config which passed initial validity checks") 390 logger.Criticalf("%+v", err) 391 results <- &blockValidationResult{ 392 tIdx: tIdx, 393 err: err, 394 } 395 return 396 } 397 398 if err := v.ChannelResources.Apply(configEnvelope); err != nil { 399 err = errors.WithMessage(err, "error validating config which passed initial validity checks") 400 logger.Criticalf("%+v", err) 401 results <- &blockValidationResult{ 402 tIdx: tIdx, 403 err: err, 404 } 405 return 406 } 407 logger.Debugf("config transaction received for chain %s", channel) 408 } else { 409 logger.Warningf("Unknown transaction type [%s] in block number [%d] transaction index [%d]", 410 common.HeaderType(chdr.Type), block.Header.Number, tIdx) 411 results <- &blockValidationResult{ 412 tIdx: tIdx, 413 validationCode: peer.TxValidationCode_UNKNOWN_TX_TYPE, 414 } 415 return 416 } 417 418 if _, err := proto.Marshal(env); err != nil { 419 logger.Warningf("Cannot marshal transaction: %s", err) 420 results <- &blockValidationResult{ 421 tIdx: tIdx, 422 validationCode: peer.TxValidationCode_MARSHAL_TX_ERROR, 423 } 424 return 425 } 426 // Succeeded to pass down here, transaction is valid 427 results <- &blockValidationResult{ 428 tIdx: tIdx, 429 txsChaincodeName: txsChaincodeName, 430 txsUpgradedChaincode: txsUpgradedChaincode, 431 validationCode: peer.TxValidationCode_VALID, 432 txid: txID, 433 } 434 return 435 } else { 436 logger.Warning("Nil tx from block") 437 results <- &blockValidationResult{ 438 tIdx: tIdx, 439 validationCode: peer.TxValidationCode_NIL_ENVELOPE, 440 } 441 return 442 } 443 } 444 445 // CheckTxIdDupsLedger returns a vlockValidationResult enhanced with the respective 446 // error codes if and only if there is transaction with the same transaction identifier 447 // in the ledger or no decision can be made for whether such transaction exists; 448 // the function returns nil if it has ensured that there is no such duplicate, such 449 // that its consumer can proceed with the transaction processing 450 func (v *TxValidator) checkTxIdDupsLedger(tIdx int, chdr *common.ChannelHeader, ldgr ledger.PeerLedger) *blockValidationResult { 451 452 // Retrieve the transaction identifier of the input header 453 txID := chdr.TxId 454 455 // Look for a transaction with the same identifier inside the ledger 456 _, err := ldgr.GetTransactionByID(txID) 457 458 switch err.(type) { 459 case nil: 460 // invalid case, returned error is nil. It means that there is already a tx in the ledger with the same id 461 logger.Error("Duplicate transaction found, ", txID, ", skipping") 462 return &blockValidationResult{ 463 tIdx: tIdx, 464 validationCode: peer.TxValidationCode_DUPLICATE_TXID, 465 } 466 case ledger.NotFoundInIndexErr: 467 // valid case, returned error is of type NotFoundInIndexErr. 468 // It means that no tx with the same id is found in the ledger 469 return nil 470 default: 471 // invalid case, returned error is not of type NotFoundInIndexErr. 472 // It means that we could not verify whether a tx with the supplied id is in the ledger 473 logger.Errorf("Ledger failure while attempting to detect duplicate status for txid %s: %s", txID, err) 474 return &blockValidationResult{ 475 tIdx: tIdx, 476 err: err, 477 } 478 } 479 } 480 481 // generateCCKey generates a unique identifier for chaincode in specific channel 482 func (v *TxValidator) generateCCKey(ccName, chainID string) string { 483 return fmt.Sprintf("%s/%s", ccName, chainID) 484 } 485 486 // invalidTXsForUpgradeCC invalid all txs that should be invalided because of chaincode upgrade txs 487 func (v *TxValidator) invalidTXsForUpgradeCC(txsChaincodeNames map[int]*sysccprovider.ChaincodeInstance, txsUpgradedChaincodes map[int]*sysccprovider.ChaincodeInstance, txsfltr ledgerUtil.TxValidationFlags) { 488 if len(txsUpgradedChaincodes) == 0 { 489 return 490 } 491 492 // Invalid former cc upgrade txs if there're two or more txs upgrade the same cc 493 finalValidUpgradeTXs := make(map[string]int) 494 upgradedChaincodes := make(map[string]*sysccprovider.ChaincodeInstance) 495 for tIdx, cc := range txsUpgradedChaincodes { 496 if cc == nil { 497 continue 498 } 499 upgradedCCKey := v.generateCCKey(cc.ChaincodeName, cc.ChannelID) 500 501 if finalIdx, exist := finalValidUpgradeTXs[upgradedCCKey]; !exist { 502 finalValidUpgradeTXs[upgradedCCKey] = tIdx 503 upgradedChaincodes[upgradedCCKey] = cc 504 } else if finalIdx < tIdx { 505 logger.Infof("Invalid transaction with index %d: chaincode was upgraded by latter tx", finalIdx) 506 txsfltr.SetFlag(finalIdx, peer.TxValidationCode_CHAINCODE_VERSION_CONFLICT) 507 508 // record latter cc upgrade tx info 509 finalValidUpgradeTXs[upgradedCCKey] = tIdx 510 upgradedChaincodes[upgradedCCKey] = cc 511 } else { 512 logger.Infof("Invalid transaction with index %d: chaincode was upgraded by latter tx", tIdx) 513 txsfltr.SetFlag(tIdx, peer.TxValidationCode_CHAINCODE_VERSION_CONFLICT) 514 } 515 } 516 517 // invalid txs which invoke the upgraded chaincodes 518 for tIdx, cc := range txsChaincodeNames { 519 if cc == nil { 520 continue 521 } 522 ccKey := v.generateCCKey(cc.ChaincodeName, cc.ChannelID) 523 if _, exist := upgradedChaincodes[ccKey]; exist { 524 if txsfltr.IsValid(tIdx) { 525 logger.Infof("Invalid transaction with index %d: chaincode was upgraded in the same block", tIdx) 526 txsfltr.SetFlag(tIdx, peer.TxValidationCode_CHAINCODE_VERSION_CONFLICT) 527 } 528 } 529 } 530 } 531 532 func (v *TxValidator) getTxCCInstance(payload *common.Payload) (invokeCCIns, upgradeCCIns *sysccprovider.ChaincodeInstance, err error) { 533 // This is duplicated unpacking work, but make test easier. 534 chdr, err := protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader) 535 if err != nil { 536 return nil, nil, err 537 } 538 539 // Channel ID 540 channelID := chdr.ChannelId // it is guaranteed to be an existing channel by now 541 542 // ChaincodeID 543 hdrExt, err := protoutil.UnmarshalChaincodeHeaderExtension(chdr.Extension) 544 if err != nil { 545 return nil, nil, err 546 } 547 invokeCC := hdrExt.ChaincodeId 548 invokeIns := &sysccprovider.ChaincodeInstance{ChannelID: channelID, ChaincodeName: invokeCC.Name, ChaincodeVersion: invokeCC.Version} 549 550 // Transaction 551 tx, err := protoutil.UnmarshalTransaction(payload.Data) 552 if err != nil { 553 logger.Errorf("GetTransaction failed: %+v", err) 554 return invokeIns, nil, nil 555 } 556 557 // ChaincodeActionPayload 558 cap, err := protoutil.UnmarshalChaincodeActionPayload(tx.Actions[0].Payload) 559 if err != nil { 560 logger.Errorf("GetChaincodeActionPayload failed: %+v", err) 561 return invokeIns, nil, nil 562 } 563 564 // ChaincodeProposalPayload 565 cpp, err := protoutil.UnmarshalChaincodeProposalPayload(cap.ChaincodeProposalPayload) 566 if err != nil { 567 logger.Errorf("GetChaincodeProposalPayload failed: %+v", err) 568 return invokeIns, nil, nil 569 } 570 571 // ChaincodeInvocationSpec 572 cis := &peer.ChaincodeInvocationSpec{} 573 err = proto.Unmarshal(cpp.Input, cis) 574 if err != nil { 575 logger.Errorf("GetChaincodeInvokeSpec failed: %+v", err) 576 return invokeIns, nil, nil 577 } 578 579 if invokeCC.Name == "lscc" { 580 if string(cis.ChaincodeSpec.Input.Args[0]) == "upgrade" { 581 upgradeIns, err := v.getUpgradeTxInstance(channelID, cis.ChaincodeSpec.Input.Args[2]) 582 if err != nil { 583 return invokeIns, nil, nil 584 } 585 return invokeIns, upgradeIns, nil 586 } 587 } 588 589 return invokeIns, nil, nil 590 } 591 592 func (v *TxValidator) getUpgradeTxInstance(channelID string, cdsBytes []byte) (*sysccprovider.ChaincodeInstance, error) { 593 cds, err := protoutil.UnmarshalChaincodeDeploymentSpec(cdsBytes) 594 if err != nil { 595 return nil, err 596 } 597 598 if cds.ChaincodeSpec.Type.String() != "GOLANG" { 599 return nil, errors.Errorf("unexpected chaincode type: %s", cds.ChaincodeSpec.Type.String()) 600 } 601 602 return &sysccprovider.ChaincodeInstance{ 603 ChannelID: channelID, 604 ChaincodeName: cds.ChaincodeSpec.ChaincodeId.Name, 605 ChaincodeVersion: cds.ChaincodeSpec.ChaincodeId.Version, 606 }, nil 607 } 608 609 type dynamicDeserializer struct { 610 cr ChannelResources 611 } 612 613 func (ds *dynamicDeserializer) DeserializeIdentity(serializedIdentity []byte) (msp.Identity, error) { 614 return ds.cr.MSPManager().DeserializeIdentity(serializedIdentity) 615 } 616 617 func (ds *dynamicDeserializer) IsWellFormed(identity *mspprotos.SerializedIdentity) error { 618 return ds.cr.MSPManager().IsWellFormed(identity) 619 } 620 621 type dynamicCapabilities struct { 622 cr ChannelResources 623 } 624 625 func (ds *dynamicCapabilities) ACLs() bool { 626 return ds.cr.Capabilities().ACLs() 627 } 628 629 func (ds *dynamicCapabilities) CollectionUpgrade() bool { 630 return ds.cr.Capabilities().CollectionUpgrade() 631 } 632 633 func (ds *dynamicCapabilities) StorePvtDataOfInvalidTx() bool { 634 return ds.cr.Capabilities().StorePvtDataOfInvalidTx() 635 } 636 637 func (ds *dynamicCapabilities) ForbidDuplicateTXIdInBlock() bool { 638 return ds.cr.Capabilities().ForbidDuplicateTXIdInBlock() 639 } 640 641 func (ds *dynamicCapabilities) KeyLevelEndorsement() bool { 642 return ds.cr.Capabilities().KeyLevelEndorsement() 643 } 644 645 func (ds *dynamicCapabilities) MetadataLifecycle() bool { 646 // This capability no longer exists and should not be referenced in validation anyway 647 return false 648 } 649 650 func (ds *dynamicCapabilities) PrivateChannelData() bool { 651 return ds.cr.Capabilities().PrivateChannelData() 652 } 653 654 func (ds *dynamicCapabilities) Supported() error { 655 return ds.cr.Capabilities().Supported() 656 } 657 658 func (ds *dynamicCapabilities) V1_1Validation() bool { 659 return ds.cr.Capabilities().V1_1Validation() 660 } 661 662 func (ds *dynamicCapabilities) V1_2Validation() bool { 663 return ds.cr.Capabilities().V1_2Validation() 664 } 665 666 func (ds *dynamicCapabilities) V1_3Validation() bool { 667 return ds.cr.Capabilities().V1_3Validation() 668 } 669 670 func (ds *dynamicCapabilities) V2_0Validation() bool { 671 return ds.cr.Capabilities().V2_0Validation() 672 }