Added whisper interface for xeth, added examples, updated RPC

* Added RPC methods for whisper
* Added whisper example
This commit is contained in:
obscuren 2015-01-30 13:25:12 +01:00
parent c48644490f
commit c03d403437
10 changed files with 296 additions and 17 deletions

@ -1,6 +1,6 @@
<!doctype>
<html>
<title>JevCoin</title>
<head>
<script type="text/javascript" src="../ext/bignumber.min.js"></script>
<script type="text/javascript" src="../ext/ethereum.js/dist/ethereum.js"></script>

@ -0,0 +1,42 @@
<!doctype>
<html>
<title>Whisper test</title>
<head>
<script type="text/javascript" src="../ext/bignumber.min.js"></script>
<script type="text/javascript" src="../ext/ethereum.js/dist/ethereum.js"></script>
</head>
<body>
<h1>Whisper test</h1>
<button onclick="test()">Send</button>
<table width="100%" id="table">
<tr>
<td>ID</td>
<td id="id"></td>
</tr>
</table>
</body>
<script type="text/javascript">
var web3 = require('web3');
web3.setProvider(new web3.providers.HttpSyncProvider('http://localhost:8080'));
var shh = web3.shh;
var id = shh.newIdentity();
document.querySelector("#id").innerHTML = id;
shh.watch({topics: ["test"]}).arrived(function(message) {
document.querySelector("#table").innerHTML += "<tr><td colspan='2'>"+JSON.stringify(message)+"</td></tr>";
});
function test() {
shh.post({topics: ["test"], payload: web3.fromAscii("test it")})
}
</script>
</html>

@ -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);
}

@ -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"));
}

@ -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
}

@ -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
}

@ -44,18 +44,22 @@ type EthereumApi struct {
xeth *xeth.XEth
filterManager *filter.FilterManager
mut sync.RWMutex
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))
}

@ -1,3 +1,4 @@
// QWhisper package. This package is temporarily on hold until QML DApp dev will reemerge.
package qwhisper
import (

116
xeth/whisper.go Normal file

@ -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())),
}
}

@ -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) 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)