From 162382ee6e4bc92f0e114d384a894671763e41cf Mon Sep 17 00:00:00 2001 From: Goran Rojovic <100121253+goran-ethernal@users.noreply.github.com> Date: Mon, 15 Apr 2024 15:56:31 +0200 Subject: [PATCH] Load test runner implementation (#194) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Load test runner implementation * UTs build fix * Lint fix * load-test command * Better UX * add ZexCoin to test artifacts * add erc20 test name log * gas price increase * collect all errors for tx sending and just log them * stale sequence check (temp fix) * more gasPrice increase * gather receipt errors and print them * lint fix * feeData * Lint fix * fix * ERC721 support * results to json file * lint fix * remove comment * receipts gather in separate routines * remove receipts timeout from fund and deploy functions * ticker timeout and gasPrice increase * lock in gathering receipts * increase maxDemotionsNum * waitForTxPool flag, and gathering results in parallel * lint fix * fix ut * Go mod tidy * comments fix * lint fix * more statistics * small fix * small fix --------- Co-authored-by: Stefan Negovanović --- command/bridge/fund/fund.go | 23 +- command/loadtest/load_test_run.go | 135 +++ command/loadtest/params.go | 73 ++ command/root/root.go | 2 + consensus/polybft/checkpoint_manager_test.go | 4 + consensus/polybft/contractsapi/init.go | 14 + .../test-contracts/ZexCoinERC20.json | 262 ++++++ .../contractsapi/test-contracts/ZexNFT.json | 376 ++++++++ consensus/polybft/fsm.go | 8 +- consensus/polybft/polybft.go | 23 + consensus/polybft/stake_manager_test.go | 4 + consensus/polybft/stale_sequence_check.go | 89 ++ go.mod | 8 +- go.sum | 19 +- jsonrpc/client.go | 14 + loadtest/runner/base_load_test_runner.go | 839 ++++++++++++++++++ loadtest/runner/eoa_runner.go | 144 +++ loadtest/runner/erc20_runner.go | 297 +++++++ loadtest/runner/erc721_runner.go | 209 +++++ loadtest/runner/load_test_runner.go | 98 ++ loadtest/runner/load_test_runner_test.go | 40 + scripts/cluster | 7 +- txpool/txpool.go | 32 +- txpool/txpool_test.go | 10 +- txrelayer/txrelayer.go | 62 +- 25 files changed, 2768 insertions(+), 24 deletions(-) create mode 100644 command/loadtest/load_test_run.go create mode 100644 command/loadtest/params.go create mode 100644 consensus/polybft/contractsapi/test-contracts/ZexCoinERC20.json create mode 100644 consensus/polybft/contractsapi/test-contracts/ZexNFT.json create mode 100644 consensus/polybft/stale_sequence_check.go create mode 100644 loadtest/runner/base_load_test_runner.go create mode 100644 loadtest/runner/eoa_runner.go create mode 100644 loadtest/runner/erc20_runner.go create mode 100644 loadtest/runner/erc721_runner.go create mode 100644 loadtest/runner/load_test_runner.go create mode 100644 loadtest/runner/load_test_runner_test.go diff --git a/command/bridge/fund/fund.go b/command/bridge/fund/fund.go index 791c2cf070..eea006df3d 100644 --- a/command/bridge/fund/fund.go +++ b/command/bridge/fund/fund.go @@ -11,6 +11,7 @@ import ( "github.com/0xPolygon/polygon-edge/command/bridge/helper" cmdHelper "github.com/0xPolygon/polygon-edge/command/helper" polybftsecrets "github.com/0xPolygon/polygon-edge/command/secrets/init" + "github.com/0xPolygon/polygon-edge/jsonrpc" "github.com/0xPolygon/polygon-edge/txrelayer" "github.com/0xPolygon/polygon-edge/types" ) @@ -78,8 +79,18 @@ func runCommand(cmd *cobra.Command, _ []string) { outputter := command.InitializeOutputter(cmd) defer outputter.WriteOutput() - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPCAddress), - txrelayer.WithReceiptsTimeout(params.txTimeout)) + client, err := jsonrpc.NewEthClient(params.jsonRPCAddress) + if err != nil { + outputter.SetError(fmt.Errorf("failed to initialize eth client: %w", err)) + + return + } + + txRelayer, err := txrelayer.NewTxRelayer( + txrelayer.WithClient(client), + txrelayer.WithReceiptsTimeout(params.txTimeout), + txrelayer.WithoutNonceGet(), + ) if err != nil { outputter.SetError(fmt.Errorf("failed to initialize tx relayer: %w", err)) @@ -93,6 +104,13 @@ func runCommand(cmd *cobra.Command, _ []string) { return } + senderNonce, err := client.GetNonce(deployerKey.Address(), jsonrpc.PendingBlockNumberOrHash) + if err != nil { + outputter.SetError(fmt.Errorf("failed to get deployer nonce: %w", err)) + + return + } + results := make([]command.CommandResult, len(params.addresses)) g, ctx := errgroup.WithContext(cmd.Context()) @@ -107,6 +125,7 @@ func runCommand(cmd *cobra.Command, _ []string) { default: fundAddr := params.addresses[i] txn := helper.CreateTransaction(types.ZeroAddress, &fundAddr, nil, params.amountValues[i], true) + txn.SetNonce(senderNonce + uint64(i)) var ( receipt *ethgo.Receipt diff --git a/command/loadtest/load_test_run.go b/command/loadtest/load_test_run.go new file mode 100644 index 0000000000..179bdcd8b6 --- /dev/null +++ b/command/loadtest/load_test_run.go @@ -0,0 +1,135 @@ +package loadtest + +import ( + "time" + + "github.com/0xPolygon/polygon-edge/command" + "github.com/0xPolygon/polygon-edge/command/helper" + "github.com/0xPolygon/polygon-edge/loadtest/runner" + "github.com/spf13/cobra" +) + +var ( + params loadTestParams +) + +func GetCommand() *cobra.Command { + loadTestCmd := &cobra.Command{ + Use: "load-test", + Short: "Runs a load test on a specified network", + PreRunE: preRunCommand, + Run: runCommand, + } + + helper.RegisterJSONRPCFlag(loadTestCmd) + + setFlags(loadTestCmd) + + return loadTestCmd +} + +func preRunCommand(cmd *cobra.Command, _ []string) error { + params.jsonRPCAddress = helper.GetJSONRPCAddress(cmd) + + return params.validateFlags() +} + +func setFlags(cmd *cobra.Command) { + cmd.Flags().StringVar( + ¶ms.mnemonic, + mnemonicFlag, + "", + "the mnemonic used to generate and fund virtual users", + ) + + cmd.Flags().StringVar( + ¶ms.loadTestType, + loadTestTypeFlag, + "eoa", + "the type of load test to run (supported types: eoa, erc20, erc721)", + ) + + cmd.Flags().StringVar( + ¶ms.loadTestName, + loadTestNameFlag, + "load test", + "the name of the load test", + ) + + cmd.Flags().IntVar( + ¶ms.vus, + vusFlag, + 1, + "the number of virtual users", + ) + + cmd.Flags().IntVar( + ¶ms.txsPerUser, + txsPerUserFlag, + 1, + "the number of transactions per virtual user", + ) + + cmd.Flags().BoolVar( + ¶ms.dynamicTxs, + dynamicTxsFlag, + false, + "indicates whether the load test should generate dynamic transactions", + ) + + cmd.Flags().DurationVar( + ¶ms.receiptsTimeout, + receiptsTimeoutFlag, + 30*time.Second, + "the timeout for waiting for transaction receipts", + ) + + cmd.Flags().DurationVar( + ¶ms.txPoolTimeout, + txPoolTimeoutFlag, + 10*time.Minute, + "the timeout for waiting for the transaction pool to empty", + ) + + cmd.Flags().BoolVar( + ¶ms.toJSON, + saveToJSONFlag, + false, + "saves results to JSON file", + ) + + cmd.Flags().BoolVar( + ¶ms.waitForTxPoolToEmpty, + waitForTxPoolToEmptyFlag, + false, + "waits for tx pool to empty before collecting results", + ) + + _ = cmd.MarkFlagRequired(mnemonicFlag) + _ = cmd.MarkFlagRequired(loadTestTypeFlag) +} + +func runCommand(cmd *cobra.Command, _ []string) { + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + loadTestRunner := &runner.LoadTestRunner{} + + err := loadTestRunner.Run(runner.LoadTestConfig{ + Mnemonnic: params.mnemonic, + LoadTestType: params.loadTestType, + LoadTestName: params.loadTestName, + JSONRPCUrl: params.jsonRPCAddress, + ReceiptsTimeout: params.receiptsTimeout, + TxPoolTimeout: params.txPoolTimeout, + VUs: params.vus, + TxsPerUser: params.txsPerUser, + DynamicTxs: params.dynamicTxs, + ResultsToJSON: params.toJSON, + WaitForTxPoolToEmpty: params.waitForTxPoolToEmpty, + }) + + if err != nil { + outputter.SetError(err) + } +} diff --git a/command/loadtest/params.go b/command/loadtest/params.go new file mode 100644 index 0000000000..4f49cd6a15 --- /dev/null +++ b/command/loadtest/params.go @@ -0,0 +1,73 @@ +package loadtest + +import ( + "errors" + "time" + + "github.com/0xPolygon/polygon-edge/loadtest/runner" +) + +const ( + mnemonicFlag = "mnemonic" + loadTestTypeFlag = "type" + loadTestNameFlag = "name" + + receiptsTimeoutFlag = "receipts-timeout" + txPoolTimeoutFlag = "txpool-timeout" + + vusFlag = "vus" + txsPerUserFlag = "txs-per-user" + dynamicTxsFlag = "dynamic" + + saveToJSONFlag = "to-json" + waitForTxPoolToEmptyFlag = "wait-txpool" +) + +var ( + errNoMnemonicProvided = errors.New("no mnemonic provided") + errNoLoadTestTypeProvided = errors.New("no load test type provided") + errUnsupportedLoadTestType = errors.New("unsupported load test type") + errInvalidVUs = errors.New("vus must be greater than 0") + errInvalidTxsPerUser = errors.New("txs-per-user must be greater than 0") +) + +type loadTestParams struct { + mnemonic string + loadTestType string + loadTestName string + jsonRPCAddress string + + receiptsTimeout time.Duration + txPoolTimeout time.Duration + + vus int + txsPerUser int + + dynamicTxs bool + toJSON bool + waitForTxPoolToEmpty bool +} + +func (ltp *loadTestParams) validateFlags() error { + if ltp.mnemonic == "" { + return errNoMnemonicProvided + } + + if ltp.loadTestType == "" { + return errNoLoadTestTypeProvided + } + + if !runner.IsLoadTestSupported(ltp.loadTestType) { + return errUnsupportedLoadTestType + } + + if ltp.vus < 1 { + return errInvalidVUs + } + + if ltp.txsPerUser < 1 { + return errInvalidTxsPerUser + } + + return nil +} diff --git a/command/root/root.go b/command/root/root.go index 38572fd8e9..e2b6476838 100644 --- a/command/root/root.go +++ b/command/root/root.go @@ -10,6 +10,7 @@ import ( "github.com/0xPolygon/polygon-edge/command/bridge" "github.com/0xPolygon/polygon-edge/command/genesis" "github.com/0xPolygon/polygon-edge/command/helper" + "github.com/0xPolygon/polygon-edge/command/loadtest" "github.com/0xPolygon/polygon-edge/command/mint" "github.com/0xPolygon/polygon-edge/command/monitor" "github.com/0xPolygon/polygon-edge/command/peers" @@ -57,6 +58,7 @@ func (rc *RootCommand) registerSubCommands() { regenesis.GetCommand(), mint.GetCommand(), validator.GetCommand(), + loadtest.GetCommand(), ) } diff --git a/consensus/polybft/checkpoint_manager_test.go b/consensus/polybft/checkpoint_manager_test.go index 2cb1411f05..819a1eabef 100644 --- a/consensus/polybft/checkpoint_manager_test.go +++ b/consensus/polybft/checkpoint_manager_test.go @@ -499,6 +499,10 @@ func (d *dummyTxRelayer) Client() *jsonrpc.EthClient { return nil } +func (d *dummyTxRelayer) GetTxnHashes() []types.Hash { + return nil +} + func getBlockNumberCheckpointSubmitInput(t *testing.T, input []byte) uint64 { t.Helper() diff --git a/consensus/polybft/contractsapi/init.go b/consensus/polybft/contractsapi/init.go index aebae82f40..a4e0ed100d 100644 --- a/consensus/polybft/contractsapi/init.go +++ b/consensus/polybft/contractsapi/init.go @@ -73,6 +73,8 @@ var ( TestRewardToken *contracts.Artifact Wrapper *contracts.Artifact NumberPersister *contracts.Artifact + ZexCoinERC20 *contracts.Artifact + ZexNFT *contracts.Artifact contractArtifacts map[string]*contracts.Artifact ) @@ -330,6 +332,16 @@ func init() { log.Fatal(err) } + ZexCoinERC20, err = contracts.DecodeArtifact(readTestContractContent("ZexCoinERC20.json")) + if err != nil { + log.Fatal(err) + } + + ZexNFT, err = contracts.DecodeArtifact(readTestContractContent("ZexNFT.json")) + if err != nil { + log.Fatal(err) + } + contractArtifacts = map[string]*contracts.Artifact{ "CheckpointManager": CheckpointManager, "ExitHelper": ExitHelper, @@ -381,6 +393,8 @@ func init() { "RootERC20": RootERC20, "TestSimple": TestSimple, "TestRewardToken": TestRewardToken, + "ZexCoinERC20": ZexCoinERC20, + "ZexNFT": ZexNFT, } } diff --git a/consensus/polybft/contractsapi/test-contracts/ZexCoinERC20.json b/consensus/polybft/contractsapi/test-contracts/ZexCoinERC20.json new file mode 100644 index 0000000000..4d47e1b465 --- /dev/null +++ b/consensus/polybft/contractsapi/test-contracts/ZexCoinERC20.json @@ -0,0 +1,262 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "ZexCoinERC20", + "sourceName": "contracts/Zex_erc20.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "total", + "type": "uint256" + }, + { + "internalType": "string", + "name": "coinName", + "type": "string" + }, + { + "internalType": "string", + "name": "coinSymbol", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "tokenOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tokens", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tokens", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "delegate", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegate", + "type": "address" + }, + { + "internalType": "uint256", + "name": "numTokens", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenOwner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "numTokens", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "buyer", + "type": "address" + }, + { + "internalType": "uint256", + "name": "numTokens", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ], + "bytecode": "0x60806040523480156200001157600080fd5b506040516200146538038062001465833981810160405281019062000037919062000220565b81600090805190602001906200004f929190620000db565b50806001908051906020019062000068929190620000db565b506005600260006101000a81548160ff021916908360ff16021790555082600581905550600554600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555050505062000462565b828054620000e99062000359565b90600052602060002090601f0160209004810192826200010d576000855562000159565b82601f106200012857805160ff191683800117855562000159565b8280016001018555821562000159579182015b82811115620001585782518255916020019190600101906200013b565b5b5090506200016891906200016c565b5090565b5b80821115620001875760008160009055506001016200016d565b5090565b6000620001a26200019c84620002e3565b620002ba565b905082815260208101848484011115620001c157620001c062000428565b5b620001ce84828562000323565b509392505050565b600082601f830112620001ee57620001ed62000423565b5b8151620002008482602086016200018b565b91505092915050565b6000815190506200021a8162000448565b92915050565b6000806000606084860312156200023c576200023b62000432565b5b60006200024c8682870162000209565b935050602084015167ffffffffffffffff81111562000270576200026f6200042d565b5b6200027e86828701620001d6565b925050604084015167ffffffffffffffff811115620002a257620002a16200042d565b5b620002b086828701620001d6565b9150509250925092565b6000620002c6620002d9565b9050620002d482826200038f565b919050565b6000604051905090565b600067ffffffffffffffff821115620003015762000300620003f4565b5b6200030c8262000437565b9050602081019050919050565b6000819050919050565b60005b838110156200034357808201518184015260208101905062000326565b8381111562000353576000848401525b50505050565b600060028204905060018216806200037257607f821691505b60208210811415620003895762000388620003c5565b5b50919050565b6200039a8262000437565b810181811067ffffffffffffffff82111715620003bc57620003bb620003f4565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b620004538162000319565b81146200045f57600080fd5b50565b610ff380620004726000396000f3fe60806040526004361061008a5760003560e01c8063313ce56711610059578063313ce5671461016957806370a082311461019457806395d89b41146101d1578063a9059cbb146101fc578063dd62ed3e1461023957610094565b806306fdde0314610099578063095ea7b3146100c457806318160ddd1461010157806323b872dd1461012c57610094565b3661009457600080fd5b600080fd5b3480156100a557600080fd5b506100ae610276565b6040516100bb9190610c16565b60405180910390f35b3480156100d057600080fd5b506100eb60048036038101906100e69190610aec565b610304565b6040516100f89190610bfb565b60405180910390f35b34801561010d57600080fd5b506101166103f6565b6040516101239190610c98565b60405180910390f35b34801561013857600080fd5b50610153600480360381019061014e9190610a99565b610400565b6040516101609190610bfb565b60405180910390f35b34801561017557600080fd5b5061017e6106f2565b60405161018b9190610cb3565b60405180910390f35b3480156101a057600080fd5b506101bb60048036038101906101b69190610a2c565b610705565b6040516101c89190610c98565b60405180910390f35b3480156101dd57600080fd5b506101e661074e565b6040516101f39190610c16565b60405180910390f35b34801561020857600080fd5b50610223600480360381019061021e9190610aec565b6107dc565b6040516102309190610bfb565b60405180910390f35b34801561024557600080fd5b50610260600480360381019061025b9190610a59565b61097b565b60405161026d9190610c98565b60405180910390f35b6000805461028390610dfc565b80601f01602080910402602001604051908101604052809291908181526020018280546102af90610dfc565b80156102fc5780601f106102d1576101008083540402835291602001916102fc565b820191906000526020600020905b8154815290600101906020018083116102df57829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516103e49190610c98565b60405180910390a36001905092915050565b6000600554905090565b6000600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054821115610484576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161047b90610c58565b60405180910390fd5b600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054821115610543576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161053a90610c38565b60405180910390fd5b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546105929190610d40565b9250508190555081600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546106259190610d40565b9250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461067b9190610cea565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516106df9190610c98565b60405180910390a3600190509392505050565b600260009054906101000a900460ff1681565b6000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6001805461075b90610dfc565b80601f016020809104026020016040519081016040528092919081815260200182805461078790610dfc565b80156107d45780601f106107a9576101008083540402835291602001916107d4565b820191906000526020600020905b8154815290600101906020018083116107b757829003601f168201915b505050505081565b6000600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054821115610860576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161085790610c78565b60405180910390fd5b81600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546108af9190610d40565b9250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546109059190610cea565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516109699190610c98565b60405180910390a36001905092915050565b6000600460008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600081359050610a1181610f8f565b92915050565b600081359050610a2681610fa6565b92915050565b600060208284031215610a4257610a41610e8c565b5b6000610a5084828501610a02565b91505092915050565b60008060408385031215610a7057610a6f610e8c565b5b6000610a7e85828601610a02565b9250506020610a8f85828601610a02565b9150509250929050565b600080600060608486031215610ab257610ab1610e8c565b5b6000610ac086828701610a02565b9350506020610ad186828701610a02565b9250506040610ae286828701610a17565b9150509250925092565b60008060408385031215610b0357610b02610e8c565b5b6000610b1185828601610a02565b9250506020610b2285828601610a17565b9150509250929050565b610b3581610d86565b82525050565b6000610b4682610cce565b610b508185610cd9565b9350610b60818560208601610dc9565b610b6981610e91565b840191505092915050565b6000610b81604083610cd9565b9150610b8c82610ea2565b604082019050919050565b6000610ba4603983610cd9565b9150610baf82610ef1565b604082019050919050565b6000610bc7603a83610cd9565b9150610bd282610f40565b604082019050919050565b610be681610db2565b82525050565b610bf581610dbc565b82525050565b6000602082019050610c106000830184610b2c565b92915050565b60006020820190508181036000830152610c308184610b3b565b905092915050565b60006020820190508181036000830152610c5181610b74565b9050919050565b60006020820190508181036000830152610c7181610b97565b9050919050565b60006020820190508181036000830152610c9181610bba565b9050919050565b6000602082019050610cad6000830184610bdd565b92915050565b6000602082019050610cc86000830184610bec565b92915050565b600081519050919050565b600082825260208201905092915050565b6000610cf582610db2565b9150610d0083610db2565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610d3557610d34610e2e565b5b828201905092915050565b6000610d4b82610db2565b9150610d5683610db2565b925082821015610d6957610d68610e2e565b5b828203905092915050565b6000610d7f82610d92565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015610de7578082015181840152602081019050610dcc565b83811115610df6576000848401525b50505050565b60006002820490506001821680610e1457607f821691505b60208210811415610e2857610e27610e5d565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600080fd5b6000601f19601f8301169050919050565b7f5468652064656c656761746520646f73656e2774206861766520656e6f75676860008201527f20616c6c6f77616e636520746f206d616b6520746865207472616e7366657221602082015250565b7f546865206f776e657220646f65736e2774206861766520656e6f75676820667560008201527f6e647320746f206d616b6520746865207472616e736665722100000000000000602082015250565b7f5468652073656e64657220646f73656e2774206861766520656e6f756768206660008201527f756e647320746f206d616b6520746865207472616e7366657221000000000000602082015250565b610f9881610d74565b8114610fa357600080fd5b50565b610faf81610db2565b8114610fba57600080fd5b5056fea26469706673582212200e5255656b357df40d9b2f85e514df2c5ce154371a2e0849bd0dfde95ed02c0764736f6c63430008070033", + "deployedBytecode": "0x60806040526004361061008a5760003560e01c8063313ce56711610059578063313ce5671461016957806370a082311461019457806395d89b41146101d1578063a9059cbb146101fc578063dd62ed3e1461023957610094565b806306fdde0314610099578063095ea7b3146100c457806318160ddd1461010157806323b872dd1461012c57610094565b3661009457600080fd5b600080fd5b3480156100a557600080fd5b506100ae610276565b6040516100bb9190610c16565b60405180910390f35b3480156100d057600080fd5b506100eb60048036038101906100e69190610aec565b610304565b6040516100f89190610bfb565b60405180910390f35b34801561010d57600080fd5b506101166103f6565b6040516101239190610c98565b60405180910390f35b34801561013857600080fd5b50610153600480360381019061014e9190610a99565b610400565b6040516101609190610bfb565b60405180910390f35b34801561017557600080fd5b5061017e6106f2565b60405161018b9190610cb3565b60405180910390f35b3480156101a057600080fd5b506101bb60048036038101906101b69190610a2c565b610705565b6040516101c89190610c98565b60405180910390f35b3480156101dd57600080fd5b506101e661074e565b6040516101f39190610c16565b60405180910390f35b34801561020857600080fd5b50610223600480360381019061021e9190610aec565b6107dc565b6040516102309190610bfb565b60405180910390f35b34801561024557600080fd5b50610260600480360381019061025b9190610a59565b61097b565b60405161026d9190610c98565b60405180910390f35b6000805461028390610dfc565b80601f01602080910402602001604051908101604052809291908181526020018280546102af90610dfc565b80156102fc5780601f106102d1576101008083540402835291602001916102fc565b820191906000526020600020905b8154815290600101906020018083116102df57829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516103e49190610c98565b60405180910390a36001905092915050565b6000600554905090565b6000600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054821115610484576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161047b90610c58565b60405180910390fd5b600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054821115610543576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161053a90610c38565b60405180910390fd5b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546105929190610d40565b9250508190555081600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546106259190610d40565b9250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461067b9190610cea565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516106df9190610c98565b60405180910390a3600190509392505050565b600260009054906101000a900460ff1681565b6000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6001805461075b90610dfc565b80601f016020809104026020016040519081016040528092919081815260200182805461078790610dfc565b80156107d45780601f106107a9576101008083540402835291602001916107d4565b820191906000526020600020905b8154815290600101906020018083116107b757829003601f168201915b505050505081565b6000600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054821115610860576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161085790610c78565b60405180910390fd5b81600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546108af9190610d40565b9250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546109059190610cea565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516109699190610c98565b60405180910390a36001905092915050565b6000600460008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600081359050610a1181610f8f565b92915050565b600081359050610a2681610fa6565b92915050565b600060208284031215610a4257610a41610e8c565b5b6000610a5084828501610a02565b91505092915050565b60008060408385031215610a7057610a6f610e8c565b5b6000610a7e85828601610a02565b9250506020610a8f85828601610a02565b9150509250929050565b600080600060608486031215610ab257610ab1610e8c565b5b6000610ac086828701610a02565b9350506020610ad186828701610a02565b9250506040610ae286828701610a17565b9150509250925092565b60008060408385031215610b0357610b02610e8c565b5b6000610b1185828601610a02565b9250506020610b2285828601610a17565b9150509250929050565b610b3581610d86565b82525050565b6000610b4682610cce565b610b508185610cd9565b9350610b60818560208601610dc9565b610b6981610e91565b840191505092915050565b6000610b81604083610cd9565b9150610b8c82610ea2565b604082019050919050565b6000610ba4603983610cd9565b9150610baf82610ef1565b604082019050919050565b6000610bc7603a83610cd9565b9150610bd282610f40565b604082019050919050565b610be681610db2565b82525050565b610bf581610dbc565b82525050565b6000602082019050610c106000830184610b2c565b92915050565b60006020820190508181036000830152610c308184610b3b565b905092915050565b60006020820190508181036000830152610c5181610b74565b9050919050565b60006020820190508181036000830152610c7181610b97565b9050919050565b60006020820190508181036000830152610c9181610bba565b9050919050565b6000602082019050610cad6000830184610bdd565b92915050565b6000602082019050610cc86000830184610bec565b92915050565b600081519050919050565b600082825260208201905092915050565b6000610cf582610db2565b9150610d0083610db2565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610d3557610d34610e2e565b5b828201905092915050565b6000610d4b82610db2565b9150610d5683610db2565b925082821015610d6957610d68610e2e565b5b828203905092915050565b6000610d7f82610d92565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015610de7578082015181840152602081019050610dcc565b83811115610df6576000848401525b50505050565b60006002820490506001821680610e1457607f821691505b60208210811415610e2857610e27610e5d565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600080fd5b6000601f19601f8301169050919050565b7f5468652064656c656761746520646f73656e2774206861766520656e6f75676860008201527f20616c6c6f77616e636520746f206d616b6520746865207472616e7366657221602082015250565b7f546865206f776e657220646f65736e2774206861766520656e6f75676820667560008201527f6e647320746f206d616b6520746865207472616e736665722100000000000000602082015250565b7f5468652073656e64657220646f73656e2774206861766520656e6f756768206660008201527f756e647320746f206d616b6520746865207472616e7366657221000000000000602082015250565b610f9881610d74565b8114610fa357600080fd5b50565b610faf81610db2565b8114610fba57600080fd5b5056fea26469706673582212200e5255656b357df40d9b2f85e514df2c5ce154371a2e0849bd0dfde95ed02c0764736f6c63430008070033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/consensus/polybft/contractsapi/test-contracts/ZexNFT.json b/consensus/polybft/contractsapi/test-contracts/ZexNFT.json new file mode 100644 index 0000000000..8bb080520f --- /dev/null +++ b/consensus/polybft/contractsapi/test-contracts/ZexNFT.json @@ -0,0 +1,376 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "ZexNFTs", + "sourceName": "contracts/Zex_ERC721.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "string", + "name": "tokenName", + "type": "string" + }, + { + "internalType": "string", + "name": "tokenSymbol", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "tokenURI", + "type": "string" + } + ], + "name": "createNFT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x60806040523480156200001157600080fd5b5060405162002e9838038062002e988339818101604052810190620000379190620001a3565b818181600090805190602001906200005192919062000075565b5080600190805190602001906200006a92919062000075565b5050505050620003ac565b8280546200008390620002bd565b90600052602060002090601f016020900481019282620000a75760008555620000f3565b82601f10620000c257805160ff1916838001178555620000f3565b82800160010185558215620000f3579182015b82811115620000f2578251825591602001919060010190620000d5565b5b50905062000102919062000106565b5090565b5b808211156200012157600081600090555060010162000107565b5090565b60006200013c620001368462000251565b62000228565b9050828152602081018484840111156200015b576200015a6200038c565b5b6200016884828562000287565b509392505050565b600082601f83011262000188576200018762000387565b5b81516200019a84826020860162000125565b91505092915050565b60008060408385031215620001bd57620001bc62000396565b5b600083015167ffffffffffffffff811115620001de57620001dd62000391565b5b620001ec8582860162000170565b925050602083015167ffffffffffffffff81111562000210576200020f62000391565b5b6200021e8582860162000170565b9150509250929050565b60006200023462000247565b9050620002428282620002f3565b919050565b6000604051905090565b600067ffffffffffffffff8211156200026f576200026e62000358565b5b6200027a826200039b565b9050602081019050919050565b60005b83811015620002a75780820151818401526020810190506200028a565b83811115620002b7576000848401525b50505050565b60006002820490506001821680620002d657607f821691505b60208210811415620002ed57620002ec62000329565b5b50919050565b620002fe826200039b565b810181811067ffffffffffffffff8211171562000320576200031f62000358565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b612adc80620003bc6000396000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c806370a082311161008c578063b88d4fde11610066578063b88d4fde1461025b578063c87b56dd14610277578063da2ed03e146102a7578063e985e9c5146102d7576100ea565b806370a08231146101f157806395d89b4114610221578063a22cb4651461023f576100ea565b8063095ea7b3116100c8578063095ea7b31461016d57806323b872dd1461018957806342842e0e146101a55780636352211e146101c1576100ea565b806301ffc9a7146100ef57806306fdde031461011f578063081812fc1461013d575b600080fd5b61010960048036038101906101049190611e39565b610307565b6040516101169190612208565b60405180910390f35b6101276103e9565b6040516101349190612223565b60405180910390f35b61015760048036038101906101529190611edc565b61047b565b60405161016491906121a1565b60405180910390f35b61018760048036038101906101829190611df9565b6104c1565b005b6101a3600480360381019061019e9190611ce3565b6105d9565b005b6101bf60048036038101906101ba9190611ce3565b610639565b005b6101db60048036038101906101d69190611edc565b610659565b6040516101e891906121a1565b60405180910390f35b61020b60048036038101906102069190611c76565b6106e0565b60405161021891906123c5565b60405180910390f35b610229610798565b6040516102369190612223565b60405180910390f35b61025960048036038101906102549190611db9565b61082a565b005b61027560048036038101906102709190611d36565b610840565b005b610291600480360381019061028c9190611edc565b6108a2565b60405161029e9190612223565b60405180910390f35b6102c160048036038101906102bc9190611e93565b6109b5565b6040516102ce91906123c5565b60405180910390f35b6102f160048036038101906102ec9190611ca3565b6109eb565b6040516102fe9190612208565b60405180910390f35b60007f80ac58cd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806103d257507f5b5e139f000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b806103e257506103e182610a7f565b5b9050919050565b6060600080546103f8906125ea565b80601f0160208091040260200160405190810160405280929190818152602001828054610424906125ea565b80156104715780601f1061044657610100808354040283529160200191610471565b820191906000526020600020905b81548152906001019060200180831161045457829003601f168201915b5050505050905090565b600061048682610ae9565b6004600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b60006104cc82610659565b90508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561053d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161053490612385565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff1661055c610b34565b73ffffffffffffffffffffffffffffffffffffffff16148061058b575061058a81610585610b34565b6109eb565b5b6105ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105c1906123a5565b60405180910390fd5b6105d48383610b3c565b505050565b6105ea6105e4610b34565b82610bf5565b610629576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161062090612245565b60405180910390fd5b610634838383610c8a565b505050565b61065483838360405180602001604052806000815250610840565b505050565b60008061066583610f84565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156106d7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106ce90612365565b60405180910390fd5b80915050919050565b60008073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610751576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161074890612305565b60405180910390fd5b600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6060600180546107a7906125ea565b80601f01602080910402602001604051908101604052809291908181526020018280546107d3906125ea565b80156108205780601f106107f557610100808354040283529160200191610820565b820191906000526020600020905b81548152906001019060200180831161080357829003601f168201915b5050505050905090565b61083c610835610b34565b8383610fc1565b5050565b61085161084b610b34565b83610bf5565b610890576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161088790612245565b60405180910390fd5b61089c8484848461112e565b50505050565b60606108ad82610ae9565b60006006600084815260200190815260200160002080546108cd906125ea565b80601f01602080910402602001604051908101604052809291908181526020018280546108f9906125ea565b80156109465780601f1061091b57610100808354040283529160200191610946565b820191906000526020600020905b81548152906001019060200180831161092957829003601f168201915b50505050509050600061095761118a565b905060008151141561096d5781925050506109b0565b6000825111156109a257808260405160200161098a92919061217d565b604051602081830303815290604052925050506109b0565b6109ab846111a1565b925050505b919050565b6000806109c26007611209565b90506109ce3382611217565b6109d88184611235565b6109e260076112a9565b80915050919050565b6000600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905092915050565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b610af2816112bf565b610b31576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b2890612365565b60405180910390fd5b50565b600033905090565b816004600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff16610baf83610659565b73ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b600080610c0183610659565b90508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161480610c435750610c4281856109eb565b5b80610c8157508373ffffffffffffffffffffffffffffffffffffffff16610c698461047b565b73ffffffffffffffffffffffffffffffffffffffff16145b91505092915050565b8273ffffffffffffffffffffffffffffffffffffffff16610caa82610659565b73ffffffffffffffffffffffffffffffffffffffff1614610d00576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610cf790612285565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610d70576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d67906122c5565b60405180910390fd5b610d7d8383836001611300565b8273ffffffffffffffffffffffffffffffffffffffff16610d9d82610659565b73ffffffffffffffffffffffffffffffffffffffff1614610df3576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610dea90612285565b60405180910390fd5b6004600082815260200190815260200160002060006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556001600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055506001600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550816002600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4610f7f8383836001611426565b505050565b60006002600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611030576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611027906122e5565b60405180910390fd5b80600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31836040516111219190612208565b60405180910390a3505050565b611139848484610c8a565b6111458484848461142c565b611184576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161117b90612265565b60405180910390fd5b50505050565b606060405180602001604052806000815250905090565b60606111ac82610ae9565b60006111b661118a565b905060008151116111d65760405180602001604052806000815250611201565b806111e0846115c3565b6040516020016111f192919061217d565b6040516020818303038152906040525b915050919050565b600081600001549050919050565b61123182826040518060200160405280600081525061169b565b5050565b61123e826112bf565b61127d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161127490612325565b60405180910390fd5b806006600084815260200190815260200160002090805190602001906112a4929190611a8a565b505050565b6001816000016000828254019250508190555050565b60008073ffffffffffffffffffffffffffffffffffffffff166112e183610f84565b73ffffffffffffffffffffffffffffffffffffffff1614159050919050565b600181111561142057600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16146113945780600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461138c9190612500565b925050819055505b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161461141f5780600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461141791906124aa565b925050819055505b5b50505050565b50505050565b600061144d8473ffffffffffffffffffffffffffffffffffffffff166116f6565b156115b6578373ffffffffffffffffffffffffffffffffffffffff1663150b7a02611476610b34565b8786866040518563ffffffff1660e01b815260040161149894939291906121bc565b602060405180830381600087803b1580156114b257600080fd5b505af19250505080156114e357506040513d601f19601f820116820180604052508101906114e09190611e66565b60015b611566573d8060008114611513576040519150601f19603f3d011682016040523d82523d6000602084013e611518565b606091505b5060008151141561155e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161155590612265565b60405180910390fd5b805181602001fd5b63150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149150506115bb565b600190505b949350505050565b6060600060016115d284611719565b01905060008167ffffffffffffffff8111156115f1576115f06126da565b5b6040519080825280601f01601f1916602001820160405280156116235781602001600182028036833780820191505090505b509050600082602001820190505b600115611690578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a858161167a5761167961267c565b5b049450600085141561168b57611690565b611631565b819350505050919050565b6116a5838361186c565b6116b2600084848461142c565b6116f1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016116e890612265565b60405180910390fd5b505050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b600080600090507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310611777577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000838161176d5761176c61267c565b5b0492506040810190505b6d04ee2d6d415b85acef810000000083106117b4576d04ee2d6d415b85acef810000000083816117aa576117a961267c565b5b0492506020810190505b662386f26fc1000083106117e357662386f26fc1000083816117d9576117d861267c565b5b0492506010810190505b6305f5e100831061180c576305f5e10083816118025761180161267c565b5b0492506008810190505b61271083106118315761271083816118275761182661267c565b5b0492506004810190505b60648310611854576064838161184a5761184961267c565b5b0492506002810190505b600a8310611863576001810190505b80915050919050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156118dc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016118d390612345565b60405180910390fd5b6118e5816112bf565b15611925576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161191c906122a5565b60405180910390fd5b611933600083836001611300565b61193c816112bf565b1561197c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611973906122a5565b60405180910390fd5b6001600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550816002600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4611a86600083836001611426565b5050565b828054611a96906125ea565b90600052602060002090601f016020900481019282611ab85760008555611aff565b82601f10611ad157805160ff1916838001178555611aff565b82800160010185558215611aff579182015b82811115611afe578251825591602001919060010190611ae3565b5b509050611b0c9190611b10565b5090565b5b80821115611b29576000816000905550600101611b11565b5090565b6000611b40611b3b84612405565b6123e0565b905082815260208101848484011115611b5c57611b5b61270e565b5b611b678482856125a8565b509392505050565b6000611b82611b7d84612436565b6123e0565b905082815260208101848484011115611b9e57611b9d61270e565b5b611ba98482856125a8565b509392505050565b600081359050611bc081612a4a565b92915050565b600081359050611bd581612a61565b92915050565b600081359050611bea81612a78565b92915050565b600081519050611bff81612a78565b92915050565b600082601f830112611c1a57611c19612709565b5b8135611c2a848260208601611b2d565b91505092915050565b600082601f830112611c4857611c47612709565b5b8135611c58848260208601611b6f565b91505092915050565b600081359050611c7081612a8f565b92915050565b600060208284031215611c8c57611c8b612718565b5b6000611c9a84828501611bb1565b91505092915050565b60008060408385031215611cba57611cb9612718565b5b6000611cc885828601611bb1565b9250506020611cd985828601611bb1565b9150509250929050565b600080600060608486031215611cfc57611cfb612718565b5b6000611d0a86828701611bb1565b9350506020611d1b86828701611bb1565b9250506040611d2c86828701611c61565b9150509250925092565b60008060008060808587031215611d5057611d4f612718565b5b6000611d5e87828801611bb1565b9450506020611d6f87828801611bb1565b9350506040611d8087828801611c61565b925050606085013567ffffffffffffffff811115611da157611da0612713565b5b611dad87828801611c05565b91505092959194509250565b60008060408385031215611dd057611dcf612718565b5b6000611dde85828601611bb1565b9250506020611def85828601611bc6565b9150509250929050565b60008060408385031215611e1057611e0f612718565b5b6000611e1e85828601611bb1565b9250506020611e2f85828601611c61565b9150509250929050565b600060208284031215611e4f57611e4e612718565b5b6000611e5d84828501611bdb565b91505092915050565b600060208284031215611e7c57611e7b612718565b5b6000611e8a84828501611bf0565b91505092915050565b600060208284031215611ea957611ea8612718565b5b600082013567ffffffffffffffff811115611ec757611ec6612713565b5b611ed384828501611c33565b91505092915050565b600060208284031215611ef257611ef1612718565b5b6000611f0084828501611c61565b91505092915050565b611f1281612534565b82525050565b611f2181612546565b82525050565b6000611f3282612467565b611f3c818561247d565b9350611f4c8185602086016125b7565b611f558161271d565b840191505092915050565b6000611f6b82612472565b611f75818561248e565b9350611f858185602086016125b7565b611f8e8161271d565b840191505092915050565b6000611fa482612472565b611fae818561249f565b9350611fbe8185602086016125b7565b80840191505092915050565b6000611fd7602d8361248e565b9150611fe28261272e565b604082019050919050565b6000611ffa60328361248e565b91506120058261277d565b604082019050919050565b600061201d60258361248e565b9150612028826127cc565b604082019050919050565b6000612040601c8361248e565b915061204b8261281b565b602082019050919050565b600061206360248361248e565b915061206e82612844565b604082019050919050565b600061208660198361248e565b915061209182612893565b602082019050919050565b60006120a960298361248e565b91506120b4826128bc565b604082019050919050565b60006120cc602e8361248e565b91506120d78261290b565b604082019050919050565b60006120ef60208361248e565b91506120fa8261295a565b602082019050919050565b600061211260188361248e565b915061211d82612983565b602082019050919050565b600061213560218361248e565b9150612140826129ac565b604082019050919050565b6000612158603d8361248e565b9150612163826129fb565b604082019050919050565b6121778161259e565b82525050565b60006121898285611f99565b91506121958284611f99565b91508190509392505050565b60006020820190506121b66000830184611f09565b92915050565b60006080820190506121d16000830187611f09565b6121de6020830186611f09565b6121eb604083018561216e565b81810360608301526121fd8184611f27565b905095945050505050565b600060208201905061221d6000830184611f18565b92915050565b6000602082019050818103600083015261223d8184611f60565b905092915050565b6000602082019050818103600083015261225e81611fca565b9050919050565b6000602082019050818103600083015261227e81611fed565b9050919050565b6000602082019050818103600083015261229e81612010565b9050919050565b600060208201905081810360008301526122be81612033565b9050919050565b600060208201905081810360008301526122de81612056565b9050919050565b600060208201905081810360008301526122fe81612079565b9050919050565b6000602082019050818103600083015261231e8161209c565b9050919050565b6000602082019050818103600083015261233e816120bf565b9050919050565b6000602082019050818103600083015261235e816120e2565b9050919050565b6000602082019050818103600083015261237e81612105565b9050919050565b6000602082019050818103600083015261239e81612128565b9050919050565b600060208201905081810360008301526123be8161214b565b9050919050565b60006020820190506123da600083018461216e565b92915050565b60006123ea6123fb565b90506123f6828261261c565b919050565b6000604051905090565b600067ffffffffffffffff8211156124205761241f6126da565b5b6124298261271d565b9050602081019050919050565b600067ffffffffffffffff821115612451576124506126da565b5b61245a8261271d565b9050602081019050919050565b600081519050919050565b600081519050919050565b600082825260208201905092915050565b600082825260208201905092915050565b600081905092915050565b60006124b58261259e565b91506124c08361259e565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156124f5576124f461264d565b5b828201905092915050565b600061250b8261259e565b91506125168361259e565b9250828210156125295761252861264d565b5b828203905092915050565b600061253f8261257e565b9050919050565b60008115159050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b82818337600083830152505050565b60005b838110156125d55780820151818401526020810190506125ba565b838111156125e4576000848401525b50505050565b6000600282049050600182168061260257607f821691505b60208210811415612616576126156126ab565b5b50919050565b6126258261271d565b810181811067ffffffffffffffff82111715612644576126436126da565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560008201527f72206f7220617070726f76656400000000000000000000000000000000000000602082015250565b7f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560008201527f63656976657220696d706c656d656e7465720000000000000000000000000000602082015250565b7f4552433732313a207472616e736665722066726f6d20696e636f72726563742060008201527f6f776e6572000000000000000000000000000000000000000000000000000000602082015250565b7f4552433732313a20746f6b656e20616c7265616479206d696e74656400000000600082015250565b7f4552433732313a207472616e7366657220746f20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f4552433732313a20617070726f766520746f2063616c6c657200000000000000600082015250565b7f4552433732313a2061646472657373207a65726f206973206e6f74206120766160008201527f6c6964206f776e65720000000000000000000000000000000000000000000000602082015250565b7f45524337323155524953746f726167653a2055524920736574206f66206e6f6e60008201527f6578697374656e7420746f6b656e000000000000000000000000000000000000602082015250565b7f4552433732313a206d696e7420746f20746865207a65726f2061646472657373600082015250565b7f4552433732313a20696e76616c696420746f6b656e2049440000000000000000600082015250565b7f4552433732313a20617070726f76616c20746f2063757272656e74206f776e6560008201527f7200000000000000000000000000000000000000000000000000000000000000602082015250565b7f4552433732313a20617070726f76652063616c6c6572206973206e6f7420746f60008201527f6b656e206f776e6572206f7220617070726f76656420666f7220616c6c000000602082015250565b612a5381612534565b8114612a5e57600080fd5b50565b612a6a81612546565b8114612a7557600080fd5b50565b612a8181612552565b8114612a8c57600080fd5b50565b612a988161259e565b8114612aa357600080fd5b5056fea26469706673582212203742ed7cadb31d3a818f72e6ea2e336c2bb8e9a5d924b44225b01dae9740066464736f6c63430008070033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100ea5760003560e01c806370a082311161008c578063b88d4fde11610066578063b88d4fde1461025b578063c87b56dd14610277578063da2ed03e146102a7578063e985e9c5146102d7576100ea565b806370a08231146101f157806395d89b4114610221578063a22cb4651461023f576100ea565b8063095ea7b3116100c8578063095ea7b31461016d57806323b872dd1461018957806342842e0e146101a55780636352211e146101c1576100ea565b806301ffc9a7146100ef57806306fdde031461011f578063081812fc1461013d575b600080fd5b61010960048036038101906101049190611e39565b610307565b6040516101169190612208565b60405180910390f35b6101276103e9565b6040516101349190612223565b60405180910390f35b61015760048036038101906101529190611edc565b61047b565b60405161016491906121a1565b60405180910390f35b61018760048036038101906101829190611df9565b6104c1565b005b6101a3600480360381019061019e9190611ce3565b6105d9565b005b6101bf60048036038101906101ba9190611ce3565b610639565b005b6101db60048036038101906101d69190611edc565b610659565b6040516101e891906121a1565b60405180910390f35b61020b60048036038101906102069190611c76565b6106e0565b60405161021891906123c5565b60405180910390f35b610229610798565b6040516102369190612223565b60405180910390f35b61025960048036038101906102549190611db9565b61082a565b005b61027560048036038101906102709190611d36565b610840565b005b610291600480360381019061028c9190611edc565b6108a2565b60405161029e9190612223565b60405180910390f35b6102c160048036038101906102bc9190611e93565b6109b5565b6040516102ce91906123c5565b60405180910390f35b6102f160048036038101906102ec9190611ca3565b6109eb565b6040516102fe9190612208565b60405180910390f35b60007f80ac58cd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806103d257507f5b5e139f000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b806103e257506103e182610a7f565b5b9050919050565b6060600080546103f8906125ea565b80601f0160208091040260200160405190810160405280929190818152602001828054610424906125ea565b80156104715780601f1061044657610100808354040283529160200191610471565b820191906000526020600020905b81548152906001019060200180831161045457829003601f168201915b5050505050905090565b600061048682610ae9565b6004600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b60006104cc82610659565b90508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561053d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161053490612385565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff1661055c610b34565b73ffffffffffffffffffffffffffffffffffffffff16148061058b575061058a81610585610b34565b6109eb565b5b6105ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105c1906123a5565b60405180910390fd5b6105d48383610b3c565b505050565b6105ea6105e4610b34565b82610bf5565b610629576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161062090612245565b60405180910390fd5b610634838383610c8a565b505050565b61065483838360405180602001604052806000815250610840565b505050565b60008061066583610f84565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156106d7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106ce90612365565b60405180910390fd5b80915050919050565b60008073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610751576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161074890612305565b60405180910390fd5b600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6060600180546107a7906125ea565b80601f01602080910402602001604051908101604052809291908181526020018280546107d3906125ea565b80156108205780601f106107f557610100808354040283529160200191610820565b820191906000526020600020905b81548152906001019060200180831161080357829003601f168201915b5050505050905090565b61083c610835610b34565b8383610fc1565b5050565b61085161084b610b34565b83610bf5565b610890576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161088790612245565b60405180910390fd5b61089c8484848461112e565b50505050565b60606108ad82610ae9565b60006006600084815260200190815260200160002080546108cd906125ea565b80601f01602080910402602001604051908101604052809291908181526020018280546108f9906125ea565b80156109465780601f1061091b57610100808354040283529160200191610946565b820191906000526020600020905b81548152906001019060200180831161092957829003601f168201915b50505050509050600061095761118a565b905060008151141561096d5781925050506109b0565b6000825111156109a257808260405160200161098a92919061217d565b604051602081830303815290604052925050506109b0565b6109ab846111a1565b925050505b919050565b6000806109c26007611209565b90506109ce3382611217565b6109d88184611235565b6109e260076112a9565b80915050919050565b6000600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905092915050565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b610af2816112bf565b610b31576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b2890612365565b60405180910390fd5b50565b600033905090565b816004600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff16610baf83610659565b73ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b600080610c0183610659565b90508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161480610c435750610c4281856109eb565b5b80610c8157508373ffffffffffffffffffffffffffffffffffffffff16610c698461047b565b73ffffffffffffffffffffffffffffffffffffffff16145b91505092915050565b8273ffffffffffffffffffffffffffffffffffffffff16610caa82610659565b73ffffffffffffffffffffffffffffffffffffffff1614610d00576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610cf790612285565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610d70576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d67906122c5565b60405180910390fd5b610d7d8383836001611300565b8273ffffffffffffffffffffffffffffffffffffffff16610d9d82610659565b73ffffffffffffffffffffffffffffffffffffffff1614610df3576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610dea90612285565b60405180910390fd5b6004600082815260200190815260200160002060006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556001600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055506001600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550816002600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4610f7f8383836001611426565b505050565b60006002600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611030576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611027906122e5565b60405180910390fd5b80600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31836040516111219190612208565b60405180910390a3505050565b611139848484610c8a565b6111458484848461142c565b611184576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161117b90612265565b60405180910390fd5b50505050565b606060405180602001604052806000815250905090565b60606111ac82610ae9565b60006111b661118a565b905060008151116111d65760405180602001604052806000815250611201565b806111e0846115c3565b6040516020016111f192919061217d565b6040516020818303038152906040525b915050919050565b600081600001549050919050565b61123182826040518060200160405280600081525061169b565b5050565b61123e826112bf565b61127d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161127490612325565b60405180910390fd5b806006600084815260200190815260200160002090805190602001906112a4929190611a8a565b505050565b6001816000016000828254019250508190555050565b60008073ffffffffffffffffffffffffffffffffffffffff166112e183610f84565b73ffffffffffffffffffffffffffffffffffffffff1614159050919050565b600181111561142057600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16146113945780600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461138c9190612500565b925050819055505b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161461141f5780600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461141791906124aa565b925050819055505b5b50505050565b50505050565b600061144d8473ffffffffffffffffffffffffffffffffffffffff166116f6565b156115b6578373ffffffffffffffffffffffffffffffffffffffff1663150b7a02611476610b34565b8786866040518563ffffffff1660e01b815260040161149894939291906121bc565b602060405180830381600087803b1580156114b257600080fd5b505af19250505080156114e357506040513d601f19601f820116820180604052508101906114e09190611e66565b60015b611566573d8060008114611513576040519150601f19603f3d011682016040523d82523d6000602084013e611518565b606091505b5060008151141561155e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161155590612265565b60405180910390fd5b805181602001fd5b63150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149150506115bb565b600190505b949350505050565b6060600060016115d284611719565b01905060008167ffffffffffffffff8111156115f1576115f06126da565b5b6040519080825280601f01601f1916602001820160405280156116235781602001600182028036833780820191505090505b509050600082602001820190505b600115611690578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a858161167a5761167961267c565b5b049450600085141561168b57611690565b611631565b819350505050919050565b6116a5838361186c565b6116b2600084848461142c565b6116f1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016116e890612265565b60405180910390fd5b505050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b600080600090507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310611777577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000838161176d5761176c61267c565b5b0492506040810190505b6d04ee2d6d415b85acef810000000083106117b4576d04ee2d6d415b85acef810000000083816117aa576117a961267c565b5b0492506020810190505b662386f26fc1000083106117e357662386f26fc1000083816117d9576117d861267c565b5b0492506010810190505b6305f5e100831061180c576305f5e10083816118025761180161267c565b5b0492506008810190505b61271083106118315761271083816118275761182661267c565b5b0492506004810190505b60648310611854576064838161184a5761184961267c565b5b0492506002810190505b600a8310611863576001810190505b80915050919050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156118dc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016118d390612345565b60405180910390fd5b6118e5816112bf565b15611925576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161191c906122a5565b60405180910390fd5b611933600083836001611300565b61193c816112bf565b1561197c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611973906122a5565b60405180910390fd5b6001600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550816002600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4611a86600083836001611426565b5050565b828054611a96906125ea565b90600052602060002090601f016020900481019282611ab85760008555611aff565b82601f10611ad157805160ff1916838001178555611aff565b82800160010185558215611aff579182015b82811115611afe578251825591602001919060010190611ae3565b5b509050611b0c9190611b10565b5090565b5b80821115611b29576000816000905550600101611b11565b5090565b6000611b40611b3b84612405565b6123e0565b905082815260208101848484011115611b5c57611b5b61270e565b5b611b678482856125a8565b509392505050565b6000611b82611b7d84612436565b6123e0565b905082815260208101848484011115611b9e57611b9d61270e565b5b611ba98482856125a8565b509392505050565b600081359050611bc081612a4a565b92915050565b600081359050611bd581612a61565b92915050565b600081359050611bea81612a78565b92915050565b600081519050611bff81612a78565b92915050565b600082601f830112611c1a57611c19612709565b5b8135611c2a848260208601611b2d565b91505092915050565b600082601f830112611c4857611c47612709565b5b8135611c58848260208601611b6f565b91505092915050565b600081359050611c7081612a8f565b92915050565b600060208284031215611c8c57611c8b612718565b5b6000611c9a84828501611bb1565b91505092915050565b60008060408385031215611cba57611cb9612718565b5b6000611cc885828601611bb1565b9250506020611cd985828601611bb1565b9150509250929050565b600080600060608486031215611cfc57611cfb612718565b5b6000611d0a86828701611bb1565b9350506020611d1b86828701611bb1565b9250506040611d2c86828701611c61565b9150509250925092565b60008060008060808587031215611d5057611d4f612718565b5b6000611d5e87828801611bb1565b9450506020611d6f87828801611bb1565b9350506040611d8087828801611c61565b925050606085013567ffffffffffffffff811115611da157611da0612713565b5b611dad87828801611c05565b91505092959194509250565b60008060408385031215611dd057611dcf612718565b5b6000611dde85828601611bb1565b9250506020611def85828601611bc6565b9150509250929050565b60008060408385031215611e1057611e0f612718565b5b6000611e1e85828601611bb1565b9250506020611e2f85828601611c61565b9150509250929050565b600060208284031215611e4f57611e4e612718565b5b6000611e5d84828501611bdb565b91505092915050565b600060208284031215611e7c57611e7b612718565b5b6000611e8a84828501611bf0565b91505092915050565b600060208284031215611ea957611ea8612718565b5b600082013567ffffffffffffffff811115611ec757611ec6612713565b5b611ed384828501611c33565b91505092915050565b600060208284031215611ef257611ef1612718565b5b6000611f0084828501611c61565b91505092915050565b611f1281612534565b82525050565b611f2181612546565b82525050565b6000611f3282612467565b611f3c818561247d565b9350611f4c8185602086016125b7565b611f558161271d565b840191505092915050565b6000611f6b82612472565b611f75818561248e565b9350611f858185602086016125b7565b611f8e8161271d565b840191505092915050565b6000611fa482612472565b611fae818561249f565b9350611fbe8185602086016125b7565b80840191505092915050565b6000611fd7602d8361248e565b9150611fe28261272e565b604082019050919050565b6000611ffa60328361248e565b91506120058261277d565b604082019050919050565b600061201d60258361248e565b9150612028826127cc565b604082019050919050565b6000612040601c8361248e565b915061204b8261281b565b602082019050919050565b600061206360248361248e565b915061206e82612844565b604082019050919050565b600061208660198361248e565b915061209182612893565b602082019050919050565b60006120a960298361248e565b91506120b4826128bc565b604082019050919050565b60006120cc602e8361248e565b91506120d78261290b565b604082019050919050565b60006120ef60208361248e565b91506120fa8261295a565b602082019050919050565b600061211260188361248e565b915061211d82612983565b602082019050919050565b600061213560218361248e565b9150612140826129ac565b604082019050919050565b6000612158603d8361248e565b9150612163826129fb565b604082019050919050565b6121778161259e565b82525050565b60006121898285611f99565b91506121958284611f99565b91508190509392505050565b60006020820190506121b66000830184611f09565b92915050565b60006080820190506121d16000830187611f09565b6121de6020830186611f09565b6121eb604083018561216e565b81810360608301526121fd8184611f27565b905095945050505050565b600060208201905061221d6000830184611f18565b92915050565b6000602082019050818103600083015261223d8184611f60565b905092915050565b6000602082019050818103600083015261225e81611fca565b9050919050565b6000602082019050818103600083015261227e81611fed565b9050919050565b6000602082019050818103600083015261229e81612010565b9050919050565b600060208201905081810360008301526122be81612033565b9050919050565b600060208201905081810360008301526122de81612056565b9050919050565b600060208201905081810360008301526122fe81612079565b9050919050565b6000602082019050818103600083015261231e8161209c565b9050919050565b6000602082019050818103600083015261233e816120bf565b9050919050565b6000602082019050818103600083015261235e816120e2565b9050919050565b6000602082019050818103600083015261237e81612105565b9050919050565b6000602082019050818103600083015261239e81612128565b9050919050565b600060208201905081810360008301526123be8161214b565b9050919050565b60006020820190506123da600083018461216e565b92915050565b60006123ea6123fb565b90506123f6828261261c565b919050565b6000604051905090565b600067ffffffffffffffff8211156124205761241f6126da565b5b6124298261271d565b9050602081019050919050565b600067ffffffffffffffff821115612451576124506126da565b5b61245a8261271d565b9050602081019050919050565b600081519050919050565b600081519050919050565b600082825260208201905092915050565b600082825260208201905092915050565b600081905092915050565b60006124b58261259e565b91506124c08361259e565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156124f5576124f461264d565b5b828201905092915050565b600061250b8261259e565b91506125168361259e565b9250828210156125295761252861264d565b5b828203905092915050565b600061253f8261257e565b9050919050565b60008115159050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b82818337600083830152505050565b60005b838110156125d55780820151818401526020810190506125ba565b838111156125e4576000848401525b50505050565b6000600282049050600182168061260257607f821691505b60208210811415612616576126156126ab565b5b50919050565b6126258261271d565b810181811067ffffffffffffffff82111715612644576126436126da565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560008201527f72206f7220617070726f76656400000000000000000000000000000000000000602082015250565b7f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560008201527f63656976657220696d706c656d656e7465720000000000000000000000000000602082015250565b7f4552433732313a207472616e736665722066726f6d20696e636f72726563742060008201527f6f776e6572000000000000000000000000000000000000000000000000000000602082015250565b7f4552433732313a20746f6b656e20616c7265616479206d696e74656400000000600082015250565b7f4552433732313a207472616e7366657220746f20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f4552433732313a20617070726f766520746f2063616c6c657200000000000000600082015250565b7f4552433732313a2061646472657373207a65726f206973206e6f74206120766160008201527f6c6964206f776e65720000000000000000000000000000000000000000000000602082015250565b7f45524337323155524953746f726167653a2055524920736574206f66206e6f6e60008201527f6578697374656e7420746f6b656e000000000000000000000000000000000000602082015250565b7f4552433732313a206d696e7420746f20746865207a65726f2061646472657373600082015250565b7f4552433732313a20696e76616c696420746f6b656e2049440000000000000000600082015250565b7f4552433732313a20617070726f76616c20746f2063757272656e74206f776e6560008201527f7200000000000000000000000000000000000000000000000000000000000000602082015250565b7f4552433732313a20617070726f76652063616c6c6572206973206e6f7420746f60008201527f6b656e206f776e6572206f7220617070726f76656420666f7220616c6c000000602082015250565b612a5381612534565b8114612a5e57600080fd5b50565b612a6a81612546565b8114612a7557600080fd5b50565b612a8181612552565b8114612a8c57600080fd5b50565b612a988161259e565b8114612aa357600080fd5b5056fea26469706673582212203742ed7cadb31d3a818f72e6ea2e336c2bb8e9a5d924b44225b01dae9740066464736f6c63430008070033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/consensus/polybft/fsm.go b/consensus/polybft/fsm.go index e754c8920f..262882d2f7 100644 --- a/consensus/polybft/fsm.go +++ b/consensus/polybft/fsm.go @@ -211,6 +211,8 @@ func (f *fsm) BuildProposal(currentRound uint64) ([]byte, error) { } f.logger.Debug("[FSM Build Proposal]", + "block", stateBlock.Block.Number(), + "round", currentRound, "txs", len(stateBlock.Block.Transactions), "proposal hash", checkpointHash.String()) } @@ -392,7 +394,11 @@ func (f *fsm) Validate(proposal []byte) error { return fmt.Errorf("failed to calculate proposal hash: %w", err) } - f.logger.Debug("[FSM Validate]", "txs", len(block.Transactions), "proposal hash", checkpointHash) + f.logger.Debug("[FSM Validate] Finish", + "block num", block.Number(), + "proposer", types.BytesToHash(block.Header.Miner), + "txs", len(block.Transactions), + "proposal hash", checkpointHash) } f.target = stateBlock diff --git a/consensus/polybft/polybft.go b/consensus/polybft/polybft.go index acc74dade4..f1d8561569 100644 --- a/consensus/polybft/polybft.go +++ b/consensus/polybft/polybft.go @@ -629,6 +629,16 @@ func (p *Polybft) startConsensusProtocol() { stopSequence func() ) + // check every block time * 1.5, so we don't artificially close a sequence + // before the actual sequence has ended properly + checkOffset := p.config.BlockTime + if checkOffset > 1 { + checkOffset /= 2 + } + + checkFrequency := time.Duration(p.config.BlockTime + checkOffset) + staleChecker := newStaleSequenceCheck(p.logger, p.blockchain.CurrentHeader, checkFrequency*time.Second) + for { latestHeader := p.blockchain.CurrentHeader() @@ -652,6 +662,8 @@ func (p *Polybft) startConsensusProtocol() { } sequenceCh, stopSequence = p.ibft.runSequence(latestHeader.Number + 1) + staleChecker.setSequence(latestHeader.Number + 1) + staleChecker.startChecking() } now := time.Now().UTC() @@ -662,15 +674,26 @@ func (p *Polybft) startConsensusProtocol() { stopSequence() p.logger.Info("canceled sequence", "sequence", latestHeader.Number+1) } + case <-staleChecker.sequenceShouldStop: + if isValidator { + stopSequence() + p.logger.Info("canceled sequence via stale checker", "sequence", latestHeader.Number+1) + } case <-sequenceCh: case <-p.closeCh: + p.logger.Debug("stoping sequence", "block number", latestHeader.Number+1) if isValidator { stopSequence() + staleChecker.stopChecking() } return } + if isValidator { + staleChecker.stopChecking() + } + p.logger.Debug("time to run the sequence", "seconds", time.Since(now)) } } diff --git a/consensus/polybft/stake_manager_test.go b/consensus/polybft/stake_manager_test.go index ab0fce6a4e..87bc2e6d4f 100644 --- a/consensus/polybft/stake_manager_test.go +++ b/consensus/polybft/stake_manager_test.go @@ -537,3 +537,7 @@ func (d *dummyStakeTxRelayer) SendTransactionLocal(txn *types.Transaction) (*eth func (d *dummyStakeTxRelayer) Client() *jsonrpc.EthClient { return nil } + +func (d *dummyStakeTxRelayer) GetTxnHashes() []types.Hash { + return nil +} diff --git a/consensus/polybft/stale_sequence_check.go b/consensus/polybft/stale_sequence_check.go new file mode 100644 index 0000000000..5c3aeb01bd --- /dev/null +++ b/consensus/polybft/stale_sequence_check.go @@ -0,0 +1,89 @@ +package polybft + +import ( + "sync" + "time" + + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" +) + +type staleSequenceCheck struct { + logger hclog.Logger + currentSequence uint64 + mtx *sync.Mutex + checkFrequency time.Duration + sequenceShouldStop chan struct{} + stop chan struct{} + stopped chan struct{} + getHeader func() *types.Header +} + +func newStaleSequenceCheck(logger hclog.Logger, + getHeader func() *types.Header, + checkDuration time.Duration, +) *staleSequenceCheck { + return &staleSequenceCheck{ + logger: logger, + currentSequence: 0, + mtx: &sync.Mutex{}, + checkFrequency: checkDuration, + getHeader: getHeader, + } +} + +func (s *staleSequenceCheck) startChecking() { + s.sequenceShouldStop = make(chan struct{}, 1) + s.stop = make(chan struct{}) + s.stopped = make(chan struct{}) + + ticker := time.NewTicker(s.checkFrequency) + + go func() { + defer close(s.stopped) + + for { + select { + case <-s.stop: + close(s.sequenceShouldStop) + ticker.Stop() + + return + case <-ticker.C: + s.checkForStaleness() + } + } + }() +} + +func (s *staleSequenceCheck) stopChecking() { + close(s.stop) + <-s.stopped +} + +func (s *staleSequenceCheck) setSequence(sequence uint64) { + s.mtx.Lock() + defer s.mtx.Unlock() + + s.currentSequence = sequence +} + +func (s *staleSequenceCheck) checkForStaleness() { + s.logger.Info("[staleSequenceCheck] checking for stale sequence") + header := s.getHeader() + s.chainHeightUpdated(header.Number) +} + +func (s *staleSequenceCheck) chainHeightUpdated(height uint64) { + s.mtx.Lock() + defer s.mtx.Unlock() + + if s.currentSequence == 0 { + return + } + + if height >= s.currentSequence { + s.logger.Info("[staleSequenceCheck] stale sequence detected", "height", height, "currentSequence", s.currentSequence) + s.sequenceShouldStop <- struct{}{} + } +} diff --git a/go.mod b/go.mod index b2d308d7de..661a79620d 100644 --- a/go.mod +++ b/go.mod @@ -28,10 +28,12 @@ require ( github.com/libp2p/go-libp2p-kbucket v0.6.3 github.com/libp2p/go-libp2p-pubsub v0.10.0 github.com/multiformats/go-multiaddr v0.12.3 + github.com/olekukonko/tablewriter v0.0.5 github.com/prometheus/client_golang v1.19.0 github.com/quasilyte/go-ruleguard v0.4.2 github.com/quasilyte/go-ruleguard/dsl v0.3.22 github.com/ryanuber/columnize v2.1.2+incompatible + github.com/schollz/progressbar/v3 v3.14.2 github.com/sethvargo/go-retry v0.2.4 github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.9.0 @@ -141,10 +143,12 @@ require ( github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/miekg/dns v1.1.56 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/minio/sha256-simd v1.0.1 // indirect + github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect @@ -184,6 +188,7 @@ require ( github.com/quic-go/webtransport-go v0.6.0 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -212,7 +217,8 @@ require ( golang.org/x/mod v0.16.0 // indirect golang.org/x/net v0.22.0 // indirect golang.org/x/oauth2 v0.17.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/term v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect diff --git a/go.sum b/go.sum index 8d37acc24f..4998825401 100644 --- a/go.sum +++ b/go.sum @@ -351,6 +351,7 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -415,6 +416,9 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= @@ -431,6 +435,8 @@ github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1 github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= @@ -481,6 +487,8 @@ github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= @@ -570,6 +578,9 @@ github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtB github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 h1:Qp27Idfgi6ACvFQat5+VJvlYToylpM/hcyLBI3WaKPA= github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052/go.mod h1:uvX/8buq8uVeiZiFht+0lqSLBHF+uGV8BrTv8W/SIwk= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= @@ -581,6 +592,8 @@ github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/schollz/progressbar/v3 v3.14.2 h1:EducH6uNLIWsr560zSV1KrTeUb/wZGAHqyMFIEa99ks= +github.com/schollz/progressbar/v3 v3.14.2/go.mod h1:aQAZQnhF4JGFtRJiw/eobaXpsqpVQAftEQ+hLGXaRc4= github.com/secure-systems-lab/go-securesystemslib v0.7.0 h1:OwvJ5jQf9LnIAS83waAjPbcMsODrTQUpJ02eNLUoxBg= github.com/secure-systems-lab/go-securesystemslib v0.7.0/go.mod h1:/2gYnlnHVQ6xeGtfIqFy7Do03K4cdCY0A/GlJLDKLHI= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -845,14 +858,16 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/jsonrpc/client.go b/jsonrpc/client.go index 254b93aba9..9e667c6c50 100644 --- a/jsonrpc/client.go +++ b/jsonrpc/client.go @@ -218,3 +218,17 @@ func (e *EthClient) GetLogs(filter *ethgo.LogFilter) ([]*ethgo.Log, error) { return out, nil } + +// TxPoolStatus returns the transaction pool status (pending and queued transactions) +func (e *EthClient) TxPoolStatus() (*StatusResponse, error) { + var out StatusResponse + if err := e.client.Call("txpool_status", &out); err != nil { + return nil, err + } + + return &out, nil +} + +func (e *EthClient) Close() error { + return e.client.Close() +} diff --git a/loadtest/runner/base_load_test_runner.go b/loadtest/runner/base_load_test_runner.go new file mode 100644 index 0000000000..05d8a8b696 --- /dev/null +++ b/loadtest/runner/base_load_test_runner.go @@ -0,0 +1,839 @@ +package runner + +import ( + "context" + "encoding/json" + "fmt" + "math" + "math/big" + "os" + "sort" + "sync" + "time" + + "github.com/0xPolygon/polygon-edge/crypto" + "github.com/0xPolygon/polygon-edge/helper/common" + "github.com/0xPolygon/polygon-edge/jsonrpc" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/olekukonko/tablewriter" + "github.com/schollz/progressbar/v3" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/wallet" + "golang.org/x/sync/errgroup" +) + +const emptyBlocksNum = 10 + +type stats struct { + totalTxs int + blockInfo map[uint64]*BlockInfo + foundErrors []error +} + +type feeData struct { + gasPrice *big.Int + gasTipCap *big.Int + gasFeeCap *big.Int +} + +// BaseLoadTestRunner represents a base load test runner. +type BaseLoadTestRunner struct { + cfg LoadTestConfig + + loadTestAccount *account + vus []*account + + client *jsonrpc.EthClient + + resultsCollectedCh chan *stats + done chan error +} + +// NewBaseLoadTestRunner creates a new instance of BaseLoadTestRunner with the provided LoadTestConfig. +// It initializes the load test runner with the given configuration, including the mnemonic for the wallet, +// and sets up the necessary components such as the Ethereum key, binary path, and JSON-RPC client. +// If any error occurs during the initialization process, it returns nil and the error. +// Otherwise, it returns a pointer to the initialized BaseLoadTestRunner and nil error. +func NewBaseLoadTestRunner(cfg LoadTestConfig) (*BaseLoadTestRunner, error) { + key, err := wallet.NewWalletFromMnemonic(cfg.Mnemonnic) + if err != nil { + return nil, err + } + + raw, err := key.MarshallPrivateKey() + if err != nil { + return nil, err + } + + ecdsaKey, err := crypto.NewECDSAKeyFromRawPrivECDSA(raw) + if err != nil { + return nil, err + } + + client, err := jsonrpc.NewEthClient(cfg.JSONRPCUrl) + if err != nil { + return nil, err + } + + return &BaseLoadTestRunner{ + cfg: cfg, + loadTestAccount: &account{key: ecdsaKey}, + client: client, + resultsCollectedCh: make(chan *stats), + done: make(chan error), + }, nil +} + +// Close closes the BaseLoadTestRunner by closing the underlying client connection. +// It returns an error if there was a problem closing the connection. +func (r *BaseLoadTestRunner) Close() error { + return r.client.Close() +} + +// createVUs creates virtual users (VUs) for the load test. +// It generates ECDSA keys for each VU and stores them in the `vus` slice. +// Returns an error if there was a problem generating the keys. +func (r *BaseLoadTestRunner) createVUs() error { + fmt.Println("=============================================================") + + start := time.Now().UTC() + bar := progressbar.Default(int64(r.cfg.VUs), "Creating virtual users") + + defer func() { + _ = bar.Close() + + fmt.Println("Creating virtual users took", time.Since(start)) + }() + + for i := 0; i < r.cfg.VUs; i++ { + key, err := crypto.GenerateECDSAKey() + if err != nil { + return err + } + + r.vus = append(r.vus, &account{key: key}) + _ = bar.Add(1) + } + + return nil +} + +// fundVUs funds virtual users by transferring a specified amount of Ether to their addresses. +// It uses the provided load test account's private key to sign the transactions. +// The funding process is performed by executing a command-line bridge tool with the necessary arguments. +// The amount to fund is set to 1000 Ether. +// The function returns an error if there was an issue during the funding process. +func (r *BaseLoadTestRunner) fundVUs() error { + fmt.Println("=============================================================") + + start := time.Now().UTC() + bar := progressbar.Default(int64(r.cfg.VUs), "Funding virtual users with native tokens") + + defer func() { + _ = bar.Close() + + fmt.Println("Funding took", time.Since(start)) + }() + + amountToFund := ethgo.Ether(1000) + + txRelayer, err := txrelayer.NewTxRelayer( + txrelayer.WithClient(r.client), + txrelayer.WithoutNonceGet(), + ) + if err != nil { + return err + } + + nonce, err := r.client.GetNonce(r.loadTestAccount.key.Address(), jsonrpc.PendingBlockNumberOrHash) + if err != nil { + return err + } + + g, ctx := errgroup.WithContext(context.Background()) + + for i, vu := range r.vus { + i := i + vu := vu + + g.Go(func() error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + to := vu.key.Address() + tx := types.NewTx(types.NewLegacyTx( + types.WithTo(&to), + types.WithNonce(nonce+uint64(i)), + types.WithFrom(r.loadTestAccount.key.Address()), + types.WithValue(amountToFund), + types.WithGas(21000), + )) + + receipt, err := txRelayer.SendTransaction(tx, r.loadTestAccount.key) + if err != nil { + return err + } + + if receipt == nil || receipt.Status != uint64(types.ReceiptSuccess) { + return fmt.Errorf("failed to mint ERC20 tokens to %s", vu.key.Address()) + } + + _ = bar.Add(1) + + return nil + } + }) + } + + if err := g.Wait(); err != nil { + return err + } + + return nil +} + +// waitForTxPoolToEmpty waits for the transaction pool to become empty. +// It continuously checks the status of the transaction pool and returns +// when there are no pending or queued transactions. +// If the transaction pool does not become empty within the specified timeout, +// it returns an error. +func (r *BaseLoadTestRunner) waitForTxPoolToEmpty() error { + fmt.Println("=============================================================") + fmt.Println("Waiting for tx pool to empty...") + + timer := time.NewTimer(r.cfg.TxPoolTimeout) + defer timer.Stop() + + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + txPoolStatus, err := r.client.TxPoolStatus() + if err != nil { + return err + } + + fmt.Println("Tx pool content. Pending:", txPoolStatus.Pending, "Queued:", txPoolStatus.Queued) + + if txPoolStatus.Pending == 0 && txPoolStatus.Queued == 0 { + return nil + } + + case <-timer.C: + return fmt.Errorf("timeout while waiting for tx pool to empty") + } + } +} + +// waitForReceiptsParallel waits for the receipts of the given transaction hashes in in a separate go routine. +// It continuously checks for the receipts until they are found or the timeout is reached. +// If the receipts are found, it sends the transaction statistics to the resultsCollectedCh channel. +// If the timeout is reached before the receipts are found, it returns. +// if there is a predefined number of empty blocks, it stops the results gathering before the timer. +func (r *BaseLoadTestRunner) waitForReceiptsParallel() { + startBlock, err := r.client.BlockNumber() + if err != nil { + fmt.Println("Error getting start block on gathering block info:", err) + + return + } + + currentBlock := startBlock + blockInfoMap := make(map[uint64]*BlockInfo) + foundErrors := make([]error, 0) + sequentialEmptyBlocks := 0 + totalTxsExecuted := 0 + + timer := time.NewTimer(30 * time.Minute) + ticker := time.NewTicker(2 * time.Second) + + defer func() { + timer.Stop() + ticker.Stop() + fmt.Println("Gathering results in parallel finished.") + r.resultsCollectedCh <- &stats{totalTxs: totalTxsExecuted, blockInfo: blockInfoMap, foundErrors: foundErrors} + }() + + for { + select { + case <-timer.C: + fmt.Println("Timeout while gathering block info") + + return + case <-ticker.C: + if sequentialEmptyBlocks >= emptyBlocksNum { + return + } + + block, err := r.client.GetBlockByNumber(jsonrpc.BlockNumber(currentBlock), true) + if err != nil { + foundErrors = append(foundErrors, err) + + continue + } + + if block == nil { + continue + } + + if (len(block.Transactions) == 1 && block.Transactions[0].Type() == types.StateTxType) || + len(block.Transactions) == 0 { + sequentialEmptyBlocks++ + currentBlock++ + + continue + } + + sequentialEmptyBlocks = 0 + + gasUsed := new(big.Int).SetUint64(block.Header.GasUsed) + gasLimit := new(big.Int).SetUint64(block.Header.GasLimit) + gasUtilization := new(big.Int).Mul(gasUsed, big.NewInt(10000)) + gasUtilization = gasUtilization.Div(gasUtilization, gasLimit).Div(gasUtilization, big.NewInt(100)) + + gu, _ := gasUtilization.Float64() + + blockInfoMap[block.Number()] = &BlockInfo{ + Number: block.Number(), + CreatedAt: block.Header.Timestamp, + NumTxs: len(block.Transactions), + GasUsed: new(big.Int).SetUint64(block.Header.GasUsed), + GasLimit: new(big.Int).SetUint64(block.Header.GasLimit), + GasUtilization: gu, + } + + totalTxsExecuted += len(block.Transactions) + currentBlock++ + } + } +} + +// waitForReceipts waits for the receipts of the given transaction hashes and returns +// a map of block information, transaction statistics, and an error if any. +func (r *BaseLoadTestRunner) waitForReceipts(txHashes []types.Hash) (map[uint64]*BlockInfo, int) { + fmt.Println("=============================================================") + + start := time.Now().UTC() + blockInfoMap := make(map[uint64]*BlockInfo) + txToBlockMap := make(map[types.Hash]uint64) + bar := progressbar.Default(int64(len(txHashes)), "Gathering receipts") + + defer func() { + _ = bar.Close() + + fmt.Println("Waiting for receipts took", time.Since(start)) + }() + + foundErrors := make([]error, 0) + + var lock sync.Mutex + + getTxReceipts := func(txHashes []types.Hash) { + for _, txHash := range txHashes { + lock.Lock() + if _, exists := txToBlockMap[txHash]; exists { + _ = bar.Add(1) + lock.Unlock() + + continue + } + + lock.Unlock() + + receipt, err := r.waitForReceipt(txHash) + if err != nil { + lock.Lock() + foundErrors = append(foundErrors, err) + lock.Unlock() + + continue + } + + _ = bar.Add(1) + + block, err := r.client.GetBlockByNumber(jsonrpc.BlockNumber(receipt.BlockNumber), true) + if err != nil { + lock.Lock() + foundErrors = append(foundErrors, err) + lock.Unlock() + + continue + } + + gasUsed := new(big.Int).SetUint64(block.Header.GasUsed) + gasLimit := new(big.Int).SetUint64(block.Header.GasLimit) + gasUtilization := new(big.Int).Mul(gasUsed, big.NewInt(10000)) + gasUtilization = gasUtilization.Div(gasUtilization, gasLimit).Div(gasUtilization, big.NewInt(100)) + + gu, _ := gasUtilization.Float64() + + lock.Lock() + blockInfoMap[receipt.BlockNumber] = &BlockInfo{ + Number: receipt.BlockNumber, + CreatedAt: block.Header.Timestamp, + NumTxs: len(block.Transactions), + GasUsed: new(big.Int).SetUint64(block.Header.GasUsed), + GasLimit: new(big.Int).SetUint64(block.Header.GasLimit), + GasUtilization: gu, + } + + for _, txn := range block.Transactions { + txToBlockMap[txn.Hash()] = receipt.BlockNumber + } + lock.Unlock() + } + } + + totalTxns := len(txHashes) + + // split the txHashes into batches so we can get them in parallel routines + batchSize := totalTxns / 10 + if batchSize == 0 { + batchSize = 1 + } + + var wg sync.WaitGroup + + for i := 0; i < totalTxns; i += batchSize { + end := i + batchSize + if end > totalTxns { + end = totalTxns + } + + wg.Add(1) + + go func(txHashes []types.Hash) { + defer wg.Done() + + getTxReceipts(txHashes) + }(txHashes[i:end]) + } + + wg.Wait() + + if len(foundErrors) > 0 { + fmt.Println("Errors found while waiting for receipts:") + + for _, err := range foundErrors { + fmt.Println(err) + } + } + + return blockInfoMap, len(txHashes) +} + +// waitForReceipt waits for the transaction receipt of the given transaction hash. +// It continuously checks for the receipt until it is found or the timeout is reached. +// If the receipt is found, it returns the receipt and nil error. +// If the timeout is reached before the receipt is found, it returns nil receipt and an error. +func (r *BaseLoadTestRunner) waitForReceipt(txHash types.Hash) (*ethgo.Receipt, error) { + timer := time.NewTimer(r.cfg.ReceiptsTimeout) + defer timer.Stop() + + tickerTimeout := time.Second + if r.cfg.ReceiptsTimeout <= time.Second { + tickerTimeout = r.cfg.ReceiptsTimeout / 2 + } + + ticker := time.NewTicker(tickerTimeout) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + receipt, err := r.client.GetTransactionReceipt(txHash) + if err != nil { + if err.Error() != "not found" { + return nil, err + } + } + + if receipt != nil { + return receipt, nil + } + case <-timer.C: + return nil, fmt.Errorf("timeout while waiting for transaction %s to be processed", txHash) + } + } +} + +// calculateResultsParallel calculates the results of load test. +// Should be used in a separate go routine. +func (r *BaseLoadTestRunner) calculateResultsParallel() { + stats := <-r.resultsCollectedCh + + if len(stats.foundErrors) > 0 { + fmt.Println("Errors found while gathering results:") + + for _, err := range stats.foundErrors { + fmt.Println(err) + } + } + + r.done <- r.calculateResults(stats.blockInfo, stats.totalTxs) +} + +// calculateResults calculates the results of a load test for a given set of +// block information and transaction statistics. +// It takes a map of block information and an array of transaction statistics as input. +// The function iterates over the transaction statistics and calculates the TPS for each block. +// It also calculates the minimum and maximum TPS values, as well as the total time taken to mine the transactions. +// The calculated TPS values are displayed in a table using the tablewriter package. +// The function returns an error if there is any issue retrieving block information or calculating TPS. +func (r *BaseLoadTestRunner) calculateResults(blockInfos map[uint64]*BlockInfo, totalTxs int) error { + fmt.Println("=============================================================") + fmt.Println("Calculating results...") + + var ( + totalTime float64 + maxTxsPerSecond float64 + minTxsPerSecond = math.MaxFloat64 + blockTimeMap = make(map[uint64]uint64) + uniqueBlocks = map[uint64]struct{}{} + infos = make([]*BlockInfo, 0, len(blockInfos)) + totalGasUsed = big.NewInt(0) + minGasUtilization = math.MaxFloat64 + maxGasUtilization float64 + totalGasUtilization float64 + ) + + for num, stat := range blockInfos { + uniqueBlocks[num] = struct{}{} + + infos = append(infos, stat) + } + + for block := range uniqueBlocks { + currentBlockTxsNum := 0 + parentBlockNum := block - 1 + + if _, exists := blockTimeMap[parentBlockNum]; !exists { + if parentBlockInfo, exists := blockInfos[parentBlockNum]; !exists { + parentBlock, err := r.client.GetBlockByNumber(jsonrpc.BlockNumber(parentBlockNum), false) + if err != nil { + return err + } + + blockTimeMap[parentBlockNum] = parentBlock.Header.Timestamp + } else { + blockTimeMap[parentBlockNum] = parentBlockInfo.CreatedAt + } + } + + parentBlockTimestamp := blockTimeMap[parentBlockNum] + + if _, ok := blockTimeMap[block]; !ok { + if currentBlockInfo, ok := blockInfos[block]; !ok { + currentBlock, err := r.client.GetBlockByNumber(jsonrpc.BlockNumber(parentBlockNum), true) + if err != nil { + return err + } + + blockTimeMap[block] = currentBlock.Header.Timestamp + currentBlockTxsNum = len(currentBlock.Transactions) + } else { + blockTimeMap[block] = currentBlockInfo.CreatedAt + currentBlockTxsNum = currentBlockInfo.NumTxs + } + } + + if currentBlockTxsNum == 0 { + currentBlockTxsNum = blockInfos[block].NumTxs + } + + currentBlockTimestamp := blockTimeMap[block] + blockTime := math.Abs(float64(currentBlockTimestamp - parentBlockTimestamp)) + + currentBlockTxsPerSecond := float64(currentBlockTxsNum) / blockTime + + if currentBlockTxsPerSecond > maxTxsPerSecond { + maxTxsPerSecond = currentBlockTxsPerSecond + } + + if currentBlockTxsPerSecond < minTxsPerSecond { + minTxsPerSecond = currentBlockTxsPerSecond + } + + if blockInfos[block].GasUtilization < minGasUtilization { + minGasUtilization = blockInfos[block].GasUtilization + } + + if blockInfos[block].GasUtilization > maxGasUtilization { + maxGasUtilization = blockInfos[block].GasUtilization + } + + totalTime += blockTime + totalGasUtilization += blockInfos[block].GasUtilization + totalGasUsed.Add(totalGasUsed, blockInfos[block].GasUsed) + } + + for _, info := range blockInfos { + info.BlockTime = math.Abs(float64(info.CreatedAt - blockTimeMap[info.Number-1])) + info.TPS = float64(info.NumTxs) / info.BlockTime + } + + sort.Slice(infos, func(i, j int) bool { + return infos[i].Number < infos[j].Number + }) + + avgTxsPerSecond := math.Ceil(float64(totalTxs) / totalTime) + avgGasPerTx := new(big.Int).Div(totalGasUsed, big.NewInt(int64(totalTxs))) + avgGasUtilization := totalGasUtilization / float64(len(blockInfos)) + + if !r.cfg.ResultsToJSON { + return printResults( + totalTxs, totalTime, totalGasUsed, + maxTxsPerSecond, minTxsPerSecond, avgTxsPerSecond, avgGasPerTx, + minGasUtilization, maxGasUtilization, avgGasUtilization, + infos, + ) + } + + return r.saveResultsToJSONFile( + totalTxs, totalTime, totalGasUsed, + maxTxsPerSecond, minTxsPerSecond, avgTxsPerSecond, avgGasPerTx, + minGasUtilization, maxGasUtilization, avgGasUtilization, + infos, + ) +} + +// saveResultsToJSONFile saves the load test results to a JSON file. +// It takes the total number of transactions (totalTxs), total time taken (totalTime), +// maximum transactions per second (maxTxsPerSecond), minimum transactions per second (minTxsPerSecond), +// average transactions per second (avgTxsPerSecond), and a map of block information (blockInfos). +// It returns an error if there was a problem saving the results to the file. +func (r *BaseLoadTestRunner) saveResultsToJSONFile( + totalTxs int, totalTime float64, totalGasUsed *big.Int, + maxTxsPerSecond, minTxsPerSecond, avgTxsPerSecond float64, avgGasPerTx *big.Int, + minGasUtilization, maxGasUtilization, avgGasUtilization float64, + blockInfos []*BlockInfo) error { + fmt.Println("Saving results to JSON file...") + + type Result struct { + TotalBlocks int `json:"totalBlocks"` + TotalTxs int `json:"totalTxs"` + TotalTime float64 `json:"totalTime"` + TotalGasUsed string `json:"totalGasUsed"` + MinTxsPerSecond float64 `json:"minTxsPerSecond"` + MaxTxsPerSecond float64 `json:"maxTxsPerSecond"` + AvgTxsPerSecond float64 `json:"avgTxsPerSecond"` + AvgGasPerTx string `json:"avgGasPerTx"` + MinGasUtilization float64 `json:"minGasUtilization"` + MaxGasUtilization float64 `json:"maxGasUtilization"` + AvgGasUtilization float64 `json:"avgGasUtilization"` + Blocks []*BlockInfo `json:"blocks"` + } + + result := Result{ + TotalBlocks: len(blockInfos), + TotalTxs: totalTxs, + TotalTime: totalTime, + TotalGasUsed: totalGasUsed.String(), + MinTxsPerSecond: minTxsPerSecond, + MaxTxsPerSecond: maxTxsPerSecond, + AvgTxsPerSecond: avgTxsPerSecond, + Blocks: blockInfos, + AvgGasPerTx: avgGasPerTx.String(), + MinGasUtilization: minGasUtilization, + MaxGasUtilization: maxGasUtilization, + AvgGasUtilization: avgGasUtilization, + } + + jsonData, err := json.Marshal(result) + if err != nil { + return err + } + + fileName := fmt.Sprintf("./%s_%s.json", r.cfg.LoadTestName, r.cfg.LoadTestType) + + err = common.SaveFileSafe(fileName, jsonData, 0600) + if err != nil { + return err + } + + fmt.Println("Results saved to JSON file", fileName) + + return nil +} + +// sendTransactions sends transactions for each virtual user (vu) and returns the transaction hashes. +// It retrieves the chain ID from the client and uses it to send transactions for each user. +// The function runs concurrently for each user using errgroup. +// If the context is canceled, the function returns the context error. +// The transaction hashes are appended to the allTxnHashes slice. +// Finally, the function prints the time taken to send the transactions +// and returns the transaction hashes and nil error. +func (r *BaseLoadTestRunner) sendTransactions( + sendFn func(*account, *big.Int, *progressbar.ProgressBar) ([]types.Hash, []error, error)) ([]types.Hash, error) { + fmt.Println("=============================================================") + + chainID, err := r.client.ChainID() + if err != nil { + return nil, err + } + + start := time.Now().UTC() + totalTxs := r.cfg.VUs * r.cfg.TxsPerUser + foundErrs := make([]error, 0) + bar := progressbar.Default(int64(totalTxs), "Sending transactions") + + defer func() { + _ = bar.Close() + + fmt.Println("Sending transactions took", time.Since(start)) + }() + + allTxnHashes := make([]types.Hash, 0) + + g, ctx := errgroup.WithContext(context.Background()) + + for _, vu := range r.vus { + vu := vu + + g.Go(func() error { + select { + case <-ctx.Done(): + return ctx.Err() + + default: + txnHashes, sendErrors, err := sendFn(vu, chainID, bar) + if err != nil { + return err + } + + foundErrs = append(foundErrs, sendErrors...) + allTxnHashes = append(allTxnHashes, txnHashes...) + + return nil + } + }) + } + + if err := g.Wait(); err != nil { + return nil, err + } + + if len(foundErrs) > 0 { + fmt.Println("Errors found while sending transactions:") + + for _, err := range foundErrs { + fmt.Println(err) + } + } + + return allTxnHashes, nil +} + +// getFeeData retrieves fee data based on the provided JSON-RPC Ethereum client and dynamicTxs flag. +// If dynamicTxs is true, it calculates the gasTipCap and gasFeeCap based on the MaxPriorityFeePerGas, +// FeeHistory, and BaseFee values obtained from the client. If dynamicTxs is false, it calculates the +// gasPrice based on the GasPrice value obtained from the client. +// The function returns a feeData struct containing the calculated fee values. +// If an error occurs during the retrieval or calculation, the function returns nil and the error. +func getFeeData(client *jsonrpc.EthClient, dynamicTxs bool) (*feeData, error) { + feeData := &feeData{} + + if dynamicTxs { + mpfpg, err := client.MaxPriorityFeePerGas() + if err != nil { + return nil, err + } + + gasTipCap := new(big.Int).Mul(mpfpg, big.NewInt(2)) + + feeHistory, err := client.FeeHistory(1, jsonrpc.LatestBlockNumber, nil) + if err != nil { + return nil, err + } + + baseFee := big.NewInt(0) + + if len(feeHistory.BaseFee) != 0 { + baseFee = baseFee.SetUint64(feeHistory.BaseFee[len(feeHistory.BaseFee)-1]) + } + + gasFeeCap := new(big.Int).Add(baseFee, mpfpg) + gasFeeCap.Mul(gasFeeCap, big.NewInt(2)) + + feeData.gasTipCap = gasTipCap + feeData.gasFeeCap = gasFeeCap + } else { + gp, err := client.GasPrice() + if err != nil { + return nil, err + } + + gasPrice := new(big.Int).SetUint64(gp + (gp * 50 / 100)) + + feeData.gasPrice = gasPrice + } + + return feeData, nil +} + +// printResults prints the results of the load test to stdout in a form of a table +func printResults(totalTxs int, totalTime float64, totalGasUsed *big.Int, + maxTxsPerSecond, minTxsPerSecond, avgTxsPerSecond float64, avgGasPerTx *big.Int, + minGasUtilization, maxGasUtilization, avgGasUtilization float64, + blockInfos []*BlockInfo) error { + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{ + "Block Number", + "Block Time (s)", + "Num Txs", + "Gas Used", + "Gas Limit", + "Gas Utilization", + "TPS", + }) + + for _, blockInfo := range blockInfos { + table.Append([]string{ + fmt.Sprintf("%d", blockInfo.Number), + fmt.Sprintf("%.2f", blockInfo.BlockTime), + fmt.Sprintf("%d", blockInfo.NumTxs), + fmt.Sprintf("%d", blockInfo.GasUsed.Uint64()), + fmt.Sprintf("%d", blockInfo.GasLimit.Uint64()), + fmt.Sprintf("%.2f", blockInfo.GasUtilization), + fmt.Sprintf("%.2f", blockInfo.TPS), + }) + } + + table.Render() + + table = tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{ + "Total Blocks", + "Total Txs", + "Total Time To Mine (s)", + "Total Gas Used", + "Average Gas Per Tx", + "Min TPS", + "Max TPS", + "Average TPS", + "Min Gas Utilization", + "Max Gas Utilization", + "Average Gas Utilization", + }) + table.Append([]string{ + fmt.Sprintf("%d", len(blockInfos)), + fmt.Sprintf("%d", totalTxs), + fmt.Sprintf("%.2f", totalTime), + totalGasUsed.String(), + avgGasPerTx.String(), + fmt.Sprintf("%.2f", minTxsPerSecond), + fmt.Sprintf("%.2f", maxTxsPerSecond), + fmt.Sprintf("%.2f", avgTxsPerSecond), + fmt.Sprintf("%.2f", minGasUtilization), + fmt.Sprintf("%.2f", maxGasUtilization), + fmt.Sprintf("%.2f", avgGasUtilization), + }) + + table.Render() + + return nil +} diff --git a/loadtest/runner/eoa_runner.go b/loadtest/runner/eoa_runner.go new file mode 100644 index 0000000000..2ae0bc1cc7 --- /dev/null +++ b/loadtest/runner/eoa_runner.go @@ -0,0 +1,144 @@ +package runner + +import ( + "fmt" + "math/big" + + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/schollz/progressbar/v3" + "github.com/umbracle/ethgo" +) + +// EOARunner represents a runner for executing load tests specific to EOAs (Externally Owned Accounts). +type EOARunner struct { + *BaseLoadTestRunner +} + +// NewEOARunner creates a new EOARunner instance with the given LoadTestConfig. +// It returns a pointer to the created EOARunner and an error, if any. +func NewEOARunner(cfg LoadTestConfig) (*EOARunner, error) { + runner, err := NewBaseLoadTestRunner(cfg) + if err != nil { + return nil, err + } + + return &EOARunner{runner}, nil +} + +// Run executes the EOA load test. +// It performs the following steps: +// 1. Creates virtual users (VUs). +// 2. Funds the VUs with native tokens. +// 3. Sends EOA transactions using the VUs. +// 4. Waits for the transaction pool to empty. +// 5. Waits for transaction receipts. +// 6. Calculates the transactions per second (TPS) based on block information and transaction statistics. +// Returns an error if any of the steps fail. +func (e *EOARunner) Run() error { + fmt.Println("Running EOA load test", e.cfg.LoadTestName) + + if err := e.createVUs(); err != nil { + return err + } + + if err := e.fundVUs(); err != nil { + return err + } + + if !e.cfg.WaitForTxPoolToEmpty { + go e.waitForReceiptsParallel() + go e.calculateResultsParallel() + + _, err := e.sendTransactions() + if err != nil { + return err + } + + return <-e.done + } + + txHashes, err := e.sendTransactions() + if err != nil { + return err + } + + if err := e.waitForTxPoolToEmpty(); err != nil { + return err + } + + return e.calculateResults(e.waitForReceipts(txHashes)) +} + +// sendTransactions sends transactions for the load test. +func (e *EOARunner) sendTransactions() ([]types.Hash, error) { + return e.BaseLoadTestRunner.sendTransactions(e.sendTransactionsForUser) +} + +// sendTransactionsForUser sends multiple transactions for a user account on a specific chain. +// It uses the provided client and chain ID to send transactions using either dynamic or legacy fee models. +// For each transaction, it increments the account's nonce and returns the transaction hashes. +// If an error occurs during the transaction sending process, it returns the error. +func (e *EOARunner) sendTransactionsForUser(account *account, chainID *big.Int, + bar *progressbar.ProgressBar) ([]types.Hash, []error, error) { + txRelayer, err := txrelayer.NewTxRelayer( + txrelayer.WithClient(e.client), + txrelayer.WithChainID(chainID), + txrelayer.WithCollectTxnHashes(), + txrelayer.WithNoWaiting(), + txrelayer.WithoutNonceGet(), + ) + if err != nil { + return nil, nil, err + } + + feeData, err := getFeeData(e.client, e.cfg.DynamicTxs) + if err != nil { + return nil, nil, err + } + + sendErrs := make([]error, 0) + checkFeeDataNum := e.cfg.TxsPerUser / 5 + + for i := 0; i < e.cfg.TxsPerUser; i++ { + var err error + + if i%checkFeeDataNum == 0 { + feeData, err = getFeeData(e.client, e.cfg.DynamicTxs) + if err != nil { + return nil, nil, err + } + } + + if e.cfg.DynamicTxs { + _, err = txRelayer.SendTransaction(types.NewTx(types.NewDynamicFeeTx( + types.WithNonce(account.nonce), + types.WithTo(&receiverAddr), + types.WithValue(ethgo.Gwei(1)), + types.WithGas(21000), + types.WithFrom(account.key.Address()), + types.WithGasFeeCap(feeData.gasFeeCap), + types.WithGasTipCap(feeData.gasTipCap), + types.WithChainID(chainID), + )), account.key) + } else { + _, err = txRelayer.SendTransaction(types.NewTx(types.NewLegacyTx( + types.WithNonce(account.nonce), + types.WithTo(&receiverAddr), + types.WithValue(ethgo.Gwei(1)), + types.WithGas(21000), + types.WithGasPrice(feeData.gasPrice), + types.WithFrom(account.key.Address()), + )), account.key) + } + + if err != nil { + sendErrs = append(sendErrs, err) + } + + account.nonce++ + _ = bar.Add(1) + } + + return txRelayer.GetTxnHashes(), sendErrs, nil +} diff --git a/loadtest/runner/erc20_runner.go b/loadtest/runner/erc20_runner.go new file mode 100644 index 0000000000..e178d6ec5e --- /dev/null +++ b/loadtest/runner/erc20_runner.go @@ -0,0 +1,297 @@ +package runner + +import ( + "context" + "fmt" + "math/big" + "time" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/jsonrpc" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/schollz/progressbar/v3" + "golang.org/x/sync/errgroup" +) + +// ERC20Runner represents a load test runner for ERC20 tokens. +type ERC20Runner struct { + *BaseLoadTestRunner + + erc20Token types.Address + erc20TokenArtifact *contracts.Artifact +} + +// NewERC20Runner creates a new ERC20Runner instance with the given LoadTestConfig. +// It returns a pointer to the created ERC20Runner and an error, if any. +func NewERC20Runner(cfg LoadTestConfig) (*ERC20Runner, error) { + runner, err := NewBaseLoadTestRunner(cfg) + if err != nil { + return nil, err + } + + return &ERC20Runner{BaseLoadTestRunner: runner}, nil +} + +// Run executes the ERC20 load test. +// It performs the following steps: +// 1. Creates virtual users (VUs). +// 2. Funds the VUs with native tokens. +// 3. Deploys the ERC20 token contract. +// 4. Mints ERC20 tokens to the VUs. +// 5. Sends transactions using the VUs. +// 6. Waits for the transaction pool to empty. +// 7. Waits for transaction receipts. +// 8. Calculates the transactions per second (TPS) based on block information and transaction statistics. +// Returns an error if any of the steps fail. +func (e *ERC20Runner) Run() error { + fmt.Println("Running ERC20 load test", e.cfg.LoadTestName) + + if err := e.createVUs(); err != nil { + return err + } + + if err := e.fundVUs(); err != nil { + return err + } + + if err := e.deployERC20Token(); err != nil { + return err + } + + if err := e.mintERC20TokenToVUs(); err != nil { + return err + } + + if !e.cfg.WaitForTxPoolToEmpty { + go e.waitForReceiptsParallel() + go e.calculateResultsParallel() + + _, err := e.sendTransactions() + if err != nil { + return err + } + + return <-e.done + } + + txHashes, err := e.sendTransactions() + if err != nil { + return err + } + + if err := e.waitForTxPoolToEmpty(); err != nil { + return err + } + + return e.calculateResults(e.waitForReceipts(txHashes)) +} + +// deployERC20Token deploys an ERC20 token contract. +// It loads the contract artifact from the specified file path, +// encodes the constructor inputs, creates a new transaction, +// sends the transaction using a transaction relayer, +// and retrieves the deployment receipt. +// If the deployment is successful, it sets the ERC20 token address +// and artifact in the ERC20Runner instance. +// Returns an error if any step of the deployment process fails. +func (e *ERC20Runner) deployERC20Token() error { + fmt.Println("=============================================================") + fmt.Println("Deploying ERC20 token contract") + + start := time.Now().UTC() + artifact := contractsapi.ZexCoinERC20 + + input, err := artifact.Abi.Constructor.Inputs.Encode(map[string]interface{}{ + "coinName": "ZexCoin", + "coinSymbol": "ZEX", + "total": 500000000000, + }) + + if err != nil { + return err + } + + txn := types.NewTx(types.NewLegacyTx( + types.WithTo(nil), + types.WithInput(append(artifact.Bytecode, input...)), + types.WithFrom(e.loadTestAccount.key.Address()), + )) + + txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithClient(e.client)) + if err != nil { + return err + } + + receipt, err := txRelayer.SendTransaction(txn, e.loadTestAccount.key) + if err != nil { + return err + } + + if receipt == nil || receipt.Status == uint64(types.ReceiptFailed) { + return fmt.Errorf("failed to deploy ERC20 token") + } + + e.erc20Token = types.Address(receipt.ContractAddress) + e.erc20TokenArtifact = artifact + + fmt.Printf("Deploying ERC20 token took %s\n", time.Since(start)) + + return nil +} + +// mintERC20TokenToVUs mints ERC20 tokens to the specified virtual users (VUs). +// It sends a transfer transaction to each VU's address, minting the specified number of tokens. +// The transaction is sent using a transaction relayer, and the result is checked for success. +// If any error occurs during the minting process, an error is returned. +func (e *ERC20Runner) mintERC20TokenToVUs() error { + fmt.Println("=============================================================") + + start := time.Now().UTC() + bar := progressbar.Default(int64(e.cfg.VUs), "Minting ERC20 tokens to VUs") + + defer func() { + _ = bar.Close() + + fmt.Printf("Minting ERC20 tokens took %s\n", time.Since(start)) + }() + + txRelayer, err := txrelayer.NewTxRelayer( + txrelayer.WithClient(e.client), + txrelayer.WithoutNonceGet(), + ) + if err != nil { + return err + } + + nonce, err := e.client.GetNonce(e.loadTestAccount.key.Address(), jsonrpc.PendingBlockNumberOrHash) + if err != nil { + return err + } + + g, ctx := errgroup.WithContext(context.Background()) + + for i, vu := range e.vus { + i := i + vu := vu + + g.Go(func() error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + input, err := e.erc20TokenArtifact.Abi.Methods["transfer"].Encode(map[string]interface{}{ + "receiver": vu.key.Address(), + "numTokens": big.NewInt(int64(e.cfg.TxsPerUser)), + }) + if err != nil { + return err + } + + tx := types.NewTx(types.NewLegacyTx( + types.WithTo(&e.erc20Token), + types.WithInput(input), + types.WithNonce(nonce+uint64(i)), + types.WithFrom(e.loadTestAccount.key.Address()), + )) + + receipt, err := txRelayer.SendTransaction(tx, e.loadTestAccount.key) + if err != nil { + return err + } + + if receipt == nil || receipt.Status != uint64(types.ReceiptSuccess) { + return fmt.Errorf("failed to mint ERC20 tokens to %s", vu.key.Address()) + } + + _ = bar.Add(1) + + return nil + } + }) + } + + if err := g.Wait(); err != nil { + return err + } + + return nil +} + +// sendTransactions sends transactions for the load test. +func (e *ERC20Runner) sendTransactions() ([]types.Hash, error) { + return e.BaseLoadTestRunner.sendTransactions(e.sendTransactionsForUser) +} + +// sendTransactionsForUser sends ERC20 token transactions for a given user account. +// It takes an account pointer and a chainID as input parameters. +// It returns a slice of transaction hashes and an error if any. +func (e *ERC20Runner) sendTransactionsForUser(account *account, chainID *big.Int, + bar *progressbar.ProgressBar) ([]types.Hash, []error, error) { + txRelayer, err := txrelayer.NewTxRelayer( + txrelayer.WithClient(e.client), + txrelayer.WithChainID(chainID), + txrelayer.WithCollectTxnHashes(), + txrelayer.WithNoWaiting(), + txrelayer.WithEstimateGasFallback(), + txrelayer.WithoutNonceGet(), + ) + if err != nil { + return nil, nil, err + } + + feeData, err := getFeeData(e.client, e.cfg.DynamicTxs) + if err != nil { + return nil, nil, err + } + + sendErrs := make([]error, 0) + checkFeeDataNum := e.cfg.TxsPerUser / 5 + + for i := 0; i < e.cfg.TxsPerUser; i++ { + input, err := e.erc20TokenArtifact.Abi.Methods["transfer"].Encode(map[string]interface{}{ + "receiver": receiverAddr, + "numTokens": big.NewInt(1), + }) + if err != nil { + return nil, nil, err + } + + if i%checkFeeDataNum == 0 { + feeData, err = getFeeData(e.client, e.cfg.DynamicTxs) + if err != nil { + return nil, nil, err + } + } + + if e.cfg.DynamicTxs { + _, err = txRelayer.SendTransaction(types.NewTx(types.NewDynamicFeeTx( + types.WithNonce(account.nonce), + types.WithTo(&e.erc20Token), + types.WithFrom(account.key.Address()), + types.WithGasFeeCap(feeData.gasFeeCap), + types.WithGasTipCap(feeData.gasTipCap), + types.WithChainID(chainID), + types.WithInput(input), + )), account.key) + } else { + _, err = txRelayer.SendTransaction(types.NewTx(types.NewLegacyTx( + types.WithNonce(account.nonce), + types.WithTo(&e.erc20Token), + types.WithGasPrice(feeData.gasPrice), + types.WithFrom(account.key.Address()), + types.WithInput(input), + )), account.key) + } + + if err != nil { + sendErrs = append(sendErrs, err) + } + + account.nonce++ + _ = bar.Add(1) + } + + return txRelayer.GetTxnHashes(), sendErrs, nil +} diff --git a/loadtest/runner/erc721_runner.go b/loadtest/runner/erc721_runner.go new file mode 100644 index 0000000000..d36eb0134a --- /dev/null +++ b/loadtest/runner/erc721_runner.go @@ -0,0 +1,209 @@ +package runner + +import ( + "fmt" + "math/big" + "time" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/schollz/progressbar/v3" +) + +const nftURL = "https://really-valuable-nft-page.io" + +// ERC721Runner represents a load test runner for ERC721 tokens. +type ERC721Runner struct { + *BaseLoadTestRunner + + erc721Token types.Address + erc721TokenArtifact *contracts.Artifact +} + +// NewERC721Runner creates a new ERC721Runner instance with the given LoadTestConfig. +// It returns a pointer to the created ERC721Runner and an error, if any. +func NewERC721Runner(cfg LoadTestConfig) (*ERC721Runner, error) { + runner, err := NewBaseLoadTestRunner(cfg) + if err != nil { + return nil, err + } + + return &ERC721Runner{BaseLoadTestRunner: runner}, nil +} + +// Run executes the ERC20 load test. +// It performs the following steps: +// 1. Creates virtual users (VUs). +// 2. Funds the VUs with native tokens. +// 3. Deploys the ERC721 token contract. +// 4. Sends NFT transactions using the VUs. +// 5. Waits for the transaction pool to empty. +// 6. Waits for transaction receipts. +// 7. Calculates the transactions per second (TPS) based on block information and transaction statistics. +// Returns an error if any of the steps fail. +func (e *ERC721Runner) Run() error { + fmt.Println("Running ERC721 load test", e.cfg.LoadTestName) + + if err := e.createVUs(); err != nil { + return err + } + + if err := e.fundVUs(); err != nil { + return err + } + + if err := e.deployERC21Token(); err != nil { + return err + } + + if !e.cfg.WaitForTxPoolToEmpty { + go e.waitForReceiptsParallel() + go e.calculateResultsParallel() + + _, err := e.sendTransactions() + if err != nil { + return err + } + + return <-e.done + } + + txHashes, err := e.sendTransactions() + if err != nil { + return err + } + + if err := e.waitForTxPoolToEmpty(); err != nil { + return err + } + + return e.calculateResults(e.waitForReceipts(txHashes)) +} + +// deployERC21Token deploys an ERC721 token contract. +// It loads the contract artifact from the specified file path, +// encodes the constructor inputs, creates a new transaction, +// sends the transaction using a transaction relayer, +// and retrieves the deployment receipt. +// If the deployment is successful, it sets the ERC721 token address +// and artifact in the ERC721Runner instance. +// Returns an error if any step of the deployment process fails. +func (e *ERC721Runner) deployERC21Token() error { + fmt.Println("=============================================================") + fmt.Println("Deploying ERC721 token contract") + + start := time.Now().UTC() + artifact := contractsapi.ZexNFT + + input, err := artifact.Abi.Constructor.Inputs.Encode(map[string]interface{}{ + "tokenName": "ZexCoin", + "tokenSymbol": "ZEX", + }) + + if err != nil { + return err + } + + txn := types.NewTx(types.NewLegacyTx( + types.WithTo(nil), + types.WithInput(append(artifact.Bytecode, input...)), + types.WithFrom(e.loadTestAccount.key.Address()), + )) + + txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithClient(e.client)) + if err != nil { + return err + } + + receipt, err := txRelayer.SendTransaction(txn, e.loadTestAccount.key) + if err != nil { + return err + } + + if receipt == nil || receipt.Status == uint64(types.ReceiptFailed) { + return fmt.Errorf("failed to deploy ERC721 token") + } + + e.erc721Token = types.Address(receipt.ContractAddress) + e.erc721TokenArtifact = artifact + + fmt.Printf("Deploying ERC721 token took %s\n", time.Since(start)) + + return nil +} + +// sendTransactions sends transactions for the load test. +func (e *ERC721Runner) sendTransactions() ([]types.Hash, error) { + return e.BaseLoadTestRunner.sendTransactions(e.sendTransactionsForUser) +} + +// sendTransactionsForUser sends ERC20 token transactions for a given user account. +// It takes an account pointer and a chainID as input parameters. +// It returns a slice of transaction hashes and an error if any. +func (e *ERC721Runner) sendTransactionsForUser(account *account, chainID *big.Int, + bar *progressbar.ProgressBar) ([]types.Hash, []error, error) { + txRelayer, err := txrelayer.NewTxRelayer( + txrelayer.WithClient(e.client), + txrelayer.WithChainID(chainID), + txrelayer.WithCollectTxnHashes(), + txrelayer.WithNoWaiting(), + txrelayer.WithEstimateGasFallback(), + txrelayer.WithoutNonceGet(), + ) + if err != nil { + return nil, nil, err + } + + feeData, err := getFeeData(e.client, e.cfg.DynamicTxs) + if err != nil { + return nil, nil, err + } + + sendErrs := make([]error, 0) + checkFeeDataNum := e.cfg.TxsPerUser / 5 + + for i := 0; i < e.cfg.TxsPerUser; i++ { + input, err := e.erc721TokenArtifact.Abi.Methods["createNFT"].Encode(map[string]interface{}{"tokenURI": nftURL}) + if err != nil { + return nil, nil, err + } + + if i%checkFeeDataNum == 0 { + feeData, err = getFeeData(e.client, e.cfg.DynamicTxs) + if err != nil { + return nil, nil, err + } + } + + if e.cfg.DynamicTxs { + _, err = txRelayer.SendTransaction(types.NewTx(types.NewDynamicFeeTx( + types.WithNonce(account.nonce), + types.WithTo(&e.erc721Token), + types.WithFrom(account.key.Address()), + types.WithGasFeeCap(feeData.gasFeeCap), + types.WithGasTipCap(feeData.gasTipCap), + types.WithChainID(chainID), + types.WithInput(input), + )), account.key) + } else { + _, err = txRelayer.SendTransaction(types.NewTx(types.NewLegacyTx( + types.WithNonce(account.nonce), + types.WithTo(&e.erc721Token), + types.WithGasPrice(feeData.gasPrice), + types.WithFrom(account.key.Address()), + types.WithInput(input), + )), account.key) + } + + if err != nil { + sendErrs = append(sendErrs, err) + } + + account.nonce++ + _ = bar.Add(1) + } + + return txRelayer.GetTxnHashes(), sendErrs, nil +} diff --git a/loadtest/runner/load_test_runner.go b/loadtest/runner/load_test_runner.go new file mode 100644 index 0000000000..5acecd7c3b --- /dev/null +++ b/loadtest/runner/load_test_runner.go @@ -0,0 +1,98 @@ +package runner + +import ( + "fmt" + "math/big" + "strings" + "time" + + "github.com/0xPolygon/polygon-edge/crypto" + "github.com/0xPolygon/polygon-edge/types" +) + +const ( + EOATestType = "eoa" + ERC20TestType = "erc20" + ERC721TestType = "erc721" +) + +var receiverAddr = types.StringToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") + +func IsLoadTestSupported(loadTestType string) bool { + ltp := strings.ToLower(loadTestType) + + return ltp == EOATestType || ltp == ERC20TestType || ltp == ERC721TestType +} + +type account struct { + nonce uint64 + key *crypto.ECDSAKey +} + +type BlockInfo struct { + Number uint64 + CreatedAt uint64 + NumTxs int + + GasUsed *big.Int + GasLimit *big.Int + GasUtilization float64 + + TPS float64 + BlockTime float64 +} + +// LoadTestConfig represents the configuration for a load test. +type LoadTestConfig struct { + Mnemonnic string // Mnemonnic is the mnemonic phrase used for account generation, and VUs funding. + + LoadTestType string // LoadTestType is the type of load test. + LoadTestName string // LoadTestName is the name of the load test. + + JSONRPCUrl string // JSONRPCUrl is the URL of the JSON-RPC server. + ReceiptsTimeout time.Duration // ReceiptsTimeout is the timeout for waiting for transaction receipts. + TxPoolTimeout time.Duration // TxPoolTimeout is the timeout for waiting for tx pool to empty. + + VUs int // VUs is the number of virtual users. + TxsPerUser int // TxsPerUser is the number of transactions per user. + DynamicTxs bool // DynamicTxs indicates whether the load test should generate dynamic transactions. + + ResultsToJSON bool // ResultsToJSON indicates whether the results should be written in JSON format. + WaitForTxPoolToEmpty bool // WaitForTxPoolToEmpty indicates whether the load test + // should wait for the tx pool to empty before gathering results +} + +// LoadTestRunner represents a runner for load tests. +type LoadTestRunner struct{} + +// Run executes the load test based on the provided LoadTestConfig. +// It determines the load test type from the configuration and creates +// the corresponding runner. Then, it runs the load test using the +// created runner and returns any error encountered during the process. +func (r *LoadTestRunner) Run(cfg LoadTestConfig) error { + switch strings.ToLower(cfg.LoadTestType) { + case EOATestType: + eoaRunner, err := NewEOARunner(cfg) + if err != nil { + return err + } + + return eoaRunner.Run() + case ERC20TestType: + erc20Runner, err := NewERC20Runner(cfg) + if err != nil { + return err + } + + return erc20Runner.Run() + case ERC721TestType: + erc721Runner, err := NewERC721Runner(cfg) + if err != nil { + return err + } + + return erc721Runner.Run() + default: + return fmt.Errorf("unknown load test type %s", cfg.LoadTestType) + } +} diff --git a/loadtest/runner/load_test_runner_test.go b/loadtest/runner/load_test_runner_test.go new file mode 100644 index 0000000000..d0fde0e6ee --- /dev/null +++ b/loadtest/runner/load_test_runner_test.go @@ -0,0 +1,40 @@ +package runner + +import ( + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/crypto" + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo/wallet" +) + +func TestMnemonic(t *testing.T) { + realAddress := types.StringToAddress("0x85da99c8a7c2c95964c8efd687e95e632fc533d6") + + key, _ := wallet.NewWalletFromMnemonic("code code code code code code code code code code code quality") + raw, _ := key.MarshallPrivateKey() + + ecdsaKey, _ := crypto.NewECDSAKeyFromRawPrivECDSA(raw) + require.Equal(t, realAddress, ecdsaKey.Address()) +} + +func TestLoadRunner(t *testing.T) { + t.Skip("this is only added for the sake of the example and running it in local") + + cfg := LoadTestConfig{ + Mnemonnic: "code code code code code code code code code code code quality", + LoadTestType: "erc20", + LoadTestName: "test", + JSONRPCUrl: "http://localhost:10002", + VUs: 10, + TxsPerUser: 100, + ReceiptsTimeout: 30 * time.Second, + TxPoolTimeout: 30 * time.Minute, + } + + runner := &LoadTestRunner{} + + require.NoError(t, runner.Run(cfg)) +} diff --git a/scripts/cluster b/scripts/cluster index 76ba81f904..86bf293837 100755 --- a/scripts/cluster +++ b/scripts/cluster @@ -59,7 +59,7 @@ function initPolybftConsensus() { function createGenesis() { ./blade genesis $genesis_params \ - --block-gas-limit 10000000 \ + --block-gas-limit 50000000 \ --premine 0x85da99c8a7c2c95964c8efd687e95e632fc533d6 \ --premine 0x0000000000000000000000000000000000000000 \ --epoch-size 10 \ @@ -67,7 +67,8 @@ function createGenesis() { --native-token-config "Blade:BLADE:18:true" \ --blade-admin $address1 \ --proxy-contracts-admin 0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed \ - --base-fee-config 1000000000 + --base-fee-config 1000000000 \ + --burn-contract "0:0x0000000000000000000000000000000000000000" } function initBridge() { @@ -127,11 +128,13 @@ function startNodes() { ./blade server --data-dir "$data_dir" --chain genesis.json \ --grpc-address ":$grpc_port" --libp2p ":$libp2p_port" --jsonrpc ":$jsonrpc_port" \ --num-block-confirmations 2 $relayer_arg \ + --json-rpc-batch-request-limit 0 \ --log-level DEBUG 2>&1 | tee $log_file & else ./blade server --data-dir "$data_dir" --chain genesis.json \ --grpc-address ":$grpc_port" --libp2p ":$libp2p_port" --jsonrpc ":$jsonrpc_port" \ --num-block-confirmations 2 $relayer_arg \ + --json-rpc-batch-request-limit 0 \ --log-level DEBUG & fi diff --git a/txpool/txpool.go b/txpool/txpool.go index 6eb1bebb26..a05ad79596 100644 --- a/txpool/txpool.go +++ b/txpool/txpool.go @@ -28,10 +28,10 @@ const ( topicNameV1 = "txpool/0.1" // maximum allowed number of times an account was excluded from block building - maxAccountDemotions uint64 = 10 + maxAccountDemotions uint64 = 1000 // maximum allowed number of consecutive blocks that don't have the account's transaction - maxAccountSkips = uint64(10) + maxAccountSkips = uint64(1000) pruningCooldown = 5000 * time.Millisecond @@ -621,9 +621,14 @@ func (p *TxPool) validateTx(tx *types.Transaction) error { } // check if the given tx is not underpriced (same as Legacy approach) - if tx.GetGasPrice(p.GetBaseFee()).Cmp(big.NewInt(0).SetUint64(p.priceLimit)) < 0 { + if tx.GetGasPrice(baseFee).Cmp(big.NewInt(0).SetUint64(p.priceLimit)) < 0 { metrics.IncrCounter([]string{txPoolMetrics, "underpriced_tx"}, 1) + p.logger.Debug("access list tx is undepriced", + "gasPrice", tx.GetGasPrice(baseFee).String(), + "baseFee", baseFee, + "priceLimit", p.priceLimit) + return ErrUnderpriced } } else if tx.Type() == types.DynamicFeeTxType { @@ -638,6 +643,10 @@ func (p *TxPool) validateTx(tx *types.Transaction) error { if tx.GasFeeCap() == nil || tx.GasTipCap() == nil { metrics.IncrCounter([]string{txPoolMetrics, "underpriced_tx"}, 1) + p.logger.Debug("dynamic tx is undepriced because no fee cap or tip cap is set", + "baseFee", baseFee, + "priceLimit", p.priceLimit) + return ErrUnderpriced } @@ -663,6 +672,11 @@ func (p *TxPool) validateTx(tx *types.Transaction) error { if tx.GasFeeCap().Cmp(new(big.Int).SetUint64(baseFee)) < 0 { metrics.IncrCounter([]string{txPoolMetrics, "underpriced_tx"}, 1) + p.logger.Debug("dynamic tx is undepriced", + "gasFeeCap", tx.GasFeeCap().String(), + "baseFee", baseFee, + "priceLimit", p.priceLimit) + return ErrUnderpriced } } else { @@ -670,6 +684,11 @@ func (p *TxPool) validateTx(tx *types.Transaction) error { if forks.London && tx.GasPrice().Cmp(new(big.Int).SetUint64(baseFee)) < 0 { metrics.IncrCounter([]string{txPoolMetrics, "underpriced_tx"}, 1) + p.logger.Debug("legacy tx is undepriced on london fork", + "gasPrice", tx.GasPrice().String(), + "baseFee", baseFee, + "priceLimit", p.priceLimit) + return ErrUnderpriced } } @@ -678,6 +697,11 @@ func (p *TxPool) validateTx(tx *types.Transaction) error { // Make sure that the transaction is not underpriced metrics.IncrCounter([]string{txPoolMetrics, "underpriced_tx"}, 1) + p.logger.Debug("tx is undepriced in regards to price limit", + "gasPrice", tx.GetGasPrice(baseFee).String(), + "baseFee", baseFee, + "priceLimit", p.priceLimit) + return ErrUnderpriced } @@ -770,7 +794,7 @@ func (p *TxPool) pruneAccountsWithNonceHoles() { // (only once) and an enqueueRequest is signaled. func (p *TxPool) addTx(origin txOrigin, tx *types.Transaction) error { if p.logger.IsDebug() { - p.logger.Debug("add tx", "origin", origin.String(), "hash", tx.Hash().String()) + p.logger.Debug("add tx", "origin", origin.String(), "hash", tx.Hash().String(), "type", tx.Type()) } // validate incoming tx diff --git a/txpool/txpool_test.go b/txpool/txpool_test.go index 22c01dbad6..57e50bdded 100644 --- a/txpool/txpool_test.go +++ b/txpool/txpool_test.go @@ -2030,8 +2030,8 @@ func Test_updateAccountSkipsCounts(t *testing.T) { assert.Equal(t, slotsRequired(tx), pool.gauge.read()) checkTxExistence(t, pool, tx.Hash(), true) - // set 9 to skips in order to drop transaction next - accountMap.skips = 9 + // set 999 to skips in order to drop transaction next + accountMap.skips = maxAccountSkips - 1 pool.updateAccountSkipsCounts(map[types.Address]uint64{ // empty @@ -2065,8 +2065,8 @@ func Test_updateAccountSkipsCounts(t *testing.T) { assert.Equal(t, slotsRequired(tx), pool.gauge.read()) checkTxExistence(t, pool, tx.Hash(), true) - // set 9 to skips in order to drop transaction next - accountMap.skips = 9 + // set 999 to skips in order to drop transaction next + accountMap.skips = maxAccountSkips - 1 pool.updateAccountSkipsCounts(map[types.Address]uint64{ // empty @@ -2100,7 +2100,7 @@ func Test_updateAccountSkipsCounts(t *testing.T) { assert.Equal(t, slotsRequired(tx), pool.gauge.read()) checkTxExistence(t, pool, tx.Hash(), true) - // set 9 to skips in order to drop transaction next + // set 5 to skips in order to drop transaction next accountMap.skips = 5 pool.updateAccountSkipsCounts(map[types.Address]uint64{ diff --git a/txrelayer/txrelayer.go b/txrelayer/txrelayer.go index 188279b42a..042c5966fb 100644 --- a/txrelayer/txrelayer.go +++ b/txrelayer/txrelayer.go @@ -46,6 +46,8 @@ type TxRelayer interface { SendTransactionLocal(txn *types.Transaction) (*ethgo.Receipt, error) // Client returns jsonrpc client Client() *jsonrpc.EthClient + // GetTxnHashes returns hashes of sent transactions + GetTxnHashes() []types.Hash } var _ TxRelayer = (*TxRelayerImpl)(nil) @@ -57,6 +59,11 @@ type TxRelayerImpl struct { receiptsTimeout time.Duration noWaitReceipt bool estimateGasFallback bool + nonceGet bool + collectTxnHashes bool + chainID *big.Int + + txnHashes []types.Hash lock sync.Mutex @@ -68,6 +75,7 @@ func NewTxRelayer(opts ...TxRelayerOption) (TxRelayer, error) { ipAddress: DefaultRPCAddress, receiptsPollFreq: DefaultPollFreq, receiptsTimeout: DefaultTimeoutTransactions, + nonceGet: true, } for _, opt := range opts { opt(t) @@ -96,6 +104,11 @@ func NewTxRelayer(opts ...TxRelayerOption) (TxRelayer, error) { return t, nil } +// GetTxnHashes returns hashes of sent transactions +func (t *TxRelayerImpl) GetTxnHashes() []types.Hash { + return t.txnHashes +} + // Call executes a message call immediately without creating a transaction on the blockchain func (t *TxRelayerImpl) Call(from types.Address, to types.Address, input []byte) (string, error) { callMsg := &jsonrpc.CallMsg{ @@ -134,6 +147,10 @@ func (t *TxRelayerImpl) SendTransaction(txn *types.Transaction, key crypto.Key) return nil, err } + if t.collectTxnHashes { + t.txnHashes = append(t.txnHashes, txnHash) + } + return t.waitForReceipt(txnHash) } @@ -146,18 +163,27 @@ func (t *TxRelayerImpl) sendTransactionLocked(txn *types.Transaction, key crypto t.lock.Lock() defer t.lock.Unlock() - nonce, err := t.client.GetNonce(key.Address(), jsonrpc.PendingBlockNumberOrHash) - if err != nil { - return types.ZeroHash, fmt.Errorf("failed to get nonce: %w", err) + var err error + + if t.nonceGet { + nonce, err := t.client.GetNonce(key.Address(), jsonrpc.PendingBlockNumberOrHash) + if err != nil { + return types.ZeroHash, fmt.Errorf("failed to get nonce: %w", err) + } + + txn.SetNonce(nonce) } - chainID, err := t.client.ChainID() - if err != nil { - return types.ZeroHash, err + chainID := t.chainID + + if chainID == nil { + chainID, err = t.client.ChainID() + if err != nil { + return types.ZeroHash, err + } } txn.SetChainID(chainID) - txn.SetNonce(nonce) if txn.From() == types.ZeroAddress { txn.SetFrom(key.Address()) @@ -261,6 +287,10 @@ func (t *TxRelayerImpl) SendTransactionLocal(txn *types.Transaction) (*ethgo.Rec return nil, err } + if t.collectTxnHashes { + t.txnHashes = append(t.txnHashes, txnHash) + } + return t.waitForReceipt(txnHash) } @@ -389,3 +419,21 @@ func WithEstimateGasFallback() TxRelayerOption { t.estimateGasFallback = true } } + +func WithChainID(chainID *big.Int) TxRelayerOption { + return func(t *TxRelayerImpl) { + t.chainID = chainID + } +} + +func WithoutNonceGet() TxRelayerOption { + return func(t *TxRelayerImpl) { + t.nonceGet = false + } +} + +func WithCollectTxnHashes() TxRelayerOption { + return func(t *TxRelayerImpl) { + t.collectTxnHashes = true + } +}