Skip to content

Commit

Permalink
feat: add debug api for vm
Browse files Browse the repository at this point in the history
  • Loading branch information
samlior committed Aug 5, 2021
1 parent e02d0bf commit 75d2368
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 27 deletions.
115 changes: 93 additions & 22 deletions packages/vm/src/runBlock.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { debug as createDebugLogger } from 'debug'
import { encode } from 'rlp'
import { BaseTrie as Trie } from 'merkle-patricia-tree'
import { Account, Address, BN, intToBuffer } from 'ethereumjs-util'
import { Account, Address, BN, intToBuffer, generateAddress } from 'ethereumjs-util'
import { Block } from '@gxchain2-ethereumjs/block'
import VM from './index'
import Bloom from './bloom'
import { StateManager } from './state'
import { short } from './evm/opcodes'
import { Capability, TypedTransaction } from '@gxchain2-ethereumjs/tx'
import { Capability, TypedTransaction, FeeMarketEIP1559Transaction } from '@gxchain2-ethereumjs/tx'
import type { RunTxResult } from './runTx'
import type { TxReceipt } from './types'
import type { TxReceipt, IDebug } from './types'
import { InterpreterStep } from './evm/interpreter'
import * as DAOConfig from './config/dao_fork_accounts_config.json'

// For backwards compatibility from v5.3.0,
Expand Down Expand Up @@ -58,6 +59,14 @@ export interface RunBlockOpts {
* If true, skips the balance check
*/
skipBalance?: boolean
/**
* Debug callback
*/
debug?: IDebug
/**
* Clique signer for generating new block
*/
cliqueSigner?: Buffer
}

/**
Expand Down Expand Up @@ -88,6 +97,10 @@ export interface RunBlockResult {
* The receipt root after executing the block
*/
receiptRoot: Buffer
/**
* The generated block
*/
block?: Block
}

export interface AfterBlockEvent extends RunBlockResult {
Expand Down Expand Up @@ -193,7 +206,10 @@ export default async function runBlock(this: VM, opts: RunBlockOpts): Promise<Ru
...block,
header: { ...block.header, ...generatedFields },
}
block = Block.fromBlockData(blockData, { common: this._common })
block = Block.fromBlockData(blockData, {
common: this._common,
cliqueSigner: opts.cliqueSigner,
})
} else {
if (result.receiptRoot && !result.receiptRoot.equals(block.header.receiptTrie)) {
if (this.DEBUG) {
Expand Down Expand Up @@ -240,6 +256,7 @@ export default async function runBlock(this: VM, opts: RunBlockOpts): Promise<Ru
gasUsed: result.gasUsed,
logsBloom: result.bloom.bitvector,
receiptRoot: result.receiptRoot,
block: generateFields ? block : undefined,
}

const afterBlockEvent: AfterBlockEvent = { ...results, block }
Expand Down Expand Up @@ -312,9 +329,18 @@ async function applyTransactions(this: VM, block: Block, opts: RunBlockOpts) {
const receipts = []
const txResults = []

let handler: undefined | ((step: InterpreterStep, next: () => void) => void)
if (opts.debug) {
handler = async (step: InterpreterStep, next: () => void) => {
await opts.debug!.captureState(step)
next()
}
}

/*
* Process transactions
*/
let catchedErr: any
for (let txIdx = 0; txIdx < block.transactions.length; txIdx++) {
const tx = block.transactions[txIdx]

Expand All @@ -329,37 +355,82 @@ async function applyTransactions(this: VM, block: Block, opts: RunBlockOpts) {

const gasLimitIsHigherThanBlock = maxGasLimit.lt(tx.gasLimit.add(gasUsed))
if (gasLimitIsHigherThanBlock) {
if (handler) {
this.removeListener('step', handler)
}
throw new Error('tx has a higher gas limit than the block')
}

// Call tx exec start
let time: undefined | number
if (opts.debug && (!opts.debug.hash || opts.debug.hash.equals(tx.hash()))) {
time = Date.now()
await opts.debug.captureStart(
tx.getSenderAddress().buf,
tx?.to?.buf || generateAddress(tx.getSenderAddress().buf, tx.nonce.toArrayLike(Buffer)),
tx.toCreationAddress(),
tx.data,
tx.gasLimit,
tx instanceof FeeMarketEIP1559Transaction ? new BN(0) : tx.gasPrice,
tx.value,
block.header.number,
this.stateManager
)
this.on('step', handler)
}

// Run the tx through the VM
const { skipBalance, skipNonce } = opts

const txRes = await this.runTx({
tx,
block,
skipBalance,
skipNonce,
blockGasUsed: gasUsed,
})
txResults.push(txRes)
let txRes: undefined | RunTxResult
try {
txRes = await this.runTx({
tx,
block,
skipBalance,
skipNonce,
blockGasUsed: gasUsed,
})
txResults.push(txRes)
} catch (err) {
catchedErr = err
}

if (this.DEBUG) {
debug('-'.repeat(100))
}

// Add to total block gas usage
gasUsed = gasUsed.add(txRes.gasUsed)
if (this.DEBUG) {
debug(`Add tx gas used (${txRes.gasUsed}) to total block gas usage (-> ${gasUsed})`)
if (txRes) {
// Add to total block gas usage
gasUsed = gasUsed.add(txRes.gasUsed)
if (this.DEBUG) {
debug(`Add tx gas used (${txRes.gasUsed}) to total block gas usage (-> ${gasUsed})`)
}

// Combine blooms via bitwise OR
bloom.or(txRes.bloom)

// Add receipt to trie to later calculate receipt root
receipts.push(txRes.receipt)
const encodedReceipt = encodeReceipt(tx, txRes.receipt)
await receiptTrie.put(encode(txIdx), encodedReceipt)
}

// Combine blooms via bitwise OR
bloom.or(txRes.bloom)
// Call tx exec over
if (opts.debug && (!opts.debug.hash || opts.debug.hash.equals(tx.hash()))) {
if (txRes) {
await opts.debug.captureEnd(txRes.execResult.returnValue, txRes.gasUsed, Date.now() - time!)
} else {
await opts.debug.captureEnd(Buffer.alloc(0), new BN(0), Date.now() - time!)
}
if (handler) {
this.removeListener('step', handler)
}
}

// Add receipt to trie to later calculate receipt root
receipts.push(txRes.receipt)
const encodedReceipt = encodeReceipt(tx, txRes.receipt)
await receiptTrie.put(encode(txIdx), encodedReceipt)
if (catchedErr) {
break
}
}

return {
Expand Down
64 changes: 60 additions & 4 deletions packages/vm/src/runCall.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Address, BN } from 'ethereumjs-util'
import { Address, BN, generateAddress } from 'ethereumjs-util'
import { Block } from '@gxchain2-ethereumjs/block'
import VM from './index'
import TxContext from './evm/txContext'
import Message from './evm/message'
import { InterpreterStep } from './evm/interpreter'
import type { IDebug } from './types'
import { default as EVM, EVMResult } from './evm/evm'

/**
Expand All @@ -27,12 +29,13 @@ export interface RunCallOpts {
salt?: Buffer
selfdestruct?: { [k: string]: boolean }
delegatecall?: boolean
debug?: IDebug
}

/**
* @ignore
*/
export default function runCall(this: VM, opts: RunCallOpts): Promise<EVMResult> {
export default async function runCall(this: VM, opts: RunCallOpts): Promise<EVMResult> {
const block = opts.block ?? Block.fromBlockData({}, { common: this._common })

const txContext = new TxContext(
Expand All @@ -55,7 +58,60 @@ export default function runCall(this: VM, opts: RunCallOpts): Promise<EVMResult>
delegatecall: opts.delegatecall ?? false,
})

const evm = new EVM(this, txContext, block)
// Update from account's nonce and balance
const state = this.stateManager
let fromAccount = await state.getAccount(message.caller)
fromAccount.nonce.iaddn(1)
await state.putAccount(message.caller, fromAccount)

return evm.executeMessage(message)
let time: undefined | number
let handler: undefined | ((step: InterpreterStep, next: () => void) => void)
if (opts.debug) {
handler = async (step: InterpreterStep, next: () => void) => {
await opts.debug!.captureState(step)
next()
}
this.on('step', handler)
time = Date.now()
await opts.debug.captureStart(
message?.caller?.buf,
message?.to?.buf ||
generateAddress(message.caller.buf, fromAccount.nonce.subn(1).toArrayLike(Buffer)),
message.to === undefined,
message.data,
message.gasLimit,
new BN(0),
message.value,
block.header.number,
this.stateManager
)
}

let result: undefined | EVMResult
let catchedErr: any
try {
const evm = new EVM(this, txContext, block)
result = await evm.executeMessage(message)
} catch (err) {
catchedErr = err
}

// Remove Listener
if (handler) {
this.removeListener('step', handler)
}

// Call tx exec over
if (opts.debug) {
if (result) {
await opts.debug.captureEnd(result.execResult.returnValue, result.gasUsed, Date.now() - time!)
} else {
await opts.debug.captureEnd(Buffer.alloc(0), new BN(0), Date.now() - time!)
}
}

if (!result) {
throw catchedErr
}
return result
}
39 changes: 39 additions & 0 deletions packages/vm/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Log } from './evm/types'
import { BN } from 'ethereumjs-util'
import { InterpreterStep } from './evm/interpreter'
import { StateManager } from './state'

export type TxReceipt = PreByzantiumTxReceipt | PostByzantiumTxReceipt | EIP2930Receipt

Expand Down Expand Up @@ -55,3 +58,39 @@ export interface EIP2930Receipt extends PostByzantiumTxReceipt {}
* @deprecated Please use PostByzantiumTxReceipt instead
*/
export interface EIP1559Receipt extends PostByzantiumTxReceipt {}

/**
* Options for debugging.
*/
export interface IDebug {
/**
* Target transaction hash
*/
hash?: Buffer
/**
* Called when the transaction starts processing
*/
captureStart(
from: undefined | Buffer,
to: undefined | Buffer,
create: boolean,
input: Buffer,
gas: BN,
gasPrice: BN,
value: BN,
number: BN,
stateManager: StateManager
): Promise<void>
/**
* Called at every step of processing a transaction
*/
captureState(step: InterpreterStep): Promise<void>
/**
* Called when a transaction error occurs
*/
captureFault(step: InterpreterStep, err: any): Promise<void>
/**
* Called when processing a transaction
*/
captureEnd(output: Buffer, gasUsed: BN, time: number): Promise<void>
}
2 changes: 1 addition & 1 deletion packages/vm/tests/BlockchainTestsRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default async function runBlockchainTest(options: any, testData: any, t:
})

if (validatePow) {
blockchain._ethash!.cacheDB = cacheDB
// blockchain._ethash!.cacheDB = cacheDB
}

let VM
Expand Down

0 comments on commit 75d2368

Please sign in to comment.