This repository has been archived by the owner on Nov 8, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
delete state tx isolation in mockstub (#63)
* delete state tx isolation in mockstub * state cache wrapper * mockStub dumps state date only if peer.Response is shim.OK * state Clone method
- Loading branch information
Showing
10 changed files
with
473 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,134 @@ | ||
package state | ||
|
||
import ( | ||
"sort" | ||
"strings" | ||
|
||
"github.com/hyperledger/fabric-chaincode-go/shim" | ||
"github.com/hyperledger/fabric-protos-go/ledger/queryresult" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
type ( | ||
TxWriteSet map[string][]byte | ||
TxDeleteSet map[string]interface{} | ||
|
||
Cached struct { | ||
State | ||
TxCache map[string][]byte | ||
TxWriteSet TxWriteSet | ||
TxDeleteSet TxDeleteSet | ||
} | ||
|
||
CachedQueryIterator struct { | ||
current int | ||
closed bool | ||
KVs []*queryresult.KV | ||
} | ||
) | ||
|
||
// WithCached returns state with tx level state cache | ||
func WithCache(ss State) *Cached { | ||
s := ss.(*Impl) | ||
cached := &Cached{ | ||
State: s, | ||
TxCache: make(map[string][]byte), | ||
State: s, | ||
TxWriteSet: make(map[string][]byte), | ||
TxDeleteSet: make(map[string]interface{}), | ||
} | ||
|
||
// PutState wrapper | ||
s.PutState = func(key string, bb []byte) error { | ||
cached.TxCache[key] = bb | ||
cached.TxWriteSet[key] = bb | ||
return s.stub.PutState(key, bb) | ||
} | ||
|
||
// GetState wrapper | ||
s.GetState = func(key string) ([]byte, error) { | ||
if bb, ok := cached.TxCache[key]; ok { | ||
if bb, ok := cached.TxWriteSet[key]; ok { | ||
return bb, nil | ||
} | ||
|
||
if _, ok := cached.TxDeleteSet[key]; ok { | ||
return []byte{}, nil | ||
} | ||
return s.stub.GetState(key) | ||
} | ||
|
||
s.DelState = func(key string) error { | ||
delete(cached.TxWriteSet, key) | ||
cached.TxDeleteSet[key] = nil | ||
|
||
return s.stub.DelState(key) | ||
} | ||
|
||
s.GetStateByPartialCompositeKey = func(objectType string, keys []string) (shim.StateQueryIteratorInterface, error) { | ||
iterator, err := s.stub.GetStateByPartialCompositeKey(objectType, keys) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
prefix, err := s.stub.CreateCompositeKey(objectType, keys) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return NewCachedQueryIterator(iterator, prefix, cached.TxWriteSet, cached.TxDeleteSet) | ||
} | ||
|
||
return cached | ||
} | ||
|
||
func NewCachedQueryIterator(iterator shim.StateQueryIteratorInterface, prefix string, writeSet TxWriteSet, deleteSet TxDeleteSet) (*CachedQueryIterator, error) { | ||
queryIterator := &CachedQueryIterator{ | ||
current: -1, | ||
} | ||
for iterator.HasNext() { | ||
kv, err := iterator.Next() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if _, ok := deleteSet[kv.Key]; ok { | ||
continue | ||
} | ||
|
||
queryIterator.KVs = append(queryIterator.KVs, kv) | ||
} | ||
|
||
for wroteKey, wroteValue := range writeSet { | ||
if strings.HasPrefix(wroteKey, prefix) { | ||
queryIterator.KVs = append(queryIterator.KVs, &queryresult.KV{ | ||
Namespace: "", | ||
Key: wroteKey, | ||
Value: wroteValue, | ||
}) | ||
} | ||
} | ||
|
||
sort.Slice(queryIterator.KVs, func(i, j int) bool { | ||
return queryIterator.KVs[i].Key < queryIterator.KVs[j].Key | ||
}) | ||
|
||
return queryIterator, nil | ||
} | ||
|
||
func (i *CachedQueryIterator) Next() (*queryresult.KV, error) { | ||
if !i.HasNext() { | ||
return nil, errors.New(`no next items`) | ||
} | ||
|
||
i.current++ | ||
return i.KVs[i.current], nil | ||
} | ||
|
||
// HasNext returns true if the range query iterator contains additional keys | ||
// and values. | ||
func (i *CachedQueryIterator) HasNext() bool { | ||
return i.current < len(i.KVs)-1 | ||
} | ||
|
||
// Close closes the iterator. This should be called when done | ||
// reading from the iterator to free up resources. | ||
func (i *CachedQueryIterator) Close() error { | ||
i.closed = true | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package state_test | ||
|
||
import ( | ||
. "github.com/onsi/ginkgo" | ||
. "github.com/onsi/gomega" | ||
|
||
"github.com/s7techlab/cckit/state/testdata" | ||
testcc "github.com/s7techlab/cckit/testing" | ||
expectcc "github.com/s7techlab/cckit/testing/expect" | ||
) | ||
|
||
const ( | ||
StateCachedChaincode = `state_cached` | ||
) | ||
|
||
var _ = Describe(`State caching`, func() { | ||
|
||
//Create chaincode mocks | ||
stateCachedCC := testcc.NewMockStub(StateCachedChaincode, testdata.NewStateCachedCC()) | ||
|
||
It("Read after write returns non empty entry", func() { | ||
resp := expectcc.PayloadIs(stateCachedCC.Invoke(testdata.TxStateCachedReadAfterWrite), &testdata.Value{}) | ||
Expect(resp).To(Equal(testdata.KeyValue(testdata.Keys[0]))) | ||
}) | ||
|
||
It("Read after delete returns empty entry", func() { | ||
resp := stateCachedCC.Invoke(testdata.TxStateCachedReadAfterDelete) | ||
Expect(resp.Payload).To(Equal([]byte{})) | ||
}) | ||
|
||
It("List after write returns list", func() { | ||
resp := expectcc.PayloadIs( | ||
stateCachedCC.Invoke(testdata.TxStateCachedListAfterWrite), &[]testdata.Value{}).([]testdata.Value) | ||
|
||
// all key exists | ||
Expect(resp).To(Equal([]testdata.Value{ | ||
testdata.KeyValue(testdata.Keys[0]), testdata.KeyValue(testdata.Keys[1]), testdata.KeyValue(testdata.Keys[2])})) | ||
}) | ||
|
||
It("List after delete returns list without deleted item", func() { | ||
resp := expectcc.PayloadIs( | ||
stateCachedCC.Invoke(testdata.TxStateCachedListAfterDelete), &[]testdata.Value{}).([]testdata.Value) | ||
|
||
// first key is deleted | ||
Expect(resp).To(Equal([]testdata.Value{ | ||
testdata.KeyValue(testdata.Keys[1]), testdata.KeyValue(testdata.Keys[2])})) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.