diff --git a/cmd/loadtest/loadtest.go b/cmd/loadtest/loadtest.go index f97935b4..b43cf0e3 100644 --- a/cmd/loadtest/loadtest.go +++ b/cmd/loadtest/loadtest.go @@ -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() }() diff --git a/cmd/rpcfuzz/rpcfuzz.go b/cmd/rpcfuzz/rpcfuzz.go index d5bc82ae..7d667c5f 100644 --- a/cmd/rpcfuzz/rpcfuzz.go +++ b/cmd/rpcfuzz/rpcfuzz.go @@ -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 ( @@ -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", @@ -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 @@ -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 @@ -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, }) @@ -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", @@ -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{ @@ -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, }) @@ -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", @@ -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 "{}" @@ -859,7 +895,7 @@ 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{ @@ -867,7 +903,7 @@ func setupTests(ctx context.Context, rpcClient *rpc.Client) { 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{ @@ -875,7 +911,7 @@ func setupTests(ctx context.Context, rpcClient *rpc.Client) { 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 [] @@ -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", @@ -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", @@ -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", @@ -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", @@ -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{}) diff --git a/cmd/rpcfuzz/usage.md b/cmd/rpcfuzz/usage.md index 7c3e0098..4f118147 100644 --- a/cmd/rpcfuzz/usage.md +++ b/cmd/rpcfuzz/usage.md @@ -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 diff --git a/doc/polycli_rpcfuzz.md b/doc/polycli_rpcfuzz.md index 3e6cc3fc..62338e09 100644 --- a/doc/polycli_rpcfuzz.md +++ b/doc/polycli_rpcfuzz.md @@ -26,47 +26,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