status-go/protocol/communities/community_event_message.go

286 lines
10 KiB
Go

package communities
import (
"bytes"
"crypto/ecdsa"
"encoding/json"
"errors"
"sort"
"github.com/golang/protobuf/proto"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/protocol/protobuf"
)
type CommunityEvent struct {
CommunityEventClock uint64 `json:"communityEventClock"`
Type protobuf.CommunityEvent_EventType `json:"type"`
CommunityConfig *protobuf.CommunityConfig `json:"communityConfig,omitempty"`
TokenPermission *protobuf.CommunityTokenPermission `json:"tokenPermissions,omitempty"`
CategoryData *protobuf.CategoryData `json:"categoryData,omitempty"`
ChannelData *protobuf.ChannelData `json:"channelData,omitempty"`
MemberToAction string `json:"memberToAction,omitempty"`
MembersAdded map[string]*protobuf.CommunityMember `json:"membersAdded,omitempty"`
RejectedRequestsToJoin map[string]*protobuf.CommunityRequestToJoin `json:"rejectedRequestsToJoin,omitempty"`
AcceptedRequestsToJoin map[string]*protobuf.CommunityRequestToJoin `json:"acceptedRequestsToJoin,omitempty"`
TokenMetadata *protobuf.CommunityTokenMetadata `json:"tokenMetadata,omitempty"`
Payload []byte `json:"payload"`
Signature []byte `json:"signature"`
}
func (e *CommunityEvent) ToProtobuf() *protobuf.CommunityEvent {
return &protobuf.CommunityEvent{
CommunityEventClock: e.CommunityEventClock,
Type: e.Type,
CommunityConfig: e.CommunityConfig,
TokenPermission: e.TokenPermission,
CategoryData: e.CategoryData,
ChannelData: e.ChannelData,
MemberToAction: e.MemberToAction,
MembersAdded: e.MembersAdded,
RejectedRequestsToJoin: e.RejectedRequestsToJoin,
AcceptedRequestsToJoin: e.AcceptedRequestsToJoin,
TokenMetadata: e.TokenMetadata,
}
}
func communityEventFromProtobuf(msg *protobuf.SignedCommunityEvent) (*CommunityEvent, error) {
decodedEvent := protobuf.CommunityEvent{}
err := proto.Unmarshal(msg.Payload, &decodedEvent)
if err != nil {
return nil, err
}
return &CommunityEvent{
CommunityEventClock: decodedEvent.CommunityEventClock,
Type: decodedEvent.Type,
CommunityConfig: decodedEvent.CommunityConfig,
TokenPermission: decodedEvent.TokenPermission,
CategoryData: decodedEvent.CategoryData,
ChannelData: decodedEvent.ChannelData,
MemberToAction: decodedEvent.MemberToAction,
MembersAdded: decodedEvent.MembersAdded,
RejectedRequestsToJoin: decodedEvent.RejectedRequestsToJoin,
AcceptedRequestsToJoin: decodedEvent.AcceptedRequestsToJoin,
TokenMetadata: decodedEvent.TokenMetadata,
Payload: msg.Payload,
Signature: msg.Signature,
}, nil
}
func (e *CommunityEvent) RecoverSigner() (*ecdsa.PublicKey, error) {
if e.Signature == nil || len(e.Signature) == 0 {
return nil, errors.New("missing signature")
}
signer, err := crypto.SigToPub(
crypto.Keccak256(e.Payload),
e.Signature,
)
if err != nil {
return nil, errors.New("failed to recover signer")
}
return signer, nil
}
func (e *CommunityEvent) Sign(pk *ecdsa.PrivateKey) error {
sig, err := crypto.Sign(crypto.Keccak256(e.Payload), pk)
if err != nil {
return err
}
e.Signature = sig
return nil
}
type CommunityEventsMessage struct {
CommunityID []byte `json:"communityId"`
EventsBaseCommunityDescription []byte `json:"eventsBaseCommunityDescription"`
Events []CommunityEvent `json:"events,omitempty"`
}
func (m *CommunityEventsMessage) ToProtobuf() protobuf.CommunityEventsMessage {
result := protobuf.CommunityEventsMessage{
CommunityId: m.CommunityID,
EventsBaseCommunityDescription: m.EventsBaseCommunityDescription,
SignedEvents: []*protobuf.SignedCommunityEvent{},
}
for _, event := range m.Events {
signedEvent := &protobuf.SignedCommunityEvent{
Signature: event.Signature,
Payload: event.Payload,
}
result.SignedEvents = append(result.SignedEvents, signedEvent)
}
return result
}
func CommunityEventsMessageFromProtobuf(msg *protobuf.CommunityEventsMessage) (*CommunityEventsMessage, error) {
result := &CommunityEventsMessage{
CommunityID: msg.CommunityId,
EventsBaseCommunityDescription: msg.EventsBaseCommunityDescription,
Events: []CommunityEvent{},
}
for _, signedEvent := range msg.SignedEvents {
event, err := communityEventFromProtobuf(signedEvent)
if err != nil {
return nil, err
}
result.Events = append(result.Events, *event)
}
return result, nil
}
func (m *CommunityEventsMessage) Marshal() ([]byte, error) {
pb := m.ToProtobuf()
return proto.Marshal(&pb)
}
func (c *Community) mergeCommunityEvents(communityEventMessage *CommunityEventsMessage) {
if c.config.EventsData == nil {
c.config.EventsData = &EventsData{
EventsBaseCommunityDescription: communityEventMessage.EventsBaseCommunityDescription,
Events: communityEventMessage.Events,
}
return
}
for _, update := range communityEventMessage.Events {
var exists bool
for _, existing := range c.config.EventsData.Events {
if isCommunityEventsEqual(update, existing) {
exists = true
break
}
}
if !exists {
c.config.EventsData.Events = append(c.config.EventsData.Events, update)
}
}
c.sortCommunityEvents()
}
func (c *Community) sortCommunityEvents() {
sort.Slice(c.config.EventsData.Events, func(i, j int) bool {
return c.config.EventsData.Events[i].CommunityEventClock < c.config.EventsData.Events[j].CommunityEventClock
})
}
func validateCommunityEvent(communityEvent *CommunityEvent) error {
switch communityEvent.Type {
case protobuf.CommunityEvent_COMMUNITY_EDIT:
if communityEvent.CommunityConfig == nil || communityEvent.CommunityConfig.Identity == nil ||
communityEvent.CommunityConfig.Permissions == nil || communityEvent.CommunityConfig.AdminSettings == nil {
return errors.New("invalid config change admin event")
}
case protobuf.CommunityEvent_COMMUNITY_MEMBER_TOKEN_PERMISSION_CHANGE:
if communityEvent.TokenPermission == nil || len(communityEvent.TokenPermission.Id) == 0 {
return errors.New("invalid token permission change event")
}
case protobuf.CommunityEvent_COMMUNITY_MEMBER_TOKEN_PERMISSION_DELETE:
if communityEvent.TokenPermission == nil || len(communityEvent.TokenPermission.Id) == 0 {
return errors.New("invalid token permission delete event")
}
case protobuf.CommunityEvent_COMMUNITY_CATEGORY_CREATE:
if communityEvent.CategoryData == nil || len(communityEvent.CategoryData.CategoryId) == 0 {
return errors.New("invalid community category create event")
}
case protobuf.CommunityEvent_COMMUNITY_CATEGORY_DELETE:
if communityEvent.CategoryData == nil || len(communityEvent.CategoryData.CategoryId) == 0 {
return errors.New("invalid community category delete event")
}
case protobuf.CommunityEvent_COMMUNITY_CATEGORY_EDIT:
if communityEvent.CategoryData == nil || len(communityEvent.CategoryData.CategoryId) == 0 {
return errors.New("invalid community category edit event")
}
case protobuf.CommunityEvent_COMMUNITY_CHANNEL_CREATE:
if communityEvent.ChannelData == nil || len(communityEvent.ChannelData.ChannelId) == 0 ||
communityEvent.ChannelData.Channel == nil {
return errors.New("invalid community channel create event")
}
case protobuf.CommunityEvent_COMMUNITY_CHANNEL_DELETE:
if communityEvent.ChannelData == nil || len(communityEvent.ChannelData.ChannelId) == 0 {
return errors.New("invalid community channel delete event")
}
case protobuf.CommunityEvent_COMMUNITY_CHANNEL_EDIT:
if communityEvent.ChannelData == nil || len(communityEvent.ChannelData.ChannelId) == 0 ||
communityEvent.ChannelData.Channel == nil {
return errors.New("invalid community channel edit event")
}
case protobuf.CommunityEvent_COMMUNITY_CHANNEL_REORDER:
if communityEvent.ChannelData == nil || len(communityEvent.ChannelData.ChannelId) == 0 {
return errors.New("invalid community channel reorder event")
}
case protobuf.CommunityEvent_COMMUNITY_CATEGORY_REORDER:
if communityEvent.CategoryData == nil || len(communityEvent.CategoryData.CategoryId) == 0 {
return errors.New("invalid community category reorder event")
}
case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT:
if communityEvent.AcceptedRequestsToJoin == nil {
return errors.New("invalid community request to join accepted event")
}
case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_REJECT:
if communityEvent.RejectedRequestsToJoin == nil {
return errors.New("invalid community request to join reject event")
}
case protobuf.CommunityEvent_COMMUNITY_MEMBER_KICK:
if len(communityEvent.MemberToAction) == 0 {
return errors.New("invalid community member kick event")
}
case protobuf.CommunityEvent_COMMUNITY_MEMBER_BAN:
if len(communityEvent.MemberToAction) == 0 {
return errors.New("invalid community member ban event")
}
case protobuf.CommunityEvent_COMMUNITY_MEMBER_UNBAN:
if len(communityEvent.MemberToAction) == 0 {
return errors.New("invalid community member unban event")
}
case protobuf.CommunityEvent_COMMUNITY_TOKEN_ADD:
if communityEvent.TokenMetadata == nil || len(communityEvent.TokenMetadata.ContractAddresses) == 0 {
return errors.New("invalid add community token event")
}
}
return nil
}
func isCommunityEventsEqual(left CommunityEvent, right CommunityEvent) bool {
return bytes.Equal(left.Payload, right.Payload)
}
func communityEventsToJSONEncodedBytes(communityEvents []CommunityEvent) ([]byte, error) {
return json.Marshal(communityEvents)
}
func communityEventsFromJSONEncodedBytes(jsonEncodedRawEvents []byte) ([]CommunityEvent, error) {
var events []CommunityEvent
err := json.Unmarshal(jsonEncodedRawEvents, &events)
if err != nil {
return nil, err
}
return events, nil
}