github.com/kaituanwang/hyperledger@v2.0.1+incompatible/core/committer/txvalidator/v20/plugindispatcher/dispatcher.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package plugindispatcher 8 9 import ( 10 "fmt" 11 12 "github.com/golang/protobuf/proto" 13 "github.com/hyperledger/fabric-protos-go/common" 14 "github.com/hyperledger/fabric-protos-go/peer" 15 commonerrors "github.com/hyperledger/fabric/common/errors" 16 "github.com/hyperledger/fabric/common/flogging" 17 validation "github.com/hyperledger/fabric/core/handlers/validation/api" 18 s "github.com/hyperledger/fabric/core/handlers/validation/api/state" 19 "github.com/hyperledger/fabric/core/ledger" 20 "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/rwsetutil" 21 "github.com/hyperledger/fabric/protoutil" 22 "github.com/pkg/errors" 23 ) 24 25 // ChannelResources provides access to channel artefacts or 26 // functions to interact with them 27 type ChannelResources interface { 28 // GetMSPIDs returns the IDs for the application MSPs 29 // that have been defined in the channel 30 GetMSPIDs() []string 31 } 32 33 // LedgerResources provides access to ledger artefacts or 34 // functions to interact with them 35 type LedgerResources interface { 36 // NewQueryExecutor gives handle to a query executor. 37 // A client can obtain more than one 'QueryExecutor's for parallel execution. 38 // Any synchronization should be performed at the implementation level if required 39 NewQueryExecutor() (ledger.QueryExecutor, error) 40 } 41 42 // LifecycleResources provides access to chaincode lifecycle artefacts or 43 // functions to interact with them 44 type LifecycleResources interface { 45 // ValidationInfo returns the name and arguments of the validation plugin for the supplied 46 // chaincode. The function returns two types of errors, unexpected errors and validation 47 // errors. The reason for this is that this function is called from the validation code, 48 // which needs to differentiate the two types of error to halt processing on the channel 49 // if the unexpected error is not nil and mark the transaction as invalid if the validation 50 // error is not nil. 51 ValidationInfo(channelID, chaincodeName string, qe ledger.SimpleQueryExecutor) (plugin string, args []byte, unexpectedErr error, validationErr error) 52 } 53 54 // CollectionResources provides access to collection artefacts 55 type CollectionResources interface { 56 // CollectionValidationInfo returns collection-level endorsement policy for the supplied chaincode. 57 // The function returns two types of errors, unexpected errors and validation errors. The 58 // reason for this is that this function is to be called from the validation code, which 59 // needs to tell apart the two types of error to halt processing on the channel if the 60 // unexpected error is not nil and mark the transaction as invalid if the validation error 61 // is not nil. 62 CollectionValidationInfo(chaincodeName, collectionName string, state s.State) (args []byte, unexpectedErr error, validationErr error) 63 } 64 65 // CollectionAndLifecycleResources provides access to resources 66 // about chaincodes and their lifecycle and collections and their 67 // policies 68 type CollectionAndLifecycleResources interface { 69 LifecycleResources 70 71 // CollectionValidationInfo is exactly like the method defined in CollectionResources but 72 // also takes the channel ID. This is necessary to determine if the org collection names are valid. 73 CollectionValidationInfo(channelID, chaincodeName, collectionName string, state s.State) (args []byte, unexpectedErr error, validationErr error) 74 } 75 76 //go:generate mockery -dir . -name LifecycleResources -case underscore -output mocks/ 77 78 var logger = flogging.MustGetLogger("committer.txvalidator") 79 80 // dispatcherImpl is the implementation used to call 81 // the validation plugin and validate block transactions 82 type dispatcherImpl struct { 83 chainID string 84 cr ChannelResources 85 ler LedgerResources 86 lcr LifecycleResources 87 pluginValidator *PluginValidator 88 } 89 90 // New creates new plugin dispatcher 91 func New(chainID string, cr ChannelResources, ler LedgerResources, lcr LifecycleResources, pluginValidator *PluginValidator) *dispatcherImpl { 92 return &dispatcherImpl{ 93 chainID: chainID, 94 cr: cr, 95 ler: ler, 96 lcr: lcr, 97 pluginValidator: pluginValidator, 98 } 99 } 100 101 // Dispatch executes the validation plugin(s) for transaction 102 func (v *dispatcherImpl) Dispatch(seq int, payload *common.Payload, envBytes []byte, block *common.Block) (error, peer.TxValidationCode) { 103 chainID := v.chainID 104 logger.Debugf("[%s] Dispatch starts for bytes %p", chainID, envBytes) 105 106 // get channel header 107 chdr, err := protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader) 108 if err != nil { 109 return err, peer.TxValidationCode_BAD_CHANNEL_HEADER 110 } 111 112 // get header extensions so we have the chaincode ID 113 hdrExt, err := protoutil.UnmarshalChaincodeHeaderExtension(chdr.Extension) 114 if err != nil { 115 return err, peer.TxValidationCode_BAD_HEADER_EXTENSION 116 } 117 118 /* obtain the list of namespaces we're writing to */ 119 respPayload, err := protoutil.GetActionFromEnvelope(envBytes) 120 if err != nil { 121 return errors.WithMessage(err, "GetActionFromEnvelope failed"), peer.TxValidationCode_BAD_RESPONSE_PAYLOAD 122 } 123 txRWSet := &rwsetutil.TxRwSet{} 124 if err = txRWSet.FromProtoBytes(respPayload.Results); err != nil { 125 return errors.WithMessage(err, "txRWSet.FromProtoBytes failed"), peer.TxValidationCode_BAD_RWSET 126 } 127 128 // Verify the header extension and response payload contain the ChaincodeId 129 if hdrExt.ChaincodeId == nil { 130 return errors.New("nil ChaincodeId in header extension"), peer.TxValidationCode_INVALID_OTHER_REASON 131 } 132 133 if respPayload.ChaincodeId == nil { 134 return errors.New("nil ChaincodeId in ChaincodeAction"), peer.TxValidationCode_INVALID_OTHER_REASON 135 } 136 137 // get name and version of the cc we invoked 138 ccID := hdrExt.ChaincodeId.Name 139 ccVer := respPayload.ChaincodeId.Version 140 141 // sanity check on ccID 142 if ccID == "" { 143 err = errors.New("invalid chaincode ID") 144 logger.Errorf("%+v", err) 145 return err, peer.TxValidationCode_INVALID_CHAINCODE 146 } 147 if ccID != respPayload.ChaincodeId.Name { 148 err = errors.Errorf("inconsistent ccid info (%s/%s)", ccID, respPayload.ChaincodeId.Name) 149 logger.Errorf("%+v", err) 150 return err, peer.TxValidationCode_INVALID_CHAINCODE 151 } 152 // sanity check on ccver 153 if ccVer == "" { 154 err = errors.New("invalid chaincode version") 155 logger.Errorf("%+v", err) 156 return err, peer.TxValidationCode_INVALID_CHAINCODE 157 } 158 159 wrNamespace := map[string]bool{} 160 wrNamespace[ccID] = true 161 if respPayload.Events != nil { 162 ccEvent := &peer.ChaincodeEvent{} 163 if err = proto.Unmarshal(respPayload.Events, ccEvent); err != nil { 164 return errors.Wrapf(err, "invalid chaincode event"), peer.TxValidationCode_INVALID_OTHER_REASON 165 } 166 if ccEvent.ChaincodeId != ccID { 167 return errors.Errorf("chaincode event chaincode id does not match chaincode action chaincode id"), peer.TxValidationCode_INVALID_OTHER_REASON 168 } 169 } 170 171 namespaces := make(map[string]struct{}) 172 for _, ns := range txRWSet.NsRwSets { 173 // check to make sure there is no duplicate namespace in txRWSet 174 if _, ok := namespaces[ns.NameSpace]; ok { 175 logger.Errorf("duplicate namespace '%s' in txRWSet", ns.NameSpace) 176 return errors.Errorf("duplicate namespace '%s' in txRWSet", ns.NameSpace), 177 peer.TxValidationCode_ILLEGAL_WRITESET 178 } 179 namespaces[ns.NameSpace] = struct{}{} 180 181 if v.txWritesToNamespace(ns) { 182 wrNamespace[ns.NameSpace] = true 183 } 184 } 185 186 // we've gathered all the info required to proceed to validation; 187 // validation will behave differently depending on the chaincode 188 189 // validate *EACH* read write set according to its chaincode's endorsement policy 190 for ns := range wrNamespace { 191 // Get latest chaincode validation plugin name and policy 192 validationPlugin, args, err := v.GetInfoForValidate(chdr, ns) 193 if err != nil { 194 logger.Errorf("GetInfoForValidate for txId = %s returned error: %+v", chdr.TxId, err) 195 return err, peer.TxValidationCode_INVALID_CHAINCODE 196 } 197 198 // invoke the plugin 199 ctx := &Context{ 200 Seq: seq, 201 Envelope: envBytes, 202 Block: block, 203 TxID: chdr.TxId, 204 Channel: chdr.ChannelId, 205 Namespace: ns, 206 Policy: args, 207 PluginName: validationPlugin, 208 } 209 if err = v.invokeValidationPlugin(ctx); err != nil { 210 switch err.(type) { 211 case *commonerrors.VSCCEndorsementPolicyError: 212 return err, peer.TxValidationCode_ENDORSEMENT_POLICY_FAILURE 213 default: 214 return err, peer.TxValidationCode_INVALID_OTHER_REASON 215 } 216 } 217 } 218 219 logger.Debugf("[%s] Dispatch completes env bytes %p", chainID, envBytes) 220 return nil, peer.TxValidationCode_VALID 221 } 222 223 func (v *dispatcherImpl) invokeValidationPlugin(ctx *Context) error { 224 logger.Debug("Validating", ctx, "with plugin") 225 err := v.pluginValidator.ValidateWithPlugin(ctx) 226 if err == nil { 227 return nil 228 } 229 // If the error is a pluggable validation execution error, cast it to the common errors ExecutionFailureError. 230 if e, isExecutionError := err.(*validation.ExecutionFailureError); isExecutionError { 231 return &commonerrors.VSCCExecutionFailureError{Err: e} 232 } 233 // Else, treat it as an endorsement error. 234 return &commonerrors.VSCCEndorsementPolicyError{Err: err} 235 } 236 237 func (v *dispatcherImpl) getCDataForCC(channelID, ccid string) (string, []byte, error) { 238 qe, err := v.ler.NewQueryExecutor() 239 if err != nil { 240 return "", nil, errors.WithMessage(err, "could not retrieve QueryExecutor") 241 } 242 defer qe.Done() 243 244 plugin, args, unexpectedErr, validationErr := v.lcr.ValidationInfo(channelID, ccid, qe) 245 if unexpectedErr != nil { 246 return "", nil, &commonerrors.VSCCInfoLookupFailureError{ 247 Reason: fmt.Sprintf("Could not retrieve state for chaincode %s, error %s", ccid, unexpectedErr), 248 } 249 } 250 if validationErr != nil { 251 return "", nil, validationErr 252 } 253 254 if plugin == "" { 255 return "", nil, errors.Errorf("chaincode definition for [%s] is invalid, plugin field must be set", ccid) 256 } 257 258 if len(args) == 0 { 259 return "", nil, errors.Errorf("chaincode definition for [%s] is invalid, policy field must be set", ccid) 260 } 261 262 return plugin, args, nil 263 } 264 265 // GetInfoForValidate gets the ChaincodeInstance(with latest version) of tx, validation plugin and policy 266 func (v *dispatcherImpl) GetInfoForValidate(chdr *common.ChannelHeader, ccID string) (string, []byte, error) { 267 // obtain name of the validation plugin and the policy 268 plugin, args, err := v.getCDataForCC(chdr.ChannelId, ccID) 269 if err != nil { 270 msg := fmt.Sprintf("Unable to get chaincode data from ledger for txid %s, due to %s", chdr.TxId, err) 271 logger.Errorf(msg) 272 return "", nil, err 273 } 274 return plugin, args, nil 275 } 276 277 // txWritesToNamespace returns true if the supplied NsRwSet 278 // performs a ledger write 279 func (v *dispatcherImpl) txWritesToNamespace(ns *rwsetutil.NsRwSet) bool { 280 // check for public writes first 281 if ns.KvRwSet != nil && len(ns.KvRwSet.Writes) > 0 { 282 return true 283 } 284 285 // check for private writes for all collections 286 for _, c := range ns.CollHashedRwSets { 287 if c.HashedRwSet != nil && len(c.HashedRwSet.HashedWrites) > 0 { 288 return true 289 } 290 291 // private metadata updates 292 if c.HashedRwSet != nil && len(c.HashedRwSet.MetadataWrites) > 0 { 293 return true 294 } 295 } 296 297 if ns.KvRwSet != nil && len(ns.KvRwSet.MetadataWrites) > 0 { 298 return true 299 } 300 301 return false 302 }