Built-in actor events first draft

This commit is contained in:
Aarsh Shah 2023-12-19 15:07:08 +04:00 committed by Rod Vagg
parent 9aef2ec8b5
commit f007a012af
29 changed files with 810 additions and 115 deletions

View File

@ -906,6 +906,20 @@ type FullNode interface {
RaftState(ctx context.Context) (*RaftStateData, error) //perm:read
RaftLeader(ctx context.Context) (peer.ID, error) //perm:read
// Actor events
// GetActorEvents returns all FVM and built-in Actor events that match the given filter.
// This is a request/response API.
GetActorEvents(ctx context.Context, filter *types.ActorEventFilter) ([]*types.ActorEvent, error) //perm:read
// SubscribeActorEvents returns a long-lived stream of all FVM and built-in Actor events that match the given filter.
// Events that match the given filter are written to the stream in real-time as they are emitted from the FVM.
// The response stream is closed when the client disconnects or if there is an error while writing an event to the stream.
// This API also allows clients to read all historical events matching the given filter before
// any real-time events are written to the response stream.
// NOTE: THIS API IS ONLY SUPPORTED OVER WEBSOCKETS FOR NOW
SubscribeActorEvents(ctx context.Context, filter *types.SubActorEventFilter) (<-chan *types.ActorEvent, error) //perm:read
}
// reverse interface to the client, called after EthSubscribe

View File

@ -129,4 +129,7 @@ type Gateway interface {
Web3ClientVersion(ctx context.Context) (string, error)
EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtypes.EthTraceBlock, error)
EthTraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error)
GetActorEvents(ctx context.Context, filter *types.ActorEventFilter) ([]*types.ActorEvent, error)
SubscribeActorEvents(ctx context.Context, filter *types.SubActorEventFilter) (<-chan *types.ActorEvent, error)
}

View File

@ -414,6 +414,42 @@ func init() {
VerifiedAllocationKey: nil,
Notify: nil,
})
addExample(&types.ActorEventBlock{
Codec: 0x51,
Value: []byte("data"),
})
addExample(&types.ActorEventFilter{
Addresses: []address.Address{addr},
Fields: map[string][]types.ActorEventBlock{
"abc": {
{
Codec: 0x51,
Value: []byte("data"),
},
},
},
MinEpoch: 2301220,
MaxEpoch: 2301220,
})
addExample(&types.SubActorEventFilter{
Filter: types.ActorEventFilter{
Addresses: []address.Address{addr},
Fields: map[string][]types.ActorEventBlock{
"abc": {
{
Codec: 0x51,
Value: []byte("data"),
},
},
},
MinEpoch: 2301220,
MaxEpoch: 2301220,
},
Prefill: true,
})
}
func GetAPIType(name, pkg string) (i interface{}, t reflect.Type, permStruct []reflect.Type) {

View File

@ -1626,6 +1626,21 @@ func (mr *MockFullNodeMockRecorder) GasEstimateMessageGas(arg0, arg1, arg2, arg3
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GasEstimateMessageGas", reflect.TypeOf((*MockFullNode)(nil).GasEstimateMessageGas), arg0, arg1, arg2, arg3)
}
// GetActorEvents mocks base method.
func (m *MockFullNode) GetActorEvents(arg0 context.Context, arg1 *types.ActorEventFilter) ([]*types.ActorEvent, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetActorEvents", arg0, arg1)
ret0, _ := ret[0].([]*types.ActorEvent)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetActorEvents indicates an expected call of GetActorEvents.
func (mr *MockFullNodeMockRecorder) GetActorEvents(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActorEvents", reflect.TypeOf((*MockFullNode)(nil).GetActorEvents), arg0, arg1)
}
// ID mocks base method.
func (m *MockFullNode) ID(arg0 context.Context) (peer.ID, error) {
m.ctrl.T.Helper()
@ -3968,6 +3983,21 @@ func (mr *MockFullNodeMockRecorder) StateWaitMsg(arg0, arg1, arg2, arg3, arg4 in
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateWaitMsg", reflect.TypeOf((*MockFullNode)(nil).StateWaitMsg), arg0, arg1, arg2, arg3, arg4)
}
// SubscribeActorEvents mocks base method.
func (m *MockFullNode) SubscribeActorEvents(arg0 context.Context, arg1 *types.SubActorEventFilter) (<-chan *types.ActorEvent, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SubscribeActorEvents", arg0, arg1)
ret0, _ := ret[0].(<-chan *types.ActorEvent)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SubscribeActorEvents indicates an expected call of SubscribeActorEvents.
func (mr *MockFullNodeMockRecorder) SubscribeActorEvents(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubscribeActorEvents", reflect.TypeOf((*MockFullNode)(nil).SubscribeActorEvents), arg0, arg1)
}
// SyncCheckBad mocks base method.
func (m *MockFullNode) SyncCheckBad(arg0 context.Context, arg1 cid.Cid) (string, error) {
m.ctrl.T.Helper()

View File

@ -335,6 +335,8 @@ type FullNodeMethods struct {
GasEstimateMessageGas func(p0 context.Context, p1 *types.Message, p2 *MessageSendSpec, p3 types.TipSetKey) (*types.Message, error) `perm:"read"`
GetActorEvents func(p0 context.Context, p1 *types.ActorEventFilter) ([]*types.ActorEvent, error) `perm:"read"`
MarketAddBalance func(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) `perm:"sign"`
MarketGetReserved func(p0 context.Context, p1 address.Address) (types.BigInt, error) `perm:"sign"`
@ -589,6 +591,8 @@ type FullNodeMethods struct {
StateWaitMsg func(p0 context.Context, p1 cid.Cid, p2 uint64, p3 abi.ChainEpoch, p4 bool) (*MsgLookup, error) `perm:"read"`
SubscribeActorEvents func(p0 context.Context, p1 *types.SubActorEventFilter) (<-chan *types.ActorEvent, error) `perm:"read"`
SyncCheckBad func(p0 context.Context, p1 cid.Cid) (string, error) `perm:"read"`
SyncCheckpoint func(p0 context.Context, p1 types.TipSetKey) error `perm:"admin"`
@ -755,6 +759,8 @@ type GatewayMethods struct {
GasEstimateMessageGas func(p0 context.Context, p1 *types.Message, p2 *MessageSendSpec, p3 types.TipSetKey) (*types.Message, error) ``
GetActorEvents func(p0 context.Context, p1 *types.ActorEventFilter) ([]*types.ActorEvent, error) ``
MinerGetBaseInfo func(p0 context.Context, p1 address.Address, p2 abi.ChainEpoch, p3 types.TipSetKey) (*MiningBaseInfo, error) ``
MpoolGetNonce func(p0 context.Context, p1 address.Address) (uint64, error) ``
@ -829,6 +835,8 @@ type GatewayMethods struct {
StateWaitMsg func(p0 context.Context, p1 cid.Cid, p2 uint64, p3 abi.ChainEpoch, p4 bool) (*MsgLookup, error) ``
SubscribeActorEvents func(p0 context.Context, p1 *types.SubActorEventFilter) (<-chan *types.ActorEvent, error) ``
Version func(p0 context.Context) (APIVersion, error) ``
WalletBalance func(p0 context.Context, p1 address.Address) (types.BigInt, error) ``
@ -2584,6 +2592,17 @@ func (s *FullNodeStub) GasEstimateMessageGas(p0 context.Context, p1 *types.Messa
return nil, ErrNotSupported
}
func (s *FullNodeStruct) GetActorEvents(p0 context.Context, p1 *types.ActorEventFilter) ([]*types.ActorEvent, error) {
if s.Internal.GetActorEvents == nil {
return *new([]*types.ActorEvent), ErrNotSupported
}
return s.Internal.GetActorEvents(p0, p1)
}
func (s *FullNodeStub) GetActorEvents(p0 context.Context, p1 *types.ActorEventFilter) ([]*types.ActorEvent, error) {
return *new([]*types.ActorEvent), ErrNotSupported
}
func (s *FullNodeStruct) MarketAddBalance(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) {
if s.Internal.MarketAddBalance == nil {
return *new(cid.Cid), ErrNotSupported
@ -3981,6 +4000,17 @@ func (s *FullNodeStub) StateWaitMsg(p0 context.Context, p1 cid.Cid, p2 uint64, p
return nil, ErrNotSupported
}
func (s *FullNodeStruct) SubscribeActorEvents(p0 context.Context, p1 *types.SubActorEventFilter) (<-chan *types.ActorEvent, error) {
if s.Internal.SubscribeActorEvents == nil {
return nil, ErrNotSupported
}
return s.Internal.SubscribeActorEvents(p0, p1)
}
func (s *FullNodeStub) SubscribeActorEvents(p0 context.Context, p1 *types.SubActorEventFilter) (<-chan *types.ActorEvent, error) {
return nil, ErrNotSupported
}
func (s *FullNodeStruct) SyncCheckBad(p0 context.Context, p1 cid.Cid) (string, error) {
if s.Internal.SyncCheckBad == nil {
return "", ErrNotSupported
@ -4828,6 +4858,17 @@ func (s *GatewayStub) GasEstimateMessageGas(p0 context.Context, p1 *types.Messag
return nil, ErrNotSupported
}
func (s *GatewayStruct) GetActorEvents(p0 context.Context, p1 *types.ActorEventFilter) ([]*types.ActorEvent, error) {
if s.Internal.GetActorEvents == nil {
return *new([]*types.ActorEvent), ErrNotSupported
}
return s.Internal.GetActorEvents(p0, p1)
}
func (s *GatewayStub) GetActorEvents(p0 context.Context, p1 *types.ActorEventFilter) ([]*types.ActorEvent, error) {
return *new([]*types.ActorEvent), ErrNotSupported
}
func (s *GatewayStruct) MinerGetBaseInfo(p0 context.Context, p1 address.Address, p2 abi.ChainEpoch, p3 types.TipSetKey) (*MiningBaseInfo, error) {
if s.Internal.MinerGetBaseInfo == nil {
return nil, ErrNotSupported
@ -5235,6 +5276,17 @@ func (s *GatewayStub) StateWaitMsg(p0 context.Context, p1 cid.Cid, p2 uint64, p3
return nil, ErrNotSupported
}
func (s *GatewayStruct) SubscribeActorEvents(p0 context.Context, p1 *types.SubActorEventFilter) (<-chan *types.ActorEvent, error) {
if s.Internal.SubscribeActorEvents == nil {
return nil, ErrNotSupported
}
return s.Internal.SubscribeActorEvents(p0, p1)
}
func (s *GatewayStub) SubscribeActorEvents(p0 context.Context, p1 *types.SubActorEventFilter) (<-chan *types.ActorEvent, error) {
return nil, ErrNotSupported
}
func (s *GatewayStruct) Version(p0 context.Context) (APIVersion, error) {
if s.Internal.Version == nil {
return *new(APIVersion), ErrNotSupported

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -28,13 +28,14 @@ func isIndexedValue(b uint8) bool {
}
type EventFilter struct {
id types.FilterID
minHeight abi.ChainEpoch // minimum epoch to apply filter or -1 if no minimum
maxHeight abi.ChainEpoch // maximum epoch to apply filter or -1 if no maximum
tipsetCid cid.Cid
addresses []address.Address // list of f4 actor addresses that are extpected to emit the event
keys map[string][][]byte // map of key names to a list of alternate values that may match
maxResults int // maximum number of results to collect, 0 is unlimited
id types.FilterID
minHeight abi.ChainEpoch // minimum epoch to apply filter or -1 if no minimum
maxHeight abi.ChainEpoch // maximum epoch to apply filter or -1 if no maximum
tipsetCid cid.Cid
addresses []address.Address // list of f4 actor addresses that are extpected to emit the event
keysWithCodec map[string][]types.ActorEventBlock // map of key names to a list of alternate values that may match
maxResults int // maximum number of results to collect, 0 is unlimited
mu sync.Mutex
collected []*CollectedEvent
@ -194,7 +195,7 @@ func (f *EventFilter) matchAddress(o address.Address) bool {
}
func (f *EventFilter) matchKeys(ees []types.EventEntry) bool {
if len(f.keys) == 0 {
if len(f.keysWithCodec) == 0 {
return true
}
// TODO: optimize this naive algorithm
@ -216,19 +217,19 @@ func (f *EventFilter) matchKeys(ees []types.EventEntry) bool {
continue
}
wantlist, ok := f.keys[keyname]
wantlist, ok := f.keysWithCodec[keyname]
if !ok || len(wantlist) == 0 {
continue
}
for _, w := range wantlist {
if bytes.Equal(w, ee.Value) {
if bytes.Equal(w.Value, ee.Value) && w.Codec == ee.Codec {
matched[keyname] = true
break
}
}
if len(matched) == len(f.keys) {
if len(matched) == len(f.keysWithCodec) {
// all keys have been matched
return true
}
@ -362,7 +363,8 @@ func (m *EventFilterManager) Revert(ctx context.Context, from, to *types.TipSet)
return nil
}
func (m *EventFilterManager) Install(ctx context.Context, minHeight, maxHeight abi.ChainEpoch, tipsetCid cid.Cid, addresses []address.Address, keys map[string][][]byte) (*EventFilter, error) {
func (m *EventFilterManager) Install(ctx context.Context, minHeight, maxHeight abi.ChainEpoch, tipsetCid cid.Cid, addresses []address.Address,
keysWithCodec map[string][]types.ActorEventBlock, excludeReverted bool) (*EventFilter, error) {
m.mu.Lock()
currentHeight := m.currentHeight
m.mu.Unlock()
@ -377,18 +379,18 @@ func (m *EventFilterManager) Install(ctx context.Context, minHeight, maxHeight a
}
f := &EventFilter{
id: id,
minHeight: minHeight,
maxHeight: maxHeight,
tipsetCid: tipsetCid,
addresses: addresses,
keys: keys,
maxResults: m.MaxFilterResults,
id: id,
minHeight: minHeight,
maxHeight: maxHeight,
tipsetCid: tipsetCid,
addresses: addresses,
keysWithCodec: keysWithCodec,
maxResults: m.MaxFilterResults,
}
if m.EventIndex != nil && minHeight != -1 && minHeight < currentHeight {
// Filter needs historic events
if err := m.EventIndex.PrefillFilter(ctx, f, true); err != nil {
if err := m.EventIndex.PrefillFilter(ctx, f, excludeReverted); err != nil {
return nil, err
}
}

View File

@ -22,6 +22,19 @@ import (
"github.com/filecoin-project/lotus/chain/types"
)
func keysToKeysWithCodec(keys map[string][][]byte) map[string][]types.ActorEventBlock {
keysWithCodec := make(map[string][]types.ActorEventBlock)
for k, v := range keys {
for _, vv := range v {
keysWithCodec[k] = append(keysWithCodec[k], types.ActorEventBlock{
Codec: cid.Raw,
Value: vv,
})
}
}
return keysWithCodec
}
func TestEventFilterCollectEvents(t *testing.T) {
rng := pseudo.New(pseudo.NewSource(299792458))
a1 := randomF4Addr(t, rng)
@ -139,11 +152,11 @@ func TestEventFilterCollectEvents(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("approval"),
},
},
}),
},
te: events14000,
want: oneCollectedEvent,
@ -153,13 +166,13 @@ func TestEventFilterCollectEvents(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("cancel"),
[]byte("propose"),
[]byte("approval"),
},
},
}),
},
te: events14000,
want: oneCollectedEvent,
@ -169,12 +182,12 @@ func TestEventFilterCollectEvents(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("cancel"),
[]byte("propose"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -184,11 +197,11 @@ func TestEventFilterCollectEvents(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"method": {
[]byte("approval"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -198,14 +211,14 @@ func TestEventFilterCollectEvents(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("approval"),
},
"signer": {
[]byte("addr1"),
},
},
}),
},
te: events14000,
want: oneCollectedEvent,
@ -215,14 +228,14 @@ func TestEventFilterCollectEvents(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("approval"),
},
"approver": {
[]byte("addr1"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -232,14 +245,14 @@ func TestEventFilterCollectEvents(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("approval"),
},
"signer": {
[]byte("addr2"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -249,11 +262,11 @@ func TestEventFilterCollectEvents(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"amount": {
[]byte("2988181"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,

View File

@ -514,9 +514,9 @@ func (ei *EventIndex) PrefillFilter(ctx context.Context, f *EventFilter, exclude
clauses = append(clauses, "("+strings.Join(subclauses, " OR ")+")")
}
if len(f.keys) > 0 {
if len(f.keysWithCodec) > 0 {
join := 0
for key, vals := range f.keys {
for key, vals := range f.keysWithCodec {
if len(vals) > 0 {
join++
joinAlias := fmt.Sprintf("ee%d", join)
@ -525,8 +525,8 @@ func (ei *EventIndex) PrefillFilter(ctx context.Context, f *EventFilter, exclude
values = append(values, key)
subclauses := []string{}
for _, val := range vals {
subclauses = append(subclauses, fmt.Sprintf("%s.value=?", joinAlias))
values = append(values, val)
subclauses = append(subclauses, fmt.Sprintf("(%s.value=? AND %[1]s.codec=?)", joinAlias))
values = append(values, val.Value, val.Codec)
}
clauses = append(clauses, "("+strings.Join(subclauses, " OR ")+")")
}

View File

@ -148,11 +148,11 @@ func TestEventIndexPrefillFilter(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("approval"),
},
},
}),
},
te: events14000,
want: oneCollectedEvent,
@ -162,13 +162,13 @@ func TestEventIndexPrefillFilter(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("cancel"),
[]byte("propose"),
[]byte("approval"),
},
},
}),
},
te: events14000,
want: oneCollectedEvent,
@ -178,12 +178,12 @@ func TestEventIndexPrefillFilter(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("cancel"),
[]byte("propose"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -193,11 +193,11 @@ func TestEventIndexPrefillFilter(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"method": {
[]byte("approval"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -207,14 +207,14 @@ func TestEventIndexPrefillFilter(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("approval"),
},
"signer": {
[]byte("addr1"),
},
},
}),
},
te: events14000,
want: oneCollectedEvent,
@ -224,14 +224,14 @@ func TestEventIndexPrefillFilter(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("approval"),
},
"approver": {
[]byte("addr1"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -241,14 +241,14 @@ func TestEventIndexPrefillFilter(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("approval"),
},
"signer": {
[]byte("addr2"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -258,11 +258,11 @@ func TestEventIndexPrefillFilter(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"amount": {
[]byte("2988181"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -495,11 +495,11 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("approval"),
},
},
}),
},
te: events14000,
want: twoCollectedEvent,
@ -509,13 +509,13 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("cancel"),
[]byte("propose"),
[]byte("approval"),
},
},
}),
},
te: events14000,
want: twoCollectedEvent,
@ -525,12 +525,12 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("cancel"),
[]byte("propose"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -540,11 +540,11 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"method": {
[]byte("approval"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -554,14 +554,14 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("approval"),
},
"signer": {
[]byte("addr1"),
},
},
}),
},
te: events14000,
want: oneCollectedEvent,
@ -571,14 +571,14 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("approval"),
},
"signer": {
[]byte("addr2"),
},
},
}),
},
te: revertedEvents14000,
want: oneCollectedRevertedEvent,
@ -588,14 +588,14 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("approval"),
},
"approver": {
[]byte("addr1"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -605,14 +605,14 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("approval"),
},
"signer": {
[]byte("addr3"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -622,11 +622,11 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"amount": {
[]byte("2988181"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -636,11 +636,11 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"amount": {
[]byte("2988182"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -735,11 +735,11 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("approval"),
},
},
}),
},
te: events14000,
want: oneCollectedEvent,
@ -749,13 +749,13 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("cancel"),
[]byte("propose"),
[]byte("approval"),
},
},
}),
},
te: events14000,
want: oneCollectedEvent,
@ -765,12 +765,12 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("cancel"),
[]byte("propose"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -780,11 +780,11 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"method": {
[]byte("approval"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -794,14 +794,14 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("approval"),
},
"signer": {
[]byte("addr1"),
},
},
}),
},
te: events14000,
want: oneCollectedEvent,
@ -811,14 +811,14 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("approval"),
},
"approver": {
[]byte("addr1"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -828,14 +828,14 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("approval"),
},
"signer": {
[]byte("addr2"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -845,14 +845,14 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"type": {
[]byte("approval"),
},
"signer": {
[]byte("addr3"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,
@ -862,11 +862,11 @@ func TestEventIndexPrefillFilterExcludeReverted(t *testing.T) {
filter: &EventFilter{
minHeight: -1,
maxHeight: -1,
keys: map[string][][]byte{
keysWithCodec: keysToKeysWithCodec(map[string][][]byte{
"amount": {
[]byte("2988181"),
},
},
}),
},
te: events14000,
want: noCollectedEvents,

View File

@ -0,0 +1,47 @@
package types
import (
"github.com/ipfs/go-cid"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
)
type ActorEventBlock struct {
// what value codec does client want to match on ?
Codec uint64 `json:"codec"`
// data associated with the "event key"
Value []byte `json:"value"`
}
type SubActorEventFilter struct {
Filter ActorEventFilter `json:"filter"`
Prefill bool `json:"prefill"`
}
type ActorEventFilter struct {
// Matches events from one of these actors, or any actor if empty.
// TODO: Should we also allow Eth addresses here?
// For now, this MUST be a Filecoin address.
Addresses []address.Address `json:"address"`
// Matches events with the specified key/values, or all events if empty.
// If the `Blocks` slice is empty, matches on the key only.
Fields map[string][]ActorEventBlock `json:"fields"`
// Epoch based filtering ?
// Start epoch for the filter; -1 means no minimum
MinEpoch abi.ChainEpoch `json:"minEpoch,omitempty"`
// End epoch for the filter; -1 means no maximum
MaxEpoch abi.ChainEpoch `json:"maxEpoch,omitempty"`
}
type ActorEvent struct {
Entries []EventEntry
EmitterAddr address.Address // f4 address of emitter
Reverted bool
Height abi.ChainEpoch
TipSetKey cid.Cid // tipset that contained the message
MsgCid cid.Cid // cid of message that produced event
}

View File

@ -115,6 +115,8 @@
* [GasEstimateGasLimit](#GasEstimateGasLimit)
* [GasEstimateGasPremium](#GasEstimateGasPremium)
* [GasEstimateMessageGas](#GasEstimateMessageGas)
* [Get](#Get)
* [GetActorEvents](#GetActorEvents)
* [I](#I)
* [ID](#ID)
* [Log](#Log)
@ -282,6 +284,8 @@
* [StateVerifiedRegistryRootKey](#StateVerifiedRegistryRootKey)
* [StateVerifierStatus](#StateVerifierStatus)
* [StateWaitMsg](#StateWaitMsg)
* [Subscribe](#Subscribe)
* [SubscribeActorEvents](#SubscribeActorEvents)
* [Sync](#Sync)
* [SyncCheckBad](#SyncCheckBad)
* [SyncCheckpoint](#SyncCheckpoint)
@ -3382,6 +3386,62 @@ Response:
}
```
## Get
### GetActorEvents
GetActorEvents returns all FVM and built-in Actor events that match the given filter.
This is a request/response API.
Perms: read
Inputs:
```json
[
{
"address": [
"f01234"
],
"fields": {
"abc": [
{
"codec": 81,
"value": "ZGF0YQ=="
}
]
},
"minEpoch": 2301220,
"maxEpoch": 2301220
}
]
```
Response:
```json
[
{
"Entries": [
{
"Flags": 7,
"Key": "string value",
"Codec": 42,
"Value": "Ynl0ZSBhcnJheQ=="
}
],
"EmitterAddr": "f01234",
"Reverted": true,
"Height": 10101,
"TipSetKey": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
},
"MsgCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
}
]
```
## I
@ -8758,6 +8818,67 @@ Response:
}
```
## Subscribe
### SubscribeActorEvents
SubscribeActorEvents returns a long-lived stream of all FVM and built-in Actor events that match the given filter.
Events that match the given filter are written to the stream in real-time as they are emitted from the FVM.
The response stream is closed when the client disconnects or if there is an error while writing an event to the stream.
This API also allows clients to read all historical events matching the given filter before
any real-time events are written to the response stream.
NOTE: THIS API IS ONLY SUPPORTED OVER WEBSOCKETS FOR NOW
Perms: read
Inputs:
```json
[
{
"filter": {
"address": [
"f01234"
],
"fields": {
"abc": [
{
"codec": 81,
"value": "ZGF0YQ=="
}
]
},
"minEpoch": 2301220,
"maxEpoch": 2301220
},
"prefill": true
}
]
```
Response:
```json
{
"Entries": [
{
"Flags": 7,
"Key": "string value",
"Codec": 42,
"Value": "Ynl0ZSBhcnJheQ=="
}
],
"EmitterAddr": "f01234",
"Reverted": true,
"Height": 10101,
"TipSetKey": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
},
"MsgCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
}
```
## Sync
The Sync method group contains methods for interacting with and
observing the lotus sync service.

View File

@ -330,6 +330,10 @@
# env var: LOTUS_FEVM_ENABLEETHRPC
#EnableEthRPC = false
# type: bool
# env var: LOTUS_FEVM_ENABLEACTOREVENTSAPI
#EnableActorEventsAPI = false
# EthTxHashMappingLifetimeDays the transaction hash lookup database will delete mappings that have been stored for more than x days
# Set to 0 to keep all mappings
#

View File

@ -146,6 +146,9 @@ type TargetAPI interface {
Web3ClientVersion(ctx context.Context) (string, error)
EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtypes.EthTraceBlock, error)
EthTraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error)
GetActorEvents(ctx context.Context, filter *types.ActorEventFilter) ([]*types.ActorEvent, error)
SubscribeActorEvents(ctx context.Context, filter *types.SubActorEventFilter) (<-chan *types.ActorEvent, error)
}
var _ TargetAPI = *new(api.FullNode) // gateway depends on latest

View File

@ -437,6 +437,20 @@ func (gw *Node) StateWaitMsg(ctx context.Context, msg cid.Cid, confidence uint64
return gw.target.StateWaitMsg(ctx, msg, confidence, limit, allowReplaced)
}
func (gw *Node) GetActorEvents(ctx context.Context, filter *types.ActorEventFilter) ([]*types.ActorEvent, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return nil, err
}
return gw.target.GetActorEvents(ctx, filter)
}
func (gw *Node) SubscribeActorEvents(ctx context.Context, filter *types.SubActorEventFilter) (<-chan *types.ActorEvent, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return nil, err
}
return gw.target.SubscribeActorEvents(ctx, filter)
}
func (gw *Node) StateReadState(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*api.ActorState, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return nil, err

View File

@ -0,0 +1,94 @@
package itests
import (
"context"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/itests/kit"
)
func TestGetActorEvents(t *testing.T) {
t.Skip("skipping for now")
//require := require.New(t)
kit.QuietAllLogsExcept("events", "messagepool")
blockTime := 100 * time.Millisecond
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC())
ens.InterconnectAll().BeginMining(blockTime)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
// Set up the test fixture with a standard list of invocations
contract1, contract2, invocations := prepareEventMatrixInvocations(ctx, t, client)
fmt.Printf("contract1:%s; contract2:%s\n", contract1, contract2)
cf1, err := contract1.ToFilecoinAddress()
if err != nil {
panic(err)
}
cf2, err := contract2.ToFilecoinAddress()
if err != nil {
panic(err)
}
fmt.Printf("contract1 f4 is:%s; contract2 f4 is:%s\n", cf1.String(), cf2.String())
testCases := getCombinationFilterTestCases(contract1, contract2, "0x0")
messages := invokeAndWaitUntilAllOnChain(t, client, invocations)
// f410fiy2dwcbbvc5c6xwwrhlwgi2dby4rzgamxllpgva
for _, tc := range testCases {
tc := tc // appease the lint despot
t.Run(tc.name, func(t *testing.T) {
res, err := client.EthGetLogs(ctx, tc.spec)
require.NoError(t, err)
/*ch, _ := client.SubscribeActorEvents(ctx, &types.SubActorEventFilter{
Prefill: true,
ActorEventFilter: types.ActorEventFilter{
MinEpoch: 0,
MaxEpoch: 1000,
},
})
for i := range ch {
fmt.Println("Hello Chan", i.Entries[0].Key, i.Entries[0].Codec, i.EmitterAddr.String())
}*/
res2, _ := client.GetActorEvents(ctx, &types.ActorEventFilter{
MinEpoch: 0,
MaxEpoch: -1,
Addresses: []address.Address{cf2},
//EthAddresses: []ethtypes.EthAddress{
// contract1,
//},
})
for _, res := range res2 {
res := res
fmt.Println("Emitter Address is", res.EmitterAddr.String())
for _, entry := range res.Entries {
fmt.Println("Hello", entry.Key, entry.Codec, string(entry.Value))
}
}
fmt.Println("Hello", res2[0].Entries[0].Key, res2[0].Entries[0].Codec, res2[0].EmitterAddr.String())
elogs, err := parseEthLogsFromFilterResult(res)
require.NoError(t, err)
AssertEthLogs(t, elogs, tc.expected, messages)
})
}
}

View File

@ -63,6 +63,7 @@ var DefaultNodeOpts = nodeOpts{
// test defaults
cfg.Fevm.EnableEthRPC = true
cfg.Fevm.EnableActorEventsAPI = true
return nil
},
},

View File

@ -18,6 +18,7 @@ import (
"github.com/filecoin-project/lotus/chain/consensus"
"github.com/filecoin-project/lotus/chain/consensus/filcns"
"github.com/filecoin-project/lotus/chain/events"
"github.com/filecoin-project/lotus/chain/events/filter"
"github.com/filecoin-project/lotus/chain/exchange"
"github.com/filecoin-project/lotus/chain/gen/slashfilter"
"github.com/filecoin-project/lotus/chain/index"
@ -155,6 +156,7 @@ var ChainNode = Options(
Override(new(stmgr.StateManagerAPI), rpcstmgr.NewRPCStateManager),
Override(new(full.EthModuleAPI), From(new(api.Gateway))),
Override(new(full.EthEventAPI), From(new(api.Gateway))),
Override(new(full.ActorEventAPI), From(new(api.Gateway))),
),
// Full node API / service startup
@ -265,6 +267,8 @@ func ConfigFullNode(c interface{}) Option {
// Actor event filtering support
Override(new(events.EventAPI), From(new(modules.EventAPI))),
Override(new(*filter.EventFilterManager), modules.EventFilterManager(cfg.Fevm)),
// in lite-mode Eth api is provided by gateway
ApplyIf(isFullNode,
If(cfg.Fevm.EnableEthRPC,
@ -277,6 +281,15 @@ func ConfigFullNode(c interface{}) Option {
),
),
ApplyIf(isFullNode,
If(cfg.Fevm.EnableActorEventsAPI,
Override(new(full.ActorEventAPI), modules.ActorEventAPI(cfg.Fevm)),
),
If(!cfg.Fevm.EnableActorEventsAPI,
Override(new(full.ActorEventAPI), &full.ActorEventDummy{}),
),
),
// enable message index for full node when configured by the user, otherwise use dummy.
If(cfg.Index.EnableMsgIndex, Override(new(index.MsgIndex), modules.MsgIndex)),
If(!cfg.Index.EnableMsgIndex, Override(new(index.MsgIndex), modules.DummyMsgIndex)),

View File

@ -109,6 +109,7 @@ func DefaultFullNode() *FullNode {
Cluster: *DefaultUserRaftConfig(),
Fevm: FevmConfig{
EnableEthRPC: false,
EnableActorEventsAPI: false,
EthTxHashMappingLifetimeDays: 0,
Events: Events{
DisableRealTimeFilterAPI: false,

View File

@ -455,6 +455,12 @@ rewards. This address should have adequate funds to cover gas fees.`,
Comment: `EnableEthRPC enables eth_ rpc, and enables storing a mapping of eth transaction hashes to filecoin message Cids.
This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, but they can be disabled by config options above.`,
},
{
Name: "EnableActorEventsAPI",
Type: "bool",
Comment: ``,
},
{
Name: "EthTxHashMappingLifetimeDays",
Type: "int",

View File

@ -786,6 +786,8 @@ type FevmConfig struct {
// This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, but they can be disabled by config options above.
EnableEthRPC bool
EnableActorEventsAPI bool
// EthTxHashMappingLifetimeDays the transaction hash lookup database will delete mappings that have been stored for more than x days
// Set to 0 to keep all mappings
EthTxHashMappingLifetimeDays int

View File

@ -36,6 +36,7 @@ type FullNodeAPI struct {
full.SyncAPI
full.RaftAPI
full.EthAPI
full.ActorEventsAPI
DS dtypes.MetadataDS
NetworkName dtypes.NetworkName

View File

@ -0,0 +1,171 @@
package full
import (
"context"
"fmt"
"github.com/ipfs/go-cid"
"go.uber.org/fx"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/events/filter"
"github.com/filecoin-project/lotus/chain/types"
)
type ActorEventAPI interface {
GetActorEvents(ctx context.Context, filter *types.ActorEventFilter) ([]*types.ActorEvent, error)
SubscribeActorEvents(ctx context.Context, filter *types.SubActorEventFilter) (<-chan *types.ActorEvent, error)
}
var (
_ ActorEventAPI = *new(api.FullNode)
_ ActorEventAPI = *new(api.Gateway)
)
type ActorEvent struct {
EventFilterManager *filter.EventFilterManager
MaxFilterHeightRange abi.ChainEpoch
}
var _ ActorEventAPI = (*ActorEvent)(nil)
type ActorEventsAPI struct {
fx.In
ActorEventAPI
}
func (a *ActorEvent) GetActorEvents(ctx context.Context, filter *types.ActorEventFilter) ([]*types.ActorEvent, error) {
if a.EventFilterManager == nil {
return nil, api.ErrNotSupported
}
// Create a temporary filter
f, err := a.EventFilterManager.Install(ctx, filter.MinEpoch, filter.MaxEpoch, cid.Undef, filter.Addresses, filter.Fields, false)
if err != nil {
return nil, err
}
evs, err := getCollected(ctx, f)
_ = a.EventFilterManager.Remove(ctx, f.ID())
return evs, err
}
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)
if err != nil {
return nil, err
}
out := make(chan *types.ActorEvent, 25)
go func() {
defer func() {
// Tell the caller we're done
close(out)
// Unsubscribe.
fm.ClearSubChannel()
_ = a.EventFilterManager.Remove(ctx, fm.ID())
}()
if f.Prefill {
evs, err := getCollected(ctx, fm)
if err != nil {
log.Errorf("failed to get collected events: %w", err)
return
}
for _, ev := range evs {
ev := ev
select {
case out <- ev:
case <-ctx.Done():
return
default:
log.Errorf("closing event subscription due to slow reader")
return
}
}
}
in := make(chan interface{}, 256)
fm.SetSubChannel(in)
for {
select {
case val, ok := <-in:
if !ok {
// Shutting down.
return
}
ce, ok := val.(*filter.CollectedEvent)
if !ok {
log.Errorf("got unexpected value from event filter: %T", val)
return
}
c, err := ce.TipSetKey.Cid()
if err != nil {
log.Errorf("failed to get tipset cid: %w", err)
return
}
ev := &types.ActorEvent{
Entries: ce.Entries,
EmitterAddr: ce.EmitterAddr,
Reverted: ce.Reverted,
Height: ce.Height,
TipSetKey: c,
MsgCid: ce.MsgCid,
}
select {
case out <- ev:
default:
log.Errorf("closing event subscription due to slow reader")
return
}
if len(out) > 5 {
log.Warnf("event subscription is slow, has %d buffered entries", len(out))
}
case <-ctx.Done():
return
}
}
}()
return out, nil
}
func getCollected(ctx context.Context, f *filter.EventFilter) ([]*types.ActorEvent, error) {
ces := f.TakeCollectedEvents(ctx)
var out []*types.ActorEvent
for _, e := range ces {
e := e
c, err := e.TipSetKey.Cid()
if err != nil {
return nil, fmt.Errorf("failed to get tipset cid: %w", err)
}
ev := &types.ActorEvent{
Entries: e.Entries,
EmitterAddr: e.EmitterAddr,
Reverted: e.Reverted,
Height: e.Height,
TipSetKey: c,
MsgCid: e.MsgCid,
}
out = append(out, ev)
}
return out, nil
}

View File

@ -11,6 +11,7 @@ import (
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/types/ethtypes"
)
@ -188,3 +189,17 @@ func (e *EthModuleDummy) EthTraceReplayBlockTransactions(ctx context.Context, bl
var _ EthModuleAPI = &EthModuleDummy{}
var _ EthEventAPI = &EthModuleDummy{}
var ErrActorEventModuleDisabled = errors.New("module disabled, enable with Fevm.EnableActorEventsAPI")
type ActorEventDummy struct{}
func (a *ActorEventDummy) GetActorEvents(ctx context.Context, filter *types.ActorEventFilter) ([]*types.ActorEvent, error) {
return nil, ErrActorEventModuleDisabled
}
func (a *ActorEventDummy) SubscribeActorEvents(ctx context.Context, filter *types.SubActorEventFilter) (<-chan *types.ActorEvent, error) {
return nil, ErrActorEventModuleDisabled
}
var _ ActorEventAPI = &ActorEventDummy{}

View File

@ -12,6 +12,7 @@ import (
"time"
"github.com/ipfs/go-cid"
"github.com/multiformats/go-multicodec"
cbg "github.com/whyrusleeping/cbor-gen"
"go.uber.org/fx"
"golang.org/x/xerrors"
@ -1353,7 +1354,20 @@ func (e *EthEvent) installEthFilterSpec(ctx context.Context, filterSpec *ethtype
return nil, err
}
return e.EventFilterManager.Install(ctx, minHeight, maxHeight, tipsetCid, addresses, keys)
return e.EventFilterManager.Install(ctx, minHeight, maxHeight, tipsetCid, addresses, keysToKeysWithCodec(keys), true)
}
func keysToKeysWithCodec(keys map[string][][]byte) map[string][]types.ActorEventBlock {
keysWithCodec := make(map[string][]types.ActorEventBlock)
for k, v := range keys {
for _, vv := range v {
keysWithCodec[k] = append(keysWithCodec[k], types.ActorEventBlock{
Codec: uint64(multicodec.Raw),
Value: vv,
})
}
}
return keysWithCodec
}
func (e *EthEvent) EthNewFilter(ctx context.Context, filterSpec *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) {
@ -1527,7 +1541,7 @@ func (e *EthEvent) EthSubscribe(ctx context.Context, p jsonrpc.RawParams) (ethty
}
}
f, err := e.EventFilterManager.Install(ctx, -1, -1, cid.Undef, addresses, keys)
f, err := e.EventFilterManager.Install(ctx, -1, -1, cid.Undef, addresses, keysToKeysWithCodec(keys), true)
if err != nil {
// clean up any previous filters added and stop the sub
_, _ = e.EthUnsubscribe(ctx, sub.id)

View File

@ -33,8 +33,8 @@ type EventAPI struct {
var _ events.EventAPI = &EventAPI{}
func EthEventAPI(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRepo, fx.Lifecycle, *store.ChainStore, *stmgr.StateManager, EventAPI, *messagepool.MessagePool, full.StateAPI, full.ChainAPI) (*full.EthEvent, error) {
return func(mctx helpers.MetricsCtx, r repo.LockedRepo, lc fx.Lifecycle, cs *store.ChainStore, sm *stmgr.StateManager, evapi EventAPI, mp *messagepool.MessagePool, stateapi full.StateAPI, chainapi full.ChainAPI) (*full.EthEvent, error) {
func EthEventAPI(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRepo, fx.Lifecycle, *filter.EventFilterManager, *store.ChainStore, *stmgr.StateManager, EventAPI, *messagepool.MessagePool, full.StateAPI, full.ChainAPI) (*full.EthEvent, error) {
return func(mctx helpers.MetricsCtx, r repo.LockedRepo, lc fx.Lifecycle, fm *filter.EventFilterManager, cs *store.ChainStore, sm *stmgr.StateManager, evapi EventAPI, mp *messagepool.MessagePool, stateapi full.StateAPI, chainapi full.ChainAPI) (*full.EthEvent, error) {
ctx := helpers.LifecycleCtx(mctx, lc)
ee := &full.EthEvent{
@ -64,6 +64,41 @@ func EthEventAPI(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRepo
},
})
ee.TipSetFilterManager = &filter.TipSetFilterManager{
MaxFilterResults: cfg.Events.MaxFilterResults,
}
ee.MemPoolFilterManager = &filter.MemPoolFilterManager{
MaxFilterResults: cfg.Events.MaxFilterResults,
}
ee.EventFilterManager = fm
lc.Append(fx.Hook{
OnStart: func(context.Context) error {
ev, err := events.NewEvents(ctx, &evapi)
if err != nil {
return err
}
// ignore returned tipsets
_ = ev.Observe(ee.TipSetFilterManager)
ch, err := mp.Updates(ctx)
if err != nil {
return err
}
go ee.MemPoolFilterManager.WaitForMpoolUpdates(ctx, ch)
return nil
},
})
return ee, nil
}
}
func EventFilterManager(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRepo, fx.Lifecycle, *store.ChainStore, *stmgr.StateManager, EventAPI, full.ChainAPI) (*filter.EventFilterManager, error) {
return func(mctx helpers.MetricsCtx, r repo.LockedRepo, lc fx.Lifecycle, cs *store.ChainStore, sm *stmgr.StateManager, evapi EventAPI, chainapi full.ChainAPI) (*filter.EventFilterManager, error) {
ctx := helpers.LifecycleCtx(mctx, lc)
// Enable indexing of actor events
var eventIndex *filter.EventIndex
if !cfg.Events.DisableHistoricFilterAPI {
@ -91,7 +126,7 @@ func EthEventAPI(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRepo
})
}
ee.EventFilterManager = &filter.EventFilterManager{
fm := &filter.EventFilterManager{
ChainStore: cs,
EventIndex: eventIndex, // will be nil unless EnableHistoricFilterAPI is true
AddressResolver: func(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool) {
@ -111,6 +146,7 @@ func EthEventAPI(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRepo
return address.Undef, false
}
// we have an f4 address, make sure it's assigned by the EAM
// What happens when we introduce events for built-in Actor events here ?
if namespace, _, err := varint.FromUvarint(actor.Address.Payload()); err != nil || namespace != builtintypes.EthereumAddressManagerActorID {
return address.Undef, false
}
@ -119,12 +155,6 @@ func EthEventAPI(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRepo
MaxFilterResults: cfg.Events.MaxFilterResults,
}
ee.TipSetFilterManager = &filter.TipSetFilterManager{
MaxFilterResults: cfg.Events.MaxFilterResults,
}
ee.MemPoolFilterManager = &filter.MemPoolFilterManager{
MaxFilterResults: cfg.Events.MaxFilterResults,
}
lc.Append(fx.Hook{
OnStart: func(context.Context) error {
@ -132,20 +162,28 @@ func EthEventAPI(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRepo
if err != nil {
return err
}
// ignore returned tipsets
_ = ev.Observe(ee.EventFilterManager)
_ = ev.Observe(ee.TipSetFilterManager)
ch, err := mp.Updates(ctx)
if err != nil {
return err
}
go ee.MemPoolFilterManager.WaitForMpoolUpdates(ctx, ch)
_ = ev.Observe(fm)
return nil
},
})
return fm, nil
}
}
func ActorEventAPI(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRepo, fx.Lifecycle, *filter.EventFilterManager, *store.ChainStore, *stmgr.StateManager, EventAPI, *messagepool.MessagePool, full.StateAPI, full.ChainAPI) (*full.ActorEvent, error) {
return func(mctx helpers.MetricsCtx, r repo.LockedRepo, lc fx.Lifecycle, fm *filter.EventFilterManager, cs *store.ChainStore, sm *stmgr.StateManager, evapi EventAPI, mp *messagepool.MessagePool, stateapi full.StateAPI, chainapi full.ChainAPI) (*full.ActorEvent, error) {
ee := &full.ActorEvent{
MaxFilterHeightRange: abi.ChainEpoch(cfg.Events.MaxFilterHeightRange),
}
if !cfg.EnableActorEventsAPI {
// all Actor events functionality is disabled
return ee, nil
}
ee.EventFilterManager = fm
return ee, nil
}
}