cmd/wnode, whisper: add whisper CLI tool and mail server (#3580)
This commit is contained in:
170
whisper/mailserver/mailserver.go
Normal file
170
whisper/mailserver/mailserver.go
Normal file
@ -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
|
||||
}
|
@ -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.
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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[:]
|
||||
}
|
||||
|
@ -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"
|
||||
)
|
||||
@ -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 {
|
||||
@ -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 {
|
||||
@ -347,9 +350,6 @@ 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.
|
||||
@ -357,25 +357,22 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error {
|
||||
// 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.
|
||||
@ -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
|
||||
}
|
||||
|
@ -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.")
|
||||
|
Reference in New Issue
Block a user