From bc269e64712c90133a47b0590b8c49dd341e2b3e Mon Sep 17 00:00:00 2001 From: Troy Ronda Date: Fri, 9 Feb 2018 14:29:11 -0500 Subject: [PATCH] [FAB-8191] Split ledger queries from channel This patch creates a new ChannelLedger type that contains query methods to a channel's underlying ledger. Change-Id: Ie94e04b6139075af6462d239c47689d18e1bca59 Signed-off-by: Troy Ronda --- api/apifabclient/channel.go | 9 + pkg/fabric-client/channel/channel.go | 106 ++++- pkg/fabric-client/channel/channel_test.go | 40 ++ pkg/fabric-client/channel/ledger.go | 418 ++++++++++++++++++ .../channel/{query_test.go => ledger_test.go} | 172 ++----- pkg/fabric-client/channel/query.go | 302 ------------- pkg/fabric-client/chconfig/chconfig_test.go | 2 +- pkg/fabsdk/deprecated.go | 2 +- test/integration/fab/channel_queries_test.go | 2 +- 9 files changed, 601 insertions(+), 452 deletions(-) create mode 100644 pkg/fabric-client/channel/ledger.go rename pkg/fabric-client/channel/{query_test.go => ledger_test.go} (51%) delete mode 100644 pkg/fabric-client/channel/query.go diff --git a/api/apifabclient/channel.go b/api/apifabclient/channel.go index 3a4cfd0a42..5f17cc6407 100644 --- a/api/apifabclient/channel.go +++ b/api/apifabclient/channel.go @@ -59,6 +59,15 @@ type Channel interface { QueryConfigBlock(peers []Peer, minResponses int) (*common.ConfigEnvelope, error) } +// ChannelLedger provides access to the underlying ledger for a channel. +type ChannelLedger interface { + QueryInfo(targets []ProposalProcessor) (*common.BlockchainInfo, error) + QueryBlock(blockNumber int, targets []ProposalProcessor) (*common.Block, error) + QueryBlockByHash(blockHash []byte, targets []ProposalProcessor) (*common.Block, error) + QueryTransaction(transactionID string, targets []ProposalProcessor) (*pb.ProcessedTransaction, error) + QueryInstantiatedChaincodes(targets []ProposalProcessor) (*pb.ChaincodeQueryResponse, error) +} + // OrgAnchorPeer contains information about an anchor peer on this channel type OrgAnchorPeer struct { Org string diff --git a/pkg/fabric-client/channel/channel.go b/pkg/fabric-client/channel/channel.go index 1c022f3517..98afbdfd89 100644 --- a/pkg/fabric-client/channel/channel.go +++ b/pkg/fabric-client/channel/channel.go @@ -7,21 +7,20 @@ SPDX-License-Identifier: Apache-2.0 package channel import ( - fab "github.com/hyperledger/fabric-sdk-go/api/apifabclient" - "crypto/x509" - "encoding/pem" - + "regexp" "strings" - "regexp" + "github.com/pkg/errors" "github.com/hyperledger/fabric-sdk-go/api/apiconfig" + fab "github.com/hyperledger/fabric-sdk-go/api/apifabclient" "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric/msp" "github.com/hyperledger/fabric-sdk-go/pkg/fabric-client/orderer" "github.com/hyperledger/fabric-sdk-go/pkg/logging" - "github.com/pkg/errors" + "github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/protos/common" + pb "github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/protos/peer" ) var logger = logging.NewLogger("fabric_sdk_go") @@ -327,3 +326,98 @@ func resolveOrdererURL(ordererURL string) string { } return "grpcs://" + ordererURL } + +// QueryInfo queries for various useful information on the state of the channel +// (height, known peers). +// This query will be made to the primary peer. +func (c *Channel) QueryInfo() (*common.BlockchainInfo, error) { + l := NewLedger(c.clientContext, c.name) + resps, err := l.QueryInfo([]fab.ProposalProcessor{c.PrimaryPeer()}) + if err != nil { + return nil, err + } + return resps[0], err +} + +// QueryBlockByHash queries the ledger for Block by block hash. +// This query will be made to the primary peer. +// Returns the block. +func (c *Channel) QueryBlockByHash(blockHash []byte) (*common.Block, error) { + l := NewLedger(c.clientContext, c.name) + resps, err := l.QueryBlockByHash(blockHash, []fab.ProposalProcessor{c.PrimaryPeer()}) + if err != nil { + return nil, err + } + return resps[0], err +} + +// QueryBlock queries the ledger for Block by block number. +// This query will be made to the primary peer. +// blockNumber: The number which is the ID of the Block. +// It returns the block. +func (c *Channel) QueryBlock(blockNumber int) (*common.Block, error) { + l := NewLedger(c.clientContext, c.name) + resps, err := l.QueryBlock(blockNumber, []fab.ProposalProcessor{c.PrimaryPeer()}) + if err != nil { + return nil, err + } + return resps[0], err +} + +// QueryTransaction queries the ledger for Transaction by number. +// This query will be made to the primary peer. +// Returns the ProcessedTransaction information containing the transaction. +// TODO: add optional target +func (c *Channel) QueryTransaction(transactionID string) (*pb.ProcessedTransaction, error) { + l := NewLedger(c.clientContext, c.name) + resps, err := l.QueryTransaction(transactionID, []fab.ProposalProcessor{c.PrimaryPeer()}) + if err != nil { + return nil, err + } + return resps[0], err +} + +// QueryInstantiatedChaincodes queries the instantiated chaincodes on this channel. +// This query will be made to the primary peer. +func (c *Channel) QueryInstantiatedChaincodes() (*pb.ChaincodeQueryResponse, error) { + l := NewLedger(c.clientContext, c.name) + resps, err := l.QueryInstantiatedChaincodes([]fab.ProposalProcessor{c.PrimaryPeer()}) + if err != nil { + return nil, err + } + return resps[0], err + +} + +// QueryConfigBlock returns the current configuration block for the specified channel. If the +// peer doesn't belong to the channel, return error +func (c *Channel) QueryConfigBlock(peers []fab.Peer, minResponses int) (*common.ConfigEnvelope, error) { + l := NewLedger(c.clientContext, c.name) + return l.QueryConfigBlock(peers, minResponses) +} + +// QueryByChaincode sends a proposal to one or more endorsing peers that will be handled by the chaincode. +// This request will be presented to the chaincode 'invoke' and must understand +// from the arguments that this is a query request. The chaincode must also return +// results in the byte array format and the caller will have to be able to decode. +// these results. +func (c *Channel) QueryByChaincode(request fab.ChaincodeInvokeRequest) ([][]byte, error) { + targets, err := c.chaincodeInvokeRequestAddDefaultPeers(request.Targets) + if err != nil { + return nil, err + } + resps, err := queryChaincode(c.clientContext, c.name, request, targets) + return filterProposalResponses(resps, err) +} + +// QueryBySystemChaincode invokes a chaincode that isn't part of a channel. +// +// TODO: This function's name is confusing - call the normal QueryByChaincode for system chaincode on a channel. +func (c *Channel) QueryBySystemChaincode(request fab.ChaincodeInvokeRequest) ([][]byte, error) { + targets, err := c.chaincodeInvokeRequestAddDefaultPeers(request.Targets) + if err != nil { + return nil, err + } + resps, err := queryChaincode(c.clientContext, systemChannel, request, targets) + return filterProposalResponses(resps, err) +} diff --git a/pkg/fabric-client/channel/channel_test.go b/pkg/fabric-client/channel/channel_test.go index 4fe4dd61a8..737b7195fd 100644 --- a/pkg/fabric-client/channel/channel_test.go +++ b/pkg/fabric-client/channel/channel_test.go @@ -7,6 +7,7 @@ package channel import ( "fmt" + "reflect" "testing" fab "github.com/hyperledger/fabric-sdk-go/api/apifabclient" @@ -165,6 +166,45 @@ func TestPrimaryPeer(t *testing.T) { } +func TestQueryOnSystemChannel(t *testing.T) { + channel, _ := setupChannel(systemChannel) + peer := mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil, Status: 200} + err := channel.AddPeer(&peer) + if err != nil { + t.Fatalf("Error adding peer to channel: %s", err) + } + + request := fab.ChaincodeInvokeRequest{ + ChaincodeID: "ccID", + Fcn: "method", + Args: [][]byte{[]byte("arg")}, + } + if _, err := channel.QueryByChaincode(request); err != nil { + t.Fatalf("Error invoking chaincode on system channel: %s", err) + } +} + +func TestQueryBySystemChaincode(t *testing.T) { + channel, _ := setupTestChannel() + + peer := mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil, Payload: []byte("A"), Status: 200} + channel.AddPeer(&peer) + + request := fab.ChaincodeInvokeRequest{ + ChaincodeID: "cc", + Fcn: "Hello", + } + resp, err := channel.QueryBySystemChaincode(request) + if err != nil { + t.Fatalf("Failed to query: %s", err) + } + expectedResp := []byte("A") + + if !reflect.DeepEqual(resp[0], expectedResp) { + t.Fatalf("Unexpected transaction proposal response: %v", resp) + } +} + func isValueInList(value string, list []string) bool { for _, v := range list { if v == value { diff --git a/pkg/fabric-client/channel/ledger.go b/pkg/fabric-client/channel/ledger.go new file mode 100644 index 0000000000..9059ff1870 --- /dev/null +++ b/pkg/fabric-client/channel/ledger.go @@ -0,0 +1,418 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package channel + +import ( + "bytes" + "net/http" + "strconv" + "strings" + + "github.com/golang/protobuf/proto" + "github.com/pkg/errors" + + fab "github.com/hyperledger/fabric-sdk-go/api/apifabclient" + "github.com/hyperledger/fabric-sdk-go/pkg/fabric-client/txn" + "github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/protos/common" + pb "github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/protos/peer" +) + +const ( + systemChannel = "" +) + +// Ledger is a client that provides access to the underlying ledger of a channel. +type Ledger struct { + ctx fab.Context + chName string +} + +// NewLedger constructs a Ledger client for the current context and named channel. +func NewLedger(ctx fab.Context, chName string) *Ledger { + l := Ledger{ + ctx: ctx, + chName: chName, + } + return &l +} + +// QueryInfo queries for various useful information on the state of the channel +// (height, known peers). +func (c *Ledger) QueryInfo(targets []fab.ProposalProcessor) ([]*common.BlockchainInfo, error) { + logger.Debug("queryInfo - start") + + // prepare arguments to call qscc GetChainInfo function + var args [][]byte + args = append(args, []byte(c.chName)) + + request := fab.ChaincodeInvokeRequest{ + ChaincodeID: "qscc", + Fcn: "GetChainInfo", + Args: args, + } + tprs, err := queryChaincode(c.ctx, systemChannel, request, targets) + processed, err := processTxnProposalResponse(tprs, err, createBlockchainInfo) + + responses := []*common.BlockchainInfo{} + for _, p := range processed { + responses = append(responses, p.(*common.BlockchainInfo)) + } + return responses, err +} + +func createBlockchainInfo(tpr *fab.TransactionProposalResponse, err error) (interface{}, error) { + response := common.BlockchainInfo{} + if err != nil { + // response had an error - do not process. + return &response, err + } + + err = proto.Unmarshal(tpr.ProposalResponse.GetResponse().Payload, &response) + if err != nil { + return nil, errors.Wrap(err, "unmarshal of transaction proposal response failed") + } + return &response, err +} + +// QueryBlockByHash queries the ledger for Block by block hash. +// This query will be made to the primary peer. +// Returns the block. +func (c *Ledger) QueryBlockByHash(blockHash []byte, targets []fab.ProposalProcessor) ([]*common.Block, error) { + + if blockHash == nil { + return nil, errors.New("blockHash is required") + } + + // prepare arguments to call qscc GetBlockByNumber function + var args [][]byte + args = append(args, []byte(c.chName)) + args = append(args, blockHash[:len(blockHash)]) + + request := fab.ChaincodeInvokeRequest{ + ChaincodeID: "qscc", + Fcn: "GetBlockByHash", + Args: args, + } + tprs, err := queryChaincode(c.ctx, systemChannel, request, targets) + processed, err := processTxnProposalResponse(tprs, err, createCommonBlock) + + responses := []*common.Block{} + for _, p := range processed { + responses = append(responses, p.(*common.Block)) + } + return responses, err +} + +// QueryBlock queries the ledger for Block by block number. +// This query will be made to the primary peer. +// blockNumber: The number which is the ID of the Block. +// It returns the block. +func (c *Ledger) QueryBlock(blockNumber int, targets []fab.ProposalProcessor) ([]*common.Block, error) { + + if blockNumber < 0 { + return nil, errors.New("blockNumber must be a positive integer") + } + + // prepare arguments to call qscc GetBlockByNumber function + var args [][]byte + args = append(args, []byte(c.chName)) + args = append(args, []byte(strconv.Itoa(blockNumber))) + + request := fab.ChaincodeInvokeRequest{ + ChaincodeID: "qscc", + Fcn: "GetBlockByNumber", + Args: args, + } + + tprs, err := queryChaincode(c.ctx, systemChannel, request, targets) + processed, err := processTxnProposalResponse(tprs, err, createCommonBlock) + + responses := []*common.Block{} + for _, p := range processed { + responses = append(responses, p.(*common.Block)) + } + return responses, err +} + +func createCommonBlock(tpr *fab.TransactionProposalResponse, err error) (interface{}, error) { + response := common.Block{} + if err != nil { + // response had an error - do not process. + return &response, err + } + + err = proto.Unmarshal(tpr.ProposalResponse.GetResponse().Payload, &response) + if err != nil { + return nil, errors.Wrap(err, "unmarshal of transaction proposal response failed") + } + return &response, err +} + +// QueryTransaction queries the ledger for Transaction by number. +// This query will be made to the primary peer. +// Returns the ProcessedTransaction information containing the transaction. +// TODO: add optional target +func (c *Ledger) QueryTransaction(transactionID string, targets []fab.ProposalProcessor) ([]*pb.ProcessedTransaction, error) { + + // prepare arguments to call qscc GetTransactionByID function + var args [][]byte + args = append(args, []byte(c.chName)) + args = append(args, []byte(transactionID)) + + request := fab.ChaincodeInvokeRequest{ + ChaincodeID: "qscc", + Fcn: "GetTransactionByID", + Args: args, + } + + tprs, err := queryChaincode(c.ctx, systemChannel, request, targets) + processed, err := processTxnProposalResponse(tprs, err, createProcessedTransaction) + + responses := []*pb.ProcessedTransaction{} + for _, p := range processed { + responses = append(responses, p.(*pb.ProcessedTransaction)) + } + return responses, err +} + +func createProcessedTransaction(tpr *fab.TransactionProposalResponse, err error) (interface{}, error) { + response := pb.ProcessedTransaction{} + if err != nil { + // response had an error - do not process. + return &response, err + } + + err = proto.Unmarshal(tpr.ProposalResponse.GetResponse().Payload, &response) + if err != nil { + return nil, errors.Wrap(err, "unmarshal of transaction proposal response failed") + } + return &response, err +} + +// QueryInstantiatedChaincodes queries the instantiated chaincodes on this channel. +// This query will be made to the primary peer. +func (c *Ledger) QueryInstantiatedChaincodes(targets []fab.ProposalProcessor) ([]*pb.ChaincodeQueryResponse, error) { + request := fab.ChaincodeInvokeRequest{ + ChaincodeID: "lscc", + Fcn: "getchaincodes", + } + + tprs, err := queryChaincode(c.ctx, c.chName, request, targets) + processed, err := processTxnProposalResponse(tprs, err, createChaincodeQueryResponse) + + responses := []*pb.ChaincodeQueryResponse{} + for _, p := range processed { + responses = append(responses, p.(*pb.ChaincodeQueryResponse)) + } + return responses, err +} + +func createChaincodeQueryResponse(tpr *fab.TransactionProposalResponse, err error) (interface{}, error) { + response := pb.ChaincodeQueryResponse{} + if err != nil { + // response had an error - do not process. + return &response, err + } + + err = proto.Unmarshal(tpr.ProposalResponse.GetResponse().Payload, &response) + if err != nil { + return nil, errors.Wrap(err, "unmarshal of transaction proposal response failed") + } + return &response, err +} + +// QueryConfigBlock returns the current configuration block for the specified channel. If the +// peer doesn't belong to the channel, return error +func (c *Ledger) QueryConfigBlock(peers []fab.Peer, minResponses int) (*common.ConfigEnvelope, error) { + + if len(peers) == 0 { + return nil, errors.New("peer(s) required") + } + + if minResponses <= 0 { + return nil, errors.New("Minimum endorser has to be greater than zero") + } + + request := fab.ChaincodeInvokeRequest{ + ChaincodeID: "cscc", + Fcn: "GetConfigBlock", + Args: [][]byte{[]byte(c.chName)}, + } + tpr, err := queryChaincode(c.ctx, c.chName, request, peersToTxnProcessors(peers)) + if err != nil { + return nil, errors.WithMessage(err, "queryChaincode failed") + } + + responses, err := filterProposalResponses(tpr, err) + if err != nil { + return nil, err + } + + if len(responses) < minResponses { + return nil, errors.Errorf("Required minimum %d endorsments got %d", minResponses, len(responses)) + } + + r := responses[0] + for _, p := range responses { + if bytes.Compare(r, p) != 0 { + return nil, errors.New("Payloads for config block do not match") + } + } + + block := &common.Block{} + err = proto.Unmarshal(responses[0], block) + if err != nil { + return nil, errors.Wrap(err, "unmarshal block failed") + } + + if block.Data == nil || block.Data.Data == nil { + return nil, errors.New("config block data is nil") + } + + if len(block.Data.Data) != 1 { + return nil, errors.New("config block must contain one transaction") + } + + return createConfigEnvelope(block.Data.Data[0]) + +} + +type txnProposalResponseOp func(*fab.TransactionProposalResponse, error) (interface{}, error) + +func processTxnProposalResponse(tprs []*fab.TransactionProposalResponse, tperr error, op txnProposalResponseOp) ([]interface{}, error) { + + // examine errors from peers and prepare error slice that can be checked during each response' processing. + var errs MultiError + if tperr != nil { + var ok bool + errs, ok = tperr.(MultiError) + if !ok { + return nil, errors.WithMessage(tperr, "chaincode query failed") + } + } else { + errs = make([]error, len(tprs)) + } + + // process each response and set processing error, if needed. + responses := []interface{}{} + var resperrs MultiError + isErr := false + for i, tpr := range tprs { + var resp interface{} + var err error + + resp, err = op(tpr, errs[i]) + if err != nil { + isErr = true + } + + responses = append(responses, resp) + resperrs = append(resperrs, err) + } + + // when any error has occurred return responses and errors as a MultiError. + if isErr { + return responses, resperrs + } + return responses, nil +} + +func filterProposalResponses(tprs []*fab.TransactionProposalResponse, tperr error) ([][]byte, error) { + // examine errors from peers and prepare error slice that can be checked during each response' processing. + var errs MultiError + if tperr != nil { + var ok bool + errs, ok = tperr.(MultiError) + if !ok { + return nil, errors.WithMessage(tperr, "chaincode query failed") + } + } else { + errs = make(MultiError, len(tprs)) + } + + responses := [][]byte{} + errMsg := "" + for i, tpr := range tprs { + if errs[i] != nil { + errMsg = errMsg + errs[i].Error() + "\n" + } else { + responses = append(responses, tpr.ProposalResponse.GetResponse().Payload) + } + } + + if len(errMsg) > 0 { + return responses, errors.New(errMsg) + } + return responses, nil +} + +// MultiError represents a slice of errors originating from each target peer. +type MultiError []error + +func (me MultiError) Error() string { + msg := []string{} + for _, e := range me { + msg = append(msg, e.Error()) + } + return strings.Join(msg, ",") +} + +func queryChaincode(ctx fab.Context, channel string, request fab.ChaincodeInvokeRequest, targets []fab.ProposalProcessor) ([]*fab.TransactionProposalResponse, error) { + errors := MultiError{} + responses := []*fab.TransactionProposalResponse{} + isErr := false + + // TODO: this can be done concurrently. + for _, target := range targets { + resp, err := queryChaincodeWithTarget(ctx, channel, request, target) + + responses = append(responses, resp) + errors = append(errors, err) + + if err != nil { + isErr = true + } + } + if isErr { + return responses, errors + } + return responses, nil +} + +func queryChaincodeWithTarget(ctx fab.Context, channel string, request fab.ChaincodeInvokeRequest, target fab.ProposalProcessor) (*fab.TransactionProposalResponse, error) { + + targets := []fab.ProposalProcessor{target} + + tp, err := txn.NewProposal(ctx, channel, request) + if err != nil { + return nil, errors.WithMessage(err, "NewProposal failed") + } + + tpr, err := txn.SendProposal(tp, targets) + if err != nil { + return nil, errors.WithMessage(err, "SendProposal failed") + } + + err = validateResponse(tpr[0]) + if err != nil { + return nil, errors.WithMessage(err, "transaction proposal failed") + } + + return tpr[0], nil +} + +func validateResponse(response *fab.TransactionProposalResponse) error { + if response.Err != nil { + return errors.Errorf("error from %s (%s)", response.Endorser, response.Err.Error()) + } + + if response.Status != http.StatusOK { + return errors.Errorf("bad status from %s (%d)", response.Endorser, response.Status) + } + + return nil +} diff --git a/pkg/fabric-client/channel/query_test.go b/pkg/fabric-client/channel/ledger_test.go similarity index 51% rename from pkg/fabric-client/channel/query_test.go rename to pkg/fabric-client/channel/ledger_test.go index d50ce83d19..17b528e781 100644 --- a/pkg/fabric-client/channel/query_test.go +++ b/pkg/fabric-client/channel/ledger_test.go @@ -6,7 +6,6 @@ SPDX-License-Identifier: Apache-2.0 package channel import ( - "reflect" "testing" "github.com/golang/protobuf/proto" @@ -15,79 +14,32 @@ import ( ) func TestQueryMethods(t *testing.T) { - channel, _ := setupTestChannel() + channel, _ := setupTestLedger() + peer := mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil} - _, err := channel.QueryBlock(-1) + _, err := channel.QueryBlock(-1, []fab.ProposalProcessor{&peer}) if err == nil { t.Fatalf("Query block cannot be negative number") } - _, err = channel.QueryBlockByHash(nil) + _, err = channel.QueryBlockByHash(nil, []fab.ProposalProcessor{&peer}) if err == nil { t.Fatalf("Query hash cannot be nil") } - - badRequest1 := fab.ChaincodeInvokeRequest{ - Fcn: "method", - Args: [][]byte{[]byte("arg")}, - } - _, err = channel.QueryByChaincode(badRequest1) - if err == nil { - t.Fatalf("QueryByChannelcode: name cannot be empty") - } - - badRequest2 := fab.ChaincodeInvokeRequest{ - ChaincodeID: "qscc", - } - _, err = channel.QueryByChaincode(badRequest2) - if err == nil { - t.Fatalf("QueryByChannelcode: arguments cannot be empty") - } - - badRequest3 := fab.ChaincodeInvokeRequest{ - ChaincodeID: "qscc", - Fcn: "method", - Args: [][]byte{[]byte("arg")}, - } - _, err = channel.QueryByChaincode(badRequest3) - if err == nil { - t.Fatalf("QueryByChannelcode: targets cannot be empty") - } - -} - -func TestQueryOnSystemChannel(t *testing.T) { - channel, _ := setupChannel(systemChannel) - peer := mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil} - err := channel.AddPeer(&peer) - if err != nil { - t.Fatalf("Error adding peer to channel: %s", err) - } - - request := fab.ChaincodeInvokeRequest{ - ChaincodeID: "ccID", - Fcn: "method", - Args: [][]byte{[]byte("arg")}, - } - if _, err := channel.QueryByChaincode(request); err != nil { - t.Fatalf("Error invoking chaincode on system channel: %s", err) - } } func TestChannelQueryBlock(t *testing.T) { - channel, _ := setupTestChannel() - - peer := mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil} - err := channel.AddPeer(&peer) + channel, _ := setupTestLedger() + peer := mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil, Status: 200} - _, err = channel.QueryBlock(1) + _, err := channel.QueryBlock(1, []fab.ProposalProcessor{&peer}) if err != nil { - t.Fatal("Test channel query block failed,") + t.Fatalf("Test channel query block failed: %s", err) } - _, err = channel.QueryBlockByHash([]byte("")) + _, err = channel.QueryBlockByHash([]byte(""), []fab.ProposalProcessor{&peer}) if err != nil { t.Fatal("Test channel query block by hash failed,") @@ -96,12 +48,10 @@ func TestChannelQueryBlock(t *testing.T) { } func TestQueryInstantiatedChaincodes(t *testing.T) { - channel, _ := setupTestChannel() + channel, _ := setupTestLedger() + peer := mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil, Status: 200} - peer := mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil} - err := channel.AddPeer(&peer) - - res, err := channel.QueryInstantiatedChaincodes() + res, err := channel.QueryInstantiatedChaincodes([]fab.ProposalProcessor{&peer}) if err != nil || res == nil { t.Fatalf("Test QueryInstatiated chaincode failed: %v", err) @@ -110,12 +60,10 @@ func TestQueryInstantiatedChaincodes(t *testing.T) { } func TestQueryTransaction(t *testing.T) { - channel, _ := setupTestChannel() - - peer := mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil} - err := channel.AddPeer(&peer) + channel, _ := setupTestLedger() + peer := mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil, Status: 200} - res, err := channel.QueryTransaction("txid") + res, err := channel.QueryTransaction("txid", []fab.ProposalProcessor{&peer}) if err != nil || res == nil { t.Fatal("Test QueryTransaction failed") @@ -123,86 +71,18 @@ func TestQueryTransaction(t *testing.T) { } func TestQueryInfo(t *testing.T) { - channel, _ := setupTestChannel() + channel, _ := setupTestLedger() + peer := mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil, Status: 200} - peer := mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil} - err := channel.AddPeer(&peer) - - res, err := channel.QueryInfo() + res, err := channel.QueryInfo([]fab.ProposalProcessor{&peer}) if err != nil || res == nil { t.Fatalf("Test QueryInfo failed: %v", err) } } -func TestQueryMissingParams(t *testing.T) { - channel, _ := setupTestChannel() - - request := fab.ChaincodeInvokeRequest{ - ChaincodeID: "cc", - Fcn: "Hello", - } - _, err := channel.QueryByChaincode(request) - if err == nil { - t.Fatalf("Expected error") - } - _, err = queryByChaincode(channel.clientContext, "", request, request.Targets) - if err == nil { - t.Fatalf("Expected error") - } - - peer := mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil, Payload: []byte("A")} - channel.AddPeer(&peer) - - request = fab.ChaincodeInvokeRequest{ - Fcn: "Hello", - } - _, err = channel.QueryByChaincode(request) - if err == nil { - t.Fatalf("Expected error") - } - - request = fab.ChaincodeInvokeRequest{ - ChaincodeID: "cc", - } - _, err = channel.QueryByChaincode(request) - if err == nil { - t.Fatalf("Expected error") - } - - request = fab.ChaincodeInvokeRequest{ - ChaincodeID: "cc", - Fcn: "Hello", - } - _, err = channel.QueryByChaincode(request) - if err != nil { - t.Fatalf("Expected success") - } -} - -func TestQueryBySystemChaincode(t *testing.T) { - channel, _ := setupTestChannel() - - peer := mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil, Payload: []byte("A")} - channel.AddPeer(&peer) - - request := fab.ChaincodeInvokeRequest{ - ChaincodeID: "cc", - Fcn: "Hello", - } - resp, err := channel.QueryBySystemChaincode(request) - if err != nil { - t.Fatalf("Failed to query: %s", err) - } - expectedResp := []byte("A") - - if !reflect.DeepEqual(resp[0], expectedResp) { - t.Fatalf("Unexpected transaction proposal response: %v", resp) - } -} - func TestQueryConfig(t *testing.T) { - channel, _ := setupTestChannel() + channel, _ := setupTestLedger() // empty targets _, err := channel.QueryConfigBlock([]fab.Peer{}, 1) @@ -243,7 +123,7 @@ func TestQueryConfig(t *testing.T) { } // peer with valid config block payload - peer := mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil, Payload: payload} + peer := mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil, Payload: payload, Status: 200} // fail with min endorsers res, err := channel.QueryConfigBlock([]fab.Peer{&peer}, 2) @@ -258,7 +138,7 @@ func TestQueryConfig(t *testing.T) { } // create second endorser with same payload - peer2 := mocks.MockPeer{MockName: "Peer2", MockURL: "http://peer2.com", MockRoles: []string{}, MockCert: nil, Payload: payload} + peer2 := mocks.MockPeer{MockName: "Peer2", MockURL: "http://peer2.com", MockRoles: []string{}, MockCert: nil, Payload: payload, Status: 200} // success with two endorsers res, err = channel.QueryConfigBlock([]fab.Peer{&peer, &peer2}, 2) @@ -293,3 +173,13 @@ func TestQueryConfig(t *testing.T) { } } + +func setupTestLedger() (*Ledger, error) { + return setupLedger("testChannel") +} + +func setupLedger(channelID string) (*Ledger, error) { + user := mocks.NewMockUser("test") + ctx := mocks.NewMockContext(user) + return NewLedger(ctx, channelID), nil +} diff --git a/pkg/fabric-client/channel/query.go b/pkg/fabric-client/channel/query.go deleted file mode 100644 index 6b76d803cf..0000000000 --- a/pkg/fabric-client/channel/query.go +++ /dev/null @@ -1,302 +0,0 @@ -/* -Copyright SecureKey Technologies Inc. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package channel - -import ( - "bytes" - "strconv" - - "github.com/golang/protobuf/proto" - "github.com/pkg/errors" - - fab "github.com/hyperledger/fabric-sdk-go/api/apifabclient" - "github.com/hyperledger/fabric-sdk-go/pkg/fabric-client/txn" - "github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/protos/common" - pb "github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/protos/peer" -) - -const ( - systemChannel = "" -) - -// QueryInfo queries for various useful information on the state of the channel -// (height, known peers). -// This query will be made to the primary peer. -func (c *Channel) QueryInfo() (*common.BlockchainInfo, error) { - logger.Debug("queryInfo - start") - - // prepare arguments to call qscc GetChainInfo function - var args [][]byte - args = append(args, []byte(c.Name())) - - payload, err := c.queryBySystemChaincodeByTarget("qscc", "GetChainInfo", args, c.PrimaryPeer()) - if err != nil { - return nil, errors.WithMessage(err, "qscc.GetChainInfo failed") - } - - bci := &common.BlockchainInfo{} - err = proto.Unmarshal(payload, bci) - if err != nil { - return nil, errors.Wrap(err, "unmarshal of BlockchainInfo failed") - } - - return bci, nil -} - -// QueryBlockByHash queries the ledger for Block by block hash. -// This query will be made to the primary peer. -// Returns the block. -func (c *Channel) QueryBlockByHash(blockHash []byte) (*common.Block, error) { - - if blockHash == nil { - return nil, errors.New("blockHash is required") - } - - // prepare arguments to call qscc GetBlockByNumber function - var args [][]byte - args = append(args, []byte(c.Name())) - args = append(args, blockHash[:len(blockHash)]) - - payload, err := c.queryBySystemChaincodeByTarget("qscc", "GetBlockByHash", args, c.PrimaryPeer()) - if err != nil { - return nil, errors.WithMessage(err, "qscc.GetBlockByHash failed") - } - - block := &common.Block{} - err = proto.Unmarshal(payload, block) - if err != nil { - return nil, errors.Wrap(err, "unmarshal of BlockchainInfo failed") - } - - return block, nil -} - -// QueryBlock queries the ledger for Block by block number. -// This query will be made to the primary peer. -// blockNumber: The number which is the ID of the Block. -// It returns the block. -func (c *Channel) QueryBlock(blockNumber int) (*common.Block, error) { - - if blockNumber < 0 { - return nil, errors.New("blockNumber must be a positive integer") - } - - // prepare arguments to call qscc GetBlockByNumber function - var args [][]byte - args = append(args, []byte(c.Name())) - args = append(args, []byte(strconv.Itoa(blockNumber))) - - payload, err := c.queryBySystemChaincodeByTarget("qscc", "GetBlockByNumber", args, c.PrimaryPeer()) - if err != nil { - return nil, errors.WithMessage(err, "qscc.GetBlockByNumber failed") - } - - block := &common.Block{} - err = proto.Unmarshal(payload, block) - if err != nil { - return nil, errors.Wrap(err, "unmarshal of BlockchainInfo failed") - } - - return block, nil -} - -// QueryTransaction queries the ledger for Transaction by number. -// This query will be made to the primary peer. -// Returns the ProcessedTransaction information containing the transaction. -// TODO: add optional target -func (c *Channel) QueryTransaction(transactionID string) (*pb.ProcessedTransaction, error) { - - // prepare arguments to call qscc GetTransactionByID function - var args [][]byte - args = append(args, []byte(c.Name())) - args = append(args, []byte(transactionID)) - - payload, err := c.queryBySystemChaincodeByTarget("qscc", "GetTransactionByID", args, c.PrimaryPeer()) - if err != nil { - return nil, errors.WithMessage(err, "qscc.GetTransactionByID failed") - } - - transaction := new(pb.ProcessedTransaction) - err = proto.Unmarshal(payload, transaction) - if err != nil { - return nil, errors.Wrap(err, "unmarshal of ProcessedTransaction failed") - } - - return transaction, nil -} - -// QueryInstantiatedChaincodes queries the instantiated chaincodes on this channel. -// This query will be made to the primary peer. -func (c *Channel) QueryInstantiatedChaincodes() (*pb.ChaincodeQueryResponse, error) { - - targets := []fab.ProposalProcessor{c.PrimaryPeer()} - request := fab.ChaincodeInvokeRequest{ - ChaincodeID: "lscc", - Fcn: "getchaincodes", - } - - payload, err := queryByChaincode(c.clientContext, c.name, request, targets) - if err != nil { - return nil, errors.WithMessage(err, "lscc.getchaincodes failed") - } - - response := new(pb.ChaincodeQueryResponse) - err = proto.Unmarshal(payload[0], response) - if err != nil { - return nil, errors.Wrap(err, "unmarshal of ChaincodeQueryResponse failed") - } - - return response, nil -} - -// QueryByChaincode sends a proposal to one or more endorsing peers that will be handled by the chaincode. -// This request will be presented to the chaincode 'invoke' and must understand -// from the arguments that this is a query request. The chaincode must also return -// results in the byte array format and the caller will have to be able to decode. -// these results. -func (c *Channel) QueryByChaincode(request fab.ChaincodeInvokeRequest) ([][]byte, error) { - targets, err := c.chaincodeInvokeRequestAddDefaultPeers(request.Targets) - if err != nil { - return nil, err - } - return queryByChaincode(c.clientContext, c.name, request, targets) -} - -func filterProposalResponses(tpr []*fab.TransactionProposalResponse) ([][]byte, error) { - var responses [][]byte - errMsg := "" - for _, response := range tpr { - if response.Err != nil { - errMsg = errMsg + response.Err.Error() + "\n" - } else { - responses = append(responses, response.ProposalResponse.GetResponse().Payload) - } - } - - if len(errMsg) > 0 { - return responses, errors.New(errMsg) - } - return responses, nil -} - -func queryByChaincode(clientContext fab.Context, channelID string, request fab.ChaincodeInvokeRequest, targets []fab.ProposalProcessor) ([][]byte, error) { - if err := validateChaincodeInvokeRequest(request); err != nil { - return nil, err - } - - tp, err := txn.NewProposal(clientContext, channelID, request) - if err != nil { - return nil, errors.WithMessage(err, "NewProposal failed") - } - - tpr, err := txn.SendProposal(tp, targets) - if err != nil { - return nil, errors.WithMessage(err, "SendProposal failed") - } - - return filterProposalResponses(tpr) -} - -// queryBySystemChaincodeByTarget is an internal helper function that queries system chaincode. -// This function is not exported to keep the external interface of this package to only expose -// request structs. -func (c *Channel) queryBySystemChaincodeByTarget(chaincodeID string, fcn string, args [][]byte, target fab.ProposalProcessor) ([]byte, error) { - targets := []fab.ProposalProcessor{target} - request := fab.ChaincodeInvokeRequest{ - ChaincodeID: chaincodeID, - Fcn: fcn, - Args: args, - Targets: targets, - } - responses, err := c.QueryBySystemChaincode(request) - - // we are only querying one peer hence one result - if err != nil || len(responses) != 1 { - return nil, errors.Errorf("QueryBySystemChaincode should have one result only, actual result is %d", len(responses)) - } - - return responses[0], nil -} - -// QueryBySystemChaincode invokes a chaincode that isn't part of a channel. -// -// TODO: This function's name is confusing - call the normal QueryByChaincode for system chaincode on a channel. -func (c *Channel) QueryBySystemChaincode(request fab.ChaincodeInvokeRequest) ([][]byte, error) { - targets, err := c.chaincodeInvokeRequestAddDefaultPeers(request.Targets) - if err != nil { - return nil, err - } - return queryByChaincode(c.clientContext, systemChannel, request, targets) -} - -// QueryBySystemChaincode invokes a system chaincode -// TODO - should be moved. -func QueryBySystemChaincode(request fab.ChaincodeInvokeRequest, clientContext fab.Context) ([][]byte, error) { - return queryByChaincode(clientContext, systemChannel, request, request.Targets) -} - -// QueryConfigBlock returns the current configuration block for the specified channel. If the -// peer doesn't belong to the channel, return error -func (c *Channel) QueryConfigBlock(peers []fab.Peer, minResponses int) (*common.ConfigEnvelope, error) { - - if len(peers) == 0 { - return nil, errors.New("peer(s) required") - } - - if minResponses <= 0 { - return nil, errors.New("Minimum endorser has to be greater than zero") - } - - request := fab.ChaincodeInvokeRequest{ - ChaincodeID: "cscc", - Fcn: "GetConfigBlock", - Args: [][]byte{[]byte(c.Name())}, - } - - tp, err := txn.NewProposal(c.clientContext, c.Name(), request) - if err != nil { - return nil, errors.WithMessage(err, "NewProposal failed") - } - - tpr, err := txn.SendProposal(tp, peersToTxnProcessors(peers)) - if err != nil { - return nil, errors.WithMessage(err, "SendProposal failed") - } - - responses, err := filterProposalResponses(tpr) - if err != nil { - return nil, err - } - - if len(responses) < minResponses { - return nil, errors.Errorf("Required minimum %d endorsments got %d", minResponses, len(responses)) - } - - r := responses[0] - for _, p := range responses { - if bytes.Compare(r, p) != 0 { - return nil, errors.New("Payloads for config block do not match") - } - } - - block := &common.Block{} - err = proto.Unmarshal(responses[0], block) - if err != nil { - return nil, errors.Wrap(err, "unmarshal block failed") - } - - if block.Data == nil || block.Data.Data == nil { - return nil, errors.New("config block data is nil") - } - - if len(block.Data.Data) != 1 { - return nil, errors.New("config block must contain one transaction") - } - - return createConfigEnvelope(block.Data.Data[0]) - -} diff --git a/pkg/fabric-client/chconfig/chconfig_test.go b/pkg/fabric-client/chconfig/chconfig_test.go index f6f73133f6..0fe23afbba 100644 --- a/pkg/fabric-client/chconfig/chconfig_test.go +++ b/pkg/fabric-client/chconfig/chconfig_test.go @@ -105,7 +105,7 @@ func getPeerWithConfigBlockPayload(t *testing.T) fab.Peer { } // peer with valid config block payload - peer := &mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil, Payload: payload} + peer := &mocks.MockPeer{MockName: "Peer1", MockURL: "http://peer1.com", MockRoles: []string{}, MockCert: nil, Payload: payload, Status: 200} return peer } diff --git a/pkg/fabsdk/deprecated.go b/pkg/fabsdk/deprecated.go index c39de4adf0..7b6a32f36e 100644 --- a/pkg/fabsdk/deprecated.go +++ b/pkg/fabsdk/deprecated.go @@ -138,5 +138,5 @@ func (sdk *FabricSDK) newSessionFromIdentityName(orgID string, id string) (*sess // // Deprecated: the system client is being replaced with the interfaces supplied by NewClient() func (sdk *FabricSDK) NewSystemClient(s apisdk.SessionContext) (apifabclient.Resource, error) { - return sdk.FabricProvider().CreateResourceClient(s) + return sdk.fabricProvider.CreateResourceClient(s) } diff --git a/test/integration/fab/channel_queries_test.go b/test/integration/fab/channel_queries_test.go index 64d9c04b3e..6e81a3b5eb 100644 --- a/test/integration/fab/channel_queries_test.go +++ b/test/integration/fab/channel_queries_test.go @@ -321,7 +321,7 @@ func testQueryByChaincode(t *testing.T, channel fab.Channel, testSetup *integrat // Verify that valid targets returned response if len(queryResponses) != len(targets) { - t.Fatalf("QueryByChaincode number of results mismatch. Expected: %d Got: %d", len(targets), len(queryResponses)) + t.Fatalf("QueryByChaincode number of results mismatch. Expected: %d Got: %d (and error %v)", len(targets), len(queryResponses), err) } channel.RemovePeer(firstInvalidTarget)