mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 06:36:43 +00:00
whisper: mail server introduced
This commit is contained in:
parent
b392986c5c
commit
202ac23df2
5 changed files with 235 additions and 63 deletions
168
cmd/wnode/mailserver.go
Normal file
168
cmd/wnode/mailserver.go
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
// 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 main
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
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 + sizeOfInt
|
||||
var k DBKey
|
||||
k.timestamp = t
|
||||
k.hash = h
|
||||
k.raw = make([]byte, sz)
|
||||
binary.BigEndian.PutUint32(k.raw, k.timestamp)
|
||||
copy(k.raw[sizeOfInt:], 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 = envelope.DecodeBytes(i.Value())
|
||||
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) < sizeOfInt*2 {
|
||||
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[:sizeOfInt])
|
||||
upper := binary.BigEndian.Uint32(decrypted.Payload[sizeOfInt : sizeOfInt*2])
|
||||
|
||||
if len(decrypted.Payload) == sizeOfInt*2+whisper.TopicLength {
|
||||
topic = whisper.BytesToTopic(decrypted.Payload[sizeOfInt*2 : sizeOfInt*2+whisper.TopicLength])
|
||||
}
|
||||
|
||||
return true, lower, upper, topic
|
||||
}
|
||||
|
|
@ -43,7 +43,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
|
@ -53,7 +52,7 @@ const sizeOfInt = 4
|
|||
var (
|
||||
quitCommand = "~Q"
|
||||
enodePrefix = "enode://"
|
||||
mailServerKeyName = "fd8c027e29852ef"
|
||||
mailServerKeyName = "958e04ab302fb36ad2616a352cbac79dfa08f43d325d0833e07061a9ef8c383d"
|
||||
)
|
||||
|
||||
// singletons
|
||||
|
|
@ -81,7 +80,7 @@ var (
|
|||
echoMode = flag.Bool("e", false, "echo mode: prints some arguments for diagnostics")
|
||||
bootstrapMode = flag.Bool("b", false, "boostrap node: don't actively connect to peers, wait for incoming connections")
|
||||
forwarderMode = flag.Bool("f", false, "forwarder mode: only forward messages, neither send nor decrypt messages")
|
||||
mailServerMode = flag.Bool("m", false, "mail server mode: delivers expired messages on demand")
|
||||
mailServerMode = flag.Bool("s", false, "mail server mode: delivers expired messages on demand")
|
||||
requestMail = flag.Bool("r", false, "request expired messages from the bootstrap server")
|
||||
asymmetricMode = flag.Bool("a", false, "use asymmetric encryption")
|
||||
testMode = flag.Bool("t", false, "use of predefined parameters for diagnostics")
|
||||
|
|
@ -150,7 +149,6 @@ func echo() {
|
|||
fmt.Printf("mspow = %f \n", *argServerPoW)
|
||||
fmt.Printf("ip = %s \n", *argIP)
|
||||
fmt.Printf("salt = %s \n", *argSalt)
|
||||
fmt.Printf("topic = %x \n", topic)
|
||||
fmt.Printf("pub = %s \n", common.ToHex(crypto.FromECDSAPub(pub)))
|
||||
fmt.Printf("idfile = %s \n", *argIDFile)
|
||||
fmt.Printf("dbpath = %s \n", *argDBPath)
|
||||
|
|
@ -175,7 +173,7 @@ func initialize() {
|
|||
|
||||
if *bootstrapMode {
|
||||
if len(*argIP) == 0 {
|
||||
argIP = scanLineA("Please enter your IP and port (e.g. 127.0.0.1:30303): ")
|
||||
argIP = scanLineA("Please enter your IP and port (e.g. 127.0.0.1:30348): ")
|
||||
}
|
||||
} else {
|
||||
if len(*argEnode) == 0 {
|
||||
|
|
@ -185,12 +183,13 @@ func initialize() {
|
|||
peers = append(peers, peer)
|
||||
}
|
||||
|
||||
if *mailServerMode && len(msPassword) == 0 {
|
||||
if *mailServerMode {
|
||||
if len(msPassword) == 0 {
|
||||
msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read Mail Server password: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
shh = whisper.NewWhisper(&mailServer)
|
||||
mailServer.Init(shh, *argDBPath, msPassword, *argServerPoW)
|
||||
} else {
|
||||
|
|
@ -247,18 +246,31 @@ func isKeyValid(k *ecdsa.PublicKey) bool {
|
|||
|
||||
func configureNode() {
|
||||
var err error
|
||||
var p2pAccept bool
|
||||
|
||||
if *forwarderMode {
|
||||
return
|
||||
}
|
||||
|
||||
if *asymmetricMode && len(*argPub) == 0 {
|
||||
if *asymmetricMode {
|
||||
if len(*argPub) == 0 {
|
||||
s := scanLine("Please enter the peer's public key: ")
|
||||
pub = crypto.ToECDSAPub(common.FromHex(s))
|
||||
if !isKeyValid(pub) {
|
||||
utils.Fatalf("Error: invalid public key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if *requestMail {
|
||||
p2pAccept = true
|
||||
if len(msPassword) == 0 {
|
||||
msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read Mail Server password: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !*asymmetricMode && !*forwarderMode && !*testMode {
|
||||
pass, err := console.Stdin.PromptPassword("Please enter the password: ")
|
||||
|
|
@ -283,15 +295,14 @@ func configureNode() {
|
|||
}
|
||||
}
|
||||
|
||||
if *requestMail && len(msPassword) == 0 {
|
||||
msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read Mail Server password: %s", err)
|
||||
filter := whisper.Filter{
|
||||
KeySym: symKey,
|
||||
KeyAsym: asymKey,
|
||||
Topics: []whisper.TopicType{topic},
|
||||
AcceptP2P: p2pAccept,
|
||||
}
|
||||
}
|
||||
|
||||
filter := &whisper.Filter{KeySym: symKey, KeyAsym: asymKey, Topics: []whisper.TopicType{topic}}
|
||||
filterID = shh.Watch(filter)
|
||||
filterID = shh.Watch(&filter)
|
||||
fmt.Printf("Filter is configured for the topic: %x \n", topic)
|
||||
}
|
||||
|
||||
func generateTopic(password, salt []byte) {
|
||||
|
|
@ -451,7 +462,10 @@ func printMessageInfo(msg *whisper.ReceivedMessage) {
|
|||
|
||||
func requestExpiredMessagesLoop() {
|
||||
var key, peerID []byte
|
||||
var timeLow, timeUpp, t uint32
|
||||
var timeLow, timeUpp uint32
|
||||
var t string
|
||||
var xt, empty whisper.TopicType
|
||||
|
||||
err := shh.AddSymKey(mailServerKeyName, []byte(msPassword))
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to create symmetric key for mail request: %s", err)
|
||||
|
|
@ -463,16 +477,23 @@ func requestExpiredMessagesLoop() {
|
|||
for {
|
||||
timeLow = scanUint("Please enter the lower limit of the time range (unix timestamp): ")
|
||||
timeUpp = scanUint("Please enter the upper limit of the time range (unix timestamp): ")
|
||||
t = scanUint("Please enter the topic: ")
|
||||
t = scanLine("Please enter the topic (hexadecimal): ")
|
||||
if len(t) >= whisper.TopicLength*2 {
|
||||
x, err := hex.DecodeString(t)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to parse the topic: %s", err)
|
||||
}
|
||||
xt = whisper.BytesToTopic(x)
|
||||
}
|
||||
if timeUpp == 0 {
|
||||
timeUpp = 0xFFFFFFFF
|
||||
}
|
||||
|
||||
data := make([]byte, sizeOfInt*3)
|
||||
data := make([]byte, sizeOfInt*2+whisper.TopicLength)
|
||||
binary.BigEndian.PutUint32(data, timeLow)
|
||||
binary.BigEndian.PutUint32(data[sizeOfInt:], timeUpp)
|
||||
binary.BigEndian.PutUint32(data[sizeOfInt*2:], t)
|
||||
if t == 0 {
|
||||
copy(data[sizeOfInt*2:], xt[:])
|
||||
if xt == empty {
|
||||
data = data[:sizeOfInt*2]
|
||||
}
|
||||
|
||||
|
|
@ -489,12 +510,7 @@ func requestExpiredMessagesLoop() {
|
|||
utils.Fatalf("Wrap failed: %s", err)
|
||||
}
|
||||
|
||||
encoded, err := rlp.EncodeToBytes(env)
|
||||
if err != nil {
|
||||
utils.Fatalf("RLP encoding failed: %s", err)
|
||||
}
|
||||
|
||||
err = shh.RequestHistoricMessages(peerID, encoded)
|
||||
err = shh.RequestHistoricMessages(peerID, env)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to send P2P message: %s", err)
|
||||
}
|
||||
|
|
@ -526,11 +542,3 @@ func extractIdFromEnode(s string) []byte {
|
|||
|
||||
return b
|
||||
}
|
||||
|
||||
// this is a temporary stub, will be expanded later
|
||||
type WMailServer struct{}
|
||||
|
||||
func (s *WMailServer) Archive(env *whisper.Envelope) {}
|
||||
func (s *WMailServer) DeliverMail(peer *whisper.Peer, data []byte) {}
|
||||
func (s *WMailServer) Init(w *whisper.Whisper, path string, password string, pow float64) {}
|
||||
func (s *WMailServer) Close() {}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -361,25 +360,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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue