diff options
Diffstat (limited to 'rpc')
-rw-r--r-- | rpc/api.go | 734 | ||||
-rw-r--r-- | rpc/api_test.go | 38 | ||||
-rw-r--r-- | rpc/args.go | 258 | ||||
-rw-r--r-- | rpc/http/server.go | 114 | ||||
-rw-r--r-- | rpc/messages.go | 419 | ||||
-rw-r--r-- | rpc/util.go | 139 | ||||
-rw-r--r-- | rpc/ws/server.go | 121 |
7 files changed, 1823 insertions, 0 deletions
diff --git a/rpc/api.go b/rpc/api.go new file mode 100644 index 000000000..28024c206 --- /dev/null +++ b/rpc/api.go @@ -0,0 +1,734 @@ +/* +For each request type, define the following: + +1. RpcRequest "To" method [message.go], which does basic validation and conversion to "Args" type via json.Decoder() +2. json.Decoder() calls "UnmarshalON" defined on each "Args" struct +3. EthereumApi method, taking the "Args" type and replying with an interface to be marshalled to ON + +*/ +package rpc + +import ( + "fmt" + "math/big" + "strings" + "sync" + "time" + + "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/ethutil" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/event/filter" + "github.com/ethereum/go-ethereum/state" + "github.com/ethereum/go-ethereum/ui" + "github.com/ethereum/go-ethereum/xeth" +) + +var ( + defaultGasPrice = big.NewInt(10000000000000) + defaultGas = big.NewInt(10000) + filterTickerTime = 15 * time.Second +) + +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 ethutil.Database + + defaultBlockAge int64 +} + +func NewEthereumApi(eth *xeth.XEth) *EthereumApi { + db, _ := ethdb.NewLDBDatabase("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, + defaultBlockAge: -1, + } + go api.filterManager.Start() + go api.start() + + return api +} + +func (self *EthereumApi) setStateByBlockNumber(num int64) { + chain := self.xeth().Backend().ChainManager() + var block *types.Block + + if self.defaultBlockAge < 0 { + num = chain.CurrentBlock().Number().Int64() + num + 1 + } + block = chain.GetBlockByNumber(uint64(num)) + + if block != nil { + self.useState(state.New(block.Root(), self.xeth().Backend().Db())) + } else { + self.useState(chain.State()) + } +} + +func (self *EthereumApi) start() { + timer := time.NewTicker(filterTickerTime) + events := self.mux.Subscribe(core.ChainEvent{}) + +done: + for { + select { + case ev := <-events.Chan(): + switch ev.(type) { + case core.ChainEvent: + if self.defaultBlockAge < 0 { + self.setStateByBlockNumber(self.defaultBlockAge) + } + } + case <-timer.C: + self.logMut.Lock() + self.messagesMut.Lock() + for id, filter := range self.logs { + if time.Since(filter.timeout) > 20*time.Second { + self.filterManager.UninstallFilter(id) + delete(self.logs, id) + } + } + + for id, filter := range self.messages { + if time.Since(filter.timeout) > 20*time.Second { + self.xeth().Whisper().Unwatch(id) + delete(self.messages, id) + } + } + self.logMut.Unlock() + self.messagesMut.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 = id + + return nil +} + +func (self *EthereumApi) UninstallFilter(id int, reply *interface{}) error { + delete(self.logs, id) + self.filterManager.UninstallFilter(id) + *reply = true + return nil +} + +func (self *EthereumApi) NewFilterString(args string, reply *interface{}) error { + var id int + filter := core.NewFilter(self.xeth().Backend()) + + callback := func(block *types.Block) { + self.logMut.Lock() + defer self.logMut.Unlock() + + self.logs[id].add(&state.StateLog{}) + } + if args == "pending" { + filter.PendingCallback = callback + } else if args == "chain" { + filter.BlockCallback = callback + } + + id = self.filterManager.InstallFilter(filter) + self.logs[id] = &logFilter{timeout: time.Now()} + *reply = id + + 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) GetBlock(args *GetBlockArgs, reply *interface{}) error { + // This seems a bit precarious Maybe worth splitting to discrete functions + if len(args.Hash) > 0 { + *reply = p.xeth().BlockByHash(args.Hash) + } else { + *reply = p.xeth().BlockByNumber(args.BlockNumber) + } + return nil +} + +func (p *EthereumApi) Transact(args *NewTxArgs, reply *interface{}) error { + if len(args.Gas) == 0 { + args.Gas = defaultGas.String() + } + + if len(args.GasPrice) == 0 { + args.GasPrice = defaultGasPrice.String() + } + + // 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(fromHex(args.From)) + if account != nil { + if account.Unlocked() { + if !unlockAccount(account) { + return + } + } + + result, _ := account.Transact(fromHex(args.To), fromHex(args.Value), fromHex(args.Gas), fromHex(args.GasPrice), fromHex(args.Data)) + if len(result) > 0 { + *reply = toHex(result) + } + } else if _, exists := p.register[args.From]; exists { + p.register[ags.From] = append(p.register[args.From], args) + } + */ + result, _ := p.xeth().Transact( /* TODO specify account */ args.To, args.Value, args.Gas, args.GasPrice, args.Data) + *reply = result + //} + + return nil +} + +func (p *EthereumApi) Call(args *NewTxArgs, reply *interface{}) error { + result, err := p.xeth().Call( /* TODO specify account */ args.To, args.Value, args.Gas, args.GasPrice, args.Data) + if err != nil { + return err + } + + *reply = result + return nil +} + +func (p *EthereumApi) PushTx(args *PushTxArgs, reply *interface{}) error { + err := args.requirementsPushTx() + if err != nil { + return err + } + result, _ := p.xeth().PushTx(args.Tx) + *reply = result + return nil +} + +func (p *EthereumApi) GetStateAt(args *GetStateArgs, reply *interface{}) error { + err := args.requirements() + if err != nil { + return err + } + + state := p.xeth().State().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 = ethutil.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) GetStorageAt(args *GetStorageArgs, reply *interface{}) error { + err := args.requirements() + if err != nil { + return err + } + + *reply = p.xeth().State().SafeGet(args.Address).Storage() + return nil +} + +func (p *EthereumApi) GetPeerCount(reply *interface{}) error { + *reply = p.xeth().PeerCount() + return nil +} + +func (p *EthereumApi) GetIsListening(reply *interface{}) error { + *reply = p.xeth().IsListening() + return nil +} + +func (p *EthereumApi) GetCoinbase(reply *interface{}) error { + *reply = p.xeth().Coinbase() + return nil +} + +func (p *EthereumApi) Accounts(reply *interface{}) error { + *reply = p.xeth().Accounts() + return nil +} + +func (p *EthereumApi) GetIsMining(reply *interface{}) error { + *reply = p.xeth().IsMining() + return nil +} + +func (p *EthereumApi) SetMining(shouldmine bool, reply *interface{}) error { + *reply = p.xeth().SetMining(shouldmine) + return nil +} + +func (p *EthereumApi) GetDefaultBlockAge(reply *interface{}) error { + *reply = p.defaultBlockAge + return nil +} + +func (p *EthereumApi) SetDefaultBlockAge(defaultBlockAge int64, reply *interface{}) error { + p.defaultBlockAge = defaultBlockAge + p.setStateByBlockNumber(p.defaultBlockAge) + + *reply = true + return nil +} + +func (p *EthereumApi) BlockNumber(reply *interface{}) error { + *reply = p.xeth().Backend().ChainManager().CurrentBlock().Number() + return nil +} + +func (p *EthereumApi) GetTxCountAt(args *GetTxCountArgs, reply *interface{}) error { + err := args.requirements() + if err != nil { + return err + } + *reply = p.xeth().TxCountAt(args.Address) + return nil +} + +func (p *EthereumApi) GetBalanceAt(args *GetBalanceArgs, reply *interface{}) error { + err := args.requirements() + if err != nil { + return err + } + state := p.xeth().State().SafeGet(args.Address) + *reply = toHex(state.Balance().Bytes()) + return nil +} + +func (p *EthereumApi) GetCodeAt(args *GetCodeAtArgs, reply *interface{}) error { + err := args.requirements() + if err != nil { + return err + } + *reply = p.xeth().CodeAt(args.Address) + return nil +} + +func (p *EthereumApi) GetCompilers(reply *interface{}) error { + c := []string{"serpent"} + *reply = c + return nil +} + +func (p *EthereumApi) CompileSerpent(script string, reply *interface{}) error { + res, err := ethutil.Compile(script, false) + if err != nil { + return err + } + *reply = res + return nil +} + +func (p *EthereumApi) Sha3(args *Sha3Args, reply *interface{}) error { + *reply = toHex(crypto.Sha3(fromHex(args.Data))) + return nil +} + +func (p *EthereumApi) DbPut(args *DbArgs, reply *interface{}) error { + err := args.requirements() + if 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 { + err := args.requirements() + if 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) NewWhisperFilter(args *xeth.Options, reply *interface{}) error { + var id int + args.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(args) + p.messages[id] = &whisperFilter{timeout: time.Now()} + *reply = id + 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.Topic, 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) GetRequestReply(req *RpcRequest, reply *interface{}) error { + // Spec at https://github.com/ethereum/wiki/wiki/Generic-JSON-RPC + rpclogger.DebugDetailf("%T %s", req.Params, req.Params) + switch req.Method { + case "eth_coinbase": + return p.GetCoinbase(reply) + case "eth_listening": + return p.GetIsListening(reply) + case "eth_mining": + return p.GetIsMining(reply) + case "eth_setMining": + args, err := req.ToBoolArgs() + if err != nil { + return err + } + return p.SetMining(args, reply) + case "eth_defaultBlock": + return p.GetDefaultBlockAge(reply) + case "eth_setDefaultBlock": + args, err := req.ToIntArgs() + if err != nil { + return err + } + return p.SetDefaultBlockAge(int64(args), reply) + case "eth_peerCount": + return p.GetPeerCount(reply) + case "eth_number": + return p.BlockNumber(reply) + case "eth_accounts": + return p.Accounts(reply) + case "eth_countAt": + args, err := req.ToGetTxCountArgs() + if err != nil { + return err + } + return p.GetTxCountAt(args, reply) + case "eth_codeAt": + args, err := req.ToGetCodeAtArgs() + if err != nil { + return err + } + return p.GetCodeAt(args, reply) + case "eth_balanceAt": + args, err := req.ToGetBalanceArgs() + if err != nil { + return err + } + return p.GetBalanceAt(args, reply) + case "eth_stateAt": + args, err := req.ToGetStateArgs() + if err != nil { + return err + } + return p.GetStateAt(args, reply) + case "eth_storageAt": + args, err := req.ToStorageAtArgs() + if err != nil { + return err + } + return p.GetStorageAt(args, reply) + case "eth_blockByNumber", "eth_blockByHash": + args, err := req.ToGetBlockArgs() + if err != nil { + return err + } + return p.GetBlock(args, reply) + case "eth_transact": + args, err := req.ToNewTxArgs() + if err != nil { + return err + } + return p.Transact(args, reply) + case "eth_call": + args, err := req.ToNewTxArgs() + if err != nil { + return err + } + return p.Call(args, reply) + case "eth_newFilter": + args, err := req.ToFilterArgs() + if err != nil { + return err + } + return p.NewFilter(args, reply) + case "eth_newFilterString": + args, err := req.ToFilterStringArgs() + if err != nil { + return err + } + return p.NewFilterString(args, reply) + case "eth_uninstallFilter": + args, err := req.ToUninstallFilterArgs() + if err != nil { + return err + } + return p.UninstallFilter(args, reply) + case "eth_changed": + args, err := req.ToIdArgs() + if err != nil { + return err + } + return p.FilterChanged(args, reply) + case "eth_filterLogs": + args, err := req.ToIdArgs() + if err != nil { + return err + } + return p.Logs(args, reply) + case "eth_logs": + args, err := req.ToFilterArgs() + if err != nil { + return err + } + return p.AllLogs(args, reply) + case "eth_gasPrice": + *reply = toHex(defaultGasPrice.Bytes()) + return nil + case "eth_register": + args, err := req.ToRegisterArgs() + if err != nil { + return err + } + return p.Register(args, reply) + case "eth_unregister": + args, err := req.ToRegisterArgs() + if err != nil { + return err + } + return p.Unregister(args, reply) + case "eth_watchTx": + args, err := req.ToWatchTxArgs() + if err != nil { + return err + } + return p.WatchTx(args, reply) + case "eth_compilers": + return p.GetCompilers(reply) + case "eth_serpent": + args, err := req.ToCompileArgs() + if err != nil { + return err + } + return p.CompileSerpent(args, reply) + case "web3_sha3": + args, err := req.ToSha3Args() + if err != nil { + return err + } + return p.Sha3(args, reply) + case "db_put": + args, err := req.ToDbPutArgs() + if err != nil { + return err + } + return p.DbPut(args, reply) + case "db_get": + args, err := req.ToDbGetArgs() + if err != nil { + return err + } + return p.DbGet(args, reply) + case "shh_newIdentity": + return p.NewWhisperIdentity(reply) + case "shh_newFilter": + args, err := req.ToWhisperFilterArgs() + if err != nil { + return err + } + return p.NewWhisperFilter(args, reply) + case "shh_changed": + args, err := req.ToIdArgs() + if err != nil { + return err + } + return p.MessagesChanged(args, reply) + case "shh_post": + args, err := req.ToWhisperPostArgs() + if err != nil { + return err + } + return p.WhisperPost(args, reply) + case "shh_haveIdentity": + args, err := req.ToWhisperHasIdentityArgs() + if err != nil { + return err + } + return p.HasWhisperIdentity(args, reply) + case "shh_getMessages": + args, err := req.ToIdArgs() + if err != nil { + return err + } + return p.WhisperMessages(args, reply) + default: + return NewErrorWithMessage(errNotImplemented, req.Method) + } + + rpclogger.DebugDetailf("Reply: %T %s", reply, reply) + return nil +} + +func (self *EthereumApi) xeth() *xeth.XEth { + self.xethMu.RLock() + defer self.xethMu.RUnlock() + + return self.eth +} + +func (self *EthereumApi) useState(statedb *state.StateDB) { + self.xethMu.Lock() + defer self.xethMu.Unlock() + + self.eth = self.eth.UseState(statedb) +} + +func t(f ui.Frontend) { + // Call the password dialog + ret, err := f.Call("PasswordDialog") + if err != nil { + fmt.Println(err) + } + // Get the first argument + t, _ := ret.Get(0) + fmt.Println("return:", t) +} diff --git a/rpc/api_test.go b/rpc/api_test.go new file mode 100644 index 000000000..a9fc16cd3 --- /dev/null +++ b/rpc/api_test.go @@ -0,0 +1,38 @@ +package rpc + +import ( + "sync" + "testing" + "time" +) + +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") + } + + if len(api.messages) != 0 { + t.Error("expected messages to be empty") + } +} diff --git a/rpc/args.go b/rpc/args.go new file mode 100644 index 000000000..e839da8bf --- /dev/null +++ b/rpc/args.go @@ -0,0 +1,258 @@ +package rpc + +import "encoding/json" + +import "github.com/ethereum/go-ethereum/core" + +type GetBlockArgs struct { + BlockNumber int32 + Hash string +} + +func (obj *GetBlockArgs) UnmarshalJSON(b []byte) (err error) { + argint, argstr := int32(0), "" + if err = json.Unmarshal(b, &argint); err == nil { + obj.BlockNumber = argint + return + } + if err = json.Unmarshal(b, &argstr); err == nil { + obj.Hash = argstr + return + } + return errDecodeArgs +} + +type NewTxArgs struct { + From string `json:"from"` + To string `json:"to"` + Value string `json:"value"` + Gas string `json:"gas"` + GasPrice string `json:"gasPrice"` + Data string `json:"data"` +} + +func (obj *NewTxArgs) UnmarshalJSON(b []byte) (err error) { + // Data can be either specified as "data" or "code" :-/ + var ext struct { + From string + To string + Value string + Gas string + GasPrice string + Data string + Code string + } + + if err = json.Unmarshal(b, &ext); err == nil { + if len(ext.Data) == 0 { + ext.Data = ext.Code + } + obj.From = ext.From + obj.To = ext.To + obj.Value = ext.Value + obj.Gas = ext.Gas + obj.GasPrice = ext.GasPrice + obj.Data = ext.Data + + return + } + + return errDecodeArgs +} + +type PushTxArgs struct { + Tx string `json:"tx"` +} + +func (obj *PushTxArgs) UnmarshalJSON(b []byte) (err error) { + arg0 := "" + if err = json.Unmarshal(b, &arg0); err == nil { + obj.Tx = arg0 + return + } + return errDecodeArgs +} + +func (a *PushTxArgs) requirementsPushTx() error { + if a.Tx == "" { + return NewErrorWithMessage(errArguments, "PushTx requires a 'tx' as argument") + } + return nil +} + +type GetStorageArgs struct { + Address string +} + +func (obj *GetStorageArgs) UnmarshalJSON(b []byte) (err error) { + if err = json.Unmarshal(b, &obj.Address); err != nil { + return errDecodeArgs + } + return +} + +func (a *GetStorageArgs) requirements() error { + if len(a.Address) == 0 { + return NewErrorWithMessage(errArguments, "GetStorageAt requires an 'address' value as argument") + } + return nil +} + +type GetStateArgs struct { + Address string + Key string +} + +func (obj *GetStateArgs) UnmarshalJSON(b []byte) (err error) { + arg0 := "" + if err = json.Unmarshal(b, &arg0); err == nil { + obj.Address = arg0 + return + } + return errDecodeArgs +} + +func (a *GetStateArgs) requirements() error { + if a.Address == "" { + return NewErrorWithMessage(errArguments, "GetStorageAt requires an 'address' value as argument") + } + if a.Key == "" { + return NewErrorWithMessage(errArguments, "GetStorageAt requires an 'key' value as argument") + } + return nil +} + +type GetTxCountArgs struct { + Address string `json:"address"` +} + +func (obj *GetTxCountArgs) UnmarshalJSON(b []byte) (err error) { + arg0 := "" + if err = json.Unmarshal(b, &arg0); err == nil { + obj.Address = arg0 + return + } + return errDecodeArgs +} + +func (a *GetTxCountArgs) requirements() error { + if a.Address == "" { + return NewErrorWithMessage(errArguments, "GetTxCountAt requires an 'address' value as argument") + } + return nil +} + +type GetBalanceArgs struct { + Address string +} + +func (obj *GetBalanceArgs) UnmarshalJSON(b []byte) (err error) { + arg0 := "" + if err = json.Unmarshal(b, &arg0); err == nil { + obj.Address = arg0 + return + } + return errDecodeArgs +} + +func (a *GetBalanceArgs) requirements() error { + if a.Address == "" { + return NewErrorWithMessage(errArguments, "GetBalanceAt requires an 'address' value as argument") + } + return nil +} + +type GetCodeAtArgs struct { + Address string +} + +func (obj *GetCodeAtArgs) UnmarshalJSON(b []byte) (err error) { + arg0 := "" + if err = json.Unmarshal(b, &arg0); err == nil { + obj.Address = arg0 + return + } + return errDecodeArgs +} + +func (a *GetCodeAtArgs) requirements() error { + if a.Address == "" { + return NewErrorWithMessage(errArguments, "GetCodeAt requires an 'address' value as argument") + } + return nil +} + +type Sha3Args struct { + Data string +} + +func (obj *Sha3Args) UnmarshalJSON(b []byte) (err error) { + if err = json.Unmarshal(b, &obj.Data); err != nil { + return errDecodeArgs + } + return +} + +type FilterOptions struct { + Earliest int64 + Latest int64 + Address interface{} + Topic []string + Skip int + Max int +} + +func toFilterOptions(options *FilterOptions) core.FilterOptions { + var opts core.FilterOptions + + // Convert optional address slice/string to byte slice + if str, ok := options.Address.(string); ok { + opts.Address = [][]byte{fromHex(str)} + } else if slice, ok := options.Address.([]interface{}); ok { + bslice := make([][]byte, len(slice)) + for i, addr := range slice { + if saddr, ok := addr.(string); ok { + bslice[i] = fromHex(saddr) + } + } + opts.Address = bslice + } + + opts.Earliest = options.Earliest + opts.Latest = options.Latest + opts.Topics = make([][]byte, len(options.Topic)) + for i, topic := range options.Topic { + opts.Topics[i] = fromHex(topic) + } + + return opts +} + +type FilterChangedArgs struct { + n int +} + +type DbArgs struct { + Database string + Key string + Value string +} + +func (a *DbArgs) requirements() error { + if len(a.Database) == 0 { + return NewErrorWithMessage(errArguments, "DbPutArgs requires an 'Database' value as argument") + } + if len(a.Key) == 0 { + return NewErrorWithMessage(errArguments, "DbPutArgs requires an 'Key' value as argument") + } + return nil +} + +type WhisperMessageArgs struct { + Payload string + To string + From string + Topic []string + Priority uint32 + Ttl uint32 +} diff --git a/rpc/http/server.go b/rpc/http/server.go new file mode 100644 index 000000000..fa66eed48 --- /dev/null +++ b/rpc/http/server.go @@ -0,0 +1,114 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +package rpchttp + +import ( + "fmt" + "net" + "net/http" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/xeth" +) + +var rpchttplogger = logger.NewLogger("RPC-HTTP") +var JSON rpc.JsonWrapper + +func NewRpcHttpServer(pipe *xeth.XEth, port int) (*RpcHttpServer, error) { + sport := fmt.Sprintf("127.0.0.1:%d", port) + l, err := net.Listen("tcp", sport) + if err != nil { + return nil, err + } + + return &RpcHttpServer{ + listener: l, + quit: make(chan bool), + pipe: pipe, + port: port, + }, nil +} + +type RpcHttpServer struct { + quit chan bool + listener net.Listener + pipe *xeth.XEth + port int +} + +func (s *RpcHttpServer) exitHandler() { +out: + for { + select { + case <-s.quit: + s.listener.Close() + break out + } + } + + rpchttplogger.Infoln("Shutdown RPC-HTTP server") +} + +func (s *RpcHttpServer) Stop() { + close(s.quit) +} + +func (s *RpcHttpServer) Start() { + rpchttplogger.Infof("Starting RPC-HTTP server on port %d", s.port) + go s.exitHandler() + + api := rpc.NewEthereumApi(s.pipe) + h := s.apiHandler(api) + http.Handle("/", h) + + err := http.Serve(s.listener, nil) + // FIX Complains on shutdown due to listner already being closed + if err != nil { + rpchttplogger.Errorln("Error on RPC-HTTP interface:", err) + } +} + +func (s *RpcHttpServer) apiHandler(api *rpc.EthereumApi) http.Handler { + var jsonrpcver string = "2.0" + fn := func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + + rpchttplogger.DebugDetailln("Handling request") + + reqParsed, reqerr := JSON.ParseRequestBody(req) + if reqerr != nil { + jsonerr := &rpc.RpcErrorObject{-32700, "Error: Could not parse request"} + JSON.Send(w, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) + return + } + + var response interface{} + reserr := api.GetRequestReply(&reqParsed, &response) + if reserr != nil { + rpchttplogger.Warnln(reserr) + jsonerr := &rpc.RpcErrorObject{-32603, reserr.Error()} + JSON.Send(w, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Error: jsonerr}) + return + } + + rpchttplogger.DebugDetailf("Generated response: %T %s", response, response) + JSON.Send(w, &rpc.RpcSuccessResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Result: response}) + } + + return http.HandlerFunc(fn) +} diff --git a/rpc/messages.go b/rpc/messages.go new file mode 100644 index 000000000..b37d8229d --- /dev/null +++ b/rpc/messages.go @@ -0,0 +1,419 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +package rpc + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/xeth" +) + +var ( + errArguments = errors.New("Error: Insufficient arguments") + errNotImplemented = errors.New("Error: Method not implemented") + errUnknown = errors.New("Error: Unknown error") + errDecodeArgs = errors.New("Error: Could not decode arguments") +) + +type RpcRequest struct { + ID interface{} `json:"id"` + JsonRpc string `json:"jsonrpc"` + Method string `json:"method"` + Params []json.RawMessage `json:"params"` +} + +type RpcSuccessResponse struct { + ID interface{} `json:"id"` + JsonRpc string `json:"jsonrpc"` + Result interface{} `json:"result"` +} + +type RpcErrorResponse struct { + ID interface{} `json:"id"` + JsonRpc string `json:"jsonrpc"` + Error *RpcErrorObject `json:"error"` +} + +type RpcErrorObject struct { + Code int `json:"code"` + Message string `json:"message"` + // Data interface{} `json:"data"` +} + +func NewErrorWithMessage(err error, msg string) error { + return fmt.Errorf("%s: %s", err.Error(), msg) +} + +func (req *RpcRequest) ToSha3Args() (*Sha3Args, error) { + if len(req.Params) < 1 { + return nil, errArguments + } + + args := new(Sha3Args) + r := bytes.NewReader(req.Params[0]) + if err := json.NewDecoder(r).Decode(args); err != nil { + return nil, errDecodeArgs + } + + return args, nil +} + +func (req *RpcRequest) ToGetBlockArgs() (*GetBlockArgs, error) { + if len(req.Params) < 1 { + return nil, errArguments + } + + args := new(GetBlockArgs) + r := bytes.NewReader(req.Params[0]) + err := json.NewDecoder(r).Decode(args) + if err != nil { + return nil, errDecodeArgs + } + + return args, nil +} + +func (req *RpcRequest) ToNewTxArgs() (*NewTxArgs, error) { + if len(req.Params) < 1 { + return nil, errArguments + } + + args := new(NewTxArgs) + r := bytes.NewReader(req.Params[0]) + err := json.NewDecoder(r).Decode(args) + if err != nil { + return nil, NewErrorWithMessage(errDecodeArgs, err.Error()) + } + + return args, nil +} + +func (req *RpcRequest) ToPushTxArgs() (*PushTxArgs, error) { + if len(req.Params) < 1 { + return nil, errArguments + } + + args := new(PushTxArgs) + r := bytes.NewReader(req.Params[0]) + err := json.NewDecoder(r).Decode(args) + if err != nil { + return nil, errDecodeArgs + } + + return args, nil +} + +func (req *RpcRequest) ToGetStateArgs() (*GetStateArgs, error) { + if len(req.Params) < 1 { + return nil, errArguments + } + + args := new(GetStateArgs) + // TODO need to pass both arguments + r := bytes.NewReader(req.Params[0]) + err := json.NewDecoder(r).Decode(args) + if err != nil { + return nil, errDecodeArgs + } + + return args, nil +} + +func (req *RpcRequest) ToStorageAtArgs() (*GetStorageArgs, error) { + if len(req.Params) < 1 { + return nil, errArguments + } + + args := new(GetStorageArgs) + r := bytes.NewReader(req.Params[0]) + err := json.NewDecoder(r).Decode(args) + if err != nil { + return nil, errDecodeArgs + } + + return args, nil +} + +func (req *RpcRequest) ToGetTxCountArgs() (*GetTxCountArgs, error) { + if len(req.Params) < 1 { + return nil, errArguments + } + + args := new(GetTxCountArgs) + r := bytes.NewReader(req.Params[0]) + err := json.NewDecoder(r).Decode(args) + if err != nil { + return nil, errDecodeArgs + } + + return args, nil +} + +func (req *RpcRequest) ToGetBalanceArgs() (*GetBalanceArgs, error) { + if len(req.Params) < 1 { + return nil, errArguments + } + + args := new(GetBalanceArgs) + r := bytes.NewReader(req.Params[0]) + err := json.NewDecoder(r).Decode(args) + if err != nil { + return nil, errDecodeArgs + } + + return args, nil +} + +func (req *RpcRequest) ToGetCodeAtArgs() (*GetCodeAtArgs, error) { + if len(req.Params) < 1 { + return nil, errArguments + } + + args := new(GetCodeAtArgs) + r := bytes.NewReader(req.Params[0]) + err := json.NewDecoder(r).Decode(args) + if err != nil { + return nil, errDecodeArgs + } + + return args, nil +} + +func (req *RpcRequest) ToBoolArgs() (bool, error) { + if len(req.Params) < 1 { + return false, errArguments + } + + var args bool + err := json.Unmarshal(req.Params[0], &args) + if err != nil { + return false, errDecodeArgs + } + + return args, nil +} + +func (req *RpcRequest) ToIntArgs() (int, error) { + if len(req.Params) < 1 { + return 0, errArguments + } + + var args int + if err := json.Unmarshal(req.Params[0], &args); err != nil { + return 0, errArguments + } + + return args, nil +} + +func (req *RpcRequest) ToCompileArgs() (string, error) { + if len(req.Params) < 1 { + return "", errArguments + } + + var args string + err := json.Unmarshal(req.Params[0], &args) + if err != nil { + return "", errDecodeArgs + } + + return args, nil +} + +func (req *RpcRequest) ToFilterArgs() (*FilterOptions, error) { + if len(req.Params) < 1 { + return nil, errArguments + } + + args := new(FilterOptions) + r := bytes.NewReader(req.Params[0]) + err := json.NewDecoder(r).Decode(args) + if err != nil { + return nil, errDecodeArgs + } + return args, nil +} + +func (req *RpcRequest) ToFilterStringArgs() (string, error) { + if len(req.Params) < 1 { + return "", errArguments + } + + var args string + err := json.Unmarshal(req.Params[0], &args) + if err != nil { + return "", errDecodeArgs + } + + return args, nil +} + +func (req *RpcRequest) ToUninstallFilterArgs() (int, error) { + if len(req.Params) < 1 { + return 0, errArguments + } + + var args int + err := json.Unmarshal(req.Params[0], &args) + if err != nil { + return 0, errDecodeArgs + } + + return args, nil +} + +func (req *RpcRequest) ToFilterChangedArgs() (int, error) { + if len(req.Params) < 1 { + return 0, errArguments + } + + var id int + r := bytes.NewReader(req.Params[0]) + err := json.NewDecoder(r).Decode(&id) + if err != nil { + return 0, errDecodeArgs + } + return id, nil +} + +func (req *RpcRequest) ToDbPutArgs() (*DbArgs, error) { + if len(req.Params) < 3 { + return nil, errArguments + } + + var args DbArgs + err := json.Unmarshal(req.Params[0], &args.Database) + if err != nil { + return nil, NewErrorWithMessage(errDecodeArgs, err.Error()) + } + err = json.Unmarshal(req.Params[1], &args.Key) + if err != nil { + return nil, NewErrorWithMessage(errDecodeArgs, err.Error()) + } + err = json.Unmarshal(req.Params[2], &args.Value) + if err != nil { + return nil, NewErrorWithMessage(errDecodeArgs, err.Error()) + } + + return &args, nil +} + +func (req *RpcRequest) ToDbGetArgs() (*DbArgs, error) { + if len(req.Params) < 2 { + return nil, errArguments + } + + var args DbArgs + err := json.Unmarshal(req.Params[0], &args.Database) + if err != nil { + return nil, NewErrorWithMessage(errDecodeArgs, err.Error()) + } + + err = json.Unmarshal(req.Params[1], &args.Key) + if err != nil { + return nil, NewErrorWithMessage(errDecodeArgs, err.Error()) + } + + return &args, nil +} + +func (req *RpcRequest) ToWhisperFilterArgs() (*xeth.Options, error) { + if len(req.Params) < 1 { + return nil, errArguments + } + + var args xeth.Options + err := json.Unmarshal(req.Params[0], &args) + if err != nil { + return nil, NewErrorWithMessage(errDecodeArgs, err.Error()) + } + + return &args, nil +} + +func (req *RpcRequest) ToIdArgs() (int, error) { + if len(req.Params) < 1 { + return 0, errArguments + } + + var id int + err := json.Unmarshal(req.Params[0], &id) + if err != nil { + return 0, errDecodeArgs + } + + return id, nil +} + +func (req *RpcRequest) ToWhisperPostArgs() (*WhisperMessageArgs, error) { + if len(req.Params) < 1 { + return nil, errArguments + } + + var args WhisperMessageArgs + err := json.Unmarshal(req.Params[0], &args) + if err != nil { + return nil, err + } + + return &args, nil +} + +func (req *RpcRequest) ToWhisperHasIdentityArgs() (string, error) { + if len(req.Params) < 1 { + return "", errArguments + } + + var args string + err := json.Unmarshal(req.Params[0], &args) + if err != nil { + return "", err + } + + return args, nil +} + +func (req *RpcRequest) ToRegisterArgs() (string, error) { + if len(req.Params) < 1 { + return "", errArguments + } + + var args string + err := json.Unmarshal(req.Params[0], &args) + if err != nil { + return "", err + } + + return args, nil +} + +func (req *RpcRequest) ToWatchTxArgs() (string, error) { + if len(req.Params) < 1 { + return "", errArguments + } + + var args string + err := json.Unmarshal(req.Params[0], &args) + if err != nil { + return "", err + } + + return args, nil +} diff --git a/rpc/util.go b/rpc/util.go new file mode 100644 index 000000000..3e8ca3fef --- /dev/null +++ b/rpc/util.go @@ -0,0 +1,139 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +package rpc + +import ( + "encoding/json" + "io" + "net/http" + "time" + + "github.com/ethereum/go-ethereum/ethutil" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/state" + "github.com/ethereum/go-ethereum/xeth" +) + +var rpclogger = logger.NewLogger("RPC") + +type JsonWrapper struct{} + +func (self JsonWrapper) Send(writer io.Writer, v interface{}) (n int, err error) { + var payload []byte + payload, err = json.Marshal(v) + if err != nil { + rpclogger.Fatalln("Error marshalling JSON", err) + return 0, err + } + rpclogger.DebugDetailf("Sending payload: %s", payload) + + return writer.Write(payload) +} + +func (self JsonWrapper) ParseRequestBody(req *http.Request) (RpcRequest, error) { + var reqParsed RpcRequest + + // Convert JSON to native types + d := json.NewDecoder(req.Body) + defer req.Body.Close() + err := d.Decode(&reqParsed) + + if err != nil { + rpclogger.Errorln("Error decoding JSON: ", err) + return reqParsed, err + } + + rpclogger.DebugDetailf("Parsed request: %s", reqParsed) + + return reqParsed, nil +} + +func toHex(b []byte) string { + return "0x" + ethutil.Bytes2Hex(b) +} +func fromHex(s string) []byte { + if len(s) > 1 { + if s[0:2] == "0x" { + s = s[2:] + } + return ethutil.Hex2Bytes(s) + } + return nil +} + +type RpcServer interface { + Start() + Stop() +} + +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 = toHex(log.Address()) + l.Data = toHex(log.Data()) + l.Number = log.Number() + for j, topic := range log.Topics() { + l.Topic[j] = 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/rpc/ws/server.go b/rpc/ws/server.go new file mode 100644 index 000000000..2c2988f5d --- /dev/null +++ b/rpc/ws/server.go @@ -0,0 +1,121 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +package rpcws + +import ( + "fmt" + "net" + "net/http" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/xeth" + "golang.org/x/net/websocket" +) + +var wslogger = logger.NewLogger("RPC-WS") +var JSON rpc.JsonWrapper + +type WebSocketServer struct { + pipe *xeth.XEth + port int + doneCh chan bool + listener net.Listener +} + +func NewWebSocketServer(pipe *xeth.XEth, port int) (*WebSocketServer, error) { + sport := fmt.Sprintf(":%d", port) + l, err := net.Listen("tcp", sport) + if err != nil { + return nil, err + } + + return &WebSocketServer{ + pipe, + port, + make(chan bool), + l, + }, nil +} + +func (self *WebSocketServer) handlerLoop() { + for { + select { + case <-self.doneCh: + wslogger.Infoln("Shutdown RPC-WS server") + return + } + } +} + +func (self *WebSocketServer) Stop() { + close(self.doneCh) +} + +func (self *WebSocketServer) Start() { + wslogger.Infof("Starting RPC-WS server on port %d", self.port) + go self.handlerLoop() + + api := rpc.NewEthereumApi(self.pipe) + h := self.apiHandler(api) + http.Handle("/ws", h) + + err := http.Serve(self.listener, nil) + if err != nil { + wslogger.Errorln("Error on RPC-WS interface:", err) + } +} + +func (s *WebSocketServer) apiHandler(api *rpc.EthereumApi) http.Handler { + fn := func(w http.ResponseWriter, req *http.Request) { + h := sockHandler(api) + s := websocket.Server{Handler: h} + s.ServeHTTP(w, req) + } + + return http.HandlerFunc(fn) +} + +func sockHandler(api *rpc.EthereumApi) websocket.Handler { + var jsonrpcver string = "2.0" + fn := func(conn *websocket.Conn) { + for { + wslogger.Debugln("Handling connection") + var reqParsed rpc.RpcRequest + + // reqParsed, reqerr := JSON.ParseRequestBody(conn.Request()) + if err := websocket.JSON.Receive(conn, &reqParsed); err != nil { + jsonerr := &rpc.RpcErrorObject{-32700, "Error: Could not parse request"} + JSON.Send(conn, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) + continue + } + + var response interface{} + reserr := api.GetRequestReply(&reqParsed, &response) + if reserr != nil { + wslogger.Warnln(reserr) + jsonerr := &rpc.RpcErrorObject{-32603, reserr.Error()} + JSON.Send(conn, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Error: jsonerr}) + continue + } + + wslogger.Debugf("Generated response: %T %s", response, response) + JSON.Send(conn, &rpc.RpcSuccessResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Result: response}) + } + } + return websocket.Handler(fn) +} |