-
Notifications
You must be signed in to change notification settings - Fork 20.2k
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
core/vm: 64 bit gas instructions #3514
Conversation
@obscuren, thanks for your PR! By analyzing the history of the files in this pull request, we identified @Gustav-Simonsson, @leijurv and @pirapira to be potential reviewers. |
7ff8ada
to
4e4775a
Compare
PR vs
PR vs
|
Regarding the intpool... It looks a bit 'dangerous', in that it seems relatively easy to make a mistake there. I feel it would be good to have some additional tests for coverage; not only to ensure that no consensus error occurs, but specifically it would be good to run the VM in 'checked' mode where it performs additional tests on the pool. Examples of what could be done in checked mode:
I'm also concerned whether journal reverts can somehow hold a reference to a pooled bigint. And how we can ensure that such case does not occur in the future (because from what I've seen now, all journal entries appear to instantiate new bigints). Third, it probably makes sense to have some rudimentary counter on the pool - it does not appear limited? If you run a node for several weeks, and there's a skewed balance (if there are more pushing than popping), couldn't that eventually become a problem.. ? The |
common/math/integer.go
Outdated
return x + y, y > gmath.MaxUint64-x | ||
} | ||
|
||
// IsAddSafe returns whether the multiplication is safe and does not overflow. |
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.
// IsAddSafe returns whether the multiplication is safe and does not overflow.
Should be
// SafeMul returns multiplication result and whether overflow occurred.
common/math/integer.go
Outdated
return x - y, x < y | ||
} | ||
|
||
// IsAddSafe returns whether the addition is safe and does not overflow. |
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.
// IsAddSafe returns whether the addition is safe and does not overflow.
Should be
// SafeAdd returns the result and whether overflow occurred.
common/math/integer.go
Outdated
* NOTE: The following methods need to be optimised using either bit checking or asm | ||
*/ | ||
|
||
// IsMinSafe returns whether the subtraction is safe and does not overflow. |
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.
Should be
// SafeSub returns subtraction result and whether overflow occurred
core/state_transition.go
Outdated
} | ||
} | ||
|
||
// ApplyMessage computes the new state by applying the given message | ||
// against the old state within the environment. | ||
// against the old state within the evmironment. |
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.
environment
core/state_transition.go
Outdated
@@ -209,34 +212,40 @@ func (self *StateTransition) preCheck() (err error) { | |||
return nil | |||
} | |||
|
|||
// TransitionDb will move the state by applying the message against the given environment. | |||
// TransitionDb will move the state by applying the message against the given evmironment. |
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.
environment
I agree, I'll work on those.
If that were the case we'd already have issues (*). I'm going to assume it's not the case and that our current network would have already had ran in to any issues.
(*) The int pool is being recreated each time a interpreter is instantiated, which happens for each transaction. I'm not sure how the pool can grow very large either. Ints must come from either a |
core/vm/contracts.go
Outdated
n.Mul(n, params.Sha256WordGas) | ||
return n.Add(n, params.Sha256Gas) | ||
func (c *sha256) RequiredGas(inputSize int) uint64 { | ||
return uint64(inputSize+31)/32*params.Sha256WordGas + params.Sha256Gas |
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.
Shouldn't these use the SafeXXX
-functions?
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.
Technically yes, but you'd need a significant amount of gas to make this overfow. There simply isn't enough ether to make it significant. Even if you'd pass it zeros only, you'd still need an enormous amount amount of ether to make the initial call.
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.
Right, because the memory expansion happens first... If there was a precompile which actually was calleable with e.g. a "num_rounds" of hashing, it could be an issue, but you're right.
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.
That and the initial call (tx) you pay per byte for the transaction.
core/vm/contracts.go
Outdated
n.Mul(n, params.Ripemd160WordGas) | ||
return n.Add(n, params.Ripemd160Gas) | ||
func (c *ripemd160) RequiredGas(inputSize int) uint64 { | ||
return uint64(inputSize+31)/32*params.Ripemd160WordGas + params.Ripemd160Gas |
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.
And here?
core/vm/contracts.go
Outdated
|
||
return n.Add(n, params.IdentityGas) | ||
func (c *dataCopy) RequiredGas(inputSize int) uint64 { | ||
return uint64(inputSize+31)/32*params.IdentityWordGas + params.IdentityGas |
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.
Dito
core/vm/gas.go
Outdated
} | ||
return nil | ||
func toWordSize(size uint64) uint64 { | ||
return (size + 31) / 32 |
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.
Is this function still used ? If so, should check for overflow... (?)
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.
The same as above. The size would have to be very large for it to become an issue and there isn't enough ether for this. This number is always the results of the furthest memory byte, not the extra bytes added.
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.
On a second thought, I think it might be worth it to call this unsafeToWordSize
indicating caution.
//fmt.Printf("%04d: %8v cost = %-8d stack = %-8d ERR = %v\n", pc, op, cost, stack.len(), err) | ||
// TODO update the tracer | ||
g, c := new(big.Int).SetUint64(contract.Gas), new(big.Int).SetUint64(cost) | ||
evm.cfg.Tracer.CaptureState(evm.env, pc, op, g, c, mem, stack, contract, evm.env.depth, err) |
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.
You've added a comment to update the tracer, but as it is, the tracer could potentially hold on to bigints from the pool, no ? So that should go in before it is merged.
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.
Update the tracer to use uint64
, I meant. I'm not sure how the tracer can retain pooled integers? If anything, it could already hold on to integers by accessing the stack directly.
b70dc5c
to
0e8c41e
Compare
EVM integer pool integrity verifier has been added @holiman: |
core/vm/intpool.go
Outdated
// can be reused for all big.Int operations. | ||
type intPool struct { | ||
pool *Stack | ||
debug bool |
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.
Maybe I'm blind.. but I don't see any usage of debug
?
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, that needs to be removed. Thanks
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.
✔️
core/state_transition.go
Outdated
// TODO convert to uint64 | ||
intrinsicGas := IntrinsicGas(self.data, contractCreation, homestead) | ||
if intrinsicGas.BitLen() > 64 { | ||
return nil, nil, nil, vm.ErrOutOfGas |
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.
Use InvalidTxError
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.
✔️
core/vm/contracts.go
Outdated
n := big.NewInt(int64(inputSize+31) / 32) | ||
n.Mul(n, params.Sha256WordGas) | ||
return n.Add(n, params.Sha256Gas) | ||
func (c *sha256) RequiredGas(inputSize int) uint64 { |
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.
Leave in code comment about why no overflow checking is performed.
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.
✔️
core/vm/evm.go
Outdated
evm.StateDB.RevertToSnapshot(snapshot) | ||
contract.UseGas(contract.Gas) |
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.
Remove this line
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.
✔️
core/vm/gas.go
Outdated
GasMidStep = big.NewInt(8) | ||
GasSlowStep = big.NewInt(10) | ||
GasExtStep = big.NewInt(20) | ||
const ( |
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.
Make these typed
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.
✔️
core/vm/gas.go
Outdated
if gasTable.CreateBySuicide > 0 { | ||
availableGas = availableGas - base | ||
gas := availableGas - availableGas/64 | ||
if callCost.BitLen() > 64 || gas < callCost.Uint64() { |
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.
Add comment on the BitLen check
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.
✔️
return nil, fmt.Errorf("invalid opcode %x", DELEGATECALL) | ||
} | ||
|
||
gas, to, inOffset, inSize, outOffset, outSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() | ||
gas, to, inOffset, inSize, outOffset, outSize := stack.pop().Uint64(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() |
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.
Check 64bit unmetered
core/vm/jump_table.go
Outdated
"math/big" | ||
|
||
"github.com/ethereum/go-ethereum/params" | ||
) | ||
|
||
type ( | ||
executionFunc func(pc *uint64, env *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) | ||
gasFunc func(params.GasTable, *EVM, *Contract, *Stack, *Memory, *big.Int) *big.Int | ||
gasFunc func(params.GasTable, *EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) |
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.
add paramater name or comment
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.
✔️
core/vm/stack_table.go
Outdated
@@ -12,8 +12,8 @@ func makeStackFunc(pop, push int) stackValidationFunc { | |||
return err | |||
} | |||
|
|||
if push > 0 && int64(stack.len()-pop+push) > params.StackLimit.Int64() { | |||
return fmt.Errorf("stack limit reached %d (%d)", stack.len(), params.StackLimit.Int64()) | |||
if push > 0 && int64(stack.len()-pop+push) > int64(params.StackLimit) { |
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.
conversion
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.
What was this again @fjl ...
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.
✔️
params/protocol_params.go
Outdated
|
||
MaxCodeSize = 24576 | ||
) | ||
|
||
var ( | ||
GasLimitBoundDivisor *big.Int = big.NewInt(1024) // The bound divisor of the gas limit, used in update calculations. |
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.
remove *big.Int
type
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.
✔️
core/vm/int_pool_verifier.go
Outdated
|
||
const verifyPool = true | ||
|
||
var checkVal = big.NewInt(-42) |
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.
Move to int_pool.go and use this variable for i.Set(checkVal)
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.
✔️
e4e93da
to
f83884e
Compare
Before anyone merges, ping me. I want to clean up the commit message. |
internal/ethapi/api.go
Outdated
}() | ||
|
||
// Setup the gas pool (also for unmetered requests) | ||
// and apple the message. |
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.
apple -> apply
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.
The code looks OK. It's hard to see whether all possible overflow situations are covered though.
I'm running a full sync in checked mode, will report back here when it's done.
Reworked the EVM gas instructions to use 64bit integers rather than arbitrary size big ints. All gas operations, be it additions, multiplications or divisions, are checked and guarded against 64 bit integer overflows. In additon, most of the protocol paramaters in the params package have been converted to uint64 and are now constants rather than variables. * common/math: added overflow check ops * core: vmenv, env renamed to evm * eth, internal/ethapi, les: unmetered eth_call and cancel methods * core/vm: implemented big.Int pool for evm instructions * core/vm: unexported intPool methods & verification methods * core/vm: added memoryGasCost overflow check and test
Revert "params: core, core/vm, miner: 64bit gas instructions (#3514)"
Reworked the EVM gas instructions to use 64bit integers rather than arbitrary size big ints. All gas operations, be it additions, multiplications or divisions, are checked and guarded against 64 bit integer overflows. In additon, most of the protocol paramaters in the params package have been converted to uint64 and are now constants rather than variables. * common/math: added overflow check ops * core: vmenv, env renamed to evm * eth, internal/ethapi, les: unmetered eth_call and cancel methods * core/vm: implemented big.Int pool for evm instructions * core/vm: unexported intPool methods & verification methods * core/vm: added memoryGasCost overflow check and test
This PR changes all gas instructions from
*big.Int
touint64
, a new theoretical limit.It also implements a new
*big.Int
instruction pool, which contains reusable*big.Int
s. Instructions found incore/vm/instructions.go
do no longer initialise newbig.Int
s but instead callintPool.Get()
returning either a reusable integer -- beware this may contain an old value -- ornew(big.Int)
if the pool is empty. I've decided to implement my own pool instead of using thesync.Pool
because I do not require a thread safe pool and I do not want the typecasting overhead required with the `sync.Pool.