github.com/iotexproject/iotex-core@v1.14.1-rc1/tools/util/injectorutil.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 util 7 8 import ( 9 "context" 10 "encoding/hex" 11 "math/big" 12 "math/rand" 13 "os" 14 "path/filepath" 15 "sync" 16 "sync/atomic" 17 "time" 18 19 "github.com/cenkalti/backoff" 20 "github.com/iotexproject/go-pkgs/cache/ttl" 21 "github.com/iotexproject/go-pkgs/crypto" 22 "github.com/iotexproject/go-pkgs/hash" 23 "github.com/iotexproject/iotex-proto/golang/iotexapi" 24 "github.com/pkg/errors" 25 "go.uber.org/zap" 26 "gopkg.in/yaml.v2" 27 28 "github.com/iotexproject/iotex-proto/golang/iotextypes" 29 30 "github.com/iotexproject/iotex-core/action" 31 "github.com/iotexproject/iotex-core/api" 32 "github.com/iotexproject/iotex-core/pkg/log" 33 "github.com/iotexproject/iotex-core/pkg/unit" 34 "github.com/iotexproject/iotex-core/tools/executiontester/blockchain" 35 ) 36 37 // KeyPairs indicate the keypair of accounts getting transfers from Creator in genesis block 38 type KeyPairs struct { 39 Pairs []KeyPair `yaml:"pkPairs"` 40 } 41 42 // KeyPair contains the public and private key of an address 43 type KeyPair struct { 44 PK string `yaml:"pubKey"` 45 SK string `yaml:"priKey"` 46 } 47 48 // AddressKey contains the encoded address and private key of an account 49 type AddressKey struct { 50 EncodedAddr string 51 PriKey crypto.PrivateKey 52 } 53 54 var ( 55 totalTsfCreated = uint64(0) 56 totalTsfSentToAPI = uint64(0) 57 totalTsfSucceeded = uint64(0) 58 totalTsfFailed = uint64(0) 59 ) 60 61 // GetTotalTsfCreated returns number of total transfer action created 62 func GetTotalTsfCreated() uint64 { 63 return totalTsfCreated 64 } 65 66 // GetTotalTsfSentToAPI returns number of total transfer action successfully send through GRPC 67 func GetTotalTsfSentToAPI() uint64 { 68 return totalTsfSentToAPI 69 } 70 71 // GetTotalTsfSucceeded returns number of total transfer action created 72 func GetTotalTsfSucceeded() uint64 { 73 return totalTsfSucceeded 74 } 75 76 // GetTotalTsfFailed returns number of total transfer action failed 77 func GetTotalTsfFailed() uint64 { 78 return totalTsfFailed 79 } 80 81 // LoadAddresses loads key pairs from key pair path and construct addresses 82 func LoadAddresses(keypairsPath string, chainID uint32) ([]*AddressKey, error) { 83 // Load Senders' public/private key pairs 84 keyPairBytes, err := os.ReadFile(filepath.Clean(keypairsPath)) 85 if err != nil { 86 return nil, errors.Wrap(err, "failed to read key pairs file") 87 } 88 var keypairs KeyPairs 89 if err := yaml.Unmarshal(keyPairBytes, &keypairs); err != nil { 90 return nil, errors.Wrap(err, "failed to unmarshal key pairs bytes") 91 } 92 93 // Construct iotex addresses from loaded key pairs 94 addrKeys := make([]*AddressKey, 0) 95 for _, pair := range keypairs.Pairs { 96 pk, err := crypto.HexStringToPublicKey(pair.PK) 97 if err != nil { 98 return nil, errors.Wrap(err, "failed to decode public key") 99 } 100 sk, err := crypto.HexStringToPrivateKey(pair.SK) 101 if err != nil { 102 return nil, errors.Wrap(err, "failed to decode private key") 103 } 104 addr := pk.Address() 105 if addr == nil { 106 return nil, errors.New("failed to get address") 107 } 108 addrKeys = append(addrKeys, &AddressKey{EncodedAddr: addr.String(), PriKey: sk}) 109 } 110 return addrKeys, nil 111 } 112 113 // InitCounter initializes the map of nonce counter of each address 114 func InitCounter(client iotexapi.APIServiceClient, addrKeys []*AddressKey) (map[string]uint64, error) { 115 counter := make(map[string]uint64) 116 for _, addrKey := range addrKeys { 117 addr := addrKey.EncodedAddr 118 err := backoff.Retry(func() error { 119 acctDetails, err := client.GetAccount(context.Background(), &iotexapi.GetAccountRequest{Address: addr}) 120 if err != nil { 121 return err 122 } 123 counter[addr] = acctDetails.GetAccountMeta().PendingNonce 124 return nil 125 }, backoff.NewExponentialBackOff()) 126 if err != nil { 127 return nil, errors.Wrapf(err, "failed to get address details of %s", addrKey.EncodedAddr) 128 } 129 } 130 return counter, nil 131 } 132 133 // InjectByAps injects Actions in APS Mode 134 func InjectByAps( 135 wg *sync.WaitGroup, 136 aps float64, 137 counter map[string]uint64, 138 transferGasLimit int, 139 transferGasPrice int64, 140 transferPayload string, 141 voteGasLimit int, 142 voteGasPrice int64, 143 contract string, 144 executionAmount int, 145 executionGasLimit int, 146 executionGasPrice int64, 147 executionData string, 148 fpToken blockchain.FpToken, 149 fpContract string, 150 debtor *AddressKey, 151 creditor *AddressKey, 152 client iotexapi.APIServiceClient, 153 admins []*AddressKey, 154 delegates []*AddressKey, 155 duration time.Duration, 156 retryNum int, 157 retryInterval int, 158 resetInterval int, 159 expectedBalances map[string]*big.Int, 160 cs api.CoreService, 161 pendingActionMap *ttl.Cache, 162 ) { 163 timeout := time.After(duration) 164 tick := time.NewTicker(time.Duration(1/aps*1000000) * time.Microsecond) 165 reset := time.NewTicker(time.Duration(resetInterval) * time.Second) 166 rand.Seed(time.Now().UnixNano()) 167 168 loop: 169 for { 170 select { 171 case <-timeout: 172 break loop 173 case <-reset.C: 174 for _, admin := range admins { 175 addr := admin.EncodedAddr 176 err := backoff.Retry(func() error { 177 acctDetails, err := client.GetAccount(context.Background(), &iotexapi.GetAccountRequest{Address: addr}) 178 if err != nil { 179 return err 180 } 181 counter[addr] = acctDetails.GetAccountMeta().PendingNonce 182 return nil 183 }, backoff.NewExponentialBackOff()) 184 if err != nil { 185 log.L().Fatal("Failed to inject actions by APS", 186 zap.Error(err), 187 zap.String("addr", admin.EncodedAddr)) 188 } 189 } 190 for _, delegate := range delegates { 191 addr := delegate.EncodedAddr 192 err := backoff.Retry(func() error { 193 acctDetails, err := client.GetAccount(context.Background(), &iotexapi.GetAccountRequest{Address: addr}) 194 if err != nil { 195 return err 196 } 197 counter[addr] = acctDetails.GetAccountMeta().PendingNonce 198 return nil 199 }, backoff.NewExponentialBackOff()) 200 if err != nil { 201 log.L().Fatal("Failed to inject actions by APS", 202 zap.Error(err), 203 zap.String("addr", delegate.EncodedAddr)) 204 } 205 } 206 case <-tick.C: 207 wg.Add(1) 208 // TODO Currently Vote is skipped because it will fail on balance test and is planned to be removed 209 if _, err := CheckPendingActionList(cs, 210 pendingActionMap, 211 expectedBalances, 212 ); err != nil { 213 log.L().Error(err.Error()) 214 } 215 rerand: 216 switch rand.Intn(3) { 217 case 0: 218 sender, recipient, nonce, amount := createTransferInjection(counter, delegates) 219 atomic.AddUint64(&totalTsfCreated, 1) 220 go injectTransfer(wg, client, sender, recipient, nonce, amount, uint64(transferGasLimit), 221 big.NewInt(transferGasPrice), transferPayload, retryNum, retryInterval, pendingActionMap) 222 case 1: 223 if fpToken == nil { 224 goto rerand 225 } 226 go injectFpTokenTransfer(wg, fpToken, fpContract, debtor, creditor) 227 case 2: 228 executor, nonce := createExecutionInjection(counter, delegates) 229 go injectExecInteraction(wg, client, executor, contract, nonce, big.NewInt(int64(executionAmount)), 230 uint64(executionGasLimit), big.NewInt(executionGasPrice), 231 executionData, retryNum, retryInterval, pendingActionMap) 232 } 233 } 234 } 235 } 236 237 // InjectByInterval injects Actions in Interval Mode 238 func InjectByInterval( 239 transferNum int, 240 transferGasLimit int, 241 transferGasPrice int, 242 transferPayload string, 243 voteNum int, 244 voteGasLimit int, 245 voteGasPrice int, 246 executionNum int, 247 contract string, 248 executionAmount int, 249 executionGasLimit int, 250 executionGasPrice int, 251 executionData string, 252 interval int, 253 counter map[string]uint64, 254 client iotexapi.APIServiceClient, 255 admins []*AddressKey, 256 delegates []*AddressKey, 257 retryNum int, 258 retryInterval int, 259 ) { 260 rand.Seed(time.Now().UnixNano()) 261 for transferNum > 0 && voteNum > 0 && executionNum > 0 { 262 sender, recipient, nonce, amount := createTransferInjection(counter, delegates) 263 injectTransfer(nil, client, sender, recipient, nonce, amount, uint64(transferGasLimit), 264 big.NewInt(int64(transferGasPrice)), transferPayload, retryNum, retryInterval, nil) 265 time.Sleep(time.Second * time.Duration(interval)) 266 267 executor, nonce := createExecutionInjection(counter, delegates) 268 injectExecInteraction(nil, client, executor, contract, nonce, big.NewInt(int64(executionAmount)), 269 uint64(executionGasLimit), big.NewInt(int64(executionGasPrice)), executionData, retryNum, retryInterval, nil) 270 time.Sleep(time.Second * time.Duration(interval)) 271 272 transferNum-- 273 voteNum-- 274 executionNum-- 275 } 276 switch { 277 case transferNum > 0 && voteNum > 0: 278 for transferNum > 0 && voteNum > 0 { 279 sender, recipient, nonce, amount := createTransferInjection(counter, delegates) 280 injectTransfer(nil, client, sender, recipient, nonce, amount, uint64(transferGasLimit), 281 big.NewInt(int64(transferGasPrice)), transferPayload, retryNum, retryInterval, nil) 282 time.Sleep(time.Second * time.Duration(interval)) 283 284 transferNum-- 285 voteNum-- 286 } 287 case transferNum > 0 && executionNum > 0: 288 for transferNum > 0 && executionNum > 0 { 289 sender, recipient, nonce, amount := createTransferInjection(counter, delegates) 290 injectTransfer(nil, client, sender, recipient, nonce, amount, uint64(transferGasLimit), 291 big.NewInt(int64(transferGasPrice)), transferPayload, retryNum, retryInterval, nil) 292 time.Sleep(time.Second * time.Duration(interval)) 293 294 executor, nonce := createExecutionInjection(counter, delegates) 295 injectExecInteraction(nil, client, executor, contract, nonce, big.NewInt(int64(executionAmount)), 296 uint64(executionGasLimit), big.NewInt(int64(executionGasPrice)), executionData, retryNum, retryInterval, nil) 297 time.Sleep(time.Second * time.Duration(interval)) 298 299 transferNum-- 300 executionNum-- 301 } 302 case voteNum > 0 && executionNum > 0: 303 for voteNum > 0 && executionNum > 0 { 304 executor, nonce := createExecutionInjection(counter, delegates) 305 injectExecInteraction(nil, client, executor, contract, nonce, big.NewInt(int64(executionAmount)), 306 uint64(executionGasLimit), big.NewInt(int64(executionGasPrice)), executionData, retryNum, retryInterval, nil) 307 time.Sleep(time.Second * time.Duration(interval)) 308 309 voteNum-- 310 executionNum-- 311 } 312 } 313 switch { 314 case transferNum > 0: 315 for transferNum > 0 { 316 sender, recipient, nonce, amount := createTransferInjection(counter, delegates) 317 injectTransfer(nil, client, sender, recipient, nonce, amount, uint64(transferGasLimit), 318 big.NewInt(int64(transferGasPrice)), transferPayload, retryNum, retryInterval, nil) 319 time.Sleep(time.Second * time.Duration(interval)) 320 transferNum-- 321 } 322 case executionNum > 0: 323 for executionNum > 0 { 324 executor, nonce := createExecutionInjection(counter, delegates) 325 injectExecInteraction(nil, client, executor, contract, nonce, big.NewInt(int64(executionAmount)), 326 uint64(executionGasLimit), big.NewInt(int64(executionGasPrice)), executionData, retryNum, retryInterval, nil) 327 time.Sleep(time.Second * time.Duration(interval)) 328 executionNum-- 329 } 330 } 331 } 332 333 // DeployContract deploys a smart contract before starting action injections 334 func DeployContract( 335 client iotexapi.APIServiceClient, 336 counter map[string]uint64, 337 delegates []*AddressKey, 338 executionGasLimit int, 339 executionGasPrice int64, 340 executionData string, 341 retryNum int, 342 retryInterval int, 343 ) (hash.Hash256, error) { 344 executor, nonce := createExecutionInjection(counter, delegates) 345 selp, execution, err := createSignedExecution(executor, action.EmptyAddress, nonce, big.NewInt(0), 346 uint64(executionGasLimit), big.NewInt(int64(executionGasPrice)), executionData) 347 if err != nil { 348 return hash.ZeroHash256, errors.Wrap(err, "failed to create signed execution") 349 } 350 log.L().Info("Created signed execution") 351 352 injectExecution(selp, execution, client, retryNum, retryInterval) 353 selpHash, err := selp.Hash() 354 if err != nil { 355 return hash.ZeroHash256, errors.Wrap(err, "failed to get hash") 356 } 357 return selpHash, nil 358 } 359 360 func injectTransfer( 361 wg *sync.WaitGroup, 362 c iotexapi.APIServiceClient, 363 sender *AddressKey, 364 recipient *AddressKey, 365 nonce uint64, 366 amount int64, 367 gasLimit uint64, 368 gasPrice *big.Int, 369 payload string, 370 retryNum int, 371 retryInterval int, 372 pendingActionMap *ttl.Cache, 373 ) { 374 selp, _, err := createSignedTransfer(sender, recipient, unit.ConvertIotxToRau(amount), nonce, gasLimit, 375 gasPrice, payload) 376 if err != nil { 377 log.L().Fatal("Failed to inject transfer", zap.Error(err)) 378 } 379 380 log.L().Info("Created signed transfer") 381 382 bo := backoff.WithMaxRetries(backoff.NewConstantBackOff(time.Duration(retryInterval)*time.Second), uint64(retryNum)) 383 if err := backoff.Retry(func() error { 384 _, err := c.SendAction(context.Background(), &iotexapi.SendActionRequest{Action: selp.Proto()}) 385 return err 386 }, bo); err != nil { 387 log.L().Error("Failed to inject transfer", zap.Error(err)) 388 } else if pendingActionMap != nil { 389 selpHash, err := selp.Hash() 390 if err != nil { 391 log.L().Fatal("Failed to get hash", zap.Error(err)) 392 } 393 pendingActionMap.Set(selpHash, 1) 394 atomic.AddUint64(&totalTsfSentToAPI, 1) 395 } 396 397 if wg != nil { 398 wg.Done() 399 } 400 } 401 402 func injectExecInteraction( 403 wg *sync.WaitGroup, 404 c iotexapi.APIServiceClient, 405 executor *AddressKey, 406 contract string, 407 nonce uint64, 408 amount *big.Int, 409 gasLimit uint64, 410 gasPrice *big.Int, 411 data string, 412 retryNum int, 413 retryInterval int, 414 pendingActionMap *ttl.Cache, 415 ) { 416 selp, execution, err := createSignedExecution(executor, contract, nonce, amount, gasLimit, gasPrice, data) 417 if err != nil { 418 log.L().Fatal("Failed to inject execution", zap.Error(err)) 419 } 420 421 log.L().Info("Created signed execution") 422 423 injectExecution(selp, execution, c, retryNum, retryInterval) 424 425 if pendingActionMap != nil { 426 selpHash, err := selp.Hash() 427 if err != nil { 428 log.L().Error("Failed to inject transfer", zap.Error(err)) 429 } 430 pendingActionMap.Set(selpHash, 1) 431 } 432 433 if wg != nil { 434 wg.Done() 435 } 436 } 437 438 func injectFpTokenTransfer( 439 wg *sync.WaitGroup, 440 fpToken blockchain.FpToken, 441 fpContract string, 442 debtor *AddressKey, 443 creditor *AddressKey, 444 ) { 445 sender := debtor 446 recipient := creditor 447 balance, err := fpToken.ReadValue(fpContract, "70a08231", debtor.EncodedAddr) 448 if err != nil { 449 log.L().Error("Failed to read debtor's asset balance", zap.Error(err)) 450 } 451 if balance == int64(0) { 452 sender = creditor 453 recipient = debtor 454 balance, err = fpToken.ReadValue(fpContract, "70a08231", creditor.EncodedAddr) 455 if err != nil { 456 log.L().Error("Failed to read creditor's asset balance", zap.Error(err)) 457 } 458 } 459 transfer := rand.Int63n(balance) 460 senderPriKey := sender.PriKey.HexString() 461 // Transfer fp token 462 if _, err := fpToken.Transfer(fpContract, sender.EncodedAddr, senderPriKey, 463 recipient.EncodedAddr, transfer); err != nil { 464 log.L().Error("Failed to transfer fp token from debtor to creditor", zap.Error(err)) 465 } 466 if wg != nil { 467 wg.Done() 468 } 469 } 470 471 func injectStake( 472 wg *sync.WaitGroup, 473 c iotexapi.APIServiceClient, 474 sender *AddressKey, 475 nonce uint64, 476 amount string, 477 duration uint32, 478 autoStake bool, 479 gasLimit uint64, 480 gasPrice *big.Int, 481 payload string, 482 retryNum int, 483 retryInterval int, 484 pendingActionMap *ttl.Cache, 485 ) { 486 selp, _, err := createSignedStake(sender, nonce, sender.EncodedAddr, amount, duration, autoStake, []byte(payload), gasLimit, gasPrice) 487 if err != nil { 488 log.L().Fatal("Failed to inject Stake", zap.Error(err)) 489 } 490 log.L().Info("Created signed stake") 491 492 bo := backoff.WithMaxRetries(backoff.NewConstantBackOff(time.Duration(retryInterval)*time.Second), uint64(retryNum)) 493 if err := backoff.Retry(func() error { 494 _, err := c.SendAction(context.Background(), &iotexapi.SendActionRequest{Action: selp.Proto()}) 495 return err 496 }, bo); err != nil { 497 log.L().Error("Failed to inject stake", zap.Error(err)) 498 } else if pendingActionMap != nil { 499 selpHash, err := selp.Hash() 500 if err != nil { 501 log.L().Fatal("Failed to get hash", zap.Error(err)) 502 } 503 pendingActionMap.Set(selpHash, 1) 504 atomic.AddUint64(&totalTsfSentToAPI, 1) 505 } 506 507 if wg != nil { 508 wg.Done() 509 } 510 } 511 512 // Helper function to get the sender, recipient, nonce, and amount of next injected transfer 513 func createTransferInjection( 514 counter map[string]uint64, 515 addrs []*AddressKey, 516 ) (*AddressKey, *AddressKey, uint64, int64) { 517 sender := addrs[rand.Intn(len(addrs))] 518 recipient := addrs[rand.Intn(len(addrs))] 519 nonce := counter[sender.EncodedAddr] 520 amount := int64(0) 521 for amount == int64(0) { 522 amount = int64(rand.Intn(5)) 523 } 524 counter[sender.EncodedAddr]++ 525 return sender, recipient, nonce, amount 526 } 527 528 // Helper function to get the sender, recipient, and nonce of next injected vote 529 func createVoteInjection( 530 counter map[string]uint64, 531 admins []*AddressKey, 532 delegates []*AddressKey, 533 ) (*AddressKey, *AddressKey, uint64) { 534 sender := admins[rand.Intn(len(admins))] 535 recipient := delegates[rand.Intn(len(delegates))] 536 nonce := counter[sender.EncodedAddr] 537 counter[sender.EncodedAddr]++ 538 return sender, recipient, nonce 539 } 540 541 // Helper function to get the executor and nonce of next injected execution 542 func createExecutionInjection( 543 counter map[string]uint64, 544 addrs []*AddressKey, 545 ) (*AddressKey, uint64) { 546 executor := addrs[rand.Intn(len(addrs))] 547 nonce := counter[executor.EncodedAddr] 548 counter[executor.EncodedAddr]++ 549 return executor, nonce 550 } 551 552 // Helper function to create and sign a transfer 553 func createSignedTransfer( 554 sender *AddressKey, 555 recipient *AddressKey, 556 amount *big.Int, 557 nonce uint64, 558 gasLimit uint64, 559 gasPrice *big.Int, 560 payload string, 561 ) (*action.SealedEnvelope, *action.Transfer, error) { 562 transferPayload, err := hex.DecodeString(payload) 563 if err != nil { 564 return nil, nil, errors.Wrapf(err, "failed to decode payload %s", payload) 565 } 566 transfer, err := action.NewTransfer( 567 nonce, amount, recipient.EncodedAddr, transferPayload, gasLimit, gasPrice) 568 if err != nil { 569 return nil, nil, errors.Wrap(err, "failed to create raw transfer") 570 } 571 bd := &action.EnvelopeBuilder{} 572 elp := bd.SetNonce(nonce). 573 SetGasPrice(gasPrice). 574 SetGasLimit(gasLimit). 575 SetAction(transfer).Build() 576 selp, err := action.Sign(elp, sender.PriKey) 577 if err != nil { 578 return nil, nil, errors.Wrapf(err, "failed to sign transfer %v", elp) 579 } 580 return selp, transfer, nil 581 } 582 583 // Helper function to create and sign an execution 584 func createSignedExecution( 585 executor *AddressKey, 586 contract string, 587 nonce uint64, 588 amount *big.Int, 589 gasLimit uint64, 590 gasPrice *big.Int, 591 data string, 592 ) (*action.SealedEnvelope, *action.Execution, error) { 593 executionData, err := hex.DecodeString(data) 594 if err != nil { 595 return nil, nil, errors.Wrapf(err, "failed to decode data %s", data) 596 } 597 execution, err := action.NewExecution(contract, nonce, amount, gasLimit, gasPrice, executionData) 598 if err != nil { 599 return nil, nil, errors.Wrap(err, "failed to create raw execution") 600 } 601 bd := &action.EnvelopeBuilder{} 602 elp := bd.SetNonce(nonce). 603 SetGasPrice(gasPrice). 604 SetGasLimit(gasLimit). 605 SetAction(execution).Build() 606 selp, err := action.Sign(elp, executor.PriKey) 607 if err != nil { 608 return nil, nil, errors.Wrapf(err, "failed to sign execution %v", elp) 609 } 610 return selp, execution, nil 611 } 612 613 func createSignedStake( 614 executor *AddressKey, 615 nonce uint64, 616 candidateName string, 617 amount string, 618 duration uint32, 619 autoStake bool, 620 payload []byte, 621 gasLimit uint64, 622 gasPrice *big.Int, 623 ) (*action.SealedEnvelope, *action.CreateStake, error) { 624 createStake, err := action.NewCreateStake(nonce, candidateName, amount, duration, autoStake, payload, gasLimit, gasPrice) 625 if err != nil { 626 return nil, nil, err 627 } 628 bd := &action.EnvelopeBuilder{} 629 elp := bd.SetNonce(nonce). 630 SetGasPrice(gasPrice). 631 SetGasLimit(gasLimit). 632 SetAction(createStake). 633 Build() 634 selp, err := action.Sign(elp, executor.PriKey) 635 if err != nil { 636 return nil, nil, err 637 } 638 return selp, createStake, nil 639 } 640 641 func injectExecution( 642 selp *action.SealedEnvelope, 643 _ *action.Execution, 644 c iotexapi.APIServiceClient, 645 retryNum int, 646 retryInterval int, 647 ) { 648 bo := backoff.WithMaxRetries(backoff.NewConstantBackOff(time.Duration(retryInterval)*time.Second), uint64(retryNum)) 649 if err := backoff.Retry(func() error { 650 _, err := c.SendAction(context.Background(), &iotexapi.SendActionRequest{Action: selp.Proto()}) 651 return err 652 }, bo); err != nil { 653 log.L().Error("Failed to inject execution", zap.Error(err)) 654 } 655 } 656 657 // GetAllBalanceMap returns a account balance map of all admins and delegates 658 func GetAllBalanceMap( 659 client iotexapi.APIServiceClient, 660 chainaddrs []*AddressKey, 661 ) map[string]*big.Int { 662 balanceMap := make(map[string]*big.Int) 663 for _, chainaddr := range chainaddrs { 664 addr := chainaddr.EncodedAddr 665 err := backoff.Retry(func() error { 666 acctDetails, err := client.GetAccount(context.Background(), &iotexapi.GetAccountRequest{Address: addr}) 667 if err != nil { 668 return err 669 } 670 if acctDetails.GetAccountMeta().Balance == "" { 671 balanceMap[addr] = big.NewInt(0) 672 } else { 673 baddr, ok := new(big.Int).SetString(acctDetails.GetAccountMeta().Balance, 10) 674 if !ok { 675 return errors.Errorf("invalid balance %s", acctDetails.GetAccountMeta().Balance) 676 } 677 balanceMap[addr] = baddr 678 } 679 return nil 680 }, backoff.NewExponentialBackOff()) 681 if err != nil { 682 log.L().Fatal("Failed to Get account balance", 683 zap.Error(err), 684 zap.String("addr", chainaddr.EncodedAddr)) 685 } 686 } 687 return balanceMap 688 } 689 690 // CheckPendingActionList will go through the pending action list, for an executed action: 691 // 1) update the expectation balance map if the action has been run successfully 692 // 2) remove the action from pending list 693 func CheckPendingActionList( 694 cs api.CoreService, 695 pendingActionMap *ttl.Cache, 696 balancemap map[string]*big.Int, 697 ) (bool, error) { 698 var retErr error 699 empty := true 700 701 pendingActionMap.Range(func(selphash, vi interface{}) error { 702 empty = false 703 sh, _ := selphash.(hash.Hash256) 704 receipt, err := GetReceiptByAction(cs, sh) 705 if err == nil { 706 actInfo, err := GetActionByActionHash(cs, selphash.(hash.Hash256)) 707 if err != nil { 708 retErr = err 709 return nil 710 } 711 executoraddr := actInfo.GetSender() 712 if receipt.Status == uint64(iotextypes.ReceiptStatus_Success) { 713 pbAct := actInfo.GetAction().GetCore() 714 gasLimit := actInfo.GetAction().Core.GetGasLimit() 715 gasPrice, ok := new(big.Int).SetString(actInfo.GetAction().Core.GetGasPrice(), 10) 716 if !ok { 717 return errors.New("failed to set gas price") 718 } 719 switch { 720 case pbAct.GetTransfer() != nil: 721 act := &action.Transfer{} 722 if err := act.LoadProto(pbAct.GetTransfer()); err != nil { 723 retErr = err 724 return nil 725 } 726 updateTransferExpectedBalanceMap(balancemap, executoraddr, 727 act.Recipient(), act.Amount(), act.Payload(), gasLimit, gasPrice) 728 atomic.AddUint64(&totalTsfSucceeded, 1) 729 case pbAct.GetExecution() != nil: 730 act := &action.Execution{} 731 if err := act.LoadProto(pbAct.GetExecution()); err != nil { 732 retErr = err 733 return nil 734 } 735 updateExecutionExpectedBalanceMap(balancemap, executoraddr, gasLimit, gasPrice) 736 case pbAct.GetStakeCreate() != nil: 737 act := &action.CreateStake{} 738 if err := act.LoadProto(pbAct.GetStakeCreate()); err != nil { 739 retErr = err 740 return nil 741 } 742 cost, err := act.Cost() 743 if err != nil { 744 retErr = err 745 return nil 746 } 747 updateStakeExpectedBalanceMap(balancemap, executoraddr, cost) 748 default: 749 retErr = errors.New("Unsupported action type for balance check") 750 return nil 751 } 752 } else { 753 atomic.AddUint64(&totalTsfFailed, 1) 754 } 755 return errors.New("return error so LruCache will remove this key") 756 } 757 return nil 758 }) 759 760 return empty, retErr 761 } 762 763 func updateTransferExpectedBalanceMap( 764 balancemap map[string]*big.Int, 765 senderAddr string, 766 recipientAddr string, 767 amount *big.Int, 768 payload []byte, 769 gasLimit uint64, 770 gasPrice *big.Int, 771 ) { 772 773 gasLimitBig := big.NewInt(int64(gasLimit)) 774 775 // calculate gas consumed by payload 776 gasUnitPayloadConsumed := new(big.Int).Mul(new(big.Int).SetUint64(action.TransferPayloadGas), 777 new(big.Int).SetUint64(uint64(len(payload)))) 778 gasUnitTransferConsumed := new(big.Int).SetUint64(action.TransferBaseIntrinsicGas) 779 780 // calculate total gas consumed by payload and transfer action 781 gasUnitConsumed := new(big.Int).Add(gasUnitPayloadConsumed, gasUnitTransferConsumed) 782 if gasLimitBig.Cmp(gasUnitConsumed) < 0 { 783 log.L().Fatal("Not enough gas") 784 } 785 786 // convert to gas cost 787 gasConsumed := new(big.Int).Mul(gasUnitConsumed, gasPrice) 788 789 // total cost of transferred amount, payload, transfer intrinsic 790 totalUsed := new(big.Int).Add(gasConsumed, amount) 791 792 // update sender balance 793 senderBalance := balancemap[senderAddr] 794 if senderBalance.Cmp(totalUsed) < 0 { 795 log.L().Fatal("Not enough balance") 796 } 797 balancemap[senderAddr].Sub(senderBalance, totalUsed) 798 799 // update recipient balance 800 recipientBalance := balancemap[recipientAddr] 801 balancemap[recipientAddr].Add(recipientBalance, amount) 802 } 803 804 func updateExecutionExpectedBalanceMap( 805 balancemap map[string]*big.Int, 806 executor string, 807 gasLimit uint64, 808 gasPrice *big.Int, 809 ) { 810 gasLimitBig := new(big.Int).SetUint64(gasLimit) 811 812 // NOTE: This hard-coded gas consumption value is precalculated on minicluster deployed test contract only 813 gasUnitConsumed := new(big.Int).SetUint64(12014) 814 815 if gasLimitBig.Cmp(gasUnitConsumed) < 0 { 816 log.L().Fatal("Not enough gas") 817 } 818 gasConsumed := new(big.Int).Mul(gasUnitConsumed, gasPrice) 819 820 executorBalance := balancemap[executor] 821 if executorBalance.Cmp(gasConsumed) < 0 { 822 log.L().Fatal("Not enough balance") 823 } 824 balancemap[executor].Sub(executorBalance, gasConsumed) 825 } 826 827 func updateStakeExpectedBalanceMap( 828 balancemap map[string]*big.Int, 829 candidateAddr string, 830 cost *big.Int, 831 ) { 832 // update sender balance 833 senderBalance := balancemap[candidateAddr] 834 if senderBalance.Cmp(cost) < 0 { 835 log.L().Fatal("Not enough balance") 836 } 837 balancemap[candidateAddr].Sub(senderBalance, cost) 838 } 839 840 // GetActionByActionHash acquires action by calling coreService 841 func GetActionByActionHash(api api.CoreService, actHash hash.Hash256) (*iotexapi.ActionInfo, error) { 842 act, err := api.Action(hex.EncodeToString(actHash[:]), false) 843 if err != nil { 844 return nil, err 845 } 846 return act, nil 847 } 848 849 // GetReceiptByAction acquires receipt by calling coreService 850 func GetReceiptByAction(api api.CoreService, actHash hash.Hash256) (*iotextypes.Receipt, error) { 851 receipt, err := api.ReceiptByActionHash(actHash) 852 if err != nil { 853 return nil, err 854 } 855 return receipt.ConvertToReceiptPb(), nil 856 }