Re-send messages with ResendAutomatically=true
This commit is contained in:
parent
c433908834
commit
384543d3a6
|
@ -210,6 +210,7 @@ func newCommunitiesTestMessenger(shh types.Waku, privateKey *ecdsa.PrivateKey, l
|
|||
WithMultiAccounts(madb),
|
||||
WithAccount(iai.ToMultiAccount()),
|
||||
WithDatasync(),
|
||||
WithResendParams(3, 3),
|
||||
WithTokenManager(tokenManager),
|
||||
}
|
||||
|
||||
|
|
|
@ -82,9 +82,6 @@ const (
|
|||
privateChat chatContext = "private-chat"
|
||||
)
|
||||
|
||||
const messageResendMinDelay = 30
|
||||
const messageResendMaxCount = 3
|
||||
|
||||
var communityAdvertiseIntervalSecond int64 = 60 * 60
|
||||
|
||||
// messageCacheIntervalMs is how long we should keep processed messages in the cache, in ms
|
||||
|
@ -287,7 +284,7 @@ func NewMessenger(
|
|||
) (*Messenger, error) {
|
||||
var messenger *Messenger
|
||||
|
||||
c := config{}
|
||||
c := config{messageResendMinDelay: 30, messageResendMaxCount: 3}
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(&c); err != nil {
|
||||
|
@ -667,22 +664,9 @@ func (m *Messenger) processSentMessages(ids []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func shouldResendMessage(message *common.RawMessage, t common.TimeSource) (bool, error) {
|
||||
if !(message.MessageType == protobuf.ApplicationMetadataMessage_EMOJI_REACTION ||
|
||||
message.MessageType == protobuf.ApplicationMetadataMessage_CHAT_MESSAGE) {
|
||||
return false, errors.Errorf("Should resend only specific types of messages, can't resend %v", message.MessageType)
|
||||
}
|
||||
|
||||
if message.Sent {
|
||||
return false, errors.New("Should resend only non-sent messages")
|
||||
}
|
||||
|
||||
if message.SendCount > messageResendMaxCount {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (m *Messenger) shouldResendMessage(message *common.RawMessage, t common.TimeSource) (bool, error) {
|
||||
//exponential backoff depends on how many attempts to send message already made
|
||||
backoff := uint64(math.Pow(2, float64(message.SendCount-1))) * messageResendMinDelay * uint64(time.Second.Milliseconds())
|
||||
backoff := uint64(math.Pow(2, float64(message.SendCount-1))) * uint64(m.config.messageResendMinDelay) * uint64(time.Second.Milliseconds())
|
||||
backoffElapsed := t.GetCurrentTime() > (message.LastSent + backoff)
|
||||
return backoffElapsed, nil
|
||||
}
|
||||
|
@ -692,7 +676,7 @@ func (m *Messenger) resendExpiredMessages() error {
|
|||
return errors.New("offline")
|
||||
}
|
||||
|
||||
ids, err := m.persistence.ExpiredMessagesIDs(messageResendMaxCount)
|
||||
ids, err := m.persistence.ExpiredMessagesIDs(m.config.messageResendMaxCount)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Can't get expired reactions from db")
|
||||
}
|
||||
|
@ -712,7 +696,7 @@ func (m *Messenger) resendExpiredMessages() error {
|
|||
return errors.New("Only public chats and community chats messages are resent")
|
||||
}
|
||||
|
||||
ok, err = shouldResendMessage(rawMessage, m.getTimesource())
|
||||
ok, err = m.shouldResendMessage(rawMessage, m.getTimesource())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -109,6 +109,9 @@ type config struct {
|
|||
|
||||
telemetryServerURL string
|
||||
wakuService *wakuv2.Waku
|
||||
|
||||
messageResendMinDelay int
|
||||
messageResendMaxCount int
|
||||
}
|
||||
|
||||
type Option func(*config) error
|
||||
|
@ -136,6 +139,14 @@ func WithVerifyTransactionClient(client EthClient) Option {
|
|||
}
|
||||
}
|
||||
|
||||
func WithResendParams(minDelay int, maxCount int) Option {
|
||||
return func(c *config) error {
|
||||
c.messageResendMinDelay = minDelay
|
||||
c.messageResendMaxCount = maxCount
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithDatabase(db *sql.DB) Option {
|
||||
return func(c *config) error {
|
||||
c.appDb = db
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
package protocol
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"go.uber.org/zap"
|
||||
|
||||
gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/protocol/common"
|
||||
"github.com/status-im/status-go/protocol/communities"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
"github.com/status-im/status-go/protocol/requests"
|
||||
"github.com/status-im/status-go/protocol/tt"
|
||||
)
|
||||
|
||||
type MessengerOfflineSuite struct {
|
||||
suite.Suite
|
||||
|
||||
owner *Messenger
|
||||
bob *Messenger
|
||||
alice *Messenger
|
||||
|
||||
ownerWaku types.Waku
|
||||
bobWaku types.Waku
|
||||
aliceWaku types.Waku
|
||||
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func TestMessengerOfflineSuite(t *testing.T) {
|
||||
suite.Run(t, new(MessengerOfflineSuite))
|
||||
}
|
||||
|
||||
func (s *MessengerOfflineSuite) SetupTest() {
|
||||
s.logger = tt.MustCreateTestLogger()
|
||||
|
||||
wakuNodes := CreateWakuV2Network(&s.Suite, s.logger, []string{"owner", "bob", "alice"})
|
||||
|
||||
ownerLogger := s.logger.With(zap.String("name", "owner"))
|
||||
s.ownerWaku = wakuNodes[0]
|
||||
s.owner = s.newMessenger(s.ownerWaku, ownerLogger)
|
||||
|
||||
bobLogger := s.logger.With(zap.String("name", "bob"))
|
||||
s.bobWaku = wakuNodes[1]
|
||||
s.bob = s.newMessenger(s.bobWaku, bobLogger)
|
||||
|
||||
aliceLogger := s.logger.With(zap.String("name", "alice"))
|
||||
s.aliceWaku = wakuNodes[2]
|
||||
s.alice = s.newMessenger(s.aliceWaku, aliceLogger)
|
||||
|
||||
_, err := s.owner.Start()
|
||||
s.Require().NoError(err)
|
||||
_, err = s.bob.Start()
|
||||
s.Require().NoError(err)
|
||||
_, err = s.alice.Start()
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.owner.communitiesManager.RekeyInterval = 50 * time.Millisecond
|
||||
}
|
||||
|
||||
func (s *MessengerOfflineSuite) TearDownTest() {
|
||||
if s.owner != nil {
|
||||
s.Require().NoError(s.owner.Shutdown())
|
||||
}
|
||||
if s.ownerWaku != nil {
|
||||
s.Require().NoError(gethbridge.GetGethWakuV2From(s.ownerWaku).Stop())
|
||||
}
|
||||
|
||||
if s.bob != nil {
|
||||
s.Require().NoError(s.bob.Shutdown())
|
||||
}
|
||||
if s.bobWaku != nil {
|
||||
s.Require().NoError(gethbridge.GetGethWakuV2From(s.bobWaku).Stop())
|
||||
}
|
||||
if s.alice != nil {
|
||||
s.Require().NoError(s.alice.Shutdown())
|
||||
}
|
||||
if s.aliceWaku != nil {
|
||||
s.Require().NoError(gethbridge.GetGethWakuV2From(s.aliceWaku).Stop())
|
||||
}
|
||||
_ = s.logger.Sync()
|
||||
}
|
||||
|
||||
func (s *MessengerOfflineSuite) newMessenger(waku types.Waku, logger *zap.Logger) *Messenger {
|
||||
privateKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
m, err := newCommunitiesTestMessenger(waku, privateKey, logger, nil, nil, nil)
|
||||
s.Require().NoError(err)
|
||||
return m
|
||||
}
|
||||
|
||||
func (s *MessengerOfflineSuite) advertiseCommunityTo(community *communities.Community, owner *Messenger, user *Messenger) {
|
||||
advertiseCommunityTo(&s.Suite, community, owner, user)
|
||||
}
|
||||
|
||||
func (s *MessengerOfflineSuite) joinCommunity(community *communities.Community, owner *Messenger, user *Messenger) {
|
||||
request := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
|
||||
joinCommunity(&s.Suite, community, owner, user, request, "")
|
||||
}
|
||||
|
||||
func (s *MessengerOfflineSuite) TestCommunityOfflineEdit() {
|
||||
community, chat := createCommunity(&s.Suite, s.owner)
|
||||
|
||||
chatID := chat.ID
|
||||
inputMessage := common.NewMessage()
|
||||
inputMessage.ChatId = chatID
|
||||
inputMessage.ContentType = protobuf.ChatMessage_TEXT_PLAIN
|
||||
inputMessage.Text = "some text"
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
s.advertiseCommunityTo(community, s.owner, s.alice)
|
||||
s.joinCommunity(community, s.owner, s.alice)
|
||||
|
||||
_, err := s.alice.SendChatMessage(ctx, inputMessage)
|
||||
s.Require().NoError(err)
|
||||
s.checkMessageDelivery(ctx, inputMessage)
|
||||
|
||||
// Simulate going offline
|
||||
wakuv2 := gethbridge.GetGethWakuV2From(s.aliceWaku)
|
||||
wakuv2.SkipPublishToTopic(true)
|
||||
|
||||
resp, err := s.alice.SendChatMessage(ctx, inputMessage)
|
||||
messageID := types.Hex2Bytes(resp.Messages()[0].ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Check that message is re-sent once back online
|
||||
wakuv2.SkipPublishToTopic(false)
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
s.checkMessageDelivery(ctx, inputMessage)
|
||||
|
||||
editedText := "some text edited"
|
||||
editedMessage := &requests.EditMessage{
|
||||
ID: messageID,
|
||||
Text: editedText,
|
||||
}
|
||||
|
||||
wakuv2.SkipPublishToTopic(true)
|
||||
sendResponse, err := s.alice.EditMessage(ctx, editedMessage)
|
||||
s.Require().NotNil(sendResponse)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Check that message is re-sent once back online
|
||||
wakuv2.SkipPublishToTopic(false)
|
||||
time.Sleep(5 * time.Second)
|
||||
inputMessage.Text = editedText
|
||||
|
||||
s.checkMessageDelivery(ctx, inputMessage)
|
||||
}
|
||||
|
||||
func (s *MessengerOfflineSuite) checkMessageDelivery(ctx context.Context, inputMessage *common.Message) {
|
||||
var response *MessengerResponse
|
||||
// Pull message and make sure org is received
|
||||
err := tt.RetryWithBackOff(func() error {
|
||||
var err error
|
||||
response, err = s.owner.RetrieveAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(response.messages) == 0 {
|
||||
return errors.New("message not received")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
s.Require().NoError(err)
|
||||
s.Require().Len(response.Messages(), 1)
|
||||
s.Require().Equal(inputMessage.Text, response.Messages()[0].Text)
|
||||
|
||||
// check if response contains the chat we're interested in
|
||||
// we use this instead of checking just the length of the chat because
|
||||
// a CommunityDescription message might be received in the meantime due to syncing
|
||||
// hence response.Chats() might contain the general chat, and the new chat;
|
||||
// or only the new chat if the CommunityDescription message has not arrived
|
||||
found := false
|
||||
for _, chat := range response.Chats() {
|
||||
if chat.ID == inputMessage.ChatId {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
s.Require().True(found)
|
||||
}
|
|
@ -2220,74 +2220,76 @@ func (s *MessengerSuite) TestLastSentField() {
|
|||
s.NotEqual(uint64(0), rawMessage.LastSent, "rawMessage.LastSent should be non-zero after sending")
|
||||
}
|
||||
|
||||
func (s *MessengerSuite) TestShouldResendEmoji() {
|
||||
// shouldn't try to resend non-emoji messages.
|
||||
ok, err := shouldResendMessage(&common.RawMessage{
|
||||
MessageType: protobuf.ApplicationMetadataMessage_CONTACT_UPDATE,
|
||||
Sent: false,
|
||||
SendCount: 2,
|
||||
}, s.m.getTimesource())
|
||||
s.Error(err)
|
||||
s.False(ok)
|
||||
// TODO rewrite test to use persistence calls, as shouldResendMessage does not contain
|
||||
// relevant logic now
|
||||
// func (s *MessengerSuite) TestShouldResendEmoji() {
|
||||
// // shouldn't try to resend non-emoji messages.
|
||||
// ok, err := s.m.shouldResendMessage(&common.RawMessage{
|
||||
// MessageType: protobuf.ApplicationMetadataMessage_CONTACT_UPDATE,
|
||||
// Sent: false,
|
||||
// SendCount: 2,
|
||||
// }, s.m.getTimesource())
|
||||
// s.Error(err)
|
||||
// s.False(ok)
|
||||
|
||||
// shouldn't try to resend already sent message
|
||||
ok, err = shouldResendMessage(&common.RawMessage{
|
||||
MessageType: protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
|
||||
Sent: true,
|
||||
SendCount: 1,
|
||||
}, s.m.getTimesource())
|
||||
s.Error(err)
|
||||
s.False(ok)
|
||||
// // shouldn't try to resend already sent message
|
||||
// ok, err = s.m.shouldResendMessage(&common.RawMessage{
|
||||
// MessageType: protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
|
||||
// Sent: true,
|
||||
// SendCount: 1,
|
||||
// }, s.m.getTimesource())
|
||||
// s.Error(err)
|
||||
// s.False(ok)
|
||||
|
||||
// messages that already sent to many times shouldn't be resend
|
||||
ok, err = shouldResendMessage(&common.RawMessage{
|
||||
MessageType: protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
|
||||
Sent: false,
|
||||
SendCount: messageResendMaxCount + 1,
|
||||
}, s.m.getTimesource())
|
||||
s.NoError(err)
|
||||
s.False(ok)
|
||||
// // messages that already sent to many times shouldn't be resend
|
||||
// ok, err = s.m.shouldResendMessage(&common.RawMessage{
|
||||
// MessageType: protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
|
||||
// Sent: false,
|
||||
// SendCount: s.m.config.messageResendMaxCount + 1,
|
||||
// }, s.m.getTimesource())
|
||||
// s.NoError(err)
|
||||
// s.False(ok)
|
||||
|
||||
// message sent one time CAN'T be resend in 15 seconds (only after 30)
|
||||
ok, err = shouldResendMessage(&common.RawMessage{
|
||||
MessageType: protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
|
||||
Sent: false,
|
||||
SendCount: 1,
|
||||
LastSent: s.m.getTimesource().GetCurrentTime() - 15*uint64(time.Second.Milliseconds()),
|
||||
}, s.m.getTimesource())
|
||||
s.NoError(err)
|
||||
s.False(ok)
|
||||
// // message sent one time CAN'T be resend in 15 seconds (only after 30)
|
||||
// ok, err = s.m.shouldResendMessage(&common.RawMessage{
|
||||
// MessageType: protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
|
||||
// Sent: false,
|
||||
// SendCount: 1,
|
||||
// LastSent: s.m.getTimesource().GetCurrentTime() - 15*uint64(time.Second.Milliseconds()),
|
||||
// }, s.m.getTimesource())
|
||||
// s.NoError(err)
|
||||
// s.False(ok)
|
||||
|
||||
// message sent one time CAN be resend in 35 seconds
|
||||
ok, err = shouldResendMessage(&common.RawMessage{
|
||||
MessageType: protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
|
||||
Sent: false,
|
||||
SendCount: 1,
|
||||
LastSent: s.m.getTimesource().GetCurrentTime() - 35*uint64(time.Second.Milliseconds()),
|
||||
}, s.m.getTimesource())
|
||||
s.NoError(err)
|
||||
s.True(ok)
|
||||
// // message sent one time CAN be resend in 35 seconds
|
||||
// ok, err = s.m.shouldResendMessage(&common.RawMessage{
|
||||
// MessageType: protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
|
||||
// Sent: false,
|
||||
// SendCount: 1,
|
||||
// LastSent: s.m.getTimesource().GetCurrentTime() - 35*uint64(time.Second.Milliseconds()),
|
||||
// }, s.m.getTimesource())
|
||||
// s.NoError(err)
|
||||
// s.True(ok)
|
||||
|
||||
// message sent three times CAN'T be resend in 100 seconds (only after 120)
|
||||
ok, err = shouldResendMessage(&common.RawMessage{
|
||||
MessageType: protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
|
||||
Sent: false,
|
||||
SendCount: 3,
|
||||
LastSent: s.m.getTimesource().GetCurrentTime() - 100*uint64(time.Second.Milliseconds()),
|
||||
}, s.m.getTimesource())
|
||||
s.NoError(err)
|
||||
s.False(ok)
|
||||
// // message sent three times CAN'T be resend in 100 seconds (only after 120)
|
||||
// ok, err = s.m.shouldResendMessage(&common.RawMessage{
|
||||
// MessageType: protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
|
||||
// Sent: false,
|
||||
// SendCount: 3,
|
||||
// LastSent: s.m.getTimesource().GetCurrentTime() - 100*uint64(time.Second.Milliseconds()),
|
||||
// }, s.m.getTimesource())
|
||||
// s.NoError(err)
|
||||
// s.False(ok)
|
||||
|
||||
// message sent tow times CAN be resend in 65 seconds
|
||||
ok, err = shouldResendMessage(&common.RawMessage{
|
||||
MessageType: protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
|
||||
Sent: false,
|
||||
SendCount: 3,
|
||||
LastSent: s.m.getTimesource().GetCurrentTime() - 125*uint64(time.Second.Milliseconds()),
|
||||
}, s.m.getTimesource())
|
||||
s.NoError(err)
|
||||
s.True(ok)
|
||||
}
|
||||
// // message sent tow times CAN be resend in 65 seconds
|
||||
// ok, err = s.m.shouldResendMessage(&common.RawMessage{
|
||||
// MessageType: protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
|
||||
// Sent: false,
|
||||
// SendCount: 3,
|
||||
// LastSent: s.m.getTimesource().GetCurrentTime() - 125*uint64(time.Second.Milliseconds()),
|
||||
// }, s.m.getTimesource())
|
||||
// s.NoError(err)
|
||||
// s.True(ok)
|
||||
// }
|
||||
|
||||
func (s *MessengerSuite) TestSendMessageWithPreviews() {
|
||||
httpServer, err := server.NewMediaServer(s.m.database, nil, nil)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1 @@
|
|||
CREATE INDEX idx_resend_automatically ON raw_messages(resend_automatically);
|
|
@ -856,7 +856,7 @@ func (db sqlitePersistence) ExpiredMessagesIDs(maxSendCount int) ([]string, erro
|
|||
FROM
|
||||
raw_messages
|
||||
WHERE
|
||||
message_type IN (?, ?) AND sent = ? AND send_count <= ?`,
|
||||
(message_type IN (?, ?) OR resend_automatically) AND sent = ? AND send_count <= ?`,
|
||||
protobuf.ApplicationMetadataMessage_CHAT_MESSAGE,
|
||||
protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
|
||||
false,
|
||||
|
|
|
@ -736,6 +736,7 @@ func TestMessagesIDsByType(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestExpiredMessagesIDs(t *testing.T) {
|
||||
messageResendMaxCount := 30
|
||||
db, err := openTestDB()
|
||||
require.NoError(t, err)
|
||||
p := newSQLitePersistence(db)
|
||||
|
|
|
@ -89,6 +89,7 @@ type settings struct {
|
|||
EnableDiscV5 bool // Indicates whether discv5 is enabled or not
|
||||
DefaultPubsubTopic string // Pubsub topic to be used by default for messages that do not have a topic assigned (depending whether sharding is used or not)
|
||||
Options []node.WakuNodeOption
|
||||
SkipPublishToTopic bool // used in testing
|
||||
}
|
||||
|
||||
type ITelemetryClient interface {
|
||||
|
@ -1013,6 +1014,10 @@ func (w *Waku) UnsubscribeMany(ids []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (w *Waku) SkipPublishToTopic(value bool) {
|
||||
w.settings.SkipPublishToTopic = value
|
||||
}
|
||||
|
||||
func (w *Waku) broadcast() {
|
||||
for {
|
||||
select {
|
||||
|
@ -1020,8 +1025,11 @@ func (w *Waku) broadcast() {
|
|||
pubsubTopic := envelope.PubsubTopic()
|
||||
var err error
|
||||
logger := w.logger.With(zap.String("envelopeHash", hexutil.Encode(envelope.Hash())), zap.String("pubsubTopic", pubsubTopic), zap.String("contentTopic", envelope.Message().ContentTopic), zap.Int64("timestamp", envelope.Message().Timestamp))
|
||||
if w.settings.LightClient {
|
||||
logger.Info("publishing message via lightpush")
|
||||
// For now only used in testing to simulate going offline
|
||||
if w.settings.SkipPublishToTopic {
|
||||
err = errors.New("Test send failure")
|
||||
} else if w.settings.LightClient {
|
||||
w.logger.Info("publishing message via lightpush")
|
||||
_, err = w.node.Lightpush().Publish(w.ctx, envelope.Message(), lightpush.WithPubSubTopic(pubsubTopic))
|
||||
} else {
|
||||
logger.Info("publishing message via relay")
|
||||
|
|
Loading…
Reference in New Issue