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

Commit f731064

Browse files
committed
[FAB-3003] Added join channel functionality
Change-Id: I7eb223e4c27725fc55e37c7f6834424a3cf51d0c Signed-off-by: Divyank Katira <Divyank.Katira@securekey.com>
1 parent 17cd4e0 commit f731064

File tree

14 files changed

+705
-107
lines changed

14 files changed

+705
-107
lines changed

fabric-client/chain.go

Lines changed: 143 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"time"
2828

2929
"github.com/golang/protobuf/proto"
30+
"github.com/hyperledger/fabric-sdk-go/fabric-client/util"
3031
"github.com/hyperledger/fabric/bccsp"
3132
msp "github.com/hyperledger/fabric/msp"
3233
"github.com/hyperledger/fabric/protos/common"
@@ -63,7 +64,8 @@ type Chain interface {
6364
AddOrderer(orderer Orderer)
6465
RemoveOrderer(orderer Orderer)
6566
GetOrderers() []Orderer
66-
CreateChannel(request CreateChannelRequest) error
67+
CreateChannel(request *CreateChannelRequest) error
68+
JoinChannel(request *JoinChannelRequest) error
6769
UpdateChain() bool
6870
IsReadonly() bool
6971
QueryInfo() (*common.BlockchainInfo, error)
@@ -174,6 +176,13 @@ type CreateChannelRequest struct {
174176
ConfigData []byte
175177
}
176178

179+
// JoinChannelRequest allows a set of peers to transact on a channel on the network
180+
type JoinChannelRequest struct {
181+
Targets []Peer
182+
TxID string
183+
Nonce []byte
184+
}
185+
177186
// NewChain ...
178187
/**
179188
* @param {string} name to identify different chain instances. The naming of chain instances
@@ -388,10 +397,10 @@ func (c *chain) GetOrderers() []Orderer {
388397
// CreateChannel calls the an orderer to create a channel on the network
389398
// @param {CreateChannelRequest} request Contains cofiguration information
390399
// @returns {bool} result of the channel creation
391-
func (c *chain) CreateChannel(request CreateChannelRequest) error {
400+
func (c *chain) CreateChannel(request *CreateChannelRequest) error {
392401
var failureCount int
393402
// Validate request
394-
if request.ConfigData == nil {
403+
if request == nil || request.ConfigData == nil {
395404
return fmt.Errorf("Configuration is required to create a chanel")
396405
}
397406

@@ -426,6 +435,89 @@ func (c *chain) CreateChannel(request CreateChannelRequest) error {
426435
return nil
427436
}
428437

438+
// JoinChannel instructs a set of peers to join the channel represented by
439+
// this chain
440+
// @param {JoinChannelRequest} Join channel request
441+
// @returns error, if applicable
442+
func (c *chain) JoinChannel(request *JoinChannelRequest) error {
443+
joinCommand := "JoinChain"
444+
err := validateJoinChannelRequest(request)
445+
if err != nil {
446+
return err
447+
}
448+
// Fetch genesis block
449+
block, err := c.fetchGenesisBlock()
450+
if err != nil {
451+
return err
452+
}
453+
blockBytes, err := proto.Marshal(block)
454+
if err != nil {
455+
return fmt.Errorf("Error unmarshalling block: %s", err)
456+
}
457+
// Get user enrolment info and serialize for signing requests
458+
user, err := c.clientContext.GetUserContext("")
459+
if err != nil {
460+
return fmt.Errorf("GetUserContext returned error: %s", err)
461+
}
462+
creatorID, err := getSerializedIdentity(user.GetEnrollmentCertificate())
463+
if err != nil {
464+
return err
465+
}
466+
// Create join channel transaction proposal for target peers
467+
var args [][]byte
468+
args = append(args, []byte(joinCommand))
469+
args = append(args, blockBytes)
470+
ccis := &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{
471+
Type: pb.ChaincodeSpec_GOLANG, ChaincodeId: &pb.ChaincodeID{Name: "cscc"},
472+
Input: &pb.ChaincodeInput{Args: args}}}
473+
474+
// create a proposal from a ChaincodeInvocationSpec
475+
proposal, _, err := protos_utils.
476+
CreateChaincodeProposalWithTxIDNonceAndTransient(request.TxID,
477+
common.HeaderType_ENDORSER_TRANSACTION, "", ccis,
478+
request.Nonce, creatorID, nil)
479+
if err != nil {
480+
return fmt.Errorf("Could not create chaincode proposal, err %s", err)
481+
}
482+
// Serialize join proposal
483+
proposalBytes, err := protos_utils.GetBytesProposal(proposal)
484+
if err != nil {
485+
return err
486+
}
487+
// Sign join proposal
488+
signature, err := c.signObjectWithKey(proposalBytes, user.GetPrivateKey(),
489+
&bccsp.SHAOpts{}, nil)
490+
if err != nil {
491+
return err
492+
}
493+
// Send join proposal
494+
proposalResponses, err := c.SendTransactionProposal(&TransactionProposal{
495+
TransactionID: request.TxID,
496+
signedProposal: &pb.SignedProposal{
497+
ProposalBytes: proposalBytes,
498+
Signature: signature},
499+
proposal: proposal,
500+
}, 0, request.Targets)
501+
if err != nil {
502+
return fmt.Errorf("Error sending join transaction proposal: %s", err)
503+
}
504+
505+
// Check responses from target peers for success/failure
506+
var joinError string
507+
for _, response := range proposalResponses {
508+
if response.Err != nil {
509+
joinError = joinError +
510+
fmt.Sprintf("Join channel proposal response error: %s \n",
511+
response.Err.Error())
512+
}
513+
}
514+
if joinError != "" {
515+
return fmt.Errorf(joinError)
516+
}
517+
518+
return nil
519+
}
520+
429521
// UpdateChain ...
430522
/**
431523
* Calls the orderer(s) to update an existing chain. This allows the addition and
@@ -1204,6 +1296,38 @@ func (c *chain) signProposal(proposal *pb.Proposal) (*pb.SignedProposal, error)
12041296
return &pb.SignedProposal{ProposalBytes: proposalBytes, Signature: signature}, nil
12051297
}
12061298

1299+
// fetchGenesisBlock fetches the configuration block for this channel
1300+
func (c *chain) fetchGenesisBlock() (*common.Block, error) {
1301+
// Get user enrolment info and serialize for signing requests
1302+
user, err := c.clientContext.GetUserContext("")
1303+
if err != nil {
1304+
return nil, fmt.Errorf("GetUserContext returned error: %s", err)
1305+
}
1306+
creatorID, err := getSerializedIdentity(user.GetEnrollmentCertificate())
1307+
if err != nil {
1308+
return nil, err
1309+
}
1310+
// Seek block zero (the configuration tx for this channel)
1311+
payload := util.CreateGenesisBlockRequest(c.name, creatorID)
1312+
blockRequest, err := c.SignPayload(payload)
1313+
if err != nil {
1314+
return nil, fmt.Errorf("Error signing payload: %s", err)
1315+
}
1316+
// Request genesis block from ordering service
1317+
var block *common.Block
1318+
// TODO: what if the primary orderer is down?
1319+
responses, errors := c.GetOrderers()[0].SendDeliver(blockRequest)
1320+
// Block on channels for genesis block or error
1321+
select {
1322+
case block = <-responses:
1323+
logger.Debugf("Got genesis block from ordering service: %#v", block)
1324+
case err = <-errors:
1325+
return nil, fmt.Errorf("Error from SendDeliver(): %s", err)
1326+
}
1327+
1328+
return block, nil
1329+
}
1330+
12071331
func getSerializedIdentity(userCertificate []byte) ([]byte, error) {
12081332
serializedIdentity := &msp.SerializedIdentity{Mspid: config.GetFabricCAID(),
12091333
IdBytes: userCertificate}
@@ -1242,3 +1366,19 @@ func buildChaincodePolicy(mspid string) (*common.SignaturePolicyEnvelope, error)
12421366
}
12431367
return p, nil
12441368
}
1369+
func validateJoinChannelRequest(request *JoinChannelRequest) error {
1370+
// Validate arguments
1371+
if request == nil {
1372+
return fmt.Errorf("JoinChannelRequest argument is required to join channel")
1373+
}
1374+
if request.Targets == nil || len(request.Targets) == 0 {
1375+
return fmt.Errorf("Atleast one target peer is required to join channel")
1376+
}
1377+
if request.TxID == "" {
1378+
return fmt.Errorf("Transaction ID is required to join channel")
1379+
}
1380+
if request.Nonce == nil {
1381+
return fmt.Errorf("Nonce is required to join channel")
1382+
}
1383+
return nil
1384+
}

fabric-client/chain_test.go

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,18 @@ package fabricclient
2222
import (
2323
"fmt"
2424
"io/ioutil"
25+
"net"
2526
"testing"
2627

28+
"google.golang.org/grpc"
29+
2730
mocks "github.com/hyperledger/fabric-sdk-go/fabric-client/mocks"
31+
"github.com/hyperledger/fabric-sdk-go/fabric-client/util"
2832
pb "github.com/hyperledger/fabric/protos/peer"
2933
)
3034

35+
var testAddress = "0.0.0.0:5244"
36+
3137
func TestChainMethods(t *testing.T) {
3238
client := NewClient()
3339
chain, err := NewChain("testChain", client)
@@ -181,7 +187,7 @@ func TestCreateChain(t *testing.T) {
181187
t.Fatalf(err.Error())
182188
}
183189
// Create channel without configuration
184-
err = chain.CreateChannel(CreateChannelRequest{})
190+
err = chain.CreateChannel(&CreateChannelRequest{})
185191
if err == nil {
186192
t.Fatalf("Expected error creating channel without config tx")
187193
}
@@ -194,7 +200,7 @@ func TestCreateChain(t *testing.T) {
194200
orderer := mockOrderer{fmt.Sprintf("0.0.0.0:1234"), nil}
195201
chain.AddOrderer(&orderer)
196202
// Test with valid cofiguration
197-
err = chain.CreateChannel(CreateChannelRequest{ConfigData: configTx})
203+
err = chain.CreateChannel(&CreateChannelRequest{ConfigData: configTx})
198204
if err != nil {
199205
t.Fatalf("Did not expect error from create channel. Got error: %s", err.Error())
200206
}
@@ -238,6 +244,55 @@ func TestConcurrentOrderers(t *testing.T) {
238244
}
239245
}
240246

247+
func TestJoinChannel(t *testing.T) {
248+
var peers []Peer
249+
endorserServer := startEndorserServer(t)
250+
chain, _ := setupTestChain()
251+
peer, _ := CreateNewPeer(testAddress, "", "")
252+
peers = append(peers, peer)
253+
orderer := &mockOrderer{}
254+
nonce, _ := util.GenerateRandomNonce()
255+
txID, _ := util.ComputeTxID(nonce, []byte("testID"))
256+
request := &JoinChannelRequest{Targets: peers, Nonce: nonce, TxID: txID}
257+
chain.AddOrderer(orderer)
258+
chain.AddPeer(peer)
259+
// Test join channel with valid arguments
260+
err := chain.JoinChannel(request)
261+
if err != nil {
262+
t.Fatalf("Did not expect error from join channel. Got: %s", err)
263+
}
264+
// Test join channel without request
265+
err = chain.JoinChannel(nil)
266+
if err.Error() != "JoinChannelRequest argument is required to join channel" {
267+
t.Fatalf("Expected error without join channel request")
268+
}
269+
// Test join channel without target peers
270+
request = &JoinChannelRequest{Targets: nil, Nonce: nonce, TxID: txID}
271+
err = chain.JoinChannel(request)
272+
if err.Error() != "Atleast one target peer is required to join channel" {
273+
t.Fatalf("Expected error without target peers")
274+
}
275+
// Test join channel without nonce
276+
request = &JoinChannelRequest{Targets: peers, Nonce: nil, TxID: txID}
277+
err = chain.JoinChannel(request)
278+
if err.Error() != "Nonce is required to join channel" {
279+
t.Fatalf("Expected error without nonce")
280+
}
281+
// Test join channel without TxID
282+
request = &JoinChannelRequest{Targets: peers, Nonce: nonce, TxID: ""}
283+
err = chain.JoinChannel(request)
284+
if err.Error() != "Transaction ID is required to join channel" {
285+
t.Fatalf("Expected error without transaction ID")
286+
}
287+
// Test failed proposal error handling
288+
endorserServer.ProposalError = fmt.Errorf("Test Error")
289+
request = &JoinChannelRequest{Targets: peers, Nonce: nonce, TxID: txID}
290+
err = chain.JoinChannel(request)
291+
if err == nil {
292+
t.Fatalf("Expected error")
293+
}
294+
}
295+
241296
func setupTestChain() (Chain, error) {
242297
client := NewClient()
243298
user := NewUser("test")
@@ -265,3 +320,17 @@ func setupMassiveTestChain(numberOfPeers int, numberOfOrderers int) (Chain, erro
265320

266321
return chain, error
267322
}
323+
324+
func startEndorserServer(t *testing.T) *mocks.MockEndorserServer {
325+
grpcServer := grpc.NewServer()
326+
lis, err := net.Listen("tcp", testAddress)
327+
endorserServer := &mocks.MockEndorserServer{}
328+
pb.RegisterEndorserServer(grpcServer, endorserServer)
329+
if err != nil {
330+
fmt.Printf("Error starting test server %s", err)
331+
t.FailNow()
332+
}
333+
fmt.Printf("Starting test server\n")
334+
go grpcServer.Serve(lis)
335+
return endorserServer
336+
}

fabric-client/mockorderer.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,22 @@ limitations under the License.
1919

2020
package fabricclient
2121

22+
import (
23+
"github.com/hyperledger/fabric/protos/common"
24+
ab "github.com/hyperledger/fabric/protos/orderer"
25+
)
26+
27+
// TestBlock is a test block
28+
var testBlock = &ab.DeliverResponse{
29+
Type: &ab.DeliverResponse_Block{
30+
Block: &common.Block{
31+
Data: &common.BlockData{
32+
Data: [][]byte{[]byte("test")},
33+
},
34+
},
35+
},
36+
}
37+
2238
// mockOrderer is a mock fabricclient.Orderer
2339
type mockOrderer struct {
2440
MockURL string
@@ -34,3 +50,12 @@ func (o *mockOrderer) GetURL() string {
3450
func (o *mockOrderer) SendBroadcast(envelope *SignedEnvelope) error {
3551
return o.MockError
3652
}
53+
54+
// SendBroadcast mocks sending a deliver request to the ordering service
55+
func (o *mockOrderer) SendDeliver(envelope *SignedEnvelope) (chan *common.Block,
56+
chan error) {
57+
responses := make(chan *common.Block, 1)
58+
errors := make(chan error, 1)
59+
responses <- testBlock.GetBlock()
60+
return responses, errors
61+
}

0 commit comments

Comments
 (0)