Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Index field on receipt logs does not return correct value for blocks with multiple transactions & logs #462

Merged
merged 3 commits into from
Aug 26, 2024
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
3 changes: 2 additions & 1 deletion models/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ func NewCadenceEvents(events flow.BlockEvents) (*CadenceEvents, error) {
return nil, err
}

logIndex := uint(0) // todo revisit if we can reconstruct it simply like this
// Log index field holds the index position in the entire block
logIndex := uint(0)
for i, rcp := range e.receipts {
// add transaction hashes to the block
e.block.TransactionHashes = append(e.block.TransactionHashes, rcp.TxHash)
Expand Down
8 changes: 8 additions & 0 deletions storage/index_testsuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ func (s *ReceiptTestSuite) TestStoreReceipt() {
mocks.NewReceipt(height, common.HexToHash("0x2")),
mocks.NewReceipt(height, common.HexToHash("0x3")),
}
// Log index field holds the index position in the entire block
logIndex := uint(0)
for _, receipt := range receipts {
for _, log := range receipt.Logs {
log.Index = logIndex
logIndex++
}
}

err := s.ReceiptIndexer.Store(receipts, nil)
s.Require().NoError(err)
Expand Down
10 changes: 7 additions & 3 deletions storage/pebble/receipts.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,18 @@ func (r *Receipts) getByBlockHeight(height []byte, batch *pebble.Batch) ([]*mode
)
}

// Log index field holds the index position in the entire block
logIndex := uint(0)
for _, rcp := range receipts {
// dynamically populate the values since they are not stored to save space
for i, l := range rcp.Logs {
l.BlockHash = rcp.BlockHash
for _, l := range rcp.Logs {
l.BlockNumber = rcp.BlockNumber.Uint64()
l.BlockHash = rcp.BlockHash
l.TxHash = rcp.TxHash
l.TxIndex = rcp.TransactionIndex
l.Index = uint(i)
l.Index = logIndex
l.Removed = false
logIndex++
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

damn, nice find

}
}

Expand Down
75 changes: 75 additions & 0 deletions tests/e2e_web3js_test.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
package tests

import (
_ "embed"
"encoding/hex"
"math/big"
"testing"

"github.com/onflow/cadence"
"github.com/onflow/flow-emulator/emulator"
"github.com/onflow/flow-go/fvm/evm/testutils"
"github.com/onflow/go-ethereum/common"
"github.com/onflow/go-ethereum/crypto"
"github.com/stretchr/testify/require"
)

var (
//go:embed fixtures/storage.byte
storageByteCode string

//go:embed fixtures/storageABI.json
storageABI string
)

func TestWeb3_E2E(t *testing.T) {

t.Run("test setup sanity check", func(t *testing.T) {
Expand Down Expand Up @@ -112,6 +122,71 @@ func TestWeb3_E2E(t *testing.T) {
})
})

t.Run("batch run transactions with logs", func(t *testing.T) {
// create multiple transactions that emit logs and batch run them before the test
runWeb3TestWithSetup(t, "eth_batch_tx_logs_test", func(emu emulator.Emulator) {
contractCode, err := hex.DecodeString(storageByteCode)
require.NoError(t, err)
storageContract := testutils.TestContract{
ByteCode: contractCode,
ABI: storageABI,
}

// create test account for contract deployment and contract calls
nonce := uint64(0)
accountKey, err := crypto.HexToECDSA("f6d5333177711e562cabf1f311916196ee6ffc2a07966d9d4628094073bd5442")
require.NoError(t, err)

// contract deployment transaction
deployPayload, _, err := evmSign(big.NewInt(0), 350_000, accountKey, nonce, nil, contractCode)
require.NoError(t, err)
nonce += 1

const batchSize = 6
payloads := make([][]byte, batchSize)
payloads[0] = deployPayload
contractAddress := common.HexToAddress("99a64c993965f8d69f985b5171bc20065cc32fab")
// contract call transactions, these emit logs/events
for i := 1; i <= 5; i++ {
callData := storageContract.MakeCallData(t, "sum", big.NewInt(10), big.NewInt(20))
callPayload, _, err := evmSign(big.NewInt(0), 55_000, accountKey, nonce, &contractAddress, callData)
require.NoError(t, err)
payloads[i] = callPayload
nonce += 1
}
encodedTxs := make([]cadence.Value, batchSize)
for i := range encodedTxs {
tx, err := cadence.NewString(hex.EncodeToString(payloads[i]))
require.NoError(t, err)

encodedTxs[i] = tx
}

res, err := flowSendTransaction(
emu,
`transaction(encodedTxs: [String]) {
prepare(signer: auth(Storage) &Account) {
var txs: [[UInt8]] = []
for enc in encodedTxs {
txs.append(enc.decodeHex())
}

let txResults = EVM.batchRun(
txs: txs,
coinbase: EVM.EVMAddress(bytes: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
)
for txResult in txResults {
assert(txResult.status == EVM.Status.successful, message: "failed to execute tx, error: ".concat(txResult.errorCode.toString()))
}
}
}`,
cadence.NewArray(encodedTxs),
)
require.NoError(t, err)
require.NoError(t, res.Error)
})
})

t.Run("streaming of entities and subscription", func(t *testing.T) {
runWeb3Test(t, "eth_streaming_test")
})
Expand Down
2 changes: 2 additions & 0 deletions tests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-dap v0.11.0 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/uuid v1.6.0 // indirect
Expand Down Expand Up @@ -232,6 +233,7 @@ require (
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools v2.2.0+incompatible // indirect
lukechampine.com/blake3 v1.3.0 // indirect
modernc.org/libc v1.37.6 // indirect
modernc.org/mathutil v1.6.0 // indirect
Expand Down
54 changes: 54 additions & 0 deletions tests/web3js/eth_batch_tx_logs_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const { assert } = require('chai')
const conf = require('./config')
const web3 = conf.web3

it('should retrieve batch transactions with logs', async () => {
// this test relies on the setup of batched transactions with logs found in ../e2e_web3js_test.go

let latestHeight = await web3.eth.getBlockNumber()
let block = await web3.eth.getBlock(latestHeight)

let batchSize = 6
assert.lengthOf(block.transactions, batchSize)

let blockLogs = []

for (let i = 0; i < block.transactions.length; i++) {
let tx = await web3.eth.getTransactionFromBlock(latestHeight, i)
let receipt = await web3.eth.getTransactionReceipt(tx.hash)
assert.equal(receipt.blockNumber, block.number, 'wrong block number')
assert.equal(receipt.blockHash, block.hash, 'wrong block hash')
assert.equal(receipt.type, 0, 'wrong tx type')
assert.equal(receipt.transactionIndex, i, 'wrong tx index')
assert.isBelow(i, batchSize, 'wrong batch size')

// the contract deployment transaction has no logs
if (receipt.logs.length == 0) {
continue
}

for (const log of receipt.logs) {
assert.equal(log.blockNumber, block.number, 'wrong block number')
assert.equal(log.blockHash, block.hash, 'wrong block hash')
assert.equal(log.transactionHash, tx.hash, 'wrong tx hash')
assert.equal(log.transactionIndex, i, 'wrong tx index')
// the 1st transaction contains the contract deployment
assert.equal(log.logIndex, log.transactionIndex - 1n, 'wrong log index')
m-Peter marked this conversation as resolved.
Show resolved Hide resolved
assert.isFalse(log.removed, 'log should not be removed')
assert.equal(log.address, '0x99a64c993965f8d69f985b5171bc20065cc32fab', 'wrong log address')
assert.equal(log.topics.length, 4, 'wrong topics length')
assert.equal(log.data.length, 66, 'wrong data length')

blockLogs.push(log)
}
}

let logs = await web3.eth.getPastLogs({
fromBlock: block.number,
toBlock: block.number
})

for (let i = 0; i < logs; i++) {
assert.deepEqual(logs[i], blockLogs[i])
}
})
1 change: 0 additions & 1 deletion tests/web3js/eth_logs_filtering_test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
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
Expand Down
Loading