Skip to content

Commit

Permalink
Merge PR #5482: Update Query Txs by Events Command
Browse files Browse the repository at this point in the history
  • Loading branch information
alexanderbez authored Jan 7, 2020
1 parent 7f433a7 commit 09705d9
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 28 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Improvements

* (tendermint) Bump Tendermint version to [v0.32.8](https://github.com/tendermint/tendermint/releases/tag/v0.32.8)
* (cli) [\#5482](https://github.com/cosmos/cosmos-sdk/pull/5482) Remove old "tags" nomenclature from the `q txs` command in
favor of the new events system. Functionality remains unchanged except that `=` is used instead of `:` to be
consistent with the API's use of event queries.

### Bug Fixes

Expand Down
95 changes: 95 additions & 0 deletions docs/core/events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Events

## Pre-Requisite Readings

- [Anatomy of an SDK application](../basics/app-anatomy.md)

## Events

Events are implemented in the Cosmos SDK as an alias of the ABCI `Event` type and
take the form of: `{eventType}.{eventAttribute}={value}`.

Events contain:

- A `type`, which is meant to categorize an event at a high-level (e.g. by module or action).
- A list of `attributes`, which are key-value pairs that give more information about
the categorized `event`.

Events are returned to the underlying consensus engine in the response of the following ABCI messages:

- [`BeginBlock`](./baseapp.md#beginblock)
- [`EndBlock`](./baseapp.md#endblock)
- [`CheckTx`](./baseapp.md#checktx)
- [`DeliverTx`](./baseapp.md#delivertx)

Events, the `type` and `attributes`, are defined on a **per-module basis** in the module's
`/internal/types/events.go` file, and triggered from the module's [`handler`](../building-modules/handler.md)
via the [`EventManager`](#eventmanager). In addition, each module documents its events under
`spec/xx_events.md`.

## EventManager

In Cosmos SDK applications, events are managed by an abstraction called the `EventManager`.
Internally, the `EventManager` tracks a list of `Events` for the entire execution flow of a
transaction or `BeginBlock`/`EndBlock`.

+++ https://github.com/cosmos/cosmos-sdk/blob/7d7821b9af132b0f6131640195326aa02b6751db/types/events.go#L16-L20

The `EventManager` comes with a set of useful methods to manage events. Among them, the one that is
used the most by module and application developers is the `EmitEvent` method, which tracks
an `event` in the `EventManager`.

+++ https://github.com/cosmos/cosmos-sdk/blob/7d7821b9af132b0f6131640195326aa02b6751db/types/events.go#L29-L31

Module developers should handle event emission via the `EventManager#EmitEvent` in each message
`Handler` and in each `BeginBlock`/`EndBlock` handler. The `EventManager` is accessed via
the [`Context`](./context.md), where event emission generally follows this pattern:

```go
ctx.EventManager().EmitEvent(
sdk.NewEvent(eventType, sdk.NewAttribute(attributeKey, attributeValue)),
)
```

See the [`Handler`](../building-modules/handler.md) concept doc for a more detailed
view on how to typically implement `Events` and use the `EventManager` in modules.

## Subscribing to Events

It is possible to subscribe to `Events` via Tendermint's [Websocket](https://tendermint.com/docs/app-dev/subscribing-to-events-via-websocket.html#subscribing-to-events-via-websocket).
This is done by calling the `subscribe` RPC method via Websocket:

```json
{
"jsonrpc": "2.0",
"method": "subscribe",
"id": "0",
"params": {
"query": "tm.event='eventCategory' AND eventType.eventAttribute='attributeValue'"
}
}
```

The main `eventCategory` you can subscribe to are:

- `NewBlock`: Contains `events` triggered during `BeginBlock` and `EndBlock`.
- `Tx`: Contains `events` triggered during `DeliverTx` (i.e. transaction processing).
- `ValidatorSetUpdates`: Contains validator set updates for the block.

These events are triggered from the `state` package after a block is committed. You can get the
full list of `event` categories [here](https://godoc.org/github.com/tendermint/tendermint/types#pkg-constants).

The `type` and `attribute` value of the `query` allow you to filter the specific `event` you are looking for. For example, a `transfer` transaction triggers an `event` of type `Transfer` and has `Recipient` and `Sender` as `attributes` (as defined in the [`events` file of the `bank` module](https://github.com/cosmos/cosmos-sdk/blob/master/x/bank/internal/types/events.go)). Subscribing to this `event` would be done like so:

```json
{
"jsonrpc": "2.0",
"method": "subscribe",
"id": "0",
"params": {
"query": "tm.event='Tx' AND transfer.sender='senderAddress'"
}
}
```

where `senderAddress` is an address following the [`AccAddress`](../basics/accounts.md#addresses) format.
65 changes: 37 additions & 28 deletions x/auth/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/cosmos/cosmos-sdk/version"
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
"github.com/cosmos/cosmos-sdk/x/auth/types"

tmtypes "github.com/tendermint/tendermint/types"
)

const (
flagTags = "tags"
flagPage = "page"
flagLimit = "limit"
flagEvents = "events"
flagPage = "page"
flagLimit = "limit"

eventFormat = "{eventType}.{eventAttribute}={value}"
)

// GetTxCmd returns the transaction commands for this module
Expand Down Expand Up @@ -76,47 +79,52 @@ func GetAccountCmd(cdc *codec.Codec) *cobra.Command {
func QueryTxsByEventsCmd(cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "txs",
Short: "Query for paginated transactions that match a set of tags",
Long: strings.TrimSpace(`
Search for transactions that match the exact given tags where results are paginated.
Short: "Query for paginated transactions that match a set of events",
Long: strings.TrimSpace(
fmt.Sprintf(`
Search for transactions that match the exact given events where results are paginated.
Each event takes the form of '%s'. Please refer
to each module's documentation for the full set of events to query for. Each module
documents its respective events under 'xx_events.md'.
Example:
$ <appcli> query txs --tags '<tag1>:<value1>&<tag2>:<value2>' --page 1 --limit 30
`),
$ %s query txs --%s 'message.sender=cosmos1...&message.action=withdraw_delegator_reward' --page 1 --limit 30
`, eventFormat, version.ClientName, flagEvents),
),
RunE: func(cmd *cobra.Command, args []string) error {
tagsStr := viper.GetString(flagTags)
tagsStr = strings.Trim(tagsStr, "'")
eventsStr := strings.Trim(viper.GetString(flagEvents), "'")

var tags []string
if strings.Contains(tagsStr, "&") {
tags = strings.Split(tagsStr, "&")
var events []string
if strings.Contains(eventsStr, "&") {
events = strings.Split(eventsStr, "&")
} else {
tags = append(tags, tagsStr)
events = append(events, eventsStr)
}

var tmTags []string
for _, tag := range tags {
if !strings.Contains(tag, ":") {
return fmt.Errorf("%s should be of the format <key>:<value>", tagsStr)
} else if strings.Count(tag, ":") > 1 {
return fmt.Errorf("%s should only contain one <key>:<value> pair", tagsStr)
var tmEvents []string

for _, event := range events {
if !strings.Contains(event, "=") {
return fmt.Errorf("invalid event; event %s should be of the format: %s", event, eventFormat)
} else if strings.Count(event, "=") > 1 {
return fmt.Errorf("invalid event; event %s should be of the format: %s", event, eventFormat)
}

keyValue := strings.Split(tag, ":")
if keyValue[0] == tmtypes.TxHeightKey {
tag = fmt.Sprintf("%s=%s", keyValue[0], keyValue[1])
tokens := strings.Split(event, "=")
if tokens[0] == tmtypes.TxHeightKey {
event = fmt.Sprintf("%s=%s", tokens[0], tokens[1])
} else {
tag = fmt.Sprintf("%s='%s'", keyValue[0], keyValue[1])
event = fmt.Sprintf("%s='%s'", tokens[0], tokens[1])
}

tmTags = append(tmTags, tag)
tmEvents = append(tmEvents, event)
}

page := viper.GetInt(flagPage)
limit := viper.GetInt(flagLimit)

cliCtx := context.NewCLIContext().WithCodec(cdc)
txs, err := utils.QueryTxsByEvents(cliCtx, tmTags, page, limit)
txs, err := utils.QueryTxsByEvents(cliCtx, tmEvents, page, limit)
if err != nil {
return err
}
Expand All @@ -139,13 +147,14 @@ $ <appcli> query txs --tags '<tag1>:<value1>&<tag2>:<value2>' --page 1 --limit 3

cmd.Flags().StringP(flags.FlagNode, "n", "tcp://localhost:26657", "Node to connect to")
viper.BindPFlag(flags.FlagNode, cmd.Flags().Lookup(flags.FlagNode))

cmd.Flags().Bool(flags.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)")
viper.BindPFlag(flags.FlagTrustNode, cmd.Flags().Lookup(flags.FlagTrustNode))

cmd.Flags().String(flagTags, "", "tag:value list of tags that must match")
cmd.Flags().String(flagEvents, "", fmt.Sprintf("list of transaction events in the form of %s", eventFormat))
cmd.Flags().Uint32(flagPage, rest.DefaultPage, "Query a specific page of paginated results")
cmd.Flags().Uint32(flagLimit, rest.DefaultLimit, "Query number of transactions results per page returned")
cmd.MarkFlagRequired(flagTags)
cmd.MarkFlagRequired(flagEvents)

return cmd
}
Expand Down

0 comments on commit 09705d9

Please sign in to comment.