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
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = m.handleCommunityTokensMetadata(community); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Control node applies events and publish updated CommunityDescription
|
// Control node applies events and publish updated CommunityDescription
|
||||||
if community.IsControlNode() {
|
if community.IsControlNode() {
|
||||||
appliedEvents := map[string]uint64{}
|
appliedEvents := map[string]uint64{}
|
||||||
|
@ -5289,11 +5281,75 @@ func (m *Manager) promoteSelfToControlNode(community *Community, clock uint64) (
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = m.handleCommunityEvents(community)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
community.increaseClock()
|
community.increaseClock()
|
||||||
|
|
||||||
return ownerChanged, nil
|
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 {
|
func (m *Manager) shareRequestsToJoinWithNewPrivilegedMembers(community *Community, newPrivilegedMembers map[protobuf.CommunityMember_Roles][]*ecdsa.PublicKey) error {
|
||||||
requestsToJoin, err := m.GetCommunityRequestsToJoinWithRevealedAddresses(community.ID())
|
requestsToJoin, err := m.GetCommunityRequestsToJoinWithRevealedAddresses(community.ID())
|
||||||
if err != nil {
|
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
|
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 {
|
type FetchCommunityRequest struct {
|
||||||
// CommunityKey should be either a public or a private community key
|
// CommunityKey should be either a public or a private community key
|
||||||
CommunityKey string `json:"communityKey"`
|
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) {
|
func (m *Messenger) GenerateJoiningCommunityRequestsForSigning(memberPubKey string, communityID types.HexBytes, addressesToReveal []string) ([]account.SignParams, error) {
|
||||||
if len(communityID) == 0 {
|
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)
|
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 {
|
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)
|
keypair, err := m.settings.GetKeypairByKeyUID(account.KeyUID)
|
||||||
|
@ -1263,7 +1275,7 @@ func (m *Messenger) SignData(signParams []account.SignParams) ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
if keypair.MigratedToKeycard() {
|
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)
|
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()) {
|
if !community.HasMember(m.IdentityPublicKey()) {
|
||||||
return nil, errors.New("not part of the community")
|
return nil, errors.New(ErrNotPartOfCommunity)
|
||||||
}
|
}
|
||||||
|
|
||||||
revealedAddresses := make([]gethcommon.Address, 0)
|
revealedAddresses := make([]gethcommon.Address, 0)
|
||||||
|
@ -2386,7 +2398,7 @@ func (m *Messenger) SetCommunityShard(request *requests.SetCommunityShard) (*Mes
|
||||||
}
|
}
|
||||||
|
|
||||||
if !community.IsControlNode() {
|
if !community.IsControlNode() {
|
||||||
return nil, errors.New("not admin or owner")
|
return nil, errors.New(ErrNotAdminOrOwner)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the community private key
|
// Reset the community private key
|
||||||
|
@ -2465,7 +2477,7 @@ func (m *Messenger) SetCommunityStorenodes(request *requests.SetCommunityStoreno
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !community.IsControlNode() {
|
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 {
|
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
|
signer := state.CurrentMessageState.PublicKey
|
||||||
if signer == nil {
|
if signer == nil {
|
||||||
return errors.New("signer can't be nil")
|
return errors.New(ErrReceiverIsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = m.handleCommunityShardAndFiltersFromProto(community, message)
|
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 {
|
func (m *Messenger) handleCommunityPrivilegedUserSyncMessage(state *ReceivedMessageState, signer *ecdsa.PublicKey, message *protobuf.CommunityPrivilegedUserSyncMessage) error {
|
||||||
if signer == nil {
|
if signer == nil {
|
||||||
return errors.New("signer can't be nil")
|
return errors.New(ErrSignerIsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
community, err := m.communitiesManager.GetByID(message.CommunityId)
|
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
|
// CONTROL_NODE were sent by a control node
|
||||||
isControlNodeMsg := common.IsPubKeyEqual(community.ControlNode(), signer)
|
isControlNodeMsg := common.IsPubKeyEqual(community.ControlNode(), signer)
|
||||||
if !isControlNodeMsg {
|
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)
|
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) {
|
func (m *Messenger) sendSharedAddressToControlNode(receiver *ecdsa.PublicKey, community *communities.Community) (*communities.RequestToJoin, error) {
|
||||||
if receiver == nil {
|
if receiver == nil {
|
||||||
return nil, errors.New("receiver can't be nil")
|
return nil, errors.New(ErrReceiverIsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if community == nil {
|
if community == nil {
|
||||||
|
@ -4555,6 +4567,10 @@ func (m *Messenger) PromoteSelfToControlNode(communityID types.HexBytes) (*Messe
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !communities.HasTokenOwnership(community.Description()) {
|
||||||
|
return nil, errors.New(ErrOwnerTokenNeeded)
|
||||||
|
}
|
||||||
|
|
||||||
changes, err := m.communitiesManager.PromoteSelfToControlNode(community, clock)
|
changes, err := m.communitiesManager.PromoteSelfToControlNode(community, clock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
Loading…
Reference in New Issue