From c9b7480eee45074d9a40ccc6432d7fce45d7778a Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 5 Aug 2021 12:08:58 +0200 Subject: [PATCH 01/11] Start documenting events system --- EVENTS.md | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 EVENTS.md diff --git a/EVENTS.md b/EVENTS.md new file mode 100644 index 0000000000..1f804a3312 --- /dev/null +++ b/EVENTS.md @@ -0,0 +1,69 @@ +# Event System + +## Usage in the SDK + +Events are an essential part of the Cosmos SDK. They are similar to "logs" in Ethereum and allow a blockchain +app to attach key-value pairs to a transaction that can later be used to search for it or extract some information +in human readable form. Events are not written to the application state, nor do they form part of the AppHash, +but mainly intended for client use (and become an essential API for any reactive app or app that searches for txs). + +In contrast, transactions also have a binary "data" field that is part of the AppHash (provable with light client proofs, +part of consensus). This data is not searchable, but given a tx hash, you can be gauranteed what the data returned is. +This is often empty, but sometimes custom protobuf formats to return essential information from an execution. + +Every message in the SDK may add events to the EventManager and these are then added to the final ABCI result that is returned +to Tendermint. Events are exposed in 3 different ways over the Tendermint API (which is the only way a client can query). +First of all is the `events` field on the transaction result (when you query a transaction by hash, you can see all event emitted +by it). Secondly is the `log` field on the same transaction result. And third is the query interface to search or subscribe for +transactions. + +The `log` field actually has the best data. It contains an array of array of events. The first array is one entry per incoming message. +Transactions in the Cosmos SDK may consist of multiple messages that are executed atomically. Maybe we send tokens, then issue a swap +on a DEX. Each action would return it's own list of Events and in the logs, these are separated. For each message, it maintains a list +of Events, exactly in the order returned by the application. This is JSON encoded and can be parsed by a client. + +In Tendermint 0.35, the `events` field will be one flattened list of events over all messages. Just appending the lists returned +from each message. However, currently (until Tendermint 0.34 used in Cosmos SDK 0.40-0.43), they are flattened on type. Meaning all events +with type `wasm` get merged into one. This makes the API not very useful to understanding more complex events currently. (TODO: link PR fixing this) + +In the search/subscribe interface, you can query for transactions by `AND`ing a number of conditions. Each is expressed like +`.=`. For example, `message.signer=cosmos1234567890`. It will return all transactions that emitted an event matching this filter. + +### Examples + +TODO: show event structure. + +TODO: contrast flattened/unflattened events + +### Standard Events in the SDK + +TODO: what is added by the AnteHandlers + +TODO: what is emitted by bank (transfer event), as this is a very important base event + +## Usage in wasmd + +In `x/wasm` we also use Events system. On one hand, `x/wasm` emits standard event for each message it processes to convey, +for example, "uploaded code, id 6" or "executed code, address wasm1234567890". Furthermore, it allows contracts to +emit custom events based on their execution state, so they can for example say "dex swap, BTC-ATOM, in 0.23, out 512" +which require internal knowledge of the contract and is very useful for custom dApp UIs. + +In addition, when a smart contract executes a SubMsg and processes the reply, it receives not only the `data` response +from the message exection, but also the list of events + +### Standard Events in x/wasm + +TODO: document what we emit in `x/wasm` regardless of the contract return results + +### Emitted Custom Events from a Contract + +TODO: document how we process attributes and events fields in the Response. + +* Base event `wasm` +* Event name mangling (prepend `wasm-`) +* Append trusted attribute _contract_address +* Validation requirements (_ reserved, non-empty, min-length 2 for type) + +## Event Details for wasmd + +Beyond the basic Event system and emitted events, we \ No newline at end of file From f1ac7c0f33662208c11627ca2cc29749aa325aff Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 5 Aug 2021 12:17:39 +0200 Subject: [PATCH 02/11] Fill out rest of the skeleton --- EVENTS.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/EVENTS.md b/EVENTS.md index 1f804a3312..6c9dd218ce 100644 --- a/EVENTS.md +++ b/EVENTS.md @@ -37,7 +37,7 @@ TODO: contrast flattened/unflattened events ### Standard Events in the SDK -TODO: what is added by the AnteHandlers +TODO: what is added by the AnteHandlers (message.signer? auth?) TODO: what is emitted by bank (transfer event), as this is a very important base event @@ -66,4 +66,24 @@ TODO: document how we process attributes and events fields in the Response. ## Event Details for wasmd -Beyond the basic Event system and emitted events, we \ No newline at end of file +Beyond the basic Event system and emitted events, we must handle more advanced cases in `x/wasm` +and thus add some more logic to the event processing. Remember that CosmWasm contracts dispatch other +messages themselves, so far from the flattened event structure, or even a list of list (separated +by message index in the tx), we actually have a tree of messages, each with their own events. And we must +flatten that in a meaningful way to feed it into the event system. + +Furthermore, with the sub-message reply handlers, we end up with eg. "Contract A execute", "Contract B execute", +"Contract A reply". If we return all events by all of these, we may end up with many repeated event types and +a confusing results, especially for Tendermint 0.34 where they are merged together. + +While designing this, we wish to make something that is usable with Tendermint 0.34, but focus on using the +behavior of Tendermint 0.35+ (which is the same behavior as we have internally in the SDK... submessages +all have their own list of Events). Thus, we may emit more events than in previous wasmd versions (as we assume +they will be returned in an ordered list rather than merged). + +### Combining Events from Sub-Messages + +TODO +### Exposing Events to Reply + +TODO From b34398471ddea113236b36dafa5916473a58e8e8 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 6 Aug 2021 00:53:23 +0200 Subject: [PATCH 03/11] Clarified event processing in the sdk --- EVENTS.md | 69 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 15 deletions(-) diff --git a/EVENTS.md b/EVENTS.md index 6c9dd218ce..d9140d758b 100644 --- a/EVENTS.md +++ b/EVENTS.md @@ -8,7 +8,7 @@ in human readable form. Events are not written to the application state, nor do but mainly intended for client use (and become an essential API for any reactive app or app that searches for txs). In contrast, transactions also have a binary "data" field that is part of the AppHash (provable with light client proofs, -part of consensus). This data is not searchable, but given a tx hash, you can be gauranteed what the data returned is. +part of consensus). This data is not searchable, but given a tx hash, you can be guaranteed what the data returned is. This is often empty, but sometimes custom protobuf formats to return essential information from an execution. Every message in the SDK may add events to the EventManager and these are then added to the final ABCI result that is returned @@ -20,20 +20,59 @@ transactions. The `log` field actually has the best data. It contains an array of array of events. The first array is one entry per incoming message. Transactions in the Cosmos SDK may consist of multiple messages that are executed atomically. Maybe we send tokens, then issue a swap on a DEX. Each action would return it's own list of Events and in the logs, these are separated. For each message, it maintains a list -of Events, exactly in the order returned by the application. This is JSON encoded and can be parsed by a client. - -In Tendermint 0.35, the `events` field will be one flattened list of events over all messages. Just appending the lists returned -from each message. However, currently (until Tendermint 0.34 used in Cosmos SDK 0.40-0.43), they are flattened on type. Meaning all events -with type `wasm` get merged into one. This makes the API not very useful to understanding more complex events currently. (TODO: link PR fixing this) - -In the search/subscribe interface, you can query for transactions by `AND`ing a number of conditions. Each is expressed like -`.=`. For example, `message.signer=cosmos1234567890`. It will return all transactions that emitted an event matching this filter. - -### Examples - -TODO: show event structure. - -TODO: contrast flattened/unflattened events +of Events, exactly in the order returned by the application. This is JSON encoded and can be parsed by a client. In fact this is +how [CosmJS](https://github.com/cosmos/cosmjs) gets the events it shows to the client. + +In Tendermint 0.35, the `events` field will be one flattened list of events over all messages. Just as if we concatenated all +the per-message arrays contained in the `log` field. This fix was made as +[part of an event system refactoring](https://github.com/tendermint/tendermint/pull/6634). This refactoring is also giving us +[pluggable event indexing engines](https://github.com/tendermint/tendermint/pull/6411), so we can use eg. PostgreSQL to +store and query the events with more powerful indexes. + +However, currently (until Tendermint 0.34 used in Cosmos SDK 0.40-0.43), all events of one transaction are "flat-mapped" on type. +Meaning all events with type `wasm` get merged into one. This makes the API not very useful to understanding more complex events +currently. There are also a number of limitations of the power of queries in the search interface. + +Given the state of affairs, and given that we seek to provide a stable API for contracts looking into the future, we consider the +`log` output and the Tendermint 0.35 event handling to be the standard that clients should adhere to. And we will expose a similar +API to the smart contracts internally (all events from the message appended, unmerged). +### Data Format + +The event has a string type, and a list of attributes. Each of them being a key value pair. All of these maintain a +consistent order (and avoid dictionaries/hashes). Here is a simple Event in JSON: + +```json +{ + "type": "wasm", + "attributes": [ + {"key": "_contract_address", "value": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6"}, + {"key": "transfered", "value": "777000"} + ] +} +``` + +And here is a sample log output for a transaction with one message, which emitted 2 events: + +```json +[ + [ + { + "type": "message", + "attributes": [ + {"key": "module", "value": "bank"}, + {"key": "action", "value": "send"} + ] + }, + { + "type": "transfer", + "attributes": [ + {"key": "recipient", "value": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6"}, + {"key": "amount", "value": "777000uatom"} + ] + } + ] +] +``` ### Standard Events in the SDK From b71f7262aa05cadf921de8cc97ad223d00cad1bf Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 6 Aug 2021 01:15:26 +0200 Subject: [PATCH 04/11] Import docs on bank / distribution modules --- EVENTS.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/EVENTS.md b/EVENTS.md index d9140d758b..c08f984a7e 100644 --- a/EVENTS.md +++ b/EVENTS.md @@ -74,24 +74,70 @@ And here is a sample log output for a transaction with one message, which emitte ] ``` +### Default Events in the SDK + +There are two places events that are emitted in every transaction regardless of the module which is executed. +[The first is `{"type": "message"}`](https://github.com/cosmos/cosmos-sdk/blob/6888de1d86026c25197c1227dae3d7da4d41a441/baseapp/baseapp.go#L746-L748) +defining an `action` attribute. This is emitted for each top-level (user-signed) message, but the action names have changed between +0.42 and 0.43. + +The other place is in the [signature verification AnteHandler](https://github.com/cosmos/cosmos-sdk/blob/v0.42.9/x/auth/ante/sigverify.go#L103-L120), where it emits information on the account sequences and signatures on the transaction. + +These are all handled in BaseApp and the middleware *before* any module is called and thus not exposed to CosmWasm contracts at all. + ### Standard Events in the SDK -TODO: what is added by the AnteHandlers (message.signer? auth?) +The events that will actually make it to the contracts are the events that are emitted by the other modules / keepers. Let's look +at some good examples of what they look like: + +The most basic one is `bank`, which emits two events on every send, a [custom "transfer" event](https://github.com/cosmos/cosmos-sdk/blob/v0.42.9/x/bank/keeper/send.go#L142-L147) as well as "sender" information under the [standard "message" type](https://github.com/cosmos/cosmos-sdk/blob/v0.42.9/x/bank/keeper/send.go#L148-L151). Replacing variables with string literals, they look like this: + +```go +sdk.NewEvent( + "transfer" + sdk.NewAttribute("recipient", toAddr.String()), + sdk.NewAttribute("sender", fromAddr.String()), + sdk.NewAttribute("amount", amt.String()), // eg 12456uatom +), +sdk.NewEvent( + "message", + sdk.NewAttribute("sender", fromAddr.String()), +), +``` -TODO: what is emitted by bank (transfer event), as this is a very important base event +The delegation module seems a bit more refined, emitting a generic "message" type event in [`msg_server.go`](https://github.com/cosmos/cosmos-sdk/blob/v0.42.9/x/distribution/keeper/msg_server.go#L42-L46) including the module name, **before** +emitting some custom event types closer to the actual code logic in +[`keeper.go`](https://github.com/cosmos/cosmos-sdk/blob/v0.42.9/x/distribution/keeper/keeper.go#L74-L77). + +This looks something like: + +```go +sdk.NewEvent( + "message", + sdk.NewAttribute("module", "distribution"), + sdk.NewAttribute("sender", msg.DelegatorAddress), +), +sdk.NewEvent( + "set_withdraw_address", + sdk.NewAttribute("withdraw_address", withdrawAddr.String()), +), +``` ## Usage in wasmd -In `x/wasm` we also use Events system. On one hand, `x/wasm` emits standard event for each message it processes to convey, -for example, "uploaded code, id 6" or "executed code, address wasm1234567890". Furthermore, it allows contracts to +In `x/wasm` we also use Events system. On one hand, the Go implementation of `x/wasm` emits standard events for each +message it processes, using the `distribution` module as an example. Furthermore, it allows contracts to emit custom events based on their execution state, so they can for example say "dex swap, BTC-ATOM, in 0.23, out 512" which require internal knowledge of the contract and is very useful for custom dApp UIs. -In addition, when a smart contract executes a SubMsg and processes the reply, it receives not only the `data` response -from the message exection, but also the list of events +`x/wasm` is also a consumer of events, since when a smart contract executes a SubMsg and processes the reply, it receives +not only the `data` response from the message exection, but also the list of events. This makes it even more important for +us to document a standard event processing format. ### Standard Events in x/wasm +Following the model of `distribution`, we + TODO: document what we emit in `x/wasm` regardless of the contract return results ### Emitted Custom Events from a Contract From 37d4105f8f95a264cb5a113b3139aaf815469252 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 6 Aug 2021 01:46:37 +0200 Subject: [PATCH 05/11] Split x/wasm events into multiple types, document message concat --- EVENTS.md | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 166 insertions(+), 11 deletions(-) diff --git a/EVENTS.md b/EVENTS.md index c08f984a7e..ccd1bad0fe 100644 --- a/EVENTS.md +++ b/EVENTS.md @@ -131,17 +131,98 @@ emit custom events based on their execution state, so they can for example say " which require internal knowledge of the contract and is very useful for custom dApp UIs. `x/wasm` is also a consumer of events, since when a smart contract executes a SubMsg and processes the reply, it receives -not only the `data` response from the message exection, but also the list of events. This makes it even more important for +not only the `data` response from the message execution, but also the list of events. This makes it even more important for us to document a standard event processing format. ### Standard Events in x/wasm -Following the model of `distribution`, we +Following the model of `distribution`, we will split the emitted events into two parts. All calls to the message server, will receive +the following event: -TODO: document what we emit in `x/wasm` regardless of the contract return results +```go +sdk.NewEvent( + "message", + sdk.NewAttribute("module", "wasm"), + // Note: this was "signer" before 0.18 + sdk.NewAttribute("sender", msg.Sender), +), +``` + +No further information will be added to the generic "message" type, but rather be contained in a more context-specific event type. +Here are some examples: + +```go +// Store Code +sdk.NewEvent( + "store_code", + sdk.NewAttribute("code_id", fmt.Sprintf("%d", codeID)), + // features required by the contract + // see https://github.com/CosmWasm/wasmd/issues/574 + sdk.NewAttribute("feature", "stargate"), + sdk.NewAttribute("feature", "staking"), +) + +// Instantiate Contract +sdk.NewEvent( + "instantiate", + sdk.NewAttribute("code_id", fmt.Sprintf("%d", msg.CodeID)), + sdk.NewAttribute("_contract_addr", contractAddr.String()), + sdk.NewAttribute("result", hex.EncodeToString(data)), +) + +// Execute Contract +sdk.NewEvent( + "execute", + sdk.NewAttribute("_contract_addr", contractAddr.String()), + sdk.NewAttribute("result", hex.EncodeToString(data)), +) + +// Migrate Contract +sdk.NewEvent( + "migrate", + // Note: this is the new code id that is being migrated to + sdk.NewAttribute("code_id", fmt.Sprintf("%d", msg.CodeID)), + sdk.NewAttribute("_contract_addr", contractAddr.String()), + sdk.NewAttribute("result", hex.EncodeToString(data)), +) + +// Set new admin +sdk.NewEvent( + "update_admin", + sdk.NewAttribute("_contract_addr", contractAddr.String()), + sdk.NewAttribute("admin", msg.NewAdmin), +) + +// Clear admin +sdk.NewEvent( + "clear_admin", + sdk.NewAttribute("_contract_addr", contractAddr.String()), +) + +// Pin Code +sdk.NewEvent( + "pin_code", + sdk.NewAttribute("code_id", strconv.FormatUint(msg.CodeID, 10)), +) + +// Unpin Code +sdk.NewEvent( + "unpin_code", + sdk.NewAttribute("code_id", strconv.FormatUint(msg.CodeID, 10)), +) + +// Emitted when processing a submessage reply +sdk.NewEvent( + "reply", + sdk.NewAttribute("_contract_addr", contractAddr.String()), +) + +``` ### Emitted Custom Events from a Contract +**TODO** + TODO: document how we process attributes and events fields in the Response. * Base event `wasm` @@ -159,16 +240,90 @@ flatten that in a meaningful way to feed it into the event system. Furthermore, with the sub-message reply handlers, we end up with eg. "Contract A execute", "Contract B execute", "Contract A reply". If we return all events by all of these, we may end up with many repeated event types and -a confusing results, especially for Tendermint 0.34 where they are merged together. - -While designing this, we wish to make something that is usable with Tendermint 0.34, but focus on using the -behavior of Tendermint 0.35+ (which is the same behavior as we have internally in the SDK... submessages -all have their own list of Events). Thus, we may emit more events than in previous wasmd versions (as we assume -they will be returned in an ordered list rather than merged). +a confusing results. However, we may use the standard "message" events to separate the sub-messages as it marks +where the next one starts. With careful analysis of the "sender" field on these "message" markers, we may be able +to reconstruct much of the tree execution path. We should ensure all this information is exposed in the most +consistent way possible. ### Combining Events from Sub-Messages -TODO +Each time a contract is executed, it not only returns the `message` event from its call, the `execute` event for the +contact and the `wasm` event with any custom fields from the contract itself. It will also return the same set of information +for all messages that it returned, which were later dispatched. + +Until 0.18.0, we stripped out the `message` events that were returned when dispatching `Response.messages`. However, +they contain useful information to trace the topography of the call, especially when we call into native modules, +and it makese sense to maintain the full information. + +Note that these are all appended in order called. So if I execute a contract, which returns two messages, one to instantiate a new +contract and the other to set the withdrawl address, while also using `ReplyOnSuccess` for the instantiation (to get the +address), it will emit a series of events that looks something like this: + +```go +/// original execution (top-level message) +sdk.NewEvent( + "message", + sdk.NewAttribute("module", "wasm"), + sdk.NewAttribute("sender", msg.Sender), +), +sdk.NewEvent( + "execute", + sdk.NewAttribute("_contract_addr", contractAddr.String()), + sdk.NewAttribute("result", hex.EncodeToString(data)), +), +sdk.NewEvent( + "wasm", + sdk.NewAttribute("_contract_addr", contractAddr.String()), + sdk.NewAttribute("custom", "from contract"), +), + +// instantiating contract (returned message) +sdk.NewEvent( + "message", + sdk.NewAttribute("module", "wasm"), + sdk.NewAttribute("sender", contractAddr.String()), +), +sdk.NewEvent( + "instantiate", + sdk.NewAttribute("code_id", fmt.Sprintf("%d", msg.CodeID)), + sdk.NewAttribute("_contract_addr", newContract.String()), + sdk.NewAttribute("result", hex.EncodeToString(initData)), +) +sdk.NewEvent( + "wasm", + sdk.NewAttribute("_contract_addr", newContract.String()), + sdk.NewAttribute("initialization", "succeeded"), +), +sdk.NewEvent( + "wasm-custom", + sdk.NewAttribute("_contract_addr", newContract.String()), + sdk.NewAttribute("foobar", "baz"), +), + +// handling the reply (this doesn't emit a message event as it never goes through the message server) +sdk.NewEvent( + "reply", + sdk.NewAttribute("_contract_addr", contractAddr.String()), +), +sdk.NewEvent( + "wasm", + sdk.NewAttribute("_contract_addr", contractAddr.String()), + sdk.NewAttribute("custom", "from contract"), +), + + +// calling the distribution module +sdk.NewEvent( + "message", + sdk.NewAttribute("module", "distribution"), + sdk.NewAttribute("sender", contractAddr.String()), +), +sdk.NewEvent( + "set_withdraw_address", + sdk.NewAttribute("withdraw_address", withdrawAddr.String()), +), + +``` + ### Exposing Events to Reply -TODO From 90d73a3475212012bedef2518120066462a2c134 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 6 Aug 2021 02:12:25 +0200 Subject: [PATCH 06/11] _code_id, document emitting custom events, handling events in reply --- EVENTS.md | 111 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 99 insertions(+), 12 deletions(-) diff --git a/EVENTS.md b/EVENTS.md index ccd1bad0fe..baa3d7bd39 100644 --- a/EVENTS.md +++ b/EVENTS.md @@ -155,8 +155,9 @@ Here are some examples: // Store Code sdk.NewEvent( "store_code", - sdk.NewAttribute("code_id", fmt.Sprintf("%d", codeID)), - // features required by the contract + // Update in 0.18: _code_id is also a reserved prefix + sdk.NewAttribute("_code_id", fmt.Sprintf("%d", codeID)), + // features required by the contract (new in 0.18) // see https://github.com/CosmWasm/wasmd/issues/574 sdk.NewAttribute("feature", "stargate"), sdk.NewAttribute("feature", "staking"), @@ -165,7 +166,7 @@ sdk.NewEvent( // Instantiate Contract sdk.NewEvent( "instantiate", - sdk.NewAttribute("code_id", fmt.Sprintf("%d", msg.CodeID)), + sdk.NewAttribute("_code_id", fmt.Sprintf("%d", msg.CodeID)), sdk.NewAttribute("_contract_addr", contractAddr.String()), sdk.NewAttribute("result", hex.EncodeToString(data)), ) @@ -181,7 +182,7 @@ sdk.NewEvent( sdk.NewEvent( "migrate", // Note: this is the new code id that is being migrated to - sdk.NewAttribute("code_id", fmt.Sprintf("%d", msg.CodeID)), + sdk.NewAttribute("_code_id", fmt.Sprintf("%d", msg.CodeID)), sdk.NewAttribute("_contract_addr", contractAddr.String()), sdk.NewAttribute("result", hex.EncodeToString(data)), ) @@ -202,13 +203,13 @@ sdk.NewEvent( // Pin Code sdk.NewEvent( "pin_code", - sdk.NewAttribute("code_id", strconv.FormatUint(msg.CodeID, 10)), + sdk.NewAttribute("_code_id", strconv.FormatUint(msg.CodeID, 10)), ) // Unpin Code sdk.NewEvent( "unpin_code", - sdk.NewAttribute("code_id", strconv.FormatUint(msg.CodeID, 10)), + sdk.NewAttribute("_code_id", strconv.FormatUint(msg.CodeID, 10)), ) // Emitted when processing a submessage reply @@ -219,16 +220,70 @@ sdk.NewEvent( ``` +Note that every event that affects a contract (not store code, pin or unpin) will return the contract_addr as +`_contract_addr`. The events that are related to a particular wasm code (store code, instantiate, pin, unpin, and migrate) +will emit that as `_code_id`. All attributes prefixed with `_` are reserved and may not be emitted by a smart contract, +so we use consistently with the underscore prefix, as they may also be present in the wasm events. + ### Emitted Custom Events from a Contract -**TODO** +When a CosmWasm contract returns a `Response` from one of the calls, it may return a list of attributes as well as a list +of events (in addition to data and a list of messages to dispatch). These are then processed in `x/wasm` to create events that +are emitted to the blockchain. + +Every contract execution, be it execute, instantiate, migrate, reply, will receive a `wasm` type event. This event will +always be tagged with `_contract_address` by the Go module, so this is trust-worthy. The contract itself cannot overwrite +this field. (QUESTION: do we want to emit `_code_id` as well for the code id that was just executed?) Beyond this, if the +contract returned any `attributes`, these are appended to the same event after the standard tags. + +A contact may also return custom `events`. These are multiple events, each with their own type as well as attributes. +When they are received, `x/wasm` prepends `wasm-` to the event type returned by the contact to avoid them trying to fake +an eg. `transfer` event from the bank module. The output here may look like: + +```go +sdk.NewEvent( + "wasm-promote" + sdk.NewAttribute("_contract_addr", contractAddr.String()), + sdk.NewAttribute("batch_id", "6"), + sdk.NewAttribute("address", "cosmos1234567"), + sdk.NewAttribute("address", "cosmos1765432"), +), +sdk.NewEvent( + "wasm-promote" + sdk.NewAttribute("_contract_addr", contractAddr.String()), + sdk.NewAttribute("batch_id", "7"), + sdk.NewAttribute("address", "cosmos19875632"), +) +``` + +Note that these custom events also have the `_contract_address` attribute automatically injected for easier attribution in the clients. +The multiple event API was designed to allow the contract to make logical groupings that are persisted in the event system, +more than flattening them all into one event like: + +```go +sdk.NewEvent( + "wasm" + sdk.NewAttribute("_contract_addr", contractAddr.String()), + sdk.NewAttribute("action", "promote"), + sdk.NewAttribute("batch_id", "6"), + sdk.NewAttribute("address", "cosmos1234567"), + sdk.NewAttribute("address", "cosmos1765432"), + sdk.NewAttribute("batch_id", "7"), + sdk.NewAttribute("address", "cosmos19875632"), +) +``` + +### Validation Rules -TODO: document how we process attributes and events fields in the Response. +While the `wasm` and `wasm-*` namespacing does sandbox the smart contract events and limits malicious activity they could +undertake, we also perform a number of further validation checks on the contracts: -* Base event `wasm` -* Event name mangling (prepend `wasm-`) -* Append trusted attribute _contract_address -* Validation requirements (_ reserved, non-empty, min-length 2 for type) +* No attribute key may start with `_`. This is currently used for `_contract_address` and `_code_id` and is reserved for a + namespace for injecting more *trusted* attributes from the `x/wasm` module as opposed to the contract itself +* Event types are trimmed of whitespace, and must have at least two characters prior to prepending `wasm-`. If the contract returns + " hello\n", the event type will look like `wasm-hello`. If it emits " a ", this will be rejected with an error (aborting execution!) +* Attribute keys and values (both in `attributes` and under `events`) are trimmed of leading/trailing whitespace. If they are empty after + trimming, they are rejected as above (aborting the execution). Otherwise, they are passed verbatim. ## Event Details for wasmd @@ -327,3 +382,35 @@ sdk.NewEvent( ### Exposing Events to Reply +When the `reply` clause in a contract is called, it will receive the data returned from the message it +applies to, as well as all events from that message. In the above case, when the `reply` function was called +on `contractAddr` in response to initializing a contact, it would get the binary-encoded `initData` in the `data` +field, and the following in the `events` field: + +```go +sdk.NewEvent( + "message", + sdk.NewAttribute("module", "wasm"), + sdk.NewAttribute("sender", contractAddr.String()), +), +sdk.NewEvent( + "instantiate", + sdk.NewAttribute("code_id", fmt.Sprintf("%d", msg.CodeID)), + sdk.NewAttribute("_contract_addr", newContract.String()), + sdk.NewAttribute("result", hex.EncodeToString(initData)), +) +sdk.NewEvent( + "wasm", + sdk.NewAttribute("_contract_addr", newContract.String()), + sdk.NewAttribute("initialization", "succeeded"), +), +sdk.NewEvent( + "wasm-custom", + sdk.NewAttribute("_contract_addr", newContract.String()), + sdk.NewAttribute("foobar", "baz"), +), +``` + +If the original contract execution example above was actually the result of a message returned by an eg. factory contract, +and it registered a ReplyOn clause, the `reply` function on that contract would receive the entire 11 events in the example +above, and would need to use the `message` markers to locate the segment of interest. \ No newline at end of file From 33d58d56e78c4ddc69312f556c0e9b94f07e8879 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 6 Aug 2021 12:37:26 +0200 Subject: [PATCH 07/11] Remove all _code_id references, no injection in wasm events --- EVENTS.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/EVENTS.md b/EVENTS.md index baa3d7bd39..e2fccc86aa 100644 --- a/EVENTS.md +++ b/EVENTS.md @@ -155,8 +155,7 @@ Here are some examples: // Store Code sdk.NewEvent( "store_code", - // Update in 0.18: _code_id is also a reserved prefix - sdk.NewAttribute("_code_id", fmt.Sprintf("%d", codeID)), + sdk.NewAttribute("code_id", fmt.Sprintf("%d", codeID)), // features required by the contract (new in 0.18) // see https://github.com/CosmWasm/wasmd/issues/574 sdk.NewAttribute("feature", "stargate"), @@ -166,7 +165,7 @@ sdk.NewEvent( // Instantiate Contract sdk.NewEvent( "instantiate", - sdk.NewAttribute("_code_id", fmt.Sprintf("%d", msg.CodeID)), + sdk.NewAttribute("code_id", fmt.Sprintf("%d", msg.CodeID)), sdk.NewAttribute("_contract_addr", contractAddr.String()), sdk.NewAttribute("result", hex.EncodeToString(data)), ) @@ -182,7 +181,7 @@ sdk.NewEvent( sdk.NewEvent( "migrate", // Note: this is the new code id that is being migrated to - sdk.NewAttribute("_code_id", fmt.Sprintf("%d", msg.CodeID)), + sdk.NewAttribute("code_id", fmt.Sprintf("%d", msg.CodeID)), sdk.NewAttribute("_contract_addr", contractAddr.String()), sdk.NewAttribute("result", hex.EncodeToString(data)), ) @@ -203,13 +202,13 @@ sdk.NewEvent( // Pin Code sdk.NewEvent( "pin_code", - sdk.NewAttribute("_code_id", strconv.FormatUint(msg.CodeID, 10)), + sdk.NewAttribute("code_id", strconv.FormatUint(msg.CodeID, 10)), ) // Unpin Code sdk.NewEvent( "unpin_code", - sdk.NewAttribute("_code_id", strconv.FormatUint(msg.CodeID, 10)), + sdk.NewAttribute("code_id", strconv.FormatUint(msg.CodeID, 10)), ) // Emitted when processing a submessage reply @@ -222,8 +221,8 @@ sdk.NewEvent( Note that every event that affects a contract (not store code, pin or unpin) will return the contract_addr as `_contract_addr`. The events that are related to a particular wasm code (store code, instantiate, pin, unpin, and migrate) -will emit that as `_code_id`. All attributes prefixed with `_` are reserved and may not be emitted by a smart contract, -so we use consistently with the underscore prefix, as they may also be present in the wasm events. +will emit that as `code_id`. All attributes prefixed with `_` are reserved and may not be emitted by a smart contract, +so we use the underscore prefix consistently with attributes that may be injected into custom events. ### Emitted Custom Events from a Contract @@ -233,8 +232,7 @@ are emitted to the blockchain. Every contract execution, be it execute, instantiate, migrate, reply, will receive a `wasm` type event. This event will always be tagged with `_contract_address` by the Go module, so this is trust-worthy. The contract itself cannot overwrite -this field. (QUESTION: do we want to emit `_code_id` as well for the code id that was just executed?) Beyond this, if the -contract returned any `attributes`, these are appended to the same event after the standard tags. +this field. Beyond this, if the contract returned any `attributes`, these are appended to the same event after the standard tags. A contact may also return custom `events`. These are multiple events, each with their own type as well as attributes. When they are received, `x/wasm` prepends `wasm-` to the event type returned by the contact to avoid them trying to fake @@ -278,7 +276,7 @@ sdk.NewEvent( While the `wasm` and `wasm-*` namespacing does sandbox the smart contract events and limits malicious activity they could undertake, we also perform a number of further validation checks on the contracts: -* No attribute key may start with `_`. This is currently used for `_contract_address` and `_code_id` and is reserved for a +* No attribute key may start with `_`. This is currently used for `_contract_address` and is reserved for a namespace for injecting more *trusted* attributes from the `x/wasm` module as opposed to the contract itself * Event types are trimmed of whitespace, and must have at least two characters prior to prepending `wasm-`. If the contract returns " hello\n", the event type will look like `wasm-hello`. If it emits " a ", this will be rejected with an error (aborting execution!) From 3c920a1fe36bb75a58a1751bc1c330e688b6afa0 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 6 Aug 2021 12:41:57 +0200 Subject: [PATCH 08/11] Empty Response.attributes -> no wasm event emitted --- EVENTS.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/EVENTS.md b/EVENTS.md index e2fccc86aa..9795551b0a 100644 --- a/EVENTS.md +++ b/EVENTS.md @@ -230,9 +230,9 @@ When a CosmWasm contract returns a `Response` from one of the calls, it may retu of events (in addition to data and a list of messages to dispatch). These are then processed in `x/wasm` to create events that are emitted to the blockchain. -Every contract execution, be it execute, instantiate, migrate, reply, will receive a `wasm` type event. This event will +If the response contains a non-empty list of `attributes`, `x/wasm` will emit a `wasm` type event. This event will always be tagged with `_contract_address` by the Go module, so this is trust-worthy. The contract itself cannot overwrite -this field. Beyond this, if the contract returned any `attributes`, these are appended to the same event after the standard tags. +this field. Beyond this, the `attributes` returned by the contract, these are appended to the same event. A contact may also return custom `events`. These are multiple events, each with their own type as well as attributes. When they are received, `x/wasm` prepends `wasm-` to the event type returned by the contact to avoid them trying to fake @@ -271,6 +271,11 @@ sdk.NewEvent( ) ``` +If the Response contains neither `event` nor `attributes`, not `wasm*` events will be emitted, just the standard `message` +type as well as the action-dependent event (like `execute` or `migrate`). This is a significant change from pre-0.18 versions +where one could count on the `wasm` event to always be emitted. Now it is recommended to search for `execute._contract_address="foo"` +to find all transactions related to the contract. + ### Validation Rules While the `wasm` and `wasm-*` namespacing does sandbox the smart contract events and limits malicious activity they could From be3fcf1b73588992a0c131018eb532a3a4f95b9b Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 9 Aug 2021 10:46:11 +0200 Subject: [PATCH 09/11] Remove submsg_start/stop, improve documentation of flattening --- EVENTS.md | 65 ++++++++++++++++++++++++------------------------------- 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/EVENTS.md b/EVENTS.md index 9795551b0a..1f22069798 100644 --- a/EVENTS.md +++ b/EVENTS.md @@ -215,8 +215,14 @@ sdk.NewEvent( sdk.NewEvent( "reply", sdk.NewAttribute("_contract_addr", contractAddr.String()), + sdk.NewAttribute("success", strconv.FormatBool(err == nil)), ) +// Emitted when handling sudo +sdk.NewEvent( + "sudo", + sdk.NewAttribute("_contract_addr", contractAddr.String()), +) ``` Note that every event that affects a contract (not store code, pin or unpin) will return the contract_addr as @@ -307,23 +313,29 @@ consistent way possible. Each time a contract is executed, it not only returns the `message` event from its call, the `execute` event for the contact and the `wasm` event with any custom fields from the contract itself. It will also return the same set of information -for all messages that it returned, which were later dispatched. +for all messages that it returned, which were later dispatched. The event system was really designed for one main +action emitting events, so we define a structure to flatten this event tree: -Until 0.18.0, we stripped out the `message` events that were returned when dispatching `Response.messages`. However, -they contain useful information to trace the topography of the call, especially when we call into native modules, -and it makese sense to maintain the full information. +* We only emit one event of type `message`. This is the top-level call, just like the standard Go modules. For all + dispatched submessages, we filter out this event type. +* All events are returned in execution order as [defined by CosmWasm docs](https://github.com/CosmWasm/cosmwasm/blob/main/SEMANTICS.md#dispatching-messages) +* `x/wasm` keeper emits a custom event for each call to a contract entry point. Not just `execute`, `instantiate`, + and `migrate`, but also `reply`, `sudo` and all ibc entry points. +* This means all `wasm*` events are preceeded by the cosmwasm entry point that returned them. -Note that these are all appended in order called. So if I execute a contract, which returns two messages, one to instantiate a new +To make this more clear, I will provide an example of executing a contract, which returns two messages, one to instantiate a new contract and the other to set the withdrawl address, while also using `ReplyOnSuccess` for the instantiation (to get the -address), it will emit a series of events that looks something like this: +address). It will emit a series of events that looks something like this: ```go -/// original execution (top-level message) +/// original execution (top-level message is the only one that gets the message tag) sdk.NewEvent( "message", sdk.NewAttribute("module", "wasm"), sdk.NewAttribute("sender", msg.Sender), ), + +// top-level exection call sdk.NewEvent( "execute", sdk.NewAttribute("_contract_addr", contractAddr.String()), @@ -335,23 +347,14 @@ sdk.NewEvent( sdk.NewAttribute("custom", "from contract"), ), -// instantiating contract (returned message) -sdk.NewEvent( - "message", - sdk.NewAttribute("module", "wasm"), - sdk.NewAttribute("sender", contractAddr.String()), -), +// instantiating contract (first dipatched message) sdk.NewEvent( "instantiate", sdk.NewAttribute("code_id", fmt.Sprintf("%d", msg.CodeID)), sdk.NewAttribute("_contract_addr", newContract.String()), sdk.NewAttribute("result", hex.EncodeToString(initData)), ) -sdk.NewEvent( - "wasm", - sdk.NewAttribute("_contract_addr", newContract.String()), - sdk.NewAttribute("initialization", "succeeded"), -), +// didn't emit any attributes, but one event sdk.NewEvent( "wasm-custom", sdk.NewAttribute("_contract_addr", newContract.String()), @@ -362,6 +365,7 @@ sdk.NewEvent( sdk.NewEvent( "reply", sdk.NewAttribute("_contract_addr", contractAddr.String()), + sdk.NewAttribute("success", "true"), ), sdk.NewEvent( "wasm", @@ -369,18 +373,11 @@ sdk.NewEvent( sdk.NewAttribute("custom", "from contract"), ), - -// calling the distribution module -sdk.NewEvent( - "message", - sdk.NewAttribute("module", "distribution"), - sdk.NewAttribute("sender", contractAddr.String()), -), +// calling the distribution module (second dispatched message) sdk.NewEvent( "set_withdraw_address", sdk.NewAttribute("withdraw_address", withdrawAddr.String()), ), - ``` ### Exposing Events to Reply @@ -391,22 +388,12 @@ on `contractAddr` in response to initializing a contact, it would get the binary field, and the following in the `events` field: ```go -sdk.NewEvent( - "message", - sdk.NewAttribute("module", "wasm"), - sdk.NewAttribute("sender", contractAddr.String()), -), sdk.NewEvent( "instantiate", sdk.NewAttribute("code_id", fmt.Sprintf("%d", msg.CodeID)), sdk.NewAttribute("_contract_addr", newContract.String()), sdk.NewAttribute("result", hex.EncodeToString(initData)), ) -sdk.NewEvent( - "wasm", - sdk.NewAttribute("_contract_addr", newContract.String()), - sdk.NewAttribute("initialization", "succeeded"), -), sdk.NewEvent( "wasm-custom", sdk.NewAttribute("_contract_addr", newContract.String()), @@ -416,4 +403,8 @@ sdk.NewEvent( If the original contract execution example above was actually the result of a message returned by an eg. factory contract, and it registered a ReplyOn clause, the `reply` function on that contract would receive the entire 11 events in the example -above, and would need to use the `message` markers to locate the segment of interest. \ No newline at end of file +above, and would need to use the `message` markers to locate the segment of interest. + +## IBC Events + +TODO: define what the default SDK messages are here and what we add to our custom keeper events. \ No newline at end of file From 79f3fadf45780b55636d303c79eaf726ab9e2dca Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 9 Aug 2021 10:47:05 +0200 Subject: [PATCH 10/11] Remove hex-encoded result attribute --- EVENTS.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/EVENTS.md b/EVENTS.md index 1f22069798..38a4ff1e11 100644 --- a/EVENTS.md +++ b/EVENTS.md @@ -167,14 +167,12 @@ sdk.NewEvent( "instantiate", sdk.NewAttribute("code_id", fmt.Sprintf("%d", msg.CodeID)), sdk.NewAttribute("_contract_addr", contractAddr.String()), - sdk.NewAttribute("result", hex.EncodeToString(data)), ) // Execute Contract sdk.NewEvent( "execute", sdk.NewAttribute("_contract_addr", contractAddr.String()), - sdk.NewAttribute("result", hex.EncodeToString(data)), ) // Migrate Contract @@ -183,7 +181,6 @@ sdk.NewEvent( // Note: this is the new code id that is being migrated to sdk.NewAttribute("code_id", fmt.Sprintf("%d", msg.CodeID)), sdk.NewAttribute("_contract_addr", contractAddr.String()), - sdk.NewAttribute("result", hex.EncodeToString(data)), ) // Set new admin @@ -339,7 +336,6 @@ sdk.NewEvent( sdk.NewEvent( "execute", sdk.NewAttribute("_contract_addr", contractAddr.String()), - sdk.NewAttribute("result", hex.EncodeToString(data)), ), sdk.NewEvent( "wasm", @@ -352,7 +348,6 @@ sdk.NewEvent( "instantiate", sdk.NewAttribute("code_id", fmt.Sprintf("%d", msg.CodeID)), sdk.NewAttribute("_contract_addr", newContract.String()), - sdk.NewAttribute("result", hex.EncodeToString(initData)), ) // didn't emit any attributes, but one event sdk.NewEvent( @@ -392,7 +387,6 @@ sdk.NewEvent( "instantiate", sdk.NewAttribute("code_id", fmt.Sprintf("%d", msg.CodeID)), sdk.NewAttribute("_contract_addr", newContract.String()), - sdk.NewAttribute("result", hex.EncodeToString(initData)), ) sdk.NewEvent( "wasm-custom", From e4987f7fa55a16e25cda21fafe98acc13ac8bb2b Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 9 Aug 2021 13:55:48 +0200 Subject: [PATCH 11/11] Use mode: handle_failure/success attribute on reply --- EVENTS.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/EVENTS.md b/EVENTS.md index 38a4ff1e11..20008d5e5b 100644 --- a/EVENTS.md +++ b/EVENTS.md @@ -212,7 +212,10 @@ sdk.NewEvent( sdk.NewEvent( "reply", sdk.NewAttribute("_contract_addr", contractAddr.String()), - sdk.NewAttribute("success", strconv.FormatBool(err == nil)), + // If the submessage was successful, and reply is processing the success case + sdk.NewAttribute("mode", "handle_success"), + // If the submessage returned an error that was "caught" by the reply block + sdk.NewAttribute("mode", "handle_failure"), ) // Emitted when handling sudo @@ -360,7 +363,7 @@ sdk.NewEvent( sdk.NewEvent( "reply", sdk.NewAttribute("_contract_addr", contractAddr.String()), - sdk.NewAttribute("success", "true"), + sdk.NewAttribute("mode", "handle_success"), ), sdk.NewEvent( "wasm",