package kv
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
dbm "github.com/tendermint/tm-db"
|
|
|
|
"github.com/gogo/protobuf/proto"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
abci "github.com/tendermint/tendermint/abci/types"
|
|
"github.com/tendermint/tendermint/internal/pubsub/query"
|
|
"github.com/tendermint/tendermint/internal/state/indexer"
|
|
kvtx "github.com/tendermint/tendermint/internal/state/indexer/tx/kv"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
func TestType(t *testing.T) {
|
|
kvSink := NewEventSink(dbm.NewMemDB())
|
|
assert.Equal(t, indexer.KV, kvSink.Type())
|
|
}
|
|
|
|
func TestStop(t *testing.T) {
|
|
kvSink := NewEventSink(dbm.NewMemDB())
|
|
assert.Nil(t, kvSink.Stop())
|
|
}
|
|
|
|
func TestBlockFuncs(t *testing.T) {
|
|
store := dbm.NewPrefixDB(dbm.NewMemDB(), []byte("block_events"))
|
|
indexer := NewEventSink(store)
|
|
|
|
require.NoError(t, indexer.IndexBlockEvents(types.EventDataNewBlockHeader{
|
|
Header: types.Header{Height: 1},
|
|
ResultFinalizeBlock: abci.ResponseFinalizeBlock{
|
|
Events: []abci.Event{
|
|
{
|
|
Type: "finalize_eventA",
|
|
Attributes: []abci.EventAttribute{
|
|
{
|
|
Key: "proposer",
|
|
Value: "FCAA001",
|
|
Index: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "finalize_eventB",
|
|
Attributes: []abci.EventAttribute{
|
|
{
|
|
Key: "foo",
|
|
Value: "100",
|
|
Index: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}))
|
|
|
|
b, e := indexer.HasBlock(1)
|
|
assert.Nil(t, e)
|
|
assert.True(t, b)
|
|
|
|
for i := 2; i < 12; i++ {
|
|
var index bool
|
|
if i%2 == 0 {
|
|
index = true
|
|
}
|
|
|
|
require.NoError(t, indexer.IndexBlockEvents(types.EventDataNewBlockHeader{
|
|
Header: types.Header{Height: int64(i)},
|
|
ResultFinalizeBlock: abci.ResponseFinalizeBlock{
|
|
Events: []abci.Event{
|
|
{
|
|
Type: "finalize_eventA",
|
|
Attributes: []abci.EventAttribute{
|
|
{
|
|
Key: "proposer",
|
|
Value: "FCAA001",
|
|
Index: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "finalize_eventB",
|
|
Attributes: []abci.EventAttribute{
|
|
{
|
|
Key: "foo",
|
|
Value: fmt.Sprintf("%d", i),
|
|
Index: index,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}))
|
|
}
|
|
|
|
testCases := map[string]struct {
|
|
q *query.Query
|
|
results []int64
|
|
}{
|
|
"block.height = 100": {
|
|
q: query.MustCompile(`block.height = 100`),
|
|
results: []int64{},
|
|
},
|
|
"block.height = 5": {
|
|
q: query.MustCompile(`block.height = 5`),
|
|
results: []int64{5},
|
|
},
|
|
"finalize_eventA.key1 = 'value1'": {
|
|
q: query.MustCompile(`finalize_eventA.key1 = 'value1'`),
|
|
results: []int64{},
|
|
},
|
|
"finalize_eventA.proposer = 'FCAA001'": {
|
|
q: query.MustCompile(`finalize_eventA.proposer = 'FCAA001'`),
|
|
results: []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
|
|
},
|
|
"finalize_eventB.foo <= 5": {
|
|
q: query.MustCompile(`finalize_eventB.foo <= 5`),
|
|
results: []int64{2, 4},
|
|
},
|
|
"finalize_eventB.foo >= 100": {
|
|
q: query.MustCompile(`finalize_eventB.foo >= 100`),
|
|
results: []int64{1},
|
|
},
|
|
"block.height > 2 AND finalize_eventB.foo <= 8": {
|
|
q: query.MustCompile(`block.height > 2 AND finalize_eventB.foo <= 8`),
|
|
results: []int64{4, 6, 8},
|
|
},
|
|
"finalize_eventA.proposer CONTAINS 'FFFFFFF'": {
|
|
q: query.MustCompile(`finalize_eventA.proposer CONTAINS 'FFFFFFF'`),
|
|
results: []int64{},
|
|
},
|
|
"finalize_eventA.proposer CONTAINS 'FCAA001'": {
|
|
q: query.MustCompile(`finalize_eventA.proposer CONTAINS 'FCAA001'`),
|
|
results: []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
|
|
},
|
|
}
|
|
|
|
for name, tc := range testCases {
|
|
tc := tc
|
|
t.Run(name, func(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
results, err := indexer.SearchBlockEvents(ctx, tc.q)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.results, results)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTxSearchWithCancelation(t *testing.T) {
|
|
indexer := NewEventSink(dbm.NewMemDB())
|
|
|
|
txResult := txResultWithEvents([]abci.Event{
|
|
{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}}},
|
|
{Type: "account", Attributes: []abci.EventAttribute{{Key: "owner", Value: "Ivan", Index: true}}},
|
|
{Type: "", Attributes: []abci.EventAttribute{{Key: "not_allowed", Value: "Vlad", Index: true}}},
|
|
})
|
|
err := indexer.IndexTxEvents([]*abci.TxResult{txResult})
|
|
require.NoError(t, err)
|
|
|
|
r, e := indexer.GetTxByHash(types.Tx("HELLO WORLD").Hash())
|
|
assert.Nil(t, e)
|
|
assert.Equal(t, r, txResult)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
results, err := indexer.SearchTxEvents(ctx, query.MustCompile(`account.number = 1`))
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, results)
|
|
}
|
|
|
|
func TestTxSearchDeprecatedIndexing(t *testing.T) {
|
|
esdb := dbm.NewMemDB()
|
|
indexer := NewEventSink(esdb)
|
|
|
|
// index tx using events indexing (composite key)
|
|
txResult1 := txResultWithEvents([]abci.Event{
|
|
{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}}},
|
|
})
|
|
hash1 := types.Tx(txResult1.Tx).Hash()
|
|
|
|
err := indexer.IndexTxEvents([]*abci.TxResult{txResult1})
|
|
require.NoError(t, err)
|
|
|
|
// index tx also using deprecated indexing (event as key)
|
|
txResult2 := txResultWithEvents(nil)
|
|
txResult2.Tx = types.Tx("HELLO WORLD 2")
|
|
|
|
hash2 := types.Tx(txResult2.Tx).Hash()
|
|
b := esdb.NewBatch()
|
|
|
|
rawBytes, err := proto.Marshal(txResult2)
|
|
require.NoError(t, err)
|
|
|
|
depKey := []byte(fmt.Sprintf("%s/%s/%d/%d",
|
|
"sender",
|
|
"addr1",
|
|
txResult2.Height,
|
|
txResult2.Index,
|
|
))
|
|
|
|
err = b.Set(depKey, hash2)
|
|
require.NoError(t, err)
|
|
err = b.Set(kvtx.KeyFromHeight(txResult2), hash2)
|
|
require.NoError(t, err)
|
|
err = b.Set(hash2, rawBytes)
|
|
require.NoError(t, err)
|
|
err = b.Write()
|
|
require.NoError(t, err)
|
|
|
|
testCases := []struct {
|
|
q string
|
|
results []*abci.TxResult
|
|
}{
|
|
// search by hash
|
|
{fmt.Sprintf("tx.hash = '%X'", hash1), []*abci.TxResult{txResult1}},
|
|
// search by hash
|
|
{fmt.Sprintf("tx.hash = '%X'", hash2), []*abci.TxResult{txResult2}},
|
|
// search by exact match (one key)
|
|
{"account.number = 1", []*abci.TxResult{txResult1}},
|
|
{"account.number >= 1 AND account.number <= 5", []*abci.TxResult{txResult1}},
|
|
// search by range (lower bound)
|
|
{"account.number >= 1", []*abci.TxResult{txResult1}},
|
|
// search by range (upper bound)
|
|
{"account.number <= 5", []*abci.TxResult{txResult1}},
|
|
// search using not allowed key
|
|
{"not_allowed = 'boom'", []*abci.TxResult{}},
|
|
// search for not existing tx result
|
|
{"account.number >= 2 AND account.number <= 5", []*abci.TxResult{}},
|
|
// search using not existing key
|
|
{"account.date >= TIME 2013-05-03T14:45:00Z", []*abci.TxResult{}},
|
|
// search by deprecated key
|
|
{"sender = 'addr1'", []*abci.TxResult{txResult2}},
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
for _, tc := range testCases {
|
|
tc := tc
|
|
t.Run(tc.q, func(t *testing.T) {
|
|
results, err := indexer.SearchTxEvents(ctx, query.MustCompile(tc.q))
|
|
require.NoError(t, err)
|
|
for _, txr := range results {
|
|
for _, tr := range tc.results {
|
|
assert.True(t, proto.Equal(tr, txr))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTxSearchOneTxWithMultipleSameTagsButDifferentValues(t *testing.T) {
|
|
indexer := NewEventSink(dbm.NewMemDB())
|
|
|
|
txResult := txResultWithEvents([]abci.Event{
|
|
{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}}},
|
|
{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "2", Index: true}}},
|
|
})
|
|
|
|
err := indexer.IndexTxEvents([]*abci.TxResult{txResult})
|
|
require.NoError(t, err)
|
|
|
|
ctx := context.Background()
|
|
|
|
results, err := indexer.SearchTxEvents(ctx, query.MustCompile(`account.number >= 1`))
|
|
assert.NoError(t, err)
|
|
|
|
assert.Len(t, results, 1)
|
|
for _, txr := range results {
|
|
assert.True(t, proto.Equal(txResult, txr))
|
|
}
|
|
}
|
|
|
|
func TestTxSearchMultipleTxs(t *testing.T) {
|
|
indexer := NewEventSink(dbm.NewMemDB())
|
|
|
|
// indexed first, but bigger height (to test the order of transactions)
|
|
txResult := txResultWithEvents([]abci.Event{
|
|
{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}}},
|
|
})
|
|
|
|
txResult.Tx = types.Tx("Bob's account")
|
|
txResult.Height = 2
|
|
txResult.Index = 1
|
|
err := indexer.IndexTxEvents([]*abci.TxResult{txResult})
|
|
require.NoError(t, err)
|
|
|
|
// indexed second, but smaller height (to test the order of transactions)
|
|
txResult2 := txResultWithEvents([]abci.Event{
|
|
{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "2", Index: true}}},
|
|
})
|
|
txResult2.Tx = types.Tx("Alice's account")
|
|
txResult2.Height = 1
|
|
txResult2.Index = 2
|
|
|
|
err = indexer.IndexTxEvents([]*abci.TxResult{txResult2})
|
|
require.NoError(t, err)
|
|
|
|
// indexed third (to test the order of transactions)
|
|
txResult3 := txResultWithEvents([]abci.Event{
|
|
{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "3", Index: true}}},
|
|
})
|
|
txResult3.Tx = types.Tx("Jack's account")
|
|
txResult3.Height = 1
|
|
txResult3.Index = 1
|
|
err = indexer.IndexTxEvents([]*abci.TxResult{txResult3})
|
|
require.NoError(t, err)
|
|
|
|
// indexed fourth (to test we don't include txs with similar events)
|
|
// https://github.com/tendermint/tendermint/issues/2908
|
|
txResult4 := txResultWithEvents([]abci.Event{
|
|
{Type: "account", Attributes: []abci.EventAttribute{{Key: "number.id", Value: "1", Index: true}}},
|
|
})
|
|
txResult4.Tx = types.Tx("Mike's account")
|
|
txResult4.Height = 2
|
|
txResult4.Index = 2
|
|
err = indexer.IndexTxEvents([]*abci.TxResult{txResult4})
|
|
require.NoError(t, err)
|
|
|
|
ctx := context.Background()
|
|
|
|
results, err := indexer.SearchTxEvents(ctx, query.MustCompile(`account.number >= 1`))
|
|
assert.NoError(t, err)
|
|
|
|
require.Len(t, results, 3)
|
|
}
|
|
|
|
func txResultWithEvents(events []abci.Event) *abci.TxResult {
|
|
tx := types.Tx("HELLO WORLD")
|
|
return &abci.TxResult{
|
|
Height: 1,
|
|
Index: 0,
|
|
Tx: tx,
|
|
Result: abci.ResponseDeliverTx{
|
|
Data: []byte{0},
|
|
Code: abci.CodeTypeOK,
|
|
Log: "",
|
|
Events: events,
|
|
},
|
|
}
|
|
}
|