diff --git a/README.md b/README.md index 741c3ca..0642dd7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,58 @@ # tx_spammer -Util for sending large numbers of txs for testing purposes +Tools to enable the semi-reproducible growth of a large and complex chain over RPC, for testing and benchmarking purposes + +Usage: + +`./tx_spammer autoSend --config=./environments/gen.toml` + +The `autoSend` command takes as input a .toml config of the below format, the fields can be overridden with the env variables in the comments. +It uses the provided key pairs and configuraiton parameters to generate and deploy a number of contracts with a simple interface for `Put`ing to a dynamic data structure. +It can then spam these contracts with `Put` operations at deterministically generated storage locations in order to grow the storage tries indefinitely and in a reproducible manner. +Additionally, it can be configured to send large numbers of eth value transfers to a set of manually designated or deterministically derived addresses, +in order to grow the state trie. + +This process is semi-reproducible, in that given the same input it will generate and send the same set of contract deployment, eth transfer, +and contract calling transactions. The precise ordering of transactions is not guaranteed, and uncertainty is introduced by the +miner processing the transactions. + +```toml +[eth] + keyDirPath = "" # path to the directory with all of the key pairs to use - env: $ETH_KEY_DIR_PATH + addrFilePath = "" # path to a file with a newline seperated list of addresses we want to send value transfers to - env: $ETH_ADDR_DIR_PATH + httpPath = "" # http url for the node we wish to send all our transactions to - env: $ETH_HTTP_PATH + chainID = 421 # chain id - env: $ETH_CHAIN_ID + type = "L2" # tx type (EIP1559, Standard, or L2) - env: $ETH_TX_TYPE + +[deployment] + number = 1 # number of contracts we will deploy for each key at keyPath - env: $ETH_DEPLOYMENT_NUMBER + hexData = "" # hex data for the contracts we will deploy - env: $ETH_DEPLOYMENT_HEX_DATA + gasLimit = 0 # gasLimit to use for the deployment txs - env: $ETH_DEPLOYMENT_GAS_LIMIT + gasPrice = "0" # gasPrice to use for the deployment txs - env: $ETH_DEPLOYMENT_GAS_PRICE + +[optimism] + l1Sender = "" # l1 sender address hex to use for all txs - env: $ETH_OPTIMISM_L1_SENDER + l1RollupTxId = 0 # rollup tx id to use for all txs - env: $ETH_OPTIMISM_ROLLUP_TX_ID + sigHashType = 0 # sig hash type to use for all txs - env: $ETH_OPTIMISM_SIG_HASH_TYPE + queueOrigin = 0 # queue origin id to use for all txs - env: $ETH_OPTIMISM_QUEUE_ORIGIN + +[contractSpammer] + frequency = 30 # how often to send a transaction (in seconds) - env: $ETH_CALL_FREQ + totalNumber = 10000 # total number of transactions to send (across all senders) - env: $ETH_CALL_TOTAL_NUMBER + abiPath = "" # path to the abi file for the contract we are calling - env: $ETH_CALL_ABI_PATH + # NOTE: we expect to be calling a method such as Put(address addr, uint256 val) where the first argument is an + # integer than we can increment to store values at new locations in the contract trie (to grow it) and + # the second argument is an integer value that we store at these positions + methodName = "Put" # the method name we are calling - env: $ETH_CALL_METHOD_NAME + storageValue = 1337 # the value we store at each position - env: $ETH_CALL_STORAGE_VALUE + gasLimit = 0 # gasLimit to use for the eth call txs - env: $ETH_CALL_GAS_LIMIT + gasPrice = "0" # gasPrice to use for the eth call txs - env: $ETH_CALL_GAS_PRICE + +[sendSpammer] + frequency = 30 # how often to send a transaction (in seconds) - env: $ETH_SEND_FREQ + totalNumber = 10000 # total number of transactions to send (across all senders) - env: $ETH_SEND_TOTAL_NUMBER + amount = "1" # amount of wei (1x10^-18 ETH) to send in each tx (be mindful of the genesis allocations) - env: $ETH_SEND_AMOUNT + gasLimit = 0 # gasLimit to use for the eth transfer txs - env: $ETH_SEND_GAS_LIMIT + gasPrice = "0" # gasPrice to use for the eth transfer txs - env: $ETH_SEND_GAS_PRICE +``` + +TODO: better documentation and document the other commands \ No newline at end of file diff --git a/environments/gen.toml b/environments/gen.toml index c23b447..85ba4bf 100644 --- a/environments/gen.toml +++ b/environments/gen.toml @@ -1,6 +1,5 @@ [eth] keyDirPath = "" # path to the directory with all of the key pairs to use - env: $ETH_KEY_DIR_PATH - addrFilePath = "" # path to a file with a newline seperated list of addresses we want to send value transfers to - env: $ETH_ADDR_DIR_PATH httpPath = "" # http url for the node we wish to send all our transactions to - env: $ETH_HTTP_PATH chainID = 421 # chain id - env: $ETH_CHAIN_ID type = "L2" # tx type (EIP1559, Standard, or L2) - env: $ETH_TX_TYPE @@ -19,7 +18,7 @@ [contractSpammer] frequency = 30 # how often to send a transaction (in seconds) - env: $ETH_CALL_FREQ - totalNumber = 10000 # total number of transactions to send (across all senders) - env: $ETH_CALL_TOTAL_NUMBER + totalNumber = 10000 # total number of transactions to send (per sender) - env: $ETH_CALL_TOTAL_NUMBER abiPath = "" # path to the abi file for the contract we are calling - env: $ETH_CALL_ABI_PATH # NOTE: we expect to be calling a method such as Put(address addr, uint256 val) where the first argument is an # integer than we can increment to store values at new locations in the contract trie (to grow it) and @@ -31,7 +30,7 @@ [sendSpammer] frequency = 30 # how often to send a transaction (in seconds) - env: $ETH_SEND_FREQ - totalNumber = 10000 # total number of transactions to send (across all senders) - env: $ETH_SEND_TOTAL_NUMBER + totalNumber = 10000 # total number of transactions to send (per sender) - env: $ETH_SEND_TOTAL_NUMBER amount = "1" # amount of wei (1x10^-18 ETH) to send in each tx (be mindful of the genesis allocations) - env: $ETH_SEND_AMOUNT gasLimit = 0 # gasLimit to use for the eth transfer txs - env: $ETH_SEND_GAS_LIMIT gasPrice = "0" # gasPrice to use for the eth transfer txs - env: $ETH_SEND_GAS_PRICE diff --git a/pkg/auto/config.go b/pkg/auto/config.go index 9075827..5b1ab5e 100644 --- a/pkg/auto/config.go +++ b/pkg/auto/config.go @@ -17,13 +17,11 @@ package auto import ( - "bufio" "bytes" "crypto/ecdsa" "fmt" "io/ioutil" "math/big" - "os" "path/filepath" "strings" "time" @@ -38,6 +36,11 @@ import ( "github.com/vulcanize/tx_spammer/pkg/shared" ) +var ( + receiverAddressSeed = common.HexToAddress("0xe48C9A989438606a79a7560cfba3d34BAfBAC38E") + storageAddressSeed = common.HexToAddress("0x029298Ac95662F2b54A7F1116f3F8105eb2b00F5") +) + func init() { bindEnv() } @@ -95,14 +98,12 @@ type CallConfig struct { GasLimit uint64 GasPrice *big.Int - MethodName string - ABI abi.ABI - PositionStart uint64 - PositionEnd uint64 - StorageValue uint64 + MethodName string + ABI abi.ABI + StorageValue uint64 + StorageAddrs []common.Address Frequency time.Duration - Number uint64 } // SendConfig holds the parameters for the eth transfer txs @@ -113,12 +114,10 @@ type SendConfig struct { DestinationAddresses []common.Address Frequency time.Duration - Number uint64 } // todo: EIP1559Config -type EIP1559Config struct { -} +type EIP1559Config struct{} func NewConfig() (*Config, error) { // Initialize rpc client @@ -158,12 +157,6 @@ func NewConfig() (*Config, error) { senderAddrs = append(senderAddrs, crypto.PubkeyToAddress(key.PublicKey)) } - // Load eth transfer destination addresses - addrs, err := loadAddresses() - if err != nil { - return nil, err - } - // Load tx type txType, err := shared.TxTypeFromString(viper.GetString(ethType)) if err != nil { @@ -196,7 +189,7 @@ func NewConfig() (*Config, error) { } // Load send config - sendConfig, err := NewSendConfig(addrs) + sendConfig, err := NewSendConfig() if err != nil { return nil, err } @@ -277,20 +270,24 @@ func NewCallConfig() (*CallConfig, error) { if !exist { return nil, fmt.Errorf("method '%s' not found in provided abi", methodName) } - + number := viper.GetUint64(ethCallTotalNumber) + addrs := make([]common.Address, number) + for i := uint64(0); i < number; i++ { + addrs[i] = crypto.CreateAddress(storageAddressSeed, i) + } return &CallConfig{ - Number: viper.GetUint64(ethCallTotalNumber), - GasPrice: gasPrice, - GasLimit: viper.GetUint64(ethCallGasLimit), - MethodName: methodName, - ABI: parsedABI, - StorageValue: viper.GetUint64(ethCallStorageValue), - Frequency: viper.GetDuration(ethCallFrequency), + GasPrice: gasPrice, + GasLimit: viper.GetUint64(ethCallGasLimit), + MethodName: methodName, + ABI: parsedABI, + StorageValue: viper.GetUint64(ethCallStorageValue), + Frequency: viper.GetDuration(ethCallFrequency), + StorageAddrs: addrs, }, nil } // NewSendConfig constructs and returns a new SendConfig -func NewSendConfig(destinationAddrs []common.Address) (*SendConfig, error) { +func NewSendConfig() (*SendConfig, error) { amountStr := viper.GetString(ethSendAmount) amount, ok := new(big.Int).SetString(amountStr, 10) if !ok { @@ -301,36 +298,16 @@ func NewSendConfig(destinationAddrs []common.Address) (*SendConfig, error) { if !ok { return nil, fmt.Errorf("unable to convert gasPrice string (%s) into big.Int", gasPriceStr) } + number := viper.GetUint64(ethSendTotalNumber) + addrs := make([]common.Address, number) + for i := uint64(0); i < number; i++ { + addrs[i] = crypto.CreateAddress(receiverAddressSeed, i) + } return &SendConfig{ - DestinationAddresses: destinationAddrs, + DestinationAddresses: addrs, Frequency: viper.GetDuration(ethSendFrequency), - Number: viper.GetUint64(ethSendTotalNumber), Amount: amount, GasPrice: gasPrice, GasLimit: viper.GetUint64(ethSendGasLimit), }, nil } - -// Load eth transfer destination addresses -func loadAddresses() ([]common.Address, error) { - addrs := make([]common.Address, 0) - addrFilePath := viper.GetString(ethAddrFilePath) - if addrFilePath == "" { - return nil, fmt.Errorf("missing %s", ethAddrFilePath) - } - addrFile, err := os.Open(addrFilePath) - if err != nil { - return nil, err - } - defer addrFile.Close() - scanner := bufio.NewScanner(addrFile) - for scanner.Scan() { - addrBytes := scanner.Bytes() - addr := common.BytesToAddress(addrBytes) - addrs = append(addrs, addr) - } - if err := scanner.Err(); err != nil { - return nil, err - } - return addrs, nil -} diff --git a/pkg/auto/deployer.go b/pkg/auto/deployer.go index 536b27b..e04abbd 100644 --- a/pkg/auto/deployer.go +++ b/pkg/auto/deployer.go @@ -32,46 +32,48 @@ const ( // ContractDeployer is responsible for deploying contracts type ContractDeployer struct { client *rpc.Client - ty shared.TxType + ty shared.TxType txGenerator *TxGenerator - senderKeys []*ecdsa.PrivateKey + senderKeys []*ecdsa.PrivateKey senderAddrs []common.Address - config *DeploymentConfig + config *DeploymentConfig } // NewContractDeployer returns a new ContractDeployer func NewContractDeployer(config *Config, gen *TxGenerator) *ContractDeployer { return &ContractDeployer{ - client: config.Client, - ty: config.Type, + client: config.Client, + ty: config.Type, txGenerator: gen, - config: config.DeploymentConfig, - senderKeys: config.SenderKeys, + config: config.DeploymentConfig, + senderKeys: config.SenderKeys, senderAddrs: config.SenderAddrs, } } // Deploy deploys the contracts according to the config provided at construction -func (cp *ContractDeployer) Deploy() error { +func (cp *ContractDeployer) Deploy() ([]common.Address, error) { + contractAddrs := make([]common.Address, 0, cp.config.Number*uint64(len(cp.senderKeys))) ticker := time.NewTicker(contractDeploymentDelay) + defer ticker.Stop() for i := uint64(0); i < cp.config.Number; i++ { - <- ticker.C + <-ticker.C for i, key := range cp.senderKeys { - txBytes, err := cp.txGenerator.GenerateTx(cp.ty, &GenParams{ - Sender: cp.senderAddrs[i], + txBytes, contractAddr, err := cp.txGenerator.GenerateTx(cp.ty, &GenParams{ + Sender: cp.senderAddrs[i], SenderKey: key, - GasLimit: cp.config.GasLimit, - GasPrice: cp.config.GasPrice, - Data: cp.config.Data, + GasLimit: cp.config.GasLimit, + GasPrice: cp.config.GasPrice, + Data: cp.config.Data, }) if err != nil { - return err + return nil, err } if err := shared.SendRawTransaction(cp.client, txBytes); err != nil { - return err + return nil, err } - + contractAddrs = append(contractAddrs, contractAddr) } } - return nil + return contractAddrs, nil } diff --git a/pkg/auto/env.go b/pkg/auto/env.go index 344bf2c..2c112c5 100644 --- a/pkg/auto/env.go +++ b/pkg/auto/env.go @@ -20,11 +20,10 @@ import "github.com/spf13/viper" const ( // env variables - ETH_KEY_DIR_PATH = "ETH_KEY_DIR_PATH" - ETH_ADDR_DIR_PATH = "ETH_ADDR_DIR_PATH" - ETH_HTTP_PATH = "ETH_HTTP_PATH" - ETH_CHAIN_ID = "ETH_CHAIN_ID" - ETH_TX_TYPE = "ETH_TX_TYPE" + ETH_KEY_DIR_PATH = "ETH_KEY_DIR_PATH" + ETH_HTTP_PATH = "ETH_HTTP_PATH" + ETH_CHAIN_ID = "ETH_CHAIN_ID" + ETH_TX_TYPE = "ETH_TX_TYPE" ETH_DEPLOYMENT_NUMBER = "ETH_DEPLOYMENT_NUMBER" ETH_DEPLOYMENT_HEX_DATA = "ETH_DEPLOYMENT_HEX_DATA" @@ -36,13 +35,13 @@ const ( ETH_OPTIMISM_SIG_HASH_TYPE = "ETH_OPTIMISM_SIG_HASH_TYPE" ETH_OPTIMISM_QUEUE_ORIGIN = "ETH_OPTIMISM_QUEUE_ORIGIN" - ETH_CALL_FREQ = "ETH_CALL_FREQ" - ETH_CALL_TOTAL_NUMBER = "ETH_CALL_TOTAL_NUMBER" - ETH_CALL_ABI_PATH = "ETH_CALL_ABI_PATH" - ETH_CALL_METHOD_NAME = "ETH_CALL_METHOD_NAME" - ETH_CALL_STORAGE_VALUE = "ETH_CALL_STORAGE_VALUE" - ETH_CALL_GAS_LIMIT = "ETH_CALL_GAS_LIMIT" - ETH_CALL_GAS_PRICE = "ETH_CALL_GAS_PRICE" + ETH_CALL_FREQ = "ETH_CALL_FREQ" + ETH_CALL_TOTAL_NUMBER = "ETH_CALL_TOTAL_NUMBER" + ETH_CALL_ABI_PATH = "ETH_CALL_ABI_PATH" + ETH_CALL_METHOD_NAME = "ETH_CALL_METHOD_NAME" + ETH_CALL_STORAGE_VALUE = "ETH_CALL_STORAGE_VALUE" + ETH_CALL_GAS_LIMIT = "ETH_CALL_GAS_LIMIT" + ETH_CALL_GAS_PRICE = "ETH_CALL_GAS_PRICE" ETH_SEND_FREQ = "ETH_SEND_FREQ" ETH_SEND_TOTAL_NUMBER = "ETH_SEND_TOTAL_NUMBER" @@ -51,11 +50,10 @@ const ( ETH_SEND_GAS_PRICE = "ETH_SEND_GAS_PRICE" // toml bindings - ethKeyDirPath = "eth.keyDirPath" - ethAddrFilePath = "eth.addrFilePath" - ethHttpPath = "eth.httpPath" - ethChainID = "eth.chainID" - ethType = "eth.type" + ethKeyDirPath = "eth.keyDirPath" + ethHttpPath = "eth.httpPath" + ethChainID = "eth.chainID" + ethType = "eth.type" ethDeploymentNumber = "deployment.number" ethDeploymentData = "deployment.hexData" @@ -67,13 +65,13 @@ const ( ethOptimismSigHashType = "optimism.sigHashType" ethOptimismQueueOrigin = "optimism.queueOrigin" - ethCallFrequency = "contractSpammer.frequency" - ethCallTotalNumber = "contractSpammer.totalNumber" - ethCallABIPath = "contractSpammer.abiPath" - ethCallMethodName = "contractSpammer.methodName" - ethCallStorageValue = "contractSpammer.storageValue" - ethCallGasLimit = "contractSpammer.gasLimit" - ethCallGasPrice = "contractSpammer.gasPrice" + ethCallFrequency = "contractSpammer.frequency" + ethCallTotalNumber = "contractSpammer.totalNumber" + ethCallABIPath = "contractSpammer.abiPath" + ethCallMethodName = "contractSpammer.methodName" + ethCallStorageValue = "contractSpammer.storageValue" + ethCallGasLimit = "contractSpammer.gasLimit" + ethCallGasPrice = "contractSpammer.gasPrice" ethSendFrequency = "sendSpammer.frequency" ethSendTotalNumber = "sendSpammer.totalNumber" @@ -84,7 +82,6 @@ const ( func bindEnv() { viper.BindEnv(ethKeyDirPath, ETH_KEY_DIR_PATH) - viper.BindEnv(ethAddrFilePath, ETH_ADDR_DIR_PATH) viper.BindEnv(ethHttpPath, ETH_HTTP_PATH) viper.BindEnv(ethType, ETH_TX_TYPE) viper.BindEnv(ethChainID, ETH_CHAIN_ID) diff --git a/pkg/auto/sender.go b/pkg/auto/sender.go index 9a10667..a5073f5 100644 --- a/pkg/auto/sender.go +++ b/pkg/auto/sender.go @@ -36,11 +36,11 @@ func NewEthSender(config *Config) *EthSender { // Send awaits txs off the provided work queue and sends them func (s *EthSender) Send(quitChan <-chan bool, txRlpChan <-chan []byte) (<-chan bool, <-chan error) { - // done channel to signal quit signal has been received (any ongoing jobs were finished) - doneChan := make(chan bool) // err channel returned to calling context errChan := make(chan error) + doneChan := make(chan bool) go func() { + defer close(doneChan) for { select { case tx := <-txRlpChan: @@ -48,8 +48,7 @@ func (s *EthSender) Send(quitChan <-chan bool, txRlpChan <-chan []byte) (<-chan errChan <- err } case <-quitChan: - logrus.Info("quitting the Send loop") - close(doneChan) + logrus.Info("quitting Send loop") return } } diff --git a/pkg/auto/service.go b/pkg/auto/service.go index 7fbad57..6d89810 100644 --- a/pkg/auto/service.go +++ b/pkg/auto/service.go @@ -17,52 +17,61 @@ package auto import ( + "fmt" + "github.com/sirupsen/logrus" "github.com/vulcanize/tx_spammer/pkg/shared" ) // Spammer underlying struct type for spamming service type Spammer struct { - Deployer *ContractDeployer - Sender *EthSender + Deployer *ContractDeployer + Sender *EthSender TxGenerator *TxGenerator + config *Config } // NewTxSpammer creates a new tx spamming service func NewTxSpammer(config *Config) shared.Service { gen := NewTxGenerator(config) return &Spammer{ - Deployer: NewContractDeployer(config, gen), - Sender: NewEthSender(config), + Deployer: NewContractDeployer(config, gen), + Sender: NewEthSender(config), TxGenerator: gen, + config: config, } } func (s *Spammer) Loop(quitChan <-chan bool) (<-chan bool, error) { - if err := s.Deployer.Deploy(); err != nil { - return nil, err + contractAddrs, err := s.Deployer.Deploy() + if err != nil { + return nil, fmt.Errorf("contract deployment error: %v", err) } + genQuit := make(chan bool) senderQuit := make(chan bool) - generatorQuit := make(chan bool) - genDoneChan, txRlpChan, genErrChan := s.TxGenerator.GenerateTxs(generatorQuit) - - doneChan, errChan := s.Sender.Send(senderQuit, txRlpChan) + doneChan := make(chan bool) + genDoneChan, txRlpChan, genErrChan := s.TxGenerator.GenerateTxs(genQuit, contractAddrs) + sendDoneChan, sendErrChan := s.Sender.Send(senderQuit, txRlpChan) go func() { + defer close(doneChan) for { select { - case <-genDoneChan: - logrus.Info("all txs have been generated, beginning shut down sequence") - senderQuit <- true case err := <-genErrChan: - logrus.Error(err) - senderQuit <- true - case err := <-errChan: - logrus.Error(err) - senderQuit <- true // NOTE: sender will close doneChan when it receives a quit signal + logrus.Errorf("tx generation error: %v", err) + close(genQuit) + <-genDoneChan + close(senderQuit) + case err := <-sendErrChan: + logrus.Errorf("tx sending error: %v", err) + close(genQuit) + <-genDoneChan + close(senderQuit) case <-quitChan: - senderQuit <- true - case <-doneChan: // NOTE CONT: which will be received here so that this context can close down only once the sender and generator have - generatorQuit <- true + logrus.Error("shutting down tx spammer") + close(genQuit) + <-genDoneChan + close(senderQuit) + case <-sendDoneChan: return } } diff --git a/pkg/auto/tx_generator.go b/pkg/auto/tx_generator.go index a88ba94..790453e 100644 --- a/pkg/auto/tx_generator.go +++ b/pkg/auto/tx_generator.go @@ -9,7 +9,7 @@ // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. +// GNU Affero General Public License for more detailgen. // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . @@ -21,7 +21,9 @@ import ( "errors" "fmt" "math/big" + "sync" "sync/atomic" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -31,9 +33,7 @@ import ( // TxGenerator generates and signs txs type TxGenerator struct { - signer types.Signer - optimismConfig *OptimismConfig - eip1559Config *EIP1559Config + config *Config // keep track of account nonces locally so we aren't spamming to determine the nonce // this assumes these accounts are not sending txs outside this process nonces map[common.Address]*uint64 @@ -47,10 +47,8 @@ func NewTxGenerator(config *Config) *TxGenerator { nonces[addr] = &startingNonce } return &TxGenerator{ - signer: config.Signer, - nonces: nonces, - optimismConfig: config.OptimismConfig, - eip1559Config: config.EIP1559Config, + nonces: nonces, + config: config, } } @@ -65,57 +63,127 @@ type GenParams struct { Data []byte } -// GenerateTxs loops and generates txs according the configuration passed in during construction -func (tg TxGenerator) GenerateTxs(quitChan <-chan bool) (<-chan bool, <-chan []byte, <-chan error) { - // TODO: this - return nil, nil, nil +func (gen *TxGenerator) GenerateTxs(quitChan <-chan bool, contractAddrs []common.Address) (<-chan bool, <-chan []byte, <-chan error) { + txRlpChan := make(chan []byte) + errChan := make(chan error) + wg := new(sync.WaitGroup) + for i, sender := range gen.config.SenderKeys { + if len(gen.config.SendConfig.DestinationAddresses) > 0 { + wg.Add(1) + go gen.genSends(wg, gen.config.Type, txRlpChan, errChan, quitChan, sender, gen.config.SenderAddrs[i], gen.config.SendConfig) + } + if len(gen.config.CallConfig.StorageAddrs) > 0 { + wg.Add(1) + go gen.genCalls(wg, gen.config.Type, txRlpChan, errChan, quitChan, sender, gen.config.SenderAddrs[i], gen.config.CallConfig) + } + } + doneChan := make(chan bool) + go func() { + wg.Wait() + close(doneChan) + }() + return doneChan, txRlpChan, errChan +} + +func (gen *TxGenerator) genSends(wg *sync.WaitGroup, ty shared.TxType, txRlpChan chan<- []byte, errChan chan<- error, quitChan <-chan bool, senderKey *ecdsa.PrivateKey, senderAddr common.Address, sendConfig *SendConfig) { + defer wg.Done() + ticker := time.NewTicker(sendConfig.Frequency) + for _, dst := range sendConfig.DestinationAddresses { + select { + case <-ticker.C: + txRlp, _, err := gen.GenerateTx(ty, &GenParams{ + Sender: senderAddr, + SenderKey: senderKey, + GasLimit: sendConfig.GasLimit, + GasPrice: sendConfig.GasPrice, + Amount: sendConfig.Amount, + To: &dst, + }) + if err != nil { + errChan <- err + continue + } + txRlpChan <- txRlp + case <-quitChan: + return + } + } +} + +func (gen *TxGenerator) genCalls(wg *sync.WaitGroup, ty shared.TxType, txRlpChan chan<- []byte, errChan chan<- error, quitChan <-chan bool, senderKey *ecdsa.PrivateKey, senderAddr common.Address, callConfig *CallConfig) { + defer wg.Done() + ticker := time.NewTicker(callConfig.Frequency) + for _, addr := range callConfig.StorageAddrs { + select { + case <-ticker.C: + data, err := callConfig.ABI.Pack(callConfig.MethodName, addr, callConfig.StorageValue) + if err != nil { + errChan <- err + continue + } + txRlp, _, err := gen.GenerateTx(ty, &GenParams{ + Sender: senderAddr, + SenderKey: senderKey, + GasLimit: callConfig.GasLimit, + GasPrice: callConfig.GasPrice, + Data: data, + }) + if err != nil { + errChan <- err + continue + } + txRlpChan <- txRlp + case <-quitChan: + return + } + } } // GenerateTx generates tx from the provided params -func (tg TxGenerator) GenerateTx(ty shared.TxType, params *GenParams) ([]byte, error) { - tx := make([]byte, 0) +func (gen TxGenerator) GenerateTx(ty shared.TxType, params *GenParams) ([]byte, common.Address, error) { switch ty { case shared.OptimismL2: - return tg.genL2(params, tg.optimismConfig) + return gen.genL2(params, gen.config.OptimismConfig) case shared.Standard: - return tg.gen(params) + return gen.gen(params) case shared.EIP1559: - return tg.gen1559(params, tg.eip1559Config) + return gen.gen1559(params, gen.config.EIP1559Config) default: - return nil, fmt.Errorf("unsupported tx type: %s", ty.String()) + return nil, common.Address{}, fmt.Errorf("unsupported tx type: %s", ty.String()) } - return tx, nil } -func (gen TxGenerator) genL2(params *GenParams, op *OptimismConfig) ([]byte, error) { +func (gen TxGenerator) genL2(params *GenParams, op *OptimismConfig) ([]byte, common.Address, error) { nonce := atomic.AddUint64(gen.nonces[params.Sender], 1) tx := new(types.Transaction) + var contractAddr common.Address + var err error if params.To == nil { tx = types.NewContractCreation(nonce, params.Amount, params.GasLimit, params.GasPrice, params.Data, op.L1SenderAddr, op.L1RollupTxId, op.QueueOrigin) - if err := shared.WriteContractAddr(shared.DefaultDeploymentAddrLogPathPrefix, params.Sender, nonce); err != nil { - return nil, err + contractAddr, err = shared.WriteContractAddr(shared.DefaultDeploymentAddrLogPathPrefix, params.Sender, nonce) + if err != nil { + return nil, common.Address{}, err } - } else { tx = types.NewTransaction(nonce, *params.To, params.Amount, params.GasLimit, params.GasPrice, params.Data, op.L1SenderAddr, op.L1RollupTxId, op.QueueOrigin, op.SigHashType) } - signedTx, err := types.SignTx(tx, gen.signer, params.SenderKey) + signedTx, err := types.SignTx(tx, gen.config.Signer, params.SenderKey) if err != nil { - return nil, err + return nil, common.Address{}, err } txRlp, err := rlp.EncodeToBytes(signedTx) if err != nil { - return nil, err + return nil, common.Address{}, err } - return txRlp, nil + return txRlp, contractAddr, err } -func (gen TxGenerator) gen(params *GenParams) ([]byte, error) { +func (gen TxGenerator) gen(params *GenParams) ([]byte, common.Address, error) { // TODO: support standard geth - return nil, errors.New("L1 support not yet available") + return nil, common.Address{}, errors.New("L1 support not yet available") } -func (gen TxGenerator) gen1559(params *GenParams, eip1559Config *EIP1559Config) ([]byte, error) { +func (gen TxGenerator) gen1559(params *GenParams, eip1559Config *EIP1559Config) ([]byte, common.Address, error) { // TODO: support EIP1559 - return nil, errors.New("1559 support not yet available") + return nil, common.Address{}, errors.New("1559 support not yet available") } diff --git a/pkg/manual/tx_generator.go b/pkg/manual/tx_generator.go index 1f7eae7..3c882f7 100644 --- a/pkg/manual/tx_generator.go +++ b/pkg/manual/tx_generator.go @@ -66,7 +66,7 @@ func (gen TxGenerator) genL2(params TxParams) ([]byte, error) { tx := new(types.Transaction) if params.To == nil { tx = types.NewContractCreation(nonce, params.Amount, params.GasLimit, params.GasPrice, params.Data, params.L1SenderAddr, params.L1RollupTxId, params.QueueOrigin) - if err := shared.WriteContractAddr(params.ContractAddrWritePath, params.Sender, nonce); err != nil { + if _, err := shared.WriteContractAddr(params.ContractAddrWritePath, params.Sender, nonce); err != nil { return nil, err } diff --git a/pkg/shared/util.go b/pkg/shared/util.go index 832039a..8082f45 100644 --- a/pkg/shared/util.go +++ b/pkg/shared/util.go @@ -48,17 +48,17 @@ func SendRawTransaction(rpcClient *rpc.Client, txRlp []byte) error { } // WriteContractAddr appends a contract addr to an out file -func WriteContractAddr(filePath string, senderAddr common.Address, nonce uint64) error { +func WriteContractAddr(filePath string, senderAddr common.Address, nonce uint64) (common.Address, error) { if filePath == "" { filePath = DefaultDeploymentAddrLogPathPrefix + senderAddr.Hex() } contractAddr := crypto.CreateAddress(senderAddr, nonce) f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { - return err + return common.Address{}, err } if _, err := f.WriteString(contractAddr.Hex() + "\n"); err != nil { - return err + return common.Address{}, err } - return f.Close() + return contractAddr, f.Close() }