Tests for builtin actor events API

This commit is contained in:
Aarsh Shah
2024-03-22 07:00:28 +01:00
committed by Phi-rjan
parent 74a46fed3d
commit 0ca169d252
19 changed files with 519 additions and 247 deletions
+57 -2
View File
@@ -3,6 +3,7 @@ package full
import (
"context"
"fmt"
"strings"
"github.com/ipfs/go-cid"
"go.uber.org/fx"
@@ -11,6 +12,7 @@ import (
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/events/filter"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
)
@@ -27,6 +29,7 @@ var (
type ActorEvent struct {
EventFilterManager *filter.EventFilterManager
MaxFilterHeightRange abi.ChainEpoch
Chain *store.ChainStore
}
var _ ActorEventAPI = (*ActorEvent)(nil)
@@ -41,8 +44,13 @@ func (a *ActorEvent) GetActorEvents(ctx context.Context, filter *types.ActorEven
return nil, api.ErrNotSupported
}
params, err := a.parseFilter(filter)
if err != nil {
return nil, err
}
// Create a temporary filter
f, err := a.EventFilterManager.Install(ctx, filter.MinEpoch, filter.MaxEpoch, cid.Undef, filter.Addresses, filter.Fields, false)
f, err := a.EventFilterManager.Install(ctx, params.MinHeight, params.MaxHeight, params.TipSetCid, filter.Addresses, filter.Fields, false)
if err != nil {
return nil, err
}
@@ -52,11 +60,58 @@ func (a *ActorEvent) GetActorEvents(ctx context.Context, filter *types.ActorEven
return evs, err
}
type filterParams struct {
MinHeight abi.ChainEpoch
MaxHeight abi.ChainEpoch
TipSetCid cid.Cid
}
func (a *ActorEvent) parseFilter(f *types.ActorEventFilter) (*filterParams, error) {
if f.TipSetCid != nil {
if len(f.FromEpoch) != 0 || len(f.ToEpoch) != 0 {
return nil, fmt.Errorf("cannot specify both TipSetCid and FromEpoch/ToEpoch")
}
return &filterParams{
MinHeight: 0,
MaxHeight: 0,
TipSetCid: *f.TipSetCid,
}, nil
}
from := f.FromEpoch
if len(from) != 0 && from != "latest" && from != "earliest" && !strings.HasPrefix(from, "0x") {
from = "0x" + from
}
to := f.ToEpoch
if len(to) != 0 && to != "latest" && to != "earliest" && !strings.HasPrefix(to, "0x") {
to = "0x" + to
}
min, max, err := parseBlockRange(a.Chain.GetHeaviestTipSet().Height(), &from, &to, a.MaxFilterHeightRange)
if err != nil {
return nil, err
}
return &filterParams{
MinHeight: min,
MaxHeight: max,
TipSetCid: cid.Undef,
}, nil
}
func (a *ActorEvent) SubscribeActorEvents(ctx context.Context, f *types.SubActorEventFilter) (<-chan *types.ActorEvent, error) {
if a.EventFilterManager == nil {
return nil, api.ErrNotSupported
}
fm, err := a.EventFilterManager.Install(ctx, f.Filter.MinEpoch, f.Filter.MaxEpoch, cid.Undef, f.Filter.Addresses, f.Filter.Fields, false)
params, err := a.parseFilter(&f.Filter)
if err != nil {
return nil, err
}
fm, err := a.EventFilterManager.Install(ctx, params.MinHeight, params.MaxHeight, params.TipSetCid, f.Filter.Addresses, f.Filter.Fields, false)
if err != nil {
return nil, err
}
+58 -58
View File
@@ -1264,6 +1264,59 @@ func (e *EthEvent) EthGetFilterLogs(ctx context.Context, id ethtypes.EthFilterID
return nil, xerrors.Errorf("wrong filter type")
}
func parseBlockRange(heaviest abi.ChainEpoch, fromBlock, toBlock *string, maxRange abi.ChainEpoch) (minHeight abi.ChainEpoch, maxHeight abi.ChainEpoch, err error) {
if fromBlock == nil || *fromBlock == "latest" || len(*fromBlock) == 0 {
minHeight = heaviest
} else if *fromBlock == "earliest" {
minHeight = 0
} else {
if !strings.HasPrefix(*fromBlock, "0x") {
return 0, 0, xerrors.Errorf("FromBlock is not a hex")
}
epoch, err := ethtypes.EthUint64FromHex(*fromBlock)
if err != nil {
return 0, 0, xerrors.Errorf("invalid epoch")
}
minHeight = abi.ChainEpoch(epoch)
}
if toBlock == nil || *toBlock == "latest" || len(*toBlock) == 0 {
// here latest means the latest at the time
maxHeight = -1
} else if *toBlock == "earliest" {
maxHeight = 0
} else {
if !strings.HasPrefix(*toBlock, "0x") {
return 0, 0, xerrors.Errorf("ToBlock is not a hex")
}
epoch, err := ethtypes.EthUint64FromHex(*toBlock)
if err != nil {
return 0, 0, xerrors.Errorf("invalid epoch")
}
maxHeight = abi.ChainEpoch(epoch)
}
// Validate height ranges are within limits set by node operator
if minHeight == -1 && maxHeight > 0 {
// Here the client is looking for events between the head and some future height
if maxHeight-heaviest > maxRange {
return 0, 0, xerrors.Errorf("invalid epoch range: to block is too far in the future (maximum: %d)", maxRange)
}
} else if minHeight >= 0 && maxHeight == -1 {
// Here the client is looking for events between some time in the past and the current head
if heaviest-minHeight > maxRange {
return 0, 0, xerrors.Errorf("invalid epoch range: from block is too far in the past (maximum: %d)", maxRange)
}
} else if minHeight >= 0 && maxHeight >= 0 {
if minHeight > maxHeight {
return 0, 0, xerrors.Errorf("invalid epoch range: to block (%d) must be after from block (%d)", minHeight, maxHeight)
} else if maxHeight-minHeight > maxRange {
return 0, 0, xerrors.Errorf("invalid epoch range: range between to and from blocks is too large (maximum: %d)", maxRange)
}
}
return minHeight, maxHeight, nil
}
func (e *EthEvent) installEthFilterSpec(ctx context.Context, filterSpec *ethtypes.EthFilterSpec) (*filter.EventFilter, error) {
var (
minHeight abi.ChainEpoch
@@ -1280,64 +1333,11 @@ func (e *EthEvent) installEthFilterSpec(ctx context.Context, filterSpec *ethtype
tipsetCid = filterSpec.BlockHash.ToCid()
} else {
if filterSpec.FromBlock == nil || *filterSpec.FromBlock == "latest" {
ts := e.Chain.GetHeaviestTipSet()
minHeight = ts.Height()
} else if *filterSpec.FromBlock == "earliest" {
minHeight = 0
} else if *filterSpec.FromBlock == "pending" {
return nil, api.ErrNotSupported
} else {
if !strings.HasPrefix(*filterSpec.FromBlock, "0x") {
return nil, xerrors.Errorf("FromBlock is not a hex")
}
epoch, err := ethtypes.EthUint64FromHex(*filterSpec.FromBlock)
if err != nil {
return nil, xerrors.Errorf("invalid epoch")
}
minHeight = abi.ChainEpoch(epoch)
var err error
minHeight, maxHeight, err = parseBlockRange(e.Chain.GetHeaviestTipSet().Height(), filterSpec.FromBlock, filterSpec.ToBlock, e.MaxFilterHeightRange)
if err != nil {
return nil, err
}
if filterSpec.ToBlock == nil || *filterSpec.ToBlock == "latest" {
// here latest means the latest at the time
maxHeight = -1
} else if *filterSpec.ToBlock == "earliest" {
maxHeight = 0
} else if *filterSpec.ToBlock == "pending" {
return nil, api.ErrNotSupported
} else {
if !strings.HasPrefix(*filterSpec.ToBlock, "0x") {
return nil, xerrors.Errorf("ToBlock is not a hex")
}
epoch, err := ethtypes.EthUint64FromHex(*filterSpec.ToBlock)
if err != nil {
return nil, xerrors.Errorf("invalid epoch")
}
maxHeight = abi.ChainEpoch(epoch)
}
// Validate height ranges are within limits set by node operator
if minHeight == -1 && maxHeight > 0 {
// Here the client is looking for events between the head and some future height
ts := e.Chain.GetHeaviestTipSet()
if maxHeight-ts.Height() > e.MaxFilterHeightRange {
return nil, xerrors.Errorf("invalid epoch range: to block is too far in the future (maximum: %d)", e.MaxFilterHeightRange)
}
} else if minHeight >= 0 && maxHeight == -1 {
// Here the client is looking for events between some time in the past and the current head
ts := e.Chain.GetHeaviestTipSet()
if ts.Height()-minHeight > e.MaxFilterHeightRange {
return nil, xerrors.Errorf("invalid epoch range: from block is too far in the past (maximum: %d)", e.MaxFilterHeightRange)
}
} else if minHeight >= 0 && maxHeight >= 0 {
if minHeight > maxHeight {
return nil, xerrors.Errorf("invalid epoch range: to block (%d) must be after from block (%d)", minHeight, maxHeight)
} else if maxHeight-minHeight > e.MaxFilterHeightRange {
return nil, xerrors.Errorf("invalid epoch range: range between to and from blocks is too large (maximum: %d)", e.MaxFilterHeightRange)
}
}
}
// Convert all addresses to filecoin f4 addresses
@@ -1362,7 +1362,7 @@ func keysToKeysWithCodec(keys map[string][][]byte) map[string][]types.ActorEvent
for k, v := range keys {
for _, vv := range v {
keysWithCodec[k] = append(keysWithCodec[k], types.ActorEventBlock{
Codec: uint64(multicodec.Raw),
Codec: uint64(multicodec.Raw), // FEVM smart contract events are always encoded with the `raw` Codec.
Value: vv,
})
}
+76
View File
@@ -3,6 +3,7 @@ package full
import (
"bytes"
"encoding/hex"
"fmt"
"testing"
"github.com/ipfs/go-cid"
@@ -10,12 +11,87 @@ import (
"github.com/stretchr/testify/require"
cbg "github.com/whyrusleeping/cbor-gen"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/types/ethtypes"
)
func TestParseBlockRange(t *testing.T) {
pstring := func(s string) *string { return &s }
tcs := map[string]struct {
heaviest abi.ChainEpoch
from *string
to *string
maxRange abi.ChainEpoch
minOut abi.ChainEpoch
maxOut abi.ChainEpoch
errStr string
}{
"fails when both are specified and range is greater than max allowed range": {
heaviest: 100,
from: pstring("0x100"),
to: pstring("0x200"),
maxRange: 10,
minOut: 0,
maxOut: 0,
errStr: "too large",
},
"fails when min is specified and range is greater than max allowed range": {
heaviest: 500,
from: pstring("0x10"),
to: pstring("latest"),
maxRange: 10,
minOut: 0,
maxOut: 0,
errStr: "too far in the past",
},
"fails when max is specified and range is greater than max allowed range": {
heaviest: 500,
from: pstring("earliest"),
to: pstring("0x10000"),
maxRange: 10,
minOut: 0,
maxOut: 0,
errStr: "too large",
},
"works when range is valid": {
heaviest: 500,
from: pstring("earliest"),
to: pstring("latest"),
maxRange: 1000,
minOut: 0,
maxOut: -1,
},
"works when range is valid and specified": {
heaviest: 500,
from: pstring("0x10"),
to: pstring("0x30"),
maxRange: 1000,
minOut: 16,
maxOut: 48,
},
}
for name, tc := range tcs {
tc2 := tc
t.Run(name, func(t *testing.T) {
min, max, err := parseBlockRange(tc2.heaviest, tc2.from, tc2.to, tc2.maxRange)
require.Equal(t, tc2.minOut, min)
require.Equal(t, tc2.maxOut, max)
if tc2.errStr != "" {
fmt.Println(err)
require.Error(t, err)
require.Contains(t, err.Error(), tc2.errStr)
} else {
require.NoError(t, err)
}
})
}
}
func TestEthLogFromEvent(t *testing.T) {
// basic empty
data, topics, ok := ethLogFromEvent(nil)