Commit 9cd71355 authored by gluk256's avatar gluk256 Committed by Felix Lange

whisper: big refactoring (#13852)

* whisper: GetMessages fixed; size restriction updated
* whisper: made PoW and MaxMsgSize customizable
* whisper: test added
* whisper: sym key management changed
* whisper: identity management refactored
* whisper: API refactoring (Post and Filter)
* whisper: big refactoring complete
* whisper: spelling fix
* whisper: variable topic size allowed for a filter
* whisper: final update
* whisper: formatting
* whisper: file exchange introduced in wnode
* whisper: bugfix
* whisper: API updated + new tests
* whisper: statistics updated
* whisper: wnode server updated
* whisper: allowed filtering for variable topic size
* whisper: tests added
* whisper: resolving merge conflicts
* whisper: refactoring (documenting mostly)
* whsiper: tests fixed
* whisper: down cased error messages
* whisper: documenting the API functions
* whisper: logging fixed
* whisper: fixed wnode parameters
* whisper: logs fixed (typos)
parent 8570ef19
This diff is collapsed.
......@@ -31,8 +31,6 @@ import (
"github.com/syndtr/goleveldb/leveldb/util"
)
const MailServerKeyName = "958e04ab302fb36ad2616a352cbac79d"
type WMailServer struct {
db *leveldb.DB
w *whisper.Whisper
......@@ -75,11 +73,14 @@ func (s *WMailServer) Init(shh *whisper.Whisper, path string, password string, p
s.w = shh
s.pow = pow
err = s.w.AddSymKey(MailServerKeyName, []byte(password))
MailServerKeyID, err := s.w.AddSymKeyFromPassword(password)
if err != nil {
utils.Fatalf("Failed to create symmetric key for MailServer: %s", err)
}
s.key = s.w.GetSymKey(MailServerKeyName)
s.key, err = s.w.GetSymKey(MailServerKeyID)
if err != nil {
utils.Fatalf("Failed to save symmetric key for MailServer")
}
}
func (s *WMailServer) Close() {
......
......@@ -30,8 +30,8 @@ import (
)
const powRequirement = 0.00001
const keyName = "6d604bac5401ce9a6b995f1b45a4ab"
var keyID string
var shh *whisper.Whisper
var seed = time.Now().Unix()
......@@ -90,7 +90,7 @@ func TestMailServer(t *testing.T) {
server.Init(shh, dir, password, powRequirement)
defer server.Close()
err = shh.AddSymKey(keyName, []byte(password))
keyID, err = shh.AddSymKeyFromPassword(password)
if err != nil {
t.Fatalf("Failed to create symmetric key for mail request: %s", err)
}
......@@ -102,7 +102,14 @@ func TestMailServer(t *testing.T) {
}
func deliverTest(t *testing.T, server *WMailServer, env *whisper.Envelope) {
testPeerID := shh.NewIdentity()
id, err := shh.NewKeyPair()
if err != nil {
t.Fatalf("failed to generate new key pair with seed %d: %s.", seed, err)
}
testPeerID, err := shh.GetPrivateKey(id)
if err != nil {
t.Fatalf("failed to retrieve new key pair with seed %d: %s.", seed, err)
}
birth := env.Expiry - env.TTL
p := &ServerTestParams{
topic: env.Topic,
......@@ -167,8 +174,13 @@ func createRequest(t *testing.T, p *ServerTestParams) *whisper.Envelope {
binary.BigEndian.PutUint32(data[4:], p.upp)
copy(data[8:], p.topic[:])
key, err := shh.GetSymKey(keyID)
if err != nil {
t.Fatalf("failed to retrieve sym key with seed %d: %s.", seed, err)
}
params := &whisper.MessageParams{
KeySym: shh.GetSymKey(keyName),
KeySym: key,
Topic: p.topic,
Payload: data,
PoW: powRequirement * 2,
......
This diff is collapsed.
This diff is collapsed.
......@@ -54,9 +54,10 @@ const (
aesKeyLength = 32
saltLength = 12
AESNonceMaxLength = 12
keyIdSize = 32
MaxMessageLength = 0x0FFFFF // todo: remove this restriction after testing. this should be regulated by PoW.
MinimumPoW = 10.0 // todo: review after testing.
DefaultMaxMessageLength = 1024 * 1024
DefaultMinimumPoW = 1.0 // todo: review after testing.
padSizeLimitLower = 128 // it can not be less - we don't want to reveal the absence of signature
padSizeLimitUpper = 256 // just an arbitrary number, could be changed without losing compatibility
......
......@@ -21,7 +21,6 @@ package whisperv5
import (
"crypto/ecdsa"
"encoding/binary"
"errors"
"fmt"
gmath "math"
"math/big"
......@@ -83,7 +82,7 @@ func (e *Envelope) isAsymmetric() bool {
}
func (e *Envelope) Ver() uint64 {
return bytesToIntLittleEndian(e.Version)
return bytesToUintLittleEndian(e.Version)
}
// Seal closes the envelope by spending the requested amount of time as a proof
......@@ -95,6 +94,9 @@ func (e *Envelope) Seal(options *MessageParams) error {
e.Expiry += options.WorkTime
} else {
target = e.powToFirstBit(options.PoW)
if target < 1 {
target = 1
}
}
buf := make([]byte, 64)
......@@ -118,7 +120,7 @@ func (e *Envelope) Seal(options *MessageParams) error {
}
if target > 0 && bestBit < target {
return errors.New("Failed to reach the PoW target, insufficient work time")
return fmt.Errorf("failed to reach the PoW target, specified pow time (%d seconds) was insufficient", options.WorkTime)
}
return nil
......
......@@ -18,7 +18,6 @@ package whisperv5
import (
"crypto/ecdsa"
crand "crypto/rand"
"fmt"
"sync"
......@@ -30,9 +29,9 @@ type Filter struct {
Src *ecdsa.PublicKey // Sender of the message
KeyAsym *ecdsa.PrivateKey // Private Key of recipient
KeySym []byte // Key associated with the Topic
Topics []TopicType // Topics to filter messages with
Topics [][]byte // Topics to filter messages with
PoW float64 // Proof of work as described in the Whisper spec
AcceptP2P bool // Indicates whether this filter is interested in direct peer-to-peer messages
AllowP2P bool // Indicates whether this filter is interested in direct peer-to-peer messages
SymKeyHash common.Hash // The Keccak256Hash of the symmetric key, needed for optimization
Messages map[common.Hash]*ReceivedMessage
......@@ -52,47 +51,35 @@ func NewFilters(w *Whisper) *Filters {
}
}
func (fs *Filters) generateRandomID() (id string, err error) {
buf := make([]byte, 20)
for i := 0; i < 3; i++ {
_, err = crand.Read(buf)
if err != nil {
continue
}
if !validateSymmetricKey(buf) {
err = fmt.Errorf("error in generateRandomID: crypto/rand failed to generate random data")
continue
}
id = common.Bytes2Hex(buf)
if fs.watchers[id] != nil {
err = fmt.Errorf("error in generateRandomID: generated same ID twice")
continue
}
return id, err
}
return "", err
}
func (fs *Filters) Install(watcher *Filter) (string, error) {
if watcher.Messages == nil {
watcher.Messages = make(map[common.Hash]*ReceivedMessage)
}
id, err := GenerateRandomID()
if err != nil {
return "", err
}
fs.mutex.Lock()
defer fs.mutex.Unlock()
id, err := fs.generateRandomID()
if err == nil {
fs.watchers[id] = watcher
if fs.watchers[id] != nil {
return "", fmt.Errorf("failed to generate unique ID")
}
fs.watchers[id] = watcher
return id, err
}
func (fs *Filters) Uninstall(id string) {
func (fs *Filters) Uninstall(id string) bool {
fs.mutex.Lock()
defer fs.mutex.Unlock()
if fs.watchers[id] != nil {
delete(fs.watchers, id)
return true
}
return false
}
func (fs *Filters) Get(id string) *Filter {
......@@ -102,11 +89,16 @@ func (fs *Filters) Get(id string) *Filter {
}
func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) {
fs.mutex.RLock()
var msg *ReceivedMessage
for j, watcher := range fs.watchers {
if p2pMessage && !watcher.AcceptP2P {
log.Trace(fmt.Sprintf("msg [%x], filter [%s]: p2p messages are not allowed", env.Hash(), j))
fs.mutex.RLock()
defer fs.mutex.RUnlock()
i := -1 // only used for logging info
for _, watcher := range fs.watchers {
i++
if p2pMessage && !watcher.AllowP2P {
log.Trace(fmt.Sprintf("msg [%x], filter [%d]: p2p messages are not allowed", env.Hash(), i))
continue
}
......@@ -118,22 +110,32 @@ func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) {
if match {
msg = env.Open(watcher)
if msg == nil {
log.Trace(fmt.Sprintf("msg [%x], filter [%s]: failed to open", env.Hash(), j))
log.Trace("processing message: failed to open", "message", env.Hash().Hex(), "filter", i)
}
} else {
log.Trace(fmt.Sprintf("msg [%x], filter [%s]: does not match", env.Hash(), j))
log.Trace("processing message: does not match", "message", env.Hash().Hex(), "filter", i)
}
}
if match && msg != nil {
log.Trace("processing message: decrypted", "hash", env.Hash().Hex())
watcher.Trigger(msg)
}
}
fs.mutex.RUnlock() // we need to unlock before calling addDecryptedMessage
}
func (f *Filter) processEnvelope(env *Envelope) *ReceivedMessage {
if f.MatchEnvelope(env) {
msg := env.Open(f)
if msg != nil {
fs.whisper.addDecryptedMessage(msg)
return msg
} else {
log.Trace("processing envelope: failed to open", "hash", env.Hash().Hex())
}
} else {
log.Trace("processing envelope: does not match", "hash", env.Hash().Hex())
}
return nil
}
func (f *Filter) expectsAsymmetricEncryption() bool {
......@@ -200,20 +202,33 @@ func (f *Filter) MatchTopic(topic TopicType) bool {
return true
}
for _, t := range f.Topics {
if t == topic {
for _, bt := range f.Topics {
if matchSingleTopic(topic, bt) {
return true
}
}
return false
}
func matchSingleTopic(topic TopicType, bt []byte) bool {
if len(bt) > 4 {
bt = bt[:4]
}
for j, b := range bt {
if topic[j] != b {
return false
}
}
return true
}
func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool {
if !ValidatePublicKey(a) {
return false
} else if !ValidatePublicKey(b) {
return false
}
// the Curve is always the same, just compare the points
// the curve is always the same, just compare the points
return a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0
}
......@@ -53,8 +53,9 @@ func generateFilter(t *testing.T, symmetric bool) (*Filter, error) {
f.Messages = make(map[common.Hash]*ReceivedMessage)
const topicNum = 8
f.Topics = make([]TopicType, topicNum)
f.Topics = make([][]byte, topicNum)
for i := 0; i < topicNum; i++ {
f.Topics[i] = make([]byte, 4)
mrand.Read(f.Topics[i][:])
f.Topics[i][0] = 0x01
}
......@@ -108,7 +109,7 @@ func TestInstallFilters(t *testing.T) {
t.Fatalf("seed %d: failed to install filter: %s", seed, err)
}
tst[i].id = j
if len(j) != 40 {
if len(j) != keyIdSize*2 {
t.Fatalf("seed %d: wrong filter id size [%d]", seed, len(j))
}
}
......@@ -194,8 +195,8 @@ func TestMatchEnvelope(t *testing.T) {
// encrypt symmetrically
i := mrand.Int() % 4
fsym.Topics[i] = params.Topic
fasym.Topics[i] = params.Topic
fsym.Topics[i] = params.Topic[:]
fasym.Topics[i] = params.Topic[:]
msg = NewSentMessage(params)
env, err = msg.Wrap(params)
if err != nil {
......@@ -320,7 +321,7 @@ func TestMatchMessageSym(t *testing.T) {
const index = 1
params.KeySym = f.KeySym
params.Topic = f.Topics[index]
params.Topic = BytesToTopic(f.Topics[index])
sentMessage := NewSentMessage(params)
env, err := sentMessage.Wrap(params)
......@@ -413,7 +414,7 @@ func TestMatchMessageAsym(t *testing.T) {
}
const index = 1
params.Topic = f.Topics[index]
params.Topic = BytesToTopic(f.Topics[index])
params.Dst = &f.KeyAsym.PublicKey
keySymOrig := params.KeySym
params.KeySym = nil
......@@ -491,7 +492,7 @@ func cloneFilter(orig *Filter) *Filter {
clone.KeySym = orig.KeySym
clone.Topics = orig.Topics
clone.PoW = orig.PoW
clone.AcceptP2P = orig.AcceptP2P
clone.AllowP2P = orig.AllowP2P
clone.SymKeyHash = orig.SymKeyHash
return &clone
}
......@@ -504,7 +505,7 @@ func generateCompatibeEnvelope(t *testing.T, f *Filter) *Envelope {
}
params.KeySym = f.KeySym
params.Topic = f.Topics[2]
params.Topic = BytesToTopic(f.Topics[2])
sentMessage := NewSentMessage(params)
env, err := sentMessage.Wrap(params)
if err != nil {
......@@ -655,7 +656,7 @@ func TestWatchers(t *testing.T) {
if f == nil {
t.Fatalf("failed to get the filter with seed %d.", seed)
}
f.AcceptP2P = true
f.AllowP2P = true
total = 0
filters.NotifyWatchers(envelopes[0], true)
......@@ -668,3 +669,40 @@ func TestWatchers(t *testing.T) {
t.Fatalf("failed with seed %d: total: got %d, want 1.", seed, total)
}
}
func TestVariableTopics(t *testing.T) {
InitSingleTest()
var match bool
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("failed Wrap with seed %d: %s.", seed, err)
}
f, err := generateFilter(t, true)
if err != nil {
t.Fatalf("failed generateFilter with seed %d: %s.", seed, err)
}
for i := 0; i < 4; i++ {
arr := make([]byte, i+1, 4)
copy(arr, env.Topic[:i+1])
f.Topics[4] = arr
match = f.MatchEnvelope(env)
if !match {
t.Fatalf("failed MatchEnvelope symmetric with seed %d, step %d.", seed, i)
}
f.Topics[4][i]++
match = f.MatchEnvelope(env)
if match {
t.Fatalf("MatchEnvelope symmetric with seed %d, step %d: false positive.", seed, i)
}
}
}
......@@ -25,8 +25,6 @@ import (
crand "crypto/rand"
"crypto/sha256"
"errors"
"fmt"
mrand "math/rand"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
......@@ -102,14 +100,18 @@ func NewSentMessage(params *MessageParams) *SentMessage {
msg := SentMessage{}
msg.Raw = make([]byte, 1, len(params.Payload)+len(params.Payload)+signatureLength+padSizeLimitUpper)
msg.Raw[0] = 0 // set all the flags to zero
msg.appendPadding(params)
err := msg.appendPadding(params)
if err != nil {
log.Error("failed to create NewSentMessage", "err", err)
return nil
}
msg.Raw = append(msg.Raw, params.Payload...)
return &msg
}
// appendPadding appends the pseudorandom padding bytes and sets the padding flag.
// The last byte contains the size of padding (thus, its size must not exceed 256).
func (msg *SentMessage) appendPadding(params *MessageParams) {
func (msg *SentMessage) appendPadding(params *MessageParams) error {
total := len(params.Payload) + 1
if params.Src != nil {
total += signatureLength
......@@ -128,7 +130,10 @@ func (msg *SentMessage) appendPadding(params *MessageParams) {
panic("please fix the padding algorithm before releasing new version")
}
buf := make([]byte, padSize)
mrand.Read(buf[1:])
_, err := crand.Read(buf[1:])
if err != nil {
return err
}
buf[0] = byte(padSize)
if params.Padding != nil {
copy(buf[1:], params.Padding)
......@@ -136,6 +141,7 @@ func (msg *SentMessage) appendPadding(params *MessageParams) {
msg.Raw = append(msg.Raw, buf...)
msg.Raw[0] |= byte(0x1) // number of bytes indicating the padding size
}
return nil
}
// sign calculates and sets the cryptographic signature for the message,
......@@ -143,7 +149,7 @@ func (msg *SentMessage) appendPadding(params *MessageParams) {
func (msg *SentMessage) sign(key *ecdsa.PrivateKey) error {
if isMessageSigned(msg.Raw[0]) {
// this should not happen, but no reason to panic
log.Error(fmt.Sprintf("Trying to sign a message which was already signed"))
log.Error("failed to sign the message: already signed")
return nil
}
......@@ -161,7 +167,7 @@ func (msg *SentMessage) sign(key *ecdsa.PrivateKey) error {
// encryptAsymmetric encrypts a message with a public key.
func (msg *SentMessage) encryptAsymmetric(key *ecdsa.PublicKey) error {
if !ValidatePublicKey(key) {
return fmt.Errorf("Invalid public key provided for asymmetric encryption")
return errors.New("invalid public key provided for asymmetric encryption")
}
encrypted, err := ecies.Encrypt(crand.Reader, ecies.ImportECDSAPublic(key), msg.Raw, nil, nil)
if err == nil {
......@@ -215,17 +221,6 @@ func (msg *SentMessage) encryptSymmetric(key []byte) (salt []byte, nonce []byte,
}
// Wrap bundles the message into an Envelope to transmit over the network.
//
// pow (Proof Of Work) controls how much time to spend on hashing the message,
// inherently controlling its priority through the network (smaller hash, bigger
// priority).
//
// The user can control the amount of identity, privacy and encryption through
// the options parameter as follows:
// - options.From == nil && options.To == nil: anonymous broadcast
// - options.From != nil && options.To == nil: signed broadcast (known sender)
// - options.From == nil && options.To != nil: encrypted anonymous message
// - options.From != nil && options.To != nil: encrypted signed message
func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err error) {
if options.TTL == 0 {
options.TTL = DefaultTTL
......@@ -236,17 +231,13 @@ func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er
return nil, err
}
}
if len(msg.Raw) > MaxMessageLength {
log.Error(fmt.Sprintf("Message size must not exceed %d bytes", MaxMessageLength))
return nil, errors.New("Oversized message")
}
var salt, nonce []byte
if options.Dst != nil {
err = msg.encryptAsymmetric(options.Dst)
} else if options.KeySym != nil {
salt, nonce, err = msg.encryptSymmetric(options.KeySym)
} else {
err = errors.New("Unable to encrypt the message: neither Dst nor Key")
err = errors.New("unable to encrypt the message: neither symmetric nor assymmetric key provided")
}
if err != nil {
......@@ -258,7 +249,6 @@ func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er
if err != nil {
return nil, err
}
return envelope, nil
}
......@@ -279,9 +269,8 @@ func (msg *ReceivedMessage) decryptSymmetric(key []byte, salt []byte, nonce []by
return err
}
if len(nonce) != aesgcm.NonceSize() {
info := fmt.Sprintf("Wrong AES nonce size - want: %d, got: %d", len(nonce), aesgcm.NonceSize())
log.Error(fmt.Sprintf(info))
return errors.New(info)
log.Error("decrypting the message", "AES nonce size", len(nonce))
return errors.New("wrong AES nonce size")
}
decrypted, err := aesgcm.Open(nil, nonce, msg.Raw, nil)
if err != nil {
......@@ -336,7 +325,7 @@ func (msg *ReceivedMessage) extractPadding(end int) (int, bool) {
paddingSize := 0
sz := int(msg.Raw[0] & paddingMask) // number of bytes containing the entire size of padding, could be zero
if sz != 0 {
paddingSize = int(bytesToIntLittleEndian(msg.Raw[1 : 1+sz]))
paddingSize = int(bytesToUintLittleEndian(msg.Raw[1 : 1+sz]))
if paddingSize < sz || paddingSize+1 > end {
return 0, false
}
......@@ -351,7 +340,7 @@ func (msg *ReceivedMessage) SigToPubKey() *ecdsa.PublicKey {
pub, err := crypto.SigToPub(msg.hash(), msg.Signature)
if err != nil {
log.Error(fmt.Sprintf("Could not get public key from signature: %v", err))
log.Error("failed to recover public key from signature", "err", err)
return nil
}
return pub
......
......@@ -55,13 +55,13 @@ func newPeer(host *Whisper, remote *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
// into the network.
func (p *Peer) start() {
go p.update()
log.Debug(fmt.Sprintf("%v: whisper started", p.peer))
log.Trace("start", "peer", p.ID())
}
// stop terminates the peer updater, stopping message forwarding to it.
func (p *Peer) stop() {
close(p.quit)
log.Debug(fmt.Sprintf("%v: whisper stopped", p.peer))
log.Trace("stop", "peer", p.ID())
}
// handshake sends the protocol initiation status message to the remote peer and
......@@ -78,19 +78,19 @@ func (p *Peer) handshake() error {
return err
}
if packet.Code != statusCode {
return fmt.Errorf("peer sent %x before status packet", packet.Code)
return fmt.Errorf("peer [%x] sent packet %x before status packet", p.ID(), packet.Code)
}
s := rlp.NewStream(packet.Payload, uint64(packet.Size))
peerVersion, err := s.Uint()
if err != nil {
return fmt.Errorf("bad status message: %v", err)
return fmt.Errorf("peer [%x] sent bad status message: %v", p.ID(), err)
}
if peerVersion != ProtocolVersion {
return fmt.Errorf("protocol version mismatch %d != %d", peerVersion, ProtocolVersion)
return fmt.Errorf("peer [%x]: protocol version mismatch %d != %d", p.ID(), peerVersion, ProtocolVersion)
}
// Wait until out own status is consumed too
if err := <-errc; err != nil {
return fmt.Errorf("failed to send status packet: %v", err)
return fmt.Errorf("peer [%x] failed to send status packet: %v", p.ID(), err)
}
return nil
}
......@@ -110,7 +110,7 @@ func (p *Peer) update() {
case <-transmit.C:
if err := p.broadcast(); err != nil {
log.Info(fmt.Sprintf("%v: broadcast failed: %v", p.peer, err))
log.Trace("broadcast failed", "reason", err, "peer", p.ID())
return
}
......@@ -165,7 +165,7 @@ func (p *Peer) broadcast() error {
if err := p2p.Send(p.ws, messagesCode, transmit); err != nil {
return err
}
log.Trace(fmt.Sprint(p.peer, "broadcasted", len(transmit), "message(s)"))
log.Trace("broadcast", "num. messages", len(transmit))
return nil
}
......
......@@ -114,12 +114,13 @@ func initialize(t *testing.T) {
for i := 0; i < NumNodes; i++ {
var node TestNode
node.shh = New()
node.shh.test = true
node.shh.SetMinimumPoW(0.00000001)
node.shh.Start(nil)
topics := make([]TopicType, 0)
topics = append(topics, sharedTopic)
f := Filter{KeySym: sharedKey, Topics: topics}
node.filerId, err = node.shh.Watch(&f)
f := Filter{KeySym: sharedKey}
f.Topics = [][]byte{topics[0][:]}
node.filerId, err = node.shh.Subscribe(&f)
if err != nil {
t.Fatalf("failed to install the filter: %s.", err)
}
......@@ -166,7 +167,7 @@ func stopServers() {
for i := 0; i < NumNodes; i++ {
n := nodes[i]
if n != nil {
n.shh.Unwatch(n.filerId)
n.shh.Unsubscribe(n.filerId)
n.shh.Stop()
n.server.Stop()
}
......@@ -257,7 +258,7 @@ func sendMsg(t *testing.T, expected bool, id int) {
return
}
opt := MessageParams{KeySym: sharedKey, Topic: sharedTopic, Payload: expectedMessage, PoW: 0.00000001}
opt := MessageParams{KeySym: sharedKey, Topic: sharedTopic, Payload: expectedMessage, PoW: 0.00000001, WorkTime: 1}
if !expected {
opt.KeySym[0]++
opt.Topic[0]++
......@@ -267,12 +268,12 @@ func sendMsg(t *testing.T, expected bool, id int) {
msg := NewSentMessage(&opt)
envelope, err := msg.Wrap(&opt)
if err != nil {
t.Fatalf("failed to seal message.")
t.Fatalf("failed to seal message: %s", err)
}
err = nodes[id].shh.Send(envelope)
if err != nil {
t.Fatalf("failed to send message.")
t.Fatalf("failed to send message: %s", err)
}
}
......
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment