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 25, 2024
1 parent 9e5462e commit f184645
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 17 deletions.
39 changes: 23 additions & 16 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 Expand Up @@ -311,11 +313,16 @@ func (o *Community) addNewCommunityEvent(event *CommunityEvent) error {
}

func (o *Community) toCommunityEventsMessage() *CommunityEventsMessage {
return &CommunityEventsMessage{
CommunityID: o.ID(),
EventsBaseCommunityDescription: o.config.EventsData.EventsBaseCommunityDescription,
Events: o.config.EventsData.Events,
message := &CommunityEventsMessage{
CommunityID: o.ID(),
}

if o.config != nil && o.config.EventsData != nil {
message.EventsBaseCommunityDescription = o.config.EventsData.EventsBaseCommunityDescription
message.Events = o.config.EventsData.Events
}

return message
}

func validateAndGetEventsMessageCommunityDescription(signedDescription []byte, signerPubkey *ecdsa.PublicKey) (*protobuf.CommunityDescription, error) {
Expand Down
56 changes: 55 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,64 @@ 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 {
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
102 changes: 102 additions & 0 deletions protocol/communities_messenger_signers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -732,3 +732,105 @@ func (s *MessengerCommunitiesSignersSuite) TestSyncTokenGatedCommunity() {
})
}
}

func (s *MessengerCommunitiesSignersSuite) TestPendingCommunityEventsAppliedUponMakingDeviceControlNode() {
community := s.createCommunity(s.john)

s.advertiseCommunityTo(s.john, community, s.bob)
s.joinCommunity(s.john, community, s.bob)

// makes Alice to be admin
grantPermission(&s.Suite, community, s.john, s.bob, protobuf.CommunityMember_ROLE_ADMIN)

// 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{}})

s.Require().True(common.IsPubKeyEqual(community.ControlNode(), &s.john.identity.PublicKey))

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

// admin bob creates a permission that would require the owner to be present
createTokenPermission := &requests.CreateCommunityTokenPermission{
CommunityID: community.ID(),
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: []*protobuf.TokenCriteria{
&protobuf.TokenCriteria{
Type: protobuf.CommunityTokenType_ERC20,
ContractAddresses: map[uint64]string{uint64(testChainID1): "0x123"},
Symbol: "TEST",
AmountInWei: "100000000000000000000",
Decimals: uint64(18),
},
},
}

response, err := s.bob.CreateCommunityTokenPermission(createTokenPermission)
s.Require().NotNil(response)
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)

community, err = s.bob.communitiesManager.GetByID(community.ID())
s.Require().NoError(err)
s.Require().False(community.IsControlNode())
s.Require().False(common.IsPubKeyEqual(community.ControlNode(), &s.bob.identity.PublicKey))
s.Require().False(community.IsOwner())
s.Require().Equal(communities.TokenPermissionAdditionPending, addedPermission.State)

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

addedPermission = func() *communities.CommunityTokenPermission {
for _, permission := range response.CommunityChanges[0].TokenPermissionsAdded {
return permission
}
return nil
}()
s.Require().NotNil(addedPermission)

community, err = s.bob.communitiesManager.GetByID(community.ID())
s.Require().NoError(err)
s.Require().True(community.IsControlNode())
s.Require().True(common.IsPubKeyEqual(community.ControlNode(), &s.bob.identity.PublicKey))
s.Require().True(community.IsOwner())
s.Require().Equal(communities.TokenPermissionApproved, addedPermission.State)
}

0 comments on commit f184645

Please sign in to comment.