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 }