``` --- fip: <to be assigned> title: Actor events author: @raulk, @stebalien discussions-to: <URL> status: Draft type: Technical Core category: Core created: <date created on, in ISO 8601 (yyyy-mm-dd) format> spec-sections: - <section-id> - <section-id> requires (*optional): <FIP number(s)> replaces (*optional): <FIP number(s)> --- ``` ## Simple Summary This FIP adds the ability for actors to emit events during execution. Events are discrete self-contained payloads signalling that some externally-relevant action or transition has ocurred during the execution of an actor. Events enhance observability by external agents, and may eventually become the source of internal triggers. ## Abstract This FIP introduces a minimal design for actor events, including their schema, a new syscall, a change in the `MessageReceipt` structure, and mechanics to commit these execution side effects on the chain. By minimal we mean that the protocol stays largely unopinionated and unprescriptive around eventual indexing, traversal, and proofs (e.g. inclusion proofs for light clients), and other future features build on events. ## Change Motivation There are two main motivations for introducing actor events at this time. 1. They have been long missing from the protocol, forcing Filecoin network monitoring or accounting tools to fork and instrument built-in actor code and/or observe state changes by using high-level JSON-RPC methods like `StateReplay`. Once this feature is introduced, built-in actors could emit events when power changes, sectors are onboarded, deals are made, sectors are terminated, penalties are incurred, etc. 2. The upcoming introduction of the Filecoin EVM runtime necessitates an event mechanism to handle the LOG{0..4} opcodes, and the corresponding Ethereum JSON-RPC methods. ## Specification ### New chain types We introduce a DAG-CBOR encoded `Event` type to represent actor events. It is inspired by structured logging concepts (key-value entries). It includes an `indexed` bitmap to indicate which entries are to be indexed. At this stage, this information is used merely as a client hint; in the future it may be built upon to introduce protocol-level features like populating (adaptable) bloom filters, block-level indices, on-chain event subscriptions, and more. ```rust /// Represents an event emitted throughout actor execution. struct Event { /// Key values making up this event. entries: Vec<Entry>, /// A hint specifying to clients which indices of `entries` should be indexed, /// encoded as a plain bitmap of bit length equal to the length of the entries list. indexed: [u8], } struct Entry { /// The key of this event. key: String, /// Any DAG-CBOR encodeable type. value: any, } ``` Choosing a _list of tuples_ instead of a map representation is deliberate, as it enables repeatable keys, and, thus, array-like entries, e.g. signers in a multisig transaction. We expressly discarded solutions involving an opaque event payload accompanied by some form of a type discriminator, as that would necessitate an IDL upfront to interpret the payload. We believe that this unwrapped data model enables straightforward introspection and comprehension by chain explorers, developer tools, and monitoring tools. Over time, we expect the community to standardise on a set of keys through FRC proposals. > TODO: > - Define maximum lengths for entries, key size, value size? > - Do we want to introduce standard keys now, e.g. type? **Event examples** The following examples demonstrate how to use these structures in practice. _Fungible token transfer_ ```rust Event { entries: [ ("type", "transfer"), ("sender", <id address>), ("receiver", <id address>), ("amount", <token amount>), ], indexed: 1110, // bitmap hinting to index type, sender, receiver } ``` _Non-fungible token transfer_ ```rust Event { entries: [ ("type", "transfer"), ("sender", <id address>), ("receiver", <id address>), ("token_id", <identifier>), ], indexed: 1111, // bitmap hinting to index all fields } ``` _Multisig approval_ ```rust Event { entries: [ ("type", "approval"), ("signer", <id address>), ("signer", <id address>), ("signer", <id address>), ("callee", <id address>), ("amount", <token amount>), ("msg_cid", <message cid>), ], indexed: 1111000, // bitmap hinting to index the type and all signers } ``` ### Chain commitment The existing `MessageReceipt` chain data structure is augmented with a new `events` field: ```rust struct MessageReceipt { exit_code: ExitCode, return_data: RawBytes, gas_used: i64, events: Vec<Cid>, // new field: Vec<Event> } ``` During message execution, DAG-CBOR encoded Events are accumulated inside the FVM, in the same order they were emitted. Upon finishing, the FVM computes the CID of every event using the BLAKE2b-256 multihash and the DAG-CBOR multicodec, and returns the list of CIDs in the `events` field, preserving the order. Events are committed on chain implicitly through the root CID of the `BlockHeader#ParentMessageReceipts` HAMT. ### Client handling Honouring indexing hints is optional but highly encouraged at this stage. Clients may offer new JSON-RPC operations to subscribe to and query events. Clients may wish to track event data in a dedicated store, segregated from the chain store, potentially backed by a different database engine more suited to the expected write, query, and indexing patterns. How event writes are routed to a different store is an implementation detail. However, clients can rely on the timing difference between when event CIDs are returned (at message execution), and when event IPLD blocks are written to the store (at Machine flush time), to identify writes for events and route them to the appropriate store. ### New `vm::emit_event` syscall We define a new syscall under the `vm` namespace to enable actors to emit events. ```rust /// Emits an actor event. The supplied payload must be a DAG-CBOR encoded Event type. /// The FVM will validate the structural, syntatic, and semantic correctness of the /// supplied payload and fail with `IllegalArgument` if the payload was invalid. /// /// Calling this syscall may immediately halt execution with an out of gas error, if /// such condition arises. fn emit_event(evt_off: *const u8, evt_len: u32) -> Result<()>; ``` We expect FVM SDKs to offer utilities, macros, and sugar to ergonomically construct event payloads. **Gas costs** In addition to the [syscall gas cost], emitting an event carries the following dynamic costs: - Per non-indexed entry: <<TODO>> gas per <<TODO>>. - Per indexed entry: <<TODO>> gas per <<TODO>>. > TODO need to make syscall gas dynamic based on param length The following limits apply. Exceeding these limits will result in the event not being emitted, and the call failing with `IllegalArgument`: > - Total event payload size: <<TODO, if any; may be bound by gas, once syscall > gas varies based on param length?>> > - Maximum number of keys: <<TODO, if any; may be bound by gas>> > - Maximum number of indexed keys: <<TODO, if any; may be bound by gas>> ### Mapping to EVM logs > This is defined here for convenience, and will be moved to the upcoming > Filecoin EVM FIP once submitted. TODO. ## Design Rationale <!--The rationale fleshes out the specification by describing what motivated the design and why particular design decisions were made. It should describe alternate designs that were considered and related work, e.g. how the feature is supported in other languages. The rationale may also provide evidence of consensus within the community, and should discuss important objections or concerns raised during discussion.--> TODO. ## Backwards Compatibility <!--All FIPs that introduce backwards incompatibilities must include a section describing these incompatibilities and their severity. The FIP must explain how the author proposes to deal with these incompatibilities. FIP submissions without a sufficient backwards compatibility treatise may be rejected outright.--> TODO. ## Test Cases <!--Test cases for an implementation are mandatory for FIPs that are affecting consensus changes. Other FIPs can choose to include links to test cases if applicable.--> TODO. ## Security Considerations <!--All FIPs must contain a section that discusses the security implications/considerations relevant to the proposed change. Include information that might be important for security discussions, surfaces risks and can be used throughout the life cycle of the proposal. E.g. include security-relevant design decisions, concerns, important discussions, implementation-specific guidance and pitfalls, an outline of threats and risks and how they are being addressed. FIP submissions missing the "Security Considerations" section will be rejected. A FIP cannot proceed to status "Final" without a Security Considerations discussion deemed sufficient by the reviewers.--> TODO. ## Incentive Considerations <!--All FIPs must contain a section that discusses the incentive implications/considerations relative to the proposed change. Include information that might be important for incentive discussion. A discussion on how the proposed change will incentivize reliable and useful storage is required. FIP submissions missing the "Incentive Considerations" section will be rejected. An FIP cannot proceed to status "Final" without a Incentive Considerations discussion deemed sufficient by the reviewers.--> TODO. ## Product Considerations <!--All FIPs must contain a section that discusses the product implications/considerations relative to the proposed change. Include information that might be important for product discussion. A discussion on how the proposed change will enable better storage-related goods and services to be developed on Filecoin. FIP submissions missing the "Product Considerations" section will be rejected. An FIP cannot proceed to status "Final" without a Product Considerations discussion deemed sufficient by the reviewers.--> TODO. ## Implementation <!--The implementations must be completed before any core FIP is given status "Final", but it need not be completed before the FIP is accepted. While there is merit to the approach of reaching consensus on the specification and rationale before writing code, the principle of "rough consensus and running code" is still useful when it comes to resolving many discussions of API details.--> TODO. ## Copyright Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). [`MessageReceipt` type]: https://spec.filecoin.io/#section-systems.filecoin_vm.message.message-semantic-validation [syscall gas cost]: https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0032.md#syscall-gas