From 342cc122b43f01301d0188de1e333c32ed64ae8c Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 4 Aug 2014 16:25:53 +0200 Subject: [PATCH] Added general Pipe API --- ethpipe/config.go | 47 +++++++++++++++ ethpipe/pipe.go | 135 +++++++++++++++++++++++++++++++++++++++++++ ethpipe/pipe_test.go | 57 ++++++++++++++++++ ethpipe/vm_env.go | 33 +++++++++++ ethpipe/world.go | 60 +++++++++++++++++++ ethutil/value.go | 13 +++++ 6 files changed, 345 insertions(+) create mode 100644 ethpipe/config.go create mode 100644 ethpipe/pipe.go create mode 100644 ethpipe/pipe_test.go create mode 100644 ethpipe/vm_env.go create mode 100644 ethpipe/world.go diff --git a/ethpipe/config.go b/ethpipe/config.go new file mode 100644 index 0000000000..764f5596f6 --- /dev/null +++ b/ethpipe/config.go @@ -0,0 +1,47 @@ +package ethpipe + +import ( + "github.com/ethereum/eth-go/ethstate" + "github.com/ethereum/eth-go/ethutil" +) + +var cnfCtr = ethutil.Hex2Bytes("661005d2720d855f1d9976f88bb10c1a3398c77f") + +type object struct { + *ethstate.StateObject +} + +func (self object) StorageString(str string) *ethutil.Value { + if ethutil.IsHex(str) { + return self.Storage(ethutil.Hex2Bytes(str[2:])) + } else { + return self.Storage(ethutil.RightPadBytes([]byte(str), 32)) + } +} + +func (self object) Storage(addr []byte) *ethutil.Value { + return self.StateObject.GetStorage(ethutil.BigD(addr)) +} + +type config struct { + pipe *Pipe +} + +func (self *config) Get(name string) object { + configCtrl := self.pipe.World().safeGet(cnfCtr) + var addr []byte + + switch name { + case "NameReg": + addr = []byte{0} + default: + addr = ethutil.RightPadBytes([]byte(name), 32) + } + + objectAddr := configCtrl.GetStorage(ethutil.BigD(addr)) + return object{self.pipe.World().safeGet(objectAddr.Bytes())} +} + +func (self *config) Exist() bool { + return self.pipe.World().Get(cnfCtr) != nil +} diff --git a/ethpipe/pipe.go b/ethpipe/pipe.go new file mode 100644 index 0000000000..710fc4e7c3 --- /dev/null +++ b/ethpipe/pipe.go @@ -0,0 +1,135 @@ +package ethpipe + +import ( + "strings" + + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethcrypto" + "github.com/ethereum/eth-go/ethlog" + "github.com/ethereum/eth-go/ethstate" + "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/eth-go/ethvm" +) + +var logger = ethlog.NewLogger("PIPE") + +type Pipe struct { + obj ethchain.EthManager + stateManager *ethchain.StateManager + blockChain *ethchain.BlockChain + world *world +} + +func New(obj ethchain.EthManager) *Pipe { + pipe := &Pipe{ + obj: obj, + stateManager: obj.StateManager(), + blockChain: obj.BlockChain(), + } + pipe.world = NewWorld(pipe) + + return pipe +} + +func (self *Pipe) Balance(addr []byte) *ethutil.Value { + return ethutil.NewValue(self.World().safeGet(addr).Balance) +} + +func (self *Pipe) Nonce(addr []byte) uint64 { + return self.World().safeGet(addr).Nonce +} + +func (self *Pipe) Execute(addr []byte, data []byte, value, gas, price *ethutil.Value) ([]byte, error) { + return self.ExecuteObject(self.World().safeGet(addr), data, value, gas, price) +} + +func (self *Pipe) ExecuteObject(object *ethstate.StateObject, data []byte, value, gas, price *ethutil.Value) ([]byte, error) { + var ( + initiator = ethstate.NewStateObject([]byte{0}) + state = self.World().State().Copy() + block = self.blockChain.CurrentBlock + ) + + vm := ethvm.New(NewEnv(state, block, value.BigInt(), initiator.Address())) + + closure := ethvm.NewClosure(initiator, object, object.Code, gas.BigInt(), price.BigInt()) + ret, _, err := closure.Call(vm, data) + + return ret, err +} + +func (self *Pipe) Block(hash []byte) *ethchain.Block { + return self.blockChain.GetBlock(hash) +} + +func (self *Pipe) Storage(addr, storageAddr []byte) *ethutil.Value { + return self.World().safeGet(addr).GetStorage(ethutil.BigD(storageAddr)) +} + +func (self *Pipe) ToAddress(priv []byte) []byte { + pair, err := ethcrypto.NewKeyPairFromSec(priv) + if err != nil { + return nil + } + + return pair.Address() +} + +func (self *Pipe) TransactString(key *ethcrypto.KeyPair, rec string, value, gas, price *ethutil.Value, data []byte) error { + // Check if an address is stored by this address + var hash []byte + addr := self.World().Config().Get("NameReg").StorageString(rec).Bytes() + if len(addr) > 0 { + hash = addr + } else if ethutil.IsHex(rec) { + hash = ethutil.Hex2Bytes(rec[2:]) + } else { + hash = ethutil.Hex2Bytes(rec) + } + + return self.Transact(key, hash, value, gas, price, data) +} + +func (self *Pipe) Transact(key *ethcrypto.KeyPair, rec []byte, value, gas, price *ethutil.Value, data []byte) error { + var hash []byte + var contractCreation bool + if rec == nil { + contractCreation = true + } + + var tx *ethchain.Transaction + // Compile and assemble the given data + if contractCreation { + script, err := ethutil.Compile(string(data), false) + if err != nil { + return err + } + + tx = ethchain.NewContractCreationTx(value.BigInt(), gas.BigInt(), price.BigInt(), script) + } else { + data := ethutil.StringToByteFunc(string(data), func(s string) (ret []byte) { + slice := strings.Split(s, "\n") + for _, dataItem := range slice { + d := ethutil.FormatData(dataItem) + ret = append(ret, d...) + } + return + }) + + tx = ethchain.NewTransactionMessage(hash, value.BigInt(), gas.BigInt(), price.BigInt(), data) + } + + acc := self.stateManager.TransState().GetOrNewStateObject(key.Address()) + tx.Nonce = acc.Nonce + acc.Nonce += 1 + self.stateManager.TransState().UpdateStateObject(acc) + + tx.Sign(key.PrivateKey) + self.obj.TxPool().QueueTransaction(tx) + + if contractCreation { + logger.Infof("Contract addr %x", tx.CreationAddress()) + } + + return nil +} diff --git a/ethpipe/pipe_test.go b/ethpipe/pipe_test.go new file mode 100644 index 0000000000..d0b8ef9484 --- /dev/null +++ b/ethpipe/pipe_test.go @@ -0,0 +1,57 @@ +package ethpipe + +import ( + "testing" + + "github.com/ethereum/eth-go/ethcrypto" + "github.com/ethereum/eth-go/ethstate" + "github.com/ethereum/eth-go/ethutil" +) + +func Val(v interface{}) *ethutil.Value { + return ethutil.NewValue(v) +} + +func TestNew(t *testing.T) { + pipe := New(nil) + + var addr, privy, recp, data []byte + var object *ethstate.StateObject + var key *ethcrypto.KeyPair + + world := pipe.World() + world.Get(addr) + world.Coinbase() + world.IsMining() + world.IsListening() + world.State() + peers := world.Peers() + peers.Len() + + // Shortcut functions + pipe.Balance(addr) + pipe.Nonce(addr) + pipe.Block(addr) + pipe.Storage(addr, addr) + pipe.ToAddress(privy) + // Doesn't change state + pipe.Execute(addr, nil, Val(0), Val(1000000), Val(10)) + // Doesn't change state + pipe.ExecuteObject(object, nil, Val(0), Val(1000000), Val(10)) + + conf := world.Config() + namereg := conf.Get("NameReg") + namereg.Storage(addr) + + var err error + // Transact + err = pipe.Transact(key, recp, ethutil.NewValue(0), ethutil.NewValue(0), ethutil.NewValue(0), nil) + if err != nil { + t.Error(err) + } + // Create + err = pipe.Transact(key, nil, ethutil.NewValue(0), ethutil.NewValue(0), ethutil.NewValue(0), data) + if err != nil { + t.Error(err) + } +} diff --git a/ethpipe/vm_env.go b/ethpipe/vm_env.go new file mode 100644 index 0000000000..c06a2a763b --- /dev/null +++ b/ethpipe/vm_env.go @@ -0,0 +1,33 @@ +package ethpipe + +import ( + "math/big" + + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethstate" +) + +type VMEnv struct { + state *ethstate.State + block *ethchain.Block + value *big.Int + sender []byte +} + +func NewEnv(state *ethstate.State, block *ethchain.Block, value *big.Int, sender []byte) *VMEnv { + return &VMEnv{ + state: state, + block: block, + value: value, + sender: sender, + } +} + +func (self *VMEnv) Origin() []byte { return self.sender } +func (self *VMEnv) BlockNumber() *big.Int { return self.block.Number } +func (self *VMEnv) PrevHash() []byte { return self.block.PrevHash } +func (self *VMEnv) Coinbase() []byte { return self.block.Coinbase } +func (self *VMEnv) Time() int64 { return self.block.Time } +func (self *VMEnv) Difficulty() *big.Int { return self.block.Difficulty } +func (self *VMEnv) Value() *big.Int { return self.value } +func (self *VMEnv) State() *ethstate.State { return self.state } diff --git a/ethpipe/world.go b/ethpipe/world.go new file mode 100644 index 0000000000..507391521c --- /dev/null +++ b/ethpipe/world.go @@ -0,0 +1,60 @@ +package ethpipe + +import ( + "container/list" + + "github.com/ethereum/eth-go/ethstate" +) + +type world struct { + pipe *Pipe + cfg *config +} + +func NewWorld(pipe *Pipe) *world { + world := &world{pipe, nil} + world.cfg = &config{pipe} + + return world +} + +func (self *Pipe) World() *world { + return self.world +} + +func (self *world) State() *ethstate.State { + return self.pipe.stateManager.CurrentState() +} + +func (self *world) Get(addr []byte) *ethstate.StateObject { + return self.State().GetStateObject(addr) +} + +func (self *world) safeGet(addr []byte) *ethstate.StateObject { + object := self.Get(addr) + if object != nil { + return object + } + + return ethstate.NewStateObject(addr) +} + +func (self *world) Coinbase() *ethstate.StateObject { + return nil +} + +func (self *world) IsMining() bool { + return self.pipe.obj.IsMining() +} + +func (self *world) IsListening() bool { + return self.pipe.obj.IsListening() +} + +func (self *world) Peers() *list.List { + return self.obj.Peers() +} + +func (self *world) Config() *config { + return self.cfg +} diff --git a/ethutil/value.go b/ethutil/value.go index 3128cd724d..2233b978cd 100644 --- a/ethutil/value.go +++ b/ethutil/value.go @@ -122,6 +122,14 @@ func (val *Value) Bytes() []byte { return []byte{} } +func (val *Value) Err() error { + if err, ok := val.Val.(error); ok { + return err + } + + return nil +} + func (val *Value) Slice() []interface{} { if d, ok := val.Val.([]interface{}); ok { return d @@ -157,6 +165,11 @@ func (val *Value) IsStr() bool { return val.Type() == reflect.String } +func (self *Value) IsErr() bool { + _, ok := self.Val.(error) + return ok +} + // Special list checking function. Something is considered // a list if it's of type []interface{}. The list is usually // used in conjunction with rlp decoded streams.