github.com/iotexproject/iotex-core@v1.14.1-rc1/blockchain/filedao/filedao_test.go (about)

     1  // Copyright (c) 2020 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package filedao
     7  
     8  import (
     9  	"context"
    10  	"encoding/hex"
    11  	"os"
    12  	"testing"
    13  
    14  	"github.com/iotexproject/go-pkgs/crypto"
    15  	"github.com/iotexproject/go-pkgs/hash"
    16  	"github.com/stretchr/testify/require"
    17  
    18  	"github.com/iotexproject/iotex-core/blockchain/block"
    19  	"github.com/iotexproject/iotex-core/db"
    20  	"github.com/iotexproject/iotex-core/pkg/compress"
    21  )
    22  
    23  func TestChecksumNamespaceAndKeys(t *testing.T) {
    24  	r := require.New(t)
    25  
    26  	a := []hash.Hash256{
    27  		// filedao
    28  		hash.BytesToHash256([]byte(_blockHashHeightMappingNS)),
    29  		hash.BytesToHash256([]byte(_systemLogNS)),
    30  		hash.BytesToHash256(_topHeightKey),
    31  		hash.BytesToHash256(_topHashKey),
    32  		hash.BytesToHash256(_hashPrefix),
    33  		// filedao_legacy
    34  		hash.BytesToHash256([]byte(_blockNS)),
    35  		hash.BytesToHash256([]byte(_blockHeaderNS)),
    36  		hash.BytesToHash256([]byte(_blockBodyNS)),
    37  		hash.BytesToHash256([]byte(_blockFooterNS)),
    38  		hash.BytesToHash256([]byte(_receiptsNS)),
    39  		hash.BytesToHash256(_heightPrefix),
    40  		hash.BytesToHash256(_heightToFileBucket),
    41  		// filedao_v2
    42  		hash.BytesToHash256([]byte(FileV2)),
    43  		hash.BytesToHash256([]byte{16}),
    44  		hash.BytesToHash256([]byte(compress.Gzip)),
    45  		hash.BytesToHash256([]byte(compress.Snappy)),
    46  		hash.BytesToHash256([]byte(_hashDataNS)),
    47  		hash.BytesToHash256([]byte(_blockDataNS)),
    48  		hash.BytesToHash256([]byte(_headerDataNs)),
    49  		hash.BytesToHash256(_fileHeaderKey),
    50  	}
    51  
    52  	checksum := crypto.NewMerkleTree(a)
    53  	r.NotNil(checksum)
    54  	h := checksum.HashTree()
    55  	r.Equal("18747e1ac5364ce3f398e03092f159121b55166449657f65ba1f9243e8830391", hex.EncodeToString(h[:]))
    56  }
    57  
    58  func TestReadFileHeader(t *testing.T) {
    59  	r := require.New(t)
    60  
    61  	cfg := db.DefaultConfig
    62  	cfg.DbPath = "./filedao_v2.db"
    63  
    64  	// test non-existing file
    65  	_, err := readFileHeader(cfg.DbPath, FileLegacyMaster)
    66  	r.Equal(ErrFileNotExist, err)
    67  	_, err = readFileHeader(cfg.DbPath, FileAll)
    68  	r.Equal(ErrFileNotExist, err)
    69  
    70  	// empty legacy file is invalid
    71  	deser := block.NewDeserializer(_defaultEVMNetworkID)
    72  	legacy, err := newFileDAOLegacy(cfg, deser)
    73  	r.NoError(err)
    74  	ctx := context.Background()
    75  	r.NoError(legacy.Start(ctx))
    76  	r.NoError(legacy.Stop(ctx))
    77  	_, err = readFileHeader(cfg.DbPath, FileLegacyMaster)
    78  	r.Equal(ErrFileInvalid, err)
    79  	_, err = readFileHeader(cfg.DbPath, FileAll)
    80  	r.Equal(ErrFileInvalid, err)
    81  
    82  	// commit 1 block to make it a valid legacy file
    83  	r.NoError(legacy.Start(ctx))
    84  	builder := block.NewTestingBuilder()
    85  	blk := createTestingBlock(builder, 1, hash.ZeroHash256)
    86  	r.NoError(legacy.PutBlock(ctx, blk))
    87  	height, err := legacy.Height()
    88  	r.NoError(err)
    89  	r.EqualValues(1, height)
    90  	r.NoError(legacy.Stop(ctx))
    91  
    92  	type testCheckFile struct {
    93  		checkType, version string
    94  		err                error
    95  	}
    96  
    97  	// test valid legacy master file
    98  	test1 := []testCheckFile{
    99  		{FileLegacyMaster, FileLegacyMaster, nil},
   100  		{FileLegacyAuxiliary, FileLegacyMaster, nil},
   101  		{FileV2, "", ErrFileInvalid},
   102  		{FileAll, FileLegacyMaster, nil},
   103  	}
   104  	for _, v := range test1 {
   105  		h, err := readFileHeader(cfg.DbPath, v.checkType)
   106  		r.Equal(v.err, err)
   107  		if err == nil {
   108  			r.Equal(v.version, h.Version)
   109  		}
   110  	}
   111  	os.RemoveAll(cfg.DbPath)
   112  	// test valid v2 master file
   113  	r.NoError(createNewV2File(1, cfg, deser))
   114  	defer os.RemoveAll(cfg.DbPath)
   115  
   116  	test2 := []testCheckFile{
   117  		{FileLegacyMaster, "", ErrFileInvalid},
   118  		{FileLegacyAuxiliary, "", ErrFileInvalid},
   119  		{FileV2, FileV2, nil},
   120  		{FileAll, FileV2, nil},
   121  	}
   122  	for _, v := range test2 {
   123  		h, err := readFileHeader(cfg.DbPath, v.checkType)
   124  		r.Equal(v.err, err)
   125  		if err == nil {
   126  			r.Equal(v.version, h.Version)
   127  		}
   128  	}
   129  
   130  	r.Panics(func() { readFileHeader(cfg.DbPath, "") })
   131  }
   132  
   133  func TestNewFileDAOSplitV2(t *testing.T) {
   134  	r := require.New(t)
   135  
   136  	cfg := db.DefaultConfig
   137  	cfg.V2BlocksToSplitDB = 10
   138  	cfg.DbPath = "./filedao_v2.db"
   139  	defer os.RemoveAll(cfg.DbPath)
   140  
   141  	// test non-existing file
   142  	_, err := readFileHeader(cfg.DbPath, FileAll)
   143  	r.Equal(ErrFileNotExist, err)
   144  
   145  	// test empty db file, this will create new v2 file
   146  	deser := block.NewDeserializer(_defaultEVMNetworkID)
   147  	fd, err := NewFileDAO(cfg, deser)
   148  	r.NoError(err)
   149  	r.NotNil(fd)
   150  	h, err := readFileHeader(cfg.DbPath, FileAll)
   151  	r.NoError(err)
   152  	r.Equal(FileV2, h.Version)
   153  	ctx := context.Background()
   154  	r.NoError(fd.Start(ctx))
   155  	fm := fd.(*fileDAO)
   156  	r.EqualValues(0, fm.topIndex)
   157  	r.EqualValues(1, fm.splitHeight)
   158  	r.NoError(testCommitBlocks(t, fd, 1, 10, hash.ZeroHash256))
   159  	r.EqualValues(0, fm.topIndex)
   160  	r.EqualValues(1, fm.splitHeight)
   161  	testVerifyChainDB(t, fd, 1, 10)
   162  
   163  	// block 11 will split a new file
   164  	// and test PutBlock() fail right after splitting file
   165  	testFailFd := newTestFailPutBlock(fm, 11)
   166  	r.Equal(ErrInvalidTipHeight, testCommitBlocks(t, testFailFd, 11, 11, hash.ZeroHash256))
   167  	r.EqualValues(1, fm.topIndex)
   168  	r.EqualValues(11, fm.splitHeight)
   169  	// commit correct block 11 won't split file again
   170  	r.NoError(testCommitBlocks(t, fd, 11, 11, hash.ZeroHash256))
   171  	r.EqualValues(1, fm.topIndex)
   172  	r.EqualValues(11, fm.splitHeight)
   173  	r.NoError(testCommitBlocks(t, fd, 12, 25, hash.ZeroHash256))
   174  	r.EqualValues(2, fm.topIndex)
   175  	r.EqualValues(21, fm.splitHeight)
   176  	testVerifyChainDB(t, fd, 1, 25)
   177  	r.NoError(fd.Stop(ctx))
   178  	top, files := checkAuxFiles(cfg.DbPath, FileV2)
   179  	r.EqualValues(2, top)
   180  	r.Equal(2, len(files))
   181  	file1 := kthAuxFileName("./filedao_v2.db", 1)
   182  	file2 := kthAuxFileName("./filedao_v2.db", 2)
   183  	r.Equal(files[0], file1)
   184  	r.Equal(files[1], file2)
   185  	os.RemoveAll(file1)
   186  	os.RemoveAll(file2)
   187  }
   188  
   189  func TestNewFileDAOSplitLegacy(t *testing.T) {
   190  	r := require.New(t)
   191  
   192  	cfg := db.DefaultConfig
   193  	cfg.DbPath = "./filedao_v2.db"
   194  	defer os.RemoveAll(cfg.DbPath)
   195  
   196  	cfg.SplitDBHeight = 5
   197  	cfg.SplitDBSizeMB = 20
   198  
   199  	deser := block.NewDeserializer(_defaultEVMNetworkID)
   200  	fd, err := newFileDAOLegacy(cfg, deser)
   201  	r.NoError(err)
   202  	ctx := context.Background()
   203  	r.NoError(fd.Start(ctx))
   204  	r.NoError(testCommitBlocks(t, fd, 1, 10, hash.ZeroHash256))
   205  	testVerifyChainDB(t, fd, 1, 10)
   206  	r.NoError(fd.Stop(ctx))
   207  	// block 1~5 in default file.db, block 6~10 in file-000000001.db
   208  	file1 := kthAuxFileName("./filedao_v2.db", 1)
   209  	defer os.RemoveAll(file1)
   210  
   211  	// set FileDAO to split at height 15, 30 and 40
   212  	cfg.V2BlocksToSplitDB = 15
   213  
   214  	fd, err = NewFileDAO(cfg, deser)
   215  	r.NoError(err)
   216  	r.NoError(fd.Start(ctx))
   217  	fm := fd.(*fileDAO)
   218  	r.EqualValues(1, fm.topIndex)
   219  	r.EqualValues(1, fm.splitHeight)
   220  	r.NoError(testCommitBlocks(t, fd, 11, 28, hash.ZeroHash256))
   221  	r.EqualValues(2, fm.topIndex)
   222  	r.EqualValues(16, fm.splitHeight)
   223  	// skip block 29, commit block 30 which is a split height
   224  	r.Equal(ErrInvalidTipHeight, testCommitBlocks(t, fd, 30, 55, hash.ZeroHash256))
   225  	r.NoError(testCommitBlocks(t, fd, 29, 55, hash.ZeroHash256))
   226  	r.EqualValues(4, fm.topIndex)
   227  	r.EqualValues(46, fm.splitHeight)
   228  	testVerifyChainDB(t, fd, 1, 55)
   229  	r.NoError(fd.Stop(ctx))
   230  
   231  	// now we should have:
   232  	// block 1~5 in legacy file.db
   233  	// block 6~15 in legacy file-000000001.db
   234  	// block 16~30 in v2 file-000000002.db
   235  	// block 31~45 in v2 file-000000003.db
   236  	// block 46~55 in v2 file-000000004.db
   237  	file2 := kthAuxFileName("./filedao_v2.db", 2)
   238  	file3 := kthAuxFileName("./filedao_v2.db", 3)
   239  	file4 := kthAuxFileName("./filedao_v2.db", 4)
   240  	defer os.RemoveAll(file2)
   241  	defer os.RemoveAll(file3)
   242  	defer os.RemoveAll(file4)
   243  	h, err := readFileHeader(cfg.DbPath, FileAll)
   244  	r.NoError(err)
   245  	r.Equal(FileLegacyMaster, h.Version)
   246  	h, err = readFileHeader(file1, FileLegacyAuxiliary)
   247  	r.NoError(err)
   248  	r.Equal(FileLegacyAuxiliary, h.Version)
   249  	h, err = readFileHeader(file2, FileV2)
   250  	r.NoError(err)
   251  	r.Equal(FileV2, h.Version)
   252  	h, err = readFileHeader(file3, FileV2)
   253  	r.NoError(err)
   254  	r.Equal(FileV2, h.Version)
   255  	h, err = readFileHeader(file4, FileV2)
   256  	r.NoError(err)
   257  	r.Equal(FileV2, h.Version)
   258  	top, files := checkAuxFiles(cfg.DbPath, FileLegacyAuxiliary)
   259  	r.EqualValues(1, top)
   260  	r.Equal(1, len(files))
   261  	r.Equal(files[0], file1)
   262  	top, files = checkAuxFiles(cfg.DbPath, FileV2)
   263  	r.EqualValues(4, top)
   264  	r.Equal(3, len(files))
   265  	r.Equal(files[0], file2)
   266  	r.Equal(files[1], file3)
   267  	r.Equal(files[2], file4)
   268  
   269  	// open 4 db files and verify again
   270  	fd, err = NewFileDAO(cfg, deser)
   271  	fm = fd.(*fileDAO)
   272  	r.EqualValues(4, fm.topIndex)
   273  	r.EqualValues(1, fm.splitHeight)
   274  	r.NoError(err)
   275  	r.NoError(fd.Start(ctx))
   276  	// Start() will update splitHeight from top v2 file
   277  	r.EqualValues(4, fm.topIndex)
   278  	r.EqualValues(46, fm.splitHeight)
   279  	// commit another 20 blocks
   280  	r.NoError(testCommitBlocks(t, fd, 56, 75, hash.ZeroHash256))
   281  	r.EqualValues(5, fm.topIndex)
   282  	r.EqualValues(61, fm.splitHeight)
   283  	testVerifyChainDB(t, fd, 1, 75)
   284  	fd.Stop(ctx)
   285  	os.RemoveAll(kthAuxFileName("./filedao_v2.db", fm.topIndex))
   286  }
   287  
   288  func TestCheckFiles(t *testing.T) {
   289  	r := require.New(t)
   290  
   291  	auxTests := []struct {
   292  		file, base string
   293  		index      uint64
   294  		ok         bool
   295  	}{
   296  		{"/tmp/chain-00000003.db", "/tmp/chain", 0, false},
   297  		{"/tmp/chain-00000003", "/tmp/chain.db", 0, false},
   298  		{"/tmp/chain-00000003.dat", "/tmp/chain.db", 0, false},
   299  		{"/tmp/chair-00000003.dat", "/tmp/chain.db", 0, false},
   300  		{"/tmp/chain=00000003.db", "/tmp/chain.db", 0, false},
   301  		{"/tmp/chain-0000003.db", "/tmp/chain.db", 0, false},
   302  		{"/tmp/chain--0000003.db", "/tmp/chain.db", 0, false},
   303  		{"/tmp/chain-00000003.db", "/tmp/chain.db", 3, true},
   304  	}
   305  
   306  	for _, v := range auxTests {
   307  		index, ok := isAuxFile(v.file, v.base)
   308  		r.Equal(v.index, index)
   309  		r.Equal(v.ok, ok)
   310  	}
   311  
   312  	cfg := db.DefaultConfig
   313  	cfg.DbPath = "./filedao_v2.db"
   314  	_, files := checkAuxFiles(cfg.DbPath, FileLegacyAuxiliary)
   315  	r.Nil(files)
   316  	_, files = checkAuxFiles(cfg.DbPath, FileV2)
   317  	r.Nil(files)
   318  
   319  	deser := block.NewDeserializer(_defaultEVMNetworkID)
   320  	// create 3 v2 files
   321  	for i := 1; i <= 3; i++ {
   322  		cfg.DbPath = kthAuxFileName("./filedao_v2.db", uint64(i))
   323  		r.NoError(createNewV2File(1, cfg, deser))
   324  	}
   325  	defer func() {
   326  		for i := 1; i <= 3; i++ {
   327  			os.RemoveAll(kthAuxFileName("./filedao_v2.db", uint64(i)))
   328  		}
   329  	}()
   330  	top, files := checkAuxFiles("./filedao_v2.db", FileV2)
   331  	r.EqualValues(3, top)
   332  	r.Equal(3, len(files))
   333  	for i := 1; i <= 3; i++ {
   334  		r.Equal(files[i-1], kthAuxFileName("./filedao_v2.db", uint64(i)))
   335  	}
   336  }