Skip to content

Commit 921b896

Browse files
manish-sethimastersingh24
authored andcommitted
Fix historydb issue for keys containing nil bytes
historydb code assumes that the keys does not contain a nil byte. In the presence of such keys, a historydb query can behave in an unpredictable way that ranges from including the wrong results to a panic crash. The main issue is that when the keys have nil bytes, additional keys fall in the results of the range query that is formed for scanning the history of a key. This CR provides a simple fix in which these non-relevant results will be skipped. Though, ideally, the keys layout should be such that the non-relevant results of a scan should be zero, however, that solution requires a change in key formats and will require a rebuild of the historydb and hence not suitable for an interim release and will potentially be provided with 2.0 release. done #FAB-11244 Change-Id: I843f1babc2673d3a38c47e9e04a660203180a3de Signed-off-by: manish <manish.sethi@gmail.com>
1 parent abf24aa commit 921b896

File tree

4 files changed

+138
-43
lines changed

4 files changed

+138
-43
lines changed

core/ledger/kvledger/history/historydb/histmgr_helper.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,18 @@ import (
2222
"github.com/hyperledger/fabric/common/ledger/util"
2323
)
2424

25-
var compositeKeySep = []byte{0x00}
25+
// CompositeKeySep is a nil byte used as a separator between different components of a composite key
26+
var CompositeKeySep = []byte{0x00}
2627

2728
//ConstructCompositeHistoryKey builds the History Key of namespace~key~blocknum~trannum
2829
// using an order preserving encoding so that history query results are ordered by height
2930
func ConstructCompositeHistoryKey(ns string, key string, blocknum uint64, trannum uint64) []byte {
3031

3132
var compositeKey []byte
3233
compositeKey = append(compositeKey, []byte(ns)...)
33-
compositeKey = append(compositeKey, compositeKeySep...)
34+
compositeKey = append(compositeKey, CompositeKeySep...)
3435
compositeKey = append(compositeKey, []byte(key)...)
35-
compositeKey = append(compositeKey, compositeKeySep...)
36+
compositeKey = append(compositeKey, CompositeKeySep...)
3637
compositeKey = append(compositeKey, util.EncodeOrderPreservingVarUint64(blocknum)...)
3738
compositeKey = append(compositeKey, util.EncodeOrderPreservingVarUint64(trannum)...)
3839

@@ -44,9 +45,9 @@ func ConstructCompositeHistoryKey(ns string, key string, blocknum uint64, trannu
4445
func ConstructPartialCompositeHistoryKey(ns string, key string, endkey bool) []byte {
4546
var compositeKey []byte
4647
compositeKey = append(compositeKey, []byte(ns)...)
47-
compositeKey = append(compositeKey, compositeKeySep...)
48+
compositeKey = append(compositeKey, CompositeKeySep...)
4849
compositeKey = append(compositeKey, []byte(key)...)
49-
compositeKey = append(compositeKey, compositeKeySep...)
50+
compositeKey = append(compositeKey, CompositeKeySep...)
5051
if endkey {
5152
compositeKey = append(compositeKey, []byte{0xff}...)
5253
}

core/ledger/kvledger/history/historydb/histmgr_helper_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222
"github.com/hyperledger/fabric/common/ledger/testutil"
2323
)
2424

25-
var strKeySep = string(compositeKeySep)
25+
var strKeySep = string(CompositeKeySep)
2626

2727
func TestConstructCompositeKey(t *testing.T) {
2828
compositeKey := ConstructCompositeHistoryKey("ns1", "key1", 1, 1)

core/ledger/kvledger/history/historydb/historyleveldb/historyleveldb_query_executer.go

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ SPDX-License-Identifier: Apache-2.0
77
package historyleveldb
88

99
import (
10+
"bytes"
11+
1012
commonledger "github.com/hyperledger/fabric/common/ledger"
1113
"github.com/hyperledger/fabric/common/ledger/blkstorage"
1214
"github.com/hyperledger/fabric/common/ledger/util"
@@ -58,32 +60,53 @@ func newHistoryScanner(compositePartialKey []byte, namespace string, key string,
5860
}
5961

6062
func (scanner *historyScanner) Next() (commonledger.QueryResult, error) {
61-
if !scanner.dbItr.Next() {
62-
return nil, nil
63-
}
64-
historyKey := scanner.dbItr.Key() // history key is in the form namespace~key~blocknum~trannum
65-
66-
// SplitCompositeKey(namespace~key~blocknum~trannum, namespace~key~) will return the blocknum~trannum in second position
67-
_, blockNumTranNumBytes := historydb.SplitCompositeHistoryKey(historyKey, scanner.compositePartialKey)
68-
blockNum, bytesConsumed := util.DecodeOrderPreservingVarUint64(blockNumTranNumBytes[0:])
69-
tranNum, _ := util.DecodeOrderPreservingVarUint64(blockNumTranNumBytes[bytesConsumed:])
70-
logger.Debugf("Found history record for namespace:%s key:%s at blockNumTranNum %v:%v\n",
71-
scanner.namespace, scanner.key, blockNum, tranNum)
72-
73-
// Get the transaction from block storage that is associated with this history record
74-
tranEnvelope, err := scanner.blockStore.RetrieveTxByBlockNumTranNum(blockNum, tranNum)
75-
if err != nil {
76-
return nil, err
77-
}
78-
79-
// Get the txid, key write value, timestamp, and delete indicator associated with this transaction
80-
queryResult, err := getKeyModificationFromTran(tranEnvelope, scanner.namespace, scanner.key)
81-
if err != nil {
82-
return nil, err
63+
for {
64+
if !scanner.dbItr.Next() {
65+
return nil, nil
66+
}
67+
historyKey := scanner.dbItr.Key() // history key is in the form namespace~key~blocknum~trannum
68+
69+
// SplitCompositeKey(namespace~key~blocknum~trannum, namespace~key~) will return the blocknum~trannum in second position
70+
_, blockNumTranNumBytes := historydb.SplitCompositeHistoryKey(historyKey, scanner.compositePartialKey)
71+
72+
// check that blockNumTranNumBytes does not contain a nil byte (FAB-11244) - except the last byte.
73+
// if this contains a nil byte that indicate that its a different key other than the one we are
74+
// scanning the history for. However, the last byte can be nil even for the valid key (indicating the transaction numer being zero)
75+
// This is because, if 'blockNumTranNumBytes' really is the suffix of the desired key - only possibility of this containing a nil byte
76+
// is the last byte when the transaction number in blockNumTranNumBytes is zero).
77+
// On the other hand, if 'blockNumTranNumBytes' really is NOT the suffix of the desired key, then this has to be a prefix
78+
// of some other key (other than the desired key) and in this case, there has to be at least one nil byte (other than the last byte),
79+
// for the 'last' CompositeKeySep in the composite key
80+
// Take an example of two keys "key" and "key\x00" in a namespace ns. The entries for these keys will be
81+
// of type "ns-\x00-key-\x00-blkNumTranNumBytes" and ns-\x00-key-\x00-\x00-blkNumTranNumBytes respectively.
82+
// "-" in above examples are just for readability. Further, when scanning the range
83+
// {ns-\x00-key-\x00 - ns-\x00-key-xff} for getting the history for <ns, key>, the entry for the other key
84+
// falls in the range and needs to be ignored
85+
if bytes.Contains(blockNumTranNumBytes[:len(blockNumTranNumBytes)-1], historydb.CompositeKeySep) {
86+
logger.Debugf("Some other key [%#v] found in the range while scanning history for key [%#v]. Skipping...",
87+
historyKey, scanner.key)
88+
continue
89+
}
90+
blockNum, bytesConsumed := util.DecodeOrderPreservingVarUint64(blockNumTranNumBytes[0:])
91+
tranNum, _ := util.DecodeOrderPreservingVarUint64(blockNumTranNumBytes[bytesConsumed:])
92+
logger.Debugf("Found history record for namespace:%s key:%s at blockNumTranNum %v:%v\n",
93+
scanner.namespace, scanner.key, blockNum, tranNum)
94+
95+
// Get the transaction from block storage that is associated with this history record
96+
tranEnvelope, err := scanner.blockStore.RetrieveTxByBlockNumTranNum(blockNum, tranNum)
97+
if err != nil {
98+
return nil, err
99+
}
100+
101+
// Get the txid, key write value, timestamp, and delete indicator associated with this transaction
102+
queryResult, err := getKeyModificationFromTran(tranEnvelope, scanner.namespace, scanner.key)
103+
if err != nil {
104+
return nil, err
105+
}
106+
logger.Debugf("Found historic key value for namespace:%s key:%s from transaction %s\n",
107+
scanner.namespace, scanner.key, queryResult.(*queryresult.KeyModification).TxId)
108+
return queryResult, nil
83109
}
84-
logger.Debugf("Found historic key value for namespace:%s key:%s from transaction %s\n",
85-
scanner.namespace, scanner.key, queryResult.(*queryresult.KeyModification).TxId)
86-
return queryResult, nil
87110
}
88111

89112
func (scanner *historyScanner) Close() {

core/ledger/kvledger/history/historydb/historyleveldb/historyleveldb_test.go

Lines changed: 83 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,7 @@
11
/*
2-
Copyright IBM Corp. 2016 All Rights Reserved.
2+
Copyright IBM Corp. All Rights Reserved.
33
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.
4+
SPDX-License-Identifier: Apache-2.0
155
*/
166

177
package historyleveldb
@@ -22,6 +12,7 @@ import (
2212
"testing"
2313

2414
configtxtest "github.com/hyperledger/fabric/common/configtx/test"
15+
"github.com/hyperledger/fabric/common/flogging"
2516
"github.com/hyperledger/fabric/common/ledger/testutil"
2617
util2 "github.com/hyperledger/fabric/common/util"
2718
"github.com/hyperledger/fabric/core/ledger"
@@ -34,6 +25,8 @@ import (
3425

3526
func TestMain(m *testing.M) {
3627
viper.Set("peer.fileSystemPath", "/tmp/fabric/ledgertests/kvledger/history/historydb/historyleveldb")
28+
flogging.SetModuleLevel("leveldbhelper", "debug")
29+
flogging.SetModuleLevel("historyleveldb", "debug")
3730
os.Exit(m.Run())
3831
}
3932

@@ -266,3 +259,81 @@ func TestGenesisBlockNoError(t *testing.T) {
266259
err = env.testHistoryDB.Commit(block)
267260
testutil.AssertNoError(t, err, "")
268261
}
262+
263+
// TestHistoryWithKeyContainingNilBytes tests historydb when keys contains nil bytes (FAB-11244) -
264+
// which happens to be used as a separator in the composite keys that is formed for the entries in the historydb
265+
func TestHistoryWithKeyContainingNilBytes(t *testing.T) {
266+
env := newTestHistoryEnv(t)
267+
defer env.cleanup()
268+
provider := env.testBlockStorageEnv.provider
269+
ledger1id := "ledger1"
270+
store1, err := provider.OpenBlockStore(ledger1id)
271+
testutil.AssertNoError(t, err, "Error upon provider.OpenBlockStore()")
272+
defer store1.Shutdown()
273+
274+
bg, gb := testutil.NewBlockGenerator(t, ledger1id, false)
275+
testutil.AssertNoError(t, store1.AddBlock(gb), "")
276+
testutil.AssertNoError(t, env.testHistoryDB.Commit(gb), "")
277+
278+
//block1
279+
txid := util2.GenerateUUID()
280+
simulator, _ := env.txmgr.NewTxSimulator(txid)
281+
simulator.SetState("ns1", "key", []byte("value1")) // add a key <key> that contains no nil byte
282+
simulator.Done()
283+
simRes, _ := simulator.GetTxSimulationResults()
284+
pubSimResBytes, _ := simRes.GetPubSimulationBytes()
285+
block1 := bg.NextBlock([][]byte{pubSimResBytes})
286+
err = store1.AddBlock(block1)
287+
testutil.AssertNoError(t, err, "")
288+
err = env.testHistoryDB.Commit(block1)
289+
testutil.AssertNoError(t, err, "")
290+
291+
//block2 tran1
292+
simulationResults := [][]byte{}
293+
txid = util2.GenerateUUID()
294+
simulator, _ = env.txmgr.NewTxSimulator(txid)
295+
simulator.SetState("ns1", "key", []byte("value2")) // add another value for the key <key>
296+
simulator.Done()
297+
simRes, _ = simulator.GetTxSimulationResults()
298+
pubSimResBytes, _ = simRes.GetPubSimulationBytes()
299+
simulationResults = append(simulationResults, pubSimResBytes)
300+
301+
//block2 tran2
302+
txid2 := util2.GenerateUUID()
303+
simulator2, _ := env.txmgr.NewTxSimulator(txid2)
304+
// add another key <key\x00\x01\x01\x15> that contains a nil byte - such that when a range query is formed using old format, this key falls in the range
305+
simulator2.SetState("ns1", "key\x00\x01\x01\x15", []byte("dummyVal1"))
306+
simulator2.SetState("ns1", "\x00key\x00\x01\x01\x15", []byte("dummyVal2"))
307+
simulator2.Done()
308+
simRes2, _ := simulator2.GetTxSimulationResults()
309+
pubSimResBytes2, _ := simRes2.GetPubSimulationBytes()
310+
simulationResults = append(simulationResults, pubSimResBytes2)
311+
block2 := bg.NextBlock(simulationResults)
312+
err = store1.AddBlock(block2)
313+
testutil.AssertNoError(t, err, "")
314+
err = env.testHistoryDB.Commit(block2)
315+
testutil.AssertNoError(t, err, "")
316+
317+
qhistory, err := env.testHistoryDB.NewHistoryQueryExecutor(store1)
318+
testutil.AssertNoError(t, err, "Error upon NewHistoryQueryExecutor")
319+
testutilVerifyResults(t, qhistory, "ns1", "key", []string{"value1", "value2"})
320+
testutilVerifyResults(t, qhistory, "ns1", "key\x00\x01\x01\x15", []string{"dummyVal1"})
321+
testutilVerifyResults(t, qhistory, "ns1", "\x00key\x00\x01\x01\x15", []string{"dummyVal2"})
322+
}
323+
324+
func testutilVerifyResults(t *testing.T, hqe ledger.HistoryQueryExecutor, ns, key string, expectedVals []string) {
325+
itr, err := hqe.GetHistoryForKey(ns, key)
326+
testutil.AssertNoError(t, err, "Error upon GetHistoryForKey()")
327+
retrievedVals := []string{}
328+
for {
329+
kmod, _ := itr.Next()
330+
if kmod == nil {
331+
break
332+
}
333+
txid := kmod.(*queryresult.KeyModification).TxId
334+
retrievedValue := string(kmod.(*queryresult.KeyModification).Value)
335+
retrievedVals = append(retrievedVals, retrievedValue)
336+
t.Logf("Retrieved history record at TxId=%s with value %s", txid, retrievedValue)
337+
}
338+
testutil.AssertEquals(t, retrievedVals, expectedVals)
339+
}

0 commit comments

Comments
 (0)