876 lines
26 KiB
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)
|
|
})
|
|
}
|
|
}
|