-
Notifications
You must be signed in to change notification settings - Fork 87
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
Add gas_charge
function to the interpreter
#8
Conversation
The gas cost for every opcode is expected to be decomposed into atomic `GasUnits`. These will represent individual operations performed while executing the opcode. Some examples are: Arithmetic Branching MemoryAlloc(bytes) MemoryOwnership MemoryWrite(bytes) RegisterRead RegisterWrite All these atomic operations are associated with a constant gas cost. Having a mapping between a given opcode + VM state -> decomposed gas units, it is trivial to calculate the gas cost for any code execution. Currently, the opcode decomposition into gas units is performed manually. It can, however, and trivially, be converted into build-time opcode execution syntax tree conversion into gas units.
src/interpreter/gas.rs
Outdated
// TODO enable const flag | ||
// https:/rust-lang/rust/issues/57349 | ||
pub fn gas_charge(&mut self, op: &Opcode) -> bool { | ||
let cost = self.gas_price() * self.gas_cost(op); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, so gas price * gas used is the cost in coins that ends up being deducted from the free balance of the zero-color coin. When you do gas accounting during execution (here) you only need to care about gas used, not gas price.
At the very end of transaction execution (either nice termination or panic), unused coins are refunded as change: https:/FuelLabs/fuel-specs/blob/c332683080ea3d65d43abe32f143b39526dcdc96/specs/protocol/tx_validity.md#correct-change
let cost = self.gas_price() * self.gas_cost(op); | |
let cost = self.gas_cost(op); |
src/interpreter/executors.rs
Outdated
@@ -356,205 +356,238 @@ where | |||
} | |||
|
|||
match op { | |||
Opcode::ADD(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { | |||
Opcode::ADD(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) && self.gas_charge(&op) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it be better to have these functions return results with more specific error types besides ExecuteError::OpcodeFailure(op)
?
Maybe something like this for example:
Opcode::ADD(ra, rb, rc) => {
Self::is_valid_register_triple_alu(ra, rb, rc)?;
self.gas_charge(&op)?;
self.alu_overflow(ra, Word::overflowing_add, self.registers[rb], self.registers[rc])
}
where gas_charge(&op)
returns something like Result<(), ExecuteError>
That way you can have errors for things like "OutOfGas" get propagated directly from the source and simplify the matching syntax a little.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm usually first returning a single bool
for these new functions and then later guaranteeing we have no added instructions for error traceability. What I have in mind is to create a raw super-optimal executor, and enable a feature "gas-profiler" that will allow us to trace the gas costs.
However, I just checked the release profile for an OutOfGas
error without additional metadata, and we have no additional overhead in this case! I'll add it, its indeed clearer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enable a feature "gas-profiler" that will allow us to trace the gas costs.
Just so we're on the same page, by gas profiler do you mean accounting for gas, period, or profiling things like hot spots of gas usage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hot spots. The idea I have in mind is to have a tracer that will decompose an executed transaction into a vector of GasUnit
Very often a opcode is created out of sized slices that are guaranteed to contain the needed bytes. This method will provide a fast way to perform conversion of bytes to opcodes without additional overhead or code verbosity.
Very often a opcode is created out of sized slices that are guaranteed to contain the needed bytes. This method will provide a fast way to perform conversion of bytes to opcodes without additional overhead or code verbosity.
Very often a opcode is created out of sized slices that are guaranteed to contain the needed bytes. This method will provide a fast way to perform conversion of bytes to opcodes without additional overhead or code verbosity.
Very often a opcode is created out of sized slices that are guaranteed to contain the needed bytes. This method will provide a fast way to perform conversion of bytes to opcodes without additional overhead or code verbosity.
The gas cost for every opcode is expected to be decomposed into atomic
GasUnits
.These will represent individual operations performed while executing the
opcode. Some examples are:
Arithmetic
Branching
MemoryOwnership
MemoryWrite(bytes)
RegisterRead
RegisterWrite
All these atomic operations are associated with a constant gas cost.
Having a mapping between a given opcode + VM state -> decomposed gas
units, it is trivial to calculate the gas cost for any code execution.
Currently, the opcode decomposition into gas units is performed
manually. It can, however, and trivially, be converted into build-time
opcode execution syntax tree conversion into gas units.