diff --git a/cmd/sendTxs.go b/cmd/sendTxs.go index ff5d5fc..cdeca5c 100644 --- a/cmd/sendTxs.go +++ b/cmd/sendTxs.go @@ -16,36 +16,42 @@ package cmd import ( - "fmt" + "os" + "os/signal" + "sync" "github.com/spf13/cobra" + "github.com/vulcanize/tx_spammer/pkg" ) // sendTxsCmd represents the sendTxs command var sendTxsCmd = &cobra.Command{ Use: "sendTxs", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, + Short: "send large volumes of different tx types to different nodes", + Long: `Loads tx configuration from .toml config file +Generates txs from configuration and sends them to designated node according to set frequency and number`, Run: func(cmd *cobra.Command, args []string) { - fmt.Println("sendTxs called") + sendTxs() }, } +func sendTxs() { + params, err := tx_spammer.NewTxParams() + if err != nil { + logWithCommand.Fatal(err) + } + txSpammer := tx_spammer.NewTxSpammer(params) + wg := new(sync.WaitGroup) + quitChan := make(chan bool) + txSpammer.Loop(wg, quitChan) + + shutdown := make(chan os.Signal) + signal.Notify(shutdown, os.Interrupt) + <-shutdown + close(quitChan) + wg.Wait() + +} func init() { rootCmd.AddCommand(sendTxsCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // sendTxsCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // sendTxsCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } diff --git a/environment/example.toml b/environment/example.toml index 464dd54..b77a652 100644 --- a/environment/example.toml +++ b/environment/example.toml @@ -1,5 +1,5 @@ [eth] - txs = ["L2ContractPutCall", "L2ContractGetCall"] + txs = ["L2ContractDeployment", "L2ContractPutCall", "L2ContractGetCall"] [L2ContractDeployment] type = "L2" @@ -11,8 +11,8 @@ data = "" senderKeyPath = "" writeSenderPath = "" - frequency = 15 - totalNumber = 1500 + frequency = 1 + totalNumber = 1 chainID = 420 [L2ContractPutCall] diff --git a/pkg/config.go b/pkg/config.go index cca9416..ae32aa1 100644 --- a/pkg/config.go +++ b/pkg/config.go @@ -40,21 +40,21 @@ type TxParams struct { Type TxType // Universal tx fields - To *common.Address + To *common.Address GasLimit uint64 GasPrice *big.Int // nil if eip1559 - Amount *big.Int - Data []byte - Sender common.Address + Amount *big.Int + Data []byte + Sender common.Address // Optimism-specific metadata fields L1SenderAddr *common.Address L1RollupTxId uint64 - SigHashType uint8 + SigHashType uint8 // EIP1559-specific fields GasPremium *big.Int - FeeCap *big.Int + FeeCap *big.Int // Sender key, if left the senderKeyPath is empty we generate a new key SenderKey *ecdsa.PrivateKey @@ -71,22 +71,22 @@ type TxParams struct { const ( ETH_TX_LIST = "ETH_TX_LIST" - typeSuffix = ".type" - httpPathSuffix = ".http" - toSuffix = ".to" - amountSuffix = ".amount" - gasLimitSuffix = ".gasLimit" - gasPriceSuffix = ".gasPrice" - gasPremiumSuffix = ".gasPremium" - feeCapSuffix = ".feeCap" - dataSuffix = ".data" - senderKeyPathSuffix = ".senderKeyPath" + typeSuffix = ".type" + httpPathSuffix = ".http" + toSuffix = ".to" + amountSuffix = ".amount" + gasLimitSuffix = ".gasLimit" + gasPriceSuffix = ".gasPrice" + gasPremiumSuffix = ".gasPremium" + feeCapSuffix = ".feeCap" + dataSuffix = ".data" + senderKeyPathSuffix = ".senderKeyPath" writeSenderPathSuffix = ".writeSenderPath" - l1SenderSuffix = ".l1Sender" - l1RollupTxIdSuffix = ".l1RollupTxId" - sigHashTypeSuffix = ".sigHashType" - frequencySuffix = ".frequency" - totalNumberSuffix = ".totalNumber" + l1SenderSuffix = ".l1Sender" + l1RollupTxIdSuffix = ".l1RollupTxId" + sigHashTypeSuffix = ".sigHashType" + frequencySuffix = ".frequency" + totalNumberSuffix = ".totalNumber" ) // NewConfig returns a new tx spammer config @@ -97,7 +97,7 @@ func NewTxParams() ([]TxParams, error) { txParams := make([]TxParams, len(txs)) for i, txName := range txs { // Get http client - httpPathStr := viper.GetString(txName+httpPathSuffix) + httpPathStr := viper.GetString(txName + httpPathSuffix) if httpPathStr == "" { return nil, fmt.Errorf("tx %s is missing an httpPath", txName) } @@ -110,7 +110,7 @@ func NewTxParams() ([]TxParams, error) { } // Get tx type - txTypeStr := viper.GetString(txName+typeSuffix) + txTypeStr := viper.GetString(txName + typeSuffix) if txTypeStr == "" { return nil, fmt.Errorf("need tx type for tx %s", txName) } @@ -120,20 +120,20 @@ func NewTxParams() ([]TxParams, error) { } // Get basic fields - toStr := viper.GetString(txName+toSuffix) + toStr := viper.GetString(txName + toSuffix) var toAddr *common.Address if toStr != "" { to := common.HexToAddress(toStr) toAddr = &to } - amountStr := viper.GetString(txName+amountSuffix) + amountStr := viper.GetString(txName + amountSuffix) amount := new(big.Int) if amountStr != "" { if _, ok := amount.SetString(amountStr, 10); !ok { return nil, fmt.Errorf("amount (%s) for tx %s is not valid", amountStr, txName) } } - gasPriceStr := viper.GetString(txName+gasPriceSuffix) + gasPriceStr := viper.GetString(txName + gasPriceSuffix) var gasPrice *big.Int if gasPriceStr != "" { gasPrice = new(big.Int) @@ -141,15 +141,15 @@ func NewTxParams() ([]TxParams, error) { return nil, fmt.Errorf("gasPrice (%s) for tx %s is not valid", gasPriceStr, txName) } } - gasLimit := viper.GetUint64(txName+gasLimitSuffix) - hex := viper.GetString(txName+dataSuffix) + gasLimit := viper.GetUint64(txName + gasLimitSuffix) + hex := viper.GetString(txName + dataSuffix) data := make([]byte, 0) if hex != "" { data = common.Hex2Bytes(hex) } // Load or generate sender key - senderKeyPath := viper.GetString(txName+senderKeyPathSuffix) + senderKeyPath := viper.GetString(txName + senderKeyPathSuffix) var key *ecdsa.PrivateKey if senderKeyPath != "" { key, err = crypto.LoadECDSA(senderKeyPath) @@ -161,7 +161,7 @@ func NewTxParams() ([]TxParams, error) { if err != nil { return nil, fmt.Errorf("unable to generate ecdsa key for tx %s", txName) } - writePath := viper.GetString(txName+writeSenderPathSuffix) + writePath := viper.GetString(txName + writeSenderPathSuffix) if writePath != "" { if err := crypto.SaveECDSA(writePath, key); err != nil { return nil, err @@ -170,25 +170,25 @@ func NewTxParams() ([]TxParams, error) { } // Attempt to load Optimism fields - l1SenderStr := viper.GetString(txName+l1SenderSuffix) + l1SenderStr := viper.GetString(txName + l1SenderSuffix) var l1Sender *common.Address if l1SenderStr != "" { sender := common.HexToAddress(l1SenderStr) l1Sender = &sender } - l1RollupTxId := viper.GetUint64(txName+l1RollupTxIdSuffix) - sigHashType := viper.GetUint(txName+sigHashTypeSuffix) + l1RollupTxId := viper.GetUint64(txName + l1RollupTxIdSuffix) + sigHashType := viper.GetUint(txName + sigHashTypeSuffix) // If gasPrice was empty, attempt to load EIP1559 fields var feeCap, gasPremium *big.Int if gasPrice == nil { - feeCapStr := viper.GetString(txName+feeCapSuffix) - gasPremiumString := viper.GetString(txName+gasPremiumSuffix) + feeCapStr := viper.GetString(txName + feeCapSuffix) + gasPremiumString := viper.GetString(txName + gasPremiumSuffix) if feeCapStr == "" { return nil, fmt.Errorf("tx %s is missing feeCapStr", txName) } - if gasPremiumString == "" { + if gasPremiumString == "" { return nil, fmt.Errorf("tx %s is missing gasPremiumStr", txName) } feeCap = new(big.Int) @@ -202,25 +202,25 @@ func NewTxParams() ([]TxParams, error) { } // Load the sending paramas - frequency := viper.GetDuration(txName+frequencySuffix) - totalNumber := viper.GetUint64(txName+totalNumberSuffix) + frequency := viper.GetDuration(txName + frequencySuffix) + totalNumber := viper.GetUint64(txName + totalNumberSuffix) txParams[i] = TxParams{ - Client: rpcClient, - Type: txType, - Name: txName, - To: toAddr, - Amount: amount, - GasLimit: gasLimit, - GasPrice: gasPrice, - GasPremium: gasPremium, - FeeCap: feeCap, - Data: data, + Client: rpcClient, + Type: txType, + Name: txName, + To: toAddr, + Amount: amount, + GasLimit: gasLimit, + GasPrice: gasPrice, + GasPremium: gasPremium, + FeeCap: feeCap, + Data: data, L1SenderAddr: l1Sender, L1RollupTxId: l1RollupTxId, - SigHashType: uint8(sigHashType), - Frequency: frequency, - TotalNumber: totalNumber, + SigHashType: uint8(sigHashType), + Frequency: frequency, + TotalNumber: totalNumber, } } return txParams, nil diff --git a/pkg/sender.go b/pkg/sender.go index 3c1bd25..4547d1b 100644 --- a/pkg/sender.go +++ b/pkg/sender.go @@ -19,58 +19,10 @@ package tx_spammer import ( "context" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" ) -// SendTxArgs represents the arguments to submit a transaction -type SendTxArgs struct { - From common.MixedcaseAddress `json:"from"` - To *common.MixedcaseAddress `json:"to"` - Gas hexutil.Uint64 `json:"gas"` - GasPrice hexutil.Big `json:"gasPrice"` - Value hexutil.Big `json:"value"` - Nonce hexutil.Uint64 `json:"nonce"` - // We accept "data" and "input" for backwards-compatibility reasons. - Data *hexutil.Bytes `json:"data"` - Input *hexutil.Bytes `json:"input,omitempty"` -} - -/* -// SendTransaction creates a transaction for the given argument, sign it and submit it to the -// transaction pool. -func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) { - // Look up the wallet containing the requested signer - account := accounts.Account{Address: args.From} - - wallet, err := s.b.AccountManager().Find(account) - if err != nil { - return common.Hash{}, err - } - - if args.Nonce == nil { - // Hold the addresse's mutex around signing to prevent concurrent assignment of - // the same nonce to multiple accounts. - s.nonceLock.LockAddr(args.From) - defer s.nonceLock.UnlockAddr(args.From) - } - - // Set some sanity defaults and terminate on failure - if err := args.setDefaults(ctx, s.b); err != nil { - return common.Hash{}, err - } - // Assemble the transaction and sign with the wallet - tx := args.toTransaction() - - signed, err := wallet.SignTx(account, tx, s.b.ChainConfig().ChainID) - if err != nil { - return common.Hash{}, err - } - return SubmitTransaction(ctx, s.b, signed) -} - */ - type TxSender struct { TxGen *TxGenerator } @@ -80,10 +32,15 @@ func NewTxSender(params []TxParams) *TxSender { TxGen: NewTxGenerator(params), } } -func (s *TxSender) Send() <-chan error { +func (s *TxSender) Send(quitChan <-chan bool) <-chan error { errChan := make(chan error) go func() { for s.TxGen.Next() { + select { + case <-quitChan: + return + default: + } if err := sendRawTransaction(s.TxGen.Current()); err != nil { errChan <- err } @@ -92,8 +49,9 @@ func (s *TxSender) Send() <-chan error { errChan <- s.TxGen.Error() } }() + return errChan } func sendRawTransaction(rpcClient *rpc.Client, txRlp []byte) error { return rpcClient.CallContext(context.Background(), nil, "eth_sendRawTransaction", hexutil.Encode(txRlp)) -} \ No newline at end of file +} diff --git a/pkg/service.go b/pkg/service.go index 380c257..10b2564 100644 --- a/pkg/service.go +++ b/pkg/service.go @@ -16,19 +16,39 @@ package tx_spammer -import "sync" +import ( + "sync" + + "github.com/sirupsen/logrus" +) type Service interface { - Loop(wg *sync.WaitGroup) error + Loop(wg *sync.WaitGroup, quitChan <-chan bool) } -type Tx struct { - Spammer *Sender - Generator *TxGenerator - Config *Config +type Spammer struct { + Sender *TxSender } -func NewTxSpammer(params []TxParams) (TxSpammer, error) { - - return &txSpammer{}, nil +func NewTxSpammer(params []TxParams) Service { + return &Spammer{ + Sender: NewTxSender(params), + } +} + +func (s *Spammer) Loop(wg *sync.WaitGroup, quitChan <-chan bool) { + forwardQuit := make(chan bool) + errChan := s.Sender.Send(forwardQuit) + go func() { + wg.Add(1) + defer wg.Done() + for { + select { + case err := <-errChan: + logrus.Error(err) + case forwardQuit <- <-quitChan: + return + } + } + }() } diff --git a/pkg/touch b/pkg/touch deleted file mode 100644 index e69de29..0000000 diff --git a/pkg/tx_generator.go b/pkg/tx_generator.go index b81a3f4..169db1c 100644 --- a/pkg/tx_generator.go +++ b/pkg/tx_generator.go @@ -20,10 +20,10 @@ import "github.com/ethereum/go-ethereum/rpc" // TxGenerator generates and signs txs type TxGenerator struct { - TxParams []TxParams - currentTx []byte + TxParams []TxParams + currentTx []byte currentClient *rpc.Client - err error + err error } func NewTxGenerator(params []TxParams) *TxGenerator { @@ -46,4 +46,4 @@ func (gen TxGenerator) Error() error { func (gen TxGenerator) gen(params TxParams) ([]byte, error) { return nil, nil -} \ No newline at end of file +} diff --git a/pkg/tx_type.go b/pkg/tx_type.go index 860d60a..9b0ec61 100644 --- a/pkg/tx_type.go +++ b/pkg/tx_type.go @@ -45,4 +45,4 @@ func TxTypeFromString(str string) (TxType, error) { default: return Unkown, fmt.Errorf("unsupported tx type: %s", str) } -} \ No newline at end of file +} diff --git a/pkg/util.go b/pkg/util.go index 6dc418a..0a3d813 100644 --- a/pkg/util.go +++ b/pkg/util.go @@ -1,10 +1,27 @@ +// VulcanizeDB +// Copyright © 2020 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// 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. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package tx_spammer import ( "fmt" + "math/big" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" - "math/big" ) // ChainConfig returns the appropriate ethereum chain config for the provided chain id @@ -16,9 +33,8 @@ func ChainConfig(chainID uint64) (*params.ChainConfig, error) { return params.TestnetChainConfig, nil // Ropsten case 4: return params.RinkebyChainConfig, nil - case 5: + case 5, 420: return params.GoerliChainConfig, nil - case 420: default: return nil, fmt.Errorf("chain config for chainid %d not available", chainID) } @@ -27,17 +43,11 @@ func ChainConfig(chainID uint64) (*params.ChainConfig, error) { // ChainConfig returns the appropriate ethereum chain config for the provided chain id func TxSigner(chainID uint64) (types.Signer, error) { switch chainID { - case 1: - return params.MainnetChainConfig, nil - case 3: - return params.TestnetChainConfig, nil // Ropsten - case 4: - return params.RinkebyChainConfig, nil - case 5: - return params.GoerliChainConfig, nil + case 1, 3, 4, 5: + return types.NewEIP155Signer(new(big.Int).SetUint64(chainID)), nil case 420: - return types.NewOVMSigner(big.NewInt()), nil + return types.NewOVMSigner(new(big.Int).SetUint64(chainID)), nil default: return nil, fmt.Errorf("chain config for chainid %d not available", chainID) } -} \ No newline at end of file +}