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 }