From 340b8829b244d34778e359c72575f88c637cb2ee Mon Sep 17 00:00:00 2001 From: Minhyuk Kim Date: Fri, 7 Mar 2025 08:15:46 +0900 Subject: [PATCH] Add rpcs flag which uses random backend when sending rpc requests --- cmd/livefuzzer/main.go | 2 ++ flags/flags.go | 6 ++++++ spammer/addresslist.go | 16 ++++++++------ spammer/basic.go | 14 ++++++------ spammer/blob.go | 14 ++++++------ spammer/config.go | 48 +++++++++++++++++++++++++++++++----------- spammer/eip7702.go | 15 +++++++------ spammer/helper.go | 9 +++++--- 8 files changed, 83 insertions(+), 41 deletions(-) diff --git a/cmd/livefuzzer/main.go b/cmd/livefuzzer/main.go index 8766c4b..87baab9 100644 --- a/cmd/livefuzzer/main.go +++ b/cmd/livefuzzer/main.go @@ -19,6 +19,7 @@ var airdropCommand = &cli.Command{ Flags: []cli.Flag{ flags.SkFlag, flags.RpcFlag, + flags.RpcsFlag, }, } @@ -50,6 +51,7 @@ var createCommand = &cli.Command{ Flags: []cli.Flag{ flags.CountFlag, flags.RpcFlag, + flags.RpcsFlag, }, } diff --git a/flags/flags.go b/flags/flags.go index 76d8d42..5aac162 100644 --- a/flags/flags.go +++ b/flags/flags.go @@ -38,6 +38,11 @@ var ( Value: "http://127.0.0.1:8545", } + RpcsFlag = &cli.StringFlag{ + Name: "rpcs", + Usage: "Comma separated list of RPC providers to use for sending transactions", + } + TxCountFlag = &cli.IntFlag{ Name: "txcount", Usage: "Number of transactions send per account per block, 0 = best estimate", @@ -62,6 +67,7 @@ var ( NoALFlag, CorpusFlag, RpcFlag, + RpcsFlag, TxCountFlag, CountFlag, GasLimitFlag, diff --git a/spammer/addresslist.go b/spammer/addresslist.go index 1704365..5349755 100644 --- a/spammer/addresslist.go +++ b/spammer/addresslist.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/big" + "math/rand" "time" "github.com/ethereum/go-ethereum" @@ -144,24 +145,25 @@ func CreateAddresses(N int) ([]string, []string) { } func Airdrop(config *Config, value *big.Int) error { - backend := ethclient.NewClient(config.backend) + backend := config.backends[rand.Intn(len(config.backends))] + client := ethclient.NewClient(backend) sender := crypto.PubkeyToAddress(config.faucet.PublicKey) fmt.Printf("Airdrop faucet is at %x\n", sender) var tx *types.Transaction - chainid, err := backend.ChainID(context.Background()) + chainid, err := client.ChainID(context.Background()) if err != nil { fmt.Printf("error getting chain ID; could not airdrop: %v\n", err) return err } for _, addr := range config.keys { - nonce, err := backend.PendingNonceAt(context.Background(), sender) + nonce, err := client.PendingNonceAt(context.Background(), sender) if err != nil { fmt.Printf("error getting pending nonce; could not airdrop: %v\n", err) return err } to := crypto.PubkeyToAddress(addr.PublicKey) - gp, _ := backend.SuggestGasPrice(context.Background()) - gas, err := backend.EstimateGas(context.Background(), ethereum.CallMsg{ + gp, _ := client.SuggestGasPrice(context.Background()) + gas, err := client.EstimateGas(context.Background(), ethereum.CallMsg{ From: crypto.PubkeyToAddress(config.faucet.PublicKey), To: &to, Gas: 30_000_000, @@ -175,7 +177,7 @@ func Airdrop(config *Config, value *big.Int) error { } tx2 := types.NewTransaction(nonce, to, value, gas, gp, nil) signedTx, _ := types.SignTx(tx2, types.LatestSignerForChainID(chainid), config.faucet) - if err := backend.SendTransaction(context.Background(), signedTx); err != nil { + if err := client.SendTransaction(context.Background(), signedTx); err != nil { fmt.Printf("error sending transaction; could not airdrop: %v\n", err) return err } @@ -183,7 +185,7 @@ func Airdrop(config *Config, value *big.Int) error { time.Sleep(10 * time.Millisecond) } // Wait for the last transaction to be mined - if _, err := bind.WaitMined(context.Background(), backend, tx); err != nil { + if _, err := bind.WaitMined(context.Background(), client, tx); err != nil { return err } return nil diff --git a/spammer/basic.go b/spammer/basic.go index 01a5ca6..c378b69 100644 --- a/spammer/basic.go +++ b/spammer/basic.go @@ -5,6 +5,7 @@ import ( "crypto/ecdsa" "fmt" "math/big" + "math/rand" "time" "github.com/MariusVanDerWijden/FuzzyVM/filler" @@ -19,9 +20,10 @@ import ( const TX_TIMEOUT = 5 * time.Minute func SendBasicTransactions(config *Config, key *ecdsa.PrivateKey, f *filler.Filler) error { - backend := ethclient.NewClient(config.backend) + backend := config.backends[rand.Intn(len(config.backends))] + client := ethclient.NewClient(backend) sender := crypto.PubkeyToAddress(key.PublicKey) - chainID, err := backend.ChainID(context.Background()) + chainID, err := client.ChainID(context.Background()) if err != nil { log.Warn("Could not get chainID, using default") chainID = big.NewInt(0x01000666) @@ -29,11 +31,11 @@ func SendBasicTransactions(config *Config, key *ecdsa.PrivateKey, f *filler.Fill var lastTx *types.Transaction for i := uint64(0); i < config.N; i++ { - nonce, err := backend.NonceAt(context.Background(), sender, big.NewInt(-1)) + nonce, err := client.NonceAt(context.Background(), sender, big.NewInt(-1)) if err != nil { return err } - tx, err := txfuzz.RandomValidTx(config.backend, f, sender, nonce, nil, nil, config.accessList) + tx, err := txfuzz.RandomValidTx(backend, f, sender, nonce, nil, nil, config.accessList) if err != nil { log.Warn("Could not create valid tx: %v", nonce) return err @@ -42,7 +44,7 @@ func SendBasicTransactions(config *Config, key *ecdsa.PrivateKey, f *filler.Fill if err != nil { return err } - if err := backend.SendTransaction(context.Background(), signedTx); err != nil { + if err := client.SendTransaction(context.Background(), signedTx); err != nil { log.Warn("Could not submit transaction: %v", err) return err } @@ -52,7 +54,7 @@ func SendBasicTransactions(config *Config, key *ecdsa.PrivateKey, f *filler.Fill if lastTx != nil { ctx, cancel := context.WithTimeout(context.Background(), TX_TIMEOUT) defer cancel() - if _, err := bind.WaitMined(ctx, backend, lastTx); err != nil { + if _, err := bind.WaitMined(ctx, client, lastTx); err != nil { fmt.Printf("Waiting for transactions to be mined failed: %v\n", err.Error()) } } diff --git a/spammer/blob.go b/spammer/blob.go index 69c844b..b5c6f26 100644 --- a/spammer/blob.go +++ b/spammer/blob.go @@ -5,6 +5,7 @@ import ( "crypto/ecdsa" "fmt" "math/big" + "math/rand" "time" "github.com/MariusVanDerWijden/FuzzyVM/filler" @@ -17,9 +18,10 @@ import ( ) func SendBlobTransactions(config *Config, key *ecdsa.PrivateKey, f *filler.Filler) error { - backend := ethclient.NewClient(config.backend) + backend := config.backends[rand.Intn(len(config.backends))] + client := ethclient.NewClient(backend) sender := crypto.PubkeyToAddress(key.PublicKey) - chainID, err := backend.ChainID(context.Background()) + chainID, err := client.ChainID(context.Background()) if err != nil { log.Warn("Could not get chainID, using default") chainID = big.NewInt(0x01000666) @@ -27,11 +29,11 @@ func SendBlobTransactions(config *Config, key *ecdsa.PrivateKey, f *filler.Fille var lastTx *types.Transaction for i := uint64(0); i < config.N; i++ { - nonce, err := backend.NonceAt(context.Background(), sender, big.NewInt(-1)) + nonce, err := client.NonceAt(context.Background(), sender, big.NewInt(-1)) if err != nil { return err } - tx, err := txfuzz.RandomBlobTx(config.backend, f, sender, nonce, nil, nil, config.accessList) + tx, err := txfuzz.RandomBlobTx(backend, f, sender, nonce, nil, nil, config.accessList) if err != nil { log.Warn("Could not create valid tx: %v", nonce) return err @@ -40,7 +42,7 @@ func SendBlobTransactions(config *Config, key *ecdsa.PrivateKey, f *filler.Fille if err != nil { return err } - if err := backend.SendTransaction(context.Background(), signedTx); err != nil { + if err := client.SendTransaction(context.Background(), signedTx); err != nil { log.Warn("Could not submit transaction: %v", err) return err } @@ -51,7 +53,7 @@ func SendBlobTransactions(config *Config, key *ecdsa.PrivateKey, f *filler.Fille if lastTx != nil { ctx, cancel := context.WithTimeout(context.Background(), TX_TIMEOUT) defer cancel() - if _, err := bind.WaitMined(ctx, backend, lastTx); err != nil { + if _, err := bind.WaitMined(ctx, client, lastTx); err != nil { fmt.Printf("Waiting for transactions to be mined failed: %v\n", err.Error()) } } diff --git a/spammer/config.go b/spammer/config.go index c91ab8d..8cb0625 100644 --- a/spammer/config.go +++ b/spammer/config.go @@ -5,9 +5,11 @@ import ( "crypto/ecdsa" crand "crypto/rand" "encoding/binary" + "errors" "fmt" "math/rand" "os" + "strings" txfuzz "github.com/MariusVanDerWijden/tx-fuzz" "github.com/MariusVanDerWijden/tx-fuzz/flags" @@ -20,7 +22,7 @@ import ( ) type Config struct { - backend *rpc.Client // connection to the rpc provider + backends []*rpc.Client // connection to the rpc provider N uint64 // number of transactions send per account faucet *ecdsa.PrivateKey // private key of the faucet account @@ -34,11 +36,19 @@ type Config struct { mut *mutator.Mutator // Mutator based on the seed } -func NewDefaultConfig(rpcAddr string, N uint64, accessList bool, rng *rand.Rand) (*Config, error) { +func NewDefaultConfig(rpcAddrs string, N uint64, accessList bool, rng *rand.Rand) (*Config, error) { // Setup RPC - backend, err := rpc.Dial(rpcAddr) - if err != nil { - return nil, err + backends := make([]*rpc.Client, 0) + + for _, rpcAddr := range strings.Split(rpcAddrs, ",") { + backend, err := rpc.Dial(rpcAddr) + if err == nil { + backends = append(backends, backend) + } + } + + if len(backends) == 0 { + return nil, errors.New("no valid rpc addresses found") } // Setup Keys @@ -48,7 +58,7 @@ func NewDefaultConfig(rpcAddr string, N uint64, accessList bool, rng *rand.Rand) } return &Config{ - backend: backend, + backends: backends, N: N, faucet: crypto.ToECDSAUnsafe(common.FromHex(txfuzz.SK)), keys: keys, @@ -62,14 +72,28 @@ func NewDefaultConfig(rpcAddr string, N uint64, accessList bool, rng *rand.Rand) func NewConfigFromContext(c *cli.Context) (*Config, error) { // Setup RPC - rpcAddr := c.String(flags.RpcFlag.Name) - backend, err := rpc.Dial(rpcAddr) - if err != nil { - return nil, err + var rpcAddrs []string + if rpcs := c.String(flags.RpcsFlag.Name); rpcs != "" { + rpcAddrs = strings.Split(rpcs, ",") + } else { + rpcAddrs = []string{c.String(flags.RpcFlag.Name)} + } + + backends := make([]*rpc.Client, 0) + for _, rpcAddr := range rpcAddrs { + backend, err := rpc.Dial(rpcAddr) + if err == nil { + backends = append(backends, backend) + } + } + + if len(backends) == 0 { + return nil, errors.New("no valid rpc addresses found") } // Setup faucet faucet := crypto.ToECDSAUnsafe(common.FromHex(txfuzz.SK)) + var err error if sk := c.String(flags.SkFlag.Name); sk != "" { faucet, err = crypto.ToECDSA(common.FromHex(sk)) if err != nil { @@ -94,7 +118,7 @@ func NewConfigFromContext(c *cli.Context) (*Config, error) { // Setup N N := c.Int(flags.TxCountFlag.Name) if N == 0 { - N, err = setupN(backend, len(keys), gasLimit) + N, err = setupN(backends[0], len(keys), gasLimit) if err != nil { return nil, err } @@ -124,7 +148,7 @@ func NewConfigFromContext(c *cli.Context) (*Config, error) { } return &Config{ - backend: backend, + backends: backends, N: uint64(N), faucet: faucet, accessList: !c.Bool(flags.NoALFlag.Name), diff --git a/spammer/eip7702.go b/spammer/eip7702.go index 9d71e19..49b2a1c 100644 --- a/spammer/eip7702.go +++ b/spammer/eip7702.go @@ -18,9 +18,10 @@ import ( ) func Send7702Transactions(config *Config, key *ecdsa.PrivateKey, f *filler.Filler) error { - backend := ethclient.NewClient(config.backend) + backend := config.backends[rand.Intn(len(config.backends))] + client := ethclient.NewClient(backend) sender := crypto.PubkeyToAddress(key.PublicKey) - chainID, err := backend.ChainID(context.Background()) + chainID, err := client.ChainID(context.Background()) if err != nil { log.Warn("Could not get chainID, using default") chainID = big.NewInt(0x01000666) @@ -28,13 +29,13 @@ func Send7702Transactions(config *Config, key *ecdsa.PrivateKey, f *filler.Fille var lastTx *types.Transaction for i := uint64(0); i < config.N; i++ { - nonce, err := backend.NonceAt(context.Background(), sender, big.NewInt(-1)) + nonce, err := client.NonceAt(context.Background(), sender, big.NewInt(-1)) if err != nil { return err } authorizer := config.keys[rand.Intn(len(config.keys))] - nonceAuth, err := backend.NonceAt(context.Background(), crypto.PubkeyToAddress(authorizer.PublicKey), big.NewInt(-1)) + nonceAuth, err := client.NonceAt(context.Background(), crypto.PubkeyToAddress(authorizer.PublicKey), big.NewInt(-1)) if err != nil { return err } @@ -50,7 +51,7 @@ func Send7702Transactions(config *Config, key *ecdsa.PrivateKey, f *filler.Fille return err } - tx, err := txfuzz.RandomAuthTx(config.backend, f, sender, nonce, nil, nil, config.accessList, types.AuthorizationList{auth}) + tx, err := txfuzz.RandomAuthTx(backend, f, sender, nonce, nil, nil, config.accessList, types.AuthorizationList{auth}) if err != nil { fmt.Printf("Could not create valid tx: %v", nonce) return err @@ -59,7 +60,7 @@ func Send7702Transactions(config *Config, key *ecdsa.PrivateKey, f *filler.Fille if err != nil { return err } - if err := backend.SendTransaction(context.Background(), signedTx); err != nil { + if err := client.SendTransaction(context.Background(), signedTx); err != nil { fmt.Printf("Could not submit transaction: %v", err) return err } @@ -69,7 +70,7 @@ func Send7702Transactions(config *Config, key *ecdsa.PrivateKey, f *filler.Fille if lastTx != nil { ctx, cancel := context.WithTimeout(context.Background(), TX_TIMEOUT) defer cancel() - if _, err := bind.WaitMined(ctx, backend, lastTx); err != nil { + if _, err := bind.WaitMined(ctx, client, lastTx); err != nil { fmt.Printf("Waiting for transactions to be mined failed: %v\n", err.Error()) } } diff --git a/spammer/helper.go b/spammer/helper.go index 856b583..5e060f5 100644 --- a/spammer/helper.go +++ b/spammer/helper.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "math/big" + "math/rand" "time" "github.com/ethereum/go-ethereum" @@ -75,8 +76,9 @@ func Unstuck(config *Config) error { func tryUnstuck(config *Config, sk *ecdsa.PrivateKey) error { var ( - client = ethclient.NewClient(config.backend) - addr = crypto.PubkeyToAddress(sk.PublicKey) + backend = config.backends[rand.Intn(len(config.backends))] + client = ethclient.NewClient(backend) + addr = crypto.PubkeyToAddress(sk.PublicKey) ) for i := 0; i < 100; i++ { noTx, err := isStuck(config, addr) @@ -107,7 +109,8 @@ func tryUnstuck(config *Config, sk *ecdsa.PrivateKey) error { } func isStuck(config *Config, account common.Address) (uint64, error) { - client := ethclient.NewClient(config.backend) + backend := config.backends[rand.Intn(len(config.backends))] + client := ethclient.NewClient(backend) nonce, err := client.NonceAt(context.Background(), account, nil) if err != nil { return 0, err