Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 30af8c2

Browse files
jmaliceviclasarojcsergio-menathanethomson
authoredMay 11, 2023
Backport of PR #771 to 0.34 : big int parsing (#798)
Replaced int64 with big.int Co-authored-by: Lasaro <[email protected]> Co-authored-by: Sergio Mena <[email protected]> Co-authored-by: Thane Thomson <[email protected]>
1 parent 82c6c08 commit 30af8c2

File tree

13 files changed

+399
-64
lines changed

13 files changed

+399
-64
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- `[state/kvindex]` Querying event attributes that are bigger than int64 is now enabled.
2+
([\#771](https://github.com/cometbft/cometbft/pull/771))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- `[pubsub]` Pubsub queries are now able to parse big integers (larger than int64). Very big floats
2+
are also properly parsed into very big integers instead of being truncated to int64.
3+
([\#771](https://github.com/cometbft/cometbft/pull/771))

‎docs/app-dev/indexing-transactions.md

+11
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,14 @@ is ignored and the data is retrieved as if `match_events=false`.
267267
Additionally, if a node that was running Tendermint Core
268268
when the data was first indexed, and switched to CometBFT, is queried, it will retrieve this previously indexed
269269
data as if `match_events=false` (attributes can match the query conditions across different events on the same height).
270+
271+
272+
# Event attribute value types
273+
274+
Users can use anything as an event value. However, if the event attribute value is a number, the following restrictions apply:
275+
276+
- Negative numbers will not be properly retrieved when querying the indexer
277+
- When querying the events using `tx_search` and `block_search`, the value given as part of the condition cannot be a float.
278+
- Any event value retrieved from the database will be represented as a `BigInt` (from `math/big`)
279+
- Floating point values are not read from the database even with the introduction of `BigInt`. This was intentionally done
280+
to keep the same beheaviour as was historically present and not introduce breaking changes. This will be fixed in the 0.38 series.

‎docs/core/subscription.md

+14
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,20 @@ You can also use tags, given you had included them into DeliverTx
4040
response, to query transaction results. See [Indexing
4141
transactions](./indexing-transactions.md) for details.
4242

43+
44+
## Query parameter and event type restrictions
45+
46+
While CometBFT imposes no restrictions on the application with regards to the type of
47+
the event output, there are several restrictions when it comes to querying
48+
events whose attribute values are numeric.
49+
50+
- Queries cannot include negative numbers
51+
- If floating points are compared to integers, they are converted to an integer
52+
- Floating point to floating point comparison leads to a loss of precision for very big floating point numbers
53+
(e.g., `10000000000000000000.0` is treated the same as `10000000000000000000.6`)
54+
- When floating points do get converted to integers, they are always rounded down.
55+
This has been done to preserve the behaviour present before introducing the support for BigInts in the query parameters.
56+
4357
## ValidatorSetUpdates
4458

4559
When validator set changes, ValidatorSetUpdates event is published. The

‎libs/pubsub/query/query.go

+60-41
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ package query
1111

1212
import (
1313
"fmt"
14+
"math/big"
1415
"reflect"
1516
"regexp"
1617
"strconv"
@@ -151,16 +152,17 @@ func (q *Query) Conditions() ([]Condition, error) {
151152

152153
conditions = append(conditions, Condition{eventAttr, op, value})
153154
} else {
154-
value, err := strconv.ParseInt(number, 10, 64)
155-
if err != nil {
156-
err = fmt.Errorf(
157-
"got %v while trying to parse %s as int64 (should never happen if the grammar is correct)",
158-
err, number,
155+
valueBig := new(big.Int)
156+
_, ok := valueBig.SetString(number, 10)
157+
if !ok {
158+
err := fmt.Errorf(
159+
"problem parsing %s as bigint (should never happen if the grammar is correct)",
160+
number,
159161
)
160162
return nil, err
161163
}
164+
conditions = append(conditions, Condition{eventAttr, op, valueBig})
162165

163-
conditions = append(conditions, Condition{eventAttr, op, value})
164166
}
165167

166168
case ruletime:
@@ -298,11 +300,12 @@ func (q *Query) Matches(events map[string][]string) (bool, error) {
298300
return false, nil
299301
}
300302
} else {
301-
value, err := strconv.ParseInt(number, 10, 64)
302-
if err != nil {
303-
err = fmt.Errorf(
304-
"got %v while trying to parse %s as int64 (should never happen if the grammar is correct)",
305-
err, number,
303+
value := new(big.Int)
304+
_, ok := value.SetString(number, 10)
305+
if !ok {
306+
err := fmt.Errorf(
307+
"problem parsing %s as bigInt (should never happen if the grammar is correct)",
308+
number,
306309
)
307310
return false, err
308311
}
@@ -451,42 +454,58 @@ func matchValue(value string, op Operator, operand reflect.Value) (bool, error)
451454
return v == operandFloat64, nil
452455
}
453456

454-
case reflect.Int64:
455-
var v int64
457+
case reflect.Pointer:
456458

457-
operandInt := operand.Interface().(int64)
458-
filteredValue := numRegex.FindString(value)
459+
switch operand.Interface().(type) {
460+
case *big.Int:
461+
filteredValue := numRegex.FindString(value)
462+
operandVal := operand.Interface().(*big.Int)
463+
v := new(big.Int)
464+
if strings.ContainsAny(filteredValue, ".") {
465+
// We do this just to check whether the string can be parsed as a float
466+
_, err := strconv.ParseFloat(filteredValue, 64)
467+
if err != nil {
468+
err = fmt.Errorf(
469+
"got %v while trying to parse %s as float64 (should never happen if the grammar is correct)",
470+
err, filteredValue,
471+
)
472+
return false, err
473+
}
459474

460-
// if value looks like float, we try to parse it as float
461-
if strings.ContainsAny(filteredValue, ".") {
462-
v1, err := strconv.ParseFloat(filteredValue, 64)
463-
if err != nil {
464-
return false, fmt.Errorf("failed to convert value %v from event attribute to float64: %w", filteredValue, err)
465-
}
475+
// If yes, we get the int part of the string.
476+
// We could simply cast the float to an int and use that to create a big int but
477+
// if it is a number bigger than int64, it will not be parsed properly.
478+
// If we use bigFloat and convert that to a string, the values will be rounded which
479+
// is not what we want either.
480+
// Here we are simulating the behavior that int64(floatValue). This was the default behavior
481+
// before introducing BigInts and we do not want to break the logic in minor releases.
482+
_, ok := v.SetString(strings.Split(filteredValue, ".")[0], 10)
483+
if !ok {
484+
return false, fmt.Errorf("failed to convert value %s from float to big int", filteredValue)
485+
}
486+
} else {
487+
// try our best to convert value from tags to big int
488+
_, ok := v.SetString(filteredValue, 10)
489+
if !ok {
490+
return false, fmt.Errorf("failed to convert value %v from event attribute to big int", filteredValue)
491+
}
466492

467-
v = int64(v1)
468-
} else {
469-
var err error
470-
// try our best to convert value from tags to int64
471-
v, err = strconv.ParseInt(filteredValue, 10, 64)
472-
if err != nil {
473-
return false, fmt.Errorf("failed to convert value %v from event attribute to int64: %w", filteredValue, err)
474493
}
475-
}
494+
cmpRes := operandVal.Cmp(v)
495+
switch op {
496+
case OpLessEqual:
497+
return cmpRes == 0 || cmpRes == 1, nil
498+
case OpGreaterEqual:
499+
return cmpRes == 0 || cmpRes == -1, nil
500+
case OpLess:
501+
return cmpRes == 1, nil
502+
case OpGreater:
503+
return cmpRes == -1, nil
504+
case OpEqual:
505+
return cmpRes == 0, nil
506+
}
476507

477-
switch op {
478-
case OpLessEqual:
479-
return v <= operandInt, nil
480-
case OpGreaterEqual:
481-
return v >= operandInt, nil
482-
case OpLess:
483-
return v < operandInt, nil
484-
case OpGreater:
485-
return v > operandInt, nil
486-
case OpEqual:
487-
return v == operandInt, nil
488508
}
489-
490509
case reflect.String:
491510
switch op {
492511
case OpEqual:

‎libs/pubsub/query/query_test.go

+74-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package query_test
22

33
import (
44
"fmt"
5+
"math/big"
56
"testing"
67
"time"
78

@@ -11,6 +12,57 @@ import (
1112
"github.com/tendermint/tendermint/libs/pubsub/query"
1213
)
1314

15+
func TestBigNumbers(t *testing.T) {
16+
bigInt := "10000000000000000000"
17+
bigIntAsFloat := "10000000000000000000.0"
18+
bigFloat := "10000000000000000000.6"
19+
bigFloatLowerRounding := "10000000000000000000.1"
20+
doubleBigInt := "20000000000000000000"
21+
22+
testCases := []struct {
23+
s string
24+
events map[string][]string
25+
err bool
26+
matches bool
27+
matchErr bool
28+
}{
29+
30+
{"account.balance <= " + bigInt, map[string][]string{"account.balance": {bigInt}}, false, true, false},
31+
{"account.balance <= " + bigInt, map[string][]string{"account.balance": {bigIntAsFloat}}, false, true, false},
32+
{"account.balance <= " + doubleBigInt, map[string][]string{"account.balance": {bigInt}}, false, true, false},
33+
{"account.balance <= " + bigInt, map[string][]string{"account.balance": {"10000000000000000001"}}, false, false, false},
34+
{"account.balance <= " + doubleBigInt, map[string][]string{"account.balance": {bigFloat}}, false, true, false},
35+
// To maintain compatibility with the old implementation which did a simple cast of float to int64, we do not round the float
36+
// Thus both 10000000000000000000.6 and "10000000000000000000.1 are equal to 10000000000000000000
37+
// and the test does not find a match
38+
{"account.balance > " + bigInt, map[string][]string{"account.balance": {bigFloat}}, false, false, false},
39+
{"account.balance > " + bigInt, map[string][]string{"account.balance": {bigFloatLowerRounding}}, true, false, false},
40+
// This test should also find a match, but floats that are too big cannot be properly converted, thus
41+
// 10000000000000000000.6 gets rounded to 10000000000000000000
42+
{"account.balance > " + bigIntAsFloat, map[string][]string{"account.balance": {bigFloat}}, false, false, false},
43+
{"account.balance > 11234.0", map[string][]string{"account.balance": {"11234.6"}}, false, true, false},
44+
{"account.balance <= " + bigInt, map[string][]string{"account.balance": {"1000.45"}}, false, true, false},
45+
}
46+
47+
for _, tc := range testCases {
48+
q, err := query.New(tc.s)
49+
if !tc.err {
50+
require.Nil(t, err)
51+
}
52+
require.NotNil(t, q, "Query '%s' should not be nil", tc.s)
53+
54+
if tc.matches {
55+
match, err := q.Matches(tc.events)
56+
assert.Nil(t, err, "Query '%s' should not error on match %v", tc.s, tc.events)
57+
assert.True(t, match, "Query '%s' should match %v", tc.s, tc.events)
58+
} else {
59+
match, err := q.Matches(tc.events)
60+
assert.Equal(t, tc.matchErr, err != nil, "Unexpected error for query '%s' match %v", tc.s, tc.events)
61+
assert.False(t, match, "Query '%s' should not match %v", tc.s, tc.events)
62+
}
63+
}
64+
}
65+
1466
func TestMatches(t *testing.T) {
1567
var (
1668
txDate = "2017-01-01"
@@ -180,6 +232,10 @@ func TestConditions(t *testing.T) {
180232
txTime, err := time.Parse(time.RFC3339, "2013-05-03T14:45:00Z")
181233
require.NoError(t, err)
182234

235+
bigInt := new(big.Int)
236+
bigInt, ok := bigInt.SetString("10000000000000000000", 10)
237+
require.True(t, ok)
238+
183239
testCases := []struct {
184240
s string
185241
conditions []query.Condition
@@ -193,8 +249,24 @@ func TestConditions(t *testing.T) {
193249
{
194250
s: "tx.gas > 7 AND tx.gas < 9",
195251
conditions: []query.Condition{
196-
{CompositeKey: "tx.gas", Op: query.OpGreater, Operand: int64(7)},
197-
{CompositeKey: "tx.gas", Op: query.OpLess, Operand: int64(9)},
252+
{CompositeKey: "tx.gas", Op: query.OpGreater, Operand: big.NewInt(7)},
253+
{CompositeKey: "tx.gas", Op: query.OpLess, Operand: big.NewInt(9)},
254+
},
255+
},
256+
{
257+
258+
s: "tx.gas > 7.5 AND tx.gas < 9",
259+
conditions: []query.Condition{
260+
{CompositeKey: "tx.gas", Op: query.OpGreater, Operand: 7.5},
261+
{CompositeKey: "tx.gas", Op: query.OpLess, Operand: big.NewInt(9)},
262+
},
263+
},
264+
{
265+
266+
s: "tx.gas > " + bigInt.String() + " AND tx.gas < 9",
267+
conditions: []query.Condition{
268+
{CompositeKey: "tx.gas", Op: query.OpGreater, Operand: bigInt},
269+
{CompositeKey: "tx.gas", Op: query.OpLess, Operand: big.NewInt(9)},
198270
},
199271
},
200272
{

‎state/indexer/block/kv/kv.go

+9-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"errors"
77
"fmt"
8+
"math/big"
89
"sort"
910
"strconv"
1011
"strings"
@@ -311,9 +312,10 @@ LOOP:
311312
continue
312313
}
313314

314-
if _, ok := qr.AnyBound().(int64); ok {
315-
v, err := strconv.ParseInt(eventValue, 10, 64)
316-
if err != nil {
315+
if _, ok := qr.AnyBound().(*big.Int); ok {
316+
v := new(big.Int)
317+
v, ok := v.SetString(eventValue, 10)
318+
if !ok { // If the number was not int it might be a float but this behavior is kept the same as before the patch
317319
continue LOOP
318320
}
319321

@@ -385,15 +387,16 @@ func (idx *BlockerIndexer) setTmpHeights(tmpHeights map[string][]byte, it dbm.It
385387
}
386388
}
387389

388-
func checkBounds(ranges indexer.QueryRange, v int64) bool {
390+
func checkBounds(ranges indexer.QueryRange, v *big.Int) bool {
389391
include := true
390392
lowerBound := ranges.LowerBoundValue()
391393
upperBound := ranges.UpperBoundValue()
392-
if lowerBound != nil && v < lowerBound.(int64) {
394+
395+
if lowerBound != nil && v.Cmp(lowerBound.(*big.Int)) == -1 {
393396
include = false
394397
}
395398

396-
if upperBound != nil && v > upperBound.(int64) {
399+
if upperBound != nil && v.Cmp(upperBound.(*big.Int)) == 1 {
397400
include = false
398401
}
399402

‎state/indexer/block/kv/kv_test.go

+129
Original file line numberDiff line numberDiff line change
@@ -358,3 +358,132 @@ func TestBlockIndexerMulti(t *testing.T) {
358358
})
359359
}
360360
}
361+
362+
func TestBigInt(t *testing.T) {
363+
364+
bigInt := "10000000000000000000"
365+
store := db.NewPrefixDB(db.NewMemDB(), []byte("block_events"))
366+
indexer := blockidxkv.New(store)
367+
368+
require.NoError(t, indexer.Index(types.EventDataNewBlockHeader{
369+
Header: types.Header{Height: 1},
370+
ResultBeginBlock: abci.ResponseBeginBlock{
371+
Events: []abci.Event{},
372+
},
373+
ResultEndBlock: abci.ResponseEndBlock{
374+
Events: []abci.Event{
375+
{
376+
Type: "end_event",
377+
Attributes: []abci.EventAttribute{
378+
{
379+
Key: []byte("foo"),
380+
Value: []byte("100"),
381+
Index: true,
382+
},
383+
{
384+
Key: []byte("bar"),
385+
Value: []byte("10000000000000000000.76"),
386+
Index: true,
387+
},
388+
{
389+
Key: []byte("bar_lower"),
390+
Value: []byte("10000000000000000000.1"),
391+
Index: true,
392+
},
393+
},
394+
},
395+
{
396+
Type: "end_event",
397+
Attributes: []abci.EventAttribute{
398+
{
399+
Key: []byte("foo"),
400+
Value: []byte(bigInt),
401+
Index: true,
402+
},
403+
{
404+
Key: []byte("bar"),
405+
Value: []byte("500"),
406+
Index: true,
407+
},
408+
{
409+
Key: []byte("bla"),
410+
Value: []byte("500.5"),
411+
Index: true,
412+
},
413+
},
414+
},
415+
},
416+
},
417+
}))
418+
419+
testCases := map[string]struct {
420+
q *query.Query
421+
results []int64
422+
}{
423+
424+
"query return all events from a height - exact": {
425+
q: query.MustParse("block.height = 1"),
426+
results: []int64{1},
427+
},
428+
"query return all events from a height - exact (deduplicate height)": {
429+
q: query.MustParse("block.height = 1 AND block.height = 2"),
430+
results: []int64{1},
431+
},
432+
"query return all events from a height - range": {
433+
q: query.MustParse("block.height < 2 AND block.height > 0 AND block.height > 0"),
434+
results: []int64{1},
435+
},
436+
"query matches fields with big int and height - no match": {
437+
q: query.MustParse("end_event.foo = " + bigInt + " AND end_event.bar = 500 AND block.height = 2"),
438+
results: []int64{},
439+
},
440+
"query matches fields with big int with less and height - no match": {
441+
q: query.MustParse("end_event.foo <= " + bigInt + " AND end_event.bar = 500 AND block.height = 2"),
442+
results: []int64{},
443+
},
444+
"query matches fields with big int and height - match": {
445+
q: query.MustParse("end_event.foo = " + bigInt + " AND end_event.bar = 500 AND block.height = 1"),
446+
results: []int64{1},
447+
},
448+
"query matches big int in range": {
449+
q: query.MustParse("end_event.foo = " + bigInt),
450+
results: []int64{1},
451+
},
452+
"query matches big int in range with float - does not pass as float is not converted to int": {
453+
q: query.MustParse("end_event.bar >= " + bigInt),
454+
results: []int64{},
455+
},
456+
"query matches big int in range with float - fails because float is converted to int": {
457+
q: query.MustParse("end_event.bar > " + bigInt),
458+
results: []int64{},
459+
},
460+
"query matches big int in range with float lower dec point - fails because float is converted to int": {
461+
q: query.MustParse("end_event.bar_lower > " + bigInt),
462+
results: []int64{},
463+
},
464+
"query matches big int in range with float with less - found": {
465+
q: query.MustParse("end_event.foo <= " + bigInt),
466+
results: []int64{1},
467+
},
468+
"query matches big int in range with float with less with height range - found": {
469+
q: query.MustParse("end_event.foo <= " + bigInt + " AND block.height > 0"),
470+
results: []int64{1},
471+
},
472+
"query matches big int in range with float with less - not found": {
473+
q: query.MustParse("end_event.foo < " + bigInt + " AND end_event.foo > 100"),
474+
results: []int64{},
475+
},
476+
"query does not parse float": {
477+
q: query.MustParse("end_event.bla >= 500"),
478+
results: []int64{},
479+
},
480+
}
481+
for name, tc := range testCases {
482+
tc := tc
483+
t.Run(name, func(t *testing.T) {
484+
results, err := indexer.Search(context.Background(), tc.q)
485+
require.NoError(t, err)
486+
require.Equal(t, tc.results, results)
487+
})
488+
}
489+
}

‎state/indexer/block/kv/util.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package kv
33
import (
44
"encoding/binary"
55
"fmt"
6+
"math/big"
67
"strconv"
78

89
"github.com/google/orderedcode"
@@ -135,7 +136,7 @@ func parseEventSeqFromEventKey(key []byte) (int64, error) {
135136
func lookForHeight(conditions []query.Condition) (int64, bool, int) {
136137
for i, c := range conditions {
137138
if c.CompositeKey == types.BlockHeightKey && c.Op == query.OpEqual {
138-
return c.Operand.(int64), true, i
139+
return c.Operand.(*big.Int).Int64(), true, i
139140
}
140141
}
141142

@@ -159,7 +160,7 @@ func dedupHeight(conditions []query.Condition) (dedupConditions []query.Conditio
159160
continue
160161
} else {
161162
heightCondition = append(heightCondition, c)
162-
heightInfo.height = c.Operand.(int64)
163+
heightInfo.height = c.Operand.(*big.Int).Int64() // As height is assumed to always be int64
163164
found = true
164165
}
165166
} else {
@@ -196,7 +197,7 @@ func dedupMatchEvents(conditions []query.Condition) ([]query.Condition, bool) {
196197
for i, c := range conditions {
197198
if c.CompositeKey == types.MatchEventKey {
198199
// Match events should be added only via RPC as the very first query condition
199-
if i == 0 && c.Op == query.OpEqual && c.Operand.(int64) == 1 {
200+
if i == 0 && c.Op == query.OpEqual && c.Operand.(*big.Int).Int64() == 1 {
200201
dedupConditions = append(dedupConditions, c)
201202
matchEvents = true
202203
}
@@ -210,7 +211,7 @@ func dedupMatchEvents(conditions []query.Condition) ([]query.Condition, bool) {
210211

211212
func checkHeightConditions(heightInfo HeightInfo, keyHeight int64) bool {
212213
if heightInfo.heightRange.Key != "" {
213-
if !checkBounds(heightInfo.heightRange, keyHeight) {
214+
if !checkBounds(heightInfo.heightRange, big.NewInt(keyHeight)) {
214215
return false
215216
}
216217
} else {

‎state/indexer/query_range.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package indexer
22

33
import (
4+
"math/big"
45
"time"
56

67
"github.com/tendermint/tendermint/libs/pubsub/query"
@@ -44,6 +45,9 @@ func (qr QueryRange) LowerBoundValue() interface{} {
4445
switch t := qr.LowerBound.(type) {
4546
case int64:
4647
return t + 1
48+
case *big.Int:
49+
tmp := new(big.Int)
50+
return tmp.Add(t, big.NewInt(1))
4751

4852
case time.Time:
4953
return t.Unix() + 1
@@ -67,7 +71,9 @@ func (qr QueryRange) UpperBoundValue() interface{} {
6771
switch t := qr.UpperBound.(type) {
6872
case int64:
6973
return t - 1
70-
74+
case *big.Int:
75+
tmp := new(big.Int)
76+
return tmp.Sub(t, big.NewInt(1))
7177
case time.Time:
7278
return t.Unix() - 1
7379

‎state/txindex/kv/kv.go

+10-7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"encoding/hex"
77
"fmt"
8+
"math/big"
89
"strconv"
910
"strings"
1011

@@ -349,7 +350,7 @@ func lookForHash(conditions []query.Condition) (hash []byte, ok bool, err error)
349350
func lookForHeight(conditions []query.Condition) (height int64, heightIdx int) {
350351
for i, c := range conditions {
351352
if c.CompositeKey == types.TxHeightKey && c.Op == query.OpEqual {
352-
return c.Operand.(int64), i
353+
return c.Operand.(*big.Int).Int64(), i
353354
}
354355
}
355356
return 0, -1
@@ -551,9 +552,11 @@ LOOP:
551552
continue
552553
}
553554

554-
if _, ok := qr.AnyBound().(int64); ok {
555-
v, err := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64)
556-
if err != nil {
555+
if _, ok := qr.AnyBound().(*big.Int); ok {
556+
v := new(big.Int)
557+
eventValue := extractValueFromKey(it.Key())
558+
v, ok := v.SetString(eventValue, 10)
559+
if !ok {
557560
continue LOOP
558561
}
559562

@@ -693,15 +696,15 @@ func startKey(fields ...interface{}) []byte {
693696
return b.Bytes()
694697
}
695698

696-
func checkBounds(ranges indexer.QueryRange, v int64) bool {
699+
func checkBounds(ranges indexer.QueryRange, v *big.Int) bool {
697700
include := true
698701
lowerBound := ranges.LowerBoundValue()
699702
upperBound := ranges.UpperBoundValue()
700-
if lowerBound != nil && v < lowerBound.(int64) {
703+
if lowerBound != nil && v.Cmp(lowerBound.(*big.Int)) == -1 {
701704
include = false
702705
}
703706

704-
if upperBound != nil && v > upperBound.(int64) {
707+
if upperBound != nil && v.Cmp(upperBound.(*big.Int)) == 1 {
705708
include = false
706709
}
707710

‎state/txindex/kv/kv_test.go

+71
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,77 @@ import (
1919
"github.com/tendermint/tendermint/types"
2020
)
2121

22+
func TestBigInt(t *testing.T) {
23+
indexer := NewTxIndex(db.NewMemDB())
24+
25+
bigInt := "10000000000000000000"
26+
bigIntPlus1 := "10000000000000000001"
27+
bigFloat := bigInt + ".76"
28+
bigFloatLower := bigInt + ".1"
29+
30+
txResult := txResultWithEvents([]abci.Event{
31+
{Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte(bigInt), Index: true}}},
32+
{Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte(bigIntPlus1), Index: true}}},
33+
{Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte(bigFloatLower), Index: true}}},
34+
{Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("owner"), Value: []byte("/Ivan/"), Index: true}}},
35+
{Type: "", Attributes: []abci.EventAttribute{{Key: []byte("not_allowed"), Value: []byte("Vlad"), Index: true}}},
36+
})
37+
hash := types.Tx(txResult.Tx).Hash()
38+
39+
err := indexer.Index(txResult)
40+
41+
require.NoError(t, err)
42+
43+
txResult2 := txResultWithEvents([]abci.Event{
44+
{Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte(bigFloat), Index: true}}},
45+
{Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte(bigFloat), Index: true}, {Key: []byte("amount"), Value: []byte("5"), Index: true}}},
46+
})
47+
48+
txResult2.Tx = types.Tx("NEW TX")
49+
txResult2.Height = 2
50+
txResult2.Index = 2
51+
52+
hash2 := types.Tx(txResult2.Tx).Hash()
53+
54+
err = indexer.Index(txResult2)
55+
require.NoError(t, err)
56+
testCases := []struct {
57+
q string
58+
txRes *abci.TxResult
59+
resultsLength int
60+
}{
61+
// search by hash
62+
{fmt.Sprintf("tx.hash = '%X'", hash), txResult, 1},
63+
// search by hash (lower)
64+
{fmt.Sprintf("tx.hash = '%x'", hash), txResult, 1},
65+
{fmt.Sprintf("tx.hash = '%x'", hash2), txResult2, 1},
66+
// search by exact match (one key) - bigint
67+
{"match.events = 1 AND account.number >= " + bigInt, nil, 1},
68+
// search by exact match (one key) - bigint range
69+
{"match.events = 1 AND account.number >= " + bigInt + " AND tx.height > 0", nil, 1},
70+
{"match.events = 1 AND account.number >= " + bigInt + " AND tx.height > 0 AND account.owner = '/Ivan/'", nil, 0},
71+
// Floats are not parsed
72+
{"match.events = 1 AND account.number >= " + bigInt + " AND tx.height > 0 AND account.amount > 4", txResult2, 0},
73+
{"match.events = 1 AND account.number >= " + bigInt + " AND tx.height > 0 AND account.amount = 5", txResult2, 0},
74+
{"match.events = 1 AND account.number >= " + bigInt + " AND account.amount <= 5", txResult2, 0},
75+
{"match.events = 1 AND account.number < " + bigInt + " AND tx.height = 1", nil, 0},
76+
}
77+
78+
ctx := context.Background()
79+
80+
for _, tc := range testCases {
81+
tc := tc
82+
t.Run(tc.q, func(t *testing.T) {
83+
results, err := indexer.Search(ctx, query.MustParse(tc.q))
84+
assert.NoError(t, err)
85+
assert.Len(t, results, tc.resultsLength)
86+
if tc.resultsLength > 0 && tc.txRes != nil {
87+
assert.True(t, proto.Equal(results[0], tc.txRes))
88+
}
89+
})
90+
}
91+
}
92+
2293
func TestTxIndex(t *testing.T) {
2394
indexer := NewTxIndex(db.NewMemDB())
2495

‎state/txindex/kv/utils.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package kv
22

33
import (
44
"fmt"
5+
"math/big"
56

67
"github.com/google/orderedcode"
78
"github.com/tendermint/tendermint/libs/pubsub/query"
@@ -33,7 +34,7 @@ func dedupMatchEvents(conditions []query.Condition) ([]query.Condition, bool) {
3334
for i, c := range conditions {
3435
if c.CompositeKey == types.MatchEventKey {
3536
// Match events should be added only via RPC as the very first query condition
36-
if i == 0 && c.Op == query.OpEqual && c.Operand.(int64) == 1 {
37+
if i == 0 && c.Op == query.OpEqual && c.Operand.(*big.Int).Int64() == 1 {
3738
dedupConditions = append(dedupConditions, c)
3839
matchEvents = true
3940
}
@@ -79,7 +80,7 @@ func dedupHeight(conditions []query.Condition) (dedupConditions []query.Conditio
7980
} else {
8081
found = true
8182
heightCondition = append(heightCondition, c)
82-
heightInfo.height = c.Operand.(int64)
83+
heightInfo.height = c.Operand.(*big.Int).Int64() //Height is always int64
8384
}
8485
} else {
8586
heightInfo.onlyHeightEq = false
@@ -110,7 +111,7 @@ func dedupHeight(conditions []query.Condition) (dedupConditions []query.Conditio
110111

111112
func checkHeightConditions(heightInfo HeightInfo, keyHeight int64) bool {
112113
if heightInfo.heightRange.Key != "" {
113-
if !checkBounds(heightInfo.heightRange, keyHeight) {
114+
if !checkBounds(heightInfo.heightRange, big.NewInt(keyHeight)) {
114115
return false
115116
}
116117
} else {

0 commit comments

Comments
 (0)
Please sign in to comment.