Skip to content

Commit

Permalink
Merge pull request #697 from onflow/mpeter/fix-eth-get-logs-response
Browse files Browse the repository at this point in the history
Return empty `Logs` array when block is empty
  • Loading branch information
j1010001 authored Dec 6, 2024
2 parents a540d9c + c93e055 commit b3224f6
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 7 deletions.
11 changes: 10 additions & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,16 @@ func (b *BlockChainAPI) GetLogs(

// if filter provided specific block ID
if criteria.BlockHash != nil {
// Check if the block exists, and return an error if not.
block, err := b.blocks.GetByID(*criteria.BlockHash)
if err != nil {
return nil, err
}
// If the block has no transactions, we can simply return an empty Logs array.
if len(block.TransactionHashes) == 0 {
return []*types.Log{}, nil
}

f, err := logs.NewIDFilter(*criteria.BlockHash, filter, b.blocks, b.receipts)
if err != nil {
return handleError[[]*types.Log](err, l, b.collector)
Expand All @@ -687,7 +697,6 @@ func (b *BlockChainAPI) GetLogs(
}

// otherwise we use the block range as the filter

// assign default values to latest block number, unless provided
from := models.LatestBlockNumber
if criteria.FromBlock != nil {
Expand Down
4 changes: 2 additions & 2 deletions storage/index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,8 +385,8 @@ func (s *ReceiptTestSuite) TestGetReceiptByBlockHeight() {

s.Run("non-existing block height", func() {
retReceipt, err := s.ReceiptIndexer.GetByBlockHeight(1337)
s.Require().Nil(retReceipt)
s.Require().ErrorIs(err, errors.ErrEntityNotFound)
s.Require().NoError(err)
s.Require().Len(retReceipt, 0)
})
}

Expand Down
8 changes: 7 additions & 1 deletion storage/pebble/receipts.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package pebble

import (
"encoding/binary"
"errors"
"fmt"

"github.com/cockroachdb/pebble"
Expand Down Expand Up @@ -113,9 +114,14 @@ func (r *Receipts) GetByBlockHeight(height uint64) ([]*models.Receipt, error) {
}

func (r *Receipts) getByBlockHeight(height []byte) ([]*models.Receipt, error) {

val, err := r.store.get(receiptHeightKey, height)
if err != nil {
// For empty blocks, we do not store transactions & receipts. So when
// we encounter an `ErrEntityNotFound`, we should return an empty
// Receipts array, instead of an error.
if errors.Is(err, errs.ErrEntityNotFound) {
return []*models.Receipt{}, nil
}
return nil, err
}

Expand Down
18 changes: 17 additions & 1 deletion tests/e2e_web3js_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,23 @@ func TestWeb3_E2E(t *testing.T) {
})

t.Run("logs emitting and filtering", func(t *testing.T) {
runWeb3Test(t, "eth_logs_filtering_test")
runWeb3TestWithSetup(t, "eth_logs_filtering_test", func(emu emulator.Emulator) {
// Run an arbitrary transaction, to form an empty EVM block
// through the system chunk transaction. This is needed
// to emulate the `eth_getLogs` by passing the block hash
// of a block without EVM transactions/receipts.
res, err := flowSendTransaction(
emu,
`transaction() {
prepare(signer: auth(Storage) &Account) {
let currentBlock = getCurrentBlock()
assert(currentBlock.height > 0, message: "current block is zero")
}
}`,
)
require.NoError(t, err)
require.NoError(t, res.Error)
})
})

t.Run("test filter-related endpoints", func(t *testing.T) {
Expand Down
90 changes: 88 additions & 2 deletions tests/web3js/eth_logs_filtering_test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,43 @@
const { assert } = require('chai')
const conf = require('./config')
const helpers = require('./helpers')
const web3 = conf.web3

it('emit logs and retrieve them using different filters', async () => {
setTimeout(() => process.exit(1), 19 * 1000) // hack if the ws connection is not closed
let latestBlockNumber = await web3.eth.getBlockNumber()
let latestBlock = await web3.eth.getBlock(latestBlockNumber)

let deployed = await helpers.deployContract("storage")
let blockHashFilter = {
blockHash: latestBlock.hash,
address: ['0x0000000071727de22e5e9d8baf0edac6f37da032'],
topics: ['0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4']
}
let response = await helpers.callRPCMethod('eth_getLogs', [blockHashFilter])
assert.equal(response.status, 200)
assert.isDefined(response.body)
assert.deepEqual(response.body.result, [])

blockHashFilter.blockHash = '0x048641726d25605a990c439b75fcfaa5f6b1691eaa718b72dd71e02a2264f5da'
response = await helpers.callRPCMethod('eth_getLogs', [blockHashFilter])
assert.equal(response.status, 200)
assert.isDefined(response.body.error)
assert.equal(
response.body.error.message,
'failed to get EVM block by ID: ' + blockHashFilter.blockHash + ', with: entity not found'
)

let blockRangeFilter = {
fromBlock: '0x1',
toBlock: 'latest',
address: ['0x0000000071727de22e5e9d8baf0edac6f37da032'],
topics: ['0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4']
}
response = await helpers.callRPCMethod('eth_getLogs', [blockRangeFilter])
assert.equal(response.status, 200)
assert.isDefined(response.body)
assert.deepEqual(response.body.result, [])

let deployed = await helpers.deployContract('storage')
let contractAddress = deployed.receipt.contractAddress

let repeatA = 10
Expand Down Expand Up @@ -81,4 +113,58 @@ it('emit logs and retrieve them using different filters', async () => {
assert.lengthOf(events, 2)
assert.equal(events[0].returnValues.numB, 2)
assert.equal(events[1].returnValues.numB, -2)

latestBlockNumber = await web3.eth.getBlockNumber()
latestBlock = await web3.eth.getBlock(latestBlockNumber)

filterCriteria = {
blockHash: latestBlock.hash,
address: [contractAddress],
topics: [
'0x76efea95e5da1fa661f235b2921ae1d89b99e457ec73fb88e34a1d150f95c64b',
'0x000000000000000000000000facf71692421039876a5bb4f10ef7a439d8ef61e',
'0x000000000000000000000000000000000000000000000000000000000000000a',
'0x0000000000000000000000000000000000000000000000000000000000000190'
]
}
response = await helpers.callRPCMethod('eth_getLogs', [filterCriteria])
assert.equal(response.status, 200)
assert.isDefined(response.body)
assert.deepEqual(
response.body.result,
[
{
address: '0x99a64c993965f8d69f985b5171bc20065cc32fab',
topics: [
'0x76efea95e5da1fa661f235b2921ae1d89b99e457ec73fb88e34a1d150f95c64b',
'0x000000000000000000000000facf71692421039876a5bb4f10ef7a439d8ef61e',
'0x000000000000000000000000000000000000000000000000000000000000000a',
'0x0000000000000000000000000000000000000000000000000000000000000190'
],
data: '0x000000000000000000000000000000000000000000000000000000000000019a',
blockNumber: '0xa',
transactionHash: '0x0c2b2477ab81c9132c5c4fd4f50935bc5807fbf4cf3bf3b69173491b68d2ca8b',
transactionIndex: '0x0',
blockHash: latestBlock.hash,
logIndex: '0x0',
removed: false
}
]
)

blockRangeCriteria = {
fromBlock: web3.utils.numberToHex(latestBlock.number),
toBlock: web3.utils.numberToHex(latestBlock.number),
address: [contractAddress],
topics: [
'0x76efea95e5da1fa661f235b2921ae1d89b99e457ec73fb88e34a1d150f95c64b',
'0x000000000000000000000000facf71692421039876a5bb4f10ef7a439d8ef61e',
'0x000000000000000000000000000000000000000000000000000000000000000a',
'0x0000000000000000000000000000000000000000000000000000000000000190'
]
}
response = await helpers.callRPCMethod('eth_getLogs', [blockRangeFilter])
assert.equal(response.status, 200)
assert.isDefined(response.body)
assert.deepEqual(response.body.result, [])
})

0 comments on commit b3224f6

Please sign in to comment.