|
|
|
@ -1,21 +1,18 @@ |
|
|
|
|
'use strict' |
|
|
|
|
var TraceAnalyser = require('./traceAnalyser') |
|
|
|
|
var TraceRetriever = require('./traceRetriever') |
|
|
|
|
var TraceCache = require('./traceCache') |
|
|
|
|
var TraceStepManager = require('./traceStepManager') |
|
|
|
|
var traceManagerUtil = require('./traceManagerUtil') |
|
|
|
|
|
|
|
|
|
function TraceManager (_web3) { |
|
|
|
|
this.web3 = _web3 |
|
|
|
|
|
|
|
|
|
this.isLoading = false |
|
|
|
|
this.trace = null |
|
|
|
|
|
|
|
|
|
// vmtrace changes section
|
|
|
|
|
this.depthChanges = [] |
|
|
|
|
this.callStack = {} |
|
|
|
|
this.memoryChanges = [] |
|
|
|
|
this.callDataChanges = [] |
|
|
|
|
|
|
|
|
|
// storage section
|
|
|
|
|
this.storageChanges = [] |
|
|
|
|
this.vmTraceIndexByStorageChange = {} |
|
|
|
|
this.vmTraceChangesRef = [] |
|
|
|
|
this.storages = {} |
|
|
|
|
this.traceCache = new TraceCache() |
|
|
|
|
this.traceAnalyser = new TraceAnalyser(this.traceCache) |
|
|
|
|
this.traceRetriever = new TraceRetriever(_web3) |
|
|
|
|
this.traceStepManager = new TraceStepManager(this.traceAnalyser) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// init section
|
|
|
|
@ -24,111 +21,26 @@ TraceManager.prototype.resolveTrace = function (blockNumber, txNumber, callback) |
|
|
|
|
this.init() |
|
|
|
|
if (!this.web3) callback(false) |
|
|
|
|
var self = this |
|
|
|
|
this.web3.debug.trace(blockNumber, parseInt(txNumber), function (error, result) { |
|
|
|
|
if (!error) { |
|
|
|
|
self.computeTrace(result) |
|
|
|
|
callback(true) |
|
|
|
|
} else { |
|
|
|
|
this.traceRetriever.getTrace(blockNumber, parseInt(txNumber), function (error, result) { |
|
|
|
|
self.trace = result |
|
|
|
|
if (error) { |
|
|
|
|
console.log(error) |
|
|
|
|
callback(false) |
|
|
|
|
} else { |
|
|
|
|
self.traceAnalyser.analyse(result, function (error, result) { |
|
|
|
|
if (error) { |
|
|
|
|
console.log(error) |
|
|
|
|
callback(false) |
|
|
|
|
} else { |
|
|
|
|
callback(true) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
this.isLoading = false |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TraceManager.prototype.init = function () { |
|
|
|
|
this.trace = null |
|
|
|
|
this.depthChanges = [] |
|
|
|
|
this.memoryChanges = [] |
|
|
|
|
this.callDataChanges = [] |
|
|
|
|
this.storageChanges = [] |
|
|
|
|
this.vmTraceIndexByStorageChange = {} |
|
|
|
|
this.vmTraceChangesRef = [] |
|
|
|
|
this.callStack = {} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TraceManager.prototype.computeTrace = function (trace) { |
|
|
|
|
this.trace = trace |
|
|
|
|
var currentDepth = 0 |
|
|
|
|
var currentStorageAddress |
|
|
|
|
var callStack = [] |
|
|
|
|
for (var k in this.trace) { |
|
|
|
|
var step = this.trace[k] |
|
|
|
|
|
|
|
|
|
this.buildCalldata(k, step) |
|
|
|
|
this.buildMemory(k, step) |
|
|
|
|
currentStorageAddress = this.buildStorage(k, step, currentStorageAddress) |
|
|
|
|
var depth = this.buildDepth(k, step, currentDepth, callStack) |
|
|
|
|
if (depth) { |
|
|
|
|
currentDepth = depth |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// compute trace section
|
|
|
|
|
TraceManager.prototype.buildCalldata = function (index, step) { |
|
|
|
|
if (step.calldata) { |
|
|
|
|
this.callDataChanges.push(index) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TraceManager.prototype.buildMemory = function (index, step) { |
|
|
|
|
if (step.memory) { |
|
|
|
|
this.memoryChanges.push(index) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TraceManager.prototype.buildStorage = function (index, step, currentAddress) { |
|
|
|
|
var change = false |
|
|
|
|
if (step.address) { |
|
|
|
|
// new context
|
|
|
|
|
this.storageChanges.push({ address: step.address, changes: [] }) |
|
|
|
|
change = true |
|
|
|
|
} else if (step.inst === 'SSTORE') { |
|
|
|
|
this.storageChanges[this.storageChanges.length - 1].changes.push( |
|
|
|
|
{ |
|
|
|
|
'key': step.stack[step.stack.length - 1], |
|
|
|
|
'value': step.stack[step.stack.length - 2] |
|
|
|
|
}) |
|
|
|
|
change = true |
|
|
|
|
} else if (!step.address && step.depth) { |
|
|
|
|
// returned from context
|
|
|
|
|
var address = this.storageChanges[this.storageChanges.length - 2].address |
|
|
|
|
this.storageChanges.push({ address: address, changes: [] }) |
|
|
|
|
change = true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (change) { |
|
|
|
|
this.vmTraceIndexByStorageChange[index] = { |
|
|
|
|
context: this.storageChanges.length - 1, |
|
|
|
|
changes: this.storageChanges[this.storageChanges.length - 1].changes.length - 1 |
|
|
|
|
} |
|
|
|
|
this.vmTraceChangesRef.push(index) |
|
|
|
|
} |
|
|
|
|
return currentAddress |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TraceManager.prototype.buildDepth = function (index, step, currentDepth, callStack) { |
|
|
|
|
if (step.depth === undefined) return |
|
|
|
|
if (step.depth > currentDepth) { |
|
|
|
|
if (index === 0) { |
|
|
|
|
callStack.push('0x' + step.address) // new context
|
|
|
|
|
} else { |
|
|
|
|
// getting the address from the stack
|
|
|
|
|
var callTrace = this.trace[index - 1] |
|
|
|
|
var address = callTrace.stack[callTrace.stack.length - 2] |
|
|
|
|
callStack.push(address) // new context
|
|
|
|
|
} |
|
|
|
|
} else if (step.depth < currentDepth) { |
|
|
|
|
callStack.pop() // returning from context
|
|
|
|
|
} |
|
|
|
|
this.callStack[index] = { |
|
|
|
|
stack: callStack.slice(0), |
|
|
|
|
depth: step.depth, |
|
|
|
|
address: step.address |
|
|
|
|
} |
|
|
|
|
this.depthChanges.push(index) |
|
|
|
|
return step.depth |
|
|
|
|
this.traceCache.init() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// API section
|
|
|
|
@ -138,39 +50,31 @@ TraceManager.prototype.getLength = function (callback) { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TraceManager.prototype.getStorageAt = function (stepIndex, blockNumber, txIndex, callback) { |
|
|
|
|
var stoChange = this.findLowerBound(stepIndex, this.vmTraceChangesRef) |
|
|
|
|
if (!stoChange) { |
|
|
|
|
callback('cannot rebuild storage', null) |
|
|
|
|
} |
|
|
|
|
var stoChange = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.storageChanges) |
|
|
|
|
|
|
|
|
|
var changeRefs = this.vmTraceIndexByStorageChange[stoChange] |
|
|
|
|
var address = this.storageChanges[changeRefs.context].address |
|
|
|
|
var address = this.traceCache.sstore[stoChange].address |
|
|
|
|
var self = this |
|
|
|
|
this.retrieveStorage(address, blockNumber, txIndex, function (storage) { |
|
|
|
|
for (var k = 0; k < changeRefs.context; k++) { |
|
|
|
|
var context = self.storageChanges[k] |
|
|
|
|
if (context.address === address) { |
|
|
|
|
for (var i = 0; i < context.changes.length; i++) { |
|
|
|
|
if (i > changeRefs.changes) break |
|
|
|
|
var change = context.changes[i] |
|
|
|
|
storage[change.key] = change.value |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
this.traceRetriever.getStorage(blockNumber, txIndex, address, function (error, result) { |
|
|
|
|
if (error) { |
|
|
|
|
console.log(error) |
|
|
|
|
callback(error, null) |
|
|
|
|
} else { |
|
|
|
|
var storage = self.traceCache.rebuildStorage(address, result, stepIndex) |
|
|
|
|
callback(null, storage) |
|
|
|
|
} |
|
|
|
|
callback(null, storage) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TraceManager.prototype.getCallDataAt = function (stepIndex, callback) { |
|
|
|
|
var callDataChange = this.findLowerBound(stepIndex, this.callDataChanges) |
|
|
|
|
var callDataChange = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.callDataChanges) |
|
|
|
|
if (!callDataChange) return callback('no calldata found', null) |
|
|
|
|
callback(null, [this.trace[callDataChange].calldata]) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TraceManager.prototype.getCallStackAt = function (stepIndex, callback) { |
|
|
|
|
var callStackChange = this.findLowerBound(stepIndex, this.depthChanges) |
|
|
|
|
var callStackChange = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.depthChanges) |
|
|
|
|
if (!callStackChange) return callback('no callstack found', null) |
|
|
|
|
callback(null, this.callStack[callStackChange].stack) |
|
|
|
|
callback(null, this.traceCache.callStack[callStackChange].stack) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TraceManager.prototype.getStackAt = function (stepIndex, callback) { |
|
|
|
@ -185,7 +89,7 @@ TraceManager.prototype.getStackAt = function (stepIndex, callback) { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TraceManager.prototype.getLastDepthIndexChangeSince = function (stepIndex, callback) { |
|
|
|
|
var depthIndex = this.findLowerBound(stepIndex, this.depthChanges) |
|
|
|
|
var depthIndex = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.depthChanges) |
|
|
|
|
callback(null, depthIndex) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -195,13 +99,13 @@ TraceManager.prototype.getCurrentCalledAddressAt = function (stepIndex, callback |
|
|
|
|
if (error) { |
|
|
|
|
callback(error, null) |
|
|
|
|
} else { |
|
|
|
|
callback(null, self.resolveAddress(addressIndex)) |
|
|
|
|
callback(null, traceManagerUtil.resolveCalledAddress(addressIndex, self.trace)) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TraceManager.prototype.getMemoryAt = function (stepIndex, callback) { |
|
|
|
|
var lastChanges = this.findLowerBound(stepIndex, this.memoryChanges) |
|
|
|
|
var lastChanges = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.memoryChanges) |
|
|
|
|
if (!lastChanges) return callback('no memory found', null) |
|
|
|
|
callback(null, this.trace[lastChanges].memory) |
|
|
|
|
} |
|
|
|
@ -227,114 +131,20 @@ TraceManager.prototype.getRemainingGas = function (stepIndex, callback) { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// step section
|
|
|
|
|
TraceManager.prototype.isCallInstruction = function (index) { |
|
|
|
|
var state = this.trace[index] |
|
|
|
|
return state.instname === 'CALL' || state.instname === 'CALLCODE' || state.instname === 'CREATE' || state.instname === 'DELEGATECALL' |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TraceManager.prototype.isReturnInstruction = function (index) { |
|
|
|
|
var state = this.trace[index] |
|
|
|
|
return state.instname === 'RETURN' |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TraceManager.prototype.findStepOverBack = function (currentStep) { |
|
|
|
|
if (this.isReturnInstruction(currentStep - 1)) { |
|
|
|
|
return this.findStepOutBack(currentStep) |
|
|
|
|
} else { |
|
|
|
|
return currentStep - 1 |
|
|
|
|
} |
|
|
|
|
return this.traceStepManager.findStepOverBack(currentStep) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TraceManager.prototype.findStepOverForward = function (currentStep) { |
|
|
|
|
if (this.isCallInstruction(currentStep)) { |
|
|
|
|
return this.findStepOutForward(currentStep) |
|
|
|
|
} else { |
|
|
|
|
return currentStep + 1 |
|
|
|
|
} |
|
|
|
|
return this.traceStepManager.findStepOverForward(currentStep) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TraceManager.prototype.findStepOutBack = function (currentStep) { |
|
|
|
|
var i = currentStep - 1 |
|
|
|
|
var depth = 0 |
|
|
|
|
while (--i >= 0) { |
|
|
|
|
if (this.isCallInstruction(i)) { |
|
|
|
|
if (depth === 0) { |
|
|
|
|
break |
|
|
|
|
} else { |
|
|
|
|
depth-- |
|
|
|
|
} |
|
|
|
|
} else if (this.isReturnInstruction(i)) { |
|
|
|
|
depth++ |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return i |
|
|
|
|
return this.traceStepManager.findStepOutBack(currentStep) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TraceManager.prototype.findStepOutForward = function (currentStep) { |
|
|
|
|
var i = currentStep |
|
|
|
|
var depth = 0 |
|
|
|
|
while (++i < this.trace.length) { |
|
|
|
|
if (this.isReturnInstruction(i)) { |
|
|
|
|
if (depth === 0) { |
|
|
|
|
break |
|
|
|
|
} else { |
|
|
|
|
depth-- |
|
|
|
|
} |
|
|
|
|
} else if (this.isCallInstruction(i)) { |
|
|
|
|
depth++ |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return i + 1 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// util section
|
|
|
|
|
TraceManager.prototype.findLowerBound = function (target, changes) { |
|
|
|
|
if (changes.length === 1) { |
|
|
|
|
if (changes[0] > target) { |
|
|
|
|
// we only a closest maximum, returning 0
|
|
|
|
|
return null |
|
|
|
|
} else { |
|
|
|
|
return changes[0] |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var middle = Math.floor(changes.length / 2) |
|
|
|
|
if (changes[middle] > target) { |
|
|
|
|
return this.findLowerBound(target, changes.slice(0, middle)) |
|
|
|
|
} else if (changes[middle] < target) { |
|
|
|
|
return this.findLowerBound(target, changes.slice(middle, changes.length)) |
|
|
|
|
} else { |
|
|
|
|
return changes[middle] |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TraceManager.prototype.resolveAddress = function (vmTraceIndex) { |
|
|
|
|
var address = this.trace[vmTraceIndex].address |
|
|
|
|
if (vmTraceIndex > 0) { |
|
|
|
|
var stack = this.trace[vmTraceIndex - 1].stack // callcode, delegatecall, ...
|
|
|
|
|
address = stack[stack.length - 2] |
|
|
|
|
} |
|
|
|
|
return address |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// retrieve the storage of an account just after the execution of tx
|
|
|
|
|
TraceManager.prototype.retrieveStorage = function (address, blockNumber, txIndex, callBack) { |
|
|
|
|
if (this.storages[address]) { |
|
|
|
|
callBack(this.storages[address]) |
|
|
|
|
} |
|
|
|
|
var self = this |
|
|
|
|
if (blockNumber !== null && txIndex !== null) { |
|
|
|
|
this.web3.debug.storageAt(blockNumber, txIndex, address, function (error, result) { |
|
|
|
|
if (error) { |
|
|
|
|
console.log(error) |
|
|
|
|
} else { |
|
|
|
|
self.storages[address] = result |
|
|
|
|
callBack(result) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
} else { |
|
|
|
|
console.log('blockNumber/txIndex are not defined') |
|
|
|
|
} |
|
|
|
|
return this.traceStepManager.findStepOutForward(currentStep) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
module.exports = TraceManager |
|
|
|
|