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

StateManager interface #763

Merged
merged 7 commits into from
Jun 5, 2020
Merged
Show file tree
Hide file tree
Changes from 5 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: 10 additions & 7 deletions packages/vm/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import BN = require('bn.js')
import Account from 'ethereumjs-account'
import Blockchain from 'ethereumjs-blockchain'
import Common from 'ethereumjs-common'
import { StateManager } from './state/index'
import { StateManager, DefaultStateManager } from './state/index'
import { default as runCode, RunCodeOpts } from './runCode'
import { default as runCall, RunCallOpts } from './runCall'
import { default as runTx, RunTxOpts, RunTxResult } from './runTx'
import { default as runBlock, RunBlockOpts, RunBlockResult } from './runBlock'
import { EVMResult, ExecResult } from './evm/evm'
import { OpcodeList, getOpcodesForHF } from './evm/opcodes'
import { precompiles } from './evm/precompiles'
import runBlockchain from './runBlockchain'
const AsyncEventEmitter = require('async-eventemitter')
const Trie = require('merkle-patricia-tree/secure.js')
Expand Down Expand Up @@ -72,6 +73,7 @@ export default class VM extends AsyncEventEmitter {
_opcodes: OpcodeList
public readonly _emit: (topic: string, data: any) => Promise<void>
protected isInitialized: boolean = false

/**
* VM async constructor. Creates engine instance and initializes it.
*
Expand All @@ -82,6 +84,7 @@ export default class VM extends AsyncEventEmitter {
await vm.init()
return vm
}

/**
* Instantiates a new [[VM]] Object.
* @param opts - Default values for the options are:
Expand Down Expand Up @@ -124,7 +127,7 @@ export default class VM extends AsyncEventEmitter {
this.stateManager = opts.stateManager
} else {
const trie = opts.state || new Trie()
this.stateManager = new StateManager({ trie, common: this._common })
this.stateManager = new DefaultStateManager({ trie, common: this._common })
}

this.blockchain = opts.blockchain || new Blockchain({ common: this._common })
Expand All @@ -145,11 +148,11 @@ export default class VM extends AsyncEventEmitter {
const { opts } = this

if (opts.activatePrecompiles && !opts.stateManager) {
const trie = this.stateManager._trie
const put = promisify(trie.put.bind(trie))
for (let i = 1; i <= 8; i++) {
await put(new BN(i).toArrayLike(Buffer, 'be', 20), new Account().serialize())
}
this.stateManager.checkpoint()
Object.keys(precompiles)
.map((k: string): Buffer => Buffer.from(k, 'hex'))
.forEach(async (k: Buffer) => await this.stateManager.putAccount(k, new Account()))
await this.stateManager.commit()
Comment on lines -148 to +155
Copy link
Contributor

Choose a reason for hiding this comment

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

nice improvement

}

this.isInitialized = true
Expand Down
1 change: 0 additions & 1 deletion packages/vm/lib/runTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ async function _runTx(this: VM, opts: RunTxOpts): Promise<RunTxResult> {
value: tx.value,
data: tx.data,
})
state._clearOriginalStorageCache()
const evm = new EVM(this, txContext, block)
const results = (await evm.executeMessage(message)) as RunTxResult

Expand Down
3 changes: 2 additions & 1 deletion packages/vm/lib/state/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as StateManager } from './stateManager'
export { StateManager } from './interface'
export { default as DefaultStateManager } from './stateManager'
32 changes: 32 additions & 0 deletions packages/vm/lib/state/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Account from 'ethereumjs-account'

/**
* Storage values of an account
*/
export interface StorageDump {
[key: string]: string
}

export interface StateManager {
Copy link
Contributor

Choose a reason for hiding this comment

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

👌

copy(): StateManager
getAccount(address: Buffer): Promise<Account>
putAccount(address: Buffer, account: Account): Promise<void>
touchAccount(address: Buffer): void
putContractCode(address: Buffer, value: Buffer): Promise<void>
getContractCode(address: Buffer): Promise<Buffer>
getContractStorage(address: Buffer, key: Buffer): Promise<Buffer>
getOriginalContractStorage(address: Buffer, key: Buffer): Promise<Buffer>
putContractStorage(address: Buffer, key: Buffer, value: Buffer): Promise<void>
clearContractStorage(address: Buffer): Promise<void>
checkpoint(): Promise<void>
commit(): Promise<void>
revert(): Promise<void>
getStateRoot(): Promise<Buffer>
setStateRoot(stateRoot: Buffer): Promise<void>
dumpStorage(address: Buffer): Promise<StorageDump>
hasGenesisState(): Promise<boolean>
generateCanonicalGenesis(): Promise<void>
generateGenesis(initState: any): Promise<void>
accountIsEmpty(address: Buffer): Promise<boolean>
cleanupTouchedAccounts(): Promise<void>
}
35 changes: 15 additions & 20 deletions packages/vm/lib/state/stateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,17 @@ import { encode, decode } from 'rlp'
import Common from 'ethereumjs-common'
import { genesisStateByName } from 'ethereumjs-common/dist/genesisStates'
import Account from 'ethereumjs-account'
import { StateManager, StorageDump } from './interface'
import Cache from './cache'
import { ripemdPrecompileAddress } from '../evm/precompiles'

// Temporary type until new `merkle-patricia-tree` release with types
type Trie = any

/**
* Storage values of an account
*/
export interface StorageDump {
[key: string]: string
}

/**
* Options for constructing a [[StateManager]].
*/
export interface StateManagerOpts {
export interface DefaultStateManagerOpts {
/**
* Parameters of the chain ([`Common`](https://github.com/ethereumjs/ethereumjs-common))
*/
Expand All @@ -38,7 +32,7 @@ export interface StateManagerOpts {
* Interface for getting and setting data from an underlying
* state trie.
*/
export default class StateManager {
export default class DefaultStateManager implements StateManager {
_common: Common
_trie: Trie
_storageTries: any
Expand All @@ -51,7 +45,7 @@ export default class StateManager {
/**
* Instantiate the StateManager interface.
*/
constructor(opts: StateManagerOpts = {}) {
constructor(opts: DefaultStateManagerOpts = {}) {
let common = opts.common
if (!common) {
common = new Common('mainnet', 'petersburg')
Expand All @@ -73,7 +67,7 @@ export default class StateManager {
* checkpoints were reverted.
*/
copy(): StateManager {
return new StateManager({ trie: this._trie.copy(), common: this._common })
return new DefaultStateManager({ trie: this._trie.copy(), common: this._common })
}

/**
Expand Down Expand Up @@ -222,6 +216,14 @@ export default class StateManager {
}
}

/**
* Clears the original storage cache. Refer to [[getOriginalContractStorage]]
* for more explanation.
*/
_clearOriginalStorageCache(): void {
this._originalStorageCache = new Map()
}

/**
* Modifies the storage trie of an account.
* @private
Expand Down Expand Up @@ -318,6 +320,7 @@ export default class StateManager {

if (this._checkpointCount === 0) {
await this._cache.flush()
this._clearOriginalStorageCache()
}
}

Expand Down Expand Up @@ -347,6 +350,7 @@ export default class StateManager {

if (this._checkpointCount === 0) {
await this._cache.flush()
this._clearOriginalStorageCache()
}
}

Expand Down Expand Up @@ -502,13 +506,4 @@ export default class StateManager {
}
this._touched.clear()
}

/**
* Clears the original storage cache. Refer to [[getOriginalContractStorage]]
* for more explanation.
* @ignore
*/
_clearOriginalStorageCache(): void {
this._originalStorageCache = new Map()
}
}
4 changes: 2 additions & 2 deletions packages/vm/tests/api/runBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const Block = require('ethereumjs-block')
const Common = require('ethereumjs-common').default
const util = require('ethereumjs-util')
const runBlock = require('../../dist/runBlock').default
const { StateManager } = require('../../dist/state')
const { DefaultStateManager } = require('../../dist/state')
const testData = require('./testdata.json')
const { setupVM } = require('./utils')
const { setupPreConditions } = require('../util')
Expand All @@ -14,7 +14,7 @@ function setup(vm = null) {
// The mock includes mocked runTx and runCall which
// always return an error.
if (vm === null) {
const stateManager = new StateManager()
const stateManager = new DefaultStateManager()
vm = {
stateManager,
emit: (e, val, cb) => cb(),
Expand Down
4 changes: 2 additions & 2 deletions packages/vm/tests/api/runBlockchain.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const Block = require('ethereumjs-block')
const Common = require('ethereumjs-common').default
const util = require('ethereumjs-util')
const runBlockchain = require('../../dist/runBlockchain').default
const { StateManager } = require('../../dist/state')
const { DefaultStateManager } = require('../../dist/state')
const { createGenesis } = require('./utils')

tape('runBlockchain', (t) => {
Expand All @@ -16,7 +16,7 @@ tape('runBlockchain', (t) => {
chain: 'goerli',
validate: false,
})
const stateManager = new StateManager({ common: new Common('goerli') })
const stateManager = new DefaultStateManager({ common: new Common('goerli') })
const vm = {
stateManager,
blockchain: blockchain,
Expand Down
4 changes: 2 additions & 2 deletions packages/vm/tests/api/runTx.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ const tape = require('tape')
const Transaction = require('ethereumjs-tx').Transaction
const ethUtil = require('ethereumjs-util')
const runTx = require('../../dist/runTx').default
const { StateManager } = require('../../dist/state')
const { DefaultStateManager } = require('../../dist/state')
const VM = require('../../dist/index').default
const { createAccount } = require('./utils')

function setup(vm = null) {
if (vm === null) {
const stateManager = new StateManager({})
const stateManager = new DefaultStateManager({})
vm = {
stateManager,
emit: (e, val, cb) => {
Expand Down
33 changes: 17 additions & 16 deletions packages/vm/tests/api/state/stateManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ const tape = require('tape')
const { parallel } = require('async')
const { toBuffer, keccak256, KECCAK256_RLP } = require('ethereumjs-util')
const Common = require('ethereumjs-common').default
const { StateManager } = require('../../../dist/state')
const { DefaultStateManager } = require('../../../dist/state')
const { createAccount } = require('../utils')
const { isRunningInKarma } = require('../../util')

tape('StateManager', t => {
t.test('should instantiate', async st => {
const stateManager = new StateManager()
const stateManager = new DefaultStateManager()

st.deepEqual(stateManager._trie.root, KECCAK256_RLP, 'it has default root')
st.equal(stateManager._common.hardfork(), 'petersburg', 'it has default hardfork')
Expand All @@ -18,7 +18,7 @@ tape('StateManager', t => {
})

t.test('should clear the cache when the state root is set', async st => {
const stateManager = new StateManager()
const stateManager = new DefaultStateManager()
const addressBuffer = Buffer.from('a94f5374fce5edbc8e2a8697c15331677e6ebf0b', 'hex')
const account = createAccount()

Expand Down Expand Up @@ -78,7 +78,7 @@ tape('StateManager', t => {
t.test(
'should put and get account, and add to the underlying cache if the account is not found',
async st => {
const stateManager = new StateManager()
const stateManager = new DefaultStateManager()
const account = createAccount()

await stateManager.putAccount('a94f5374fce5edbc8e2a8697c15331677e6ebf0b', account)
Expand All @@ -100,7 +100,7 @@ tape('StateManager', t => {
t.test(
'should call the callback with a boolean representing emptiness, when the account is empty',
async st => {
const stateManager = new StateManager()
const stateManager = new DefaultStateManager()

let res = await stateManager.accountIsEmpty('a94f5374fce5edbc8e2a8697c15331677e6ebf0b')

Expand All @@ -113,7 +113,7 @@ tape('StateManager', t => {
t.test(
'should call the callback with a false boolean representing non-emptiness when the account is not empty',
async st => {
const stateManager = new StateManager()
const stateManager = new DefaultStateManager()
const account = createAccount('0x1', '0x1')

await stateManager.putAccount('a94f5374fce5edbc8e2a8697c15331677e6ebf0b', account)
Expand All @@ -138,7 +138,7 @@ tape('StateManager', t => {
const genesisData = require('ethereumjs-testing').getSingleFile(
'BasicTests/genesishashestest.json',
)
const stateManager = new StateManager()
const stateManager = new DefaultStateManager()

await stateManager.generateCanonicalGenesis()
let stateRoot = await stateManager.getStateRoot()
Expand All @@ -152,7 +152,7 @@ tape('StateManager', t => {
// 2. Test generating from common
const common = new Common('mainnet', 'petersburg')
const expectedStateRoot = Buffer.from(common.genesis().stateRoot.slice(2), 'hex')
const stateManager = new StateManager({ common: common })
const stateManager = new DefaultStateManager({ common: common })

await stateManager.generateCanonicalGenesis()
let stateRoot = await stateManager.getStateRoot()
Expand All @@ -174,7 +174,7 @@ tape('StateManager', t => {
for (const chain of chains) {
const common = new Common(chain, 'petersburg')
const expectedStateRoot = Buffer.from(common.genesis().stateRoot.slice(2), 'hex')
const stateManager = new StateManager({ common: common })
const stateManager = new DefaultStateManager({ common: common })

await stateManager.generateCanonicalGenesis()
let stateRoot = await stateManager.getStateRoot()
Expand All @@ -188,7 +188,7 @@ tape('StateManager', t => {
})

t.test('should dump storage', async st => {
const stateManager = new StateManager()
const stateManager = new DefaultStateManager()
const addressBuffer = Buffer.from('a94f5374fce5edbc8e2a8697c15331677e6ebf0b', 'hex')
const account = createAccount()

Expand All @@ -206,7 +206,7 @@ tape('StateManager', t => {
})

t.test('should pass Common object when copying the state manager', st => {
const stateManager = new StateManager({
const stateManager = new DefaultStateManager({
common: new Common('goerli', 'byzantium'),
})

Expand All @@ -221,7 +221,7 @@ tape('StateManager', t => {
})

t.test("should validate the key's length when modifying a contract's storage", async st => {
const stateManager = new StateManager()
const stateManager = new DefaultStateManager()
const addressBuffer = Buffer.from('a94f5374fce5edbc8e2a8697c15331677e6ebf0b', 'hex')
try {
await stateManager.putContractStorage(addressBuffer, Buffer.alloc(12), toBuffer('0x1231'))
Expand All @@ -236,7 +236,7 @@ tape('StateManager', t => {
})

t.test("should validate the key's length when reading a contract's storage", async st => {
const stateManager = new StateManager()
const stateManager = new DefaultStateManager()
const addressBuffer = Buffer.from('a94f5374fce5edbc8e2a8697c15331677e6ebf0b', 'hex')
try {
await stateManager.getContractStorage(addressBuffer, Buffer.alloc(12))
Expand All @@ -252,7 +252,7 @@ tape('StateManager', t => {
})

tape('Original storage cache', async t => {
const stateManager = new StateManager()
const stateManager = new DefaultStateManager()

const address = 'a94f5374fce5edbc8e2a8697c15331677e6ebf0b'
const addressBuffer = Buffer.from(address, 'hex')
Expand All @@ -263,13 +263,14 @@ tape('Original storage cache', async t => {
const value = Buffer.from('1234', 'hex')

t.test('should initially have empty storage value', async st => {
await stateManager.checkpoint()
const res = await stateManager.getContractStorage(addressBuffer, key)
st.deepEqual(res, Buffer.alloc(0))

const origRes = await stateManager.getOriginalContractStorage(addressBuffer, key)
st.deepEqual(origRes, Buffer.alloc(0))

stateManager._clearOriginalStorageCache()
await stateManager.commit()

st.end()
})
Expand Down Expand Up @@ -341,4 +342,4 @@ tape('Original storage cache', async t => {
st.fail('Should have failed')
st.end()
})
})
})