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

Emit events before and after executing a message #577

Merged
merged 5 commits into from
Sep 10, 2019
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: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,23 @@ The VM processes state changes at many levels.

The opFns for `CREATE`, `CALL`, and `CALLCODE` call back up to `runCall`.

## VM's tracing events

You can subscribe to the following events of the VM:

- `beforeBlock`: Emits a `Block` right before running it.
- `afterBlock`: Emits `RunBlockResult` right after running a block.
- `beforeTx`: Emits a `Transaction` right before running it.
- `afterTx`: Emits a `RunTxResult` right after running a transaction.
- `beforeMessage`: Emits a `Message` right after running it.
- `afterMessage`: Emits an `EVMResult` right after running a message.
- `step`: Emits an `InterpreterStep` right before running an EVM step.
- `newContract`: Emits a `NewContractEvent` right before creating a contract. This event contains the deployment code, not the deployed code, as the creation message may not return such a code.

If an `async` function is used as an event handler, the VM will wait for it to resolve before continuing executing.

If an exception is thrown from within an event handler, it will bubble into the VM and interrupt it, possibly corrupting its state. It's strongly recommended not to throw from event handlers.

# DEVELOPMENT

Developer documentation - currently mainly with information on testing and debugging - can be found [here](./developer.md).
Expand Down
18 changes: 16 additions & 2 deletions lib/evm/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ export interface ExecResult {
selfdestruct?: { [k: string]: Buffer }
}

export interface NewContractEvent {
address: Buffer
// The deployment code
code: Buffer
}

export function OOGResult(gasLimit: BN): ExecResult {
return {
returnValue: Buffer.alloc(0),
Expand Down Expand Up @@ -104,6 +110,8 @@ export default class EVM {
* if an exception happens during the message execution.
*/
async executeMessage(message: Message): Promise<EVMResult> {
await this._vm._emit('beforeMessage', message)

await this._state.checkpoint()

let result
Expand All @@ -130,6 +138,8 @@ export default class EVM {
await this._state.commit()
}

await this._vm._emit('afterMessage', result)

return result
}

Expand Down Expand Up @@ -197,10 +207,14 @@ export default class EVM {
}

await this._state.clearContractStorage(message.to)
await this._vm._emit('newContract', {

const newContractEvent: NewContractEvent = {
address: message.to,
code: message.code,
})
}

await this._vm._emit('newContract', newContractEvent)

toAccount = await this._state.getAccount(message.to)
toAccount.nonce = new BN(toAccount.nonce).addn(1).toArrayLike(Buffer)

Expand Down
16 changes: 15 additions & 1 deletion lib/evm/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Stack from './stack'
import EEI from './eei'
import { Opcode } from './opcodes'
import { handlers as opHandlers, OpHandler } from './opFns.js'
import Account from 'ethereumjs-account'

export interface InterpreterOpts {
pc?: number
Expand All @@ -32,6 +33,19 @@ export interface InterpreterResult {
exceptionError?: VmError
}

export interface InterpreterStep {
gasLeft: BN
stateManager: StateManager
stack: BN[]
pc: number
depth: number
address: Buffer
memory: number[]
memoryWordCount: BN
opcode: Opcode
account: Account
}

/**
* Parses and executes EVM bytecode.
*/
Expand Down Expand Up @@ -161,7 +175,7 @@ export default class Interpreter {
}

async _runStepHook(): Promise<void> {
const eventObj = {
const eventObj: InterpreterStep = {
pc: this._runState.programCounter,
gasLeft: this._eei.getGasLeft(),
opcode: this.lookupOpInfo(this._runState.opCode, true),
Expand Down
180 changes: 180 additions & 0 deletions tests/api/events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
const tape = require('tape')
const util = require('ethereumjs-util')
const { Transaction } = require('ethereumjs-tx')
const Block = require('ethereumjs-block')
const VM = require('../../dist/index').default

tape('VM events', t => {
t.test('should the Block before running it', async st => {
const vm = new VM()

let emitted
vm.on('beforeBlock', val => {
emitted = val
})

const block = new Block()

await vm.runBlock({
block,
generate: true,
skipBlockValidation: true
})

st.equal(emitted, block)

st.end()
})

t.test('should emit a RunBlockResult after running a block', async st => {
const vm = new VM()

let emitted
vm.on('afterBlock', val => {
emitted = val
})

const block = new Block()

await vm.runBlock({
block,
generate: true,
skipBlockValidation: true
})

st.deepEqual(emitted.receipts, [])
st.deepEqual(emitted.results, [])

st.end()
})

t.test('should the Transaction before running it', async st => {
const vm = new VM()

let emitted
vm.on('beforeTx', val => {
emitted = val
})

const tx = new Transaction({ gas: 200000, to: '0x1111111111111111111111111111111111111111' })
tx.sign(util.toBuffer('0xa5737ecdc1b89ca0091647e727ba082ed8953f29182e94adc397210dda643b07'))
await vm.runTx({ tx, skipBalance: true })

st.equal(emitted, tx)

st.end()
})

t.test('should emit RunTxResult after running a tx', async st => {
const vm = new VM()

let emitted
vm.on('afterTx', val => {
emitted = val
})

const tx = new Transaction({
gas: 200000,
to: '0x1111111111111111111111111111111111111111',
value: 1
})
tx.sign(util.toBuffer('0xa5737ecdc1b89ca0091647e727ba082ed8953f29182e94adc397210dda643b07'))
await vm.runTx({ tx, skipBalance: true })

st.equal(util.bufferToHex(emitted.execResult.returnValue), '0x')

st.end()
})

t.test('should emit the Message before running it', async st => {
const vm = new VM()

let emitted
vm.on('beforeMessage', val => {
emitted = val
})

const tx = new Transaction({
gas: 200000,
to: '0x1111111111111111111111111111111111111111',
value: 1
})
tx.sign(util.toBuffer('0xa5737ecdc1b89ca0091647e727ba082ed8953f29182e94adc397210dda643b07'))
await vm.runTx({ tx, skipBalance: true })

st.equal(util.bufferToHex(emitted.to), '0x1111111111111111111111111111111111111111')
st.equal(util.bufferToHex(emitted.code), '0x')

st.end()
})

t.test('should emit EVMResult after running a message', async st => {
const vm = new VM()

let emitted
vm.on('beforeMessage', val => {
emitted = val
})

const tx = new Transaction({
gas: 200000,
to: '0x1111111111111111111111111111111111111111',
value: 1
})
tx.sign(util.toBuffer('0xa5737ecdc1b89ca0091647e727ba082ed8953f29182e94adc397210dda643b07'))
await vm.runTx({ tx, skipBalance: true })

st.equal(util.bufferToHex(emitted.createdAddress), '0x')

st.end()
})

t.test('should emit InterpreterStep on each step', async st => {
const vm = new VM()

let lastEmitted
vm.on('step', val => {
lastEmitted = val
})

// This a deployment transaction that pushes 0x41 (i.e. ascii A) followed by 31 0s to
// the stack, stores that in memory, and then returns the first byte from memory.
// This deploys a contract which a single byte of code, 0x41.
const tx = new Transaction({
gas: 200000,
data: '0x7f410000000000000000000000000000000000000000000000000000000000000060005260016000f3'
})
tx.sign(util.toBuffer('0xa5737ecdc1b89ca0091647e727ba082ed8953f29182e94adc397210dda643b07'))
await vm.runTx({ tx, skipBalance: true })

st.equal(lastEmitted.opcode.name, 'RETURN')

st.end()
})

t.test('should emit a NewContractEvent on new contracts', async st => {
const vm = new VM()

let emitted
vm.on('newContract', val => {
emitted = val
})

// This a deployment transaction that pushes 0x41 (i.e. ascii A) followed by 31 0s to
// the stack, stores that in memory, and then returns the first byte from memory.
// This deploys a contract which a single byte of code, 0x41.
const tx = new Transaction({
gas: 200000,
data: '0x7f410000000000000000000000000000000000000000000000000000000000000060005260016000f3'
})
tx.sign(util.toBuffer('0xa5737ecdc1b89ca0091647e727ba082ed8953f29182e94adc397210dda643b07'))
await vm.runTx({ tx, skipBalance: true })

st.equal(
util.bufferToHex(emitted.code),
'0x7f410000000000000000000000000000000000000000000000000000000000000060005260016000f3'
)

st.end()
})
})