Skip to content

Commit

Permalink
cmd/wnode, whisper: add whisper CLI tool and mail server (ethereum#3580)
Browse files Browse the repository at this point in the history
  • Loading branch information
gluk256 authored and fjl committed Jan 31, 2017
1 parent 1c140f7 commit 690f6ea
Show file tree
Hide file tree
Showing 8 changed files with 769 additions and 30 deletions.
537 changes: 537 additions & 0 deletions cmd/wnode/main.go

Large diffs are not rendered by default.

170 changes: 170 additions & 0 deletions whisper/mailserver/mailserver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// Copyright 2016 The go-ethereum Authors
// 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 mailserver

import (
"bytes"
"encoding/binary"

"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/rlp"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
)

const MailServerKeyName = "958e04ab302fb36ad2616a352cbac79d"

type WMailServer struct {
db *leveldb.DB
w *whisper.Whisper
pow float64
key []byte
}

type DBKey struct {
timestamp uint32
hash common.Hash
raw []byte
}

func NewDbKey(t uint32, h common.Hash) *DBKey {
const sz = common.HashLength + 4
var k DBKey
k.timestamp = t
k.hash = h
k.raw = make([]byte, sz)
binary.BigEndian.PutUint32(k.raw, k.timestamp)
copy(k.raw[4:], k.hash[:])
return &k
}

func (s *WMailServer) Init(shh *whisper.Whisper, path string, password string, pow float64) {
var err error
if len(path) == 0 {
utils.Fatalf("DB file is not specified")
}

if len(password) == 0 {
utils.Fatalf("Password is not specified for MailServer")
}

s.db, err = leveldb.OpenFile(path, nil)
if err != nil {
utils.Fatalf("Failed to open DB file: %s", err)
}

s.w = shh
s.pow = pow

err = s.w.AddSymKey(MailServerKeyName, []byte(password))
if err != nil {
utils.Fatalf("Failed to create symmetric key for MailServer: %s", err)
}
s.key = s.w.GetSymKey(MailServerKeyName)
}

func (s *WMailServer) Close() {
if s.db != nil {
s.db.Close()
}
}

func (s *WMailServer) Archive(env *whisper.Envelope) {
key := NewDbKey(env.Expiry-env.TTL, env.Hash())
rawEnvelope, err := rlp.EncodeToBytes(env)
if err != nil {
glog.V(logger.Error).Infof("rlp.EncodeToBytes failed: %s", err)
} else {
err = s.db.Put(key.raw, rawEnvelope, nil)
if err != nil {
glog.V(logger.Error).Infof("Writing to DB failed: %s", err)
}
}
}

func (s *WMailServer) DeliverMail(peer *whisper.Peer, request *whisper.Envelope) {
ok, lower, upper, topic := s.validate(peer, request)
if !ok {
return
}

var err error
var zero common.Hash
var empty whisper.TopicType
kl := NewDbKey(lower, zero)
ku := NewDbKey(upper, zero)
i := s.db.NewIterator(&util.Range{Start: kl.raw, Limit: ku.raw}, nil)
defer i.Release()

for i.Next() {
var envelope whisper.Envelope
err = rlp.DecodeBytes(i.Value(), &envelope)
if err != nil {
glog.V(logger.Error).Infof("RLP decoding failed: %s", err)
}

if topic == empty || envelope.Topic == topic {
err = s.w.SendP2PDirect(peer, &envelope)
if err != nil {
glog.V(logger.Error).Infof("Failed to send direct message to peer: %s", err)
return
}
}
}

err = i.Error()
if err != nil {
glog.V(logger.Error).Infof("Level DB iterator error: %s", err)
}
}

func (s *WMailServer) validate(peer *whisper.Peer, request *whisper.Envelope) (bool, uint32, uint32, whisper.TopicType) {
var topic whisper.TopicType
if s.pow > 0.0 && request.PoW() < s.pow {
return false, 0, 0, topic
}

f := whisper.Filter{KeySym: s.key}
decrypted := request.Open(&f)
if decrypted == nil {
glog.V(logger.Warn).Infof("Failed to decrypt p2p request")
return false, 0, 0, topic
}

if len(decrypted.Payload) < 8 {
glog.V(logger.Warn).Infof("Undersized p2p request")
return false, 0, 0, topic
}

if bytes.Equal(peer.ID(), decrypted.Signature) {
glog.V(logger.Warn).Infof("Wrong signature of p2p request")
return false, 0, 0, topic
}

lower := binary.BigEndian.Uint32(decrypted.Payload[:4])
upper := binary.BigEndian.Uint32(decrypted.Payload[4:8])

if len(decrypted.Payload) >= 8+whisper.TopicLength {
topic = whisper.BytesToTopic(decrypted.Payload[8:])
}

return true, lower, upper, topic
}
12 changes: 6 additions & 6 deletions whisper/shhapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,12 @@ func (api *PublicWhisperAPI) MarkPeerTrusted(peerID hexutil.Bytes) error {
// data contains parameters (time frame, payment details, etc.), required
// by the remote email-like server. Whisper is not aware about the data format,
// it will just forward the raw data to the server.
func (api *PublicWhisperAPI) RequestHistoricMessages(peerID hexutil.Bytes, data hexutil.Bytes) error {
if api.whisper == nil {
return whisperOffLineErr
}
return api.whisper.RequestHistoricMessages(peerID, data)
}
//func (api *PublicWhisperAPI) RequestHistoricMessages(peerID hexutil.Bytes, data hexutil.Bytes) error {
// if api.whisper == nil {
// return whisperOffLineErr
// }
// return api.whisper.RequestHistoricMessages(peerID, data)
//}

// HasIdentity checks if the whisper node is configured with the private key
// of the specified public pair.
Expand Down
2 changes: 1 addition & 1 deletion whisper/whisperv5/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,5 @@ func (e unknownVersionError) Error() string {
// in order to bypass the expiry checks.
type MailServer interface {
Archive(env *Envelope)
DeliverMail(whisperPeer *Peer, data []byte)
DeliverMail(whisperPeer *Peer, request *Envelope)
}
33 changes: 33 additions & 0 deletions whisper/whisperv5/message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"testing"

"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
)

func copyFromBuf(dst []byte, src []byte, beg int) int {
Expand Down Expand Up @@ -311,3 +312,35 @@ func TestEncryptWithZeroKey(t *testing.T) {
t.Fatalf("wrapped with nil key, seed: %d.", seed)
}
}

func TestRlpEncode(t *testing.T) {
InitSingleTest()

params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
msg := NewSentMessage(params)
env, err := msg.Wrap(params)
if err != nil {
t.Fatalf("wrapped with zero key, seed: %d.", seed)
}

raw, err := rlp.EncodeToBytes(env)
if err != nil {
t.Fatalf("RLP encode failed: %s.", err)
}

var decoded Envelope
rlp.DecodeBytes(raw, &decoded)
if err != nil {
t.Fatalf("RLP decode failed: %s.", err)
}

he := env.Hash()
hd := decoded.Hash()

if he != hd {
t.Fatalf("Hashes are not equal: %x vs. %x", he, hd)
}
}
5 changes: 5 additions & 0 deletions whisper/whisperv5/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,8 @@ func (p *Peer) broadcast() error {
glog.V(logger.Detail).Infoln(p.peer, "broadcasted", len(transmit), "message(s)")
return nil
}

func (p *Peer) ID() []byte {
id := p.peer.ID()
return id[:]
}
34 changes: 17 additions & 17 deletions whisper/whisperv5/whisper.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import (
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rlp"
"golang.org/x/crypto/pbkdf2"
set "gopkg.in/fatih/set.v0"
)
Expand Down Expand Up @@ -125,13 +124,13 @@ func (w *Whisper) MarkPeerTrusted(peerID []byte) error {
return nil
}

func (w *Whisper) RequestHistoricMessages(peerID []byte, data []byte) error {
func (w *Whisper) RequestHistoricMessages(peerID []byte, envelope *Envelope) error {
p, err := w.getPeer(peerID)
if err != nil {
return err
}
p.trusted = true
return p2p.Send(p.ws, p2pRequestCode, data)
return p2p.Send(p.ws, p2pRequestCode, envelope)
}

func (w *Whisper) SendP2PMessage(peerID []byte, envelope *Envelope) error {
Expand All @@ -142,6 +141,10 @@ func (w *Whisper) SendP2PMessage(peerID []byte, envelope *Envelope) error {
return p2p.Send(p.ws, p2pCode, envelope)
}

func (w *Whisper) SendP2PDirect(peer *Peer, envelope *Envelope) error {
return p2p.Send(peer.ws, p2pCode, envelope)
}

// NewIdentity generates a new cryptographic identity for the client, and injects
// it into the known identities for message decryption.
func (w *Whisper) NewIdentity() *ecdsa.PrivateKey {
Expand Down Expand Up @@ -347,35 +350,29 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error {
return fmt.Errorf("invalid envelope")
}
p.mark(envelope)
if wh.mailServer != nil {
wh.mailServer.Archive(envelope)
}
}
case p2pCode:
// peer-to-peer message, sent directly to peer bypassing PoW checks, etc.
// this message is not supposed to be forwarded to other peers, and
// therefore might not satisfy the PoW, expiry and other requirements.
// these messages are only accepted from the trusted peer.
if p.trusted {
var envelopes []*Envelope
if err := packet.Decode(&envelopes); err != nil {
var envelope Envelope
if err := packet.Decode(&envelope); err != nil {
glog.V(logger.Warn).Infof("%v: failed to decode direct message: [%v], peer will be disconnected", p.peer, err)
return fmt.Errorf("garbage received (directMessage)")
}
for _, envelope := range envelopes {
wh.postEvent(envelope, true)
}
wh.postEvent(&envelope, true)
}
case p2pRequestCode:
// Must be processed if mail server is implemented. Otherwise ignore.
if wh.mailServer != nil {
s := rlp.NewStream(packet.Payload, uint64(packet.Size))
data, err := s.Bytes()
if err == nil {
wh.mailServer.DeliverMail(p, data)
} else {
glog.V(logger.Error).Infof("%v: bad requestHistoricMessages received: [%v]", p.peer, err)
var request Envelope
if err := packet.Decode(&request); err != nil {
glog.V(logger.Warn).Infof("%v: failed to decode p2p request message: [%v], peer will be disconnected", p.peer, err)
return fmt.Errorf("garbage received (p2p request)")
}
wh.mailServer.DeliverMail(p, &request)
}
default:
// New message types might be implemented in the future versions of Whisper.
Expand Down Expand Up @@ -454,6 +451,9 @@ func (wh *Whisper) add(envelope *Envelope) error {
} else {
glog.V(logger.Detail).Infof("cached whisper envelope [%x]: %v\n", envelope.Hash(), envelope)
wh.postEvent(envelope, false) // notify the local node about the new message
if wh.mailServer != nil {
wh.mailServer.Archive(envelope)
}
}
return nil
}
Expand Down
6 changes: 0 additions & 6 deletions whisper/whisperv5/whisper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,6 @@ func TestWhisperBasic(t *testing.T) {
if err := w.MarkPeerTrusted(peerID); err == nil {
t.Fatalf("failed MarkPeerTrusted.")
}
if err := w.RequestHistoricMessages(peerID, peerID); err == nil {
t.Fatalf("failed RequestHistoricMessages.")
}
if err := w.SendP2PMessage(peerID, nil); err == nil {
t.Fatalf("failed SendP2PMessage.")
}
exist := w.HasSymKey("non-existing")
if exist {
t.Fatalf("failed HasSymKey.")
Expand Down

0 comments on commit 690f6ea

Please sign in to comment.