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

update tracer, fix update-tracer build rule #86

Merged
merged 1 commit into from
Jan 12, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ build-release:
docker save rosetta-ethereum:$(version) | gzip > rosetta-ethereum-$(version).tar.gz;

update-tracer:
curl https://raw.githubusercontent.com/ethereum/go-ethereum/master/eth/tracers/internal/tracers/call_tracer.js -o ethereum/client/call_tracer.js
curl https://raw.githubusercontent.com/ethereum/go-ethereum/master/eth/tracers/js/internal/tracers/call_tracer_js.js -o ethereum/call_tracer.js

update-bootstrap-balances:
go run main.go utils:generate-bootstrap ethereum/genesis_files/mainnet.json rosetta-cli-conf/mainnet/bootstrap_balances.json;
Expand Down
243 changes: 50 additions & 193 deletions ethereum/call_tracer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2017 The go-ethereum Authors
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
Expand All @@ -14,190 +14,14 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

// callTracer is a full blown transaction tracer that extracts and reports all
// the internal calls made by a transaction, along with any useful information.

// callFrameTracer uses the new call frame tracing methods to report useful information
// about internal messages of a transaction.
{
// callstack is the current recursive call stack of the EVM execution.
callstack: [{}],

// descended tracks whether we've just descended from an outer transaction into
// an inner call.
descended: false,

// step is invoked for every opcode that the VM executes.
step: function(log, db) {
// Capture any errors immediately
var error = log.getError();
if (error !== undefined) {
this.fault(log, db);
return;
}
// We only care about system opcodes, faster if we pre-check once
var syscall = (log.op.toNumber() & 0xf0) == 0xf0;
if (syscall) {
var op = log.op.toString();
}
// If a new contract is being created, add to the call stack
if (syscall && (op == 'CREATE' || op == "CREATE2")) {
var inOff = log.stack.peek(1).valueOf();
var inEnd = inOff + log.stack.peek(2).valueOf();

// Assemble the internal call report and store for completion
var call = {
type: op,
from: toHex(log.contract.getAddress()),
input: toHex(log.memory.slice(inOff, inEnd)),
gasIn: log.getGas(),
gasCost: log.getCost(),
value: '0x' + log.stack.peek(0).toString(16)
};
this.callstack.push(call);
this.descended = true
return;
}
// If a contract is being self destructed, gather that as a subcall too
if (syscall && op == 'SELFDESTRUCT') {
var left = this.callstack.length;
if (this.callstack[left-1].calls === undefined) {
this.callstack[left-1].calls = [];
}
this.callstack[left-1].calls.push({
type: op,
from: toHex(log.contract.getAddress()),
to: toHex(toAddress(log.stack.peek(0).toString(16))),
gasIn: log.getGas(),
gasCost: log.getCost(),
value: '0x' + db.getBalance(log.contract.getAddress()).toString(16)
});
return
}
// If a new method invocation is being done, add to the call stack
if (syscall && (op == 'CALL' || op == 'CALLCODE' || op == 'DELEGATECALL' || op == 'STATICCALL')) {
var to = toAddress(log.stack.peek(1).toString(16));

// We don't skip any pre-compile invocations unlike the official
// geth tracer. This can silence meaningful transfers.
// if (isPrecompiled(to)) {
// return
// }

var off = (op == 'DELEGATECALL' || op == 'STATICCALL' ? 0 : 1);

var inOff = log.stack.peek(2 + off).valueOf();
var inEnd = inOff + log.stack.peek(3 + off).valueOf();

// Assemble the internal call report and store for completion
var call = {
type: op,
from: toHex(log.contract.getAddress()),
to: toHex(to),
input: toHex(log.memory.slice(inOff, inEnd)),
gasIn: log.getGas(),
gasCost: log.getCost(),
outOff: log.stack.peek(4 + off).valueOf(),
outLen: log.stack.peek(5 + off).valueOf()
};
if (op != 'DELEGATECALL' && op != 'STATICCALL') {
call.value = '0x' + log.stack.peek(2).toString(16);
}
this.callstack.push(call);
this.descended = true
return;
}
// If we've just descended into an inner call, retrieve it's true allowance. We
// need to extract if from within the call as there may be funky gas dynamics
// with regard to requested and actually given gas (2300 stipend, 63/64 rule).
if (this.descended) {
if (log.getDepth() >= this.callstack.length) {
this.callstack[this.callstack.length - 1].gas = log.getGas();
} else {
// TODO(karalabe): The call was made to a plain account. We currently don't
// have access to the true gas amount inside the call and so any amount will
// mostly be wrong since it depends on a lot of input args. Skip gas for now.
}
this.descended = false;
}
// If an existing call is returning, pop off the call stack
if (syscall && op == 'REVERT') {
this.callstack[this.callstack.length - 1].error = "execution reverted";
return;
}
if (log.getDepth() == this.callstack.length - 1) {
// Pop off the last call and get the execution results
var call = this.callstack.pop();

if (call.type == 'CREATE' || call.type == "CREATE2") {
// If the call was a CREATE, retrieve the contract address and output code
call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost - log.getGas()).toString(16);
delete call.gasIn; delete call.gasCost;

var ret = log.stack.peek(0);
if (!ret.equals(0)) {
call.to = toHex(toAddress(ret.toString(16)));
call.output = toHex(db.getCode(toAddress(ret.toString(16))));
} else if (call.error === undefined) {
call.error = "internal failure"; // TODO(karalabe): surface these faults somehow
}
} else {
// If the call was a contract call, retrieve the gas usage and output
if (call.gas !== undefined) {
call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost + call.gas - log.getGas()).toString(16);
}
var ret = log.stack.peek(0);
if (!ret.equals(0)) {
call.output = toHex(log.memory.slice(call.outOff, call.outOff + call.outLen));
} else if (call.error === undefined) {
call.error = "internal failure"; // TODO(karalabe): surface these faults somehow
}
delete call.gasIn; delete call.gasCost;
delete call.outOff; delete call.outLen;
}
if (call.gas !== undefined) {
call.gas = '0x' + bigInt(call.gas).toString(16);
}
// Inject the call into the previous one
var left = this.callstack.length;
if (this.callstack[left-1].calls === undefined) {
this.callstack[left-1].calls = [];
}
this.callstack[left-1].calls.push(call);
}
},

// fault is invoked when the actual execution of an opcode fails.
fault: function(log, db) {
// If the topmost call already reverted, don't handle the additional fault again
if (this.callstack[this.callstack.length - 1].error !== undefined) {
return;
}
// Pop off the just failed call
var call = this.callstack.pop();
call.error = log.getError();

// Consume all available gas and clean any leftovers
if (call.gas !== undefined) {
call.gas = '0x' + bigInt(call.gas).toString(16);
call.gasUsed = call.gas
}
delete call.gasIn; delete call.gasCost;
delete call.outOff; delete call.outLen;

// Flatten the failed call into its parent
var left = this.callstack.length;
if (left > 0) {
if (this.callstack[left-1].calls === undefined) {
this.callstack[left-1].calls = [];
}
this.callstack[left-1].calls.push(call);
return;
}
// Last call failed too, leave it in the stack
this.callstack.push(call);
},

// result is invoked when all the opcodes have been iterated over and returns
// the final result of the tracing.
fault: function(log, db) {},
result: function(ctx, db) {
// Prepare outer message info
var result = {
type: ctx.type,
from: toHex(ctx.from),
Expand All @@ -207,22 +31,55 @@
gasUsed: '0x' + bigInt(ctx.gasUsed).toString(16),
input: toHex(ctx.input),
output: toHex(ctx.output),
time: ctx.time,
};
}
if (this.callstack[0].calls !== undefined) {
result.calls = this.callstack[0].calls;
result.calls = this.callstack[0].calls
}
if (this.callstack[0].error !== undefined) {
result.error = this.callstack[0].error;
result.error = this.callstack[0].error
} else if (ctx.error !== undefined) {
result.error = ctx.error;
result.error = ctx.error
}
if (result.error !== undefined && (result.error !== "execution reverted" || result.output ==="0x")) {
delete result.output;
delete result.output
}
return this.finalize(result);
},

return this.finalize(result)
},
enter: function(frame) {
var call = {
type: frame.getType(),
from: toHex(frame.getFrom()),
to: toHex(frame.getTo()),
input: toHex(frame.getInput()),
gas: '0x' + bigInt(frame.getGas()).toString('16'),
}
if (frame.getValue() !== undefined){
call.value='0x' + bigInt(frame.getValue()).toString(16)
}
this.callstack.push(call)
},
exit: function(frameResult) {
var len = this.callstack.length
if (len > 1) {
var call = this.callstack.pop()
call.gasUsed = '0x' + bigInt(frameResult.getGasUsed()).toString('16')
var error = frameResult.getError()
if (error === undefined) {
call.output = toHex(frameResult.getOutput())
} else {
call.error = error
if (call.type === 'CREATE' || call.type === 'CREATE2') {
delete call.to
}
}
len -= 1
if (this.callstack[len-1].calls === undefined) {
this.callstack[len-1].calls = []
}
this.callstack[len-1].calls.push(call)
}
},
// finalize recreates a call object using the final desired field oder for json
// serialization. This is a nicety feature to pass meaningfully ordered results
// to users who don't interpret it, just display it.
Expand All @@ -242,14 +99,14 @@
}
for (var key in sorted) {
if (sorted[key] === undefined) {
delete sorted[key];
delete sorted[key]
}
}
if (sorted.calls !== undefined) {
for (var i=0; i<sorted.calls.length; i++) {
sorted.calls[i] = this.finalize(sorted.calls[i]);
sorted.calls[i] = this.finalize(sorted.calls[i])
}
}
return sorted;
return sorted
}
}