gateway: eth_subscribe support

This commit is contained in:
Łukasz Magiera 2023-01-16 15:28:55 +01:00
parent 6491becbe1
commit 1286d76988
22 changed files with 255 additions and 94 deletions

View File

@ -6,8 +6,6 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/filecoin-project/go-jsonrpc"
"github.com/google/uuid" "github.com/google/uuid"
blocks "github.com/ipfs/go-block-format" blocks "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid" "github.com/ipfs/go-cid"
@ -18,6 +16,7 @@ import (
datatransfer "github.com/filecoin-project/go-data-transfer" datatransfer "github.com/filecoin-project/go-data-transfer"
"github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-fil-markets/retrievalmarket"
"github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/go-fil-markets/storagemarket"
"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"
"github.com/filecoin-project/go-state-types/builtin/v8/paych" "github.com/filecoin-project/go-state-types/builtin/v8/paych"

View File

@ -102,6 +102,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) (<-chan ethtypes.EthSubscriptionResponse, error) EthSubscribe(ctx context.Context, eventType string, params *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error)
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error)
} }

View File

@ -35,10 +35,10 @@ func NewFullNodeRPCV0(ctx context.Context, addr string, requestHeader http.Heade
} }
// NewFullNodeRPCV1 creates a new http jsonrpc client. // NewFullNodeRPCV1 creates a new http jsonrpc client.
func NewFullNodeRPCV1(ctx context.Context, addr string, requestHeader http.Header) (api.FullNode, jsonrpc.ClientCloser, error) { func NewFullNodeRPCV1(ctx context.Context, addr string, requestHeader http.Header, opts ...jsonrpc.Option) (api.FullNode, jsonrpc.ClientCloser, error) {
var res v1api.FullNodeStruct var res v1api.FullNodeStruct
closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin", closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin",
api.GetInternalStructs(&res), requestHeader, jsonrpc.WithErrors(api.RPCErrors)) api.GetInternalStructs(&res), requestHeader, append([]jsonrpc.Option{jsonrpc.WithErrors(api.RPCErrors)}, opts...)...)
return &res, closer, err return &res, closer, err
} }

View File

@ -1388,10 +1388,10 @@ 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) (<-chan ethtypes.EthSubscriptionResponse, error) { func (m *MockFullNode) EthSubscribe(arg0 context.Context, arg1 string, arg2 *ethtypes.EthSubscriptionParams) (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, arg2)
ret0, _ := ret[0].(<-chan ethtypes.EthSubscriptionResponse) ret0, _ := ret[0].(ethtypes.EthSubscriptionID)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }

View File

@ -7,6 +7,15 @@ import (
"encoding/json" "encoding/json"
"time" "time"
"github.com/google/uuid"
blocks "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
"github.com/libp2p/go-libp2p/core/metrics"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address" "github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-bitfield" "github.com/filecoin-project/go-bitfield"
datatransfer "github.com/filecoin-project/go-data-transfer" datatransfer "github.com/filecoin-project/go-data-transfer"
@ -23,6 +32,7 @@ import (
"github.com/filecoin-project/go-state-types/dline" "github.com/filecoin-project/go-state-types/dline"
abinetwork "github.com/filecoin-project/go-state-types/network" abinetwork "github.com/filecoin-project/go-state-types/network"
"github.com/filecoin-project/go-state-types/proof" "github.com/filecoin-project/go-state-types/proof"
apitypes "github.com/filecoin-project/lotus/api/types" apitypes "github.com/filecoin-project/lotus/api/types"
builtinactors "github.com/filecoin-project/lotus/chain/actors/builtin" builtinactors "github.com/filecoin-project/lotus/chain/actors/builtin"
lminer "github.com/filecoin-project/lotus/chain/actors/builtin/miner" lminer "github.com/filecoin-project/lotus/chain/actors/builtin/miner"
@ -35,14 +45,6 @@ import (
"github.com/filecoin-project/lotus/storage/sealer/fsutil" "github.com/filecoin-project/lotus/storage/sealer/fsutil"
"github.com/filecoin-project/lotus/storage/sealer/sealtasks" "github.com/filecoin-project/lotus/storage/sealer/sealtasks"
"github.com/filecoin-project/lotus/storage/sealer/storiface" "github.com/filecoin-project/lotus/storage/sealer/storiface"
"github.com/google/uuid"
blocks "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
"github.com/libp2p/go-libp2p/core/metrics"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
"golang.org/x/xerrors"
) )
var ErrNotSupported = xerrors.New("method not supported") var ErrNotSupported = xerrors.New("method not supported")
@ -702,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) (<-chan ethtypes.EthSubscriptionResponse, error) `` EthSubscribe func(p0 context.Context, p1 string, p2 *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error) ``
EthUninstallFilter func(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) `` EthUninstallFilter func(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) ``
@ -4463,15 +4465,15 @@ 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) (<-chan ethtypes.EthSubscriptionResponse, error) { func (s *GatewayStruct) EthSubscribe(p0 context.Context, p1 string, p2 *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error) {
if s.Internal.EthSubscribe == nil { if s.Internal.EthSubscribe == nil {
return nil, ErrNotSupported return *new(ethtypes.EthSubscriptionID), ErrNotSupported
} }
return s.Internal.EthSubscribe(p0, p1, p2) return s.Internal.EthSubscribe(p0, p1, p2)
} }
func (s *GatewayStub) EthSubscribe(p0 context.Context, p1 string, p2 *ethtypes.EthSubscriptionParams) (<-chan ethtypes.EthSubscriptionResponse, error) { func (s *GatewayStub) EthSubscribe(p0 context.Context, p1 string, p2 *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error) {
return nil, ErrNotSupported return *new(ethtypes.EthSubscriptionID), ErrNotSupported
} }
func (s *GatewayStruct) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) { func (s *GatewayStruct) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) {

View File

@ -5,6 +5,11 @@ package v0api
import ( import (
"context" "context"
blocks "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
"github.com/libp2p/go-libp2p/core/peer"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address" "github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-bitfield" "github.com/filecoin-project/go-bitfield"
datatransfer "github.com/filecoin-project/go-data-transfer" datatransfer "github.com/filecoin-project/go-data-transfer"
@ -17,6 +22,7 @@ import (
"github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/crypto"
"github.com/filecoin-project/go-state-types/dline" "github.com/filecoin-project/go-state-types/dline"
abinetwork "github.com/filecoin-project/go-state-types/network" abinetwork "github.com/filecoin-project/go-state-types/network"
"github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api"
apitypes "github.com/filecoin-project/lotus/api/types" apitypes "github.com/filecoin-project/lotus/api/types"
lminer "github.com/filecoin-project/lotus/chain/actors/builtin/miner" lminer "github.com/filecoin-project/lotus/chain/actors/builtin/miner"
@ -24,10 +30,6 @@ import (
marketevents "github.com/filecoin-project/lotus/markets/loggers" marketevents "github.com/filecoin-project/lotus/markets/loggers"
"github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/filecoin-project/lotus/node/modules/dtypes"
"github.com/filecoin-project/lotus/node/repo/imports" "github.com/filecoin-project/lotus/node/repo/imports"
blocks "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
"github.com/libp2p/go-libp2p/core/peer"
"golang.org/x/xerrors"
) )
var ErrNotSupported = xerrors.New("method not supported") var ErrNotSupported = xerrors.New("method not supported")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -319,11 +319,33 @@ func GetFullNodeAPIV1Single(ctx *cli.Context) (v1api.FullNode, jsonrpc.ClientClo
return v1API, closer, nil return v1API, closer, nil
} }
func GetFullNodeAPIV1(ctx *cli.Context) (v1api.FullNode, jsonrpc.ClientCloser, error) { type GetFullNodeOptions struct {
ethSubHandler api.EthSubscriber
}
type GetFullNodeOption func(*GetFullNodeOptions)
func FullNodeWithEthSubscribtionHandler(sh api.EthSubscriber) GetFullNodeOption {
return func(opts *GetFullNodeOptions) {
opts.ethSubHandler = sh
}
}
func GetFullNodeAPIV1(ctx *cli.Context, opts ...GetFullNodeOption) (v1api.FullNode, jsonrpc.ClientCloser, error) {
if tn, ok := ctx.App.Metadata["testnode-full"]; ok { if tn, ok := ctx.App.Metadata["testnode-full"]; ok {
return tn.(v1api.FullNode), func() {}, nil return tn.(v1api.FullNode), func() {}, nil
} }
var options GetFullNodeOptions
for _, opt := range opts {
opt(&options)
}
var rpcOpts []jsonrpc.Option
if options.ethSubHandler != nil {
rpcOpts = append(rpcOpts, jsonrpc.WithClientHandler("Filecoin", options.ethSubHandler), jsonrpc.WithClientHandlerAlias("eth_subscription", "Filecoin.EthSubscription"))
}
heads, err := GetRawAPIMulti(ctx, repo.FullNode, "v1") heads, err := GetRawAPIMulti(ctx, repo.FullNode, "v1")
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err

View File

@ -162,7 +162,9 @@ var runCmd = &cli.Command{
log.Fatalf("Cannot register the view: %v", err) log.Fatalf("Cannot register the view: %v", err)
} }
api, closer, err := lcli.GetFullNodeAPIV1(cctx) subHnd := gateway.NewEthSubHandler()
api, closer, err := lcli.GetFullNodeAPIV1(cctx, cliutil.FullNodeWithEthSubscribtionHandler(subHnd))
if err != nil { if err != nil {
return err return err
} }
@ -195,7 +197,7 @@ var runCmd = &cli.Command{
return xerrors.Errorf("failed to convert endpoint address to multiaddr: %w", err) return xerrors.Errorf("failed to convert endpoint address to multiaddr: %w", err)
} }
gwapi := gateway.NewNode(api, lookbackCap, waitLookback, rateLimit, rateLimitTimeout) gwapi := gateway.NewNode(api, subHnd, lookbackCap, waitLookback, rateLimit, rateLimitTimeout)
h, err := gateway.Handler(gwapi, api, perConnRateLimit, connPerMinute, serverOptions...) h, err := gateway.Handler(gwapi, api, perConnRateLimit, connPerMinute, serverOptions...)
if err != nil { if err != nil {
return xerrors.Errorf("failed to set up gateway HTTP handler") return xerrors.Errorf("failed to set up gateway HTTP handler")

View File

@ -3073,8 +3073,7 @@ Inputs:
Response: Response:
```json ```json
{ [
"subscription": [
55, 55,
105, 105,
12, 12,
@ -3107,9 +3106,7 @@ Response:
39, 39,
53, 53,
94 94
], ]
"result": {}
}
``` ```
### EthUninstallFilter ### EthUninstallFilter

70
gateway/eth_sub.go Normal file
View File

@ -0,0 +1,70 @@
package gateway
import (
"context"
"sync"
"github.com/filecoin-project/go-jsonrpc"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/types/ethtypes"
)
type EthSubHandler struct {
queued map[ethtypes.EthSubscriptionID][]ethtypes.EthSubscriptionResponse
sinks map[ethtypes.EthSubscriptionID]func(context.Context, *ethtypes.EthSubscriptionResponse) error
lk sync.Mutex
}
func NewEthSubHandler() *EthSubHandler {
return &EthSubHandler{
queued: make(map[ethtypes.EthSubscriptionID][]ethtypes.EthSubscriptionResponse),
sinks: make(map[ethtypes.EthSubscriptionID]func(context.Context, *ethtypes.EthSubscriptionResponse) error),
}
}
func (e *EthSubHandler) addSub(ctx context.Context, id ethtypes.EthSubscriptionID, sink func(context.Context, *ethtypes.EthSubscriptionResponse) error) error {
e.lk.Lock()
defer e.lk.Unlock()
for _, p := range e.queued[id] {
if err := sink(ctx, &p); err != nil {
return err
}
}
delete(e.queued, id)
e.sinks[id] = sink
return nil
}
func (e *EthSubHandler) removeSub(id ethtypes.EthSubscriptionID) {
e.lk.Lock()
defer e.lk.Unlock()
delete(e.sinks, id)
delete(e.queued, id)
}
func (e *EthSubHandler) EthSubscription(ctx context.Context, r jsonrpc.RawParams) error {
p, err := jsonrpc.DecodeParams[ethtypes.EthSubscriptionResponse](r)
if err != nil {
return err
}
e.lk.Lock()
sink := e.sinks[p.SubscriptionID]
if sink == nil {
e.queued[p.SubscriptionID] = append(e.queued[p.SubscriptionID], p)
e.lk.Unlock()
return nil
}
e.lk.Unlock()
return sink(ctx, &p) // todo track errors and auto-unsubscribe on rpc conn close?
}
var _ api.EthSubscriber = (*EthSubHandler)(nil)

View File

@ -27,7 +27,7 @@ const perConnLimiterKey perConnLimiterKeyType = "limiter"
type filterTrackerKeyType string type filterTrackerKeyType string
const filterTrackerKey filterTrackerKeyType = "filterTracker" const statefulCallTrackerKey filterTrackerKeyType = "statefulCallTracker"
// Handler returns a gateway http.Handler, to be mounted as-is on the server. // Handler returns a gateway http.Handler, to be mounted as-is on the server.
func Handler(gwapi lapi.Gateway, api lapi.FullNode, rateLimit int64, connPerMinute int64, opts ...jsonrpc.ServerOption) (http.Handler, error) { func Handler(gwapi lapi.Gateway, api lapi.FullNode, rateLimit int64, connPerMinute int64, opts ...jsonrpc.ServerOption) (http.Handler, error) {
@ -90,7 +90,7 @@ func (h RateLimiterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r = r.WithContext(context.WithValue(r.Context(), perConnLimiterKey, h.limiter)) r = r.WithContext(context.WithValue(r.Context(), perConnLimiterKey, h.limiter))
// also add a filter tracker to the context // also add a filter tracker to the context
r = r.WithContext(context.WithValue(r.Context(), filterTrackerKey, newFilterTracker())) r = r.WithContext(context.WithValue(r.Context(), statefulCallTrackerKey, newStatefulCallTracker()))
h.handler.ServeHTTP(w, r) h.handler.ServeHTTP(w, r)
} }

View File

@ -117,7 +117,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) (<-chan ethtypes.EthSubscriptionResponse, error) EthSubscribe(ctx context.Context, eventType string, params *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error)
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error)
} }
@ -125,6 +125,7 @@ var _ TargetAPI = *new(api.FullNode) // gateway depends on latest
type Node struct { type Node struct {
target TargetAPI target TargetAPI
subHnd *EthSubHandler
lookbackCap time.Duration lookbackCap time.Duration
stateWaitLookbackLimit abi.ChainEpoch stateWaitLookbackLimit abi.ChainEpoch
rateLimiter *rate.Limiter rateLimiter *rate.Limiter
@ -141,7 +142,7 @@ var (
) )
// NewNode creates a new gateway node. // NewNode creates a new gateway node.
func NewNode(api TargetAPI, lookbackCap time.Duration, stateWaitLookbackLimit abi.ChainEpoch, rateLimit int64, rateLimitTimeout time.Duration) *Node { func NewNode(api TargetAPI, sHnd *EthSubHandler, lookbackCap time.Duration, stateWaitLookbackLimit abi.ChainEpoch, rateLimit int64, rateLimitTimeout time.Duration) *Node {
var limit rate.Limit var limit rate.Limit
if rateLimit == 0 { if rateLimit == 0 {
limit = rate.Inf limit = rate.Inf
@ -150,6 +151,7 @@ func NewNode(api TargetAPI, lookbackCap time.Duration, stateWaitLookbackLimit ab
} }
return &Node{ return &Node{
target: api, target: api,
subHnd: sHnd,
lookbackCap: lookbackCap, lookbackCap: lookbackCap,
stateWaitLookbackLimit: stateWaitLookbackLimit, stateWaitLookbackLimit: stateWaitLookbackLimit,
rateLimiter: rate.NewLimiter(limit, stateRateLimitTokens), rateLimiter: rate.NewLimiter(limit, stateRateLimitTokens),

View File

@ -89,7 +89,7 @@ func TestGatewayAPIChainGetTipSetByHeight(t *testing.T) {
tt := tt tt := tt
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
mock := &mockGatewayDepsAPI{} mock := &mockGatewayDepsAPI{}
a := NewNode(mock, DefaultLookbackCap, DefaultStateWaitLookbackLimit, 0, time.Minute) a := NewNode(mock, nil, DefaultLookbackCap, DefaultStateWaitLookbackLimit, 0, time.Minute)
// Create tipsets from genesis up to tskh and return the highest // Create tipsets from genesis up to tskh and return the highest
ts := mock.createTipSets(tt.args.tskh, tt.args.genesisTS) ts := mock.createTipSets(tt.args.tskh, tt.args.genesisTS)
@ -245,7 +245,7 @@ func TestGatewayVersion(t *testing.T) {
//stm: @GATEWAY_NODE_GET_VERSION_001 //stm: @GATEWAY_NODE_GET_VERSION_001
ctx := context.Background() ctx := context.Background()
mock := &mockGatewayDepsAPI{} mock := &mockGatewayDepsAPI{}
a := NewNode(mock, DefaultLookbackCap, DefaultStateWaitLookbackLimit, 0, time.Minute) a := NewNode(mock, nil, DefaultLookbackCap, DefaultStateWaitLookbackLimit, 0, time.Minute)
v, err := a.Version(ctx) v, err := a.Version(ctx)
require.NoError(t, err) require.NoError(t, err)
@ -256,7 +256,7 @@ func TestGatewayLimitTokensAvailable(t *testing.T) {
ctx := context.Background() ctx := context.Background()
mock := &mockGatewayDepsAPI{} mock := &mockGatewayDepsAPI{}
tokens := 3 tokens := 3
a := NewNode(mock, DefaultLookbackCap, DefaultStateWaitLookbackLimit, int64(tokens), time.Minute) a := NewNode(mock, nil, DefaultLookbackCap, DefaultStateWaitLookbackLimit, int64(tokens), time.Minute)
require.NoError(t, a.limit(ctx, tokens), "requests should not be limited when there are enough tokens available") require.NoError(t, a.limit(ctx, tokens), "requests should not be limited when there are enough tokens available")
} }
@ -264,7 +264,7 @@ func TestGatewayLimitTokensNotAvailable(t *testing.T) {
ctx := context.Background() ctx := context.Background()
mock := &mockGatewayDepsAPI{} mock := &mockGatewayDepsAPI{}
tokens := 3 tokens := 3
a := NewNode(mock, DefaultLookbackCap, DefaultStateWaitLookbackLimit, int64(1), time.Millisecond) a := NewNode(mock, nil, DefaultLookbackCap, DefaultStateWaitLookbackLimit, int64(1), time.Millisecond)
var err error var err error
// try to be rate limited // try to be rate limited
for i := 0; i <= 1000; i++ { for i := 0; i <= 1000; i++ {

View File

@ -3,12 +3,14 @@ package gateway
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/json"
"fmt" "fmt"
"sync" "sync"
"time" "time"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"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"
@ -352,7 +354,7 @@ func (gw *Node) EthGetFilterChanges(ctx context.Context, id ethtypes.EthFilterID
return nil, err return nil, err
} }
ft := filterTrackerFromContext(ctx) ft := statefulCallFromContext(ctx)
ft.lk.Lock() ft.lk.Lock()
_, ok := ft.userFilters[id] _, ok := ft.userFilters[id]
ft.lk.Unlock() ft.lk.Unlock()
@ -369,7 +371,7 @@ func (gw *Node) EthGetFilterLogs(ctx context.Context, id ethtypes.EthFilterID) (
return nil, err return nil, err
} }
ft := filterTrackerFromContext(ctx) ft := statefulCallFromContext(ctx)
ft.lk.Lock() ft.lk.Lock()
_, ok := ft.userFilters[id] _, ok := ft.userFilters[id]
ft.lk.Unlock() ft.lk.Unlock()
@ -417,7 +419,7 @@ func (gw *Node) EthUninstallFilter(ctx context.Context, id ethtypes.EthFilterID)
} }
// check if the filter belongs to this connection // check if the filter belongs to this connection
ft := filterTrackerFromContext(ctx) ft := statefulCallFromContext(ctx)
ft.lk.Lock() ft.lk.Lock()
defer ft.lk.Unlock() defer ft.lk.Unlock()
@ -434,18 +436,82 @@ 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) (<-chan ethtypes.EthSubscriptionResponse, error) { func (gw *Node) EthSubscribe(ctx context.Context, eventType string, params *ethtypes.EthSubscriptionParams) (ethtypes.EthSubscriptionID, error) {
return nil, xerrors.Errorf("not implemented") if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return ethtypes.EthSubscriptionID{}, err
}
if gw.subHnd == nil {
return ethtypes.EthSubscriptionID{}, xerrors.New("subscription support not enabled")
}
ethCb, ok := jsonrpc.ExtractReverseClient[api.EthSubscriberMethods](ctx)
if !ok {
return ethtypes.EthSubscriptionID{}, xerrors.Errorf("connection doesn't support callbacks")
}
ft := statefulCallFromContext(ctx)
ft.lk.Lock()
defer ft.lk.Unlock()
if len(ft.userSubscriptions) >= EthMaxFiltersPerConn {
return ethtypes.EthSubscriptionID{}, fmt.Errorf("too many subscriptions")
}
sub, err := gw.target.EthSubscribe(ctx, eventType, params)
if err != nil {
return ethtypes.EthSubscriptionID{}, err
}
err = gw.subHnd.addSub(ctx, sub, func(ctx context.Context, response *ethtypes.EthSubscriptionResponse) error {
outParam, err := json.Marshal(response)
if err != nil {
return err
}
return ethCb.EthSubscription(ctx, outParam)
})
if err != nil {
return ethtypes.EthSubscriptionID{}, err
}
ft.userSubscriptions[sub] = time.Now()
return sub, err
} }
func (gw *Node) EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) { func (gw *Node) EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) {
return false, xerrors.Errorf("not implemented") if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return false, err
}
// check if the filter belongs to this connection
ft := statefulCallFromContext(ctx)
ft.lk.Lock()
defer ft.lk.Unlock()
if _, ok := ft.userSubscriptions[id]; !ok {
return false, nil
}
ok, err := gw.target.EthUnsubscribe(ctx, id)
if err != nil {
return false, err
}
delete(ft.userSubscriptions, id)
if gw.subHnd != nil {
gw.subHnd.removeSub(id)
}
return ok, nil
} }
var EthMaxFiltersPerConn = 16 // todo make this configurable var EthMaxFiltersPerConn = 16 // todo make this configurable
func addUserFilterLimited(ctx context.Context, cb func() (ethtypes.EthFilterID, error)) (ethtypes.EthFilterID, error) { func addUserFilterLimited(ctx context.Context, cb func() (ethtypes.EthFilterID, error)) (ethtypes.EthFilterID, error) {
ft := filterTrackerFromContext(ctx) ft := statefulCallFromContext(ctx)
ft.lk.Lock() ft.lk.Lock()
defer ft.lk.Unlock() defer ft.lk.Unlock()
@ -463,19 +529,21 @@ func addUserFilterLimited(ctx context.Context, cb func() (ethtypes.EthFilterID,
return id, nil return id, nil
} }
func filterTrackerFromContext(ctx context.Context) *filterTracker { func statefulCallFromContext(ctx context.Context) *statefulCallTracker {
return ctx.Value(filterTrackerKey).(*filterTracker) return ctx.Value(statefulCallTrackerKey).(*statefulCallTracker)
} }
type filterTracker struct { type statefulCallTracker struct {
lk sync.Mutex lk sync.Mutex
userFilters map[ethtypes.EthFilterID]time.Time userFilters map[ethtypes.EthFilterID]time.Time
userSubscriptions map[ethtypes.EthSubscriptionID]time.Time
} }
// called per request (ws connection) // called per request (ws connection)
func newFilterTracker() *filterTracker { func newStatefulCallTracker() *statefulCallTracker {
return &filterTracker{ return &statefulCallTracker{
userFilters: make(map[ethtypes.EthFilterID]time.Time), userFilters: make(map[ethtypes.EthFilterID]time.Time),
userSubscriptions: make(map[ethtypes.EthSubscriptionID]time.Time),
} }
} }

2
go.mod
View File

@ -40,7 +40,7 @@ require (
github.com/filecoin-project/go-fil-commcid v0.1.0 github.com/filecoin-project/go-fil-commcid v0.1.0
github.com/filecoin-project/go-fil-commp-hashhash v0.1.0 github.com/filecoin-project/go-fil-commp-hashhash v0.1.0
github.com/filecoin-project/go-fil-markets v1.25.2 github.com/filecoin-project/go-fil-markets v1.25.2
github.com/filecoin-project/go-jsonrpc v0.1.10-0.20230116095704-da92324a76e1 github.com/filecoin-project/go-jsonrpc v0.2.0
github.com/filecoin-project/go-legs v0.4.4 github.com/filecoin-project/go-legs v0.4.4
github.com/filecoin-project/go-padreader v0.0.1 github.com/filecoin-project/go-padreader v0.0.1
github.com/filecoin-project/go-paramfetch v0.0.4 github.com/filecoin-project/go-paramfetch v0.0.4

7
go.sum
View File

@ -340,10 +340,8 @@ github.com/filecoin-project/go-hamt-ipld/v2 v2.0.0/go.mod h1:7aWZdaQ1b16BVoQUYR+
github.com/filecoin-project/go-hamt-ipld/v3 v3.0.1/go.mod h1:gXpNmr3oQx8l3o7qkGyDjJjYSRX7hp/FGOStdqrWyDI= github.com/filecoin-project/go-hamt-ipld/v3 v3.0.1/go.mod h1:gXpNmr3oQx8l3o7qkGyDjJjYSRX7hp/FGOStdqrWyDI=
github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0 h1:rVVNq0x6RGQIzCo1iiJlGFm9AGIZzeifggxtKMU7zmI= github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0 h1:rVVNq0x6RGQIzCo1iiJlGFm9AGIZzeifggxtKMU7zmI=
github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0/go.mod h1:bxmzgT8tmeVQA1/gvBwFmYdT8SOFUwB3ovSUfG1Ux0g= github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0/go.mod h1:bxmzgT8tmeVQA1/gvBwFmYdT8SOFUwB3ovSUfG1Ux0g=
github.com/filecoin-project/go-jsonrpc v0.1.9 h1:HRWLxo7HAWzI3xZGeFG4LZJoYpms+Q+8kwmMTLnyS3A= github.com/filecoin-project/go-jsonrpc v0.2.0 h1:lhcu0Oa7xxwdclU4EyBPgnZihiZOaTFiAnW6P3NRqko=
github.com/filecoin-project/go-jsonrpc v0.1.9/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= github.com/filecoin-project/go-jsonrpc v0.2.0/go.mod h1:jBSvPTl8V1N7gSTuCR4bis8wnQnIjHbRPpROol6iQKM=
github.com/filecoin-project/go-jsonrpc v0.1.10-0.20230116095704-da92324a76e1 h1:GcF3gSvesv1epB2SrTfalrYvhFzT1UnmSS1Bh+jprQY=
github.com/filecoin-project/go-jsonrpc v0.1.10-0.20230116095704-da92324a76e1/go.mod h1:jBSvPTl8V1N7gSTuCR4bis8wnQnIjHbRPpROol6iQKM=
github.com/filecoin-project/go-legs v0.4.4 h1:mpMmAOOnamaz0CV9rgeKhEWA8j9kMC+f+UGCGrxKaZo= github.com/filecoin-project/go-legs v0.4.4 h1:mpMmAOOnamaz0CV9rgeKhEWA8j9kMC+f+UGCGrxKaZo=
github.com/filecoin-project/go-legs v0.4.4/go.mod h1:JQ3hA6xpJdbR8euZ2rO0jkxaMxeidXf0LDnVuqPAe9s= github.com/filecoin-project/go-legs v0.4.4/go.mod h1:JQ3hA6xpJdbR8euZ2rO0jkxaMxeidXf0LDnVuqPAe9s=
github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20/go.mod h1:mPn+LRRd5gEKNAtc+r3ScpW2JRU/pj4NBKdADYWHiak= github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20/go.mod h1:mPn+LRRd5gEKNAtc+r3ScpW2JRU/pj4NBKdADYWHiak=
@ -826,7 +824,6 @@ github.com/ipfs/go-log/v2 v2.0.1/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBW
github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0=
github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0=
github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw=
github.com/ipfs/go-log/v2 v2.0.8/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw=
github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM=
github.com/ipfs/go-log/v2 v2.1.2-0.20200626104915-0016c0b4b3e4/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/ipfs/go-log/v2 v2.1.2-0.20200626104915-0016c0b4b3e4/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM=
github.com/ipfs/go-log/v2 v2.1.2/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/ipfs/go-log/v2 v2.1.2/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM=

View File

@ -290,7 +290,7 @@ func startNodes(
ens.InterconnectAll().BeginMining(blocktime) ens.InterconnectAll().BeginMining(blocktime)
// Create a gateway server in front of the full node // Create a gateway server in front of the full node
gwapi := gateway.NewNode(full, lookbackCap, stateWaitLookbackLimit, 0, time.Minute) gwapi := gateway.NewNode(full, nil, lookbackCap, stateWaitLookbackLimit, 0, time.Minute)
handler, err := gateway.Handler(gwapi, full, 0, 0) handler, err := gateway.Handler(gwapi, full, 0, 0)
require.NoError(t, err) require.NoError(t, err)

View File

@ -6,7 +6,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/filecoin-project/go-jsonrpc"
"strconv" "strconv"
"sync" "sync"
"time" "time"
@ -18,6 +17,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"
builtintypes "github.com/filecoin-project/go-state-types/builtin" builtintypes "github.com/filecoin-project/go-state-types/builtin"