-
Notifications
You must be signed in to change notification settings - Fork 175
/
event_emitter.go
312 lines (256 loc) · 8.34 KB
/
event_emitter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
package environment
import (
"fmt"
"github.com/onflow/cadence"
"github.com/onflow/flow-go/fvm/errors"
"github.com/onflow/flow-go/fvm/storage/state"
"github.com/onflow/flow-go/fvm/systemcontracts"
"github.com/onflow/flow-go/fvm/tracing"
"github.com/onflow/flow-go/model/convert"
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/module/trace"
)
const (
DefaultEventCollectionByteSizeLimit = 256_000 // 256KB
)
type EventEmitterParams struct {
ServiceEventCollectionEnabled bool
EventCollectionByteSizeLimit uint64
EventEncoder EventEncoder
}
func DefaultEventEmitterParams() EventEmitterParams {
return EventEmitterParams{
ServiceEventCollectionEnabled: false,
EventCollectionByteSizeLimit: DefaultEventCollectionByteSizeLimit,
EventEncoder: NewCadenceEventEncoder(),
}
}
// EventEmitter collect events, separates out service events, and enforces
// event size limits.
//
// Note that scripts do not emit events, but must expose the API in compliance
// with the runtime environment interface.
type EventEmitter interface {
// EmitEvent satisfies Cadence's runtime API.
// This will encode the cadence event
//
// Note that the script variant will return OperationNotSupportedError.
EmitEvent(event cadence.Event) error
Events() flow.EventsList
ServiceEvents() flow.EventsList
ConvertedServiceEvents() flow.ServiceEventList
Reset()
}
type ParseRestrictedEventEmitter struct {
txnState state.NestedTransactionPreparer
impl EventEmitter
}
func NewParseRestrictedEventEmitter(
txnState state.NestedTransactionPreparer,
impl EventEmitter,
) EventEmitter {
return ParseRestrictedEventEmitter{
txnState: txnState,
impl: impl,
}
}
func (emitter ParseRestrictedEventEmitter) EmitEvent(event cadence.Event) error {
return parseRestrict1Arg(
emitter.txnState,
trace.FVMEnvEmitEvent,
emitter.impl.EmitEvent,
event)
}
func (emitter ParseRestrictedEventEmitter) Events() flow.EventsList {
return emitter.impl.Events()
}
func (emitter ParseRestrictedEventEmitter) ServiceEvents() flow.EventsList {
return emitter.impl.ServiceEvents()
}
func (emitter ParseRestrictedEventEmitter) ConvertedServiceEvents() flow.ServiceEventList {
return emitter.impl.ConvertedServiceEvents()
}
func (emitter ParseRestrictedEventEmitter) Reset() {
emitter.impl.Reset()
}
var _ EventEmitter = NoEventEmitter{}
// NoEventEmitter is usually used in the environment for script execution,
// where emitting an event does nothing.
type NoEventEmitter struct{}
func (NoEventEmitter) EmitEvent(cadence.Event) error {
return nil
}
func (NoEventEmitter) Events() flow.EventsList {
return flow.EventsList{}
}
func (NoEventEmitter) ServiceEvents() flow.EventsList {
return flow.EventsList{}
}
func (NoEventEmitter) ConvertedServiceEvents() flow.ServiceEventList {
return flow.ServiceEventList{}
}
func (NoEventEmitter) Reset() {
}
type eventEmitter struct {
tracer tracing.TracerSpan
meter Meter
chain flow.Chain
txID flow.Identifier
txIndex uint32
payer flow.Address
EventEmitterParams
eventCollection *EventCollection
}
// NewEventEmitter constructs a new eventEmitter
func NewEventEmitter(
tracer tracing.TracerSpan,
meter Meter,
chain flow.Chain,
txInfo TransactionInfoParams,
params EventEmitterParams,
) EventEmitter {
emitter := &eventEmitter{
tracer: tracer,
meter: meter,
chain: chain,
txID: txInfo.TxId,
txIndex: txInfo.TxIndex,
payer: txInfo.TxBody.Payer,
EventEmitterParams: params,
}
emitter.Reset()
return emitter
}
func (emitter *eventEmitter) Reset() {
// TODO: for now we are not resetting meter here because we don't check meter
// metrics after the first metering failure and when limit is disabled.
emitter.eventCollection = NewEventCollection(emitter.meter)
}
func (emitter *eventEmitter) EventCollection() *EventCollection {
return emitter.eventCollection
}
func (emitter *eventEmitter) EmitEvent(event cadence.Event) error {
err := emitter.meter.MeterComputation(ComputationKindEncodeEvent, 1)
if err != nil {
return fmt.Errorf("emit event, event encoding failed: %w", err)
}
payload, err := emitter.EventEncoder.Encode(event)
if err != nil {
return errors.NewEventEncodingError(err)
}
emitter.tracer.StartExtensiveTracingChildSpan(trace.FVMEnvEncodeEvent).End()
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)
}
eventType := flow.EventType(event.EventType.ID())
flowEvent := flow.Event{
Type: eventType,
TransactionID: emitter.txID,
TransactionIndex: emitter.txIndex,
EventIndex: emitter.eventCollection.TotalEventCounter(),
Payload: payload,
}
// TODO: to set limit to maximum when it is service account and get rid of this flag
isServiceAccount := emitter.payer == emitter.chain.ServiceAddress()
if emitter.ServiceEventCollectionEnabled {
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,
uint64(payloadSize))
// skip limit if payer is service account
// TODO skip only limit-related errors
if !isServiceAccount && eventEmitError != nil {
return eventEmitError
}
}
// We don't return and append the service event into event collection
// as well.
}
eventEmitError := emitter.eventCollection.AppendEvent(flowEvent, uint64(payloadSize))
// skip limit if payer is service account
if !isServiceAccount {
return eventEmitError
}
return nil
}
func (emitter *eventEmitter) Events() flow.EventsList {
return emitter.eventCollection.events
}
func (emitter *eventEmitter) ServiceEvents() flow.EventsList {
return emitter.eventCollection.serviceEvents
}
func (emitter *eventEmitter) ConvertedServiceEvents() flow.ServiceEventList {
return emitter.eventCollection.convertedServiceEvents
}
type EventCollection struct {
events flow.EventsList
serviceEvents flow.EventsList
convertedServiceEvents flow.ServiceEventList
eventCounter uint32
meter Meter
}
func NewEventCollection(meter Meter) *EventCollection {
return &EventCollection{
events: make(flow.EventsList, 0, 10),
serviceEvents: make(flow.EventsList, 0, 10),
convertedServiceEvents: make(flow.ServiceEventList, 0, 10),
eventCounter: uint32(0),
meter: meter,
}
}
func (collection *EventCollection) Events() flow.EventsList {
return collection.events
}
func (collection *EventCollection) AppendEvent(event flow.Event, size uint64) error {
collection.events = append(collection.events, event)
collection.eventCounter++
return collection.meter.MeterEmittedEvent(size)
}
func (collection *EventCollection) ServiceEvents() flow.EventsList {
return collection.serviceEvents
}
func (collection *EventCollection) ConvertedServiceEvents() flow.ServiceEventList {
return collection.convertedServiceEvents
}
func (collection *EventCollection) AppendServiceEvent(
chain flow.Chain,
event flow.Event,
size uint64,
) error {
convertedEvent, err := convert.ServiceEvent(chain.ChainID(), event)
if err != nil {
return fmt.Errorf("could not convert service event: %w", err)
}
collection.serviceEvents = append(collection.serviceEvents, event)
collection.convertedServiceEvents = append(
collection.convertedServiceEvents,
*convertedEvent)
collection.eventCounter++
return collection.meter.MeterEmittedEvent(size)
}
func (collection *EventCollection) TotalByteSize() uint64 {
return collection.meter.TotalEmittedEventBytes()
}
func (collection *EventCollection) TotalEventCounter() uint32 {
return collection.eventCounter
}
// IsServiceEvent determines whether or not an emitted Cadence event is
// considered a service event for the given chain.
func IsServiceEvent(eventType flow.EventType, chain flow.ChainID) (bool, error) {
// retrieve the service event information for this chain
events := systemcontracts.ServiceEventsForChain(chain)
for _, serviceEvent := range events.All() {
if serviceEvent.EventType() == eventType {
return true, nil
}
}
return false, nil
}