From c03d403437c20584bcbf3cf3fa9d79ac7a0a8ca7 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 30 Jan 2015 13:25:12 +0100 Subject: [PATCH] Added whisper interface for xeth, added examples, updated RPC * Added RPC methods for whisper * Added whisper example --- cmd/mist/assets/examples/coin.html | 2 +- cmd/mist/assets/examples/whisper.html | 42 ++++++++++ cmd/mist/assets/qml/main.qml | 2 +- cmd/mist/assets/qml/views/browser.qml | 5 +- rpc/args.go | 9 ++ rpc/message.go | 44 ++++++++++ rpc/packages.go | 80 ++++++++++++++++-- ui/qt/qwhisper/whisper.go | 1 + xeth/whisper.go | 116 ++++++++++++++++++++++++++ xeth/xeth.go | 12 +-- 10 files changed, 296 insertions(+), 17 deletions(-) create mode 100644 cmd/mist/assets/examples/whisper.html create mode 100644 xeth/whisper.go diff --git a/cmd/mist/assets/examples/coin.html b/cmd/mist/assets/examples/coin.html index 572f6959dd..edeabe5e86 100644 --- a/cmd/mist/assets/examples/coin.html +++ b/cmd/mist/assets/examples/coin.html @@ -1,6 +1,6 @@ - +JevCoin diff --git a/cmd/mist/assets/examples/whisper.html b/cmd/mist/assets/examples/whisper.html new file mode 100644 index 0000000000..51d7004de4 --- /dev/null +++ b/cmd/mist/assets/examples/whisper.html @@ -0,0 +1,42 @@ + + +Whisper test + + + + + + +

Whisper test

+ + + + + + + + +
ID
+ + + + + + + diff --git a/cmd/mist/assets/qml/main.qml b/cmd/mist/assets/qml/main.qml index 1a8885e586..da1f4a0c6e 100644 --- a/cmd/mist/assets/qml/main.qml +++ b/cmd/mist/assets/qml/main.qml @@ -110,7 +110,7 @@ ApplicationWindow { function newBrowserTab(url) { var window = addPlugin("./views/browser.qml", {noAdd: true, close: true, section: "apps", active: true}); window.view.url = url; - window.menuItem.title = "Browser Tab"; + window.menuItem.title = "Mist"; activeView(window.view, window.menuItem); } diff --git a/cmd/mist/assets/qml/views/browser.qml b/cmd/mist/assets/qml/views/browser.qml index d6a7622784..277a5b7ebf 100644 --- a/cmd/mist/assets/qml/views/browser.qml +++ b/cmd/mist/assets/qml/views/browser.qml @@ -11,7 +11,7 @@ Rectangle { anchors.fill: parent color: "#00000000" - property var title: "DApps" + property var title: "" property var iconSource: "../browser.png" property var menuItem property var hideUrl: true @@ -154,6 +154,9 @@ Rectangle { onLoadingChanged: { if (loadRequest.status == WebEngineView.LoadSucceededStatus) { + webview.runJavaScript("document.title", function(pageTitle) { + menuItem.title = pageTitle; + }); webview.runJavaScript(eth.readFile("bignumber.min.js")); webview.runJavaScript(eth.readFile("ethereum.js/dist/ethereum.js")); } diff --git a/rpc/args.go b/rpc/args.go index aaa017c4ec..75eef873d5 100644 --- a/rpc/args.go +++ b/rpc/args.go @@ -251,3 +251,12 @@ func (a *DbArgs) requirements() error { } return nil } + +type WhisperMessageArgs struct { + Payload string + To string + From string + Topics []string + Priority uint32 + Ttl uint32 +} diff --git a/rpc/message.go b/rpc/message.go index 5045adb8fc..9193029219 100644 --- a/rpc/message.go +++ b/rpc/message.go @@ -21,6 +21,8 @@ import ( "encoding/json" "errors" "fmt" + + "github.com/ethereum/go-ethereum/xeth" ) const ( @@ -270,3 +272,45 @@ func (req *RpcRequest) ToDbGetArgs() (*DbArgs, error) { rpclogger.DebugDetailf("%T %v", args, args) return &args, nil } + +func (req *RpcRequest) ToWhisperFilterArgs() (*xeth.Options, error) { + if len(req.Params) < 1 { + return nil, NewErrorResponse(ErrorArguments) + } + + var args xeth.Options + err := json.Unmarshal(req.Params[0], &args) + if err != nil { + return nil, NewErrorResponseWithError(ErrorDecodeArgs, err) + } + rpclogger.DebugDetailf("%T %v", args, args) + return &args, nil +} + +func (req *RpcRequest) ToWhisperChangedArgs() (int, error) { + if len(req.Params) < 1 { + return 0, NewErrorResponse(ErrorArguments) + } + + var id int + err := json.Unmarshal(req.Params[0], &id) + if err != nil { + return 0, NewErrorResponse(ErrorDecodeArgs) + } + rpclogger.DebugDetailf("%T %v", id, id) + return id, nil +} + +func (req *RpcRequest) ToWhisperPostArgs() (*WhisperMessageArgs, error) { + if len(req.Params) < 1 { + return nil, NewErrorResponse(ErrorArguments) + } + + var args WhisperMessageArgs + err := json.Unmarshal(req.Params[0], &args) + if err != nil { + return nil, err + } + rpclogger.DebugDetailf("%T %v", args, args) + return &args, nil +} diff --git a/rpc/packages.go b/rpc/packages.go index aa51aad42e..8344d6a46f 100644 --- a/rpc/packages.go +++ b/rpc/packages.go @@ -44,18 +44,22 @@ type EthereumApi struct { xeth *xeth.XEth filterManager *filter.FilterManager - mut sync.RWMutex - logs map[int]state.Logs + logMut sync.RWMutex + logs map[int]state.Logs + + messagesMut sync.RWMutex + messages map[int][]xeth.WhisperMessage db ethutil.Database } -func NewEthereumApi(xeth *xeth.XEth) *EthereumApi { +func NewEthereumApi(eth *xeth.XEth) *EthereumApi { db, _ := ethdb.NewLDBDatabase("dapps") api := &EthereumApi{ - xeth: xeth, - filterManager: filter.NewFilterManager(xeth.Backend().EventMux()), + xeth: eth, + filterManager: filter.NewFilterManager(eth.Backend().EventMux()), logs: make(map[int]state.Logs), + messages: make(map[int][]xeth.WhisperMessage), db: db, } go api.filterManager.Start() @@ -67,8 +71,8 @@ func (self *EthereumApi) NewFilter(args *FilterOptions, reply *interface{}) erro var id int filter := core.NewFilter(self.xeth.Backend()) filter.LogsCallback = func(logs state.Logs) { - self.mut.Lock() - defer self.mut.Unlock() + self.logMut.Lock() + defer self.logMut.Unlock() self.logs[id] = append(self.logs[id], logs...) } @@ -79,8 +83,8 @@ func (self *EthereumApi) NewFilter(args *FilterOptions, reply *interface{}) erro } func (self *EthereumApi) FilterChanged(id int, reply *interface{}) error { - self.mut.RLock() - defer self.mut.RUnlock() + self.logMut.RLock() + defer self.logMut.RUnlock() *reply = toLogs(self.logs[id]) @@ -257,6 +261,44 @@ func (p *EthereumApi) DbGet(args *DbArgs, reply *interface{}) error { 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] = append(p.messages[id], msg) + } + id = p.xeth.Whisper().Watch(args) + *reply = id + return nil +} + +func (self *EthereumApi) MessagesChanged(id int, reply *interface{}) error { + self.messagesMut.RLock() + defer self.messagesMut.RUnlock() + + *reply = self.messages[id] + + self.messages[id] = nil // empty the messages + + 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) GetRequestReply(req *RpcRequest, reply *interface{}) error { // Spec at https://github.com/ethereum/wiki/wiki/Generic-ON-RPC rpclogger.DebugDetailf("%T %s", req.Params, req.Params) @@ -354,6 +396,26 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error 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.ToWhisperChangedArgs() + if err != nil { + return err + } + return p.MessagesChanged(args, reply) + case "shh_post": + args, err := req.ToWhisperPostArgs() + if err != nil { + return nil + } + return p.WhisperPost(args, reply) default: return NewErrorResponse(fmt.Sprintf("%v %s", ErrorNotImplemented, req.Method)) } diff --git a/ui/qt/qwhisper/whisper.go b/ui/qt/qwhisper/whisper.go index 22671f1b5d..98bfc69b0f 100644 --- a/ui/qt/qwhisper/whisper.go +++ b/ui/qt/qwhisper/whisper.go @@ -1,3 +1,4 @@ +// QWhisper package. This package is temporarily on hold until QML DApp dev will reemerge. package qwhisper import ( diff --git a/xeth/whisper.go b/xeth/whisper.go new file mode 100644 index 0000000000..31201271b9 --- /dev/null +++ b/xeth/whisper.go @@ -0,0 +1,116 @@ +package xeth + +import ( + "errors" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethutil" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/whisper" +) + +var qlogger = logger.NewLogger("XSHH") + +type Whisper struct { + *whisper.Whisper +} + +func NewWhisper(w *whisper.Whisper) *Whisper { + return &Whisper{w} +} + +func (self *Whisper) Post(payload string, to, from string, topics []string, priority, ttl uint32) error { + if priority == 0 { + priority = 1000 + } + + if ttl == 0 { + ttl = 100 + } + + pk := crypto.ToECDSAPub(fromHex(from)) + if key := self.Whisper.GetIdentity(pk); key != nil || len(from) == 0 { + msg := whisper.NewMessage(fromHex(payload)) + envelope, err := msg.Seal(time.Duration(priority*100000), whisper.Opts{ + Ttl: time.Duration(ttl) * time.Second, + To: crypto.ToECDSAPub(fromHex(to)), + From: key, + Topics: whisper.TopicsFromString(topics...), + }) + + if err != nil { + return err + } + + if err := self.Whisper.Send(envelope); err != nil { + return err + } + } else { + return errors.New("unmatched pub / priv for seal") + } + + return nil +} + +func (self *Whisper) NewIdentity() string { + key := self.Whisper.NewIdentity() + + return toHex(crypto.FromECDSAPub(&key.PublicKey)) +} + +func (self *Whisper) HasIdentity(key string) bool { + return self.Whisper.HasIdentity(crypto.ToECDSAPub(fromHex(key))) +} + +func (self *Whisper) Watch(opts *Options) int { + filter := whisper.Filter{ + To: crypto.ToECDSA(fromHex(opts.To)), + From: crypto.ToECDSAPub(fromHex(opts.From)), + Topics: whisper.TopicsFromString(opts.Topics...), + } + + var i int + filter.Fn = func(msg *whisper.Message) { + opts.Fn(NewWhisperMessage(msg)) + } + fmt.Println("new filter", filter) + + i = self.Whisper.Watch(filter) + + return i +} + +func (self *Whisper) Messages(id int) (messages []WhisperMessage) { + msgs := self.Whisper.Messages(id) + messages = make([]WhisperMessage, len(msgs)) + for i, message := range msgs { + messages[i] = NewWhisperMessage(message) + } + + return +} + +type Options struct { + To string + From string + Topics []string + Fn func(msg WhisperMessage) +} + +type WhisperMessage struct { + ref *whisper.Message + Flags int32 `json:"flags"` + Payload string `json:"payload"` + From string `json:"from"` +} + +func NewWhisperMessage(msg *whisper.Message) WhisperMessage { + return WhisperMessage{ + ref: msg, + Flags: int32(msg.Flags), + Payload: "0x" + ethutil.Bytes2Hex(msg.Payload), + From: "0x" + ethutil.Bytes2Hex(crypto.FromECDSAPub(msg.Recover())), + } +} diff --git a/xeth/xeth.go b/xeth/xeth.go index 4cfa104d0e..a65fa13315 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -16,6 +16,7 @@ import ( "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") @@ -33,6 +34,7 @@ type Backend interface { ClientIdentity() p2p.ClientIdentity Db() ethutil.Database EventMux() *event.TypeMux + Whisper() *whisper.Whisper } type XEth struct { @@ -40,6 +42,7 @@ type XEth struct { blockProcessor *core.BlockProcessor chainManager *core.ChainManager state *State + whisper *Whisper } func New(eth Backend) *XEth { @@ -47,17 +50,16 @@ func New(eth Backend) *XEth { eth: eth, blockProcessor: eth.BlockProcessor(), chainManager: eth.ChainManager(), + whisper: NewWhisper(eth.Whisper()), } xeth.state = NewState(xeth) return xeth } -func (self *XEth) Backend() Backend { - return self.eth -} - -func (self *XEth) State() *State { return self.state } +func (self *XEth) Backend() Backend { return self.eth } +func (self *XEth) State() *State { return self.state } +func (self *XEth) Whisper() *Whisper { return self.whisper } func (self *XEth) BlockByHash(strHash string) *Block { hash := fromHex(strHash)