github.com/darrenli6/fabric-sdk-example@v0.0.0-20220109053535-94b13b56df8c/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 "fmt" 11 12 "github.com/golang/protobuf/proto" 13 "github.com/hyperledger/fabric/common/flogging" 14 "golang.org/x/net/context" 15 16 "errors" 17 18 "github.com/hyperledger/fabric/common/policies" 19 "github.com/hyperledger/fabric/common/util" 20 "github.com/hyperledger/fabric/core/chaincode" 21 "github.com/hyperledger/fabric/core/chaincode/shim" 22 "github.com/hyperledger/fabric/core/common/ccprovider" 23 "github.com/hyperledger/fabric/core/common/validation" 24 "github.com/hyperledger/fabric/core/ledger" 25 "github.com/hyperledger/fabric/core/peer" 26 "github.com/hyperledger/fabric/core/policy" 27 syscc "github.com/hyperledger/fabric/core/scc" 28 "github.com/hyperledger/fabric/msp" 29 "github.com/hyperledger/fabric/msp/mgmt" 30 "github.com/hyperledger/fabric/protos/common" 31 pb "github.com/hyperledger/fabric/protos/peer" 32 putils "github.com/hyperledger/fabric/protos/utils" 33 ) 34 35 // >>>>> begin errors section >>>>> 36 //chaincodeError is a fabric error signifying error from chaincode 37 type chaincodeError struct { 38 status int32 39 msg string 40 } 41 42 func (ce chaincodeError) Error() string { 43 return fmt.Sprintf("chaincode error (status: %d, message: %s)", ce.status, ce.msg) 44 } 45 46 // <<<<< end errors section <<<<<< 47 48 var endorserLogger = flogging.MustGetLogger("endorser") 49 50 // The Jira issue that documents Endorser flow along with its relationship to 51 // the lifecycle chaincode - https://jira.hyperledger.org/browse/FAB-181 52 53 // Endorser provides the Endorser service ProcessProposal 54 type Endorser struct { 55 policyChecker policy.PolicyChecker 56 } 57 58 // NewEndorserServer creates and returns a new Endorser server instance. 59 func NewEndorserServer() pb.EndorserServer { 60 e := new(Endorser) 61 e.policyChecker = policy.NewPolicyChecker( 62 peer.NewChannelPolicyManagerGetter(), 63 mgmt.GetLocalMSP(), 64 mgmt.NewLocalMSPPrincipalGetter(), 65 ) 66 67 return e 68 } 69 70 // checkACL checks that the supplied proposal complies 71 // with the writers policy of the chain 72 func (e *Endorser) checkACL(signedProp *pb.SignedProposal, chdr *common.ChannelHeader, shdr *common.SignatureHeader, hdrext *pb.ChaincodeHeaderExtension) error { 73 return e.policyChecker.CheckPolicy(chdr.ChannelId, policies.ChannelApplicationWriters, signedProp) 74 } 75 76 //TODO - check for escc and vscc 77 func (*Endorser) checkEsccAndVscc(prop *pb.Proposal) error { 78 return nil 79 } 80 81 func (*Endorser) getTxSimulator(ledgername string) (ledger.TxSimulator, error) { 82 lgr := peer.GetLedger(ledgername) 83 if lgr == nil { 84 return nil, fmt.Errorf("channel does not exist: %s", ledgername) 85 } 86 return lgr.NewTxSimulator() 87 } 88 89 func (*Endorser) getHistoryQueryExecutor(ledgername string) (ledger.HistoryQueryExecutor, error) { 90 lgr := peer.GetLedger(ledgername) 91 if lgr == nil { 92 return nil, fmt.Errorf("channel does not exist: %s", ledgername) 93 } 94 return lgr.NewHistoryQueryExecutor() 95 } 96 97 //call specified chaincode (system or user) 98 func (e *Endorser) callChaincode(ctxt context.Context, chainID string, version string, txid string, signedProp *pb.SignedProposal, prop *pb.Proposal, cis *pb.ChaincodeInvocationSpec, cid *pb.ChaincodeID, txsim ledger.TxSimulator) (*pb.Response, *pb.ChaincodeEvent, error) { 99 endorserLogger.Debugf("Entry - txid: %s channel id: %s version: %s", txid, chainID, version) 100 defer endorserLogger.Debugf("Exit") 101 var err error 102 var res *pb.Response 103 var ccevent *pb.ChaincodeEvent 104 105 if txsim != nil { 106 ctxt = context.WithValue(ctxt, chaincode.TXSimulatorKey, txsim) 107 } 108 109 //is this a system chaincode 110 scc := syscc.IsSysCC(cid.Name) 111 112 cccid := ccprovider.NewCCContext(chainID, cid.Name, version, txid, scc, signedProp, prop) 113 114 res, ccevent, err = chaincode.ExecuteChaincode(ctxt, cccid, cis.ChaincodeSpec.Input.Args) 115 116 if err != nil { 117 return nil, nil, err 118 } 119 120 //per doc anything < 400 can be sent as TX. 121 //fabric errors will always be >= 400 (ie, unambiguous errors ) 122 //"lscc" will respond with status 200 or 500 (ie, unambiguous OK or ERROR) 123 if res.Status >= shim.ERRORTHRESHOLD { 124 return res, nil, nil 125 } 126 127 //----- BEGIN - SECTION THAT MAY NEED TO BE DONE IN LSCC ------ 128 //if this a call to deploy a chaincode, We need a mechanism 129 //to pass TxSimulator into LSCC. Till that is worked out this 130 //special code does the actual deploy, upgrade here so as to collect 131 //all state under one TxSimulator 132 // 133 //NOTE that if there's an error all simulation, including the chaincode 134 //table changes in lscc will be thrown away 135 if cid.Name == "lscc" && len(cis.ChaincodeSpec.Input.Args) >= 3 && (string(cis.ChaincodeSpec.Input.Args[0]) == "deploy" || string(cis.ChaincodeSpec.Input.Args[0]) == "upgrade") { 136 var cds *pb.ChaincodeDeploymentSpec 137 cds, err = putils.GetChaincodeDeploymentSpec(cis.ChaincodeSpec.Input.Args[2]) 138 if err != nil { 139 return nil, nil, err 140 } 141 142 //this should not be a system chaincode 143 if syscc.IsSysCC(cds.ChaincodeSpec.ChaincodeId.Name) { 144 return nil, nil, fmt.Errorf("attempting to deploy a system chaincode %s/%s", cds.ChaincodeSpec.ChaincodeId.Name, chainID) 145 } 146 147 cccid = ccprovider.NewCCContext(chainID, cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version, txid, false, signedProp, prop) 148 149 _, _, err = chaincode.Execute(ctxt, cccid, cds) 150 if err != nil { 151 return nil, nil, fmt.Errorf("%s", err) 152 } 153 } 154 //----- END ------- 155 156 return res, ccevent, err 157 } 158 159 //TO BE REMOVED WHEN JAVA CC IS ENABLED 160 //disableJavaCCInst if trying to install, instantiate or upgrade Java CC 161 func (e *Endorser) disableJavaCCInst(cid *pb.ChaincodeID, cis *pb.ChaincodeInvocationSpec) error { 162 //if not lscc we don't care 163 if cid.Name != "lscc" { 164 return nil 165 } 166 167 //non-nil spec ? leave it to callers to handle error if this is an error 168 if cis.ChaincodeSpec == nil || cis.ChaincodeSpec.Input == nil { 169 return nil 170 } 171 172 //should at least have a command arg, leave it to callers if this is an error 173 if len(cis.ChaincodeSpec.Input.Args) < 1 { 174 return nil 175 } 176 177 var argNo int 178 switch string(cis.ChaincodeSpec.Input.Args[0]) { 179 case "install": 180 argNo = 1 181 case "deploy", "upgrade": 182 argNo = 2 183 default: 184 //what else can it be ? leave it caller to handle it if error 185 return nil 186 } 187 188 if argNo >= len(cis.ChaincodeSpec.Input.Args) { 189 return errors.New("Too few arguments passed") 190 } 191 192 //the inner dep spec will contain the type 193 ccpack, err := ccprovider.GetCCPackage(cis.ChaincodeSpec.Input.Args[argNo]) 194 if err != nil { 195 return err 196 } 197 198 cds := ccpack.GetDepSpec() 199 200 //finally, if JAVA error out 201 if cds.ChaincodeSpec.Type == pb.ChaincodeSpec_JAVA { 202 return fmt.Errorf("Java chaincode is work-in-progress and disabled") 203 } 204 205 //not a java install, instantiate or upgrade op 206 return nil 207 } 208 209 //simulate the proposal by calling the chaincode 210 func (e *Endorser) simulateProposal(ctx context.Context, chainID string, txid string, signedProp *pb.SignedProposal, prop *pb.Proposal, cid *pb.ChaincodeID, txsim ledger.TxSimulator) (*ccprovider.ChaincodeData, *pb.Response, []byte, *pb.ChaincodeEvent, error) { 211 endorserLogger.Debugf("Entry - txid: %s channel id: %s", txid, chainID) 212 defer endorserLogger.Debugf("Exit") 213 //we do expect the payload to be a ChaincodeInvocationSpec 214 //if we are supporting other payloads in future, this be glaringly point 215 //as something that should change 216 cis, err := putils.GetChaincodeInvocationSpec(prop) 217 if err != nil { 218 return nil, nil, nil, nil, err 219 } 220 221 //disable Java install,instantiate,upgrade for now 222 if err = e.disableJavaCCInst(cid, cis); err != nil { 223 return nil, nil, nil, nil, err 224 } 225 226 //---1. check ESCC and VSCC for the chaincode 227 if err = e.checkEsccAndVscc(prop); err != nil { 228 return nil, nil, nil, nil, err 229 } 230 231 var cdLedger *ccprovider.ChaincodeData 232 var version string 233 234 if !syscc.IsSysCC(cid.Name) { 235 cdLedger, err = e.getCDSFromLSCC(ctx, chainID, txid, signedProp, prop, cid.Name, txsim) 236 if err != nil { 237 return nil, nil, nil, nil, fmt.Errorf("%s - make sure the chaincode %s has been successfully instantiated and try again", err, cid.Name) 238 } 239 version = cdLedger.Version 240 241 err = ccprovider.CheckInsantiationPolicy(cid.Name, version, cdLedger) 242 if err != nil { 243 return nil, nil, nil, nil, err 244 } 245 } else { 246 version = util.GetSysCCVersion() 247 } 248 249 //---3. execute the proposal and get simulation results 250 var simResult []byte 251 var res *pb.Response 252 var ccevent *pb.ChaincodeEvent 253 res, ccevent, err = e.callChaincode(ctx, chainID, version, txid, signedProp, prop, cis, cid, txsim) 254 if err != nil { 255 endorserLogger.Errorf("failed to invoke chaincode %s on transaction %s, error: %s", cid, txid, err) 256 return nil, nil, nil, nil, err 257 } 258 259 if txsim != nil { 260 if simResult, err = txsim.GetTxSimulationResults(); err != nil { 261 return nil, nil, nil, nil, err 262 } 263 } 264 265 return cdLedger, res, simResult, ccevent, nil 266 } 267 268 func (e *Endorser) getCDSFromLSCC(ctx context.Context, chainID string, txid string, signedProp *pb.SignedProposal, prop *pb.Proposal, chaincodeID string, txsim ledger.TxSimulator) (*ccprovider.ChaincodeData, error) { 269 ctxt := ctx 270 if txsim != nil { 271 ctxt = context.WithValue(ctx, chaincode.TXSimulatorKey, txsim) 272 } 273 274 return chaincode.GetChaincodeDataFromLSCC(ctxt, txid, signedProp, prop, chainID, chaincodeID) 275 } 276 277 //endorse the proposal by calling the ESCC 278 func (e *Endorser) endorseProposal(ctx context.Context, chainID string, txid string, signedProp *pb.SignedProposal, proposal *pb.Proposal, response *pb.Response, simRes []byte, event *pb.ChaincodeEvent, visibility []byte, ccid *pb.ChaincodeID, txsim ledger.TxSimulator, cd *ccprovider.ChaincodeData) (*pb.ProposalResponse, error) { 279 endorserLogger.Debugf("Entry - txid: %s channel id: %s chaincode id: %s", txid, chainID, ccid) 280 defer endorserLogger.Debugf("Exit") 281 282 isSysCC := cd == nil 283 // 1) extract the name of the escc that is requested to endorse this chaincode 284 var escc string 285 //ie, not "lscc" or system chaincodes 286 if isSysCC { 287 // FIXME: getCDSFromLSCC seems to fail for lscc - not sure this is expected? 288 // TODO: who should endorse a call to LSCC? 289 escc = "escc" 290 } else { 291 escc = cd.Escc 292 if escc == "" { // this should never happen, LSCC always fills this field 293 panic("No ESCC specified in ChaincodeData") 294 } 295 } 296 297 endorserLogger.Debugf("info: escc for chaincode id %s is %s", ccid, escc) 298 299 // marshalling event bytes 300 var err error 301 var eventBytes []byte 302 if event != nil { 303 eventBytes, err = putils.GetBytesChaincodeEvent(event) 304 if err != nil { 305 return nil, fmt.Errorf("failed to marshal event bytes - %s", err) 306 } 307 } 308 309 resBytes, err := putils.GetBytesResponse(response) 310 if err != nil { 311 return nil, fmt.Errorf("failed to marshal response bytes - %s", err) 312 } 313 314 // set version of executing chaincode 315 if isSysCC { 316 // if we want to allow mixed fabric levels we should 317 // set syscc version to "" 318 ccid.Version = util.GetSysCCVersion() 319 } else { 320 ccid.Version = cd.Version 321 } 322 323 ccidBytes, err := putils.Marshal(ccid) 324 if err != nil { 325 return nil, fmt.Errorf("failed to marshal ChaincodeID - %s", err) 326 } 327 328 // 3) call the ESCC we've identified 329 // arguments: 330 // args[0] - function name (not used now) 331 // args[1] - serialized Header object 332 // args[2] - serialized ChaincodeProposalPayload object 333 // args[3] - ChaincodeID of executing chaincode 334 // args[4] - result of executing chaincode 335 // args[5] - binary blob of simulation results 336 // args[6] - serialized events 337 // args[7] - payloadVisibility 338 args := [][]byte{[]byte(""), proposal.Header, proposal.Payload, ccidBytes, resBytes, simRes, eventBytes, visibility} 339 version := util.GetSysCCVersion() 340 ecccis := &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{Type: pb.ChaincodeSpec_GOLANG, ChaincodeId: &pb.ChaincodeID{Name: escc}, Input: &pb.ChaincodeInput{Args: args}}} 341 res, _, err := e.callChaincode(ctx, chainID, version, txid, signedProp, proposal, ecccis, &pb.ChaincodeID{Name: escc}, txsim) 342 if err != nil { 343 return nil, err 344 } 345 346 if res.Status >= shim.ERRORTHRESHOLD { 347 return &pb.ProposalResponse{Response: res}, nil 348 } 349 350 prBytes := res.Payload 351 // Note that we do not extract any simulation results from 352 // the call to ESCC. This is intentional becuse ESCC is meant 353 // to endorse (i.e. sign) the simulation results of a chaincode, 354 // but it can't obviously sign its own. Furthermore, ESCC runs 355 // on private input (its own signing key) and so if it were to 356 // produce simulationr results, they are likely to be different 357 // from other ESCCs, which would stand in the way of the 358 // endorsement process. 359 360 //3 -- respond 361 pResp, err := putils.GetProposalResponse(prBytes) 362 if err != nil { 363 return nil, err 364 } 365 366 return pResp, nil 367 } 368 369 // ProcessProposal process the Proposal 370 func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedProposal) (*pb.ProposalResponse, error) { 371 endorserLogger.Debugf("Entry") 372 defer endorserLogger.Debugf("Exit") 373 // at first, we check whether the message is valid 374 prop, hdr, hdrExt, err := validation.ValidateProposalMessage(signedProp) 375 if err != nil { 376 return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err 377 } 378 379 chdr, err := putils.UnmarshalChannelHeader(hdr.ChannelHeader) 380 if err != nil { 381 return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err 382 } 383 384 shdr, err := putils.GetSignatureHeader(hdr.SignatureHeader) 385 if err != nil { 386 return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err 387 } 388 389 // block invocations to security-sensitive system chaincodes 390 if syscc.IsSysCCAndNotInvokableExternal(hdrExt.ChaincodeId.Name) { 391 endorserLogger.Errorf("Error: an attempt was made by %#v to invoke system chaincode %s", 392 shdr.Creator, hdrExt.ChaincodeId.Name) 393 err = fmt.Errorf("Chaincode %s cannot be invoked through a proposal", hdrExt.ChaincodeId.Name) 394 return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err 395 } 396 397 chainID := chdr.ChannelId 398 399 // Check for uniqueness of prop.TxID with ledger 400 // Notice that ValidateProposalMessage has already verified 401 // that TxID is computed properly 402 txid := chdr.TxId 403 if txid == "" { 404 err = errors.New("Invalid txID. It must be different from the empty string.") 405 return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err 406 } 407 endorserLogger.Debugf("processing txid: %s", txid) 408 if chainID != "" { 409 // here we handle uniqueness check and ACLs for proposals targeting a chain 410 lgr := peer.GetLedger(chainID) 411 if lgr == nil { 412 return nil, fmt.Errorf("failure while looking up the ledger %s", chainID) 413 } 414 if _, err := lgr.GetTransactionByID(txid); err == nil { 415 return nil, fmt.Errorf("Duplicate transaction found [%s]. Creator [%x]. [%s]", txid, shdr.Creator, err) 416 } 417 418 // check ACL only for application chaincodes; ACLs 419 // for system chaincodes are checked elsewhere 420 if !syscc.IsSysCC(hdrExt.ChaincodeId.Name) { 421 // check that the proposal complies with the channel's writers 422 if err = e.checkACL(signedProp, chdr, shdr, hdrExt); err != nil { 423 return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err 424 } 425 } 426 } else { 427 // chainless proposals do not/cannot affect ledger and cannot be submitted as transactions 428 // ignore uniqueness checks; also, chainless proposals are not validated using the policies 429 // of the chain since by definition there is no chain; they are validated against the local 430 // MSP of the peer instead by the call to ValidateProposalMessage above 431 } 432 433 // obtaining once the tx simulator for this proposal. This will be nil 434 // for chainless proposals 435 // Also obtain a history query executor for history queries, since tx simulator does not cover history 436 var txsim ledger.TxSimulator 437 var historyQueryExecutor ledger.HistoryQueryExecutor 438 if chainID != "" { 439 if txsim, err = e.getTxSimulator(chainID); err != nil { 440 return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err 441 } 442 if historyQueryExecutor, err = e.getHistoryQueryExecutor(chainID); err != nil { 443 return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err 444 } 445 // Add the historyQueryExecutor to context 446 // TODO shouldn't we also add txsim to context here as well? Rather than passing txsim parameter 447 // around separately, since eventually it gets added to context anyways 448 ctx = context.WithValue(ctx, chaincode.HistoryQueryExecutorKey, historyQueryExecutor) 449 450 defer txsim.Done() 451 } 452 //this could be a request to a chainless SysCC 453 454 // TODO: if the proposal has an extension, it will be of type ChaincodeAction; 455 // if it's present it means that no simulation is to be performed because 456 // we're trying to emulate a submitting peer. On the other hand, we need 457 // to validate the supplied action before endorsing it 458 459 //1 -- simulate 460 cd, res, simulationResult, ccevent, err := e.simulateProposal(ctx, chainID, txid, signedProp, prop, hdrExt.ChaincodeId, txsim) 461 if err != nil { 462 return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err 463 } 464 if res != nil { 465 if res.Status >= shim.ERROR { 466 endorserLogger.Errorf("simulateProposal() resulted in chaincode response status %d for txid: %s", res.Status, txid) 467 var cceventBytes []byte 468 if ccevent != nil { 469 cceventBytes, err = putils.GetBytesChaincodeEvent(ccevent) 470 if err != nil { 471 return nil, fmt.Errorf("failed to marshal event bytes - %s", err) 472 } 473 } 474 pResp, err := putils.CreateProposalResponseFailure(prop.Header, prop.Payload, res, simulationResult, cceventBytes, hdrExt.ChaincodeId, hdrExt.PayloadVisibility) 475 if err != nil { 476 return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err 477 } 478 479 return pResp, &chaincodeError{res.Status, res.Message} 480 } 481 } 482 483 //2 -- endorse and get a marshalled ProposalResponse message 484 var pResp *pb.ProposalResponse 485 486 //TODO till we implement global ESCC, CSCC for system chaincodes 487 //chainless proposals (such as CSCC) don't have to be endorsed 488 if chainID == "" { 489 pResp = &pb.ProposalResponse{Response: res} 490 } else { 491 pResp, err = e.endorseProposal(ctx, chainID, txid, signedProp, prop, res, simulationResult, ccevent, hdrExt.PayloadVisibility, hdrExt.ChaincodeId, txsim, cd) 492 if err != nil { 493 return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err 494 } 495 if pResp != nil { 496 if res.Status >= shim.ERRORTHRESHOLD { 497 endorserLogger.Debugf("endorseProposal() resulted in chaincode error for txid: %s", txid) 498 return pResp, &chaincodeError{res.Status, res.Message} 499 } 500 } 501 } 502 503 // Set the proposal response payload - it 504 // contains the "return value" from the 505 // chaincode invocation 506 pResp.Response.Payload = res.Payload 507 508 return pResp, nil 509 } 510 511 // Only exposed for testing purposes - commit the tx simulation so that 512 // a deploy transaction is persisted and that chaincode can be invoked. 513 // This makes the endorser test self-sufficient 514 func (e *Endorser) commitTxSimulation(proposal *pb.Proposal, chainID string, signer msp.SigningIdentity, pResp *pb.ProposalResponse, blockNumber uint64) error { 515 tx, err := putils.CreateSignedTx(proposal, signer, pResp) 516 if err != nil { 517 return err 518 } 519 520 lgr := peer.GetLedger(chainID) 521 if lgr == nil { 522 return fmt.Errorf("failure while looking up the ledger") 523 } 524 525 txBytes, err := proto.Marshal(tx) 526 if err != nil { 527 return err 528 } 529 block := common.NewBlock(blockNumber, []byte{}) 530 block.Data.Data = [][]byte{txBytes} 531 block.Header.DataHash = block.Data.Hash() 532 if err = lgr.Commit(block); err != nil { 533 return err 534 } 535 536 return nil 537 }