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 // - logs: notify new event logs that match a criteria
// params contains additional parameters used with the log event type // params contains additional parameters used with the log event type
// The client will receive a stream of EthSubscriptionResponse values until EthUnsubscribe is called. // 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 // Unsubscribe from a websocket subscription
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) //perm:write 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/ipfs/go-cid"
"github.com/filecoin-project/go-address" "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/abi"
"github.com/filecoin-project/go-state-types/builtin/v9/miner" "github.com/filecoin-project/go-state-types/builtin/v9/miner"
"github.com/filecoin-project/go-state-types/dline" "github.com/filecoin-project/go-state-types/dline"
@ -102,6 +103,6 @@ type Gateway interface {
EthNewBlockFilter(ctx context.Context) (ethtypes.EthFilterID, error) EthNewBlockFilter(ctx context.Context) (ethtypes.EthFilterID, error)
EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error) EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error)
EthUninstallFilter(ctx context.Context, id ethtypes.EthFilterID) (bool, 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) EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error)
} }

View File

@ -23,6 +23,7 @@ import (
bitfield "github.com/filecoin-project/go-bitfield" bitfield "github.com/filecoin-project/go-bitfield"
datatransfer "github.com/filecoin-project/go-data-transfer" datatransfer "github.com/filecoin-project/go-data-transfer"
retrievalmarket "github.com/filecoin-project/go-fil-markets/retrievalmarket" retrievalmarket "github.com/filecoin-project/go-fil-markets/retrievalmarket"
jsonrpc "github.com/filecoin-project/go-jsonrpc"
auth "github.com/filecoin-project/go-jsonrpc/auth" auth "github.com/filecoin-project/go-jsonrpc/auth"
abi "github.com/filecoin-project/go-state-types/abi" abi "github.com/filecoin-project/go-state-types/abi"
big "github.com/filecoin-project/go-state-types/big" big "github.com/filecoin-project/go-state-types/big"
@ -1388,18 +1389,18 @@ func (mr *MockFullNodeMockRecorder) EthSendRawTransaction(arg0, arg1 interface{}
} }
// EthSubscribe mocks base method. // 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() 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) ret0, _ := ret[0].(ethtypes.EthSubscriptionID)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// EthSubscribe indicates an expected call of EthSubscribe. // 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() 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. // 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"` 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"` 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) `` 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) `` 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 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 { if s.Internal.EthSubscribe == nil {
return *new(ethtypes.EthSubscriptionID), ErrNotSupported 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 return *new(ethtypes.EthSubscriptionID), ErrNotSupported
} }
@ -4465,14 +4465,14 @@ func (s *GatewayStub) EthSendRawTransaction(p0 context.Context, p1 ethtypes.EthB
return *new(ethtypes.EthHash), ErrNotSupported 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 { if s.Internal.EthSubscribe == nil {
return *new(ethtypes.EthSubscriptionID), ErrNotSupported 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 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"` 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 { type EthSubscriptionParams struct {
// List of topics to be matched. // List of topics to be matched.
// Optional, default: empty list // Optional, default: empty list

View File

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

View File

@ -12,6 +12,7 @@ import (
"github.com/filecoin-project/go-address" "github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-bitfield" "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/abi"
"github.com/filecoin-project/go-state-types/dline" "github.com/filecoin-project/go-state-types/dline"
"github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/go-state-types/network"
@ -117,7 +118,7 @@ type TargetAPI interface {
EthNewBlockFilter(ctx context.Context) (ethtypes.EthFilterID, error) EthNewBlockFilter(ctx context.Context) (ethtypes.EthFilterID, error)
EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error) EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error)
EthUninstallFilter(ctx context.Context, id ethtypes.EthFilterID) (bool, 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) 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 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 { if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return ethtypes.EthSubscriptionID{}, err 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") 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 { if err != nil {
return ethtypes.EthSubscriptionID{}, err return ethtypes.EthSubscriptionID{}, err
} }

View File

@ -6,6 +6,7 @@ import (
"context" "context"
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"encoding/json"
"fmt" "fmt"
"os" "os"
"sort" "sort"
@ -21,6 +22,7 @@ import (
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/filecoin-project/go-address" "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/abi"
"github.com/filecoin-project/go-state-types/big" "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"
"github.com/filecoin-project/lotus/chain/types/ethtypes" "github.com/filecoin-project/lotus/chain/types/ethtypes"
"github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/lotus/itests/kit"
res "github.com/filecoin-project/lotus/lib/result"
) )
// SolidityContractDef holds information about one of the test contracts // 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) t.Logf("actor ID address is %s", idAddr)
// install filter // 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) require.NoError(err)
var subResponses []ethtypes.EthSubscriptionResponse var subResponses []ethtypes.EthSubscriptionResponse
@ -567,44 +570,33 @@ func TestEthSubscribeLogs(t *testing.T) {
testResponses := map[string]chan ethtypes.EthSubscriptionResponse{} 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 // Create all the filters
for _, tc := range testCases { for _, tc := range testCases {
// subscribe to topics in filter // 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) require.NoError(err)
responseCh := make(chan ethtypes.EthSubscriptionResponse, len(invocations)) responseCh := make(chan ethtypes.EthSubscriptionResponse, len(invocations))
testResponses[tc.name] = responseCh testResponses[tc.name] = responseCh
// start a goroutine to forward responses from subscription to a buffered channel with guaranteed capacity err = client.EthSubRouter.AddSub(ctx, subId, func(ctx context.Context, resp *ethtypes.EthSubscriptionResponse) error {
go func(subCh <-chan ethtypes.EthSubscriptionResponse, responseCh chan<- ethtypes.EthSubscriptionResponse, quit chan struct{}) { responseCh <- *resp
defer func() { return nil
close(responseCh) })
}() require.NoError(err)
for {
select {
case resp := <-subCh:
responseCh <- resp
case <-quit:
return
case <-ctx.Done():
return
}
}
}(subCh, responseCh, quit)
} }
// Perform all the invocations // Perform all the invocations
messages := invokeAndWaitUntilAllOnChain(t, client, 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) time.Sleep(blockTime * 6)
close(quit)
for _, tc := range testCases { for _, tc := range testCases {
tc := tc // appease the lint despot tc := tc // appease the lint despot
@ -612,6 +604,9 @@ func TestEthSubscribeLogs(t *testing.T) {
responseCh, ok := testResponses[tc.name] responseCh, ok := testResponses[tc.name]
require.True(ok) require.True(ok)
// don't expect any more responses
close(responseCh)
var elogs []*ethtypes.EthLog var elogs []*ethtypes.EthLog
for resp := range responseCh { for resp := range responseCh {
rlist, ok := resp.Result.([]interface{}) 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 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) EthNewBlockFilter(ctx context.Context) (ethtypes.EthFilterID, error)
EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error) EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error)
EthUninstallFilter(ctx context.Context, id ethtypes.EthFilterID) (bool, 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) EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error)
} }
@ -1103,7 +1103,12 @@ const (
EthSubscribeEventTypeLogs = "logs" 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 { if e.SubManager == nil {
return ethtypes.EthSubscriptionID{}, api.ErrNotSupported return ethtypes.EthSubscriptionID{}, api.ErrNotSupported
} }
@ -1118,7 +1123,7 @@ func (e *EthEvent) EthSubscribe(ctx context.Context, eventType string, params *e
return ethtypes.EthSubscriptionID{}, err return ethtypes.EthSubscriptionID{}, err
} }
switch eventType { switch params.EventType {
case EthSubscribeEventTypeHeads: case EthSubscribeEventTypeHeads:
f, err := e.TipSetFilterManager.Install(ctx) f, err := e.TipSetFilterManager.Install(ctx)
if err != nil { if err != nil {
@ -1130,13 +1135,13 @@ func (e *EthEvent) EthSubscribe(ctx context.Context, eventType string, params *e
case EthSubscribeEventTypeLogs: case EthSubscribeEventTypeLogs:
keys := map[string][][]byte{} keys := map[string][][]byte{}
if params != nil { if params.Params != nil {
var err error var err error
keys, err = parseEthTopics(params.Topics) keys, err = parseEthTopics(params.Params.Topics)
if err != nil { if err != nil {
// clean up any previous filters added and stop the sub // clean up any previous filters added and stop the sub
_, _ = e.EthUnsubscribe(ctx, sub.id) _, _ = 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) sub.addFilter(ctx, f)
default: 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 return sub.id, nil