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

876 lines
26 KiB
Go

/*
Copyright 2021 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package gateway
import (
"context"
"fmt"
"testing"
"time"
"github.com/golang/protobuf/proto"
cp "github.com/hyperledger/fabric-protos-go/common"
dp "github.com/hyperledger/fabric-protos-go/discovery"
pb "github.com/hyperledger/fabric-protos-go/gateway"
"github.com/hyperledger/fabric-protos-go/msp"
ab "github.com/hyperledger/fabric-protos-go/orderer"
"github.com/hyperledger/fabric/internal/pkg/gateway/mocks"
"github.com/hyperledger/fabric/internal/pkg/peer/orderers"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestSubmit(t *testing.T) {
tests := []testDef{
{
name: "two endorsers",
plan: endorsementPlan{
"g1": {{endorser: localhostMock, height: 3}},
"g2": {{endorser: peer1Mock, height: 3}},
},
},
{
name: "discovery fails",
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
postSetup: func(t *testing.T, def *preparedTest) {
def.discovery.ConfigReturnsOnCall(1, nil, fmt.Errorf("jabberwocky"))
},
errCode: codes.FailedPrecondition,
errString: "failed to get config for channel [test_channel]: jabberwocky",
},
{
name: "no orderers",
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
postSetup: func(t *testing.T, def *preparedTest) {
def.discovery.ConfigReturns(&dp.ConfigResult{
Orderers: map[string]*dp.Endpoints{},
Msps: map[string]*msp.FabricMSPConfig{},
}, nil)
},
errCode: codes.Unavailable,
errString: "no orderer nodes available",
},
{
name: "orderer broadcast fails",
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
endpointDefinition: &endpointDef{
proposalResponseStatus: 200,
ordererBroadcastError: status.Error(codes.FailedPrecondition, "Orderer not listening!"),
},
errCode: codes.Unavailable,
errString: "no orderers could successfully process transaction",
errDetails: []*pb.ErrorDetail{{
Address: "orderer1:7050",
MspId: "msp1",
Message: "rpc error: code = FailedPrecondition desc = Orderer not listening!",
}},
},
{
name: "send to orderer fails",
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
endpointDefinition: &endpointDef{
proposalResponseStatus: 200,
ordererSendError: status.Error(codes.Internal, "Orderer says no!"),
},
errCode: codes.Unavailable,
errString: "no orderers could successfully process transaction",
errDetails: []*pb.ErrorDetail{{
Address: "orderer1:7050",
MspId: "msp1",
Message: "rpc error: code = Internal desc = Orderer says no!",
}},
},
{
name: "receive from orderer fails",
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
endpointDefinition: &endpointDef{
proposalResponseStatus: 200,
ordererRecvError: status.Error(codes.FailedPrecondition, "Orderer not happy!"),
},
errCode: codes.Unavailable,
errString: "no orderers could successfully process transaction",
errDetails: []*pb.ErrorDetail{{
Address: "orderer1:7050",
MspId: "msp1",
Message: "rpc error: code = FailedPrecondition desc = Orderer not happy!",
}},
},
{
name: "orderer Recv() returns nil",
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
postSetup: func(t *testing.T, def *preparedTest) {
abbc := &mocks.ABBClient{}
abbc.RecvReturns(nil, nil)
orderer1Mock.client.(*mocks.ABClient).BroadcastReturns(abbc, nil)
},
errCode: codes.Unavailable,
errString: "no orderers could successfully process transaction",
},
{
name: "orderer returns unsuccessful response",
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
postSetup: func(t *testing.T, def *preparedTest) {
abbc := &mocks.ABBClient{}
response := &ab.BroadcastResponse{
Status: cp.Status_BAD_REQUEST,
Info: "err-info",
}
abbc.RecvReturns(response, nil)
orderer1Mock.client.(*mocks.ABClient).BroadcastReturns(abbc, nil)
},
errCode: codes.Aborted,
errString: fmt.Sprintf("received unsuccessful response from orderer: status=%s, info=err-info", cp.Status_name[int32(cp.Status_BAD_REQUEST)]),
},
{
name: "dialing orderer endpoint fails",
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
postSetup: func(t *testing.T, def *preparedTest) {
def.dialer.Calls(func(_ context.Context, target string, _ ...grpc.DialOption) (*grpc.ClientConn, error) {
if target == "orderer1:7050" {
return nil, fmt.Errorf("orderer not answering")
}
return nil, nil
})
},
errCode: codes.Unavailable,
errString: "no orderer nodes available",
},
{
name: "multiple non-BFT orderers - only one needs to succeed",
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
config: &dp.ConfigResult{
Orderers: map[string]*dp.Endpoints{
"msp1": {
Endpoint: []*dp.Endpoint{
{Host: "orderer1", Port: 7050},
{Host: "orderer2", Port: 7050},
{Host: "orderer3", Port: 7050},
},
},
},
Msps: map[string]*msp.FabricMSPConfig{
"msp1": {
TlsRootCerts: [][]byte{},
},
},
},
postTest: func(t *testing.T, def *preparedTest) {
// only one orderer is invoked
var invoked int
for _, o := range ordererMocks {
invoked += o.client.(*mocks.ABClient).BroadcastCallCount()
}
require.Equal(t, invoked, 1)
},
},
{
name: "orderer retry",
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
config: &dp.ConfigResult{
Orderers: map[string]*dp.Endpoints{
"msp1": {
Endpoint: []*dp.Endpoint{
{Host: "orderer1", Port: 7050},
{Host: "orderer2", Port: 7050},
{Host: "orderer3", Port: 7050},
},
},
},
Msps: map[string]*msp.FabricMSPConfig{
"msp1": {
TlsRootCerts: [][]byte{},
},
},
},
postSetup: func(t *testing.T, def *preparedTest) {
abbc := &mocks.ABBClient{}
abbc.SendReturnsOnCall(0, status.Error(codes.Unavailable, "First orderer error"))
abbc.SendReturnsOnCall(1, status.Error(codes.Unavailable, "Second orderer error"))
abbc.SendReturnsOnCall(2, nil) // third time lucky
abbc.RecvReturns(&ab.BroadcastResponse{
Info: "success",
Status: cp.Status(200),
}, nil)
orderer1Mock.client.(*mocks.ABClient).BroadcastReturns(abbc, nil)
orderer2Mock.client.(*mocks.ABClient).BroadcastReturns(abbc, nil)
orderer3Mock.client.(*mocks.ABClient).BroadcastReturns(abbc, nil)
},
},
{
name: "orderer bad response retry",
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
config: &dp.ConfigResult{
Orderers: map[string]*dp.Endpoints{
"msp1": {
Endpoint: []*dp.Endpoint{
{Host: "orderer1", Port: 7050},
{Host: "orderer2", Port: 7050},
{Host: "orderer3", Port: 7050},
},
},
},
Msps: map[string]*msp.FabricMSPConfig{
"msp1": {
TlsRootCerts: [][]byte{},
},
},
},
postSetup: func(t *testing.T, def *preparedTest) {
abbc := &mocks.ABBClient{}
abbc.SendReturns(nil)
abbc.RecvReturnsOnCall(0, &ab.BroadcastResponse{
Info: "internal error",
Status: cp.Status(500),
}, nil)
abbc.RecvReturnsOnCall(1, &ab.BroadcastResponse{
Info: "success",
Status: cp.Status(200),
}, nil)
orderer1Mock.client.(*mocks.ABClient).BroadcastReturns(abbc, nil)
orderer2Mock.client.(*mocks.ABClient).BroadcastReturns(abbc, nil)
orderer3Mock.client.(*mocks.ABClient).BroadcastReturns(abbc, nil)
},
},
{
name: "orderer timeout - retry",
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
config: &dp.ConfigResult{
Orderers: map[string]*dp.Endpoints{
"msp1": {
Endpoint: []*dp.Endpoint{
{Host: "orderer1", Port: 7050},
{Host: "orderer2", Port: 7050},
{Host: "orderer3", Port: 7050},
},
},
},
},
postSetup: func(t *testing.T, def *preparedTest) {
def.ctx, def.cancel = context.WithTimeout(def.ctx, 300*time.Millisecond)
broadcastTime := 200 * time.Millisecond // first invocation exceeds BroadcastTimeout
abbc := &mocks.ABBClient{}
abbc.SendReturns(nil)
abbc.RecvReturns(&ab.BroadcastResponse{
Info: "success",
Status: cp.Status(200),
}, nil)
bs := func(ctx context.Context, co ...grpc.CallOption) (ab.AtomicBroadcast_BroadcastClient, error) {
defer func() {
broadcastTime = time.Millisecond // subsequent invocations will not timeout
}()
select {
case <-time.After(broadcastTime):
return abbc, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
orderer1Mock.client.(*mocks.ABClient).BroadcastStub = bs
orderer2Mock.client.(*mocks.ABClient).BroadcastStub = bs
orderer3Mock.client.(*mocks.ABClient).BroadcastStub = bs
},
postTest: func(t *testing.T, def *preparedTest) {
def.cancel()
},
},
{
name: "submit timeout",
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
config: &dp.ConfigResult{
Orderers: map[string]*dp.Endpoints{
"msp1": {
Endpoint: []*dp.Endpoint{
{Host: "orderer1", Port: 7050},
{Host: "orderer2", Port: 7050},
{Host: "orderer3", Port: 7050},
},
},
},
},
postSetup: func(t *testing.T, def *preparedTest) {
def.ctx, def.cancel = context.WithTimeout(def.ctx, 50*time.Millisecond)
broadcastTime := 200 * time.Millisecond // invocation exceeds BroadcastTimeout
abbc := &mocks.ABBClient{}
abbc.SendReturns(nil)
abbc.RecvReturns(&ab.BroadcastResponse{
Info: "success",
Status: cp.Status(200),
}, nil)
bs := func(ctx context.Context, co ...grpc.CallOption) (ab.AtomicBroadcast_BroadcastClient, error) {
select {
case <-time.After(broadcastTime):
return abbc, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
orderer1Mock.client.(*mocks.ABClient).BroadcastStub = bs
orderer2Mock.client.(*mocks.ABClient).BroadcastStub = bs
orderer3Mock.client.(*mocks.ABClient).BroadcastStub = bs
},
postTest: func(t *testing.T, def *preparedTest) {
def.cancel()
},
errCode: codes.DeadlineExceeded,
errString: "submit timeout expired while broadcasting to ordering service",
},
{
name: "multiple orderers all fail",
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
config: &dp.ConfigResult{
Orderers: map[string]*dp.Endpoints{
"msp1": {
Endpoint: []*dp.Endpoint{
{Host: "orderer1", Port: 7050},
{Host: "orderer2", Port: 7050},
{Host: "orderer3", Port: 7050},
},
},
},
Msps: map[string]*msp.FabricMSPConfig{
"msp1": {
TlsRootCerts: [][]byte{},
},
},
},
endpointDefinition: &endpointDef{
proposalResponseStatus: 200,
ordererBroadcastError: status.Error(codes.Unavailable, "Orderer not listening!"),
},
errCode: codes.Unavailable,
errString: "no orderers could successfully process transaction",
errDetails: []*pb.ErrorDetail{
{
Address: "orderer1:7050",
MspId: "msp1",
Message: "rpc error: code = Unavailable desc = Orderer not listening!",
},
{
Address: "orderer2:7050",
MspId: "msp1",
Message: "rpc error: code = Unavailable desc = Orderer not listening!",
},
{
Address: "orderer3:7050",
MspId: "msp1",
Message: "rpc error: code = Unavailable desc = Orderer not listening!",
},
},
},
{
name: "orderer endpoint overrides",
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
ordererEndpointOverrides: map[string]*orderers.Endpoint{
"orderer1:7050": {Address: "override1:1234"},
"orderer3:7050": {Address: "override3:4321"},
},
config: &dp.ConfigResult{
Orderers: map[string]*dp.Endpoints{
"msp1": {
Endpoint: []*dp.Endpoint{
{Host: "orderer1", Port: 7050},
{Host: "orderer2", Port: 7050},
{Host: "orderer3", Port: 7050},
},
},
},
Msps: map[string]*msp.FabricMSPConfig{
"msp1": {
TlsRootCerts: [][]byte{},
},
},
},
endpointDefinition: &endpointDef{
proposalResponseStatus: 200,
ordererBroadcastError: status.Error(codes.Unavailable, "Orderer not listening!"),
},
errCode: codes.Unavailable,
errString: "no orderers could successfully process transaction",
errDetails: []*pb.ErrorDetail{
{
Address: "override1:1234 (mapped from orderer1:7050)",
MspId: "msp1",
Message: "rpc error: code = Unavailable desc = Orderer not listening!",
},
{
Address: "orderer2:7050",
MspId: "msp1",
Message: "rpc error: code = Unavailable desc = Orderer not listening!",
},
{
Address: "override3:4321 (mapped from orderer3:7050)",
MspId: "msp1",
Message: "rpc error: code = Unavailable desc = Orderer not listening!",
},
},
postTest: func(t *testing.T, def *preparedTest) {
var addresses []string
for i := 0; i < def.dialer.CallCount(); i++ {
_, address, _ := def.dialer.ArgsForCall(i)
addresses = append(addresses, address)
}
require.Contains(t, addresses, "override1:1234")
require.NotContains(t, addresses, "orderer1:7050")
require.Contains(t, addresses, "orderer2:7050")
require.Contains(t, addresses, "override3:4321")
require.NotContains(t, addresses, "orderer3:7050")
},
},
{
name: "multiple BFT orderers are all invoked",
isBFT: true,
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
config: &dp.ConfigResult{
Orderers: map[string]*dp.Endpoints{
"msp1": {
Endpoint: []*dp.Endpoint{
{Host: "orderer1", Port: 7050},
{Host: "orderer2", Port: 7050},
{Host: "orderer3", Port: 7050},
{Host: "orderer4", Port: 7050},
},
},
},
Msps: map[string]*msp.FabricMSPConfig{
"msp1": {
TlsRootCerts: [][]byte{},
},
},
},
endpointDefinition: &endpointDef{
proposalResponseStatus: 200,
ordererStatus: 200,
},
postTest: func(t *testing.T, def *preparedTest) {
require.Eventually(t, func() bool {
return orderer1Mock.client.(*mocks.ABClient).BroadcastCallCount() == 1 &&
orderer2Mock.client.(*mocks.ABClient).BroadcastCallCount() == 1 &&
orderer3Mock.client.(*mocks.ABClient).BroadcastCallCount() == 1 &&
orderer4Mock.client.(*mocks.ABClient).BroadcastCallCount() == 1
}, broadcastTimeout, 10*time.Millisecond)
},
},
{
name: "multiple BFT orderers all fail",
isBFT: true,
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
config: &dp.ConfigResult{
Orderers: map[string]*dp.Endpoints{
"msp1": {
Endpoint: []*dp.Endpoint{
{Host: "orderer1", Port: 7050},
{Host: "orderer2", Port: 7050},
{Host: "orderer3", Port: 7050},
},
},
},
Msps: map[string]*msp.FabricMSPConfig{
"msp1": {
TlsRootCerts: [][]byte{},
},
},
},
endpointDefinition: &endpointDef{
proposalResponseStatus: 200,
ordererBroadcastError: status.Error(codes.Unavailable, "Orderer not listening!"),
},
errCode: codes.Unavailable,
errString: "insufficient number of orderers could successfully process transaction to satisfy quorum requirement",
},
{
name: "7 BFT orderers can tolerate 2 faults",
isBFT: true,
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
config: &dp.ConfigResult{
Orderers: map[string]*dp.Endpoints{
"msp1": {
Endpoint: []*dp.Endpoint{
{Host: "orderer1", Port: 7050},
{Host: "orderer2", Port: 7050},
{Host: "orderer3", Port: 7050},
{Host: "orderer4", Port: 7050},
{Host: "orderer5", Port: 7050},
{Host: "orderer6", Port: 7050},
{Host: "orderer7", Port: 7050},
},
},
},
Msps: map[string]*msp.FabricMSPConfig{
"msp1": {
TlsRootCerts: [][]byte{},
},
},
},
endpointDefinition: &endpointDef{
proposalResponseStatus: 200,
ordererStatus: 200,
},
postSetup: func(t *testing.T, def *preparedTest) {
orderer1Mock.client.(*mocks.ABClient).BroadcastReturns(nil, errors.New("orderer1 fails"))
orderer4Mock.client.(*mocks.ABClient).BroadcastReturns(nil, errors.New("orderer4 fails"))
},
},
{
name: "7 BFT orderers cannot tolerate 3 faults",
isBFT: true,
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
config: &dp.ConfigResult{
Orderers: map[string]*dp.Endpoints{
"msp1": {
Endpoint: []*dp.Endpoint{
{Host: "orderer1", Port: 7050},
{Host: "orderer2", Port: 7050},
{Host: "orderer3", Port: 7050},
{Host: "orderer4", Port: 7050},
{Host: "orderer5", Port: 7050},
{Host: "orderer6", Port: 7050},
{Host: "orderer7", Port: 7050},
},
},
},
Msps: map[string]*msp.FabricMSPConfig{
"msp1": {
TlsRootCerts: [][]byte{},
},
},
},
endpointDefinition: &endpointDef{
proposalResponseStatus: 200,
ordererStatus: 200,
},
postSetup: func(t *testing.T, def *preparedTest) {
abbc := &mocks.ABBClient{}
response := &ab.BroadcastResponse{
Status: cp.Status_BAD_REQUEST,
Info: "extra details",
}
abbc.RecvReturns(response, nil)
orderer1Mock.client.(*mocks.ABClient).BroadcastReturns(nil, status.Error(codes.Unavailable, "orderer1 fails"))
orderer4Mock.client.(*mocks.ABClient).BroadcastReturns(nil, status.Error(codes.Unavailable, "orderer4 fails"))
orderer7Mock.client.(*mocks.ABClient).BroadcastReturns(abbc, nil)
},
errCode: codes.Unavailable,
errString: "insufficient number of orderers could successfully process transaction to satisfy quorum requirement",
errDetails: []*pb.ErrorDetail{
{
Address: "orderer1:7050",
MspId: "msp1",
Message: "rpc error: code = Unavailable desc = orderer1 fails",
},
{
Address: "orderer4:7050",
MspId: "msp1",
Message: "rpc error: code = Unavailable desc = orderer4 fails",
},
{
Address: "orderer7:7050",
MspId: "msp1",
Message: "received unsuccessful response from orderer: status=BAD_REQUEST, info=extra details",
},
},
},
{
name: "all BFT orderers exceed timeout",
isBFT: true,
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
config: &dp.ConfigResult{
Orderers: map[string]*dp.Endpoints{
"msp1": {
Endpoint: []*dp.Endpoint{
{Host: "orderer1", Port: 7050},
{Host: "orderer2", Port: 7050},
{Host: "orderer3", Port: 7050},
},
},
},
Msps: map[string]*msp.FabricMSPConfig{
"msp1": {
TlsRootCerts: [][]byte{},
},
},
},
endpointDefinition: &endpointDef{
proposalResponseStatus: 200,
ordererStatus: 200,
},
postSetup: func(t *testing.T, def *preparedTest) {
def.ctx, def.cancel = context.WithTimeout(def.ctx, 50*time.Millisecond)
abbc := &mocks.ABBClient{}
abbc.SendReturns(nil)
abbc.RecvStub = func() (*ab.BroadcastResponse, error) {
time.Sleep(100 * time.Millisecond)
return nil, nil
}
orderer1Mock.client.(*mocks.ABClient).BroadcastReturns(abbc, nil)
orderer2Mock.client.(*mocks.ABClient).BroadcastReturns(abbc, nil)
orderer3Mock.client.(*mocks.ABClient).BroadcastReturns(abbc, nil)
},
postTest: func(t *testing.T, def *preparedTest) {
def.cancel()
require.Equal(t, 1, orderer1Mock.client.(*mocks.ABClient).BroadcastCallCount())
require.Equal(t, 1, orderer2Mock.client.(*mocks.ABClient).BroadcastCallCount())
require.Equal(t, 1, orderer3Mock.client.(*mocks.ABClient).BroadcastCallCount())
},
errCode: codes.DeadlineExceeded,
errString: "submit timeout expired while broadcasting to ordering service",
},
{
name: "one BFT orderer exceeds timeout",
isBFT: true,
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
config: &dp.ConfigResult{
Orderers: map[string]*dp.Endpoints{
"msp1": {
Endpoint: []*dp.Endpoint{
{Host: "orderer1", Port: 7050},
{Host: "orderer2", Port: 7050},
{Host: "orderer3", Port: 7050},
{Host: "orderer4", Port: 7050},
},
},
},
Msps: map[string]*msp.FabricMSPConfig{
"msp1": {
TlsRootCerts: [][]byte{},
},
},
},
endpointDefinition: &endpointDef{
proposalResponseStatus: 200,
ordererStatus: 200,
},
postSetup: func(t *testing.T, def *preparedTest) {
def.ctx, def.cancel = context.WithTimeout(def.ctx, 50*time.Millisecond)
abbc := &mocks.ABBClient{}
abbc.SendReturns(nil)
abbc.RecvStub = func() (*ab.BroadcastResponse, error) {
time.Sleep(100 * time.Millisecond)
return nil, nil
}
orderer2Mock.client.(*mocks.ABClient).BroadcastReturns(abbc, nil)
},
postTest: func(t *testing.T, def *preparedTest) {
def.cancel()
require.Eventually(t, func() bool {
return orderer1Mock.client.(*mocks.ABClient).BroadcastCallCount() == 1 &&
orderer2Mock.client.(*mocks.ABClient).BroadcastCallCount() == 1 &&
orderer3Mock.client.(*mocks.ABClient).BroadcastCallCount() == 1 &&
orderer4Mock.client.(*mocks.ABClient).BroadcastCallCount() == 1
}, broadcastTimeout, 10*time.Millisecond)
},
},
{
name: "one BFT orderer cannot connect - quorum satisfied",
isBFT: true,
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
config: &dp.ConfigResult{
Orderers: map[string]*dp.Endpoints{
"msp1": {
Endpoint: []*dp.Endpoint{
{Host: "orderer1", Port: 7050},
{Host: "orderer2", Port: 7050},
{Host: "orderer3", Port: 7050},
{Host: "orderer4", Port: 7050},
},
},
},
Msps: map[string]*msp.FabricMSPConfig{
"msp1": {
TlsRootCerts: [][]byte{},
},
},
},
endpointDefinition: &endpointDef{
proposalResponseStatus: 200,
ordererStatus: 200,
},
postSetup: func(t *testing.T, def *preparedTest) {
def.dialer.Calls(func(_ context.Context, target string, _ ...grpc.DialOption) (*grpc.ClientConn, error) {
if target == "orderer2:7050" {
return nil, fmt.Errorf("orderer not answering")
}
return nil, nil
})
},
postTest: func(t *testing.T, def *preparedTest) {
require.Equal(t, 1, orderer1Mock.client.(*mocks.ABClient).BroadcastCallCount())
require.Equal(t, 0, orderer2Mock.client.(*mocks.ABClient).BroadcastCallCount())
require.Equal(t, 1, orderer3Mock.client.(*mocks.ABClient).BroadcastCallCount())
require.Equal(t, 1, orderer4Mock.client.(*mocks.ABClient).BroadcastCallCount())
},
},
{
name: "two BFT orderers cannot connect - quorum not satisfied",
isBFT: true,
plan: endorsementPlan{
"g1": {{endorser: localhostMock}},
},
config: &dp.ConfigResult{
Orderers: map[string]*dp.Endpoints{
"msp1": {
Endpoint: []*dp.Endpoint{
{Host: "orderer1", Port: 7050},
{Host: "orderer2", Port: 7050},
{Host: "orderer3", Port: 7050},
{Host: "orderer4", Port: 7050},
},
},
},
Msps: map[string]*msp.FabricMSPConfig{
"msp1": {
TlsRootCerts: [][]byte{},
},
},
},
endpointDefinition: &endpointDef{
proposalResponseStatus: 200,
ordererStatus: 200,
},
postSetup: func(t *testing.T, def *preparedTest) {
def.dialer.Calls(func(_ context.Context, target string, _ ...grpc.DialOption) (*grpc.ClientConn, error) {
if target == "orderer2:7050" || target == "orderer3:7050" {
return nil, fmt.Errorf("orderer not answering")
}
return nil, nil
})
},
postTest: func(t *testing.T, def *preparedTest) {
require.Equal(t, 1, orderer1Mock.client.(*mocks.ABClient).BroadcastCallCount())
require.Equal(t, 0, orderer2Mock.client.(*mocks.ABClient).BroadcastCallCount())
require.Equal(t, 0, orderer3Mock.client.(*mocks.ABClient).BroadcastCallCount())
require.Equal(t, 1, orderer4Mock.client.(*mocks.ABClient).BroadcastCallCount())
},
errCode: codes.Unavailable,
errString: "insufficient number of orderers could successfully process transaction to satisfy quorum requirement",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
test := prepareTest(t, &tt)
// first call endorse to prepare the tx
endorseResponse, err := test.server.Endorse(test.ctx, &pb.EndorseRequest{ProposedTransaction: test.signedProposal})
require.NoError(t, err)
preparedTx := endorseResponse.GetPreparedTransaction()
// sign the envelope
preparedTx.Signature = []byte("mysignature")
// submit
submitResponse, err := test.server.Submit(test.ctx, &pb.SubmitRequest{PreparedTransaction: preparedTx, ChannelId: testChannel})
if checkError(t, &tt, err) {
require.Nil(t, submitResponse, "response on error")
if tt.postTest != nil {
tt.postTest(t, test)
}
return
}
require.NoError(t, err)
require.True(t, proto.Equal(&pb.SubmitResponse{}, submitResponse), "Incorrect response")
if tt.postTest != nil {
tt.postTest(t, test)
}
})
}
}
func TestSubmitUnsigned(t *testing.T) {
server := &Server{}
req := &pb.SubmitRequest{
TransactionId: "transaction-id",
ChannelId: "channel-id",
PreparedTransaction: &cp.Envelope{},
}
_, err := server.Submit(context.Background(), req)
require.Error(t, err)
require.Equal(t, err, status.Error(codes.InvalidArgument, "prepared transaction must be signed"))
}
// copied from the smartbft library...
func TestComputeBFTQuorum(t *testing.T) {
// Ensure that quorum size is as expected.
type quorum struct {
N uint64
F int
Q int
}
quorums := []quorum{
{4, 1, 3},
{5, 1, 4},
{6, 1, 4},
{7, 2, 5},
{8, 2, 6},
{9, 2, 6},
{10, 3, 7},
{11, 3, 8},
{12, 3, 8},
}
for _, testCase := range quorums {
t.Run(fmt.Sprintf("%d nodes", testCase.N), func(t *testing.T) {
Q, F := computeBFTQuorum(testCase.N)
require.Equal(t, testCase.Q, Q)
require.Equal(t, testCase.F, F)
})
}
}