Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Event Indexing #7121

Merged
merged 8 commits into from
Aug 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,9 @@ be used to retrieve the actual proposal `Content`. Also the `NewMsgSubmitProposa
* (genesis) [\#7000](https://github.com/cosmos/cosmos-sdk/pull/7000) The root `GenesisState` is now decoded using `encoding/json` instead of amino so `int64` and `uint64` types are now encoded as integers as opposed to strings.
* (types) [\#7032](https://github.com/cosmos/cosmos-sdk/pull/7032) All types ending with `ID` (e.g. `ProposalID`) now end with `Id` (e.g. `ProposalId`), to match default Protobuf generated format. Also see [\#7033](https://github.com/cosmos/cosmos-sdk/pull/7033) for more details.


### Features

* (events) [\#7121](https://github.com/cosmos/cosmos-sdk/pull/7121) The application now drives what events are indexed by Tendermint via the `index-events` configuration in `app.toml`, which is a list of events taking the form `{eventType}.{attributeKey}`.
* [\#6089](https://github.com/cosmos/cosmos-sdk/pull/6089) Transactions can now have a `TimeoutHeight` set which allows the transaction to be rejected if it's committed at a height greater than the timeout.
* (tests) [\#6489](https://github.com/cosmos/cosmos-sdk/pull/6489) Introduce package `testutil`, new in-process testing network framework for use in integration and unit tests.
* (crypto/multisig) [\#6241](https://github.com/cosmos/cosmos-sdk/pull/6241) Add Multisig type directly to the repo. Previously this was in tendermint.
Expand Down
6 changes: 4 additions & 2 deletions baseapp/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg

if app.beginBlocker != nil {
res = app.beginBlocker(app.deliverState.ctx, req)
res.Events = sdk.MarkEventsToIndex(res.Events, app.indexEvents)
}
// set the signed validators for addition to context in deliverTx
app.voteInfos = req.LastCommitInfo.GetVotes()
Expand All @@ -161,6 +162,7 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc

if app.endBlocker != nil {
res = app.endBlocker(app.deliverState.ctx, req)
res.Events = sdk.MarkEventsToIndex(res.Events, app.indexEvents)
}

if cp := app.GetConsensusParams(app.deliverState.ctx); cp != nil {
Expand Down Expand Up @@ -207,7 +209,7 @@ func (app *BaseApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx {
GasUsed: int64(gInfo.GasUsed), // TODO: Should type accept unsigned ints?
Log: result.Log,
Data: result.Data,
Events: result.Events,
Events: sdk.MarkEventsToIndex(result.Events, app.indexEvents),
}
}

Expand Down Expand Up @@ -245,7 +247,7 @@ func (app *BaseApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx
GasUsed: int64(gInfo.GasUsed), // TODO: Should type accept unsigned ints?
Log: result.Log,
Data: result.Data,
Events: result.Events,
Events: sdk.MarkEventsToIndex(result.Events, app.indexEvents),
}
}

Expand Down
12 changes: 12 additions & 0 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ type BaseApp struct { // nolint: maligned

// trace set will return full stack traces for errors in ABCI Log field
trace bool

// indexEvents defines the set of events in the form {eventType}.{attributeKey},
// which informs Tendermint what to index. If empty, all events will be indexed.
indexEvents map[string]struct{}
}

// NewBaseApp returns a reference to an initialized BaseApp. It accepts a
Expand Down Expand Up @@ -283,6 +287,14 @@ func (app *BaseApp) setTrace(trace bool) {
app.trace = trace
}

func (app *BaseApp) setIndexEvents(ie []string) {
app.indexEvents = make(map[string]struct{})

for _, e := range ie {
app.indexEvents[e] = struct{}{}
}
}

// Router returns the router of the BaseApp.
func (app *BaseApp) Router() sdk.Router {
if app.sealed {
Expand Down
5 changes: 5 additions & 0 deletions baseapp/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ func SetTrace(trace bool) func(*BaseApp) {
return func(app *BaseApp) { app.setTrace(trace) }
}

// SetIndexEvents provides a BaseApp option function that sets the events to index.
func SetIndexEvents(ie []string) func(*BaseApp) {
return func(app *BaseApp) { app.setIndexEvents(ie) }
}

// SetInterBlockCache provides a BaseApp option function that sets the
// inter-block cache.
func SetInterBlockCache(cache sdk.MultiStorePersistentCache) func(*BaseApp) {
Expand Down
6 changes: 6 additions & 0 deletions server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ type BaseConfig struct {

// InterBlockCache enables inter-block caching.
InterBlockCache bool `mapstructure:"inter-block-cache"`

// IndexEvents defines the set of events in the form {eventType}.{attributeKey},
// which informs Tendermint what to index. If empty, all events will be indexed.
IndexEvents []string `mapstructure:"index-events"`
}

// APIConfig defines the API listener configuration.
Expand Down Expand Up @@ -134,6 +138,7 @@ func DefaultConfig() *Config {
PruningKeepRecent: "0",
PruningKeepEvery: "0",
PruningInterval: "0",
IndexEvents: make([]string, 0),
},
Telemetry: telemetry.Config{
Enabled: false,
Expand Down Expand Up @@ -175,6 +180,7 @@ func GetConfig(v *viper.Viper) Config {
PruningInterval: v.GetString("pruning-interval"),
HaltHeight: v.GetUint64("halt-height"),
HaltTime: v.GetUint64("halt-time"),
IndexEvents: v.GetStringSlice("index-events"),
},
Telemetry: telemetry.Config{
ServiceName: v.GetString("telemetry.service-name"),
Expand Down
7 changes: 7 additions & 0 deletions server/config/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ halt-time = {{ .BaseConfig.HaltTime }}
# InterBlockCache enables inter-block caching.
inter-block-cache = {{ .BaseConfig.InterBlockCache }}

# IndexEvents defines the set of events in the form {eventType}.{attributeKey},
# which informs Tendermint what to index. If empty, all events will be indexed.
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
#
# Example:
# ["message.sender", "message.recipient"]
index-events = {{ .BaseConfig.IndexEvents }}

###############################################################################
### Telemetry Configuration ###
###############################################################################
Expand Down
1 change: 1 addition & 0 deletions server/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const (
FlagPruningKeepRecent = "pruning-keep-recent"
FlagPruningKeepEvery = "pruning-keep-every"
FlagPruningInterval = "pruning-interval"
FlagIndexEvents = "index-events"
)

// GRPC-related flags.
Expand Down
1 change: 1 addition & 0 deletions simapp/simd/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts serverty
baseapp.SetHaltTime(cast.ToUint64(appOpts.Get(server.FlagHaltTime))),
baseapp.SetInterBlockCache(cache),
baseapp.SetTrace(cast.ToBool(appOpts.Get(server.FlagTrace))),
baseapp.SetIndexEvents(cast.ToStringSlice(appOpts.Get(server.FlagIndexEvents))),
)
}

Expand Down
27 changes: 27 additions & 0 deletions types/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,30 @@ func StringifyEvents(events []abci.Event) StringEvents {

return res.Flatten()
}

// MarkEventsToIndex returns the set of ABCI events, where each event's attribute
// has it's index value marked based on the provided set of events to index.
func MarkEventsToIndex(events []abci.Event, indexSet map[string]struct{}) []abci.Event {
updatedEvents := make([]abci.Event, len(events))
for i, e := range events {
updatedEvent := abci.Event{
Type: e.Type,
Attributes: make([]abci.EventAttribute, len(e.Attributes)),
}

for j, attr := range e.Attributes {
_, index := indexSet[fmt.Sprintf("%s.%s", e.Type, attr.Key)]
updatedAttr := abci.EventAttribute{
Key: attr.Key,
Value: attr.Value,
Index: index,
}

updatedEvent.Attributes[j] = updatedAttr
}

updatedEvents[i] = updatedEvent
}

return updatedEvents
}
87 changes: 87 additions & 0 deletions types/events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
)

func TestAppendEvents(t *testing.T) {
Expand Down Expand Up @@ -69,3 +70,89 @@ func TestStringifyEvents(t *testing.T) {
expectedJSONStr := "[{\"type\":\"message\",\"attributes\":[{\"key\":\"sender\",\"value\":\"foo\"},{\"key\":\"module\",\"value\":\"bank\"}]}]"
require.Equal(t, expectedJSONStr, string(bz))
}

func TestMarkEventsToIndex(t *testing.T) {
events := []abci.Event{
{
Type: "message",
Attributes: []abci.EventAttribute{
{Key: []byte("sender"), Value: []byte("foo")},
{Key: []byte("recipient"), Value: []byte("bar")},
},
},
{
Type: "staking",
Attributes: []abci.EventAttribute{
{Key: []byte("deposit"), Value: []byte("5")},
{Key: []byte("unbond"), Value: []byte("10")},
},
},
}

testCases := map[string]struct {
events []abci.Event
indexSet map[string]struct{}
expected []abci.Event
}{
"empty index set": {
events: events,
expected: events,
indexSet: map[string]struct{}{},
},
"index some events": {
events: events,
expected: []abci.Event{
{
Type: "message",
Attributes: []abci.EventAttribute{
{Key: []byte("sender"), Value: []byte("foo"), Index: true},
{Key: []byte("recipient"), Value: []byte("bar")},
},
},
{
Type: "staking",
Attributes: []abci.EventAttribute{
{Key: []byte("deposit"), Value: []byte("5"), Index: true},
{Key: []byte("unbond"), Value: []byte("10")},
},
},
},
indexSet: map[string]struct{}{
"message.sender": {},
"staking.deposit": {},
},
},
"index all events": {
events: events,
expected: []abci.Event{
{
Type: "message",
Attributes: []abci.EventAttribute{
{Key: []byte("sender"), Value: []byte("foo"), Index: true},
{Key: []byte("recipient"), Value: []byte("bar"), Index: true},
},
},
{
Type: "staking",
Attributes: []abci.EventAttribute{
{Key: []byte("deposit"), Value: []byte("5"), Index: true},
{Key: []byte("unbond"), Value: []byte("10"), Index: true},
},
},
},
indexSet: map[string]struct{}{
"message.sender": {},
"message.recipient": {},
"staking.deposit": {},
"staking.unbond": {},
},
},
}

for name, tc := range testCases {
tc := tc
t.Run(name, func(t *testing.T) {
require.Equal(t, tc.expected, MarkEventsToIndex(tc.events, tc.indexSet))
})
}
}