Skip to content

Commit

Permalink
FAB-13687 Add ledger shim for SimpleQueryExecutor
Browse files Browse the repository at this point in the history
This is yet another ledger shim to allow uniform access to state for the
purposes of serialization/deserialization.

Change-Id: If43b8fbe4afaec9fb744df52b7667da7f34a10d9
Signed-off-by: Jason Yellick <jyellick@us.ibm.com>
  • Loading branch information
Jason Yellick committed Feb 6, 2019
1 parent 57e72dc commit c287775
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 5 deletions.
74 changes: 69 additions & 5 deletions core/chaincode/lifecycle/ledger_shim.go
Expand Up @@ -7,24 +7,34 @@ SPDX-License-Identifier: Apache-2.0
package lifecycle

import (
commonledger "github.com/hyperledger/fabric/common/ledger"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/core/ledger"
"github.com/hyperledger/fabric/protos/ledger/queryresult"

"github.com/pkg/errors"
)

type StateIterator interface {
Close() error
Next() (*queryresult.KV, error)
}

// StateIteratorToMap takes an iterator, and iterates over the entire thing, encoding the KVs
// into a map, and then closes it.
func StateIteratorToMap(itr shim.StateQueryIteratorInterface) (map[string][]byte, error) {
func StateIteratorToMap(itr StateIterator) (map[string][]byte, error) {
defer itr.Close()
result := map[string][]byte{}
for itr.HasNext() {
for {
entry, err := itr.Next()
if err != nil {
return nil, errors.WithMessage(err, "could not iterate over range")
}
if entry == nil {
return result, nil
}
result[entry.Key] = entry.Value
}
return result, nil
}

// ChaincodePublicLedgerShim decorates the chaincode shim to support the state interfaces
Expand All @@ -40,7 +50,22 @@ func (cls *ChaincodePublicLedgerShim) GetStateRange(prefix string) (map[string][
if err != nil {
return nil, errors.WithMessage(err, "could not get state iterator")
}
return StateIteratorToMap(itr)
return StateIteratorToMap(&ChaincodeResultIteratorShim{ResultsIterator: itr})
}

type ChaincodeResultIteratorShim struct {
ResultsIterator shim.StateQueryIteratorInterface
}

func (cris *ChaincodeResultIteratorShim) Next() (*queryresult.KV, error) {
if !cris.ResultsIterator.HasNext() {
return nil, nil
}
return cris.ResultsIterator.Next()
}

func (cris *ChaincodeResultIteratorShim) Close() error {
return cris.ResultsIterator.Close()
}

// ChaincodePrivateLedgerShim wraps the chaincode shim to make access to keys in a collection
Expand All @@ -57,7 +82,7 @@ func (cls *ChaincodePrivateLedgerShim) GetStateRange(prefix string) (map[string]
if err != nil {
return nil, errors.WithMessage(err, "could not get state iterator")
}
return StateIteratorToMap(itr)
return StateIteratorToMap(&ChaincodeResultIteratorShim{ResultsIterator: itr})
}

// GetState returns the value for the key in the configured collection.
Expand All @@ -79,3 +104,42 @@ func (cls *ChaincodePrivateLedgerShim) PutState(key string, value []byte) error
func (cls *ChaincodePrivateLedgerShim) DelState(key string) error {
return cls.Stub.DelPrivateData(cls.Collection, key)
}

// SimpleQueryExecutorShim implements the ReadableState and RangeableState interfaces
// based on an underlying ledger.SimpleQueryExecutor
type SimpleQueryExecutorShim struct {
Namespace string
SimpleQueryExecutor ledger.SimpleQueryExecutor
}

func (sqes *SimpleQueryExecutorShim) GetState(key string) ([]byte, error) {
return sqes.SimpleQueryExecutor.GetState(sqes.Namespace, key)
}

func (sqes *SimpleQueryExecutorShim) GetStateRange(prefix string) (map[string][]byte, error) {
itr, err := sqes.SimpleQueryExecutor.GetStateRangeScanIterator(sqes.Namespace, prefix, prefix+"\x7f")
if err != nil {
return nil, errors.WithMessage(err, "could not get state iterator")
}
return StateIteratorToMap(&ResultsIteratorShim{ResultsIterator: itr})
}

type ResultsIteratorShim struct {
ResultsIterator commonledger.ResultsIterator
}

func (ris *ResultsIteratorShim) Next() (*queryresult.KV, error) {
res, err := ris.ResultsIterator.Next()
if err != nil {
return nil, err
}
if res == nil {
return nil, nil
}
return res.(*queryresult.KV), err
}

func (ris *ResultsIteratorShim) Close() error {
ris.ResultsIterator.Close()
return nil
}
78 changes: 78 additions & 0 deletions core/chaincode/lifecycle/ledger_shim_test.go
Expand Up @@ -191,4 +191,82 @@ var _ = Describe("LedgerShims", func() {
})
})
})

Describe("SimpleQueryExecutorShim", func() {
var (
sqes *lifecycle.SimpleQueryExecutorShim
fakeSimpleQueryExecutor *mock.SimpleQueryExecutor
)

BeforeEach(func() {
fakeSimpleQueryExecutor = &mock.SimpleQueryExecutor{}
sqes = &lifecycle.SimpleQueryExecutorShim{
Namespace: "cc-namespace",
SimpleQueryExecutor: fakeSimpleQueryExecutor,
}
})

Describe("GetState", func() {
BeforeEach(func() {
fakeSimpleQueryExecutor.GetStateReturns([]byte("fake-state"), fmt.Errorf("fake-error"))
})

It("passes through to the query executor", func() {
res, err := sqes.GetState("fake-prefix")
Expect(res).To(Equal([]byte("fake-state")))
Expect(err).To(MatchError("fake-error"))
})
})

Describe("GetStateRange", func() {
var (
resItr *mock.ResultsIterator
)

BeforeEach(func() {
resItr = &mock.ResultsIterator{}
resItr.NextReturnsOnCall(0, &queryresult.KV{
Key: "fake-key",
Value: []byte("key-value"),
}, nil)
fakeSimpleQueryExecutor.GetStateRangeScanIteratorReturns(resItr, nil)
})

It("passes through to the query executor", func() {
res, err := sqes.GetStateRange("fake-key")
Expect(err).NotTo(HaveOccurred())
Expect(res).To(Equal(map[string][]byte{
"fake-key": []byte("key-value"),
}))

Expect(fakeSimpleQueryExecutor.GetStateRangeScanIteratorCallCount()).To(Equal(1))
namespace, start, end := fakeSimpleQueryExecutor.GetStateRangeScanIteratorArgsForCall(0)
Expect(namespace).To(Equal("cc-namespace"))
Expect(start).To(Equal("fake-key"))
Expect(end).To(Equal("fake-key\x7f"))
})

Context("when the result iterator returns an error", func() {
BeforeEach(func() {
resItr.NextReturns(nil, fmt.Errorf("fake-error"))
})

It("returns the error", func() {
_, err := sqes.GetStateRange("fake-key")
Expect(err).To(MatchError("could not iterate over range: fake-error"))
})
})

Context("when getting the state iterator fails", func() {
BeforeEach(func() {
fakeSimpleQueryExecutor.GetStateRangeScanIteratorReturns(nil, fmt.Errorf("fake-range-error"))
})

It("wraps and returns the error", func() {
_, err := sqes.GetStateRange("fake-key")
Expect(err).To(MatchError("could not get state iterator: fake-range-error"))
})
})
})
})
})
6 changes: 6 additions & 0 deletions core/chaincode/lifecycle/lifecycle_suite_test.go
Expand Up @@ -10,6 +10,7 @@ import (
"testing"

"github.com/hyperledger/fabric/common/channelconfig"
commonledger "github.com/hyperledger/fabric/common/ledger"
"github.com/hyperledger/fabric/core/chaincode"
"github.com/hyperledger/fabric/core/chaincode/lifecycle"
"github.com/hyperledger/fabric/core/chaincode/shim"
Expand Down Expand Up @@ -55,6 +56,11 @@ type simpleQueryExecutor interface {
ledger.SimpleQueryExecutor
}

//go:generate counterfeiter -o mock/results_iterator.go --fake-name ResultsIterator . resultsIterator
type resultsIterator interface {
commonledger.ResultsIterator
}

//go:generate counterfeiter -o mock/chaincode_lifecycle.go --fake-name ChaincodeLifecycle . chaincodeLifecycle
type chaincodeLifecycle interface {
chaincode.Lifecycle
Expand Down
112 changes: 112 additions & 0 deletions core/chaincode/lifecycle/mock/results_iterator.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit c287775

Please sign in to comment.