status-go/t/e2e/whisper/whisper_mailbox_test.go
Dmitry Shulyak 357786eeca Fix bloom filter expectations in group chat test
Every peer must be subscribed to the topic that is used to send messages.
In the test Alice was communicating with Bob and Charlie over custom topic, but
that topic wasn't added to a bloom filter, thus a certain flake was possible.

Normally it wasn't causing problems because syncAllowance in whisper, which is 10s:
- we set bloom filter to all zeros
- but we still will accept all envelopes for 10s
- in case we send first envelope into such channel after sync allowance - we will get an error such
that envelope doesn't match the bloom filter
2018-05-04 10:09:27 +03:00

516 lines
18 KiB
Go

package whisper
import (
"encoding/json"
"path/filepath"
"strconv"
"testing"
"time"
"os"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p/discover"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
"github.com/status-im/status-go/geth/api"
"github.com/status-im/status-go/geth/rpc"
. "github.com/status-im/status-go/t/utils"
"github.com/stretchr/testify/suite"
)
type WhisperMailboxSuite struct {
suite.Suite
}
func TestWhisperMailboxTestSuite(t *testing.T) {
suite.Run(t, new(WhisperMailboxSuite))
}
func (s *WhisperMailboxSuite) TestRequestMessageFromMailboxAsync() {
var err error
// Start mailbox and status node.
mailboxBackend, stop := s.startMailboxBackend()
defer stop()
s.Require().True(mailboxBackend.IsNodeRunning())
mailboxNode := mailboxBackend.StatusNode().GethNode()
mailboxEnode := mailboxNode.Server().NodeInfo().Enode
sender, stop := s.startBackend("sender")
defer stop()
s.Require().True(sender.IsNodeRunning())
node := sender.StatusNode().GethNode()
s.Require().NotEqual(mailboxEnode, node.Server().NodeInfo().Enode)
err = sender.StatusNode().AddPeer(mailboxEnode)
s.Require().NoError(err)
// Wait async processes on adding peer.
time.Sleep(time.Second)
senderWhisperService, err := sender.StatusNode().WhisperService()
s.Require().NoError(err)
// Mark mailbox node trusted.
parsedNode, err := discover.ParseNode(mailboxNode.Server().NodeInfo().Enode)
s.Require().NoError(err)
mailboxPeer := parsedNode.ID[:]
mailboxPeerStr := parsedNode.ID.String()
err = senderWhisperService.AllowP2PMessagesFromPeer(mailboxPeer)
s.Require().NoError(err)
// Generate mailbox symkey.
password := "status-offline-inbox"
MailServerKeyID, err := senderWhisperService.AddSymKeyFromPassword(password)
s.Require().NoError(err)
rpcClient := sender.StatusNode().RPCClient()
s.Require().NotNil(rpcClient)
// Create topic.
topic := whisper.BytesToTopic([]byte("topic name"))
// Add key pair to whisper.
keyID, err := senderWhisperService.NewKeyPair()
s.Require().NoError(err)
key, err := senderWhisperService.GetPrivateKey(keyID)
s.Require().NoError(err)
pubkey := hexutil.Bytes(crypto.FromECDSAPub(&key.PublicKey))
// Create message filter.
messageFilterID := s.createPrivateChatMessageFilter(rpcClient, keyID, topic.String())
// There are no messages at filter.
messages := s.getMessagesByMessageFilterID(rpcClient, messageFilterID)
s.Require().Equal(0, len(messages))
// Post message matching with filter (key and topic).
s.postMessageToPrivate(rpcClient, pubkey.String(), topic.String(), hexutil.Encode([]byte("Hello world!")))
// Get message to make sure that it will come from the mailbox later.
time.Sleep(1 * time.Second)
messages = s.getMessagesByMessageFilterID(rpcClient, messageFilterID)
s.Require().Equal(1, len(messages))
// Act.
// Request messages (including the previous one, expired) from mailbox.
reqMessagesBody := `{
"jsonrpc": "2.0",
"id": 1,
"method": "shhext_requestMessages",
"params": [{
"mailServerPeer":"` + mailboxPeerStr + `",
"topic":"` + topic.String() + `",
"symKeyID":"` + MailServerKeyID + `",
"from":0,
"to":` + strconv.FormatInt(time.Now().Unix(), 10) + `
}]
}`
resp := rpcClient.CallRaw(reqMessagesBody)
reqMessagesResp := baseRPCResponse{}
err = json.Unmarshal([]byte(resp), &reqMessagesResp)
s.Require().NoError(err)
s.Require().Nil(reqMessagesResp.Error)
// Wait to receive message.
time.Sleep(time.Second)
// And we receive message, it comes from mailbox.
messages = s.getMessagesByMessageFilterID(rpcClient, messageFilterID)
s.Require().Equal(1, len(messages))
// Check that there are no messages.
messages = s.getMessagesByMessageFilterID(rpcClient, messageFilterID)
s.Require().Equal(0, len(messages))
}
func (s *WhisperMailboxSuite) TestRequestMessagesInGroupChat() {
var err error
// Start mailbox, alice, bob, charlie node.
mailboxBackend, stop := s.startMailboxBackend()
defer stop()
aliceBackend, stop := s.startBackend("alice")
defer stop()
bobBackend, stop := s.startBackend("bob")
defer stop()
charlieBackend, stop := s.startBackend("charlie")
defer stop()
// Add mailbox to static peers.
s.Require().True(mailboxBackend.IsNodeRunning())
mailboxNode := mailboxBackend.StatusNode().GethNode()
mailboxEnode := mailboxNode.Server().NodeInfo().Enode
err = aliceBackend.StatusNode().AddPeer(mailboxEnode)
s.Require().NoError(err)
err = bobBackend.StatusNode().AddPeer(mailboxEnode)
s.Require().NoError(err)
err = charlieBackend.StatusNode().AddPeer(mailboxEnode)
s.Require().NoError(err)
// Wait async processes on adding peer.
time.Sleep(time.Second)
// Get whisper service.
aliceWhisperService, err := aliceBackend.StatusNode().WhisperService()
s.Require().NoError(err)
bobWhisperService, err := bobBackend.StatusNode().WhisperService()
s.Require().NoError(err)
charlieWhisperService, err := charlieBackend.StatusNode().WhisperService()
s.Require().NoError(err)
// Get rpc client.
aliceRPCClient := aliceBackend.StatusNode().RPCClient()
bobRPCClient := bobBackend.StatusNode().RPCClient()
charlieRPCClient := charlieBackend.StatusNode().RPCClient()
// Bob and charlie add the mailserver key.
password := "status-offline-inbox"
bobMailServerKeyID, err := bobWhisperService.AddSymKeyFromPassword(password)
s.Require().NoError(err)
charlieMailServerKeyID, err := charlieWhisperService.AddSymKeyFromPassword(password)
s.Require().NoError(err)
// Generate a group chat symkey and topic.
groupChatKeyID, err := aliceWhisperService.GenerateSymKey()
s.Require().NoError(err)
groupChatKey, err := aliceWhisperService.GetSymKey(groupChatKeyID)
s.Require().NoError(err)
// Generate a group chat topic.
groupChatTopic := whisper.BytesToTopic([]byte("groupChatTopic"))
// sender must be subscribed to message topic it sends
s.NotNil(s.createGroupChatMessageFilter(aliceRPCClient, groupChatKeyID, groupChatTopic.String()))
groupChatPayload := newGroupChatParams(groupChatKey, groupChatTopic)
payloadStr, err := groupChatPayload.Encode()
s.Require().NoError(err)
// Add Bob and Charlie's key pairs to receive the symmetric key for the group chat from Alice.
bobKeyID, err := bobWhisperService.NewKeyPair()
s.Require().NoError(err)
bobKey, err := bobWhisperService.GetPrivateKey(bobKeyID)
s.Require().NoError(err)
bobPubkey := hexutil.Bytes(crypto.FromECDSAPub(&bobKey.PublicKey))
bobAliceKeySendTopic := whisper.BytesToTopic([]byte("bobAliceKeySendTopic "))
charlieKeyID, err := charlieWhisperService.NewKeyPair()
s.Require().NoError(err)
charlieKey, err := charlieWhisperService.GetPrivateKey(charlieKeyID)
s.Require().NoError(err)
charliePubkey := hexutil.Bytes(crypto.FromECDSAPub(&charlieKey.PublicKey))
charlieAliceKeySendTopic := whisper.BytesToTopic([]byte("charlieAliceKeySendTopic "))
// Alice must add peers topics into her own bloom filter.
aliceKeyID, err := aliceWhisperService.NewKeyPair()
s.Require().NoError(err)
s.createPrivateChatMessageFilter(aliceRPCClient, aliceKeyID, bobAliceKeySendTopic.String())
s.createPrivateChatMessageFilter(aliceRPCClient, aliceKeyID, charlieAliceKeySendTopic.String())
// Bob and charlie create message filter.
bobMessageFilterID := s.createPrivateChatMessageFilter(bobRPCClient, bobKeyID, bobAliceKeySendTopic.String())
charlieMessageFilterID := s.createPrivateChatMessageFilter(charlieRPCClient, charlieKeyID, charlieAliceKeySendTopic.String())
// Alice send message with symkey and topic to Bob and Charlie.
s.postMessageToPrivate(aliceRPCClient, bobPubkey.String(), bobAliceKeySendTopic.String(), payloadStr)
s.postMessageToPrivate(aliceRPCClient, charliePubkey.String(), charlieAliceKeySendTopic.String(), payloadStr)
// Wait to receive.
time.Sleep(5 * time.Second)
// Bob receive group chat data and add it to his node.
// Bob get group chat details.
messages := s.getMessagesByMessageFilterID(bobRPCClient, bobMessageFilterID)
s.Require().Equal(1, len(messages))
bobGroupChatData := groupChatParams{}
err = bobGroupChatData.Decode(messages[0]["payload"].(string))
s.Require().NoError(err)
s.EqualValues(groupChatPayload, bobGroupChatData)
// Bob add symkey to his node.
bobGroupChatSymkeyID := s.addSymKey(bobRPCClient, bobGroupChatData.Key)
s.Require().NotEmpty(bobGroupChatSymkeyID)
// Bob create message filter to node by group chat topic.
bobGroupChatMessageFilterID := s.createGroupChatMessageFilter(bobRPCClient, bobGroupChatSymkeyID, bobGroupChatData.Topic)
// Charlie receive group chat data and add it to his node.
// Charlie get group chat details.
messages = s.getMessagesByMessageFilterID(charlieRPCClient, charlieMessageFilterID)
s.Require().Equal(1, len(messages))
charlieGroupChatData := groupChatParams{}
err = charlieGroupChatData.Decode(messages[0]["payload"].(string))
s.Require().NoError(err)
s.EqualValues(groupChatPayload, charlieGroupChatData)
// Charlie add symkey to his node.
charlieGroupChatSymkeyID := s.addSymKey(charlieRPCClient, charlieGroupChatData.Key)
s.Require().NotEmpty(charlieGroupChatSymkeyID)
// Charlie create message filter to node by group chat topic.
charlieGroupChatMessageFilterID := s.createGroupChatMessageFilter(charlieRPCClient, charlieGroupChatSymkeyID, charlieGroupChatData.Topic)
// Alice send message to group chat.
helloWorldMessage := hexutil.Encode([]byte("Hello world!"))
s.postMessageToGroup(aliceRPCClient, groupChatKeyID, groupChatTopic.String(), helloWorldMessage)
// It need to receive envelopes by bob and charlie nodes.
time.Sleep(5 * time.Second)
// Bob receive group chat message.
messages = s.getMessagesByMessageFilterID(bobRPCClient, bobGroupChatMessageFilterID)
s.Require().Equal(1, len(messages))
s.Require().Equal(helloWorldMessage, messages[0]["payload"].(string))
// Charlie receive group chat message.
messages = s.getMessagesByMessageFilterID(charlieRPCClient, charlieGroupChatMessageFilterID)
s.Require().Equal(1, len(messages))
s.Require().Equal(helloWorldMessage, messages[0]["payload"].(string))
// Check that we don't receive messages each one time.
messages = s.getMessagesByMessageFilterID(bobRPCClient, bobGroupChatMessageFilterID)
s.Require().Equal(0, len(messages))
messages = s.getMessagesByMessageFilterID(charlieRPCClient, charlieGroupChatMessageFilterID)
s.Require().Equal(0, len(messages))
// Request each one messages from mailbox using enode.
s.requestHistoricMessages(bobRPCClient, mailboxEnode, bobMailServerKeyID, groupChatTopic.String())
s.requestHistoricMessages(charlieRPCClient, mailboxEnode, charlieMailServerKeyID, groupChatTopic.String())
// Wait to receive p2p messages.
time.Sleep(5 * time.Second)
// Bob receive p2p message from group chat filter.
messages = s.getMessagesByMessageFilterID(bobRPCClient, bobGroupChatMessageFilterID)
s.Require().Equal(1, len(messages))
s.Require().Equal(helloWorldMessage, messages[0]["payload"].(string))
// Charlie receive p2p message from group chat filter.
messages = s.getMessagesByMessageFilterID(charlieRPCClient, charlieGroupChatMessageFilterID)
s.Require().Equal(1, len(messages))
s.Require().Equal(helloWorldMessage, messages[0]["payload"].(string))
}
func newGroupChatParams(symkey []byte, topic whisper.TopicType) groupChatParams {
groupChatKeyStr := hexutil.Bytes(symkey).String()
return groupChatParams{
Key: groupChatKeyStr,
Topic: topic.String(),
}
}
type groupChatParams struct {
Key string
Topic string
}
func (d *groupChatParams) Decode(i string) error {
b, err := hexutil.Decode(i)
if err != nil {
return err
}
return json.Unmarshal(b, &d)
}
func (d *groupChatParams) Encode() (string, error) {
payload, err := json.Marshal(d)
if err != nil {
return "", err
}
return hexutil.Bytes(payload).String(), nil
}
// Start status node.
func (s *WhisperMailboxSuite) startBackend(name string) (*api.StatusBackend, func()) {
datadir := filepath.Join(RootDir, ".ethereumtest/mailbox", name)
backend := api.NewStatusBackend()
nodeConfig, err := MakeTestNodeConfig(GetNetworkID())
nodeConfig.DataDir = datadir
s.Require().NoError(err)
s.Require().False(backend.IsNodeRunning())
nodeConfig.WhisperConfig.LightClient = true
if addr, err := GetRemoteURL(); err == nil {
nodeConfig.UpstreamConfig.Enabled = true
nodeConfig.UpstreamConfig.URL = addr
}
s.Require().NoError(backend.StartNode(nodeConfig))
s.Require().True(backend.IsNodeRunning())
return backend, func() {
s.True(backend.IsNodeRunning())
s.NoError(backend.StopNode())
s.False(backend.IsNodeRunning())
err = os.RemoveAll(datadir)
s.Require().NoError(err)
}
}
// Start mailbox node.
func (s *WhisperMailboxSuite) startMailboxBackend() (*api.StatusBackend, func()) {
mailboxBackend := api.NewStatusBackend()
mailboxConfig, err := MakeTestNodeConfig(GetNetworkID())
s.Require().NoError(err)
datadir := filepath.Join(RootDir, ".ethereumtest/mailbox/mailserver")
mailboxConfig.LightEthConfig.Enabled = false
mailboxConfig.WhisperConfig.Enabled = true
mailboxConfig.KeyStoreDir = datadir
mailboxConfig.WhisperConfig.EnableMailServer = true
mailboxConfig.WhisperConfig.PasswordFile = filepath.Join(RootDir, "/static/keys/wnodepassword")
mailboxConfig.WhisperConfig.DataDir = filepath.Join(datadir, "data")
mailboxConfig.DataDir = datadir
s.Require().False(mailboxBackend.IsNodeRunning())
s.Require().NoError(mailboxBackend.StartNode(mailboxConfig))
s.Require().True(mailboxBackend.IsNodeRunning())
return mailboxBackend, func() {
s.True(mailboxBackend.IsNodeRunning())
s.NoError(mailboxBackend.StopNode())
s.False(mailboxBackend.IsNodeRunning())
err = os.RemoveAll(datadir)
s.Require().NoError(err)
}
}
// createPrivateChatMessageFilter create message filter with asymmetric encryption.
func (s *WhisperMailboxSuite) createPrivateChatMessageFilter(rpcCli *rpc.Client, privateKeyID string, topic string) string {
resp := rpcCli.CallRaw(`{
"jsonrpc": "2.0",
"method": "shh_newMessageFilter", "params": [
{"privateKeyID": "` + privateKeyID + `", "topics": [ "` + topic + `"], "allowP2P":true}
],
"id": 1
}`)
msgFilterResp := returnedIDResponse{}
err := json.Unmarshal([]byte(resp), &msgFilterResp)
messageFilterID := msgFilterResp.Result
s.Require().NoError(err)
s.Require().Nil(msgFilterResp.Error)
s.Require().NotEqual("", messageFilterID, resp)
return messageFilterID
}
// createGroupChatMessageFilter create message filter with symmetric encryption.
func (s *WhisperMailboxSuite) createGroupChatMessageFilter(rpcCli *rpc.Client, symkeyID string, topic string) string {
resp := rpcCli.CallRaw(`{
"jsonrpc": "2.0",
"method": "shh_newMessageFilter", "params": [
{"symKeyID": "` + symkeyID + `", "topics": [ "` + topic + `"], "allowP2P":true}
],
"id": 1
}`)
msgFilterResp := returnedIDResponse{}
err := json.Unmarshal([]byte(resp), &msgFilterResp)
messageFilterID := msgFilterResp.Result
s.Require().NoError(err)
s.Require().Nil(msgFilterResp.Error)
s.Require().NotEqual("", messageFilterID, resp)
return messageFilterID
}
func (s *WhisperMailboxSuite) postMessageToPrivate(rpcCli *rpc.Client, bobPubkey string, topic string, payload string) {
resp := rpcCli.CallRaw(`{
"jsonrpc": "2.0",
"method": "shh_post",
"params": [
{
"pubKey": "` + bobPubkey + `",
"topic": "` + topic + `",
"payload": "` + payload + `",
"powTarget": 0.001,
"powTime": 2
}
],
"id": 1}`)
postResp := baseRPCResponse{}
err := json.Unmarshal([]byte(resp), &postResp)
s.Require().NoError(err)
s.Require().Nil(postResp.Error)
}
func (s *WhisperMailboxSuite) postMessageToGroup(rpcCli *rpc.Client, groupChatKeyID string, topic string, payload string) {
resp := rpcCli.CallRaw(`{
"jsonrpc": "2.0",
"method": "shh_post",
"params": [
{
"symKeyID": "` + groupChatKeyID + `",
"topic": "` + topic + `",
"payload": "` + payload + `",
"powTarget": 0.001,
"powTime": 2
}
],
"id": 1}`)
postResp := baseRPCResponse{}
err := json.Unmarshal([]byte(resp), &postResp)
s.Require().NoError(err)
s.Require().Nil(postResp.Error)
}
// getMessagesByMessageFilterID gets received messages by messageFilterID.
func (s *WhisperMailboxSuite) getMessagesByMessageFilterID(rpcCli *rpc.Client, messageFilterID string) []map[string]interface{} {
resp := rpcCli.CallRaw(`{
"jsonrpc": "2.0",
"method": "shh_getFilterMessages",
"params": ["` + messageFilterID + `"],
"id": 1}`)
messages := getFilterMessagesResponse{}
err := json.Unmarshal([]byte(resp), &messages)
s.Require().NoError(err)
s.Require().Nil(messages.Error)
return messages.Result
}
// addSymKey added symkey to node and return symkeyID.
func (s *WhisperMailboxSuite) addSymKey(rpcCli *rpc.Client, symkey string) string {
resp := rpcCli.CallRaw(`{"jsonrpc":"2.0","method":"shh_addSymKey",
"params":["` + symkey + `"],
"id":1}`)
symkeyAddResp := returnedIDResponse{}
err := json.Unmarshal([]byte(resp), &symkeyAddResp)
s.Require().NoError(err)
s.Require().Nil(symkeyAddResp.Error)
symkeyID := symkeyAddResp.Result
s.Require().NotEmpty(symkeyID)
return symkeyID
}
// requestHistoricMessages asks a mailnode to resend messages.
func (s *WhisperMailboxSuite) requestHistoricMessages(rpcCli *rpc.Client, mailboxEnode, mailServerKeyID, topic string) {
resp := rpcCli.CallRaw(`{
"jsonrpc": "2.0",
"id": 2,
"method": "shhext_requestMessages",
"params": [{
"mailServerPeer":"` + mailboxEnode + `",
"topic":"` + topic + `",
"symKeyID":"` + mailServerKeyID + `",
"from":0,
"to":` + strconv.FormatInt(time.Now().Unix(), 10) + `
}]
}`)
reqMessagesResp := baseRPCResponse{}
err := json.Unmarshal([]byte(resp), &reqMessagesResp)
s.Require().NoError(err)
s.Require().Nil(reqMessagesResp.Error)
}
type getFilterMessagesResponse struct {
Result []map[string]interface{}
Error interface{}
}
type returnedIDResponse struct {
Result string
Error interface{}
}
type baseRPCResponse struct {
Result interface{}
Error interface{}
}