From c1dee6cd7e620d55617f8818d9b45b364bad8b48 Mon Sep 17 00:00:00 2001 From: Rens Rooimans Date: Thu, 21 Sep 2023 10:40:21 +0200 Subject: [PATCH] add IndexedLogsByTxHash & index (#10731) * add IndexedLogsByTxHash & index * add orm test * don't use brin --- core/chains/evm/logpoller/disabled.go | 4 ++ core/chains/evm/logpoller/log_poller.go | 5 ++ core/chains/evm/logpoller/mocks/log_poller.go | 33 ++++++++++ core/chains/evm/logpoller/observability.go | 6 ++ core/chains/evm/logpoller/orm.go | 16 +++++ core/chains/evm/logpoller/orm_test.go | 66 +++++++++++++++++++ .../0196_add_txhash_index_evm_logs.sql | 5 ++ 7 files changed, 135 insertions(+) create mode 100644 core/store/migrate/migrations/0196_add_txhash_index_evm_logs.sql diff --git a/core/chains/evm/logpoller/disabled.go b/core/chains/evm/logpoller/disabled.go index c561c1fabcd..d6fb2b7cb0d 100644 --- a/core/chains/evm/logpoller/disabled.go +++ b/core/chains/evm/logpoller/disabled.go @@ -69,6 +69,10 @@ func (disabled) IndexedLogsByBlockRange(start, end int64, eventSig common.Hash, return nil, ErrDisabled } +func (d disabled) IndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { + return nil, ErrDisabled +} + func (disabled) IndexedLogsTopicGreaterThan(eventSig common.Hash, address common.Address, topicIndex int, topicValueMin common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { return nil, ErrDisabled } diff --git a/core/chains/evm/logpoller/log_poller.go b/core/chains/evm/logpoller/log_poller.go index 81634add35a..d6e55286029 100644 --- a/core/chains/evm/logpoller/log_poller.go +++ b/core/chains/evm/logpoller/log_poller.go @@ -52,6 +52,7 @@ type LogPoller interface { IndexedLogs(eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) IndexedLogsByBlockRange(start, end int64, eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, qopts ...pg.QOpt) ([]Log, error) IndexedLogsCreatedAfter(eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, after time.Time, confs int, qopts ...pg.QOpt) ([]Log, error) + IndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) IndexedLogsTopicGreaterThan(eventSig common.Hash, address common.Address, topicIndex int, topicValueMin common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) IndexedLogsTopicRange(eventSig common.Hash, address common.Address, topicIndex int, topicValueMin common.Hash, topicValueMax common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) IndexedLogsWithSigsExcluding(address common.Address, eventSigA, eventSigB common.Hash, topicIndex int, fromBlock, toBlock int64, confs int, qopts ...pg.QOpt) ([]Log, error) @@ -961,6 +962,10 @@ func (lp *logPoller) IndexedLogsCreatedAfter(eventSig common.Hash, address commo return lp.orm.SelectIndexedLogsCreatedAfter(address, eventSig, topicIndex, topicValues, after, confs, qopts...) } +func (lp *logPoller) IndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { + return lp.orm.SelectIndexedLogsByTxHash(eventSig, txHash, qopts...) +} + // LogsDataWordGreaterThan note index is 0 based. func (lp *logPoller) LogsDataWordGreaterThan(eventSig common.Hash, address common.Address, wordIndex int, wordValueMin common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { return lp.orm.SelectDataWordGreaterThan(address, eventSig, wordIndex, wordValueMin, confs, qopts...) diff --git a/core/chains/evm/logpoller/mocks/log_poller.go b/core/chains/evm/logpoller/mocks/log_poller.go index 455a6a146fd..39d37f31a32 100644 --- a/core/chains/evm/logpoller/mocks/log_poller.go +++ b/core/chains/evm/logpoller/mocks/log_poller.go @@ -164,6 +164,39 @@ func (_m *LogPoller) IndexedLogsByBlockRange(start int64, end int64, eventSig co return r0, r1 } +// IndexedLogsByTxHash provides a mock function with given fields: eventSig, txHash, qopts +func (_m *LogPoller) IndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]logpoller.Log, error) { + _va := make([]interface{}, len(qopts)) + for _i := range qopts { + _va[_i] = qopts[_i] + } + var _ca []interface{} + _ca = append(_ca, eventSig, txHash) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 []logpoller.Log + var r1 error + if rf, ok := ret.Get(0).(func(common.Hash, common.Hash, ...pg.QOpt) ([]logpoller.Log, error)); ok { + return rf(eventSig, txHash, qopts...) + } + if rf, ok := ret.Get(0).(func(common.Hash, common.Hash, ...pg.QOpt) []logpoller.Log); ok { + r0 = rf(eventSig, txHash, qopts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]logpoller.Log) + } + } + + if rf, ok := ret.Get(1).(func(common.Hash, common.Hash, ...pg.QOpt) error); ok { + r1 = rf(eventSig, txHash, qopts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // IndexedLogsCreatedAfter provides a mock function with given fields: eventSig, address, topicIndex, topicValues, after, confs, qopts func (_m *LogPoller) IndexedLogsCreatedAfter(eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash, after time.Time, confs int, qopts ...pg.QOpt) ([]logpoller.Log, error) { _va := make([]interface{}, len(qopts)) diff --git a/core/chains/evm/logpoller/observability.go b/core/chains/evm/logpoller/observability.go index 38a4ca2143a..8dfa6e81d0d 100644 --- a/core/chains/evm/logpoller/observability.go +++ b/core/chains/evm/logpoller/observability.go @@ -110,6 +110,12 @@ func (o *ObservedLogPoller) IndexedLogsCreatedAfter(eventSig common.Hash, addres }) } +func (o *ObservedLogPoller) IndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { + return withObservedQueryAndResults(o, "IndexedLogsByTxHash", func() ([]Log, error) { + return o.LogPoller.IndexedLogsByTxHash(eventSig, txHash, qopts...) + }) +} + func (o *ObservedLogPoller) IndexedLogsTopicGreaterThan(eventSig common.Hash, address common.Address, topicIndex int, topicValueMin common.Hash, confs int, qopts ...pg.QOpt) ([]Log, error) { return withObservedQueryAndResults(o, "IndexedLogsTopicGreaterThan", func() ([]Log, error) { return o.LogPoller.IndexedLogsTopicGreaterThan(eventSig, address, topicIndex, topicValueMin, confs, qopts...) diff --git a/core/chains/evm/logpoller/orm.go b/core/chains/evm/logpoller/orm.go index 3a0fb52cd6c..c062ef3e080 100644 --- a/core/chains/evm/logpoller/orm.go +++ b/core/chains/evm/logpoller/orm.go @@ -516,6 +516,22 @@ func (o *ORM) SelectIndexedLogsCreatedAfter(address common.Address, eventSig com return logs, nil } +func (o *ORM) SelectIndexedLogsByTxHash(eventSig common.Hash, txHash common.Hash, qopts ...pg.QOpt) ([]Log, error) { + q := o.q.WithOpts(qopts...) + var logs []Log + err := q.Select(&logs, ` + SELECT * FROM evm.logs + WHERE evm.logs.evm_chain_id = $1 + AND tx_hash = $2 + AND event_sig = $3 + ORDER BY (evm.logs.block_number, evm.logs.log_index)`, + utils.NewBig(o.chainID), txHash.Bytes(), eventSig.Bytes()) + if err != nil { + return nil, err + } + return logs, nil +} + // SelectIndexedLogsWithSigsExcluding query's for logs that have signature A and exclude logs that have a corresponding signature B, matching is done based on the topic index both logs should be inside the block range and have the minimum number of confirmations func (o *ORM) SelectIndexedLogsWithSigsExcluding(sigA, sigB common.Hash, topicIndex int, address common.Address, startBlock, endBlock int64, confs int, qopts ...pg.QOpt) ([]Log, error) { if err := validateTopicIndex(topicIndex); err != nil { diff --git a/core/chains/evm/logpoller/orm_test.go b/core/chains/evm/logpoller/orm_test.go index e8b5a90ce0c..28f3e8da8e6 100644 --- a/core/chains/evm/logpoller/orm_test.go +++ b/core/chains/evm/logpoller/orm_test.go @@ -502,6 +502,72 @@ func TestORM_IndexedLogs(t *testing.T) { assert.Equal(t, 1, len(lgs)) } +func TestORM_SelectIndexedLogsByTxHash(t *testing.T) { + th := SetupTH(t, 0, 3, 2) + o1 := th.ORM + eventSig := common.HexToHash("0x1599") + txHash := common.HexToHash("0x1888") + addr := common.HexToAddress("0x1234") + + require.NoError(t, o1.InsertBlock(common.HexToHash("0x1"), 1, time.Now())) + logs := []logpoller.Log{ + { + EvmChainId: utils.NewBig(th.ChainID), + LogIndex: int64(0), + BlockHash: common.HexToHash("0x1"), + BlockNumber: int64(1), + EventSig: eventSig, + Topics: [][]byte{eventSig[:]}, + Address: addr, + TxHash: txHash, + Data: logpoller.EvmWord(1).Bytes(), + }, + { + EvmChainId: utils.NewBig(th.ChainID), + LogIndex: int64(1), + BlockHash: common.HexToHash("0x1"), + BlockNumber: int64(1), + EventSig: eventSig, + Topics: [][]byte{eventSig[:]}, + Address: addr, + TxHash: txHash, + Data: append(logpoller.EvmWord(2).Bytes(), logpoller.EvmWord(3).Bytes()...), + }, + // Different txHash + { + EvmChainId: utils.NewBig(th.ChainID), + LogIndex: int64(2), + BlockHash: common.HexToHash("0x1"), + BlockNumber: int64(1), + EventSig: eventSig, + Topics: [][]byte{eventSig[:]}, + Address: addr, + TxHash: common.HexToHash("0x1889"), + Data: append(logpoller.EvmWord(2).Bytes(), logpoller.EvmWord(3).Bytes()...), + }, + // Different eventSig + { + EvmChainId: utils.NewBig(th.ChainID), + LogIndex: int64(3), + BlockHash: common.HexToHash("0x1"), + BlockNumber: int64(1), + EventSig: common.HexToHash("0x1600"), + Topics: [][]byte{eventSig[:]}, + Address: addr, + TxHash: txHash, + Data: append(logpoller.EvmWord(2).Bytes(), logpoller.EvmWord(3).Bytes()...), + }, + } + require.NoError(t, o1.InsertLogs(logs)) + + retrievedLogs, err := o1.SelectIndexedLogsByTxHash(eventSig, txHash) + require.NoError(t, err) + + require.Equal(t, 2, len(retrievedLogs)) + require.Equal(t, retrievedLogs[0].LogIndex, logs[0].LogIndex) + require.Equal(t, retrievedLogs[1].LogIndex, logs[1].LogIndex) +} + func TestORM_DataWords(t *testing.T) { th := SetupTH(t, 2, 3, 2) o1 := th.ORM diff --git a/core/store/migrate/migrations/0196_add_txhash_index_evm_logs.sql b/core/store/migrate/migrations/0196_add_txhash_index_evm_logs.sql new file mode 100644 index 00000000000..a0bfe31dd9a --- /dev/null +++ b/core/store/migrate/migrations/0196_add_txhash_index_evm_logs.sql @@ -0,0 +1,5 @@ +-- +goose Up +create index evm_logs_idx_tx_hash on evm.logs (tx_hash); + +-- +goose Down +DROP INDEX IF EXISTS evm_logs_idx_tx_hash; \ No newline at end of file