From f9cc350902ef8fc31dacbb1a1b816480f6dbf0d9 Mon Sep 17 00:00:00 2001 From: manish Date: Thu, 27 Apr 2017 15:18:11 +0530 Subject: [PATCH] [FAB-3407] Increase test coverage for mvcc validation This CR - Introduces unit tests to improve the code coverage for package /fabric/core/ledger/kvledger/txmgmt/validator/statebasedval - Fixes a bug that was discovered by the new unit test for a boundary condition Change-Id: I2d7b41e3d721bfe6f4fc5869d22ffc7db66dc884 Signed-off-by: manish --- .../statebasedval/range_query_validator.go | 3 - .../range_query_validator_test.go | 84 +++++++++++++++++++ .../state_based_validator_test.go | 52 ++++++++---- 3 files changed, 121 insertions(+), 18 deletions(-) create mode 100644 core/ledger/kvledger/txmgmt/validator/statebasedval/range_query_validator_test.go diff --git a/core/ledger/kvledger/txmgmt/validator/statebasedval/range_query_validator.go b/core/ledger/kvledger/txmgmt/validator/statebasedval/range_query_validator.go index 54743227401..036517e0402 100644 --- a/core/ledger/kvledger/txmgmt/validator/statebasedval/range_query_validator.go +++ b/core/ledger/kvledger/txmgmt/validator/statebasedval/range_query_validator.go @@ -76,9 +76,6 @@ func (v *rangeQueryResultsValidator) validate() (bool, error) { return false, err } } - if result, err = itr.Next(); err != nil { - return false, err - } if result != nil { // iterator is not exhausted - which means that there are extra results in the given range logger.Debugf("Extra result = [%#v]", result) diff --git a/core/ledger/kvledger/txmgmt/validator/statebasedval/range_query_validator_test.go b/core/ledger/kvledger/txmgmt/validator/statebasedval/range_query_validator_test.go new file mode 100644 index 00000000000..d29cc8cc697 --- /dev/null +++ b/core/ledger/kvledger/txmgmt/validator/statebasedval/range_query_validator_test.go @@ -0,0 +1,84 @@ +/* +Copyright IBM Corp. 2017 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 statebasedval + +import ( + "testing" + + "github.com/hyperledger/fabric/common/ledger/testutil" + "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/rwsetutil" + "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb" + "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb/stateleveldb" + "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/version" + "github.com/hyperledger/fabric/protos/ledger/rwset/kvrwset" +) + +func TestRangeQueryBoundaryConditions(t *testing.T) { + batch := statedb.NewUpdateBatch() + batch.Put("ns1", "key1", []byte("value1"), version.NewHeight(1, 0)) + batch.Put("ns1", "key2", []byte("value2"), version.NewHeight(1, 1)) + batch.Put("ns1", "key3", []byte("value3"), version.NewHeight(1, 2)) + batch.Put("ns1", "key4", []byte("value4"), version.NewHeight(1, 3)) + batch.Put("ns1", "key5", []byte("value5"), version.NewHeight(1, 4)) + + testcase1 := "NoResults" + rqi1 := &kvrwset.RangeQueryInfo{StartKey: "key7", EndKey: "key10", ItrExhausted: true} + rqi1.SetRawReads([]*kvrwset.KVRead{}) + testRangeQuery(t, testcase1, batch, version.NewHeight(1, 4), "ns1", rqi1, true) + + testcase2 := "NoResultsDuringValidation" + rqi2 := &kvrwset.RangeQueryInfo{StartKey: "key7", EndKey: "key10", ItrExhausted: true} + rqi2.SetRawReads([]*kvrwset.KVRead{rwsetutil.NewKVRead("key8", version.NewHeight(1, 8))}) + testRangeQuery(t, testcase2, batch, version.NewHeight(1, 4), "ns1", rqi2, false) + + testcase3 := "OneExtraTailingResultsDuringValidation" + rqi3 := &kvrwset.RangeQueryInfo{StartKey: "key1", EndKey: "key4", ItrExhausted: true} + rqi3.SetRawReads([]*kvrwset.KVRead{ + rwsetutil.NewKVRead("key1", version.NewHeight(1, 0)), + rwsetutil.NewKVRead("key2", version.NewHeight(1, 1)), + }) + testRangeQuery(t, testcase3, batch, version.NewHeight(1, 4), "ns1", rqi3, false) + + testcase4 := "TwoExtraTailingResultsDuringValidation" + rqi4 := &kvrwset.RangeQueryInfo{StartKey: "key1", EndKey: "key5", ItrExhausted: true} + rqi4.SetRawReads([]*kvrwset.KVRead{ + rwsetutil.NewKVRead("key1", version.NewHeight(1, 0)), + rwsetutil.NewKVRead("key2", version.NewHeight(1, 1)), + }) + testRangeQuery(t, testcase4, batch, version.NewHeight(1, 4), "ns1", rqi4, false) +} + +func testRangeQuery(t *testing.T, testcase string, stateData *statedb.UpdateBatch, savepoint *version.Height, + ns string, rqi *kvrwset.RangeQueryInfo, expectedResult bool) { + t.Run(testcase, func(t *testing.T) { + testDBEnv := stateleveldb.NewTestVDBEnv(t) + defer testDBEnv.Cleanup() + db, err := testDBEnv.DBProvider.GetDBHandle("TestDB") + testutil.AssertNoError(t, err, "") + if stateData != nil { + db.ApplyUpdates(stateData, savepoint) + } + + itr, err := db.GetStateRangeScanIterator(ns, rqi.StartKey, rqi.EndKey) + testutil.AssertNoError(t, err, "") + validator := &rangeQueryResultsValidator{} + validator.init(rqi, itr) + isValid, err := validator.validate() + testutil.AssertNoError(t, err, "") + testutil.AssertEquals(t, isValid, expectedResult) + }) +} diff --git a/core/ledger/kvledger/txmgmt/validator/statebasedval/state_based_validator_test.go b/core/ledger/kvledger/txmgmt/validator/statebasedval/state_based_validator_test.go index 59c9ce2df47..12f2f4f1f3e 100644 --- a/core/ledger/kvledger/txmgmt/validator/statebasedval/state_based_validator_test.go +++ b/core/ledger/kvledger/txmgmt/validator/statebasedval/state_based_validator_test.go @@ -28,6 +28,7 @@ import ( "github.com/hyperledger/fabric/core/ledger/util" "github.com/hyperledger/fabric/protos/common" "github.com/hyperledger/fabric/protos/ledger/rwset/kvrwset" + "github.com/hyperledger/fabric/protos/peer" "github.com/spf13/viper" ) @@ -58,17 +59,17 @@ func TestValidator(t *testing.T) { rwsetBuilder1 := rwsetutil.NewRWSetBuilder() rwsetBuilder1.AddToReadSet("ns1", "key1", version.NewHeight(1, 0)) rwsetBuilder1.AddToReadSet("ns2", "key2", nil) - checkValidation(t, validator, []*rwsetutil.TxRwSet{rwsetBuilder1.GetTxReadWriteSet()}, []int{}) + checkValidation(t, validator, []*rwsetutil.TxRwSet{rwsetBuilder1.GetTxReadWriteSet()}, nil, []int{}) //rwset2 should not be valid rwsetBuilder2 := rwsetutil.NewRWSetBuilder() rwsetBuilder2.AddToReadSet("ns1", "key1", version.NewHeight(1, 1)) - checkValidation(t, validator, []*rwsetutil.TxRwSet{rwsetBuilder2.GetTxReadWriteSet()}, []int{0}) + checkValidation(t, validator, []*rwsetutil.TxRwSet{rwsetBuilder2.GetTxReadWriteSet()}, nil, []int{0}) //rwset3 should not be valid rwsetBuilder3 := rwsetutil.NewRWSetBuilder() rwsetBuilder3.AddToReadSet("ns1", "key1", nil) - checkValidation(t, validator, []*rwsetutil.TxRwSet{rwsetBuilder3.GetTxReadWriteSet()}, []int{0}) + checkValidation(t, validator, []*rwsetutil.TxRwSet{rwsetBuilder3.GetTxReadWriteSet()}, nil, []int{0}) // rwset4 and rwset5 within same block - rwset4 should be valid and makes rwset5 as invalid rwsetBuilder4 := rwsetutil.NewRWSetBuilder() @@ -78,7 +79,27 @@ func TestValidator(t *testing.T) { rwsetBuilder5 := rwsetutil.NewRWSetBuilder() rwsetBuilder5.AddToReadSet("ns1", "key1", version.NewHeight(1, 0)) checkValidation(t, validator, - []*rwsetutil.TxRwSet{rwsetBuilder4.GetTxReadWriteSet(), rwsetBuilder5.GetTxReadWriteSet()}, []int{1}) + []*rwsetutil.TxRwSet{rwsetBuilder4.GetTxReadWriteSet(), rwsetBuilder5.GetTxReadWriteSet()}, nil, []int{1}) +} + +func TestValidatorSkipInvalidTxs(t *testing.T) { + testDBEnv := stateleveldb.NewTestVDBEnv(t) + defer testDBEnv.Cleanup() + db, err := testDBEnv.DBProvider.GetDBHandle("TestDB") + testutil.AssertNoError(t, err, "") + validator := NewValidator(db) + + rwsetBuilder1 := rwsetutil.NewRWSetBuilder() + rwsetBuilder1.AddToWriteSet("ns1", "key1", []byte("value1")) + rwsetBuilder2 := rwsetutil.NewRWSetBuilder() + rwsetBuilder2.AddToWriteSet("ns1", "key2", []byte("value2")) + rwsetBuilder3 := rwsetutil.NewRWSetBuilder() + rwsetBuilder3.AddToWriteSet("ns1", "key3", []byte("value3")) + flags := util.NewTxValidationFlags(4) + flags.SetFlag(1, peer.TxValidationCode_BAD_CREATOR_SIGNATURE) + checkValidation(t, validator, + []*rwsetutil.TxRwSet{rwsetBuilder1.GetTxReadWriteSet(), rwsetBuilder2.GetTxReadWriteSet(), rwsetBuilder3.GetTxReadWriteSet()}, + flags, []int{1}) } func TestPhantomValidation(t *testing.T) { @@ -106,7 +127,7 @@ func TestPhantomValidation(t *testing.T) { rwsetutil.NewKVRead("key2", version.NewHeight(1, 1)), rwsetutil.NewKVRead("key3", version.NewHeight(1, 2))}) rwsetBuilder1.AddToRangeQuerySet("ns1", rqi1) - checkValidation(t, validator, []*rwsetutil.TxRwSet{rwsetBuilder1.GetTxReadWriteSet()}, []int{}) + checkValidation(t, validator, []*rwsetutil.TxRwSet{rwsetBuilder1.GetTxReadWriteSet()}, nil, []int{}) //rwset2 should not be valid - Version of key4 changed rwsetBuilder2 := rwsetutil.NewRWSetBuilder() @@ -116,7 +137,7 @@ func TestPhantomValidation(t *testing.T) { rwsetutil.NewKVRead("key3", version.NewHeight(1, 2)), rwsetutil.NewKVRead("key4", version.NewHeight(1, 2))}) rwsetBuilder2.AddToRangeQuerySet("ns1", rqi2) - checkValidation(t, validator, []*rwsetutil.TxRwSet{rwsetBuilder2.GetTxReadWriteSet()}, []int{0}) + checkValidation(t, validator, []*rwsetutil.TxRwSet{rwsetBuilder2.GetTxReadWriteSet()}, nil, []int{0}) //rwset3 should not be valid - simulate key3 got commited to db rwsetBuilder3 := rwsetutil.NewRWSetBuilder() @@ -125,7 +146,7 @@ func TestPhantomValidation(t *testing.T) { rwsetutil.NewKVRead("key2", version.NewHeight(1, 1)), rwsetutil.NewKVRead("key4", version.NewHeight(1, 3))}) rwsetBuilder3.AddToRangeQuerySet("ns1", rqi3) - checkValidation(t, validator, []*rwsetutil.TxRwSet{rwsetBuilder3.GetTxReadWriteSet()}, []int{0}) + checkValidation(t, validator, []*rwsetutil.TxRwSet{rwsetBuilder3.GetTxReadWriteSet()}, nil, []int{0}) // //Remove a key in rwset4 and rwset5 should become invalid rwsetBuilder4 := rwsetutil.NewRWSetBuilder() @@ -138,7 +159,7 @@ func TestPhantomValidation(t *testing.T) { rwsetutil.NewKVRead("key4", version.NewHeight(1, 3))}) rwsetBuilder5.AddToRangeQuerySet("ns1", rqi5) checkValidation(t, validator, []*rwsetutil.TxRwSet{ - rwsetBuilder4.GetTxReadWriteSet(), rwsetBuilder5.GetTxReadWriteSet()}, []int{1}) + rwsetBuilder4.GetTxReadWriteSet(), rwsetBuilder5.GetTxReadWriteSet()}, nil, []int{1}) //Add a key in rwset6 and rwset7 should become invalid rwsetBuilder6 := rwsetutil.NewRWSetBuilder() @@ -152,7 +173,7 @@ func TestPhantomValidation(t *testing.T) { rwsetutil.NewKVRead("key4", version.NewHeight(1, 3))}) rwsetBuilder7.AddToRangeQuerySet("ns1", rqi7) checkValidation(t, validator, []*rwsetutil.TxRwSet{ - rwsetBuilder6.GetTxReadWriteSet(), rwsetBuilder7.GetTxReadWriteSet()}, []int{1}) + rwsetBuilder6.GetTxReadWriteSet(), rwsetBuilder7.GetTxReadWriteSet()}, nil, []int{1}) } func TestPhantomHashBasedValidation(t *testing.T) { @@ -190,7 +211,7 @@ func TestPhantomHashBasedValidation(t *testing.T) { } rqi1.SetMerkelSummary(buildTestHashResults(t, 2, kvReadsDuringSimulation1)) rwsetBuilder1.AddToRangeQuerySet("ns1", rqi1) - checkValidation(t, validator, []*rwsetutil.TxRwSet{rwsetBuilder1.GetTxReadWriteSet()}, []int{}) + checkValidation(t, validator, []*rwsetutil.TxRwSet{rwsetBuilder1.GetTxReadWriteSet()}, nil, []int{}) rwsetBuilder2 := rwsetutil.NewRWSetBuilder() rqi2 := &kvrwset.RangeQueryInfo{StartKey: "key1", EndKey: "key9", ItrExhausted: false} @@ -207,10 +228,11 @@ func TestPhantomHashBasedValidation(t *testing.T) { } rqi2.SetMerkelSummary(buildTestHashResults(t, 2, kvReadsDuringSimulation2)) rwsetBuilder2.AddToRangeQuerySet("ns1", rqi2) - checkValidation(t, validator, []*rwsetutil.TxRwSet{rwsetBuilder2.GetTxReadWriteSet()}, []int{0}) + checkValidation(t, validator, []*rwsetutil.TxRwSet{rwsetBuilder2.GetTxReadWriteSet()}, nil, []int{0}) } -func checkValidation(t *testing.T, validator *Validator, rwsets []*rwsetutil.TxRwSet, invalidTxIndexes []int) { +func checkValidation(t *testing.T, validator *Validator, rwsets []*rwsetutil.TxRwSet, + alreadyMarkedFlags util.TxValidationFlags, expectedInvalidTxIndexes []int) { simulationResults := [][]byte{} for _, txRWS := range rwsets { sr, err := txRWS.ToProtoBytes() @@ -218,7 +240,7 @@ func checkValidation(t *testing.T, validator *Validator, rwsets []*rwsetutil.TxR simulationResults = append(simulationResults, sr) } block := testutil.ConstructBlock(t, 1, []byte("dummyPreviousHash"), simulationResults, false) - block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] = util.NewTxValidationFlags(len(block.Data.Data)) + block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] = alreadyMarkedFlags _, err := validator.ValidateAndPrepareBatch(block, true) txsFltr := util.TxValidationFlags(block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER]) invalidTxs := make([]int, 0) @@ -228,8 +250,8 @@ func checkValidation(t *testing.T, validator *Validator, rwsets []*rwsetutil.TxR } } testutil.AssertNoError(t, err, "") - testutil.AssertEquals(t, len(invalidTxs), len(invalidTxIndexes)) - testutil.AssertContainsAll(t, invalidTxs, invalidTxIndexes) + testutil.AssertEquals(t, len(invalidTxs), len(expectedInvalidTxIndexes)) + testutil.AssertContainsAll(t, invalidTxs, expectedInvalidTxIndexes) } func buildTestHashResults(t *testing.T, maxDegree int, kvReads []*kvrwset.KVRead) *kvrwset.QueryReadsMerkleSummary {