rpcfuzz

package
v0.1.26 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 21, 2023 License: AGPL-3.0 Imports: 23 Imported by: 0

Documentation

Overview

Package rpcfuzz is meant to have some basic RPC fuzzing and conformance tests. Each test is meant to be self-contained i.e. the success or failure of a test should have no impact on other tests. The benefit here is that each test is an object and can be modified, decorated, fuzzed, etc.

The conformance test should also run successful on a network that is or isn't isolated. In some circumstances, it might be better to run the conformance test in once process while there is load being applied. The only consideration is that you shouldn't use the same key to load test as is used to run the conformance tests.

Index

Constants

This section is empty.

Variables

View Source
var RPCFuzzCmd = &cobra.Command{
	Use:   "rpcfuzz http://localhost:8545",
	Short: "Continually run a variety of RPC calls and fuzzers",
	Long: `

This command will run a series of RPC calls against a given json rpc
endpoint. The idea is to be able to check for various features and
function to see if the RPC generally conforms to typical geth
standards for the RPC

Some setup might be neede depending on how you're testing. We'll
demonstrate with geth. In order to quickly test this, you can run geth
in dev mode:

# ./build/bin/geth --dev --dev.period 5 --http --http.addr localhost \
    --http.port 8545 \
    --http.api 'admin,debug,web3,eth,txpool,personal,clique,miner,net' \
    --verbosity 5 --rpc.gascap 50000000  --rpc.txfeecap 0 \
    --miner.gaslimit  10 --miner.gasprice 1 --gpo.blocks 1 \
    --gpo.percentile 1 --gpo.maxprice 10 --gpo.ignoreprice 2 \
    --dev.gaslimit 50000000

Once your Eth client is running and the RPC is functional, you'll need
to transfer some amount of ether to a known account that ca be used
for testing

# cast send --from "$(cast rpc --rpc-url localhost:8545 eth_coinbase | jq -r '.')" \
    --rpc-url localhost:8545 --unlocked --value 100ether \
    0x85dA99c8a7C2C95964c8EfD687E95E632Fc533D6

Then we might want to deploy some test smart contracts. For the
purposes of testing we'll our ERC20 contract:

# cast send --from 0x85dA99c8a7C2C95964c8EfD687E95E632Fc533D6 \
    --private-key 0x42b6e34dc21598a807dc19d7784c71b2a7a01f6480dc6f58258f78e539f1a1fa \
    --rpc-url localhost:8545 --create \
    "$(cat ./contracts/ERC20.bin)"

Once this has been completed this will be the address of the contract:
0x6fda56c57b0acadb96ed5624ac500c0429d59429

# docker run -v $PWD/contracts:/contracts ethereum/solc:stable --storage-layout /contracts/ERC20.sol

- https://ethereum.github.io/execution-apis/api-documentation/
- https://ethereum.org/en/developers/docs/apis/json-rpc/
- https://json-schema.org/
- https://www.liquid-technologies.com/online-json-to-schema-converter

`,
	RunE: func(cmd *cobra.Command, args []string) error {
		ctx := cmd.Context()
		rpcClient, err := rpc.DialContext(ctx, args[0])
		if err != nil {
			return err
		}
		log.Trace().Msg("Doing test setup")
		setupTests(ctx, rpcClient)
		nonce, err := GetTestAccountNonce(ctx, rpcClient)
		if err != nil {
			return err
		}
		chainId, err := GetCurrentChainID(ctx, rpcClient)
		if err != nil {
			return err
		}
		testAccountNonce = nonce
		currentChainID = chainId

		for _, t := range allTests {
			if !shouldRunTest(t) {
				log.Trace().Str("name", t.GetName()).Str("method", t.GetMethod()).Msg("Skipping test")
				continue
			}
			log.Trace().Str("name", t.GetName()).Str("method", t.GetMethod()).Msg("Running Test")
			var result interface{}
			err = rpcClient.CallContext(ctx, &result, t.GetMethod(), t.GetArgs()...)
			if err != nil && !t.ExpectError() {
				log.Error().Err(err).Str("method", t.GetMethod()).Msg("Method test failed")
				continue
			}

			if t.ExpectError() {
				err = t.Validate(err)
			} else {
				err = t.Validate(result)
			}

			if err != nil {
				log.Error().Err(err).Str("method", t.GetMethod()).Msg("Failed to validate")
				continue
			}
			log.Info().Str("method", t.GetMethod()).Msg("Successfully validated")
		}
		return nil
	},
	Args: func(cmd *cobra.Command, args []string) error {
		if len(args) != 1 {
			return fmt.Errorf("Expected 1 argument, but got %d", len(args))
		}

		privateKey, err := ethcrypto.HexToECDSA(*testPrivateHexKey)
		if err != nil {
			log.Error().Err(err).Msg("Couldn't process the hex private key")
			return err
		}

		ethAddress := ethcrypto.PubkeyToAddress(privateKey.PublicKey)
		log.Info().Str("ethAddress", ethAddress.String()).Msg("Loaded private key")

		nsValidator := regexp.MustCompile("^[a-z0-9]*$")
		rawNameSpaces := strings.Split(*testNamespaces, ",")
		enabledNamespaces = make([]string, 0)
		for _, ns := range rawNameSpaces {
			if !nsValidator.MatchString(ns) {
				return fmt.Errorf("The namespace %s is not valid", ns)
			}
			enabledNamespaces = append(enabledNamespaces, ns+"_")
		}
		log.Info().Strs("namespaces", enabledNamespaces).Msg("enabling namespaces")

		testPrivateKey = privateKey
		testEthAddress = ethAddress

		return nil
	},
}

Functions

func ArgsBlockFilterID

func ArgsBlockFilterID(ctx context.Context, rpcClient *rpc.Client, extraArgs ...interface{}) func() []interface{}

ArgsBlockFilterID will inject an argument that's a filter id corresponding to a block filte

func ArgsCoinbase

func ArgsCoinbase(ctx context.Context, rpcClient *rpc.Client, extraArgs ...interface{}) func() []interface{}

ArgsCoinbase would return arguments where the first argument is now the coinbase

func ArgsCoinbaseTransaction

func ArgsCoinbaseTransaction(ctx context.Context, rpcClient *rpc.Client, tx *RPCTestTransactionArgs) func() []interface{}

ArgsCoinbaseTransaction will return arguments where the from is replace with the current coinbase

func ArgsFilterID

func ArgsFilterID(ctx context.Context, rpcClient *rpc.Client, filterArgs RPCTestFilterArgs, extraArgs ...interface{}) func() []interface{}

ArgsFilterID will inject an argument that's a filter id corresponding to the provide filter args

func ArgsLatestBlockHash

func ArgsLatestBlockHash(ctx context.Context, rpcClient *rpc.Client, extraArgs ...interface{}) func() []interface{}

ArgsLatestBlockHash is meant to generate an argument with the latest block hash for testing

func ArgsLatestBlockNumber

func ArgsLatestBlockNumber(ctx context.Context, rpcClient *rpc.Client, extraArgs ...interface{}) func() []interface{}

func ArgsSignTransaction

func ArgsSignTransaction(ctx context.Context, rpcClient *rpc.Client, tx *RPCTestTransactionArgs) func() []interface{}

ArgsSignTransaction will take the junk transaction type that we've created, convert it to a geth style dynamic fee transaction and sign it with the user provide key.

func ArgsTransactionBlockHashAndIndex

func ArgsTransactionBlockHashAndIndex(ctx context.Context, rpcClient *rpc.Client, tx *RPCTestTransactionArgs) func() []interface{}

ArgsTransactionBlockHashAndIndex will execute the provided transaction and return the block hash and index of the given transaction

func ArgsTransactionBlockNumberAndIndex

func ArgsTransactionBlockNumberAndIndex(ctx context.Context, rpcClient *rpc.Client, tx *RPCTestTransactionArgs) func() []interface{}

ArgsTransactionBlockNumberAndIndex will execute the provided transaction and return the block number and index of the given transaction

func ArgsTransactionHash

func ArgsTransactionHash(ctx context.Context, rpcClient *rpc.Client, tx *RPCTestTransactionArgs) func() []interface{}

ArgsTransactionHash will execute the provided transaction and return the transaction hash as an argument to be used in other tests.

func GenericTransactionToDynamicFeeTx

func GenericTransactionToDynamicFeeTx(tx *RPCTestTransactionArgs) ethtypes.DynamicFeeTx

GenericTransactionToDynamicFeeTx convert the simple tx representation that we have into a standard eth type

func GetCurrentChainID

func GetCurrentChainID(ctx context.Context, rpcClient *rpc.Client) (*big.Int, error)

GetCurrentChainID will attempt to determin the chain for the current network

func GetTestAccountNonce

func GetTestAccountNonce(ctx context.Context, rpcClient *rpc.Client) (uint64, error)

GetTestAccountNonce will attempt to get the current nonce for the current test account

func RequireAll

func RequireAll(validators ...func(interface{}) error) func(result interface{}) error

func RequireAny

func RequireAny(validators ...func(interface{}) error) func(result interface{}) error

func ValidateError

func ValidateError(errorMessageRegex string) func(result interface{}) error

ValidateError will check the error message text against the provide regular expression

func ValidateExact

func ValidateExact(expected interface{}) func(result interface{}) error

ValidateExact will validate against the exact value expected.

func ValidateExactJSON

func ValidateExactJSON(expected string) func(result interface{}) error

func ValidateHashedResponse

func ValidateHashedResponse(expectedHash string) func(result interface{}) error

ValidateHashedResponse will take a hex encoded hash and return a function that will validate that a given result has the same hash. The expected has does not start with 0x

func ValidateJSONSchema

func ValidateJSONSchema(schema string) func(result interface{}) error

ValidateJSONSchema is used to validate the response against a JSON Schema

func ValidateRegexString

func ValidateRegexString(regEx string) func(result interface{}) error

ValidateRegexString will match a string from the json response against a regular expression

Types

type RPCTest

type RPCTest interface {
	// GetName returns a more descriptive name of the test being executed
	GetName() string

	// GetMethod returns the json rpc method name
	GetMethod() string

	// GetArgs will return the list of arguments that will be used when calling the rpc
	GetArgs() []interface{}

	// Validate will return an error of the result fails validation
	Validate(result interface{}) error

	// ExpectError is used by the validation code to understand of the test typically returns an error
	ExpectError() bool
}

RPCTest is the common interface for a test. In the future we'll need some addition methods in particular if don't want to run tests that require unlocked accounts or if we want to skip certain namepaces

type RPCTestDynamicArgs

type RPCTestDynamicArgs struct {
	Name           string
	Method         string
	Args           func() []interface{}
	Validator      func(result interface{}) error
	IsError        bool
	RequiresUnlock bool
}

RPCTestDynamicArgs is a simple implementation of the RPCTest that requires a function for Args which will be used to generate the args for testing.

func (*RPCTestDynamicArgs) ExpectError

func (r *RPCTestDynamicArgs) ExpectError() bool

func (*RPCTestDynamicArgs) GetArgs

func (r *RPCTestDynamicArgs) GetArgs() []interface{}

func (*RPCTestDynamicArgs) GetMethod

func (r *RPCTestDynamicArgs) GetMethod() string

func (*RPCTestDynamicArgs) GetName

func (r *RPCTestDynamicArgs) GetName() string

func (*RPCTestDynamicArgs) Validate

func (r *RPCTestDynamicArgs) Validate(result interface{}) error

type RPCTestFilterArgs

type RPCTestFilterArgs struct {
	FromBlock string        `json:"fromBlock,omitempty"`
	ToBlock   string        `json:"toBlock,omitempty"`
	Address   string        `json:"address,omitempty"`
	Topics    []interface{} `json:"topics,omitempty"`
}

type RPCTestGeneric

type RPCTestGeneric struct {
	Name           string
	Method         string
	Args           []interface{}
	Validator      func(result interface{}) error
	IsError        bool
	RequiresUnlock bool
}

RPCTestGeneric is the simplist implementation of the RPCTest. Basically the implementation of the interface is managed by just returning hard coded values for method, args, validator, and error

func (*RPCTestGeneric) ExpectError

func (r *RPCTestGeneric) ExpectError() bool

func (*RPCTestGeneric) GetArgs

func (r *RPCTestGeneric) GetArgs() []interface{}

func (*RPCTestGeneric) GetMethod

func (r *RPCTestGeneric) GetMethod() string

func (*RPCTestGeneric) GetName

func (r *RPCTestGeneric) GetName() string

func (*RPCTestGeneric) Validate

func (r *RPCTestGeneric) Validate(result interface{}) error

type RPCTestTransactionArgs

type RPCTestTransactionArgs struct {
	From                 string `json:"from,omitempty"`
	To                   string `json:"to,omitempty"`
	Gas                  string `json:"gas,omitempty"`
	GasPrice             string `json:"gasPrice,omitempty"`
	MaxFeePerGas         string `json:"maxFeePerGas,omitempty"`
	MaxPriorityFeePerGas string `json:"maxPriorityFeePerGas,omitempty"`
	Value                string `json:"value,omitempty"`
	Nonce                string `json:"nonce,omitempty"`
	Data                 string `json:"data"`
}

RPCTestTransactionArgs is used to send transactions

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL