Skip to content

Commit 5196359

Browse files
committed
This commit fixes the bug reported at FAB-903
https://jira.hyperledger.org/browse/FAB-903 The bug was caused by an assumption in the code - that the minimum size of a block would be 8 bytes. This assumption is broken during testing when security is off and a block does not include any transaction Change-Id: Ib48ea3da2a661fbd8e4122573bac938c8b35f3d1 Signed-off-by: manish <[email protected]>
1 parent 18a44d0 commit 5196359

File tree

4 files changed

+140
-30
lines changed

4 files changed

+140
-30
lines changed

core/ledger/blkstorage/fsblkstorage/block_stream.go

+48-18
Original file line numberDiff line numberDiff line change
@@ -87,40 +87,65 @@ func (s *blockfileStream) nextBlockBytes() ([]byte, error) {
8787
return blockBytes, err
8888
}
8989

90+
// nextBlockBytesAndPlacementInfo returns bytes for the next block
91+
// along with the offset information in the block file.
92+
// An error `ErrUnexpectedEndOfBlockfile` is returned if a partial written data is detected
93+
// which is possible towards the tail of the file if a crash had taken place during appending of a block
9094
func (s *blockfileStream) nextBlockBytesAndPlacementInfo() ([]byte, *blockPlacementInfo, error) {
9195
var lenBytes []byte
9296
var err error
93-
if lenBytes, err = s.reader.Peek(8); err != nil {
94-
// reader.Peek raises io.EOF error if enough bytes not available
95-
if err == io.EOF {
96-
if len(lenBytes) > 0 {
97-
return nil, nil, ErrUnexpectedEndOfBlockfile
98-
}
99-
return nil, nil, nil
100-
}
97+
var fileInfo os.FileInfo
98+
moreContentAvailable := true
99+
100+
if fileInfo, err = s.file.Stat(); err != nil {
101101
return nil, nil, err
102102
}
103-
len, n := proto.DecodeVarint(lenBytes)
104-
if n == 0 {
105-
panic(fmt.Errorf("Error in decoding varint bytes"))
103+
if s.currentOffset == fileInfo.Size() {
104+
logger.Debugf("Finished reading file number [%d]", s.fileNum)
105+
return nil, nil, nil
106106
}
107-
if _, err = s.reader.Discard(n); err != nil {
107+
remainingBytes := fileInfo.Size() - s.currentOffset
108+
// Peek 8 or smaller number of bytes (if remaining bytes are less than 8)
109+
// Assumption is that a block size would be small enough to be represented in 8 bytes varint
110+
peekBytes := 8
111+
if remainingBytes < int64(peekBytes) {
112+
peekBytes = int(remainingBytes)
113+
moreContentAvailable = false
114+
}
115+
logger.Debugf("Remaining bytes=[%d], Going to peek [%d] bytes", remainingBytes, peekBytes)
116+
if lenBytes, err = s.reader.Peek(peekBytes); err != nil {
108117
return nil, nil, err
109118
}
110-
blockBytes := make([]byte, len)
111-
if _, err = io.ReadAtLeast(s.reader, blockBytes, int(len)); err != nil {
112-
// io.ReadAtLeast raises io.ErrUnexpectedEOF error if it is able to
113-
// read a fewer (non-zero) bytes and io.EOF is encountered
114-
if err == io.ErrUnexpectedEOF {
119+
length, n := proto.DecodeVarint(lenBytes)
120+
if n == 0 {
121+
// proto.DecodeVarint did not consume any byte at all which means that the bytes
122+
// representing the size of the block are partial bytes
123+
if !moreContentAvailable {
115124
return nil, nil, ErrUnexpectedEndOfBlockfile
116125
}
126+
panic(fmt.Errorf("Error in decoding varint bytes [%#v]", lenBytes))
127+
}
128+
bytesExpected := int64(n) + int64(length)
129+
if bytesExpected > remainingBytes {
130+
logger.Debugf("At least [%d] bytes expected. Remaining bytes = [%d]. Returning with error [%s]",
131+
bytesExpected, remainingBytes, ErrUnexpectedEndOfBlockfile)
132+
return nil, nil, ErrUnexpectedEndOfBlockfile
133+
}
134+
// skip the bytes representing the block size
135+
if _, err = s.reader.Discard(n); err != nil {
136+
return nil, nil, err
137+
}
138+
blockBytes := make([]byte, length)
139+
if _, err = io.ReadAtLeast(s.reader, blockBytes, int(length)); err != nil {
140+
logger.Debugf("Error while trying to read [%d] bytes from fileNum [%d]: %s", length, s.fileNum, err)
117141
return nil, nil, err
118142
}
119143
blockPlacementInfo := &blockPlacementInfo{
120144
fileNum: s.fileNum,
121145
blockStartOffset: s.currentOffset,
122146
blockBytesOffset: s.currentOffset + int64(n)}
123-
s.currentOffset += int64(n) + int64(len)
147+
s.currentOffset += int64(n) + int64(length)
148+
logger.Debugf("Returning blockbytes - length=[%d], placementInfo={%s}", len(blockBytes), blockPlacementInfo)
124149
return blockBytes, blockPlacementInfo, nil
125150
}
126151

@@ -179,3 +204,8 @@ func (s *blockStream) nextBlockBytesAndPlacementInfo() ([]byte, *blockPlacementI
179204
func (s *blockStream) close() error {
180205
return s.currentFileStream.close()
181206
}
207+
208+
func (i *blockPlacementInfo) String() string {
209+
return fmt.Sprintf("fileNum=[%d], startOffset=[%d], bytesOffset=[%d]",
210+
i.fileNum, i.blockStartOffset, i.blockBytesOffset)
211+
}

core/ledger/blkstorage/fsblkstorage/blockfile_mgr.go

+16-9
Original file line numberDiff line numberDiff line change
@@ -449,25 +449,32 @@ func (mgr *blockfileMgr) saveCurrentInfo(i *checkpointInfo, flush bool) error {
449449
return nil
450450
}
451451

452+
// scanForLastCompleteBlock scan a given block file and detects the last offset in the file
453+
// after which there may lie a block partially written (towards the end of the file in a crash scenario).
452454
func scanForLastCompleteBlock(rootDir string, fileNum int, startingOffset int64) (int64, int, error) {
453455
numBlocks := 0
454-
blockStream, err := newBlockfileStream(rootDir, fileNum, startingOffset)
455-
if err != nil {
456-
return 0, 0, err
456+
blockStream, errOpen := newBlockfileStream(rootDir, fileNum, startingOffset)
457+
if errOpen != nil {
458+
return 0, 0, errOpen
457459
}
458460
defer blockStream.close()
461+
var errRead error
462+
var blockBytes []byte
459463
for {
460-
blockBytes, err := blockStream.nextBlockBytes()
461-
if blockBytes == nil || err == ErrUnexpectedEndOfBlockfile {
462-
logger.Debugf(`scanForLastCompleteBlock(): error=[%s].
463-
The error may happen if a crash has happened during block appending.
464-
Returning current offset as a last complete block's end offset`, err)
464+
blockBytes, errRead = blockStream.nextBlockBytes()
465+
if blockBytes == nil || errRead != nil {
465466
break
466467
}
467468
numBlocks++
468469
}
470+
if errRead == ErrUnexpectedEndOfBlockfile {
471+
logger.Debugf(`Error:%s
472+
The error may happen if a crash has happened during block appending.
473+
Resetting error to nil and returning current offset as a last complete block's end offset`, errRead)
474+
errRead = nil
475+
}
469476
logger.Debugf("scanForLastCompleteBlock(): last complete block ends at offset=[%d]", blockStream.currentOffset)
470-
return blockStream.currentOffset, numBlocks, err
477+
return blockStream.currentOffset, numBlocks, errRead
471478
}
472479

473480
// checkpointInfo
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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 fsblkstorage
18+
19+
import (
20+
"os"
21+
"testing"
22+
23+
"github.com/hyperledger/fabric/core/ledger/testutil"
24+
"github.com/hyperledger/fabric/core/ledger/util"
25+
"github.com/hyperledger/fabric/protos"
26+
)
27+
28+
func TestBlockFileScanSmallTxOnly(t *testing.T) {
29+
env := newTestEnv(t)
30+
defer env.Cleanup()
31+
blkfileMgrWrapper := newTestBlockfileWrapper(t, env)
32+
blocks := []*protos.Block2{}
33+
blocks = append(blocks, testutil.ConstructTestBlock(t, 0, 0, 0))
34+
blocks = append(blocks, testutil.ConstructTestBlock(t, 0, 0, 0))
35+
blocks = append(blocks, testutil.ConstructTestBlock(t, 0, 0, 0))
36+
blkfileMgrWrapper.addBlocks(blocks)
37+
blkfileMgrWrapper.close()
38+
39+
filePath := deriveBlockfilePath(env.conf.blockfilesDir, 0)
40+
_, fileSize, err := util.FileExists(filePath)
41+
testutil.AssertNoError(t, err, "")
42+
43+
endOffsetLastBlock, numBlocks, err := scanForLastCompleteBlock(env.conf.blockfilesDir, 0, 0)
44+
testutil.AssertNoError(t, err, "")
45+
testutil.AssertEquals(t, numBlocks, len(blocks))
46+
testutil.AssertEquals(t, endOffsetLastBlock, fileSize)
47+
}
48+
49+
func TestBlockFileScanSmallTxLastTxIncomplete(t *testing.T) {
50+
env := newTestEnv(t)
51+
defer env.Cleanup()
52+
blkfileMgrWrapper := newTestBlockfileWrapper(t, env)
53+
blocks := []*protos.Block2{}
54+
blocks = append(blocks, testutil.ConstructTestBlock(t, 0, 0, 0))
55+
blocks = append(blocks, testutil.ConstructTestBlock(t, 0, 0, 0))
56+
blocks = append(blocks, testutil.ConstructTestBlock(t, 0, 0, 0))
57+
blkfileMgrWrapper.addBlocks(blocks)
58+
blkfileMgrWrapper.close()
59+
60+
filePath := deriveBlockfilePath(env.conf.blockfilesDir, 0)
61+
_, fileSize, err := util.FileExists(filePath)
62+
testutil.AssertNoError(t, err, "")
63+
64+
file, err := os.OpenFile(filePath, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0660)
65+
defer file.Close()
66+
testutil.AssertNoError(t, err, "")
67+
err = file.Truncate(fileSize - 1)
68+
testutil.AssertNoError(t, err, "")
69+
70+
_, numBlocks, err := scanForLastCompleteBlock(env.conf.blockfilesDir, 0, 0)
71+
testutil.AssertNoError(t, err, "")
72+
testutil.AssertEquals(t, numBlocks, len(blocks)-1)
73+
}

core/ledger/testutil/test_helper.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,16 @@ func ConstructBlockForSimulationResults(t *testing.T, simulationResults [][]byte
3838
func ConstructTestBlocks(t *testing.T, numBlocks int) []*protos.Block2 {
3939
blocks := []*protos.Block2{}
4040
for i := 0; i < numBlocks; i++ {
41-
blocks = append(blocks, ConstructTestBlock(t, 10, i*10))
41+
blocks = append(blocks, ConstructTestBlock(t, 10, 100, i*10))
4242
}
4343
return blocks
4444
}
4545

4646
// ConstructTestBlock constructs a block with 'numTx' number of transactions for testing
47-
func ConstructTestBlock(t *testing.T, numTx int, startingTxID int) *protos.Block2 {
47+
func ConstructTestBlock(t *testing.T, numTx int, txSize int, startingTxID int) *protos.Block2 {
4848
txs := []*protos.Transaction2{}
4949
for i := startingTxID; i < numTx+startingTxID; i++ {
50-
tx, _ := putils.CreateTx(protos.Header_CHAINCODE, []byte{}, []byte{}, ConstructRandomBytes(t, 100), []*protos.Endorsement{})
50+
tx, _ := putils.CreateTx(protos.Header_CHAINCODE, []byte{}, []byte{}, ConstructRandomBytes(t, txSize), []*protos.Endorsement{})
5151
txs = append(txs, tx)
5252
}
5353
return newBlock(txs)

0 commit comments

Comments
 (0)