diff --git a/shim/handler.go b/shim/handler.go index 9c5fea7..346a15a 100644 --- a/shim/handler.go +++ b/shim/handler.go @@ -19,7 +19,8 @@ const ( established state = "established" // connection established ready state = "ready" // ready for requests - defaultMaxSizeWriteBatch = 100 + defaultMaxSizeWriteBatch = 100 + defaultMaxSizeGetMultipleKeys = 100 ) // PeerChaincodeStream is the common stream interface for Peer - chaincode communication. @@ -51,6 +52,9 @@ type Handler struct { // if you can send the changes in batches. usePeerWriteBatch bool maxSizeWriteBatch uint32 + // if you can get the multiple keys in batches. + usePeerGetMultipleKeys bool + maxSizeGetMultipleKeys uint32 // Multiple queries (and one transaction) with different txids can be executing in parallel for this chaincode // responseChannels is the channel on which responses are communicated by the shim to the chaincodeStub. @@ -267,6 +271,80 @@ func (h *Handler) handleGetState(collection string, key string, channelID string return nil, fmt.Errorf("[%s] incorrect chaincode message %s received. Expecting %s or %s", shorttxid(responseMsg.Txid), responseMsg.Type, peer.ChaincodeMessage_RESPONSE, peer.ChaincodeMessage_ERROR) } +// handleGetMultipleStates communicates with the peer to fetch the requested state information from the ledger. +func (h *Handler) handleGetMultipleStates(collection string, keys []string, channelID string, txID string) ([][]byte, error) { + if len(keys) == 0 { + return nil, nil + } + + responses := make([][]byte, 0, len(keys)) + + if !h.usePeerGetMultipleKeys { + for _, key := range keys { + resp, err := h.handleGetState(collection, key, channelID, txID) + if err != nil { + return nil, err + } + responses = append(responses, resp) + } + return responses, nil + } + + for ; len(keys) > int(h.maxSizeGetMultipleKeys); keys = keys[h.maxSizeGetMultipleKeys:] { + resp, err := h.handleOneSendGetMultipleStates(collection, keys[:h.maxSizeGetMultipleKeys], channelID, txID) + if err != nil { + return nil, err + } + responses = append(responses, resp...) + } + + if len(keys) > 0 { + resp, err := h.handleOneSendGetMultipleStates(collection, keys, channelID, txID) + if err != nil { + return nil, err + } + responses = append(responses, resp...) + } + + for i := range responses { + if len(responses[i]) == 0 { + responses[i] = nil + } + } + + return responses, nil +} + +// handleOneSendGetMultipleStates communicates with the peer to fetch one batch of keys from the ledger. +func (h *Handler) handleOneSendGetMultipleStates(collection string, keys []string, channelID string, txID string) ([][]byte, error) { + // Construct payload for GET_STATE_MULTIPLE + payloadBytes := marshalOrPanic(&peer.GetStateMultiple{Keys: keys, Collection: collection}) + + msg := &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_GET_STATE_MULTIPLE, Payload: payloadBytes, Txid: txID, ChannelId: channelID} + responseMsg, err := h.callPeerWithChaincodeMsg(msg, channelID, txID) + if err != nil { + return nil, fmt.Errorf("[%s] error sending %s: %s", shorttxid(txID), peer.ChaincodeMessage_GET_STATE_MULTIPLE, err) + } + + if responseMsg.Type == peer.ChaincodeMessage_RESPONSE { + // Success response + var gmkResult peer.GetStateMultipleResult + err = proto.Unmarshal(responseMsg.Payload, &gmkResult) + if err != nil { + return nil, errors.New("could not unmarshal get state multiple keys response") + } + + return gmkResult.GetValues(), nil + } + if responseMsg.Type == peer.ChaincodeMessage_ERROR { + // Error response + return nil, fmt.Errorf("%s", responseMsg.Payload[:]) + } + + // Incorrect chaincode message received + return nil, fmt.Errorf("[%s] incorrect chaincode message %s received. Expecting %s or %s", shorttxid(responseMsg.Txid), responseMsg.Type, peer.ChaincodeMessage_RESPONSE, peer.ChaincodeMessage_ERROR) +} + func (h *Handler) handleGetPrivateDataHash(collection string, key string, channelID string, txid string) ([]byte, error) { // Construct payload for GET_PRIVATE_DATA_HASH payloadBytes := marshalOrPanic(&peer.GetState{Collection: collection, Key: key}) @@ -733,6 +811,13 @@ func (h *Handler) handleEstablished(msg *peer.ChaincodeMessage) error { h.maxSizeWriteBatch = defaultMaxSizeWriteBatch } + h.usePeerGetMultipleKeys = ccAdditionalParams.UseGetMultipleKeys + h.maxSizeGetMultipleKeys = ccAdditionalParams.MaxSizeGetMultipleKeys + + if h.usePeerGetMultipleKeys && h.maxSizeGetMultipleKeys < defaultMaxSizeGetMultipleKeys { + h.maxSizeGetMultipleKeys = defaultMaxSizeGetMultipleKeys + } + return nil } diff --git a/shim/interfaces.go b/shim/interfaces.go index 27f9ba1..66993fb 100644 --- a/shim/interfaces.go +++ b/shim/interfaces.go @@ -80,6 +80,15 @@ type ChaincodeStubInterface interface { // If the key does not exist in the state database, (nil, nil) is returned. GetState(key string) ([]byte, error) + // GetMultipleStates retrieves the values of the specified keys from the ledger. + // It has similar semantics to the GetState function regarding read-your-own-writes behavior, + // meaning that updates made by a previous PutState operation within the same chaincode + // session are not visible to this function. + // If no key is passed, the function will return (nil, nil). + // GetMultipleStates returns values in the same order in which the keys are provided, + // and any keys missing from the ledger return a nil. + GetMultipleStates(keys ...string) ([][]byte, error) + // PutState puts the specified `key` and `value` into the transaction's // writeset as a data-write proposal. PutState doesn't effect the ledger // until the transaction is validated and successfully committed. @@ -247,6 +256,15 @@ type ChaincodeStubInterface interface { // that has not been committed. GetPrivateData(collection, key string) ([]byte, error) + // GetMultiplePrivateData retrieves the values of the specified keys from the specified collection. + // It has similar semantics to the GetePrivateData function regarding read-your-own-writes behavior, + // meaning that updates made by a previous PutPrivateData operation within the same chaincode + // session are not visible to this function. + // If no key is passed, the function will return (nil, nil). + // GetMultiplePrivateData returns values in the same order in which the keys are provided, + // and any keys missing from the ledger return a nil. + GetMultiplePrivateData(collection string, keys ...string) ([][]byte, error) + // GetPrivateDataHash returns the hash of the value of the specified `key` from the specified // `collection` GetPrivateDataHash(collection, key string) ([]byte, error) diff --git a/shim/stub.go b/shim/stub.go index 6655d14..2553f24 100644 --- a/shim/stub.go +++ b/shim/stub.go @@ -166,6 +166,13 @@ func (s *ChaincodeStub) GetState(key string) ([]byte, error) { return s.handler.handleGetState(collection, key, s.ChannelID, s.TxID) } +// GetMultipleStates documentation can be found in interfaces.go +func (s *ChaincodeStub) GetMultipleStates(keys ...string) ([][]byte, error) { + // Access public data by setting the collection to empty string + collection := "" + return s.handler.handleGetMultipleStates(collection, keys, s.ChannelID, s.TxID) +} + // SetStateValidationParameter documentation can be found in interfaces.go func (s *ChaincodeStub) SetStateValidationParameter(key string, ep []byte) error { return s.putStateMetadataEntry("", key, s.validationParameterMetakey, ep) @@ -262,6 +269,14 @@ func (s *ChaincodeStub) GetPrivateData(collection string, key string) ([]byte, e return s.handler.handleGetState(collection, key, s.ChannelID, s.TxID) } +// GetMultiplePrivateData documentation can be found in interfaces.go +func (s *ChaincodeStub) GetMultiplePrivateData(collection string, keys ...string) ([][]byte, error) { + if collection == "" { + return nil, fmt.Errorf("collection must not be an empty string") + } + return s.handler.handleGetMultipleStates(collection, keys, s.ChannelID, s.TxID) +} + // GetPrivateDataHash documentation can be found in interfaces.go func (s *ChaincodeStub) GetPrivateDataHash(collection string, key string) ([]byte, error) { if collection == "" { diff --git a/shim/stub_test.go b/shim/stub_test.go index ffe251e..8ab02bc 100644 --- a/shim/stub_test.go +++ b/shim/stub_test.go @@ -252,12 +252,19 @@ func TestGetMSPID(t *testing.T) { } func TestChaincodeStubHandlers(t *testing.T) { + gmkResult := &peer.GetStateMultipleResult{ + Values: [][]byte{[]byte("myvalue"), []byte("myvalue")}, + } + getMultipleKeysBytes, err := proto.Marshal(gmkResult) + assert.NoError(t, err) + var tests = []struct { - name string - resType peer.ChaincodeMessage_Type - payload []byte - usePeerWriteBatch bool - testFunc func(*ChaincodeStub, *Handler, *testing.T, []byte) + name string + resType peer.ChaincodeMessage_Type + payload []byte + usePeerWriteBatch bool + usePeerGetMultipleKeys bool + testFunc func(*ChaincodeStub, *Handler, *testing.T, []byte) }{ { name: "Simple Response", @@ -730,6 +737,86 @@ func TestChaincodeStubHandlers(t *testing.T) { checkWriteBatch(t, s.handler.chatStream, 1, 2) }, }, + { + name: "Get Multiple Keys - peer new", + resType: peer.ChaincodeMessage_RESPONSE, + payload: getMultipleKeysBytes, + usePeerGetMultipleKeys: true, + testFunc: func(s *ChaincodeStub, h *Handler, t *testing.T, payload []byte) { + resp, err := s.GetMultipleStates("key", "key2", "key3", "key4") + assert.NoError(t, err) + assert.Len(t, resp, 4) + for _, r := range resp { + assert.Equal(t, []byte("myvalue"), r) + } + + resp, err = s.GetMultiplePrivateData("col", "key", "key2", "key3", "key4") + assert.NoError(t, err) + assert.Len(t, resp, 4) + for _, r := range resp { + assert.Equal(t, []byte("myvalue"), r) + } + }, + }, + { + name: "Get Multiple Keys - peer old", + resType: peer.ChaincodeMessage_RESPONSE, + payload: []byte("myvalue"), + testFunc: func(s *ChaincodeStub, h *Handler, t *testing.T, payload []byte) { + resp, err := s.GetMultipleStates("key", "key2", "key3", "key4") + assert.NoError(t, err) + assert.Len(t, resp, 4) + for _, r := range resp { + assert.Equal(t, payload, r) + } + + resp, err = s.GetMultiplePrivateData("col", "key", "key2", "key3", "key4") + assert.NoError(t, err) + assert.Len(t, resp, 4) + for _, r := range resp { + assert.Equal(t, payload, r) + } + }, + }, + { + name: "Get Multiple Keys error - peer new", + resType: peer.ChaincodeMessage_ERROR, + payload: []byte("error"), + usePeerGetMultipleKeys: true, + testFunc: func(s *ChaincodeStub, h *Handler, t *testing.T, payload []byte) { + _, err := s.GetMultipleStates("key", "key2") + assert.EqualError(t, err, string(payload)) + + _, err = s.GetMultiplePrivateData("col", "key", "key2") + assert.EqualError(t, err, string(payload)) + }, + }, + { + name: "Get Multiple Keys error - peer old", + resType: peer.ChaincodeMessage_ERROR, + payload: []byte("error"), + testFunc: func(s *ChaincodeStub, h *Handler, t *testing.T, payload []byte) { + _, err := s.GetMultipleStates("key", "key2") + assert.EqualError(t, err, string(payload)) + + _, err = s.GetMultiplePrivateData("col", "key", "key2") + assert.EqualError(t, err, string(payload)) + }, + }, + { + name: "Get Multiple Keys - without keys", + resType: peer.ChaincodeMessage_RESPONSE, + payload: []byte("myvalue"), + testFunc: func(s *ChaincodeStub, h *Handler, t *testing.T, payload []byte) { + resp, err := s.GetMultipleStates() + assert.NoError(t, err) + assert.Len(t, resp, 0) + + resp, err = s.GetMultiplePrivateData("col") + assert.NoError(t, err) + assert.Len(t, resp, 0) + }, + }, } for _, test := range tests { @@ -738,11 +825,13 @@ func TestChaincodeStubHandlers(t *testing.T) { t.Parallel() handler := &Handler{ - cc: &mockChaincode{}, - responseChannels: map[string]chan *peer.ChaincodeMessage{}, - state: ready, - usePeerWriteBatch: test.usePeerWriteBatch, - maxSizeWriteBatch: 100, + cc: &mockChaincode{}, + responseChannels: map[string]chan *peer.ChaincodeMessage{}, + state: ready, + usePeerWriteBatch: test.usePeerWriteBatch, + maxSizeWriteBatch: 100, + usePeerGetMultipleKeys: test.usePeerGetMultipleKeys, + maxSizeGetMultipleKeys: 2, } stub := &ChaincodeStub{ ChannelID: "channel",