Commit ce3caa69 authored by yann300's avatar yann300

cache the preimages for a debug session (using the initial state)

parent e59e5644
...@@ -11,12 +11,13 @@ class Mapping extends RefType { ...@@ -11,12 +11,13 @@ class Mapping extends RefType {
} }
async decodeFromStorage (location, storageResolver) { async decodeFromStorage (location, storageResolver) {
var mappingsPreimages
try { try {
var mappingsPreimages = await storageResolver.mappingPreimages() mappingsPreimages = await storageResolver.mappingsLocation()
} catch (e) { } catch (e) {
return { return {
value: '<error> ' + e.message, value: e.message,
type: this.type type: this.typeName
} }
} }
var mapSlot = util.normalizeHex(ethutil.bufferToHex(location.slot)) var mapSlot = util.normalizeHex(ethutil.bufferToHex(location.slot))
......
var global = require('../helpers/global') var global = require('../helpers/global')
module.exports = { module.exports = {
extractMappingPreimages: extractMappingPreimages decodeMappingsKeys: decodeMappingsKeys
} }
/** /**
* Uses the storageViewer to retrieve the storage and returns a mapping containing possible solidity mappping type location. * extract the mappings location from the storage
* like { "<mapping_slot>" : { "<mapping-key1>": preimageOf1 }, { "<mapping-key2>": preimageOf2 }, ... } * like { "<mapping_slot>" : { "<mapping-key1>": preimageOf1 }, { "<mapping-key2>": preimageOf2 }, ... }
* *
* @param {Object} address - storageViewer * @param {Object} storage - storage given by storage Viewer (basically a mapping hashedkey : {key, value})
* @return {Map} - solidity mapping location * @param {Function} callback - calback
*/ * @return {Map} - solidity mapping location (e.g { "<mapping_slot>" : { "<mapping-key1>": preimageOf1 }, { "<mapping-key2>": preimageOf2 }, ... })
async function extractMappingPreimages (storageViewer) { */
return new Promise((resolve, reject) => {
storageViewer.storageRange(function (error, storage) {
if (!error) {
decodeMappingsKeys(storage, (error, mappings) => {
if (error) {
reject(error)
} else {
resolve(mappings)
}
})
} else {
reject(error)
}
})
})
}
/**
* Uses the storageViewer to retrieve the storage and returns a mapping containing possible solidity mappping type location.
* like { "<mapping_slot>" : { "<mapping-key1>": preimageOf1 }, { "<mapping-key2>": preimageOf2 }, ... }
*
* @param {Object} storage - storage given by storage Viewer (basically a mapping hashedkey : {key, value})
* @param {Function} callback - calback
* @return {Map} - solidity mapping location (e.g { "<mapping_slot>" : { "<mapping-key1>": preimageOf1 }, { "<mapping-key2>": preimageOf2 }, ... })
*/
async function decodeMappingsKeys (storage, callback) { async function decodeMappingsKeys (storage, callback) {
var ret = {} var ret = {}
for (var hashedLoc in storage) { for (var hashedLoc in storage) {
...@@ -61,11 +36,11 @@ async function decodeMappingsKeys (storage, callback) { ...@@ -61,11 +36,11 @@ async function decodeMappingsKeys (storage, callback) {
} }
/** /**
* Uses web3 to return preimage of a key * Uses web3 to return preimage of a key
* *
* @param {String} key - key to retrieve the preimage of * @param {String} key - key to retrieve the preimage of
* @return {String} - preimage of the given key * @return {String} - preimage of the given key
*/ */
function getPreimage (key) { function getPreimage (key) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
global.web3.debug.preimage(key, function (error, preimage) { global.web3.debug.preimage(key, function (error, preimage) {
......
'use strict' 'use strict'
var traceHelper = require('../helpers/traceHelper') var traceHelper = require('../helpers/traceHelper')
var util = require('../helpers/global') var util = require('../helpers/global')
var mappingPreimages = require('./mappingPreimages')
/**
* Basically one instance is created for one debugging session.
* (TODO: one instance need to be shared over all the components)
*/
class StorageResolver { class StorageResolver {
constructor () { constructor () {
this.storageByAddress = {} this.storageByAddress = {}
this.preimagesMappingByAddress = {}
this.maxSize = 100 this.maxSize = 100
} }
...@@ -22,6 +28,33 @@ class StorageResolver { ...@@ -22,6 +28,33 @@ class StorageResolver {
} }
/** /**
* compute the mappgings type locations for the current address (cached for a debugging session)
* note: that only retrieve the first 100 items.
*
* @param {String} address - contract address
* @param {Object} address - storage
* @return {Function} - callback
*/
initialPreimagesMappings (tx, stepIndex, address, callback) {
if (this.preimagesMappingByAddress[address]) {
return callback(null, this.preimagesMappingByAddress[address])
}
this.storageRange(tx, stepIndex, address, (error, storage) => {
if (error) {
return callback(error)
}
mappingPreimages.decodeMappingsKeys(storage, (error, mappings) => {
if (error) {
callback(error)
} else {
this.preimagesMappingByAddress[address] = mappings
callback(null, mappings)
}
})
})
}
/**
* return a slot value for the given context (address and vm trace index) * return a slot value for the given context (address and vm trace index)
* *
* @param {String} - slot - slot key * @param {String} - slot - slot key
...@@ -96,11 +129,11 @@ function fromCache (self, address) { ...@@ -96,11 +129,11 @@ function fromCache (self, address) {
} }
/** /**
* store the result of `storageRangeAtInternal` * store the result of `storageRangeAtInternal`
* *
* @param {String} address - contract address * @param {String} address - contract address
* @param {Object} storage - result of `storageRangeAtInternal`, contains {key, hashedKey, value} * @param {Object} storage - result of `storageRangeAtInternal`, contains {key, hashedKey, value}
*/ */
function toCache (self, address, storage) { function toCache (self, address, storage) {
if (!self.storageByAddress[address]) { if (!self.storageByAddress[address]) {
self.storageByAddress[address] = {} self.storageByAddress[address] = {}
......
'use strict' 'use strict'
var helper = require('../helpers/util') var helper = require('../helpers/util')
var mappingPreimagesExtractor = require('./mappingPreimages') var mappingPreimages = require('./mappingPreimages')
/**
* easier access to the storage resolver
* Basically one instance is created foreach execution step and foreach component that need it.
* (TODO: one instance need to be shared over all the components)
*/
class StorageViewer { class StorageViewer {
constructor (_context, _storageResolver, _traceManager) { constructor (_context, _storageResolver, _traceManager) {
this.context = _context this.context = _context
this.storageResolver = _storageResolver this.storageResolver = _storageResolver
// contains [mappingSlot][mappingkey] = preimage
// this map is renewed for each execution step
// this map is shared among all the mapping types
this.mappingsPreimages = null
_traceManager.accumulateStorageChanges(this.context.stepIndex, this.context.address, {}, (error, storageChanges) => { _traceManager.accumulateStorageChanges(this.context.stepIndex, this.context.address, {}, (error, storageChanges) => {
if (!error) { if (!error) {
this.storageChanges = storageChanges this.storageChanges = storageChanges
...@@ -20,11 +21,11 @@ class StorageViewer { ...@@ -20,11 +21,11 @@ class StorageViewer {
} }
/** /**
* return the storage for the current context (address and vm trace index) * return the storage for the current context (address and vm trace index)
* by default now returns the range 0 => 1000 * by default now returns the range 0 => 1000
* *
* @param {Function} - callback - contains a map: [hashedKey] = {key, hashedKey, value} * @param {Function} - callback - contains a map: [hashedKey] = {key, hashedKey, value}
*/ */
storageRange (callback) { storageRange (callback) {
this.storageResolver.storageRange(this.context.tx, this.context.stepIndex, this.context.address, (error, storage) => { this.storageResolver.storageRange(this.context.tx, this.context.stepIndex, this.context.address, (error, storage) => {
if (error) { if (error) {
...@@ -64,12 +65,58 @@ class StorageViewer { ...@@ -64,12 +65,58 @@ class StorageViewer {
return this.storageResolver.isComplete(address) return this.storageResolver.isComplete(address)
} }
async mappingPreimages () { /**
if (!this.mappingsPreimages) { * return all the possible mappings locations for the current context (cached)
this.mappingsPreimages = await mappingPreimagesExtractor.extractMappingPreimages(this) *
* @param {Function} callback
*/
async mappingsLocation () {
return new Promise((resolve, reject) => {
if (this.completeMappingsLocation) {
return resolve(this.completeMappingsLocation)
}
this.storageResolver.initialPreimagesMappings(this.context.tx, this.context.stepIndex, this.context.address, (error, initialMappingsLocation) => {
if (error) {
reject(error)
} else {
this.extractMappingsLocationChanges(this.storageChanges, (error, mappingsLocationChanges) => {
if (error) {
return reject(error)
}
this.completeMappingsLocation = Object.assign({}, initialMappingsLocation)
for (var key in mappingsLocationChanges) {
if (!initialMappingsLocation[key]) {
initialMappingsLocation[key] = {}
}
this.completeMappingsLocation[key] = Object.assign({}, initialMappingsLocation[key], mappingsLocationChanges[key])
}
resolve(this.completeMappingsLocation)
})
}
})
})
}
/**
* retrieve mapping location changes from the storage changes.
*
* @param {Function} callback
*/
extractMappingsLocationChanges (storageChanges, callback) {
if (this.mappingsLocationChanges) {
return callback(null, this.mappingsLocationChanges)
} }
return this.mappingsPreimages mappingPreimages.decodeMappingsKeys(storageChanges, (error, mappings) => {
if (!error) {
this.mappingsLocationChanges = mappings
return callback(null, this.mappingsLocationChanges)
} else {
callback(error)
}
})
} }
} }
module.exports = StorageViewer module.exports = StorageViewer
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment