-
Notifications
You must be signed in to change notification settings - Fork 247
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Community messages are ignored if sender is not known as community member #3869
Comments
Below is a test that reproduces the bug. status-go/protocol/communities_messenger_test.go Lines 2683 to 2746 in 7bdb5e2
I also tried to write a same test, but with 3 different users instead of pairing admin devices. Expand to see codefunc (s *MessengerCommunitiesSuite) TestSyncCommunity_Messaging() {
admin := s.admin
user1 := s.alice
user2 := s.bob
// Main setup
community, chat := createCommunity(&s.Suite, admin)
joinRequest := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
// User 1 joins community
advertiseCommunityTo(&s.Suite, community, admin, user1)
joinCommunity(&s.Suite, community, admin, user1, joinRequest)
// User 2 joins community
advertiseCommunityTo(&s.Suite, community, admin, user2)
joinCommunity(&s.Suite, community, admin, user2, joinRequest)
// Common func for sending messages
var sentMessages = map[string]struct{}{}
// User 2: send a message to community
messageId := s.sendTestMessage(user2, chat.ID, "A")
sentMessages[messageId] = struct{}{}
// User 1: Go online
// NOTE: We should be receiving both things here:
// - new community member (User 2)
// - message "A"
response, err := WaitOnMessengerResponse(user1, func(r *MessengerResponse) bool {
return len(r.Communities()) == 1 && len(r.Communities()[0].Members()) == 3
}, "member not received")
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Messages(), 1)
s.Require().Equal(protobuf.ChatMessage_COMMUNITY, response.Messages()[0].ContentType)
// User 2: send a message to community
messageId = s.sendTestMessage(user2, chat.ID, "B")
sentMessages[messageId] = struct{}{}
// Wait for User 1 to receive messages
response, _ = WaitOnMessengerResponse(user1, func(r *MessengerResponse) bool {
return len(r.Messages()) == 1
}, "message not received")
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Equal(sentMessages, response.messageIds())
} |
/cc @cammellos @osmaczko |
Thank you for reporting. I would recommend a variant of Option A. Option B, without adequate spam protection, could result in storage problems if a malicious node (which isn't a member) sends a multitude of large messages. Option A:
How exactly would you do that? The most straightforward method would be to attach a |
@osmaczko there's some code already kind of scattered around (
We probably want to discuss how to approach it as with admin events things are a bit different (we could for example use the event itself as a "grant", but there's some challenges with that). |
I see; this is somewhat similar to what I had envisioned. How do we prevent a member from using an old grant if it has been removed from the community? If we simply compare clocks, then old messages sent before the member was kicked out wouldn't be received. But I suppose that's acceptable... 🤔
Why a bit different with admin events? Admins are not authorized to modify the member list, only control node can do that. |
I don't think there's a solution for that unfortunately, even if we passed the whole community description it might still happen, we basically "optimistically" accept the message.
I thought admin could add users to the community?
the message from U2 should be accepted, is that correct? (that's were the complications are) APologies if I misunderstand something |
Admin A can add (or accept) user U1, but control node has to approve it anyway. That's because control node it the ultimate source of truth when it comes to permissions check. User U1 can't post into the community until control node adds it to community. See: #3843. |
oh I see, I didn't know that, that makes things much simpler actually on many levels. |
@igor-sirotin I tested the test you posted above and it fails at the bottom when comparing I tested both on develop and on @shinnok 's PR that fixes posting in a community without being a member #4064 I had to tweak the test a little, because the util test functions changed. Here in my new code, maybe I made a mistake, let me know: func (s *MessengerCommunitiesSuite) TestSyncCommunity_Messaging() {
admin1 := s.owner
admin2 := s.createOtherDevice(admin1)
user1 := s.alice
// Pair Admin devices
PairDevices(&s.Suite, admin1, admin2)
PairDevices(&s.Suite, admin2, admin1)
// Create community
community, chat := createCommunity(&s.Suite, admin1)
joinRequest := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
// Make sure Admin-2 receives the created community
response, err := WaitOnMessengerResponse(admin2, func(r *MessengerResponse) bool {
return len(r.Communities()) == 1
}, "community not received")
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
s.Require().Equal(response.Communities()[0].ID(), community.ID())
// User 1 joins community
advertiseCommunityTo(&s.Suite, community, admin1, user1)
joinCommunity(&s.Suite, community, admin1, user1, joinRequest)
// Common func for sending messages
var sentMessages = make([]string, 0)
// User 1: send a message to community
chatID := chat.ID
inputMessage := common.NewMessage()
inputMessage.ChatId = chatID
inputMessage.ContentType = protobuf.ChatMessage_TEXT_PLAIN
inputMessage.Text = "A"
response, err = user1.SendChatMessage(context.Background(), inputMessage)
s.Require().NoError(err)
s.Require().NotNil(response)
messageId := response.Messages()[0].ID
sentMessages = append(sentMessages, messageId)
// Admin-2: Go online
// NOTE: We should be receiving both things here:
// - new community member (User 2)
// - message "A"
// Unfortunately, we get message "A" first and therefor never get message "A"
// https://github.com/status-im/status-go/issues/3869
response, err = WaitOnMessengerResponse(admin2, func(r *MessengerResponse) bool {
return len(r.Communities()) == 1 && len(r.Communities()[0].Members()) == 2 // && len(r.Messages()) == 2
}, "member not received")
// FIXME: Message "A" is lost here
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Messages(), 1)
s.Require().Equal(protobuf.ChatMessage_COMMUNITY, response.Messages()[0].ContentType)
// User 1: send another message to community
inputMessage = common.NewMessage()
inputMessage.ChatId = chatID
inputMessage.ContentType = protobuf.ChatMessage_TEXT_PLAIN
inputMessage.Text = "B"
response, err = user1.SendChatMessage(context.Background(), inputMessage)
s.Require().NoError(err)
s.Require().NotNil(response)
messageId = response.Messages()[0].ID
sentMessages = append(sentMessages, messageId)
// This time Admin-2 receives the message
response, _ = WaitOnMessengerResponse(user1, func(r *MessengerResponse) bool {
return len(r.Messages()) == 1
}, "message not received")
s.Require().NoError(err)
s.Require().NotNil(response)
for _, messageID := range sentMessages {
m := response.GetMessage(messageID)
s.Require().NotNil(m, "Missing message:"+messageID)
}
} It fails at the end with
which makes sense IMO, the user1 didn't receive the original message. |
@jrainville sorry for late reply. First of all, I see a mistake in my test here: status-go/protocol/communities_messenger_test.go Lines 2738 to 2739 in 7bdb5e2
The comment says "Admin 2", but the code runs on I can fix the test if we want to do it now, assign it to me if needed. It would be actually nice to rewrite the test, so that we manually push the "communtiy description" message and "A" message in this order to reproduce the bug. The test above is flaky I'd say. |
lol, immediate punishment for initiative 🤣 |
BTW, just recreate the test for now. No need to fix the full issue. Just do an analysis of the issue and see what would be needed to fix. Then we can determine if it's good to work on it. |
@jrainville, I've updated the test. It fails 🙂 I've removed the redundant part with second message. NOTE: though it reproduces the bug 100% cases for me, I think it's flaky, because we don't force the order of received messages. status-go/protocol/communities_messenger_test.go Lines 2908 to 2973 in 7a90ad2
|
@jrainville I will unassign myself, as you told only to fix the test for now. |
I wonder if the advancements in applied cryptography make it so that we could solves this with some sort of zero knowledge proof. The problem we had with grants was that the code was super out of date and also someone could keep posting with an old grant until it expired. Maybe with the new advancements, there is a way to prove that you belong to a community without the other members knowing it yet that also gets revoked when the person leaves or gets kicked. I don't personally have experience or knowledge of that, but I know there has been a lot of cool projects using that sort of technology. |
@iurimatias and I discussed this and one way to get rid of this issue once and for all would be to move the member list management on chain, either with deMLS or our own contract. However, this is a very big change and we need to go through it from first principles and make good decisions from the start. So we won't tackle it straight away. Maybe we'll start by applying it to new types of groups first for example. All that to say that unless there is a super easy fix/hack for this issue (which I doubt there is since it's been so long), then we'll just wait until we actually fix communities from the protocol level. |
There could still be timing issues (e.g., users sending messages before the contract is updated) and more edge cases, such as a failure to fetch data from the contract (e.g., due to provider outages or similar). I would rather go with some form of proof. The grants mechanism was never finalized but was theoretically designed to address these exact issues. However, it has drawbacks, primarily expiration time and the need for grant redistribution. Perhaps there’s a smarter solution using zk. |
Problem
Consider a community member
Bob
being offline.And another user
Charlie
joins the community and sends a messageA
.When
Bob
goes online, if he receives the messageA
earlier than thatCharlie
is a community member, the message will be ignored and hidden forever.Reproduce steps:
Alice
,Bob
,Charlie
: create new profileAlice
: create a new community "Doodles"Bob
: join DoodlesBob
: go offlineCharlie
: join DoodlesCharlie
: send a messageA
to DoodlesBob
: go online. Here Bob will receive 2 things:Charlie
(not sure in what form it comes, will call it furthernew member
)A
❌ If
Bob
receives the messageA
earlier thannew member
message, it will not be processed and shown.Implementation
We discussed 2 possible fixes for this.
Option 1. "Sign" community messages.
This is the same as we do in group chats.
When one sends a message to a community, he signs is a being a community member.
When a user receives a community message, even if the sender is not known as community member, we can still check the sign and show the message.
Option 2. Save original waku messages until member is received
Save all such waku messages.
Check the history when a new member is known.
Acceptance Criteria
All messages of new community members are visible.
Notes
Also consider the case when a user is removed from community.
The text was updated successfully, but these errors were encountered: