diff options
author | Maran <maran.hidskes@gmail.com> | 2014-03-24 17:24:39 +0800 |
---|---|---|
committer | Maran <maran.hidskes@gmail.com> | 2014-03-24 17:24:39 +0800 |
commit | 97786d03d57e1ca79e34ce5fd9aa172c61c3e665 (patch) | |
tree | 251a5e54305e5cedf568f2fb73dbff9302df4185 /ethchain | |
parent | 274d5cc91c45349ec8d7a1f5a20ef29896b38b2e (diff) | |
parent | 6a86c517c4f4b372cad0ae1d92e926a482eac5ba (diff) | |
download | go-tangerine-97786d03d57e1ca79e34ce5fd9aa172c61c3e665.tar.gz go-tangerine-97786d03d57e1ca79e34ce5fd9aa172c61c3e665.tar.zst go-tangerine-97786d03d57e1ca79e34ce5fd9aa172c61c3e665.zip |
Merge branch 'master' into miner
Diffstat (limited to 'ethchain')
-rw-r--r-- | ethchain/address.go | 34 | ||||
-rw-r--r-- | ethchain/block.go | 2 | ||||
-rw-r--r-- | ethchain/block_manager_test.go | 5 | ||||
-rw-r--r-- | ethchain/closure.go | 90 | ||||
-rw-r--r-- | ethchain/contract.go | 52 | ||||
-rw-r--r-- | ethchain/stack.go | 296 | ||||
-rw-r--r-- | ethchain/state.go | 55 | ||||
-rw-r--r-- | ethchain/state_manager.go | 15 | ||||
-rw-r--r-- | ethchain/transaction.go | 21 | ||||
-rw-r--r-- | ethchain/vm.go | 461 | ||||
-rw-r--r-- | ethchain/vm_test.go | 70 |
11 files changed, 658 insertions, 443 deletions
diff --git a/ethchain/address.go b/ethchain/address.go index aa1709f2c..0b3ef7c05 100644 --- a/ethchain/address.go +++ b/ethchain/address.go @@ -6,23 +6,39 @@ import ( ) type Account struct { - Amount *big.Int - Nonce uint64 + address []byte + Amount *big.Int + Nonce uint64 } -func NewAccount(amount *big.Int) *Account { - return &Account{Amount: amount, Nonce: 0} +func NewAccount(address []byte, amount *big.Int) *Account { + return &Account{address, amount, 0} } -func NewAccountFromData(data []byte) *Account { - address := &Account{} - address.RlpDecode(data) +func NewAccountFromData(address, data []byte) *Account { + account := &Account{address: address} + account.RlpDecode(data) - return address + return account } func (a *Account) AddFee(fee *big.Int) { - a.Amount.Add(a.Amount, fee) + a.AddFunds(fee) +} + +func (a *Account) AddFunds(funds *big.Int) { + a.Amount.Add(a.Amount, funds) +} + +func (a *Account) Address() []byte { + return a.address +} + +// Implements Callee +func (a *Account) ReturnGas(value *big.Int, state *State) { + // Return the value back to the sender + a.AddFunds(value) + state.UpdateAccount(a.address, a) } func (a *Account) RlpEncode() []byte { diff --git a/ethchain/block.go b/ethchain/block.go index d42aa7d83..732739c1b 100644 --- a/ethchain/block.go +++ b/ethchain/block.go @@ -142,7 +142,7 @@ func (block *Block) PayFee(addr []byte, fee *big.Int) bool { data := block.state.trie.Get(string(block.Coinbase)) // Get the ether (Coinbase) and add the fee (gief fee to miner) - ether := NewAccountFromData([]byte(data)) + ether := NewAccountFromData(block.Coinbase, []byte(data)) base = new(big.Int) ether.Amount = base.Add(ether.Amount, fee) diff --git a/ethchain/block_manager_test.go b/ethchain/block_manager_test.go index ec4fbe8c5..3a1e5f510 100644 --- a/ethchain/block_manager_test.go +++ b/ethchain/block_manager_test.go @@ -1,5 +1,6 @@ package ethchain +/* import ( _ "fmt" "github.com/ethereum/eth-go/ethdb" @@ -14,9 +15,10 @@ func TestVm(t *testing.T) { db, _ := ethdb.NewMemDatabase() ethutil.Config.Db = db - bm := NewBlockManager(nil) + bm := NewStateManager(nil) block := bm.bc.genesisBlock + bm.Prepare(block.State(), block.State()) script := Compile([]string{ "PUSH", "1", @@ -31,3 +33,4 @@ func TestVm(t *testing.T) { tx2.Sign([]byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) bm.ApplyTransactions(block, []*Transaction{tx2}) } +*/ diff --git a/ethchain/closure.go b/ethchain/closure.go new file mode 100644 index 000000000..2e809aa9d --- /dev/null +++ b/ethchain/closure.go @@ -0,0 +1,90 @@ +package ethchain + +// TODO Re write VM to use values instead of big integers? + +import ( + "github.com/ethereum/eth-go/ethutil" + "math/big" +) + +type Callee interface { + ReturnGas(*big.Int, *State) + Address() []byte +} + +type ClosureBody interface { + Callee + ethutil.RlpEncodable + GetMem(*big.Int) *ethutil.Value + SetMem(*big.Int, *ethutil.Value) +} + +// Basic inline closure object which implement the 'closure' interface +type Closure struct { + callee Callee + object ClosureBody + State *State + + Gas *big.Int + Value *big.Int + + Args []byte +} + +// Create a new closure for the given data items +func NewClosure(callee Callee, object ClosureBody, state *State, gas, val *big.Int) *Closure { + return &Closure{callee, object, state, gas, val, nil} +} + +// Retuns the x element in data slice +func (c *Closure) GetMem(x *big.Int) *ethutil.Value { + m := c.object.GetMem(x) + if m == nil { + return ethutil.EmptyValue() + } + + return m +} + +func (c *Closure) SetMem(x *big.Int, val *ethutil.Value) { + c.object.SetMem(x, val) +} + +func (c *Closure) Address() []byte { + return c.object.Address() +} + +func (c *Closure) Call(vm *Vm, args []byte) []byte { + c.Args = args + + return vm.RunClosure(c) +} + +func (c *Closure) Return(ret []byte) []byte { + // Return the remaining gas to the callee + // If no callee is present return it to + // the origin (i.e. contract or tx) + if c.callee != nil { + c.callee.ReturnGas(c.Gas, c.State) + } else { + c.object.ReturnGas(c.Gas, c.State) + // TODO incase it's a POST contract we gotta serialise the contract again. + // But it's not yet defined + } + + return ret +} + +// Implement the Callee interface +func (c *Closure) ReturnGas(gas *big.Int, state *State) { + // Return the gas to the closure + c.Gas.Add(c.Gas, gas) +} + +func (c *Closure) Object() ClosureBody { + return c.object +} + +func (c *Closure) Callee() Callee { + return c.callee +} diff --git a/ethchain/contract.go b/ethchain/contract.go index 21ac828fe..f7ae01753 100644 --- a/ethchain/contract.go +++ b/ethchain/contract.go @@ -9,26 +9,22 @@ type Contract struct { Amount *big.Int Nonce uint64 //state *ethutil.Trie - state *State + state *State + address []byte } -func NewContract(Amount *big.Int, root []byte) *Contract { - contract := &Contract{Amount: Amount, Nonce: 0} +func NewContract(address []byte, Amount *big.Int, root []byte) *Contract { + contract := &Contract{address: address, Amount: Amount, Nonce: 0} contract.state = NewState(ethutil.NewTrie(ethutil.Config.Db, string(root))) return contract } -func (c *Contract) RlpEncode() []byte { - return ethutil.Encode([]interface{}{c.Amount, c.Nonce, c.state.trie.Root}) -} +func NewContractFromBytes(address, data []byte) *Contract { + contract := &Contract{address: address} + contract.RlpDecode(data) -func (c *Contract) RlpDecode(data []byte) { - decoder := ethutil.NewValueFromBytes(data) - - c.Amount = decoder.Get(0).BigInt() - c.Nonce = decoder.Get(1).Uint() - c.state = NewState(ethutil.NewTrie(ethutil.Config.Db, decoder.Get(2).Interface())) + return contract } func (c *Contract) Addr(addr []byte) *ethutil.Value { @@ -43,19 +39,45 @@ func (c *Contract) State() *State { return c.state } -func (c *Contract) GetMem(num int) *ethutil.Value { - nb := ethutil.BigToBytes(big.NewInt(int64(num)), 256) +func (c *Contract) GetMem(num *big.Int) *ethutil.Value { + nb := ethutil.BigToBytes(num, 256) return c.Addr(nb) } +func (c *Contract) SetMem(num *big.Int, val *ethutil.Value) { + addr := ethutil.BigToBytes(num, 256) + c.state.trie.Update(string(addr), string(val.Encode())) +} + +// Return the gas back to the origin. Used by the Virtual machine or Closures +func (c *Contract) ReturnGas(val *big.Int, state *State) { + c.Amount.Add(c.Amount, val) +} + +func (c *Contract) Address() []byte { + return c.address +} + +func (c *Contract) RlpEncode() []byte { + return ethutil.Encode([]interface{}{c.Amount, c.Nonce, c.state.trie.Root}) +} + +func (c *Contract) RlpDecode(data []byte) { + decoder := ethutil.NewValueFromBytes(data) + + c.Amount = decoder.Get(0).BigInt() + c.Nonce = decoder.Get(1).Uint() + c.state = NewState(ethutil.NewTrie(ethutil.Config.Db, decoder.Get(2).Interface())) +} + func MakeContract(tx *Transaction, state *State) *Contract { // Create contract if there's no recipient if tx.IsContract() { addr := tx.Hash()[12:] value := tx.Value - contract := NewContract(value, []byte("")) + contract := NewContract(addr, value, []byte("")) state.trie.Update(string(addr), string(contract.RlpEncode())) for i, val := range tx.Data { if len(val) > 0 { diff --git a/ethchain/stack.go b/ethchain/stack.go index 13b0f247b..3c2899e62 100644 --- a/ethchain/stack.go +++ b/ethchain/stack.go @@ -2,6 +2,7 @@ package ethchain import ( "fmt" + _ "github.com/ethereum/eth-go/ethutil" "math/big" ) @@ -9,111 +10,142 @@ type OpCode int // Op codes const ( - oSTOP = 0x00 - oADD = 0x01 - oMUL = 0x02 - oSUB = 0x03 - oDIV = 0x04 - oSDIV = 0x05 - oMOD = 0x06 - oSMOD = 0x07 - oEXP = 0x08 - oNEG = 0x09 - oLT = 0x0a - oLE = 0x0b - oGT = 0x0c - oGE = 0x0d - oEQ = 0x0e - oNOT = 0x0f - oMYADDRESS = 0x10 - oTXSENDER = 0x11 - oTXVALUE = 0x12 - oTXDATAN = 0x13 - oTXDATA = 0x14 - oBLK_PREVHASH = 0x15 - oBLK_COINBASE = 0x16 - oBLK_TIMESTAMP = 0x17 - oBLK_NUMBER = 0x18 - oBLK_DIFFICULTY = 0x19 - oBLK_NONCE = 0x1a - oBASEFEE = 0x1b - oSHA256 = 0x20 - oRIPEMD160 = 0x21 - oECMUL = 0x22 - oECADD = 0x23 - oECSIGN = 0x24 - oECRECOVER = 0x25 - oECVALID = 0x26 - oSHA3 = 0x27 - oPUSH = 0x30 - oPOP = 0x31 - oDUP = 0x32 - oSWAP = 0x33 - oMLOAD = 0x34 - oMSTORE = 0x35 - oSLOAD = 0x36 - oSSTORE = 0x37 - oJMP = 0x38 - oJMPI = 0x39 - oIND = 0x3a - oEXTRO = 0x3b - oBALANCE = 0x3c - oMKTX = 0x3d - oSUICIDE = 0x3f + // 0x0 range - arithmetic ops + oSTOP = 0x00 + oADD = 0x01 + oMUL = 0x02 + oSUB = 0x03 + oDIV = 0x04 + oSDIV = 0x05 + oMOD = 0x06 + oSMOD = 0x07 + oEXP = 0x08 + oNEG = 0x09 + oLT = 0x0a + oGT = 0x0b + oEQ = 0x0c + oNOT = 0x0d + + // 0x10 range - bit ops + oAND = 0x10 + oOR = 0x11 + oXOR = 0x12 + oBYTE = 0x13 + + // 0x20 range - crypto + oSHA3 = 0x20 + + // 0x30 range - closure state + oADDRESS = 0x30 + oBALANCE = 0x31 + oORIGIN = 0x32 + oCALLER = 0x33 + oCALLVALUE = 0x34 + oCALLDATA = 0x35 + oCALLDATASIZE = 0x36 + oGASPRICE = 0x37 + + // 0x40 range - block operations + oPREVHASH = 0x40 + oCOINBASE = 0x41 + oTIMESTAMP = 0x42 + oNUMBER = 0x43 + oDIFFICULTY = 0x44 + oGASLIMIT = 0x45 + + // 0x50 range - 'storage' and execution + oPUSH = 0x50 + oPOP = 0x51 + oDUP = 0x52 + oSWAP = 0x53 + oMLOAD = 0x54 + oMSTORE = 0x55 + oMSTORE8 = 0x56 + oSLOAD = 0x57 + oSSTORE = 0x58 + oJUMP = 0x59 + oJUMPI = 0x5a + oPC = 0x5b + oMSIZE = 0x5c + + // 0x60 range - closures + oCREATE = 0x60 + oCALL = 0x61 + oRETURN = 0x62 + + // 0x70 range - other + oLOG = 0x70 // XXX Unofficial + oSUICIDE = 0x7f ) // Since the opcodes aren't all in order we can't use a regular slice var opCodeToString = map[OpCode]string{ - oSTOP: "STOP", - oADD: "ADD", - oMUL: "MUL", - oSUB: "SUB", - oDIV: "DIV", - oSDIV: "SDIV", - oMOD: "MOD", - oSMOD: "SMOD", - oEXP: "EXP", - oNEG: "NEG", - oLT: "LT", - oLE: "LE", - oGT: "GT", - oGE: "GE", - oEQ: "EQ", - oNOT: "NOT", - oMYADDRESS: "MYADDRESS", - oTXSENDER: "TXSENDER", - oTXVALUE: "TXVALUE", - oTXDATAN: "TXDATAN", - oTXDATA: "TXDATA", - oBLK_PREVHASH: "BLK_PREVHASH", - oBLK_COINBASE: "BLK_COINBASE", - oBLK_TIMESTAMP: "BLK_TIMESTAMP", - oBLK_NUMBER: "BLK_NUMBER", - oBLK_DIFFICULTY: "BLK_DIFFICULTY", - oBASEFEE: "BASEFEE", - oSHA256: "SHA256", - oRIPEMD160: "RIPEMD160", - oECMUL: "ECMUL", - oECADD: "ECADD", - oECSIGN: "ECSIGN", - oECRECOVER: "ECRECOVER", - oECVALID: "ECVALID", - oSHA3: "SHA3", - oPUSH: "PUSH", - oPOP: "POP", - oDUP: "DUP", - oSWAP: "SWAP", - oMLOAD: "MLOAD", - oMSTORE: "MSTORE", - oSLOAD: "SLOAD", - oSSTORE: "SSTORE", - oJMP: "JMP", - oJMPI: "JMPI", - oIND: "IND", - oEXTRO: "EXTRO", - oBALANCE: "BALANCE", - oMKTX: "MKTX", - oSUICIDE: "SUICIDE", + // 0x0 range - arithmetic ops + oSTOP: "STOP", + oADD: "ADD", + oMUL: "MUL", + oSUB: "SUB", + oDIV: "DIV", + oSDIV: "SDIV", + oMOD: "MOD", + oSMOD: "SMOD", + oEXP: "EXP", + oNEG: "NEG", + oLT: "LT", + oGT: "GT", + oEQ: "EQ", + oNOT: "NOT", + + // 0x10 range - bit ops + oAND: "AND", + oOR: "OR", + oXOR: "XOR", + oBYTE: "BYTE", + + // 0x20 range - crypto + oSHA3: "SHA3", + + // 0x30 range - closure state + oADDRESS: "ADDRESS", + oBALANCE: "BALANCE", + oORIGIN: "ORIGIN", + oCALLER: "CALLER", + oCALLVALUE: "CALLVALUE", + oCALLDATA: "CALLDATA", + oCALLDATASIZE: "CALLDATASIZE", + oGASPRICE: "TXGASPRICE", + + // 0x40 range - block operations + oPREVHASH: "PREVHASH", + oCOINBASE: "COINBASE", + oTIMESTAMP: "TIMESTAMP", + oNUMBER: "NUMBER", + oDIFFICULTY: "DIFFICULTY", + oGASLIMIT: "GASLIMIT", + + // 0x50 range - 'storage' and execution + oPUSH: "PUSH", + oPOP: "POP", + oDUP: "DUP", + oSWAP: "SWAP", + oMLOAD: "MLOAD", + oMSTORE: "MSTORE", + oMSTORE8: "MSTORE8", + oSLOAD: "SLOAD", + oSSTORE: "SSTORE", + oJUMP: "JUMP", + oJUMPI: "JUMPI", + oPC: "PC", + oMSIZE: "MSIZE", + + // 0x60 range - closures + oCREATE: "CREATE", + oCALL: "CALL", + oRETURN: "RETURN", + + // 0x70 range - other + oLOG: "LOG", + oSUICIDE: "SUICIDE", } func (o OpCode) String() string { @@ -141,35 +173,27 @@ func NewStack() *Stack { } func (st *Stack) Pop() *big.Int { - s := len(st.data) - - str := st.data[s-1] - st.data = st.data[:s-1] + str := st.data[0] + st.data = st.data[1:] return str } func (st *Stack) Popn() (*big.Int, *big.Int) { - s := len(st.data) - - ints := st.data[s-2:] - st.data = st.data[:s-2] + ints := st.data[:2] + st.data = st.data[2:] return ints[0], ints[1] } func (st *Stack) Peek() *big.Int { - s := len(st.data) - - str := st.data[s-1] + str := st.data[0] return str } func (st *Stack) Peekn() (*big.Int, *big.Int) { - s := len(st.data) - - ints := st.data[s-2:] + ints := st.data[:2] return ints[0], ints[1] } @@ -188,3 +212,45 @@ func (st *Stack) Print() { } fmt.Println("#############") } + +type Memory struct { + store []byte +} + +func (m *Memory) Set(offset, size int64, value []byte) { + totSize := offset + size + lenSize := int64(len(m.store) - 1) + if totSize > lenSize { + // Calculate the diff between the sizes + diff := totSize - lenSize + if diff > 0 { + // Create a new empty slice and append it + newSlice := make([]byte, diff-1) + // Resize slice + m.store = append(m.store, newSlice...) + } + } + copy(m.store[offset:offset+size], value) +} + +func (m *Memory) Get(offset, size int64) []byte { + return m.store[offset : offset+size] +} + +func (m *Memory) Len() int { + return len(m.store) +} + +func (m *Memory) Print() { + fmt.Println("### MEM ###") + if len(m.store) > 0 { + addr := 0 + for i := 0; i+32 < len(m.store); i += 32 { + fmt.Printf("%03d %v\n", addr, m.store[i:i+32]) + addr++ + } + } else { + fmt.Println("-- empty --") + } + fmt.Println("###########") +} diff --git a/ethchain/state.go b/ethchain/state.go index b9c2c576d..1860647f2 100644 --- a/ethchain/state.go +++ b/ethchain/state.go @@ -63,8 +63,7 @@ func (s *State) GetContract(addr []byte) *Contract { } // build contract - contract := &Contract{} - contract.RlpDecode([]byte(data)) + contract := NewContractFromBytes(addr, []byte(data)) // Check if there's a cached state for this contract cachedState := s.states[string(addr)] @@ -78,27 +77,19 @@ func (s *State) GetContract(addr []byte) *Contract { 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) - } +func (s *State) UpdateContract(contract *Contract) { + addr := contract.Address() - return + s.states[string(addr)] = contract.state + s.trie.Update(string(addr), string(contract.RlpEncode())) } func (s *State) GetAccount(addr []byte) (account *Account) { data := s.trie.Get(string(addr)) if data == "" { - account = NewAccount(big.NewInt(0)) + account = NewAccount(addr, big.NewInt(0)) } else { - account = NewAccountFromData([]byte(data)) + account = NewAccountFromData(addr, []byte(data)) } return @@ -153,3 +144,35 @@ func (s *State) Get(key []byte) (*ethutil.Value, ObjType) { return val, typ } + +func (s *State) Put(key, object []byte) { + s.trie.Update(string(key), string(object)) +} + +func (s *State) Root() interface{} { + return s.trie.Root +} + +// Script compilation functions +// Compiles strings to machine code +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 CompileToValues(code []string) (script []*ethutil.Value) { + script = make([]*ethutil.Value, len(code)) + for i, val := range code { + instr, _ := ethutil.CompileInstr(val) + + script[i] = ethutil.NewValue(instr) + } + + return +} diff --git a/ethchain/state_manager.go b/ethchain/state_manager.go index 9118db211..5692a1d88 100644 --- a/ethchain/state_manager.go +++ b/ethchain/state_manager.go @@ -308,18 +308,17 @@ func (sm *StateManager) ProcessContract(contract *Contract, tx *Transaction, blo } }() */ - - vm := &Vm{} - //vm.Process(contract, block.state, RuntimeVars{ - vm.Process(contract, sm.procState, RuntimeVars{ - address: tx.Hash()[12:], + caller := sm.procState.GetAccount(tx.Sender()) + closure := NewClosure(caller, contract, sm.procState, tx.Gas, tx.Value) + vm := NewVm(sm.procState, RuntimeVars{ + origin: caller.Address(), blockNumber: block.BlockInfo().Number, - sender: tx.Sender(), prevHash: block.PrevHash, coinbase: block.Coinbase, time: block.Time, diff: block.Difficulty, - txValue: tx.Value, - txData: tx.Data, + // XXX Tx data? Could be just an argument to the closure instead + txData: nil, }) + closure.Call(vm, nil) } diff --git a/ethchain/transaction.go b/ethchain/transaction.go index 57df9cdc4..3b07c81d4 100644 --- a/ethchain/transaction.go +++ b/ethchain/transaction.go @@ -13,22 +13,31 @@ type Transaction struct { Nonce uint64 Recipient []byte Value *big.Int + Gas *big.Int + Gasprice *big.Int Data []string - Memory []int v byte r, s []byte } func NewTransaction(to []byte, value *big.Int, data []string) *Transaction { - tx := Transaction{Recipient: to, Value: value} - tx.Nonce = 0 - - // Serialize the data - tx.Data = data + tx := Transaction{Recipient: to, Value: value, Nonce: 0, Data: data} return &tx } +func NewContractCreationTx(value, gasprice *big.Int, data []string) *Transaction { + return &Transaction{Value: value, Gasprice: gasprice, Data: data} +} + +func NewContractMessageTx(to []byte, value, gasprice, gas *big.Int, data []string) *Transaction { + return &Transaction{Recipient: to, Value: value, Gasprice: gasprice, Gas: gas, Data: data} +} + +func NewTx(to []byte, value *big.Int, data []string) *Transaction { + return &Transaction{Recipient: to, Value: value, Gasprice: big.NewInt(0), Gas: big.NewInt(0), Nonce: 0, Data: data} +} + // XXX Deprecated func NewTransactionFromData(data []byte) *Transaction { return NewTransactionFromBytes(data) diff --git a/ethchain/vm.go b/ethchain/vm.go index 7e119ac99..126592b25 100644 --- a/ethchain/vm.go +++ b/ethchain/vm.go @@ -1,12 +1,12 @@ package ethchain import ( - "bytes" - "fmt" + _ "bytes" + _ "fmt" "github.com/ethereum/eth-go/ethutil" - "github.com/obscuren/secp256k1-go" + _ "github.com/obscuren/secp256k1-go" "log" - "math" + _ "math" "math/big" ) @@ -18,122 +18,102 @@ type Vm struct { mem map[string]*big.Int vars RuntimeVars + + state *State } type RuntimeVars struct { - address []byte + origin []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() +func NewVm(state *State, vars RuntimeVars) *Vm { + return &Vm{vars: vars, state: state} +} - addr := vars.address // tx.Hash()[12:] - // Instruction pointer - pc := 0 +var Pow256 = ethutil.BigPow(2, 256) - if contract == nil { - fmt.Println("Contract not found") - return +func (vm *Vm) RunClosure(closure *Closure) []byte { + // If the amount of gas supplied is less equal to 0 + if closure.Gas.Cmp(big.NewInt(0)) <= 0 { + // TODO Do something } - Pow256 := ethutil.BigPow(2, 256) + // Memory for the current closure + mem := &Memory{} + // New stack (should this be shared?) + stack := NewStack() + // Instruction pointer + pc := big.NewInt(0) + // Current step count + step := 0 + // The base for all big integer arithmetic + base := new(big.Int) 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) + step++ + // Get the memory location of pc + val := closure.GetMem(pc) + // Get the opcode (it must be an opcode!) 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) + if ethutil.Config.Debug { + ethutil.Config.Log.Debugf("%-3d %-4s", pc, op.String()) } - 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) + // TODO Get each instruction cost properly + fee := new(big.Int) + fee.Add(fee, big.NewInt(1000)) - if ethutil.Config.Debug { - ethutil.Config.Log.Debugf("%-3d %-4s", pc, op.String()) + if closure.Gas.Cmp(fee) < 0 { + return closure.Return(nil) } switch op { - case oSTOP: - fmt.Println("") - break out + case oLOG: + stack.Print() + mem.Print() + case oSTOP: // Stop the closure + return closure.Return(nil) + + // 0x20 range case oADD: - x, y := vm.stack.Popn() + x, y := stack.Popn() // (x + y) % 2 ** 256 base.Add(x, y) base.Mod(base, Pow256) // Pop result back on the stack - vm.stack.Push(base) + stack.Push(base) case oSUB: - x, y := vm.stack.Popn() + x, y := stack.Popn() // (x - y) % 2 ** 256 base.Sub(x, y) base.Mod(base, Pow256) // Pop result back on the stack - vm.stack.Push(base) + stack.Push(base) case oMUL: - x, y := vm.stack.Popn() + x, y := stack.Popn() // (x * y) % 2 ** 256 base.Mul(x, y) base.Mod(base, Pow256) // Pop result back on the stack - vm.stack.Push(base) + stack.Push(base) case oDIV: - x, y := vm.stack.Popn() + x, y := stack.Popn() // floor(x / y) base.Div(x, y) // Pop result back on the stack - vm.stack.Push(base) + stack.Push(base) case oSDIV: - x, y := vm.stack.Popn() + x, y := stack.Popn() // n > 2**255 if x.Cmp(Pow256) > 0 { x.Sub(Pow256, x) @@ -147,13 +127,13 @@ out: z.Sub(Pow256, z) } // Push result on to the stack - vm.stack.Push(z) + stack.Push(z) case oMOD: - x, y := vm.stack.Popn() + x, y := stack.Popn() base.Mod(x, y) - vm.stack.Push(base) + stack.Push(base) case oSMOD: - x, y := vm.stack.Popn() + x, y := stack.Popn() // n > 2**255 if x.Cmp(Pow256) > 0 { x.Sub(Pow256, x) @@ -167,250 +147,189 @@ out: z.Sub(Pow256, z) } // Push result on to the stack - vm.stack.Push(z) + stack.Push(z) case oEXP: - x, y := vm.stack.Popn() + x, y := stack.Popn() base.Exp(x, y, Pow256) - vm.stack.Push(base) + stack.Push(base) case oNEG: - base.Sub(Pow256, vm.stack.Pop()) - vm.stack.Push(base) + base.Sub(Pow256, stack.Pop()) + stack.Push(base) case oLT: - x, y := vm.stack.Popn() + x, y := 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) + stack.Push(ethutil.BigTrue) } else { - vm.stack.Push(ethutil.BigFalse) + stack.Push(ethutil.BigFalse) } case oGT: - x, y := vm.stack.Popn() + x, y := 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) + stack.Push(ethutil.BigTrue) } else { - vm.stack.Push(ethutil.BigFalse) + stack.Push(ethutil.BigFalse) } case oNOT: - x, y := vm.stack.Popn() + x, y := stack.Popn() // x != y if x.Cmp(y) != 0 { - vm.stack.Push(ethutil.BigTrue) + stack.Push(ethutil.BigTrue) } else { - vm.stack.Push(ethutil.BigFalse) + 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) + // 0x10 range + case oAND: + case oOR: + case oXOR: + case oBYTE: - // 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)) - } + // 0x20 range + case oSHA3: - 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() + // 0x30 range + case oADDRESS: + stack.Push(ethutil.BigD(closure.Object().Address())) + case oBALANCE: + stack.Push(closure.Value) + case oORIGIN: + stack.Push(ethutil.BigD(vm.vars.origin)) + case oCALLER: + stack.Push(ethutil.BigD(closure.Callee().Address())) + case oCALLVALUE: + // FIXME: Original value of the call, not the current value + stack.Push(closure.Value) + case oCALLDATA: + offset := stack.Pop() + mem.Set(offset.Int64(), int64(len(closure.Args)), closure.Args) + case oCALLDATASIZE: + stack.Push(big.NewInt(int64(len(closure.Args)))) + case oGASPRICE: + // TODO - //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") - //} + // 0x40 range + case oPREVHASH: + stack.Push(ethutil.BigD(vm.vars.prevHash)) + case oCOINBASE: + stack.Push(ethutil.BigD(vm.vars.coinbase)) + case oTIMESTAMP: + stack.Push(big.NewInt(vm.vars.time)) + case oNUMBER: + stack.Push(big.NewInt(int64(vm.vars.blockNumber))) + case oDIFFICULTY: + stack.Push(vm.vars.diff) + case oGASLIMIT: + // TODO - case oECADD: - case oECSIGN: - case oECRECOVER: - case oECVALID: - case oPUSH: - pc++ - vm.stack.Push(contract.GetMem(pc).BigInt()) + // 0x50 range + case oPUSH: // Push PC+1 on to the stack + pc.Add(pc, ethutil.Big1) + + val := closure.GetMem(pc).BigInt() + stack.Push(val) case oPOP: - // Pop current value of the stack - vm.stack.Pop() + stack.Pop() case oDUP: - // Dup top stack - x := vm.stack.Pop() - vm.stack.Push(x) - vm.stack.Push(x) + stack.Push(stack.Peek()) case oSWAP: - // Swap two top most values - x, y := vm.stack.Popn() - vm.stack.Push(y) - vm.stack.Push(x) + x, y := stack.Popn() + stack.Push(y) + 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 + 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 + // Pop value of the stack + val, mStart := stack.Popn() + mem.Set(mStart.Int64(), 32, ethutil.BigToBytes(val, 256)) + case oMSTORE8: + val, mStart := stack.Popn() + base.And(val, new(big.Int).SetInt64(0xff)) + mem.Set(mStart.Int64(), 32, ethutil.BigToBytes(base, 256)) 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) - } + loc := stack.Pop() + val := closure.GetMem(loc) + stack.Push(val.BigInt()) 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-- + val, loc := stack.Popn() + closure.SetMem(loc, ethutil.NewValue(val)) + case oJUMP: + pc = stack.Pop() + case oJUMPI: + pos, cond := stack.Popn() + if cond.Cmp(big.NewInt(0)) > 0 { + pc = pos } - case oIND: - vm.stack.Push(big.NewInt(int64(pc))) - case oEXTRO: - memAddr := vm.stack.Pop() - contractAddr := vm.stack.Pop().Bytes() + case oPC: + stack.Push(pc) + case oMSIZE: + stack.Push(big.NewInt(int64(mem.Len()))) + // 0x60 range + case oCALL: + // Pop return size and offset + retSize, retOffset := stack.Popn() + // Pop input size and offset + inSize, inOffset := stack.Popn() + // Get the arguments from the memory + args := mem.Get(inOffset.Int64(), inSize.Int64()) + // 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, args) - // 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() + mem.Set(retOffset.Int64(), retSize.Int64(), ret) + case oRETURN: + size, offset := stack.Popn() + ret := mem.Get(offset.Int64(), size.Int64()) - makeInlineTx(addr.Bytes(), value, from, length, contract, state) + return closure.Return(ret) 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), "") + /* + recAddr := 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 + ethutil.Config.Log.Debugf("(%d) => %x\n", deletedMemory, recAddr) + break out + */ default: - fmt.Printf("Invalid OPCODE: %x\n", op) + ethutil.Config.Log.Debugln("Invalid opcode", op) } - ethutil.Config.Log.Debugln("") - //vm.stack.Print() - pc++ - } - state.UpdateContract(addr, contract) + pc.Add(pc, ethutil.Big1) + } } 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 := 0 + j := int64(0) dataItems := make([]string, int(length.Uint64())) - for i := from.Uint64(); i < length.Uint64(); i++ { - dataItems[j] = contract.GetMem(j).Str() + for i := from.Int64(); i < length.Int64(); i++ { + dataItems[j] = contract.GetMem(big.NewInt(j)).Str() j++ } tx := NewTransaction(addr, value, dataItems) if tx.IsContract() { contract := MakeContract(tx, state) - state.UpdateContract(tx.Hash()[12:], contract) + state.UpdateContract(contract) } else { account := state.GetAccount(tx.Recipient) account.Amount.Add(account.Amount, tx.Value) diff --git a/ethchain/vm_test.go b/ethchain/vm_test.go index 6ceb0ff15..047531e09 100644 --- a/ethchain/vm_test.go +++ b/ethchain/vm_test.go @@ -1,13 +1,15 @@ package ethchain import ( - "fmt" + "bytes" "github.com/ethereum/eth-go/ethdb" "github.com/ethereum/eth-go/ethutil" "math/big" "testing" ) +/* + func TestRun(t *testing.T) { InitFees() @@ -104,3 +106,69 @@ func TestRun2(t *testing.T) { txData: tx.Data, }) } +*/ + +// XXX Full stack test +func TestRun3(t *testing.T) { + ethutil.ReadConfig("") + + db, _ := ethdb.NewMemDatabase() + state := NewState(ethutil.NewTrie(db, "")) + + script := Compile([]string{ + "PUSH", "300", + "PUSH", "0", + "MSTORE", + + "PUSH", "32", + "CALLDATA", + + "PUSH", "64", + "PUSH", "0", + "RETURN", + }) + tx := NewTransaction(ContractAddr, ethutil.Big("100000000000000000000000000000000000000000000000000"), script) + addr := tx.Hash()[12:] + contract := MakeContract(tx, state) + state.UpdateContract(contract) + + callerScript := ethutil.Compile( + "PUSH", 1337, // Argument + "PUSH", 65, // argument mem offset + "MSTORE", + "PUSH", 64, // ret size + "PUSH", 0, // ret offset + + "PUSH", 32, // arg size + "PUSH", 65, // arg offset + "PUSH", 1000, /// Gas + "PUSH", 0, /// value + "PUSH", addr, // Sender + "CALL", + "PUSH", 64, + "PUSH", 0, + "RETURN", + ) + callerTx := NewTransaction(ContractAddr, ethutil.Big("100000000000000000000000000000000000000000000000000"), callerScript) + + // Contract addr as test address + account := NewAccount(ContractAddr, big.NewInt(10000000)) + callerClosure := NewClosure(account, MakeContract(callerTx, state), state, big.NewInt(1000000000), new(big.Int)) + + vm := NewVm(state, RuntimeVars{ + origin: account.Address(), + blockNumber: 1, + prevHash: ethutil.FromHex("5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6"), + coinbase: ethutil.FromHex("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"), + time: 1, + diff: big.NewInt(256), + // XXX Tx data? Could be just an argument to the closure instead + txData: nil, + }) + ret := callerClosure.Call(vm, nil) + + exp := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 57} + if bytes.Compare(ret, exp) != 0 { + t.Errorf("expected return value to be %v, got %v", exp, ret) + } +} |