send blob txs

This commit is contained in:
Roy Crihfield 2024-05-18 23:08:53 +08:00
parent 8d565a9340
commit 4003a37ffb
10 changed files with 277 additions and 33 deletions

View File

@ -21,6 +21,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/cerc-io/tx-spammer/pkg/auto"
)
@ -43,6 +44,7 @@ func autoSend() {
if err != nil {
logWithCommand.Fatal(err)
}
logrus.WithFields(logrus.Fields{"config": config}).Debug("Loaded config")
txSpammer := auto.NewTxSpammer(config)
quitChan := make(chan bool)
doneChan, err := txSpammer.Loop(quitChan)
@ -61,4 +63,4 @@ func autoSend() {
func init() {
rootCmd.AddCommand(autoSendCmd)
}
}

View File

@ -71,7 +71,7 @@ func logLevel() error {
return err
}
log.SetLevel(lvl)
if lvl > log.InfoLevel {
if lvl > log.DebugLevel {
log.SetReportCaller(true)
}
log.Info("Log level set to ", lvl.String())

View File

@ -28,3 +28,13 @@
gasLimit = 21000 # gasLimit to use for the eth transfer txs - env: $ETH_SEND_GAS_LIMIT
gasTipCap = "1000000000" # gasTipCap to use for the eth transfer txs - env: $ETH_SEND_GAS_TIP_CAP
gasFeeCap = "1000000007" # gasFeeCap to use for the eth transfer txs - env: $ETH_SEND_GAS_FEE_CAP
[blobSpammer]
frequency = 500 # how often to send a transaction (in milliseconds, -1 for no delay) - env: $ETH_SEND_FREQ
totalNumber = -1 # total number of transactions to send (per sender, -1 for unlimited) - env: $ETH_SEND_TOTAL_NUMBER
amount = "10000" # amount of wei (1x10^-18 ETH) to send in each tx (be mindful of the genesis allocations) - env: $ETH_SEND_AMOUNT
gasLimit = 21000 # gasLimit to use for the blob txs - env: $ETH_SEND_GAS_LIMIT
gasTipCap = "1000000000" # gasTipCap to use for the blob txs - env: $ETH_SEND_GAS_TIP_CAP
gasFeeCap = "1000000007" # gasFeeCap to use for the blob txs - env: $ETH_SEND_GAS_FEE_CAP
blobCount = 1 # number of blob sidecars to send with blob txs - env: $ETH_SEND_BLOB_LENGTH
blobFeeCap = "1000000" # blobFeeCap to use for the eth blob txs - env: $ETH_SEND_BLOB_FEE_CAP

View File

@ -29,6 +29,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/holiman/uint256"
"github.com/cerc-io/tx-spammer/pkg/shared"
"github.com/ethereum/go-ethereum/accounts/abi"
@ -53,7 +54,6 @@ type Config struct {
// HTTP client for sending transactions
RpcClient *rpc.Client
EthClient *ethclient.Client
ChainID *big.Int
// Key pairs for the accounts we will use to deploy contracts and send txs
SenderKeys []*ecdsa.PrivateKey
@ -70,6 +70,9 @@ type Config struct {
// Configuration for the eth transfer txs
SendConfig *SendConfig
// Configuration for the blob txs
BlobTxConfig *BlobTxConfig
}
// DeploymentConfig holds the parameters for the contract deployment contracts
@ -111,6 +114,21 @@ type SendConfig struct {
TotalNumber int
}
// SendConfig holds the parameters for the blob txs
type BlobTxConfig struct {
ChainID *big.Int
GasLimit uint64
GasFeeCap *big.Int
GasTipCap *big.Int
Amount *big.Int
BlobFeeCap *uint256.Int
BlobCount int
Frequency time.Duration
TotalNumber int
}
func NewConfig() (*Config, error) {
// Initialize rpc client
httpPathStr := viper.GetString(ethHttpPath)
@ -181,6 +199,12 @@ func NewConfig() (*Config, error) {
return nil, err
}
// Load blob tx config
blobTxConfig, err := NewBlobTxConfig(chainID)
if err != nil {
return nil, err
}
// Assemble and return
return &Config{
RpcClient: rpcClient,
@ -191,6 +215,7 @@ func NewConfig() (*Config, error) {
DeploymentConfig: deploymentConfig,
CallConfig: callConfig,
SendConfig: sendConfig,
BlobTxConfig: blobTxConfig,
}, nil
}
@ -292,3 +317,37 @@ func NewSendConfig(chainID *big.Int) (*SendConfig, error) {
TotalNumber: totalNumber,
}, nil
}
// NewBlobTxConfig constructs and returns a new SendConfig
func NewBlobTxConfig(chainID *big.Int) (*BlobTxConfig, error) {
amountStr := viper.GetString(ethBlobTxAmount)
amount, ok := new(big.Int).SetString(amountStr, 10)
if !ok {
return nil, fmt.Errorf("unable to convert amount string (%s) into big.Int", amountStr)
}
var frequency time.Duration
tmpFreq := viper.GetDuration(ethBlobTxFrequency)
if tmpFreq <= 0 {
frequency = time.Microsecond
} else {
frequency = tmpFreq * time.Millisecond
}
totalNumber := viper.GetInt(ethBlobTxTotalNumber)
if totalNumber < 0 {
totalNumber = math.MaxInt
}
return &BlobTxConfig{
ChainID: chainID,
Frequency: frequency,
Amount: amount,
GasLimit: viper.GetUint64(ethBlobTxGasLimit),
GasFeeCap: big.NewInt(viper.GetInt64(ethBlobTxGasFeeCap)),
GasTipCap: big.NewInt(viper.GetInt64(ethBlobTxGasTipCap)),
BlobFeeCap: uint256.MustFromBig(big.NewInt(viper.GetInt64(ethBlobTxBlobFeeCap))),
BlobCount: viper.GetInt(ethBlobTxBlobCount),
TotalNumber: totalNumber,
}, nil
}

View File

@ -60,7 +60,7 @@ func (cp *ContractDeployer) Deploy() ([]common.Address, error) {
<-ticker.C
for i, key := range cp.senderKeys {
logrus.Debugf("Generating contract deployment for %s.", cp.senderAddrs[i].Hex())
signedTx, contractAddr, err := cp.txGenerator.GenerateTx(&GenParams{
signedTx, contractAddr, err := cp.txGenerator.createTx(&GenParams{
ChainID: cp.config.ChainID,
Sender: cp.senderAddrs[i],
SenderKey: key,

View File

@ -20,8 +20,9 @@ import "github.com/spf13/viper"
const (
// env variables
ETH_KEY_DIR_PATH = "ETH_KEY_DIR_PATH"
ETH_HTTP_PATH = "ETH_HTTP_PATH"
ETH_KEY_DIR_PATH = "ETH_KEY_DIR_PATH"
ETH_HTTP_PATH = "ETH_HTTP_PATH"
ETH_CONTRACT_ADDR_PATH = "ETH_CONTRACT_ADDR_PATH"
ETH_DEPLOYMENT_NUMBER = "ETH_DEPLOYMENT_NUMBER"
ETH_DEPLOYMENT_BIN_PATH = "ETH_DEPLOYMENT_BIN_PATH"
@ -44,9 +45,19 @@ const (
ETH_SEND_GAS_FEE_CAP = "ETH_SEND_GAS_FEE_CAP"
ETH_SEND_GAS_TIP_CAP = "ETH_SEND_GAS_TIP_CAP"
ETH_BLOBTX_FREQ = "ETH_BLOBTX_FREQ"
ETH_BLOBTX_TOTAL_NUMBER = "ETH_BLOBTX_TOTAL_NUMBER"
ETH_BLOBTX_AMOUNT = "ETH_BLOBTX_AMOUNT"
ETH_BLOBTX_GAS_LIMIT = "ETH_BLOBTX_GAS_LIMIT"
ETH_BLOBTX_GAS_FEE_CAP = "ETH_BLOBTX_GAS_FEE_CAP"
ETH_BLOBTX_GAS_TIP_CAP = "ETH_BLOBTX_GAS_TIP_CAP"
ETH_BLOBTX_BLOB_FEE_CAP = "ETH_BLOBTX_BLOB_FEE_CAP"
ETH_BLOBTX_BLOB_COUNT = "ETH_BLOBTX_BLOB_COUNT"
// toml bindings
ethKeyDirPath = "eth.keyDirPath"
ethHttpPath = "eth.httpPath"
ethKeyDirPath = "eth.keyDirPath"
ethHttpPath = "eth.httpPath"
ethContractAddrPath = "eth.contractAddrPath"
ethDeploymentBinPath = "deployment.binPath"
ethDeploymentNumber = "deployment.number"
@ -68,11 +79,21 @@ const (
ethSendGasLimit = "sendSpammer.gasLimit"
ethSendGasFeeCap = "sendSpammer.gasFeeCap"
ethSendGasTipCap = "sendSpammer.gasTipCap"
ethBlobTxFrequency = "blobSpammer.frequency"
ethBlobTxTotalNumber = "blobSpammer.totalNumber"
ethBlobTxAmount = "blobSpammer.amount"
ethBlobTxGasLimit = "blobSpammer.gasLimit"
ethBlobTxGasFeeCap = "blobSpammer.gasFeeCap"
ethBlobTxGasTipCap = "blobSpammer.gasTipCap"
ethBlobTxBlobFeeCap = "blobSpammer.blobFeeCap"
ethBlobTxBlobCount = "blobSpammer.blobCount"
)
func bindEnv() {
viper.BindEnv(ethKeyDirPath, ETH_KEY_DIR_PATH)
viper.BindEnv(ethHttpPath, ETH_HTTP_PATH)
viper.BindEnv(ethContractAddrPath, ETH_CONTRACT_ADDR_PATH)
viper.BindEnv(ethDeploymentNumber, ETH_DEPLOYMENT_NUMBER)
viper.BindEnv(ethDeploymentBinPath, ETH_DEPLOYMENT_BIN_PATH)
@ -95,4 +116,13 @@ func bindEnv() {
viper.BindEnv(ethSendGasFeeCap, ETH_SEND_GAS_FEE_CAP)
viper.BindEnv(ethSendGasTipCap, ETH_SEND_GAS_TIP_CAP)
viper.BindEnv(ethSendGasLimit, ETH_CALL_GAS_LIMIT)
viper.BindEnv(ethBlobTxFrequency, ETH_BLOBTX_FREQ)
viper.BindEnv(ethBlobTxTotalNumber, ETH_BLOBTX_TOTAL_NUMBER)
viper.BindEnv(ethBlobTxAmount, ETH_BLOBTX_AMOUNT)
viper.BindEnv(ethBlobTxGasLimit, ETH_BLOBTX_GAS_LIMIT)
viper.BindEnv(ethBlobTxGasFeeCap, ETH_BLOBTX_GAS_FEE_CAP)
viper.BindEnv(ethBlobTxGasTipCap, ETH_BLOBTX_GAS_TIP_CAP)
viper.BindEnv(ethBlobTxBlobFeeCap, ETH_BLOBTX_BLOB_FEE_CAP)
viper.BindEnv(ethBlobTxBlobCount, ETH_BLOBTX_BLOB_COUNT)
}

View File

@ -17,10 +17,12 @@
package auto
import (
"fmt"
"github.com/cerc-io/tx-spammer/pkg/shared"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rpc"
"github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus"
)
// EthSender sends eth value transfer txs
@ -36,24 +38,25 @@ func NewEthSender(config *Config) *EthSender {
}
// Send awaits txs off the provided work queue and sends them
func (s *EthSender) Send(quitChan <-chan bool, txChan <-chan *types.Transaction, sentCh chan *types.Transaction) (<-chan bool, <-chan error) {
func (s *EthSender) Send(quitChan <-chan bool, txChan <-chan *types.Transaction, sentCh chan<- *types.Transaction) (<-chan bool, <-chan error) {
// err channel returned to calling context
errChan := make(chan error)
doneChan := make(chan bool)
go func() {
defer close(doneChan)
defer close(errChan)
counter := 0
for {
select {
case tx := <-txChan:
counter += 1
if err := shared.SendTransaction(s.client, tx); err != nil {
errChan <- err
errChan <- fmt.Errorf("%w: tx = %s", err, tx.Hash())
} else {
sentCh <- tx
}
case <-quitChan:
logrus.Infof("quitting Send loop (sent %d)", counter)
log.Infof("quitting Send loop (sent %d)", counter)
return
}
}

View File

@ -19,12 +19,15 @@ package auto
import (
"context"
"crypto/ecdsa"
"errors"
"math/big"
"math/rand"
"sync"
"time"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/holiman/uint256"
log "github.com/sirupsen/logrus"
"github.com/cerc-io/tx-spammer/pkg/shared"
@ -74,6 +77,14 @@ type GenParams struct {
To *common.Address
Amount *big.Int
Data []byte
BlobParams *BlobParams
}
type BlobParams struct {
BlobFeeCap *uint256.Int
BlobHashes []common.Hash
Sidecar *types.BlobTxSidecar
}
func (gen *TxGenerator) GenerateTxs(quitChan <-chan bool) (<-chan bool, <-chan *types.Transaction, <-chan error) {
@ -89,10 +100,22 @@ func (gen *TxGenerator) GenerateTxs(quitChan <-chan bool) (<-chan bool, <-chan *
wg.Add(1)
go gen.genCalls(wg, txChan, errChan, quitChan, sender, gen.config.SenderAddrs[i], gen.config.CallConfig)
}
if gen.config.BlobTxConfig.TotalNumber > 0 {
wg.Add(1)
go gen.genBlobTx(senderArgs{
wg: wg,
txChan: txChan,
errChan: errChan,
quitChan: quitChan,
senderKey: sender,
senderAddr: gen.config.SenderAddrs[i],
}, gen.config.BlobTxConfig)
}
}
doneChan := make(chan bool)
go func() {
wg.Wait()
close(errChan)
close(doneChan)
}()
return doneChan, txChan, errChan
@ -106,7 +129,7 @@ func (gen *TxGenerator) genSends(wg *sync.WaitGroup, txChan chan<- *types.Transa
case <-ticker.C:
dst := crypto.CreateAddress(receiverAddressSeed, uint64(i))
log.Debugf("Generating send from %s to %s.", senderAddr.Hex(), dst.Hex())
rawTx, _, err := gen.GenerateTx(&GenParams{
params := &GenParams{
ChainID: sendConfig.ChainID,
To: &dst,
Sender: senderAddr,
@ -115,7 +138,8 @@ func (gen *TxGenerator) genSends(wg *sync.WaitGroup, txChan chan<- *types.Transa
GasFeeCap: sendConfig.GasFeeCap,
GasTipCap: sendConfig.GasTipCap,
Amount: sendConfig.Amount,
})
}
rawTx, _, err := gen.createTx(params)
if err != nil {
errChan <- err
continue
@ -141,7 +165,7 @@ func (gen *TxGenerator) genCalls(wg *sync.WaitGroup, txChan chan<- *types.Transa
errChan <- err
continue
}
rawTx, _, err := gen.GenerateTx(&GenParams{
rawTx, _, err := gen.createTx(&GenParams{
Sender: senderAddr,
SenderKey: senderKey,
GasLimit: callConfig.GasLimit,
@ -162,13 +186,71 @@ func (gen *TxGenerator) genCalls(wg *sync.WaitGroup, txChan chan<- *types.Transa
log.Info("Done generating calls for ", senderAddr.Hex())
}
// GenerateTx generates tx from the provided params
func (gen *TxGenerator) GenerateTx(params *GenParams) (*types.Transaction, common.Address, error) {
type senderArgs struct {
wg *sync.WaitGroup
txChan chan<- *types.Transaction
errChan chan<- error
quitChan <-chan bool
senderKey *ecdsa.PrivateKey
senderAddr common.Address
}
func (gen *TxGenerator) genBlobTx(args senderArgs, blobTxConfig *BlobTxConfig) {
defer args.wg.Done()
ticker := time.NewTicker(blobTxConfig.Frequency)
for i := 0; i < blobTxConfig.TotalNumber; i++ {
select {
case <-ticker.C:
dst := crypto.CreateAddress(receiverAddressSeed, uint64(i))
log.Debugf("Generating send from %s to %s.", args.senderAddr, dst)
params := &GenParams{
ChainID: blobTxConfig.ChainID,
To: &dst,
Sender: args.senderAddr,
SenderKey: args.senderKey,
GasLimit: blobTxConfig.GasLimit,
GasFeeCap: blobTxConfig.GasFeeCap,
GasTipCap: blobTxConfig.GasTipCap,
Amount: blobTxConfig.Amount,
}
blobdata := make([]byte, blobTxConfig.BlobCount)
for i := range blobdata {
blobdata[i] = byte(i + 1)
}
sidecar, err := makeSidecar(blobdata)
if err != nil {
args.errChan <- err
continue
}
params.BlobParams = &BlobParams{
BlobFeeCap: blobTxConfig.BlobFeeCap,
BlobHashes: sidecar.BlobHashes(),
Sidecar: sidecar,
}
rawTx, _, err := gen.createTx(params)
if err != nil {
args.errChan <- err
continue
}
args.txChan <- rawTx
case <-args.quitChan:
return
}
}
log.Info("Done generating sends for ", args.senderAddr)
}
// createTx generates tx from the provided params
func (gen *TxGenerator) createTx(params *GenParams) (*types.Transaction, common.Address, error) {
nonce := gen.claimNonce(params.Sender)
tx := new(types.Transaction)
var contractAddr common.Address
var err error
if params.To == nil {
if params.BlobParams != nil {
return nil, common.Address{}, errors.New("BlobTx cannot be used for contract creation")
}
tx = types.NewTx(
&types.DynamicFeeTx{
ChainID: params.ChainID,
@ -184,7 +266,7 @@ func (gen *TxGenerator) GenerateTx(params *GenParams) (*types.Transaction, commo
if err != nil {
return nil, common.Address{}, err
}
} else {
} else if params.BlobParams == nil {
tx = types.NewTx(
&types.DynamicFeeTx{
ChainID: params.ChainID,
@ -196,10 +278,53 @@ func (gen *TxGenerator) GenerateTx(params *GenParams) (*types.Transaction, commo
Value: params.Amount,
Data: params.Data,
})
} else {
tx = types.NewTx(
&types.BlobTx{
ChainID: uint256.MustFromBig(params.ChainID),
Nonce: nonce,
Gas: params.GasLimit,
GasTipCap: uint256.MustFromBig(params.GasTipCap),
GasFeeCap: uint256.MustFromBig(params.GasFeeCap),
To: *params.To,
Value: uint256.MustFromBig(params.Amount),
Data: params.Data,
BlobFeeCap: params.BlobParams.BlobFeeCap,
BlobHashes: params.BlobParams.BlobHashes,
Sidecar: params.BlobParams.Sidecar,
})
}
signedTx, err := types.SignTx(tx, gen.config.Signer, params.SenderKey)
if err != nil {
return nil, common.Address{}, err
}
return signedTx, contractAddr, err
}
// From go-ethereum/cmd/devp2p/internal/ethtest/suite.go
func makeSidecar(data []byte) (*types.BlobTxSidecar, error) {
var (
blobs = make([]kzg4844.Blob, len(data))
commitments []kzg4844.Commitment
proofs []kzg4844.Proof
)
for i := range blobs {
blobs[i][0] = data[i]
c, err := kzg4844.BlobToCommitment(blobs[i])
if err != nil {
return nil, err
}
p, err := kzg4844.ComputeBlobProof(blobs[i], c)
if err != nil {
return nil, err
}
commitments = append(commitments, c)
proofs = append(proofs, p)
}
return &types.BlobTxSidecar{
Blobs: blobs,
Commitments: commitments,
Proofs: proofs,
}, nil
}

View File

@ -45,8 +45,8 @@ func (tw *TxWatcher) Start() {
sleep *= 2
} else {
elapsed := time.Now().Sub(tw.startedAt)
logrus.Debugf("TxW: TX %s found in block %s after %dms.", tx.Hash().Hex(),
receipt.BlockNumber.String(), time.Now().Sub(start).Milliseconds())
logrus.Debugf("TxW: TX %s found in block %s after %v.", tx.Hash().Hex(),
receipt.BlockNumber.String(), time.Now().Sub(start))
logrus.Infof("TxW: %d in %.0f seconds (%.2f/sec, %d pending)",
tw.counter, elapsed.Seconds(), float64(tw.counter)/elapsed.Seconds(), len(tw.PendingTxCh))
}

View File

@ -33,23 +33,38 @@ import (
// TxSigner returns the London signer at the provided block height
func TxSigner(chainID *big.Int) types.Signer {
return types.NewLondonSigner(chainID)
return types.NewCancunSigner(chainID)
}
// SendTransaction sends a signed tx using the provided client
func SendTransaction(rpcClient *rpc.Client, tx *types.Transaction) error {
msg, _ := core.TransactionToMessage(tx, TxSigner(tx.ChainId()), big.NewInt(1))
if nil == tx.To() {
logrus.Debugf("TX %s to create contract %s (sender %s)",
tx.Hash().Hex(), crypto.CreateAddress(msg.From, tx.Nonce()), msg.From.Hex())
} else if nil == tx.Data() || len(tx.Data()) == 0 {
logrus.Debugf("TX %s sending %s Wei to %s (sender %s)",
tx.Hash().Hex(), tx.Value().String(), tx.To().Hex(), msg.From.Hex())
} else {
logrus.Debugf("TX %s calling contract %s (sender %s)",
tx.Hash().Hex(), tx.To().Hex(), msg.From.Hex())
msg, err := core.TransactionToMessage(tx, TxSigner(tx.ChainId()), big.NewInt(1))
if err != nil {
return err
}
if nil == tx.To() {
logrus.WithFields(logrus.Fields{
"hash": tx.Hash(),
"from": msg.From,
"contract": crypto.CreateAddress(msg.From, tx.Nonce()),
}).Debug("Sending TX to create contract")
} else {
fields := logrus.Fields{
"hash": tx.Hash(),
"from": msg.From,
"to": tx.To(),
"nonce": tx.Nonce(),
}
if numblobs := len(tx.BlobHashes()); numblobs > 0 {
fields["blobs"] = numblobs
}
if len(tx.Data()) == 0 {
fields["value"] = tx.Value()
logrus.WithFields(fields).Debug("Sending TX to transfer ETH")
} else {
logrus.WithFields(fields).Debug("Sending TX to call contract")
}
}
data, err := tx.MarshalBinary()
if err != nil {
return err
@ -59,7 +74,7 @@ func SendTransaction(rpcClient *rpc.Client, tx *types.Transaction) error {
// SendRawTransaction sends a raw, signed tx using the provided client
func SendRawTransaction(rpcClient *rpc.Client, raw []byte) error {
logrus.Debug("eth_sendRawTransaction: ", hexutil.Encode(raw))
logrus.Debugf("eth_sendRawTransaction: %x... (%d bytes)", raw[:min(10, len(raw))], len(raw))
return rpcClient.CallContext(context.Background(), nil, "eth_sendRawTransaction", hexutil.Encode(raw))
}