7705b23f24
Transactions can no longer serve as callers. Accounts are now the initial callee of closures. Transactions now serve as transport to call closures.
528 lines
13 KiB
Go
528 lines
13 KiB
Go
package ethchain
|
|
|
|
import (
|
|
_ "bytes"
|
|
"fmt"
|
|
"github.com/ethereum/eth-go/ethutil"
|
|
_ "github.com/obscuren/secp256k1-go"
|
|
"log"
|
|
_ "math"
|
|
"math/big"
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
type RuntimeVars struct {
|
|
address []byte
|
|
blockNumber uint64
|
|
sender []byte
|
|
prevHash []byte
|
|
coinbase []byte
|
|
time int64
|
|
diff *big.Int
|
|
txValue *big.Int
|
|
txData []string
|
|
}
|
|
|
|
func NewVm(state *State, vars RuntimeVars) *Vm {
|
|
return &Vm{vars: vars, state: state}
|
|
}
|
|
|
|
func (vm *Vm) RunClosure(closure *Closure) []byte {
|
|
// If the amount of gas supplied is less equal to 0
|
|
if closure.GetGas().Cmp(big.NewInt(0)) <= 0 {
|
|
// TODO Do something
|
|
}
|
|
|
|
// Memory for the current closure
|
|
mem := &Memory{}
|
|
// New stack (should this be shared?)
|
|
stack := NewStack()
|
|
// Instruction pointer
|
|
pc := int64(0)
|
|
// Current address
|
|
//addr := vars.address
|
|
step := 0
|
|
|
|
if ethutil.Config.Debug {
|
|
ethutil.Config.Log.Debugf("# op\n")
|
|
}
|
|
|
|
for {
|
|
step++
|
|
// Get the memory location of pc
|
|
val := closure.GetMem(pc)
|
|
// Get the opcode (it must be an opcode!)
|
|
op := OpCode(val.Uint())
|
|
if ethutil.Config.Debug {
|
|
ethutil.Config.Log.Debugf("%-3d %-4s", pc, op.String())
|
|
}
|
|
|
|
// TODO Get each instruction cost properly
|
|
fee := new(big.Int)
|
|
fee.Add(fee, big.NewInt(1000))
|
|
|
|
if closure.GetGas().Cmp(fee) < 0 {
|
|
return closure.Return(nil)
|
|
}
|
|
|
|
switch op {
|
|
case oSTOP: // Stop the closure
|
|
return closure.Return(nil)
|
|
case oPUSH: // Push PC+1 on to the stack
|
|
pc++
|
|
val := closure.GetMem(pc).BigInt()
|
|
stack.Push(val)
|
|
case oMSTORE: // Store the value at stack top-1 in to memory at location stack top
|
|
// Pop value of the stack
|
|
val, mStart := stack.Popn()
|
|
mem.Set(mStart.Int64(), 32, ethutil.BigToBytes(val, 256))
|
|
|
|
case oCALLDATA:
|
|
offset := stack.Pop()
|
|
mem.Set(offset.Int64(), int64(len(closure.Args)), closure.Args)
|
|
case oCALL:
|
|
// Pop return size and offset
|
|
retSize, retOffset := stack.Popn()
|
|
// Pop input size and offset
|
|
inSize, inOffset := stack.Popn()
|
|
// TODO remove me.
|
|
fmt.Sprintln(inSize, inOffset)
|
|
// Pop gas and value of the stack.
|
|
gas, value := stack.Popn()
|
|
// Closure addr
|
|
addr := stack.Pop()
|
|
// Fetch the contract which will serve as the closure body
|
|
contract := vm.state.GetContract(addr.Bytes())
|
|
// Create a new callable closure
|
|
closure := NewClosure(closure, contract, vm.state, gas, value)
|
|
// Executer the closure and get the return value (if any)
|
|
ret := closure.Call(vm, nil)
|
|
mem.Set(retOffset.Int64(), retSize.Int64(), ret)
|
|
case oRETURN:
|
|
size, offset := stack.Popn()
|
|
ret := mem.Get(offset.Int64(), size.Int64())
|
|
|
|
return closure.Return(ret)
|
|
}
|
|
|
|
pc++
|
|
}
|
|
}
|
|
|
|
/*
|
|
// Old VM code
|
|
func (vm *Vm) Process(contract *Contract, state *State, vars RuntimeVars) {
|
|
vm.mem = make(map[string]*big.Int)
|
|
vm.stack = NewStack()
|
|
|
|
addr := vars.address // tx.Hash()[12:]
|
|
// Instruction pointer
|
|
pc := int64(0)
|
|
|
|
if contract == nil {
|
|
fmt.Println("Contract not found")
|
|
return
|
|
}
|
|
|
|
Pow256 := ethutil.BigPow(2, 256)
|
|
|
|
if ethutil.Config.Debug {
|
|
ethutil.Config.Log.Debugf("# op\n")
|
|
}
|
|
|
|
stepcount := 0
|
|
totalFee := new(big.Int)
|
|
|
|
out:
|
|
for {
|
|
stepcount++
|
|
// The base big int for all calculations. Use this for any results.
|
|
base := new(big.Int)
|
|
val := contract.GetMem(pc)
|
|
//fmt.Printf("%x = %d, %v %x\n", r, len(r), v, nb)
|
|
op := OpCode(val.Uint())
|
|
|
|
var fee *big.Int = new(big.Int)
|
|
var fee2 *big.Int = new(big.Int)
|
|
if stepcount > 16 {
|
|
fee.Add(fee, StepFee)
|
|
}
|
|
|
|
// Calculate the fees
|
|
switch op {
|
|
case oSSTORE:
|
|
y, x := vm.stack.Peekn()
|
|
val := contract.Addr(ethutil.BigToBytes(x, 256))
|
|
if val.IsEmpty() && len(y.Bytes()) > 0 {
|
|
fee2.Add(DataFee, StoreFee)
|
|
} else {
|
|
fee2.Sub(DataFee, StoreFee)
|
|
}
|
|
case oSLOAD:
|
|
fee.Add(fee, StoreFee)
|
|
case oEXTRO, oBALANCE:
|
|
fee.Add(fee, ExtroFee)
|
|
case oSHA256, oRIPEMD160, oECMUL, oECADD, oECSIGN, oECRECOVER, oECVALID:
|
|
fee.Add(fee, CryptoFee)
|
|
case oMKTX:
|
|
fee.Add(fee, ContractFee)
|
|
}
|
|
|
|
tf := new(big.Int).Add(fee, fee2)
|
|
if contract.Amount.Cmp(tf) < 0 {
|
|
fmt.Println("Insufficient fees to continue running the contract", tf, contract.Amount)
|
|
break
|
|
}
|
|
// Add the fee to the total fee. It's subtracted when we're done looping
|
|
totalFee.Add(totalFee, tf)
|
|
|
|
if ethutil.Config.Debug {
|
|
ethutil.Config.Log.Debugf("%-3d %-4s", pc, op.String())
|
|
}
|
|
|
|
switch op {
|
|
case oSTOP:
|
|
fmt.Println("")
|
|
break out
|
|
case oADD:
|
|
x, y := vm.stack.Popn()
|
|
// (x + y) % 2 ** 256
|
|
base.Add(x, y)
|
|
base.Mod(base, Pow256)
|
|
// Pop result back on the stack
|
|
vm.stack.Push(base)
|
|
case oSUB:
|
|
x, y := vm.stack.Popn()
|
|
// (x - y) % 2 ** 256
|
|
base.Sub(x, y)
|
|
base.Mod(base, Pow256)
|
|
// Pop result back on the stack
|
|
vm.stack.Push(base)
|
|
case oMUL:
|
|
x, y := vm.stack.Popn()
|
|
// (x * y) % 2 ** 256
|
|
base.Mul(x, y)
|
|
base.Mod(base, Pow256)
|
|
// Pop result back on the stack
|
|
vm.stack.Push(base)
|
|
case oDIV:
|
|
x, y := vm.stack.Popn()
|
|
// floor(x / y)
|
|
base.Div(x, y)
|
|
// Pop result back on the stack
|
|
vm.stack.Push(base)
|
|
case oSDIV:
|
|
x, y := vm.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
|
|
vm.stack.Push(z)
|
|
case oMOD:
|
|
x, y := vm.stack.Popn()
|
|
base.Mod(x, y)
|
|
vm.stack.Push(base)
|
|
case oSMOD:
|
|
x, y := vm.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
|
|
vm.stack.Push(z)
|
|
case oEXP:
|
|
x, y := vm.stack.Popn()
|
|
base.Exp(x, y, Pow256)
|
|
|
|
vm.stack.Push(base)
|
|
case oNEG:
|
|
base.Sub(Pow256, vm.stack.Pop())
|
|
vm.stack.Push(base)
|
|
case oLT:
|
|
x, y := vm.stack.Popn()
|
|
// x < y
|
|
if x.Cmp(y) < 0 {
|
|
vm.stack.Push(ethutil.BigTrue)
|
|
} else {
|
|
vm.stack.Push(ethutil.BigFalse)
|
|
}
|
|
case oLE:
|
|
x, y := vm.stack.Popn()
|
|
// x <= y
|
|
if x.Cmp(y) < 1 {
|
|
vm.stack.Push(ethutil.BigTrue)
|
|
} else {
|
|
vm.stack.Push(ethutil.BigFalse)
|
|
}
|
|
case oGT:
|
|
x, y := vm.stack.Popn()
|
|
// x > y
|
|
if x.Cmp(y) > 0 {
|
|
vm.stack.Push(ethutil.BigTrue)
|
|
} else {
|
|
vm.stack.Push(ethutil.BigFalse)
|
|
}
|
|
case oGE:
|
|
x, y := vm.stack.Popn()
|
|
// x >= y
|
|
if x.Cmp(y) > -1 {
|
|
vm.stack.Push(ethutil.BigTrue)
|
|
} else {
|
|
vm.stack.Push(ethutil.BigFalse)
|
|
}
|
|
case oNOT:
|
|
x, y := vm.stack.Popn()
|
|
// x != y
|
|
if x.Cmp(y) != 0 {
|
|
vm.stack.Push(ethutil.BigTrue)
|
|
} else {
|
|
vm.stack.Push(ethutil.BigFalse)
|
|
}
|
|
case oMYADDRESS:
|
|
vm.stack.Push(ethutil.BigD(addr))
|
|
case oTXSENDER:
|
|
vm.stack.Push(ethutil.BigD(vars.sender))
|
|
case oTXVALUE:
|
|
vm.stack.Push(vars.txValue)
|
|
case oTXDATAN:
|
|
vm.stack.Push(big.NewInt(int64(len(vars.txData))))
|
|
case oTXDATA:
|
|
v := vm.stack.Pop()
|
|
// v >= len(data)
|
|
if v.Cmp(big.NewInt(int64(len(vars.txData)))) >= 0 {
|
|
vm.stack.Push(ethutil.Big("0"))
|
|
} else {
|
|
vm.stack.Push(ethutil.Big(vars.txData[v.Uint64()]))
|
|
}
|
|
case oBLK_PREVHASH:
|
|
vm.stack.Push(ethutil.BigD(vars.prevHash))
|
|
case oBLK_COINBASE:
|
|
vm.stack.Push(ethutil.BigD(vars.coinbase))
|
|
case oBLK_TIMESTAMP:
|
|
vm.stack.Push(big.NewInt(vars.time))
|
|
case oBLK_NUMBER:
|
|
vm.stack.Push(big.NewInt(int64(vars.blockNumber)))
|
|
case oBLK_DIFFICULTY:
|
|
vm.stack.Push(vars.diff)
|
|
case oBASEFEE:
|
|
// e = 10^21
|
|
e := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(21), big.NewInt(0))
|
|
d := new(big.Rat)
|
|
d.SetInt(vars.diff)
|
|
c := new(big.Rat)
|
|
c.SetFloat64(0.5)
|
|
// d = diff / 0.5
|
|
d.Quo(d, c)
|
|
// base = floor(d)
|
|
base.Div(d.Num(), d.Denom())
|
|
|
|
x := new(big.Int)
|
|
x.Div(e, base)
|
|
|
|
// x = floor(10^21 / floor(diff^0.5))
|
|
vm.stack.Push(x)
|
|
case oSHA256, oSHA3, oRIPEMD160:
|
|
// This is probably save
|
|
// ceil(pop / 32)
|
|
length := int(math.Ceil(float64(vm.stack.Pop().Uint64()) / 32.0))
|
|
// New buffer which will contain the concatenated popped items
|
|
data := new(bytes.Buffer)
|
|
for i := 0; i < length; i++ {
|
|
// Encode the number to bytes and have it 32bytes long
|
|
num := ethutil.NumberToBytes(vm.stack.Pop().Bytes(), 256)
|
|
data.WriteString(string(num))
|
|
}
|
|
|
|
if op == oSHA256 {
|
|
vm.stack.Push(base.SetBytes(ethutil.Sha256Bin(data.Bytes())))
|
|
} else if op == oSHA3 {
|
|
vm.stack.Push(base.SetBytes(ethutil.Sha3Bin(data.Bytes())))
|
|
} else {
|
|
vm.stack.Push(base.SetBytes(ethutil.Ripemd160(data.Bytes())))
|
|
}
|
|
case oECMUL:
|
|
y := vm.stack.Pop()
|
|
x := vm.stack.Pop()
|
|
//n := vm.stack.Pop()
|
|
|
|
//if ethutil.Big(x).Cmp(ethutil.Big(y)) {
|
|
data := new(bytes.Buffer)
|
|
data.WriteString(x.String())
|
|
data.WriteString(y.String())
|
|
if secp256k1.VerifyPubkeyValidity(data.Bytes()) == 1 {
|
|
// TODO
|
|
} else {
|
|
// Invalid, push infinity
|
|
vm.stack.Push(ethutil.Big("0"))
|
|
vm.stack.Push(ethutil.Big("0"))
|
|
}
|
|
//} else {
|
|
// // Invalid, push infinity
|
|
// vm.stack.Push("0")
|
|
// vm.stack.Push("0")
|
|
//}
|
|
|
|
case oECADD:
|
|
case oECSIGN:
|
|
case oECRECOVER:
|
|
case oECVALID:
|
|
case oPUSH:
|
|
pc++
|
|
vm.stack.Push(contract.GetMem(pc).BigInt())
|
|
case oPOP:
|
|
// Pop current value of the stack
|
|
vm.stack.Pop()
|
|
case oDUP:
|
|
// Dup top stack
|
|
x := vm.stack.Pop()
|
|
vm.stack.Push(x)
|
|
vm.stack.Push(x)
|
|
case oSWAP:
|
|
// Swap two top most values
|
|
x, y := vm.stack.Popn()
|
|
vm.stack.Push(y)
|
|
vm.stack.Push(x)
|
|
case oMLOAD:
|
|
x := vm.stack.Pop()
|
|
vm.stack.Push(vm.mem[x.String()])
|
|
case oMSTORE:
|
|
x, y := vm.stack.Popn()
|
|
vm.mem[x.String()] = y
|
|
case oSLOAD:
|
|
// Load the value in storage and push it on the stack
|
|
x := vm.stack.Pop()
|
|
// decode the object as a big integer
|
|
decoder := contract.Addr(x.Bytes())
|
|
if !decoder.IsNil() {
|
|
vm.stack.Push(decoder.BigInt())
|
|
} else {
|
|
vm.stack.Push(ethutil.BigFalse)
|
|
}
|
|
case oSSTORE:
|
|
// Store Y at index X
|
|
y, x := vm.stack.Popn()
|
|
addr := ethutil.BigToBytes(x, 256)
|
|
fmt.Printf(" => %x (%v) @ %v", y.Bytes(), y, ethutil.BigD(addr))
|
|
contract.SetAddr(addr, y)
|
|
//contract.State().Update(string(idx), string(y))
|
|
case oJMP:
|
|
x := vm.stack.Pop().Int64()
|
|
// Set pc to x - 1 (minus one so the incrementing at the end won't effect it)
|
|
pc = x
|
|
pc--
|
|
case oJMPI:
|
|
x := vm.stack.Pop()
|
|
// Set pc to x if it's non zero
|
|
if x.Cmp(ethutil.BigFalse) != 0 {
|
|
pc = x.Int64()
|
|
pc--
|
|
}
|
|
case oIND:
|
|
vm.stack.Push(big.NewInt(int64(pc)))
|
|
case oEXTRO:
|
|
memAddr := vm.stack.Pop()
|
|
contractAddr := vm.stack.Pop().Bytes()
|
|
|
|
// Push the contract's memory on to the stack
|
|
vm.stack.Push(contractMemory(state, contractAddr, memAddr))
|
|
case oBALANCE:
|
|
// Pushes the balance of the popped value on to the stack
|
|
account := state.GetAccount(vm.stack.Pop().Bytes())
|
|
vm.stack.Push(account.Amount)
|
|
case oMKTX:
|
|
addr, value := vm.stack.Popn()
|
|
from, length := vm.stack.Popn()
|
|
|
|
makeInlineTx(addr.Bytes(), value, from, length, contract, state)
|
|
case oSUICIDE:
|
|
recAddr := vm.stack.Pop().Bytes()
|
|
// Purge all memory
|
|
deletedMemory := contract.state.Purge()
|
|
// Add refunds to the pop'ed address
|
|
refund := new(big.Int).Mul(StoreFee, big.NewInt(int64(deletedMemory)))
|
|
account := state.GetAccount(recAddr)
|
|
account.Amount.Add(account.Amount, refund)
|
|
// Update the refunding address
|
|
state.UpdateAccount(recAddr, account)
|
|
// Delete the contract
|
|
state.trie.Update(string(addr), "")
|
|
|
|
ethutil.Config.Log.Debugf("(%d) => %x\n", deletedMemory, recAddr)
|
|
break out
|
|
default:
|
|
fmt.Printf("Invalid OPCODE: %x\n", op)
|
|
}
|
|
ethutil.Config.Log.Debugln("")
|
|
//vm.stack.Print()
|
|
pc++
|
|
}
|
|
|
|
state.UpdateContract(addr, contract)
|
|
}
|
|
*/
|
|
|
|
func makeInlineTx(addr []byte, value, from, length *big.Int, contract *Contract, state *State) {
|
|
ethutil.Config.Log.Debugf(" => creating inline tx %x %v %v %v", addr, value, from, length)
|
|
j := int64(0)
|
|
dataItems := make([]string, int(length.Uint64()))
|
|
for i := from.Int64(); i < length.Int64(); i++ {
|
|
dataItems[j] = contract.GetMem(j).Str()
|
|
j++
|
|
}
|
|
|
|
tx := NewTransaction(addr, value, dataItems)
|
|
if tx.IsContract() {
|
|
contract := MakeContract(tx, state)
|
|
state.UpdateContract(tx.Hash()[12:], contract)
|
|
} else {
|
|
account := state.GetAccount(tx.Recipient)
|
|
account.Amount.Add(account.Amount, tx.Value)
|
|
state.UpdateAccount(tx.Recipient, account)
|
|
}
|
|
}
|
|
|
|
// Returns an address from the specified contract's address
|
|
func contractMemory(state *State, contractAddr []byte, memAddr *big.Int) *big.Int {
|
|
contract := state.GetContract(contractAddr)
|
|
if contract == nil {
|
|
log.Panicf("invalid contract addr %x", contractAddr)
|
|
}
|
|
val := state.trie.Get(memAddr.String())
|
|
|
|
// decode the object as a big integer
|
|
decoder := ethutil.NewValueFromBytes([]byte(val))
|
|
if decoder.IsNil() {
|
|
return ethutil.BigFalse
|
|
}
|
|
|
|
return decoder.BigInt()
|
|
}
|