github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/committer/txvalidator/v14/validator.go (about) 1 /* 2 Copyright hechain. 2022 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/hechain20/hechain/bccsp" 16 "github.com/hechain20/hechain/common/channelconfig" 17 "github.com/hechain20/hechain/common/configtx" 18 commonerrors "github.com/hechain20/hechain/common/errors" 19 "github.com/hechain20/hechain/common/flogging" 20 "github.com/hechain20/hechain/core/committer/txvalidator/plugin" 21 "github.com/hechain20/hechain/core/common/sysccprovider" 22 "github.com/hechain20/hechain/core/common/validation" 23 "github.com/hechain20/hechain/core/ledger" 24 "github.com/hechain20/hechain/internal/pkg/txflags" 25 "github.com/hechain20/hechain/msp" 26 "github.com/hechain20/hechain/protoutil" 27 "github.com/hyperledger/fabric-protos-go/common" 28 mspprotos "github.com/hyperledger/fabric-protos-go/msp" 29 "github.com/hyperledger/fabric-protos-go/peer" 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) (peer.TxValidationCode, error) 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 := txflags.New(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 txflags.ValidationFlags, 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 txflags.ValidationFlags) { 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 cde, err := 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 // Retrieve the transaction identifier of the input header 452 txID := chdr.TxId 453 454 // Look for a transaction with the same identifier inside the ledger 455 exists, err := ldgr.TxIDExists(txID) 456 if err != nil { 457 logger.Errorf("Ledger failure while attempting to detect duplicate status for txid %s: %s", txID, err) 458 return &blockValidationResult{ 459 tIdx: tIdx, 460 err: err, 461 } 462 } 463 464 if exists { 465 logger.Error("Duplicate transaction found, ", txID, ", skipping") 466 return &blockValidationResult{ 467 tIdx: tIdx, 468 validationCode: peer.TxValidationCode_DUPLICATE_TXID, 469 } 470 } 471 472 return nil 473 } 474 475 // generateCCKey generates a unique identifier for chaincode in specific channel 476 func (v *TxValidator) generateCCKey(ccName, chainID string) string { 477 return fmt.Sprintf("%s/%s", ccName, chainID) 478 } 479 480 // invalidTXsForUpgradeCC invalid all txs that should be invalided because of chaincode upgrade txs 481 func (v *TxValidator) invalidTXsForUpgradeCC(txsChaincodeNames map[int]*sysccprovider.ChaincodeInstance, txsUpgradedChaincodes map[int]*sysccprovider.ChaincodeInstance, txsfltr txflags.ValidationFlags) { 482 if len(txsUpgradedChaincodes) == 0 { 483 return 484 } 485 486 // Invalid former cc upgrade txs if there're two or more txs upgrade the same cc 487 finalValidUpgradeTXs := make(map[string]int) 488 upgradedChaincodes := make(map[string]*sysccprovider.ChaincodeInstance) 489 for tIdx, cc := range txsUpgradedChaincodes { 490 if cc == nil { 491 continue 492 } 493 upgradedCCKey := v.generateCCKey(cc.ChaincodeName, cc.ChannelID) 494 495 if finalIdx, exist := finalValidUpgradeTXs[upgradedCCKey]; !exist { 496 finalValidUpgradeTXs[upgradedCCKey] = tIdx 497 upgradedChaincodes[upgradedCCKey] = cc 498 } else if finalIdx < tIdx { 499 logger.Infof("Invalid transaction with index %d: chaincode was upgraded by latter tx", finalIdx) 500 txsfltr.SetFlag(finalIdx, peer.TxValidationCode_CHAINCODE_VERSION_CONFLICT) 501 502 // record latter cc upgrade tx info 503 finalValidUpgradeTXs[upgradedCCKey] = tIdx 504 upgradedChaincodes[upgradedCCKey] = cc 505 } else { 506 logger.Infof("Invalid transaction with index %d: chaincode was upgraded by latter tx", tIdx) 507 txsfltr.SetFlag(tIdx, peer.TxValidationCode_CHAINCODE_VERSION_CONFLICT) 508 } 509 } 510 511 // invalid txs which invoke the upgraded chaincodes 512 for tIdx, cc := range txsChaincodeNames { 513 if cc == nil { 514 continue 515 } 516 ccKey := v.generateCCKey(cc.ChaincodeName, cc.ChannelID) 517 if _, exist := upgradedChaincodes[ccKey]; exist { 518 if txsfltr.IsValid(tIdx) { 519 logger.Infof("Invalid transaction with index %d: chaincode was upgraded in the same block", tIdx) 520 txsfltr.SetFlag(tIdx, peer.TxValidationCode_CHAINCODE_VERSION_CONFLICT) 521 } 522 } 523 } 524 } 525 526 func (v *TxValidator) getTxCCInstance(payload *common.Payload) (invokeCCIns, upgradeCCIns *sysccprovider.ChaincodeInstance, err error) { 527 // This is duplicated unpacking work, but make test easier. 528 chdr, err := protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader) 529 if err != nil { 530 return nil, nil, err 531 } 532 533 // Channel ID 534 channelID := chdr.ChannelId // it is guaranteed to be an existing channel by now 535 536 // ChaincodeID 537 hdrExt, err := protoutil.UnmarshalChaincodeHeaderExtension(chdr.Extension) 538 if err != nil { 539 return nil, nil, err 540 } 541 invokeCC := hdrExt.ChaincodeId 542 invokeIns := &sysccprovider.ChaincodeInstance{ChannelID: channelID, ChaincodeName: invokeCC.Name, ChaincodeVersion: invokeCC.Version} 543 544 // Transaction 545 tx, err := protoutil.UnmarshalTransaction(payload.Data) 546 if err != nil { 547 logger.Errorf("GetTransaction failed: %+v", err) 548 return invokeIns, nil, nil 549 } 550 551 // ChaincodeActionPayload 552 cap, err := protoutil.UnmarshalChaincodeActionPayload(tx.Actions[0].Payload) 553 if err != nil { 554 logger.Errorf("GetChaincodeActionPayload failed: %+v", err) 555 return invokeIns, nil, nil 556 } 557 558 // ChaincodeProposalPayload 559 cpp, err := protoutil.UnmarshalChaincodeProposalPayload(cap.ChaincodeProposalPayload) 560 if err != nil { 561 logger.Errorf("GetChaincodeProposalPayload failed: %+v", err) 562 return invokeIns, nil, nil 563 } 564 565 // ChaincodeInvocationSpec 566 cis := &peer.ChaincodeInvocationSpec{} 567 err = proto.Unmarshal(cpp.Input, cis) 568 if err != nil { 569 logger.Errorf("GetChaincodeInvokeSpec failed: %+v", err) 570 return invokeIns, nil, nil 571 } 572 573 if invokeCC.Name == "lscc" { 574 if string(cis.ChaincodeSpec.Input.Args[0]) == "upgrade" { 575 upgradeIns, err := v.getUpgradeTxInstance(channelID, cis.ChaincodeSpec.Input.Args[2]) 576 if err != nil { 577 return invokeIns, nil, nil 578 } 579 return invokeIns, upgradeIns, nil 580 } 581 } 582 583 return invokeIns, nil, nil 584 } 585 586 func (v *TxValidator) getUpgradeTxInstance(channelID string, cdsBytes []byte) (*sysccprovider.ChaincodeInstance, error) { 587 cds, err := protoutil.UnmarshalChaincodeDeploymentSpec(cdsBytes) 588 if err != nil { 589 return nil, err 590 } 591 592 if cds.ChaincodeSpec.Type.String() != "GOLANG" { 593 return nil, errors.Errorf("unexpected chaincode type: %s", cds.ChaincodeSpec.Type.String()) 594 } 595 596 return &sysccprovider.ChaincodeInstance{ 597 ChannelID: channelID, 598 ChaincodeName: cds.ChaincodeSpec.ChaincodeId.Name, 599 ChaincodeVersion: cds.ChaincodeSpec.ChaincodeId.Version, 600 }, nil 601 } 602 603 type dynamicDeserializer struct { 604 cr ChannelResources 605 } 606 607 func (ds *dynamicDeserializer) DeserializeIdentity(serializedIdentity []byte) (msp.Identity, error) { 608 return ds.cr.MSPManager().DeserializeIdentity(serializedIdentity) 609 } 610 611 func (ds *dynamicDeserializer) IsWellFormed(identity *mspprotos.SerializedIdentity) error { 612 return ds.cr.MSPManager().IsWellFormed(identity) 613 } 614 615 type dynamicCapabilities struct { 616 cr ChannelResources 617 } 618 619 func (ds *dynamicCapabilities) ACLs() bool { 620 return ds.cr.Capabilities().ACLs() 621 } 622 623 func (ds *dynamicCapabilities) CollectionUpgrade() bool { 624 return ds.cr.Capabilities().CollectionUpgrade() 625 } 626 627 func (ds *dynamicCapabilities) StorePvtDataOfInvalidTx() bool { 628 return ds.cr.Capabilities().StorePvtDataOfInvalidTx() 629 } 630 631 func (ds *dynamicCapabilities) ForbidDuplicateTXIdInBlock() bool { 632 return ds.cr.Capabilities().ForbidDuplicateTXIdInBlock() 633 } 634 635 func (ds *dynamicCapabilities) KeyLevelEndorsement() bool { 636 return ds.cr.Capabilities().KeyLevelEndorsement() 637 } 638 639 func (ds *dynamicCapabilities) MetadataLifecycle() bool { 640 // This capability no longer exists and should not be referenced in validation anyway 641 return false 642 } 643 644 func (ds *dynamicCapabilities) PrivateChannelData() bool { 645 return ds.cr.Capabilities().PrivateChannelData() 646 } 647 648 func (ds *dynamicCapabilities) Supported() error { 649 return ds.cr.Capabilities().Supported() 650 } 651 652 func (ds *dynamicCapabilities) V1_1Validation() bool { 653 return ds.cr.Capabilities().V1_1Validation() 654 } 655 656 func (ds *dynamicCapabilities) V1_2Validation() bool { 657 return ds.cr.Capabilities().V1_2Validation() 658 } 659 660 func (ds *dynamicCapabilities) V1_3Validation() bool { 661 return ds.cr.Capabilities().V1_3Validation() 662 } 663 664 func (ds *dynamicCapabilities) V2_0Validation() bool { 665 return ds.cr.Capabilities().V2_0Validation() 666 }