github.com/klaytn/klaytn@v1.12.1/tests/pregenerated_data_generation_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  	"crypto/ecdsa"
    21  	"errors"
    22  	"fmt"
    23  	"math/big"
    24  	"math/rand"
    25  	"os"
    26  	"path"
    27  	"path/filepath"
    28  	"runtime/pprof"
    29  	"strings"
    30  	"testing"
    31  	"time"
    32  
    33  	"github.com/klaytn/klaytn/blockchain"
    34  	"github.com/klaytn/klaytn/blockchain/state"
    35  	"github.com/klaytn/klaytn/blockchain/types"
    36  	"github.com/klaytn/klaytn/common"
    37  	"github.com/klaytn/klaytn/params"
    38  	"github.com/klaytn/klaytn/storage/database"
    39  	"github.com/otiai10/copy"
    40  	"github.com/syndtr/goleveldb/leveldb/opt"
    41  )
    42  
    43  func init() {
    44  	rand.Seed(time.Now().UnixNano())
    45  }
    46  
    47  var errNoOriginalDataDir = errors.New("original data directory does not exist, aborting the test")
    48  
    49  const (
    50  	// All databases are compressed by Snappy, CompactionTableSize = 2MiB, CompactionTableSizeMultiplier = 1.0
    51  	aspen500_orig = "aspen500_orig"
    52  	// All databases are compressed by Snappy, CompactionTableSize = 4MiB, CompactionTableSizeMultiplier = 2.0
    53  	baobab500_orig = "baobab500_orig"
    54  
    55  	// Only receipt database is compressed by Snappy, CompactionTableSize = 2MiB, CompactionTableSizeMultiplier = 1.0
    56  	candidate500LevelDB_orig = "candidate500LevelDB_orig"
    57  	// Using BadgerDB with its default options.
    58  	candidate500BadgerDB_orig = "candidate500BadgerDB_orig"
    59  
    60  	// Same configuration as Baobab network, however only 10,000 accounts exist.
    61  	baobab1_orig = "baobab1_orig"
    62  )
    63  
    64  // randomIndex is used to access data with random index.
    65  func randomIndex(index, lenAddrs int) int {
    66  	return rand.Intn(lenAddrs)
    67  }
    68  
    69  // sequentialIndex is used to access data with sequential index.
    70  func sequentialIndex(index, lenAddrs int) int {
    71  	return index % lenAddrs
    72  }
    73  
    74  // fixedIndex is used to access data with same index.
    75  func fixedIndex(index int) func(int, int) int {
    76  	return func(int, int) int {
    77  		return index
    78  	}
    79  }
    80  
    81  // makeTxsWithStateDB generates transactions with the nonce retrieved from stateDB.
    82  // stateDB is used only once to initialize nonceMap, and then nonceMap is used instead of stateDB.
    83  func makeTxsWithStateDB(isGenerate bool, stateDB *state.StateDB, fromAddrs []*common.Address, fromKeys []*ecdsa.PrivateKey, toAddrs []*common.Address, signer types.Signer, numTransactions int, indexPicker func(int, int) int) (types.Transactions, map[common.Address]uint64, error) {
    84  	if len(fromAddrs) != len(fromKeys) {
    85  		return nil, nil, fmt.Errorf("len(fromAddrs) %v != len(fromKeys) %v", len(fromAddrs), len(fromKeys))
    86  	}
    87  
    88  	// Use nonceMap, not to change the nonce of stateDB.
    89  	nonceMap := make(map[common.Address]uint64)
    90  	for _, addr := range fromAddrs {
    91  		nonce := stateDB.GetNonce(*addr)
    92  		nonceMap[*addr] = nonce
    93  	}
    94  
    95  	// Generate value transfer transactions from initial account to the given "toAddrs".
    96  	return makeTxsWithNonceMap(isGenerate, nonceMap, fromAddrs, fromKeys, toAddrs, signer, numTransactions, indexPicker)
    97  }
    98  
    99  // makeTxsWithStateDB generates transactions with the nonce retrieved from nonceMap.
   100  func makeTxsWithNonceMap(isGenerate bool, nonceMap map[common.Address]uint64, fromAddrs []*common.Address, fromKeys []*ecdsa.PrivateKey, toAddrs []*common.Address, signer types.Signer, numTransactions int, indexPicker func(int, int) int) (types.Transactions, map[common.Address]uint64, error) {
   101  	txs := make(types.Transactions, 0, numTransactions)
   102  	lenFromAddrs := len(fromAddrs)
   103  	lenToAddrs := len(toAddrs)
   104  
   105  	var transferValue *big.Int
   106  	if isGenerate {
   107  		transferValue = new(big.Int).Mul(big.NewInt(1e4), big.NewInt(params.KLAY))
   108  	} else {
   109  		transferValue = new(big.Int).Mul(big.NewInt(1e3), big.NewInt(params.Peb))
   110  	}
   111  
   112  	for i := 0; i < numTransactions; i++ {
   113  		fromIdx := indexPicker(i, lenFromAddrs)
   114  		toIdx := indexPicker(i, lenToAddrs)
   115  
   116  		fromAddr := *fromAddrs[fromIdx]
   117  		fromKey := fromKeys[fromIdx]
   118  		fromNonce := nonceMap[fromAddr]
   119  
   120  		toAddr := *toAddrs[toIdx]
   121  
   122  		tx := types.NewTransaction(fromNonce, toAddr, transferValue, 1000000, new(big.Int).SetInt64(25000000000), nil)
   123  		signedTx, err := types.SignTx(tx, signer, fromKey)
   124  		if err != nil {
   125  			return nil, nil, err
   126  		}
   127  
   128  		txs = append(txs, signedTx)
   129  		nonceMap[fromAddr]++
   130  	}
   131  
   132  	return txs, nonceMap, nil
   133  }
   134  
   135  // setupTestDir does two things. If it is a data-generating test, it will just
   136  // return the target path. If it is not a data-generating test, it will remove
   137  // previously existing path and then copy the original data to the target path.
   138  func setupTestDir(originalDataDirName string, isGenerateTest bool) (string, error) {
   139  	wd, err := os.Getwd()
   140  	if err != nil {
   141  		return "", err
   142  	}
   143  
   144  	// Original data directory should be located at github.com/klaytn
   145  	// Therefore, it should be something like github.com/klaytn/testdata150_orig
   146  	grandParentPath := filepath.Dir(filepath.Dir(wd))
   147  	originalDataDirPath := path.Join(grandParentPath, originalDataDirName)
   148  
   149  	// If it is generating test case, just returns the path.
   150  	if isGenerateTest {
   151  		return originalDataDirPath, nil
   152  	}
   153  
   154  	if _, err = os.Stat(originalDataDirPath); err != nil {
   155  		return "", errNoOriginalDataDir
   156  	}
   157  
   158  	testDir := strings.Split(originalDataDirName, "_orig")[0]
   159  
   160  	originalDataPath := path.Join(grandParentPath, originalDataDirName)
   161  	testDataPath := path.Join(grandParentPath, testDir)
   162  
   163  	os.RemoveAll(testDataPath)
   164  	if err := copy.Copy(originalDataPath, testDataPath); err != nil {
   165  		return "", err
   166  	}
   167  	return testDataPath, nil
   168  }
   169  
   170  type preGeneratedTC struct {
   171  	isGenerateTest  bool
   172  	testName        string
   173  	originalDataDir string
   174  
   175  	numTotalAccountsToGenerate int
   176  	numTxsPerGen               int
   177  
   178  	numTotalSenders    int // senders are loaded once at the test initialization time.
   179  	numReceiversPerRun int // receivers are loaded repetitively for every tx generation run.
   180  
   181  	filePicker func(int, int) int // determines the index of address file to use.
   182  	addrPicker func(int, int) int // determines the index of address while making tx.
   183  
   184  	dbc           *database.DBConfig
   185  	levelDBOption *opt.Options
   186  	cacheConfig   *blockchain.CacheConfig
   187  }
   188  
   189  // BenchmarkDataGeneration_Aspen generates the data with Aspen network's database configurations.
   190  func BenchmarkDataGeneration_Aspen(b *testing.B) {
   191  	tc := getGenerationTestDefaultTC()
   192  	tc.testName = "BenchmarkDataGeneration_Aspen"
   193  	tc.originalDataDir = aspen500_orig
   194  
   195  	tc.cacheConfig = defaultCacheConfig()
   196  
   197  	tc.dbc, tc.levelDBOption = genAspenOptions()
   198  
   199  	dataGenerationTest(b, tc)
   200  }
   201  
   202  // BenchmarkDataGeneration_Baobab generates the data with Baobab network's database configurations.
   203  func BenchmarkDataGeneration_Baobab(b *testing.B) {
   204  	tc := getGenerationTestDefaultTC()
   205  	tc.testName = "BenchmarkDataGeneration_Baobab"
   206  	tc.originalDataDir = baobab500_orig
   207  
   208  	tc.cacheConfig = defaultCacheConfig()
   209  
   210  	tc.dbc, tc.levelDBOption = genBaobabOptions()
   211  
   212  	dataGenerationTest(b, tc)
   213  }
   214  
   215  // BenchmarkDataGeneration_CandidateLevelDB generates the data for main-net's
   216  // with candidate configurations, using LevelDB.
   217  func BenchmarkDataGeneration_CandidateLevelDB(b *testing.B) {
   218  	tc := getGenerationTestDefaultTC()
   219  	tc.testName = "BenchmarkDataGeneration_CandidateLevelDB"
   220  	tc.originalDataDir = candidate500LevelDB_orig
   221  
   222  	tc.cacheConfig = defaultCacheConfig()
   223  
   224  	tc.dbc, tc.levelDBOption = genCandidateLevelDBOptions()
   225  
   226  	dataGenerationTest(b, tc)
   227  }
   228  
   229  // BenchmarkDataGeneration_CandidateBadgerDB generates the data for main-net's
   230  // with candidate configurations, using BadgerDB.
   231  func BenchmarkDataGeneration_CandidateBadgerDB(b *testing.B) {
   232  	tc := getGenerationTestDefaultTC()
   233  	tc.testName = "BenchmarkDataGeneration_CandidateBadgerDB"
   234  	tc.originalDataDir = candidate500BadgerDB_orig
   235  
   236  	tc.cacheConfig = defaultCacheConfig()
   237  
   238  	tc.dbc, tc.levelDBOption = genCandidateBadgerDBOptions()
   239  
   240  	dataGenerationTest(b, tc)
   241  }
   242  
   243  // BenchmarkDataGeneration_Baobab_ControlGroup generates the data with Baobab network's database configurations.
   244  // To work as a control group, it only generates 10,000 accounts.
   245  func BenchmarkDataGeneration_Baobab_ControlGroup(b *testing.B) {
   246  	tc := getGenerationTestDefaultTC()
   247  	tc.testName = "BenchmarkDataGeneration_Baobab_ControlGroup"
   248  	tc.originalDataDir = baobab1_orig
   249  	tc.numTotalAccountsToGenerate = 10000
   250  
   251  	tc.cacheConfig = defaultCacheConfig()
   252  
   253  	tc.dbc, tc.levelDBOption = genBaobabOptions()
   254  
   255  	dataGenerationTest(b, tc)
   256  }
   257  
   258  // dataGenerationTest generates given number of accounts for pre-generated tests.
   259  // Newly generated data directory will be located at "$GOPATH/src/github.com/klaytn/"
   260  func dataGenerationTest(b *testing.B, tc *preGeneratedTC) {
   261  	testDataDir, profileFile, err := setUpTest(tc)
   262  	if err != nil {
   263  		b.Fatal(err)
   264  	}
   265  
   266  	bcData, err := NewBCDataForPreGeneratedTest(testDataDir, tc)
   267  	if err != nil {
   268  		b.Fatal(err)
   269  	}
   270  
   271  	defer bcData.db.Close()
   272  	defer bcData.bc.Stop()
   273  
   274  	txPool := makeTxPool(bcData, tc.numTxsPerGen)
   275  	signer := types.MakeSigner(bcData.bc.Config(), bcData.bc.CurrentHeader().Number)
   276  
   277  	b.ResetTimer()
   278  	b.StopTimer()
   279  
   280  	numTxGenerationRuns := tc.numTotalAccountsToGenerate / tc.numTxsPerGen
   281  	for run := 1; run < numTxGenerationRuns; run++ {
   282  		toAddrs, _, err := makeOrGenerateAddrsAndKeys(testDataDir, run, tc)
   283  		if err != nil {
   284  			b.Fatal(err)
   285  		}
   286  
   287  		// Generate transactions
   288  		stateDB, err := bcData.bc.State()
   289  		if err != nil {
   290  			b.Fatal(err)
   291  		}
   292  
   293  		txs, _, err := makeTxsWithStateDB(true, stateDB, bcData.addrs, bcData.privKeys, toAddrs, signer, tc.numTxsPerGen, tc.addrPicker)
   294  		if err != nil {
   295  			b.Fatal(err)
   296  		}
   297  
   298  		for _, tx := range txs {
   299  			tx.AsMessageWithAccountKeyPicker(signer, stateDB, bcData.bc.CurrentBlock().NumberU64())
   300  		}
   301  
   302  		b.StartTimer()
   303  		if run == numTxGenerationRuns {
   304  			pprof.StartCPUProfile(profileFile)
   305  		}
   306  
   307  		txPool.AddRemotes(txs)
   308  
   309  		for {
   310  			if err := bcData.GenABlockWithTxPoolWithoutAccountMap(txPool); err != nil {
   311  				if err == errEmptyPending {
   312  					break
   313  				}
   314  				b.Fatal(err)
   315  			}
   316  		}
   317  
   318  		if run == numTxGenerationRuns {
   319  			pprof.StopCPUProfile()
   320  		}
   321  		b.StopTimer()
   322  	}
   323  }
   324  
   325  // getGenerationTestDefaultTC returns default TC of data generation tests.
   326  func getGenerationTestDefaultTC() *preGeneratedTC {
   327  	return &preGeneratedTC{
   328  		isGenerateTest:             true,
   329  		numTotalAccountsToGenerate: 500 * 10000, numTxsPerGen: 10000,
   330  		numTotalSenders: 10000, numReceiversPerRun: 10000,
   331  		filePicker: sequentialIndex, addrPicker: sequentialIndex,
   332  		cacheConfig: defaultCacheConfig(),
   333  	}
   334  }