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: return an error for null rounds from RPC methods #12655

Merged
merged 11 commits into from
Oct 31, 2024
48 changes: 48 additions & 0 deletions api/api_errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package api

import (
"errors"
"fmt"
"reflect"

"golang.org/x/xerrors"
Expand All @@ -22,6 +23,7 @@ const (
EF3ParticipationTicketStartBeforeExisting
EF3NotReady
EExecutionReverted
ENullRound
)

var (
Expand Down Expand Up @@ -54,6 +56,8 @@ var (
_ error = (*errF3NotReady)(nil)
_ error = (*ErrExecutionReverted)(nil)
_ jsonrpc.RPCErrorCodec = (*ErrExecutionReverted)(nil)
_ error = (*ErrNullRound)(nil)
_ jsonrpc.RPCErrorCodec = (*ErrNullRound)(nil)
)

func init() {
Expand All @@ -67,6 +71,7 @@ func init() {
RPCErrors.Register(EF3ParticipationTicketStartBeforeExisting, new(*errF3ParticipationTicketStartBeforeExisting))
RPCErrors.Register(EF3NotReady, new(*errF3NotReady))
RPCErrors.Register(EExecutionReverted, new(*ErrExecutionReverted))
RPCErrors.Register(ENullRound, new(*ErrNullRound))
}

func ErrorIsIn(err error, errorTypes []error) bool {
Expand Down Expand Up @@ -160,3 +165,46 @@ func NewErrExecutionReverted(reason string) *ErrExecutionReverted {
Data: reason,
}
}

type ErrNullRound struct {
Epoch int64
Message string
}

func NewErrNullRound(epoch int64) *ErrNullRound {
return &ErrNullRound{
Epoch: epoch,
Message: fmt.Sprintf("requested epoch was a null round (%d)", epoch),
}
}

func (e *ErrNullRound) Error() string {
return e.Message
}

func (e *ErrNullRound) FromJSONRPCError(jerr jsonrpc.JSONRPCError) error {
if jerr.Code != ENullRound {
return fmt.Errorf("unexpected error code: %d", jerr.Code)
}

epoch, ok := jerr.Data.(float64)
if !ok {
return fmt.Errorf("expected number data in null round error, got %T", jerr.Data)
}

e.Epoch = int64(epoch)
e.Message = jerr.Message
return nil
}

func (e *ErrNullRound) ToJSONRPCError() (jsonrpc.JSONRPCError, error) {
return jsonrpc.JSONRPCError{
Code: ENullRound,
Message: e.Message,
Data: e.Epoch,
}, nil
}
virajbhartiya marked this conversation as resolved.
Show resolved Hide resolved
func (e *ErrNullRound) Is(target error) bool {
virajbhartiya marked this conversation as resolved.
Show resolved Hide resolved
_, ok := target.(*ErrNullRound)
return ok
}
91 changes: 91 additions & 0 deletions itests/fevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"github.com/stretchr/testify/require"

"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-jsonrpc"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
builtintypes "github.com/filecoin-project/go-state-types/builtin"
Expand Down Expand Up @@ -1659,3 +1660,93 @@
})
}
}

func TestEthNullRoundHandling(t *testing.T) {
blockTime := 100 * time.Millisecond
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC())

bms := ens.InterconnectAll().BeginMining(blockTime)

ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()

virajbhartiya marked this conversation as resolved.
Show resolved Hide resolved
client.WaitTillChain(ctx, kit.HeightAtLeast(10))

bms[0].InjectNulls(10)

tctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
ch, err := client.ChainNotify(tctx)
require.NoError(t, err)
<-ch
hc := <-ch
require.Equal(t, store.HCApply, hc[0].Type)

afterNullHeight := hc[0].Val.Height()

nullHeight := afterNullHeight - 1
for nullHeight > 0 {
ts, err := client.ChainGetTipSetByHeight(ctx, nullHeight, types.EmptyTSK)
require.NoError(t, err)
if ts.Height() == nullHeight {
nullHeight--
} else {
break
}
}

nullBlockHex := fmt.Sprintf("0x%x", int(nullHeight))
client.WaitTillChain(ctx, kit.HeightAtLeast(nullHeight+2))
testCases := []struct {
name string
testFunc func() error
}{
{
name: "EthGetBlockByNumber",
testFunc: func() error {
_, err := client.EthGetBlockByNumber(ctx, nullBlockHex, true)
return err
},
},
{
name: "EthFeeHistory",
testFunc: func() error {
_, err := client.EthFeeHistory(ctx, jsonrpc.RawParams([]byte(`[1,"`+nullBlockHex+`",[]]`)))
return err
},
},
{
name: "EthTraceBlock",
testFunc: func() error {
_, err := client.EthTraceBlock(ctx, nullBlockHex)
return err
},
},
{
name: "EthTraceReplayBlockTransactions",
testFunc: func() error {
_, err := client.EthTraceReplayBlockTransactions(ctx, nullBlockHex, []string{"trace"})
return err
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := tc.testFunc()
t.Logf("error: %v", err)
require.Error(t, err)

// Test errors.Is
require.ErrorIs(t, err, new(api.ErrNullRound), "error should be or wrap ErrNullRound")

// Test errors.As and verify message
var nullRoundErr *api.ErrNullRound
require.ErrorAs(t, err, &nullRoundErr, "error should be convertible to ErrNullRound")

expectedMsg := fmt.Sprintf("requested epoch was a null round (%d)", nullHeight)
require.Equal(t, expectedMsg, nullRoundErr.Error())
require.Equal(t, abi.ChainEpoch(nullHeight), abi.ChainEpoch(nullRoundErr.Epoch))

Check failure on line 1749 in itests/fevm_test.go

View workflow job for this annotation

GitHub Actions / Check (lint-all)

unnecessary conversion (unconvert)
virajbhartiya marked this conversation as resolved.
Show resolved Hide resolved
})
}
}
18 changes: 8 additions & 10 deletions node/impl/full/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,6 @@ type EthAPI struct {
EthEventAPI
}

var ErrNullRound = errors.New("requested epoch was a null round")

func (a *EthModule) StateNetworkName(ctx context.Context) (dtypes.NetworkName, error) {
return stmgr.GetNetworkName(ctx, a.StateManager, a.Chain.GetHeaviestTipSet().ParentState())
}
Expand Down Expand Up @@ -246,7 +244,7 @@ func (a *EthAPI) FilecoinAddressToEthAddress(ctx context.Context, p jsonrpc.RawP
// Get the tipset for the specified block
ts, err := getTipsetByBlockNumber(ctx, a.Chain, blkParam, true)
if err != nil {
return ethtypes.EthAddress{}, xerrors.Errorf("failed to get tipset for block %s: %w", blkParam, err)
return ethtypes.EthAddress{}, err
}

// Lookup the ID address
Expand Down Expand Up @@ -571,7 +569,7 @@ func (a *EthAPI) EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHa
func (a *EthAPI) EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkParam string, index ethtypes.EthUint64) (*ethtypes.EthTx, error) {
ts, err := getTipsetByBlockNumber(ctx, a.Chain, blkParam, true)
if err != nil {
return nil, xerrors.Errorf("failed to get tipset for block %s: %w", blkParam, err)
return nil, err
}

if ts == nil {
Expand Down Expand Up @@ -940,9 +938,9 @@ func (a *EthModule) EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (eth
}
}

ts, err := getTipsetByBlockNumber(ctx, a.Chain, params.NewestBlkNum, false)
ts, err := getTipsetByBlockNumber(ctx, a.Chain, params.NewestBlkNum, true)
if err != nil {
return ethtypes.EthFeeHistory{}, fmt.Errorf("bad block parameter %s: %s", params.NewestBlkNum, err)
return ethtypes.EthFeeHistory{}, err
}

var (
Expand Down Expand Up @@ -1099,9 +1097,9 @@ func (a *EthModule) Web3ClientVersion(ctx context.Context) (string, error) {
}

func (a *EthModule) EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtypes.EthTraceBlock, error) {
ts, err := getTipsetByBlockNumber(ctx, a.Chain, blkNum, false)
ts, err := getTipsetByBlockNumber(ctx, a.Chain, blkNum, true)
if err != nil {
return nil, xerrors.Errorf("failed to get tipset: %w", err)
return nil, err
}

stRoot, trace, err := a.StateManager.ExecutionTrace(ctx, ts)
Expand Down Expand Up @@ -1171,9 +1169,9 @@ func (a *EthModule) EthTraceReplayBlockTransactions(ctx context.Context, blkNum
return nil, fmt.Errorf("only 'trace' is supported")
}

ts, err := getTipsetByBlockNumber(ctx, a.Chain, blkNum, false)
ts, err := getTipsetByBlockNumber(ctx, a.Chain, blkNum, true)
if err != nil {
return nil, xerrors.Errorf("failed to get tipset: %w", err)
return nil, err
}

stRoot, trace, err := a.StateManager.ExecutionTrace(ctx, ts)
Expand Down
2 changes: 1 addition & 1 deletion node/impl/full/eth_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func getTipsetByBlockNumber(ctx context.Context, chain *store.ChainStore, blkPar
return nil, fmt.Errorf("cannot get tipset at height: %v", num)
}
if strict && ts.Height() != abi.ChainEpoch(num) {
return nil, ErrNullRound
return nil, api.NewErrNullRound(int64(num))
virajbhartiya marked this conversation as resolved.
Show resolved Hide resolved
}
return ts, nil
}
Expand Down
Loading