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:
- call
preProcess()
Method preprocess the signature proposal message to verify its validity - call
simulateProposal()
Method starts the chain code container and simulates the execution of the proposal. The result read/write set is recorded in the simulated trader. - call
endorseProposal()
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 pass
ValidateProposalMessage()
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…