github.com/status-im/status-go@v1.1.0/protocol/communities/community_events_processing.go (about)

     1  package communities
     2  
     3  import (
     4  	"crypto/ecdsa"
     5  	"errors"
     6  	"sort"
     7  
     8  	"github.com/golang/protobuf/proto"
     9  	"go.uber.org/zap"
    10  
    11  	utils "github.com/status-im/status-go/common"
    12  	"github.com/status-im/status-go/protocol/common"
    13  	"github.com/status-im/status-go/protocol/protobuf"
    14  )
    15  
    16  var ErrInvalidCommunityEventClock = errors.New("clock for admin event message is outdated")
    17  
    18  func (o *Community) processEvents(message *CommunityEventsMessage, lastlyAppliedEvents map[string]uint64) error {
    19  	processor := &eventsProcessor{
    20  		community:           o,
    21  		message:             message,
    22  		logger:              o.config.Logger.Named("eventsProcessor"),
    23  		lastlyAppliedEvents: lastlyAppliedEvents,
    24  	}
    25  	return processor.exec()
    26  }
    27  
    28  type eventsProcessor struct {
    29  	community           *Community
    30  	message             *CommunityEventsMessage
    31  	logger              *zap.Logger
    32  	lastlyAppliedEvents map[string]uint64
    33  
    34  	eventsToApply []CommunityEvent
    35  }
    36  
    37  func (e *eventsProcessor) exec() error {
    38  	e.community.mutex.Lock()
    39  	defer e.community.mutex.Unlock()
    40  
    41  	err := e.validateDescription()
    42  	if err != nil {
    43  		return err
    44  	}
    45  
    46  	e.filterEvents()
    47  	e.mergeEvents()
    48  	e.retainNewestEventsPerEventTypeID()
    49  	e.sortEvents()
    50  	e.applyEvents()
    51  
    52  	return nil
    53  }
    54  
    55  func (e *eventsProcessor) validateDescription() error {
    56  	description, err := validateAndGetEventsMessageCommunityDescription(e.message.EventsBaseCommunityDescription, e.community.ControlNode())
    57  	if err != nil {
    58  		return err
    59  	}
    60  
    61  	// Control node is the only entity that can apply events from past description.
    62  	// In this case, events are compared against the clocks of the most recently applied events.
    63  	if e.community.IsControlNode() && description.Clock < e.community.config.CommunityDescription.Clock {
    64  		return nil
    65  	}
    66  
    67  	if description.Clock != e.community.config.CommunityDescription.Clock {
    68  		return ErrInvalidCommunityEventClock
    69  	}
    70  
    71  	return nil
    72  }
    73  
    74  func (e *eventsProcessor) validateEvent(event *CommunityEvent) error {
    75  	if e.lastlyAppliedEvents != nil {
    76  		if clock, found := e.lastlyAppliedEvents[event.EventTypeID()]; found && clock >= event.CommunityEventClock {
    77  			return errors.New("event outdated")
    78  		}
    79  	}
    80  
    81  	signer, err := event.RecoverSigner()
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	return e.community.validateEvent(event, signer)
    87  }
    88  
    89  // Filter invalid and outdated events.
    90  func (e *eventsProcessor) filterEvents() {
    91  	for _, ev := range e.message.Events {
    92  		event := ev
    93  		if err := e.validateEvent(&event); err == nil {
    94  			e.eventsToApply = append(e.eventsToApply, event)
    95  		} else {
    96  			e.logger.Warn("invalid community event", zap.String("EventTypeID", event.EventTypeID()), zap.Uint64("clock", event.CommunityEventClock), zap.Error(err))
    97  		}
    98  	}
    99  }
   100  
   101  // Merge message's events with community's events.
   102  func (e *eventsProcessor) mergeEvents() {
   103  	if e.community.config.EventsData != nil {
   104  		for _, ev := range e.community.config.EventsData.Events {
   105  			event := ev
   106  			if err := e.validateEvent(&event); err == nil {
   107  				e.eventsToApply = append(e.eventsToApply, event)
   108  			} else {
   109  				// NOTE: this should not happen, events should be validated before they are saved in the db.
   110  				// It has been identified that an invalid event is saved to the database for some reason.
   111  				// The code flow leading to this behavior is not yet known.
   112  				// https://github.com/status-im/status-desktop/issues/14106
   113  				e.logger.Error("invalid community event read from db", zap.String("EventTypeID", event.EventTypeID()), zap.Uint64("clock", event.CommunityEventClock), zap.Error(err))
   114  			}
   115  		}
   116  	}
   117  }
   118  
   119  // Keep only the newest event per PropertyTypeID.
   120  func (e *eventsProcessor) retainNewestEventsPerEventTypeID() {
   121  	eventsMap := make(map[string]CommunityEvent)
   122  
   123  	for _, event := range e.eventsToApply {
   124  		if existingEvent, found := eventsMap[event.EventTypeID()]; !found || event.CommunityEventClock > existingEvent.CommunityEventClock {
   125  			eventsMap[event.EventTypeID()] = event
   126  		}
   127  	}
   128  
   129  	e.eventsToApply = []CommunityEvent{}
   130  	for _, event := range eventsMap {
   131  		e.eventsToApply = append(e.eventsToApply, event)
   132  	}
   133  }
   134  
   135  // Sorts events by clock.
   136  func (e *eventsProcessor) sortEvents() {
   137  	sort.Slice(e.eventsToApply, func(i, j int) bool {
   138  		if e.eventsToApply[i].CommunityEventClock == e.eventsToApply[j].CommunityEventClock {
   139  			return e.eventsToApply[i].Type < e.eventsToApply[j].Type
   140  		}
   141  		return e.eventsToApply[i].CommunityEventClock < e.eventsToApply[j].CommunityEventClock
   142  	})
   143  }
   144  
   145  func (e *eventsProcessor) applyEvents() {
   146  	if e.community.config.EventsData == nil {
   147  		e.community.config.EventsData = &EventsData{
   148  			EventsBaseCommunityDescription: e.message.EventsBaseCommunityDescription,
   149  		}
   150  	}
   151  	e.community.config.EventsData.Events = e.eventsToApply
   152  
   153  	e.community.applyEvents()
   154  }
   155  
   156  func (o *Community) applyEvents() {
   157  	if o.config.EventsData == nil {
   158  		return
   159  	}
   160  
   161  	for _, event := range o.config.EventsData.Events {
   162  		err := o.applyEvent(event)
   163  		if err != nil {
   164  			o.config.Logger.Warn("failed to apply event", zap.String("EventTypeID", event.EventTypeID()), zap.Uint64("clock", event.CommunityEventClock), zap.Error(err))
   165  		}
   166  	}
   167  }
   168  
   169  func (o *Community) applyEvent(communityEvent CommunityEvent) error {
   170  	switch communityEvent.Type {
   171  	case protobuf.CommunityEvent_COMMUNITY_EDIT:
   172  		o.config.CommunityDescription.Identity = communityEvent.CommunityConfig.Identity
   173  		o.config.CommunityDescription.Permissions = communityEvent.CommunityConfig.Permissions
   174  		o.config.CommunityDescription.AdminSettings = communityEvent.CommunityConfig.AdminSettings
   175  		o.config.CommunityDescription.IntroMessage = communityEvent.CommunityConfig.IntroMessage
   176  		o.config.CommunityDescription.OutroMessage = communityEvent.CommunityConfig.OutroMessage
   177  		o.config.CommunityDescription.Tags = communityEvent.CommunityConfig.Tags
   178  
   179  	case protobuf.CommunityEvent_COMMUNITY_MEMBER_TOKEN_PERMISSION_CHANGE:
   180  		if o.IsControlNode() {
   181  			_, err := o.upsertTokenPermission(communityEvent.TokenPermission)
   182  			if err != nil {
   183  				return err
   184  			}
   185  		}
   186  
   187  	case protobuf.CommunityEvent_COMMUNITY_MEMBER_TOKEN_PERMISSION_DELETE:
   188  		if o.IsControlNode() {
   189  			_, err := o.deleteTokenPermission(communityEvent.TokenPermission.Id)
   190  			if err != nil {
   191  				return err
   192  			}
   193  		}
   194  
   195  	case protobuf.CommunityEvent_COMMUNITY_CATEGORY_CREATE:
   196  		_, err := o.createCategory(communityEvent.CategoryData.CategoryId, communityEvent.CategoryData.Name, communityEvent.CategoryData.ChannelsIds)
   197  		if err != nil {
   198  			return err
   199  		}
   200  
   201  	case protobuf.CommunityEvent_COMMUNITY_CATEGORY_DELETE:
   202  		_, err := o.deleteCategory(communityEvent.CategoryData.CategoryId)
   203  		if err != nil {
   204  			return err
   205  		}
   206  
   207  	case protobuf.CommunityEvent_COMMUNITY_CATEGORY_EDIT:
   208  		_, err := o.editCategory(communityEvent.CategoryData.CategoryId, communityEvent.CategoryData.Name, communityEvent.CategoryData.ChannelsIds)
   209  		if err != nil {
   210  			return err
   211  		}
   212  
   213  	case protobuf.CommunityEvent_COMMUNITY_CHANNEL_CREATE:
   214  		err := o.createChat(communityEvent.ChannelData.ChannelId, communityEvent.ChannelData.Channel)
   215  		if err != nil {
   216  			return err
   217  		}
   218  
   219  	case protobuf.CommunityEvent_COMMUNITY_CHANNEL_DELETE:
   220  		o.deleteChat(communityEvent.ChannelData.ChannelId)
   221  
   222  	case protobuf.CommunityEvent_COMMUNITY_CHANNEL_EDIT:
   223  		err := o.editChat(communityEvent.ChannelData.ChannelId, communityEvent.ChannelData.Channel)
   224  		if err != nil {
   225  			return err
   226  		}
   227  
   228  	case protobuf.CommunityEvent_COMMUNITY_CHANNEL_REORDER:
   229  		_, err := o.reorderChat(communityEvent.ChannelData.CategoryId, communityEvent.ChannelData.ChannelId, int(communityEvent.ChannelData.Position))
   230  		if err != nil {
   231  			return err
   232  		}
   233  
   234  	case protobuf.CommunityEvent_COMMUNITY_CATEGORY_REORDER:
   235  		_, err := o.reorderCategories(communityEvent.CategoryData.CategoryId, int(communityEvent.CategoryData.Position))
   236  		if err != nil {
   237  			return err
   238  		}
   239  
   240  	case protobuf.CommunityEvent_COMMUNITY_MEMBER_KICK:
   241  		if o.IsControlNode() {
   242  			_ = o.RemoveMembersFromOrg([]string{communityEvent.MemberToAction})
   243  		}
   244  	case protobuf.CommunityEvent_COMMUNITY_MEMBER_BAN:
   245  		if o.IsControlNode() {
   246  			pk, err := common.HexToPubkey(communityEvent.MemberToAction)
   247  			if err != nil {
   248  				return err
   249  			}
   250  			o.banUserFromCommunity(pk, &protobuf.CommunityBanInfo{DeleteAllMessages: false})
   251  		}
   252  	case protobuf.CommunityEvent_COMMUNITY_MEMBER_UNBAN:
   253  		if o.IsControlNode() {
   254  			pk, err := common.HexToPubkey(communityEvent.MemberToAction)
   255  			if err != nil {
   256  				return err
   257  			}
   258  			o.unbanUserFromCommunity(pk)
   259  		}
   260  	case protobuf.CommunityEvent_COMMUNITY_TOKEN_ADD:
   261  		o.config.CommunityDescription.CommunityTokensMetadata = append(o.config.CommunityDescription.CommunityTokensMetadata, communityEvent.TokenMetadata)
   262  	case protobuf.CommunityEvent_COMMUNITY_DELETE_BANNED_MEMBER_MESSAGES:
   263  		if o.IsControlNode() {
   264  			pk, err := common.HexToPubkey(communityEvent.MemberToAction)
   265  			if err != nil {
   266  				return err
   267  			}
   268  
   269  			err = o.deleteBannedMemberAllMessages(pk)
   270  			if err != nil {
   271  				return err
   272  			}
   273  		}
   274  	}
   275  	return nil
   276  }
   277  
   278  func (o *Community) addNewCommunityEvent(event *CommunityEvent) error {
   279  	err := event.Validate()
   280  	if err != nil {
   281  		return err
   282  	}
   283  
   284  	// All events must be built on top of the control node CommunityDescription
   285  	// If there were no events before, extract CommunityDescription from CommunityDescriptionProtocolMessage
   286  	// and check the signature
   287  	if o.config.EventsData == nil || len(o.config.EventsData.EventsBaseCommunityDescription) == 0 {
   288  		_, err := validateAndGetEventsMessageCommunityDescription(o.config.CommunityDescriptionProtocolMessage, o.ControlNode())
   289  		if err != nil {
   290  			return err
   291  		}
   292  
   293  		o.config.EventsData = &EventsData{
   294  			EventsBaseCommunityDescription: o.config.CommunityDescriptionProtocolMessage,
   295  			Events:                         []CommunityEvent{},
   296  		}
   297  	}
   298  
   299  	event.Payload, err = proto.Marshal(event.ToProtobuf())
   300  	if err != nil {
   301  		return err
   302  	}
   303  
   304  	o.config.EventsData.Events = append(o.config.EventsData.Events, *event)
   305  
   306  	return nil
   307  }
   308  
   309  func (o *Community) toCommunityEventsMessage() *CommunityEventsMessage {
   310  	return &CommunityEventsMessage{
   311  		CommunityID:                    o.ID(),
   312  		EventsBaseCommunityDescription: o.config.EventsData.EventsBaseCommunityDescription,
   313  		Events:                         o.config.EventsData.Events,
   314  	}
   315  }
   316  
   317  func validateAndGetEventsMessageCommunityDescription(signedDescription []byte, signerPubkey *ecdsa.PublicKey) (*protobuf.CommunityDescription, error) {
   318  	metadata := &protobuf.ApplicationMetadataMessage{}
   319  
   320  	err := proto.Unmarshal(signedDescription, metadata)
   321  	if err != nil {
   322  		return nil, err
   323  	}
   324  
   325  	if metadata.Type != protobuf.ApplicationMetadataMessage_COMMUNITY_DESCRIPTION {
   326  		return nil, ErrInvalidMessage
   327  	}
   328  
   329  	signer, err := utils.RecoverKey(metadata)
   330  	if err != nil {
   331  		return nil, err
   332  	}
   333  
   334  	if signer == nil {
   335  		return nil, errors.New("CommunityDescription does not contain the control node signature")
   336  	}
   337  
   338  	if !signer.Equal(signerPubkey) {
   339  		return nil, errors.New("CommunityDescription was not signed by an owner")
   340  	}
   341  
   342  	description := &protobuf.CommunityDescription{}
   343  
   344  	err = proto.Unmarshal(metadata.Payload, description)
   345  	if err != nil {
   346  		return nil, err
   347  	}
   348  
   349  	return description, nil
   350  }