aboutsummaryrefslogtreecommitdiffstats
path: root/ethchain
diff options
context:
space:
mode:
authorobscuren <geffobscura@gmail.com>2014-02-24 19:10:45 +0800
committerobscuren <geffobscura@gmail.com>2014-02-24 19:10:45 +0800
commit377c9951033d4f8d157221fd36d15c39ae17cddc (patch)
tree41c1c6ec8e2e4998f10fba488ea2664c8d8e8ff3 /ethchain
parent3a45cdeaf9682dea0407f827571353220eaf257b (diff)
downloadgo-tangerine-377c9951033d4f8d157221fd36d15c39ae17cddc.tar.gz
go-tangerine-377c9951033d4f8d157221fd36d15c39ae17cddc.tar.zst
go-tangerine-377c9951033d4f8d157221fd36d15c39ae17cddc.zip
Separated the VM from the block manager and added states
Diffstat (limited to 'ethchain')
-rw-r--r--ethchain/state.go56
-rw-r--r--ethchain/vm.go437
-rw-r--r--ethchain/vm_test.go106
3 files changed, 599 insertions, 0 deletions
diff --git a/ethchain/state.go b/ethchain/state.go
new file mode 100644
index 000000000..1a18ea1d7
--- /dev/null
+++ b/ethchain/state.go
@@ -0,0 +1,56 @@
+package ethchain
+
+import (
+ "github.com/ethereum/eth-go/ethutil"
+ "math/big"
+)
+
+type State struct {
+ trie *ethutil.Trie
+}
+
+func NewState(trie *ethutil.Trie) *State {
+ return &State{trie: trie}
+}
+
+func (s *State) GetContract(addr []byte) *Contract {
+ data := s.trie.Get(string(addr))
+ if data == "" {
+ return nil
+ }
+
+ contract := &Contract{}
+ contract.RlpDecode([]byte(data))
+
+ return contract
+}
+
+func (s *State) UpdateContract(addr []byte, contract *Contract) {
+ s.trie.Update(string(addr), string(contract.RlpEncode()))
+}
+
+func Compile(code []string) (script []string) {
+ script = make([]string, len(code))
+ for i, val := range code {
+ instr, _ := ethutil.CompileInstr(val)
+
+ script[i] = string(instr)
+ }
+
+ return
+}
+
+func (s *State) GetAccount(addr []byte) (account *Address) {
+ data := s.trie.Get(string(addr))
+ if data == "" {
+ account = NewAddress(big.NewInt(0))
+ } else {
+ account = NewAddressFromData([]byte(data))
+ }
+
+ return
+}
+
+func (s *State) UpdateAccount(addr []byte, account *Address) {
+ s.trie.Update(string(addr), string(account.RlpEncode()))
+}
diff --git a/ethchain/vm.go b/ethchain/vm.go
new file mode 100644
index 000000000..d5f4d7ad6
--- /dev/null
+++ b/ethchain/vm.go
@@ -0,0 +1,437 @@
+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
+}
+
+type RuntimeVars struct {
+ address []byte
+ blockNumber uint64
+ sender []byte
+ prevHash []byte
+ coinbase []byte
+ time int64
+ diff *big.Int
+ txValue *big.Int
+ txData []string
+}
+
+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 := 0
+
+ if contract == nil {
+ fmt.Println("Contract not found")
+ return
+ }
+
+ Pow256 := ethutil.BigPow(2, 256)
+
+ if ethutil.Config.Debug {
+ fmt.Printf("# 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("Contract fee", ContractFee)
+ 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 {
+ fmt.Printf("%-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 := ethutil.NewValueFromBytes([]byte(contract.State().Get(x.String())))
+ 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 := int(vm.stack.Pop().Uint64())
+ // 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 = int(x.Uint64())
+ 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.NewIterator().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), "")
+
+ fmt.Printf("(%d) => %x\n", deletedMemory, recAddr)
+ break out
+ default:
+ fmt.Printf("Invalid OPCODE: %x\n", op)
+ }
+ fmt.Println("")
+ vm.stack.Print()
+ pc++
+ }
+
+ state.UpdateContract(addr, contract)
+}
+
+func makeInlineTx(addr []byte, value, from, length *big.Int, contract *Contract, state *State) {
+ fmt.Printf(" => creating inline tx %x %v %v %v", addr, value, from, length)
+ j := 0
+ dataItems := make([]string, int(length.Uint64()))
+ for i := from.Uint64(); i < length.Uint64(); 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()
+}
diff --git a/ethchain/vm_test.go b/ethchain/vm_test.go
new file mode 100644
index 000000000..6ceb0ff15
--- /dev/null
+++ b/ethchain/vm_test.go
@@ -0,0 +1,106 @@
+package ethchain
+
+import (
+ "fmt"
+ "github.com/ethereum/eth-go/ethdb"
+ "github.com/ethereum/eth-go/ethutil"
+ "math/big"
+ "testing"
+)
+
+func TestRun(t *testing.T) {
+ InitFees()
+
+ ethutil.ReadConfig("")
+
+ db, _ := ethdb.NewMemDatabase()
+ state := NewState(ethutil.NewTrie(db, ""))
+
+ script := Compile([]string{
+ "TXSENDER",
+ "SUICIDE",
+ })
+
+ tx := NewTransaction(ContractAddr, big.NewInt(1e17), script)
+ fmt.Printf("contract addr %x\n", tx.Hash()[12:])
+ contract := MakeContract(tx, state)
+ vm := &Vm{}
+
+ vm.Process(contract, state, RuntimeVars{
+ address: tx.Hash()[12:],
+ blockNumber: 1,
+ sender: ethutil.FromHex("cd1722f3947def4cf144679da39c4c32bdc35681"),
+ prevHash: ethutil.FromHex("5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6"),
+ coinbase: ethutil.FromHex("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"),
+ time: 1,
+ diff: big.NewInt(256),
+ txValue: tx.Value,
+ txData: tx.Data,
+ })
+}
+
+func TestRun1(t *testing.T) {
+ ethutil.ReadConfig("")
+
+ db, _ := ethdb.NewMemDatabase()
+ state := NewState(ethutil.NewTrie(db, ""))
+
+ script := Compile([]string{
+ "PUSH", "0",
+ "PUSH", "0",
+ "TXSENDER",
+ "PUSH", "10000000",
+ "MKTX",
+ })
+ fmt.Println(ethutil.NewValue(script))
+
+ tx := NewTransaction(ContractAddr, ethutil.Big("100000000000000000000000000000000000000000000000000"), script)
+ fmt.Printf("contract addr %x\n", tx.Hash()[12:])
+ contract := MakeContract(tx, state)
+ vm := &Vm{}
+
+ vm.Process(contract, state, RuntimeVars{
+ address: tx.Hash()[12:],
+ blockNumber: 1,
+ sender: ethutil.FromHex("cd1722f3947def4cf144679da39c4c32bdc35681"),
+ prevHash: ethutil.FromHex("5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6"),
+ coinbase: ethutil.FromHex("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"),
+ time: 1,
+ diff: big.NewInt(256),
+ txValue: tx.Value,
+ txData: tx.Data,
+ })
+}
+
+func TestRun2(t *testing.T) {
+ ethutil.ReadConfig("")
+
+ db, _ := ethdb.NewMemDatabase()
+ state := NewState(ethutil.NewTrie(db, ""))
+
+ script := Compile([]string{
+ "PUSH", "0",
+ "PUSH", "0",
+ "TXSENDER",
+ "PUSH", "10000000",
+ "MKTX",
+ })
+ fmt.Println(ethutil.NewValue(script))
+
+ tx := NewTransaction(ContractAddr, ethutil.Big("100000000000000000000000000000000000000000000000000"), script)
+ fmt.Printf("contract addr %x\n", tx.Hash()[12:])
+ contract := MakeContract(tx, state)
+ vm := &Vm{}
+
+ vm.Process(contract, state, RuntimeVars{
+ address: tx.Hash()[12:],
+ blockNumber: 1,
+ sender: ethutil.FromHex("cd1722f3947def4cf144679da39c4c32bdc35681"),
+ prevHash: ethutil.FromHex("5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6"),
+ coinbase: ethutil.FromHex("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"),
+ time: 1,
+ diff: big.NewInt(256),
+ txValue: tx.Value,
+ txData: tx.Data,
+ })
+}