Skip to content

Commit

Permalink
[FAB-10622] Add Invocation Chain to Channel Client
Browse files Browse the repository at this point in the history
Added Invocation Chain meta-data to the Channel Client Request
so that the Selection Service chooses endorsers that satisfy the
endorsement policies of all chaincodes involved in an invocation
chain (i.e. for CC-to-CC invocations). Each chaincode may also
be associated with a set of private data collection names (used
by the Fabric Selection service) to exclude endorsers that do NOT
have read access to the collections.

Change-Id: I561289c941696d66e109621303873f191cffda34
Signed-off-by: Bob Stasyszyn <Bob.Stasyszyn@securekey.com>
  • Loading branch information
bstasyszyn committed Jun 13, 2018
1 parent 7dca4ca commit 6c6462d
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 22 deletions.
10 changes: 10 additions & 0 deletions pkg/client/channel/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ type Request struct {
Fcn string
Args [][]byte
TransientMap map[string][]byte

// InvocationChain contains meta-data that's used by some Selection Service implementations
// to choose endorsers that satisfy the endorsement policies of all chaincodes involved
// in an invocation chain (i.e. for CC-to-CC invocations).
// Each chaincode may also be associated with a set of private data collection names
// which are used by some Selection Services (e.g. Fabric Selection) to exclude endorsers
// that do NOT have read access to the collections.
// The invoked chaincode (specified by ChaincodeID) may optionally be added to the invocation
// chain along with any collections, otherwise it may be omitted.
InvocationChain []*fab.ChaincodeCall
}

//Response contains response parameters for query and execute an invocation transaction
Expand Down
10 changes: 10 additions & 0 deletions pkg/client/channel/invoke/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ type Request struct {
Fcn string
Args [][]byte
TransientMap map[string][]byte

// InvocationChain contains meta-data that's used by some Selection Service implementations
// to choose endorsers that satisfy the endorsement policies of all chaincodes involved
// in an invocation chain (i.e. for CC-to-CC invocations).
// Each chaincode may also be associated with a set of private data collection names
// which are used by some Selection Services (e.g. Fabric Selection) to exclude endorsers
// that do NOT have read access to the collections.
// The invoked chaincode (specified by ChaincodeID) may optionally be added to the invocation
// chain along with any collections, otherwise it may be omitted.
InvocationChain []*fab.ChaincodeCall
}

//Response contains response parameters for query and execute transaction
Expand Down
25 changes: 13 additions & 12 deletions pkg/client/channel/invoke/txnhandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,7 @@ func (h *ProposalProcessorHandler) Handle(requestContext *RequestContext, client
selectionOpts = append(selectionOpts, selectopts.WithPeerFilter(requestContext.SelectionFilter))
}

// TODO: Add optional meta-data to RequestContext that specifies invoked chaincodes
// (in CC-to-CC calls) where the policy of the invoked chaincodes need to be taken
// into consideration. Also, associatied private data collections may be specified
// in order to prioritize those endorsers which have read access to the private data
// collections.
chaincodes := []*fab.ChaincodeCall{
{
ID: requestContext.Request.ChaincodeID,
Collections: nil,
},
}
endorsers, err := clientContext.Selection.GetEndorsersForChaincode(chaincodes, selectionOpts...)
endorsers, err := clientContext.Selection.GetEndorsersForChaincode(newChaincodeCalls(requestContext.Request), selectionOpts...)
if err != nil {
requestContext.Error = errors.WithMessage(err, "Failed to get endorsing peers")
return
Expand All @@ -96,6 +85,18 @@ func (h *ProposalProcessorHandler) Handle(requestContext *RequestContext, client
}
}

func newChaincodeCalls(request Request) []*fab.ChaincodeCall {
chaincodes := []*fab.ChaincodeCall{{ID: request.ChaincodeID}}
for _, ccCall := range request.InvocationChain {
if ccCall.ID == chaincodes[0].ID {
chaincodes[0].Collections = ccCall.Collections
} else {
chaincodes = append(chaincodes, ccCall)
}
}
return chaincodes
}

//EndorsementValidationHandler for transaction proposal response filtering
type EndorsementValidationHandler struct {
next Handler
Expand Down
50 changes: 50 additions & 0 deletions pkg/client/channel/invoke/txnhandler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

txnmocks "github.com/hyperledger/fabric-sdk-go/pkg/client/common/mocks"
"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/status"
Expand Down Expand Up @@ -268,6 +269,55 @@ func TestProposalProcessorHandler(t *testing.T) {
}
}

func TestNewChaincodeCalls(t *testing.T) {
ccID1 := "cc1"
ccID2 := "cc2"
col1 := "col1"
col2 := "col2"

request := Request{
ChaincodeID: ccID1,
Fcn: "invoke",
Args: [][]byte{[]byte("query"), []byte("b")},
InvocationChain: []*fab.ChaincodeCall{
{
ID: ccID2,
Collections: []string{col1},
},
},
}

ccCalls := newChaincodeCalls(request)
require.Truef(t, len(ccCalls) == 2, "expecting 2 CC calls")
require.Equal(t, ccID1, ccCalls[0].ID)
require.Equal(t, ccID2, ccCalls[1].ID)
require.Emptyf(t, ccCalls[0].Collections, "expecting no collections for [%s]", ccID1)
require.Truef(t, len(ccCalls[1].Collections) == 1, "expecting 1 collection for [%s]", ccID2)

request = Request{
ChaincodeID: ccID1,
Fcn: "invoke",
Args: [][]byte{[]byte("query"), []byte("b")},
InvocationChain: []*fab.ChaincodeCall{
{
ID: ccID1,
Collections: []string{col1, col2},
},
{
ID: ccID2,
Collections: []string{col1},
},
},
}

ccCalls = newChaincodeCalls(request)
require.Truef(t, len(ccCalls) == 2, "expecting 2 CC calls")
require.Equal(t, ccID1, ccCalls[0].ID)
require.Equal(t, ccID2, ccCalls[1].ID)
require.Truef(t, len(ccCalls[0].Collections) == 2, "expecting 2 collections for [%s]", ccID1)
require.Truef(t, len(ccCalls[1].Collections) == 1, "expecting 1 collection for [%s]", ccID2)
}

//prepareHandlerContexts prepares context objects for handlers
func prepareRequestContext(request Request, opts Opts, t *testing.T) *RequestContext {
requestContext := &RequestContext{Request: request,
Expand Down
27 changes: 17 additions & 10 deletions test/integration/sdk/channel_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"time"

pb "github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/protos/peer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
Expand Down Expand Up @@ -75,7 +76,10 @@ func TestChannelClient(t *testing.T) {
testQueryWithOpts("201", chaincodeID, chClient, t)

// transaction
testTransaction(chaincodeID, chClient, t)
nestedCCID := integration.GenerateRandomID()
_, err = integration.InstallAndInstantiateExampleCC(sdk, fabsdk.WithUser("Admin"), testSetup.OrgID, nestedCCID)
require.NoError(t, err)
testTransaction(chaincodeID, nestedCCID, chClient, t)

// Verify transaction
testQuery("202", chaincodeID, chClient, t)
Expand Down Expand Up @@ -128,15 +132,18 @@ func testQueryWithOpts(expected string, ccID string, chClient *channel.Client, t
}
}

func testTransaction(ccID string, chClient *channel.Client, t *testing.T) {
response, err := chClient.Execute(channel.Request{ChaincodeID: ccID, Fcn: "invoke", Args: integration.ExampleCCTxArgs()},
channel.WithRetry(retry.DefaultChannelOpts))
if err != nil {
t.Fatalf("Failed to move funds: %s", err)
}
if response.TxValidationCode != pb.TxValidationCode_VALID {
t.Fatalf("Expecting TxValidationCode to be TxValidationCode_VALID but received: %s", response.TxValidationCode)
}
func testTransaction(ccID, nestedCCID string, chClient *channel.Client, t *testing.T) {
response, err := chClient.Execute(
channel.Request{
ChaincodeID: ccID,
Fcn: "invoke",
Args: integration.ExampleCCTxArgs(),
InvocationChain: []*fab.ChaincodeCall{{ID: nestedCCID}},
},
channel.WithRetry(retry.DefaultChannelOpts),
)
require.NoError(t, err, "Failed to move funds")
assert.Equal(t, pb.TxValidationCode_VALID, response.TxValidationCode, "Expecting TxValidationCode to be TxValidationCode_VALID")
}

type testHandler struct {
Expand Down

0 comments on commit 6c6462d

Please sign in to comment.