github.com/iotexproject/iotex-core@v1.14.1-rc1/tools/actioninjector.v2/internal/cmd/inject.go (about) 1 // Copyright (c) 2019 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package cmd 7 8 import ( 9 "context" 10 "crypto/sha256" 11 "crypto/tls" 12 "encoding/binary" 13 "encoding/hex" 14 "fmt" 15 "math/big" 16 "math/rand" 17 "os" 18 "path/filepath" 19 "strings" 20 "sync" 21 "time" 22 23 "github.com/cenkalti/backoff" 24 "github.com/ethereum/go-ethereum/accounts/abi" 25 "github.com/iotexproject/go-pkgs/cache/ttl" 26 "github.com/iotexproject/go-pkgs/crypto" 27 "github.com/iotexproject/go-pkgs/hash" 28 "github.com/iotexproject/iotex-address/address" 29 "github.com/iotexproject/iotex-antenna-go/v2/account" 30 "github.com/iotexproject/iotex-antenna-go/v2/iotex" 31 "github.com/iotexproject/iotex-proto/golang/iotexapi" 32 "github.com/pkg/errors" 33 "github.com/spf13/cobra" 34 "go.uber.org/zap" 35 "google.golang.org/grpc" 36 "google.golang.org/grpc/credentials" 37 yaml "gopkg.in/yaml.v2" 38 39 "github.com/iotexproject/iotex-core/pkg/log" 40 ) 41 42 // KeyPairs indicate the keypair of accounts getting transfers from Creator in genesis block 43 type KeyPairs struct { 44 Pairs []KeyPair `yaml:"pkPairs"` 45 } 46 47 // KeyPair contains the public and private key of an address 48 type KeyPair struct { 49 PK string `yaml:"pubKey"` 50 SK string `yaml:"priKey"` 51 } 52 53 // AddressKey contains the encoded address and private key of an account 54 type AddressKey struct { 55 EncodedAddr string 56 PriKey crypto.PrivateKey 57 } 58 59 type injectProcessor struct { 60 api iotexapi.APIServiceClient 61 nonces *ttl.Cache 62 accounts []*AddressKey 63 } 64 65 func newInjectionProcessor() (*injectProcessor, error) { 66 var conn *grpc.ClientConn 67 var err error 68 grpcctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 69 defer cancel() 70 log.L().Info("Server Addr", zap.String("endpoint", injectCfg.serverAddr)) 71 if injectCfg.insecure { 72 log.L().Info("insecure connection") 73 conn, err = grpc.DialContext(grpcctx, injectCfg.serverAddr, grpc.WithBlock(), grpc.WithInsecure()) 74 } else { 75 log.L().Info("secure connection") 76 conn, err = grpc.DialContext(grpcctx, injectCfg.serverAddr, grpc.WithBlock(), grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{MinVersion: tls.VersionTLS12}))) 77 } 78 if err != nil { 79 return nil, err 80 } 81 api := iotexapi.NewAPIServiceClient(conn) 82 nonceCache, err := ttl.NewCache() 83 if err != nil { 84 return nil, err 85 } 86 p := &injectProcessor{ 87 api: api, 88 nonces: nonceCache, 89 } 90 if err = p.randAccounts(injectCfg.randAccounts); err != nil { 91 return p, err 92 } 93 if injectCfg.loadTokenAmount.BitLen() != 0 { 94 if err := p.loadAccounts(injectCfg.configPath); err != nil { 95 return p, err 96 } 97 } 98 p.syncNonces(context.Background()) 99 return p, nil 100 } 101 102 func (p *injectProcessor) randAccounts(num int) error { 103 addrKeys := make([]*AddressKey, 0, num) 104 for i := 0; i < num; i++ { 105 private, err := crypto.GenerateKey() 106 if err != nil { 107 return err 108 } 109 a, _ := account.PrivateKeyToAccount(private) 110 p.nonces.Set(a.Address().String(), 1) 111 addrKeys = append(addrKeys, &AddressKey{PriKey: private, EncodedAddr: a.Address().String()}) 112 } 113 p.accounts = addrKeys 114 return nil 115 } 116 117 func (p *injectProcessor) loadAccounts(keypairsPath string) error { 118 keyPairBytes, err := os.ReadFile(filepath.Clean(keypairsPath)) 119 if err != nil { 120 return errors.Wrap(err, "failed to read key pairs file") 121 } 122 var keypairs KeyPairs 123 if err := yaml.Unmarshal(keyPairBytes, &keypairs); err != nil { 124 return errors.Wrap(err, "failed to unmarshal key pairs bytes") 125 } 126 127 // Construct iotex addresses from loaded key pairs 128 addrKeys := make([]*AddressKey, 0) 129 for _, pair := range keypairs.Pairs { 130 pk, err := crypto.HexStringToPublicKey(pair.PK) 131 if err != nil { 132 return errors.Wrap(err, "failed to decode public key") 133 } 134 sk, err := crypto.HexStringToPrivateKey(pair.SK) 135 if err != nil { 136 return errors.Wrap(err, "failed to decode private key") 137 } 138 addr := pk.Address() 139 log.L().Info("loaded account", zap.String("addr", addr.String())) 140 if addr == nil { 141 return errors.New("failed to get address") 142 } 143 p.nonces.Set(addr.String(), 0) 144 addrKeys = append(addrKeys, &AddressKey{EncodedAddr: addr.String(), PriKey: sk}) 145 } 146 147 // send tokens 148 for i, r := range p.accounts { 149 sender := addrKeys[i%len(addrKeys)] 150 operatorAccount, _ := account.PrivateKeyToAccount(sender.PriKey) 151 152 recipient, _ := address.FromString(r.EncodedAddr) 153 log.L().Info("generated account", zap.String("addr", recipient.String())) 154 c := iotex.NewAuthedClient(p.api, operatorAccount) 155 caller := c.Transfer(recipient, injectCfg.loadTokenAmount).SetGasPrice(injectCfg.transferGasPrice).SetGasLimit(injectCfg.transferGasLimit) 156 if _, err := caller.Call(context.Background()); err != nil { 157 log.L().Error("Failed to inject.", zap.Error(err)) 158 } 159 if i != 0 && i%len(addrKeys) == 0 { 160 time.Sleep(10 * time.Second) 161 } 162 } 163 return nil 164 } 165 166 func (p *injectProcessor) syncNoncesProcess(ctx context.Context) { 167 reset := time.NewTicker(injectCfg.resetInterval) 168 for { 169 select { 170 case <-ctx.Done(): 171 return 172 case <-reset.C: 173 p.syncNonces(context.Background()) 174 } 175 } 176 } 177 178 func (p *injectProcessor) syncNonces(ctx context.Context) { 179 var addrPool []string 180 for _, v := range p.nonces.Keys() { 181 addrPool = append(addrPool, v.(string)) 182 } 183 for _, addr := range addrPool { 184 err := backoff.Retry(func() error { 185 resp, err := p.api.GetAccount(ctx, &iotexapi.GetAccountRequest{Address: addr}) 186 if err != nil { 187 return err 188 } 189 p.nonces.Set(addr, resp.GetAccountMeta().GetPendingNonce()) 190 return nil 191 }, backoff.NewExponentialBackOff()) 192 if err != nil { 193 log.L().Fatal("Failed to inject actions by APS", 194 zap.Error(err), 195 zap.String("addr", addr)) 196 } 197 time.Sleep(10 * time.Millisecond) 198 } 199 } 200 201 func (p *injectProcessor) injectProcess(ctx context.Context) { 202 var workers sync.WaitGroup 203 ticks := make(chan uint64) 204 for i := uint64(0); i < injectCfg.workers; i++ { 205 workers.Add(1) 206 go p.inject(&workers, ticks) 207 } 208 209 defer workers.Wait() 210 defer close(ticks) 211 interval := uint64(time.Second.Nanoseconds() / int64(injectCfg.aps)) 212 began, count := time.Now(), uint64(0) 213 for { 214 now, next := time.Now(), began.Add(time.Duration(count*interval)) 215 time.Sleep(next.Sub(now)) 216 select { 217 case <-ctx.Done(): 218 return 219 case ticks <- count: 220 count++ 221 default: 222 workers.Add(1) 223 go p.inject(&workers, ticks) 224 } 225 } 226 } 227 228 func (p *injectProcessor) inject(workers *sync.WaitGroup, ticks <-chan uint64) { 229 defer workers.Done() 230 for range ticks { 231 go func() { 232 caller, err := p.pickAction() 233 if err != nil { 234 log.L().Error("Failed to create an action", zap.Error(err)) 235 } 236 var actionHash hash.Hash256 237 bo := backoff.WithMaxRetries(backoff.NewConstantBackOff(injectCfg.retryInterval), injectCfg.retryNum) 238 if rerr := backoff.Retry(func() error { 239 actionHash, err = caller.Call(context.Background()) 240 return err 241 }, bo); rerr != nil { 242 log.L().Error("Failed to inject.", zap.Error(rerr)) 243 } 244 245 c := iotex.NewReadOnlyClient(p.api) 246 247 if injectCfg.checkReceipt { 248 time.Sleep(25 * time.Second) 249 var response *iotexapi.GetReceiptByActionResponse 250 if rerr := backoff.Retry(func() error { 251 response, err = c.GetReceipt(actionHash).Call(context.Background()) 252 return err 253 }, bo); rerr != nil { 254 log.L().Error("Failed to get receipt.", zap.Error(rerr)) 255 } 256 if response.ReceiptInfo.Receipt.Status != 1 { 257 log.L().Error("Receipt has failed status.", zap.Uint64("status", response.ReceiptInfo.Receipt.Status)) 258 } 259 } 260 }() 261 } 262 } 263 264 func (p *injectProcessor) pickAction() (iotex.SendActionCaller, error) { 265 switch injectCfg.actionType { 266 case "transfer": 267 return p.transferCaller() 268 case "execution": 269 return p.executionCaller() 270 case "mixed": 271 if rand.Intn(2) == 0 { 272 return p.transferCaller() 273 } 274 return p.executionCaller() 275 default: 276 return p.transferCaller() 277 } 278 } 279 280 func (p *injectProcessor) executionCaller() (iotex.SendActionCaller, error) { 281 var nonce uint64 282 sender := p.accounts[rand.Intn(len(p.accounts))] 283 if val, ok := p.nonces.Get(sender.EncodedAddr); ok { 284 nonce = val.(uint64) 285 } 286 p.nonces.Set(sender.EncodedAddr, nonce+1) 287 288 operatorAccount, _ := account.PrivateKeyToAccount(sender.PriKey) 289 c := iotex.NewAuthedClient(p.api, operatorAccount) 290 address, _ := address.FromString(injectCfg.contract) 291 abiJSONVar, _ := abi.JSON(strings.NewReader(_abiStr)) 292 contract := c.Contract(address, abiJSONVar) 293 294 data := rand.Int63() 295 var dataBuf = make([]byte, 8) 296 binary.BigEndian.PutUint64(dataBuf, uint64(data)) 297 dataHash := sha256.Sum256(dataBuf) 298 299 caller := contract.Execute("addHash", uint64(time.Now().Unix()), hex.EncodeToString(dataHash[:])). 300 SetNonce(nonce). 301 SetAmount(injectCfg.executionAmount). 302 SetGasPrice(injectCfg.executionGasPrice). 303 SetGasLimit(injectCfg.executionGasLimit) 304 305 return caller, nil 306 } 307 308 func (p *injectProcessor) transferCaller() (iotex.SendActionCaller, error) { 309 var nonce uint64 310 sender := p.accounts[rand.Intn(len(p.accounts))] 311 if val, ok := p.nonces.Get(sender.EncodedAddr); ok { 312 nonce = val.(uint64) 313 } 314 p.nonces.Set(sender.EncodedAddr, nonce+1) 315 316 operatorAccount, _ := account.PrivateKeyToAccount(sender.PriKey) 317 c := iotex.NewAuthedClient(p.api, operatorAccount) 318 319 recipient, _ := address.FromString(p.accounts[rand.Intn(len(p.accounts))].EncodedAddr) 320 data := rand.Int63() 321 var dataBuf = make([]byte, 8) 322 binary.BigEndian.PutUint64(dataBuf, uint64(data)) 323 dataHash := sha256.Sum256(dataBuf) 324 caller := c.Transfer(recipient, injectCfg.transferAmount). 325 SetPayload(dataHash[:]). 326 SetNonce(nonce). 327 SetGasPrice(injectCfg.transferGasPrice). 328 SetGasLimit(injectCfg.transferGasLimit) 329 return caller, nil 330 } 331 332 // injectCmd represents the inject command 333 var injectCmd = &cobra.Command{ 334 Use: "inject", 335 Short: "inject actions [options : -m] (default:random)", 336 Long: `inject actions [options : -m] (default:random).`, 337 Run: func(cmd *cobra.Command, args []string) { 338 fmt.Println(inject(args)) 339 }, 340 } 341 342 var rawInjectCfg = struct { 343 configPath string 344 serverAddr string 345 transferGasLimit uint64 346 transferGasPrice int64 347 transferAmount int64 348 349 contract string 350 executionAmount int64 351 executionGasLimit uint64 352 executionGasPrice int64 353 354 actionType string 355 retryNum uint64 356 retryInterval time.Duration 357 duration time.Duration 358 resetInterval time.Duration 359 aps int 360 workers uint64 361 checkReceipt bool 362 insecure bool 363 364 randAccounts int 365 loadTokenAmount string 366 }{} 367 368 var injectCfg = struct { 369 configPath string 370 serverAddr string 371 transferGasLimit uint64 372 transferGasPrice *big.Int 373 transferAmount *big.Int 374 375 contract string 376 executionAmount *big.Int 377 executionGasLimit uint64 378 executionGasPrice *big.Int 379 380 actionType string 381 retryNum uint64 382 retryInterval time.Duration 383 duration time.Duration 384 resetInterval time.Duration 385 aps int 386 workers uint64 387 checkReceipt bool 388 insecure bool 389 randAccounts int 390 loadTokenAmount *big.Int 391 }{} 392 393 func inject(_ []string) string { 394 transferAmount := big.NewInt(rawInjectCfg.transferAmount) 395 transferGasPrice := big.NewInt(rawInjectCfg.transferGasPrice) 396 executionGasPrice := big.NewInt(rawInjectCfg.executionGasPrice) 397 executionAmount := big.NewInt(rawInjectCfg.executionAmount) 398 loadTokenAmount, ok := new(big.Int).SetString(rawInjectCfg.loadTokenAmount, 10) 399 if !ok { 400 return fmt.Sprint("failed to load token amount") 401 } 402 403 injectCfg.configPath = rawInjectCfg.configPath 404 injectCfg.serverAddr = rawInjectCfg.serverAddr 405 injectCfg.transferGasLimit = rawInjectCfg.transferGasLimit 406 injectCfg.transferGasPrice = transferGasPrice 407 injectCfg.transferAmount = transferAmount 408 409 injectCfg.contract = rawInjectCfg.contract 410 injectCfg.executionAmount = executionAmount 411 injectCfg.executionGasLimit = rawInjectCfg.executionGasLimit 412 injectCfg.executionGasPrice = executionGasPrice 413 414 injectCfg.actionType = rawInjectCfg.actionType 415 injectCfg.retryNum = rawInjectCfg.retryNum 416 injectCfg.retryInterval = rawInjectCfg.retryInterval 417 injectCfg.duration = rawInjectCfg.duration 418 injectCfg.resetInterval = rawInjectCfg.resetInterval 419 injectCfg.aps = rawInjectCfg.aps 420 injectCfg.workers = rawInjectCfg.workers 421 injectCfg.checkReceipt = rawInjectCfg.checkReceipt 422 injectCfg.insecure = rawInjectCfg.insecure 423 injectCfg.randAccounts = rawInjectCfg.randAccounts 424 injectCfg.loadTokenAmount = loadTokenAmount 425 426 p, err := newInjectionProcessor() 427 if err != nil { 428 return fmt.Sprintf("failed to create injector processor: %v.", err) 429 } 430 431 ctx, cancel := context.WithTimeout(context.Background(), injectCfg.duration) 432 defer cancel() 433 go p.injectProcess(ctx) 434 go p.syncNoncesProcess(ctx) 435 <-ctx.Done() 436 return "" 437 } 438 439 func init() { 440 flag := injectCmd.Flags() 441 flag.StringVar(&rawInjectCfg.configPath, "injector-config-path", "./tools/actioninjector.v2/gentsfaddrs.yaml", 442 "path of config file of genesis transfer addresses") 443 flag.StringVar(&rawInjectCfg.serverAddr, "addr", "127.0.0.1:14014", "target ip:port for grpc connection") 444 flag.Int64Var(&rawInjectCfg.transferAmount, "transfer-amount", 0, "execution amount") 445 flag.Uint64Var(&rawInjectCfg.transferGasLimit, "transfer-gas-limit", 20000, "transfer gas limit") 446 flag.Int64Var(&rawInjectCfg.transferGasPrice, "transfer-gas-price", 1000000000000, "transfer gas price") 447 flag.StringVar(&rawInjectCfg.contract, "contract", "io1pmjhyksxmz2xpxn2qmz4gx9qq2kn2gdr8un4xq", "smart contract address") 448 flag.Int64Var(&rawInjectCfg.executionAmount, "execution-amount", 0, "execution amount") 449 flag.Uint64Var(&rawInjectCfg.executionGasLimit, "execution-gas-limit", 100000, "execution gas limit") 450 flag.Int64Var(&rawInjectCfg.executionGasPrice, "execution-gas-price", 1000000000000, "execution gas price") 451 flag.StringVar(&rawInjectCfg.actionType, "action-type", "transfer", "action type to inject") 452 flag.Uint64Var(&rawInjectCfg.retryNum, "retry-num", 5, "maximum number of rpc retries") 453 flag.DurationVar(&rawInjectCfg.retryInterval, "retry-interval", 1*time.Second, "sleep interval between two consecutive rpc retries") 454 flag.DurationVar(&rawInjectCfg.duration, "duration", 60*time.Hour, "duration when the injection will run") 455 flag.DurationVar(&rawInjectCfg.resetInterval, "reset-interval", 10*time.Second, "time interval to reset nonce counter") 456 flag.IntVar(&rawInjectCfg.aps, "aps", 30, "actions to be injected per second") 457 flag.IntVar(&rawInjectCfg.randAccounts, "rand-accounts", 20, "number of accounst to use") 458 flag.Uint64Var(&rawInjectCfg.workers, "workers", 10, "number of workers") 459 flag.BoolVar(&rawInjectCfg.insecure, "insecure", false, "insecure network") 460 flag.BoolVar(&rawInjectCfg.checkReceipt, "check-recipt", false, "check recept") 461 flag.StringVar(&rawInjectCfg.loadTokenAmount, "load-token-amount", "0", "init load how much token to inject accounts") 462 rootCmd.AddCommand(injectCmd) 463 }