auto tx generator

This commit is contained in:
Ian Norden 2020-10-28 11:03:41 -05:00
parent da9dd220d0
commit d73390966c
10 changed files with 268 additions and 161 deletions

View File

@ -1,2 +1,58 @@
# tx_spammer # 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

View File

@ -1,6 +1,5 @@
[eth] [eth]
keyDirPath = "" # path to the directory with all of the key pairs to use - env: $ETH_KEY_DIR_PATH 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 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 chainID = 421 # chain id - env: $ETH_CHAIN_ID
type = "L2" # tx type (EIP1559, Standard, or L2) - env: $ETH_TX_TYPE type = "L2" # tx type (EIP1559, Standard, or L2) - env: $ETH_TX_TYPE
@ -19,7 +18,7 @@
[contractSpammer] [contractSpammer]
frequency = 30 # how often to send a transaction (in seconds) - env: $ETH_CALL_FREQ 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 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 # 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 # integer than we can increment to store values at new locations in the contract trie (to grow it) and
@ -31,7 +30,7 @@
[sendSpammer] [sendSpammer]
frequency = 30 # how often to send a transaction (in seconds) - env: $ETH_SEND_FREQ 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 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 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 gasPrice = "0" # gasPrice to use for the eth transfer txs - env: $ETH_SEND_GAS_PRICE

View File

@ -17,13 +17,11 @@
package auto package auto
import ( import (
"bufio"
"bytes" "bytes"
"crypto/ecdsa" "crypto/ecdsa"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
@ -38,6 +36,11 @@ import (
"github.com/vulcanize/tx_spammer/pkg/shared" "github.com/vulcanize/tx_spammer/pkg/shared"
) )
var (
receiverAddressSeed = common.HexToAddress("0xe48C9A989438606a79a7560cfba3d34BAfBAC38E")
storageAddressSeed = common.HexToAddress("0x029298Ac95662F2b54A7F1116f3F8105eb2b00F5")
)
func init() { func init() {
bindEnv() bindEnv()
} }
@ -95,14 +98,12 @@ type CallConfig struct {
GasLimit uint64 GasLimit uint64
GasPrice *big.Int GasPrice *big.Int
MethodName string MethodName string
ABI abi.ABI ABI abi.ABI
PositionStart uint64 StorageValue uint64
PositionEnd uint64 StorageAddrs []common.Address
StorageValue uint64
Frequency time.Duration Frequency time.Duration
Number uint64
} }
// SendConfig holds the parameters for the eth transfer txs // SendConfig holds the parameters for the eth transfer txs
@ -113,12 +114,10 @@ type SendConfig struct {
DestinationAddresses []common.Address DestinationAddresses []common.Address
Frequency time.Duration Frequency time.Duration
Number uint64
} }
// todo: EIP1559Config // todo: EIP1559Config
type EIP1559Config struct { type EIP1559Config struct{}
}
func NewConfig() (*Config, error) { func NewConfig() (*Config, error) {
// Initialize rpc client // Initialize rpc client
@ -158,12 +157,6 @@ func NewConfig() (*Config, error) {
senderAddrs = append(senderAddrs, crypto.PubkeyToAddress(key.PublicKey)) senderAddrs = append(senderAddrs, crypto.PubkeyToAddress(key.PublicKey))
} }
// Load eth transfer destination addresses
addrs, err := loadAddresses()
if err != nil {
return nil, err
}
// Load tx type // Load tx type
txType, err := shared.TxTypeFromString(viper.GetString(ethType)) txType, err := shared.TxTypeFromString(viper.GetString(ethType))
if err != nil { if err != nil {
@ -196,7 +189,7 @@ func NewConfig() (*Config, error) {
} }
// Load send config // Load send config
sendConfig, err := NewSendConfig(addrs) sendConfig, err := NewSendConfig()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -277,20 +270,24 @@ func NewCallConfig() (*CallConfig, error) {
if !exist { if !exist {
return nil, fmt.Errorf("method '%s' not found in provided abi", methodName) 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{ return &CallConfig{
Number: viper.GetUint64(ethCallTotalNumber), GasPrice: gasPrice,
GasPrice: gasPrice, GasLimit: viper.GetUint64(ethCallGasLimit),
GasLimit: viper.GetUint64(ethCallGasLimit), MethodName: methodName,
MethodName: methodName, ABI: parsedABI,
ABI: parsedABI, StorageValue: viper.GetUint64(ethCallStorageValue),
StorageValue: viper.GetUint64(ethCallStorageValue), Frequency: viper.GetDuration(ethCallFrequency),
Frequency: viper.GetDuration(ethCallFrequency), StorageAddrs: addrs,
}, nil }, nil
} }
// NewSendConfig constructs and returns a new SendConfig // NewSendConfig constructs and returns a new SendConfig
func NewSendConfig(destinationAddrs []common.Address) (*SendConfig, error) { func NewSendConfig() (*SendConfig, error) {
amountStr := viper.GetString(ethSendAmount) amountStr := viper.GetString(ethSendAmount)
amount, ok := new(big.Int).SetString(amountStr, 10) amount, ok := new(big.Int).SetString(amountStr, 10)
if !ok { if !ok {
@ -301,36 +298,16 @@ func NewSendConfig(destinationAddrs []common.Address) (*SendConfig, error) {
if !ok { if !ok {
return nil, fmt.Errorf("unable to convert gasPrice string (%s) into big.Int", gasPriceStr) 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{ return &SendConfig{
DestinationAddresses: destinationAddrs, DestinationAddresses: addrs,
Frequency: viper.GetDuration(ethSendFrequency), Frequency: viper.GetDuration(ethSendFrequency),
Number: viper.GetUint64(ethSendTotalNumber),
Amount: amount, Amount: amount,
GasPrice: gasPrice, GasPrice: gasPrice,
GasLimit: viper.GetUint64(ethSendGasLimit), GasLimit: viper.GetUint64(ethSendGasLimit),
}, nil }, 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
}

View File

@ -32,46 +32,48 @@ const (
// ContractDeployer is responsible for deploying contracts // ContractDeployer is responsible for deploying contracts
type ContractDeployer struct { type ContractDeployer struct {
client *rpc.Client client *rpc.Client
ty shared.TxType ty shared.TxType
txGenerator *TxGenerator txGenerator *TxGenerator
senderKeys []*ecdsa.PrivateKey senderKeys []*ecdsa.PrivateKey
senderAddrs []common.Address senderAddrs []common.Address
config *DeploymentConfig config *DeploymentConfig
} }
// NewContractDeployer returns a new ContractDeployer // NewContractDeployer returns a new ContractDeployer
func NewContractDeployer(config *Config, gen *TxGenerator) *ContractDeployer { func NewContractDeployer(config *Config, gen *TxGenerator) *ContractDeployer {
return &ContractDeployer{ return &ContractDeployer{
client: config.Client, client: config.Client,
ty: config.Type, ty: config.Type,
txGenerator: gen, txGenerator: gen,
config: config.DeploymentConfig, config: config.DeploymentConfig,
senderKeys: config.SenderKeys, senderKeys: config.SenderKeys,
senderAddrs: config.SenderAddrs, senderAddrs: config.SenderAddrs,
} }
} }
// Deploy deploys the contracts according to the config provided at construction // 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) ticker := time.NewTicker(contractDeploymentDelay)
defer ticker.Stop()
for i := uint64(0); i < cp.config.Number; i++ { for i := uint64(0); i < cp.config.Number; i++ {
<- ticker.C <-ticker.C
for i, key := range cp.senderKeys { for i, key := range cp.senderKeys {
txBytes, err := cp.txGenerator.GenerateTx(cp.ty, &GenParams{ txBytes, contractAddr, err := cp.txGenerator.GenerateTx(cp.ty, &GenParams{
Sender: cp.senderAddrs[i], Sender: cp.senderAddrs[i],
SenderKey: key, SenderKey: key,
GasLimit: cp.config.GasLimit, GasLimit: cp.config.GasLimit,
GasPrice: cp.config.GasPrice, GasPrice: cp.config.GasPrice,
Data: cp.config.Data, Data: cp.config.Data,
}) })
if err != nil { if err != nil {
return err return nil, err
} }
if err := shared.SendRawTransaction(cp.client, txBytes); err != nil { if err := shared.SendRawTransaction(cp.client, txBytes); err != nil {
return err return nil, err
} }
contractAddrs = append(contractAddrs, contractAddr)
} }
} }
return nil return contractAddrs, nil
} }

View File

@ -20,11 +20,10 @@ import "github.com/spf13/viper"
const ( const (
// env variables // env variables
ETH_KEY_DIR_PATH = "ETH_KEY_DIR_PATH" ETH_KEY_DIR_PATH = "ETH_KEY_DIR_PATH"
ETH_ADDR_DIR_PATH = "ETH_ADDR_DIR_PATH" ETH_HTTP_PATH = "ETH_HTTP_PATH"
ETH_HTTP_PATH = "ETH_HTTP_PATH" ETH_CHAIN_ID = "ETH_CHAIN_ID"
ETH_CHAIN_ID = "ETH_CHAIN_ID" ETH_TX_TYPE = "ETH_TX_TYPE"
ETH_TX_TYPE = "ETH_TX_TYPE"
ETH_DEPLOYMENT_NUMBER = "ETH_DEPLOYMENT_NUMBER" ETH_DEPLOYMENT_NUMBER = "ETH_DEPLOYMENT_NUMBER"
ETH_DEPLOYMENT_HEX_DATA = "ETH_DEPLOYMENT_HEX_DATA" 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_SIG_HASH_TYPE = "ETH_OPTIMISM_SIG_HASH_TYPE"
ETH_OPTIMISM_QUEUE_ORIGIN = "ETH_OPTIMISM_QUEUE_ORIGIN" ETH_OPTIMISM_QUEUE_ORIGIN = "ETH_OPTIMISM_QUEUE_ORIGIN"
ETH_CALL_FREQ = "ETH_CALL_FREQ" ETH_CALL_FREQ = "ETH_CALL_FREQ"
ETH_CALL_TOTAL_NUMBER = "ETH_CALL_TOTAL_NUMBER" ETH_CALL_TOTAL_NUMBER = "ETH_CALL_TOTAL_NUMBER"
ETH_CALL_ABI_PATH = "ETH_CALL_ABI_PATH" ETH_CALL_ABI_PATH = "ETH_CALL_ABI_PATH"
ETH_CALL_METHOD_NAME = "ETH_CALL_METHOD_NAME" ETH_CALL_METHOD_NAME = "ETH_CALL_METHOD_NAME"
ETH_CALL_STORAGE_VALUE = "ETH_CALL_STORAGE_VALUE" ETH_CALL_STORAGE_VALUE = "ETH_CALL_STORAGE_VALUE"
ETH_CALL_GAS_LIMIT = "ETH_CALL_GAS_LIMIT" ETH_CALL_GAS_LIMIT = "ETH_CALL_GAS_LIMIT"
ETH_CALL_GAS_PRICE = "ETH_CALL_GAS_PRICE" ETH_CALL_GAS_PRICE = "ETH_CALL_GAS_PRICE"
ETH_SEND_FREQ = "ETH_SEND_FREQ" ETH_SEND_FREQ = "ETH_SEND_FREQ"
ETH_SEND_TOTAL_NUMBER = "ETH_SEND_TOTAL_NUMBER" ETH_SEND_TOTAL_NUMBER = "ETH_SEND_TOTAL_NUMBER"
@ -51,11 +50,10 @@ const (
ETH_SEND_GAS_PRICE = "ETH_SEND_GAS_PRICE" ETH_SEND_GAS_PRICE = "ETH_SEND_GAS_PRICE"
// toml bindings // toml bindings
ethKeyDirPath = "eth.keyDirPath" ethKeyDirPath = "eth.keyDirPath"
ethAddrFilePath = "eth.addrFilePath" ethHttpPath = "eth.httpPath"
ethHttpPath = "eth.httpPath" ethChainID = "eth.chainID"
ethChainID = "eth.chainID" ethType = "eth.type"
ethType = "eth.type"
ethDeploymentNumber = "deployment.number" ethDeploymentNumber = "deployment.number"
ethDeploymentData = "deployment.hexData" ethDeploymentData = "deployment.hexData"
@ -67,13 +65,13 @@ const (
ethOptimismSigHashType = "optimism.sigHashType" ethOptimismSigHashType = "optimism.sigHashType"
ethOptimismQueueOrigin = "optimism.queueOrigin" ethOptimismQueueOrigin = "optimism.queueOrigin"
ethCallFrequency = "contractSpammer.frequency" ethCallFrequency = "contractSpammer.frequency"
ethCallTotalNumber = "contractSpammer.totalNumber" ethCallTotalNumber = "contractSpammer.totalNumber"
ethCallABIPath = "contractSpammer.abiPath" ethCallABIPath = "contractSpammer.abiPath"
ethCallMethodName = "contractSpammer.methodName" ethCallMethodName = "contractSpammer.methodName"
ethCallStorageValue = "contractSpammer.storageValue" ethCallStorageValue = "contractSpammer.storageValue"
ethCallGasLimit = "contractSpammer.gasLimit" ethCallGasLimit = "contractSpammer.gasLimit"
ethCallGasPrice = "contractSpammer.gasPrice" ethCallGasPrice = "contractSpammer.gasPrice"
ethSendFrequency = "sendSpammer.frequency" ethSendFrequency = "sendSpammer.frequency"
ethSendTotalNumber = "sendSpammer.totalNumber" ethSendTotalNumber = "sendSpammer.totalNumber"
@ -84,7 +82,6 @@ const (
func bindEnv() { func bindEnv() {
viper.BindEnv(ethKeyDirPath, ETH_KEY_DIR_PATH) viper.BindEnv(ethKeyDirPath, ETH_KEY_DIR_PATH)
viper.BindEnv(ethAddrFilePath, ETH_ADDR_DIR_PATH)
viper.BindEnv(ethHttpPath, ETH_HTTP_PATH) viper.BindEnv(ethHttpPath, ETH_HTTP_PATH)
viper.BindEnv(ethType, ETH_TX_TYPE) viper.BindEnv(ethType, ETH_TX_TYPE)
viper.BindEnv(ethChainID, ETH_CHAIN_ID) viper.BindEnv(ethChainID, ETH_CHAIN_ID)

View File

@ -36,11 +36,11 @@ func NewEthSender(config *Config) *EthSender {
// Send awaits txs off the provided work queue and sends them // 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) { 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 // err channel returned to calling context
errChan := make(chan error) errChan := make(chan error)
doneChan := make(chan bool)
go func() { go func() {
defer close(doneChan)
for { for {
select { select {
case tx := <-txRlpChan: case tx := <-txRlpChan:
@ -48,8 +48,7 @@ func (s *EthSender) Send(quitChan <-chan bool, txRlpChan <-chan []byte) (<-chan
errChan <- err errChan <- err
} }
case <-quitChan: case <-quitChan:
logrus.Info("quitting the Send loop") logrus.Info("quitting Send loop")
close(doneChan)
return return
} }
} }

View File

@ -17,52 +17,61 @@
package auto package auto
import ( import (
"fmt"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/vulcanize/tx_spammer/pkg/shared" "github.com/vulcanize/tx_spammer/pkg/shared"
) )
// Spammer underlying struct type for spamming service // Spammer underlying struct type for spamming service
type Spammer struct { type Spammer struct {
Deployer *ContractDeployer Deployer *ContractDeployer
Sender *EthSender Sender *EthSender
TxGenerator *TxGenerator TxGenerator *TxGenerator
config *Config
} }
// NewTxSpammer creates a new tx spamming service // NewTxSpammer creates a new tx spamming service
func NewTxSpammer(config *Config) shared.Service { func NewTxSpammer(config *Config) shared.Service {
gen := NewTxGenerator(config) gen := NewTxGenerator(config)
return &Spammer{ return &Spammer{
Deployer: NewContractDeployer(config, gen), Deployer: NewContractDeployer(config, gen),
Sender: NewEthSender(config), Sender: NewEthSender(config),
TxGenerator: gen, TxGenerator: gen,
config: config,
} }
} }
func (s *Spammer) Loop(quitChan <-chan bool) (<-chan bool, error) { func (s *Spammer) Loop(quitChan <-chan bool) (<-chan bool, error) {
if err := s.Deployer.Deploy(); err != nil { contractAddrs, err := s.Deployer.Deploy()
return nil, err if err != nil {
return nil, fmt.Errorf("contract deployment error: %v", err)
} }
genQuit := make(chan bool)
senderQuit := make(chan bool) senderQuit := make(chan bool)
generatorQuit := make(chan bool) doneChan := make(chan bool)
genDoneChan, txRlpChan, genErrChan := s.TxGenerator.GenerateTxs(generatorQuit) genDoneChan, txRlpChan, genErrChan := s.TxGenerator.GenerateTxs(genQuit, contractAddrs)
sendDoneChan, sendErrChan := s.Sender.Send(senderQuit, txRlpChan)
doneChan, errChan := s.Sender.Send(senderQuit, txRlpChan)
go func() { go func() {
defer close(doneChan)
for { for {
select { select {
case <-genDoneChan:
logrus.Info("all txs have been generated, beginning shut down sequence")
senderQuit <- true
case err := <-genErrChan: case err := <-genErrChan:
logrus.Error(err) logrus.Errorf("tx generation error: %v", err)
senderQuit <- true close(genQuit)
case err := <-errChan: <-genDoneChan
logrus.Error(err) close(senderQuit)
senderQuit <- true // NOTE: sender will close doneChan when it receives a quit signal case err := <-sendErrChan:
logrus.Errorf("tx sending error: %v", err)
close(genQuit)
<-genDoneChan
close(senderQuit)
case <-quitChan: case <-quitChan:
senderQuit <- true logrus.Error("shutting down tx spammer")
case <-doneChan: // NOTE CONT: which will be received here so that this context can close down only once the sender and generator have close(genQuit)
generatorQuit <- true <-genDoneChan
close(senderQuit)
case <-sendDoneChan:
return return
} }
} }

View File

@ -9,7 +9,7 @@
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // 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 // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
@ -21,7 +21,9 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"sync"
"sync/atomic" "sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -31,9 +33,7 @@ import (
// TxGenerator generates and signs txs // TxGenerator generates and signs txs
type TxGenerator struct { type TxGenerator struct {
signer types.Signer config *Config
optimismConfig *OptimismConfig
eip1559Config *EIP1559Config
// keep track of account nonces locally so we aren't spamming to determine the nonce // 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 // this assumes these accounts are not sending txs outside this process
nonces map[common.Address]*uint64 nonces map[common.Address]*uint64
@ -47,10 +47,8 @@ func NewTxGenerator(config *Config) *TxGenerator {
nonces[addr] = &startingNonce nonces[addr] = &startingNonce
} }
return &TxGenerator{ return &TxGenerator{
signer: config.Signer, nonces: nonces,
nonces: nonces, config: config,
optimismConfig: config.OptimismConfig,
eip1559Config: config.EIP1559Config,
} }
} }
@ -65,57 +63,127 @@ type GenParams struct {
Data []byte Data []byte
} }
// GenerateTxs loops and generates txs according the configuration passed in during construction func (gen *TxGenerator) GenerateTxs(quitChan <-chan bool, contractAddrs []common.Address) (<-chan bool, <-chan []byte, <-chan error) {
func (tg TxGenerator) GenerateTxs(quitChan <-chan bool) (<-chan bool, <-chan []byte, <-chan error) { txRlpChan := make(chan []byte)
// TODO: this errChan := make(chan error)
return nil, nil, nil 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 // GenerateTx generates tx from the provided params
func (tg TxGenerator) GenerateTx(ty shared.TxType, params *GenParams) ([]byte, error) { func (gen TxGenerator) GenerateTx(ty shared.TxType, params *GenParams) ([]byte, common.Address, error) {
tx := make([]byte, 0)
switch ty { switch ty {
case shared.OptimismL2: case shared.OptimismL2:
return tg.genL2(params, tg.optimismConfig) return gen.genL2(params, gen.config.OptimismConfig)
case shared.Standard: case shared.Standard:
return tg.gen(params) return gen.gen(params)
case shared.EIP1559: case shared.EIP1559:
return tg.gen1559(params, tg.eip1559Config) return gen.gen1559(params, gen.config.EIP1559Config)
default: 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) nonce := atomic.AddUint64(gen.nonces[params.Sender], 1)
tx := new(types.Transaction) tx := new(types.Transaction)
var contractAddr common.Address
var err error
if params.To == nil { if params.To == nil {
tx = types.NewContractCreation(nonce, params.Amount, params.GasLimit, params.GasPrice, params.Data, op.L1SenderAddr, op.L1RollupTxId, op.QueueOrigin) 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 { contractAddr, err = shared.WriteContractAddr(shared.DefaultDeploymentAddrLogPathPrefix, params.Sender, nonce)
return nil, err if err != nil {
return nil, common.Address{}, err
} }
} else { } else {
tx = types.NewTransaction(nonce, *params.To, params.Amount, params.GasLimit, params.GasPrice, params.Data, op.L1SenderAddr, op.L1RollupTxId, op.QueueOrigin, op.SigHashType) 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 { if err != nil {
return nil, err return nil, common.Address{}, err
} }
txRlp, err := rlp.EncodeToBytes(signedTx) txRlp, err := rlp.EncodeToBytes(signedTx)
if err != nil { 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 // 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 // TODO: support EIP1559
return nil, errors.New("1559 support not yet available") return nil, common.Address{}, errors.New("1559 support not yet available")
} }

View File

@ -66,7 +66,7 @@ func (gen TxGenerator) genL2(params TxParams) ([]byte, error) {
tx := new(types.Transaction) tx := new(types.Transaction)
if params.To == nil { if params.To == nil {
tx = types.NewContractCreation(nonce, params.Amount, params.GasLimit, params.GasPrice, params.Data, params.L1SenderAddr, params.L1RollupTxId, params.QueueOrigin) 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 return nil, err
} }

View File

@ -48,17 +48,17 @@ func SendRawTransaction(rpcClient *rpc.Client, txRlp []byte) error {
} }
// WriteContractAddr appends a contract addr to an out file // 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 == "" { if filePath == "" {
filePath = DefaultDeploymentAddrLogPathPrefix + senderAddr.Hex() filePath = DefaultDeploymentAddrLogPathPrefix + senderAddr.Hex()
} }
contractAddr := crypto.CreateAddress(senderAddr, nonce) contractAddr := crypto.CreateAddress(senderAddr, nonce)
f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
return err return common.Address{}, err
} }
if _, err := f.WriteString(contractAddr.Hex() + "\n"); err != nil { if _, err := f.WriteString(contractAddr.Hex() + "\n"); err != nil {
return err return common.Address{}, err
} }
return f.Close() return contractAddr, f.Close()
} }