Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <manish.sethi@gmail.com>
- Loading branch information
1 parent
230f3cc
commit 1637217
Showing
16 changed files
with
1,083 additions
and
127 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 |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/* | ||
Copyright IBM Corp. 2016 All Rights Reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package rwset | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/version" | ||
"github.com/hyperledger/fabric/core/ledger/testutil" | ||
) | ||
|
||
func TestRWSetHolder(t *testing.T) { | ||
rwSet := NewRWSet() | ||
|
||
rwSet.AddToReadSet("ns1", "key2", version.NewHeight(1, 2)) | ||
rwSet.AddToReadSet("ns1", "key1", version.NewHeight(1, 1)) | ||
rwSet.AddToWriteSet("ns1", "key2", []byte("value2")) | ||
|
||
rqi1 := &RangeQueryInfo{"bKey", "", false, nil, nil} | ||
rqi1.EndKey = "eKey" | ||
rqi1.results = []*KVRead{NewKVRead("bKey1", version.NewHeight(2, 3)), NewKVRead("bKey2", version.NewHeight(2, 4))} | ||
rqi1.ItrExhausted = true | ||
rwSet.AddToRangeQuerySet("ns1", rqi1) | ||
|
||
rqi2 := &RangeQueryInfo{"bKey", "", false, nil, nil} | ||
rqi2.EndKey = "eKey" | ||
rqi2.results = []*KVRead{NewKVRead("bKey1", version.NewHeight(2, 3)), NewKVRead("bKey2", version.NewHeight(2, 4))} | ||
rqi2.ItrExhausted = true | ||
rwSet.AddToRangeQuerySet("ns1", rqi2) | ||
|
||
rqi3 := &RangeQueryInfo{"bKey", "", true, nil, nil} | ||
rwSet.AddToRangeQuerySet("ns1", rqi3) | ||
rqi3.EndKey = "eKey1" | ||
rqi3.results = []*KVRead{NewKVRead("bKey1", version.NewHeight(2, 3)), NewKVRead("bKey2", version.NewHeight(2, 4))} | ||
|
||
rwSet.AddToReadSet("ns2", "key2", version.NewHeight(1, 2)) | ||
rwSet.AddToWriteSet("ns2", "key3", []byte("value3")) | ||
|
||
txRWSet := rwSet.GetTxReadWriteSet() | ||
|
||
ns1RWSet := &NsReadWriteSet{"ns1", | ||
[]*KVRead{&KVRead{"key1", version.NewHeight(1, 1)}, &KVRead{"key2", version.NewHeight(1, 2)}}, | ||
[]*KVWrite{&KVWrite{"key2", false, []byte("value2")}}, | ||
[]*RangeQueryInfo{rqi1, rqi3}} | ||
|
||
ns2RWSet := &NsReadWriteSet{"ns2", | ||
[]*KVRead{&KVRead{"key2", version.NewHeight(1, 2)}}, | ||
[]*KVWrite{&KVWrite{"key3", false, []byte("value3")}}, | ||
[]*RangeQueryInfo{}} | ||
|
||
expectedTxRWSet := &TxReadWriteSet{[]*NsReadWriteSet{ns1RWSet, ns2RWSet}} | ||
t.Logf("Actual=%s\n Expected=%s", txRWSet, expectedTxRWSet) | ||
testutil.AssertEquals(t, txRWSet, expectedTxRWSet) | ||
} |
Oops, something went wrong.