Skip to content

Commit

Permalink
feat(rpcfuzz): add empty eth_estimageGas, eth_sendTransaction and…
Browse files Browse the repository at this point in the history
… `eth_sendRawTransaction` calls to `rpcfuzz` (#131)

* feat(rpcfuzz): add empty estimate gas call

* doc: update `rpcfuzz` usage

* chore: remove line break

* chore: gen-doc

* feat: add empty `eth_sendTransaction` and `eth_sendRawTransaction`

* chore: add json-rpc error codes constants

* doc: small update

* fix: add `FlagErrorValidation` to the empty tests

* chore: gen-doc
  • Loading branch information
leovct authored Oct 11, 2023
1 parent 4db8e91 commit dd3cc0f
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 54 deletions.
1 change: 0 additions & 1 deletion cmd/loadtest/loadtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -1171,7 +1171,6 @@ func loadTestRecall(ctx context.Context, c *ethclient.Client, nonce uint64, orig
}

func loadTestRPC(ctx context.Context, c *ethclient.Client, nonce uint64, ia *IndexedActivity) (t1 time.Time, t2 time.Time, err error) {

funcNum := randSrc.Intn(300)
t1 = time.Now()
defer func() { t2 = time.Now() }()
Expand Down
74 changes: 55 additions & 19 deletions cmd/rpcfuzz/rpcfuzz.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ const (
defaultMaxPriorityFeePerGas = "0x1000000000"

defaultNonceTestOffset uint64 = 0x100000000

// JSON-RPC error codes.
// https://eips.ethereum.org/EIPS/eip-1474
parseErr = -32700
invalidRequestErr = -32600
methodNotFoundErr = -32601
invalidParamsErr = -32602
internalErr = -32603
)

var (
Expand Down Expand Up @@ -173,7 +181,6 @@ var (

// setupTests will add all of the `RPCTests` to the `allTests` slice.
func setupTests(ctx context.Context, rpcClient *rpc.Client) {

// cast rpc --rpc-url localhost:8545 net_version
allTests = append(allTests, &RPCTestGeneric{
Name: "RPCTestNetVersion",
Expand Down Expand Up @@ -204,7 +211,7 @@ func setupTests(ctx context.Context, rpcClient *rpc.Client) {
Flags: FlagErrorValidation | FlagStrictValidation,
Method: "web3_sha3",
Args: []interface{}{"68656c6c6f20776f726c64"},
Validator: ValidateError(-32602, `cannot unmarshal hex string without 0x prefix`),
Validator: ValidateError(invalidParamsErr, `cannot unmarshal hex string without 0x prefix`),
})

// cast rpc --rpc-url localhost:8545 net_listening
Expand All @@ -229,7 +236,7 @@ func setupTests(ctx context.Context, rpcClient *rpc.Client) {
Flags: FlagErrorValidation | FlagStrictValidation,
Method: "eth_protocolVersion",
Args: []interface{}{},
Validator: ValidateError(-32601, `method eth_protocolVersion does not exist`),
Validator: ValidateError(methodNotFoundErr, `method eth_protocolVersion does not exist`),
})

// cast rpc --rpc-url localhost:8545 eth_syncing
Expand Down Expand Up @@ -507,7 +514,7 @@ func setupTests(ctx context.Context, rpcClient *rpc.Client) {
Name: "RPCTestEthSignFail",
Method: "eth_sign",
Args: []interface{}{testEthAddress.String(), "0xdeadbeef"},
Validator: ValidateError(-32000, `unknown account`),
Validator: ValidateError(invalidRequestErr, `unknown account`),
Flags: FlagErrorValidation | FlagStrictValidation | FlagRequiresUnlock,
})

Expand All @@ -529,6 +536,25 @@ func setupTests(ctx context.Context, rpcClient *rpc.Client) {
Flags: FlagRequiresUnlock,
})

// $ curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[],"id":1}' http://localhost:8545
// {"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"missing value for required argument 0"}}
allTests = append(allTests, &RPCTestGeneric{
Name: "RPCTestEthSendTransactionEmpty",
Method: "eth_sendTransaction",
Args: []interface{}{},
Flags: FlagErrorValidation,
Validator: ValidateError(invalidParamsErr, "missing value for required argument 0"),
})
// $ curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":[],"id":1}' http://localhost:8545
// {"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"missing value for required argument 0"}}
allTests = append(allTests, &RPCTestGeneric{
Name: "RPCTestEthSendRawTransactionEmpty",
Method: "eth_sendRawTransaction",
Args: []interface{}{},
Flags: FlagErrorValidation,
Validator: ValidateError(invalidParamsErr, "missing value for required argument 0"),
})

// cast rpc --rpc-url localhost:8545 eth_sendRawTransaction '{"from": "0xb9b1cf51a65b50f74ed8bcb258413c02cba2ec57", "to": "0x85dA99c8a7C2C95964c8EfD687E95E632Fc533D6", "data": "0x", "gas": "0x5208", "gasPrice": "0x1", "nonce": "0x1"}'
allTests = append(allTests, &RPCTestDynamicArgs{
Name: "RPCTestEthSendRawTransaction",
Expand All @@ -540,7 +566,7 @@ func setupTests(ctx context.Context, rpcClient *rpc.Client) {
Name: "RPCTestEthSendRawTransactionNonceTooLow",
Method: "eth_sendRawTransaction",
Args: ArgsSignTransactionWithNonce(ctx, rpcClient, &RPCTestTransactionArgs{To: testEthAddress.String(), Value: "0x123", Gas: "0x5208", Data: "0x", MaxFeePerGas: defaultMaxFeePerGas, MaxPriorityFeePerGas: defaultMaxPriorityFeePerGas}, 0),
Validator: ValidateError(-32000, `nonce too low`),
Validator: ValidateError(invalidRequestErr, `nonce too low`),
Flags: FlagErrorValidation | FlagStrictValidation,
})
allTests = append(allTests, &RPCTestDynamicArgs{
Expand All @@ -554,14 +580,14 @@ func setupTests(ctx context.Context, rpcClient *rpc.Client) {
Name: "RPCTestEthSendRawTransactionNonceKnown",
Method: "eth_sendRawTransaction",
Args: ArgsSignTransactionWithNonce(ctx, rpcClient, &RPCTestTransactionArgs{To: testEthAddress.String(), Value: "0x123", Gas: "0x5208", Data: "0x", MaxFeePerGas: defaultMaxFeePerGas, MaxPriorityFeePerGas: defaultMaxPriorityFeePerGas}, testAccountNonce|defaultNonceTestOffset),
Validator: ValidateError(-32000, `already known`),
Validator: ValidateError(invalidRequestErr, `already known`),
Flags: FlagErrorValidation | FlagStrictValidation | FlagOrderDependent,
})
allTests = append(allTests, &RPCTestDynamicArgs{
Name: "RPCTestEthSendRawTransactionNonceUnderpriced",
Method: "eth_sendRawTransaction",
Args: ArgsSignTransactionWithNonce(ctx, rpcClient, &RPCTestTransactionArgs{To: testEthAddress.String(), Value: "0x1234", Gas: "0x5208", Data: "0x", MaxFeePerGas: defaultMaxFeePerGas, MaxPriorityFeePerGas: defaultMaxPriorityFeePerGas}, testAccountNonce|defaultNonceTestOffset),
Validator: ValidateError(-32000, `replacement`),
Validator: ValidateError(invalidRequestErr, `replacement`),
Flags: FlagErrorValidation | FlagStrictValidation | FlagOrderDependent,
})

Expand Down Expand Up @@ -610,6 +636,16 @@ func setupTests(ctx context.Context, rpcClient *rpc.Client) {
Flags: FlagStrictValidation,
})

// $ curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc": "2.0", "method": "eth_estimateGas", "params": [], "id":1}' localhost:8545
// {"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"missing value for required argument 0"}}
allTests = append(allTests, &RPCTestGeneric{
Name: "RPCTestEthEstimateGasEmpty",
Method: "eth_estimateGas",
Args: []interface{}{},
Flags: FlagErrorValidation,
Validator: ValidateError(invalidParamsErr, `missing value for required argument 0`),
})

// cast block --rpc-url localhost:8545 latest
allTests = append(allTests, &RPCTestDynamicArgs{
Name: "RPCTestEthGetBlockByHash",
Expand Down Expand Up @@ -714,28 +750,28 @@ func setupTests(ctx context.Context, rpcClient *rpc.Client) {
Flags: FlagErrorValidation | FlagStrictValidation,
Method: "eth_getCompilers",
Args: []interface{}{},
Validator: ValidateError(-32601, `method eth_getCompilers does not exist`),
Validator: ValidateError(methodNotFoundErr, `method eth_getCompilers does not exist`),
})
allTests = append(allTests, &RPCTestGeneric{
Name: "RPCTestEthCompileSolidity",
Flags: FlagErrorValidation | FlagStrictValidation,
Method: "eth_compileSolidity",
Args: []interface{}{},
Validator: ValidateError(-32601, `method eth_compileSolidity does not exist`),
Validator: ValidateError(methodNotFoundErr, `method eth_compileSolidity does not exist`),
})
allTests = append(allTests, &RPCTestGeneric{
Name: "RPCTestEthCompileLLL",
Flags: FlagErrorValidation | FlagStrictValidation,
Method: "eth_compileLLL",
Args: []interface{}{},
Validator: ValidateError(-32601, `method eth_compileLLL does not exist`),
Validator: ValidateError(methodNotFoundErr, `method eth_compileLLL does not exist`),
})
allTests = append(allTests, &RPCTestGeneric{
Name: "RPCTestEthCompileSerpent",
Flags: FlagErrorValidation | FlagStrictValidation,
Method: "eth_compileSerpent",
Args: []interface{}{},
Validator: ValidateError(-32601, `method eth_compileSerpent does not exist`),
Validator: ValidateError(methodNotFoundErr, `method eth_compileSerpent does not exist`),
})

// cast rpc --rpc-url localhost:8545 eth_newFilter "{}"
Expand Down Expand Up @@ -859,23 +895,23 @@ func setupTests(ctx context.Context, rpcClient *rpc.Client) {
Method: "eth_getWork",
Args: []interface{}{},
Flags: FlagErrorValidation | FlagStrictValidation,
Validator: ValidateError(-32601, `method eth_getWork does not exist`),
Validator: ValidateError(methodNotFoundErr, `method eth_getWork does not exist`),
})
// cast rpc --rpc-url localhost:8545 eth_submitWork 0x0011223344556677 0x00112233445566778899AABBCCDDEEFF 0x00112233445566778899AABBCCDDEEFF
allTests = append(allTests, &RPCTestGeneric{
Name: "RPCTestSubmitWork",
Method: "eth_submitWork",
Args: []interface{}{"0x0011223344556677", "0x00112233445566778899AABBCCDDEEFF", "0x00112233445566778899AABBCCDDEEFF"},
Flags: FlagErrorValidation | FlagStrictValidation,
Validator: ValidateError(-32601, `method eth_submitWork does not exist`),
Validator: ValidateError(methodNotFoundErr, `method eth_submitWork does not exist`),
})
// cast rpc --rpc-url localhost:8545 eth_submitHashrate 0x00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF 0x00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF
allTests = append(allTests, &RPCTestGeneric{
Name: "RPCTestSubmitHashrate",
Method: "eth_submitHashrate",
Args: []interface{}{"0x00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF", "0x00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"},
Flags: FlagErrorValidation | FlagStrictValidation,
Validator: ValidateError(-32601, `method eth_submitHashrate does not exist`),
Validator: ValidateError(methodNotFoundErr, `method eth_submitHashrate does not exist`),
})

// cast rpc --rpc-url localhost:8545 eth_feeHistory 128 latest []
Expand Down Expand Up @@ -953,7 +989,7 @@ func setupTests(ctx context.Context, rpcClient *rpc.Client) {
Method: "debug_getRawBlock",
Args: []interface{}{"pending"},
Flags: FlagErrorValidation | FlagStrictValidation,
Validator: ValidateError(-32000, `not found`),
Validator: ValidateError(invalidRequestErr, `not found`),
})
allTests = append(allTests, &RPCTestGeneric{
Name: "RPCTestDebugGetRawBlockEarliest",
Expand Down Expand Up @@ -988,7 +1024,7 @@ func setupTests(ctx context.Context, rpcClient *rpc.Client) {
Method: "debug_getRawHeader",
Args: []interface{}{"pending"},
Flags: FlagErrorValidation | FlagStrictValidation,
Validator: ValidateError(-32000, `not found`),
Validator: ValidateError(invalidRequestErr, `not found`),
})
allTests = append(allTests, &RPCTestGeneric{
Name: "RPCTestDebugGetRawHeaderEarliest",
Expand Down Expand Up @@ -1050,7 +1086,7 @@ func setupTests(ctx context.Context, rpcClient *rpc.Client) {
Method: "debug_traceBlockByNumber",
Args: []interface{}{"0x0", nil},
Flags: FlagErrorValidation | FlagStrictValidation,
Validator: ValidateError(-32000, `genesis is not traceable`),
Validator: ValidateError(invalidRequestErr, `genesis is not traceable`),
})
allTests = append(allTests, &RPCTestGeneric{
Name: "RPCTestDebugTraceBlockByNumberOne",
Expand All @@ -1069,7 +1105,7 @@ func setupTests(ctx context.Context, rpcClient *rpc.Client) {
Method: "debug_traceBlockByNumber",
Args: []interface{}{"earliest", nil},
Flags: FlagErrorValidation | FlagStrictValidation,
Validator: ValidateError(-32000, `genesis is not traceable`),
Validator: ValidateError(invalidRequestErr, `genesis is not traceable`),
})
allTests = append(allTests, &RPCTestGeneric{
Name: "RPCTestDebugTraceBlockByNumberPending",
Expand Down Expand Up @@ -1098,7 +1134,7 @@ func setupTests(ctx context.Context, rpcClient *rpc.Client) {
Method: "debug_traceBlock",
Args: ArgsRawBlock(ctx, rpcClient, "0x0", nil),
Flags: FlagErrorValidation | FlagStrictValidation,
Validator: ValidateError(-32000, `genesis is not traceable`),
Validator: ValidateError(invalidRequestErr, `genesis is not traceable`),
})

uniqueTests := make(map[RPCTest]struct{})
Expand Down
58 changes: 41 additions & 17 deletions cmd/rpcfuzz/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,71 @@ Some setup might be needed depending on how you're testing. We'll demonstrate wi
In order to quickly test this, you can run geth in dev mode.

```bash
$ geth --dev --dev.period 5 --http --http.addr localhost \
$ 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 \
--http.api 'admin,debug,web3,eth,txpool,personal,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
```

If we wanted to use erigon for testing, we could do something like this as well.

```bash
$ erigon --chain dev --dev.period 5 --http --http.addr localhost \
$ erigon \
--chain 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 \
--miner.gaslimit 10 --gpo.blocks 1 \
--gpo.percentile 1 --mine
--http.api 'admin,debug,web3,eth,txpool,clique,net' \
--verbosity 5 \
--rpc.gascap 50000000 \
--miner.gaslimit 10 \
--gpo.blocks 1 \
--gpo.percentile 1 \
--mine
```

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
$ cast send \
--from "$(cast rpc --rpc-url localhost:8545 eth_coinbase | jq -r '.')" \
--rpc-url localhost:8545 \
--unlocked \
--value 100ether \
--json \
0x85dA99c8a7C2C95964c8EfD687E95E632Fc533D6 | jq
```

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

```bash
$ cast send --from 0x85dA99c8a7C2C95964c8EfD687E95E632Fc533D6 \
$ cast send \
--from 0x85dA99c8a7C2C95964c8EfD687E95E632Fc533D6 \
--private-key 0x42b6e34dc21598a807dc19d7784c71b2a7a01f6480dc6f58258f78e539f1a1fa \
--rpc-url localhost:8545 --create \
"$(cat ./contracts/ERC20.bin)"
--rpc-url localhost:8545 \
--json \
--create \
"$(cat ./contracts/tokens/ERC20/ERC20.bin)" | jq
```

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

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

### Links
Expand Down
Loading

0 comments on commit dd3cc0f

Please sign in to comment.