Skip to content

Commit

Permalink
fix_: mitigate permission stuck in pending state
Browse files Browse the repository at this point in the history
This PR mitigates permission stuck in pending state upon making device a
control node. It fixes
[#14023](status-im/status-desktop#14023)
  • Loading branch information
kounkou committed Apr 26, 2024
1 parent 9e5462e commit 212eebc
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 13 deletions.
26 changes: 14 additions & 12 deletions protocol/communities/community_events_processing.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,23 @@ import (

var ErrInvalidCommunityEventClock = errors.New("clock for admin event message is outdated")

func (o *Community) processEvents(message *CommunityEventsMessage, lastlyAppliedEvents map[string]uint64) error {
func (o *Community) processEvents(events []CommunityEvent, eventsBaseCommunityDescription []byte, lastlyAppliedEvents map[string]uint64) error {
processor := &eventsProcessor{
community: o,
message: message,
logger: o.config.Logger.Named("eventsProcessor"),
lastlyAppliedEvents: lastlyAppliedEvents,
community: o,
events: events,
logger: o.config.Logger.Named("eventsProcessor"),
lastlyAppliedEvents: lastlyAppliedEvents,
eventsBaseCommunityDescription: eventsBaseCommunityDescription,
}
return processor.exec()
}

type eventsProcessor struct {
community *Community
message *CommunityEventsMessage
logger *zap.Logger
lastlyAppliedEvents map[string]uint64
community *Community
events []CommunityEvent
logger *zap.Logger
lastlyAppliedEvents map[string]uint64
eventsBaseCommunityDescription []byte

eventsToApply []CommunityEvent
}
Expand All @@ -53,7 +55,7 @@ func (e *eventsProcessor) exec() error {
}

func (e *eventsProcessor) validateDescription() error {
description, err := validateAndGetEventsMessageCommunityDescription(e.message.EventsBaseCommunityDescription, e.community.ControlNode())
description, err := validateAndGetEventsMessageCommunityDescription(e.eventsBaseCommunityDescription, e.community.ControlNode())
if err != nil {
return err
}
Expand Down Expand Up @@ -88,7 +90,7 @@ func (e *eventsProcessor) validateEvent(event *CommunityEvent) error {

// Filter invalid and outdated events.
func (e *eventsProcessor) filterEvents() {
for _, ev := range e.message.Events {
for _, ev := range e.events {
event := ev
if err := e.validateEvent(&event); err == nil {
e.eventsToApply = append(e.eventsToApply, event)
Expand Down Expand Up @@ -145,7 +147,7 @@ func (e *eventsProcessor) sortEvents() {
func (e *eventsProcessor) applyEvents() {
if e.community.config.EventsData == nil {
e.community.config.EventsData = &EventsData{
EventsBaseCommunityDescription: e.message.EventsBaseCommunityDescription,
EventsBaseCommunityDescription: e.eventsBaseCommunityDescription,
}
}
e.community.config.EventsData.Events = e.eventsToApply
Expand Down
60 changes: 59 additions & 1 deletion protocol/communities/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -2035,7 +2035,8 @@ func (m *Manager) HandleCommunityEventsMessage(signer *ecdsa.PublicKey, message
return nil, err
}
}
err = community.processEvents(eventsMessage, lastlyAppliedEvents)

err = community.processEvents(eventsMessage.Events, eventsMessage.EventsBaseCommunityDescription, lastlyAppliedEvents)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -5188,11 +5189,68 @@ func (m *Manager) promoteSelfToControlNode(community *Community, clock uint64) (
return false, err
}

if community.IsControlNode() {
err = m.handleCommunityEvents(community)
if err != nil {
return false, err
}
}

community.increaseClock()

return ownerChanged, nil
}

func (m *Manager) handleCommunityEvents(community *Community) error {
if community.config.EventsData == nil {
return nil
}

lastlyAppliedEvents, err := m.persistence.GetAppliedCommunityEvents(community.ID())
if err != nil {
return err
}

err = community.processEvents(community.config.EventsData.Events, community.config.EventsData.EventsBaseCommunityDescription,
lastlyAppliedEvents)
if err != nil {
return err
}

_, err = m.handleAdditionalAdminChanges(community)
if err != nil {
return err
}

if err = m.handleCommunityTokensMetadata(community); err != nil {
return err
}

appliedEvents := map[string]uint64{}
if community.config.EventsData != nil {
for _, event := range community.config.EventsData.Events {
appliedEvents[event.EventTypeID()] = event.CommunityEventClock
}
}

community.config.EventsData = nil // clear events, they are already applied
community.increaseClock()

err = m.persistence.SaveCommunity(community)
if err != nil {
return err
}

err = m.persistence.UpsertAppliedCommunityEvents(community.ID(), appliedEvents)
if err != nil {
return err
}

m.publish(&Subscription{Community: community})

return nil
}

func (m *Manager) shareRequestsToJoinWithNewPrivilegedMembers(community *Community, newPrivilegedMembers map[protobuf.CommunityMember_Roles][]*ecdsa.PublicKey) error {
requestsToJoin, err := m.GetCommunityRequestsToJoinWithRevealedAddresses(community.ID())
if err != nil {
Expand Down
134 changes: 134 additions & 0 deletions protocol/communities_messenger_signers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package protocol
import (
"bytes"
"context"
"errors"
"fmt"
"testing"
"time"

Expand Down Expand Up @@ -732,3 +734,135 @@ func (s *MessengerCommunitiesSignersSuite) TestSyncTokenGatedCommunity() {
})
}
}

func (s *MessengerCommunitiesSignersSuite) TestPApplyendingCommunityEventsUponMakingDeviceControlNode() {
johnsOtherDevice := s.john
PairDevices(&s.Suite, johnsOtherDevice, s.john)

community := s.createCommunity(s.john)
s.advertiseCommunityTo(s.john, community, s.alice)
s.joinCommunity(s.john, community, s.alice)

// john mints owner token
var chainID uint64 = 1
tokenAddress := "token-address"
tokenName := "tokenName"
tokenSymbol := "TSM"
_, err := s.john.SaveCommunityToken(&token.CommunityToken{
TokenType: protobuf.CommunityTokenType_ERC721,
CommunityID: community.IDString(),
Address: tokenAddress,
ChainID: int(chainID),
Name: tokenName,
Supply: &bigint.BigInt{},
Symbol: tokenSymbol,
PrivilegesLevel: token.OwnerLevel,
}, nil)
s.Require().NoError(err)

// john adds minted owner token to community
err = s.john.AddCommunityToken(community.IDString(), int(chainID), tokenAddress)
s.Require().NoError(err)

// update mock - the signer for the community returned by the contracts should be john
s.collectiblesServiceMock.SetSignerPubkeyForCommunity(community.ID(), common.PubkeyToHex(&s.john.identity.PublicKey))
s.collectiblesServiceMock.SetMockCollectibleContractData(chainID, tokenAddress,
&communitytokens.CollectibleContractData{TotalSupply: &bigint.BigInt{}})

// alice accepts community update
_, err = WaitOnSignaledMessengerResponse(
s.alice,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && len(r.Communities()[0].TokenPermissions()) == 1
},
"no communities",
)
s.Require().NoError(err)

grantPermission(&s.Suite, community, s.john, s.alice, protobuf.CommunityMember_ROLE_ADMIN)
s.advertiseCommunityTo(s.john, community, s.bob)
s.john = nil

err = tt.RetryWithBackOff(func() error {
_, err = johnsOtherDevice.RetrieveAll()
if err != nil {
return err
}

// Do we have new synced community settings?
syncedSettings, err := johnsOtherDevice.communitiesManager.GetCommunitySettingsByID(community.ID())
if err != nil || syncedSettings == nil {
return fmt.Errorf("community with sync not received %w", err)
}
return nil
})

request := requests.CreateCommunityTokenPermission{
CommunityID: community.ID(),
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: []*protobuf.TokenCriteria{
&protobuf.TokenCriteria{
Type: protobuf.CommunityTokenType_ERC20,
ContractAddresses: map[uint64]string{testChainID1: "0x123"},
Symbol: "TEST",
AmountInWei: "100000000000000000000",
Decimals: uint64(18),
},
},
}

response, err := s.alice.CreateCommunityTokenPermission(&request)
s.Require().NoError(err)
s.Require().Len(response.CommunityChanges, 1)
s.Require().Len(response.CommunityChanges[0].TokenPermissionsAdded, 1)

addedPermission := func() *communities.CommunityTokenPermission {
for _, permission := range response.CommunityChanges[0].TokenPermissionsAdded {
return permission
}
return nil
}()
s.Require().NotNil(addedPermission)
// Permission added by event must be in pending state
s.Require().Equal(communities.TokenPermissionAdditionPending, addedPermission.State)

// Retrieve community link & community
err = tt.RetryWithBackOff(func() error {
response, err = johnsOtherDevice.RetrieveAll()
if err != nil {
return err
}
if len(response.Communities()) == 0 {
return errors.New("no communities received")
}
return nil
})
s.Require().NoError(err)

s.collectiblesServiceMock.SetSignerPubkeyForCommunity(community.ID(), common.PubkeyToHex(&s.alice.identity.PublicKey))

response, err = johnsOtherDevice.PromoteSelfToControlNode(community.ID())
s.Require().NoError(err)
s.Require().NotNil(response)

community, err = johnsOtherDevice.communitiesManager.GetByID(community.ID())
s.Require().NoError(err)
s.Require().True(community.IsControlNode())
s.Require().True(common.IsPubKeyEqual(community.ControlNode(), &johnsOtherDevice.identity.PublicKey))
s.Require().True(community.IsOwner())

responseHasApprovedTokenPermission := func(r *MessengerResponse) bool {
if len(r.Communities()) == 0 {
return false
}

receivedPermission := r.Communities()[0].TokenPermissionByID(addedPermission.Id)
return receivedPermission != nil && receivedPermission.State == communities.TokenPermissionApproved
}

// Control node receives community event & approves it
response, err = WaitOnMessengerResponse(johnsOtherDevice, responseHasApprovedTokenPermission, "community with approved permission not found")

s.Require().NoError(err)
s.Require().Equal(len(response.communities), 1)
}

0 comments on commit 212eebc

Please sign in to comment.