2018-05-11 19:43:07 +00:00
|
|
|
// Copyright 2017 The go-ethereum Authors
|
|
|
|
// This file is part of the go-ethereum library.
|
|
|
|
//
|
|
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
package mailserver
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/ecdsa"
|
|
|
|
"encoding/binary"
|
2018-09-13 16:31:29 +00:00
|
|
|
"encoding/hex"
|
2018-05-21 11:30:37 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2018-05-11 19:43:07 +00:00
|
|
|
"io/ioutil"
|
2018-07-02 07:44:02 +00:00
|
|
|
"os"
|
2018-05-11 19:43:07 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2018-10-28 22:33:58 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2019-02-22 08:55:37 +00:00
|
|
|
"github.com/syndtr/goleveldb/leveldb/iterator"
|
2018-10-28 22:33:58 +00:00
|
|
|
|
2018-05-11 19:43:07 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/ethereum/go-ethereum/crypto"
|
2018-05-21 11:30:37 +00:00
|
|
|
"github.com/ethereum/go-ethereum/rlp"
|
2018-06-08 11:29:50 +00:00
|
|
|
"github.com/status-im/status-go/params"
|
2018-09-25 07:05:38 +00:00
|
|
|
whisper "github.com/status-im/whisper/whisperv6"
|
2018-05-21 11:30:37 +00:00
|
|
|
"github.com/stretchr/testify/suite"
|
2018-05-11 19:43:07 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const powRequirement = 0.00001
|
|
|
|
|
|
|
|
var keyID string
|
|
|
|
var seed = time.Now().Unix()
|
2018-07-16 09:07:17 +00:00
|
|
|
var testPayload = []byte("test payload")
|
2018-05-11 19:43:07 +00:00
|
|
|
|
|
|
|
type ServerTestParams struct {
|
|
|
|
topic whisper.TopicType
|
2018-05-17 11:21:04 +00:00
|
|
|
birth uint32
|
2018-05-11 19:43:07 +00:00
|
|
|
low uint32
|
|
|
|
upp uint32
|
2018-07-02 07:38:10 +00:00
|
|
|
limit uint32
|
2018-05-11 19:43:07 +00:00
|
|
|
key *ecdsa.PrivateKey
|
|
|
|
}
|
|
|
|
|
2018-05-21 11:30:37 +00:00
|
|
|
func TestMailserverSuite(t *testing.T) {
|
|
|
|
suite.Run(t, new(MailserverSuite))
|
|
|
|
}
|
|
|
|
|
|
|
|
type MailserverSuite struct {
|
|
|
|
suite.Suite
|
2018-07-02 07:44:02 +00:00
|
|
|
server *WMailServer
|
|
|
|
shh *whisper.Whisper
|
|
|
|
config *params.WhisperConfig
|
|
|
|
dataDir string
|
2018-05-21 11:30:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *MailserverSuite) SetupTest() {
|
|
|
|
s.server = &WMailServer{}
|
|
|
|
s.shh = whisper.New(&whisper.DefaultConfig)
|
|
|
|
s.shh.RegisterServer(s.server)
|
2018-07-02 07:44:02 +00:00
|
|
|
|
2018-07-04 09:30:57 +00:00
|
|
|
tmpDir, err := ioutil.TempDir("", "mailserver-test")
|
|
|
|
s.Require().NoError(err)
|
|
|
|
s.dataDir = tmpDir
|
|
|
|
|
|
|
|
// required files to validate mail server decryption method
|
|
|
|
privateKey, err := crypto.GenerateKey()
|
|
|
|
s.Require().NoError(err)
|
2018-07-02 07:44:02 +00:00
|
|
|
|
2018-05-21 11:30:37 +00:00
|
|
|
s.config = ¶ms.WhisperConfig{
|
2018-09-13 16:31:29 +00:00
|
|
|
DataDir: tmpDir,
|
|
|
|
MailServerAsymKey: hex.EncodeToString(crypto.FromECDSA(privateKey)),
|
|
|
|
MailServerPassword: "testpassword",
|
2018-05-21 11:30:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-02 07:44:02 +00:00
|
|
|
func (s *MailserverSuite) TearDownTest() {
|
2018-07-04 09:30:57 +00:00
|
|
|
s.Require().NoError(os.RemoveAll(s.config.DataDir))
|
2018-07-02 07:44:02 +00:00
|
|
|
}
|
|
|
|
|
2018-05-21 11:30:37 +00:00
|
|
|
func (s *MailserverSuite) TestInit() {
|
2018-07-04 09:30:57 +00:00
|
|
|
asymKey, err := crypto.GenerateKey()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
2018-05-21 11:30:37 +00:00
|
|
|
testCases := []struct {
|
|
|
|
config params.WhisperConfig
|
|
|
|
expectedError error
|
|
|
|
info string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
config: params.WhisperConfig{DataDir: ""},
|
|
|
|
expectedError: errDirectoryNotProvided,
|
2018-07-04 09:30:57 +00:00
|
|
|
info: "config with empty DataDir",
|
2018-05-21 11:30:37 +00:00
|
|
|
},
|
|
|
|
{
|
2018-07-04 09:30:57 +00:00
|
|
|
config: params.WhisperConfig{
|
|
|
|
DataDir: "/invalid-path",
|
|
|
|
MailServerPassword: "pwd",
|
|
|
|
},
|
|
|
|
expectedError: errors.New("open DB: mkdir /invalid-path: permission denied"),
|
|
|
|
info: "config with an unexisting DataDir",
|
2018-05-21 11:30:37 +00:00
|
|
|
},
|
|
|
|
{
|
2018-07-04 09:30:57 +00:00
|
|
|
config: params.WhisperConfig{
|
|
|
|
DataDir: s.config.DataDir,
|
|
|
|
MailServerPassword: "",
|
2018-09-13 16:31:29 +00:00
|
|
|
MailServerAsymKey: "",
|
2018-07-04 09:30:57 +00:00
|
|
|
},
|
|
|
|
expectedError: errDecryptionMethodNotProvided,
|
|
|
|
info: "config with an empty password and empty asym key",
|
2018-05-21 11:30:37 +00:00
|
|
|
},
|
|
|
|
{
|
2018-07-04 09:30:57 +00:00
|
|
|
config: params.WhisperConfig{
|
|
|
|
DataDir: s.config.DataDir,
|
|
|
|
MailServerPassword: "pwd",
|
|
|
|
},
|
2018-05-21 11:30:37 +00:00
|
|
|
expectedError: nil,
|
2018-07-04 09:30:57 +00:00
|
|
|
info: "config with correct DataDir and Password",
|
2018-05-21 11:30:37 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
config: params.WhisperConfig{
|
2018-07-04 09:30:57 +00:00
|
|
|
DataDir: s.config.DataDir,
|
2018-09-13 16:31:29 +00:00
|
|
|
MailServerAsymKey: hex.EncodeToString(crypto.FromECDSA(asymKey)),
|
2018-05-21 11:30:37 +00:00
|
|
|
},
|
|
|
|
expectedError: nil,
|
2018-07-04 09:30:57 +00:00
|
|
|
info: "config with correct DataDir and AsymKey",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
config: params.WhisperConfig{
|
|
|
|
DataDir: s.config.DataDir,
|
2018-09-13 16:31:29 +00:00
|
|
|
MailServerAsymKey: hex.EncodeToString(crypto.FromECDSA(asymKey)),
|
2018-07-04 09:30:57 +00:00
|
|
|
MailServerPassword: "pwd",
|
|
|
|
},
|
|
|
|
expectedError: nil,
|
|
|
|
info: "config with both asym key and password",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
config: params.WhisperConfig{
|
|
|
|
DataDir: s.config.DataDir,
|
|
|
|
MailServerPassword: "pwd",
|
|
|
|
MailServerRateLimit: 5,
|
|
|
|
},
|
|
|
|
expectedError: nil,
|
|
|
|
info: "config with rate limit",
|
2018-05-21 11:30:37 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
s.T().Run(tc.info, func(*testing.T) {
|
2018-07-04 09:30:57 +00:00
|
|
|
mailServer := &WMailServer{}
|
|
|
|
shh := whisper.New(&whisper.DefaultConfig)
|
|
|
|
shh.RegisterServer(mailServer)
|
|
|
|
|
|
|
|
err := mailServer.Init(shh, &tc.config)
|
2018-05-21 11:30:37 +00:00
|
|
|
s.Equal(tc.expectedError, err)
|
2018-07-04 09:30:57 +00:00
|
|
|
defer mailServer.Close()
|
|
|
|
|
|
|
|
// db should be open only if there was no error
|
|
|
|
if tc.expectedError == nil {
|
|
|
|
s.NotNil(mailServer.db)
|
|
|
|
} else {
|
|
|
|
s.Nil(mailServer.db)
|
|
|
|
}
|
|
|
|
|
|
|
|
if tc.config.MailServerRateLimit > 0 {
|
2019-01-10 16:07:16 +00:00
|
|
|
s.NotNil(mailServer.rateLimiter)
|
2018-07-04 09:30:57 +00:00
|
|
|
}
|
2018-05-21 11:30:37 +00:00
|
|
|
})
|
2018-05-11 19:43:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-04 09:30:57 +00:00
|
|
|
func (s *MailserverSuite) TestSetupRequestMessageDecryptor() {
|
|
|
|
// without configured Password and AsymKey
|
|
|
|
config := *s.config
|
2018-09-13 16:31:29 +00:00
|
|
|
config.MailServerAsymKey = ""
|
|
|
|
config.MailServerPassword = ""
|
2018-07-04 09:30:57 +00:00
|
|
|
s.Error(errDecryptionMethodNotProvided, s.server.Init(s.shh, &config))
|
|
|
|
|
|
|
|
// Password should work ok
|
|
|
|
config = *s.config
|
2018-09-13 16:31:29 +00:00
|
|
|
config.MailServerAsymKey = "" // clear asym key field
|
2018-07-04 09:30:57 +00:00
|
|
|
s.NoError(s.server.Init(s.shh, &config))
|
2018-07-16 09:07:17 +00:00
|
|
|
s.Require().NotNil(s.server.symFilter)
|
|
|
|
s.NotNil(s.server.symFilter.KeySym)
|
|
|
|
s.Nil(s.server.asymFilter)
|
2018-07-04 09:30:57 +00:00
|
|
|
s.server.Close()
|
|
|
|
|
|
|
|
// AsymKey can also be used
|
|
|
|
config = *s.config
|
2018-09-13 16:31:29 +00:00
|
|
|
config.MailServerPassword = "" // clear password field
|
2018-07-04 09:30:57 +00:00
|
|
|
s.NoError(s.server.Init(s.shh, &config))
|
2018-07-16 09:07:17 +00:00
|
|
|
s.Nil(s.server.symFilter) // important: symmetric filter should be nil
|
|
|
|
s.Require().NotNil(s.server.asymFilter)
|
2018-09-13 16:31:29 +00:00
|
|
|
s.Equal(config.MailServerAsymKey, hex.EncodeToString(crypto.FromECDSA(s.server.asymFilter.KeyAsym)))
|
2018-07-04 09:30:57 +00:00
|
|
|
s.server.Close()
|
|
|
|
|
2018-07-16 09:07:17 +00:00
|
|
|
// when Password and AsymKey are set, both are supported
|
2018-07-04 09:30:57 +00:00
|
|
|
config = *s.config
|
|
|
|
s.NoError(s.server.Init(s.shh, &config))
|
2018-07-16 09:07:17 +00:00
|
|
|
s.Require().NotNil(s.server.symFilter)
|
|
|
|
s.NotNil(s.server.symFilter.KeySym)
|
|
|
|
s.NotNil(s.server.asymFilter.KeyAsym)
|
|
|
|
s.server.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *MailserverSuite) TestOpenEnvelopeWithSymKey() {
|
|
|
|
// Setup the server with a sym key
|
|
|
|
config := *s.config
|
2018-09-13 16:31:29 +00:00
|
|
|
config.MailServerAsymKey = "" // clear asym key
|
2018-07-16 09:07:17 +00:00
|
|
|
s.NoError(s.server.Init(s.shh, &config))
|
|
|
|
|
|
|
|
// Prepare a valid envelope
|
|
|
|
s.Require().NotNil(s.server.symFilter)
|
|
|
|
symKey := s.server.symFilter.KeySym
|
|
|
|
env, err := generateEnvelopeWithKeys(time.Now(), symKey, nil)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
// Test openEnvelope with a valid envelope
|
|
|
|
d := s.server.openEnvelope(env)
|
|
|
|
s.NotNil(d)
|
|
|
|
s.Equal(testPayload, d.Payload)
|
|
|
|
s.server.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *MailserverSuite) TestOpenEnvelopeWithAsymKey() {
|
|
|
|
// Setup the server with an asymetric key
|
|
|
|
config := *s.config
|
2018-09-13 16:31:29 +00:00
|
|
|
config.MailServerPassword = "" // clear password field
|
2018-07-16 09:07:17 +00:00
|
|
|
s.NoError(s.server.Init(s.shh, &config))
|
|
|
|
|
|
|
|
// Prepare a valid envelope
|
|
|
|
s.Require().NotNil(s.server.asymFilter)
|
|
|
|
pubKey := s.server.asymFilter.KeyAsym.PublicKey
|
|
|
|
env, err := generateEnvelopeWithKeys(time.Now(), nil, &pubKey)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
// Test openEnvelope with a valid asymetric key
|
|
|
|
d := s.server.openEnvelope(env)
|
|
|
|
s.NotNil(d)
|
|
|
|
s.Equal(testPayload, d.Payload)
|
2018-07-04 09:30:57 +00:00
|
|
|
s.server.Close()
|
|
|
|
}
|
|
|
|
|
2018-05-21 11:30:37 +00:00
|
|
|
func (s *MailserverSuite) TestArchive() {
|
2018-09-13 16:31:29 +00:00
|
|
|
config := *s.config
|
|
|
|
config.MailServerAsymKey = "" // clear asym key
|
2018-07-04 09:30:57 +00:00
|
|
|
|
2018-09-13 16:31:29 +00:00
|
|
|
err := s.server.Init(s.shh, &config)
|
2018-07-04 09:30:57 +00:00
|
|
|
s.Require().NoError(err)
|
2018-05-21 11:30:37 +00:00
|
|
|
defer s.server.Close()
|
|
|
|
|
|
|
|
env, err := generateEnvelope(time.Now())
|
|
|
|
s.NoError(err)
|
|
|
|
rawEnvelope, err := rlp.EncodeToBytes(env)
|
|
|
|
s.NoError(err)
|
|
|
|
|
|
|
|
s.server.Archive(env)
|
2018-12-11 10:23:47 +00:00
|
|
|
key := NewDBKey(env.Expiry-env.TTL, env.Hash())
|
|
|
|
archivedEnvelope, err := s.server.db.Get(key.Bytes(), nil)
|
2018-05-21 11:30:37 +00:00
|
|
|
s.NoError(err)
|
|
|
|
|
|
|
|
s.Equal(rawEnvelope, archivedEnvelope)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *MailserverSuite) TestManageLimits() {
|
2019-01-10 16:07:16 +00:00
|
|
|
s.server.rateLimiter = newRateLimiter(time.Duration(5) * time.Millisecond)
|
2018-05-22 15:51:05 +00:00
|
|
|
s.False(s.server.exceedsPeerRequests([]byte("peerID")))
|
2019-01-10 16:07:16 +00:00
|
|
|
s.Equal(1, len(s.server.rateLimiter.db))
|
|
|
|
firstSaved := s.server.rateLimiter.db["peerID"]
|
2018-05-21 11:30:37 +00:00
|
|
|
|
|
|
|
// second call when limit is not accomplished does not store a new limit
|
2018-05-22 15:51:05 +00:00
|
|
|
s.True(s.server.exceedsPeerRequests([]byte("peerID")))
|
2019-01-10 16:07:16 +00:00
|
|
|
s.Equal(1, len(s.server.rateLimiter.db))
|
|
|
|
s.Equal(firstSaved, s.server.rateLimiter.db["peerID"])
|
2018-05-21 11:30:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *MailserverSuite) TestDBKey() {
|
2018-05-11 19:43:07 +00:00
|
|
|
var h common.Hash
|
|
|
|
i := uint32(time.Now().Unix())
|
2018-12-11 10:23:47 +00:00
|
|
|
k := NewDBKey(i, h)
|
|
|
|
s.Equal(len(k.Bytes()), DBKeyLength, "wrong DB key length")
|
|
|
|
s.Equal(byte(i%0x100), k.Bytes()[3], "raw representation should be big endian")
|
|
|
|
s.Equal(byte(i/0x1000000), k.Bytes()[0], "big endian expected")
|
2018-05-11 19:43:07 +00:00
|
|
|
}
|
|
|
|
|
2018-07-02 07:38:10 +00:00
|
|
|
func (s *MailserverSuite) TestRequestPaginationLimit() {
|
|
|
|
s.setupServer(s.server)
|
|
|
|
defer s.server.Close()
|
|
|
|
|
|
|
|
var (
|
|
|
|
sentEnvelopes []*whisper.Envelope
|
|
|
|
reverseSentHashes []common.Hash
|
|
|
|
receivedHashes []common.Hash
|
|
|
|
archiveKeys []string
|
|
|
|
)
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
count := uint32(10)
|
|
|
|
|
|
|
|
for i := count; i > 0; i-- {
|
|
|
|
sentTime := now.Add(time.Duration(-i) * time.Second)
|
|
|
|
env, err := generateEnvelope(sentTime)
|
|
|
|
s.NoError(err)
|
|
|
|
s.server.Archive(env)
|
2018-12-11 10:23:47 +00:00
|
|
|
key := NewDBKey(env.Expiry-env.TTL, env.Hash())
|
|
|
|
archiveKeys = append(archiveKeys, fmt.Sprintf("%x", key.Bytes()))
|
2018-07-02 07:38:10 +00:00
|
|
|
sentEnvelopes = append(sentEnvelopes, env)
|
|
|
|
reverseSentHashes = append([]common.Hash{env.Hash()}, reverseSentHashes...)
|
|
|
|
}
|
|
|
|
|
2019-02-22 08:55:37 +00:00
|
|
|
reqLimit := uint32(6)
|
|
|
|
peerID, request, err := s.prepareRequest(sentEnvelopes, reqLimit)
|
|
|
|
s.NoError(err)
|
|
|
|
lower, upper, bloom, limit, cursor, err := s.server.validateRequest(peerID, request)
|
|
|
|
s.NoError(err)
|
2018-07-02 07:38:10 +00:00
|
|
|
s.Nil(cursor)
|
2019-02-22 08:55:37 +00:00
|
|
|
s.Equal(reqLimit, limit)
|
2018-07-02 07:38:10 +00:00
|
|
|
|
2018-12-06 09:48:28 +00:00
|
|
|
receivedHashes, cursor, _ = processRequestAndCollectHashes(
|
|
|
|
s.server, lower, upper, cursor, bloom, int(limit),
|
|
|
|
)
|
2018-07-02 07:38:10 +00:00
|
|
|
|
|
|
|
// 10 envelopes sent
|
|
|
|
s.Equal(count, uint32(len(sentEnvelopes)))
|
|
|
|
// 6 envelopes received
|
2018-12-06 09:48:28 +00:00
|
|
|
s.Equal(int(limit), len(receivedHashes))
|
2018-07-02 07:38:10 +00:00
|
|
|
// the 6 envelopes received should be in descending order
|
|
|
|
s.Equal(reverseSentHashes[:limit], receivedHashes)
|
2018-10-19 09:09:13 +00:00
|
|
|
// cursor should be the key of the last envelope of the last page
|
2018-07-02 07:38:10 +00:00
|
|
|
s.Equal(archiveKeys[count-limit], fmt.Sprintf("%x", cursor))
|
|
|
|
|
|
|
|
// second page
|
2018-12-06 09:48:28 +00:00
|
|
|
receivedHashes, cursor, _ = processRequestAndCollectHashes(
|
|
|
|
s.server, lower, upper, cursor, bloom, int(limit),
|
|
|
|
)
|
2018-07-02 07:38:10 +00:00
|
|
|
|
|
|
|
// 4 envelopes received
|
2018-12-06 09:48:28 +00:00
|
|
|
s.Equal(int(count-limit), len(receivedHashes))
|
2018-07-02 07:38:10 +00:00
|
|
|
// cursor is nil because there are no other pages
|
|
|
|
s.Nil(cursor)
|
|
|
|
}
|
|
|
|
|
2018-05-21 11:30:37 +00:00
|
|
|
func (s *MailserverSuite) TestMailServer() {
|
2018-05-22 15:51:05 +00:00
|
|
|
s.setupServer(s.server)
|
|
|
|
defer s.server.Close()
|
2018-05-17 11:21:04 +00:00
|
|
|
|
2018-05-21 11:30:37 +00:00
|
|
|
env, err := generateEnvelope(time.Now())
|
|
|
|
s.NoError(err)
|
|
|
|
|
2018-05-22 15:51:05 +00:00
|
|
|
s.server.Archive(env)
|
2018-07-02 07:38:10 +00:00
|
|
|
|
2018-05-21 11:30:37 +00:00
|
|
|
testCases := []struct {
|
2018-05-22 15:51:05 +00:00
|
|
|
params *ServerTestParams
|
|
|
|
expect bool
|
|
|
|
isOK bool
|
|
|
|
info string
|
2018-05-21 11:30:37 +00:00
|
|
|
}{
|
|
|
|
{
|
2018-05-22 15:51:05 +00:00
|
|
|
params: s.defaultServerParams(env),
|
|
|
|
expect: true,
|
|
|
|
isOK: true,
|
|
|
|
info: "Processing a request where from and to are equal to an existing register, should provide results",
|
2018-05-21 11:30:37 +00:00
|
|
|
},
|
|
|
|
{
|
2018-05-22 15:51:05 +00:00
|
|
|
params: func() *ServerTestParams {
|
|
|
|
params := s.defaultServerParams(env)
|
|
|
|
params.low = params.birth + 1
|
|
|
|
params.upp = params.birth + 1
|
|
|
|
|
|
|
|
return params
|
|
|
|
}(),
|
|
|
|
expect: false,
|
|
|
|
isOK: true,
|
|
|
|
info: "Processing a request where from and to are greater than any existing register, should not provide results",
|
2018-05-21 11:30:37 +00:00
|
|
|
},
|
|
|
|
{
|
2018-05-22 15:51:05 +00:00
|
|
|
params: func() *ServerTestParams {
|
|
|
|
params := s.defaultServerParams(env)
|
|
|
|
params.upp = params.birth + 1
|
|
|
|
params.topic[0] = 0xFF
|
|
|
|
|
|
|
|
return params
|
|
|
|
}(),
|
|
|
|
expect: false,
|
|
|
|
isOK: true,
|
|
|
|
info: "Processing a request where to is greater than any existing register and with a specific topic, should not provide results",
|
2018-05-21 11:30:37 +00:00
|
|
|
},
|
|
|
|
{
|
2018-05-22 15:51:05 +00:00
|
|
|
params: func() *ServerTestParams {
|
|
|
|
params := s.defaultServerParams(env)
|
2018-06-26 07:33:05 +00:00
|
|
|
params.low = params.birth
|
2018-05-22 15:51:05 +00:00
|
|
|
params.upp = params.birth - 1
|
|
|
|
|
|
|
|
return params
|
|
|
|
}(),
|
|
|
|
isOK: false,
|
|
|
|
info: "Processing a request where to is lower than from should fail",
|
2018-05-21 11:30:37 +00:00
|
|
|
},
|
|
|
|
{
|
2018-05-22 15:51:05 +00:00
|
|
|
params: func() *ServerTestParams {
|
|
|
|
params := s.defaultServerParams(env)
|
|
|
|
params.low = 0
|
|
|
|
params.upp = params.birth + 24
|
|
|
|
|
|
|
|
return params
|
|
|
|
}(),
|
|
|
|
isOK: false,
|
|
|
|
info: "Processing a request where difference between from and to is > 24 should fail",
|
2018-05-21 11:30:37 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
|
|
s.T().Run(tc.info, func(*testing.T) {
|
|
|
|
request := s.createRequest(tc.params)
|
|
|
|
src := crypto.FromECDSAPub(&tc.params.key.PublicKey)
|
2018-10-18 10:25:00 +00:00
|
|
|
lower, upper, bloom, limit, _, err := s.server.validateRequest(src, request)
|
|
|
|
s.Equal(tc.isOK, err == nil)
|
|
|
|
if err == nil {
|
2018-05-22 15:51:05 +00:00
|
|
|
s.Equal(tc.params.low, lower)
|
|
|
|
s.Equal(tc.params.upp, upper)
|
2018-07-02 07:38:10 +00:00
|
|
|
s.Equal(tc.params.limit, limit)
|
2018-05-22 15:51:05 +00:00
|
|
|
s.Equal(whisper.TopicToBloom(tc.params.topic), bloom)
|
2018-07-02 07:38:10 +00:00
|
|
|
s.Equal(tc.expect, s.messageExists(env, tc.params.low, tc.params.upp, bloom, tc.params.limit))
|
2018-05-21 11:30:37 +00:00
|
|
|
|
2018-05-22 15:51:05 +00:00
|
|
|
src[0]++
|
2018-10-18 10:25:00 +00:00
|
|
|
_, _, _, _, _, err = s.server.validateRequest(src, request)
|
|
|
|
s.True(err == nil)
|
2018-05-21 11:30:37 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2018-05-17 11:21:04 +00:00
|
|
|
}
|
|
|
|
|
2018-10-19 09:09:13 +00:00
|
|
|
func (s *MailserverSuite) TestDecodeRequest() {
|
|
|
|
s.setupServer(s.server)
|
|
|
|
defer s.server.Close()
|
|
|
|
|
2018-12-11 10:23:47 +00:00
|
|
|
payload := MessagesRequestPayload{
|
2018-10-19 09:09:13 +00:00
|
|
|
Lower: 50,
|
|
|
|
Upper: 100,
|
|
|
|
Bloom: []byte{0x01},
|
|
|
|
Limit: 10,
|
|
|
|
Cursor: []byte{},
|
|
|
|
Batch: true,
|
|
|
|
}
|
|
|
|
data, err := rlp.EncodeToBytes(payload)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
id, err := s.shh.NewKeyPair()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
srcKey, err := s.shh.GetPrivateKey(id)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
env := s.createEnvelope(whisper.TopicType{0x01}, data, srcKey)
|
|
|
|
|
|
|
|
decodedPayload, err := s.server.decodeRequest(nil, env)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
s.Equal(payload, decodedPayload)
|
|
|
|
}
|
|
|
|
|
2019-03-12 11:14:35 +00:00
|
|
|
func (s *MailserverSuite) TestDecodeRequestNoUpper() {
|
|
|
|
s.setupServer(s.server)
|
|
|
|
defer s.server.Close()
|
|
|
|
|
|
|
|
payload := MessagesRequestPayload{
|
|
|
|
Lower: 50,
|
|
|
|
Bloom: []byte{0x01},
|
|
|
|
Limit: 10,
|
|
|
|
Cursor: []byte{},
|
|
|
|
Batch: true,
|
|
|
|
}
|
|
|
|
data, err := rlp.EncodeToBytes(payload)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
id, err := s.shh.NewKeyPair()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
srcKey, err := s.shh.GetPrivateKey(id)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
env := s.createEnvelope(whisper.TopicType{0x01}, data, srcKey)
|
|
|
|
|
|
|
|
decodedPayload, err := s.server.decodeRequest(nil, env)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
s.NotEqual(0, decodedPayload.Upper)
|
|
|
|
}
|
|
|
|
|
2019-02-22 08:55:37 +00:00
|
|
|
func (s *MailserverSuite) TestProcessRequestDeadlockHandling() {
|
|
|
|
s.setupServer(s.server)
|
|
|
|
defer s.server.Close()
|
|
|
|
|
|
|
|
var archievedEnvelopes []*whisper.Envelope
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
count := uint32(10)
|
|
|
|
|
|
|
|
// Archieve some envelopes.
|
|
|
|
for i := count; i > 0; i-- {
|
|
|
|
sentTime := now.Add(time.Duration(-i) * time.Second)
|
|
|
|
env, err := generateEnvelope(sentTime)
|
|
|
|
s.NoError(err)
|
|
|
|
s.server.Archive(env)
|
|
|
|
archievedEnvelopes = append(archievedEnvelopes, env)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare a request.
|
|
|
|
peerID, request, err := s.prepareRequest(archievedEnvelopes, 5)
|
|
|
|
s.NoError(err)
|
|
|
|
lower, upper, bloom, limit, cursor, err := s.server.validateRequest(peerID, request)
|
|
|
|
s.NoError(err)
|
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
Name string
|
|
|
|
Timeout time.Duration
|
|
|
|
Verify func(
|
|
|
|
iterator.Iterator,
|
|
|
|
time.Duration, // processRequestInBundles timeout
|
|
|
|
chan []*whisper.Envelope,
|
|
|
|
)
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
Name: "finish processing using `done` channel",
|
|
|
|
Timeout: time.Second * 5,
|
|
|
|
Verify: func(
|
|
|
|
iter iterator.Iterator,
|
|
|
|
timeout time.Duration,
|
|
|
|
bundles chan []*whisper.Envelope,
|
|
|
|
) {
|
|
|
|
done := make(chan struct{})
|
|
|
|
processFinished := make(chan struct{})
|
|
|
|
|
|
|
|
go func() {
|
2019-02-27 14:30:08 +00:00
|
|
|
s.server.processRequestInBundles(iter, bloom, int(limit), timeout, "req-01", bundles, done)
|
2019-02-22 08:55:37 +00:00
|
|
|
close(processFinished)
|
|
|
|
}()
|
|
|
|
go close(done)
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-processFinished:
|
|
|
|
case <-time.After(time.Second):
|
|
|
|
s.FailNow("waiting for processing finish timed out")
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "finish processing due to timeout",
|
|
|
|
Timeout: time.Second,
|
|
|
|
Verify: func(
|
|
|
|
iter iterator.Iterator,
|
|
|
|
timeout time.Duration,
|
|
|
|
bundles chan []*whisper.Envelope,
|
|
|
|
) {
|
|
|
|
done := make(chan struct{}) // won't be closed because we test timeout of `processRequestInBundles()`
|
|
|
|
processFinished := make(chan struct{})
|
|
|
|
|
|
|
|
go func() {
|
2019-02-27 14:30:08 +00:00
|
|
|
s.server.processRequestInBundles(iter, bloom, int(limit), time.Second, "req-01", bundles, done)
|
2019-02-22 08:55:37 +00:00
|
|
|
close(processFinished)
|
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-processFinished:
|
|
|
|
case <-time.After(time.Second * 5):
|
|
|
|
s.FailNow("waiting for processing finish timed out")
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
s.T().Run(tc.Name, func(t *testing.T) {
|
|
|
|
iter := s.server.createIterator(lower, upper, cursor)
|
|
|
|
defer iter.Release()
|
|
|
|
|
|
|
|
// Nothing reads from this unbuffered channel which simulates a situation
|
|
|
|
// when a connection between a peer and mail server was dropped.
|
|
|
|
bundles := make(chan []*whisper.Envelope)
|
|
|
|
|
|
|
|
tc.Verify(iter, tc.Timeout, bundles)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-02 07:38:10 +00:00
|
|
|
func (s *MailserverSuite) messageExists(envelope *whisper.Envelope, low, upp uint32, bloom []byte, limit uint32) bool {
|
2018-12-06 09:48:28 +00:00
|
|
|
receivedHashes, _, _ := processRequestAndCollectHashes(
|
|
|
|
s.server, low, upp, nil, bloom, int(limit),
|
|
|
|
)
|
|
|
|
for _, hash := range receivedHashes {
|
|
|
|
if hash == envelope.Hash() {
|
|
|
|
return true
|
2018-05-22 15:51:05 +00:00
|
|
|
}
|
|
|
|
}
|
2018-12-06 09:48:28 +00:00
|
|
|
return false
|
2018-05-22 15:51:05 +00:00
|
|
|
}
|
|
|
|
|
2018-05-21 11:30:37 +00:00
|
|
|
func (s *MailserverSuite) TestBloomFromReceivedMessage() {
|
|
|
|
testCases := []struct {
|
|
|
|
msg whisper.ReceivedMessage
|
|
|
|
expectedBloom []byte
|
|
|
|
expectedErr error
|
|
|
|
info string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
msg: whisper.ReceivedMessage{},
|
|
|
|
expectedBloom: []byte(nil),
|
|
|
|
expectedErr: errors.New("Undersized p2p request"),
|
|
|
|
info: "getting bloom filter for an empty whisper message should produce an error",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
msg: whisper.ReceivedMessage{Payload: []byte("hohohohoho")},
|
|
|
|
expectedBloom: []byte(nil),
|
|
|
|
expectedErr: errors.New("Undersized bloom filter in p2p request"),
|
|
|
|
info: "getting bloom filter for a malformed whisper message should produce an error",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
msg: whisper.ReceivedMessage{Payload: []byte("12345678")},
|
|
|
|
expectedBloom: whisper.MakeFullNodeBloom(),
|
|
|
|
expectedErr: nil,
|
|
|
|
info: "getting bloom filter for a valid whisper message should be successful",
|
|
|
|
},
|
|
|
|
}
|
2018-05-17 11:21:04 +00:00
|
|
|
|
2018-05-21 11:30:37 +00:00
|
|
|
for _, tc := range testCases {
|
|
|
|
s.T().Run(tc.info, func(*testing.T) {
|
|
|
|
bloom, err := s.server.bloomFromReceivedMessage(&tc.msg)
|
|
|
|
s.Equal(tc.expectedErr, err)
|
|
|
|
s.Equal(tc.expectedBloom, bloom)
|
|
|
|
})
|
|
|
|
}
|
2018-05-17 11:21:04 +00:00
|
|
|
}
|
|
|
|
|
2018-05-21 11:30:37 +00:00
|
|
|
func (s *MailserverSuite) setupServer(server *WMailServer) {
|
|
|
|
const password = "password_for_this_test"
|
2018-05-11 19:43:07 +00:00
|
|
|
|
2018-05-21 11:30:37 +00:00
|
|
|
s.shh = whisper.New(&whisper.DefaultConfig)
|
|
|
|
s.shh.RegisterServer(server)
|
|
|
|
|
2018-07-04 09:30:57 +00:00
|
|
|
err := server.Init(s.shh, ¶ms.WhisperConfig{
|
|
|
|
DataDir: s.dataDir,
|
|
|
|
MailServerPassword: password,
|
|
|
|
MinimumPoW: powRequirement,
|
|
|
|
})
|
2018-05-11 19:43:07 +00:00
|
|
|
if err != nil {
|
2018-05-21 11:30:37 +00:00
|
|
|
s.T().Fatal(err)
|
2018-05-11 19:43:07 +00:00
|
|
|
}
|
2018-05-21 11:30:37 +00:00
|
|
|
|
|
|
|
keyID, err = s.shh.AddSymKeyFromPassword(password)
|
2018-05-11 19:43:07 +00:00
|
|
|
if err != nil {
|
2018-05-21 11:30:37 +00:00
|
|
|
s.T().Fatalf("failed to create symmetric key for mail request: %s", err)
|
2018-05-11 19:43:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-22 08:55:37 +00:00
|
|
|
func (s *MailserverSuite) prepareRequest(envelopes []*whisper.Envelope, limit uint32) (
|
|
|
|
[]byte, *whisper.Envelope, error,
|
|
|
|
) {
|
|
|
|
if len(envelopes) == 0 {
|
|
|
|
return nil, nil, errors.New("envelopes is empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
|
|
|
params := s.defaultServerParams(envelopes[0])
|
|
|
|
params.low = uint32(now.Add(time.Duration(-len(envelopes)) * time.Second).Unix())
|
|
|
|
params.upp = uint32(now.Unix())
|
|
|
|
params.limit = limit
|
|
|
|
|
|
|
|
request := s.createRequest(params)
|
|
|
|
peerID := crypto.FromECDSAPub(¶ms.key.PublicKey)
|
|
|
|
|
|
|
|
return peerID, request, nil
|
|
|
|
}
|
|
|
|
|
2018-05-21 11:30:37 +00:00
|
|
|
func (s *MailserverSuite) defaultServerParams(env *whisper.Envelope) *ServerTestParams {
|
|
|
|
id, err := s.shh.NewKeyPair()
|
2018-05-11 19:43:07 +00:00
|
|
|
if err != nil {
|
2018-05-21 11:30:37 +00:00
|
|
|
s.T().Fatalf("failed to generate new key pair with seed %d: %s.", seed, err)
|
2018-05-11 19:43:07 +00:00
|
|
|
}
|
2018-05-21 11:30:37 +00:00
|
|
|
testPeerID, err := s.shh.GetPrivateKey(id)
|
2018-05-11 19:43:07 +00:00
|
|
|
if err != nil {
|
2018-05-21 11:30:37 +00:00
|
|
|
s.T().Fatalf("failed to retrieve new key pair with seed %d: %s.", seed, err)
|
2018-05-11 19:43:07 +00:00
|
|
|
}
|
|
|
|
birth := env.Expiry - env.TTL
|
2018-05-17 11:21:04 +00:00
|
|
|
|
|
|
|
return &ServerTestParams{
|
2018-05-11 19:43:07 +00:00
|
|
|
topic: env.Topic,
|
2018-05-17 11:21:04 +00:00
|
|
|
birth: birth,
|
2018-05-11 19:43:07 +00:00
|
|
|
low: birth - 1,
|
|
|
|
upp: birth + 1,
|
2018-07-02 07:38:10 +00:00
|
|
|
limit: 0,
|
2018-05-11 19:43:07 +00:00
|
|
|
key: testPeerID,
|
|
|
|
}
|
2018-05-17 11:21:04 +00:00
|
|
|
}
|
2018-05-11 19:43:07 +00:00
|
|
|
|
2018-05-21 11:30:37 +00:00
|
|
|
func (s *MailserverSuite) createRequest(p *ServerTestParams) *whisper.Envelope {
|
2018-05-11 19:43:07 +00:00
|
|
|
bloom := whisper.TopicToBloom(p.topic)
|
|
|
|
data := make([]byte, 8)
|
|
|
|
binary.BigEndian.PutUint32(data, p.low)
|
|
|
|
binary.BigEndian.PutUint32(data[4:], p.upp)
|
|
|
|
data = append(data, bloom...)
|
|
|
|
|
2018-07-02 07:38:10 +00:00
|
|
|
if p.limit != 0 {
|
|
|
|
limitData := make([]byte, 4)
|
|
|
|
binary.BigEndian.PutUint32(limitData, p.limit)
|
|
|
|
data = append(data, limitData...)
|
|
|
|
}
|
|
|
|
|
2018-10-19 09:09:13 +00:00
|
|
|
return s.createEnvelope(p.topic, data, p.key)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *MailserverSuite) createEnvelope(topic whisper.TopicType, data []byte, srcKey *ecdsa.PrivateKey) *whisper.Envelope {
|
2018-05-21 11:30:37 +00:00
|
|
|
key, err := s.shh.GetSymKey(keyID)
|
2018-05-11 19:43:07 +00:00
|
|
|
if err != nil {
|
2018-05-21 11:30:37 +00:00
|
|
|
s.T().Fatalf("failed to retrieve sym key with seed %d: %s.", seed, err)
|
2018-05-11 19:43:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
params := &whisper.MessageParams{
|
|
|
|
KeySym: key,
|
2018-10-19 09:09:13 +00:00
|
|
|
Topic: topic,
|
2018-05-11 19:43:07 +00:00
|
|
|
Payload: data,
|
|
|
|
PoW: powRequirement * 2,
|
|
|
|
WorkTime: 2,
|
2018-10-19 09:09:13 +00:00
|
|
|
Src: srcKey,
|
2018-05-11 19:43:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
msg, err := whisper.NewSentMessage(params)
|
|
|
|
if err != nil {
|
2018-05-21 11:30:37 +00:00
|
|
|
s.T().Fatalf("failed to create new message with seed %d: %s.", seed, err)
|
2018-05-11 19:43:07 +00:00
|
|
|
}
|
2018-10-19 09:09:13 +00:00
|
|
|
|
2018-05-11 19:43:07 +00:00
|
|
|
env, err := msg.Wrap(params, time.Now())
|
|
|
|
if err != nil {
|
2018-05-21 11:30:37 +00:00
|
|
|
s.T().Fatalf("failed to wrap with seed %d: %s.", seed, err)
|
2018-05-11 19:43:07 +00:00
|
|
|
}
|
|
|
|
return env
|
|
|
|
}
|
2018-05-17 11:21:04 +00:00
|
|
|
|
2018-07-16 09:07:17 +00:00
|
|
|
func generateEnvelopeWithKeys(sentTime time.Time, keySym []byte, keyAsym *ecdsa.PublicKey) (*whisper.Envelope, error) {
|
2018-05-21 11:30:37 +00:00
|
|
|
params := &whisper.MessageParams{
|
|
|
|
Topic: whisper.TopicType{0x1F, 0x7E, 0xA1, 0x7F},
|
2018-07-16 09:07:17 +00:00
|
|
|
Payload: testPayload,
|
2018-05-21 11:30:37 +00:00
|
|
|
PoW: powRequirement,
|
|
|
|
WorkTime: 2,
|
2018-05-17 11:21:04 +00:00
|
|
|
}
|
|
|
|
|
2018-07-16 09:07:17 +00:00
|
|
|
if len(keySym) > 0 {
|
|
|
|
params.KeySym = keySym
|
|
|
|
} else if keyAsym != nil {
|
|
|
|
params.Dst = keyAsym
|
|
|
|
}
|
|
|
|
|
2018-05-21 11:30:37 +00:00
|
|
|
msg, err := whisper.NewSentMessage(params)
|
2018-05-17 11:21:04 +00:00
|
|
|
if err != nil {
|
2018-05-21 11:30:37 +00:00
|
|
|
return nil, fmt.Errorf("failed to create new message with seed %d: %s", seed, err)
|
2018-05-17 11:21:04 +00:00
|
|
|
}
|
2018-05-21 11:30:37 +00:00
|
|
|
env, err := msg.Wrap(params, sentTime)
|
2018-05-17 11:21:04 +00:00
|
|
|
if err != nil {
|
2018-05-21 11:30:37 +00:00
|
|
|
return nil, fmt.Errorf("failed to wrap with seed %d: %s", seed, err)
|
2018-05-17 11:21:04 +00:00
|
|
|
}
|
2018-05-21 11:30:37 +00:00
|
|
|
|
|
|
|
return env, nil
|
2018-05-17 11:21:04 +00:00
|
|
|
}
|
2018-07-16 09:07:17 +00:00
|
|
|
|
|
|
|
func generateEnvelope(sentTime time.Time) (*whisper.Envelope, error) {
|
|
|
|
h := crypto.Keccak256Hash([]byte("test sample data"))
|
|
|
|
return generateEnvelopeWithKeys(sentTime, h[:], nil)
|
|
|
|
}
|
2018-10-28 22:33:58 +00:00
|
|
|
|
2018-12-06 09:48:28 +00:00
|
|
|
func processRequestAndCollectHashes(
|
2018-12-11 10:23:47 +00:00
|
|
|
server *WMailServer, lower, upper uint32, cursor []byte, bloom []byte, limit int,
|
|
|
|
) ([]common.Hash, []byte, common.Hash) {
|
2018-12-06 09:48:28 +00:00
|
|
|
iter := server.createIterator(lower, upper, cursor)
|
|
|
|
defer iter.Release()
|
|
|
|
bundles := make(chan []*whisper.Envelope, 10)
|
|
|
|
done := make(chan struct{})
|
|
|
|
|
|
|
|
var hashes []common.Hash
|
|
|
|
go func() {
|
|
|
|
for bundle := range bundles {
|
|
|
|
for _, env := range bundle {
|
|
|
|
hashes = append(hashes, env.Hash())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
close(done)
|
|
|
|
}()
|
|
|
|
|
2019-02-27 14:30:08 +00:00
|
|
|
cursor, lastHash := server.processRequestInBundles(iter, bloom, limit, time.Minute, "req-01", bundles, done)
|
2018-12-06 09:48:28 +00:00
|
|
|
|
|
|
|
<-done
|
|
|
|
|
|
|
|
return hashes, cursor, lastHash
|
|
|
|
}
|
|
|
|
|
2018-10-28 22:33:58 +00:00
|
|
|
// mockPeerWithID is a struct that conforms to peerWithID interface.
|
|
|
|
type mockPeerWithID struct {
|
|
|
|
id []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p mockPeerWithID) ID() []byte { return p.id }
|
|
|
|
|
|
|
|
func TestPeerIDString(t *testing.T) {
|
|
|
|
a := []byte{0x01, 0x02, 0x03}
|
|
|
|
require.Equal(t, "010203", peerIDString(&mockPeerWithID{a}))
|
|
|
|
require.Equal(t, "010203", peerIDBytesString(a))
|
|
|
|
}
|