From ea53ae02b31e604f03533952aaa52f1f0c00b568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Jela=C4=8Da?= Date: Thu, 17 Aug 2023 09:32:15 +0200 Subject: [PATCH] API fuzzing. Set fuzz time to 30s (#1759) * API fuzzing. Set fuzz time to 30s * Added fuzz test for dispatcher * Filter manager fuzz tests * CR changes * Fixed fuzzing argument and seeds --- consensus/polybft/stake_manager_fuzz_test.go | 15 +- jsonrpc/codec_fuzz_test.go | 44 ++++ jsonrpc/dispatcher_fuzz_test.go | 256 +++++++++++++++++++ jsonrpc/dispatcher_test.go | 6 +- jsonrpc/filter_manager_fuzz_test.go | 160 ++++++++++++ scripts/fuzzAll | 2 +- 6 files changed, 478 insertions(+), 5 deletions(-) create mode 100644 jsonrpc/codec_fuzz_test.go create mode 100644 jsonrpc/dispatcher_fuzz_test.go create mode 100644 jsonrpc/filter_manager_fuzz_test.go diff --git a/consensus/polybft/stake_manager_fuzz_test.go b/consensus/polybft/stake_manager_fuzz_test.go index 532c2f6bab..9d7d4b97f7 100644 --- a/consensus/polybft/stake_manager_fuzz_test.go +++ b/consensus/polybft/stake_manager_fuzz_test.go @@ -12,6 +12,7 @@ import ( "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" "github.com/0xPolygon/polygon-edge/types" "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -120,6 +121,12 @@ func FuzzTestStakeManagerPostBlock(f *testing.F) { BlockID: 11, StakeValue: 70, }, + { + EpochID: 7, + ValidatorID: 1, + BlockID: 2, + StakeValue: 10, + }, } for _, seed := range seeds { @@ -149,6 +156,12 @@ func FuzzTestStakeManagerPostBlock(f *testing.F) { validatorSetAddr := types.StringToAddress("0x0001") + bcMock := new(blockchainMock) + for i := 0; i < int(data.BlockID); i++ { + bcMock.On("GetHeaderByNumber", mock.Anything).Return(&types.Header{Hash: types.Hash{6, 4}}, true).Once() + bcMock.On("GetReceiptsByHash", mock.Anything).Return([]*types.Receipt{{}}, error(nil)).Once() + } + stakeManager := newStakeManager( hclog.NewNullLogger(), state, @@ -156,7 +169,7 @@ func FuzzTestStakeManagerPostBlock(f *testing.F) { wallet.NewEcdsaSigner(validators.GetValidator("A").Key()), validatorSetAddr, types.StringToAddress("0x0002"), - nil, + bcMock, 5, ) diff --git a/jsonrpc/codec_fuzz_test.go b/jsonrpc/codec_fuzz_test.go new file mode 100644 index 0000000000..14025cc13a --- /dev/null +++ b/jsonrpc/codec_fuzz_test.go @@ -0,0 +1,44 @@ +package jsonrpc + +import ( + "testing" + + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/assert" +) + +func FuzzBlockNumberOrHashUnmarshalJSON(f *testing.F) { + var blockHash types.Hash + err := blockHash.UnmarshalText([]byte("0xe0ee62fd4a39a6988e24df0b406b90af71932e1b01d5561400a8eab943a33d68")) + assert.NoError(f, err) + + seeds := []string{ + + `{"blockHash": "", "blockNumber": ""}`, + + `{"blockHash": "0xe0ee62fd4a39a6988e24df0b406b90af71932e1b01d5561400a8eab943a33d68", "blockNumber": "0x0"}`, + + `{"blockNumber": "abc"}`, + + `{"blockNumber": ""}`, + + `"latest"`, + + `{"blockNumber": "0x0"}`, + + `"0x0"`, + + `{"blockHash": "0xe0ee62fd4a39a6988e24df0b406b90af71932e1b01d5561400a8eab943a33d68"}`, + } + + for _, seed := range seeds { + f.Add(seed) + } + + f.Fuzz(func(t *testing.T, input string) { + t.Parallel() + + bnh := BlockNumberOrHash{} + _ = bnh.UnmarshalJSON([]byte(input)) + }) +} diff --git a/jsonrpc/dispatcher_fuzz_test.go b/jsonrpc/dispatcher_fuzz_test.go new file mode 100644 index 0000000000..e95fc88245 --- /dev/null +++ b/jsonrpc/dispatcher_fuzz_test.go @@ -0,0 +1,256 @@ +package jsonrpc + +import ( + "fmt" + "testing" + + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func FuzzDispatcherFuncDecode(f *testing.F) { + srv := &mockService{msgCh: make(chan interface{}, 10)} + + dispatcher := newTestDispatcher(f, + hclog.NewNullLogger(), + newMockStore(), + &dispatcherParams{ + chainID: 0, + priceLimit: 0, + jsonRPCBatchLengthLimit: 20, + blockRangeLimit: 1000, + }, + ) + + require.NoError(f, dispatcher.registerService("mock", srv)) + + handleReq := func(typ string, msg string) interface{} { + _, err := dispatcher.handleReq(Request{ + Method: "mock_" + typ, + Params: []byte(msg), + }) + if err != nil { + return err + } + + return <-srv.msgCh + } + + addr1 := types.Address{0x1} + + seeds := []struct { + typ string + msg string + }{ + { + "block", + `["earliest"]`, + }, + { + "block", + `["latest"]`, + }, + { + "block", + `["0x1"]`, + }, + { + "type", + `["` + addr1.String() + `"]`, + }, + { + "blockPtr", + `["a"]`, + }, + { + "blockPtr", + `["a", "latest"]`, + }, + { + "filter", + `[{"fromBlock": "pending", "toBlock": "earliest"}]`, + }, + { + "block", + "[\"8\"]", + }, + { + "block", + "[\"009\"]", + }, + { + "block", + "10", + }, + } + + for _, seed := range seeds { + f.Add(seed.typ, seed.msg) + } + + f.Fuzz(func(t *testing.T, typ string, msg string) { + handleReq(typ, msg) + }) +} + +func FuzzDispatcherBatchRequest(f *testing.F) { + mock := &mockWsConn{ + SetFilterIDFn: func(s string) { + }, + GetFilterIDFn: func() string { + return "" + }, + WriteMessageFn: func(i int, b []byte) error { + return nil + }, + } + + seeds := []struct { + batchLimit uint64 + blockLimit uint64 + params string + }{ + { + batchLimit: 10, + blockLimit: 1000, + params: `["0x1", true]`, + }, + { + batchLimit: 3, + blockLimit: 1000, + params: `["0x2", true]`, + }, + { + batchLimit: 0, + blockLimit: 0, + params: `["0x68656c6c6f20776f726c64"]`, + }, + { + batchLimit: 5, + blockLimit: 30, + params: "invalid request", + }, + } + + for _, seed := range seeds { + f.Add(seed.batchLimit, seed.blockLimit, seed.params) + } + + f.Fuzz(func(t *testing.T, batchLimit uint64, blockLimit uint64, params string) { + dispatcher := newTestDispatcher(t, + hclog.NewNullLogger(), + newMockStore(), + &dispatcherParams{ + chainID: 0, + priceLimit: 0, + jsonRPCBatchLengthLimit: batchLimit, + blockRangeLimit: blockLimit, + }, + ) + + body := fmt.Sprintf(`[{"id":1,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params": %s}]`, params) + + _, err := dispatcher.HandleWs([]byte(body), mock) + assert.NoError(t, err) + _, err = dispatcher.Handle([]byte(body)) + assert.NoError(t, err) + }) +} + +func FuzzDispatcherWebsocketConnectionUnsubscribe(f *testing.F) { + store := newMockStore() + dispatcher := newTestDispatcher(f, + hclog.NewNullLogger(), + store, + &dispatcherParams{ + chainID: 0, + priceLimit: 0, + jsonRPCBatchLengthLimit: 20, + blockRangeLimit: 1000, + }, + ) + mockConn := &mockWsConn{ + SetFilterIDFn: func(s string) { + }, + GetFilterIDFn: func() string { + return "" + }, + WriteMessageFn: func(i int, b []byte) error { + return nil + }, + } + + seeds := []string{ + `{"method": "eth_unsubscribe", "params": ["787832"]}`, + `{"method": "eth_subscribe", "params": ["newHeads"]}`, + } + + for _, seed := range seeds { + f.Add(seed) + } + + f.Fuzz(func(t *testing.T, request string) { + _, err := dispatcher.HandleWs([]byte(request), mockConn) + assert.NoError(t, err) + }) +} + +func FuzzDispatcherWebSocketConnectionRequestFormats(f *testing.F) { + store := newMockStore() + dispatcher := newTestDispatcher(f, + hclog.NewNullLogger(), + store, + &dispatcherParams{ + chainID: 0, + priceLimit: 0, + jsonRPCBatchLengthLimit: 20, + blockRangeLimit: 1000, + }, + ) + mockConnection, _ := newMockWsConnWithMsgCh() + + seeds := []string{ + `{ + "method": "eth_subscribe", + "params": ["newHeads"], + "id": "abc" + }`, + `{ + "method": "eth_subscribe", + "params": ["newHeads"], + "id": null + }`, + `{ + "method": "eth_subscribe", + "params": ["newHeads"], + "id": 2.1 + }`, + `{ + "method": "eth_subscribe", + "params": ["newHeads"] + }`, + `{ + "method": "eth_subscribe", + "params": ["newHeads"], + "id": 2.0 + }`, + `{ + + }`, + `{ + "x": "x", + "y": "y", + "z": "z", + }`, + } + + for _, seed := range seeds { + f.Add(seed) + } + + f.Fuzz(func(t *testing.T, request string) { + _, _ = dispatcher.HandleWs([]byte(request), mockConnection) + }) +} diff --git a/jsonrpc/dispatcher_test.go b/jsonrpc/dispatcher_test.go index 22a350baaa..e3d1cb780f 100644 --- a/jsonrpc/dispatcher_test.go +++ b/jsonrpc/dispatcher_test.go @@ -557,11 +557,11 @@ func TestDispatcher_WebsocketConnection_Unsubscribe(t *testing.T) { assert.Equal(t, "true", string(resp.Result)) } -func newTestDispatcher(t *testing.T, logger hclog.Logger, store JSONRPCStore, params *dispatcherParams) *Dispatcher { - t.Helper() +func newTestDispatcher(tb testing.TB, logger hclog.Logger, store JSONRPCStore, params *dispatcherParams) *Dispatcher { + tb.Helper() d, err := newDispatcher(logger, store, params) - require.NoError(t, err) + require.NoError(tb, err) return d } diff --git a/jsonrpc/filter_manager_fuzz_test.go b/jsonrpc/filter_manager_fuzz_test.go new file mode 100644 index 0000000000..81ecab76aa --- /dev/null +++ b/jsonrpc/filter_manager_fuzz_test.go @@ -0,0 +1,160 @@ +package jsonrpc + +import ( + "math/big" + "strconv" + "testing" + + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/assert" +) + +func FuzzGetLogsForQuery(f *testing.F) { + // Topics we're searching for + topic1 := types.StringToHash("4") + topic2 := types.StringToHash("5") + topic3 := types.StringToHash("6") + + var topics = [][]types.Hash{{topic1}, {topic2}, {topic3}} + + // setup test + store := &mockBlockStore{ + topics: []types.Hash{topic1, topic2, topic3}, + } + store.setupLogs() + + blocks := make([]*types.Block, 5) + + for i := range blocks { + blocks[i] = &types.Block{ + Header: &types.Header{ + Number: uint64(i), + Hash: types.StringToHash(strconv.Itoa(i)), + }, + Transactions: []*types.Transaction{ + { + Value: big.NewInt(10), + }, + { + Value: big.NewInt(11), + }, + { + Value: big.NewInt(12), + }, + }, + } + } + + store.appendBlocksToStore(blocks) + + fm := NewFilterManager(hclog.NewNullLogger(), store, 1000) + + f.Cleanup(func() { + defer fm.Close() + }) + + seeds := []struct { + fromBlock int64 + toBlock int64 + blockHash []byte + }{ + { + blockHash: types.StringToHash("1").Bytes(), + fromBlock: 1, + toBlock: 3, + }, + { + blockHash: types.StringToHash("2").Bytes(), + fromBlock: 2, + toBlock: 2, + }, + {}, + { + blockHash: types.StringToHash("3").Bytes(), + fromBlock: 10, + toBlock: 5, + }, + { + blockHash: types.StringToHash("4").Bytes(), + fromBlock: 10, + toBlock: 1012, + }, + } + + for _, seed := range seeds { + f.Add(seed.blockHash, seed.fromBlock, seed.toBlock) + } + + f.Fuzz(func(t *testing.T, hash []byte, fromBlock int64, toBlock int64) { + if len(hash) != types.HashLength { + t.Skip() + } + blockHash := types.BytesToHash(hash) + + logQuery := LogQuery{ + BlockHash: &blockHash, + fromBlock: BlockNumber(fromBlock), + toBlock: BlockNumber(toBlock), + Topics: topics, + } + _, _ = fm.GetLogsForQuery(&logQuery) + }) +} + +func FuzzGetLogFilterFromID(f *testing.F) { + store := newMockStore() + + m := NewFilterManager(hclog.NewNullLogger(), store, 1000) + defer m.Close() + + go m.Run() + + seeds := []struct { + address []byte + toBlock int64 + fromBlock int64 + }{ + { + address: types.StringToAddress("1").Bytes(), + toBlock: 10, + fromBlock: 0, + }, + { + address: types.StringToAddress("2").Bytes(), + toBlock: 0, + fromBlock: -4, + }, + { + address: types.StringToAddress("40").Bytes(), + toBlock: 34, + fromBlock: 5, + }, + { + address: types.StringToAddress("12").Bytes(), + toBlock: -1, + fromBlock: -5, + }, + } + + for _, seed := range seeds { + f.Add(seed.address, seed.toBlock, seed.fromBlock) + } + + f.Fuzz(func(t *testing.T, address []byte, toBlock int64, fromBlock int64) { + if len(address) != types.AddressLength { + t.Skip() + } + logFilter := &LogQuery{ + Addresses: []types.Address{types.BytesToAddress(address)}, + toBlock: BlockNumber(toBlock), + fromBlock: BlockNumber(fromBlock), + } + retrivedLogFilter, err := m.GetLogFilterFromID( + m.NewLogFilter(logFilter, &MockClosedWSConnection{}), + ) + if err != nil { + assert.Equal(t, logFilter, retrivedLogFilter.query) + } + }) +} diff --git a/scripts/fuzzAll b/scripts/fuzzAll index ece67fb600..9f49af24d2 100755 --- a/scripts/fuzzAll +++ b/scripts/fuzzAll @@ -2,7 +2,7 @@ set -e -fuzzTime=${1:-10} +fuzzTime=${1:-30} files=$(grep -r --include='**_test.go' --files-with-matches 'func Fuzz' .)