go-ethereum/ethchain/vm.go

544 lines
12 KiB
Go
Raw Normal View History

package ethchain
import (
2014-03-20 16:51:20 -05:00
_ "bytes"
2014-04-11 12:29:57 -05:00
"fmt"
"github.com/ethereum/eth-go/ethutil"
2014-03-20 16:51:20 -05:00
_ "github.com/obscuren/secp256k1-go"
_ "math"
"math/big"
)
var (
GasStep = big.NewInt(1)
GasSha = big.NewInt(20)
GasSLoad = big.NewInt(20)
GasSStore = big.NewInt(100)
GasBalance = big.NewInt(20)
GasCreate = big.NewInt(100)
GasCall = big.NewInt(20)
GasMemory = big.NewInt(1)
)
2014-04-27 09:50:44 -05:00
func CalculateTxGas(initSize, scriptSize *big.Int) *big.Int {
totalGas := new(big.Int)
totalGas.Add(totalGas, GasCreate)
txTotalBytes := new(big.Int).Add(initSize, scriptSize)
txTotalBytes.Div(txTotalBytes, ethutil.Big32)
totalGas.Add(totalGas, new(big.Int).Mul(txTotalBytes, GasSStore))
return totalGas
}
type Vm struct {
txPool *TxPool
// Stack for processing contracts
stack *Stack
// non-persistent key/value memory storage
mem map[string]*big.Int
vars RuntimeVars
state *State
2014-04-25 18:47:55 -05:00
stateManager *StateManager
}
type RuntimeVars struct {
2014-04-11 12:29:57 -05:00
Origin []byte
BlockNumber uint64
PrevHash []byte
Coinbase []byte
Time int64
Diff *big.Int
TxData []string
}
2014-04-25 18:47:55 -05:00
func NewVm(state *State, stateManager *StateManager, vars RuntimeVars) *Vm {
return &Vm{vars: vars, state: state, stateManager: stateManager}
}
2014-03-21 05:54:36 -05:00
var Pow256 = ethutil.BigPow(2, 256)
var isRequireError = false
func (vm *Vm) RunClosure(closure *Closure, hook DebugHook) (ret []byte, err error) {
// Recover from any require exception
defer func() {
if r := recover(); r != nil /*&& isRequireError*/ {
ret = closure.Return(nil)
err = fmt.Errorf("%v", r)
fmt.Println("vm err", err)
}
}()
2014-05-01 15:14:34 -05:00
ethutil.Config.Log.Debugf("[VM] Running closure %x\n", closure.object.Address())
// Memory for the current closure
2014-03-20 16:51:20 -05:00
mem := &Memory{}
// New stack (should this be shared?)
stack := NewStack()
require := func(m int) {
2014-04-18 06:41:07 -05:00
if stack.Len() < m {
isRequireError = true
panic(fmt.Sprintf("stack = %d, req = %d", stack.Len(), m))
}
}
// Instruction pointer
pc := big.NewInt(0)
2014-03-21 05:54:36 -05:00
// Current step count
step := 0
if ethutil.Config.Debug {
ethutil.Config.Log.Debugf("# op\n")
}
for {
// The base for all big integer arithmetic
base := new(big.Int)
step++
// Get the memory location of pc
2014-04-09 11:27:54 -05:00
val := closure.Get(pc)
// Get the opcode (it must be an opcode!)
op := OpCode(val.Uint())
2014-05-01 15:14:34 -05:00
/*
if ethutil.Config.Debug {
ethutil.Config.Log.Debugf("%-3d %-4s", pc, op.String())
}
*/
gas := new(big.Int)
useGas := func(amount *big.Int) {
2014-04-19 18:31:01 -05:00
gas.Add(gas, amount)
}
switch op {
case oSHA3:
useGas(GasSha)
case oSLOAD:
useGas(GasSLoad)
case oSSTORE:
var mult *big.Int
y, x := stack.Peekn()
val := closure.GetMem(x)
if val.IsEmpty() && len(y.Bytes()) > 0 {
mult = ethutil.Big2
} else if !val.IsEmpty() && len(y.Bytes()) == 0 {
mult = ethutil.Big0
} else {
mult = ethutil.Big1
}
2014-04-24 06:30:57 -05:00
useGas(new(big.Int).Mul(mult, GasSStore))
case oBALANCE:
useGas(GasBalance)
case oCREATE:
2014-04-27 09:50:44 -05:00
require(3)
args := stack.Get(big.NewInt(3))
initSize := new(big.Int).Add(args[1], args[0])
useGas(CalculateTxGas(initSize, ethutil.Big0))
case oCALL:
useGas(GasCall)
case oMLOAD, oMSIZE, oMSTORE8, oMSTORE:
useGas(GasMemory)
default:
useGas(GasStep)
}
if closure.Gas.Cmp(gas) < 0 {
2014-03-30 18:03:28 -05:00
ethutil.Config.Log.Debugln("Insufficient gas", closure.Gas, gas)
return closure.Return(nil), fmt.Errorf("insufficient gas %v %v", closure.Gas, gas)
}
2014-04-19 18:31:01 -05:00
// Sub the amount of gas from the remaining
2014-04-18 06:41:07 -05:00
closure.Gas.Sub(closure.Gas, gas)
switch op {
2014-03-21 05:54:36 -05:00
case oLOG:
stack.Print()
mem.Print()
// 0x20 range
2014-03-21 05:54:36 -05:00
case oADD:
require(2)
2014-03-21 05:54:36 -05:00
x, y := stack.Popn()
// (x + y) % 2 ** 256
base.Add(x, y)
// Pop result back on the stack
stack.Push(base)
case oSUB:
require(2)
2014-03-21 05:54:36 -05:00
x, y := stack.Popn()
// (x - y) % 2 ** 256
base.Sub(x, y)
// Pop result back on the stack
stack.Push(base)
case oMUL:
require(2)
2014-03-21 05:54:36 -05:00
x, y := stack.Popn()
// (x * y) % 2 ** 256
base.Mul(x, y)
// Pop result back on the stack
stack.Push(base)
case oDIV:
require(2)
2014-03-21 05:54:36 -05:00
x, y := stack.Popn()
// floor(x / y)
base.Div(x, y)
// Pop result back on the stack
stack.Push(base)
case oSDIV:
require(2)
2014-03-21 05:54:36 -05:00
x, y := stack.Popn()
// n > 2**255
if x.Cmp(Pow256) > 0 {
x.Sub(Pow256, x)
}
if y.Cmp(Pow256) > 0 {
y.Sub(Pow256, y)
}
z := new(big.Int)
z.Div(x, y)
if z.Cmp(Pow256) > 0 {
z.Sub(Pow256, z)
}
// Push result on to the stack
stack.Push(z)
case oMOD:
require(2)
2014-03-21 05:54:36 -05:00
x, y := stack.Popn()
base.Mod(x, y)
stack.Push(base)
case oSMOD:
require(2)
2014-03-21 05:54:36 -05:00
x, y := stack.Popn()
// n > 2**255
if x.Cmp(Pow256) > 0 {
x.Sub(Pow256, x)
}
if y.Cmp(Pow256) > 0 {
y.Sub(Pow256, y)
}
z := new(big.Int)
z.Mod(x, y)
if z.Cmp(Pow256) > 0 {
z.Sub(Pow256, z)
}
// Push result on to the stack
stack.Push(z)
case oEXP:
require(2)
2014-03-21 05:54:36 -05:00
x, y := stack.Popn()
base.Exp(x, y, Pow256)
stack.Push(base)
case oNEG:
require(1)
2014-03-21 05:54:36 -05:00
base.Sub(Pow256, stack.Pop())
stack.Push(base)
case oLT:
require(2)
2014-03-21 05:54:36 -05:00
x, y := stack.Popn()
// x < y
if x.Cmp(y) < 0 {
stack.Push(ethutil.BigTrue)
} else {
stack.Push(ethutil.BigFalse)
}
case oGT:
require(2)
2014-03-21 05:54:36 -05:00
x, y := stack.Popn()
// x > y
if x.Cmp(y) > 0 {
stack.Push(ethutil.BigTrue)
} else {
stack.Push(ethutil.BigFalse)
}
case oEQ:
require(2)
2014-03-21 05:54:36 -05:00
x, y := stack.Popn()
// x == y
if x.Cmp(y) == 0 {
stack.Push(ethutil.BigTrue)
} else {
stack.Push(ethutil.BigFalse)
}
case oNOT:
require(1)
x := stack.Pop()
if x.Cmp(ethutil.BigFalse) == 0 {
2014-03-21 05:54:36 -05:00
stack.Push(ethutil.BigTrue)
} else {
stack.Push(ethutil.BigFalse)
}
// 0x10 range
2014-03-21 05:54:36 -05:00
case oAND:
require(2)
2014-04-09 07:08:18 -05:00
x, y := stack.Popn()
if (x.Cmp(ethutil.BigTrue) >= 0) && (y.Cmp(ethutil.BigTrue) >= 0) {
stack.Push(ethutil.BigTrue)
} else {
stack.Push(ethutil.BigFalse)
}
2014-03-21 05:54:36 -05:00
case oOR:
require(2)
2014-04-09 07:08:18 -05:00
x, y := stack.Popn()
if (x.Cmp(ethutil.BigInt0) >= 0) || (y.Cmp(ethutil.BigInt0) >= 0) {
stack.Push(ethutil.BigTrue)
} else {
stack.Push(ethutil.BigFalse)
}
2014-03-21 05:54:36 -05:00
case oXOR:
require(2)
2014-04-09 07:08:18 -05:00
x, y := stack.Popn()
stack.Push(base.Xor(x, y))
2014-03-21 05:54:36 -05:00
case oBYTE:
require(2)
2014-04-09 07:08:18 -05:00
val, th := stack.Popn()
if th.Cmp(big.NewInt(32)) < 0 {
stack.Push(big.NewInt(int64(len(val.Bytes())-1) - th.Int64()))
} else {
stack.Push(ethutil.BigFalse)
}
2014-03-21 05:54:36 -05:00
// 0x20 range
2014-03-21 05:54:36 -05:00
case oSHA3:
require(2)
2014-04-09 07:08:18 -05:00
size, offset := stack.Popn()
data := mem.Get(offset.Int64(), size.Int64())
2014-03-21 05:54:36 -05:00
2014-04-09 07:08:18 -05:00
stack.Push(ethutil.BigD(data))
// 0x30 range
2014-03-21 05:54:36 -05:00
case oADDRESS:
stack.Push(ethutil.BigD(closure.Object().Address()))
2014-03-21 05:54:36 -05:00
case oBALANCE:
stack.Push(closure.Value)
2014-03-21 05:54:36 -05:00
case oORIGIN:
2014-04-11 12:29:57 -05:00
stack.Push(ethutil.BigD(vm.vars.Origin))
2014-03-21 05:54:36 -05:00
case oCALLER:
stack.Push(ethutil.BigD(closure.Callee().Address()))
2014-03-21 05:54:36 -05:00
case oCALLVALUE:
// FIXME: Original value of the call, not the current value
stack.Push(closure.Value)
case oCALLDATALOAD:
require(1)
offset := stack.Pop().Int64()
2014-04-24 06:30:57 -05:00
val := closure.Args[offset : offset+32]
stack.Push(ethutil.BigD(val))
2014-03-21 05:54:36 -05:00
case oCALLDATASIZE:
stack.Push(big.NewInt(int64(len(closure.Args))))
case oGASPRICE:
2014-04-19 18:31:01 -05:00
stack.Push(closure.Price)
2014-03-21 05:54:36 -05:00
// 0x40 range
2014-03-21 05:54:36 -05:00
case oPREVHASH:
2014-04-11 12:29:57 -05:00
stack.Push(ethutil.BigD(vm.vars.PrevHash))
2014-03-21 05:54:36 -05:00
case oCOINBASE:
2014-04-11 12:29:57 -05:00
stack.Push(ethutil.BigD(vm.vars.Coinbase))
2014-03-21 05:54:36 -05:00
case oTIMESTAMP:
2014-04-11 12:29:57 -05:00
stack.Push(big.NewInt(vm.vars.Time))
2014-03-21 05:54:36 -05:00
case oNUMBER:
2014-04-11 12:29:57 -05:00
stack.Push(big.NewInt(int64(vm.vars.BlockNumber)))
2014-03-21 05:54:36 -05:00
case oDIFFICULTY:
2014-04-11 12:29:57 -05:00
stack.Push(vm.vars.Diff)
2014-03-21 05:54:36 -05:00
case oGASLIMIT:
2014-04-19 18:31:01 -05:00
// TODO
stack.Push(big.NewInt(0))
2014-03-21 05:54:36 -05:00
// 0x50 range
case oPUSH: // Push PC+1 on to the stack
pc.Add(pc, ethutil.Big1)
2014-04-10 13:40:12 -05:00
data := closure.Gets(pc, big.NewInt(32))
val := ethutil.BigD(data.Bytes())
2014-04-10 13:40:12 -05:00
// Push value to stack
stack.Push(val)
2014-04-10 13:40:12 -05:00
pc.Add(pc, big.NewInt(31))
2014-04-11 23:13:42 -05:00
step++
2014-04-10 17:14:19 -05:00
case oPUSH20:
pc.Add(pc, ethutil.Big1)
data := closure.Gets(pc, big.NewInt(20))
val := ethutil.BigD(data.Bytes())
// Push value to stack
stack.Push(val)
pc.Add(pc, big.NewInt(19))
2014-04-11 23:13:42 -05:00
step++
2014-03-21 05:54:36 -05:00
case oPOP:
require(1)
stack.Pop()
2014-03-21 05:54:36 -05:00
case oDUP:
require(1)
stack.Push(stack.Peek())
2014-03-21 05:54:36 -05:00
case oSWAP:
require(2)
x, y := stack.Popn()
stack.Push(y)
stack.Push(x)
2014-03-21 05:54:36 -05:00
case oMLOAD:
require(1)
2014-03-21 05:54:36 -05:00
offset := stack.Pop()
stack.Push(ethutil.BigD(mem.Get(offset.Int64(), 32)))
case oMSTORE: // Store the value at stack top-1 in to memory at location stack top
require(2)
// Pop value of the stack
val, mStart := stack.Popn()
2014-03-20 16:51:20 -05:00
mem.Set(mStart.Int64(), 32, ethutil.BigToBytes(val, 256))
2014-03-21 05:54:36 -05:00
case oMSTORE8:
require(2)
val, mStart := stack.Popn()
base.And(val, new(big.Int).SetInt64(0xff))
mem.Set(mStart.Int64(), 32, ethutil.BigToBytes(base, 256))
2014-03-21 05:54:36 -05:00
case oSLOAD:
require(1)
loc := stack.Pop()
val := closure.GetMem(loc)
stack.Push(val.BigInt())
2014-03-21 05:54:36 -05:00
case oSSTORE:
require(2)
val, loc := stack.Popn()
closure.SetMem(loc, ethutil.NewValue(val))
// Add the change to manifest
vm.stateManager.manifest.AddStorageChange(closure.Object(), loc.Bytes(), val)
2014-03-21 05:54:36 -05:00
case oJUMP:
require(1)
pc = stack.Pop()
2014-04-19 18:31:01 -05:00
// Reduce pc by one because of the increment that's at the end of this for loop
pc.Sub(pc, ethutil.Big1)
2014-03-21 05:54:36 -05:00
case oJUMPI:
require(2)
cond, pos := stack.Popn()
if cond.Cmp(ethutil.BigTrue) == 0 {
pc = pos
2014-04-19 18:31:01 -05:00
pc.Sub(pc, ethutil.Big1)
}
2014-03-21 05:54:36 -05:00
case oPC:
stack.Push(pc)
2014-03-21 05:54:36 -05:00
case oMSIZE:
stack.Push(big.NewInt(int64(mem.Len())))
// 0x60 range
case oCREATE:
2014-04-27 09:50:44 -05:00
require(3)
value := stack.Pop()
size, offset := stack.Popn()
// Generate a new address
addr := ethutil.CreateAddress(closure.callee.Address(), closure.callee.N())
// Create a new contract
contract := NewContract(addr, value, []byte(""))
// Set the init script
contract.initScript = mem.Get(offset.Int64(), size.Int64())
// Transfer all remaining gas to the new
// contract so it may run the init script
gas := new(big.Int).Set(closure.Gas)
closure.Gas.Sub(closure.Gas, gas)
// Create the closure
closure := NewClosure(closure.callee,
closure.Object(),
contract.initScript,
vm.state,
gas,
closure.Price,
value)
// Call the closure and set the return value as
// main script.
closure.Script, err = closure.Call(vm, nil, hook)
if err != nil {
stack.Push(ethutil.BigFalse)
} else {
stack.Push(ethutil.BigD(addr))
vm.state.SetStateObject(contract)
}
case oCALL:
2014-04-18 06:41:07 -05:00
require(7)
2014-04-10 13:40:12 -05:00
// Closure addr
addr := stack.Pop()
// Pop gas and value of the stack.
gas, value := stack.Popn()
// Pop input size and offset
inSize, inOffset := stack.Popn()
2014-04-10 13:40:12 -05:00
// Pop return size and offset
retSize, retOffset := stack.Popn()
2014-04-19 18:31:01 -05:00
// Make sure there's enough gas
if closure.Gas.Cmp(gas) < 0 {
stack.Push(ethutil.BigFalse)
break
}
2014-03-21 05:54:36 -05:00
// Get the arguments from the memory
args := mem.Get(inOffset.Int64(), inSize.Int64())
// Fetch the contract which will serve as the closure body
contract := vm.state.GetContract(addr.Bytes())
2014-04-19 18:31:01 -05:00
if contract != nil {
// Prepay for the gas
// If gas is set to 0 use all remaining gas for the next call
if gas.Cmp(big.NewInt(0)) == 0 {
2014-04-27 09:50:44 -05:00
// Copy
gas = new(big.Int).Set(closure.Gas)
2014-04-19 18:31:01 -05:00
}
closure.Gas.Sub(closure.Gas, gas)
// Create a new callable closure
closure := NewClosure(closure.Object(), contract, contract.script, vm.state, gas, closure.Price, value)
2014-04-19 18:31:01 -05:00
// Executer the closure and get the return value (if any)
ret, err := closure.Call(vm, args, hook)
if err != nil {
stack.Push(ethutil.BigFalse)
2014-04-25 19:06:25 -05:00
// Reset the changes applied this object
//contract.State().Reset()
2014-04-19 18:31:01 -05:00
} else {
stack.Push(ethutil.BigTrue)
2014-04-25 19:06:25 -05:00
// Notify of the changes
vm.stateManager.manifest.AddObjectChange(contract)
2014-04-19 18:31:01 -05:00
}
mem.Set(retOffset.Int64(), retSize.Int64(), ret)
} else {
2014-04-19 18:31:01 -05:00
ethutil.Config.Log.Debugf("Contract %x not found\n", addr.Bytes())
stack.Push(ethutil.BigFalse)
}
case oRETURN:
require(2)
size, offset := stack.Popn()
2014-03-20 16:51:20 -05:00
ret := mem.Get(offset.Int64(), size.Int64())
return closure.Return(ret), nil
2014-03-21 05:54:36 -05:00
case oSUICIDE:
2014-05-01 15:14:34 -05:00
require(1)
receiver := vm.state.GetAccount(stack.Pop().Bytes())
receiver.AddAmount(closure.object.Amount)
vm.stateManager.manifest.AddObjectChange(receiver)
closure.object.state.Purge()
fallthrough
case oSTOP: // Stop the closure
return closure.Return(nil), nil
2014-03-21 05:54:36 -05:00
default:
2014-04-10 13:40:12 -05:00
ethutil.Config.Log.Debugf("Invalid opcode %x\n", op)
return closure.Return(nil), fmt.Errorf("Invalid opcode %x", op)
}
pc.Add(pc, ethutil.Big1)
if hook != nil {
2014-04-11 23:13:42 -05:00
hook(step-1, op, mem, stack)
2014-04-11 12:29:57 -05:00
}
}
}