finalize support for l1 and l2

This commit is contained in:
Ian Norden 2020-10-15 22:10:18 -05:00
parent e441254891
commit 54de12e12e
8 changed files with 201 additions and 133 deletions

View File

@ -27,9 +27,10 @@ import (
// sendTxsCmd represents the sendTxs command // sendTxsCmd represents the sendTxs command
var sendTxsCmd = &cobra.Command{ var sendTxsCmd = &cobra.Command{
Use: "sendTxs", Use: "sendTxs",
Short: "send large volumes of different tx types to different nodes", Short: "Send large volumes of different tx types to different nodes for testing purposes",
Long: `Loads tx configuration from .toml config file Long: `Loads tx configuration from .toml config file
Generates txs from configuration and sends them to designated node according to set frequency and number`, Generates txs from configuration and sends them to designated node according to set frequency and number
Support standard, optimism L2, optimism L1 to L2, and EIP1559 transactions`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
sendTxs() sendTxs()
}, },
@ -45,10 +46,12 @@ func sendTxs() {
quitChan := make(chan bool) quitChan := make(chan bool)
txSpammer.Loop(wg, quitChan) txSpammer.Loop(wg, quitChan)
shutdown := make(chan os.Signal) go func() {
signal.Notify(shutdown, os.Interrupt) shutdown := make(chan os.Signal)
<-shutdown signal.Notify(shutdown, os.Interrupt)
close(quitChan) <-shutdown
close(quitChan)
}()
wg.Wait() wg.Wait()
} }

View File

@ -4,6 +4,7 @@
[L2ContractDeployment] [L2ContractDeployment]
type = "L2" type = "L2"
httpPath = "" httpPath = ""
chainID = 420
to = "" to = ""
amount = "0" amount = "0"
gasLimit = 0 gasLimit = 0
@ -13,11 +14,16 @@
writeSenderPath = "" writeSenderPath = ""
frequency = 1 frequency = 1
totalNumber = 1 totalNumber = 1
chainID = 420 delay = 0
l1Sender = ""
l1RollupTxId = 0
sigHashType = 0
queueOrigin = 0
[L2ContractPutCall] [L2ContractPutCall]
type = "L2" type = "L2"
httpPath = "" httpPath = ""
chainID = 420
to = "" to = ""
amount = "0" amount = "0"
gasLimit = 0 gasLimit = 0
@ -26,54 +32,17 @@
senderKeyPath = "" senderKeyPath = ""
writeSenderPath = "" writeSenderPath = ""
frequency = 15 frequency = 15
totalNumber = 1500 totalNumber = 1
chainID = 420 delay = 60
l1Sender = ""
l1RollupTxId = 0
sigHashType = 0
queueOrigin = 0
[L2ContractGetCall] [L2ContractGetCall]
type = "L2" type = "L2"
httpPath = "" httpPath = ""
to = ""
amount = "0"
gasLimit = 0
gasPrice = "0"
data = ""
senderKeyPath = ""
writeSenderPath = ""
frequency = 15
totalNumber = 1500
chainID = 420 chainID = 420
[GethTx]
type = "geth"
httpPath = ""
to = ""
amount = "0"
gasLimit = 0
gasPrice = "0"
data = ""
senderKeyPath = ""
writeSenderPath = ""
frequency = 0
totalNumber = 0
chainID = 1
[EIP1559]
type = "1559"
httpPath = ""
to = ""
amount = "0"
gasLimit = 0
gasPremium = "0"
feeCap = "0"
data = ""
senderKeyPath = ""
writeSenderPath = ""
frequency = 0
totalNumber = 0
[L1ToL2]
type = "L2"
httpPath = ""
to = "" to = ""
amount = "0" amount = "0"
gasLimit = 0 gasLimit = 0
@ -81,11 +50,13 @@
data = "" data = ""
senderKeyPath = "" senderKeyPath = ""
writeSenderPath = "" writeSenderPath = ""
frequency = 60
totalNumber = 2
delay = 30
l1Sender = "" l1Sender = ""
l1RollupTxId = 0 l1RollupTxId = 0
sigHashType = 0 sigHashType = 0
frequency = 0 queueOrigin = 0
totalNumber = 0
[log] [log]
level = "info" level = "info"

View File

@ -24,6 +24,8 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -39,6 +41,9 @@ type TxParams struct {
// Type of the tx // Type of the tx
Type TxType Type TxType
// Chain ID
ChainID uint64
// Universal tx fields // Universal tx fields
To *common.Address To *common.Address
GasLimit uint64 GasLimit uint64
@ -49,23 +54,25 @@ type TxParams struct {
// Optimism-specific metadata fields // Optimism-specific metadata fields
L1SenderAddr *common.Address L1SenderAddr *common.Address
L1RollupTxId uint64 L1RollupTxId *hexutil.Uint64
SigHashType uint8 SigHashType types.SignatureHashType
QueueOrigin types.QueueOrigin
// EIP1559-specific fields // EIP1559-specific fields
GasPremium *big.Int GasPremium *big.Int
FeeCap *big.Int FeeCap *big.Int
// Sender key, if left the senderKeyPath is empty we generate a new key // Sender key, if left the senderKeyPath is empty we generate a new key
SenderKey *ecdsa.PrivateKey SenderKey *ecdsa.PrivateKey
StartingNonce uint64
// Sending params // Sending params
// How often we send a tx of this type // How often we send a tx of this type
Frequency time.Duration Frequency time.Duration
// Total number of txs of this type to send // Total number of txs of this type to send
TotalNumber uint64 TotalNumber uint64
// Txs of different types will be sent according to their order (starting at 0) // Delay before beginning to send
Order uint64 Delay time.Duration
} }
const ( const (
@ -87,6 +94,10 @@ const (
sigHashTypeSuffix = ".sigHashType" sigHashTypeSuffix = ".sigHashType"
frequencySuffix = ".frequency" frequencySuffix = ".frequency"
totalNumberSuffix = ".totalNumber" totalNumberSuffix = ".totalNumber"
delaySuffix = ".delay"
startingNonceSuffix = ".startingNonce"
queueOriginSuffix = ".queueOrigin"
chainIDSuffix = ".chainID"
) )
// NewConfig returns a new tx spammer config // NewConfig returns a new tx spammer config
@ -109,7 +120,7 @@ func NewTxParams() ([]TxParams, error) {
return nil, err return nil, err
} }
// Get tx type // Get tx type and chain id
txTypeStr := viper.GetString(txName + typeSuffix) txTypeStr := viper.GetString(txName + typeSuffix)
if txTypeStr == "" { if txTypeStr == "" {
return nil, fmt.Errorf("need tx type for tx %s", txName) return nil, fmt.Errorf("need tx type for tx %s", txName)
@ -118,6 +129,7 @@ func NewTxParams() ([]TxParams, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
chainID := viper.GetUint64(txName + chainIDSuffix)
// Get basic fields // Get basic fields
toStr := viper.GetString(txName + toSuffix) toStr := viper.GetString(txName + toSuffix)
@ -176,9 +188,10 @@ func NewTxParams() ([]TxParams, error) {
sender := common.HexToAddress(l1SenderStr) sender := common.HexToAddress(l1SenderStr)
l1Sender = &sender l1Sender = &sender
} }
l1RollupTxId := viper.GetUint64(txName + l1RollupTxIdSuffix) l1RollupTxId := viper.GetUint64(txName + l1RollupTxIdSuffix)
l1rtid := (hexutil.Uint64)(l1RollupTxId)
sigHashType := viper.GetUint(txName + sigHashTypeSuffix) sigHashType := viper.GetUint(txName + sigHashTypeSuffix)
queueOrigin := viper.GetInt64(txName + queueOriginSuffix)
// If gasPrice was empty, attempt to load EIP1559 fields // If gasPrice was empty, attempt to load EIP1559 fields
var feeCap, gasPremium *big.Int var feeCap, gasPremium *big.Int
@ -201,26 +214,32 @@ func NewTxParams() ([]TxParams, error) {
} }
} }
// Load the sending paramas // Load starting nonce and sending params
startingNonce := viper.GetUint64(txName + startingNonceSuffix)
frequency := viper.GetDuration(txName + frequencySuffix) frequency := viper.GetDuration(txName + frequencySuffix)
totalNumber := viper.GetUint64(txName + totalNumberSuffix) totalNumber := viper.GetUint64(txName + totalNumberSuffix)
delay := viper.GetDuration(txName + delaySuffix)
txParams[i] = TxParams{ txParams[i] = TxParams{
Client: rpcClient, Client: rpcClient,
Type: txType, Type: txType,
Name: txName, Name: txName,
To: toAddr, To: toAddr,
Amount: amount, Amount: amount,
GasLimit: gasLimit, GasLimit: gasLimit,
GasPrice: gasPrice, GasPrice: gasPrice,
GasPremium: gasPremium, GasPremium: gasPremium,
FeeCap: feeCap, FeeCap: feeCap,
Data: data, Data: data,
L1SenderAddr: l1Sender, L1SenderAddr: l1Sender,
L1RollupTxId: l1RollupTxId, L1RollupTxId: &l1rtid,
SigHashType: uint8(sigHashType), SigHashType: (types.SignatureHashType)(uint8(sigHashType)),
Frequency: frequency, Frequency: frequency,
TotalNumber: totalNumber, TotalNumber: totalNumber,
Delay: delay,
StartingNonce: startingNonce,
QueueOrigin: (types.QueueOrigin)(queueOrigin),
ChainID: chainID,
} }
} }
return txParams, nil return txParams, nil

View File

@ -18,40 +18,78 @@ package tx_spammer
import ( import (
"context" "context"
"fmt"
"sync"
"time"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/sirupsen/logrus"
) )
// TxSender type for
type TxSender struct { type TxSender struct {
TxGen *TxGenerator TxGen *TxGenerator
TxParams []TxParams
} }
// NewTxSender returns a new tx sender
func NewTxSender(params []TxParams) *TxSender { func NewTxSender(params []TxParams) *TxSender {
return &TxSender{ return &TxSender{
TxGen: NewTxGenerator(params), TxGen: NewTxGenerator(params),
TxParams: params,
} }
} }
func (s *TxSender) Send(quitChan <-chan bool) <-chan error {
func (s *TxSender) Send(quitChan <-chan bool) (<-chan bool, <-chan error) {
// done channel to signal completion of all jobs
doneChan := make(chan bool)
// err channel returned to calling context
errChan := make(chan error) errChan := make(chan error)
go func() { // for each tx param set, spin up a goroutine to generate and send the tx at the specified delay and frequency
for s.TxGen.Next() { wg := new(sync.WaitGroup)
select { for _, txParams := range s.TxParams {
case <-quitChan: wg.Add(1)
go func(p TxParams) {
defer wg.Done()
// send the first tx after the delay
timer := time.NewTimer(p.Delay)
<-timer.C
if err := s.genAndSend(p); err != nil {
errChan <- fmt.Errorf("tx %s initial genAndSend error: %v", p.Name, err)
return return
default:
} }
if err := sendRawTransaction(s.TxGen.Current()); err != nil { // send any remaining ones at the provided frequency, also check for quit signal
errChan <- err ticker := time.NewTicker(p.Frequency)
for i := uint64(1); i < p.TotalNumber; i++ {
select {
case <-ticker.C:
if err := s.genAndSend(p); err != nil {
errChan <- fmt.Errorf("tx %s number %d genAndSend error: %v", p.Name, i, err)
return
}
case <-quitChan:
return
}
} }
} }(txParams)
if s.TxGen.Error() != nil { }
errChan <- s.TxGen.Error() go func() {
} wg.Wait()
close(doneChan)
}() }()
return errChan return doneChan, errChan
} }
func sendRawTransaction(rpcClient *rpc.Client, txRlp []byte) error { func (s *TxSender) genAndSend(p TxParams) error {
tx, err := s.TxGen.GenerateTx(p)
if err != nil {
return err
}
return sendRawTransaction(p.Client, tx, p.Name)
}
func sendRawTransaction(rpcClient *rpc.Client, txRlp []byte, name string) error {
logrus.Infof("sending tx %s", name)
return rpcClient.CallContext(context.Background(), nil, "eth_sendRawTransaction", hexutil.Encode(txRlp)) return rpcClient.CallContext(context.Background(), nil, "eth_sendRawTransaction", hexutil.Encode(txRlp))
} }

View File

@ -38,9 +38,9 @@ func NewTxSpammer(params []TxParams) Service {
func (s *Spammer) Loop(wg *sync.WaitGroup, quitChan <-chan bool) { func (s *Spammer) Loop(wg *sync.WaitGroup, quitChan <-chan bool) {
forwardQuit := make(chan bool) forwardQuit := make(chan bool)
errChan := s.Sender.Send(forwardQuit) doneChan, errChan := s.Sender.Send(forwardQuit)
wg.Add(1)
go func() { go func() {
wg.Add(1)
defer wg.Done() defer wg.Done()
for { for {
select { select {
@ -48,6 +48,8 @@ func (s *Spammer) Loop(wg *sync.WaitGroup, quitChan <-chan bool) {
logrus.Error(err) logrus.Error(err)
case forwardQuit <- <-quitChan: case forwardQuit <- <-quitChan:
return return
case <-doneChan:
return
} }
} }
}() }()

View File

@ -16,34 +16,71 @@
package tx_spammer package tx_spammer
import "github.com/ethereum/go-ethereum/rpc" import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
)
// TxGenerator generates and signs txs // TxGenerator generates and signs txs
type TxGenerator struct { type TxGenerator struct {
TxParams []TxParams // keep track of account nonces locally so we aren't spamming to determine the nonce
currentTx []byte // this assumes these accounts are not sending txs outside this process
currentClient *rpc.Client nonces map[common.Address]uint64
err error
} }
// NewTxGenerator creates a new tx generator
func NewTxGenerator(params []TxParams) *TxGenerator { func NewTxGenerator(params []TxParams) *TxGenerator {
nonces := make(map[common.Address]uint64)
for _, p := range params {
nonces[p.Sender] = p.StartingNonce
}
return &TxGenerator{ return &TxGenerator{
TxParams: params, nonces: nonces,
} }
} }
func (gen TxGenerator) Next() bool { // GenerateTx generates tx from the provided params
return false func (tg TxGenerator) GenerateTx(params TxParams) ([]byte, error) {
} tx := make([]byte, 0)
switch params.Type {
func (gen TxGenerator) Current() (*rpc.Client, []byte) { case Standard, OptimismL1ToL2, OptimismL2:
return gen.currentClient, gen.currentTx return tg.gen(params)
} case EIP1559:
return tg.gen1559(params)
func (gen TxGenerator) Error() error { default:
return gen.err return nil, fmt.Errorf("unsupported tx type: %s", params.Type.String())
}
return tx, nil
} }
func (gen TxGenerator) gen(params TxParams) ([]byte, error) { func (gen TxGenerator) gen(params TxParams) ([]byte, error) {
return nil, nil nonce := gen.nonces[params.Sender]
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)
} else {
tx = types.NewTransaction(nonce, *params.To, params.Amount, params.GasLimit, params.GasPrice, params.Data, params.L1SenderAddr, params.L1RollupTxId, params.QueueOrigin, params.SigHashType)
}
signer, err := TxSigner(params.Type, params.ChainID)
if err != nil {
return nil, err
}
signedTx, err := types.SignTx(tx, signer, params.SenderKey)
if err != nil {
return nil, err
}
txRlp, err := rlp.EncodeToBytes(signedTx)
if err != nil {
return nil, err
}
gen.nonces[params.Sender]++
return txRlp, nil
}
func (gen TxGenerator) gen1559(params TxParams) ([]byte, error) {
// TODO: support EIP1559; new to make a new major version, vendor it, or release with different pkg name so that we can import both optimism and eip1559 geth
return nil, fmt.Errorf("1559 support not yet available")
} }

View File

@ -24,13 +24,28 @@ import (
type TxType int type TxType int
const ( const (
Unkown TxType = iota Unsupported TxType = iota
Standard Standard
OptimismL2 OptimismL2
OptimismL1ToL2 OptimismL1ToL2
EIP1559 EIP1559
) )
func (tt TxType) String() string {
switch tt {
case Standard:
return "Standard"
case OptimismL2:
return "L2"
case OptimismL1ToL2:
return "L1toL2"
case EIP1559:
return "EIP1559"
default:
return "Unsupported"
}
}
// TxTypeFromString returns the tx enum type from provided string // TxTypeFromString returns the tx enum type from provided string
func TxTypeFromString(str string) (TxType, error) { func TxTypeFromString(str string) (TxType, error) {
switch strings.ToLower(str) { switch strings.ToLower(str) {
@ -43,6 +58,6 @@ func TxTypeFromString(str string) (TxType, error) {
case "eip1559": case "eip1559":
return EIP1559, nil return EIP1559, nil
default: default:
return Unkown, fmt.Errorf("unsupported tx type: %s", str) return Unsupported, fmt.Errorf("unsupported tx type: %s", str)
} }
} }

View File

@ -21,31 +21,14 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
) )
// ChainConfig returns the appropriate ethereum chain config for the provided chain id // ChainConfig returns the appropriate ethereum chain config for the provided chain id
func ChainConfig(chainID uint64) (*params.ChainConfig, error) { func TxSigner(kind TxType, chainID uint64) (types.Signer, error) {
switch chainID { switch kind {
case 1: case Standard, EIP1559:
return params.MainnetChainConfig, nil
case 3:
return params.TestnetChainConfig, nil // Ropsten
case 4:
return params.RinkebyChainConfig, nil
case 5, 420:
return params.GoerliChainConfig, nil
default:
return nil, fmt.Errorf("chain config for chainid %d not available", chainID)
}
}
// ChainConfig returns the appropriate ethereum chain config for the provided chain id
func TxSigner(chainID uint64) (types.Signer, error) {
switch chainID {
case 1, 3, 4, 5:
return types.NewEIP155Signer(new(big.Int).SetUint64(chainID)), nil return types.NewEIP155Signer(new(big.Int).SetUint64(chainID)), nil
case 420: case OptimismL2, OptimismL1ToL2:
return types.NewOVMSigner(new(big.Int).SetUint64(chainID)), nil return types.NewOVMSigner(new(big.Int).SetUint64(chainID)), nil
default: default:
return nil, fmt.Errorf("chain config for chainid %d not available", chainID) return nil, fmt.Errorf("chain config for chainid %d not available", chainID)