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

     1  // Copyright 2019 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  	"bufio"
    21  	"crypto/ecdsa"
    22  	"encoding/hex"
    23  	"errors"
    24  	"fmt"
    25  	"math"
    26  	"math/big"
    27  	"os"
    28  	"path"
    29  	"strconv"
    30  	"sync"
    31  	"time"
    32  
    33  	"github.com/klaytn/klaytn/blockchain"
    34  	"github.com/klaytn/klaytn/blockchain/types"
    35  	"github.com/klaytn/klaytn/blockchain/vm"
    36  	"github.com/klaytn/klaytn/common"
    37  	"github.com/klaytn/klaytn/consensus/istanbul"
    38  	istanbulBackend "github.com/klaytn/klaytn/consensus/istanbul/backend"
    39  	"github.com/klaytn/klaytn/crypto"
    40  	"github.com/klaytn/klaytn/governance"
    41  	"github.com/klaytn/klaytn/log"
    42  	"github.com/klaytn/klaytn/params"
    43  	"github.com/klaytn/klaytn/reward"
    44  	"github.com/klaytn/klaytn/storage/database"
    45  	"github.com/klaytn/klaytn/storage/statedb"
    46  	"github.com/klaytn/klaytn/work"
    47  	"github.com/syndtr/goleveldb/leveldb/opt"
    48  )
    49  
    50  const (
    51  	numValidatorsForTest = 4
    52  
    53  	addressDirectory    = "addrs"
    54  	privateKeyDirectory = "privatekeys"
    55  
    56  	addressFilePrefix    = "addrs_"
    57  	privateKeyFilePrefix = "privateKeys_"
    58  
    59  	chainDataDir = "chaindata"
    60  )
    61  
    62  var totalTxs = 0
    63  
    64  // writeToFile writes addresses and private keys to designated directories with given fileNum.
    65  // Addresses are stored in a file like `addrs_0` and private keys are stored in a file like `privateKeys_0`.
    66  func writeToFile(addrs []*common.Address, privKeys []*ecdsa.PrivateKey, fileNum int, dir string) {
    67  	_ = os.Mkdir(path.Join(dir, addressDirectory), os.ModePerm)
    68  	_ = os.Mkdir(path.Join(dir, privateKeyDirectory), os.ModePerm)
    69  
    70  	addrsFile, err := os.Create(path.Join(dir, addressDirectory, addressFilePrefix+strconv.Itoa(fileNum)))
    71  	if err != nil {
    72  		panic(err)
    73  	}
    74  
    75  	privateKeysFile, err := os.Create(path.Join(dir, privateKeyDirectory, privateKeyFilePrefix+strconv.Itoa(fileNum)))
    76  	if err != nil {
    77  		panic(err)
    78  	}
    79  
    80  	wg := sync.WaitGroup{}
    81  
    82  	wg.Add(2)
    83  
    84  	syncSize := len(addrs) / 2
    85  
    86  	go func() {
    87  		for i, b := range addrs {
    88  			addrsFile.WriteString(b.String() + "\n")
    89  			if (i+1)%syncSize == 0 {
    90  				addrsFile.Sync()
    91  			}
    92  		}
    93  
    94  		addrsFile.Close()
    95  		wg.Done()
    96  	}()
    97  
    98  	go func() {
    99  		for i, key := range privKeys {
   100  			privateKeysFile.WriteString(hex.EncodeToString(crypto.FromECDSA(key)) + "\n")
   101  			if (i+1)%syncSize == 0 {
   102  				privateKeysFile.Sync()
   103  			}
   104  		}
   105  
   106  		privateKeysFile.Close()
   107  		wg.Done()
   108  	}()
   109  
   110  	wg.Wait()
   111  }
   112  
   113  func readAddrsFromFile(dir string, num int) ([]*common.Address, error) {
   114  	var addrs []*common.Address
   115  
   116  	addrsFile, err := os.Open(path.Join(dir, addressDirectory, addressFilePrefix+strconv.Itoa(num)))
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	defer addrsFile.Close()
   122  
   123  	scanner := bufio.NewScanner(addrsFile)
   124  	for scanner.Scan() {
   125  		keyStr := scanner.Text()
   126  		addr := common.HexToAddress(keyStr)
   127  		addrs = append(addrs, &addr)
   128  	}
   129  
   130  	return addrs, nil
   131  }
   132  
   133  func readPrivateKeysFromFile(dir string, num int) ([]*ecdsa.PrivateKey, error) {
   134  	var privKeys []*ecdsa.PrivateKey
   135  	privateKeysFile, err := os.Open(path.Join(dir, privateKeyDirectory, privateKeyFilePrefix+strconv.Itoa(num)))
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  
   140  	defer privateKeysFile.Close()
   141  
   142  	scanner := bufio.NewScanner(privateKeysFile)
   143  	for scanner.Scan() {
   144  		keyStr := scanner.Text()
   145  
   146  		key, err := hex.DecodeString(keyStr)
   147  		if err != nil {
   148  			return nil, fmt.Errorf("%v", err)
   149  		}
   150  
   151  		if pk, err := crypto.ToECDSA(key); err != nil {
   152  			return nil, fmt.Errorf("%v", err)
   153  		} else {
   154  			privKeys = append(privKeys, pk)
   155  		}
   156  	}
   157  
   158  	return privKeys, nil
   159  }
   160  
   161  func readAddrsAndPrivateKeysFromFile(dir string, num int) ([]*common.Address, []*ecdsa.PrivateKey, error) {
   162  	addrs, err := readAddrsFromFile(dir, num)
   163  	if err != nil {
   164  		return nil, nil, err
   165  	}
   166  
   167  	privateKeys, err := readPrivateKeysFromFile(dir, num)
   168  	if err != nil {
   169  		return nil, nil, err
   170  	}
   171  
   172  	return addrs, privateKeys, nil
   173  }
   174  
   175  // getAddrsAndKeysFromFile extracts the address stored in file by numAccounts.
   176  func getAddrsAndKeysFromFile(numAccounts int, testDataDir string, run int, filePicker func(int, int) int) ([]*common.Address, []*ecdsa.PrivateKey, error) {
   177  	addrs := make([]*common.Address, 0, numAccounts)
   178  	privKeys := make([]*ecdsa.PrivateKey, 0, numAccounts)
   179  
   180  	files, err := os.ReadDir(path.Join(testDataDir, addressDirectory))
   181  	if err != nil {
   182  		return nil, nil, err
   183  	}
   184  
   185  	numFiles := len(files)
   186  	remain := numAccounts
   187  	for i := run; remain > 0; i++ {
   188  		fileIndex := filePicker(i, numFiles)
   189  
   190  		// Read recipient addresses from file.
   191  		addrsPerFile, privKeysPerFile, err := readAddrsAndPrivateKeysFromFile(testDataDir, fileIndex)
   192  		if err != nil {
   193  			return nil, nil, err
   194  		}
   195  
   196  		fmt.Println("Read addresses from " + addressFilePrefix + strconv.Itoa(fileIndex) + ", len(addrs)=" + strconv.Itoa(len(addrsPerFile)))
   197  
   198  		partSize := int(math.Min(float64(len(addrsPerFile)), float64(remain)))
   199  		addrs = append(addrs, addrsPerFile[:partSize]...)
   200  		privKeys = append(privKeys, privKeysPerFile[:partSize]...)
   201  		remain -= partSize
   202  	}
   203  
   204  	return addrs, privKeys, nil
   205  }
   206  
   207  func makeOrGenerateAddrsAndKeys(testDataDir string, run int, tc *preGeneratedTC) ([]*common.Address, []*ecdsa.PrivateKey, error) {
   208  	// If it is execution test, makeOrGenerateAddrsAndKeys is called to build validator list.
   209  	if !tc.isGenerateTest {
   210  		return getAddrsAndKeysFromFile(numValidatorsForTest, testDataDir, run, tc.filePicker)
   211  	}
   212  
   213  	wd, err := os.Getwd()
   214  	if err != nil {
   215  		return nil, nil, err
   216  	}
   217  
   218  	// If addrs directory exists in the current working directory for reuse,
   219  	// use it instead of generating addresses.
   220  	addrPathInWD := path.Join(wd, addressDirectory)
   221  	if _, err := os.Stat(addrPathInWD); err == nil {
   222  		return getAddrsAndKeysFromFile(tc.numReceiversPerRun, wd, run, tc.filePicker)
   223  	}
   224  
   225  	// No addrs directory exists. Generating and saving addresses and keys.
   226  	var newPrivateKeys []*ecdsa.PrivateKey
   227  	toAddrs, newPrivateKeys, err := createAccounts(tc.numTxsPerGen)
   228  	if err != nil {
   229  		return nil, nil, err
   230  	}
   231  
   232  	writeToFile(toAddrs, newPrivateKeys, run, testDataDir)
   233  	return toAddrs, newPrivateKeys, nil
   234  }
   235  
   236  // getValidatorAddrsAndKeys returns the first `numValidators` addresses and private keys
   237  // for validators.
   238  func getValidatorAddrsAndKeys(addrs []*common.Address, privateKeys []*ecdsa.PrivateKey, numValidators int) ([]common.Address, []*ecdsa.PrivateKey) {
   239  	validatorAddresses := make([]common.Address, numValidators)
   240  	validatorPrivateKeys := make([]*ecdsa.PrivateKey, numValidators)
   241  
   242  	for i := 0; i < numValidators; i++ {
   243  		validatorPrivateKeys[i] = privateKeys[i]
   244  		validatorAddresses[i] = *addrs[i]
   245  	}
   246  
   247  	return validatorAddresses, validatorPrivateKeys
   248  }
   249  
   250  // GenABlockWithTxPoolWithoutAccountMap basically does the same thing with GenABlockWithTxPool,
   251  // however, it does not accept AccountMap which validates the outcome with stateDB.
   252  // This is to remove the overhead of AccountMap management.
   253  func (bcdata *BCData) GenABlockWithTxPoolWithoutAccountMap(txPool *blockchain.TxPool) error {
   254  	signer := types.MakeSigner(bcdata.bc.Config(), bcdata.bc.CurrentHeader().Number)
   255  
   256  	pending, err := txPool.Pending()
   257  	if err != nil {
   258  		return err
   259  	}
   260  	if len(pending) == 0 {
   261  		return errEmptyPending
   262  	}
   263  
   264  	pooltxs := types.NewTransactionsByTimeAndNonce(signer, pending)
   265  
   266  	// Set the block header
   267  	header, err := bcdata.prepareHeader()
   268  	if err != nil {
   269  		return err
   270  	}
   271  
   272  	stateDB, err := bcdata.bc.StateAt(bcdata.bc.CurrentBlock().Root())
   273  	if err != nil {
   274  		return err
   275  	}
   276  
   277  	task := work.NewTask(bcdata.bc.Config(), signer, stateDB, header)
   278  	task.ApplyTransactions(pooltxs, bcdata.bc, *bcdata.rewardBase)
   279  	newtxs := task.Transactions()
   280  	receipts := task.Receipts()
   281  
   282  	if len(newtxs) == 0 {
   283  		return errEmptyPending
   284  	}
   285  
   286  	// Finalize the block.
   287  	b, err := bcdata.engine.Finalize(bcdata.bc, header, stateDB, newtxs, receipts)
   288  	if err != nil {
   289  		return err
   290  	}
   291  
   292  	// Seal the block.
   293  	b, err = sealBlock(b, bcdata.validatorPrivKeys)
   294  	if err != nil {
   295  		return err
   296  	}
   297  
   298  	// Write the block with state.
   299  	result, err := bcdata.bc.WriteBlockWithState(b, receipts, stateDB)
   300  	if err != nil {
   301  		return fmt.Errorf("err = %s", err)
   302  	}
   303  
   304  	if result.Status == blockchain.SideStatTy {
   305  		return fmt.Errorf("forked block is generated! number: %v, hash: %v, txs: %v", b.Number(), b.Hash(), len(b.Transactions()))
   306  	}
   307  
   308  	// Trigger post chain events after successful writing.
   309  	logs := stateDB.Logs()
   310  	events := []interface{}{blockchain.ChainEvent{Block: b, Hash: b.Hash(), Logs: logs}}
   311  	events = append(events, blockchain.ChainHeadEvent{Block: b})
   312  	bcdata.bc.PostChainEvents(events, logs)
   313  
   314  	totalTxs += len(newtxs)
   315  	fmt.Println("blockNum", b.NumberU64(), "numTxs", len(newtxs), "totalTxs", totalTxs)
   316  
   317  	return nil
   318  }
   319  
   320  // NewBCDataForPreGeneratedTest returns a new BCData pointer constructed either 1) from the scratch or 2) from the existing data.
   321  func NewBCDataForPreGeneratedTest(testDataDir string, tc *preGeneratedTC) (*BCData, error) {
   322  	totalTxs = 0
   323  
   324  	if numValidatorsForTest > tc.numTotalSenders {
   325  		return nil, errors.New("numTotalSenders should be bigger numValidatorsForTest")
   326  	}
   327  
   328  	// Remove test data directory if 1) exists and and 2) generating test.
   329  	if _, err := os.Stat(testDataDir); err == nil && tc.isGenerateTest {
   330  		os.RemoveAll(testDataDir)
   331  	}
   332  
   333  	// Remove transactions.rlp if exists
   334  	if _, err := os.Stat(transactionsJournalFilename); err == nil {
   335  		os.RemoveAll(transactionsJournalFilename)
   336  	}
   337  
   338  	////////////////////////////////////////////////////////////////////////////////
   339  	// Create a database
   340  	tc.dbc.Dir = path.Join(testDataDir, chainDataDir)
   341  	fmt.Println("DBDir", tc.dbc.Dir)
   342  
   343  	var chainDB database.DBManager
   344  	var err error
   345  	if tc.dbc.DBType == database.LevelDB {
   346  		chainDB, err = database.NewLevelDBManagerForTest(tc.dbc, tc.levelDBOption)
   347  	} else if tc.dbc.DBType == database.BadgerDB {
   348  		chainDB = database.NewDBManager(tc.dbc)
   349  	}
   350  
   351  	if err != nil {
   352  		return nil, err
   353  	}
   354  
   355  	////////////////////////////////////////////////////////////////////////////////
   356  	// Create a governance
   357  	gov := generateGovernaceDataForTest()
   358  
   359  	////////////////////////////////////////////////////////////////////////////////
   360  	// Prepare sender addresses and private keys
   361  	// 1) If generating test, create accounts and private keys as many as numTotalSenders
   362  	// 2) If executing test, load accounts and private keys from file as many as numTotalSenders
   363  	addrs, privKeys, err := makeOrGenerateAddrsAndKeys(testDataDir, 0, tc)
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  
   368  	////////////////////////////////////////////////////////////////////////////////
   369  	// Set the genesis address
   370  	genesisAddr := *addrs[0]
   371  
   372  	////////////////////////////////////////////////////////////////////////////////
   373  	// Use the first `numValidatorsForTest` accounts as validators
   374  	validatorAddresses, validatorPrivKeys := getValidatorAddrsAndKeys(addrs, privKeys, numValidatorsForTest)
   375  
   376  	////////////////////////////////////////////////////////////////////////////////
   377  	// Setup istanbul consensus backend
   378  	engine := istanbulBackend.New(&istanbulBackend.BackendOpts{
   379  		IstanbulConfig: istanbul.DefaultConfig,
   380  		Rewardbase:     genesisAddr,
   381  		PrivateKey:     validatorPrivKeys[0],
   382  		DB:             chainDB,
   383  		Governance:     gov,
   384  		NodeType:       common.CONSENSUSNODE,
   385  	})
   386  
   387  	////////////////////////////////////////////////////////////////////////////////
   388  	// Make a blockChain
   389  	// 1) If generating test, call initBlockChain
   390  	// 2) If executing test, call blockchain.NewBlockChain
   391  	var bc *blockchain.BlockChain
   392  	var genesis *blockchain.Genesis
   393  	if tc.isGenerateTest {
   394  		bc, genesis, err = initBlockChain(chainDB, tc.cacheConfig, addrs, validatorAddresses, nil, engine)
   395  	} else {
   396  		chainConfig, err := getChainConfig(chainDB)
   397  		if err != nil {
   398  			return nil, err
   399  		}
   400  		genesis = blockchain.DefaultGenesisBlock()
   401  		genesis.Config = chainConfig
   402  		bc, err = blockchain.NewBlockChain(chainDB, tc.cacheConfig, chainConfig, engine, vm.Config{})
   403  	}
   404  
   405  	if err != nil {
   406  		return nil, err
   407  	}
   408  
   409  	rewardDistributor := reward.NewRewardDistributor(gov)
   410  
   411  	return &BCData{
   412  		bc, addrs, privKeys, chainDB,
   413  		&genesisAddr, validatorAddresses,
   414  		validatorPrivKeys, engine, genesis, gov, rewardDistributor,
   415  	}, nil
   416  }
   417  
   418  // genAspenOptions returns database configurations of Aspen network.
   419  func genAspenOptions() (*database.DBConfig, *opt.Options) {
   420  	aspenDBConfig := defaultDBConfig()
   421  	aspenDBConfig.DBType = database.LevelDB
   422  	aspenDBConfig.LevelDBCompression = database.AllSnappyCompression
   423  
   424  	aspenLevelDBOptions := &opt.Options{WriteBuffer: 256 * opt.MiB}
   425  
   426  	return aspenDBConfig, aspenLevelDBOptions
   427  }
   428  
   429  // genBaobabOptions returns database configurations of Baobab network.
   430  func genBaobabOptions() (*database.DBConfig, *opt.Options) {
   431  	dbc, opts := genAspenOptions()
   432  
   433  	opts.CompactionTableSize = 4 * opt.MiB
   434  	opts.CompactionTableSizeMultiplier = 2
   435  
   436  	dbc.LevelDBCompression = database.AllSnappyCompression
   437  
   438  	return dbc, opts
   439  }
   440  
   441  // genCandidateLevelDBOptions returns candidate database configurations of main-net, using LevelDB.
   442  func genCandidateLevelDBOptions() (*database.DBConfig, *opt.Options) {
   443  	dbc, opts := genAspenOptions()
   444  
   445  	dbc.LevelDBBufferPool = true
   446  	dbc.LevelDBCompression = database.AllNoCompression
   447  
   448  	return dbc, opts
   449  }
   450  
   451  // genCandidateLevelDBOptions returns candidate database configurations of main-net, using BadgerDB.
   452  func genCandidateBadgerDBOptions() (*database.DBConfig, *opt.Options) {
   453  	dbc := defaultDBConfig()
   454  	dbc.DBType = database.BadgerDB
   455  
   456  	return dbc, &opt.Options{}
   457  }
   458  
   459  // defaultDBConfig returns default database.DBConfig for pre-generated tests.
   460  func defaultDBConfig() *database.DBConfig {
   461  	return &database.DBConfig{SingleDB: false, ParallelDBWrite: true, NumStateTrieShards: 4}
   462  }
   463  
   464  // getChainConfig returns chain config from chainDB.
   465  func getChainConfig(chainDB database.DBManager) (*params.ChainConfig, error) {
   466  	stored := chainDB.ReadBlockByNumber(0)
   467  	if stored == nil {
   468  		return nil, errors.New("chainDB.ReadBlockByNumber(0) == nil")
   469  	}
   470  
   471  	chainConfig := chainDB.ReadChainConfig(stored.Hash())
   472  	if chainConfig == nil {
   473  		return nil, errors.New("chainConfig == nil")
   474  	}
   475  
   476  	return chainConfig, nil
   477  }
   478  
   479  // defaultCacheConfig returns cache config for data generation tests.
   480  func defaultCacheConfig() *blockchain.CacheConfig {
   481  	return &blockchain.CacheConfig{
   482  		ArchiveMode:   false,
   483  		CacheSize:     512,
   484  		BlockInterval: blockchain.DefaultBlockInterval,
   485  		TriesInMemory: blockchain.DefaultTriesInMemory,
   486  		TrieNodeCacheConfig: &statedb.TrieNodeCacheConfig{
   487  			CacheType:          statedb.CacheTypeLocal,
   488  			LocalCacheSizeMiB:  4096,
   489  			FastCacheFileDir:   "",
   490  			RedisEndpoints:     nil,
   491  			RedisClusterEnable: false,
   492  		},
   493  		SnapshotCacheSize: 512,
   494  	}
   495  }
   496  
   497  // generateGovernaceDataForTest returns governance.Engine for test.
   498  func generateGovernaceDataForTest() governance.Engine {
   499  	dbm := database.NewDBManager(&database.DBConfig{DBType: database.MemoryDB})
   500  
   501  	return governance.NewMixedEngine(&params.ChainConfig{
   502  		ChainID:       big.NewInt(2018),
   503  		UnitPrice:     25000000000,
   504  		DeriveShaImpl: 0,
   505  		Istanbul: &params.IstanbulConfig{
   506  			Epoch:          istanbul.DefaultConfig.Epoch,
   507  			ProposerPolicy: uint64(istanbul.DefaultConfig.ProposerPolicy),
   508  			SubGroupSize:   istanbul.DefaultConfig.SubGroupSize,
   509  		},
   510  		Governance: params.GetDefaultGovernanceConfig(),
   511  	}, dbm)
   512  }
   513  
   514  // setUpTest sets up test data directory, verbosity and profile file.
   515  func setUpTest(tc *preGeneratedTC) (string, *os.File, error) {
   516  	log.EnableLogForTest(log.LvlCrit, log.LvlTrace)
   517  
   518  	testDataDir, err := setupTestDir(tc.originalDataDir, tc.isGenerateTest)
   519  	if err != nil {
   520  		return "", nil, fmt.Errorf("err: %v, dir: %v", err, testDataDir)
   521  	}
   522  
   523  	timeNow := time.Now()
   524  	f, err := os.Create(tc.testName + "_" + timeNow.Format("2006-01-02-1504") + ".cpu.out")
   525  	if err != nil {
   526  		return "", nil, fmt.Errorf("failed to create file for cpu profiling, err: %v", err)
   527  	}
   528  
   529  	return testDataDir, f, nil
   530  }