eth rpc: Params are optional in eth_subscribe

This commit is contained in:
Łukasz Magiera 2023-01-31 11:00:15 +01:00
parent 9701b11641
commit ad14d71978
13 changed files with 103 additions and 58 deletions

View File

@ -832,7 +832,7 @@ type FullNode interface {
// - logs: notify new event logs that match a criteria
// params contains additional parameters used with the log event type
// The client will receive a stream of EthSubscriptionResponse values until EthUnsubscribe is called.
EthSubscribe(ctx context.Context, eventType string, params *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error) //perm:write
EthSubscribe(ctx context.Context, params jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) //perm:write
// Unsubscribe from a websocket subscription
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) //perm:write

View File

@ -7,6 +7,7 @@ import (
"github.com/ipfs/go-cid"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-jsonrpc"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/builtin/v9/miner"
"github.com/filecoin-project/go-state-types/dline"
@ -102,6 +103,6 @@ type Gateway interface {
EthNewBlockFilter(ctx context.Context) (ethtypes.EthFilterID, error)
EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error)
EthUninstallFilter(ctx context.Context, id ethtypes.EthFilterID) (bool, error)
EthSubscribe(ctx context.Context, eventType string, params *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error)
EthSubscribe(ctx context.Context, params jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error)
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error)
}

View File

@ -23,6 +23,7 @@ import (
bitfield "github.com/filecoin-project/go-bitfield"
datatransfer "github.com/filecoin-project/go-data-transfer"
retrievalmarket "github.com/filecoin-project/go-fil-markets/retrievalmarket"
jsonrpc "github.com/filecoin-project/go-jsonrpc"
auth "github.com/filecoin-project/go-jsonrpc/auth"
abi "github.com/filecoin-project/go-state-types/abi"
big "github.com/filecoin-project/go-state-types/big"
@ -1388,18 +1389,18 @@ func (mr *MockFullNodeMockRecorder) EthSendRawTransaction(arg0, arg1 interface{}
}
// EthSubscribe mocks base method.
func (m *MockFullNode) EthSubscribe(arg0 context.Context, arg1 string, arg2 *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error) {
func (m *MockFullNode) EthSubscribe(arg0 context.Context, arg1 jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EthSubscribe", arg0, arg1, arg2)
ret := m.ctrl.Call(m, "EthSubscribe", arg0, arg1)
ret0, _ := ret[0].(ethtypes.EthSubscriptionID)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// EthSubscribe indicates an expected call of EthSubscribe.
func (mr *MockFullNodeMockRecorder) EthSubscribe(arg0, arg1, arg2 interface{}) *gomock.Call {
func (mr *MockFullNodeMockRecorder) EthSubscribe(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthSubscribe", reflect.TypeOf((*MockFullNode)(nil).EthSubscribe), arg0, arg1, arg2)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthSubscribe", reflect.TypeOf((*MockFullNode)(nil).EthSubscribe), arg0, arg1)
}
// EthUninstallFilter mocks base method.

View File

@ -302,7 +302,7 @@ type FullNodeMethods struct {
EthSendRawTransaction func(p0 context.Context, p1 ethtypes.EthBytes) (ethtypes.EthHash, error) `perm:"read"`
EthSubscribe func(p0 context.Context, p1 string, p2 *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error) `perm:"write"`
EthSubscribe func(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) `perm:"write"`
EthUninstallFilter func(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) `perm:"write"`
@ -704,7 +704,7 @@ type GatewayMethods struct {
EthSendRawTransaction func(p0 context.Context, p1 ethtypes.EthBytes) (ethtypes.EthHash, error) ``
EthSubscribe func(p0 context.Context, p1 string, p2 *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error) ``
EthSubscribe func(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) ``
EthUninstallFilter func(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) ``
@ -2320,14 +2320,14 @@ func (s *FullNodeStub) EthSendRawTransaction(p0 context.Context, p1 ethtypes.Eth
return *new(ethtypes.EthHash), ErrNotSupported
}
func (s *FullNodeStruct) EthSubscribe(p0 context.Context, p1 string, p2 *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error) {
func (s *FullNodeStruct) EthSubscribe(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) {
if s.Internal.EthSubscribe == nil {
return *new(ethtypes.EthSubscriptionID), ErrNotSupported
}
return s.Internal.EthSubscribe(p0, p1, p2)
return s.Internal.EthSubscribe(p0, p1)
}
func (s *FullNodeStub) EthSubscribe(p0 context.Context, p1 string, p2 *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error) {
func (s *FullNodeStub) EthSubscribe(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) {
return *new(ethtypes.EthSubscriptionID), ErrNotSupported
}
@ -4465,14 +4465,14 @@ func (s *GatewayStub) EthSendRawTransaction(p0 context.Context, p1 ethtypes.EthB
return *new(ethtypes.EthHash), ErrNotSupported
}
func (s *GatewayStruct) EthSubscribe(p0 context.Context, p1 string, p2 *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error) {
func (s *GatewayStruct) EthSubscribe(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) {
if s.Internal.EthSubscribe == nil {
return *new(ethtypes.EthSubscriptionID), ErrNotSupported
}
return s.Internal.EthSubscribe(p0, p1, p2)
return s.Internal.EthSubscribe(p0, p1)
}
func (s *GatewayStub) EthSubscribe(p0 context.Context, p1 string, p2 *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error) {
func (s *GatewayStub) EthSubscribe(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) {
return *new(ethtypes.EthSubscriptionID), ErrNotSupported
}

Binary file not shown.

Binary file not shown.

View File

@ -619,6 +619,43 @@ type EthLog struct {
BlockNumber EthUint64 `json:"blockNumber"`
}
// EthSubscribeParams handles raw jsonrpc params for eth_subscribe
type EthSubscribeParams struct {
EventType string
Params *EthSubscriptionParams
}
func (e *EthSubscribeParams) UnmarshalJSON(b []byte) error {
var params []json.RawMessage
err := json.Unmarshal(b, &params)
if err != nil {
return err
}
switch len(params) {
case 2:
err = json.Unmarshal(params[1], &e.Params)
if err != nil {
return err
}
fallthrough
case 1:
err = json.Unmarshal(params[0], &e.EventType)
if err != nil {
return err
}
default:
return xerrors.Errorf("expected 1 or 2 params, got %d", len(params))
}
return nil
}
func (e EthSubscribeParams) MarshalJSON() ([]byte, error) {
if e.Params != nil {
return json.Marshal([]interface{}{e.EventType, e.Params})
}
return json.Marshal([]interface{}{e.EventType})
}
type EthSubscriptionParams struct {
// List of topics to be matched.
// Optional, default: empty list

View File

@ -2886,14 +2886,7 @@ Perms: write
Inputs:
```json
[
"string value",
{
"topics": [
[
"0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e"
]
]
}
"Bw=="
]
```

View File

@ -12,6 +12,7 @@ import (
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-bitfield"
"github.com/filecoin-project/go-jsonrpc"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/dline"
"github.com/filecoin-project/go-state-types/network"
@ -117,7 +118,7 @@ type TargetAPI interface {
EthNewBlockFilter(ctx context.Context) (ethtypes.EthFilterID, error)
EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error)
EthUninstallFilter(ctx context.Context, id ethtypes.EthFilterID) (bool, error)
EthSubscribe(ctx context.Context, eventType string, params *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error)
EthSubscribe(ctx context.Context, params jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error)
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error)
}

View File

@ -436,7 +436,13 @@ func (gw *Node) EthUninstallFilter(ctx context.Context, id ethtypes.EthFilterID)
return ok, nil
}
func (gw *Node) EthSubscribe(ctx context.Context, eventType string, params *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error) {
func (gw *Node) EthSubscribe(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) {
// validate params
_, err := jsonrpc.DecodeParams[ethtypes.EthSubscribeParams](p)
if err != nil {
return ethtypes.EthSubscriptionID{}, xerrors.Errorf("decoding params: %w", err)
}
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return ethtypes.EthSubscriptionID{}, err
}
@ -458,7 +464,7 @@ func (gw *Node) EthSubscribe(ctx context.Context, eventType string, params *etht
return ethtypes.EthSubscriptionID{}, fmt.Errorf("too many subscriptions")
}
sub, err := gw.target.EthSubscribe(ctx, eventType, params)
sub, err := gw.target.EthSubscribe(ctx, p)
if err != nil {
return ethtypes.EthSubscriptionID{}, err
}

View File

@ -6,6 +6,7 @@ import (
"context"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"sort"
@ -21,6 +22,7 @@ import (
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-jsonrpc"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
@ -29,6 +31,7 @@ import (
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/types/ethtypes"
"github.com/filecoin-project/lotus/itests/kit"
res "github.com/filecoin-project/lotus/lib/result"
)
// SolidityContractDef holds information about one of the test contracts
@ -438,7 +441,7 @@ func TestEthSubscribeLogsNoTopicSpec(t *testing.T) {
t.Logf("actor ID address is %s", idAddr)
// install filter
subId, err := client.EthSubscribe(ctx, "logs", nil)
subId, err := client.EthSubscribe(ctx, res.Wrap[jsonrpc.RawParams](json.Marshal(ethtypes.EthSubscribeParams{EventType: "logs"})).Assert(require.NoError))
require.NoError(err)
var subResponses []ethtypes.EthSubscriptionResponse
@ -567,44 +570,33 @@ func TestEthSubscribeLogs(t *testing.T) {
testResponses := map[string]chan ethtypes.EthSubscriptionResponse{}
// quit is used to signal that we're ready to start testing collected results
quit := make(chan struct{})
// Create all the filters
for _, tc := range testCases {
// subscribe to topics in filter
subCh, err := client.EthSubscribe(ctx, "logs", &ethtypes.EthSubscriptionParams{Topics: tc.spec.Topics})
subParam, err := json.Marshal(ethtypes.EthSubscribeParams{
EventType: "logs",
Params: &ethtypes.EthSubscriptionParams{Topics: tc.spec.Topics},
})
require.NoError(err)
subId, err := client.EthSubscribe(ctx, subParam)
require.NoError(err)
responseCh := make(chan ethtypes.EthSubscriptionResponse, len(invocations))
testResponses[tc.name] = responseCh
// start a goroutine to forward responses from subscription to a buffered channel with guaranteed capacity
go func(subCh <-chan ethtypes.EthSubscriptionResponse, responseCh chan<- ethtypes.EthSubscriptionResponse, quit chan struct{}) {
defer func() {
close(responseCh)
}()
for {
select {
case resp := <-subCh:
responseCh <- resp
case <-quit:
return
case <-ctx.Done():
return
}
}
}(subCh, responseCh, quit)
err = client.EthSubRouter.AddSub(ctx, subId, func(ctx context.Context, resp *ethtypes.EthSubscriptionResponse) error {
responseCh <- *resp
return nil
})
require.NoError(err)
}
// Perform all the invocations
messages := invokeAndWaitUntilAllOnChain(t, client, invocations)
// wait a little for subscriptions to gather results and then tell all the goroutines to stop
// wait a little for subscriptions to gather results
time.Sleep(blockTime * 6)
close(quit)
for _, tc := range testCases {
tc := tc // appease the lint despot
@ -612,6 +604,9 @@ func TestEthSubscribeLogs(t *testing.T) {
responseCh, ok := testResponses[tc.name]
require.True(ok)
// don't expect any more responses
close(responseCh)
var elogs []*ethtypes.EthLog
for resp := range responseCh {
rlist, ok := resp.Result.([]interface{})

View File

@ -30,6 +30,12 @@ func Wrap[T any](value T, err error) Result[T] {
}
}
func (r *Result[T]) Unwrap() (T, error) {
func (r Result[T]) Unwrap() (T, error) {
return r.Value, r.Error
}
func (r Result[T]) Assert(noErrFn func(err error, msgAndArgs ...interface{})) T {
noErrFn(r.Error)
return r.Value
}

View File

@ -77,7 +77,7 @@ type EthEventAPI interface {
EthNewBlockFilter(ctx context.Context) (ethtypes.EthFilterID, error)
EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error)
EthUninstallFilter(ctx context.Context, id ethtypes.EthFilterID) (bool, error)
EthSubscribe(ctx context.Context, eventType string, params *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error)
EthSubscribe(ctx context.Context, params jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error)
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error)
}
@ -1103,7 +1103,12 @@ const (
EthSubscribeEventTypeLogs = "logs"
)
func (e *EthEvent) EthSubscribe(ctx context.Context, eventType string, params *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error) {
func (e *EthEvent) EthSubscribe(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) {
params, err := jsonrpc.DecodeParams[ethtypes.EthSubscribeParams](p)
if err != nil {
return ethtypes.EthSubscriptionID{}, xerrors.Errorf("decoding params: %w", err)
}
if e.SubManager == nil {
return ethtypes.EthSubscriptionID{}, api.ErrNotSupported
}
@ -1118,7 +1123,7 @@ func (e *EthEvent) EthSubscribe(ctx context.Context, eventType string, params *e
return ethtypes.EthSubscriptionID{}, err
}
switch eventType {
switch params.EventType {
case EthSubscribeEventTypeHeads:
f, err := e.TipSetFilterManager.Install(ctx)
if err != nil {
@ -1130,13 +1135,13 @@ func (e *EthEvent) EthSubscribe(ctx context.Context, eventType string, params *e
case EthSubscribeEventTypeLogs:
keys := map[string][][]byte{}
if params != nil {
if params.Params != nil {
var err error
keys, err = parseEthTopics(params.Topics)
keys, err = parseEthTopics(params.Params.Topics)
if err != nil {
// clean up any previous filters added and stop the sub
_, _ = e.EthUnsubscribe(ctx, sub.id)
return nil, err
return ethtypes.EthSubscriptionID{}, err
}
}
@ -1148,7 +1153,7 @@ func (e *EthEvent) EthSubscribe(ctx context.Context, eventType string, params *e
}
sub.addFilter(ctx, f)
default:
return ethtypes.EthSubscriptionID{}, xerrors.Errorf("unsupported event type: %s", eventType)
return ethtypes.EthSubscriptionID{}, xerrors.Errorf("unsupported event type: %s", params.EventType)
}
return sub.id, nil