go_study/fabric-main/internal/pkg/gateway/evaluate_test.go

386 lines
14 KiB
Go

/*
Copyright 2021 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package gateway
import (
"context"
"fmt"
"testing"
"time"
pb "github.com/hyperledger/fabric-protos-go/gateway"
"github.com/hyperledger/fabric-protos-go/peer"
"github.com/hyperledger/fabric/gossip/common"
gdiscovery "github.com/hyperledger/fabric/gossip/discovery"
"github.com/hyperledger/fabric/internal/pkg/gateway/mocks"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestEvaluate(t *testing.T) {
tests := []testDef{
{
name: "single endorser",
members: []networkMember{
{"id1", "localhost:7051", "msp1", 5},
},
localLedgerHeight: 5,
expectedEndorsers: []string{"localhost:7051"},
},
{
name: "no endorsers",
plan: endorsementPlan{},
members: []networkMember{},
errCode: codes.FailedPrecondition,
errString: "no peers available to evaluate chaincode test_chaincode in channel test_channel",
},
{
name: "five endorsers, prefer local org",
members: []networkMember{
{"id1", "localhost:7051", "msp1", 5},
{"id2", "peer1:8051", "msp1", 6},
{"id3", "peer2:9051", "msp2", 6},
{"id4", "peer3:10051", "msp2", 5},
{"id5", "peer4:11051", "msp3", 6},
},
localLedgerHeight: 5,
expectedEndorsers: []string{"peer1:8051"},
},
{
name: "five endorsers, prefer host peer",
members: []networkMember{
{"id1", "localhost:7051", "msp1", 5},
{"id2", "peer1:8051", "msp1", 5},
{"id3", "peer2:9051", "msp2", 6},
{"id4", "peer3:10051", "msp2", 5},
{"id5", "peer4:11051", "msp3", 6},
},
localLedgerHeight: 5,
expectedEndorsers: []string{"localhost:7051"},
},
{
name: "five endorsers, prefer host peer despite no endpoint",
members: []networkMember{
{"id1", "", "msp1", 5},
{"id2", "peer1:8051", "msp1", 5},
{"id3", "peer2:9051", "msp2", 6},
{"id4", "peer3:10051", "msp2", 5},
{"id5", "peer4:11051", "msp3", 6},
},
localLedgerHeight: 5,
expectedEndorsers: []string{"localhost:7051"},
},
{
name: "evaluate with targetOrganizations, prefer local org despite block height",
members: []networkMember{
{"id1", "localhost:7051", "msp1", 5},
{"id2", "peer1:8051", "msp1", 5},
{"id3", "peer2:9051", "msp2", 6},
{"id4", "peer3:10051", "msp2", 5},
{"id5", "peer4:11051", "msp3", 6},
},
localLedgerHeight: 5,
endorsingOrgs: []string{"msp3", "msp1"},
expectedEndorsers: []string{"localhost:7051"},
},
{
name: "evaluate with targetOrganizations that doesn't include local org, prefer highest block height",
members: []networkMember{
{"id1", "localhost:7051", "msp1", 5},
{"id2", "peer1:8051", "msp1", 5},
{"id3", "peer2:9051", "msp2", 6},
{"id4", "peer3:10051", "msp2", 5},
{"id5", "peer4:11051", "msp3", 7},
},
localLedgerHeight: 5,
endorsingOrgs: []string{"msp2", "msp3"},
expectedEndorsers: []string{"peer4:11051"},
},
{
name: "evaluate with transient data should select local org, highest block height",
members: []networkMember{
{"id1", "localhost:7051", "msp1", 4},
{"id2", "peer1:8051", "msp1", 5},
{"id3", "peer2:9051", "msp2", 6},
{"id4", "peer3:10051", "msp2", 5},
{"id5", "peer4:11051", "msp3", 7},
},
localLedgerHeight: 4,
transientData: map[string][]byte{"transient-key": []byte("transient-value")},
expectedEndorsers: []string{"peer1:8051"},
},
{
name: "evaluate with transient data should fail if local org not available",
members: []networkMember{
{"id3", "peer2:9051", "msp2", 6},
{"id4", "peer3:10051", "msp2", 5},
{"id5", "peer4:11051", "msp3", 7},
},
transientData: map[string][]byte{"transient-key": []byte("transient-value")},
errCode: codes.FailedPrecondition,
errString: "no endorsers found in the gateway's organization; retry specifying target organization(s) to protect transient data: no peers available to evaluate chaincode test_chaincode in channel test_channel",
},
{
name: "evaluate with transient data and target (non-local) orgs should select the highest block height peer",
members: []networkMember{
{"id1", "localhost:7051", "msp1", 11},
{"id2", "peer1:8051", "msp1", 5},
{"id3", "peer2:9051", "msp2", 6},
{"id4", "peer3:10051", "msp2", 9},
{"id5", "peer4:11051", "msp3", 7},
},
localLedgerHeight: 11,
transientData: map[string][]byte{"transient-key": []byte("transient-value")},
endorsingOrgs: []string{"msp2", "msp3"},
expectedEndorsers: []string{"peer3:10051"},
},
{
name: "process proposal fails",
members: []networkMember{
{"id1", "localhost:7051", "msp1", 5},
},
localLedgerHeight: 5,
endpointDefinition: &endpointDef{
proposalError: status.Error(codes.Aborted, "wibble"),
},
errCode: codes.Aborted,
errString: "failed to evaluate transaction, see attached details for more info",
errDetails: []*pb.ErrorDetail{{
Address: "localhost:7051",
MspId: "msp1",
Message: "rpc error: code = Aborted desc = wibble",
}},
},
{
name: "process proposal chaincode error",
members: []networkMember{
{"id2", "peer1:8051", "msp1", 5},
},
endpointDefinition: &endpointDef{
proposalResponseStatus: 400,
proposalResponseMessage: "Mock chaincode error",
},
errCode: codes.Unknown,
errString: "evaluate call to endorser returned error: chaincode response 400, Mock chaincode error",
errDetails: []*pb.ErrorDetail{{
Address: "peer1:8051",
MspId: "msp1",
Message: "chaincode response 400, Mock chaincode error",
}},
},
{
name: "evaluate on local org fails - retry in other org",
members: []networkMember{
{"id1", "localhost:7051", "msp1", 4},
{"id2", "peer1:8051", "msp1", 4},
{"id3", "peer2:9051", "msp2", 3},
{"id4", "peer3:10051", "msp2", 4},
{"id5", "peer4:11051", "msp3", 5},
},
localLedgerHeight: 4,
plan: endorsementPlan{
"g1": {{endorser: localhostMock, height: 4}, {endorser: peer1Mock, height: 4}}, // msp1
"g2": {{endorser: peer2Mock, height: 3}, {endorser: peer3Mock, height: 4}}, // msp2
"g3": {{endorser: peer4Mock, height: 5}}, // msp3
},
postSetup: func(t *testing.T, def *preparedTest) {
def.localEndorser.ProcessProposalReturns(createErrorResponse(t, 500, "bad local endorser", nil), nil)
peer1Mock.client.(*mocks.EndorserClient).ProcessProposalReturns(createErrorResponse(t, 500, "bad peer1 endorser", nil), nil)
},
expectedEndorsers: []string{"peer4:11051"},
},
{
name: "restrict to local org peers - which all fail",
members: []networkMember{
{"id1", "localhost:7051", "msp1", 4},
{"id2", "peer1:8051", "msp1", 4},
{"id3", "peer2:9051", "msp2", 3},
{"id4", "peer3:10051", "msp2", 4},
{"id5", "peer4:11051", "msp3", 5},
},
localLedgerHeight: 4,
plan: endorsementPlan{
"g1": {{endorser: localhostMock, height: 4}, {endorser: peer1Mock, height: 4}}, // msp1
"g2": {{endorser: peer2Mock, height: 3}, {endorser: peer3Mock, height: 4}}, // msp2
"g3": {{endorser: peer4Mock, height: 5}}, // msp3
},
postSetup: func(t *testing.T, def *preparedTest) {
def.localEndorser.ProcessProposalReturns(createErrorResponse(t, 500, "bad local endorser", nil), nil)
peer1Mock.client.(*mocks.EndorserClient).ProcessProposalReturns(createErrorResponse(t, 500, "bad peer1 endorser", nil), nil)
},
endorsingOrgs: []string{"msp1"},
errCode: codes.Aborted,
errString: "failed to evaluate transaction, see attached details for more info",
errDetails: []*pb.ErrorDetail{
{
Address: "localhost:7051",
MspId: "msp1",
Message: "bad local endorser",
},
{
Address: "peer1:8051",
MspId: "msp1",
Message: "bad peer1 endorser",
},
},
},
{
name: "fails due to invalid signature (pre-process check) - does not retry",
members: []networkMember{
{"id1", "localhost:7051", "msp1", 4},
{"id2", "peer1:8051", "msp1", 4},
{"id3", "peer2:9051", "msp2", 3},
{"id4", "peer3:10051", "msp2", 4},
{"id5", "peer4:11051", "msp3", 5},
},
localLedgerHeight: 4,
plan: endorsementPlan{
"g1": {{endorser: localhostMock, height: 4}, {endorser: peer1Mock, height: 4}}, // msp1
"g2": {{endorser: peer2Mock, height: 3}, {endorser: peer3Mock, height: 4}}, // msp2
"g3": {{endorser: peer4Mock, height: 5}}, // msp3
},
postSetup: func(t *testing.T, def *preparedTest) {
def.localEndorser.ProcessProposalReturns(createErrorResponse(t, 500, "invalid signature", nil), fmt.Errorf("invalid signature"))
},
endorsingOrgs: []string{"msp1"},
errCode: codes.FailedPrecondition, // Code path could fail for reasons other than authentication
errString: "evaluate call to endorser returned error: invalid signature",
errDetails: []*pb.ErrorDetail{
{
Address: "localhost:7051",
MspId: "msp1",
Message: "invalid signature",
},
},
},
{
name: "fails due to chaincode panic - retry on next peer",
members: []networkMember{
{"id1", "localhost:7051", "msp1", 4},
{"id2", "peer1:8051", "msp1", 4},
{"id3", "peer2:9051", "msp2", 3},
{"id4", "peer3:10051", "msp2", 4},
{"id5", "peer4:11051", "msp3", 5},
},
localLedgerHeight: 4,
plan: endorsementPlan{
"g1": {{endorser: localhostMock, height: 4}, {endorser: peer1Mock, height: 4}}, // msp1
"g2": {{endorser: peer2Mock, height: 3}, {endorser: peer3Mock, height: 4}}, // msp2
"g3": {{endorser: peer4Mock, height: 5}}, // msp3
},
postSetup: func(t *testing.T, def *preparedTest) {
def.localEndorser.ProcessProposalReturns(createErrorResponse(t, 500, "error in simulation: chaincode stream terminated", nil), nil)
peer1Mock.client.(*mocks.EndorserClient).ProcessProposalReturns(createErrorResponse(t, 500, "error in simulation: chaincode stream terminated", nil), nil)
},
endorsingOrgs: []string{"msp1"},
errCode: codes.Aborted,
errString: "failed to evaluate transaction, see attached details for more info",
errDetails: []*pb.ErrorDetail{
{
Address: "localhost:7051",
MspId: "msp1",
Message: "error in simulation: chaincode stream terminated",
},
{
Address: "peer1:8051",
MspId: "msp1",
Message: "error in simulation: chaincode stream terminated",
},
},
},
{
name: "dialing endorser endpoint fails",
members: []networkMember{
{"id3", "peer2:9051", "msp2", 5},
},
postSetup: func(t *testing.T, def *preparedTest) {
def.dialer.Calls(func(_ context.Context, target string, _ ...grpc.DialOption) (*grpc.ClientConn, error) {
if target == "peer2:9051" {
return nil, fmt.Errorf("endorser not answering")
}
return nil, nil
})
},
errCode: codes.Unavailable,
errString: "failed to create new connection: endorser not answering",
},
{
name: "discovery returns incomplete information - no Properties",
postSetup: func(t *testing.T, def *preparedTest) {
def.discovery.PeersOfChannelReturns([]gdiscovery.NetworkMember{{
Endpoint: "localhost:7051",
PKIid: []byte("ill-defined"),
}})
},
errCode: codes.FailedPrecondition,
errString: "no peers available to evaluate chaincode test_chaincode in channel test_channel",
},
{
name: "context timeout during evaluate",
plan: endorsementPlan{
"g1": {{endorser: localhostMock, height: 3}}, // msp1
},
localLedgerHeight: 3,
postSetup: func(t *testing.T, def *preparedTest) {
def.ctx, def.cancel = context.WithTimeout(def.ctx, 100*time.Millisecond)
def.localEndorser.ProcessProposalStub = func(ctx context.Context, proposal *peer.SignedProposal, option ...grpc.CallOption) (*peer.ProposalResponse, error) {
time.Sleep(200 * time.Millisecond)
return createProposalResponse(t, peer1Mock.address, "mock_response", 200, ""), nil
}
},
postTest: func(t *testing.T, def *preparedTest) {
def.cancel()
},
errCode: codes.DeadlineExceeded,
errString: "evaluate timeout expired",
},
{
name: "uses local host ledger height",
members: []networkMember{
{"id2", "peer1:8051", "msp1", 6},
{"id1", "localhost:7051", "msp1", 5},
},
localLedgerHeight: 7,
expectedEndorsers: []string{"localhost:7051"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
test := prepareTest(t, &tt)
response, err := test.server.Evaluate(test.ctx, &pb.EvaluateRequest{ProposedTransaction: test.signedProposal, TargetOrganizations: tt.endorsingOrgs})
if checkError(t, &tt, err) {
require.Nil(t, response, "response on error")
return
}
// test the assertions
require.NoError(t, err)
// assert the result is the payload from the proposal response returned by the local endorser
require.Equal(t, []byte("mock_response"), response.Result.Payload, "Incorrect result")
// check the correct endorsers (mock) were called with the right parameters
checkEndorsers(t, tt.expectedEndorsers, test)
// check the discovery service (mock) was invoked as expected
expectedChannel := common.ChannelID(testChannel)
require.Equal(t, 2, test.discovery.PeersOfChannelCallCount())
channel := test.discovery.PeersOfChannelArgsForCall(0)
require.Equal(t, expectedChannel, channel)
channel = test.discovery.PeersOfChannelArgsForCall(1)
require.Equal(t, expectedChannel, channel)
require.Equal(t, 1, test.discovery.IdentityInfoCallCount())
})
}
}