diff options
Diffstat (limited to 'xeth/xeth.go')
-rw-r--r-- | xeth/xeth.go | 707 |
1 files changed, 707 insertions, 0 deletions
diff --git a/xeth/xeth.go b/xeth/xeth.go new file mode 100644 index 000000000..7e1548964 --- /dev/null +++ b/xeth/xeth.go @@ -0,0 +1,707 @@ +// eXtended ETHereum +package xeth + +import ( + "bytes" + "encoding/json" + "fmt" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/event/filter" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/miner" +) + +var ( + pipelogger = logger.NewLogger("XETH") + filterTickerTime = 5 * time.Minute + defaultGasPrice = big.NewInt(10000000000000) //150000000000 + defaultGas = big.NewInt(90000) //500000 +) + +type XEth struct { + backend *eth.Ethereum + frontend Frontend + + state *State + whisper *Whisper + + quit chan struct{} + filterManager *filter.FilterManager + + logMut sync.RWMutex + logs map[int]*logFilter + + messagesMut sync.RWMutex + messages map[int]*whisperFilter + + // regmut sync.Mutex + // register map[string][]*interface{} // TODO improve return type + + agent *miner.RemoteAgent +} + +// New creates an XEth that uses the given frontend. +// If a nil Frontend is provided, a default frontend which +// confirms all transactions will be used. +func New(eth *eth.Ethereum, frontend Frontend) *XEth { + xeth := &XEth{ + backend: eth, + frontend: frontend, + whisper: NewWhisper(eth.Whisper()), + quit: make(chan struct{}), + filterManager: filter.NewFilterManager(eth.EventMux()), + logs: make(map[int]*logFilter), + messages: make(map[int]*whisperFilter), + agent: miner.NewRemoteAgent(), + } + eth.Miner().Register(xeth.agent) + + if frontend == nil { + xeth.frontend = dummyFrontend{} + } + xeth.state = NewState(xeth, xeth.backend.ChainManager().TransState()) + + go xeth.start() + go xeth.filterManager.Start() + + return xeth +} + +func (self *XEth) start() { + timer := time.NewTicker(2 * time.Second) +done: + for { + select { + case <-timer.C: + self.logMut.Lock() + self.messagesMut.Lock() + for id, filter := range self.logs { + if time.Since(filter.timeout) > filterTickerTime { + self.filterManager.UninstallFilter(id) + delete(self.logs, id) + } + } + + for id, filter := range self.messages { + if time.Since(filter.timeout) > filterTickerTime { + self.Whisper().Unwatch(id) + delete(self.messages, id) + } + } + self.messagesMut.Unlock() + self.logMut.Unlock() + case <-self.quit: + break done + } + } +} + +func (self *XEth) stop() { + close(self.quit) +} + +func cAddress(a []string) []common.Address { + bslice := make([]common.Address, len(a)) + for i, addr := range a { + bslice[i] = common.HexToAddress(addr) + } + return bslice +} + +func cTopics(t [][]string) [][]common.Hash { + topics := make([][]common.Hash, len(t)) + for i, iv := range t { + for j, jv := range iv { + topics[i][j] = common.HexToHash(jv) + } + } + return topics +} + +func (self *XEth) DefaultGas() *big.Int { return defaultGas } +func (self *XEth) DefaultGasPrice() *big.Int { return defaultGasPrice } + +func (self *XEth) RemoteMining() *miner.RemoteAgent { return self.agent } + +func (self *XEth) AtStateNum(num int64) *XEth { + block := self.getBlockByHeight(num) + + var st *state.StateDB + if block != nil { + st = state.New(block.Root(), self.backend.StateDb()) + } else { + st = self.backend.ChainManager().State() + } + + return self.withState(st) +} + +func (self *XEth) withState(statedb *state.StateDB) *XEth { + xeth := &XEth{ + backend: self.backend, + } + + xeth.state = NewState(xeth, statedb) + return xeth +} + +func (self *XEth) State() *State { return self.state } + +func (self *XEth) Whisper() *Whisper { return self.whisper } + +func (self *XEth) getBlockByHeight(height int64) *types.Block { + var num uint64 + + if height < 0 { + num = self.CurrentBlock().NumberU64() + uint64(-1*height) + } else { + num = uint64(height) + } + + return self.backend.ChainManager().GetBlockByNumber(num) +} + +func (self *XEth) BlockByHash(strHash string) *Block { + hash := common.HexToHash(strHash) + block := self.backend.ChainManager().GetBlock(hash) + + return NewBlock(block) +} + +func (self *XEth) EthBlockByHash(strHash string) *types.Block { + hash := common.HexToHash(strHash) + block := self.backend.ChainManager().GetBlock(hash) + + return block +} + +func (self *XEth) EthTransactionByHash(hash string) *types.Transaction { + data, _ := self.backend.ExtraDb().Get(common.FromHex(hash)) + if len(data) != 0 { + return types.NewTransactionFromBytes(data) + } + return nil +} + +func (self *XEth) BlockByNumber(num int64) *Block { + return NewBlock(self.getBlockByHeight(num)) +} + +func (self *XEth) EthBlockByNumber(num int64) *types.Block { + return self.getBlockByHeight(num) +} + +func (self *XEth) CurrentBlock() *types.Block { + return self.backend.ChainManager().CurrentBlock() +} + +func (self *XEth) Block(v interface{}) *Block { + if n, ok := v.(int32); ok { + return self.BlockByNumber(int64(n)) + } else if str, ok := v.(string); ok { + return self.BlockByHash(str) + } else if f, ok := v.(float64); ok { // Don't ask ... + return self.BlockByNumber(int64(f)) + } + + return nil +} + +func (self *XEth) Accounts() []string { + // TODO: check err? + accounts, _ := self.backend.AccountManager().Accounts() + accountAddresses := make([]string, len(accounts)) + for i, ac := range accounts { + accountAddresses[i] = common.ToHex(ac.Address) + } + return accountAddresses +} + +func (self *XEth) DbPut(key, val []byte) bool { + self.backend.ExtraDb().Put(key, val) + return true +} + +func (self *XEth) DbGet(key []byte) ([]byte, error) { + val, err := self.backend.ExtraDb().Get(key) + return val, err +} + +func (self *XEth) PeerCount() int { + return self.backend.PeerCount() +} + +func (self *XEth) IsMining() bool { + return self.backend.IsMining() +} + +func (self *XEth) EthVersion() string { + return fmt.Sprintf("%d", self.backend.EthVersion()) +} + +func (self *XEth) NetworkVersion() string { + return fmt.Sprintf("%d", self.backend.NetVersion()) +} + +func (self *XEth) WhisperVersion() string { + return fmt.Sprintf("%d", self.backend.ShhVersion()) +} + +func (self *XEth) ClientVersion() string { + return self.backend.ClientVersion() +} + +func (self *XEth) SetMining(shouldmine bool) bool { + ismining := self.backend.IsMining() + if shouldmine && !ismining { + err := self.backend.StartMining() + return err == nil + } + if ismining && !shouldmine { + self.backend.StopMining() + } + return self.backend.IsMining() +} + +func (self *XEth) IsListening() bool { + return self.backend.IsListening() +} + +func (self *XEth) Coinbase() string { + eb, _ := self.backend.Etherbase() + return eb.Hex() +} + +func (self *XEth) NumberToHuman(balance string) string { + b := common.Big(balance) + + return common.CurrencyToString(b) +} + +func (self *XEth) StorageAt(addr, storageAddr string) string { + storage := self.State().SafeGet(addr).StorageString(storageAddr) + + return common.ToHex(storage.Bytes()) +} + +func (self *XEth) BalanceAt(addr string) string { + return self.State().SafeGet(addr).Balance().String() +} + +func (self *XEth) TxCountAt(address string) int { + return int(self.State().SafeGet(address).Nonce()) +} + +func (self *XEth) CodeAt(address string) string { + return common.ToHex(self.State().SafeGet(address).Code()) +} + +func (self *XEth) IsContract(address string) bool { + return len(self.State().SafeGet(address).Code()) > 0 +} + +func (self *XEth) SecretToAddress(key string) string { + pair, err := crypto.NewKeyPairFromSec(common.FromHex(key)) + if err != nil { + return "" + } + + return common.ToHex(pair.Address()) +} + +func (self *XEth) RegisterFilter(earliest, latest int64, skip, max int, address []string, topics [][]string) int { + var id int + filter := core.NewFilter(self.backend) + filter.SetEarliestBlock(earliest) + filter.SetLatestBlock(latest) + filter.SetSkip(skip) + filter.SetMax(max) + filter.SetAddress(cAddress(address)) + filter.SetTopics(cTopics(topics)) + filter.LogsCallback = func(logs state.Logs) { + self.logMut.Lock() + defer self.logMut.Unlock() + + self.logs[id].add(logs...) + } + id = self.filterManager.InstallFilter(filter) + self.logs[id] = &logFilter{timeout: time.Now()} + + return id +} + +func (self *XEth) UninstallFilter(id int) bool { + if _, ok := self.logs[id]; ok { + delete(self.logs, id) + self.filterManager.UninstallFilter(id) + return true + } + + return false +} + +func (self *XEth) NewFilterString(word string) int { + var id int + filter := core.NewFilter(self.backend) + + switch word { + case "pending": + filter.PendingCallback = func(tx *types.Transaction) { + self.logMut.Lock() + defer self.logMut.Unlock() + + self.logs[id].add(&state.StateLog{}) + } + case "latest": + filter.BlockCallback = func(block *types.Block, logs state.Logs) { + self.logMut.Lock() + defer self.logMut.Unlock() + + for _, log := range logs { + self.logs[id].add(log) + } + self.logs[id].add(&state.StateLog{}) + } + } + + id = self.filterManager.InstallFilter(filter) + self.logs[id] = &logFilter{timeout: time.Now()} + + return id +} + +func (self *XEth) FilterChanged(id int) state.Logs { + self.logMut.Lock() + defer self.logMut.Unlock() + + if self.logs[id] != nil { + return self.logs[id].get() + } + + return nil +} + +func (self *XEth) Logs(id int) state.Logs { + self.logMut.Lock() + defer self.logMut.Unlock() + + filter := self.filterManager.GetFilter(id) + if filter != nil { + return filter.Find() + } + + return nil +} + +func (self *XEth) AllLogs(earliest, latest int64, skip, max int, address []string, topics [][]string) state.Logs { + filter := core.NewFilter(self.backend) + filter.SetEarliestBlock(earliest) + filter.SetLatestBlock(latest) + filter.SetSkip(skip) + filter.SetMax(max) + filter.SetAddress(cAddress(address)) + filter.SetTopics(cTopics(topics)) + + return filter.Find() +} + +func (p *XEth) NewWhisperFilter(opts *Options) int { + var id int + opts.Fn = func(msg WhisperMessage) { + p.messagesMut.Lock() + defer p.messagesMut.Unlock() + p.messages[id].add(msg) // = append(p.messages[id], msg) + } + id = p.Whisper().Watch(opts) + p.messages[id] = &whisperFilter{timeout: time.Now()} + return id +} + +func (p *XEth) UninstallWhisperFilter(id int) bool { + if _, ok := p.messages[id]; ok { + delete(p.messages, id) + return true + } + + return false +} + +func (self *XEth) MessagesChanged(id int) []WhisperMessage { + self.messagesMut.Lock() + defer self.messagesMut.Unlock() + + if self.messages[id] != nil { + return self.messages[id].get() + } + + return nil +} + +// func (self *XEth) Register(args string) bool { +// self.regmut.Lock() +// defer self.regmut.Unlock() + +// if _, ok := self.register[args]; ok { +// self.register[args] = nil // register with empty +// } +// return true +// } + +// func (self *XEth) Unregister(args string) bool { +// self.regmut.Lock() +// defer self.regmut.Unlock() + +// if _, ok := self.register[args]; ok { +// delete(self.register, args) +// return true +// } + +// return false +// } + +// // TODO improve return type +// func (self *XEth) PullWatchTx(args string) []*interface{} { +// self.regmut.Lock() +// defer self.regmut.Unlock() + +// txs := self.register[args] +// self.register[args] = nil + +// return txs +// } + +type KeyVal struct { + Key string `json:"key"` + Value string `json:"value"` +} + +func (self *XEth) EachStorage(addr string) string { + var values []KeyVal + object := self.State().SafeGet(addr) + it := object.Trie().Iterator() + for it.Next() { + values = append(values, KeyVal{common.ToHex(object.Trie().GetKey(it.Key)), common.ToHex(it.Value)}) + } + + valuesJson, err := json.Marshal(values) + if err != nil { + return "" + } + + return string(valuesJson) +} + +func (self *XEth) ToAscii(str string) string { + padded := common.RightPadBytes([]byte(str), 32) + + return "0x" + common.ToHex(padded) +} + +func (self *XEth) FromAscii(str string) string { + if common.IsHex(str) { + str = str[2:] + } + + return string(bytes.Trim(common.FromHex(str), "\x00")) +} + +func (self *XEth) FromNumber(str string) string { + if common.IsHex(str) { + str = str[2:] + } + + return common.BigD(common.FromHex(str)).String() +} + +func (self *XEth) PushTx(encodedTx string) (string, error) { + tx := types.NewTransactionFromBytes(common.FromHex(encodedTx)) + err := self.backend.TxPool().Add(tx) + if err != nil { + return "", err + } + + if tx.To() == nil { + addr := core.AddressFromMessage(tx) + return addr.Hex(), nil + } + return tx.Hash().Hex(), nil +} + +func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { + statedb := self.State().State() //self.eth.ChainManager().TransState() + msg := callmsg{ + from: statedb.GetOrNewStateObject(common.HexToAddress(fromStr)), + to: common.HexToAddress(toStr), + gas: common.Big(gasStr), + gasPrice: common.Big(gasPriceStr), + value: common.Big(valueStr), + data: common.FromHex(dataStr), + } + if msg.gas.Cmp(big.NewInt(0)) == 0 { + msg.gas = defaultGas + } + + if msg.gasPrice.Cmp(big.NewInt(0)) == 0 { + msg.gasPrice = defaultGasPrice + } + + block := self.CurrentBlock() + vmenv := core.NewEnv(statedb, self.backend.ChainManager(), msg, block) + + res, err := vmenv.Call(msg.from, msg.to, msg.data, msg.gas, msg.gasPrice, msg.value) + return common.ToHex(res), err +} + +func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { + var ( + from = common.HexToAddress(fromStr) + to = common.HexToAddress(toStr) + value = common.NewValue(valueStr) + gas = common.Big(gasStr) + price = common.Big(gasPriceStr) + data []byte + contractCreation bool + ) + + // TODO if no_private_key then + //if _, exists := p.register[args.From]; exists { + // p.register[args.From] = append(p.register[args.From], args) + //} else { + /* + account := accounts.Get(common.FromHex(args.From)) + if account != nil { + if account.Unlocked() { + if !unlockAccount(account) { + return + } + } + + result, _ := account.Transact(common.FromHex(args.To), common.FromHex(args.Value), common.FromHex(args.Gas), common.FromHex(args.GasPrice), common.FromHex(args.Data)) + if len(result) > 0 { + *reply = common.ToHex(result) + } + } else if _, exists := p.register[args.From]; exists { + p.register[ags.From] = append(p.register[args.From], args) + } + */ + + // TODO: align default values to have the same type, e.g. not depend on + // common.Value conversions later on + if gas.Cmp(big.NewInt(0)) == 0 { + gas = defaultGas + } + + if price.Cmp(big.NewInt(0)) == 0 { + price = defaultGasPrice + } + + data = common.FromHex(codeStr) + if len(toStr) == 0 { + contractCreation = true + } + + var tx *types.Transaction + if contractCreation { + tx = types.NewContractCreationTx(value.BigInt(), gas, price, data) + } else { + tx = types.NewTransactionMessage(to, value.BigInt(), gas, price, data) + } + + state := self.backend.ChainManager().TxState() + nonce := state.NewNonce(from) + tx.SetNonce(nonce) + + if err := self.sign(tx, from, false); err != nil { + return "", err + } + if err := self.backend.TxPool().Add(tx); err != nil { + return "", err + } + + if contractCreation { + addr := core.AddressFromMessage(tx) + pipelogger.Infof("Contract addr %x\n", addr) + + return core.AddressFromMessage(tx).Hex(), nil + } + return tx.Hash().Hex(), nil +} + +func (self *XEth) sign(tx *types.Transaction, from common.Address, didUnlock bool) error { + sig, err := self.backend.AccountManager().Sign(accounts.Account{Address: from.Bytes()}, tx.Hash().Bytes()) + if err == accounts.ErrLocked { + if didUnlock { + return fmt.Errorf("sender account still locked after successful unlock") + } + if !self.frontend.UnlockAccount(from.Bytes()) { + return fmt.Errorf("could not unlock sender account") + } + // retry signing, the account should now be unlocked. + return self.sign(tx, from, true) + } else if err != nil { + return err + } + tx.SetSignatureValues(sig) + return nil +} + +// callmsg is the message type used for call transations. +type callmsg struct { + from *state.StateObject + to common.Address + gas, gasPrice *big.Int + value *big.Int + data []byte +} + +// accessor boilerplate to implement core.Message +func (m callmsg) From() (common.Address, error) { return m.from.Address(), nil } +func (m callmsg) Nonce() uint64 { return m.from.Nonce() } +func (m callmsg) To() *common.Address { return &m.to } +func (m callmsg) GasPrice() *big.Int { return m.gasPrice } +func (m callmsg) Gas() *big.Int { return m.gas } +func (m callmsg) Value() *big.Int { return m.value } +func (m callmsg) Data() []byte { return m.data } + +type whisperFilter struct { + messages []WhisperMessage + timeout time.Time + id int +} + +func (w *whisperFilter) add(msgs ...WhisperMessage) { + w.messages = append(w.messages, msgs...) +} +func (w *whisperFilter) get() []WhisperMessage { + w.timeout = time.Now() + tmp := w.messages + w.messages = nil + return tmp +} + +type logFilter struct { + logs state.Logs + timeout time.Time + id int +} + +func (l *logFilter) add(logs ...state.Log) { + l.logs = append(l.logs, logs...) +} + +func (l *logFilter) get() state.Logs { + l.timeout = time.Now() + tmp := l.logs + l.logs = nil + return tmp +} |