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  }