567 lines
16 KiB
Go
567 lines
16 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package protoutil_test
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/hyperledger/fabric-protos-go/common"
|
|
pb "github.com/hyperledger/fabric-protos-go/peer"
|
|
"github.com/hyperledger/fabric/bccsp/sw"
|
|
"github.com/hyperledger/fabric/msp"
|
|
mspmgmt "github.com/hyperledger/fabric/msp/mgmt"
|
|
msptesttools "github.com/hyperledger/fabric/msp/mgmt/testtools"
|
|
"github.com/hyperledger/fabric/protoutil"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func createCIS() *pb.ChaincodeInvocationSpec {
|
|
return &pb.ChaincodeInvocationSpec{
|
|
ChaincodeSpec: &pb.ChaincodeSpec{
|
|
Type: pb.ChaincodeSpec_GOLANG,
|
|
ChaincodeId: &pb.ChaincodeID{Name: "chaincode_name"},
|
|
Input: &pb.ChaincodeInput{Args: [][]byte{[]byte("arg1"), []byte("arg2")}},
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestGetChaincodeDeploymentSpec(t *testing.T) {
|
|
_, err := protoutil.UnmarshalChaincodeDeploymentSpec([]byte("bad spec"))
|
|
require.Error(t, err, "Expected error with malformed spec")
|
|
|
|
cds, _ := proto.Marshal(&pb.ChaincodeDeploymentSpec{
|
|
ChaincodeSpec: &pb.ChaincodeSpec{
|
|
Type: pb.ChaincodeSpec_GOLANG,
|
|
},
|
|
})
|
|
_, err = protoutil.UnmarshalChaincodeDeploymentSpec(cds)
|
|
require.NoError(t, err, "Unexpected error getting deployment spec")
|
|
}
|
|
|
|
func TestCDSProposals(t *testing.T) {
|
|
var prop *pb.Proposal
|
|
var err error
|
|
var txid string
|
|
creator := []byte("creator")
|
|
cds := &pb.ChaincodeDeploymentSpec{
|
|
ChaincodeSpec: &pb.ChaincodeSpec{
|
|
Type: pb.ChaincodeSpec_GOLANG,
|
|
},
|
|
}
|
|
policy := []byte("policy")
|
|
escc := []byte("escc")
|
|
vscc := []byte("vscc")
|
|
chainID := "testchannelid"
|
|
|
|
// install
|
|
prop, txid, err = protoutil.CreateInstallProposalFromCDS(cds, creator)
|
|
require.NotNil(t, prop, "Install proposal should not be nil")
|
|
require.NoError(t, err, "Unexpected error creating install proposal")
|
|
require.NotEqual(t, "", txid, "txid should not be empty")
|
|
|
|
// deploy
|
|
prop, txid, err = protoutil.CreateDeployProposalFromCDS(chainID, cds, creator, policy, escc, vscc, nil)
|
|
require.NotNil(t, prop, "Deploy proposal should not be nil")
|
|
require.NoError(t, err, "Unexpected error creating deploy proposal")
|
|
require.NotEqual(t, "", txid, "txid should not be empty")
|
|
|
|
// upgrade
|
|
prop, txid, err = protoutil.CreateUpgradeProposalFromCDS(chainID, cds, creator, policy, escc, vscc, nil)
|
|
require.NotNil(t, prop, "Upgrade proposal should not be nil")
|
|
require.NoError(t, err, "Unexpected error creating upgrade proposal")
|
|
require.NotEqual(t, "", txid, "txid should not be empty")
|
|
}
|
|
|
|
func TestProposal(t *testing.T) {
|
|
// create a proposal from a ChaincodeInvocationSpec
|
|
prop, _, err := protoutil.CreateChaincodeProposalWithTransient(
|
|
common.HeaderType_ENDORSER_TRANSACTION,
|
|
testChannelID, createCIS(),
|
|
[]byte("creator"),
|
|
map[string][]byte{"certx": []byte("transient")})
|
|
if err != nil {
|
|
t.Fatalf("Could not create chaincode proposal, err %s\n", err)
|
|
return
|
|
}
|
|
|
|
// serialize the proposal
|
|
pBytes, err := proto.Marshal(prop)
|
|
if err != nil {
|
|
t.Fatalf("Could not serialize the chaincode proposal, err %s\n", err)
|
|
return
|
|
}
|
|
|
|
// deserialize it and expect it to be the same
|
|
propBack, err := protoutil.UnmarshalProposal(pBytes)
|
|
if err != nil {
|
|
t.Fatalf("Could not deserialize the chaincode proposal, err %s\n", err)
|
|
return
|
|
}
|
|
if !proto.Equal(prop, propBack) {
|
|
t.Fatalf("Proposal and deserialized proposals don't match\n")
|
|
return
|
|
}
|
|
|
|
// get back the header
|
|
hdr, err := protoutil.UnmarshalHeader(prop.Header)
|
|
if err != nil {
|
|
t.Fatalf("Could not extract the header from the proposal, err %s\n", err)
|
|
}
|
|
|
|
hdrBytes, err := protoutil.GetBytesHeader(hdr)
|
|
if err != nil {
|
|
t.Fatalf("Could not marshal the header, err %s\n", err)
|
|
}
|
|
|
|
hdr, err = protoutil.UnmarshalHeader(hdrBytes)
|
|
if err != nil {
|
|
t.Fatalf("Could not unmarshal the header, err %s\n", err)
|
|
}
|
|
|
|
chdr, err := protoutil.UnmarshalChannelHeader(hdr.ChannelHeader)
|
|
if err != nil {
|
|
t.Fatalf("Could not unmarshal channel header, err %s", err)
|
|
}
|
|
|
|
shdr, err := protoutil.UnmarshalSignatureHeader(hdr.SignatureHeader)
|
|
if err != nil {
|
|
t.Fatalf("Could not unmarshal signature header, err %s", err)
|
|
}
|
|
|
|
_, err = protoutil.GetBytesSignatureHeader(shdr)
|
|
if err != nil {
|
|
t.Fatalf("Could not marshal signature header, err %s", err)
|
|
}
|
|
|
|
// sanity check on header
|
|
if chdr.Type != int32(common.HeaderType_ENDORSER_TRANSACTION) ||
|
|
shdr.Nonce == nil ||
|
|
string(shdr.Creator) != "creator" {
|
|
t.Fatalf("Invalid header after unmarshalling\n")
|
|
return
|
|
}
|
|
|
|
// get back the header extension
|
|
hdrExt, err := protoutil.UnmarshalChaincodeHeaderExtension(chdr.Extension)
|
|
if err != nil {
|
|
t.Fatalf("Could not extract the header extensions from the proposal, err %s\n", err)
|
|
return
|
|
}
|
|
|
|
// sanity check on header extension
|
|
if string(hdrExt.ChaincodeId.Name) != "chaincode_name" {
|
|
t.Fatalf("Invalid header extension after unmarshalling\n")
|
|
return
|
|
}
|
|
|
|
cpp, err := protoutil.UnmarshalChaincodeProposalPayload(prop.Payload)
|
|
if err != nil {
|
|
t.Fatalf("could not unmarshal proposal payload")
|
|
}
|
|
|
|
cis, err := protoutil.UnmarshalChaincodeInvocationSpec(cpp.Input)
|
|
if err != nil {
|
|
t.Fatalf("could not unmarshal proposal chaincode invocation spec")
|
|
}
|
|
|
|
// sanity check on cis
|
|
if cis.ChaincodeSpec.Type != pb.ChaincodeSpec_GOLANG ||
|
|
cis.ChaincodeSpec.ChaincodeId.Name != "chaincode_name" ||
|
|
len(cis.ChaincodeSpec.Input.Args) != 2 ||
|
|
string(cis.ChaincodeSpec.Input.Args[0]) != "arg1" ||
|
|
string(cis.ChaincodeSpec.Input.Args[1]) != "arg2" {
|
|
t.Fatalf("Invalid chaincode invocation spec after unmarshalling\n")
|
|
return
|
|
}
|
|
|
|
if string(shdr.Creator) != "creator" {
|
|
t.Fatalf("Failed checking Creator field. Invalid value, expectext 'creator', got [%s]", string(shdr.Creator))
|
|
return
|
|
}
|
|
value, ok := cpp.TransientMap["certx"]
|
|
if !ok || string(value) != "transient" {
|
|
t.Fatalf("Failed checking Transient field. Invalid value, expectext 'transient', got [%s]", string(value))
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestProposalWithTxID(t *testing.T) {
|
|
// create a proposal from a ChaincodeInvocationSpec
|
|
prop, txid, err := protoutil.CreateChaincodeProposalWithTxIDAndTransient(
|
|
common.HeaderType_ENDORSER_TRANSACTION,
|
|
testChannelID,
|
|
createCIS(),
|
|
[]byte("creator"),
|
|
"testtx",
|
|
map[string][]byte{"certx": []byte("transient")},
|
|
)
|
|
require.Nil(t, err)
|
|
require.NotNil(t, prop)
|
|
require.Equal(t, txid, "testtx")
|
|
|
|
prop, txid, err = protoutil.CreateChaincodeProposalWithTxIDAndTransient(
|
|
common.HeaderType_ENDORSER_TRANSACTION,
|
|
testChannelID,
|
|
createCIS(),
|
|
[]byte("creator"),
|
|
"",
|
|
map[string][]byte{"certx": []byte("transient")},
|
|
)
|
|
require.Nil(t, err)
|
|
require.NotNil(t, prop)
|
|
require.NotEmpty(t, txid)
|
|
}
|
|
|
|
func TestProposalResponse(t *testing.T) {
|
|
events := &pb.ChaincodeEvent{
|
|
ChaincodeId: "ccid",
|
|
EventName: "EventName",
|
|
Payload: []byte("EventPayload"),
|
|
TxId: "TxID",
|
|
}
|
|
ccid := &pb.ChaincodeID{
|
|
Name: "ccid",
|
|
Version: "v1",
|
|
}
|
|
|
|
pHashBytes := []byte("proposal_hash")
|
|
pResponse := &pb.Response{Status: 200}
|
|
results := []byte("results")
|
|
eventBytes, err := protoutil.GetBytesChaincodeEvent(events)
|
|
if err != nil {
|
|
t.Fatalf("Failure while marshalling the ProposalResponsePayload")
|
|
return
|
|
}
|
|
|
|
// get the bytes of the response
|
|
pResponseBytes, err := protoutil.GetBytesResponse(pResponse)
|
|
if err != nil {
|
|
t.Fatalf("Failure while marshalling the Response")
|
|
return
|
|
}
|
|
|
|
// get the response from bytes
|
|
_, err = protoutil.UnmarshalResponse(pResponseBytes)
|
|
if err != nil {
|
|
t.Fatalf("Failure while unmarshalling the Response")
|
|
return
|
|
}
|
|
|
|
// get the bytes of the ProposalResponsePayload
|
|
prpBytes, err := protoutil.GetBytesProposalResponsePayload(pHashBytes, pResponse, results, eventBytes, ccid)
|
|
if err != nil {
|
|
t.Fatalf("Failure while marshalling the ProposalResponsePayload")
|
|
return
|
|
}
|
|
|
|
// get the ProposalResponsePayload message
|
|
prp, err := protoutil.UnmarshalProposalResponsePayload(prpBytes)
|
|
if err != nil {
|
|
t.Fatalf("Failure while unmarshalling the ProposalResponsePayload")
|
|
return
|
|
}
|
|
|
|
// get the ChaincodeAction message
|
|
act, err := protoutil.UnmarshalChaincodeAction(prp.Extension)
|
|
if err != nil {
|
|
t.Fatalf("Failure while unmarshalling the ChaincodeAction")
|
|
return
|
|
}
|
|
|
|
// sanity check on the action
|
|
if string(act.Results) != "results" {
|
|
t.Fatalf("Invalid actions after unmarshalling")
|
|
return
|
|
}
|
|
|
|
event, err := protoutil.UnmarshalChaincodeEvents(act.Events)
|
|
if err != nil {
|
|
t.Fatalf("Failure while unmarshalling the ChainCodeEvents")
|
|
return
|
|
}
|
|
|
|
// sanity check on the event
|
|
if string(event.ChaincodeId) != "ccid" {
|
|
t.Fatalf("Invalid actions after unmarshalling")
|
|
return
|
|
}
|
|
|
|
pr := &pb.ProposalResponse{
|
|
Payload: prpBytes,
|
|
Endorsement: &pb.Endorsement{Endorser: []byte("endorser"), Signature: []byte("signature")},
|
|
Version: 1, // TODO: pick right version number
|
|
Response: &pb.Response{Status: 200, Message: "OK"},
|
|
}
|
|
|
|
// create a proposal response
|
|
prBytes, err := protoutil.GetBytesProposalResponse(pr)
|
|
if err != nil {
|
|
t.Fatalf("Failure while marshalling the ProposalResponse")
|
|
return
|
|
}
|
|
|
|
// get the proposal response message back
|
|
prBack, err := protoutil.UnmarshalProposalResponse(prBytes)
|
|
if err != nil {
|
|
t.Fatalf("Failure while unmarshalling the ProposalResponse")
|
|
return
|
|
}
|
|
|
|
// sanity check on pr
|
|
if prBack.Response.Status != 200 ||
|
|
string(prBack.Endorsement.Signature) != "signature" ||
|
|
string(prBack.Endorsement.Endorser) != "endorser" ||
|
|
!bytes.Equal(prBack.Payload, prpBytes) {
|
|
t.Fatalf("Invalid ProposalResponse after unmarshalling")
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestEnvelope(t *testing.T) {
|
|
// create a proposal from a ChaincodeInvocationSpec
|
|
prop, _, err := protoutil.CreateChaincodeProposal(common.HeaderType_ENDORSER_TRANSACTION, testChannelID, createCIS(), signerSerialized)
|
|
if err != nil {
|
|
t.Fatalf("Could not create chaincode proposal, err %s\n", err)
|
|
return
|
|
}
|
|
|
|
response := &pb.Response{Status: 200, Payload: []byte("payload")}
|
|
result := []byte("res")
|
|
ccid := &pb.ChaincodeID{Name: "foo", Version: "v1"}
|
|
|
|
presp, err := protoutil.CreateProposalResponse(prop.Header, prop.Payload, response, result, nil, ccid, signer)
|
|
if err != nil {
|
|
t.Fatalf("Could not create proposal response, err %s\n", err)
|
|
return
|
|
}
|
|
|
|
tx, err := protoutil.CreateSignedTx(prop, signer, presp)
|
|
if err != nil {
|
|
t.Fatalf("Could not create signed tx, err %s\n", err)
|
|
return
|
|
}
|
|
|
|
envBytes, err := protoutil.GetBytesEnvelope(tx)
|
|
if err != nil {
|
|
t.Fatalf("Could not marshal envelope, err %s\n", err)
|
|
return
|
|
}
|
|
|
|
tx, err = protoutil.GetEnvelopeFromBlock(envBytes)
|
|
if err != nil {
|
|
t.Fatalf("Could not unmarshal envelope, err %s\n", err)
|
|
return
|
|
}
|
|
|
|
act2, err := protoutil.GetActionFromEnvelope(envBytes)
|
|
if err != nil {
|
|
t.Fatalf("Could not extract actions from envelop, err %s\n", err)
|
|
return
|
|
}
|
|
|
|
if act2.Response.Status != response.Status {
|
|
t.Fatalf("response staus don't match")
|
|
return
|
|
}
|
|
if !bytes.Equal(act2.Response.Payload, response.Payload) {
|
|
t.Fatalf("response payload don't match")
|
|
return
|
|
}
|
|
|
|
if !bytes.Equal(act2.Results, result) {
|
|
t.Fatalf("results don't match")
|
|
return
|
|
}
|
|
|
|
txpayl, err := protoutil.UnmarshalPayload(tx.Payload)
|
|
if err != nil {
|
|
t.Fatalf("Could not unmarshal payload, err %s\n", err)
|
|
return
|
|
}
|
|
|
|
tx2, err := protoutil.UnmarshalTransaction(txpayl.Data)
|
|
if err != nil {
|
|
t.Fatalf("Could not unmarshal Transaction, err %s\n", err)
|
|
return
|
|
}
|
|
|
|
sh, err := protoutil.UnmarshalSignatureHeader(tx2.Actions[0].Header)
|
|
if err != nil {
|
|
t.Fatalf("Could not unmarshal SignatureHeader, err %s\n", err)
|
|
return
|
|
}
|
|
|
|
if !bytes.Equal(sh.Creator, signerSerialized) {
|
|
t.Fatalf("creator does not match")
|
|
return
|
|
}
|
|
|
|
cap, err := protoutil.UnmarshalChaincodeActionPayload(tx2.Actions[0].Payload)
|
|
if err != nil {
|
|
t.Fatalf("Could not unmarshal ChaincodeActionPayload, err %s\n", err)
|
|
return
|
|
}
|
|
require.NotNil(t, cap)
|
|
|
|
prp, err := protoutil.UnmarshalProposalResponsePayload(cap.Action.ProposalResponsePayload)
|
|
if err != nil {
|
|
t.Fatalf("Could not unmarshal ProposalResponsePayload, err %s\n", err)
|
|
return
|
|
}
|
|
|
|
ca, err := protoutil.UnmarshalChaincodeAction(prp.Extension)
|
|
if err != nil {
|
|
t.Fatalf("Could not unmarshal ChaincodeAction, err %s\n", err)
|
|
return
|
|
}
|
|
|
|
if ca.Response.Status != response.Status {
|
|
t.Fatalf("response staus don't match")
|
|
return
|
|
}
|
|
if !bytes.Equal(ca.Response.Payload, response.Payload) {
|
|
t.Fatalf("response payload don't match")
|
|
return
|
|
}
|
|
|
|
if !bytes.Equal(ca.Results, result) {
|
|
t.Fatalf("results don't match")
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestProposalTxID(t *testing.T) {
|
|
nonce := []byte{1}
|
|
creator := []byte{2}
|
|
|
|
txid := protoutil.ComputeTxID(nonce, creator)
|
|
require.NotEmpty(t, txid, "TxID cannot be empty.")
|
|
require.Nil(t, protoutil.CheckTxID(txid, nonce, creator))
|
|
require.Error(t, protoutil.CheckTxID("", nonce, creator))
|
|
|
|
txid = protoutil.ComputeTxID(nil, nil)
|
|
require.NotEmpty(t, txid, "TxID cannot be empty.")
|
|
}
|
|
|
|
func TestComputeProposalTxID(t *testing.T) {
|
|
txid := protoutil.ComputeTxID([]byte{1}, []byte{1})
|
|
|
|
// Compute the function computed by ComputeTxID,
|
|
// namely, base64(sha256(nonce||creator))
|
|
hf := sha256.New()
|
|
hf.Write([]byte{1})
|
|
hf.Write([]byte{1})
|
|
hashOut := hf.Sum(nil)
|
|
txid2 := hex.EncodeToString(hashOut)
|
|
|
|
t.Logf("% x\n", hashOut)
|
|
t.Logf("% s\n", txid)
|
|
t.Logf("% s\n", txid2)
|
|
|
|
require.Equal(t, txid, txid2)
|
|
}
|
|
|
|
var (
|
|
signer msp.SigningIdentity
|
|
signerSerialized []byte
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
// setup the MSP manager so that we can sign/verify
|
|
err := msptesttools.LoadMSPSetupForTesting()
|
|
if err != nil {
|
|
fmt.Printf("Could not initialize msp")
|
|
os.Exit(-1)
|
|
}
|
|
cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())
|
|
if err != nil {
|
|
fmt.Printf("Could not initialize cryptoProvider")
|
|
os.Exit(-1)
|
|
}
|
|
signer, err = mspmgmt.GetLocalMSP(cryptoProvider).GetDefaultSigningIdentity()
|
|
if err != nil {
|
|
fmt.Printf("Could not get signer")
|
|
os.Exit(-1)
|
|
}
|
|
|
|
signerSerialized, err = signer.Serialize()
|
|
if err != nil {
|
|
fmt.Printf("Could not serialize identity")
|
|
os.Exit(-1)
|
|
}
|
|
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
func TestInvokedChaincodeName(t *testing.T) {
|
|
t.Run("Success", func(t *testing.T) {
|
|
name, err := protoutil.InvokedChaincodeName(protoutil.MarshalOrPanic(&pb.Proposal{
|
|
Payload: protoutil.MarshalOrPanic(&pb.ChaincodeProposalPayload{
|
|
Input: protoutil.MarshalOrPanic(&pb.ChaincodeInvocationSpec{
|
|
ChaincodeSpec: &pb.ChaincodeSpec{
|
|
ChaincodeId: &pb.ChaincodeID{
|
|
Name: "cscc",
|
|
},
|
|
},
|
|
}),
|
|
}),
|
|
}))
|
|
require.NoError(t, err)
|
|
require.Equal(t, "cscc", name)
|
|
})
|
|
|
|
t.Run("BadProposalBytes", func(t *testing.T) {
|
|
_, err := protoutil.InvokedChaincodeName([]byte("garbage"))
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "could not unmarshal proposal")
|
|
})
|
|
|
|
t.Run("BadChaincodeProposalBytes", func(t *testing.T) {
|
|
_, err := protoutil.InvokedChaincodeName(protoutil.MarshalOrPanic(&pb.Proposal{
|
|
Payload: []byte("garbage"),
|
|
}))
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "could not unmarshal chaincode proposal payload")
|
|
})
|
|
|
|
t.Run("BadChaincodeInvocationSpec", func(t *testing.T) {
|
|
_, err := protoutil.InvokedChaincodeName(protoutil.MarshalOrPanic(&pb.Proposal{
|
|
Payload: protoutil.MarshalOrPanic(&pb.ChaincodeProposalPayload{
|
|
Input: []byte("garbage"),
|
|
}),
|
|
}))
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "could not unmarshal chaincode invocation spec")
|
|
})
|
|
|
|
t.Run("NilChaincodeSpec", func(t *testing.T) {
|
|
_, err := protoutil.InvokedChaincodeName(protoutil.MarshalOrPanic(&pb.Proposal{
|
|
Payload: protoutil.MarshalOrPanic(&pb.ChaincodeProposalPayload{
|
|
Input: protoutil.MarshalOrPanic(&pb.ChaincodeInvocationSpec{}),
|
|
}),
|
|
}))
|
|
require.EqualError(t, err, "chaincode spec is nil")
|
|
})
|
|
|
|
t.Run("NilChaincodeID", func(t *testing.T) {
|
|
_, err := protoutil.InvokedChaincodeName(protoutil.MarshalOrPanic(&pb.Proposal{
|
|
Payload: protoutil.MarshalOrPanic(&pb.ChaincodeProposalPayload{
|
|
Input: protoutil.MarshalOrPanic(&pb.ChaincodeInvocationSpec{
|
|
ChaincodeSpec: &pb.ChaincodeSpec{},
|
|
}),
|
|
}),
|
|
}))
|
|
require.EqualError(t, err, "chaincode id is nil")
|
|
})
|
|
}
|