fix_: mitigate permission stuck in pending state (#5070)
This PR mitigates permission stuck in pending state upon making device a control node. It fixes [#14023](status-im/status-desktop#14023)
This commit is contained in:
parent
cb4c19cece
commit
627e23ffa5
|
@ -2107,20 +2107,12 @@ func (m *Manager) HandleCommunityEventsMessage(signer *ecdsa.PublicKey, message
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
err = community.processEvents(eventsMessage, lastlyAppliedEvents)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
additionalCommunityResponse, err := m.handleAdditionalAdminChanges(community)
|
||||
additionalCommunityResponse, err := m.handleCommunityEventsAndMetadata(community, eventsMessage, lastlyAppliedEvents)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = m.handleCommunityTokensMetadata(community); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Control node applies events and publish updated CommunityDescription
|
||||
if community.IsControlNode() {
|
||||
appliedEvents := map[string]uint64{}
|
||||
|
@ -5289,11 +5281,75 @@ func (m *Manager) promoteSelfToControlNode(community *Community, clock uint64) (
|
|||
return false, err
|
||||
}
|
||||
|
||||
err = m.handleCommunityEvents(community)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
community.increaseClock()
|
||||
|
||||
return ownerChanged, nil
|
||||
}
|
||||
|
||||
func (m *Manager) handleCommunityEventsAndMetadata(community *Community, eventsMessage *CommunityEventsMessage,
|
||||
lastlyAppliedEvents map[string]uint64) (*CommunityResponse, error) {
|
||||
err := community.processEvents(eventsMessage, lastlyAppliedEvents)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
additionalCommunityResponse, err := m.handleAdditionalAdminChanges(community)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = m.handleCommunityTokensMetadata(community); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return additionalCommunityResponse, err
|
||||
}
|
||||
|
||||
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 = m.handleCommunityEventsAndMetadata(community, community.toCommunityEventsMessage(), lastlyAppliedEvents)
|
||||
if 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 {
|
||||
|
|
|
@ -732,3 +732,96 @@ func (s *MessengerCommunitiesSignersSuite) TestSyncTokenGatedCommunity() {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MessengerCommunitiesSignersSuite) TestWithMintedOwnerTokenApplyCommunityEventsUponMakingDeviceControlNode() {
|
||||
community := s.createCommunity(s.john)
|
||||
|
||||
// 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)
|
||||
|
||||
err = s.john.AddCommunityToken(community.IDString(), int(chainID), tokenAddress)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Make sure there is no control node
|
||||
s.Require().False(common.IsPubKeyEqual(community.ControlNode(), &s.john.identity.PublicKey))
|
||||
|
||||
// Trick. We need to remove the community private key otherwise the events
|
||||
// will be signed and Events will be approved instead of being in Pending State.
|
||||
_, err = s.john.RemovePrivateKey(community.ID())
|
||||
s.Require().NoError(err)
|
||||
|
||||
request := requests.CreateCommunityTokenPermission{
|
||||
CommunityID: community.ID(),
|
||||
Type: protobuf.CommunityTokenPermission_BECOME_ADMIN,
|
||||
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.john.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)
|
||||
s.Require().Equal(communities.TokenPermissionAdditionPending, addedPermission.State)
|
||||
|
||||
messengerReponse, err := s.john.PromoteSelfToControlNode(community.ID())
|
||||
|
||||
s.Require().NoError(err)
|
||||
s.Require().Len(messengerReponse.Communities(), 1)
|
||||
|
||||
tokenPermissions := messengerReponse.Communities()[0].TokenPermissions()
|
||||
s.Require().Len(tokenPermissions, 2)
|
||||
|
||||
tokenPermissionsMap := make(map[protobuf.CommunityTokenPermission_Type]struct{}, len(tokenPermissions))
|
||||
for _, t := range tokenPermissions {
|
||||
tokenPermissionsMap[t.Type] = struct{}{}
|
||||
}
|
||||
|
||||
s.Require().Len(tokenPermissionsMap, 2)
|
||||
s.Require().Contains(tokenPermissionsMap, protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER)
|
||||
s.Require().Contains(tokenPermissionsMap, protobuf.CommunityTokenPermission_BECOME_ADMIN)
|
||||
|
||||
for _, v := range tokenPermissions {
|
||||
s.Require().Equal(communities.TokenPermissionApproved, v.State)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MessengerCommunitiesSignersSuite) TestWithoutMintedOwnerTokenMakingDeviceControlNodeIsBlocked() {
|
||||
community := s.createCommunity(s.john)
|
||||
|
||||
// Make sure there is no control node
|
||||
s.Require().False(common.IsPubKeyEqual(community.ControlNode(), &s.john.identity.PublicKey))
|
||||
|
||||
response, err := s.john.PromoteSelfToControlNode(community.ID())
|
||||
s.Require().Nil(response)
|
||||
s.Require().NotNil(err)
|
||||
s.Require().Error(err, "Owner token is needed")
|
||||
}
|
||||
|
|
|
@ -71,6 +71,18 @@ const (
|
|||
maxChunkSizeBytes = 1500000
|
||||
)
|
||||
|
||||
const (
|
||||
ErrOwnerTokenNeeded = "Owner token is needed" // #nosec G101
|
||||
ErrMissingCommunityID = "CommunityID has to be provided"
|
||||
ErrForbiddenProfileOrWatchOnlyAccount = "Cannot join a community using profile chat or watch-only account"
|
||||
ErrSigningJoinRequestForKeycardAccounts = "Signing a joining community request for accounts migrated to keycard must be done with a keycard"
|
||||
ErrNotPartOfCommunity = "Not part of the community"
|
||||
ErrNotAdminOrOwner = "Not admin or owner"
|
||||
ErrSignerIsNil = "Signer can't be nil"
|
||||
ErrSyncMessagesSentByNonControlNode = "Accepted/requested to join sync messages can be send only by the control node"
|
||||
ErrReceiverIsNil = "Receiver can't be nil"
|
||||
)
|
||||
|
||||
type FetchCommunityRequest struct {
|
||||
// CommunityKey should be either a public or a private community key
|
||||
CommunityKey string `json:"communityKey"`
|
||||
|
@ -1229,7 +1241,7 @@ func (m *Messenger) generateCommunityRequestsForSigning(memberPubKey string, com
|
|||
|
||||
func (m *Messenger) GenerateJoiningCommunityRequestsForSigning(memberPubKey string, communityID types.HexBytes, addressesToReveal []string) ([]account.SignParams, error) {
|
||||
if len(communityID) == 0 {
|
||||
return nil, errors.New("communityID has to be provided")
|
||||
return nil, errors.New(ErrMissingCommunityID)
|
||||
}
|
||||
return m.generateCommunityRequestsForSigning(memberPubKey, communityID, addressesToReveal, false)
|
||||
}
|
||||
|
@ -1254,7 +1266,7 @@ func (m *Messenger) SignData(signParams []account.SignParams) ([]string, error)
|
|||
}
|
||||
|
||||
if account.Chat || account.Type == accounts.AccountTypeWatch {
|
||||
return nil, errors.New("cannot join a community using profile chat or watch-only account")
|
||||
return nil, errors.New(ErrForbiddenProfileOrWatchOnlyAccount)
|
||||
}
|
||||
|
||||
keypair, err := m.settings.GetKeypairByKeyUID(account.KeyUID)
|
||||
|
@ -1263,7 +1275,7 @@ func (m *Messenger) SignData(signParams []account.SignParams) ([]string, error)
|
|||
}
|
||||
|
||||
if keypair.MigratedToKeycard() {
|
||||
return nil, errors.New("signing a joining community request for accounts migrated to keycard must be done with a keycard")
|
||||
return nil, errors.New(ErrSigningJoinRequestForKeycardAccounts)
|
||||
}
|
||||
|
||||
verifiedAccount, err := m.accountsManager.GetVerifiedWalletAccount(m.settings, param.Address, param.Password)
|
||||
|
@ -1473,7 +1485,7 @@ func (m *Messenger) EditSharedAddressesForCommunity(request *requests.EditShared
|
|||
}
|
||||
|
||||
if !community.HasMember(m.IdentityPublicKey()) {
|
||||
return nil, errors.New("not part of the community")
|
||||
return nil, errors.New(ErrNotPartOfCommunity)
|
||||
}
|
||||
|
||||
revealedAddresses := make([]gethcommon.Address, 0)
|
||||
|
@ -2386,7 +2398,7 @@ func (m *Messenger) SetCommunityShard(request *requests.SetCommunityShard) (*Mes
|
|||
}
|
||||
|
||||
if !community.IsControlNode() {
|
||||
return nil, errors.New("not admin or owner")
|
||||
return nil, errors.New(ErrNotAdminOrOwner)
|
||||
}
|
||||
|
||||
// Reset the community private key
|
||||
|
@ -2465,7 +2477,7 @@ func (m *Messenger) SetCommunityStorenodes(request *requests.SetCommunityStoreno
|
|||
return nil, err
|
||||
}
|
||||
if !community.IsControlNode() {
|
||||
return nil, errors.New("not admin or owner")
|
||||
return nil, errors.New(ErrNotAdminOrOwner)
|
||||
}
|
||||
|
||||
if err := m.communityStorenodes.UpdateStorenodesInDB(request.CommunityID, request.Storenodes, 0); err != nil {
|
||||
|
@ -3318,7 +3330,7 @@ func (m *Messenger) HandleCommunityShardKey(state *ReceivedMessageState, message
|
|||
|
||||
signer := state.CurrentMessageState.PublicKey
|
||||
if signer == nil {
|
||||
return errors.New("signer can't be nil")
|
||||
return errors.New(ErrReceiverIsNil)
|
||||
}
|
||||
|
||||
err = m.handleCommunityShardAndFiltersFromProto(community, message)
|
||||
|
@ -3378,7 +3390,7 @@ func (m *Messenger) handleCommunityShardAndFiltersFromProto(community *communiti
|
|||
|
||||
func (m *Messenger) handleCommunityPrivilegedUserSyncMessage(state *ReceivedMessageState, signer *ecdsa.PublicKey, message *protobuf.CommunityPrivilegedUserSyncMessage) error {
|
||||
if signer == nil {
|
||||
return errors.New("signer can't be nil")
|
||||
return errors.New(ErrSignerIsNil)
|
||||
}
|
||||
|
||||
community, err := m.communitiesManager.GetByID(message.CommunityId)
|
||||
|
@ -3391,7 +3403,7 @@ func (m *Messenger) handleCommunityPrivilegedUserSyncMessage(state *ReceivedMess
|
|||
// CONTROL_NODE were sent by a control node
|
||||
isControlNodeMsg := common.IsPubKeyEqual(community.ControlNode(), signer)
|
||||
if !isControlNodeMsg {
|
||||
return errors.New("accepted/requested to join sync messages can be send only by the control node")
|
||||
return errors.New(ErrSyncMessagesSentByNonControlNode)
|
||||
}
|
||||
|
||||
err = m.communitiesManager.ValidateCommunityPrivilegedUserSyncMessage(message)
|
||||
|
@ -3427,7 +3439,7 @@ func (m *Messenger) HandleCommunityPrivilegedUserSyncMessage(state *ReceivedMess
|
|||
|
||||
func (m *Messenger) sendSharedAddressToControlNode(receiver *ecdsa.PublicKey, community *communities.Community) (*communities.RequestToJoin, error) {
|
||||
if receiver == nil {
|
||||
return nil, errors.New("receiver can't be nil")
|
||||
return nil, errors.New(ErrReceiverIsNil)
|
||||
}
|
||||
|
||||
if community == nil {
|
||||
|
@ -4555,6 +4567,10 @@ func (m *Messenger) PromoteSelfToControlNode(communityID types.HexBytes) (*Messe
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if !communities.HasTokenOwnership(community.Description()) {
|
||||
return nil, errors.New(ErrOwnerTokenNeeded)
|
||||
}
|
||||
|
||||
changes, err := m.communitiesManager.PromoteSelfToControlNode(community, clock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
Loading…
Reference in New Issue