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

Add and pass eth_getLogs test #100

Merged
merged 17 commits into from
Apr 23, 2020
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
1 change: 1 addition & 0 deletions packages/core-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"chai-as-promised": "^7.1.1",
"debug": "^4.1.1",
"dotenv": "^8.2.0",
"ethereumjs-util": "^6.2.0",
"ethers": "^4.0.37",
"express": "^4.17.1",
"memdown": "^4.0.0",
Expand Down
84 changes: 84 additions & 0 deletions packages/core-utils/src/app/bloom_filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Thanks to the etherereumjs-vm team for this Bloom Filter implementation! ❤️
// https://github.com/ethereumjs/ethereumjs-vm/blob/master/packages/vm/lib/bloom/index.ts
//
import * as assert from 'assert'
import { zeros, keccak256 } from 'ethereumjs-util'

const BYTE_SIZE = 256

export default class BloomFilter {
public bitvector: Buffer

/**
* Represents a Bloom filter.
*/
constructor(bitvector?: Buffer) {
if (!bitvector) {
this.bitvector = zeros(BYTE_SIZE)
} else {
assert(
bitvector.length === BYTE_SIZE,
'bitvectors must be 2048 bits long'
)
this.bitvector = bitvector
}
}

/**
* Adds an element to a bit vector of a 64 byte bloom filter.
* @param e - The element to add
*/
public add(e: Buffer) {
assert(Buffer.isBuffer(e), 'Element should be buffer')
e = keccak256(e)
const mask = 2047 // binary 11111111111

for (let i = 0; i < 3; i++) {
const first2bytes = e.readUInt16BE(i * 2)
const loc = mask & first2bytes
const byteLoc = loc >> 3
const bitLoc = 1 << loc % 8
this.bitvector[BYTE_SIZE - byteLoc - 1] |= bitLoc
}
}

/**
* Checks if an element is in the bloom.
* @param e - The element to check
*/
public check(e: Buffer): boolean {
assert(Buffer.isBuffer(e), 'Element should be Buffer')
e = keccak256(e)
const mask = 2047 // binary 11111111111
let match = true

for (let i = 0; i < 3 && match; i++) {
const first2bytes = e.readUInt16BE(i * 2)
const loc = mask & first2bytes
const byteLoc = loc >> 3
const bitLoc = 1 << loc % 8
match = (this.bitvector[BYTE_SIZE - byteLoc - 1] & bitLoc) !== 0
}

return Boolean(match)
}

/**
* Checks if multiple topics are in a bloom.
* @returns `true` if every topic is in the bloom
*/
public multiCheck(topics: Buffer[]): boolean {
return topics.every((t: Buffer) => this.check(t))
}

/**
* Bitwise or blooms together.
*/
public or(bloom: BloomFilter) {
if (bloom) {
for (let i = 0; i <= BYTE_SIZE; i++) {
this.bitvector[i] = this.bitvector[i] | bloom.bitvector[i]
}
}
}
}
1 change: 1 addition & 0 deletions packages/core-utils/src/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './serialization'
export * from './transport'

export * from './buffer'
export { default as BloomFilter } from './bloom_filter'
export * from './contract-deployment'
export * from './crypto'
export * from './equals'
Expand Down
9 changes: 8 additions & 1 deletion packages/ovm/src/app/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import {
add0x,
getLogger,
hexStrToBuf,
bufToHexString,
logError,
remove0x,
ZERO_ADDRESS,
BloomFilter,
} from '@eth-optimism/core-utils'
import { ethers } from 'ethers'
import { LogDescription } from 'ethers/utils'
Expand Down Expand Up @@ -198,7 +200,12 @@ export const internalTxReceiptToOvmTxReceipt = async (
}

logger.debug('Ovm parsed logs:', ovmTxReceipt.logs)
// TODO: Fix the logsBloom to remove the txs we just removed
const logsBloom = new BloomFilter()
ovmTxReceipt.logs.forEach((log) => {
logsBloom.add(hexStrToBuf(log.address))
log.topics.forEach((topic) => logsBloom.add(hexStrToBuf(topic)))
})
ovmTxReceipt.logsBloom = bufToHexString(logsBloom.bitvector)

// Return!
return ovmTxReceipt
Expand Down
38 changes: 34 additions & 4 deletions packages/rollup-full-node/src/app/web3-rpc-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import {
} from '@eth-optimism/rollup-core'
import {
add0x,
bufToHexString,
BloomFilter,
getLogger,
hexStrToBuf,
hexStrToNumber,
logError,
numberToHexString,
Expand All @@ -17,6 +20,7 @@ import {
import {
CHAIN_ID,
GAS_LIMIT,
convertInternalLogsToOvmLogs,
internalTxReceiptToOvmTxReceipt,
l2ExecutionManagerInterface,
l2ToL1MessagePasserInterface,
Expand Down Expand Up @@ -146,7 +150,7 @@ export class DefaultWeb3Handler
break
case Web3RpcMethods.getLogs:
args = this.assertParameters(params, 1)
response = await this.getLogs([0])
response = await this.getLogs(args[0])
break
case Web3RpcMethods.getTransactionByHash:
args = this.assertParameters(params, 1)
Expand Down Expand Up @@ -387,6 +391,22 @@ export class DefaultWeb3Handler
)
}

const logsBloom = new BloomFilter()
await Promise.all(
block['transactions'].map(async (transactionOrHash) => {
const transactionHash = fullObjects
? transactionOrHash.hash
: transactionOrHash
if (transactionHash) {
const receipt = await this.getTransactionReceipt(transactionHash)
if (receipt && receipt.logsBloom) {
logsBloom.or(new BloomFilter(hexStrToBuf(receipt.logsBloom)))
}
}
})
)
block['logsBloom'] = bufToHexString(logsBloom.bitvector)

log.debug(
`Transforming block #${block['number']} complete: ${JSON.stringify(
block
Expand Down Expand Up @@ -437,9 +457,19 @@ export class DefaultWeb3Handler

public async getLogs(filter: any): Promise<any[]> {
log.debug(`Requesting logs with filter [${JSON.stringify(filter)}].`)
const res = await this.context.provider.send(Web3RpcMethods.getLogs, filter)
log.debug(`Log result: [${res}], filter: [${JSON.stringify(filter)}].`)
return res

if (filter['address']) {
const codeContractAddress = await this.context.executionManager.getCodeContractAddress(
filter.address
)
filter['address'] = codeContractAddress
}
const res = await this.context.provider.send(Web3RpcMethods.getLogs, [
filter,
])
const logs = convertInternalLogsToOvmLogs(res)
log.debug(`Log result: [${logs}], filter: [${JSON.stringify(filter)}].`)
return logs
}

public async getTransactionByHash(ovmTxHash: string): Promise<any> {
Expand Down
59 changes: 58 additions & 1 deletion packages/rollup-full-node/test/app/web-rpc-handler.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import '../setup'
/* External Imports */
import {
BloomFilter,
add0x,
getLogger,
keccak256,
numberToHexString,
hexStrToBuf,
ZERO_ADDRESS,
hexStrToBuf,
} from '@eth-optimism/core-utils'
import { CHAIN_ID } from '@eth-optimism/ovm'

Expand All @@ -19,6 +20,7 @@ import assert from 'assert'
/* Internal Imports */
import { FullnodeRpcServer, DefaultWeb3Handler } from '../../src/app'
import * as SimpleStorage from '../contracts/build/untranspiled/SimpleStorage.json'
import * as EventEmitter from '../contracts/build/untranspiled/EventEmitter.json'
import { Web3RpcMethods } from '../../src/types'

const log = getLogger('web3-handler', true)
Expand Down Expand Up @@ -214,6 +216,32 @@ describe('Web3Handler', () => {
const block = await httpProvider.getBlock('latest', false)
hexStrToBuf(block.transactions[0]).length.should.eq(32)
})

it('should return a block with the correct logsBloom', async () => {
const executionManagerAddress = await httpProvider.send(
'ovm_getExecutionManagerAddress',
[]
)
const wallet = getWallet(httpProvider)
const balance = await httpProvider.getBalance(wallet.address)
const factory = new ContractFactory(
EventEmitter.abi,
EventEmitter.bytecode,
wallet
)
const eventEmitter = await factory.deploy()
const deploymentTxReceipt = await wallet.provider.getTransactionReceipt(
eventEmitter.deployTransaction.hash
)
const tx = await eventEmitter.emitEvent(executionManagerAddress)

const block = await httpProvider.send('eth_getBlockByNumber', [
'latest',
true,
])
const bloomFilter = new BloomFilter(hexStrToBuf(block.logsBloom))
bloomFilter.check(hexStrToBuf(eventEmitter.address)).should.be.true
})
})

describe('the getBlockByHash endpoint', () => {
Expand Down Expand Up @@ -312,6 +340,35 @@ describe('Web3Handler', () => {
})
})

describe('the getLogs endpoint', () => {
it('should return logs', async () => {
const executionManagerAddress = await httpProvider.send(
'ovm_getExecutionManagerAddress',
[]
)
const wallet = getWallet(httpProvider)
const balance = await httpProvider.getBalance(wallet.address)
const factory = new ContractFactory(
EventEmitter.abi,
EventEmitter.bytecode,
wallet
)
const eventEmitter = await factory.deploy()
const deploymentTxReceipt = await wallet.provider.getTransactionReceipt(
eventEmitter.deployTransaction.hash
)
const tx = await eventEmitter.emitEvent(executionManagerAddress)

const logs = (
await httpProvider.getLogs({
address: eventEmitter.address,
})
).map((x) => factory.interface.parseLog(x))
logs.length.should.eq(1)
logs[0].name.should.eq('Event')
})
})

describe('SimpleStorage integration test', () => {
it('should set storage & retrieve the value', async () => {
const executionManagerAddress = await httpProvider.send(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pragma solidity ^0.5.0;

contract EventEmitter {
event Event();
function emitEvent(address exeMgrAddr) public {
emit Event();
}
}
1 change: 1 addition & 0 deletions tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"interface-name": false,
"max-classes-per-file": false,
"member-ordering": false,
"no-bitwise": false,
"no-empty-interface": false,
"no-implicit-dependencies": [true, "dev"],
"no-string-literal": false,
Expand Down