github.com/klaytn/klaytn@v1.12.1/tests/state_migration_test.go (about)

     1  // Copyright 2020 The klaytn Authors
     2  // This file is part of the klaytn library.
     3  //
     4  // The klaytn library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The klaytn library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the klaytn library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package tests
    18  
    19  import (
    20  	"math/big"
    21  	"os"
    22  	"path/filepath"
    23  	"strconv"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/klaytn/klaytn/common"
    28  	"github.com/klaytn/klaytn/log"
    29  	"github.com/klaytn/klaytn/node"
    30  	"github.com/klaytn/klaytn/node/cn"
    31  	"github.com/klaytn/klaytn/storage/database"
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/syndtr/goleveldb/leveldb"
    34  )
    35  
    36  // continuous occurrence of state trie migration and node restart must success
    37  func TestMigration_ContinuousRestartAndMigration(t *testing.T) {
    38  	log.EnableLogForTest(log.LvlCrit, log.LvlTrace)
    39  
    40  	fullNode, node, validator, chainID, workspace, richAccount, _, _ := newSimpleBlockchain(t, 10)
    41  	defer os.RemoveAll(workspace)
    42  
    43  	stateTriePath := []byte("statetrie")
    44  
    45  	numTxs := []int{10, 100}
    46  	for i := 0; i < len(numTxs); i++ {
    47  		numTx := numTxs[i%len(numTxs)]
    48  		t.Log("attempt", strconv.Itoa(i), " : deployRandomTxs of", strconv.Itoa(numTx))
    49  		deployRandomTxs(t, node.TxPool(), chainID, richAccount, numTx)
    50  		time.Sleep(3 * time.Second) // wait until txpool is flushed
    51  
    52  		startMigration(t, node)
    53  		time.Sleep(1 * time.Second)
    54  
    55  		t.Log("migration state before restart", node.ChainDB().InMigration())
    56  		fullNode, node = restartNode(t, fullNode, node, workspace, validator)
    57  
    58  		waitMigrationEnds(t, node)
    59  
    60  		// check if migration succeeds (StateTrieDB changes when migration finishes)
    61  		newPathKey := append([]byte("databaseDirectory"), common.Int64ToByteBigEndian(uint64(database.StateTrieDB))...)
    62  		newStateTriePath, err := node.ChainDB().GetMiscDB().Get(newPathKey)
    63  		assert.NoError(t, err)
    64  		assert.NotEqual(t, stateTriePath, newStateTriePath, "migration failed")
    65  		stateTriePath = newStateTriePath
    66  	}
    67  
    68  	stopNode(t, fullNode)
    69  }
    70  
    71  // state trie DB should be determined by the values of miscDB
    72  func TestMigration_StartMigrationByMiscDB(t *testing.T) {
    73  	log.EnableLogForTest(log.LvlCrit, log.LvlTrace)
    74  
    75  	fullNode, cn, validator, _, workspace, _, _, _ := newSimpleBlockchain(t, 10)
    76  	defer os.RemoveAll(workspace)
    77  
    78  	stateTriePathKey := append([]byte("databaseDirectory"), common.Int64ToByteBigEndian(uint64(database.StateTrieDB))...)
    79  
    80  	// use the default StateTrie DB if it is not set on miscDB
    81  	{
    82  		// check if stateDB value is not stored in miscDB
    83  		key, err := cn.ChainDB().GetMiscDB().Get(stateTriePathKey)
    84  		assert.Error(t, err)
    85  		assert.Len(t, key, 0)
    86  
    87  		// write values in stateDB and check if the values are stored in DB
    88  		entries := writeRandomValueToStateTrieDB(t, cn.ChainDB())
    89  		stopNode(t, fullNode) // stop node to release DB lock
    90  		checkIfStoredInDB(t, cn.ChainDB().GetDBConfig().NumStateTrieShards, filepath.Join(cn.ChainDB().GetDBConfig().Dir, "statetrie"), entries)
    91  		fullNode, cn = startNode(t, workspace, validator)
    92  	}
    93  
    94  	// use the state trie DB that is specified in miscDB
    95  	{
    96  		// change stateDB value in miscDB
    97  		newDBPath := "NEW_STATE_TRIE_DB_PATH"
    98  		err := cn.ChainDB().GetMiscDB().Put(stateTriePathKey, []byte(newDBPath))
    99  		assert.NoError(t, err)
   100  
   101  		// an error expected on node start
   102  		stopNode(t, fullNode)
   103  		_, _, err = newKlaytnNode(t, workspace, validator, nil, nil)
   104  		assert.Error(t, err, "start failure expected, changed state trie db has no data") // error expected
   105  	}
   106  }
   107  
   108  func writeRandomValueToStateTrieDB(t *testing.T, dbm database.DBManager) map[string]string {
   109  	batch := dbm.NewBatch(database.StateTrieDB)
   110  	entries := make(map[string]string, 10)
   111  
   112  	for i := 0; i < 10; i++ {
   113  		key, value := common.MakeRandomBytes(common.HashLength), common.MakeRandomBytes(400)
   114  		dbm.PutTrieNodeToBatch(batch, common.BytesToExtHash(key), value)
   115  		entries[string(key)] = string(value)
   116  	}
   117  	assert.NoError(t, batch.Write())
   118  
   119  	return entries
   120  }
   121  
   122  func checkIfStoredInDB(t *testing.T, numShard uint, dir string, entries map[string]string) {
   123  	dbs := make([]*leveldb.DB, numShard)
   124  	for i := 0; i < 4; i++ {
   125  		var err error
   126  		dbs[i], err = leveldb.OpenFile(dir+"/"+strconv.Itoa(i), nil)
   127  		assert.NoError(t, err)
   128  		defer dbs[i].Close()
   129  	}
   130  	for k, v := range entries {
   131  		datas := make([][]byte, 4)
   132  		for i := 0; i < 4; i++ {
   133  			datas[i], _ = dbs[i].Get([]byte(k), nil)
   134  		}
   135  		assert.Contains(t, datas, []byte(v), "value written in stateDB does not actually exist in DB")
   136  	}
   137  }
   138  
   139  // if migration status is set on miscDB and a node is restarted, migration should start
   140  func TestMigration_StartMigrationByMiscDBOnRestart(t *testing.T) {
   141  	log.EnableLogForTest(log.LvlCrit, log.LvlTrace)
   142  
   143  	fullNode, node, validator, chainID, workspace, richAccount, _, _ := newSimpleBlockchain(t, 10)
   144  	defer os.RemoveAll(workspace)
   145  	miscDB := node.ChainDB().GetMiscDB()
   146  
   147  	// size up state trie to be prepared for migration
   148  	deployRandomTxs(t, node.TxPool(), chainID, richAccount, 100)
   149  	time.Sleep(time.Second)
   150  
   151  	// set migration status in miscDB
   152  	migrationBlockNum := node.BlockChain().CurrentBlock().Header().Number.Uint64()
   153  	err := miscDB.Put([]byte("migrationStatus"), common.Int64ToByteBigEndian(migrationBlockNum))
   154  	assert.NoError(t, err)
   155  
   156  	// set migration db path in miscDB
   157  	migrationPathKey := append([]byte("databaseDirectory"), common.Int64ToByteBigEndian(uint64(database.StateTrieMigrationDB))...)
   158  	migrationPath := []byte("statetrie_migrated_" + strconv.FormatUint(migrationBlockNum, 10))
   159  	err = miscDB.Put(migrationPathKey, migrationPath)
   160  	assert.NoError(t, err)
   161  
   162  	// check migration Status in cache before restart
   163  	assert.False(t, node.ChainDB().InMigration(), "migration has not started yet")
   164  	assert.NotEqual(t, migrationBlockNum, node.ChainDB().MigrationBlockNumber(), "migration has not started yet")
   165  
   166  	fullNode, node = restartNode(t, fullNode, node, workspace, validator)
   167  	miscDB = node.ChainDB().GetMiscDB()
   168  
   169  	// check migration Status in cache after restart
   170  	if node.ChainDB().InMigration() {
   171  		assert.Equal(t, migrationBlockNum, node.ChainDB().MigrationBlockNumber(), "migration block number should match")
   172  		t.Log("Checked migration status while migration in on process")
   173  	}
   174  
   175  	waitMigrationEnds(t, node)
   176  
   177  	// state trie path should not be "statetrie" in miscDB
   178  	newPathKey := append([]byte("databaseDirectory"), common.Int64ToByteBigEndian(uint64(database.StateTrieDB))...)
   179  	dir, err := miscDB.Get(newPathKey)
   180  	assert.NoError(t, err)
   181  	assert.NotEqual(t, "statetrie", string(dir), "migration failed")
   182  
   183  	stopNode(t, fullNode)
   184  }
   185  
   186  func newSimpleBlockchain(t *testing.T, numAccounts int) (*node.Node, *cn.CN, *TestAccountType, *big.Int, string, *TestAccountType, []*TestAccountType, []*TestAccountType) {
   187  	t.Log("=========== create blockchain ==============")
   188  	fullNode, node, validator, chainID, workspace := newBlockchain(t, nil, nil)
   189  	richAccount, accounts, contractAccounts := createAccount(t, numAccounts, validator)
   190  	time.Sleep(5 * time.Second)
   191  
   192  	return fullNode, node, validator, chainID, workspace, richAccount, accounts, contractAccounts
   193  }
   194  
   195  func startMigration(t *testing.T, node *cn.CN) {
   196  	waitMigrationEnds(t, node)
   197  
   198  	t.Log("=========== migrate trie ==============")
   199  	err := node.BlockChain().PrepareStateMigration()
   200  	assert.NoError(t, err)
   201  }
   202  
   203  func restartNode(t *testing.T, fullNode *node.Node, node *cn.CN, workspace string, validator *TestAccountType) (*node.Node, *cn.CN) {
   204  	stopNode(t, fullNode)
   205  	time.Sleep(2 * time.Second)
   206  	newFullNode, newNode := startNode(t, workspace, validator)
   207  	time.Sleep(2 * time.Second)
   208  
   209  	return newFullNode, newNode
   210  }
   211  
   212  func startNode(t *testing.T, workspace string, validator *TestAccountType) (fullNode *node.Node, node *cn.CN) {
   213  	t.Log("=========== starting node ==============")
   214  	newFullNode, newNode, err := newKlaytnNode(t, workspace, validator, nil, nil)
   215  	assert.NoError(t, err)
   216  	if err := newNode.StartMining(false); err != nil {
   217  		t.Fatal()
   218  	}
   219  
   220  	return newFullNode, newNode
   221  }
   222  
   223  func stopNode(t *testing.T, fullNode *node.Node) {
   224  	if err := fullNode.Stop(); err != nil {
   225  		t.Fatal(err)
   226  	}
   227  	t.Log("=========== stopped node ==============")
   228  }
   229  
   230  func waitMigrationEnds(t *testing.T, node *cn.CN) {
   231  	for node.ChainDB().InMigration() {
   232  		t.Log("state trie migration is processing; sleep for a second before a new migration")
   233  		time.Sleep(time.Second)
   234  	}
   235  }