Skip to content

Commit

Permalink
Merge branch 'master' into use-maps-for-opcodes
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanio committed Sep 14, 2020
2 parents ad4625e + c81c247 commit 8059fb0
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 5 deletions.
3 changes: 2 additions & 1 deletion packages/block/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ module.exports = function(config) {
preprocessors: {
'./test-build/**/*.js': ['browserify'],
},
concurrency: 1,
reporters: ['dots'],
browsers: ['ChromeHeadless'],
browsers: ['FirefoxHeadless', 'ChromeHeadless'],
singleRun: true,
})
}
32 changes: 28 additions & 4 deletions packages/vm/lib/evm/opFns.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import BN = require('bn.js')
import { keccak256, setLengthRight, TWO_POW256, MAX_INTEGER, KECCAK256_NULL } from 'ethereumjs-util'
import {
keccak256,
setLengthRight,
setLengthLeft,
TWO_POW256,
MAX_INTEGER,
KECCAK256_NULL,
} from 'ethereumjs-util'
import { ERROR, VmError } from '../exceptions'
import { RunState } from './interpreter'

Expand Down Expand Up @@ -730,7 +737,7 @@ export const handlers: Map<number, OpHandler> = new Map([

// TODO: Replace getContractStorage with EEI method
const found = await getContractStorage(runState, runState.eei.getAddress(), keyBuf)
updateSstoreGas(runState, found, value)
updateSstoreGas(runState, found, setLengthLeftStorage(value))
await runState.eei.storageStore(keyBuf, value)
},
],
Expand Down Expand Up @@ -1287,12 +1294,14 @@ function jumpSubIsValid(runState: RunState, dest: number): boolean {
}

async function getContractStorage(runState: RunState, address: Buffer, key: Buffer) {
const current = await runState.stateManager.getContractStorage(address, key)
const current = setLengthLeftStorage(await runState.stateManager.getContractStorage(address, key))
if (
runState._common.hardfork() === 'constantinople' ||
runState._common.gteHardfork('istanbul')
) {
const original = await runState.stateManager.getOriginalContractStorage(address, key)
const original = setLengthLeftStorage(
await runState.stateManager.getOriginalContractStorage(address, key),
)
return { current, original }
} else {
return current
Expand Down Expand Up @@ -1436,3 +1445,18 @@ function writeCallOutput(runState: RunState, outOffset: BN, outLength: BN) {
runState.memory.write(memOffset, dataLength, data)
}
}

/**
setLengthLeftStorage
@param value: Buffer which we want to pad
This is just a proxy function to ethereumjs-util's setLengthLeft, except it returns a zero length buffer in case the buffer is full of zeros.
*/

function setLengthLeftStorage(value: Buffer) {
if (value.equals(Buffer.alloc(value.length, 0))) {
// return the empty buffer (the value is zero)
return Buffer.alloc(0)
} else {
return setLengthLeft(value, 32)
}
}
1 change: 1 addition & 0 deletions packages/vm/lib/runTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ async function _runTx(this: VM, opts: RunTxOpts): Promise<RunTxResult> {
}
}
await state.cleanupTouchedAccounts()
await state.clearOriginalStorageCache()

/**
* The `afterTx` event
Expand Down
1 change: 1 addition & 0 deletions packages/vm/lib/state/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ export interface StateManager {
accountIsEmpty(address: Buffer): Promise<boolean>
accountExists(address: Buffer): Promise<boolean>
cleanupTouchedAccounts(): Promise<void>
clearOriginalStorageCache(): void
}
8 changes: 8 additions & 0 deletions packages/vm/lib/state/stateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,14 @@ export default class DefaultStateManager implements StateManager {
this._originalStorageCache = new Map()
}

/**
* Clears the original storage cache. Refer to [[getOriginalContractStorage]]
* for more explanation. Alias of the internal _clearOriginalStorageCache
*/
clearOriginalStorageCache(): void {
this._clearOriginalStorageCache()
}

/**
* Modifies the storage trie of an account.
* @private
Expand Down
47 changes: 47 additions & 0 deletions packages/vm/tests/api/runCall.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,5 +156,52 @@ tape('Ensure that precompile activation creates non-empty accounts', async (t) =

t.assert(diff.eq(new BN(expected)), "precompiles are activated")

t.end()
})

tape('Ensure that Istanbul sstoreCleanRefundEIP2200 gas is applied correctly', async (t) => {
// setup the accounts for this test
const caller = Buffer.from('00000000000000000000000000000000000000ee', 'hex') // caller addres
const address = Buffer.from('00000000000000000000000000000000000000ff', 'hex')
// setup the vm
const common = new Common({ chain: 'mainnet', hardfork: 'istanbul' })
const vm = new VM({ common: common})
const code = "61000260005561000160005500"
/*
idea: store the original value in the storage slot, except it is now a 1-length buffer instead of a 32-length buffer
code:
PUSH2 0x0002
PUSH1 0x00
SSTORE -> make storage slot 0 "dirty"
PUSH2 0x0001
PUSH1 0x00
SSTORE -> -> restore it to the original storage value (refund sstoreCleanRefundEIP2200)
STOP
gas cost:
4x PUSH 12
2x SSTORE (slot is nonzero, so charge 5000): 10000
net 10012
gas refund
sstoreCleanRefundEIP2200 4200
gas used
10012 - 4200 = 5812
*/

await vm.stateManager.putContractCode(address, Buffer.from(code, 'hex'))
await vm.stateManager.putContractStorage(address, Buffer.alloc(32, 0), Buffer.from(("00").repeat(31) + "01", 'hex'))

// setup the call arguments
let runCallArgs = {
caller: caller, // call address
to: address,
gasLimit: new BN(0xffffffffff), // ensure we pass a lot of gas, so we do not run out of gas
}

const result = await vm.runCall(runCallArgs)
console.log(result.gasUsed, result.execResult.gasRefund)
t.equal(result.gasUsed.toNumber(), 5812, "gas used incorrect")
t.equal(result.execResult.gasRefund.toNumber(), 4200, "gas refund incorrect")

t.end()
})
35 changes: 35 additions & 0 deletions packages/vm/tests/api/runTx.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const runTx = require('../../dist/runTx').default
const { DefaultStateManager } = require('../../dist/state')
const VM = require('../../dist/index').default
const { createAccount } = require('./utils')
const Common = require('@ethereumjs/common').default

function setup(vm = null) {
if (vm === null) {
Expand Down Expand Up @@ -111,6 +112,40 @@ tape('should fail when account balance overflows (create)', async (t) => {
t.end()
})

tape('should clear storage cache after every transaction', async(t) => {
const vm = new VM({common: new Common({ chain: 'mainnet', hardfork: "istanbul" })})
const suite = setup(vm)
const privateKey = Buffer.from(
'e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109',
'hex',
)
/* Code which is deployed here:
PUSH1 01
PUSH1 00
SSTORE
INVALID
*/
const code = "6001600055FE"
const address = Buffer.from("00000000000000000000000000000000000000ff", 'hex')
await vm.stateManager.putContractCode(Buffer.from(address, 'hex'), Buffer.from(code, 'hex'))
await vm.stateManager.putContractStorage(address, Buffer.from(("00").repeat(32), 'hex'), Buffer.from(("00").repeat(31) + "01", 'hex'))
const tx = new Transaction ({
nonce: '0x00',
gasPrice: 1,
gasLimit: 100000,
to: address,
chainId: 3,
})
tx.sign(privateKey)

await vm.stateManager.putAccount(tx.from, createAccount())

await vm.runTx({tx}) // this tx will fail, but we have to ensure that the cache is cleared

t.equal(vm.stateManager._originalStorageCache.size, 0, 'storage cache should be cleared')
t.end()
})

// The following test tries to verify that running a tx
// would work, even when stateManager is not using a cache.
// It fails at the moment, and has been therefore commented.
Expand Down

1 comment on commit 8059fb0

@github-actions
Copy link

Choose a reason for hiding this comment

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

Benchmark

Benchmark suite Current: 8059fb0 Previous: c81c247 Ratio
Block 9422905 2559 ops/sec (±2.19%) 2156 ops/sec (±3.36%) 0.84
Block 9422906 2432 ops/sec (±5.95%) 2147 ops/sec (±5.86%) 0.88
Block 9422907 2541 ops/sec (±1.41%) 2194 ops/sec (±2.16%) 0.86
Block 9422908 2521 ops/sec (±1.48%) 1849 ops/sec (±10.69%) 0.73
Block 9422909 1980 ops/sec (±13.87%) 2078 ops/sec (±1.58%) 1.05
Block 9422910 2437 ops/sec (±1.39%) 2047 ops/sec (±1.72%) 0.84
Block 9422911 2387 ops/sec (±1.58%) 1743 ops/sec (±13.09%) 0.73
Block 9422912 2336 ops/sec (±1.93%) 1602 ops/sec (±13.24%) 0.69
Block 9422913 2369 ops/sec (±1.61%) 1994 ops/sec (±1.97%) 0.84
Block 9422914 2214 ops/sec (±6.33%) 1957 ops/sec (±2.20%) 0.88

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.