Skip to content
This repository was archived by the owner on Apr 25, 2025. It is now read-only.

Commit fe06786

Browse files
committed
[FAB-5520] Make Broadcast select a random orderer
The implementation of SendTransaction sends the transaction to all ordering service nodes. There is no reason to send the same transaction to all ordering service nodes. This change set makes a random ordering service instance be selected instead of sending to everyone. In case the selected (random) orderer returns a bad status, the code now tries to contact other endpoints until it succeeds or all have been tried. Change-Id: Idf5c8be69c1f90c62c7ebc164531fa740e670843 Signed-off-by: yacovm <yacovm@il.ibm.com>
1 parent 223e728 commit fe06786

File tree

5 files changed

+130
-64
lines changed

5 files changed

+130
-64
lines changed

api/apitxn/sender.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
// Sender provides the ability for a transaction to be created and sent.
1515
type Sender interface {
1616
CreateTransaction(resps []*TransactionProposalResponse) (*Transaction, error)
17-
SendTransaction(tx *Transaction) ([]*TransactionResponse, error)
17+
SendTransaction(tx *Transaction) (*TransactionResponse, error)
1818
}
1919

2020
// The Transaction object created from an endorsed proposal.

pkg/fabric-client/channel/txnsender.go

Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,27 @@ package channel
88

99
import (
1010
"fmt"
11+
"math/rand"
1112
"sync"
1213
"time"
1314

1415
"github.com/golang/protobuf/proto"
1516
proto_ts "github.com/golang/protobuf/ptypes/timestamp"
16-
"github.com/hyperledger/fabric/protos/common"
17-
18-
"github.com/hyperledger/fabric/bccsp"
19-
mspprotos "github.com/hyperledger/fabric/protos/msp"
20-
pb "github.com/hyperledger/fabric/protos/peer"
21-
protos_utils "github.com/hyperledger/fabric/protos/utils"
22-
2317
fab "github.com/hyperledger/fabric-sdk-go/api/apifabclient"
2418
"github.com/hyperledger/fabric-sdk-go/api/apitxn"
2519
fc "github.com/hyperledger/fabric-sdk-go/pkg/fabric-client/internal"
2620
"github.com/hyperledger/fabric-sdk-go/pkg/fabric-client/internal/txnproc"
21+
"github.com/hyperledger/fabric/bccsp"
22+
"github.com/hyperledger/fabric/protos/common"
23+
mspprotos "github.com/hyperledger/fabric/protos/msp"
24+
pb "github.com/hyperledger/fabric/protos/peer"
25+
protos_utils "github.com/hyperledger/fabric/protos/utils"
2726
)
2827

28+
func init() {
29+
rand.Seed(time.Now().UnixNano())
30+
}
31+
2932
// CreateTransaction create a transaction with proposal response, following the endorsement policy.
3033
func (c *Channel) CreateTransaction(resps []*apitxn.TransactionProposalResponse) (*apitxn.Transaction, error) {
3134
if len(resps) == 0 {
@@ -109,7 +112,7 @@ func (c *Channel) CreateTransaction(resps []*apitxn.TransactionProposalResponse)
109112
}
110113

111114
// SendTransaction send a transaction to the chain’s orderer service (one or more orderer endpoints) for consensus and committing to the ledger.
112-
func (c *Channel) SendTransaction(tx *apitxn.Transaction) ([]*apitxn.TransactionResponse, error) {
115+
func (c *Channel) SendTransaction(tx *apitxn.Transaction) (*apitxn.TransactionResponse, error) {
113116
if c.orderers == nil || len(c.orderers) == 0 {
114117
return nil, fmt.Errorf("orderers is nil")
115118
}
@@ -144,12 +147,12 @@ func (c *Channel) SendTransaction(tx *apitxn.Transaction) ([]*apitxn.Transaction
144147
return nil, err
145148
}
146149

147-
transactionResponses, err := c.BroadcastEnvelope(envelope)
150+
transactionResponse, err := c.BroadcastEnvelope(envelope)
148151
if err != nil {
149152
return nil, err
150153
}
151154

152-
return transactionResponses, nil
155+
return transactionResponse, nil
153156
}
154157

155158
// SendInstantiateProposal sends an instantiate proposal to one or more endorsing peers.
@@ -239,41 +242,43 @@ func (c *Channel) SignPayload(payload []byte) (*fab.SignedEnvelope, error) {
239242
return &fab.SignedEnvelope{Payload: payload, Signature: signature}, nil
240243
}
241244

242-
// BroadcastEnvelope will send the given envelope to each orderer
243-
func (c *Channel) BroadcastEnvelope(envelope *fab.SignedEnvelope) ([]*apitxn.TransactionResponse, error) {
245+
// BroadcastEnvelope will send the given envelope to some orderer, picking random endpoints
246+
// until all are exhausted
247+
func (c *Channel) BroadcastEnvelope(envelope *fab.SignedEnvelope) (*apitxn.TransactionResponse, error) {
244248
// Check if orderers are defined
245-
if c.orderers == nil || len(c.orderers) == 0 {
249+
if len(c.orderers) == 0 {
246250
return nil, fmt.Errorf("orderers not set")
247251
}
248252

249-
var responseMtx sync.Mutex
250-
var transactionResponses []*apitxn.TransactionResponse
251-
var wg sync.WaitGroup
252-
253+
// Copy aside the ordering service endpoints
254+
orderers := []fab.Orderer{}
253255
for _, o := range c.orderers {
254-
wg.Add(1)
255-
go func(orderer fab.Orderer) {
256-
defer wg.Done()
257-
var transactionResponse *apitxn.TransactionResponse
258-
259-
logger.Debugf("Broadcasting envelope to orderer :%s\n", orderer.URL())
260-
if _, err := orderer.SendBroadcast(envelope); err != nil {
261-
logger.Debugf("Receive Error Response from orderer :%v\n", err)
262-
transactionResponse = &apitxn.TransactionResponse{Orderer: orderer.URL(),
263-
Err: fmt.Errorf("Error calling orderer '%s': %s", orderer.URL(), err)}
264-
} else {
265-
logger.Debugf("Receive Success Response from orderer\n")
266-
transactionResponse = &apitxn.TransactionResponse{Orderer: orderer.URL(), Err: nil}
267-
}
256+
orderers = append(orderers, o)
257+
}
268258

269-
responseMtx.Lock()
270-
transactionResponses = append(transactionResponses, transactionResponse)
271-
responseMtx.Unlock()
272-
}(o)
259+
// Iterate them in a random order and try broadcasting 1 by 1
260+
var errResp *apitxn.TransactionResponse
261+
for _, i := range rand.Perm(len(orderers)) {
262+
resp := c.sendBroadcast(envelope, orderers[i])
263+
if resp.Err != nil {
264+
errResp = resp
265+
} else {
266+
return resp, nil
267+
}
273268
}
274-
wg.Wait()
269+
return errResp, nil
270+
}
275271

276-
return transactionResponses, nil
272+
func (c *Channel) sendBroadcast(envelope *fab.SignedEnvelope, orderer fab.Orderer) *apitxn.TransactionResponse {
273+
logger.Debugf("Broadcasting envelope to orderer :%s\n", orderer.URL())
274+
if _, err := orderer.SendBroadcast(envelope); err != nil {
275+
logger.Debugf("Receive Error Response from orderer :%v\n", err)
276+
return &apitxn.TransactionResponse{Orderer: orderer.URL(),
277+
Err: fmt.Errorf("Error calling orderer '%s': %s", orderer.URL(), err)}
278+
} else {
279+
logger.Debugf("Receive Success Response from orderer\n")
280+
return &apitxn.TransactionResponse{Orderer: orderer.URL(), Err: nil}
281+
}
277282
}
278283

279284
// SendEnvelope sends the given envelope to each orderer and returns a block response

pkg/fabric-client/channel/txnsender_test.go

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ SPDX-License-Identifier: Apache-2.0
77
package channel
88

99
import (
10+
"crypto/rand"
11+
"fmt"
1012
"os"
1113
"strconv"
14+
"strings"
1215
"testing"
1316
"time"
1417

@@ -181,19 +184,33 @@ func TestSendInstantiateProposal(t *testing.T) {
181184
if err == nil || err.Error() != "Missing peer objects for instantiate CC proposal" {
182185
t.Fatal("Missing peer objects validation is not working as expected")
183186
}
187+
}
184188

189+
type mockReader struct {
190+
err error
185191
}
186192

187-
func TestBroadcastEnvelope(t *testing.T) {
193+
func (r *mockReader) Read(p []byte) (int, error) {
194+
if r.err != nil {
195+
return 0, r.err
196+
}
197+
n, _ := rand.Read(p)
198+
return n, nil
199+
}
188200

201+
func TestBroadcastEnvelope(t *testing.T) {
189202
//Setup channel
190203
channel, _ := setupTestChannel()
191204

192-
//Create mock orderer
193-
orderer := mocks.NewMockOrderer("", nil)
205+
lsnr1 := make(chan *fab.SignedEnvelope)
206+
lsnr2 := make(chan *fab.SignedEnvelope)
207+
//Create mock orderers
208+
orderer1 := mocks.NewMockOrderer("1", lsnr1)
209+
orderer2 := mocks.NewMockOrderer("2", lsnr2)
194210

195-
//Add an orderer
196-
channel.AddOrderer(orderer)
211+
//Add the orderers
212+
channel.AddOrderer(orderer1)
213+
channel.AddOrderer(orderer2)
197214

198215
peer := mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil}
199216
channel.AddPeer(&peer)
@@ -204,17 +221,65 @@ func TestBroadcastEnvelope(t *testing.T) {
204221
}
205222
res, err := channel.BroadcastEnvelope(sigEnvelope)
206223

207-
if err != nil || res == nil {
208-
t.Fatalf("Test Broadcast Envelope Failed, cause %s", err.Error())
224+
if err != nil || res.Err != nil {
225+
t.Fatalf("Test Broadcast Envelope Failed, cause %v %v", err, res)
226+
}
227+
228+
// Ensure only 1 orderer was selected for broadcast
229+
firstSelected := 0
230+
secondSelected := 0
231+
for i := 0; i < 2; i++ {
232+
select {
233+
case <-lsnr1:
234+
firstSelected = 1
235+
case <-lsnr2:
236+
secondSelected = 1
237+
case <-time.After(time.Second):
238+
}
209239
}
210240

211-
channel.RemoveOrderer(orderer)
241+
if firstSelected+secondSelected != 1 {
242+
t.Fatal("Both or none orderers were selected for broadcast:", firstSelected+secondSelected)
243+
}
244+
245+
// Now make 1 of them fail and repeatedly broadcast
246+
broadcastCount := 50
247+
for i := 0; i < broadcastCount; i++ {
248+
orderer1.(mocks.MockOrderer).EnqueueSendBroadcastError(fmt.Errorf("Service Unavailable"))
249+
}
250+
// It should always succeed even though one of them has failed
251+
for i := 0; i < broadcastCount; i++ {
252+
if res, err := channel.BroadcastEnvelope(sigEnvelope); err != nil || res.Err != nil {
253+
t.Fatalf("Test Broadcast Envelope Failed, cause %v %v", err, res)
254+
}
255+
}
256+
257+
// Now, fail both and ensure any attempt fails
258+
for i := 0; i < broadcastCount; i++ {
259+
orderer1.(mocks.MockOrderer).EnqueueSendBroadcastError(fmt.Errorf("Service Unavailable"))
260+
orderer2.(mocks.MockOrderer).EnqueueSendBroadcastError(fmt.Errorf("Service Unavailable"))
261+
}
262+
263+
for i := 0; i < broadcastCount; i++ {
264+
res, err := channel.BroadcastEnvelope(sigEnvelope)
265+
if err != nil {
266+
t.Fatalf("Test Broadcast sending failed, cause %v", err)
267+
}
268+
if res.Err == nil {
269+
t.Fatal("Test Broadcast succeeded, but it should have failed")
270+
}
271+
if !strings.Contains(res.Err.Error(), "Service Unavailable") {
272+
t.Fatal("Test Broadcast failed but didn't return the correct reason(should contain 'Service Unavailable')")
273+
}
274+
}
275+
276+
channel.RemoveOrderer(orderer1)
277+
channel.RemoveOrderer(orderer2)
212278
_, err = channel.BroadcastEnvelope(sigEnvelope)
213279

214280
if err == nil || err.Error() != "orderers not set" {
215281
t.Fatal("orderers not set validation on broadcast envelope is not working as expected")
216282
}
217-
218283
}
219284

220285
func TestSendTransaction(t *testing.T) {

pkg/fabric-txn/internal/common.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,25 +44,23 @@ func CreateAndSendTransactionProposal(sender apitxn.ProposalSender, chainCodeID
4444
}
4545

4646
// CreateAndSendTransaction ...
47-
func CreateAndSendTransaction(sender apitxn.Sender, resps []*apitxn.TransactionProposalResponse) ([]*apitxn.TransactionResponse, error) {
47+
func CreateAndSendTransaction(sender apitxn.Sender, resps []*apitxn.TransactionProposalResponse) (*apitxn.TransactionResponse, error) {
4848

4949
tx, err := sender.CreateTransaction(resps)
5050
if err != nil {
5151
return nil, fmt.Errorf("CreateTransaction returned error: %v", err)
5252
}
5353

54-
transactionResponses, err := sender.SendTransaction(tx)
54+
transactionResponse, err := sender.SendTransaction(tx)
5555
if err != nil {
5656
return nil, fmt.Errorf("SendTransaction returned error: %v", err)
5757

5858
}
59-
for _, v := range transactionResponses {
60-
if v.Err != nil {
61-
return nil, fmt.Errorf("Orderer %s returned error: %v", v.Orderer, v.Err)
62-
}
59+
if transactionResponse.Err != nil {
60+
return nil, fmt.Errorf("Orderer %s returned error: %v", transactionResponse.Orderer, transactionResponse.Err)
6361
}
6462

65-
return transactionResponses, nil
63+
return transactionResponse, nil
6664
}
6765

6866
// RegisterTxEvent registers on the given eventhub for the given transaction id

test/integration/base_test_setup.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,14 @@ import (
1212
"path"
1313
"time"
1414

15-
"github.com/hyperledger/fabric-sdk-go/api/apitxn"
16-
"github.com/hyperledger/fabric-sdk-go/pkg/config"
17-
"github.com/hyperledger/fabric-sdk-go/pkg/fabric-client/events"
18-
"github.com/hyperledger/fabric-sdk-go/pkg/fabric-client/orderer"
19-
2015
"github.com/hyperledger/fabric-sdk-go/api/apiconfig"
2116
ca "github.com/hyperledger/fabric-sdk-go/api/apifabca"
2217
fab "github.com/hyperledger/fabric-sdk-go/api/apifabclient"
18+
"github.com/hyperledger/fabric-sdk-go/api/apitxn"
2319
deffab "github.com/hyperledger/fabric-sdk-go/def/fabapi"
20+
"github.com/hyperledger/fabric-sdk-go/pkg/config"
21+
"github.com/hyperledger/fabric-sdk-go/pkg/fabric-client/events"
22+
"github.com/hyperledger/fabric-sdk-go/pkg/fabric-client/orderer"
2423
fabricTxn "github.com/hyperledger/fabric-sdk-go/pkg/fabric-txn"
2524
admin "github.com/hyperledger/fabric-sdk-go/pkg/fabric-txn/admin"
2625
bccspFactory "github.com/hyperledger/fabric/bccsp/factory"
@@ -299,7 +298,7 @@ func (setup *BaseSetupImpl) CreateAndSendTransactionProposal(channel fab.Channel
299298
}
300299

301300
// CreateAndSendTransaction ...
302-
func (setup *BaseSetupImpl) CreateAndSendTransaction(channel fab.Channel, resps []*apitxn.TransactionProposalResponse) ([]*apitxn.TransactionResponse, error) {
301+
func (setup *BaseSetupImpl) CreateAndSendTransaction(channel fab.Channel, resps []*apitxn.TransactionProposalResponse) (*apitxn.TransactionResponse, error) {
303302

304303
tx, err := channel.CreateTransaction(resps)
305304
if err != nil {
@@ -311,10 +310,9 @@ func (setup *BaseSetupImpl) CreateAndSendTransaction(channel fab.Channel, resps
311310
return nil, fmt.Errorf("SendTransaction return error: %v", err)
312311

313312
}
314-
for _, v := range transactionResponse {
315-
if v.Err != nil {
316-
return nil, fmt.Errorf("Orderer %s return error: %v", v.Orderer, v.Err)
317-
}
313+
314+
if transactionResponse.Err != nil {
315+
return nil, fmt.Errorf("Orderer %s return error: %v", transactionResponse.Orderer, transactionResponse.Err)
318316
}
319317

320318
return transactionResponse, nil

0 commit comments

Comments
 (0)