Skip to content
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
17 changes: 8 additions & 9 deletions api/events_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,18 +233,17 @@ func ConvertRange(chain *chain.Chain, r *Range) (*logdb.Range, error) {
return &emptyRange, nil
}

// open-ended and oversized ranges end a few blocks above the head
to := block.Number(chain.HeadID()) + 6
if r.To != nil && *r.To < uint64(to) {
to = uint32(*r.To)
}
from := uint32(0)
if r.From != nil {
from = uint32(*r.From)
}

to := uint32(logdb.MaxBlockNumber)
if r.To != nil && *r.To < logdb.MaxBlockNumber {
to = uint32(*r.To)
if from > to {
return &emptyRange, nil
}

return &logdb.Range{
From: from,
To: to,
}, nil
return &logdb.Range{From: from, To: to}, nil
}
29 changes: 22 additions & 7 deletions api/events_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,7 @@ func testConvertRangeWithBlockRangeTypeWithSwitchedFromAndTo(t *testing.T, chain
convertedRng, err := ConvertRange(chain.Repo().NewBestChain(), rng)

assert.NoError(t, err)
assert.Equal(t, emptyRange.From, convertedRng.From)
assert.Equal(t, uint32(*rng.To), convertedRng.To)
assert.Equal(t, &emptyRange, convertedRng)
}

func testConvertRangeWithTimeRangeTypeLessThenGenesis(t *testing.T, chain *testchain.Chain) {
Expand Down Expand Up @@ -169,6 +168,7 @@ func TestConvertRange_Matrix(t *testing.T) {
bestChain := chain.Repo().NewBestChain()
genesis := chain.GenesisBlock().Header()
bestBlock := chain.Repo().BestBlockSummary().Header
head := bestBlock.Number()

// Cases here focus on what TestEventsTypes (which builds ranges via newRange
// with non-nil fields) does not cover: the JSON path, omitted-field defaulting,
Expand All @@ -186,7 +186,7 @@ func TestConvertRange_Matrix(t *testing.T) {
{
name: "empty object returns full block range",
json: `{}`,
expected: &logdb.Range{From: 0, To: uint32(logdb.MaxBlockNumber)},
expected: &logdb.Range{From: 0, To: head + 6},
},

// Block range - omitted-field defaulting and boundary capping.
Expand All @@ -196,19 +196,34 @@ func TestConvertRange_Matrix(t *testing.T) {
expected: &logdb.Range{From: 0, To: 10},
},
{
name: "block range with omitted to defaults to MaxBlockNumber",
name: "block range with omitted to defaults to head+6",
json: `{"unit": "block", "from": 5}`,
expected: &logdb.Range{From: 5, To: uint32(logdb.MaxBlockNumber)},
expected: &logdb.Range{From: 5, To: head + 6},
},
{
name: "block range at MaxBlockNumber",
json: `{"unit": "block", "from": 268435455, "to": 268435455}`,
expected: &logdb.Range{From: uint32(logdb.MaxBlockNumber), To: uint32(logdb.MaxBlockNumber)},
expected: &emptyRange,
},
{
name: "block range to exceeds MaxBlockNumber is capped",
json: `{"unit": "block", "from": 5, "to": 999999999999}`,
expected: &logdb.Range{From: 5, To: uint32(logdb.MaxBlockNumber)},
expected: &logdb.Range{From: 5, To: head + 6},
},
{
name: "block range to above head+6 is capped",
json: fmt.Sprintf(`{"unit": "block", "from": 0, "to": %d}`, head+7),
expected: &logdb.Range{From: 0, To: head + 6},
},
{
name: "block range from above head+6 is empty",
json: fmt.Sprintf(`{"unit": "block", "from": %d}`, head+7),
expected: &emptyRange,
},
{
name: "block range to below head is kept",
json: `{"unit": "block", "from": 0, "to": 0}`,
expected: &logdb.Range{From: 0, To: 0},
},

// Time range - omitted-field defaulting.
Expand Down
69 changes: 43 additions & 26 deletions logdb/logdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"encoding/hex"
"fmt"
"math/big"
"strings"
"time"

"github.com/vechain/thor/v2/block"
Expand Down Expand Up @@ -229,25 +230,14 @@ func (db *LogDB) Path() string {
return db.path
}

func (db *LogDB) FilterEvents(ctx context.Context, filter *EventFilter) ([]*Event, error) {
const query = `SELECT e.seq, r0.data, e.blockTime, r1.data, r2.data, e.clauseIndex, r3.data, r4.data, r5.data, r6.data, r7.data, r8.data, e.data
FROM (%v) e
LEFT JOIN ref r0 ON e.blockID = r0.id
LEFT JOIN ref r1 ON e.txID = r1.id
LEFT JOIN ref r2 ON e.txOrigin = r2.id
LEFT JOIN ref r3 ON e.address = r3.id
LEFT JOIN ref r4 ON e.topic0 = r4.id
LEFT JOIN ref r5 ON e.topic1 = r5.id
LEFT JOIN ref r6 ON e.topic2 = r6.id
LEFT JOIN ref r7 ON e.topic3 = r7.id
LEFT JOIN ref r8 ON e.topic4 = r8.id`

if filter == nil {
return db.queryEvents(ctx, fmt.Sprintf(query, "event"))
}

metricsHandleEventsFilter(filter)
// narrowRange reports whether r spans fewer than maxScanBlocks blocks.
func narrowRange(r *Range) bool {
return r != nil && r.To >= r.From && r.To-r.From < maxScanBlocks
}

// buildEventFilterSubQuery builds the seq-selecting subquery for a non-nil filter. With narrow set,
// criteria whose index lookup the seq range cannot bound are evaluated by a seq range scan instead.
func buildEventFilterSubQuery(filter *EventFilter, narrow bool) (string, []any, error) {
var (
subQuery = "SELECT seq FROM event WHERE 1"
args []any
Expand All @@ -256,11 +246,11 @@ FROM (%v) e
if filter.Range != nil {
from, err := newSequence(filter.Range.From, 0, 0)
if err != nil {
return nil, err
return "", nil, err
}
to, err := newSequence(filter.Range.To, txIndexMask, logIndexMask)
if err != nil {
return nil, err
return "", nil, err
}
// Always bind both bounds. For an inverted range (To < From) this yields
// seq >= from AND seq <= to with to < from — an empty result — instead of
Expand All @@ -270,17 +260,18 @@ FROM (%v) e
}

if len(filter.CriteriaSet) > 0 {
subQuery += " AND ("

var sb strings.Builder
sb.WriteString(" AND (")
for i, c := range filter.CriteriaSet {
cond, cargs := c.toWhereCondition()
cond, cargs := c.toWhereCondition(narrow && !c.seqBounded())
if i > 0 {
subQuery += " OR"
sb.WriteString(" OR")
}
subQuery += " (" + cond + ")"
sb.WriteString(" (" + cond + ")")
args = append(args, cargs...)
}
subQuery += ")"
sb.WriteString(")")
subQuery += sb.String()
}

// Options set: order and LIMIT are applied together in the subquery, so LIMIT takes the ordered window.
Expand All @@ -296,6 +287,32 @@ FROM (%v) e
subQuery += " LIMIT ?, ?"
args = append(args, filter.Options.Offset, filter.Options.Limit)
}
return subQuery, args, nil
}

func (db *LogDB) FilterEvents(ctx context.Context, filter *EventFilter) ([]*Event, error) {
const query = `SELECT e.seq, r0.data, e.blockTime, r1.data, r2.data, e.clauseIndex, r3.data, r4.data, r5.data, r6.data, r7.data, r8.data, e.data
FROM (%v) e
LEFT JOIN ref r0 ON e.blockID = r0.id
LEFT JOIN ref r1 ON e.txID = r1.id
LEFT JOIN ref r2 ON e.txOrigin = r2.id
LEFT JOIN ref r3 ON e.address = r3.id
LEFT JOIN ref r4 ON e.topic0 = r4.id
LEFT JOIN ref r5 ON e.topic1 = r5.id
LEFT JOIN ref r6 ON e.topic2 = r6.id
LEFT JOIN ref r7 ON e.topic3 = r7.id
LEFT JOIN ref r8 ON e.topic4 = r8.id`

if filter == nil {
return db.queryEvents(ctx, fmt.Sprintf(query, "event"))
}

metricsHandleEventsFilter(filter)

subQuery, args, err := buildEventFilterSubQuery(filter, narrowRange(filter.Range))
if err != nil {
return nil, err
}

subQuery = "SELECT e.* FROM (" + subQuery + ") s LEFT JOIN event e ON s.seq = e.seq"

Expand Down
Loading
Loading