Skip to content

Commit 1637217

Browse files
committed
Detect phantom items during validation
https://jira.hyperledger.org/browse/FAB-1668 Phantom reads problem: ----------------------------- If a transaction executes a key range query (say key_1 - key_n) leveldb serves all the keys in the given range in sorted order of keys. Between simulation and validation of a transaction, some other transaction may insert one or more keys in this range. Now, this change cannot be detected by merely checking the versions of the keys present in the read-set of the transaction. (though, this check can detect updates and deletes). A serializable isolation level does not permit phantom reads. Situations where the phantom reads may not be acceptable: ----------------------------- - A chaincode may take a decision based on aggregates of the results of the range query such as min/max/avg/sum etc. This would be affected if a new entry gets added to the range query results. - A chaincode may want to change all the marbles owned by a specific user to blue in a tran `t1`. The chaincode may do so by performing a range query to get all the marbles for the user and change their color to blue. Now, if a new white marble get added to the user's assets by an another transaction `t2`, `t1` should be marked as invalid. Solution in this change-set: ----------------------------- For solving this, we can capture the details of the range query (the query and it's results) during the simulation time and re-execute the query during validation time and compare the results. If the results (keys and versions) are still the same, we can safely commit the transaction. This changes set introduces - Structures for capturing the range query details in rwset - Detection of phantom items (insertion/deletion) that affect the results of the range query during validation - Mark the transactions that are affected by the phantom items as invalid to ensure serializability in the presence of range queries Change-Id: I909841a0234c37795ad7a4ffcca8a9ebd9c9f994 Signed-off-by: manish <[email protected]>
1 parent 230f3cc commit 1637217

16 files changed

+1083
-127
lines changed

core/ledger/kvledger/txmgmt/rwset/rwset.go

+147-9
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,38 @@ func (w *KVWrite) SetValue(value []byte) {
5454
w.IsDelete = value == nil
5555
}
5656

57+
// RangeQueryInfo captures a range query executed by a transaction
58+
// and the tuples <key,version> that are read by the transaction
59+
// This it to be used to perform a phantom-read validation during commit
60+
type RangeQueryInfo struct {
61+
StartKey string
62+
EndKey string
63+
ItrExhausted bool
64+
results []*KVRead
65+
resultHash []byte
66+
}
67+
68+
// AddResult appends the result
69+
func (rqi *RangeQueryInfo) AddResult(kvRead *KVRead) {
70+
rqi.results = append(rqi.results, kvRead)
71+
}
72+
73+
// GetResults returns the results of the range query
74+
func (rqi *RangeQueryInfo) GetResults() []*KVRead {
75+
return rqi.results
76+
}
77+
78+
// GetResultHash returns the resultHash
79+
func (rqi *RangeQueryInfo) GetResultHash() []byte {
80+
return rqi.resultHash
81+
}
82+
5783
// NsReadWriteSet - a collection of all the reads and writes that belong to a common namespace
5884
type NsReadWriteSet struct {
59-
NameSpace string
60-
Reads []*KVRead
61-
Writes []*KVWrite
85+
NameSpace string
86+
Reads []*KVRead
87+
Writes []*KVWrite
88+
RangeQueriesInfo []*RangeQueryInfo
6289
}
6390

6491
// TxReadWriteSet - a collection of all the reads and writes collected as a result of a transaction simulation
@@ -97,6 +124,79 @@ func (r *KVRead) Unmarshal(buf *proto.Buffer) error {
97124
return nil
98125
}
99126

127+
// Marshal serializes a `RangeQueryInfo`
128+
func (rqi *RangeQueryInfo) Marshal(buf *proto.Buffer) error {
129+
if err := buf.EncodeStringBytes(rqi.StartKey); err != nil {
130+
return err
131+
}
132+
if err := buf.EncodeStringBytes(rqi.EndKey); err != nil {
133+
return err
134+
}
135+
136+
itrExhausedMarker := 0 // iterator did not get exhausted
137+
if rqi.ItrExhausted {
138+
itrExhausedMarker = 1
139+
}
140+
if err := buf.EncodeVarint(uint64(itrExhausedMarker)); err != nil {
141+
return err
142+
}
143+
144+
if err := buf.EncodeVarint(uint64(len(rqi.results))); err != nil {
145+
return err
146+
}
147+
for i := 0; i < len(rqi.results); i++ {
148+
if err := rqi.results[i].Marshal(buf); err != nil {
149+
return err
150+
}
151+
}
152+
if err := buf.EncodeRawBytes(rqi.resultHash); err != nil {
153+
return err
154+
}
155+
return nil
156+
}
157+
158+
// Unmarshal deserializes a `RangeQueryInfo`
159+
func (rqi *RangeQueryInfo) Unmarshal(buf *proto.Buffer) error {
160+
var err error
161+
var numResults uint64
162+
var itrExhaustedMarker uint64
163+
164+
if rqi.StartKey, err = buf.DecodeStringBytes(); err != nil {
165+
return err
166+
}
167+
if rqi.EndKey, err = buf.DecodeStringBytes(); err != nil {
168+
return err
169+
}
170+
if itrExhaustedMarker, err = buf.DecodeVarint(); err != nil {
171+
return err
172+
}
173+
if itrExhaustedMarker == 1 {
174+
rqi.ItrExhausted = true
175+
} else {
176+
rqi.ItrExhausted = false
177+
}
178+
if numResults, err = buf.DecodeVarint(); err != nil {
179+
return err
180+
}
181+
if numResults > 0 {
182+
rqi.results = make([]*KVRead, int(numResults))
183+
}
184+
for i := 0; i < int(numResults); i++ {
185+
kvRead := &KVRead{}
186+
if err := kvRead.Unmarshal(buf); err != nil {
187+
return err
188+
}
189+
rqi.results[i] = kvRead
190+
}
191+
if rqi.resultHash, err = buf.DecodeRawBytes(false); err != nil {
192+
return err
193+
}
194+
if len(rqi.resultHash) == 0 {
195+
rqi.resultHash = nil
196+
}
197+
return nil
198+
}
199+
100200
// Marshal serializes a `KVWrite`
101201
func (w *KVWrite) Marshal(buf *proto.Buffer) error {
102202
var err error
@@ -148,13 +248,25 @@ func (nsRW *NsReadWriteSet) Marshal(buf *proto.Buffer) error {
148248
return err
149249
}
150250
for i := 0; i < len(nsRW.Reads); i++ {
151-
nsRW.Reads[i].Marshal(buf)
251+
if err = nsRW.Reads[i].Marshal(buf); err != nil {
252+
return err
253+
}
152254
}
153255
if err = buf.EncodeVarint(uint64(len(nsRW.Writes))); err != nil {
154256
return err
155257
}
156258
for i := 0; i < len(nsRW.Writes); i++ {
157-
nsRW.Writes[i].Marshal(buf)
259+
if err = nsRW.Writes[i].Marshal(buf); err != nil {
260+
return err
261+
}
262+
}
263+
if err = buf.EncodeVarint(uint64(len(nsRW.RangeQueriesInfo))); err != nil {
264+
return err
265+
}
266+
for i := 0; i < len(nsRW.RangeQueriesInfo); i++ {
267+
if err = nsRW.RangeQueriesInfo[i].Marshal(buf); err != nil {
268+
return err
269+
}
158270
}
159271
return nil
160272
}
@@ -188,6 +300,18 @@ func (nsRW *NsReadWriteSet) Unmarshal(buf *proto.Buffer) error {
188300
}
189301
nsRW.Writes = append(nsRW.Writes, w)
190302
}
303+
304+
var numRangeQueriesInfo uint64
305+
if numRangeQueriesInfo, err = buf.DecodeVarint(); err != nil {
306+
return err
307+
}
308+
for i := 0; i < int(numRangeQueriesInfo); i++ {
309+
rqInfo := &RangeQueryInfo{}
310+
if err = rqInfo.Unmarshal(buf); err != nil {
311+
return err
312+
}
313+
nsRW.RangeQueriesInfo = append(nsRW.RangeQueriesInfo, rqInfo)
314+
}
191315
return nil
192316
}
193317

@@ -234,18 +358,32 @@ func (w *KVWrite) String() string {
234358
return fmt.Sprintf("%s=[%#v]", w.Key, w.Value)
235359
}
236360

361+
// String prints a range query info
362+
func (rqi *RangeQueryInfo) String() string {
363+
return fmt.Sprintf("StartKey=%s, EndKey=%s, ItrExhausted=%t, Results=%#v, Hash=%#v",
364+
rqi.StartKey, rqi.EndKey, rqi.ItrExhausted, rqi.results, rqi.resultHash)
365+
}
366+
237367
// String prints a `NsReadWriteSet`
238368
func (nsRW *NsReadWriteSet) String() string {
239369
var buffer bytes.Buffer
240-
buffer.WriteString("ReadSet~")
370+
buffer.WriteString("ReadSet=\n")
241371
for _, r := range nsRW.Reads {
372+
buffer.WriteString("\t")
242373
buffer.WriteString(r.String())
243-
buffer.WriteString(",")
374+
buffer.WriteString("\n")
244375
}
245-
buffer.WriteString("WriteSet~")
376+
buffer.WriteString("WriteSet=\n")
246377
for _, w := range nsRW.Writes {
378+
buffer.WriteString("\t")
247379
buffer.WriteString(w.String())
248-
buffer.WriteString(",")
380+
buffer.WriteString("\n")
381+
}
382+
buffer.WriteString("RangeQueriesInfo=\n")
383+
for _, rqi := range nsRW.RangeQueriesInfo {
384+
buffer.WriteString("\t")
385+
buffer.WriteString(rqi.String())
386+
buffer.WriteString("\n")
249387
}
250388
return buffer.String()
251389
}

core/ledger/kvledger/txmgmt/rwset/rwset_holder.go

+35-19
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,28 @@ limitations under the License.
1717
package rwset
1818

1919
import (
20-
"reflect"
21-
2220
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/version"
21+
"github.com/hyperledger/fabric/core/ledger/util"
2322
logging "github.com/op/go-logging"
2423
)
2524

2625
var logger = logging.MustGetLogger("rwset")
2726

2827
type nsRWs struct {
29-
readMap map[string]*KVRead
30-
writeMap map[string]*KVWrite
28+
readMap map[string]*KVRead //for mvcc validation
29+
writeMap map[string]*KVWrite
30+
rangeQueriesMap map[rangeQueryKey]*RangeQueryInfo //for phantom read validation
31+
rangeQueriesKeys []rangeQueryKey
3132
}
3233

3334
func newNsRWs() *nsRWs {
34-
return &nsRWs{make(map[string]*KVRead), make(map[string]*KVWrite)}
35+
return &nsRWs{make(map[string]*KVRead), make(map[string]*KVWrite), make(map[rangeQueryKey]*RangeQueryInfo), nil}
36+
}
37+
38+
type rangeQueryKey struct {
39+
startKey string
40+
endKey string
41+
itrExhausted bool
3542
}
3643

3744
// RWSet maintains the read-write set
@@ -56,6 +63,17 @@ func (rws *RWSet) AddToWriteSet(ns string, key string, value []byte) {
5663
nsRWs.writeMap[key] = NewKVWrite(key, value)
5764
}
5865

66+
// AddToRangeQuerySet adds a range query info for performing phantom read validation
67+
func (rws *RWSet) AddToRangeQuerySet(ns string, rqi *RangeQueryInfo) {
68+
nsRWs := rws.getOrCreateNsRW(ns)
69+
key := rangeQueryKey{rqi.StartKey, rqi.EndKey, rqi.ItrExhausted}
70+
_, ok := nsRWs.rangeQueriesMap[key]
71+
if !ok {
72+
nsRWs.rangeQueriesMap[key] = rqi
73+
nsRWs.rangeQueriesKeys = append(nsRWs.rangeQueriesKeys, key)
74+
}
75+
}
76+
5977
// GetFromWriteSet return the value of a key from the write-set
6078
func (rws *RWSet) GetFromWriteSet(ns string, key string) ([]byte, bool) {
6179
nsRWs, ok := rws.rwMap[ns]
@@ -73,24 +91,32 @@ func (rws *RWSet) GetFromWriteSet(ns string, key string) ([]byte, bool) {
7391
// GetTxReadWriteSet returns the read-write set in the form that can be serialized
7492
func (rws *RWSet) GetTxReadWriteSet() *TxReadWriteSet {
7593
txRWSet := &TxReadWriteSet{}
76-
sortedNamespaces := getSortedKeys(rws.rwMap)
94+
sortedNamespaces := util.GetSortedKeys(rws.rwMap)
7795
for _, ns := range sortedNamespaces {
7896
//Get namespace specific read-writes
7997
nsReadWriteMap := rws.rwMap[ns]
98+
8099
//add read set
81100
reads := []*KVRead{}
82-
sortedReadKeys := getSortedKeys(nsReadWriteMap.readMap)
101+
sortedReadKeys := util.GetSortedKeys(nsReadWriteMap.readMap)
83102
for _, key := range sortedReadKeys {
84103
reads = append(reads, nsReadWriteMap.readMap[key])
85104
}
86105

87106
//add write set
88107
writes := []*KVWrite{}
89-
sortedWriteKeys := getSortedKeys(nsReadWriteMap.writeMap)
108+
sortedWriteKeys := util.GetSortedKeys(nsReadWriteMap.writeMap)
90109
for _, key := range sortedWriteKeys {
91110
writes = append(writes, nsReadWriteMap.writeMap[key])
92111
}
93-
nsRWs := &NsReadWriteSet{NameSpace: ns, Reads: reads, Writes: writes}
112+
113+
//add range query info
114+
rangeQueriesInfo := []*RangeQueryInfo{}
115+
rangeQueriesMap := nsReadWriteMap.rangeQueriesMap
116+
for _, key := range nsReadWriteMap.rangeQueriesKeys {
117+
rangeQueriesInfo = append(rangeQueriesInfo, rangeQueriesMap[key])
118+
}
119+
nsRWs := &NsReadWriteSet{NameSpace: ns, Reads: reads, Writes: writes, RangeQueriesInfo: rangeQueriesInfo}
94120
txRWSet.NsRWs = append(txRWSet.NsRWs, nsRWs)
95121
}
96122
return txRWSet
@@ -105,13 +131,3 @@ func (rws *RWSet) getOrCreateNsRW(ns string) *nsRWs {
105131
}
106132
return nsRWs
107133
}
108-
109-
func getSortedKeys(m interface{}) []string {
110-
mapVal := reflect.ValueOf(m)
111-
keyVals := mapVal.MapKeys()
112-
keys := []string{}
113-
for _, keyVal := range keyVals {
114-
keys = append(keys, keyVal.String())
115-
}
116-
return keys
117-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
Copyright IBM Corp. 2016 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package rwset
18+
19+
import (
20+
"testing"
21+
22+
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/version"
23+
"github.com/hyperledger/fabric/core/ledger/testutil"
24+
)
25+
26+
func TestRWSetHolder(t *testing.T) {
27+
rwSet := NewRWSet()
28+
29+
rwSet.AddToReadSet("ns1", "key2", version.NewHeight(1, 2))
30+
rwSet.AddToReadSet("ns1", "key1", version.NewHeight(1, 1))
31+
rwSet.AddToWriteSet("ns1", "key2", []byte("value2"))
32+
33+
rqi1 := &RangeQueryInfo{"bKey", "", false, nil, nil}
34+
rqi1.EndKey = "eKey"
35+
rqi1.results = []*KVRead{NewKVRead("bKey1", version.NewHeight(2, 3)), NewKVRead("bKey2", version.NewHeight(2, 4))}
36+
rqi1.ItrExhausted = true
37+
rwSet.AddToRangeQuerySet("ns1", rqi1)
38+
39+
rqi2 := &RangeQueryInfo{"bKey", "", false, nil, nil}
40+
rqi2.EndKey = "eKey"
41+
rqi2.results = []*KVRead{NewKVRead("bKey1", version.NewHeight(2, 3)), NewKVRead("bKey2", version.NewHeight(2, 4))}
42+
rqi2.ItrExhausted = true
43+
rwSet.AddToRangeQuerySet("ns1", rqi2)
44+
45+
rqi3 := &RangeQueryInfo{"bKey", "", true, nil, nil}
46+
rwSet.AddToRangeQuerySet("ns1", rqi3)
47+
rqi3.EndKey = "eKey1"
48+
rqi3.results = []*KVRead{NewKVRead("bKey1", version.NewHeight(2, 3)), NewKVRead("bKey2", version.NewHeight(2, 4))}
49+
50+
rwSet.AddToReadSet("ns2", "key2", version.NewHeight(1, 2))
51+
rwSet.AddToWriteSet("ns2", "key3", []byte("value3"))
52+
53+
txRWSet := rwSet.GetTxReadWriteSet()
54+
55+
ns1RWSet := &NsReadWriteSet{"ns1",
56+
[]*KVRead{&KVRead{"key1", version.NewHeight(1, 1)}, &KVRead{"key2", version.NewHeight(1, 2)}},
57+
[]*KVWrite{&KVWrite{"key2", false, []byte("value2")}},
58+
[]*RangeQueryInfo{rqi1, rqi3}}
59+
60+
ns2RWSet := &NsReadWriteSet{"ns2",
61+
[]*KVRead{&KVRead{"key2", version.NewHeight(1, 2)}},
62+
[]*KVWrite{&KVWrite{"key3", false, []byte("value3")}},
63+
[]*RangeQueryInfo{}}
64+
65+
expectedTxRWSet := &TxReadWriteSet{[]*NsReadWriteSet{ns1RWSet, ns2RWSet}}
66+
t.Logf("Actual=%s\n Expected=%s", txRWSet, expectedTxRWSet)
67+
testutil.AssertEquals(t, txRWSet, expectedTxRWSet)
68+
}

0 commit comments

Comments
 (0)