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 }