github.com/klaytn/klaytn@v1.12.1/tests/klay_blockchain_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  	"crypto/ecdsa"
    21  	"math/big"
    22  	"os"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/klaytn/klaytn/accounts/keystore"
    27  	"github.com/klaytn/klaytn/blockchain"
    28  	"github.com/klaytn/klaytn/blockchain/types"
    29  	"github.com/klaytn/klaytn/common"
    30  	"github.com/klaytn/klaytn/consensus/istanbul"
    31  	"github.com/klaytn/klaytn/crypto"
    32  	"github.com/klaytn/klaytn/log"
    33  	"github.com/klaytn/klaytn/networks/p2p"
    34  	"github.com/klaytn/klaytn/node"
    35  	"github.com/klaytn/klaytn/node/cn"
    36  	"github.com/klaytn/klaytn/params"
    37  	"github.com/klaytn/klaytn/rlp"
    38  	"github.com/klaytn/klaytn/work"
    39  	"github.com/pkg/errors"
    40  	"github.com/stretchr/testify/assert"
    41  	"github.com/stretchr/testify/require"
    42  )
    43  
    44  // TestSimpleBlockchain
    45  func TestSimpleBlockchain(t *testing.T) {
    46  	log.EnableLogForTest(log.LvlCrit, log.LvlTrace)
    47  
    48  	numAccounts := 12
    49  	fullNode, node, validator, chainId, workspace := newBlockchain(t, nil, nil)
    50  	defer os.RemoveAll(workspace)
    51  
    52  	// create account
    53  	richAccount, accounts, contractAccounts := createAccount(t, numAccounts, validator)
    54  
    55  	contractDeployCode := "0x608060405234801561001057600080fd5b506000808190555060646001819055506101848061002f6000396000f300608060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806302e5329e14610067578063197e70e41461009457806349b667d2146100c157806367e0badb146100ec575b600080fd5b34801561007357600080fd5b5061009260048036038101908080359060200190929190505050610117565b005b3480156100a057600080fd5b506100bf60048036038101908080359060200190929190505050610121565b005b3480156100cd57600080fd5b506100d6610145565b6040518082815260200191505060405180910390f35b3480156100f857600080fd5b5061010161014f565b6040518082815260200191505060405180910390f35b8060018190555050565b806000540160008190555060015460005481151561013b57fe5b0660008190555050565b6000600154905090565b600080549050905600a165627a7a72305820ef4e7e564c744de3a36cb74000c35687f7de9ecf1d29abdd3c4bcc66db981c160029"
    56  	for i := 0; i < numAccounts; i++ {
    57  		_, contractAccounts[i].Addr = deployContractDeployTx(t, node.TxPool(), chainId, richAccount, contractDeployCode)
    58  	}
    59  	time.Sleep(time.Second) // need to make a block before contract execution
    60  
    61  	// deploy
    62  	calldata := "0x197e70e40000000000000000000000000000000000000000000000000000000000000001"
    63  	for i := 0; i < numAccounts; i++ {
    64  		deployRandomTxs(t, node.TxPool(), chainId, richAccount, 10)
    65  		deployValueTransferTx(t, node.TxPool(), chainId, richAccount, accounts[i%numAccounts])
    66  		deployContractExecutionTx(t, node.TxPool(), chainId, richAccount, contractAccounts[i%numAccounts].Addr, calldata)
    67  
    68  		// time.Sleep(time.Second) // wait until txpool is flushed if needed
    69  	}
    70  
    71  	// stop full node
    72  	if err := fullNode.Stop(); err != nil {
    73  		t.Fatal(err)
    74  	}
    75  	time.Sleep(2 * time.Second)
    76  
    77  	// start full node with previous db
    78  	fullNode, node, err := newKlaytnNode(t, workspace, validator, nil, nil)
    79  	assert.NoError(t, err)
    80  	if err := node.StartMining(false); err != nil {
    81  		t.Fatal()
    82  	}
    83  	time.Sleep(2 * time.Second)
    84  
    85  	// stop node before ending the test code
    86  	if err := fullNode.Stop(); err != nil {
    87  		t.Fatal(err)
    88  	}
    89  }
    90  
    91  func newBlockchain(t *testing.T, config *params.ChainConfig, genesis *blockchain.Genesis) (*node.Node, *cn.CN, *TestAccountType, *big.Int, string) {
    92  	t.Log("Create a new blockchain")
    93  	// Prepare workspace
    94  	workspace, err := os.MkdirTemp("", "klaytn-test-state")
    95  	if err != nil {
    96  		t.Fatalf("failed to create temporary keystore: %v", err)
    97  	}
    98  	t.Log("Workspace is ", workspace)
    99  
   100  	// Prepare a validator
   101  	validator, err := createAnonymousAccount(getRandomPrivateKeyString(t))
   102  	if err != nil {
   103  		t.Fatal(err)
   104  	}
   105  
   106  	// Create a Klaytn node
   107  	fullNode, node, err := newKlaytnNode(t, workspace, validator, config, genesis)
   108  	assert.NoError(t, err)
   109  	if err := node.StartMining(false); err != nil {
   110  		t.Fatal()
   111  	}
   112  	time.Sleep(2 * time.Second) // wait for initializing mining
   113  
   114  	chainId := node.BlockChain().Config().ChainID
   115  
   116  	return fullNode, node, validator, chainId, workspace
   117  }
   118  
   119  func createAccount(t *testing.T, numAccounts int, validator *TestAccountType) (*TestAccountType, []*TestAccountType, []*TestAccountType) {
   120  	accounts := make([]*TestAccountType, numAccounts)
   121  	contractAccounts := make([]*TestAccountType, numAccounts)
   122  
   123  	// richAccount is used for deploying smart contracts
   124  	richAccount := &TestAccountType{
   125  		Addr:  validator.Addr,
   126  		Keys:  []*ecdsa.PrivateKey{validator.Keys[0]},
   127  		Nonce: uint64(0),
   128  	}
   129  
   130  	var err error
   131  	for i := 0; i < numAccounts; i++ {
   132  		if accounts[i], err = createAnonymousAccount(getRandomPrivateKeyString(t)); err != nil {
   133  			t.Fatal()
   134  		}
   135  		// address should be overwritten
   136  		if contractAccounts[i], err = createAnonymousAccount(getRandomPrivateKeyString(t)); err != nil {
   137  			t.Fatal()
   138  		}
   139  	}
   140  
   141  	return richAccount, accounts, contractAccounts
   142  }
   143  
   144  // newKlaytnNode creates a klaytn node
   145  func newKlaytnNode(t *testing.T, dir string, validator *TestAccountType, config *params.ChainConfig, genesis *blockchain.Genesis) (*node.Node, *cn.CN, error) {
   146  	var klaytnNode *cn.CN
   147  
   148  	fullNode, err := node.New(&node.Config{
   149  		DataDir:           dir,
   150  		UseLightweightKDF: true,
   151  		P2P:               p2p.Config{PrivateKey: validator.Keys[0], NoListen: true},
   152  	})
   153  	if err != nil {
   154  		t.Fatalf("failed to create node: %v", err)
   155  	}
   156  
   157  	istanbulConfData, err := rlp.EncodeToBytes(&types.IstanbulExtra{
   158  		Validators:    []common.Address{validator.Addr},
   159  		Seal:          []byte{},
   160  		CommittedSeal: [][]byte{},
   161  	})
   162  	if err != nil {
   163  		t.Fatal(err)
   164  	}
   165  
   166  	if genesis == nil {
   167  		genesis = blockchain.DefaultGenesisBlock()
   168  		genesis.ExtraData = genesis.ExtraData[:types.IstanbulExtraVanity]
   169  		genesis.ExtraData = append(genesis.ExtraData, istanbulConfData...)
   170  	}
   171  
   172  	if config == nil {
   173  		genesis.Config = params.CypressChainConfig.Copy()
   174  		genesis.Config.Istanbul.SubGroupSize = 1
   175  		genesis.Config.Istanbul.ProposerPolicy = uint64(istanbul.RoundRobin)
   176  		genesis.Config.Governance.Reward.MintingAmount = new(big.Int).Mul(big.NewInt(9000000000000000000), big.NewInt(params.KLAY))
   177  	} else {
   178  		genesis.Config = config
   179  	}
   180  
   181  	cnConf := cn.GetDefaultConfig()
   182  	cnConf.Genesis = genesis
   183  	cnConf.Rewardbase = validator.Addr
   184  	cnConf.SingleDB = false
   185  	cnConf.NumStateTrieShards = 4
   186  
   187  	ks := fullNode.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
   188  	_, _ = ks.ImportECDSA(validator.Keys[0], "") // import a node key
   189  
   190  	if err = fullNode.Register(func(ctx *node.ServiceContext) (node.Service, error) { return cn.New(ctx, cnConf) }); err != nil {
   191  		return nil, nil, errors.WithMessage(err, "failed to register Klaytn protocol")
   192  	}
   193  
   194  	if err = fullNode.Start(); err != nil {
   195  		return nil, nil, errors.WithMessage(err, "failed to start test fullNode")
   196  	}
   197  
   198  	if err := fullNode.Service(&klaytnNode); err != nil {
   199  		return nil, nil, err
   200  	}
   201  
   202  	return fullNode, klaytnNode, nil
   203  }
   204  
   205  // deployRandomTxs creates a random transaction
   206  func deployRandomTxs(t *testing.T, txpool work.TxPool, chainId *big.Int, sender *TestAccountType, txNum int) []*types.Transaction {
   207  	var tx *types.Transaction
   208  	var txs []*types.Transaction
   209  	signer := types.LatestSignerForChainID(chainId)
   210  	gasPrice := txpool.GasPrice()
   211  
   212  	txNuminABlock := 100
   213  	for i := 0; i < txNum; i++ {
   214  		if i%txNuminABlock == 0 {
   215  			time.Sleep(time.Second)
   216  		}
   217  
   218  		receiver, err := createAnonymousAccount(getRandomPrivateKeyString(t))
   219  		require.Nil(t, err)
   220  
   221  		tx, _ = genLegacyTransaction(t, signer, sender, receiver, nil, gasPrice)
   222  
   223  		err = txpool.AddLocal(tx)
   224  		require.True(t, err == nil || err == blockchain.ErrAlreadyNonceExistInPool)
   225  
   226  		txs = append(txs, tx)
   227  		sender.AddNonce()
   228  	}
   229  	return txs
   230  }
   231  
   232  // deployValueTransferTx deploy value transfer transactions
   233  func deployValueTransferTx(t *testing.T, txpool work.TxPool, chainId *big.Int, sender *TestAccountType, toAcc *TestAccountType) *types.Transaction {
   234  	signer := types.LatestSignerForChainID(chainId)
   235  	gasPrice := txpool.GasPrice()
   236  
   237  	tx, _ := genLegacyTransaction(t, signer, sender, toAcc, nil, gasPrice)
   238  
   239  	err := txpool.AddLocal(tx)
   240  	require.True(t, err == nil || err == blockchain.ErrAlreadyNonceExistInPool)
   241  
   242  	sender.AddNonce()
   243  	return tx
   244  }
   245  
   246  // deployContractDeployTx deploys a contract
   247  func deployContractDeployTx(t *testing.T, txpool work.TxPool, chainId *big.Int, sender *TestAccountType, code string) (*types.Transaction, common.Address) {
   248  	signer := types.LatestSignerForChainID(chainId)
   249  	gasPrice := txpool.GasPrice()
   250  
   251  	values := map[types.TxValueKeyType]interface{}{
   252  		types.TxValueKeyNonce:         sender.GetNonce(),
   253  		types.TxValueKeyFrom:          sender.GetAddr(),
   254  		types.TxValueKeyTo:            (*common.Address)(nil),
   255  		types.TxValueKeyAmount:        new(big.Int).SetUint64(0),
   256  		types.TxValueKeyGasLimit:      uint64(1e8),
   257  		types.TxValueKeyGasPrice:      gasPrice,
   258  		types.TxValueKeyData:          common.FromHex(code),
   259  		types.TxValueKeyCodeFormat:    params.CodeFormatEVM,
   260  		types.TxValueKeyHumanReadable: false,
   261  	}
   262  
   263  	tx, err := types.NewTransactionWithMap(types.TxTypeSmartContractDeploy, values)
   264  	require.Nil(t, err)
   265  
   266  	err = tx.SignWithKeys(signer, sender.GetTxKeys())
   267  	require.Nil(t, err)
   268  
   269  	err = txpool.AddLocal(tx)
   270  	if err != nil && err != blockchain.ErrAlreadyNonceExistInPool {
   271  		t.Fatal(err)
   272  	}
   273  
   274  	contractAddr := crypto.CreateAddress(sender.Addr, sender.Nonce)
   275  	sender.AddNonce()
   276  	return tx, contractAddr
   277  }
   278  
   279  func deployContractExecutionTx(t *testing.T, txpool work.TxPool, chainId *big.Int, sender *TestAccountType, contractAddr common.Address, calldata string) *types.Transaction {
   280  	signer := types.LatestSignerForChainID(chainId)
   281  	gasPrice := txpool.GasPrice()
   282  
   283  	values := map[types.TxValueKeyType]interface{}{
   284  		types.TxValueKeyNonce:    sender.GetNonce(),
   285  		types.TxValueKeyFrom:     sender.GetAddr(),
   286  		types.TxValueKeyTo:       contractAddr,
   287  		types.TxValueKeyAmount:   new(big.Int).SetUint64(0),
   288  		types.TxValueKeyGasLimit: uint64(1e8),
   289  		types.TxValueKeyGasPrice: gasPrice,
   290  		types.TxValueKeyData:     common.FromHex(calldata),
   291  	}
   292  
   293  	tx, err := types.NewTransactionWithMap(types.TxTypeSmartContractExecution, values)
   294  	require.Nil(t, err)
   295  
   296  	err = tx.SignWithKeys(signer, sender.GetTxKeys())
   297  	require.Nil(t, err)
   298  
   299  	err = txpool.AddLocal(tx)
   300  	if err != nil && err != blockchain.ErrAlreadyNonceExistInPool {
   301  		t.Fatal(err)
   302  	}
   303  
   304  	sender.AddNonce()
   305  	return tx
   306  }
   307  
   308  // Wait until the receipt for `txhash` is ready
   309  // Returns the receipt
   310  // Returns nil after a reasonable timeout
   311  func waitReceipt(chain *blockchain.BlockChain, txhash common.Hash) *types.Receipt {
   312  	if receipt := chain.GetReceiptByTxHash(txhash); receipt != nil {
   313  		return receipt
   314  	}
   315  	chainEventCh := make(chan blockchain.ChainEvent)
   316  	subscription := chain.SubscribeChainEvent(chainEventCh)
   317  	defer subscription.Unsubscribe()
   318  	timeout := time.NewTimer(15 * time.Second)
   319  	for {
   320  		select {
   321  		case <-timeout.C:
   322  			return nil
   323  		case <-chainEventCh:
   324  			receipt := chain.GetReceiptByTxHash(txhash)
   325  			if receipt != nil {
   326  				return receipt
   327  			}
   328  		}
   329  	}
   330  }
   331  
   332  // Wait until `num` block is mined
   333  // Returns the header with the number larger or equal to `num`
   334  // Returns nil after a reasonable timeout
   335  func waitBlock(chain work.BlockChain, num uint64) *types.Header {
   336  	head := chain.CurrentHeader()
   337  	if head.Number.Uint64() >= num {
   338  		return head
   339  	}
   340  
   341  	chainEventCh := make(chan blockchain.ChainEvent)
   342  	subscription := chain.SubscribeChainEvent(chainEventCh)
   343  	defer subscription.Unsubscribe()
   344  	// Wait until desired `num` plus 10 seconds margin
   345  	timeoutSec := num - head.Number.Uint64() + 10
   346  	timeout := time.NewTimer(time.Duration(timeoutSec) * time.Second)
   347  	for {
   348  		select {
   349  		case <-timeout.C:
   350  			return nil
   351  		case <-chainEventCh:
   352  			head := chain.CurrentHeader()
   353  			if head.Number.Uint64() >= num {
   354  				return head
   355  			}
   356  		}
   357  	}
   358  }