diff --git a/core/filter.go b/core/filter.go index 0aebcbf69..99336b3b4 100644 --- a/core/filter.go +++ b/core/filter.go @@ -46,7 +46,7 @@ func NewFilter(eth Backend) *Filter { // SetOptions copies the filter options to the filter it self. The reason for this "silly" copy // is simply because named arguments in this case is extremely nice and readable. -func (self *Filter) SetOptions(options FilterOptions) { +func (self *Filter) SetOptions(options *FilterOptions) { self.earliest = options.Earliest self.latest = options.Latest self.skip = options.Skip diff --git a/event/filter/eth_filter.go b/event/filter/eth_filter.go index ab811e90e..1abd48389 100644 --- a/event/filter/eth_filter.go +++ b/event/filter/eth_filter.go @@ -48,7 +48,9 @@ func (self *FilterManager) InstallFilter(filter *core.Filter) (id int) { func (self *FilterManager) UninstallFilter(id int) { self.filterMu.Lock() defer self.filterMu.Unlock() - delete(self.filters, id) + if _, ok := self.filters[id]; ok { + delete(self.filters, id) + } } // GetFilter retrieves a filter installed using InstallFilter. diff --git a/rpc/api.go b/rpc/api.go index fcf2ac9dc..548bace5c 100644 --- a/rpc/api.go +++ b/rpc/api.go @@ -2,494 +2,49 @@ package rpc import ( "encoding/json" - "fmt" "math/big" "path" - "strings" "sync" - "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/event/filter" - "github.com/ethereum/go-ethereum/state" "github.com/ethereum/go-ethereum/xeth" ) -var ( - defaultGasPrice = big.NewInt(150000000000) - defaultGas = big.NewInt(500000) - filterTickerTime = 5 * time.Minute -) - type EthereumApi struct { eth *xeth.XEth xethMu sync.RWMutex - mux *event.TypeMux - - quit chan struct{} - filterManager *filter.FilterManager - - logMut sync.RWMutex - logs map[int]*logFilter - - messagesMut sync.RWMutex - messages map[int]*whisperFilter - // Register keeps a list of accounts and transaction data - regmut sync.Mutex - register map[string][]*NewTxArgs - - db common.Database + db common.Database } func NewEthereumApi(eth *xeth.XEth, dataDir string) *EthereumApi { + // What about when dataDir is empty? db, _ := ethdb.NewLDBDatabase(path.Join(dataDir, "dapps")) api := &EthereumApi{ - eth: eth, - mux: eth.Backend().EventMux(), - quit: make(chan struct{}), - filterManager: filter.NewFilterManager(eth.Backend().EventMux()), - logs: make(map[int]*logFilter), - messages: make(map[int]*whisperFilter), - db: db, + eth: eth, + db: db, } - go api.filterManager.Start() - go api.start() return api } -func (self *EthereumApi) xethWithStateNum(num int64) *xeth.XEth { - chain := self.xeth().Backend().ChainManager() - var block *types.Block +func (self *EthereumApi) xeth() *xeth.XEth { + self.xethMu.RLock() + defer self.xethMu.RUnlock() - if num < 0 { - num = chain.CurrentBlock().Number().Int64() + num + 1 - } - block = chain.GetBlockByNumber(uint64(num)) - - var st *state.StateDB - if block != nil { - st = state.New(block.Root(), self.xeth().Backend().StateDb()) - } else { - st = chain.State() - } - return self.xeth().WithState(st) + return self.eth } -func (self *EthereumApi) getStateWithNum(num int64) *xeth.State { - return self.xethWithStateNum(num).State() -} - -func (self *EthereumApi) 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.xeth().Whisper().Unwatch(id) - delete(self.messages, id) - } - } - self.messagesMut.Unlock() - self.logMut.Unlock() - case <-self.quit: - break done - } - } -} - -func (self *EthereumApi) stop() { - close(self.quit) -} - -// func (self *EthereumApi) Register(args string, reply *interface{}) error { -// self.regmut.Lock() -// defer self.regmut.Unlock() - -// if _, ok := self.register[args]; ok { -// self.register[args] = nil // register with empty -// } -// return nil -// } - -// func (self *EthereumApi) Unregister(args string, reply *interface{}) error { -// self.regmut.Lock() -// defer self.regmut.Unlock() - -// delete(self.register, args) - -// return nil -// } - -// func (self *EthereumApi) WatchTx(args string, reply *interface{}) error { -// self.regmut.Lock() -// defer self.regmut.Unlock() - -// txs := self.register[args] -// self.register[args] = nil - -// *reply = txs -// return nil -// } - -func (self *EthereumApi) NewFilter(args *FilterOptions, reply *interface{}) error { - var id int - filter := core.NewFilter(self.xeth().Backend()) - filter.SetOptions(toFilterOptions(args)) - 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()} - - *reply = common.ToHex(big.NewInt(int64(id)).Bytes()) - - return nil -} - -func (self *EthereumApi) UninstallFilter(id int, reply *interface{}) error { - if _, ok := self.logs[id]; ok { - delete(self.logs, id) - } - - self.filterManager.UninstallFilter(id) - *reply = true - return nil -} - -func (self *EthereumApi) NewFilterString(args *FilterStringArgs, reply *interface{}) error { - var id int - filter := core.NewFilter(self.xeth().Backend()) - - switch args.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{}) - } - default: - return NewValidationError("Word", "Must be `latest` or `pending`") - } - - id = self.filterManager.InstallFilter(filter) - self.logs[id] = &logFilter{timeout: time.Now()} - *reply = common.ToHex(big.NewInt(int64(id)).Bytes()) - - return nil -} - -func (self *EthereumApi) FilterChanged(id int, reply *interface{}) error { - self.logMut.Lock() - defer self.logMut.Unlock() - - if self.logs[id] != nil { - *reply = toLogs(self.logs[id].get()) - } - - return nil -} - -func (self *EthereumApi) Logs(id int, reply *interface{}) error { - self.logMut.Lock() - defer self.logMut.Unlock() - - filter := self.filterManager.GetFilter(id) - if filter != nil { - *reply = toLogs(filter.Find()) - } - - return nil -} - -func (self *EthereumApi) AllLogs(args *FilterOptions, reply *interface{}) error { - filter := core.NewFilter(self.xeth().Backend()) - filter.SetOptions(toFilterOptions(args)) - - *reply = toLogs(filter.Find()) - - return nil -} - -func (p *EthereumApi) Transact(args *NewTxArgs, reply *interface{}) (err error) { - // 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) - } - */ - - if err := args.requirements(); err != nil { - return err - } - - // TODO: align default values to have the same type, e.g. not depend on - // common.Value conversions later on - if args.Gas.Cmp(big.NewInt(0)) == 0 { - args.Gas = defaultGas - } - - if args.GasPrice.Cmp(big.NewInt(0)) == 0 { - args.GasPrice = defaultGasPrice - } - - *reply, err = p.xeth().Transact(args.From, args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data) - if err != nil { - fmt.Println("err:", err) - return err - } - - return nil -} - -func (p *EthereumApi) Call(args *NewTxArgs, reply *interface{}) error { - result, err := p.xethWithStateNum(args.BlockNumber).Call(args.From, args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data) - if err != nil { - return err - } - - *reply = result - return nil -} - -func (p *EthereumApi) GetBalance(args *GetBalanceArgs, reply *interface{}) error { - if err := args.requirements(); err != nil { - return err - } - state := p.getStateWithNum(args.BlockNumber).SafeGet(args.Address) - *reply = common.ToHex(state.Balance().Bytes()) - return nil -} - -func (p *EthereumApi) GetStorage(args *GetStorageArgs, reply *interface{}) error { - if err := args.requirements(); err != nil { - return err - } - *reply = p.getStateWithNum(args.BlockNumber).SafeGet(args.Address).Storage() - return nil -} - -func (p *EthereumApi) GetStorageAt(args *GetStorageAtArgs, reply *interface{}) error { - if err := args.requirements(); err != nil { - return err - } - state := p.getStateWithNum(args.BlockNumber).SafeGet(args.Address) - - value := state.StorageString(args.Key) - var hx string - if strings.Index(args.Key, "0x") == 0 { - hx = string([]byte(args.Key)[2:]) - } else { - // Convert the incoming string (which is a bigint) into hex - i, _ := new(big.Int).SetString(args.Key, 10) - hx = common.Bytes2Hex(i.Bytes()) - } - rpclogger.Debugf("GetStateAt(%s, %s)\n", args.Address, hx) - *reply = map[string]string{args.Key: value.Str()} - return nil -} - -func (p *EthereumApi) GetTxCountAt(args *GetTxCountArgs, reply *interface{}) error { - err := args.requirements() - if err != nil { - return err - } - *reply = p.xethWithStateNum(args.BlockNumber).TxCountAt(args.Address) - return nil -} - -func (p *EthereumApi) GetData(args *GetDataArgs, reply *interface{}) error { - if err := args.requirements(); err != nil { - return err - } - *reply = p.xethWithStateNum(args.BlockNumber).CodeAt(args.Address) - return nil -} - -func (p *EthereumApi) GetCompilers(reply *interface{}) error { - c := []string{""} - *reply = c - return nil -} - -func (p *EthereumApi) DbPut(args *DbArgs, reply *interface{}) error { - if err := args.requirements(); err != nil { - return err - } - - p.db.Put([]byte(args.Database+args.Key), []byte(args.Value)) - *reply = true - return nil -} - -func (p *EthereumApi) DbGet(args *DbArgs, reply *interface{}) error { - if err := args.requirements(); err != nil { - return err - } - - res, _ := p.db.Get([]byte(args.Database + args.Key)) - *reply = string(res) - return nil -} - -func (p *EthereumApi) NewWhisperIdentity(reply *interface{}) error { - *reply = p.xeth().Whisper().NewIdentity() - return nil -} - -// func (p *EthereumApi) RemoveWhisperIdentity(args *WhisperIdentityArgs, reply *interface{}) error { -// *reply = p.xeth().Whisper().RemoveIdentity(args.Identity) -// return nil -// } - -func (p *EthereumApi) NewWhisperFilter(args *WhisperFilterArgs, reply *interface{}) error { - var id int - opts := new(xeth.Options) - opts.From = args.From - opts.To = args.To - opts.Topics = args.Topics - opts.Fn = func(msg xeth.WhisperMessage) { - p.messagesMut.Lock() - defer p.messagesMut.Unlock() - p.messages[id].add(msg) // = append(p.messages[id], msg) - } - id = p.xeth().Whisper().Watch(opts) - p.messages[id] = &whisperFilter{timeout: time.Now()} - *reply = common.ToHex(big.NewInt(int64(id)).Bytes()) - return nil -} - -func (p *EthereumApi) UninstallWhisperFilter(id int, reply *interface{}) error { - delete(p.messages, id) - *reply = true - return nil -} - -func (self *EthereumApi) MessagesChanged(id int, reply *interface{}) error { - self.messagesMut.Lock() - defer self.messagesMut.Unlock() - - if self.messages[id] != nil { - *reply = self.messages[id].get() - } - - return nil -} - -func (p *EthereumApi) WhisperPost(args *WhisperMessageArgs, reply *interface{}) error { - err := p.xeth().Whisper().Post(args.Payload, args.To, args.From, args.Topics, args.Priority, args.Ttl) - if err != nil { - return err - } - - *reply = true - return nil -} - -func (p *EthereumApi) HasWhisperIdentity(args string, reply *interface{}) error { - *reply = p.xeth().Whisper().HasIdentity(args) - return nil -} - -func (p *EthereumApi) WhisperMessages(id int, reply *interface{}) error { - *reply = p.xeth().Whisper().Messages(id) - return nil -} - -func (p *EthereumApi) GetTransactionByHash(hash string, reply *interface{}) error { - tx := p.xeth().EthTransactionByHash(hash) - if tx != nil { - *reply = NewTransactionRes(tx) - } - return nil -} - -func (p *EthereumApi) GetBlockByHash(blockhash string, includetx bool) (*BlockRes, error) { - block := p.xeth().EthBlockByHash(blockhash) - br := NewBlockRes(block) - br.fullTx = includetx - return br, nil -} - -func (p *EthereumApi) GetBlockByNumber(blocknum int64, includetx bool) (*BlockRes, error) { - block := p.xeth().EthBlockByNumber(blocknum) - br := NewBlockRes(block) - br.fullTx = includetx - return br, nil -} - -func (p *EthereumApi) GetBlockTransactionCountByHash(blockhash string) (int64, error) { - block := p.xeth().EthBlockByHash(blockhash) - br := NewBlockRes(block) - return int64(len(br.Transactions)), nil -} - -func (p *EthereumApi) GetBlockTransactionCountByNumber(blocknum int64) (int64, error) { - block := p.xeth().EthBlockByNumber(blocknum) - br := NewBlockRes(block) - return int64(len(br.Transactions)), nil -} - -func (p *EthereumApi) GetBlockUncleCountByHash(blockhash string) (int64, error) { - block := p.xeth().EthBlockByHash(blockhash) - br := NewBlockRes(block) - return int64(len(br.Uncles)), nil -} - -func (p *EthereumApi) GetBlockUncleCountByNumber(blocknum int64) (int64, error) { - block := p.xeth().EthBlockByNumber(blocknum) - br := NewBlockRes(block) - return int64(len(br.Uncles)), nil +func (self *EthereumApi) xethAtStateNum(num int64) *xeth.XEth { + return self.xeth().AtStateNum(num) } func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error { // Spec at https://github.com/ethereum/wiki/wiki/Generic-JSON-RPC rpclogger.Debugf("%s %s", req.Method, req.Params) + switch req.Method { case "web3_sha3": args := new(Sha3Args) @@ -504,7 +59,8 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error case "net_listening": *reply = p.xeth().IsListening() case "net_peerCount": - *reply = common.ToHex(big.NewInt(int64(p.xeth().PeerCount())).Bytes()) + v := p.xeth().PeerCount() + *reply = common.ToHex(big.NewInt(int64(v)).Bytes()) case "eth_coinbase": // TODO handling of empty coinbase due to lack of accounts res := p.xeth().Coinbase() @@ -516,97 +72,131 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error case "eth_mining": *reply = p.xeth().IsMining() case "eth_gasPrice": - *reply = common.ToHex(defaultGasPrice.Bytes()) + v := p.xeth().DefaultGas() + *reply = common.ToHex(v.Bytes()) case "eth_accounts": *reply = p.xeth().Accounts() case "eth_blockNumber": - *reply = common.ToHex(p.xeth().Backend().ChainManager().CurrentBlock().Number().Bytes()) + v := p.xeth().Backend().ChainManager().CurrentBlock().Number() + *reply = common.ToHex(v.Bytes()) case "eth_getBalance": args := new(GetBalanceArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.GetBalance(args, reply) + + if err := args.requirements(); err != nil { + return err + } + + v := p.xethAtStateNum(args.BlockNumber).State().SafeGet(args.Address).Balance() + *reply = common.ToHex(v.Bytes()) case "eth_getStorage", "eth_storageAt": args := new(GetStorageArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.GetStorage(args, reply) + + if err := args.requirements(); err != nil { + return err + } + + *reply = p.xethAtStateNum(args.BlockNumber).State().SafeGet(args.Address).Storage() case "eth_getStorageAt": args := new(GetStorageAtArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.GetStorageAt(args, reply) + if err := args.requirements(); err != nil { + return err + } + + state := p.xethAtStateNum(args.BlockNumber).State().SafeGet(args.Address) + value := state.StorageString(args.Key) + + *reply = common.Bytes2Hex(value.Bytes()) case "eth_getTransactionCount": args := new(GetTxCountArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.GetTxCountAt(args, reply) + + err := args.requirements() + if err != nil { + return err + } + + *reply = p.xethAtStateNum(args.BlockNumber).TxCountAt(args.Address) case "eth_getBlockTransactionCountByHash": args := new(GetBlockByHashArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - v, err := p.GetBlockTransactionCountByHash(args.BlockHash) - if err != nil { - return err - } - *reply = common.ToHex(big.NewInt(v).Bytes()) + block := NewBlockRes(p.xeth().EthBlockByHash(args.BlockHash)) + *reply = common.ToHex(big.NewInt(int64(len(block.Transactions))).Bytes()) case "eth_getBlockTransactionCountByNumber": args := new(GetBlockByNumberArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - v, err := p.GetBlockTransactionCountByNumber(args.BlockNumber) - if err != nil { - return err - } - *reply = common.ToHex(big.NewInt(v).Bytes()) + block := NewBlockRes(p.xeth().EthBlockByNumber(args.BlockNumber)) + *reply = common.ToHex(big.NewInt(int64(len(block.Transactions))).Bytes()) case "eth_getUncleCountByBlockHash": args := new(GetBlockByHashArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - v, err := p.GetBlockUncleCountByHash(args.BlockHash) - if err != nil { - return err - } - *reply = common.ToHex(big.NewInt(v).Bytes()) + block := p.xeth().EthBlockByHash(args.BlockHash) + br := NewBlockRes(block) + *reply = common.ToHex(big.NewInt(int64(len(br.Uncles))).Bytes()) case "eth_getUncleCountByBlockNumber": args := new(GetBlockByNumberArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - v, err := p.GetBlockUncleCountByNumber(args.BlockNumber) - if err != nil { - return err - } - *reply = common.ToHex(big.NewInt(v).Bytes()) + block := p.xeth().EthBlockByNumber(args.BlockNumber) + br := NewBlockRes(block) + *reply = common.ToHex(big.NewInt(int64(len(br.Uncles))).Bytes()) case "eth_getData", "eth_getCode": args := new(GetDataArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.GetData(args, reply) + if err := args.requirements(); err != nil { + return err + } + *reply = p.xethAtStateNum(args.BlockNumber).CodeAt(args.Address) case "eth_sendTransaction", "eth_transact": args := new(NewTxArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.Transact(args, reply) + + if err := args.requirements(); err != nil { + return err + } + + v, err := p.xeth().Transact(args.From, args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data) + if err != nil { + return err + } + *reply = v case "eth_call": args := new(NewTxArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.Call(args, reply) + + v, err := p.xethAtStateNum(args.BlockNumber).Call(args.From, args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data) + if err != nil { + return err + } + + *reply = v case "eth_flush": return NewNotImplementedError(req.Method) case "eth_getBlockByHash": @@ -615,52 +205,55 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error return err } - v, err := p.GetBlockByHash(args.BlockHash, args.Transactions) - if err != nil { - return err - } - *reply = v + block := p.xeth().EthBlockByHash(args.BlockHash) + br := NewBlockRes(block) + br.fullTx = args.IncludeTxs + + *reply = br case "eth_getBlockByNumber": args := new(GetBlockByNumberArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - v, err := p.GetBlockByNumber(args.BlockNumber, args.Transactions) - if err != nil { - return err - } - *reply = v + block := p.xeth().EthBlockByNumber(args.BlockNumber) + br := NewBlockRes(block) + br.fullTx = args.IncludeTxs + + *reply = br case "eth_getTransactionByHash": // HashIndexArgs used, but only the "Hash" part we need. args := new(HashIndexArgs) if err := json.Unmarshal(req.Params, &args); err != nil { } - return p.GetTransactionByHash(args.Hash, reply) + tx := p.xeth().EthTransactionByHash(args.Hash) + if tx != nil { + *reply = NewTransactionRes(tx) + } case "eth_getTransactionByBlockHashAndIndex": args := new(HashIndexArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - v, err := p.GetBlockByHash(args.Hash, true) - if err != nil { - return err - } - if args.Index > int64(len(v.Transactions)) || args.Index < 0 { + block := p.xeth().EthBlockByHash(args.Hash) + br := NewBlockRes(block) + br.fullTx = true + + if args.Index > int64(len(br.Transactions)) || args.Index < 0 { return NewValidationError("Index", "does not exist") } - *reply = v.Transactions[args.Index] + *reply = br.Transactions[args.Index] case "eth_getTransactionByBlockNumberAndIndex": args := new(BlockNumIndexArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - v, err := p.GetBlockByNumber(args.BlockNumber, true) - if err != nil { - return err - } + block := p.xeth().EthBlockByNumber(args.BlockNumber) + v := NewBlockRes(block) + v.fullTx = true + if args.Index > int64(len(v.Transactions)) || args.Index < 0 { return NewValidationError("Index", "does not exist") } @@ -671,18 +264,15 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error return err } - v, err := p.GetBlockByHash(args.Hash, false) - if err != nil { - return err - } - if args.Index > int64(len(v.Uncles)) || args.Index < 0 { + br := NewBlockRes(p.xeth().EthBlockByHash(args.Hash)) + + if args.Index > int64(len(br.Uncles)) || args.Index < 0 { return NewValidationError("Index", "does not exist") } - uncle, err := p.GetBlockByHash(common.ToHex(v.Uncles[args.Index]), false) - if err != nil { - return err - } + uhash := common.ToHex(br.Uncles[args.Index]) + uncle := NewBlockRes(p.xeth().EthBlockByHash(uhash)) + *reply = uncle case "eth_getUncleByBlockNumberAndIndex": args := new(BlockNumIndexArgs) @@ -690,59 +280,68 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error return err } - v, err := p.GetBlockByNumber(args.BlockNumber, true) - if err != nil { - return err - } + block := p.xeth().EthBlockByNumber(args.BlockNumber) + v := NewBlockRes(block) + v.fullTx = true + if args.Index > int64(len(v.Uncles)) || args.Index < 0 { return NewValidationError("Index", "does not exist") } - uncle, err := p.GetBlockByHash(common.ToHex(v.Uncles[args.Index]), false) - if err != nil { - return err - } + uhash := common.ToHex(v.Uncles[args.Index]) + uncle := NewBlockRes(p.xeth().EthBlockByHash(uhash)) + *reply = uncle case "eth_getCompilers": - return p.GetCompilers(reply) + c := []string{""} + *reply = c case "eth_compileSolidity", "eth_compileLLL", "eth_compileSerpent": return NewNotImplementedError(req.Method) case "eth_newFilter": - args := new(FilterOptions) + args := new(BlockFilterArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.NewFilter(args, reply) + + opts := toFilterOptions(args) + id := p.xeth().RegisterFilter(opts) + *reply = common.ToHex(big.NewInt(int64(id)).Bytes()) case "eth_newBlockFilter": args := new(FilterStringArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.NewFilterString(args, reply) + if err := args.requirements(); err != nil { + return err + } + + id := p.xeth().NewFilterString(args.Word) + *reply = common.ToHex(big.NewInt(int64(id)).Bytes()) case "eth_uninstallFilter": args := new(FilterIdArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.UninstallFilter(args.Id, reply) + *reply = p.xeth().UninstallFilter(args.Id) case "eth_getFilterChanges": args := new(FilterIdArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.FilterChanged(args.Id, reply) + *reply = NewLogsRes(p.xeth().FilterChanged(args.Id)) case "eth_getFilterLogs": args := new(FilterIdArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.Logs(args.Id, reply) + *reply = NewLogsRes(p.xeth().Logs(args.Id)) case "eth_getLogs": - args := new(FilterOptions) + args := new(BlockFilterArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.AllLogs(args, reply) + opts := toFilterOptions(args) + *reply = NewLogsRes(p.xeth().AllLogs(opts)) case "eth_getWork", "eth_submitWork": return NewNotImplementedError(req.Method) case "db_putString": @@ -750,13 +349,25 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.DbPut(args, reply) + + if err := args.requirements(); err != nil { + return err + } + + p.db.Put([]byte(args.Database+args.Key), []byte(args.Value)) + *reply = true case "db_getString": args := new(DbArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.DbGet(args, reply) + + if err := args.requirements(); err != nil { + return err + } + + res, _ := p.db.Get([]byte(args.Database + args.Key)) + *reply = string(res) case "db_putHex", "db_getHex": return NewNotImplementedError(req.Method) case "shh_post": @@ -764,21 +375,27 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.WhisperPost(args, reply) + + err := p.xeth().Whisper().Post(args.Payload, args.To, args.From, args.Topics, args.Priority, args.Ttl) + if err != nil { + return err + } + + *reply = true case "shh_newIdentity": - return p.NewWhisperIdentity(reply) + *reply = p.xeth().Whisper().NewIdentity() // case "shh_removeIdentity": // args := new(WhisperIdentityArgs) // if err := json.Unmarshal(req.Params, &args); err != nil { // return err // } - // return p.RemoveWhisperIdentity(args, reply) + // *reply = p.xeth().Whisper().RemoveIdentity(args.Identity) case "shh_hasIdentity": args := new(WhisperIdentityArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.HasWhisperIdentity(args.Identity, reply) + *reply = p.xeth().Whisper().HasIdentity(args.Identity) case "shh_newGroup", "shh_addToGroup": return NewNotImplementedError(req.Method) case "shh_newFilter": @@ -786,43 +403,49 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.NewWhisperFilter(args, reply) + opts := new(xeth.Options) + opts.From = args.From + opts.To = args.To + opts.Topics = args.Topics + id := p.xeth().NewWhisperFilter(opts) + *reply = common.ToHex(big.NewInt(int64(id)).Bytes()) case "shh_uninstallFilter": args := new(FilterIdArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.UninstallWhisperFilter(args.Id, reply) + *reply = p.xeth().UninstallWhisperFilter(args.Id) case "shh_getFilterChanges": args := new(FilterIdArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.MessagesChanged(args.Id, reply) + *reply = p.xeth().MessagesChanged(args.Id) case "shh_getMessages": args := new(FilterIdArgs) if err := json.Unmarshal(req.Params, &args); err != nil { return err } - return p.WhisperMessages(args.Id, reply) + *reply = p.xeth().Whisper().Messages(args.Id) // case "eth_register": - // args, err := req.ToRegisterArgs() - // if err != nil { + // // Placeholder for actual type + // args := new(HashIndexArgs) + // if err := json.Unmarshal(req.Params, &args); err != nil { // return err // } - // return p.Register(args, reply) + // *reply = p.xeth().Register(args.Hash) // case "eth_unregister": - // args, err := req.ToRegisterArgs() - // if err != nil { + // args := new(HashIndexArgs) + // if err := json.Unmarshal(req.Params, &args); err != nil { // return err // } - // return p.Unregister(args, reply) + // *reply = p.xeth().Unregister(args.Hash) // case "eth_watchTx": - // args, err := req.ToWatchTxArgs() - // if err != nil { + // args := new(HashIndexArgs) + // if err := json.Unmarshal(req.Params, &args); err != nil { // return err // } - // return p.WatchTx(args, reply) + // *reply = p.xeth().PullWatchTx(args.Hash) default: return NewNotImplementedError(req.Method) } @@ -831,14 +454,7 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error return nil } -func (self *EthereumApi) xeth() *xeth.XEth { - self.xethMu.RLock() - defer self.xethMu.RUnlock() - - return self.eth -} - -func toFilterOptions(options *FilterOptions) core.FilterOptions { +func toFilterOptions(options *BlockFilterArgs) *core.FilterOptions { var opts core.FilterOptions // Convert optional address slice/string to byte slice @@ -871,5 +487,5 @@ func toFilterOptions(options *FilterOptions) core.FilterOptions { } opts.Topics = topics - return opts + return &opts } diff --git a/rpc/api_test.go b/rpc/api_test.go index ec03822c5..727ade007 100644 --- a/rpc/api_test.go +++ b/rpc/api_test.go @@ -2,9 +2,9 @@ package rpc import ( "encoding/json" - "sync" + // "sync" "testing" - "time" + // "time" ) func TestWeb3Sha3(t *testing.T) { @@ -24,33 +24,33 @@ func TestWeb3Sha3(t *testing.T) { } } -func TestFilterClose(t *testing.T) { - t.Skip() - api := &EthereumApi{ - logs: make(map[int]*logFilter), - messages: make(map[int]*whisperFilter), - quit: make(chan struct{}), - } +// func TestFilterClose(t *testing.T) { +// t.Skip() +// api := &EthereumApi{ +// logs: make(map[int]*logFilter), +// messages: make(map[int]*whisperFilter), +// quit: make(chan struct{}), +// } - filterTickerTime = 1 - api.logs[0] = &logFilter{} - api.messages[0] = &whisperFilter{} - var wg sync.WaitGroup - wg.Add(1) - go api.start() - go func() { - select { - case <-time.After(500 * time.Millisecond): - api.stop() - wg.Done() - } - }() - wg.Wait() - if len(api.logs) != 0 { - t.Error("expected logs to be empty") - } +// filterTickerTime = 1 +// api.logs[0] = &logFilter{} +// api.messages[0] = &whisperFilter{} +// var wg sync.WaitGroup +// wg.Add(1) +// go api.start() +// go func() { +// select { +// case <-time.After(500 * time.Millisecond): +// api.stop() +// wg.Done() +// } +// }() +// wg.Wait() +// if len(api.logs) != 0 { +// t.Error("expected logs to be empty") +// } - if len(api.messages) != 0 { - t.Error("expected messages to be empty") - } -} +// if len(api.messages) != 0 { +// t.Error("expected messages to be empty") +// } +// } diff --git a/rpc/args.go b/rpc/args.go index ab1c40585..e50c9b1f5 100644 --- a/rpc/args.go +++ b/rpc/args.go @@ -35,8 +35,8 @@ func blockAge(raw interface{}, number *int64) (err error) { } type GetBlockByHashArgs struct { - BlockHash string - Transactions bool + BlockHash string + IncludeTxs bool } func (args *GetBlockByHashArgs) UnmarshalJSON(b []byte) (err error) { @@ -57,15 +57,15 @@ func (args *GetBlockByHashArgs) UnmarshalJSON(b []byte) (err error) { args.BlockHash = argstr if len(obj) > 1 { - args.Transactions = obj[1].(bool) + args.IncludeTxs = obj[1].(bool) } return nil } type GetBlockByNumberArgs struct { - BlockNumber int64 - Transactions bool + BlockNumber int64 + IncludeTxs bool } func (args *GetBlockByNumberArgs) UnmarshalJSON(b []byte) (err error) { @@ -86,7 +86,7 @@ func (args *GetBlockByNumberArgs) UnmarshalJSON(b []byte) (err error) { } if len(obj) > 1 { - args.Transactions = obj[1].(bool) + args.IncludeTxs = obj[1].(bool) } return nil @@ -433,7 +433,7 @@ func (args *Sha3Args) UnmarshalJSON(b []byte) (err error) { return nil } -type FilterOptions struct { +type BlockFilterArgs struct { Earliest int64 Latest int64 Address interface{} @@ -442,7 +442,7 @@ type FilterOptions struct { Max int } -func (args *FilterOptions) UnmarshalJSON(b []byte) (err error) { +func (args *BlockFilterArgs) UnmarshalJSON(b []byte) (err error) { var obj []struct { FromBlock interface{} `json:"fromBlock"` ToBlock interface{} `json:"toBlock"` @@ -609,6 +609,16 @@ func (args *FilterStringArgs) UnmarshalJSON(b []byte) (err error) { return nil } +func (args *FilterStringArgs) requirements() error { + switch args.Word { + case "latest", "pending": + break + default: + return NewValidationError("Word", "Must be `latest` or `pending`") + } + return nil +} + type FilterIdArgs struct { Id int } diff --git a/rpc/args_test.go b/rpc/args_test.go index 0d8dc4085..cfdd278b8 100644 --- a/rpc/args_test.go +++ b/rpc/args_test.go @@ -82,7 +82,7 @@ func TestGetBlockByHashArgs(t *testing.T) { input := `["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", true]` expected := new(GetBlockByHashArgs) expected.BlockHash = "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331" - expected.Transactions = true + expected.IncludeTxs = true args := new(GetBlockByHashArgs) if err := json.Unmarshal([]byte(input), &args); err != nil { @@ -93,8 +93,8 @@ func TestGetBlockByHashArgs(t *testing.T) { t.Errorf("BlockHash should be %v but is %v", expected.BlockHash, args.BlockHash) } - if args.Transactions != expected.Transactions { - t.Errorf("Transactions should be %v but is %v", expected.Transactions, args.Transactions) + if args.IncludeTxs != expected.IncludeTxs { + t.Errorf("IncludeTxs should be %v but is %v", expected.IncludeTxs, args.IncludeTxs) } } @@ -112,7 +112,7 @@ func TestGetBlockByNumberArgs(t *testing.T) { input := `["0x1b4", false]` expected := new(GetBlockByNumberArgs) expected.BlockNumber = 436 - expected.Transactions = false + expected.IncludeTxs = false args := new(GetBlockByNumberArgs) if err := json.Unmarshal([]byte(input), &args); err != nil { @@ -123,8 +123,8 @@ func TestGetBlockByNumberArgs(t *testing.T) { t.Errorf("BlockHash should be %v but is %v", expected.BlockNumber, args.BlockNumber) } - if args.Transactions != expected.Transactions { - t.Errorf("Transactions should be %v but is %v", expected.Transactions, args.Transactions) + if args.IncludeTxs != expected.IncludeTxs { + t.Errorf("IncludeTxs should be %v but is %v", expected.IncludeTxs, args.IncludeTxs) } } @@ -388,7 +388,7 @@ func TestGetDataEmptyArgs(t *testing.T) { } } -func TestFilterOptions(t *testing.T) { +func TestBlockFilterArgs(t *testing.T) { input := `[{ "fromBlock": "0x1", "toBlock": "0x2", @@ -396,7 +396,7 @@ func TestFilterOptions(t *testing.T) { "offset": "0x0", "address": "0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8", "topics": ["0x12341234"]}]` - expected := new(FilterOptions) + expected := new(BlockFilterArgs) expected.Earliest = 1 expected.Latest = 2 expected.Max = 3 @@ -404,7 +404,7 @@ func TestFilterOptions(t *testing.T) { expected.Address = "0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8" // expected.Topics = []string{"0x12341234"} - args := new(FilterOptions) + args := new(BlockFilterArgs) if err := json.Unmarshal([]byte(input), &args); err != nil { t.Error(err) } @@ -434,16 +434,16 @@ func TestFilterOptions(t *testing.T) { // } } -func TestFilterOptionsWords(t *testing.T) { +func TestBlockFilterArgsWords(t *testing.T) { input := `[{ "fromBlock": "latest", "toBlock": "pending" }]` - expected := new(FilterOptions) + expected := new(BlockFilterArgs) expected.Earliest = 0 expected.Latest = -1 - args := new(FilterOptions) + args := new(BlockFilterArgs) if err := json.Unmarshal([]byte(input), &args); err != nil { t.Error(err) } @@ -457,13 +457,13 @@ func TestFilterOptionsWords(t *testing.T) { } } -func TestFilterOptionsNums(t *testing.T) { +func TestBlockFilterArgsNums(t *testing.T) { input := `[{ "fromBlock": 2, "toBlock": 3 }]` - args := new(FilterOptions) + args := new(BlockFilterArgs) err := json.Unmarshal([]byte(input), &args) switch err.(type) { case *DecodeParamError: @@ -474,10 +474,10 @@ func TestFilterOptionsNums(t *testing.T) { } -func TestFilterOptionsEmptyArgs(t *testing.T) { +func TestBlockFilterArgsEmptyArgs(t *testing.T) { input := `[]` - args := new(FilterOptions) + args := new(BlockFilterArgs) err := json.Unmarshal([]byte(input), &args) if err == nil { t.Error("Expected error but didn't get one") diff --git a/rpc/http.go b/rpc/http.go index 5f2445e6c..3dfb67781 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -10,7 +10,7 @@ import ( "github.com/ethereum/go-ethereum/xeth" ) -var rpchttplogger = logger.NewLogger("RPC-HTTP") +var rpclogger = logger.NewLogger("RPC") const ( jsonrpcver = "2.0" @@ -28,7 +28,7 @@ func JSONRPC(pipe *xeth.XEth, dataDir string) http.Handler { // Limit request size to resist DoS if req.ContentLength > maxSizeReqLength { jsonerr := &RpcErrorObject{-32700, "Request too large"} - Send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr}) + send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr}) return } @@ -37,14 +37,14 @@ func JSONRPC(pipe *xeth.XEth, dataDir string) http.Handler { body, err := ioutil.ReadAll(req.Body) if err != nil { jsonerr := &RpcErrorObject{-32700, "Could not read request body"} - Send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr}) + send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr}) } // Try to parse the request as a single var reqSingle RpcRequest if err := json.Unmarshal(body, &reqSingle); err == nil { response := RpcResponse(api, &reqSingle) - Send(w, &response) + send(w, &response) return } @@ -57,13 +57,13 @@ func JSONRPC(pipe *xeth.XEth, dataDir string) http.Handler { response := RpcResponse(api, &request) resBatch[i] = response } - Send(w, resBatch) + send(w, resBatch) return } // Not a batch or single request, error jsonerr := &RpcErrorObject{-32600, "Could not decode request"} - Send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr}) + send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr}) }) } @@ -84,11 +84,11 @@ func RpcResponse(api *EthereumApi, request *RpcRequest) *interface{} { response = &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: request.Id, Error: jsonerr} } - rpchttplogger.DebugDetailf("Generated response: %T %s", response, response) + rpclogger.DebugDetailf("Generated response: %T %s", response, response) return &response } -func Send(writer io.Writer, v interface{}) (n int, err error) { +func send(writer io.Writer, v interface{}) (n int, err error) { var payload []byte payload, err = json.MarshalIndent(v, "", "\t") if err != nil { diff --git a/rpc/messages_test.go b/rpc/messages_test.go new file mode 100644 index 000000000..5274c91e4 --- /dev/null +++ b/rpc/messages_test.go @@ -0,0 +1,41 @@ +package rpc + +import ( + "testing" +) + +func TestInsufficientParamsError(t *testing.T) { + err := NewInsufficientParamsError(0, 1) + expected := "insufficient params, want 1 have 0" + + if err.Error() != expected { + t.Error(err.Error()) + } +} + +func TestNotImplementedError(t *testing.T) { + err := NewNotImplementedError("foo") + expected := "foo method not implemented" + + if err.Error() != expected { + t.Error(err.Error()) + } +} + +func TestDecodeParamError(t *testing.T) { + err := NewDecodeParamError("foo") + expected := "could not decode, foo" + + if err.Error() != expected { + t.Error(err.Error()) + } +} + +func TestValidationError(t *testing.T) { + err := NewValidationError("foo", "should be `bar`") + expected := "foo not valid, should be `bar`" + + if err.Error() != expected { + t.Error(err.Error()) + } +} diff --git a/rpc/responses.go b/rpc/responses.go index eec483fb7..a3613f380 100644 --- a/rpc/responses.go +++ b/rpc/responses.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/state" ) type BlockRes struct { @@ -211,3 +212,28 @@ type FilterWhisperRes struct { Payload string `json:"payload"` WorkProved string `json:"workProved"` } + +type LogRes struct { + Address string `json:"address"` + Topic []string `json:"topic"` + Data string `json:"data"` + Number uint64 `json:"number"` +} + +func NewLogsRes(logs state.Logs) (ls []LogRes) { + ls = make([]LogRes, len(logs)) + + for i, log := range logs { + var l LogRes + l.Topic = make([]string, len(log.Topics())) + l.Address = common.ToHex(log.Address()) + l.Data = common.ToHex(log.Data()) + l.Number = log.Number() + for j, topic := range log.Topics() { + l.Topic[j] = common.ToHex(topic) + } + ls[i] = l + } + + return +} diff --git a/rpc/util.go b/rpc/util.go deleted file mode 100644 index 0798ae1d2..000000000 --- a/rpc/util.go +++ /dev/null @@ -1,86 +0,0 @@ -/* - This file is part of go-ethereum - - go-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - go-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with go-ethereum. If not, see . -*/ -package rpc - -import ( - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/state" - "github.com/ethereum/go-ethereum/xeth" -) - -var rpclogger = logger.NewLogger("RPC") - -type Log struct { - Address string `json:"address"` - Topic []string `json:"topic"` - Data string `json:"data"` - Number uint64 `json:"number"` -} - -func toLogs(logs state.Logs) (ls []Log) { - ls = make([]Log, len(logs)) - - for i, log := range logs { - var l Log - l.Topic = make([]string, len(log.Topics())) - l.Address = common.ToHex(log.Address()) - l.Data = common.ToHex(log.Data()) - l.Number = log.Number() - for j, topic := range log.Topics() { - l.Topic[j] = common.ToHex(topic) - } - ls[i] = l - } - - return -} - -type whisperFilter struct { - messages []xeth.WhisperMessage - timeout time.Time - id int -} - -func (w *whisperFilter) add(msgs ...xeth.WhisperMessage) { - w.messages = append(w.messages, msgs...) -} -func (w *whisperFilter) get() []xeth.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 -} diff --git a/xeth/xeth.go b/xeth/xeth.go index 115bd787a..142ae7218 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -6,6 +6,8 @@ import ( "encoding/json" "fmt" "math/big" + "sync" + "time" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" @@ -13,13 +15,19 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/event/filter" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/state" "github.com/ethereum/go-ethereum/whisper" ) -var pipelogger = logger.NewLogger("XETH") +var ( + pipelogger = logger.NewLogger("XETH") + filterTickerTime = 5 * time.Minute + defaultGasPrice = big.NewInt(10000000000000) //150000000000 + defaultGas = big.NewInt(90000) //500000 +) // to resolve the import cycle type Backend interface { @@ -62,6 +70,13 @@ type Frontend interface { ConfirmTransaction(tx *types.Transaction) bool } +// dummyFrontend is a non-interactive frontend that allows all +// transactions but cannot not unlock any keys. +type dummyFrontend struct{} + +func (dummyFrontend) UnlockAccount([]byte) bool { return false } +func (dummyFrontend) ConfirmTransaction(*types.Transaction) bool { return true } + type XEth struct { eth Backend blockProcessor *core.BlockProcessor @@ -71,15 +86,20 @@ type XEth struct { whisper *Whisper frontend Frontend + + 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 } -// dummyFrontend is a non-interactive frontend that allows all -// transactions but cannot not unlock any keys. -type dummyFrontend struct{} - -func (dummyFrontend) UnlockAccount([]byte) bool { return false } -func (dummyFrontend) ConfirmTransaction(*types.Transaction) bool { return true } - // 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. @@ -90,15 +110,76 @@ func New(eth Backend, frontend Frontend) *XEth { chainManager: eth.ChainManager(), accountManager: eth.AccountManager(), whisper: NewWhisper(eth.Whisper()), + quit: make(chan struct{}), + filterManager: filter.NewFilterManager(eth.EventMux()), frontend: frontend, + logs: make(map[int]*logFilter), + messages: make(map[int]*whisperFilter), } if frontend == nil { xeth.frontend = dummyFrontend{} } xeth.state = NewState(xeth, xeth.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 (self *XEth) DefaultGas() *big.Int { return defaultGas } +func (self *XEth) DefaultGasPrice() *big.Int { return defaultGasPrice } + +func (self *XEth) AtStateNum(num int64) *XEth { + chain := self.Backend().ChainManager() + var block *types.Block + + if num < 0 { + num = chain.CurrentBlock().Number().Int64() + num + 1 + } + block = chain.GetBlockByNumber(uint64(num)) + + var st *state.StateDB + if block != nil { + st = state.New(block.Root(), self.Backend().StateDb()) + } else { + st = chain.State() + } + return self.WithState(st) +} + func (self *XEth) Backend() Backend { return self.eth } func (self *XEth) WithState(statedb *state.StateDB) *XEth { xeth := &XEth{ @@ -241,6 +322,157 @@ func (self *XEth) SecretToAddress(key string) string { return common.ToHex(pair.Address()) } +func (self *XEth) RegisterFilter(args *core.FilterOptions) int { + var id int + filter := core.NewFilter(self.Backend()) + filter.SetOptions(args) + 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(args *core.FilterOptions) state.Logs { + filter := core.NewFilter(self.Backend()) + filter.SetOptions(args) + + 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"` @@ -298,11 +530,6 @@ func (self *XEth) PushTx(encodedTx string) (string, error) { return common.ToHex(tx.Hash()), nil } -var ( - defaultGasPrice = big.NewInt(10000000000000) - defaultGas = big.NewInt(90000) -) - func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { statedb := self.State().State() //self.chainManager.TransState() msg := callmsg{ @@ -333,12 +560,44 @@ func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeSt from []byte to []byte value = common.NewValue(valueStr) - gas = common.NewValue(gasStr) - price = common.NewValue(gasPriceStr) + 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 + } + from = common.FromHex(fromStr) data = common.FromHex(codeStr) to = common.FromHex(toStr) @@ -348,9 +607,9 @@ func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeSt var tx *types.Transaction if contractCreation { - tx = types.NewContractCreationTx(value.BigInt(), gas.BigInt(), price.BigInt(), data) + tx = types.NewContractCreationTx(value.BigInt(), gas, price, data) } else { - tx = types.NewTransactionMessage(to, value.BigInt(), gas.BigInt(), price.BigInt(), data) + tx = types.NewTransactionMessage(to, value.BigInt(), gas, price, data) } state := self.chainManager.TxState() @@ -411,3 +670,36 @@ 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 +}