842 lines
25 KiB
Go
842 lines
25 KiB
Go
/*
|
|
Copyright Digital Asset Holdings, LLC. All Rights Reserved.
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package chaincode
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
cb "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/common/policydsl"
|
|
"github.com/hyperledger/fabric/core/config/configtest"
|
|
"github.com/hyperledger/fabric/internal/peer/chaincode/mock"
|
|
"github.com/hyperledger/fabric/internal/peer/common"
|
|
"github.com/hyperledger/fabric/internal/pkg/identity"
|
|
. "github.com/onsi/gomega"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
//go:generate counterfeiter -o mock/signer_serializer.go --fake-name SignerSerializer . signerSerializer
|
|
|
|
type signerSerializer interface {
|
|
identity.SignerSerializer
|
|
}
|
|
|
|
//go:generate counterfeiter -o mock/deliver.go --fake-name Deliver . deliver
|
|
|
|
type deliver interface {
|
|
pb.Deliver_DeliverClient
|
|
}
|
|
|
|
//go:generate counterfeiter -o mock/deliver_client.go --fake-name PeerDeliverClient . peerDeliverClient
|
|
|
|
type peerDeliverClient interface {
|
|
pb.DeliverClient
|
|
}
|
|
|
|
func TestCheckChaincodeCmdParamsWithNewCallingSchema(t *testing.T) {
|
|
chaincodeCtorJSON = `{ "Args":["func", "param"] }`
|
|
chaincodePath = "some/path"
|
|
chaincodeName = "somename"
|
|
require := require.New(t)
|
|
result := checkChaincodeCmdParams(&cobra.Command{})
|
|
|
|
require.Nil(result)
|
|
}
|
|
|
|
func TestCheckChaincodeCmdParamsWithOldCallingSchema(t *testing.T) {
|
|
chaincodeCtorJSON = `{ "Function":"func", "Args":["param"] }`
|
|
chaincodePath = "some/path"
|
|
chaincodeName = "somename"
|
|
require := require.New(t)
|
|
result := checkChaincodeCmdParams(&cobra.Command{})
|
|
|
|
require.Nil(result)
|
|
}
|
|
|
|
func TestCheckChaincodeCmdParamsWithoutName(t *testing.T) {
|
|
chaincodeCtorJSON = `{ "Function":"func", "Args":["param"] }`
|
|
chaincodePath = "some/path"
|
|
chaincodeName = ""
|
|
require := require.New(t)
|
|
result := checkChaincodeCmdParams(&cobra.Command{})
|
|
|
|
require.Error(result)
|
|
}
|
|
|
|
func TestCheckChaincodeCmdParamsWithFunctionOnly(t *testing.T) {
|
|
chaincodeCtorJSON = `{ "Function":"func" }`
|
|
chaincodePath = "some/path"
|
|
chaincodeName = "somename"
|
|
require := require.New(t)
|
|
result := checkChaincodeCmdParams(&cobra.Command{})
|
|
|
|
require.Error(result)
|
|
}
|
|
|
|
func TestCheckChaincodeCmdParamsEmptyCtor(t *testing.T) {
|
|
chaincodeCtorJSON = `{}`
|
|
chaincodePath = "some/path"
|
|
chaincodeName = "somename"
|
|
require := require.New(t)
|
|
result := checkChaincodeCmdParams(&cobra.Command{})
|
|
|
|
require.Error(result)
|
|
}
|
|
|
|
func TestCheckValidJSON(t *testing.T) {
|
|
validJSON := `{"Args":["a","b","c"]}`
|
|
input := &chaincodeInput{}
|
|
if err := json.Unmarshal([]byte(validJSON), &input); err != nil {
|
|
t.Fail()
|
|
t.Logf("Chaincode argument error: %s", err)
|
|
return
|
|
}
|
|
|
|
validJSON = `{"Function":"f", "Args":["a","b","c"]}`
|
|
if err := json.Unmarshal([]byte(validJSON), &input); err != nil {
|
|
t.Fail()
|
|
t.Logf("Chaincode argument error: %s", err)
|
|
return
|
|
}
|
|
|
|
validJSON = `{"Function":"f", "Args":[]}`
|
|
if err := json.Unmarshal([]byte(validJSON), &input); err != nil {
|
|
t.Fail()
|
|
t.Logf("Chaincode argument error: %s", err)
|
|
return
|
|
}
|
|
|
|
validJSON = `{"Function":"f"}`
|
|
if err := json.Unmarshal([]byte(validJSON), &input); err != nil {
|
|
t.Fail()
|
|
t.Logf("Chaincode argument error: %s", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestCheckInvalidJSON(t *testing.T) {
|
|
invalidJSON := `{["a","b","c"]}`
|
|
input := &chaincodeInput{}
|
|
if err := json.Unmarshal([]byte(invalidJSON), &input); err == nil {
|
|
t.Fail()
|
|
t.Logf("Bar argument error should have been caught: %s", invalidJSON)
|
|
return
|
|
}
|
|
|
|
invalidJSON = `{"Function":}`
|
|
if err := json.Unmarshal([]byte(invalidJSON), &input); err == nil {
|
|
t.Fail()
|
|
t.Logf("Chaincode argument error: %s", err)
|
|
t.Logf("Bar argument error should have been caught: %s", invalidJSON)
|
|
return
|
|
}
|
|
}
|
|
|
|
const sampleCollectionConfigGood = `[
|
|
{
|
|
"name": "foo",
|
|
"policy": "OR('A.member', 'B.member')",
|
|
"requiredPeerCount": 3,
|
|
"maxPeerCount": 483279847,
|
|
"blockToLive":10,
|
|
"memberOnlyRead": true,
|
|
"memberOnlyWrite": true
|
|
}
|
|
]`
|
|
|
|
const sampleCollectionConfigGoodNoMaxPeerCountOrRequiredPeerCount = `[
|
|
{
|
|
"name": "foo",
|
|
"policy": "OR('A.member', 'B.member')",
|
|
"blockToLive":10,
|
|
"memberOnlyRead": true,
|
|
"memberOnlyWrite": true
|
|
}
|
|
]`
|
|
|
|
const sampleCollectionConfigGoodWithSignaturePolicy = `[
|
|
{
|
|
"name": "foo",
|
|
"policy": "OR('A.member', 'B.member')",
|
|
"requiredPeerCount": 3,
|
|
"maxPeerCount": 483279847,
|
|
"blockToLive":10,
|
|
"memberOnlyRead": true,
|
|
"memberOnlyWrite": true,
|
|
"endorsementPolicy": {
|
|
"signaturePolicy": "OR('A.member', 'B.member')"
|
|
}
|
|
}
|
|
]`
|
|
|
|
const sampleCollectionConfigGoodWithChannelConfigPolicy = `[
|
|
{
|
|
"name": "foo",
|
|
"policy": "OR('A.member', 'B.member')",
|
|
"requiredPeerCount": 3,
|
|
"maxPeerCount": 483279847,
|
|
"blockToLive":10,
|
|
"memberOnlyRead": true,
|
|
"memberOnlyWrite": true,
|
|
"endorsementPolicy": {
|
|
"channelConfigPolicy": "/Channel/Application/Endorsement"
|
|
}
|
|
}
|
|
]`
|
|
|
|
const sampleCollectionConfigBad = `[
|
|
{
|
|
"name": "foo",
|
|
"policy": "barf",
|
|
"requiredPeerCount": 3,
|
|
"maxPeerCount": 483279847
|
|
}
|
|
]`
|
|
|
|
const sampleCollectionConfigBadInvalidSignaturePolicy = `[
|
|
{
|
|
"name": "foo",
|
|
"policy": "OR('A.member', 'B.member')",
|
|
"requiredPeerCount": 3,
|
|
"maxPeerCount": 483279847,
|
|
"blockToLive":10,
|
|
"memberOnlyRead": true,
|
|
"memberOnlyWrite": true,
|
|
"endorsementPolicy": {
|
|
"signaturePolicy": "invalid"
|
|
}
|
|
}
|
|
]`
|
|
|
|
const sampleCollectionConfigBadSignaturePolicyAndChannelConfigPolicy = `[
|
|
{
|
|
"name": "foo",
|
|
"policy": "OR('A.member', 'B.member')",
|
|
"requiredPeerCount": 3,
|
|
"maxPeerCount": 483279847,
|
|
"blockToLive":10,
|
|
"memberOnlyRead": true,
|
|
"memberOnlyWrite": true,
|
|
"endorsementPolicy": {
|
|
"signaturePolicy": "OR('A.member', 'B.member')",
|
|
"channelConfigPolicy": "/Channel/Application/Endorsement"
|
|
}
|
|
}
|
|
]`
|
|
|
|
func TestCollectionParsing(t *testing.T) {
|
|
ccp, ccpBytes, err := getCollectionConfigFromBytes([]byte(sampleCollectionConfigGood))
|
|
require.NoError(t, err)
|
|
require.NotNil(t, ccp)
|
|
require.NotNil(t, ccpBytes)
|
|
conf := ccp.Config[0].GetStaticCollectionConfig()
|
|
pol, _ := policydsl.FromString("OR('A.member', 'B.member')")
|
|
require.Equal(t, 3, int(conf.RequiredPeerCount))
|
|
require.Equal(t, 483279847, int(conf.MaximumPeerCount))
|
|
require.Equal(t, "foo", conf.Name)
|
|
require.True(t, proto.Equal(pol, conf.MemberOrgsPolicy.GetSignaturePolicy()))
|
|
require.Equal(t, 10, int(conf.BlockToLive))
|
|
require.Equal(t, true, conf.MemberOnlyRead)
|
|
require.Nil(t, conf.EndorsementPolicy)
|
|
t.Logf("conf=%s", conf)
|
|
|
|
// Test default values for RequiredPeerCount and MaxPeerCount
|
|
ccp, ccpBytes, err = getCollectionConfigFromBytes([]byte(sampleCollectionConfigGoodNoMaxPeerCountOrRequiredPeerCount))
|
|
require.NoError(t, err)
|
|
require.NotNil(t, ccp)
|
|
require.NotNil(t, ccpBytes)
|
|
conf = ccp.Config[0].GetStaticCollectionConfig()
|
|
pol, _ = policydsl.FromString("OR('A.member', 'B.member')")
|
|
require.Equal(t, 0, int(conf.RequiredPeerCount))
|
|
require.Equal(t, 1, int(conf.MaximumPeerCount))
|
|
require.Equal(t, "foo", conf.Name)
|
|
require.True(t, proto.Equal(pol, conf.MemberOrgsPolicy.GetSignaturePolicy()))
|
|
require.Equal(t, 10, int(conf.BlockToLive))
|
|
require.Equal(t, true, conf.MemberOnlyRead)
|
|
require.Nil(t, conf.EndorsementPolicy)
|
|
t.Logf("conf=%s", conf)
|
|
|
|
ccp, ccpBytes, err = getCollectionConfigFromBytes([]byte(sampleCollectionConfigGoodWithSignaturePolicy))
|
|
require.NoError(t, err)
|
|
require.NotNil(t, ccp)
|
|
require.NotNil(t, ccpBytes)
|
|
conf = ccp.Config[0].GetStaticCollectionConfig()
|
|
pol, _ = policydsl.FromString("OR('A.member', 'B.member')")
|
|
require.Equal(t, 3, int(conf.RequiredPeerCount))
|
|
require.Equal(t, 483279847, int(conf.MaximumPeerCount))
|
|
require.Equal(t, "foo", conf.Name)
|
|
require.True(t, proto.Equal(pol, conf.MemberOrgsPolicy.GetSignaturePolicy()))
|
|
require.Equal(t, 10, int(conf.BlockToLive))
|
|
require.Equal(t, true, conf.MemberOnlyRead)
|
|
require.True(t, proto.Equal(pol, conf.EndorsementPolicy.GetSignaturePolicy()))
|
|
t.Logf("conf=%s", conf)
|
|
|
|
ccp, ccpBytes, err = getCollectionConfigFromBytes([]byte(sampleCollectionConfigGoodWithChannelConfigPolicy))
|
|
require.NoError(t, err)
|
|
require.NotNil(t, ccp)
|
|
require.NotNil(t, ccpBytes)
|
|
conf = ccp.Config[0].GetStaticCollectionConfig()
|
|
pol, _ = policydsl.FromString("OR('A.member', 'B.member')")
|
|
require.Equal(t, 3, int(conf.RequiredPeerCount))
|
|
require.Equal(t, 483279847, int(conf.MaximumPeerCount))
|
|
require.Equal(t, "foo", conf.Name)
|
|
require.True(t, proto.Equal(pol, conf.MemberOrgsPolicy.GetSignaturePolicy()))
|
|
require.Equal(t, 10, int(conf.BlockToLive))
|
|
require.Equal(t, true, conf.MemberOnlyRead)
|
|
require.Equal(t, "/Channel/Application/Endorsement", conf.EndorsementPolicy.GetChannelConfigPolicyReference())
|
|
t.Logf("conf=%s", conf)
|
|
|
|
failureTests := []struct {
|
|
name string
|
|
collectionConfig string
|
|
expectedErr string
|
|
}{
|
|
{
|
|
name: "Invalid member orgs policy",
|
|
collectionConfig: sampleCollectionConfigBad,
|
|
expectedErr: "invalid policy barf: unrecognized token 'barf' in policy string",
|
|
},
|
|
{
|
|
name: "Invalid collection config",
|
|
collectionConfig: "barf",
|
|
expectedErr: "could not parse the collection configuration: invalid character 'b' looking for beginning of value",
|
|
},
|
|
{
|
|
name: "Invalid signature policy",
|
|
collectionConfig: sampleCollectionConfigBadInvalidSignaturePolicy,
|
|
expectedErr: `invalid endorsement policy [&chaincode.endorsementPolicy{ChannelConfigPolicy:"", SignaturePolicy:"invalid"}]: invalid signature policy: invalid`,
|
|
},
|
|
{
|
|
name: "Signature policy and channel config policy both specified",
|
|
collectionConfig: sampleCollectionConfigBadSignaturePolicyAndChannelConfigPolicy,
|
|
expectedErr: `invalid endorsement policy [&chaincode.endorsementPolicy{ChannelConfigPolicy:"/Channel/Application/Endorsement", SignaturePolicy:"OR('A.member', 'B.member')"}]: cannot specify both "--signature-policy" and "--channel-config-policy"`,
|
|
},
|
|
}
|
|
|
|
for _, test := range failureTests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ccp, ccpBytes, err = getCollectionConfigFromBytes([]byte(test.collectionConfig))
|
|
require.EqualError(t, err, test.expectedErr)
|
|
require.Nil(t, ccp)
|
|
require.Nil(t, ccpBytes)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidatePeerConnectionParams(t *testing.T) {
|
|
defer resetFlags()
|
|
defer viper.Reset()
|
|
require := require.New(t)
|
|
configtest.SetDevFabricConfigPath(t)
|
|
|
|
// TLS disabled
|
|
viper.Set("peer.tls.enabled", false)
|
|
|
|
// failure - more than one peer and TLS root cert - not invoke
|
|
resetFlags()
|
|
peerAddresses = []string{"peer0", "peer1"}
|
|
tlsRootCertFiles = []string{"cert0", "cert1"}
|
|
err := validatePeerConnectionParameters("query")
|
|
require.Error(err)
|
|
require.Contains(err.Error(), "command can only be executed against one peer")
|
|
|
|
// success - peer provided and no TLS root certs
|
|
// TLS disabled
|
|
resetFlags()
|
|
peerAddresses = []string{"peer0"}
|
|
err = validatePeerConnectionParameters("query")
|
|
require.NoError(err)
|
|
require.Nil(tlsRootCertFiles)
|
|
|
|
// success - more TLS root certs than peers
|
|
// TLS disabled
|
|
resetFlags()
|
|
peerAddresses = []string{"peer0"}
|
|
tlsRootCertFiles = []string{"cert0", "cert1"}
|
|
err = validatePeerConnectionParameters("invoke")
|
|
require.NoError(err)
|
|
require.Nil(tlsRootCertFiles)
|
|
|
|
// success - multiple peers and no TLS root certs - invoke
|
|
// TLS disabled
|
|
resetFlags()
|
|
peerAddresses = []string{"peer0", "peer1"}
|
|
err = validatePeerConnectionParameters("invoke")
|
|
require.NoError(err)
|
|
require.Nil(tlsRootCertFiles)
|
|
|
|
// TLS enabled
|
|
viper.Set("peer.tls.enabled", true)
|
|
|
|
// failure - uneven number of peers and TLS root certs - invoke
|
|
// TLS enabled
|
|
resetFlags()
|
|
peerAddresses = []string{"peer0", "peer1"}
|
|
tlsRootCertFiles = []string{"cert0"}
|
|
err = validatePeerConnectionParameters("invoke")
|
|
require.Error(err)
|
|
require.Contains(err.Error(), fmt.Sprintf("number of peer addresses (%d) does not match the number of TLS root cert files (%d)", len(peerAddresses), len(tlsRootCertFiles)))
|
|
|
|
// success - more than one peer and TLS root certs - invoke
|
|
// TLS enabled
|
|
resetFlags()
|
|
peerAddresses = []string{"peer0", "peer1"}
|
|
tlsRootCertFiles = []string{"cert0", "cert1"}
|
|
err = validatePeerConnectionParameters("invoke")
|
|
require.NoError(err)
|
|
|
|
// failure - connection profile doesn't exist
|
|
resetFlags()
|
|
connectionProfile = "blah"
|
|
err = validatePeerConnectionParameters("invoke")
|
|
require.Error(err)
|
|
require.Contains(err.Error(), "error reading connection profile")
|
|
|
|
// failure - connection profile has peer defined in channel config but
|
|
// not in peer config
|
|
resetFlags()
|
|
channelID = "mychannel"
|
|
connectionProfile = "testdata/connectionprofile-uneven.yaml"
|
|
err = validatePeerConnectionParameters("invoke")
|
|
require.Error(err)
|
|
require.Contains(err.Error(), "defined in the channel config but doesn't have associated peer config")
|
|
|
|
// success - connection profile exists
|
|
resetFlags()
|
|
channelID = "mychannel"
|
|
connectionProfile = "testdata/connectionprofile.yaml"
|
|
err = validatePeerConnectionParameters("invoke")
|
|
require.NoError(err)
|
|
}
|
|
|
|
func TestInitCmdFactoryFailures(t *testing.T) {
|
|
defer resetFlags()
|
|
require := require.New(t)
|
|
|
|
cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())
|
|
require.Nil(err)
|
|
|
|
// failure validating peer connection parameters
|
|
resetFlags()
|
|
peerAddresses = []string{"peer0", "peer1"}
|
|
tlsRootCertFiles = []string{"cert0", "cert1"}
|
|
cf, err := InitCmdFactory("query", true, false, cryptoProvider)
|
|
require.Error(err)
|
|
require.Contains(err.Error(), "error validating peer connection parameters: 'query' command can only be executed against one peer")
|
|
require.Nil(cf)
|
|
|
|
// failure - no peers supplied and endorser client is needed
|
|
resetFlags()
|
|
peerAddresses = []string{}
|
|
cf, err = InitCmdFactory("query", true, false, cryptoProvider)
|
|
require.Error(err)
|
|
require.Contains(err.Error(), "no endorser clients retrieved")
|
|
require.Nil(cf)
|
|
|
|
// failure - orderer client is needed, ordering endpoint is empty and no
|
|
// endorser client supplied
|
|
resetFlags()
|
|
peerAddresses = nil
|
|
cf, err = InitCmdFactory("invoke", false, true, cryptoProvider)
|
|
require.Error(err)
|
|
require.Contains(err.Error(), "no ordering endpoint or endorser client supplied")
|
|
require.Nil(cf)
|
|
}
|
|
|
|
func TestDeliverGroupConnect(t *testing.T) {
|
|
defer resetFlags()
|
|
g := NewGomegaWithT(t)
|
|
|
|
// success
|
|
mockDeliverClients := []*DeliverClient{
|
|
{
|
|
Client: getMockDeliverClientResponseWithTxStatusAndID(pb.TxValidationCode_VALID, "txid0"),
|
|
Address: "peer0",
|
|
},
|
|
{
|
|
Client: getMockDeliverClientResponseWithTxStatusAndID(pb.TxValidationCode_VALID, "txid0"),
|
|
Address: "peer1",
|
|
},
|
|
}
|
|
dg := DeliverGroup{
|
|
Clients: mockDeliverClients,
|
|
ChannelID: "testchannel",
|
|
Signer: &mock.SignerSerializer{},
|
|
Certificate: tls.Certificate{
|
|
Certificate: [][]byte{[]byte("test")},
|
|
},
|
|
TxID: "txid0",
|
|
}
|
|
err := dg.Connect(context.Background())
|
|
g.Expect(err).To(BeNil())
|
|
|
|
// failure - DeliverFiltered returns error
|
|
mockDC := &mock.PeerDeliverClient{}
|
|
mockDC.DeliverFilteredReturns(nil, errors.New("icecream"))
|
|
mockDeliverClients = []*DeliverClient{
|
|
{
|
|
Client: mockDC,
|
|
Address: "peer0",
|
|
},
|
|
}
|
|
dg = DeliverGroup{
|
|
Clients: mockDeliverClients,
|
|
ChannelID: "testchannel",
|
|
Signer: &mock.SignerSerializer{},
|
|
Certificate: tls.Certificate{
|
|
Certificate: [][]byte{[]byte("test")},
|
|
},
|
|
TxID: "txid0",
|
|
}
|
|
err = dg.Connect(context.Background())
|
|
g.Expect(err.Error()).To(ContainSubstring("error connecting to deliver filtered"))
|
|
g.Expect(err.Error()).To(ContainSubstring("icecream"))
|
|
|
|
// failure - Send returns error
|
|
mockD := &mock.Deliver{}
|
|
mockD.SendReturns(errors.New("blah"))
|
|
mockDC.DeliverFilteredReturns(mockD, nil)
|
|
mockDeliverClients = []*DeliverClient{
|
|
{
|
|
Client: mockDC,
|
|
Address: "peer0",
|
|
},
|
|
}
|
|
dg = DeliverGroup{
|
|
Clients: mockDeliverClients,
|
|
ChannelID: "testchannel",
|
|
Signer: &mock.SignerSerializer{},
|
|
Certificate: tls.Certificate{
|
|
Certificate: [][]byte{[]byte("test")},
|
|
},
|
|
TxID: "txid0",
|
|
}
|
|
err = dg.Connect(context.Background())
|
|
g.Expect(err.Error()).To(ContainSubstring("error sending deliver seek info"))
|
|
g.Expect(err.Error()).To(ContainSubstring("blah"))
|
|
|
|
// failure - deliver registration timeout
|
|
delayChan := make(chan struct{})
|
|
mockDCDelay := getMockDeliverClientRegisterAfterDelay(delayChan)
|
|
mockDeliverClients = []*DeliverClient{
|
|
{
|
|
Client: mockDCDelay,
|
|
Address: "peer0",
|
|
},
|
|
}
|
|
ctx, cancelFunc := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
|
defer cancelFunc()
|
|
dg = DeliverGroup{
|
|
Clients: mockDeliverClients,
|
|
ChannelID: "testchannel",
|
|
Signer: &mock.SignerSerializer{},
|
|
Certificate: tls.Certificate{
|
|
Certificate: [][]byte{[]byte("test")},
|
|
},
|
|
TxID: "txid0",
|
|
}
|
|
err = dg.Connect(ctx)
|
|
g.Expect(err.Error()).To(ContainSubstring("timed out waiting for connection to deliver on all peers"))
|
|
close(delayChan)
|
|
}
|
|
|
|
func TestDeliverGroupWait(t *testing.T) {
|
|
defer resetFlags()
|
|
g := NewGomegaWithT(t)
|
|
|
|
// success
|
|
mockConn := &mock.Deliver{}
|
|
filteredResp := &pb.DeliverResponse{
|
|
Type: &pb.DeliverResponse_FilteredBlock{FilteredBlock: createFilteredBlock(pb.TxValidationCode_VALID, "txid0")},
|
|
}
|
|
mockConn.RecvReturns(filteredResp, nil)
|
|
mockDeliverClients := []*DeliverClient{
|
|
{
|
|
Connection: mockConn,
|
|
Address: "peer0",
|
|
},
|
|
}
|
|
dg := DeliverGroup{
|
|
Clients: mockDeliverClients,
|
|
ChannelID: "testchannel",
|
|
Signer: &mock.SignerSerializer{},
|
|
Certificate: tls.Certificate{
|
|
Certificate: [][]byte{[]byte("test")},
|
|
},
|
|
TxID: "txid0",
|
|
}
|
|
err := dg.Wait(context.Background())
|
|
g.Expect(err).To(BeNil())
|
|
|
|
// failure - Recv returns error
|
|
mockConn = &mock.Deliver{}
|
|
mockConn.RecvReturns(nil, errors.New("avocado"))
|
|
mockDeliverClients = []*DeliverClient{
|
|
{
|
|
Connection: mockConn,
|
|
Address: "peer0",
|
|
},
|
|
}
|
|
dg = DeliverGroup{
|
|
Clients: mockDeliverClients,
|
|
ChannelID: "testchannel",
|
|
Signer: &mock.SignerSerializer{},
|
|
Certificate: tls.Certificate{
|
|
Certificate: [][]byte{[]byte("test")},
|
|
},
|
|
TxID: "txid0",
|
|
}
|
|
err = dg.Wait(context.Background())
|
|
g.Expect(err.Error()).To(ContainSubstring("error receiving from deliver filtered"))
|
|
g.Expect(err.Error()).To(ContainSubstring("avocado"))
|
|
|
|
// failure - Recv returns unexpected type
|
|
mockConn = &mock.Deliver{}
|
|
resp := &pb.DeliverResponse{
|
|
Type: &pb.DeliverResponse_Block{},
|
|
}
|
|
mockConn.RecvReturns(resp, nil)
|
|
mockDeliverClients = []*DeliverClient{
|
|
{
|
|
Connection: mockConn,
|
|
Address: "peer0",
|
|
},
|
|
}
|
|
dg = DeliverGroup{
|
|
Clients: mockDeliverClients,
|
|
ChannelID: "testchannel",
|
|
Signer: &mock.SignerSerializer{},
|
|
Certificate: tls.Certificate{
|
|
Certificate: [][]byte{[]byte("test")},
|
|
},
|
|
TxID: "txid0",
|
|
}
|
|
err = dg.Wait(context.Background())
|
|
g.Expect(err.Error()).To(ContainSubstring("unexpected response type"))
|
|
|
|
// failure - both connections return error
|
|
mockConn = &mock.Deliver{}
|
|
mockConn.RecvReturns(nil, errors.New("barbeque"))
|
|
mockConn2 := &mock.Deliver{}
|
|
mockConn2.RecvReturns(nil, errors.New("tofu"))
|
|
mockDeliverClients = []*DeliverClient{
|
|
{
|
|
Connection: mockConn,
|
|
Address: "peerBBQ",
|
|
},
|
|
{
|
|
Connection: mockConn2,
|
|
Address: "peerTOFU",
|
|
},
|
|
}
|
|
dg = DeliverGroup{
|
|
Clients: mockDeliverClients,
|
|
ChannelID: "testchannel",
|
|
Signer: &mock.SignerSerializer{},
|
|
Certificate: tls.Certificate{
|
|
Certificate: [][]byte{[]byte("test")},
|
|
},
|
|
TxID: "txid0",
|
|
}
|
|
err = dg.Wait(context.Background())
|
|
g.Expect(err.Error()).To(SatisfyAny(
|
|
ContainSubstring("barbeque"),
|
|
ContainSubstring("tofu")))
|
|
}
|
|
|
|
func TestChaincodeInvokeOrQuery_waitForEvent(t *testing.T) {
|
|
defer resetFlags()
|
|
|
|
waitForEvent = true
|
|
mockCF, err := getMockChaincodeCmdFactory()
|
|
require.NoError(t, err)
|
|
peerAddresses = []string{"peer0", "peer1"}
|
|
channelID := "testchannel"
|
|
txID := "txid0"
|
|
|
|
t.Run("success - deliver clients returns event with expected txid", func(t *testing.T) {
|
|
_, err = ChaincodeInvokeOrQuery(
|
|
&pb.ChaincodeSpec{},
|
|
channelID,
|
|
txID,
|
|
true,
|
|
mockCF.Signer,
|
|
mockCF.Certificate,
|
|
mockCF.EndorserClients,
|
|
mockCF.DeliverClients,
|
|
mockCF.BroadcastClient,
|
|
)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("success - one deliver client first receives block without txid and then one with txid", func(t *testing.T) {
|
|
filteredBlocks := []*pb.FilteredBlock{
|
|
createFilteredBlock(pb.TxValidationCode_VALID, "theseare", "notthetxidsyouarelookingfor"),
|
|
createFilteredBlock(pb.TxValidationCode_VALID, "txid0"),
|
|
}
|
|
mockDCTwoBlocks := getMockDeliverClientRespondsWithFilteredBlocks(filteredBlocks)
|
|
mockDC := getMockDeliverClientResponseWithTxStatusAndID(pb.TxValidationCode_VALID, "txid0")
|
|
mockDeliverClients := []pb.DeliverClient{mockDCTwoBlocks, mockDC}
|
|
|
|
_, err = ChaincodeInvokeOrQuery(
|
|
&pb.ChaincodeSpec{},
|
|
channelID,
|
|
txID,
|
|
true,
|
|
mockCF.Signer,
|
|
mockCF.Certificate,
|
|
mockCF.EndorserClients,
|
|
mockDeliverClients,
|
|
mockCF.BroadcastClient,
|
|
)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("failure - one of the deliver clients returns error", func(t *testing.T) {
|
|
mockDCErr := getMockDeliverClientWithErr("moist")
|
|
mockDC := getMockDeliverClientResponseWithTxStatusAndID(pb.TxValidationCode_VALID, "txid0")
|
|
mockDeliverClients := []pb.DeliverClient{mockDCErr, mockDC}
|
|
|
|
_, err = ChaincodeInvokeOrQuery(
|
|
&pb.ChaincodeSpec{},
|
|
channelID,
|
|
txID,
|
|
true,
|
|
mockCF.Signer,
|
|
mockCF.Certificate,
|
|
mockCF.EndorserClients,
|
|
mockDeliverClients,
|
|
mockCF.BroadcastClient,
|
|
)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "moist")
|
|
})
|
|
|
|
t.Run("failure - transaction committed with non-success validation code", func(t *testing.T) {
|
|
mockDC := getMockDeliverClientResponseWithTxStatusAndID(pb.TxValidationCode_VALID, "txid0")
|
|
mockDCFail := getMockDeliverClientResponseWithTxStatusAndID(pb.TxValidationCode_ENDORSEMENT_POLICY_FAILURE, "txid0")
|
|
mockDeliverClients := []pb.DeliverClient{mockDCFail, mockDC}
|
|
|
|
_, err = ChaincodeInvokeOrQuery(
|
|
&pb.ChaincodeSpec{},
|
|
channelID,
|
|
txID,
|
|
true,
|
|
mockCF.Signer,
|
|
mockCF.Certificate,
|
|
mockCF.EndorserClients,
|
|
mockDeliverClients,
|
|
mockCF.BroadcastClient,
|
|
)
|
|
require.Error(t, err)
|
|
require.Equal(t, err.Error(), "transaction invalidated with status (ENDORSEMENT_POLICY_FAILURE)")
|
|
})
|
|
|
|
t.Run("failure - deliver returns response status instead of block", func(t *testing.T) {
|
|
mockDC := &mock.PeerDeliverClient{}
|
|
mockDF := &mock.Deliver{}
|
|
resp := &pb.DeliverResponse{
|
|
Type: &pb.DeliverResponse_Status{
|
|
Status: cb.Status_FORBIDDEN,
|
|
},
|
|
}
|
|
mockDF.RecvReturns(resp, nil)
|
|
mockDC.DeliverFilteredReturns(mockDF, nil)
|
|
mockDeliverClients := []pb.DeliverClient{mockDC}
|
|
_, err = ChaincodeInvokeOrQuery(
|
|
&pb.ChaincodeSpec{},
|
|
channelID,
|
|
txID,
|
|
true,
|
|
mockCF.Signer,
|
|
mockCF.Certificate,
|
|
mockCF.EndorserClients,
|
|
mockDeliverClients,
|
|
mockCF.BroadcastClient,
|
|
)
|
|
require.Error(t, err)
|
|
require.Equal(t, err.Error(), "deliver completed with status (FORBIDDEN) before txid received")
|
|
})
|
|
|
|
t.Run(" failure - timeout occurs - both deliver clients don't return an event with the expected txid before timeout", func(t *testing.T) {
|
|
delayChan := make(chan struct{})
|
|
mockDCDelay := getMockDeliverClientRespondAfterDelay(delayChan, pb.TxValidationCode_VALID, "txid0")
|
|
mockDeliverClients := []pb.DeliverClient{mockDCDelay, mockDCDelay}
|
|
waitForEventTimeout = 10 * time.Millisecond
|
|
|
|
_, err = ChaincodeInvokeOrQuery(
|
|
&pb.ChaincodeSpec{},
|
|
channelID,
|
|
txID,
|
|
true,
|
|
mockCF.Signer,
|
|
mockCF.Certificate,
|
|
mockCF.EndorserClients,
|
|
mockDeliverClients,
|
|
mockCF.BroadcastClient,
|
|
)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "timed out")
|
|
close(delayChan)
|
|
})
|
|
}
|
|
|
|
func TestProcessProposals(t *testing.T) {
|
|
// Build clients that return a range of status codes (for verifying each client is called).
|
|
mockClients := []pb.EndorserClient{}
|
|
for i := 2; i <= 5; i++ {
|
|
response := &pb.ProposalResponse{
|
|
Response: &pb.Response{Status: int32(i * 100)},
|
|
Endorsement: &pb.Endorsement{},
|
|
}
|
|
mockClients = append(mockClients, common.GetMockEndorserClient(response, nil))
|
|
}
|
|
mockErrorClient := common.GetMockEndorserClient(nil, errors.New("failed to call endorser"))
|
|
signedProposal := &pb.SignedProposal{}
|
|
t.Run("should process a proposal for a single peer", func(t *testing.T) {
|
|
responses, err := processProposals([]pb.EndorserClient{mockClients[0]}, signedProposal)
|
|
require.NoError(t, err)
|
|
require.Len(t, responses, 1)
|
|
require.Equal(t, responses[0].Response.Status, int32(200))
|
|
})
|
|
t.Run("should process a proposal for multiple peers", func(t *testing.T) {
|
|
responses, err := processProposals(mockClients, signedProposal)
|
|
require.NoError(t, err)
|
|
require.Len(t, responses, 4)
|
|
// Sort the statuses (as they may turn up in different order) before comparing.
|
|
statuses := []int32{}
|
|
for _, response := range responses {
|
|
statuses = append(statuses, response.Response.Status)
|
|
}
|
|
sort.Slice(statuses, func(i, j int) bool { return statuses[i] < statuses[j] })
|
|
require.EqualValues(t, []int32{200, 300, 400, 500}, statuses)
|
|
})
|
|
t.Run("should return an error from processing a proposal for a single peer", func(t *testing.T) {
|
|
responses, err := processProposals([]pb.EndorserClient{mockErrorClient}, signedProposal)
|
|
require.EqualError(t, err, "failed to call endorser")
|
|
require.Nil(t, responses)
|
|
})
|
|
t.Run("should return an error from processing a proposal for a single peer within multiple peers", func(t *testing.T) {
|
|
responses, err := processProposals([]pb.EndorserClient{mockClients[0], mockErrorClient, mockClients[1]}, signedProposal)
|
|
require.EqualError(t, err, "failed to call endorser")
|
|
require.Nil(t, responses)
|
|
})
|
|
}
|