Skip to content

Commit 19d857c

Browse files
committed
[FAB-2183] fix RangeQuery key collision
When we do a range query on simple keys, it returns composite keys too. This CR make sure that only simple keys are returned by GetStateByRange() API. In order to achieve that, we make the CreateCompositeKey() chaincode API to add a null character (0x00) as a first character in composite key. This creates a separate namespace for composite key. The simpleKey must not be an empty string as it is treated as 0x00 in levelDB which results in collision between simple and compositeKey. Hence, we do not allow empty string as key in PutState(). Further, we need to ensure that a simple key does not start with a null character (0x00). Currently, we cannot impose this constraint on simpleKey as PutState() is being used for storing both <simpleKey, value> and <compositeKey, value> where the compositeKey must start with 0x00. Hence, we have only documented this constraint on simpleKey (note that it is unusual for a key to start with a null char). In future (post v1), we may introduce PutCompositeKey() API so that we can explicitly impose this constraint. Other approaches are listed in FAB-2183 Change-Id: I28aee64d81e07f2a504580b3fe87a182c130d82e Signed-off-by: senthil <[email protected]>
1 parent ecc29dd commit 19d857c

File tree

4 files changed

+95
-19
lines changed

4 files changed

+95
-19
lines changed

core/chaincode/exectransaction_test.go

+29-1
Original file line numberDiff line numberDiff line change
@@ -1167,6 +1167,34 @@ func TestQueries(t *testing.T) {
11671167
return
11681168
}
11691169

1170+
// querying for all simple key. This query should return exactly 101 simple keys (one
1171+
// call to Next()) no composite keys.
1172+
//The following open ended range query for "" to "" should return 101 marbles
1173+
f = "keys"
1174+
args = util.ToChaincodeArgs(f, "", "")
1175+
1176+
spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}}
1177+
_, _, retval, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil)
1178+
nextBlockNumber++
1179+
if err != nil {
1180+
t.Fail()
1181+
t.Logf("Error invoking <%s>: %s", ccID, err)
1182+
theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec})
1183+
return
1184+
}
1185+
1186+
//unmarshal the results
1187+
err = json.Unmarshal(retval, &keys)
1188+
1189+
//check to see if there are 101 values
1190+
//default query limit of 10000 is used, this query is effectively unlimited
1191+
if len(keys) != 101 {
1192+
t.Fail()
1193+
t.Logf("Error detected with the range query, should have returned 101 but returned %v", len(keys))
1194+
theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec})
1195+
return
1196+
}
1197+
11701198
// ExecuteQuery supported only for CouchDB and
11711199
// query limits apply for CouchDB range and rich queries only
11721200
if ledgerconfig.IsCouchDBEnabled() == true {
@@ -1251,7 +1279,7 @@ func TestQueries(t *testing.T) {
12511279
//default query limit of 10000 is used, this query is effectively unlimited
12521280
if len(keys) != 50 {
12531281
t.Fail()
1254-
t.Logf("Error detected with the rich query, should have returned 9 but returned %v", len(keys))
1282+
t.Logf("Error detected with the rich query, should have returned 50 but returned %v", len(keys))
12551283
theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec})
12561284
return
12571285
}

core/chaincode/shim/chaincode.go

+44-15
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ var chaincodeLogger = logging.MustGetLogger("shim")
4747
var logOutput = os.Stderr
4848

4949
const (
50-
minUnicodeRuneValue = 0 //U+0000
51-
maxUnicodeRuneValue = utf8.MaxRune //U+10FFFF - maximum (and unallocated) code point
50+
minUnicodeRuneValue = 0 //U+0000
51+
maxUnicodeRuneValue = utf8.MaxRune //U+10FFFF - maximum (and unallocated) code point
52+
compositeKeyNamespace = "\x00"
53+
emptyKeySubstitute = "\x01"
5254
)
5355

5456
// ChaincodeStub is an object passed to chaincode for shim side handling of
@@ -349,7 +351,14 @@ func (stub *ChaincodeStub) GetState(key string) ([]byte, error) {
349351
}
350352

351353
// PutState writes the specified `value` and `key` into the ledger.
354+
// Simple keys must not be an empty string and must not start with null
355+
// character (0x00), in order to avoid range query collisions with
356+
// composite keys, which internally get prefixed with 0x00 as composite
357+
// key namespace.
352358
func (stub *ChaincodeStub) PutState(key string, value []byte) error {
359+
if key == "" {
360+
return fmt.Errorf("key must not be an empty string")
361+
}
353362
return stub.handler.handlePutState(key, value, stub.TxID)
354363
}
355364

@@ -389,9 +398,17 @@ const (
389398
// GetStateByRange function can be invoked by a chaincode to query of a range
390399
// of keys in the state. Assuming the startKey and endKey are in lexical order,
391400
// an iterator will be returned that can be used to iterate over all keys
392-
// between the startKey and endKey, inclusive. The order in which keys are
393-
// returned by the iterator is random.
401+
// between the startKey and endKey. The startKey is inclusive whereas the endKey
402+
// is exclusive. The keys are returned by the iterator in lexical order. Note
403+
// that startKey and endKey can be empty string, which implies unbounded range
404+
// query on start or end.
394405
func (stub *ChaincodeStub) GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) {
406+
if startKey == "" {
407+
startKey = emptyKeySubstitute
408+
}
409+
if err := validateSimpleKeys(startKey, endKey); err != nil {
410+
return nil, err
411+
}
395412
response, err := stub.handler.handleGetStateByRange(startKey, endKey, stub.TxID)
396413
if err != nil {
397414
return nil, err
@@ -436,7 +453,7 @@ func createCompositeKey(objectType string, attributes []string) (string, error)
436453
if err := validateCompositeKeyAttribute(objectType); err != nil {
437454
return "", err
438455
}
439-
ck := objectType + string(minUnicodeRuneValue)
456+
ck := compositeKeyNamespace + objectType + string(minUnicodeRuneValue)
440457
for _, att := range attributes {
441458
if err := validateCompositeKeyAttribute(att); err != nil {
442459
return "", err
@@ -447,9 +464,9 @@ func createCompositeKey(objectType string, attributes []string) (string, error)
447464
}
448465

449466
func splitCompositeKey(compositeKey string) (string, []string, error) {
450-
componentIndex := 0
467+
componentIndex := 1
451468
components := []string{}
452-
for i := 0; i < len(compositeKey); i++ {
469+
for i := 1; i < len(compositeKey); i++ {
453470
if compositeKey[i] == minUnicodeRuneValue {
454471
components = append(components, compositeKey[componentIndex:i])
455472
componentIndex = i + 1
@@ -471,23 +488,35 @@ func validateCompositeKeyAttribute(str string) error {
471488
return nil
472489
}
473490

491+
//To ensure that simple keys do not go into composite key namespace,
492+
//we validate simplekey to check whether the key starts with 0x00 (which
493+
//is the namespace for compositeKey). This helps in avoding simple/composite
494+
//key collisions.
495+
func validateSimpleKeys(simpleKeys ...string) error {
496+
for _, key := range simpleKeys {
497+
if len(key) > 0 && key[0] == compositeKeyNamespace[0] {
498+
return fmt.Errorf(`First character of the key [%s] contains a null character which is not allowed`, key)
499+
}
500+
}
501+
return nil
502+
}
503+
474504
//GetStateByPartialCompositeKey function can be invoked by a chaincode to query the
475505
//state based on a given partial composite key. This function returns an
476506
//iterator which can be used to iterate over all composite keys whose prefix
477507
//matches the given partial composite key. This function should be used only for
478508
//a partial composite key. For a full composite key, an iter with empty response
479509
//would be returned.
480510
func (stub *ChaincodeStub) GetStateByPartialCompositeKey(objectType string, attributes []string) (StateQueryIteratorInterface, error) {
481-
return getStateByPartialCompositeKey(stub, objectType, attributes)
482-
}
483-
484-
func getStateByPartialCompositeKey(stub ChaincodeStubInterface, objectType string, attributes []string) (StateQueryIteratorInterface, error) {
485-
partialCompositeKey, _ := stub.CreateCompositeKey(objectType, attributes)
486-
keysIter, err := stub.GetStateByRange(partialCompositeKey, partialCompositeKey+string(maxUnicodeRuneValue))
511+
partialCompositeKey, err := stub.CreateCompositeKey(objectType, attributes)
512+
if err != nil {
513+
return nil, err
514+
}
515+
response, err := stub.handler.handleGetStateByRange(partialCompositeKey, partialCompositeKey+string(maxUnicodeRuneValue), stub.TxID)
487516
if err != nil {
488-
return nil, fmt.Errorf("Error fetching rows: %s", err)
517+
return nil, err
489518
}
490-
return keysIter, nil
519+
return &StateQueryIterator{CommonIterator: &CommonIterator{stub.handler, stub.TxID, response, 0}}, nil
491520
}
492521

493522
func (iter *StateQueryIterator) Next() (*queryresult.KV, error) {

core/chaincode/shim/mockstub.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ func (stub *MockStub) DelState(key string) error {
209209
}
210210

211211
func (stub *MockStub) GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) {
212+
if err := validateSimpleKeys(startKey, endKey); err != nil {
213+
return nil, err
214+
}
212215
return NewMockStateRangeQueryIterator(stub, startKey, endKey), nil
213216
}
214217

@@ -237,7 +240,11 @@ func (stub *MockStub) GetHistoryForKey(key string) (HistoryQueryIteratorInterfac
237240
//a partial composite key. For a full composite key, an iter with empty response
238241
//would be returned.
239242
func (stub *MockStub) GetStateByPartialCompositeKey(objectType string, attributes []string) (StateQueryIteratorInterface, error) {
240-
return getStateByPartialCompositeKey(stub, objectType, attributes)
243+
partialCompositeKey, err := stub.CreateCompositeKey(objectType, attributes)
244+
if err != nil {
245+
return nil, err
246+
}
247+
return NewMockStateRangeQueryIterator(stub, partialCompositeKey, partialCompositeKey+string(maxUnicodeRuneValue)), nil
241248
}
242249

243250
// CreateCompositeKey combines the list of attributes

examples/chaincode/go/map/map.go

+14-2
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,23 @@ func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
5555
key := args[0]
5656
value := args[1]
5757

58-
err := stub.PutState(key, []byte(value))
59-
if err != nil {
58+
if err := stub.PutState(key, []byte(value)); err != nil {
6059
fmt.Printf("Error putting state %s", err)
6160
return shim.Error(fmt.Sprintf("put operation failed. Error updating state: %s", err))
6261
}
62+
63+
indexName := "compositeKeyTest"
64+
compositeKeyTestIndex, err := stub.CreateCompositeKey(indexName, []string{key})
65+
if err != nil {
66+
return shim.Error(err.Error())
67+
}
68+
69+
valueByte := []byte{0x00}
70+
if err := stub.PutState(compositeKeyTestIndex, valueByte); err != nil {
71+
fmt.Printf("Error putting state with compositeKey %s", err)
72+
return shim.Error(fmt.Sprintf("put operation failed. Error updating state with compositeKey: %s", err))
73+
}
74+
6375
return shim.Success(nil)
6476

6577
case "remove":

0 commit comments

Comments
 (0)