Skip to content

Commit

Permalink
Merge pull request #4982 from onflow/janez/change-event-emission-code
Browse files Browse the repository at this point in the history
[FVM] Refactor event emission code
  • Loading branch information
janezpodhostnik authored Nov 17, 2023
2 parents 1628d29 + c82af09 commit 66cb9b6
Show file tree
Hide file tree
Showing 9 changed files with 58 additions and 69 deletions.
2 changes: 1 addition & 1 deletion fvm/environment/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type Environment interface {

// EventEmitter
Events() flow.EventsList
EmitFlowEvent(etype flow.EventType, payload []byte) error
EmitRawEvent(etype flow.EventType, payload []byte) error
ServiceEvents() flow.EventsList
ConvertedServiceEvents() flow.ServiceEventList

Expand Down
74 changes: 28 additions & 46 deletions fvm/environment/event_emitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,14 @@ func DefaultEventEmitterParams() EventEmitterParams {
// Note that scripts do not emit events, but must expose the API in compliance
// with the runtime environment interface.
type EventEmitter interface {
// Cadence's runtime API. Note that the script variant will return
// OperationNotSupportedError.
// EmitEvent satisfies Cadence's runtime API.
// This will encode the cadence event and call EmitRawEvent.
//
// Note that the script variant will return OperationNotSupportedError.
EmitEvent(event cadence.Event) error

// EmitFlowEvent is used to emit events that are not generated by
// Cadence runtime.
// Warning: current implementation of EmitFlowEvent does not support handling service events
// that functionality should be added if needed in the future
// TODO: we could merge this one with the EmitEvent endpoint
EmitFlowEvent(etype flow.EventType, payload []byte) error
// EmitRawEvent is used to emit events that are not Cadence events.
EmitRawEvent(eventType flow.EventType, payload []byte) error

Events() flow.EventsList
ServiceEvents() flow.EventsList
Expand Down Expand Up @@ -79,12 +77,12 @@ func (emitter ParseRestrictedEventEmitter) EmitEvent(event cadence.Event) error
event)
}

func (emitter ParseRestrictedEventEmitter) EmitFlowEvent(etype flow.EventType, payload []byte) error {
func (emitter ParseRestrictedEventEmitter) EmitRawEvent(eventType flow.EventType, payload []byte) error {
return parseRestrict2Arg(
emitter.txnState,
trace.FVMEnvEmitEvent,
emitter.impl.EmitFlowEvent,
etype,
emitter.impl.EmitRawEvent,
eventType,
payload,
)
}
Expand All @@ -111,11 +109,11 @@ var _ EventEmitter = NoEventEmitter{}
// where emitting an event does nothing.
type NoEventEmitter struct{}

func (NoEventEmitter) EmitEvent(event cadence.Event) error {
func (NoEventEmitter) EmitEvent(cadence.Event) error {
return nil
}

func (NoEventEmitter) EmitFlowEvent(etype flow.EventType, payload []byte) error {
func (NoEventEmitter) EmitRawEvent(flow.EventType, []byte) error {
return nil
}

Expand Down Expand Up @@ -180,23 +178,29 @@ func (emitter *eventEmitter) EventCollection() *EventCollection {
}

func (emitter *eventEmitter) EmitEvent(event cadence.Event) error {
defer emitter.tracer.StartExtensiveTracingChildSpan(
trace.FVMEnvEmitEvent).End()

err := emitter.meter.MeterComputation(ComputationKindEmitEvent, 1)
defer emitter.tracer.StartExtensiveTracingChildSpan(trace.FVMEnvEncodeEvent).End()
err := emitter.meter.MeterComputation(ComputationKindEncodeEvent, 1)
if err != nil {
return fmt.Errorf("emit event failed: %w", err)
return fmt.Errorf("emit event, event encoding failed: %w", err)
}

payload, err := emitter.EventEncoder.Encode(event)
if err != nil {
return errors.NewEventEncodingError(err)
}

payloadSize := uint64(len(payload))
return emitter.EmitRawEvent(flow.EventType(event.EventType.ID()), payload)
}

func (emitter *eventEmitter) EmitRawEvent(eventType flow.EventType, payload []byte) error {
defer emitter.tracer.StartExtensiveTracingChildSpan(trace.FVMEnvEmitEvent).End()
payloadSize := len(payload)
err := emitter.meter.MeterComputation(ComputationKindEmitEvent, uint(payloadSize))
if err != nil {
return fmt.Errorf("emit event failed: %w", err)
}
flowEvent := flow.Event{
Type: flow.EventType(event.EventType.ID()),
Type: eventType,
TransactionID: emitter.txID,
TransactionIndex: emitter.txIndex,
EventIndex: emitter.eventCollection.TotalEventCounter(),
Expand All @@ -207,15 +211,15 @@ func (emitter *eventEmitter) EmitEvent(event cadence.Event) error {
isServiceAccount := emitter.payer == emitter.chain.ServiceAddress()

if emitter.ServiceEventCollectionEnabled {
ok, err := IsServiceEvent(event, emitter.chain.ChainID())
ok, err := IsServiceEvent(eventType, emitter.chain.ChainID())
if err != nil {
return fmt.Errorf("unable to check service event: %w", err)
}
if ok {
eventEmitError := emitter.eventCollection.AppendServiceEvent(
emitter.chain,
flowEvent,
payloadSize)
uint64(payloadSize))

// skip limit if payer is service account
// TODO skip only limit-related errors
Expand All @@ -227,35 +231,14 @@ func (emitter *eventEmitter) EmitEvent(event cadence.Event) error {
// as well.
}

eventEmitError := emitter.eventCollection.AppendEvent(flowEvent, payloadSize)
eventEmitError := emitter.eventCollection.AppendEvent(flowEvent, uint64(payloadSize))
// skip limit if payer is service account
if !isServiceAccount {
return eventEmitError
}

return nil
}

func (emitter *eventEmitter) EmitFlowEvent(etype flow.EventType, payload []byte) error {
defer emitter.tracer.StartExtensiveTracingChildSpan(
trace.FVMEnvEmitEvent).End()

err := emitter.meter.MeterComputation(ComputationKindEmitEvent, 1)
if err != nil {
return fmt.Errorf("emit flow event failed: %w", err)
}

eventSize := uint64(len(etype) + len(payload))

flowEvent := flow.Event{
Type: etype,
TransactionID: emitter.txID,
TransactionIndex: emitter.txIndex,
EventIndex: emitter.eventCollection.TotalEventCounter(),
Payload: payload,
}

return emitter.eventCollection.AppendEvent(flowEvent, eventSize)
}

func (emitter *eventEmitter) Events() flow.EventsList {
Expand Down Expand Up @@ -334,7 +317,7 @@ func (collection *EventCollection) TotalEventCounter() uint32 {

// IsServiceEvent determines whether or not an emitted Cadence event is
// considered a service event for the given chain.
func IsServiceEvent(event cadence.Event, chain flow.ChainID) (bool, error) {
func IsServiceEvent(eventType flow.EventType, chain flow.ChainID) (bool, error) {

// retrieve the service event information for this chain
events, err := systemcontracts.ServiceEventsForChain(chain)
Expand All @@ -345,7 +328,6 @@ func IsServiceEvent(event cadence.Event, chain flow.ChainID) (bool, error) {
err)
}

eventType := flow.EventType(event.EventType.ID())
for _, serviceEvent := range events.All() {
if serviceEvent.EventType() == eventType {
return true, nil
Expand Down
24 changes: 15 additions & 9 deletions fvm/environment/event_emitter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,44 +27,50 @@ func Test_IsServiceEvent(t *testing.T) {

t.Run("correct", func(t *testing.T) {
for _, event := range events.All() {
isServiceEvent, err := environment.IsServiceEvent(cadence.Event{
event := cadence.Event{
EventType: &cadence.EventType{
Location: common.AddressLocation{
Address: common.MustBytesToAddress(
event.Address.Bytes()),
},
QualifiedIdentifier: event.QualifiedIdentifier(),
},
}, chain)
}

isServiceEvent, err := environment.IsServiceEvent(flow.EventType(event.Type().ID()), chain)
require.NoError(t, err)
assert.True(t, isServiceEvent)
}
})

t.Run("wrong chain", func(t *testing.T) {
isServiceEvent, err := environment.IsServiceEvent(cadence.Event{
event := cadence.Event{
EventType: &cadence.EventType{
Location: common.AddressLocation{
Address: common.MustBytesToAddress(
flow.Testnet.Chain().ServiceAddress().Bytes()),
},
QualifiedIdentifier: events.EpochCommit.QualifiedIdentifier(),
},
}, chain)
}

isServiceEvent, err := environment.IsServiceEvent(flow.EventType(event.Type().ID()), chain)
require.NoError(t, err)
assert.False(t, isServiceEvent)
})

t.Run("wrong type", func(t *testing.T) {
isServiceEvent, err := environment.IsServiceEvent(cadence.Event{
event := cadence.Event{
EventType: &cadence.EventType{
Location: common.AddressLocation{
Address: common.MustBytesToAddress(
chain.Chain().ServiceAddress().Bytes()),
},
QualifiedIdentifier: "SomeContract.SomeEvent",
},
}, chain)
}

isServiceEvent, err := environment.IsServiceEvent(flow.EventType(event.Type().ID()), chain)
require.NoError(t, err)
assert.False(t, isServiceEvent)
})
Expand Down Expand Up @@ -150,19 +156,19 @@ func Test_EmitEvent_Limit(t *testing.T) {
require.Error(t, err)
})

t.Run("emit flow event - exceeding limit", func(t *testing.T) {
t.Run("emit raw event - exceeding limit", func(t *testing.T) {
flowEvent := flow.Event{
Type: "sometype",
Payload: []byte{1, 2, 3, 4, 5},
}

eventSize := uint64(len(flowEvent.Type) + len(flowEvent.Payload))
eventSize := uint64(len(flowEvent.Payload))
eventEmitter := createTestEventEmitterWithLimit(
flow.Emulator,
flow.Emulator.Chain().NewAddressGenerator().CurrentAddress(),
eventSize-1)

err := eventEmitter.EmitFlowEvent(flowEvent.Type, flowEvent.Payload)
err := eventEmitter.EmitRawEvent(flowEvent.Type, flowEvent.Payload)
require.Error(t, err)
})
}
Expand Down
1 change: 1 addition & 0 deletions fvm/environment/meter.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const (
ComputationKindEVMGasUsage = 2037
ComputationKindRLPEncoding = 2038
ComputationKindRLPDecoding = 2039
ComputationKindEncodeEvent = 2040
)

type Meter interface {
Expand Down
4 changes: 2 additions & 2 deletions fvm/environment/mock/environment.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions fvm/environment/mock/event_emitter.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions fvm/evm/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,7 @@ func (h *ContractHandler) emitEvent(event *types.Event) {
// TODO add extra metering for rlp encoding
encoded, err := event.Payload.Encode()
handleError(err)

err = h.backend.EmitFlowEvent(event.Etype, encoded)
err = h.backend.EmitRawEvent(event.Etype, encoded)
handleError(err)
}

Expand Down
10 changes: 5 additions & 5 deletions fvm/evm/testutils/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func GetSimpleValueStore() *TestValueStore {
func getSimpleEventEmitter() *testEventEmitter {
events := make(flow.EventsList, 0)
return &testEventEmitter{
emitFlowEvent: func(etype flow.EventType, payload []byte) error {
emitRawEvent: func(etype flow.EventType, payload []byte) error {
events = append(events, flow.Event{Type: etype, Payload: payload})
return nil
},
Expand Down Expand Up @@ -238,7 +238,7 @@ func (m *testMeter) TotalEmittedEventBytes() uint64 {

type testEventEmitter struct {
emitEvent func(event cadence.Event) error
emitFlowEvent func(etype flow.EventType, payload []byte) error
emitRawEvent func(etype flow.EventType, payload []byte) error
events func() flow.EventsList
serviceEvents func() flow.EventsList
convertedServiceEvents func() flow.ServiceEventList
Expand All @@ -254,11 +254,11 @@ func (vs *testEventEmitter) EmitEvent(event cadence.Event) error {
return vs.emitEvent(event)
}

func (vs *testEventEmitter) EmitFlowEvent(etype flow.EventType, payload []byte) error {
if vs.emitFlowEvent == nil {
func (vs *testEventEmitter) EmitRawEvent(etype flow.EventType, payload []byte) error {
if vs.emitRawEvent == nil {
panic("method not set")
}
return vs.emitFlowEvent(etype, payload)
return vs.emitRawEvent(etype, payload)
}

func (vs *testEventEmitter) Events() flow.EventsList {
Expand Down
1 change: 1 addition & 0 deletions module/trace/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ const (
FVMEnvGetOrLoadProgram SpanName = "fvm.env.getOrLoadCachedProgram"
FVMEnvProgramLog SpanName = "fvm.env.programLog"
FVMEnvEmitEvent SpanName = "fvm.env.emitEvent"
FVMEnvEncodeEvent SpanName = "fvm.env.encodeEvent"
FVMEnvGenerateUUID SpanName = "fvm.env.generateUUID"
FVMEnvGenerateAccountLocalID SpanName = "fvm.env.generateAccountLocalID"
FVMEnvDecodeArgument SpanName = "fvm.env.decodeArgument"
Expand Down

0 comments on commit 66cb9b6

Please sign in to comment.