Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: API: support typed errors over RPC #9061

Merged
merged 6 commits into from
Sep 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions api/api_errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package api

import (
"errors"
"reflect"

"github.com/filecoin-project/go-jsonrpc"
)

const (
EOutOfGas = iota + jsonrpc.FirstUserCode
EActorNotFound
)

type ErrOutOfGas struct{}

func (e *ErrOutOfGas) Error() string {
return "call ran out of gas"
}

type ErrActorNotFound struct{}

func (e *ErrActorNotFound) Error() string {
return "actor not found"
}

var RPCErrors = jsonrpc.NewErrors()

func ErrorIsIn(err error, errorTypes []error) bool {
for _, etype := range errorTypes {
tmp := reflect.New(reflect.PointerTo(reflect.ValueOf(etype).Elem().Type())).Interface()
if errors.As(err, tmp) {
return true
}
}
return false
}

func init() {
RPCErrors.Register(EOutOfGas, new(*ErrOutOfGas))
RPCErrors.Register(EActorNotFound, new(*ErrActorNotFound))
}
18 changes: 18 additions & 0 deletions api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import (
"testing"

"github.com/stretchr/testify/require"
"golang.org/x/xerrors"

"github.com/filecoin-project/go-jsonrpc"
)

func goCmd() string {
Expand Down Expand Up @@ -124,3 +127,18 @@ func TestPermTags(t *testing.T) {
_ = PermissionedStorMinerAPI(&StorageMinerStruct{})
_ = PermissionedWorkerAPI(&WorkerStruct{})
}

func TestRetryErrorIsInTrue(t *testing.T) {
errorsToRetry := []error{&jsonrpc.RPCConnectionError{}}
require.True(t, ErrorIsIn(&jsonrpc.RPCConnectionError{}, errorsToRetry))
}

func TestRetryErrorIsInFalse(t *testing.T) {
errorsToRetry := []error{&jsonrpc.RPCConnectionError{}}
require.False(t, ErrorIsIn(xerrors.Errorf("random error"), errorsToRetry))
}

func TestRetryWrappedErrorIsInTrue(t *testing.T) {
errorsToRetry := []error{&jsonrpc.RPCConnectionError{}}
require.True(t, ErrorIsIn(xerrors.Errorf("wrapped: %w", &jsonrpc.RPCConnectionError{}), errorsToRetry))
}
13 changes: 8 additions & 5 deletions api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
func NewCommonRPCV0(ctx context.Context, addr string, requestHeader http.Header) (api.CommonNet, jsonrpc.ClientCloser, error) {
var res v0api.CommonNetStruct
closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin",
api.GetInternalStructs(&res), requestHeader)
api.GetInternalStructs(&res), requestHeader, jsonrpc.WithErrors(api.RPCErrors))

return &res, closer, err
}
Expand All @@ -29,7 +29,7 @@ func NewFullNodeRPCV0(ctx context.Context, addr string, requestHeader http.Heade
var res v0api.FullNodeStruct

closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin",
api.GetInternalStructs(&res), requestHeader)
api.GetInternalStructs(&res), requestHeader, jsonrpc.WithErrors(api.RPCErrors))

return &res, closer, err
}
Expand All @@ -38,7 +38,7 @@ func NewFullNodeRPCV0(ctx context.Context, addr string, requestHeader http.Heade
func NewFullNodeRPCV1(ctx context.Context, addr string, requestHeader http.Header) (api.FullNode, jsonrpc.ClientCloser, error) {
var res v1api.FullNodeStruct
closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin",
api.GetInternalStructs(&res), requestHeader)
api.GetInternalStructs(&res), requestHeader, jsonrpc.WithErrors(api.RPCErrors))

return &res, closer, err
}
Expand Down Expand Up @@ -72,6 +72,7 @@ func NewStorageMinerRPCV0(ctx context.Context, addr string, requestHeader http.H
api.GetInternalStructs(&res), requestHeader,
append([]jsonrpc.Option{
rpcenc.ReaderParamEncoder(pushUrl),
jsonrpc.WithErrors(api.RPCErrors),
}, opts...)...)

return &res, closer, err
Expand All @@ -90,6 +91,7 @@ func NewWorkerRPCV0(ctx context.Context, addr string, requestHeader http.Header)
rpcenc.ReaderParamEncoder(pushUrl),
jsonrpc.WithNoReconnect(),
jsonrpc.WithTimeout(30*time.Second),
jsonrpc.WithErrors(api.RPCErrors),
)

return &res, closer, err
Expand All @@ -101,7 +103,7 @@ func NewGatewayRPCV1(ctx context.Context, addr string, requestHeader http.Header
closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin",
api.GetInternalStructs(&res),
requestHeader,
opts...,
append(opts, jsonrpc.WithErrors(api.RPCErrors))...,
)

return &res, closer, err
Expand All @@ -113,7 +115,7 @@ func NewGatewayRPCV0(ctx context.Context, addr string, requestHeader http.Header
closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin",
api.GetInternalStructs(&res),
requestHeader,
opts...,
append(opts, jsonrpc.WithErrors(api.RPCErrors))...,
)

return &res, closer, err
Expand All @@ -124,6 +126,7 @@ func NewWalletRPCV0(ctx context.Context, addr string, requestHeader http.Header)
closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin",
api.GetInternalStructs(&res),
requestHeader,
jsonrpc.WithErrors(api.RPCErrors),
)

return &res, closer, err
Expand Down
5 changes: 3 additions & 2 deletions build/params_shared_vals.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,9 @@ const VerifSigCacheSize = 32000
// TODO: If this is gonna stay, it should move to specs-actors
const BlockMessageLimit = 10000

const BlockGasLimit = 10_000_000_000
const BlockGasTarget = BlockGasLimit / 2
var BlockGasLimit = int64(10_000_000_000)
var BlockGasTarget = BlockGasLimit / 2

const BaseFeeMaxChangeDenom = 8 // 12.5%
const InitialBaseFee = 100e6
const MinimumBaseFee = 100
Expand Down
2 changes: 1 addition & 1 deletion chain/messagepool/repub.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (mp *MessagePool) republishPendingMessages(ctx context.Context) error {
return chains[i].Before(chains[j])
})

gasLimit := int64(build.BlockGasLimit)
gasLimit := build.BlockGasLimit
minGas := int64(gasguess.MinGas)
var msgs []*types.SignedMessage
loop:
Expand Down
4 changes: 2 additions & 2 deletions chain/messagepool/selection.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ func (mp *MessagePool) selectMessagesOptimal(ctx context.Context, curTs, ts *typ
nextChain := 0
partitions := make([][]*msgChain, MaxBlocks)
for i := 0; i < MaxBlocks && nextChain < len(chains); i++ {
gasLimit := int64(build.BlockGasLimit)
gasLimit := build.BlockGasLimit
msgLimit := build.BlockMessageLimit
for nextChain < len(chains) {
chain := chains[nextChain]
Expand Down Expand Up @@ -590,7 +590,7 @@ func (mp *MessagePool) selectPriorityMessages(ctx context.Context, pending map[a
mpCfg := mp.getConfig()
result := &selectedMessages{
msgs: make([]*types.SignedMessage, 0, mpCfg.SizeLimitLow),
gasLimit: int64(build.BlockGasLimit),
gasLimit: build.BlockGasLimit,
blsLimit: cbg.MaxLength,
secpLimit: cbg.MaxLength,
}
Expand Down
2 changes: 1 addition & 1 deletion chain/store/basefee_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestBaseFee(t *testing.T) {
{100e6, build.BlockGasTarget, 1, 103.125e6, 100e6},
{100e6, build.BlockGasTarget * 2, 2, 103.125e6, 100e6},
{100e6, build.BlockGasLimit * 2, 2, 112.5e6, 112.5e6},
{100e6, build.BlockGasLimit * 1.5, 2, 110937500, 106.250e6},
{100e6, (build.BlockGasLimit * 15) / 10, 2, 110937500, 106.250e6},
}

for _, test := range tests {
Expand Down
9 changes: 5 additions & 4 deletions cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ const (
// has.
// 5 per tipset, but we effectively get 4 blocks worth of messages.
expectedBlocks = 4
// TODO: This will produce invalid blocks but it will accurately model the amount of gas
// we're willing to use per-tipset.
// A more correct approach would be to produce 5 blocks. We can do that later.
targetGas = build.BlockGasTarget * expectedBlocks
)

// TODO: This will produce invalid blocks but it will accurately model the amount of gas
// we're willing to use per-tipset.
// A more correct approach would be to produce 5 blocks. We can do that later.
var targetGas = build.BlockGasTarget * expectedBlocks

type BlockBuilder struct {
ctx context.Context
logger *zap.SugaredLogger
Expand Down
2 changes: 1 addition & 1 deletion cmd/lotus-wallet/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ var runCmd = &cli.Command{
rpcApi = api.PermissionedWalletAPI(rpcApi)
}

rpcServer := jsonrpc.NewServer()
rpcServer := jsonrpc.NewServer(jsonrpc.WithServerErrors(api.RPCErrors))
rpcServer.Register("Filecoin", rpcApi)

mux.Handle("/rpc/v0", rpcServer)
Expand Down
2 changes: 1 addition & 1 deletion cmd/lotus-worker/sealworker/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var log = logging.Logger("sealworker")
func WorkerHandler(authv func(ctx context.Context, token string) ([]auth.Permission, error), remote http.HandlerFunc, a api.Worker, permissioned bool) http.Handler {
mux := mux.NewRouter()
readerHandler, readerServerOpt := rpcenc.ReaderParamDecoder()
rpcServer := jsonrpc.NewServer(readerServerOpt)
rpcServer := jsonrpc.NewServer(jsonrpc.WithServerErrors(api.RPCErrors), readerServerOpt)

wapi := proxy.MetricedWorkerAPI(a)
if permissioned {
Expand Down
10 changes: 5 additions & 5 deletions cmd/lotus/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
"github.com/filecoin-project/go-jsonrpc"
"github.com/filecoin-project/go-paramfetch"

"github.com/filecoin-project/lotus/api"
lapi "github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/consensus/filcns"
"github.com/filecoin-project/lotus/chain/stmgr"
Expand Down Expand Up @@ -303,7 +303,7 @@ var DaemonCmd = &cli.Command{
}

defer closer()
liteModeDeps = node.Override(new(api.Gateway), gapi)
liteModeDeps = node.Override(new(lapi.Gateway), gapi)
}

// some libraries like ipfs/go-ds-measure and ipfs/go-ipfs-blockstore
Expand All @@ -313,7 +313,7 @@ var DaemonCmd = &cli.Command{
log.Warnf("unable to inject prometheus ipfs/go-metrics exporter; some metrics will be unavailable; err: %s", err)
}

var api api.FullNode
var api lapi.FullNode
stop, err := node.New(ctx,
node.FullAPI(&api, node.Lite(isLite)),

Expand Down Expand Up @@ -360,7 +360,7 @@ var DaemonCmd = &cli.Command{
// ----

// Populate JSON-RPC options.
serverOptions := make([]jsonrpc.ServerOption, 0)
serverOptions := []jsonrpc.ServerOption{jsonrpc.WithServerErrors(lapi.RPCErrors)}
if maxRequestSize := cctx.Int("api-max-req-size"); maxRequestSize != 0 {
serverOptions = append(serverOptions, jsonrpc.WithMaxRequestSize(int64(maxRequestSize)))
}
Expand Down Expand Up @@ -392,7 +392,7 @@ var DaemonCmd = &cli.Command{
},
}

func importKey(ctx context.Context, api api.FullNode, f string) error {
func importKey(ctx context.Context, api lapi.FullNode, f string) error {
f, err := homedir.Expand(f)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion gateway/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func Handler(gwapi lapi.Gateway, api lapi.FullNode, rateLimit int64, connPerMinu
m := mux.NewRouter()

serveRpc := func(path string, hnd interface{}) {
rpcServer := jsonrpc.NewServer(opts...)
rpcServer := jsonrpc.NewServer(append(opts, jsonrpc.WithServerErrors(lapi.RPCErrors))...)
rpcServer.Register("Filecoin", hnd)
rpcServer.AliasMethod("rpc.discover", "Filecoin.Discover")

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ require (
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-markets v1.24.0
github.com/filecoin-project/go-jsonrpc v0.1.7
github.com/filecoin-project/go-jsonrpc v0.1.8
github.com/filecoin-project/go-legs v0.4.4
github.com/filecoin-project/go-padreader v0.0.1
github.com/filecoin-project/go-paramfetch v0.0.4
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,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.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-jsonrpc v0.1.7 h1:Ti/QkQLI31v+6hvidA+i9Wv/NrS4CfHk0yXLntHX3Uk=
github.com/filecoin-project/go-jsonrpc v0.1.7/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4=
github.com/filecoin-project/go-jsonrpc v0.1.8 h1:uXX/ikAk3Q4f/k8DRd9Zw+fWnfiYb5I+UI1tzlQgHog=
github.com/filecoin-project/go-jsonrpc v0.1.8/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4=
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-padreader v0.0.0-20200903213702-ed5fae088b20/go.mod h1:mPn+LRRd5gEKNAtc+r3ScpW2JRU/pj4NBKdADYWHiak=
Expand Down
43 changes: 43 additions & 0 deletions itests/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
logging "github.com/ipfs/go-log/v2"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/stretchr/testify/require"
"golang.org/x/xerrors"

"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/big"
Expand Down Expand Up @@ -51,6 +52,8 @@ func runAPITest(t *testing.T, opts ...interface{}) {
t.Run("testMiningReal", ts.testMiningReal)
t.Run("testSlowNotify", ts.testSlowNotify)
t.Run("testSearchMsg", ts.testSearchMsg)
t.Run("testOutOfGasError", ts.testOutOfGasError)
t.Run("testLookupNotFoundError", ts.testLookupNotFoundError)
t.Run("testNonGenesisMiner", ts.testNonGenesisMiner)
}

Expand Down Expand Up @@ -149,6 +152,46 @@ func (ts *apiSuite) testSearchMsg(t *testing.T) {
require.Equalf(t, res.TipSet, searchRes.TipSet, "search ts: %s, different from wait ts: %s", searchRes.TipSet, res.TipSet)
}

func (ts *apiSuite) testOutOfGasError(t *testing.T) {
ctx := context.Background()

full, _, _ := kit.EnsembleMinimal(t, ts.opts...)

senderAddr, err := full.WalletDefaultAddress(ctx)
require.NoError(t, err)

// the gas estimator API executes the message with gasLimit = BlockGasLimit
// Lowering it to 2 will cause it to run out of gas, testing the failure case we want
originalLimit := build.BlockGasLimit
build.BlockGasLimit = 2
defer func() {
build.BlockGasLimit = originalLimit
}()

msg := &types.Message{
From: senderAddr,
To: senderAddr,
Value: big.Zero(),
}

_, err = full.GasEstimateMessageGas(ctx, msg, nil, types.EmptyTSK)
require.Error(t, err, "should have failed")
require.True(t, xerrors.Is(err, &lapi.ErrOutOfGas{}))
}

func (ts *apiSuite) testLookupNotFoundError(t *testing.T) {
ctx := context.Background()

full, _, _ := kit.EnsembleMinimal(t, ts.opts...)

addr, err := full.WalletNew(ctx, types.KTSecp256k1)
require.NoError(t, err)

_, err = full.StateLookupID(ctx, addr, types.EmptyTSK)
require.Error(t, err)
require.True(t, xerrors.Is(err, &lapi.ErrActorNotFound{}))
}

func (ts *apiSuite) testMining(t *testing.T) {
ctx := context.Background()

Expand Down
3 changes: 2 additions & 1 deletion itests/kit/blockminer.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/stretchr/testify/require"

"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/builtin"
minertypes "github.com/filecoin-project/go-state-types/builtin/v8/miner"
Expand Down Expand Up @@ -191,7 +192,7 @@ func (bm *BlockMiner) MineBlocksMustPost(ctx context.Context, blocktime time.Dur
reportSuccessFn := func(success bool, epoch abi.ChainEpoch, err error) {
// if api shuts down before mining, we may get an error which we should probably just ignore
// (fixing it will require rewriting most of the mining loop)
if err != nil && !strings.Contains(err.Error(), "websocket connection closed") {
if err != nil && !strings.Contains(err.Error(), "websocket connection closed") && !api.ErrorIsIn(err, []error{new(jsonrpc.RPCConnectionError)}) {
require.NoError(bm.t, err)
}

Expand Down
Loading