github.com/klaytn/klaytn@v1.12.1/tests/testutil_blockchain_test.go (about) 1 package tests 2 3 import ( 4 "crypto/ecdsa" 5 "crypto/sha512" 6 "errors" 7 "fmt" 8 "math/big" 9 "os" 10 "path/filepath" 11 "testing" 12 "time" 13 14 "github.com/klaytn/klaytn/accounts/abi/bind" 15 "github.com/klaytn/klaytn/blockchain" 16 "github.com/klaytn/klaytn/blockchain/types" 17 "github.com/klaytn/klaytn/common" 18 "github.com/klaytn/klaytn/consensus/istanbul" 19 "github.com/klaytn/klaytn/crypto" 20 "github.com/klaytn/klaytn/crypto/bls" 21 "github.com/klaytn/klaytn/networks/p2p" 22 "github.com/klaytn/klaytn/networks/p2p/discover" 23 "github.com/klaytn/klaytn/networks/rpc" 24 "github.com/klaytn/klaytn/node" 25 "github.com/klaytn/klaytn/node/cn" 26 "github.com/klaytn/klaytn/params" 27 "github.com/klaytn/klaytn/reward" 28 "github.com/klaytn/klaytn/rlp" 29 "github.com/stretchr/testify/assert" 30 "github.com/tyler-smith/go-bip32" 31 "golang.org/x/crypto/pbkdf2" 32 ) 33 34 // Full blockchain test context. 35 // TODO: replace newBlockchain() 36 type blockchainTestContext struct { 37 numNodes int 38 accountKeys []*ecdsa.PrivateKey 39 accountAddrs []common.Address 40 accounts []*bind.TransactOpts // accounts[0:numNodes] are node keys 41 config *params.ChainConfig 42 genesis *blockchain.Genesis 43 44 workspace string 45 nodes []*blockchainTestNode 46 } 47 48 type blockchainTestNode struct { 49 datadir string 50 node *node.Node 51 cn *cn.CN 52 } 53 54 type blockchainTestOverrides struct { 55 numNodes int // default: 1 56 numAccounts int // default: numNodes 57 config *params.ChainConfig // default: blockchainTestChainConfig 58 alloc blockchain.GenesisAlloc // default: 10_000_000 KLAY for each account 59 } 60 61 var blockchainTestChainConfig = ¶ms.ChainConfig{ 62 ChainID: big.NewInt(31337), 63 DeriveShaImpl: 2, 64 UnitPrice: 25 * params.Ston, 65 Governance: ¶ms.GovernanceConfig{ 66 GovernanceMode: "none", 67 Reward: ¶ms.RewardConfig{ 68 MintingAmount: big.NewInt(params.KLAY * 6.4), 69 Ratio: "100/0/0", 70 Kip82Ratio: "20/80", 71 UseGiniCoeff: false, 72 DeferredTxFee: true, 73 StakingUpdateInterval: 60, 74 ProposerUpdateInterval: 30, 75 MinimumStake: big.NewInt(5_000_000), 76 }, 77 }, 78 Istanbul: ¶ms.IstanbulConfig{ 79 Epoch: 120, 80 ProposerPolicy: uint64(istanbul.RoundRobin), 81 SubGroupSize: 100, 82 }, 83 } 84 85 func newBlockchainTestContext(overrides *blockchainTestOverrides) (*blockchainTestContext, error) { 86 if overrides == nil { 87 overrides = &blockchainTestOverrides{} 88 } 89 if overrides.numNodes == 0 { 90 overrides.numNodes = 1 91 } 92 if overrides.numAccounts == 0 { 93 overrides.numAccounts = overrides.numNodes 94 } 95 if overrides.numAccounts < overrides.numNodes { 96 return nil, errors.New("numAccounts less than numNodes") 97 } 98 if overrides.config == nil { 99 overrides.config = blockchainTestChainConfig 100 } 101 if overrides.alloc == nil { 102 overrides.alloc = make(blockchain.GenesisAlloc) 103 } 104 105 ctx := &blockchainTestContext{ 106 numNodes: overrides.numNodes, 107 } 108 ctx.setAccounts(overrides.numAccounts) 109 ctx.setConfig(overrides.config) 110 ctx.setGenesis(overrides.alloc) 111 ctx.setWorkspace() 112 err := ctx.setNodes(ctx.numNodes) 113 return ctx, err 114 } 115 116 func (ctx *blockchainTestContext) setAccounts(count int) { 117 ctx.accountKeys = make([]*ecdsa.PrivateKey, count) 118 ctx.accountAddrs = make([]common.Address, count) 119 ctx.accounts = make([]*bind.TransactOpts, count) 120 for i := 0; i < count; i++ { 121 privateKey := deriveTestAccount(i) 122 ctx.accountKeys[i] = privateKey 123 ctx.accountAddrs[i] = crypto.PubkeyToAddress(privateKey.PublicKey) 124 ctx.accounts[i] = bind.NewKeyedTransactor(privateKey) 125 } 126 } 127 128 func (ctx *blockchainTestContext) setConfig(config *params.ChainConfig) { 129 ctx.config = config.Copy() 130 ctx.config.Istanbul.SubGroupSize = uint64(ctx.numNodes) 131 } 132 133 func (ctx *blockchainTestContext) setGenesis(alloc blockchain.GenesisAlloc) { 134 // Genesis ExtraData from nodeAddrs 135 extra, _ := rlp.EncodeToBytes(&types.IstanbulExtra{ 136 Validators: ctx.accountAddrs[:ctx.numNodes], 137 Seal: []byte{}, 138 CommittedSeal: [][]byte{}, 139 }) 140 vanity := make([]byte, types.IstanbulExtraVanity) 141 142 // Genesis Alloc from overrides.alloc + rich accountAddrs 143 richBalance := new(big.Int).Mul(big.NewInt(params.KLAY), big.NewInt(10_000_000)) 144 for _, addr := range ctx.accountAddrs { 145 account := alloc[addr] 146 account.Balance = richBalance 147 alloc[addr] = account 148 } 149 150 ctx.genesis = &blockchain.Genesis{ 151 Config: ctx.config, 152 Timestamp: uint64(time.Now().Unix()), 153 ExtraData: append(vanity, extra...), 154 BlockScore: common.Big1, 155 Alloc: alloc, 156 } 157 } 158 159 func (ctx *blockchainTestContext) setWorkspace() { 160 workspace, _ := os.MkdirTemp("", "klaytn-test-state") 161 ctx.workspace = workspace 162 } 163 164 func (ctx *blockchainTestContext) setNodes(numNodes int) error { 165 ctx.nodes = make([]*blockchainTestNode, numNodes) 166 for i := 0; i < numNodes; i++ { 167 if err := ctx.setNode(i); err != nil { 168 return err 169 } 170 } 171 return nil 172 } 173 174 func (ctx *blockchainTestContext) setNode(nodeIndex int) (err error) { 175 tn := &blockchainTestNode{} 176 tn.datadir = filepath.Join(ctx.workspace, fmt.Sprintf("node%d", nodeIndex)) 177 178 // P2P ports: 32000, 32001, 32002... 179 // RPC ports: 38000, 38001, 38002... 180 peers := make([]*discover.Node, ctx.numNodes) 181 for i := 0; i < ctx.numNodes; i++ { 182 id := crypto.FromECDSAPub(&ctx.accountKeys[i].PublicKey)[1:] // strip 0x04 prefix byte 183 kni := fmt.Sprintf("kni://%x@127.0.0.1:%d?discport=0&type=cn", id, 32000+i) 184 peers[i], err = discover.ParseNode(kni) 185 if err != nil { 186 return 187 } 188 } 189 peers = append(peers[:nodeIndex], peers[nodeIndex+1:]...) // remove self 190 191 nodeKey := ctx.accountKeys[nodeIndex] 192 blsKey, _ := bls.DeriveFromECDSA(nodeKey) 193 nodeConf := &node.Config{ 194 DataDir: tn.datadir, 195 UseLightweightKDF: true, 196 P2P: p2p.Config{ 197 PrivateKey: nodeKey, 198 MaxPhysicalConnections: 100, // big enough 199 ConnectionType: common.CONSENSUSNODE, 200 NoDiscovery: true, 201 StaticNodes: peers, 202 ListenAddr: fmt.Sprintf("0.0.0.0:%d", 32000+nodeIndex), 203 }, 204 BlsKey: blsKey, 205 IPCPath: "klay.ipc", 206 HTTPHost: "0.0.0.0", 207 HTTPPort: 38000 + nodeIndex, 208 HTTPVirtualHosts: []string{"*"}, 209 HTTPTimeouts: rpc.DefaultHTTPTimeouts, 210 NtpRemoteServer: "", 211 } 212 if tn.node, err = node.New(nodeConf); err != nil { 213 return 214 } 215 216 cnConf := cn.GetDefaultConfig() 217 cnConf.NetworkId = ctx.config.ChainID.Uint64() 218 cnConf.Genesis = ctx.genesis 219 cnConf.Rewardbase = ctx.accountAddrs[nodeIndex] 220 cnConf.SingleDB = false // identical to regular CN 221 cnConf.NumStateTrieShards = 4 // identical to regular CN 222 cnConf.NoPruning = true // archive mode 223 err = tn.node.Register(func(ctx *node.ServiceContext) (node.Service, error) { 224 return cn.New(ctx, cnConf) 225 }) 226 if err != nil { 227 return 228 } 229 if err = tn.node.Start(); err != nil { 230 return 231 } 232 if err = tn.node.Service(&tn.cn); err != nil { 233 return 234 } 235 ctx.nodes[nodeIndex] = tn 236 return 237 } 238 239 func (ctx *blockchainTestContext) forEachNode(f func(*blockchainTestNode) error) error { 240 for _, tn := range ctx.nodes { 241 if err := f(tn); err != nil { 242 return err 243 } 244 } 245 return nil 246 } 247 248 func (ctx *blockchainTestContext) Start() error { 249 return ctx.forEachNode(func(tn *blockchainTestNode) error { 250 return tn.cn.StartMining(false) 251 }) 252 } 253 254 func (ctx *blockchainTestContext) Stop() error { 255 err := ctx.forEachNode(func(tn *blockchainTestNode) error { 256 return tn.node.Stop() 257 }) 258 if err != nil { 259 return err 260 } 261 262 // TODO: make StakingManager not singleton OR recreate new in cn.New() 263 // StakingManager is a global singleton and it never gets recreated. 264 // Manually clear StakingManager-related global states so that 265 // other tests can use StakingManager as if it's fresh. 266 reward.PurgeStakingInfoCache() 267 blockchain.ClearMigrationPrerequisites() 268 return nil 269 } 270 271 func (ctx *blockchainTestContext) Restart() error { 272 if err := ctx.Stop(); err != nil { 273 return err 274 } 275 // Recreate nodes 276 if err := ctx.setNodes(ctx.numNodes); err != nil { 277 return err 278 } 279 return ctx.Start() 280 } 281 282 func (ctx *blockchainTestContext) Cleanup() error { 283 if err := ctx.Stop(); err != nil { 284 return err 285 } 286 return os.RemoveAll(ctx.workspace) 287 } 288 289 func (ctx *blockchainTestContext) WaitBlock(t *testing.T, num uint64) { 290 block := waitBlock(ctx.nodes[0].cn.BlockChain(), num) 291 assert.NotNil(t, block) 292 } 293 294 func (ctx *blockchainTestContext) WaitTx(t *testing.T, txhash common.Hash) { 295 rc := waitReceipt(ctx.nodes[0].cn.BlockChain().(*blockchain.BlockChain), txhash) 296 assert.NotNil(t, rc) 297 if rc != nil { 298 assert.Equal(t, types.ReceiptStatusSuccessful, rc.Status) 299 } 300 } 301 302 func (ctx *blockchainTestContext) Dump(t *testing.T) { 303 for i, node := range ctx.nodes { 304 t.Logf("node[%d] http://%s %s", i, node.node.HTTPEndpoint(), node.node.IPCEndpoint()) 305 } 306 for i, addr := range ctx.accountAddrs { 307 t.Logf("account[%d] %s", i, addr.Hex()) 308 } 309 } 310 311 func (ctx *blockchainTestContext) Subscribe(t *testing.T, logFunc func(ev *blockchain.ChainEvent)) { 312 if logFunc == nil { 313 logFunc = func(ev *blockchain.ChainEvent) { 314 t.Logf("block[%d] txs=%d", ev.Block.NumberU64(), ev.Block.Transactions().Len()) 315 } 316 } 317 318 go func() { 319 chain := ctx.nodes[0].cn.BlockChain() 320 chainEventCh := make(chan blockchain.ChainEvent) 321 subscription := chain.SubscribeChainEvent(chainEventCh) 322 defer subscription.Unsubscribe() 323 for { 324 ev := <-chainEventCh 325 logFunc(&ev) 326 } 327 }() 328 } 329 330 func deriveTestAccount(index int) *ecdsa.PrivateKey { 331 // "m/44'/60'/0'/0/0" 332 mnemonic := "test test test test test test test test test test test junk" 333 seed := pbkdf2.Key([]byte(mnemonic), []byte("mnemonic"), 2048, 64, sha512.New) 334 key, _ := bip32.NewMasterKey(seed) 335 key, _ = key.NewChildKey(0x8000002c) 336 key, _ = key.NewChildKey(0x8000003c) 337 key, _ = key.NewChildKey(0x80000000) 338 key, _ = key.NewChildKey(0x00000000) 339 340 child, _ := key.NewChildKey(uint32(index)) 341 privateKey, _ := crypto.ToECDSA(child.Key) 342 return privateKey 343 }