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