github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/evm/export_operation.go (about) 1 package evm 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "runtime" 11 "strings" 12 "sync" 13 "sync/atomic" 14 15 ethcmn "github.com/ethereum/go-ethereum/common" 16 "github.com/ethereum/go-ethereum/common/hexutil" 17 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 18 "github.com/fibonacci-chain/fbc/libs/tendermint/libs/log" 19 dbm "github.com/fibonacci-chain/fbc/libs/tm-db" 20 "github.com/fibonacci-chain/fbc/x/evm/types" 21 ) 22 23 const ( 24 codeFileSuffix = ".code" 25 storageFileSuffix = ".storage" 26 codeSubPath = "code" 27 storageSubPath = "storage" 28 defaultMode = "default" 29 filesMode = "files" 30 dbMode = "db" 31 ) 32 33 var ( 34 codePath string 35 storagePath string 36 defaultPath, _ = os.Getwd() 37 38 goroutinePool chan struct{} 39 wg sync.WaitGroup 40 41 codeCount uint64 42 storageCount uint64 43 44 evmByteCodeDB, evmStateDB dbm.DB 45 ) 46 47 // initExportEnv only initializes the paths and goroutine pool 48 func initExportEnv(dataPath, mode string, goroutineNum uint64) { 49 if dataPath == "" { 50 dataPath = defaultPath 51 } 52 53 switch mode { 54 case "default": 55 return 56 case "files": 57 codePath = filepath.Join(dataPath, codeSubPath) 58 storagePath = filepath.Join(dataPath, storageSubPath) 59 60 err := os.MkdirAll(codePath, 0777) 61 if err != nil { 62 panic(err) 63 } 64 err = os.MkdirAll(storagePath, 0777) 65 if err != nil { 66 panic(err) 67 } 68 69 initGoroutinePool(goroutineNum) 70 case "db": 71 initEVMDB(dataPath) 72 initGoroutinePool(goroutineNum) 73 default: 74 panic("unsupported export mode") 75 } 76 } 77 78 // initImportEnv only initializes the paths and goroutine pool 79 func initImportEnv(dataPath, mode string, goroutineNum uint64) { 80 if dataPath == "" { 81 dataPath = defaultPath 82 } 83 switch mode { 84 case "default": 85 return 86 case "files": 87 codePath = filepath.Join(dataPath, codeSubPath) 88 storagePath = filepath.Join(dataPath, storageSubPath) 89 90 initGoroutinePool(goroutineNum) 91 case "db": 92 initEVMDB(dataPath) 93 default: 94 panic("unsupported import mode") 95 } 96 } 97 98 // exportToFile export EVM code and storage to files 99 func exportToFile(ctx sdk.Context, k Keeper, address ethcmn.Address) { 100 // write Code 101 addGoroutine() 102 go syncWriteAccountCode(ctx, k, address) 103 // write Storage 104 addGoroutine() 105 go syncWriteAccountStorage(ctx, k, address) 106 } 107 108 // importFromFile import EVM code and storage from files 109 func importFromFile(ctx sdk.Context, logger log.Logger, k Keeper, address ethcmn.Address, codeHash []byte) { 110 // read Code from file 111 addGoroutine() 112 go syncReadCodeFromFile(ctx, logger, k, address, codeHash) 113 114 // read Storage From file 115 addGoroutine() 116 go syncReadStorageFromFile(ctx, logger, k, address) 117 } 118 119 // exportToDB export EVM code and storage to leveldb 120 func exportToDB(ctx sdk.Context, k Keeper, address ethcmn.Address, codeHash []byte) { 121 if code := k.GetCode(ctx, address); len(code) > 0 { 122 // TODO repeat code 123 if err := evmByteCodeDB.Set(append(types.KeyPrefixCode, codeHash...), code); err != nil { 124 panic(err) 125 } 126 codeCount++ 127 } 128 129 addGoroutine() 130 go exportStorage(ctx, k, address, evmStateDB) 131 } 132 133 // importFromDB import EVM code and storage to leveldb 134 func importFromDB(ctx sdk.Context, k Keeper, address ethcmn.Address, codeHash []byte) { 135 if isEmptyState(evmByteCodeDB) || isEmptyState(evmStateDB) { 136 panic("failed to open evm db") 137 } 138 139 code, err := evmByteCodeDB.Get(append(types.KeyPrefixCode, codeHash...)) 140 if err != nil { 141 panic(err) 142 } 143 if len(code) != 0 { 144 k.SetCodeDirectly(ctx, codeHash, code) 145 codeCount++ 146 } 147 148 prefix := types.AddressStoragePrefix(address) 149 iterator, err := evmStateDB.Iterator(prefix, sdk.PrefixEndBytes(prefix)) 150 if err != nil { 151 panic(err) 152 } 153 for ; iterator.Valid(); iterator.Next() { 154 k.SetStateDirectly(ctx, address, ethcmn.BytesToHash(iterator.Key()[len(prefix):]), ethcmn.BytesToHash(iterator.Value())) 155 storageCount++ 156 } 157 iterator.Close() 158 } 159 160 func exportStorage(ctx sdk.Context, k Keeper, addr ethcmn.Address, db dbm.DB) { 161 defer finishGoroutine() 162 163 prefix := types.AddressStoragePrefix(addr) 164 err := k.ForEachStorage(ctx, addr, func(key, value ethcmn.Hash) bool { 165 db.Set(append(prefix, key.Bytes()...), value.Bytes()) 166 atomic.AddUint64(&storageCount, 1) 167 return false 168 }) 169 if err != nil { 170 panic(err) 171 } 172 } 173 174 func initEVMDB(path string) { 175 var err error 176 evmByteCodeDB, err = sdk.NewDB("evm_bytecode", path) 177 if err != nil { 178 panic(err) 179 } 180 evmStateDB, err = sdk.NewDB("evm_state", path) 181 if err != nil { 182 panic(err) 183 } 184 } 185 186 // initGoroutinePool creates an appropriate number of maximum goroutine 187 func initGoroutinePool(goroutineNum uint64) { 188 if goroutineNum == 0 { 189 goroutineNum = uint64(runtime.NumCPU()-1) * 16 190 } 191 goroutinePool = make(chan struct{}, goroutineNum) 192 } 193 194 // addGoroutine if goroutinePool is not full, then create a goroutine 195 func addGoroutine() { 196 goroutinePool <- struct{}{} 197 wg.Add(1) 198 } 199 200 // finishGoroutine follows the function addGoroutine 201 func finishGoroutine() { 202 <-goroutinePool 203 wg.Done() 204 } 205 206 // createFile creates a file based on a absolute path 207 func createFile(filePath string) *os.File { 208 file, err := os.Create(filePath) 209 if err != nil { 210 panic(err) 211 } 212 return file 213 } 214 215 // closeFile closes the current file and writer, in case of the waste of memory 216 func closeFile(writer *bufio.Writer, file *os.File) { 217 err := writer.Flush() 218 if err != nil { 219 panic(err) 220 } 221 err = file.Close() 222 if err != nil { 223 panic(err) 224 } 225 } 226 227 // writeOneLine only writes data into one line 228 func writeOneLine(writer *bufio.Writer, data string) { 229 _, err := writer.WriteString(data) 230 if err != nil { 231 panic(err) 232 } 233 } 234 235 // ************************************************************************************************************ 236 // the List of functions are used for writing different type of data into files 237 // 238 // First, get data from cache or db 239 // Second, format data, then write them into file 240 // note: there is no way of adding log when ExportGenesis, because it will generate many logs in genesis.json 241 // 242 // ************************************************************************************************************ 243 // syncWriteAccountCode synchronize the process of writing types.Code into individual file. 244 // It doesn't create file when there is no code linked to an account 245 func syncWriteAccountCode(ctx sdk.Context, k Keeper, address ethcmn.Address) { 246 defer finishGoroutine() 247 248 code := k.GetCode(ctx, address) 249 if len(code) != 0 { 250 file := createFile(filepath.Join(codePath, address.String()+codeFileSuffix)) 251 writer := bufio.NewWriter(file) 252 defer closeFile(writer, file) 253 writeOneLine(writer, hexutil.Bytes(code).String()) 254 atomic.AddUint64(&codeCount, 1) 255 } 256 } 257 258 // syncWriteAccountStorage synchronize the process of writing types.Storage into individual file 259 // It will delete the file when there is no storage linked to a contract 260 func syncWriteAccountStorage(ctx sdk.Context, k Keeper, address ethcmn.Address) { 261 defer finishGoroutine() 262 263 filename := filepath.Join(storagePath, address.String()+storageFileSuffix) 264 index := 0 265 defer func() { 266 if index == 0 { // make a judgement that there is a slice of ethtypes.State or not 267 if err := os.Remove(filename); err != nil { 268 panic(err) 269 } 270 } else { 271 atomic.AddUint64(&storageCount, uint64(index)) 272 } 273 }() 274 275 file := createFile(filename) 276 writer := bufio.NewWriter(file) 277 defer closeFile(writer, file) 278 279 // call this function, used for iterating all the key&value based on an address 280 err := k.ForEachStorage(ctx, address, func(key, value ethcmn.Hash) bool { 281 writeOneLine(writer, fmt.Sprintf("%s:%s\n", key.Hex(), value.Hex())) 282 index++ 283 return false 284 }) 285 if err != nil { 286 panic(err) 287 } 288 } 289 290 // ************************************************************************************************************ 291 // the List of functions are used for loading different type of data, then persists data on db 292 // 293 // First, get data from local file 294 // Second, format data, then set them into db 295 // 296 // ************************************************************************************************************ 297 // syncReadCodeFromFile synchronize the process of setting types.Code into evm db when InitGenesis 298 func syncReadCodeFromFile(ctx sdk.Context, logger log.Logger, k Keeper, address ethcmn.Address, codeHash []byte) { 299 defer finishGoroutine() 300 301 codeFilePath := filepath.Join(codePath, address.String()+codeFileSuffix) 302 if pathExist(codeFilePath) { 303 logger.Debug("start loading code", "filename", address.String()+codeFileSuffix) 304 bin, err := ioutil.ReadFile(codeFilePath) 305 if err != nil { 306 panic(err) 307 } 308 309 // make "0x608002412.....80" string into a slice of byte 310 code := hexutil.MustDecode(string(bin)) 311 312 // Set contract code into db, ignoring setting in cache 313 k.SetCodeDirectly(ctx, codeHash, code) 314 atomic.AddUint64(&codeCount, 1) 315 } 316 } 317 318 // syncReadStorageFromFile synchronize the process of setting types.Storage into evm db when InitGenesis 319 func syncReadStorageFromFile(ctx sdk.Context, logger log.Logger, k Keeper, address ethcmn.Address) { 320 defer finishGoroutine() 321 322 storageFilePath := filepath.Join(storagePath, address.String()+storageFileSuffix) 323 if pathExist(storageFilePath) { 324 logger.Debug("start loading storage", "filename", address.String()+storageFileSuffix) 325 f, err := os.Open(storageFilePath) 326 if err != nil { 327 panic(err) 328 } 329 defer f.Close() 330 rd := bufio.NewReader(f) 331 for { 332 // eg. kvStr = "0xc543bf77d2a7bddbeb14b8d8bfa3405a8410be06d8c3e68d5bd5e7b9abd43d39:0x4e584d0000000000000000000000000000000000000000000000000000000006\n" 333 kvStr, err := rd.ReadString('\n') 334 if err != nil || io.EOF == err { 335 break 336 } 337 // remove '\n' in the end of string, then split kvStr based on ':' 338 kvPair := strings.Split(strings.ReplaceAll(kvStr, "\n", ""), ":") 339 //convert hexStr into common.Hash struct 340 key, value := ethcmn.HexToHash(kvPair[0]), ethcmn.HexToHash(kvPair[1]) 341 // Set the state of key&value into db, ignoring setting in cache 342 k.SetStateDirectly(ctx, address, key, value) 343 atomic.AddUint64(&storageCount, 1) 344 } 345 } 346 } 347 348 // pathExist used for judging the file or path exist or not when InitGenesis 349 func pathExist(path string) bool { 350 _, err := os.Stat(path) 351 if err != nil { 352 if os.IsExist(err) { 353 return true 354 } 355 return false 356 } 357 return true 358 } 359 360 func isEmptyState(db dbm.DB) bool { 361 return db.Stats()["leveldb.sstables"] == "" 362 } 363 364 func CloseDB() { 365 evmByteCodeDB.Close() 366 evmStateDB.Close() 367 }