From c287775232c93bdae69d7cfb7ba84ccb6fe86db2 Mon Sep 17 00:00:00 2001 From: Jason Yellick Date: Tue, 8 Jan 2019 00:06:07 -0500 Subject: [PATCH] FAB-13687 Add ledger shim for SimpleQueryExecutor 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 --- core/chaincode/lifecycle/ledger_shim.go | 74 +++++++++++- core/chaincode/lifecycle/ledger_shim_test.go | 78 ++++++++++++ .../lifecycle/lifecycle_suite_test.go | 6 + .../lifecycle/mock/results_iterator.go | 112 ++++++++++++++++++ 4 files changed, 265 insertions(+), 5 deletions(-) create mode 100644 core/chaincode/lifecycle/mock/results_iterator.go diff --git a/core/chaincode/lifecycle/ledger_shim.go b/core/chaincode/lifecycle/ledger_shim.go index 561932ef4a0..7773c3fe9d6 100644 --- a/core/chaincode/lifecycle/ledger_shim.go +++ b/core/chaincode/lifecycle/ledger_shim.go @@ -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 @@ -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 @@ -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. @@ -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 +} diff --git a/core/chaincode/lifecycle/ledger_shim_test.go b/core/chaincode/lifecycle/ledger_shim_test.go index 0944c8e2468..24d580f6018 100644 --- a/core/chaincode/lifecycle/ledger_shim_test.go +++ b/core/chaincode/lifecycle/ledger_shim_test.go @@ -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")) + }) + }) + }) + }) }) diff --git a/core/chaincode/lifecycle/lifecycle_suite_test.go b/core/chaincode/lifecycle/lifecycle_suite_test.go index 467f25a1980..0c4fa2ed4d3 100644 --- a/core/chaincode/lifecycle/lifecycle_suite_test.go +++ b/core/chaincode/lifecycle/lifecycle_suite_test.go @@ -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" @@ -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 diff --git a/core/chaincode/lifecycle/mock/results_iterator.go b/core/chaincode/lifecycle/mock/results_iterator.go new file mode 100644 index 00000000000..598d9a86192 --- /dev/null +++ b/core/chaincode/lifecycle/mock/results_iterator.go @@ -0,0 +1,112 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package mock + +import ( + "sync" + + "github.com/hyperledger/fabric/common/ledger" +) + +type ResultsIterator struct { + NextStub func() (ledger.QueryResult, error) + nextMutex sync.RWMutex + nextArgsForCall []struct{} + nextReturns struct { + result1 ledger.QueryResult + result2 error + } + nextReturnsOnCall map[int]struct { + result1 ledger.QueryResult + result2 error + } + CloseStub func() + closeMutex sync.RWMutex + closeArgsForCall []struct{} + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *ResultsIterator) Next() (ledger.QueryResult, error) { + fake.nextMutex.Lock() + ret, specificReturn := fake.nextReturnsOnCall[len(fake.nextArgsForCall)] + fake.nextArgsForCall = append(fake.nextArgsForCall, struct{}{}) + fake.recordInvocation("Next", []interface{}{}) + fake.nextMutex.Unlock() + if fake.NextStub != nil { + return fake.NextStub() + } + if specificReturn { + return ret.result1, ret.result2 + } + return fake.nextReturns.result1, fake.nextReturns.result2 +} + +func (fake *ResultsIterator) NextCallCount() int { + fake.nextMutex.RLock() + defer fake.nextMutex.RUnlock() + return len(fake.nextArgsForCall) +} + +func (fake *ResultsIterator) NextReturns(result1 ledger.QueryResult, result2 error) { + fake.NextStub = nil + fake.nextReturns = struct { + result1 ledger.QueryResult + result2 error + }{result1, result2} +} + +func (fake *ResultsIterator) NextReturnsOnCall(i int, result1 ledger.QueryResult, result2 error) { + fake.NextStub = nil + if fake.nextReturnsOnCall == nil { + fake.nextReturnsOnCall = make(map[int]struct { + result1 ledger.QueryResult + result2 error + }) + } + fake.nextReturnsOnCall[i] = struct { + result1 ledger.QueryResult + result2 error + }{result1, result2} +} + +func (fake *ResultsIterator) Close() { + fake.closeMutex.Lock() + fake.closeArgsForCall = append(fake.closeArgsForCall, struct{}{}) + fake.recordInvocation("Close", []interface{}{}) + fake.closeMutex.Unlock() + if fake.CloseStub != nil { + fake.CloseStub() + } +} + +func (fake *ResultsIterator) CloseCallCount() int { + fake.closeMutex.RLock() + defer fake.closeMutex.RUnlock() + return len(fake.closeArgsForCall) +} + +func (fake *ResultsIterator) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.nextMutex.RLock() + defer fake.nextMutex.RUnlock() + fake.closeMutex.RLock() + defer fake.closeMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *ResultsIterator) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +}