Skip to content
This repository was archived by the owner on Dec 17, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ require (
github.com/ethereum/go-ethereum v1.13.10
github.com/holiman/uint256 v1.2.4
github.com/sirupsen/logrus v1.9.3
github.com/spf13/pflag v1.0.5
golang.org/x/crypto v0.17.0
)

require (
Expand All @@ -20,15 +22,12 @@ require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/ethereum/c-kzg-4844 v0.4.0 // indirect
github.com/go-ole/go-ole v1.2.5 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/mmcloughlin/addchain v0.4.0 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/supranational/blst v0.3.11 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/sync v0.5.0 // indirect
Expand Down
170 changes: 127 additions & 43 deletions go.sum

Large diffs are not rendered by default.

296 changes: 296 additions & 0 deletions scenarios/deploytx/deploytx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
package deploytx

import (
"bufio"
"fmt"
"math/big"
"os"
"strings"
"sync"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/holiman/uint256"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"

"github.com/ethpandaops/goomy-blob/scenariotypes"
"github.com/ethpandaops/goomy-blob/tester"
"github.com/ethpandaops/goomy-blob/txbuilder"
"github.com/ethpandaops/goomy-blob/utils"
)

type ScenarioOptions struct {
TotalCount uint64
Throughput uint64
MaxPending uint64
MaxWallets uint64
Rebroadcast uint64
GasLimit uint64
BaseFee uint64
TipFee uint64
Bytecodes string
BytecodesFile string
}

type Scenario struct {
options ScenarioOptions
logger *logrus.Entry
tester *tester.Tester

bytecodes [][]byte

pendingCount uint64
pendingChan chan bool
pendingWGroup sync.WaitGroup
}

func NewScenario() scenariotypes.Scenario {
return &Scenario{
logger: logrus.WithField("scenario", "deploytx"),
}
}

func (s *Scenario) Flags(flags *pflag.FlagSet) error {
flags.Uint64VarP(&s.options.TotalCount, "count", "c", 0, "Total number of deployment transactions to send")
flags.Uint64VarP(&s.options.Throughput, "throughput", "t", 0, "Number of deployment transactions to send per slot")
flags.Uint64Var(&s.options.MaxPending, "max-pending", 0, "Maximum number of pending transactions")
flags.Uint64Var(&s.options.MaxWallets, "max-wallets", 0, "Maximum number of child wallets to use")
flags.Uint64Var(&s.options.Rebroadcast, "rebroadcast", 120, "Number of seconds to wait before re-broadcasting a transaction")
flags.Uint64Var(&s.options.GasLimit, "gaslimit", 1000000, "Gas limit to use in deployment transactions (in gwei)")
flags.Uint64Var(&s.options.BaseFee, "basefee", 20, "Max fee per gas to use in deployment transactions (in gwei)")
flags.Uint64Var(&s.options.TipFee, "tipfee", 2, "Max tip per gas to use in deployment transactions (in gwei)")
flags.StringVar(&s.options.Bytecodes, "bytecodes", "", "Bytecodes to deploy (, separated list of hex bytecodes)")
flags.StringVar(&s.options.BytecodesFile, "bytecodes-file", "", "File with bytecodes to deploy (list with hex bytecodes)")

return nil
}

func (s *Scenario) Init(testerCfg *tester.TesterConfig) error {
if s.options.TotalCount == 0 && s.options.Throughput == 0 {
return fmt.Errorf("neither total count nor throughput limit set, must define at least one of them")
}

if s.options.MaxWallets > 0 {
testerCfg.WalletCount = s.options.MaxWallets
} else if s.options.TotalCount > 0 {
if s.options.TotalCount < 1000 {
testerCfg.WalletCount = s.options.TotalCount
} else {
testerCfg.WalletCount = 1000
}
} else {
if s.options.Throughput*10 < 1000 {
testerCfg.WalletCount = s.options.Throughput * 10
} else {
testerCfg.WalletCount = 1000
}
}

if s.options.MaxPending > 0 {
s.pendingChan = make(chan bool, s.options.MaxPending)
}

s.bytecodes = [][]byte{}
if s.options.Bytecodes != "" {
for _, hexStr := range strings.Split(s.options.Bytecodes, ",") {
s.bytecodes = append(s.bytecodes, common.FromHex(hexStr))
}
}
if s.options.BytecodesFile != "" {
fp, err := os.Open(s.options.BytecodesFile)
if err != nil {
return fmt.Errorf("cannot open bytecodes list: %w", err)
}
defer fp.Close()
scanner := bufio.NewScanner(fp)
for scanner.Scan() {
hexStr := strings.Trim(scanner.Text(), " \t")
if strings.HasPrefix(hexStr, "#") || hexStr == "" {
continue
}
s.bytecodes = append(s.bytecodes, common.FromHex(hexStr))
}
}

return nil
}

func (s *Scenario) Run(tester *tester.Tester) error {
s.tester = tester
txIdxCounter := uint64(0)
counterMutex := sync.Mutex{}
waitGroup := sync.WaitGroup{}
pendingCount := uint64(0)
txCount := uint64(0)
startTime := time.Now()

s.logger.Infof("starting scenario: deploytx")

for {
txIdx := txIdxCounter
txIdxCounter++

if s.pendingChan != nil {
// await pending transactions
s.pendingChan <- true
}
waitGroup.Add(1)
counterMutex.Lock()
pendingCount++
counterMutex.Unlock()

go func(txIdx uint64) {
defer func() {
counterMutex.Lock()
pendingCount--
counterMutex.Unlock()
waitGroup.Done()
}()

logger := s.logger
tx, client, err := s.sendTx(txIdx)
if client != nil {
logger = logger.WithField("rpc", client.GetName())
}
if err != nil {
logger.Warnf("could not send transaction: %v", err)
<-s.pendingChan
return
}

counterMutex.Lock()
txCount++
counterMutex.Unlock()
logger.Infof("sent tx #%6d: %v", txIdx+1, tx.Hash().String())
}(txIdx)

count := txCount + pendingCount
if s.options.TotalCount > 0 && count >= s.options.TotalCount {
break
}
if s.options.Throughput > 0 {
for count/((uint64(time.Since(startTime).Seconds())/utils.SecondsPerSlot)+1) >= s.options.Throughput {
time.Sleep(100 * time.Millisecond)
}
}
}
waitGroup.Wait()

s.logger.Infof("finished sending transactions, awaiting block inclusion...")
s.pendingWGroup.Wait()
s.logger.Infof("finished sending transactions, awaiting block inclusion...")

return nil
}

func (s *Scenario) sendTx(txIdx uint64) (*types.Transaction, *txbuilder.Client, error) {
client := s.tester.GetClient(tester.SelectByIndex, int(txIdx))
wallet := s.tester.GetWallet(tester.SelectByIndex, int(txIdx))

var feeCap *big.Int
var tipCap *big.Int

if s.options.BaseFee > 0 {
feeCap = new(big.Int).Mul(big.NewInt(int64(s.options.BaseFee)), big.NewInt(1000000000))
}
if s.options.TipFee > 0 {
tipCap = new(big.Int).Mul(big.NewInt(int64(s.options.TipFee)), big.NewInt(1000000000))
}

if feeCap == nil || tipCap == nil {
var err error
feeCap, tipCap, err = client.GetSuggestedFee()
if err != nil {
return nil, client, err
}
}

if feeCap.Cmp(big.NewInt(1000000000)) < 0 {
feeCap = big.NewInt(1000000000)
}
if tipCap.Cmp(big.NewInt(1000000000)) < 0 {
tipCap = big.NewInt(1000000000)
}

deployData := s.bytecodes[int(txIdx)%len(s.bytecodes)]
txData, err := txbuilder.DynFeeTx(&txbuilder.TxMetadata{
GasFeeCap: uint256.MustFromBig(feeCap),
GasTipCap: uint256.MustFromBig(tipCap),
Gas: s.options.GasLimit,
To: nil,
Value: uint256.NewInt(0),
Data: deployData,
})
if err != nil {
return nil, nil, err
}

tx, err := wallet.BuildDynamicFeeTx(txData)
if err != nil {
return nil, nil, err
}

err = client.SendTransaction(tx)
if err != nil {
return nil, client, err
}

s.pendingWGroup.Add(1)
go s.awaitTx(txIdx, tx, client, wallet)

return tx, client, nil
}

func (s *Scenario) awaitTx(txIdx uint64, tx *types.Transaction, client *txbuilder.Client, wallet *txbuilder.Wallet) {
var awaitConfirmation bool = true
defer func() {
awaitConfirmation = false
if s.pendingChan != nil {
<-s.pendingChan
}
s.pendingWGroup.Done()
}()
if s.options.Rebroadcast > 0 {
go s.delayedResend(txIdx, tx, &awaitConfirmation)
}

receipt, blockNum, err := client.AwaitTransaction(tx)
if err != nil {
s.logger.WithField("client", client.GetName()).Warnf("error while awaiting tx receipt: %v", err)
return
}

effectiveGasPrice := receipt.EffectiveGasPrice
if effectiveGasPrice == nil {
effectiveGasPrice = big.NewInt(0)
}
blobGasPrice := receipt.BlobGasPrice
if blobGasPrice == nil {
blobGasPrice = big.NewInt(0)
}
feeAmount := new(big.Int).Mul(effectiveGasPrice, big.NewInt(int64(receipt.GasUsed)))
totalAmount := new(big.Int).Add(tx.Value(), feeAmount)
wallet.SubBalance(totalAmount)

gweiTotalFee := new(big.Int).Div(totalAmount, big.NewInt(1000000000))
gweiBaseFee := new(big.Int).Div(effectiveGasPrice, big.NewInt(1000000000))
gweiBlobFee := new(big.Int).Div(blobGasPrice, big.NewInt(1000000000))

s.logger.WithField("client", client.GetName()).Infof(" transaction %d confirmed in block #%v. total fee: %v gwei (base: %v, blob: %v)", txIdx+1, blockNum, gweiTotalFee, gweiBaseFee, gweiBlobFee)
}

func (s *Scenario) delayedResend(txIdx uint64, tx *types.Transaction, awaitConfirmation *bool) {
for {
time.Sleep(time.Duration(s.options.Rebroadcast) * time.Second)

if !*awaitConfirmation {
break
}

client := s.tester.GetClient(tester.SelectRandom, 0)
client.SendTransaction(tx)
s.logger.WithField("client", client.GetName()).Infof(" transaction %d re-broadcasted.", txIdx+1)
}
}
Loading