From 2b53d717084d589cf0ddaa8064c4db9ac3c249cf Mon Sep 17 00:00:00 2001 From: Mykhailo Prakhov <117639195+mprakhov@users.noreply.github.com> Date: Fri, 22 Sep 2023 19:57:27 +0200 Subject: [PATCH] fix: Prevent (reject event <-> resend event) loop (#4055) --- protocol/communities/manager.go | 17 ++++++ protocol/communities_messenger_admin_test.go | 59 ++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/protocol/communities/manager.go b/protocol/communities/manager.go index e6f24e3f4..eed470e9d 100644 --- a/protocol/communities/manager.go +++ b/protocol/communities/manager.go @@ -1425,6 +1425,8 @@ func (m *Manager) HandleCommunityEventsMessage(signer *ecdsa.PublicKey, message err = community.UpdateCommunityByEvents(eventsMessage) if err != nil { if err == ErrInvalidCommunityEventClock && community.IsControlNode() { + // send updated CommunityDescription to the event sender on top of which he must apply his changes + eventsMessage.EventsBaseCommunityDescription = community.config.CommunityDescriptionProtocolMessage m.publish(&Subscription{ CommunityEventsMessageInvalidClock: &CommunityEventsMessageInvalidClockSignal{ Community: community, @@ -1491,6 +1493,17 @@ func (m *Manager) HandleCommunityEventsMessageRejected(signer *ecdsa.PublicKey, if err != nil { return nil, err } + + communityDescription, err := validateAndGetEventsMessageCommunityDescription(eventsMessage.EventsBaseCommunityDescription, signer) + if err != nil { + return nil, err + } + // the privileged member did not receive updated CommunityDescription so his events + // will be send on top of outdated CommunityDescription + if communityDescription.Clock != community.Clock() { + return nil, errors.New("resend rejected community events aborted, client node has outdated community description") + } + eventsMessage.Events = m.validateAndFilterEvents(community, eventsMessage.Events) myRejectedEvents := make([]CommunityEvent, 0) @@ -4813,3 +4826,7 @@ func (m *Manager) shareAcceptedRequestToJoinWithPrivilegedMembers(community *Com func (m *Manager) GetCommunityRequestsToJoinWithRevealedAddresses(communityID types.HexBytes) ([]*RequestToJoin, error) { return m.persistence.GetCommunityRequestsToJoinWithRevealedAddresses(communityID) } + +func (m *Manager) SaveCommunity(community *Community) error { + return m.persistence.SaveCommunity(community) +} diff --git a/protocol/communities_messenger_admin_test.go b/protocol/communities_messenger_admin_test.go index 936cf089c..fbd1d3429 100644 --- a/protocol/communities_messenger_admin_test.go +++ b/protocol/communities_messenger_admin_test.go @@ -381,3 +381,62 @@ func (s *AdminCommunityEventsSuite) TestReceiveRequestsToJoinWithRevealedAccount bob := s.newMessenger(accountPassword, []string{bobAccountAddress}) testMemberReceiveRequestsToJoinAfterGettingNewRole(s, bob, protobuf.CommunityTokenPermission_BECOME_ADMIN) } + +func (s *AdminCommunityEventsSuite) TestAdminDoesNotHaveRejectedEventsLoop() { + community := setUpCommunityAndRoles(s, protobuf.CommunityMember_ROLE_ADMIN) + + // admin modifies community description + adminEditRequest := &requests.EditCommunity{ + CommunityID: community.ID(), + CreateCommunity: requests.CreateCommunity{ + Name: "admin name", + Description: "admin description", + Color: "#FFFFFF", + Membership: protobuf.CommunityPermissions_ON_REQUEST, + }, + } + _, err := s.admin.EditCommunity(adminEditRequest) + s.Require().NoError(err) + + community, err = s.owner.communitiesManager.GetByID(community.ID()) + s.Require().NoError(err) + + // Update community clock without publishing new CommunityDescription + err = community.DeclineRequestToJoin(nil) + s.Require().NoError(err) + + err = s.owner.communitiesManager.SaveCommunity(community) + s.Require().NoError(err) + + waitOnAdminEventsRejection := waitOnCommunitiesEvent(s.owner, func(s *communities.Subscription) bool { + return s.CommunityEventsMessageInvalidClock != nil + }) + + // control node receives admin event and rejects it + _, err = WaitOnMessengerResponse(s.owner, func(response *MessengerResponse) bool { + select { + case err := <-waitOnAdminEventsRejection: + s.Require().NoError(err) + return true + default: + return false + } + }, "") + s.Require().NoError(err) + + community, err = s.owner.communitiesManager.GetByID(community.ID()) + s.Require().NoError(err) + s.Require().NotEqual(adminEditRequest.Description, community.DescriptionText()) + + // admin receives rejected events and re-applies them + // there is no signal whatsoever, we just wait for admin to process all incoming messages + _, _ = WaitOnMessengerResponse(s.admin, func(response *MessengerResponse) bool { + return false + }, "") + + // control node does not receives admin event + _, err = WaitOnMessengerResponse(s.owner, func(response *MessengerResponse) bool { + return len(response.Communities()) > 0 + }, "no communities in response") + s.Require().Error(err) +}