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 }