Screwing hyperledger fabric source | Endorser node endorsement services

Article and code: github.com/blockchainG…

Branches: v1.1.0

Overview of endorsement

The Endorser Endorsement node provides the ProcessProposal() service interface to receive and process requests for signed proposal messages, start the user chain code container, execute the call chain code, and endorse the simulated execution results. The Peer node starts up by parsing the Peer. Handlers configuration item in the core.yaml file and building a list of authentication filters. If there is a legal type of authentication filter, you need to first after all authentication filter calls ProcessProposal filter () method to verify, check whether the identity certificate is overdue, for example, and then submitted to the endorsement of the server serverEndorser. ProcessProposal () method for processing. The function of the method is as follows:

func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedProposal) (*pb.ProposalResponse, error){...// Check and verify the validity of the signed proposal message
	vr, err := e.preProcess(signedProp)
	...
	// Create a trade emulator and a history query executor
	var txsim ledger.TxSimulator
	var historyQueryExecutor ledger.HistoryQueryExecutor
	ifchainID ! ="" {
		// Create a transaction simulator object
		iftxsim, err = e.s.GetTxSimulator(chainID, txid); err ! =nil {
			return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err
		}
		ifhistoryQueryExecutor, err = e.s.GetHistoryQueryExecutor(chainID); err ! =nil {
			// Create a history finder object
			return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err
		}
		// Add the history query executor to the context's KV key-value pair
		ctx = context.WithValue(ctx, chaincode.HistoryQueryExecutorKey, historyQueryExecutor)
	}
	
	// Simulate transaction execution
	cd, res, simulationResult, ccevent, err := e.simulateProposal(ctx, chainID, txid, signedProp, prop, hdrExt.ChaincodeId, txsim)
	iferr ! =nil {
		// Check the response message for the result of the trading emulation run
		return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err
	}
	ifres ! =nil{...// Create proposal response message with failed endorsement
			pResp, err := putils.CreateProposalResponseFailure(prop.Header, prop.Payload, res, simulationResult, cceventBytes, hdrExt.ChaincodeId, hdrExt.PayloadVisibility)
			iferr ! =nil {
				return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err
			}

			return pResp, &chaincodeError{res.Status, res.Message}
		}
	}

	// Call the ESCC system chain code to endorse the simulated execution result and reply to the proposal response message
	var pResp *pb.ProposalResponse
	if chainID == "" {
		pResp = &pb.ProposalResponse{Response: res}
	} else { // Signature endorsement
		pResp, err = e.endorseProposal(ctx, chainID, txid, signedProp, prop, res, simulationResult, ccevent, hdrExt.PayloadVisibility, hdrExt.ChaincodeId, txsim, cd)
		iferr ! =nil {
			return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err
		}
		ifpResp ! =nil {
			if res.Status >= shim.ERRORTHRESHOLD { // Check the response message for errors
				endorserLogger.Debugf("[%s][%s] endorseProposal() resulted in chaincode %s error for txid: %s", chainID, shorttxid(txid), hdrExt.ChaincodeId, txid)
				return pResp, &chaincodeError{res.Status, res.Message}
			}
		}
	}
	pResp.Response.Payload = res.Payload // Sets the chain proposal response message payload byte array containing the return value of the chain call
Copy the code

We mainly did the following things:

  1. callpreProcess()Method preprocess the signature proposal message to verify its validity
  2. callsimulateProposal()Method starts the chain code container and simulates the execution of the proposal. The result read/write set is recorded in the simulated trader.
  3. callendorseProposal()Method endorses the simulated execution results and returns a proposal response message.

The following contents will be closely around these parts for analysis.

Preprocess the signed proposal message

Enter the preProcess function:

① : Verify the validity of the signature proposal message format and signature

prop, hdr, hdrExt, err := validation.ValidateProposalMessage(signedProp)
Copy the code

②: Check whether the proposal message allows the system chain code to be called externally

// parse the ChannelHeader structure of the message ChannelHeader
	chdr, err := putils.UnmarshalChannelHeader(hdr.ChannelHeader)
	...
	// Parse the SignatureHeader structure
	shdr, err := putils.GetSignatureHeader(hdr.SignatureHeader)
	...
	// If it is a system chain code, check whether it is a system chain code that is allowed to be called from outside: CSCC, LSCC or QSCC
	if e.s.IsSysCCAndNotInvokableExternal(hdrExt.ChaincodeId.Name) {
		endorserLogger.Errorf("Error: an attempt was made by %#v to invoke system chaincode %s",
			shdr.Creator, hdrExt.ChaincodeId.Name)
		err = errors.Errorf("chaincode %s cannot be invoked through a proposal", hdrExt.ChaincodeId.Name)
		// Construct proposal response message object: status code 500 (error) and error message
		vr.resp = &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}
		return vr, err
	}
Copy the code

③ : Check the uniqueness of the signed proposal message and whether it meets the access policy of the specified channel

chainID := chdr.ChannelId // obtain the ChannelID ChannelID, which is the chainID
	//// checks the uniqueness of transaction ids in the ledger. Note that the ValidateProposalMessage() method already validates the validity of the transaction number ID
	txid := chdr.TxId
	if txid == "" {
		err = errors.New("invalid txID. It must be different from the empty string")
		vr.resp = &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}
		return vr, err
	}
	endorserLogger.Debugf("[%s][%s] processing txid: %s", chainID, shorttxid(txid), txid)
	ifchainID ! ="" {
		// Select * from table where transaction ID = 1;
		// If this object is found, the transaction is repeated
		if _, err = e.s.GetTransactionByID(chainID, txid); err == nil {
			return vr, errors.Errorf("duplicate transaction found [%s]. Creator [%x]", txid, shdr.Creator)
		}
		/// check whether it is the system chain code and ensure that it is the user chain code
		if! e.s.IsSysCC(hdrExt.ChaincodeId.Name) {//// Check whether the proposal complies with the WRITER write channel permission policy
			iferr = e.s.CheckACL(signedProp, chdr, shdr, hdrExt); err ! =nil {
				vr.resp = &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}
				return vr, err
			}
		}
  } else {}
Copy the code

The above three parts also need to be further refined, see the following analysis.

Verify message format and signature validity

(1) : call the validation ValidateProposalMessage () function, to check the legitimacy of the signature proposal message format with the signature, to get proposal messages, message header parsing and its extension fields.

1.1 check the header

chdr, shdr, err := validateCommonHeader(hdr)
Copy the code

The validation header does a few things:

  • The validateChannelHeader(CHDR) function checks the validity of the CHANNEL header CHDR. The header type should be ENDORSER_TRANSACTION, CONFIG_UPDATE, CONFIG, or PEER_RESOURCE_UPDATE. And the Epoch field should be 0;

  • ValidateSignatureHeader (SHDR) checks the validity of the signature header SHDR, the random number Nonce, and the message

    The signer Creator should not be nil, and the object has a non-zero byte count

1.2 Checking the validity of the message signature

err = checkSignatureFromCreator(shdr.Creator, signedProp.Signature, signedProp.ProposalBytes, chdr.ChannelId)
Copy the code

The method first obtains the identity deserialization component mspObj of the current channel, resolves the signer Creator of the signature header, and calls the creator.validate () method to verify whether Creator is a valid X.509 certificate of MSP. Then, the creator.Verify() method is called to obtain the hash method and message digest (hash value), and the id.msp.bccsp.verify () method is called by the BCCSP encryption security component of the MSP component to Verify the authenticity of the message signature

1.3 Verify that the transaction ID in the proposal message header is calculated correctly

err = utils.CheckProposalTxID(
		chdr.TxId,
		shdr.Nonce,
		shdr.Creator)
Copy the code

The hash value of the message random number Nonce (to prevent replay attack) combined with the signer Creator information is recalcated and compared with the transaction ID. If they match, the transaction ID is correct.

Check if it is a system chain code that allows external calls

if e.s.IsSysCCAndNotInvokableExternal(hdrExt.ChaincodeId.Name) {
		endorserLogger.Errorf("Error: an attempt was made by %#v to invoke system chaincode %s",
			shdr.Creator, hdrExt.ChaincodeId.Name)
		err = errors.Errorf("chaincode %s cannot be invoked through a proposal", hdrExt.ChaincodeId.Name)
		// Construct proposal response message object: status code 500 (error) and error message
		vr.resp = &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}
		return vr, err
	}
Copy the code

Check the uniqueness of the signed proposal message

The preProcess() method continues to check the uniqueness of the signed proposal message to prevent replay attacks. This method extracts the chain and transaction ID from the header of the proposal message channel, including two cases:

  • If the chain ID is not an empty string, you need to check the uniqueness of the transaction ID to ensure that the transaction has not been committed to the ledger before. That is, according to the transaction ID, the transaction data and the transaction verification code are obtained from the block file of the ledger and the block index database, and constructed as the processed transaction object. If the transaction data is successfully retrieved with no errors, the transaction data for the specified transaction ID has been saved in the ledger. Therefore, the current proposal message is a duplicate submission and an error is returned. Otherwise, the signature proposal message passes the message uniqueness check.
  • If the chain ID is an empty string, there is no need to check the uniqueness of the signature proposal message and validate the channel access policy, just passValidateProposalMessage()Function to validate the proposal message.

Check whether the access permission policy for the channel is met

First call IsSysCC function to check whether the chain code is system chain code. If it is a user chain code, the CheckACL method is called to check whether the signature proposal message meets the requirements of the channel Veto permission policy and allow the message to be submitted to the specified channel for further processing. CheckACL methods are as follows:

func (d *defaultACLProvider) CheckACL(resName string, channelID string, idinfo interface{}) error {
  policy := d.defaultPolicy(resName, true)...case *pb.SignedProposal:
		return d.policyChecker.CheckPolicy(channelID, policy, idinfo.(*pb.SignedProposal))
	case *common.Envelope:
		sd, err := idinfo.(*common.Envelope).AsSignedData()
		iferr ! =nil {
			return err
		}
		return d.policyChecker.CheckPolicyBySignedData(channelID, policy, sd)
}
Copy the code

Method first calls defaultPolicy() to get the defaultPolicy named resources.veto from cResourcePolicyMap, the global channel resource policy dictionary. For SignedProposal type signature proposal, CheckACL () method call d.p olicyChecker. CheckPolicy () method, check whether the signature proposal message satisfies the requirement of the Writers write permissions on the channel strategy.

Mock implementation proposal

The ProcessProposal() method starts the chain code container and initializes the chain code execution environment, simulates the execution of a legitimate signed proposal message, and records the simulated execution results in the transaction simulator. Where the public data (including public and private data hashes) is further signed and endorsed, and presented to the Orderer node for ordering blocks, while the private data is sent to other authorized nodes within the organization via the Gossip message protocol. The core functions are as follows:

func (e *Endorser) simulateProposal(ctx context.Context, chainID string, txid string, signedProp *pb.SignedProposal, prop *pb.Proposal, cid *pb.ChaincodeID, txsim ledger.TxSimulator) (resourcesconfig.ChaincodeDefinition, *pb.Response, []byte, *pb.ChaincodeEvent, error) {
	// Parse to get the chain code call specification object
	cis, err := putils.GetChaincodeInvocationSpec(prop)
	...
	//1 Check whether it is the system chain code
	if! e.s.IsSysCC(cid.Name) {// If you are calling the user chain code, you need to ensure that the chain code is instantiated
		// === = user chain code, by calling LSCC system chain code to obtain the ChaincodeData object ChaincodeData structure saved in the ledger
		// If there is a chain code data object, the chain code has been successfully instantiated
		cdLedger, err = e.s.GetChaincodeDefinition(ctx, chainID, txid, signedProp, prop, cid.Name, txsim)
		iferr ! =nil {
			return nil.nil.nil.nil, errors.WithMessage(err, fmt.Sprintf("make sure the chaincode %s has been successfully instantiated and try again", cid.Name))
		}
		// Get the saved version of the chain code
		version = cdLedger.CCVersion()
		// Check whether the instantiation policy in the proposal matches the instantiation policy in the invocation ledger
		err = e.s.CheckInstantiationPolicy(cid.Name, version, cdLedger)
		iferr ! =nil {
			return nil.nil.nil.nil, err
		}
	} else { // === Execute the system chain code, such as LSCC
		version = util.GetSysCCVersion() // Get the system link code version}...// 2 Starts the chain code container to call the chain code
	res, ccevent, err = e.callChaincode(ctx, chainID, version, txid, signedProp, prop, cis, cid, txsim)
	iferr ! =nil {
		endorserLogger.Errorf("[%s][%s] failed to invoke chaincode %s, error: %+v", chainID, shorttxid(txid), cid, err)
		return nil.nil.nil.nil, err
	}
	//3 Obtain and process transaction simulation execution results
	iftxsim ! =nil {
		ifsimResult, err = txsim.GetTxSimulationResults(); err ! =nil {
			return nil.nil.nil.nil, err
		}

		ifsimResult.PvtSimulationResults ! =nil { // Check the validity of the simulated result private data
			if cid.Name == "lscc" {
				// TODO: remove once we can store collection configuration outside of LSCC
				// Distribute private data
				return nil.nil.nil.nil, errors.New("Private data is forbidden to be used in instantiate")}iferr := e.distributePrivateData(chainID, txid, simResult.PvtSimulationResults); err ! =nil {
				return nil.nil.nil.nil, err
			}
		}
		// Distribute private data
		ifpubSimResBytes, err = simResult.GetPubSimulationBytes(); err ! =nil {
			return nil.nil.nil.nil, err
		}
	}
	return cdLedger, res, pubSimResBytes, ccevent, nil
}
Copy the code

Execute different instantiation strategies based on the chain code type

First call GetChaincodeInvocationSpec function, extracted from the proposal in the message chain code call specification object, then calls the IsSysCC (cid) Name) method, in order to match the default system chain code Name, to determine the current chain code type is user chain code or system chain code, It can be divided into user chain code and system chain code to check the instantiation policy.

① : user chain code

if! e.s.IsSysCC(cid.Name) {// If you are calling the user chain code, you need to ensure that the chain code is instantiated
		// === = user chain code, by calling LSCC system chain code to obtain the ChaincodeData object ChaincodeData structure saved in the ledger
		// If there is a chain code data object, the chain code has been successfully instantiated
		cdLedger, err = e.s.GetChaincodeDefinition(ctx, chainID, txid, signedProp, prop, cid.Name, txsim)
		iferr ! =nil {
			return nil.nil.nil.nil, errors.WithMessage(err, fmt.Sprintf("make sure the chaincode %s has been successfully instantiated and try again", cid.Name))
		}
		// Get the saved version of the chain code
		version = cdLedger.CCVersion()
		// Check whether the instantiation policy in the proposal matches the instantiation policy in the invocation ledger
		err = e.s.CheckInstantiationPolicy(cid.Name, version, cdLedger)
		iferr ! =nil {
			return nil.nil.nil.nil, err
		}
Copy the code

② : system chain code

else { // === Execute the system chain code, such as LSCC
		version = util.GetSysCCVersion() // Get the system link code version
	}
Copy the code

Start the chain code container

res, ccevent, err = e.callChaincode(ctx, chainID, version, txid, signedProp, prop, cis, cid, txsim)
Copy the code

① : Set the KV key-value pair of the transaction simulator in the context object, where the key is TXSimulatorKey and the value is txsim of the transaction simulator

iftxsim ! =nil {
		ctxt = context.WithValue(ctxt, chaincode.TXSimulatorKey, txsim)
	}
Copy the code

② : Check whether it is the system chain code according to the chain code name

scc := e.s.IsSysCC(cid.Name)
Copy the code

③ : Execute the chain code call

res, ccevent, err = e.s.Execute(ctxt, chainID, cid.Name, version, txid, scc, signedProp, prop, cis)
Copy the code

④ : Check the call chain code name LSCC

// The first parameter is the deploy deployment or upgrade upgrade, the second parameter is the chain ID, and the third parameter is the chain code deployment specification object
	if cid.Name == "lscc" && len(cis.ChaincodeSpec.Input.Args) >= 3 && (string(cis.ChaincodeSpec.Input.Args[0= =])"deploy" || string(cis.ChaincodeSpec.Input.Args[0= =])"upgrade") {
		var cds *pb.ChaincodeDeploymentSpec
		// Get and verify the chain code deployment specification
		cds, err = putils.GetChaincodeDeploymentSpec(cis.ChaincodeSpec.Input.Args[2])
		iferr ! =nil {
			return nil.nil, err
		}

		//this should not be a system chaincode
		// If you try to deploy/upgrade the system chain code, an error is reported
		if e.s.IsSysCC(cds.ChaincodeSpec.ChaincodeId.Name) {
			return nil.nil, errors.Errorf("attempting to deploy a system chaincode %s/%s", cds.ChaincodeSpec.ChaincodeId.Name, chainID)
		}
		// Perform deployment/upgrade chain code
		_, _, err = e.s.Execute(ctxt, chainID, cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version, txid, false, signedProp, prop, cds)
		iferr ! =nil {
			return nil.nil, err
		}
Copy the code

The actual startup process is done in E.S. Ecute, as follows:

/core/chaincode/exectransaction.go/Execute()

func Execute(ctxt context.Context, cccid *ccprovider.CCContext, spec interface{}) (*pb.Response, *pb.ChaincodeEvent, error){...// === sets the initial chain code message object
	// Deploy (instantiate) the deploy command or upgrade command: invoke the chain code Init() interface method
	cctyp := pb.ChaincodeMessage_INIT
	//// check that the chain code specification object type is ChaincodeDeploymentSpec or ChaincodeInvocationSpec
	if cds, _ = spec.(*pb.ChaincodeDeploymentSpec); cds == nil {
		if ci, _ = spec.(*pb.ChaincodeInvocationSpec); ci == nil {
			panic("Execute should be called with deployment or invocation spec")}// Invoke or query: invoke () interface method
		cctyp = pb.ChaincodeMessage_TRANSACTION
	}

	// === start the chain code container, return the chain code input parameters, etc
	/ / created - > established - > ready state
	_, cMsg, err := theChaincodeSupport.Launch(ctxt, cccid, spec)
	...
	// === Simulate the execution of the trade chain code and wait for completion, listening and returning the RESP response result message
	resp, err := theChaincodeSupport.Execute(ctxt, cccid, ccMsg, theChaincodeSupport.executetimeout)
	...
	// === Process the simulation execution result
	ifresp.ChaincodeEvent ! =nil{... }... }Copy the code

The startup action is done in the following method:

, cMsg, err := theChaincodeSupport.Launch(ctxt, cccid, spec)
Copy the code

The core of this method is: launchAndWaitForRegister (), responsible for specific containers chain code, the code position: / core/chaincode/chaincode_support. Go/launchAndWaitForRegister

func (chaincodeSupport *ChaincodeSupport) launchAndWaitForRegister(ctxt context.Context, cccid *ccprovider.CCContext, cds *pb.ChaincodeDeploymentSpec, launcher launcherIntf) error{...// If the chaincodeMap dictionary already contains the corresponding chaincodeMap specification name, the chaincodeMap container has been started
	if _, hasBeenLaunched := chaincodeSupport.chaincodeHasBeenLaunched(canName); hasBeenLaunched {
		...
	}
	// Check whether the chain code container is running properly
	ifchaincodeSupport.launchStarted(canName) { ... }...// The core method: starts the container and actually calls the ccLauncherImpl method
		resp, err := launcher.launch(ctxt, notfy)
	...
	// === block waiting for processing response message, waiting for REGISTER code message
	select {
	case ok := <-notfy:
		// The Peer receives the REGISTER message from the container, triggering the FSM node of the Handler to run.
		// In the callback method beForeregister (), the notfy channel passed by the outer Handler is registered with the Peer Handler,
		// According to the result of the chain code registration success, the result message is placed in the Notfy channel, which triggers the SELECT statement here.
		// If notfy is flase, the registration failed. Otherwise, the registration is successful. }Copy the code

At this point, the chaincode.execute () function checks and starts the chain container to complete the chain request.

Process simulation execution results

Processing the simulation results is implemented by the following code:

//=== = Gets and processes the results of the transaction simulation execution
	iftxsim ! =nil {
		ifsimResult, err = txsim.GetTxSimulationResults(); err ! =nil {
			return nil.nil.nil.nil, err
		}

		ifsimResult.PvtSimulationResults ! =nil { // Check the validity of the simulated result private data
			if cid.Name == "lscc" {
				// TODO: remove once we can store collection configuration outside of LSCC
				return nil.nil.nil.nil, errors.New("Private data is forbidden to be used in instantiate")}// Distribute private data
			iferr := e.distributePrivateData(chainID, txid, simResult.PvtSimulationResults); err ! =nil {
				return nil.nil.nil.nil, err
			}
		}

		ifpubSimResBytes, err = simResult.GetPubSimulationBytes(); err ! =nil {
			return nil.nil.nil.nil, err
		}
	}
Copy the code

The two key functions are GetTxSimulationResults and distributePrivateData.

GetTxSimulationResults mainly obtains the read-write set of the private data of the transaction simulation execution result, then traverses the hash value of the calculation set of the private data, then obtains the read-write set of the public data of the transaction simulation execution result, finally constructs the TxSimulationResults structure object and returns it.

DistributePrivateData first get a specified channel handle to the privacy of data processing, and then through the handler. Distributor. Distribute Distribute data privacy,

Finally, the private data read and write set privData of the specified transaction txID is temporarily saved to the local transient privacy database through the Coordinator module. After submitting block data and privacy data, the Committer billing node actively deletes the privacy data associated with the TRANSIENT privacy database, and timely clears the expired data.

Sign and endorse the simulation results

The endorseProposal() method endorses the simulated execution result and returns the proposal response message.

func (e *Endorser) endorseProposal(...). (*pb.ProposalResponse, error){...// Call the ESCC system chain code for endorsement
	res, _, err := e.callChaincode(ctx, chainID, version, txid, signedProp, proposal, ecccis, &pb.ChaincodeID{Name: escc}, txsim)
	...
}
Copy the code

CallChaincode () method call ESCC system chain code EndorserOneValidSignature. Invoke () method, signature endorsed operation on the result of the simulation. The code is as follows:

Location: / core/SCC/escc endorser_onevalidsignature. Go/Invoke

func (e *EndorserOneValidSignature) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
	args := stub.GetArgs() // Get the parameter list
	// Check the number of parameters
	if len(args) < 6 {
		return shim.Error(fmt.Sprintf("Incorrect number of arguments (expected a minimum of 5, provided %d)".len(args)))
	} else if len(args) > 8 {
		return shim.Error(fmt.Sprintf("Incorrect number of arguments (expected a maximum of 7, provided %d)".len(args)))
	}
	...
	// Get the execution chain response message
	response, err := putils.GetResponse(args[4])...// Get the simulation execution result
	results = args[5]

	/..
	// Get the local MSP component
	localMsp := mspmgmt.GetLocalMSP()
	...
	// Get the local default signer identity entity (i.e. endorsement member)
	signingEndorser, err := localMsp.GetDefaultSigningIdentity()
	...
	// Create a signed proposal response message
	presp, err := utils.CreateProposalResponse(hdr, payl, response, results, events, ccid, visibility, signingEndorser)
	...
	// Serialize the proposal response byte array
	prBytes, err := utils.GetBytesProposalResponse(presp)
	...
	// Reply to execution success message
	return shim.Success(prBytes)
}

Copy the code

At this point, the Endorser endorsement node processes the signed proposal message.

reference

Github.com/blockchainG…