gateway: eth_subscribe support
This commit is contained in:
parent
6491becbe1
commit
1286d76988
@ -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"
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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.
@ -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
|
||||||
|
@ -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")
|
||||||
|
@ -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
70
gateway/eth_sub.go
Normal 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)
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
@ -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++ {
|
||||||
|
@ -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
2
go.mod
@ -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
7
go.sum
@ -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=
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
Loading…
Reference in New Issue
Block a user