github.com/yimialmonte/fabric@v2.1.1+incompatible/core/endorser/endorser.go (about) 1 /* 2 Copyright IBM Corp. 2016 All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package endorser 8 9 import ( 10 "context" 11 "fmt" 12 "strconv" 13 "time" 14 15 "github.com/golang/protobuf/proto" 16 "github.com/hyperledger/fabric-chaincode-go/shim" 17 pb "github.com/hyperledger/fabric-protos-go/peer" 18 "github.com/hyperledger/fabric-protos-go/transientstore" 19 "github.com/hyperledger/fabric/common/flogging" 20 "github.com/hyperledger/fabric/common/util" 21 "github.com/hyperledger/fabric/core/chaincode/lifecycle" 22 "github.com/hyperledger/fabric/core/common/ccprovider" 23 "github.com/hyperledger/fabric/core/ledger" 24 "github.com/hyperledger/fabric/internal/pkg/identity" 25 "github.com/hyperledger/fabric/msp" 26 "github.com/hyperledger/fabric/protoutil" 27 "github.com/pkg/errors" 28 "go.uber.org/zap" 29 ) 30 31 var endorserLogger = flogging.MustGetLogger("endorser") 32 33 // The Jira issue that documents Endorser flow along with its relationship to 34 // the lifecycle chaincode - https://jira.hyperledger.org/browse/FAB-181 35 36 //go:generate counterfeiter -o fake/prvt_data_distributor.go --fake-name PrivateDataDistributor . PrivateDataDistributor 37 38 type PrivateDataDistributor interface { 39 DistributePrivateData(channel string, txID string, privateData *transientstore.TxPvtReadWriteSetWithConfigInfo, blkHt uint64) error 40 } 41 42 // Support contains functions that the endorser requires to execute its tasks 43 type Support interface { 44 identity.SignerSerializer 45 // GetTxSimulator returns the transaction simulator for the specified ledger 46 // a client may obtain more than one such simulator; they are made unique 47 // by way of the supplied txid 48 GetTxSimulator(ledgername string, txid string) (ledger.TxSimulator, error) 49 50 // GetHistoryQueryExecutor gives handle to a history query executor for the 51 // specified ledger 52 GetHistoryQueryExecutor(ledgername string) (ledger.HistoryQueryExecutor, error) 53 54 // GetTransactionByID retrieves a transaction by id 55 GetTransactionByID(chid, txID string) (*pb.ProcessedTransaction, error) 56 57 // IsSysCC returns true if the name matches a system chaincode's 58 // system chaincode names are system, chain wide 59 IsSysCC(name string) bool 60 61 // Execute - execute proposal, return original response of chaincode 62 Execute(txParams *ccprovider.TransactionParams, name string, input *pb.ChaincodeInput) (*pb.Response, *pb.ChaincodeEvent, error) 63 64 // ExecuteLegacyInit - executes a deployment proposal, return original response of chaincode 65 ExecuteLegacyInit(txParams *ccprovider.TransactionParams, name, version string, spec *pb.ChaincodeInput) (*pb.Response, *pb.ChaincodeEvent, error) 66 67 // ChaincodeEndorsementInfo returns the information from lifecycle required to endorse the chaincode. 68 ChaincodeEndorsementInfo(channelID, chaincodeID string, txsim ledger.QueryExecutor) (*lifecycle.ChaincodeEndorsementInfo, error) 69 70 // CheckACL checks the ACL for the resource for the channel using the 71 // SignedProposal from which an id can be extracted for testing against a policy 72 CheckACL(channelID string, signedProp *pb.SignedProposal) error 73 74 // EndorseWithPlugin endorses the response with a plugin 75 EndorseWithPlugin(pluginName, channnelID string, prpBytes []byte, signedProposal *pb.SignedProposal) (*pb.Endorsement, []byte, error) 76 77 // GetLedgerHeight returns ledger height for given channelID 78 GetLedgerHeight(channelID string) (uint64, error) 79 80 // GetDeployedCCInfoProvider returns ledger.DeployedChaincodeInfoProvider 81 GetDeployedCCInfoProvider() ledger.DeployedChaincodeInfoProvider 82 } 83 84 //go:generate counterfeiter -o fake/channel_fetcher.go --fake-name ChannelFetcher . ChannelFetcher 85 86 // ChannelFetcher fetches the channel context for a given channel ID. 87 type ChannelFetcher interface { 88 Channel(channelID string) *Channel 89 } 90 91 type Channel struct { 92 IdentityDeserializer msp.IdentityDeserializer 93 } 94 95 // Endorser provides the Endorser service ProcessProposal 96 type Endorser struct { 97 ChannelFetcher ChannelFetcher 98 LocalMSP msp.IdentityDeserializer 99 PrivateDataDistributor PrivateDataDistributor 100 Support Support 101 PvtRWSetAssembler PvtRWSetAssembler 102 Metrics *Metrics 103 } 104 105 // call specified chaincode (system or user) 106 func (e *Endorser) callChaincode(txParams *ccprovider.TransactionParams, input *pb.ChaincodeInput, chaincodeName string) (*pb.Response, *pb.ChaincodeEvent, error) { 107 defer func(start time.Time) { 108 logger := endorserLogger.WithOptions(zap.AddCallerSkip(1)) 109 logger = decorateLogger(logger, txParams) 110 elapsedMillisec := time.Since(start).Milliseconds() 111 logger.Infof("finished chaincode: %s duration: %dms", chaincodeName, elapsedMillisec) 112 }(time.Now()) 113 114 meterLabels := []string{ 115 "channel", txParams.ChannelID, 116 "chaincode", chaincodeName, 117 } 118 119 res, ccevent, err := e.Support.Execute(txParams, chaincodeName, input) 120 if err != nil { 121 e.Metrics.SimulationFailure.With(meterLabels...).Add(1) 122 return nil, nil, err 123 } 124 125 // per doc anything < 400 can be sent as TX. 126 // fabric errors will always be >= 400 (ie, unambiguous errors ) 127 // "lscc" will respond with status 200 or 500 (ie, unambiguous OK or ERROR) 128 if res.Status >= shim.ERRORTHRESHOLD { 129 return res, nil, nil 130 } 131 132 // Unless this is the weirdo LSCC case, just return 133 if chaincodeName != "lscc" || len(input.Args) < 3 || (string(input.Args[0]) != "deploy" && string(input.Args[0]) != "upgrade") { 134 return res, ccevent, nil 135 } 136 137 // ----- BEGIN - SECTION THAT MAY NEED TO BE DONE IN LSCC ------ 138 // if this a call to deploy a chaincode, We need a mechanism 139 // to pass TxSimulator into LSCC. Till that is worked out this 140 // special code does the actual deploy, upgrade here so as to collect 141 // all state under one TxSimulator 142 // 143 // NOTE that if there's an error all simulation, including the chaincode 144 // table changes in lscc will be thrown away 145 cds, err := protoutil.UnmarshalChaincodeDeploymentSpec(input.Args[2]) 146 if err != nil { 147 e.Metrics.SimulationFailure.With(meterLabels...).Add(1) 148 return nil, nil, err 149 } 150 151 // this should not be a system chaincode 152 if e.Support.IsSysCC(cds.ChaincodeSpec.ChaincodeId.Name) { 153 e.Metrics.SimulationFailure.With(meterLabels...).Add(1) 154 return nil, nil, errors.Errorf("attempting to deploy a system chaincode %s/%s", cds.ChaincodeSpec.ChaincodeId.Name, txParams.ChannelID) 155 } 156 157 if len(cds.CodePackage) != 0 { 158 e.Metrics.SimulationFailure.With(meterLabels...).Add(1) 159 return nil, nil, errors.Errorf("lscc upgrade/deploy should not include a code packages") 160 } 161 162 _, _, err = e.Support.ExecuteLegacyInit(txParams, cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version, cds.ChaincodeSpec.Input) 163 if err != nil { 164 // increment the failure to indicate instantion/upgrade failures 165 meterLabels = []string{ 166 "channel", txParams.ChannelID, 167 "chaincode", cds.ChaincodeSpec.ChaincodeId.Name, 168 } 169 e.Metrics.InitFailed.With(meterLabels...).Add(1) 170 return nil, nil, err 171 } 172 173 return res, ccevent, err 174 175 } 176 177 // SimulateProposal simulates the proposal by calling the chaincode 178 func (e *Endorser) SimulateProposal(txParams *ccprovider.TransactionParams, chaincodeName string, chaincodeInput *pb.ChaincodeInput) (*pb.Response, []byte, *pb.ChaincodeEvent, error) { 179 logger := decorateLogger(endorserLogger, txParams) 180 181 meterLabels := []string{ 182 "channel", txParams.ChannelID, 183 "chaincode", chaincodeName, 184 } 185 186 // ---3. execute the proposal and get simulation results 187 res, ccevent, err := e.callChaincode(txParams, chaincodeInput, chaincodeName) 188 if err != nil { 189 logger.Errorf("failed to invoke chaincode %s, error: %+v", chaincodeName, err) 190 return nil, nil, nil, err 191 } 192 193 if txParams.TXSimulator == nil { 194 return res, nil, ccevent, nil 195 } 196 197 // Note, this is a little goofy, as if there is private data, Done() gets called 198 // early, so this is invoked multiple times, but that is how the code worked before 199 // this change, so, should be safe. Long term, let's move the Done up to the create. 200 defer txParams.TXSimulator.Done() 201 202 simResult, err := txParams.TXSimulator.GetTxSimulationResults() 203 if err != nil { 204 e.Metrics.SimulationFailure.With(meterLabels...).Add(1) 205 return nil, nil, nil, err 206 } 207 208 if simResult.PvtSimulationResults != nil { 209 if chaincodeName == "lscc" { 210 // TODO: remove once we can store collection configuration outside of LSCC 211 e.Metrics.SimulationFailure.With(meterLabels...).Add(1) 212 return nil, nil, nil, errors.New("Private data is forbidden to be used in instantiate") 213 } 214 pvtDataWithConfig, err := AssemblePvtRWSet(txParams.ChannelID, simResult.PvtSimulationResults, txParams.TXSimulator, e.Support.GetDeployedCCInfoProvider()) 215 // To read collection config need to read collection updates before 216 // releasing the lock, hence txParams.TXSimulator.Done() moved down here 217 txParams.TXSimulator.Done() 218 219 if err != nil { 220 e.Metrics.SimulationFailure.With(meterLabels...).Add(1) 221 return nil, nil, nil, errors.WithMessage(err, "failed to obtain collections config") 222 } 223 endorsedAt, err := e.Support.GetLedgerHeight(txParams.ChannelID) 224 if err != nil { 225 e.Metrics.SimulationFailure.With(meterLabels...).Add(1) 226 return nil, nil, nil, errors.WithMessage(err, fmt.Sprintf("failed to obtain ledger height for channel '%s'", txParams.ChannelID)) 227 } 228 // Add ledger height at which transaction was endorsed, 229 // `endorsedAt` is obtained from the block storage and at times this could be 'endorsement Height + 1'. 230 // However, since we use this height only to select the configuration (3rd parameter in distributePrivateData) and 231 // manage transient store purge for orphaned private writesets (4th parameter in distributePrivateData), this works for now. 232 // Ideally, ledger should add support in the simulator as a first class function `GetHeight()`. 233 pvtDataWithConfig.EndorsedAt = endorsedAt 234 if err := e.PrivateDataDistributor.DistributePrivateData(txParams.ChannelID, txParams.TxID, pvtDataWithConfig, endorsedAt); err != nil { 235 e.Metrics.SimulationFailure.With(meterLabels...).Add(1) 236 return nil, nil, nil, err 237 } 238 } 239 240 pubSimResBytes, err := simResult.GetPubSimulationBytes() 241 if err != nil { 242 e.Metrics.SimulationFailure.With(meterLabels...).Add(1) 243 return nil, nil, nil, err 244 } 245 246 return res, pubSimResBytes, ccevent, nil 247 } 248 249 // preProcess checks the tx proposal headers, uniqueness and ACL 250 func (e *Endorser) preProcess(up *UnpackedProposal, channel *Channel) error { 251 // at first, we check whether the message is valid 252 253 err := up.Validate(channel.IdentityDeserializer) 254 if err != nil { 255 e.Metrics.ProposalValidationFailed.Add(1) 256 return errors.WithMessage(err, "error validating proposal") 257 } 258 259 if up.ChannelHeader.ChannelId == "" { 260 // chainless proposals do not/cannot affect ledger and cannot be submitted as transactions 261 // ignore uniqueness checks; also, chainless proposals are not validated using the policies 262 // of the chain since by definition there is no chain; they are validated against the local 263 // MSP of the peer instead by the call to ValidateUnpackProposal above 264 return nil 265 } 266 267 // labels that provide context for failure metrics 268 meterLabels := []string{ 269 "channel", up.ChannelHeader.ChannelId, 270 "chaincode", up.ChaincodeName, 271 } 272 273 // Here we handle uniqueness check and ACLs for proposals targeting a chain 274 // Notice that ValidateProposalMessage has already verified that TxID is computed properly 275 if _, err = e.Support.GetTransactionByID(up.ChannelHeader.ChannelId, up.ChannelHeader.TxId); err == nil { 276 // increment failure due to duplicate transactions. Useful for catching replay attacks in 277 // addition to benign retries 278 e.Metrics.DuplicateTxsFailure.With(meterLabels...).Add(1) 279 return errors.Errorf("duplicate transaction found [%s]. Creator [%x]", up.ChannelHeader.TxId, up.SignatureHeader.Creator) 280 } 281 282 // check ACL only for application chaincodes; ACLs 283 // for system chaincodes are checked elsewhere 284 if !e.Support.IsSysCC(up.ChaincodeName) { 285 // check that the proposal complies with the Channel's writers 286 if err = e.Support.CheckACL(up.ChannelHeader.ChannelId, up.SignedProposal); err != nil { 287 e.Metrics.ProposalACLCheckFailed.With(meterLabels...).Add(1) 288 return err 289 } 290 } 291 292 return nil 293 } 294 295 // ProcessProposal process the Proposal 296 func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedProposal) (*pb.ProposalResponse, error) { 297 // start time for computing elapsed time metric for successfully endorsed proposals 298 startTime := time.Now() 299 e.Metrics.ProposalsReceived.Add(1) 300 301 addr := util.ExtractRemoteAddress(ctx) 302 endorserLogger.Debug("request from", addr) 303 304 // variables to capture proposal duration metric 305 success := false 306 307 up, err := UnpackProposal(signedProp) 308 if err != nil { 309 e.Metrics.ProposalValidationFailed.Add(1) 310 return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err 311 } 312 313 var channel *Channel 314 if up.ChannelID() != "" { 315 channel = e.ChannelFetcher.Channel(up.ChannelID()) 316 if channel == nil { 317 return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: fmt.Sprintf("channel '%s' not found", up.ChannelHeader.ChannelId)}}, nil 318 } 319 } else { 320 channel = &Channel{ 321 IdentityDeserializer: e.LocalMSP, 322 } 323 } 324 325 // 0 -- check and validate 326 err = e.preProcess(up, channel) 327 if err != nil { 328 return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err 329 } 330 331 defer func() { 332 meterLabels := []string{ 333 "channel", up.ChannelHeader.ChannelId, 334 "chaincode", up.ChaincodeName, 335 "success", strconv.FormatBool(success), 336 } 337 e.Metrics.ProposalDuration.With(meterLabels...).Observe(time.Since(startTime).Seconds()) 338 }() 339 340 pResp, err := e.ProcessProposalSuccessfullyOrError(up) 341 if err != nil { 342 return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, nil 343 } 344 345 if pResp.Endorsement != nil || up.ChannelHeader.ChannelId == "" { 346 // We mark the tx as successfull only if it was successfully endorsed, or 347 // if it was a system chaincode on a channel-less channel and therefore 348 // cannot be endorsed. 349 success = true 350 351 // total failed proposals = ProposalsReceived-SuccessfulProposals 352 e.Metrics.SuccessfulProposals.Add(1) 353 } 354 return pResp, nil 355 } 356 357 func (e *Endorser) ProcessProposalSuccessfullyOrError(up *UnpackedProposal) (*pb.ProposalResponse, error) { 358 txParams := &ccprovider.TransactionParams{ 359 ChannelID: up.ChannelHeader.ChannelId, 360 TxID: up.ChannelHeader.TxId, 361 SignedProp: up.SignedProposal, 362 Proposal: up.Proposal, 363 } 364 365 logger := decorateLogger(endorserLogger, txParams) 366 367 if acquireTxSimulator(up.ChannelHeader.ChannelId, up.ChaincodeName) { 368 txSim, err := e.Support.GetTxSimulator(up.ChannelID(), up.TxID()) 369 if err != nil { 370 return nil, err 371 } 372 373 // txsim acquires a shared lock on the stateDB. As this would impact the block commits (i.e., commit 374 // of valid write-sets to the stateDB), we must release the lock as early as possible. 375 // Hence, this txsim object is closed in simulateProposal() as soon as the tx is simulated and 376 // rwset is collected before gossip dissemination if required for privateData. For safety, we 377 // add the following defer statement and is useful when an error occur. Note that calling 378 // txsim.Done() more than once does not cause any issue. If the txsim is already 379 // released, the following txsim.Done() simply returns. 380 defer txSim.Done() 381 382 hqe, err := e.Support.GetHistoryQueryExecutor(up.ChannelID()) 383 if err != nil { 384 return nil, err 385 } 386 387 txParams.TXSimulator = txSim 388 txParams.HistoryQueryExecutor = hqe 389 } 390 391 cdLedger, err := e.Support.ChaincodeEndorsementInfo(up.ChannelID(), up.ChaincodeName, txParams.TXSimulator) 392 if err != nil { 393 return nil, errors.WithMessagef(err, "make sure the chaincode %s has been successfully defined on channel %s and try again", up.ChaincodeName, up.ChannelID()) 394 } 395 396 // 1 -- simulate 397 res, simulationResult, ccevent, err := e.SimulateProposal(txParams, up.ChaincodeName, up.Input) 398 if err != nil { 399 return nil, errors.WithMessage(err, "error in simulation") 400 } 401 402 cceventBytes, err := CreateCCEventBytes(ccevent) 403 if err != nil { 404 return nil, errors.Wrap(err, "failed to marshal chaincode event") 405 } 406 407 prpBytes, err := protoutil.GetBytesProposalResponsePayload(up.ProposalHash, res, simulationResult, cceventBytes, &pb.ChaincodeID{ 408 Name: up.ChaincodeName, 409 Version: cdLedger.Version, 410 }) 411 if err != nil { 412 logger.Warning("Failed marshaling the proposal response payload to bytes", err) 413 return nil, errors.WithMessage(err, "failed to create the proposal response") 414 } 415 416 // if error, capture endorsement failure metric 417 meterLabels := []string{ 418 "channel", up.ChannelID(), 419 "chaincode", up.ChaincodeName, 420 } 421 422 switch { 423 case res.Status >= shim.ERROR: 424 return &pb.ProposalResponse{ 425 Response: res, 426 Payload: prpBytes, 427 }, nil 428 case up.ChannelID() == "": 429 // Chaincode invocations without a channel ID is a broken concept 430 // that should be removed in the future. For now, return unendorsed 431 // success. 432 return &pb.ProposalResponse{ 433 Response: res, 434 }, nil 435 case res.Status >= shim.ERRORTHRESHOLD: 436 meterLabels = append(meterLabels, "chaincodeerror", strconv.FormatBool(true)) 437 e.Metrics.EndorsementsFailed.With(meterLabels...).Add(1) 438 logger.Debugf("chaincode error %d", res.Status) 439 return &pb.ProposalResponse{ 440 Response: res, 441 }, nil 442 } 443 444 escc := cdLedger.EndorsementPlugin 445 446 logger.Debugf("escc for chaincode %s is %s", up.ChaincodeName, escc) 447 448 // Note, mPrpBytes is the same as prpBytes by default endorsement plugin, but others could change it. 449 endorsement, mPrpBytes, err := e.Support.EndorseWithPlugin(escc, up.ChannelID(), prpBytes, up.SignedProposal) 450 if err != nil { 451 meterLabels = append(meterLabels, "chaincodeerror", strconv.FormatBool(false)) 452 e.Metrics.EndorsementsFailed.With(meterLabels...).Add(1) 453 return nil, errors.WithMessage(err, "endorsing with plugin failed") 454 } 455 456 return &pb.ProposalResponse{ 457 Version: 1, 458 Endorsement: endorsement, 459 Payload: mPrpBytes, 460 Response: res, 461 }, nil 462 } 463 464 // determine whether or not a transaction simulator should be 465 // obtained for a proposal. 466 func acquireTxSimulator(chainID string, chaincodeName string) bool { 467 if chainID == "" { 468 return false 469 } 470 471 // ¯\_(ツ)_/¯ locking. 472 // Don't get a simulator for the query and config system chaincode. 473 // These don't need the simulator and its read lock results in deadlocks. 474 switch chaincodeName { 475 case "qscc", "cscc": 476 return false 477 default: 478 return true 479 } 480 } 481 482 // shorttxid replicates the chaincode package function to shorten txids. 483 // ~~TODO utilize a common shorttxid utility across packages.~~ 484 // TODO use a formal type for transaction ID and make it a stringer 485 func shorttxid(txid string) string { 486 if len(txid) < 8 { 487 return txid 488 } 489 return txid[0:8] 490 } 491 492 func CreateCCEventBytes(ccevent *pb.ChaincodeEvent) ([]byte, error) { 493 if ccevent == nil { 494 return nil, nil 495 } 496 497 return proto.Marshal(ccevent) 498 } 499 500 func decorateLogger(logger *flogging.FabricLogger, txParams *ccprovider.TransactionParams) *flogging.FabricLogger { 501 return logger.With("channel", txParams.ChannelID, "txID", shorttxid(txParams.TxID)) 502 }