2020-11-18 09:16:51 +00:00
|
|
|
package communities
|
|
|
|
|
|
|
|
import (
|
2024-04-17 14:53:51 +00:00
|
|
|
"bytes"
|
Check token funds when handling community requests to join
This adds checks to `HandleCommunityRequestToJoin` and
`AcceptRequestToJoinCommunity` that ensure a given user's revealed
wallet addresses own the token funds required by a community.
When community has token permissions of type `BECOME_MEMBER`, the
following happens when the owner receives a request:
1. Upon verifying provided wallet addresses by the requester, the owner
node accumulates all token funds related to the given wallets that
match the token criteria in the configured permissions
2. If the requester does not meet the necessary requirements, the
request to join will be declined. If the requester does have the
funds, he'll either be automatically accepted to the community, or
enters the next stage where an owner needs to manually accept the
request.
3. The the community does not automatically accept users, then the funds
check will happen again, when the owner tries to manually accept the
request. If the necessary funds do not exist at this stage, the
request will be declined
4. Upon accepting, whether automatically or manually, the owner adds the
requester's wallet addresses to the `CommunityDescription`, such that
they can be retrieved later when doing periodic checks or when
permissions have changed.
2023-03-16 14:35:33 +00:00
|
|
|
"context"
|
2020-11-18 09:16:51 +00:00
|
|
|
"crypto/ecdsa"
|
|
|
|
"database/sql"
|
2023-11-29 17:21:21 +00:00
|
|
|
"encoding/hex"
|
2021-05-18 19:32:15 +00:00
|
|
|
"fmt"
|
2023-03-14 12:02:30 +00:00
|
|
|
"io/ioutil"
|
2022-03-21 14:18:36 +00:00
|
|
|
"os"
|
2023-11-29 17:21:21 +00:00
|
|
|
"strconv"
|
2021-07-02 18:07:49 +00:00
|
|
|
"strings"
|
2022-03-21 14:18:36 +00:00
|
|
|
"sync"
|
2021-01-11 10:32:51 +00:00
|
|
|
"time"
|
2020-11-18 09:16:51 +00:00
|
|
|
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
|
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
Check token funds when handling community requests to join
This adds checks to `HandleCommunityRequestToJoin` and
`AcceptRequestToJoinCommunity` that ensure a given user's revealed
wallet addresses own the token funds required by a community.
When community has token permissions of type `BECOME_MEMBER`, the
following happens when the owner receives a request:
1. Upon verifying provided wallet addresses by the requester, the owner
node accumulates all token funds related to the given wallets that
match the token criteria in the configured permissions
2. If the requester does not meet the necessary requirements, the
request to join will be declined. If the requester does have the
funds, he'll either be automatically accepted to the community, or
enters the next stage where an owner needs to manually accept the
request.
3. The the community does not automatically accept users, then the funds
check will happen again, when the owner tries to manually accept the
request. If the necessary funds do not exist at this stage, the
request will be declined
4. Upon accepting, whether automatically or manually, the owner adds the
requester's wallet addresses to the `CommunityDescription`, such that
they can be retrieved later when doing periodic checks or when
permissions have changed.
2023-03-16 14:35:33 +00:00
|
|
|
gethcommon "github.com/ethereum/go-ethereum/common"
|
2023-04-25 12:00:17 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
|
|
|
"github.com/status-im/status-go/account"
|
2023-11-15 15:58:15 +00:00
|
|
|
utils "github.com/status-im/status-go/common"
|
2020-11-18 09:16:51 +00:00
|
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
|
|
|
"github.com/status-im/status-go/eth-node/types"
|
2023-03-14 12:02:30 +00:00
|
|
|
"github.com/status-im/status-go/images"
|
2024-04-03 14:49:57 +00:00
|
|
|
multiaccountscommon "github.com/status-im/status-go/multiaccounts/common"
|
2022-03-21 14:18:36 +00:00
|
|
|
"github.com/status-im/status-go/params"
|
2021-01-11 10:32:51 +00:00
|
|
|
"github.com/status-im/status-go/protocol/common"
|
2023-11-15 15:58:15 +00:00
|
|
|
"github.com/status-im/status-go/protocol/common/shard"
|
2023-07-07 13:03:37 +00:00
|
|
|
community_token "github.com/status-im/status-go/protocol/communities/token"
|
2022-10-14 09:26:10 +00:00
|
|
|
"github.com/status-im/status-go/protocol/encryption"
|
2021-01-11 10:32:51 +00:00
|
|
|
"github.com/status-im/status-go/protocol/ens"
|
2020-11-18 09:16:51 +00:00
|
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
2021-01-11 10:32:51 +00:00
|
|
|
"github.com/status-im/status-go/protocol/requests"
|
2022-03-21 14:18:36 +00:00
|
|
|
"github.com/status-im/status-go/protocol/transport"
|
2024-06-27 21:27:09 +00:00
|
|
|
"github.com/status-im/status-go/rpc/network"
|
2024-07-01 18:52:57 +00:00
|
|
|
"github.com/status-im/status-go/server"
|
2023-06-21 11:20:43 +00:00
|
|
|
"github.com/status-im/status-go/services/wallet/bigint"
|
2023-06-06 18:33:09 +00:00
|
|
|
walletcommon "github.com/status-im/status-go/services/wallet/common"
|
2023-07-13 17:26:17 +00:00
|
|
|
"github.com/status-im/status-go/services/wallet/thirdparty"
|
Check token funds when handling community requests to join
This adds checks to `HandleCommunityRequestToJoin` and
`AcceptRequestToJoinCommunity` that ensure a given user's revealed
wallet addresses own the token funds required by a community.
When community has token permissions of type `BECOME_MEMBER`, the
following happens when the owner receives a request:
1. Upon verifying provided wallet addresses by the requester, the owner
node accumulates all token funds related to the given wallets that
match the token criteria in the configured permissions
2. If the requester does not meet the necessary requirements, the
request to join will be declined. If the requester does have the
funds, he'll either be automatically accepted to the community, or
enters the next stage where an owner needs to manually accept the
request.
3. The the community does not automatically accept users, then the funds
check will happen again, when the owner tries to manually accept the
request. If the necessary funds do not exist at this stage, the
request will be declined
4. Upon accepting, whether automatically or manually, the owner adds the
requester's wallet addresses to the `CommunityDescription`, such that
they can be retrieved later when doing periodic checks or when
permissions have changed.
2023-03-16 14:35:33 +00:00
|
|
|
"github.com/status-im/status-go/services/wallet/token"
|
2022-03-21 14:18:36 +00:00
|
|
|
"github.com/status-im/status-go/signal"
|
2024-03-14 08:39:06 +00:00
|
|
|
"github.com/status-im/status-go/transactions"
|
2020-11-18 09:16:51 +00:00
|
|
|
)
|
|
|
|
|
2024-05-30 15:04:47 +00:00
|
|
|
type Publisher interface {
|
|
|
|
publish(subscription *Subscription)
|
|
|
|
}
|
|
|
|
|
2022-03-21 14:18:36 +00:00
|
|
|
var defaultAnnounceList = [][]string{
|
|
|
|
{"udp://tracker.opentrackr.org:1337/announce"},
|
|
|
|
{"udp://tracker.openbittorrent.com:6969/announce"},
|
|
|
|
}
|
|
|
|
var pieceLength = 100 * 1024
|
|
|
|
|
2023-01-16 14:17:19 +00:00
|
|
|
const maxArchiveSizeInBytes = 30000000
|
|
|
|
|
2024-05-01 17:27:31 +00:00
|
|
|
var maxNbMembers = 5000
|
|
|
|
var maxNbPendingRequestedMembers = 100
|
|
|
|
|
2024-05-24 20:21:56 +00:00
|
|
|
var memberPermissionsCheckInterval = 8 * time.Hour
|
2023-07-05 17:35:22 +00:00
|
|
|
var validateInterval = 2 * time.Minute
|
|
|
|
|
|
|
|
// Used for testing only
|
|
|
|
func SetValidateInterval(duration time.Duration) {
|
|
|
|
validateInterval = duration
|
|
|
|
}
|
2024-05-01 17:27:31 +00:00
|
|
|
func SetMaxNbMembers(maxNb int) {
|
|
|
|
maxNbMembers = maxNb
|
|
|
|
}
|
|
|
|
func SetMaxNbPendingRequestedMembers(maxNb int) {
|
|
|
|
maxNbPendingRequestedMembers = maxNb
|
|
|
|
}
|
2023-03-20 12:26:20 +00:00
|
|
|
|
2023-03-23 18:48:46 +00:00
|
|
|
// errors
|
|
|
|
var (
|
|
|
|
ErrTorrentTimedout = errors.New("torrent has timed out")
|
|
|
|
ErrCommunityRequestAlreadyRejected = errors.New("that user was already rejected from the community")
|
2023-12-09 12:46:30 +00:00
|
|
|
ErrInvalidClock = errors.New("invalid clock to cancel request to join")
|
2023-03-23 18:48:46 +00:00
|
|
|
)
|
2022-10-07 10:24:50 +00:00
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
type Manager struct {
|
2024-05-29 15:09:00 +00:00
|
|
|
persistence *Persistence
|
|
|
|
encryptor *encryption.Protocol
|
|
|
|
ensSubscription chan []*ens.VerificationRecord
|
|
|
|
subscriptions []chan *Subscription
|
|
|
|
ensVerifier *ens.Verifier
|
|
|
|
ownerVerifier OwnerVerifier
|
|
|
|
identity *ecdsa.PrivateKey
|
|
|
|
installationID string
|
|
|
|
accountsManager account.Manager
|
|
|
|
tokenManager TokenManager
|
|
|
|
collectiblesManager CollectiblesManager
|
|
|
|
logger *zap.Logger
|
|
|
|
transport *transport.Transport
|
|
|
|
timesource common.TimeSource
|
|
|
|
quit chan struct{}
|
|
|
|
walletConfig *params.WalletConfig
|
|
|
|
communityTokensService CommunityTokensServiceInterface
|
|
|
|
membersReevaluationTasks sync.Map // stores `membersReevaluationTask`
|
|
|
|
forceMembersReevaluation map[string]chan struct{}
|
|
|
|
stopped bool
|
|
|
|
RekeyInterval time.Duration
|
|
|
|
PermissionChecker PermissionChecker
|
|
|
|
keyDistributor KeyDistributor
|
|
|
|
communityLock *CommunityLock
|
2024-07-01 18:52:57 +00:00
|
|
|
mediaServer server.MediaServerInterface
|
2024-02-23 02:16:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type CommunityLock struct {
|
|
|
|
logger *zap.Logger
|
|
|
|
locks map[string]*sync.Mutex
|
|
|
|
mutex sync.Mutex
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewCommunityLock(logger *zap.Logger) *CommunityLock {
|
|
|
|
return &CommunityLock{
|
|
|
|
logger: logger.Named("CommunityLock"),
|
|
|
|
locks: make(map[string]*sync.Mutex),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *CommunityLock) Lock(communityID types.HexBytes) {
|
|
|
|
c.mutex.Lock()
|
|
|
|
communityIDStr := types.EncodeHex(communityID)
|
|
|
|
lock, ok := c.locks[communityIDStr]
|
|
|
|
if !ok {
|
|
|
|
lock = &sync.Mutex{}
|
|
|
|
c.locks[communityIDStr] = lock
|
|
|
|
}
|
|
|
|
c.mutex.Unlock()
|
|
|
|
|
|
|
|
lock.Lock()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *CommunityLock) Unlock(communityID types.HexBytes) {
|
|
|
|
c.mutex.Lock()
|
|
|
|
communityIDStr := types.EncodeHex(communityID)
|
|
|
|
lock, ok := c.locks[communityIDStr]
|
|
|
|
c.mutex.Unlock()
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
lock.Unlock()
|
|
|
|
} else {
|
|
|
|
c.logger.Warn("trying to unlock a non-existent lock", zap.String("communityID", communityIDStr))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *CommunityLock) Init() {
|
|
|
|
c.locks = make(map[string]*sync.Mutex)
|
|
|
|
}
|
|
|
|
|
2022-12-12 09:22:37 +00:00
|
|
|
type HistoryArchiveDownloadTask struct {
|
2023-01-23 14:32:35 +00:00
|
|
|
CancelChan chan struct{}
|
|
|
|
Waiter sync.WaitGroup
|
|
|
|
m sync.RWMutex
|
|
|
|
Cancelled bool
|
|
|
|
}
|
|
|
|
|
2024-06-05 11:53:09 +00:00
|
|
|
type HistoryArchiveDownloadTaskInfo struct {
|
|
|
|
TotalDownloadedArchivesCount int
|
|
|
|
TotalArchivesCount int
|
|
|
|
Cancelled bool
|
|
|
|
}
|
|
|
|
|
2024-06-06 14:59:46 +00:00
|
|
|
type ArchiveFileService interface {
|
2024-06-05 11:53:09 +00:00
|
|
|
CreateHistoryArchiveTorrentFromMessages(communityID types.HexBytes, messages []*types.Message, topics []types.TopicType, startDate time.Time, endDate time.Time, partition time.Duration, encrypt bool) ([]string, error)
|
|
|
|
CreateHistoryArchiveTorrentFromDB(communityID types.HexBytes, topics []types.TopicType, startDate time.Time, endDate time.Time, partition time.Duration, encrypt bool) ([]string, error)
|
|
|
|
SaveMessageArchiveID(communityID types.HexBytes, hash string) error
|
|
|
|
GetMessageArchiveIDsToImport(communityID types.HexBytes) ([]string, error)
|
|
|
|
SetMessageArchiveIDImported(communityID types.HexBytes, hash string, imported bool) error
|
|
|
|
ExtractMessagesFromHistoryArchive(communityID types.HexBytes, archiveID string) ([]*protobuf.WakuMessage, error)
|
|
|
|
GetHistoryArchiveMagnetlink(communityID types.HexBytes) (string, error)
|
|
|
|
LoadHistoryArchiveIndexFromFile(myKey *ecdsa.PrivateKey, communityID types.HexBytes) (*protobuf.WakuMessageArchiveIndex, error)
|
|
|
|
}
|
|
|
|
|
2024-06-06 14:59:46 +00:00
|
|
|
type ArchiveService interface {
|
|
|
|
ArchiveFileService
|
2024-06-05 11:53:09 +00:00
|
|
|
|
|
|
|
SetOnline(bool)
|
|
|
|
SetTorrentConfig(*params.TorrentConfig)
|
|
|
|
StartTorrentClient() error
|
|
|
|
Stop() error
|
|
|
|
IsReady() bool
|
|
|
|
GetCommunityChatsFilters(communityID types.HexBytes) ([]*transport.Filter, error)
|
|
|
|
GetCommunityChatsTopics(communityID types.HexBytes) ([]types.TopicType, error)
|
|
|
|
GetHistoryArchivePartitionStartTimestamp(communityID types.HexBytes) (uint64, error)
|
|
|
|
CreateAndSeedHistoryArchive(communityID types.HexBytes, topics []types.TopicType, startDate time.Time, endDate time.Time, partition time.Duration, encrypt bool) error
|
|
|
|
StartHistoryArchiveTasksInterval(community *Community, interval time.Duration)
|
|
|
|
StopHistoryArchiveTasksInterval(communityID types.HexBytes)
|
|
|
|
SeedHistoryArchiveTorrent(communityID types.HexBytes) error
|
|
|
|
UnseedHistoryArchiveTorrent(communityID types.HexBytes)
|
|
|
|
IsSeedingHistoryArchiveTorrent(communityID types.HexBytes) bool
|
|
|
|
GetHistoryArchiveDownloadTask(communityID string) *HistoryArchiveDownloadTask
|
|
|
|
AddHistoryArchiveDownloadTask(communityID string, task *HistoryArchiveDownloadTask)
|
|
|
|
DownloadHistoryArchivesByMagnetlink(communityID types.HexBytes, magnetlink string, cancelTask chan struct{}) (*HistoryArchiveDownloadTaskInfo, error)
|
|
|
|
TorrentFileExists(communityID string) bool
|
|
|
|
}
|
|
|
|
|
2024-06-07 09:27:32 +00:00
|
|
|
type ArchiveManagerConfig struct {
|
|
|
|
TorrentConfig *params.TorrentConfig
|
|
|
|
Logger *zap.Logger
|
|
|
|
Persistence *Persistence
|
|
|
|
Transport *transport.Transport
|
|
|
|
Identity *ecdsa.PrivateKey
|
|
|
|
Encryptor *encryption.Protocol
|
|
|
|
Publisher Publisher
|
|
|
|
}
|
|
|
|
|
2023-01-23 14:32:35 +00:00
|
|
|
func (t *HistoryArchiveDownloadTask) IsCancelled() bool {
|
|
|
|
t.m.RLock()
|
|
|
|
defer t.m.RUnlock()
|
|
|
|
return t.Cancelled
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *HistoryArchiveDownloadTask) Cancel() {
|
|
|
|
t.m.Lock()
|
|
|
|
defer t.m.Unlock()
|
|
|
|
t.Cancelled = true
|
|
|
|
close(t.CancelChan)
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2024-03-06 08:33:52 +00:00
|
|
|
type membersReevaluationTask struct {
|
2024-05-16 13:51:04 +00:00
|
|
|
lastStartTime time.Time
|
2024-03-06 08:33:52 +00:00
|
|
|
lastSuccessTime time.Time
|
|
|
|
onDemandRequestTime time.Time
|
|
|
|
mutex sync.Mutex
|
|
|
|
}
|
|
|
|
|
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
|
|
|
type managerOptions struct {
|
2023-08-25 15:36:39 +00:00
|
|
|
accountsManager account.Manager
|
|
|
|
tokenManager TokenManager
|
|
|
|
collectiblesManager CollectiblesManager
|
|
|
|
walletConfig *params.WalletConfig
|
2024-03-14 08:39:06 +00:00
|
|
|
communityTokensService CommunityTokensServiceInterface
|
2023-10-12 15:45:23 +00:00
|
|
|
permissionChecker PermissionChecker
|
2024-05-09 19:59:51 +00:00
|
|
|
|
|
|
|
// allowForcingCommunityMembersReevaluation indicates whether we should allow forcing community members reevaluation.
|
|
|
|
// This will allow using `force` argument in ScheduleMembersReevaluation.
|
|
|
|
// Should only be used in tests.
|
|
|
|
allowForcingCommunityMembersReevaluation bool
|
2023-04-25 12:00:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type TokenManager interface {
|
2024-06-11 21:00:04 +00:00
|
|
|
GetBalancesByChain(ctx context.Context, accounts, tokens []gethcommon.Address, chainIDs []uint64) (BalancesByChain, error)
|
|
|
|
GetCachedBalancesByChain(ctx context.Context, accounts, tokenAddresses []gethcommon.Address, chainIDs []uint64) (BalancesByChain, error)
|
2023-09-11 11:45:30 +00:00
|
|
|
FindOrCreateTokenByAddress(ctx context.Context, chainID uint64, address gethcommon.Address) *token.Token
|
2023-06-06 18:33:09 +00:00
|
|
|
GetAllChainIDs() ([]uint64, error)
|
2023-04-25 12:00:17 +00:00
|
|
|
}
|
|
|
|
|
2024-03-14 08:39:06 +00:00
|
|
|
type CollectibleContractData struct {
|
|
|
|
TotalSupply *bigint.BigInt
|
|
|
|
Transferable bool
|
|
|
|
RemoteBurnable bool
|
|
|
|
InfiniteSupply bool
|
|
|
|
}
|
|
|
|
|
|
|
|
type AssetContractData struct {
|
|
|
|
TotalSupply *bigint.BigInt
|
|
|
|
InfiniteSupply bool
|
|
|
|
}
|
|
|
|
|
|
|
|
type CommunityTokensServiceInterface interface {
|
|
|
|
GetCollectibleContractData(chainID uint64, contractAddress string) (*CollectibleContractData, error)
|
|
|
|
SetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, newSignerPubKey string) (string, error)
|
|
|
|
GetAssetContractData(chainID uint64, contractAddress string) (*AssetContractData, error)
|
|
|
|
SafeGetSignerPubKey(ctx context.Context, chainID uint64, communityID string) (string, error)
|
|
|
|
DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error)
|
2024-05-24 10:34:36 +00:00
|
|
|
ProcessCommunityTokenAction(message *protobuf.CommunityTokenAction) error
|
2024-03-14 08:39:06 +00:00
|
|
|
}
|
|
|
|
|
2023-04-25 12:00:17 +00:00
|
|
|
type DefaultTokenManager struct {
|
2024-06-27 21:27:09 +00:00
|
|
|
tokenManager *token.Manager
|
|
|
|
networkManager network.ManagerInterface
|
2023-04-25 12:00:17 +00:00
|
|
|
}
|
|
|
|
|
2024-06-27 21:27:09 +00:00
|
|
|
func NewDefaultTokenManager(tm *token.Manager, nm network.ManagerInterface) *DefaultTokenManager {
|
|
|
|
return &DefaultTokenManager{tokenManager: tm, networkManager: nm}
|
2023-04-25 12:00:17 +00:00
|
|
|
}
|
|
|
|
|
2023-06-06 18:33:09 +00:00
|
|
|
type BalancesByChain = map[uint64]map[gethcommon.Address]map[gethcommon.Address]*hexutil.Big
|
|
|
|
|
|
|
|
func (m *DefaultTokenManager) GetAllChainIDs() ([]uint64, error) {
|
2024-06-27 21:27:09 +00:00
|
|
|
networks, err := m.networkManager.Get(false)
|
2023-04-25 12:00:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-06-27 21:27:09 +00:00
|
|
|
areTestNetworksEnabled, err := m.networkManager.GetTestNetworksEnabled()
|
2024-02-22 15:17:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-04-25 12:00:17 +00:00
|
|
|
chainIDs := make([]uint64, 0)
|
|
|
|
for _, network := range networks {
|
2024-02-22 15:17:35 +00:00
|
|
|
if areTestNetworksEnabled == network.IsTest {
|
|
|
|
chainIDs = append(chainIDs, network.ChainID)
|
|
|
|
}
|
2023-04-25 12:00:17 +00:00
|
|
|
}
|
2023-06-06 18:33:09 +00:00
|
|
|
return chainIDs, nil
|
|
|
|
}
|
2023-04-25 12:00:17 +00:00
|
|
|
|
2023-07-13 17:26:17 +00:00
|
|
|
type CollectiblesManager interface {
|
2023-11-14 17:16:39 +00:00
|
|
|
FetchBalancesByOwnerAndContractAddress(ctx context.Context, chainID walletcommon.ChainID, ownerAddress gethcommon.Address, contractAddresses []gethcommon.Address) (thirdparty.TokenBalancesPerContractAddress, error)
|
2024-06-11 21:00:04 +00:00
|
|
|
FetchCachedBalancesByOwnerAndContractAddress(ctx context.Context, chainID walletcommon.ChainID, ownerAddress gethcommon.Address, contractAddresses []gethcommon.Address) (thirdparty.TokenBalancesPerContractAddress, error)
|
2024-02-22 08:08:58 +00:00
|
|
|
GetCollectibleOwnership(id thirdparty.CollectibleUniqueID) ([]thirdparty.AccountBalance, error)
|
2024-05-29 14:12:16 +00:00
|
|
|
FetchCollectibleOwnersByContractAddress(ctx context.Context, chainID walletcommon.ChainID, contractAddress gethcommon.Address) (*thirdparty.CollectibleContractOwnership, error)
|
2023-07-13 17:26:17 +00:00
|
|
|
}
|
|
|
|
|
2023-06-06 18:33:09 +00:00
|
|
|
func (m *DefaultTokenManager) GetBalancesByChain(ctx context.Context, accounts, tokenAddresses []gethcommon.Address, chainIDs []uint64) (BalancesByChain, error) {
|
2023-04-25 12:00:17 +00:00
|
|
|
clients, err := m.tokenManager.RPCClient.EthClients(chainIDs)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := m.tokenManager.GetBalancesByChain(context.Background(), clients, accounts, tokenAddresses)
|
|
|
|
return resp, err
|
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
|
|
|
}
|
|
|
|
|
2024-06-11 21:00:04 +00:00
|
|
|
func (m *DefaultTokenManager) GetCachedBalancesByChain(ctx context.Context, accounts, tokenAddresses []gethcommon.Address, chainIDs []uint64) (BalancesByChain, error) {
|
|
|
|
resp, err := m.tokenManager.GetCachedBalancesByChain(accounts, tokenAddresses, chainIDs)
|
|
|
|
if err != nil {
|
|
|
|
return resp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
2023-09-11 11:45:30 +00:00
|
|
|
func (m *DefaultTokenManager) FindOrCreateTokenByAddress(ctx context.Context, chainID uint64, address gethcommon.Address) *token.Token {
|
|
|
|
return m.tokenManager.FindOrCreateTokenByAddress(ctx, chainID, address)
|
2023-08-22 09:32:27 +00:00
|
|
|
}
|
|
|
|
|
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
|
|
|
type ManagerOption func(*managerOptions)
|
|
|
|
|
2023-06-21 12:13:31 +00:00
|
|
|
func WithAccountManager(accountsManager account.Manager) ManagerOption {
|
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
|
|
|
return func(opts *managerOptions) {
|
|
|
|
opts.accountsManager = accountsManager
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-12 15:45:23 +00:00
|
|
|
func WithPermissionChecker(permissionChecker PermissionChecker) ManagerOption {
|
|
|
|
return func(opts *managerOptions) {
|
|
|
|
opts.permissionChecker = permissionChecker
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-13 17:26:17 +00:00
|
|
|
func WithCollectiblesManager(collectiblesManager CollectiblesManager) ManagerOption {
|
2023-04-25 12:00:17 +00:00
|
|
|
return func(opts *managerOptions) {
|
2023-07-13 17:26:17 +00:00
|
|
|
opts.collectiblesManager = collectiblesManager
|
2023-04-25 12:00:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func WithTokenManager(tokenManager TokenManager) ManagerOption {
|
Check token funds when handling community requests to join
This adds checks to `HandleCommunityRequestToJoin` and
`AcceptRequestToJoinCommunity` that ensure a given user's revealed
wallet addresses own the token funds required by a community.
When community has token permissions of type `BECOME_MEMBER`, the
following happens when the owner receives a request:
1. Upon verifying provided wallet addresses by the requester, the owner
node accumulates all token funds related to the given wallets that
match the token criteria in the configured permissions
2. If the requester does not meet the necessary requirements, the
request to join will be declined. If the requester does have the
funds, he'll either be automatically accepted to the community, or
enters the next stage where an owner needs to manually accept the
request.
3. The the community does not automatically accept users, then the funds
check will happen again, when the owner tries to manually accept the
request. If the necessary funds do not exist at this stage, the
request will be declined
4. Upon accepting, whether automatically or manually, the owner adds the
requester's wallet addresses to the `CommunityDescription`, such that
they can be retrieved later when doing periodic checks or when
permissions have changed.
2023-03-16 14:35:33 +00:00
|
|
|
return func(opts *managerOptions) {
|
|
|
|
opts.tokenManager = tokenManager
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-27 09:35:03 +00:00
|
|
|
func WithWalletConfig(walletConfig *params.WalletConfig) ManagerOption {
|
|
|
|
return func(opts *managerOptions) {
|
|
|
|
opts.walletConfig = walletConfig
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-14 08:39:06 +00:00
|
|
|
func WithCommunityTokensService(communityTokensService CommunityTokensServiceInterface) ManagerOption {
|
2023-07-07 13:03:37 +00:00
|
|
|
return func(opts *managerOptions) {
|
2023-08-25 15:36:39 +00:00
|
|
|
opts.communityTokensService = communityTokensService
|
2023-07-07 13:03:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-09 19:59:51 +00:00
|
|
|
func WithAllowForcingCommunityMembersReevaluation(enabled bool) ManagerOption {
|
|
|
|
return func(opts *managerOptions) {
|
|
|
|
opts.allowForcingCommunityMembersReevaluation = enabled
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-05 17:35:22 +00:00
|
|
|
type OwnerVerifier interface {
|
|
|
|
SafeGetSignerPubKey(ctx context.Context, chainID uint64, communityID string) (string, error)
|
|
|
|
}
|
|
|
|
|
2024-07-01 18:52:57 +00:00
|
|
|
func NewManager(
|
|
|
|
identity *ecdsa.PrivateKey,
|
|
|
|
installationID string,
|
|
|
|
db *sql.DB,
|
|
|
|
encryptor *encryption.Protocol,
|
|
|
|
logger *zap.Logger,
|
|
|
|
ensverifier *ens.Verifier,
|
|
|
|
ownerVerifier OwnerVerifier,
|
|
|
|
transport *transport.Transport,
|
|
|
|
timesource common.TimeSource,
|
|
|
|
keyDistributor KeyDistributor,
|
|
|
|
mediaServer server.MediaServerInterface,
|
|
|
|
opts ...ManagerOption,
|
|
|
|
) (*Manager, error) {
|
2021-01-11 10:32:51 +00:00
|
|
|
if identity == nil {
|
|
|
|
return nil, errors.New("empty identity")
|
|
|
|
}
|
|
|
|
|
2023-09-28 15:37:03 +00:00
|
|
|
if timesource == nil {
|
|
|
|
return nil, errors.New("no timesource")
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
var err error
|
|
|
|
if logger == nil {
|
|
|
|
if logger, err = zap.NewDevelopment(); err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to create a logger")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
|
|
|
managerConfig := managerOptions{}
|
|
|
|
for _, opt := range opts {
|
|
|
|
opt(&managerConfig)
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
manager := &Manager{
|
2024-05-29 15:09:00 +00:00
|
|
|
logger: logger,
|
|
|
|
encryptor: encryptor,
|
|
|
|
identity: identity,
|
|
|
|
installationID: installationID,
|
|
|
|
ownerVerifier: ownerVerifier,
|
|
|
|
quit: make(chan struct{}),
|
|
|
|
transport: transport,
|
|
|
|
timesource: timesource,
|
|
|
|
keyDistributor: keyDistributor,
|
|
|
|
communityLock: NewCommunityLock(logger),
|
2024-07-01 18:52:57 +00:00
|
|
|
mediaServer: mediaServer,
|
2023-11-30 12:25:31 +00:00
|
|
|
}
|
|
|
|
|
2024-05-31 14:20:36 +00:00
|
|
|
manager.persistence = &Persistence{
|
2023-11-30 12:25:31 +00:00
|
|
|
db: db,
|
|
|
|
recordBundleToCommunity: manager.dbRecordBundleToCommunity,
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
|
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
|
|
|
if managerConfig.accountsManager != nil {
|
|
|
|
manager.accountsManager = managerConfig.accountsManager
|
|
|
|
}
|
|
|
|
|
2023-07-13 17:26:17 +00:00
|
|
|
if managerConfig.collectiblesManager != nil {
|
|
|
|
manager.collectiblesManager = managerConfig.collectiblesManager
|
|
|
|
}
|
|
|
|
|
Check token funds when handling community requests to join
This adds checks to `HandleCommunityRequestToJoin` and
`AcceptRequestToJoinCommunity` that ensure a given user's revealed
wallet addresses own the token funds required by a community.
When community has token permissions of type `BECOME_MEMBER`, the
following happens when the owner receives a request:
1. Upon verifying provided wallet addresses by the requester, the owner
node accumulates all token funds related to the given wallets that
match the token criteria in the configured permissions
2. If the requester does not meet the necessary requirements, the
request to join will be declined. If the requester does have the
funds, he'll either be automatically accepted to the community, or
enters the next stage where an owner needs to manually accept the
request.
3. The the community does not automatically accept users, then the funds
check will happen again, when the owner tries to manually accept the
request. If the necessary funds do not exist at this stage, the
request will be declined
4. Upon accepting, whether automatically or manually, the owner adds the
requester's wallet addresses to the `CommunityDescription`, such that
they can be retrieved later when doing periodic checks or when
permissions have changed.
2023-03-16 14:35:33 +00:00
|
|
|
if managerConfig.tokenManager != nil {
|
|
|
|
manager.tokenManager = managerConfig.tokenManager
|
|
|
|
}
|
|
|
|
|
2023-03-27 09:35:03 +00:00
|
|
|
if managerConfig.walletConfig != nil {
|
|
|
|
manager.walletConfig = managerConfig.walletConfig
|
|
|
|
}
|
|
|
|
|
2023-08-25 15:36:39 +00:00
|
|
|
if managerConfig.communityTokensService != nil {
|
|
|
|
manager.communityTokensService = managerConfig.communityTokensService
|
2023-07-07 13:03:37 +00:00
|
|
|
}
|
|
|
|
|
2023-07-05 17:35:22 +00:00
|
|
|
if ensverifier != nil {
|
|
|
|
sub := ensverifier.Subscribe()
|
2021-01-11 10:32:51 +00:00
|
|
|
manager.ensSubscription = sub
|
2023-07-05 17:35:22 +00:00
|
|
|
manager.ensVerifier = ensverifier
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
|
2023-10-12 15:45:23 +00:00
|
|
|
if managerConfig.permissionChecker != nil {
|
|
|
|
manager.PermissionChecker = managerConfig.permissionChecker
|
|
|
|
} else {
|
|
|
|
manager.PermissionChecker = &DefaultPermissionChecker{
|
|
|
|
tokenManager: manager.tokenManager,
|
|
|
|
collectiblesManager: manager.collectiblesManager,
|
|
|
|
logger: logger,
|
|
|
|
ensVerifier: ensverifier,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-09 19:59:51 +00:00
|
|
|
if managerConfig.allowForcingCommunityMembersReevaluation {
|
|
|
|
manager.logger.Warn("allowing forcing community members reevaluation, this should only be used in test environment")
|
|
|
|
manager.forceMembersReevaluation = make(map[string]chan struct{}, 10)
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
return manager, nil
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Subscription struct {
|
2022-09-15 07:59:02 +00:00
|
|
|
Community *Community
|
|
|
|
CreatingHistoryArchivesSignal *signal.CreatingHistoryArchivesSignal
|
|
|
|
HistoryArchivesCreatedSignal *signal.HistoryArchivesCreatedSignal
|
|
|
|
NoHistoryArchivesCreatedSignal *signal.NoHistoryArchivesCreatedSignal
|
|
|
|
HistoryArchivesSeedingSignal *signal.HistoryArchivesSeedingSignal
|
|
|
|
HistoryArchivesUnseededSignal *signal.HistoryArchivesUnseededSignal
|
|
|
|
HistoryArchiveDownloadedSignal *signal.HistoryArchiveDownloadedSignal
|
2022-12-01 14:02:17 +00:00
|
|
|
DownloadingHistoryArchivesStartedSignal *signal.DownloadingHistoryArchivesStartedSignal
|
2022-09-15 07:59:02 +00:00
|
|
|
DownloadingHistoryArchivesFinishedSignal *signal.DownloadingHistoryArchivesFinishedSignal
|
2022-12-02 12:45:41 +00:00
|
|
|
ImportingHistoryArchiveMessagesSignal *signal.ImportingHistoryArchiveMessagesSignal
|
2023-07-18 15:06:12 +00:00
|
|
|
CommunityEventsMessage *CommunityEventsMessage
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
AcceptedRequestsToJoin []types.HexBytes
|
|
|
|
RejectedRequestsToJoin []types.HexBytes
|
2023-09-20 08:37:46 +00:00
|
|
|
CommunityPrivilegedMemberSyncMessage *CommunityPrivilegedMemberSyncMessage
|
2023-07-05 17:35:22 +00:00
|
|
|
TokenCommunityValidated *CommunityResponse
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type CommunityResponse struct {
|
2024-01-23 16:56:51 +00:00
|
|
|
Community *Community `json:"community"`
|
|
|
|
Changes *CommunityChanges `json:"changes"`
|
|
|
|
RequestsToJoin []*RequestToJoin `json:"requestsToJoin"`
|
|
|
|
FailedToDecrypt []*CommunityPrivateDataFailedToDecrypt `json:"-"`
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2024-07-01 18:52:57 +00:00
|
|
|
func (m *Manager) SetMediaServer(mediaServer server.MediaServerInterface) {
|
|
|
|
m.mediaServer = mediaServer
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (m *Manager) Subscribe() chan *Subscription {
|
|
|
|
subscription := make(chan *Subscription, 100)
|
|
|
|
m.subscriptions = append(m.subscriptions, subscription)
|
|
|
|
return subscription
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func (m *Manager) Start() error {
|
2023-06-29 09:21:48 +00:00
|
|
|
m.stopped = false
|
2024-02-23 02:16:51 +00:00
|
|
|
m.communityLock.Init()
|
2021-01-11 10:32:51 +00:00
|
|
|
if m.ensVerifier != nil {
|
|
|
|
m.runENSVerificationLoop()
|
|
|
|
}
|
2022-03-21 14:18:36 +00:00
|
|
|
|
2023-07-05 17:35:22 +00:00
|
|
|
if m.ownerVerifier != nil {
|
|
|
|
m.runOwnerVerificationLoop()
|
|
|
|
}
|
2024-05-21 16:00:32 +00:00
|
|
|
|
|
|
|
go func() {
|
2024-09-26 22:37:32 +00:00
|
|
|
defer utils.LogOnPanic()
|
2024-05-21 16:00:32 +00:00
|
|
|
_ = m.fillMissingCommunityTokens()
|
|
|
|
}()
|
|
|
|
|
2024-05-06 15:54:35 +00:00
|
|
|
return nil
|
|
|
|
}
|
2023-07-05 17:35:22 +00:00
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func (m *Manager) runENSVerificationLoop() {
|
|
|
|
go func() {
|
2024-09-26 22:37:32 +00:00
|
|
|
defer utils.LogOnPanic()
|
2021-01-11 10:32:51 +00:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-m.quit:
|
|
|
|
m.logger.Debug("quitting ens verification loop")
|
|
|
|
return
|
|
|
|
case records, more := <-m.ensSubscription:
|
|
|
|
if !more {
|
|
|
|
m.logger.Debug("no more ens records, quitting")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
m.logger.Info("received records", zap.Any("records", records))
|
2023-07-05 17:35:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2024-05-21 16:00:32 +00:00
|
|
|
// This function is mostly a way to fix any community that is missing tokens in its description
|
|
|
|
func (m *Manager) fillMissingCommunityTokens() error {
|
|
|
|
controlledCommunities, err := m.Controlled()
|
|
|
|
if err != nil {
|
|
|
|
m.logger.Error("failed to retrieve orgs", zap.Error(err))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
unlock := func() {
|
|
|
|
for _, c := range controlledCommunities {
|
|
|
|
m.communityLock.Unlock(c.ID())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, c := range controlledCommunities {
|
|
|
|
m.communityLock.Lock(c.ID())
|
|
|
|
}
|
|
|
|
defer unlock()
|
|
|
|
|
|
|
|
for _, community := range controlledCommunities {
|
|
|
|
tokens, err := m.GetCommunityTokens(community.IDString())
|
|
|
|
if err != nil {
|
|
|
|
m.logger.Error("failed to retrieve community tokens", zap.Error(err))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, token := range tokens {
|
|
|
|
if token.DeployState != community_token.Deployed {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
tokenMetadata := &protobuf.CommunityTokenMetadata{
|
|
|
|
ContractAddresses: map[uint64]string{uint64(token.ChainID): token.Address},
|
|
|
|
Description: token.Description,
|
|
|
|
Image: token.Base64Image,
|
|
|
|
Symbol: token.Symbol,
|
|
|
|
TokenType: token.TokenType,
|
|
|
|
Name: token.Name,
|
|
|
|
Decimals: uint32(token.Decimals),
|
2024-07-02 11:34:18 +00:00
|
|
|
Version: token.Version,
|
2024-05-21 16:00:32 +00:00
|
|
|
}
|
|
|
|
modified, err := community.UpsertCommunityTokensMetadata(tokenMetadata)
|
|
|
|
if err != nil {
|
|
|
|
m.logger.Error("failed to add token metadata to the description", zap.Error(err))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if modified {
|
|
|
|
err = m.saveAndPublish(community)
|
|
|
|
if err != nil {
|
|
|
|
m.logger.Error("failed to save the new community", zap.Error(err))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-07-05 17:35:22 +00:00
|
|
|
// Only for testing
|
|
|
|
func (m *Manager) CommunitiesToValidate() (map[string][]communityToValidate, error) { // nolint: golint
|
|
|
|
return m.persistence.getCommunitiesToValidate()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) runOwnerVerificationLoop() {
|
|
|
|
m.logger.Info("starting owner verification loop")
|
|
|
|
go func() {
|
2024-09-26 22:37:32 +00:00
|
|
|
defer utils.LogOnPanic()
|
2023-07-05 17:35:22 +00:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-m.quit:
|
|
|
|
m.logger.Debug("quitting owner verification loop")
|
|
|
|
return
|
|
|
|
case <-time.After(validateInterval):
|
|
|
|
// If ownerverifier is nil, we skip, this is useful for testing
|
|
|
|
if m.ownerVerifier == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
communitiesToValidate, err := m.persistence.getCommunitiesToValidate()
|
2021-01-11 10:32:51 +00:00
|
|
|
|
2023-07-05 17:35:22 +00:00
|
|
|
if err != nil {
|
|
|
|
m.logger.Error("failed to fetch communities to validate", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for id, communities := range communitiesToValidate {
|
|
|
|
m.logger.Info("validating communities", zap.String("id", id), zap.Int("count", len(communities)))
|
|
|
|
|
2024-01-05 17:09:38 +00:00
|
|
|
_, _ = m.validateCommunity(communities)
|
2023-07-05 17:35:22 +00:00
|
|
|
}
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2024-01-05 17:09:38 +00:00
|
|
|
func (m *Manager) ValidateCommunityByID(communityID types.HexBytes) (*CommunityResponse, error) {
|
2024-01-16 10:38:41 +00:00
|
|
|
communitiesToValidate, err := m.persistence.getCommunityToValidateByID(communityID)
|
2024-01-05 17:09:38 +00:00
|
|
|
if err != nil {
|
|
|
|
m.logger.Error("failed to validate community by ID", zap.String("id", communityID.String()), zap.Error(err))
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-01-16 10:38:41 +00:00
|
|
|
return m.validateCommunity(communitiesToValidate)
|
2024-01-05 17:09:38 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) validateCommunity(communityToValidateData []communityToValidate) (*CommunityResponse, error) {
|
2024-01-16 10:38:41 +00:00
|
|
|
for _, community := range communityToValidateData {
|
|
|
|
signer, description, err := UnwrapCommunityDescriptionMessage(community.payload)
|
2024-01-05 17:09:38 +00:00
|
|
|
if err != nil {
|
|
|
|
m.logger.Error("failed to unwrap community", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
chainID := CommunityDescriptionTokenOwnerChainID(description)
|
|
|
|
if chainID == 0 {
|
|
|
|
// This should not happen
|
|
|
|
m.logger.Error("chain id is 0, ignoring")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-01-16 10:38:41 +00:00
|
|
|
m.logger.Info("validating community", zap.String("id", types.EncodeHex(community.id)), zap.String("signer", common.PubkeyToHex(signer)))
|
2024-01-05 17:09:38 +00:00
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
|
|
|
|
defer cancel()
|
|
|
|
|
2024-01-16 10:38:41 +00:00
|
|
|
owner, err := m.ownerVerifier.SafeGetSignerPubKey(ctx, chainID, types.EncodeHex(community.id))
|
2024-01-05 17:09:38 +00:00
|
|
|
if err != nil {
|
|
|
|
m.logger.Error("failed to get owner", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
ownerPK, err := common.HexToPubkey(owner)
|
|
|
|
if err != nil {
|
|
|
|
m.logger.Error("failed to convert pk string to ecdsa", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: handle shards
|
2024-01-16 10:38:41 +00:00
|
|
|
response, err := m.HandleCommunityDescriptionMessage(signer, description, community.payload, ownerPK, nil)
|
2024-01-05 17:09:38 +00:00
|
|
|
if err != nil {
|
|
|
|
m.logger.Error("failed to handle community", zap.Error(err))
|
2024-01-16 10:38:41 +00:00
|
|
|
err = m.persistence.DeleteCommunityToValidate(community.id, community.clock)
|
2024-01-05 17:09:38 +00:00
|
|
|
if err != nil {
|
|
|
|
m.logger.Error("failed to delete community to validate", zap.Error(err))
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if response != nil {
|
|
|
|
|
2024-01-16 10:38:41 +00:00
|
|
|
m.logger.Info("community validated", zap.String("id", types.EncodeHex(community.id)), zap.String("signer", common.PubkeyToHex(signer)))
|
2024-01-05 17:09:38 +00:00
|
|
|
m.publish(&Subscription{TokenCommunityValidated: response})
|
2024-01-16 10:38:41 +00:00
|
|
|
err := m.persistence.DeleteCommunitiesToValidateByCommunityID(community.id)
|
2024-01-05 17:09:38 +00:00
|
|
|
if err != nil {
|
|
|
|
m.logger.Error("failed to delete communities to validate", zap.Error(err))
|
|
|
|
}
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (m *Manager) Stop() error {
|
2023-06-29 09:21:48 +00:00
|
|
|
m.stopped = true
|
2021-01-11 10:32:51 +00:00
|
|
|
close(m.quit)
|
2020-11-18 09:16:51 +00:00
|
|
|
for _, c := range m.subscriptions {
|
|
|
|
close(c)
|
|
|
|
}
|
2022-03-21 14:18:36 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (m *Manager) publish(subscription *Subscription) {
|
2023-06-29 09:21:48 +00:00
|
|
|
if m.stopped {
|
|
|
|
return
|
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
for _, s := range m.subscriptions {
|
|
|
|
select {
|
|
|
|
case s <- subscription:
|
|
|
|
default:
|
|
|
|
m.logger.Warn("subscription channel full, dropping message")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) All() ([]*Community, error) {
|
2023-12-05 14:50:45 +00:00
|
|
|
return m.persistence.AllCommunities(&m.identity.PublicKey)
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2023-10-12 19:21:49 +00:00
|
|
|
type CommunityShard struct {
|
2023-11-15 15:58:15 +00:00
|
|
|
CommunityID string `json:"communityID"`
|
|
|
|
Shard *shard.Shard `json:"shard"`
|
2023-10-12 19:21:49 +00:00
|
|
|
}
|
|
|
|
|
2023-11-14 21:06:33 +00:00
|
|
|
type CuratedCommunities struct {
|
|
|
|
ContractCommunities []string
|
|
|
|
ContractFeaturedCommunities []string
|
|
|
|
}
|
|
|
|
|
2022-06-02 12:17:52 +00:00
|
|
|
type KnownCommunitiesResponse struct {
|
2023-11-14 21:06:33 +00:00
|
|
|
ContractCommunities []string `json:"contractCommunities"`
|
|
|
|
ContractFeaturedCommunities []string `json:"contractFeaturedCommunities"`
|
2023-05-05 15:55:32 +00:00
|
|
|
Descriptions map[string]*Community `json:"communities"`
|
2023-11-14 21:06:33 +00:00
|
|
|
UnknownCommunities []string `json:"unknownCommunities"`
|
2022-06-02 12:17:52 +00:00
|
|
|
}
|
|
|
|
|
2023-11-14 21:06:33 +00:00
|
|
|
func (m *Manager) GetStoredDescriptionForCommunities(communityIDs []string) (*KnownCommunitiesResponse, error) {
|
|
|
|
response := &KnownCommunitiesResponse{
|
2022-06-02 12:17:52 +00:00
|
|
|
Descriptions: make(map[string]*Community),
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := range communityIDs {
|
2023-11-14 21:06:33 +00:00
|
|
|
communityID := communityIDs[i]
|
|
|
|
communityIDBytes, err := types.DecodeHex(communityID)
|
2022-06-02 12:17:52 +00:00
|
|
|
if err != nil {
|
2023-11-14 21:06:33 +00:00
|
|
|
return nil, err
|
2022-06-02 12:17:52 +00:00
|
|
|
}
|
|
|
|
|
2023-11-14 21:06:33 +00:00
|
|
|
community, err := m.GetByID(types.HexBytes(communityIDBytes))
|
2024-05-03 17:17:11 +00:00
|
|
|
if err != nil && err != ErrOrgNotFound {
|
2023-11-14 21:06:33 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2022-06-02 12:17:52 +00:00
|
|
|
|
|
|
|
if community != nil {
|
|
|
|
response.Descriptions[community.IDString()] = community
|
|
|
|
} else {
|
2023-11-03 10:19:38 +00:00
|
|
|
response.UnknownCommunities = append(response.UnknownCommunities, communityID)
|
2022-06-02 12:17:52 +00:00
|
|
|
}
|
2023-11-14 21:06:33 +00:00
|
|
|
|
|
|
|
response.ContractCommunities = append(response.ContractCommunities, communityID)
|
2022-06-02 12:17:52 +00:00
|
|
|
}
|
|
|
|
|
2023-11-14 21:06:33 +00:00
|
|
|
return response, nil
|
2022-06-02 12:17:52 +00:00
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (m *Manager) Joined() ([]*Community, error) {
|
2023-11-30 12:25:31 +00:00
|
|
|
return m.persistence.JoinedCommunities(&m.identity.PublicKey)
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2022-09-20 19:57:39 +00:00
|
|
|
func (m *Manager) Spectated() ([]*Community, error) {
|
2023-11-30 12:25:31 +00:00
|
|
|
return m.persistence.SpectatedCommunities(&m.identity.PublicKey)
|
2022-09-20 19:57:39 +00:00
|
|
|
}
|
|
|
|
|
2024-01-21 10:55:14 +00:00
|
|
|
func (m *Manager) CommunityUpdateLastOpenedAt(communityID types.HexBytes, timestamp int64) (*Community, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(communityID)
|
|
|
|
defer m.communityLock.Unlock(communityID)
|
|
|
|
|
2024-01-21 10:55:14 +00:00
|
|
|
community, err := m.GetByID(communityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.persistence.UpdateLastOpenedAt(community.ID(), timestamp)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
community.UpdateLastOpenedAt(timestamp)
|
|
|
|
return community, nil
|
|
|
|
}
|
|
|
|
|
2021-08-06 15:40:23 +00:00
|
|
|
func (m *Manager) JoinedAndPendingCommunitiesWithRequests() ([]*Community, error) {
|
2023-11-30 12:25:31 +00:00
|
|
|
return m.persistence.JoinedAndPendingCommunitiesWithRequests(&m.identity.PublicKey)
|
2021-08-06 15:40:23 +00:00
|
|
|
}
|
|
|
|
|
2022-04-11 16:14:08 +00:00
|
|
|
func (m *Manager) DeletedCommunities() ([]*Community, error) {
|
2023-11-30 12:25:31 +00:00
|
|
|
return m.persistence.DeletedCommunities(&m.identity.PublicKey)
|
2022-04-11 16:14:08 +00:00
|
|
|
}
|
|
|
|
|
2023-10-19 22:06:09 +00:00
|
|
|
func (m *Manager) Controlled() ([]*Community, error) {
|
2023-11-30 12:25:31 +00:00
|
|
|
communities, err := m.persistence.CommunitiesWithPrivateKey(&m.identity.PublicKey)
|
2023-09-25 11:26:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-10-19 22:06:09 +00:00
|
|
|
controlled := make([]*Community, 0, len(communities))
|
2023-09-25 11:26:17 +00:00
|
|
|
|
2023-10-19 22:06:09 +00:00
|
|
|
for _, c := range communities {
|
|
|
|
if c.IsControlNode() {
|
|
|
|
controlled = append(controlled, c)
|
2023-07-05 17:35:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-19 22:06:09 +00:00
|
|
|
return controlled, nil
|
2023-07-05 17:35:22 +00:00
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
// CreateCommunity takes a description, generates an ID for it, saves it and return it
|
2022-08-19 12:51:21 +00:00
|
|
|
func (m *Manager) CreateCommunity(request *requests.CreateCommunity, publish bool) (*Community, error) {
|
2021-05-18 19:32:15 +00:00
|
|
|
|
|
|
|
description, err := request.ToCommunityDescription()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
description.Members = make(map[string]*protobuf.CommunityMember)
|
2023-06-14 14:15:46 +00:00
|
|
|
description.Members[common.PubkeyToHex(&m.identity.PublicKey)] = &protobuf.CommunityMember{Roles: []protobuf.CommunityMember_Roles{protobuf.CommunityMember_ROLE_OWNER}}
|
2021-05-18 19:32:15 +00:00
|
|
|
|
|
|
|
err = ValidateCommunityDescription(description)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
description.Clock = 1
|
|
|
|
|
|
|
|
key, err := crypto.GenerateKey()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-05 17:35:22 +00:00
|
|
|
description.ID = types.EncodeHex(crypto.CompressPubkey(&key.PublicKey))
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
config := Config{
|
|
|
|
ID: &key.PublicKey,
|
|
|
|
PrivateKey: key,
|
2023-07-05 17:35:22 +00:00
|
|
|
ControlNode: &key.PublicKey,
|
2023-09-21 11:16:05 +00:00
|
|
|
ControlDevice: true,
|
2020-11-18 09:16:51 +00:00
|
|
|
Logger: m.logger,
|
|
|
|
Joined: true,
|
2024-01-09 18:36:47 +00:00
|
|
|
JoinedAt: time.Now().Unix(),
|
2024-06-27 15:29:03 +00:00
|
|
|
MemberIdentity: m.identity,
|
2020-11-18 09:16:51 +00:00
|
|
|
CommunityDescription: description,
|
2023-10-12 19:21:49 +00:00
|
|
|
Shard: nil,
|
2024-01-21 10:55:14 +00:00
|
|
|
LastOpenedAt: 0,
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
2023-11-29 17:21:21 +00:00
|
|
|
|
|
|
|
var descriptionEncryptor DescriptionEncryptor
|
|
|
|
if m.encryptor != nil {
|
|
|
|
descriptionEncryptor = m
|
|
|
|
}
|
2024-07-01 18:52:57 +00:00
|
|
|
community, err := New(config, m.timesource, descriptionEncryptor, m.mediaServer)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
// We join any community we create
|
|
|
|
community.Join()
|
|
|
|
|
2020-12-17 14:36:09 +00:00
|
|
|
err = m.persistence.SaveCommunity(community)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
// Save grant for own community
|
|
|
|
grant, err := community.BuildGrant(&m.identity.PublicKey, "")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-04-17 14:53:51 +00:00
|
|
|
err = m.persistence.SaveCommunityGrant(community.IDString(), grant, uint64(time.Now().UnixMilli()))
|
2024-02-15 19:13:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-09-21 11:16:05 +00:00
|
|
|
// Mark this device as the control node
|
|
|
|
syncControlNode := &protobuf.SyncCommunityControlNode{
|
|
|
|
Clock: 1,
|
|
|
|
InstallationId: m.installationID,
|
|
|
|
}
|
|
|
|
err = m.SaveSyncControlNode(community.ID(), syncControlNode)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-08-19 12:51:21 +00:00
|
|
|
if publish {
|
|
|
|
m.publish(&Subscription{Community: community})
|
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
|
2020-12-17 14:36:09 +00:00
|
|
|
return community, nil
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2023-03-02 16:27:48 +00:00
|
|
|
func (m *Manager) CreateCommunityTokenPermission(request *requests.CreateCommunityTokenPermission) (*Community, *CommunityChanges, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(request.CommunityID)
|
|
|
|
defer m.communityLock.Unlock(request.CommunityID)
|
|
|
|
|
2023-03-02 16:27:48 +00:00
|
|
|
community, err := m.GetByID(request.CommunityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2023-11-29 17:21:21 +00:00
|
|
|
// ensure key is generated before marshaling,
|
|
|
|
// as it requires key to encrypt description
|
2024-01-23 16:56:51 +00:00
|
|
|
if community.IsControlNode() && m.encryptor != nil {
|
|
|
|
key, err := m.encryptor.GenerateHashRatchetKey(community.ID())
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
keyID, err := key.GetKeyID()
|
2023-11-29 17:21:21 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2024-01-23 16:56:51 +00:00
|
|
|
m.logger.Info("generate key for token", zap.String("group-id", types.Bytes2Hex(community.ID())), zap.String("key-id", types.Bytes2Hex(keyID)))
|
|
|
|
}
|
|
|
|
|
|
|
|
community, changes, err := m.createCommunityTokenPermission(request, community)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
2023-11-29 17:21:21 +00:00
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
err = m.saveAndPublish(community)
|
2023-03-02 16:27:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return community, changes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) EditCommunityTokenPermission(request *requests.EditCommunityTokenPermission) (*Community, *CommunityChanges, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(request.CommunityID)
|
|
|
|
defer m.communityLock.Unlock(request.CommunityID)
|
|
|
|
|
2023-03-02 16:27:48 +00:00
|
|
|
community, err := m.GetByID(request.CommunityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
tokenPermission := request.ToCommunityTokenPermission()
|
|
|
|
|
2023-08-17 15:19:18 +00:00
|
|
|
changes, err := community.UpsertTokenPermission(&tokenPermission)
|
2023-03-02 16:27:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
err = m.saveAndPublish(community)
|
2023-03-02 16:27:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2023-03-20 12:26:20 +00:00
|
|
|
return community, changes, nil
|
|
|
|
}
|
2023-03-20 10:36:32 +00:00
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
type reevaluateMemberRole struct {
|
|
|
|
old protobuf.CommunityMember_Roles
|
|
|
|
new protobuf.CommunityMember_Roles
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rmr reevaluateMemberRole) hasChanged() bool {
|
|
|
|
return rmr.old != rmr.new
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rmr reevaluateMemberRole) isPrivileged() bool {
|
|
|
|
return rmr.new != protobuf.CommunityMember_ROLE_NONE
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rmr reevaluateMemberRole) hasChangedToPrivileged() bool {
|
|
|
|
return rmr.hasChanged() && rmr.old == protobuf.CommunityMember_ROLE_NONE
|
|
|
|
}
|
|
|
|
|
2024-07-05 13:25:30 +00:00
|
|
|
func (rmr reevaluateMemberRole) hasChangedPrivilegedRole() bool {
|
|
|
|
return (rmr.old == protobuf.CommunityMember_ROLE_ADMIN && rmr.new == protobuf.CommunityMember_ROLE_TOKEN_MASTER) ||
|
|
|
|
(rmr.old == protobuf.CommunityMember_ROLE_TOKEN_MASTER && rmr.new == protobuf.CommunityMember_ROLE_ADMIN)
|
|
|
|
}
|
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
type reevaluateMembersResult struct {
|
|
|
|
membersToRemove map[string]struct{}
|
|
|
|
membersRoles map[string]*reevaluateMemberRole
|
|
|
|
membersToRemoveFromChannels map[string]map[string]struct{}
|
|
|
|
membersToAddToChannels map[string]map[string]protobuf.CommunityMember_ChannelRole
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rmr *reevaluateMembersResult) newPrivilegedRoles() (map[protobuf.CommunityMember_Roles][]*ecdsa.PublicKey, error) {
|
|
|
|
result := map[protobuf.CommunityMember_Roles][]*ecdsa.PublicKey{}
|
|
|
|
|
|
|
|
for memberKey, roles := range rmr.membersRoles {
|
2024-07-05 13:25:30 +00:00
|
|
|
if roles.hasChangedToPrivileged() || roles.hasChangedPrivilegedRole() {
|
2024-05-16 13:51:04 +00:00
|
|
|
memberPubKey, err := common.HexToPubkey(memberKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if result[roles.new] == nil {
|
|
|
|
result[roles.new] = []*ecdsa.PublicKey{}
|
|
|
|
}
|
|
|
|
result[roles.new] = append(result[roles.new], memberPubKey)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2024-05-29 14:12:16 +00:00
|
|
|
// Fetch all owners for all collectibles.
|
|
|
|
func (m *Manager) fetchCollectiblesOwners(collectibles map[walletcommon.ChainID]map[gethcommon.Address]struct{}) (CollectiblesOwners, error) {
|
|
|
|
if m.collectiblesManager == nil {
|
|
|
|
return nil, errors.New("no collectibles manager")
|
|
|
|
}
|
|
|
|
|
|
|
|
collectiblesOwners := make(CollectiblesOwners)
|
|
|
|
for chainID, contractAddresses := range collectibles {
|
|
|
|
collectiblesOwners[chainID] = make(map[gethcommon.Address]*thirdparty.CollectibleContractOwnership)
|
|
|
|
|
|
|
|
for contractAddress := range contractAddresses {
|
|
|
|
ownership, err := m.collectiblesManager.FetchCollectibleOwnersByContractAddress(context.Background(), chainID, contractAddress)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
collectiblesOwners[chainID][contractAddress] = ownership
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return collectiblesOwners, nil
|
|
|
|
}
|
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
// use it only for testing purposes
|
|
|
|
func (m *Manager) ReevaluateMembers(communityID types.HexBytes) (*Community, map[protobuf.CommunityMember_Roles][]*ecdsa.PublicKey, error) {
|
|
|
|
return m.reevaluateMembers(communityID)
|
|
|
|
}
|
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
// First, the community is read from the database,
|
|
|
|
// then the members are reevaluated, and only then
|
|
|
|
// the community is locked and changes are applied.
|
|
|
|
// NOTE: Changes made to the same community
|
|
|
|
// while reevaluation is ongoing are respected
|
|
|
|
// and do not affect the result of this function.
|
|
|
|
// If permissions are changed in the meantime,
|
|
|
|
// they will be accommodated with the next reevaluation.
|
2024-05-09 19:59:51 +00:00
|
|
|
func (m *Manager) reevaluateMembers(communityID types.HexBytes) (*Community, map[protobuf.CommunityMember_Roles][]*ecdsa.PublicKey, error) {
|
2024-05-08 15:32:46 +00:00
|
|
|
community, err := m.GetByID(communityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !community.IsControlNode() {
|
|
|
|
return nil, nil, ErrNotEnoughPermissions
|
|
|
|
}
|
2024-04-16 15:06:27 +00:00
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
communityPermissionsPreParsedData, channelPermissionsPreParsedData := PreParsePermissionsData(community.tokenPermissions())
|
2023-03-20 12:26:20 +00:00
|
|
|
|
2024-05-29 14:12:16 +00:00
|
|
|
// Optimization: Fetch all collectibles owners before members iteration to avoid asking providers for the same collectibles.
|
|
|
|
collectiblesOwners, err := m.fetchCollectiblesOwners(CollectibleAddressesFromPreParsedPermissionsData(communityPermissionsPreParsedData, channelPermissionsPreParsedData))
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
result := &reevaluateMembersResult{
|
|
|
|
membersToRemove: map[string]struct{}{},
|
|
|
|
membersRoles: map[string]*reevaluateMemberRole{},
|
|
|
|
membersToRemoveFromChannels: map[string]map[string]struct{}{},
|
|
|
|
membersToAddToChannels: map[string]map[string]protobuf.CommunityMember_ChannelRole{},
|
2024-05-17 16:15:39 +00:00
|
|
|
}
|
2023-03-20 12:26:20 +00:00
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
membersAccounts, err := m.persistence.GetCommunityRequestsToJoinRevealedAddresses(community.ID())
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2023-08-04 09:49:11 +00:00
|
|
|
for memberKey := range community.Members() {
|
2023-06-14 14:15:46 +00:00
|
|
|
memberPubKey, err := common.HexToPubkey(memberKey)
|
|
|
|
if err != nil {
|
2024-05-08 15:32:46 +00:00
|
|
|
return nil, nil, err
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if memberKey == common.PubkeyToHex(&m.identity.PublicKey) || community.IsMemberOwner(memberPubKey) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
revealedAccount, memberHasWallet := membersAccounts[memberKey]
|
|
|
|
if !memberHasWallet {
|
|
|
|
result.membersToRemove[memberKey] = struct{}{}
|
|
|
|
continue
|
|
|
|
}
|
2023-08-04 09:49:11 +00:00
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
accountsAndChainIDs := revealedAccountsToAccountsAndChainIDsCombination(revealedAccount)
|
|
|
|
|
|
|
|
result.membersRoles[memberKey] = &reevaluateMemberRole{
|
|
|
|
old: community.MemberRole(memberPubKey),
|
|
|
|
new: protobuf.CommunityMember_ROLE_NONE,
|
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
becomeTokenMasterPermissions := communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER]
|
|
|
|
if becomeTokenMasterPermissions != nil {
|
2024-05-29 14:12:16 +00:00
|
|
|
permissionResponse, err := m.PermissionChecker.CheckPermissionsWithPreFetchedData(becomeTokenMasterPermissions, accountsAndChainIDs, true, collectiblesOwners)
|
2023-03-20 12:26:20 +00:00
|
|
|
if err != nil {
|
2024-05-08 15:32:46 +00:00
|
|
|
return nil, nil, err
|
2023-03-20 12:26:20 +00:00
|
|
|
}
|
2024-05-16 13:51:04 +00:00
|
|
|
|
|
|
|
if permissionResponse.Satisfied {
|
|
|
|
result.membersRoles[memberKey].new = protobuf.CommunityMember_ROLE_TOKEN_MASTER
|
|
|
|
// Skip further validation if user has TokenMaster permissions
|
|
|
|
continue
|
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
2023-03-20 10:36:32 +00:00
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
becomeAdminPermissions := communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_ADMIN]
|
|
|
|
if becomeAdminPermissions != nil {
|
2024-05-29 14:12:16 +00:00
|
|
|
permissionResponse, err := m.PermissionChecker.CheckPermissionsWithPreFetchedData(becomeAdminPermissions, accountsAndChainIDs, true, collectiblesOwners)
|
2024-05-16 13:51:04 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2023-04-25 12:00:17 +00:00
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
if permissionResponse.Satisfied {
|
|
|
|
result.membersRoles[memberKey].new = protobuf.CommunityMember_ROLE_ADMIN
|
|
|
|
// Skip further validation if user has Admin permissions
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2023-08-04 10:28:46 +00:00
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
becomeMemberPermissions := communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_MEMBER]
|
|
|
|
if becomeMemberPermissions != nil {
|
2024-05-29 14:12:16 +00:00
|
|
|
permissionResponse, err := m.PermissionChecker.CheckPermissionsWithPreFetchedData(becomeMemberPermissions, accountsAndChainIDs, true, collectiblesOwners)
|
2024-05-16 13:51:04 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !permissionResponse.Satisfied {
|
|
|
|
result.membersToRemove[memberKey] = struct{}{}
|
|
|
|
// Skip channels validation if user has been removed
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-29 14:12:16 +00:00
|
|
|
addToChannels, removeFromChannels, err := m.reevaluateMemberChannelsPermissions(community, memberPubKey, channelPermissionsPreParsedData, accountsAndChainIDs, collectiblesOwners)
|
2023-08-04 10:28:46 +00:00
|
|
|
if err != nil {
|
2024-05-08 15:32:46 +00:00
|
|
|
return nil, nil, err
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
2024-05-16 13:51:04 +00:00
|
|
|
result.membersToAddToChannels[memberKey] = addToChannels
|
|
|
|
result.membersToRemoveFromChannels[memberKey] = removeFromChannels
|
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
newPrivilegedRoles, err := result.newPrivilegedRoles()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note: community itself may have changed in the meantime of permissions reevaluation.
|
|
|
|
community, err = m.applyReevaluateMembersResult(communityID, result)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return community, newPrivilegedRoles, m.saveAndPublish(community)
|
|
|
|
}
|
2023-08-04 10:28:46 +00:00
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
// Apply results on the most up-to-date community.
|
|
|
|
func (m *Manager) applyReevaluateMembersResult(communityID types.HexBytes, result *reevaluateMembersResult) (*Community, error) {
|
|
|
|
m.communityLock.Lock(communityID)
|
|
|
|
defer m.communityLock.Unlock(communityID)
|
2023-08-04 10:28:46 +00:00
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
community, err := m.GetByID(communityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !community.IsControlNode() {
|
|
|
|
return nil, ErrNotEnoughPermissions
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove members.
|
|
|
|
for memberKey := range result.membersToRemove {
|
|
|
|
memberPubKey, err := common.HexToPubkey(memberKey)
|
2023-08-04 10:28:46 +00:00
|
|
|
if err != nil {
|
2024-05-16 13:51:04 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
_, err = community.RemoveUserFromOrg(memberPubKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
2024-05-16 13:51:04 +00:00
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
// Ensure members have proper roles.
|
|
|
|
for memberKey, roles := range result.membersRoles {
|
|
|
|
memberPubKey, err := common.HexToPubkey(memberKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !community.HasMember(memberPubKey) {
|
2023-06-14 14:15:46 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
_, err = community.SetRoleToMember(memberPubKey, roles.new)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-06-23 10:49:26 +00:00
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
// Ensure privileged members can post in all chats.
|
|
|
|
if roles.isPrivileged() {
|
|
|
|
for channelID := range community.Chats() {
|
|
|
|
_, err = community.AddMemberToChat(channelID, memberPubKey, []protobuf.CommunityMember_Roles{roles.new}, protobuf.CommunityMember_CHANNEL_ROLE_POSTER)
|
2023-06-23 10:49:26 +00:00
|
|
|
if err != nil {
|
2024-05-16 13:51:04 +00:00
|
|
|
return nil, err
|
2023-06-23 10:49:26 +00:00
|
|
|
}
|
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
2024-05-16 13:51:04 +00:00
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
// Remove members from channels.
|
|
|
|
for memberKey, channels := range result.membersToRemoveFromChannels {
|
|
|
|
memberPubKey, err := common.HexToPubkey(memberKey)
|
2024-05-17 16:15:39 +00:00
|
|
|
if err != nil {
|
2024-05-16 13:51:04 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for channelID := range channels {
|
|
|
|
_, err = community.RemoveUserFromChat(memberPubKey, channelID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-05-17 16:15:39 +00:00
|
|
|
}
|
|
|
|
}
|
2023-06-23 10:49:26 +00:00
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
// Add unprivileged members to channels.
|
|
|
|
for memberKey, channels := range result.membersToAddToChannels {
|
|
|
|
memberPubKey, err := common.HexToPubkey(memberKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !community.HasMember(memberPubKey) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for channelID, channelRole := range channels {
|
|
|
|
_, err = community.AddMemberToChat(channelID, memberPubKey, []protobuf.CommunityMember_Roles{protobuf.CommunityMember_ROLE_NONE}, channelRole)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return community, nil
|
2024-05-17 16:15:39 +00:00
|
|
|
}
|
2023-06-23 10:49:26 +00:00
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
func (m *Manager) reevaluateMemberChannelsPermissions(community *Community, memberPubKey *ecdsa.PublicKey,
|
2024-05-29 14:12:16 +00:00
|
|
|
channelPermissionsPreParsedData map[string]*PreParsedCommunityPermissionsData, accountsAndChainIDs []*AccountChainIDsCombination, collectiblesOwners CollectiblesOwners) (map[string]protobuf.CommunityMember_ChannelRole, map[string]struct{}, error) {
|
2023-06-23 10:49:26 +00:00
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
addToChannels := map[string]protobuf.CommunityMember_ChannelRole{}
|
|
|
|
removeFromChannels := map[string]struct{}{}
|
2024-05-17 16:15:39 +00:00
|
|
|
|
|
|
|
// check which permissions we satisfy and which not
|
2024-05-29 14:12:16 +00:00
|
|
|
channelPermissionsCheckResult, err := m.checkChannelsPermissionsWithPreFetchedData(channelPermissionsPreParsedData, accountsAndChainIDs, true, collectiblesOwners)
|
2024-05-17 16:15:39 +00:00
|
|
|
if err != nil {
|
2024-05-16 13:51:04 +00:00
|
|
|
return nil, nil, err
|
2024-05-17 16:15:39 +00:00
|
|
|
}
|
2023-06-23 10:49:26 +00:00
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
for channelID := range community.Chats() {
|
2024-05-16 13:51:04 +00:00
|
|
|
channelPermissionsCheckResult, hasChannelPermission := channelPermissionsCheckResult[community.ChatID(channelID)]
|
2024-03-08 19:46:59 +00:00
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
// ensure member is added if channel has no permissions
|
|
|
|
if !hasChannelPermission {
|
|
|
|
addToChannels[channelID] = protobuf.CommunityMember_CHANNEL_ROLE_POSTER
|
2024-05-17 16:15:39 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
viewAndPostSatisfied, viewAndPostPermissionExists := channelPermissionsCheckResult[protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL]
|
2024-05-17 16:15:39 +00:00
|
|
|
viewOnlySatisfied, viewOnlyPermissionExists := channelPermissionsCheckResult[protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL]
|
|
|
|
|
|
|
|
satisfied := false
|
|
|
|
channelRole := protobuf.CommunityMember_CHANNEL_ROLE_VIEWER
|
2024-05-16 13:51:04 +00:00
|
|
|
if viewAndPostPermissionExists && viewAndPostSatisfied {
|
2024-05-17 16:15:39 +00:00
|
|
|
satisfied = viewAndPostSatisfied
|
|
|
|
channelRole = protobuf.CommunityMember_CHANNEL_ROLE_POSTER
|
|
|
|
} else if !satisfied && viewOnlyPermissionExists {
|
|
|
|
satisfied = viewOnlySatisfied
|
|
|
|
}
|
|
|
|
|
|
|
|
if satisfied {
|
2024-05-16 13:51:04 +00:00
|
|
|
addToChannels[channelID] = channelRole
|
|
|
|
} else {
|
|
|
|
removeFromChannels[channelID] = struct{}{}
|
2023-03-20 10:36:32 +00:00
|
|
|
}
|
2023-03-20 12:26:20 +00:00
|
|
|
}
|
2024-05-16 13:51:04 +00:00
|
|
|
|
|
|
|
return addToChannels, removeFromChannels, nil
|
2024-05-17 16:15:39 +00:00
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
2024-05-29 14:12:16 +00:00
|
|
|
func (m *Manager) checkChannelsPermissionsImpl(channelsPermissionsPreParsedData map[string]*PreParsedCommunityPermissionsData, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool, collectiblesOwners CollectiblesOwners) (map[string]map[protobuf.CommunityTokenPermission_Type]bool, error) {
|
|
|
|
checkPermissions := func(channelsPermissionPreParsedData *PreParsedCommunityPermissionsData) (*CheckPermissionsResponse, error) {
|
|
|
|
if collectiblesOwners != nil {
|
|
|
|
return m.PermissionChecker.CheckPermissionsWithPreFetchedData(channelsPermissionPreParsedData, accountsAndChainIDs, true, collectiblesOwners)
|
|
|
|
} else {
|
|
|
|
return m.PermissionChecker.CheckPermissions(channelsPermissionPreParsedData, accountsAndChainIDs, true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
channelPermissionsCheckResult := make(map[string]map[protobuf.CommunityTokenPermission_Type]bool)
|
|
|
|
for _, channelsPermissionPreParsedData := range channelsPermissionsPreParsedData {
|
2024-05-29 14:12:16 +00:00
|
|
|
permissionResponse, err := checkPermissions(channelsPermissionPreParsedData)
|
2024-05-17 16:15:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return channelPermissionsCheckResult, err
|
|
|
|
}
|
|
|
|
// Note: in `PreParsedCommunityPermissionsData` for channels there will be only one permission
|
|
|
|
// no need to iterate over `Permissions`
|
|
|
|
for _, chatId := range channelsPermissionPreParsedData.Permissions[0].ChatIds {
|
|
|
|
if _, exists := channelPermissionsCheckResult[chatId]; !exists {
|
|
|
|
channelPermissionsCheckResult[chatId] = make(map[protobuf.CommunityTokenPermission_Type]bool)
|
|
|
|
}
|
|
|
|
satisfied, exists := channelPermissionsCheckResult[chatId][channelsPermissionPreParsedData.Permissions[0].Type]
|
|
|
|
if exists && satisfied {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
channelPermissionsCheckResult[chatId][channelsPermissionPreParsedData.Permissions[0].Type] = permissionResponse.Satisfied
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return channelPermissionsCheckResult, nil
|
2023-03-20 12:26:20 +00:00
|
|
|
}
|
2023-03-20 10:36:32 +00:00
|
|
|
|
2024-05-29 14:12:16 +00:00
|
|
|
func (m *Manager) checkChannelsPermissionsWithPreFetchedData(channelsPermissionsPreParsedData map[string]*PreParsedCommunityPermissionsData, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool, collectiblesOwners CollectiblesOwners) (map[string]map[protobuf.CommunityTokenPermission_Type]bool, error) {
|
|
|
|
return m.checkChannelsPermissionsImpl(channelsPermissionsPreParsedData, accountsAndChainIDs, shortcircuit, collectiblesOwners)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) checkChannelsPermissions(channelsPermissionsPreParsedData map[string]*PreParsedCommunityPermissionsData, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool) (map[string]map[protobuf.CommunityTokenPermission_Type]bool, error) {
|
|
|
|
return m.checkChannelsPermissionsImpl(channelsPermissionsPreParsedData, accountsAndChainIDs, shortcircuit, nil)
|
|
|
|
}
|
|
|
|
|
2024-05-08 15:32:46 +00:00
|
|
|
func (m *Manager) StartMembersReevaluationLoop(communityID types.HexBytes, reevaluateOnStart bool) {
|
|
|
|
go m.reevaluateMembersLoop(communityID, reevaluateOnStart)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) reevaluateMembersLoop(communityID types.HexBytes, reevaluateOnStart bool) {
|
2024-09-26 22:37:32 +00:00
|
|
|
defer utils.LogOnPanic()
|
2024-03-06 08:33:52 +00:00
|
|
|
if _, exists := m.membersReevaluationTasks.Load(communityID.String()); exists {
|
2023-03-20 12:26:20 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-03-06 08:33:52 +00:00
|
|
|
m.membersReevaluationTasks.Store(communityID.String(), &membersReevaluationTask{})
|
|
|
|
defer m.membersReevaluationTasks.Delete(communityID.String())
|
|
|
|
|
2024-05-09 19:59:51 +00:00
|
|
|
var forceReevaluation chan struct{}
|
|
|
|
if m.forceMembersReevaluation != nil {
|
|
|
|
forceReevaluation = make(chan struct{}, 10)
|
|
|
|
m.forceMembersReevaluation[communityID.String()] = forceReevaluation
|
|
|
|
}
|
|
|
|
|
2024-03-06 08:33:52 +00:00
|
|
|
type criticalError struct {
|
|
|
|
error
|
|
|
|
}
|
|
|
|
|
2024-05-17 13:06:33 +00:00
|
|
|
shouldReevaluate := func(task *membersReevaluationTask, force bool) bool {
|
|
|
|
task.mutex.Lock()
|
|
|
|
defer task.mutex.Unlock()
|
|
|
|
|
2024-05-24 20:21:56 +00:00
|
|
|
// Ensure reevaluation is performed not more often than once per 5 minutes
|
|
|
|
if !force && task.lastSuccessTime.After(time.Now().Add(-5*time.Minute)) {
|
2024-05-17 13:06:33 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if !task.lastSuccessTime.Before(time.Now().Add(-memberPermissionsCheckInterval)) &&
|
2024-05-16 13:51:04 +00:00
|
|
|
!task.lastStartTime.Before(task.onDemandRequestTime) {
|
2024-05-17 13:06:33 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2024-05-09 19:59:51 +00:00
|
|
|
reevaluateMembers := func(force bool) (err error) {
|
2024-03-06 08:33:52 +00:00
|
|
|
t, exists := m.membersReevaluationTasks.Load(communityID.String())
|
|
|
|
if !exists {
|
|
|
|
return criticalError{
|
|
|
|
error: errors.New("missing task"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
task, ok := t.(*membersReevaluationTask)
|
|
|
|
if !ok {
|
|
|
|
return criticalError{
|
|
|
|
error: errors.New("invalid task type"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-17 13:06:33 +00:00
|
|
|
if !shouldReevaluate(task, force) {
|
2024-05-08 15:32:46 +00:00
|
|
|
return nil
|
|
|
|
}
|
2023-03-20 12:26:20 +00:00
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
task.mutex.Lock()
|
|
|
|
task.lastStartTime = time.Now()
|
|
|
|
task.mutex.Unlock()
|
|
|
|
|
2024-05-08 15:32:46 +00:00
|
|
|
err = m.reevaluateCommunityMembersPermissions(communityID)
|
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, ErrOrgNotFound) {
|
|
|
|
return criticalError{
|
|
|
|
error: err,
|
|
|
|
}
|
2024-03-06 08:33:52 +00:00
|
|
|
}
|
2024-05-08 15:32:46 +00:00
|
|
|
return err
|
2024-03-06 08:33:52 +00:00
|
|
|
}
|
2024-05-08 15:32:46 +00:00
|
|
|
|
2024-05-17 13:06:33 +00:00
|
|
|
task.mutex.Lock()
|
2024-05-08 15:32:46 +00:00
|
|
|
task.lastSuccessTime = time.Now()
|
2024-05-16 13:51:04 +00:00
|
|
|
task.mutex.Unlock()
|
2024-05-17 13:06:33 +00:00
|
|
|
|
2024-05-16 13:51:04 +00:00
|
|
|
m.logger.Info("reevaluation finished", zap.String("communityID", communityID.String()), zap.Duration("elapsed", task.lastSuccessTime.Sub(task.lastStartTime)))
|
2024-03-06 08:33:52 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ticker := time.NewTicker(10 * time.Second)
|
2023-03-20 12:26:20 +00:00
|
|
|
defer ticker.Stop()
|
|
|
|
|
2024-05-08 15:32:46 +00:00
|
|
|
reevaluate := reevaluateOnStart
|
2024-05-09 19:59:51 +00:00
|
|
|
force := false
|
2024-03-06 08:33:52 +00:00
|
|
|
|
2023-03-20 12:26:20 +00:00
|
|
|
for {
|
2024-05-08 15:32:46 +00:00
|
|
|
if reevaluate {
|
2024-05-09 19:59:51 +00:00
|
|
|
err := reevaluateMembers(force)
|
2023-06-14 14:15:46 +00:00
|
|
|
if err != nil {
|
2024-05-08 15:32:46 +00:00
|
|
|
var criticalError *criticalError
|
|
|
|
if errors.As(err, &criticalError) {
|
2024-03-06 08:33:52 +00:00
|
|
|
return
|
|
|
|
}
|
2023-03-20 12:26:20 +00:00
|
|
|
}
|
2024-05-08 15:32:46 +00:00
|
|
|
}
|
|
|
|
|
2024-05-09 19:59:51 +00:00
|
|
|
force = false
|
|
|
|
reevaluate = false
|
|
|
|
|
2024-05-08 15:32:46 +00:00
|
|
|
select {
|
|
|
|
case <-ticker.C:
|
|
|
|
reevaluate = true
|
|
|
|
continue
|
2023-09-20 08:37:46 +00:00
|
|
|
|
2024-05-09 19:59:51 +00:00
|
|
|
case <-forceReevaluation:
|
|
|
|
reevaluate = true
|
|
|
|
force = true
|
|
|
|
continue
|
|
|
|
|
2024-03-06 08:33:52 +00:00
|
|
|
case <-m.quit:
|
2023-03-20 12:26:20 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2023-03-02 16:27:48 +00:00
|
|
|
}
|
|
|
|
|
2024-05-09 19:59:51 +00:00
|
|
|
func (m *Manager) ForceMembersReevaluation(communityID types.HexBytes) error {
|
|
|
|
if m.forceMembersReevaluation == nil {
|
|
|
|
return errors.New("forcing members reevaluation is not allowed")
|
|
|
|
}
|
|
|
|
return m.scheduleMembersReevaluation(communityID, true)
|
|
|
|
}
|
|
|
|
|
2024-03-06 08:33:52 +00:00
|
|
|
func (m *Manager) ScheduleMembersReevaluation(communityID types.HexBytes) error {
|
2024-05-09 19:59:51 +00:00
|
|
|
return m.scheduleMembersReevaluation(communityID, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) scheduleMembersReevaluation(communityID types.HexBytes, forceImmediateReevaluation bool) error {
|
2024-03-06 08:33:52 +00:00
|
|
|
t, exists := m.membersReevaluationTasks.Load(communityID.String())
|
|
|
|
if !exists {
|
2024-06-25 18:46:29 +00:00
|
|
|
// No reevaluation task yet. We start the loop which will create it
|
|
|
|
m.StartMembersReevaluationLoop(communityID, true)
|
|
|
|
return nil
|
2024-03-06 08:33:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
task, ok := t.(*membersReevaluationTask)
|
|
|
|
if !ok {
|
|
|
|
return errors.New("invalid task type")
|
|
|
|
}
|
|
|
|
task.mutex.Lock()
|
|
|
|
defer task.mutex.Unlock()
|
|
|
|
task.onDemandRequestTime = time.Now()
|
|
|
|
|
2024-05-09 19:59:51 +00:00
|
|
|
if forceImmediateReevaluation {
|
|
|
|
m.forceMembersReevaluation[communityID.String()] <- struct{}{}
|
|
|
|
}
|
|
|
|
|
2024-03-06 08:33:52 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-03-02 16:27:48 +00:00
|
|
|
func (m *Manager) DeleteCommunityTokenPermission(request *requests.DeleteCommunityTokenPermission) (*Community, *CommunityChanges, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(request.CommunityID)
|
|
|
|
defer m.communityLock.Unlock(request.CommunityID)
|
|
|
|
|
2023-03-02 16:27:48 +00:00
|
|
|
community, err := m.GetByID(request.CommunityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
changes, err := community.DeleteTokenPermission(request.PermissionID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
err = m.saveAndPublish(community)
|
2023-06-14 14:15:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2023-03-02 16:27:48 +00:00
|
|
|
return community, changes, nil
|
|
|
|
}
|
|
|
|
|
2024-05-08 15:32:46 +00:00
|
|
|
func (m *Manager) reevaluateCommunityMembersPermissions(communityID types.HexBytes) error {
|
2024-05-08 19:55:30 +00:00
|
|
|
// Publish when the reevluation started since it can take a while
|
|
|
|
signal.SendCommunityMemberReevaluationStarted(types.EncodeHex(communityID))
|
|
|
|
|
2024-05-09 19:59:51 +00:00
|
|
|
community, newPrivilegedMembers, err := m.reevaluateMembers(communityID)
|
2024-05-08 19:55:30 +00:00
|
|
|
|
|
|
|
// Publish the reevaluation ending, even if it errored
|
|
|
|
// A possible improvement would be to pass the error here
|
|
|
|
signal.SendCommunityMemberReevaluationEnded(types.EncodeHex(communityID))
|
|
|
|
|
2023-08-18 15:29:44 +00:00
|
|
|
if err != nil {
|
2023-09-20 08:37:46 +00:00
|
|
|
return err
|
2023-08-18 15:29:44 +00:00
|
|
|
}
|
|
|
|
|
2024-07-05 13:43:03 +00:00
|
|
|
return m.ShareRequestsToJoinWithPrivilegedMembers(community, newPrivilegedMembers)
|
2023-08-18 15:29:44 +00:00
|
|
|
}
|
|
|
|
|
2022-09-29 11:50:23 +00:00
|
|
|
func (m *Manager) DeleteCommunity(id types.HexBytes) error {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(id)
|
|
|
|
defer m.communityLock.Unlock(id)
|
|
|
|
|
2022-09-29 11:50:23 +00:00
|
|
|
err := m.persistence.DeleteCommunity(id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return m.persistence.DeleteCommunitySettings(id)
|
|
|
|
}
|
|
|
|
|
2024-04-16 15:06:27 +00:00
|
|
|
func (m *Manager) updateShard(community *Community, shard *shard.Shard, clock uint64) error {
|
2023-10-12 19:21:49 +00:00
|
|
|
community.config.Shard = shard
|
2024-01-30 17:56:59 +00:00
|
|
|
if shard == nil {
|
|
|
|
return m.persistence.DeleteCommunityShard(community.ID())
|
|
|
|
}
|
|
|
|
|
2024-01-26 15:29:43 +00:00
|
|
|
return m.persistence.SaveCommunityShard(community.ID(), shard, clock)
|
2023-10-12 19:21:49 +00:00
|
|
|
}
|
|
|
|
|
2024-04-16 15:06:27 +00:00
|
|
|
func (m *Manager) UpdateShard(community *Community, shard *shard.Shard, clock uint64) error {
|
|
|
|
m.communityLock.Lock(community.ID())
|
|
|
|
defer m.communityLock.Unlock(community.ID())
|
|
|
|
|
|
|
|
return m.updateShard(community, shard, clock)
|
|
|
|
}
|
|
|
|
|
2023-10-12 19:21:49 +00:00
|
|
|
// SetShard assigns a shard to a community
|
2023-11-15 15:58:15 +00:00
|
|
|
func (m *Manager) SetShard(communityID types.HexBytes, shard *shard.Shard) (*Community, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(communityID)
|
|
|
|
defer m.communityLock.Unlock(communityID)
|
|
|
|
|
2023-10-12 19:21:49 +00:00
|
|
|
community, err := m.GetByID(communityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-12-22 12:37:37 +00:00
|
|
|
community.increaseClock()
|
|
|
|
|
2024-04-16 15:06:27 +00:00
|
|
|
err = m.updateShard(community, shard, community.Clock())
|
2023-10-12 19:21:49 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-12-22 12:37:37 +00:00
|
|
|
err = m.saveAndPublish(community)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-10-12 19:21:49 +00:00
|
|
|
|
|
|
|
return community, nil
|
|
|
|
}
|
|
|
|
|
2024-01-30 17:56:59 +00:00
|
|
|
func (m *Manager) UpdatePubsubTopicPrivateKey(topic string, privKey *ecdsa.PrivateKey) error {
|
2023-10-30 18:34:21 +00:00
|
|
|
if privKey != nil {
|
2024-01-30 17:56:59 +00:00
|
|
|
return m.transport.StorePubsubTopicKey(topic, privKey)
|
2023-10-30 18:34:21 +00:00
|
|
|
}
|
|
|
|
|
2024-01-30 17:56:59 +00:00
|
|
|
return m.transport.RemovePubsubTopicKey(topic)
|
2023-10-30 18:34:21 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 18:20:12 +00:00
|
|
|
// EditCommunity takes a description, updates the community with the description,
|
2021-05-18 19:32:15 +00:00
|
|
|
// saves it and returns it
|
|
|
|
func (m *Manager) EditCommunity(request *requests.EditCommunity) (*Community, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(request.CommunityID)
|
|
|
|
defer m.communityLock.Unlock(request.CommunityID)
|
|
|
|
|
2021-05-18 19:32:15 +00:00
|
|
|
community, err := m.GetByID(request.CommunityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
2021-05-18 19:32:15 +00:00
|
|
|
newDescription, err := request.ToCommunityDescription()
|
|
|
|
if err != nil {
|
2024-05-01 17:27:31 +00:00
|
|
|
return nil, fmt.Errorf("can't create community description: %v", err)
|
2021-05-18 19:32:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// If permissions weren't explicitly set on original request, use existing ones
|
|
|
|
if newDescription.Permissions.Access == protobuf.CommunityPermissions_UNKNOWN_ACCESS {
|
|
|
|
newDescription.Permissions.Access = community.config.CommunityDescription.Permissions.Access
|
|
|
|
}
|
2022-04-15 18:20:12 +00:00
|
|
|
// Use existing images for the entries that were not updated
|
2021-05-18 19:32:15 +00:00
|
|
|
// NOTE: This will NOT allow deletion of the community image; it will need to
|
|
|
|
// be handled separately.
|
2022-04-15 18:20:12 +00:00
|
|
|
for imageName := range community.config.CommunityDescription.Identity.Images {
|
|
|
|
_, exists := newDescription.Identity.Images[imageName]
|
|
|
|
if !exists {
|
|
|
|
// If no image was set in ToCommunityDescription then Images is nil.
|
|
|
|
if newDescription.Identity.Images == nil {
|
|
|
|
newDescription.Identity.Images = make(map[string]*protobuf.IdentityImage)
|
|
|
|
}
|
|
|
|
newDescription.Identity.Images[imageName] = community.config.CommunityDescription.Identity.Images[imageName]
|
|
|
|
}
|
2021-05-18 19:32:15 +00:00
|
|
|
}
|
|
|
|
// TODO: handle delete image (if needed)
|
|
|
|
|
|
|
|
err = ValidateCommunityDescription(newDescription)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if !(community.IsControlNode() || community.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_EDIT)) {
|
|
|
|
return nil, ErrNotAuthorized
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
2021-05-18 19:32:15 +00:00
|
|
|
// Edit the community values
|
|
|
|
community.Edit(newDescription)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-08-16 12:54:55 +00:00
|
|
|
if community.IsControlNode() {
|
2023-07-18 15:06:12 +00:00
|
|
|
community.increaseClock()
|
2023-08-16 12:54:55 +00:00
|
|
|
} else {
|
|
|
|
err := community.addNewCommunityEvent(community.ToCommunityEditCommunityEvent(newDescription))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-05-18 19:32:15 +00:00
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
err = m.saveAndPublish(community)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
2021-05-18 19:32:15 +00:00
|
|
|
|
|
|
|
return community, nil
|
|
|
|
}
|
|
|
|
|
2023-07-21 09:41:26 +00:00
|
|
|
func (m *Manager) RemovePrivateKey(id types.HexBytes) (*Community, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(id)
|
|
|
|
defer m.communityLock.Unlock(id)
|
|
|
|
|
2023-07-21 09:41:26 +00:00
|
|
|
community, err := m.GetByID(id)
|
|
|
|
if err != nil {
|
|
|
|
return community, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !community.IsControlNode() {
|
|
|
|
return community, ErrNotControlNode
|
|
|
|
}
|
|
|
|
|
|
|
|
community.config.PrivateKey = nil
|
|
|
|
err = m.persistence.SaveCommunity(community)
|
|
|
|
if err != nil {
|
|
|
|
return community, err
|
|
|
|
}
|
|
|
|
return community, nil
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func (m *Manager) ExportCommunity(id types.HexBytes) (*ecdsa.PrivateKey, error) {
|
|
|
|
community, err := m.GetByID(id)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-21 09:41:26 +00:00
|
|
|
if !community.IsControlNode() {
|
|
|
|
return nil, ErrNotControlNode
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2020-12-17 14:36:09 +00:00
|
|
|
return community.config.PrivateKey, nil
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2023-09-21 11:16:05 +00:00
|
|
|
func (m *Manager) ImportCommunity(key *ecdsa.PrivateKey, clock uint64) (*Community, error) {
|
2020-12-17 14:36:09 +00:00
|
|
|
communityID := crypto.CompressPubkey(&key.PublicKey)
|
2020-11-18 09:16:51 +00:00
|
|
|
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(communityID)
|
|
|
|
defer m.communityLock.Unlock(communityID)
|
|
|
|
|
2023-09-25 11:26:17 +00:00
|
|
|
community, err := m.GetByID(communityID)
|
2024-01-09 21:47:37 +00:00
|
|
|
if err != nil && err != ErrOrgNotFound {
|
2020-11-18 09:16:51 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-12-17 14:36:09 +00:00
|
|
|
if community == nil {
|
2023-09-21 11:16:05 +00:00
|
|
|
createCommunityRequest := requests.CreateCommunity{
|
2023-10-25 13:03:26 +00:00
|
|
|
Membership: protobuf.CommunityPermissions_MANUAL_ACCEPT,
|
2023-09-21 11:16:05 +00:00
|
|
|
Name: "unknown imported",
|
|
|
|
}
|
|
|
|
|
|
|
|
description, err := createCommunityRequest.ToCommunityDescription()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = ValidateCommunityDescription(description)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2020-12-17 14:36:09 +00:00
|
|
|
}
|
|
|
|
|
2023-09-21 11:16:05 +00:00
|
|
|
description.Clock = 1
|
|
|
|
description.ID = types.EncodeHex(communityID)
|
|
|
|
|
2020-12-17 14:36:09 +00:00
|
|
|
config := Config{
|
|
|
|
ID: &key.PublicKey,
|
|
|
|
PrivateKey: key,
|
2023-10-18 15:04:02 +00:00
|
|
|
ControlNode: &key.PublicKey,
|
2023-09-21 11:16:05 +00:00
|
|
|
ControlDevice: true,
|
2020-12-17 14:36:09 +00:00
|
|
|
Logger: m.logger,
|
|
|
|
Joined: true,
|
2024-01-09 18:36:47 +00:00
|
|
|
JoinedAt: time.Now().Unix(),
|
2024-06-27 15:29:03 +00:00
|
|
|
MemberIdentity: m.identity,
|
2020-12-17 14:36:09 +00:00
|
|
|
CommunityDescription: description,
|
2024-01-21 10:55:14 +00:00
|
|
|
LastOpenedAt: 0,
|
2020-12-17 14:36:09 +00:00
|
|
|
}
|
2023-11-29 17:21:21 +00:00
|
|
|
|
|
|
|
var descriptionEncryptor DescriptionEncryptor
|
|
|
|
if m.encryptor != nil {
|
|
|
|
descriptionEncryptor = m
|
|
|
|
}
|
2024-07-01 18:52:57 +00:00
|
|
|
community, err = New(config, m.timesource, descriptionEncryptor, m.mediaServer)
|
2020-12-17 14:36:09 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
community.config.PrivateKey = key
|
2023-09-21 11:16:05 +00:00
|
|
|
community.config.ControlDevice = true
|
2020-12-17 14:36:09 +00:00
|
|
|
}
|
|
|
|
|
2022-07-01 13:54:02 +00:00
|
|
|
community.Join()
|
2020-12-17 14:36:09 +00:00
|
|
|
err = m.persistence.SaveCommunity(community)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
// Save grant for own community
|
|
|
|
grant, err := community.BuildGrant(&m.identity.PublicKey, "")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-04-17 14:53:51 +00:00
|
|
|
err = m.persistence.SaveCommunityGrant(community.IDString(), grant, uint64(time.Now().UnixMilli()))
|
2024-02-15 19:13:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-09-21 11:16:05 +00:00
|
|
|
// Mark this device as the control node
|
|
|
|
syncControlNode := &protobuf.SyncCommunityControlNode{
|
|
|
|
Clock: clock,
|
|
|
|
InstallationId: m.installationID,
|
|
|
|
}
|
|
|
|
err = m.SaveSyncControlNode(community.ID(), syncControlNode)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-12-17 14:36:09 +00:00
|
|
|
return community, nil
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
func (m *Manager) CreateChat(communityID types.HexBytes, chat *protobuf.CommunityChat, publish bool, thirdPartyID string) (*CommunityChanges, error) {
|
2024-02-23 02:16:51 +00:00
|
|
|
m.communityLock.Lock(communityID)
|
|
|
|
defer m.communityLock.Unlock(communityID)
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
community, err := m.GetByID(communityID)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
2023-07-18 15:06:12 +00:00
|
|
|
return nil, err
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
chatID := uuid.New().String()
|
2023-01-26 12:52:43 +00:00
|
|
|
if thirdPartyID != "" {
|
|
|
|
chatID = chatID + thirdPartyID
|
|
|
|
}
|
|
|
|
|
2020-12-17 14:36:09 +00:00
|
|
|
changes, err := community.CreateChat(chatID, chat)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
2023-07-18 15:06:12 +00:00
|
|
|
return nil, err
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
err = m.saveAndPublish(community)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
2023-07-18 15:06:12 +00:00
|
|
|
return nil, err
|
2022-08-19 12:51:21 +00:00
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
return changes, nil
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2021-06-01 12:13:17 +00:00
|
|
|
func (m *Manager) EditChat(communityID types.HexBytes, chatID string, chat *protobuf.CommunityChat) (*Community, *CommunityChanges, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(communityID)
|
|
|
|
defer m.communityLock.Unlock(communityID)
|
|
|
|
|
2021-06-01 12:13:17 +00:00
|
|
|
community, err := m.GetByID(communityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2021-07-02 18:07:49 +00:00
|
|
|
|
|
|
|
// Remove communityID prefix from chatID if exists
|
|
|
|
if strings.HasPrefix(chatID, communityID.String()) {
|
|
|
|
chatID = strings.TrimPrefix(chatID, communityID.String())
|
|
|
|
}
|
|
|
|
|
2024-03-07 17:30:23 +00:00
|
|
|
oldChat, err := community.GetChat(chatID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// We can't edit permissions and members with an Edit, so we set to what we had, otherwise they will be lost
|
|
|
|
chat.Permissions = oldChat.Permissions
|
|
|
|
chat.Members = oldChat.Members
|
|
|
|
|
2021-06-01 12:13:17 +00:00
|
|
|
changes, err := community.EditChat(chatID, chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
err = m.saveAndPublish(community)
|
2021-06-01 12:13:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return community, changes, nil
|
|
|
|
}
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
func (m *Manager) DeleteChat(communityID types.HexBytes, chatID string) (*Community, *CommunityChanges, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(communityID)
|
|
|
|
defer m.communityLock.Unlock(communityID)
|
|
|
|
|
2021-07-30 17:05:44 +00:00
|
|
|
community, err := m.GetByID(communityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2024-06-07 16:30:01 +00:00
|
|
|
// Check for channel permissions
|
|
|
|
changes := community.emptyCommunityChanges()
|
|
|
|
for tokenPermissionID, tokenPermission := range community.tokenPermissions() {
|
|
|
|
chats := tokenPermission.ChatIdsAsMap()
|
|
|
|
_, hasChat := chats[chatID]
|
|
|
|
if !hasChat {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(chats) == 1 {
|
|
|
|
// Delete channel permission, if there is only one channel
|
|
|
|
deletePermissionChanges, err := community.DeleteTokenPermission(tokenPermissionID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
changes.Merge(deletePermissionChanges)
|
|
|
|
} else {
|
|
|
|
// Remove the channel from the permission, if there are other channels
|
|
|
|
delete(chats, chatID)
|
|
|
|
|
|
|
|
var chatIDs []string
|
|
|
|
for chatID := range chats {
|
|
|
|
chatIDs = append(chatIDs, chatID)
|
|
|
|
}
|
|
|
|
tokenPermission.ChatIds = chatIDs
|
|
|
|
|
|
|
|
updatePermissionChanges, err := community.UpsertTokenPermission(tokenPermission.CommunityTokenPermission)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
changes.Merge(updatePermissionChanges)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-30 17:05:44 +00:00
|
|
|
// Remove communityID prefix from chatID if exists
|
|
|
|
if strings.HasPrefix(chatID, communityID.String()) {
|
|
|
|
chatID = strings.TrimPrefix(chatID, communityID.String())
|
|
|
|
}
|
2024-06-07 16:30:01 +00:00
|
|
|
|
|
|
|
deleteChanges, err := community.DeleteChat(chatID)
|
2021-07-30 17:05:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2024-06-07 16:30:01 +00:00
|
|
|
changes.Merge(deleteChanges)
|
2021-07-30 17:05:44 +00:00
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
err = m.saveAndPublish(community)
|
2021-07-30 17:05:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
return community, changes, nil
|
2021-07-30 17:05:44 +00:00
|
|
|
}
|
|
|
|
|
2022-08-19 12:51:21 +00:00
|
|
|
func (m *Manager) CreateCategory(request *requests.CreateCommunityCategory, publish bool) (*Community, *CommunityChanges, error) {
|
2024-02-23 02:16:51 +00:00
|
|
|
m.communityLock.Lock(request.CommunityID)
|
|
|
|
defer m.communityLock.Unlock(request.CommunityID)
|
|
|
|
|
2021-05-23 13:34:17 +00:00
|
|
|
community, err := m.GetByID(request.CommunityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2023-01-26 12:52:43 +00:00
|
|
|
|
2021-05-23 13:34:17 +00:00
|
|
|
categoryID := uuid.New().String()
|
2023-01-26 12:52:43 +00:00
|
|
|
if request.ThirdPartyID != "" {
|
|
|
|
categoryID = categoryID + request.ThirdPartyID
|
|
|
|
}
|
2021-07-02 18:07:49 +00:00
|
|
|
|
|
|
|
// Remove communityID prefix from chatID if exists
|
|
|
|
for i, cid := range request.ChatIDs {
|
|
|
|
if strings.HasPrefix(cid, request.CommunityID.String()) {
|
|
|
|
request.ChatIDs[i] = strings.TrimPrefix(cid, request.CommunityID.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-23 13:34:17 +00:00
|
|
|
changes, err := community.CreateCategory(categoryID, request.CategoryName, request.ChatIDs)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
err = m.saveAndPublish(community)
|
2021-05-23 13:34:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return community, changes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) EditCategory(request *requests.EditCommunityCategory) (*Community, *CommunityChanges, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(request.CommunityID)
|
|
|
|
defer m.communityLock.Unlock(request.CommunityID)
|
|
|
|
|
2021-05-23 13:34:17 +00:00
|
|
|
community, err := m.GetByID(request.CommunityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2021-07-02 18:07:49 +00:00
|
|
|
// Remove communityID prefix from chatID if exists
|
|
|
|
for i, cid := range request.ChatIDs {
|
|
|
|
if strings.HasPrefix(cid, request.CommunityID.String()) {
|
|
|
|
request.ChatIDs[i] = strings.TrimPrefix(cid, request.CommunityID.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-23 13:34:17 +00:00
|
|
|
changes, err := community.EditCategory(request.CategoryID, request.CategoryName, request.ChatIDs)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
err = m.saveAndPublish(community)
|
2021-05-23 13:34:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return community, changes, nil
|
|
|
|
}
|
|
|
|
|
2022-09-02 08:36:07 +00:00
|
|
|
func (m *Manager) EditChatFirstMessageTimestamp(communityID types.HexBytes, chatID string, timestamp uint32) (*Community, *CommunityChanges, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(communityID)
|
|
|
|
defer m.communityLock.Unlock(communityID)
|
|
|
|
|
2022-09-02 08:36:07 +00:00
|
|
|
community, err := m.GetByID(communityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove communityID prefix from chatID if exists
|
|
|
|
if strings.HasPrefix(chatID, communityID.String()) {
|
|
|
|
chatID = strings.TrimPrefix(chatID, communityID.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
changes, err := community.UpdateChatFirstMessageTimestamp(chatID, timestamp)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.persistence.SaveCommunity(community)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Advertise changes
|
|
|
|
m.publish(&Subscription{Community: community})
|
|
|
|
|
|
|
|
return community, changes, nil
|
|
|
|
}
|
|
|
|
|
2024-10-18 02:25:34 +00:00
|
|
|
func (m *Manager) UpdateChatFirstMessageTimestamp(community *Community, chatID string, timestamp uint32) (*CommunityChanges, error) {
|
|
|
|
communityID := community.ID().String()
|
|
|
|
chatID = strings.TrimPrefix(chatID, communityID)
|
|
|
|
return community.UpdateChatFirstMessageTimestamp(chatID, timestamp)
|
|
|
|
}
|
|
|
|
|
2021-05-23 13:34:17 +00:00
|
|
|
func (m *Manager) ReorderCategories(request *requests.ReorderCommunityCategories) (*Community, *CommunityChanges, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(request.CommunityID)
|
|
|
|
defer m.communityLock.Unlock(request.CommunityID)
|
|
|
|
|
2021-05-23 13:34:17 +00:00
|
|
|
community, err := m.GetByID(request.CommunityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
changes, err := community.ReorderCategories(request.CategoryID, request.Position)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
err = m.saveAndPublish(community)
|
2021-05-23 13:34:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return community, changes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) ReorderChat(request *requests.ReorderCommunityChat) (*Community, *CommunityChanges, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(request.CommunityID)
|
|
|
|
defer m.communityLock.Unlock(request.CommunityID)
|
|
|
|
|
2021-05-23 13:34:17 +00:00
|
|
|
community, err := m.GetByID(request.CommunityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2021-07-02 18:07:49 +00:00
|
|
|
// Remove communityID prefix from chatID if exists
|
|
|
|
if strings.HasPrefix(request.ChatID, request.CommunityID.String()) {
|
|
|
|
request.ChatID = strings.TrimPrefix(request.ChatID, request.CommunityID.String())
|
|
|
|
}
|
|
|
|
|
2021-05-23 13:34:17 +00:00
|
|
|
changes, err := community.ReorderChat(request.CategoryID, request.ChatID, request.Position)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
err = m.saveAndPublish(community)
|
2021-05-23 13:34:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return community, changes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) DeleteCategory(request *requests.DeleteCommunityCategory) (*Community, *CommunityChanges, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(request.CommunityID)
|
|
|
|
defer m.communityLock.Unlock(request.CommunityID)
|
|
|
|
|
2021-05-23 13:34:17 +00:00
|
|
|
community, err := m.GetByID(request.CommunityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
changes, err := community.DeleteCategory(request.CategoryID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
err = m.saveAndPublish(community)
|
2021-05-23 13:34:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
return changes.Community, changes, nil
|
2021-05-23 13:34:17 +00:00
|
|
|
}
|
|
|
|
|
2023-12-04 18:20:09 +00:00
|
|
|
func (m *Manager) GenerateRequestsToJoinForAutoApprovalOnNewOwnership(communityID types.HexBytes, kickedMembers map[string]*protobuf.CommunityMember) ([]*RequestToJoin, error) {
|
2023-10-31 14:20:40 +00:00
|
|
|
var requestsToJoin []*RequestToJoin
|
|
|
|
clock := uint64(time.Now().Unix())
|
|
|
|
for pubKeyStr := range kickedMembers {
|
|
|
|
requestToJoin := &RequestToJoin{
|
|
|
|
PublicKey: pubKeyStr,
|
|
|
|
Clock: clock,
|
|
|
|
CommunityID: communityID,
|
|
|
|
State: RequestToJoinStateAwaitingAddresses,
|
|
|
|
Our: true,
|
|
|
|
RevealedAccounts: make([]*protobuf.RevealedAccount, 0),
|
|
|
|
}
|
|
|
|
|
|
|
|
requestToJoin.CalculateID()
|
|
|
|
|
|
|
|
requestsToJoin = append(requestsToJoin, requestToJoin)
|
|
|
|
}
|
|
|
|
|
2023-12-04 18:20:09 +00:00
|
|
|
return requestsToJoin, m.persistence.SaveRequestsToJoin(requestsToJoin)
|
2023-10-31 14:20:40 +00:00
|
|
|
}
|
|
|
|
|
2023-07-05 17:35:22 +00:00
|
|
|
func (m *Manager) Queue(signer *ecdsa.PublicKey, community *Community, clock uint64, payload []byte) error {
|
|
|
|
|
|
|
|
m.logger.Info("queuing community", zap.String("id", community.IDString()), zap.String("signer", common.PubkeyToHex(signer)))
|
|
|
|
|
|
|
|
communityToValidate := communityToValidate{
|
|
|
|
id: community.ID(),
|
|
|
|
clock: clock,
|
|
|
|
payload: payload,
|
|
|
|
validateAt: uint64(time.Now().UnixNano()),
|
|
|
|
signer: crypto.CompressPubkey(signer),
|
|
|
|
}
|
|
|
|
err := m.persistence.SaveCommunityToValidate(communityToValidate)
|
|
|
|
if err != nil {
|
|
|
|
m.logger.Error("failed to save community", zap.Error(err))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-11-17 23:35:29 +00:00
|
|
|
func (m *Manager) HandleCommunityDescriptionMessage(signer *ecdsa.PublicKey, description *protobuf.CommunityDescription, payload []byte, verifiedOwner *ecdsa.PublicKey, communityShard *protobuf.Shard) (*CommunityResponse, error) {
|
2024-01-23 16:56:51 +00:00
|
|
|
m.logger.Debug("HandleCommunityDescriptionMessage", zap.String("communityID", description.ID), zap.Uint64("clock", description.Clock))
|
2023-12-15 19:50:12 +00:00
|
|
|
|
2023-03-29 11:36:13 +00:00
|
|
|
if signer == nil {
|
|
|
|
return nil, errors.New("signer can't be nil")
|
|
|
|
}
|
|
|
|
|
2023-07-05 17:35:22 +00:00
|
|
|
var id []byte
|
|
|
|
var err error
|
|
|
|
if len(description.ID) != 0 {
|
|
|
|
id, err = types.DecodeHex(description.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Backward compatibility
|
|
|
|
id = crypto.CompressPubkey(signer)
|
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
2024-02-29 09:51:38 +00:00
|
|
|
failedToDecrypt, processedDescription, err := m.preprocessDescription(id, description)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-02-23 02:16:51 +00:00
|
|
|
m.communityLock.Lock(id)
|
|
|
|
defer m.communityLock.Unlock(id)
|
2023-11-29 17:21:21 +00:00
|
|
|
community, err := m.GetByID(id)
|
2024-01-09 21:47:37 +00:00
|
|
|
if err != nil && err != ErrOrgNotFound {
|
2023-11-29 17:21:21 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2023-09-25 11:26:17 +00:00
|
|
|
|
2024-01-23 16:56:51 +00:00
|
|
|
// We don't process failed to decrypt if the whole metadata is encrypted
|
|
|
|
// and we joined the community already
|
2024-02-29 09:51:38 +00:00
|
|
|
if community != nil && community.Joined() && len(failedToDecrypt) != 0 && processedDescription != nil && len(processedDescription.Members) == 0 {
|
2024-01-23 16:56:51 +00:00
|
|
|
return &CommunityResponse{FailedToDecrypt: failedToDecrypt}, nil
|
|
|
|
}
|
|
|
|
|
2023-07-05 17:35:22 +00:00
|
|
|
// We should queue only if the community has a token owner, and the owner has been verified
|
2024-02-29 09:51:38 +00:00
|
|
|
hasTokenOwnership := HasTokenOwnership(processedDescription)
|
2023-07-05 17:35:22 +00:00
|
|
|
shouldQueue := hasTokenOwnership && verifiedOwner == nil
|
|
|
|
|
2020-12-17 14:36:09 +00:00
|
|
|
if community == nil {
|
2023-07-05 17:35:22 +00:00
|
|
|
pubKey, err := crypto.DecompressPubkey(id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-07-15 15:25:12 +00:00
|
|
|
var cShard *shard.Shard
|
|
|
|
if communityShard == nil {
|
|
|
|
cShard = &shard.Shard{Cluster: shard.MainStatusShardCluster, Index: shard.DefaultShardIndex}
|
|
|
|
} else {
|
|
|
|
cShard = shard.FromProtobuff(communityShard)
|
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
config := Config{
|
2024-02-29 09:51:38 +00:00
|
|
|
CommunityDescription: processedDescription,
|
2023-07-10 15:35:15 +00:00
|
|
|
Logger: m.logger,
|
|
|
|
CommunityDescriptionProtocolMessage: payload,
|
2024-06-27 15:29:03 +00:00
|
|
|
MemberIdentity: m.identity,
|
2023-07-05 17:35:22 +00:00
|
|
|
ID: pubKey,
|
|
|
|
ControlNode: signer,
|
2024-07-15 15:25:12 +00:00
|
|
|
Shard: cShard,
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
2023-07-05 17:35:22 +00:00
|
|
|
|
2023-11-29 17:21:21 +00:00
|
|
|
var descriptionEncryptor DescriptionEncryptor
|
|
|
|
if m.encryptor != nil {
|
|
|
|
descriptionEncryptor = m
|
|
|
|
}
|
2024-07-01 18:52:57 +00:00
|
|
|
community, err = New(config, m.timesource, descriptionEncryptor, m.mediaServer)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-07-05 17:35:22 +00:00
|
|
|
|
|
|
|
// A new community, we need to check if we need to validate async.
|
2023-10-19 16:52:57 +00:00
|
|
|
// That would be the case if it has a contract. We queue everything and process separately.
|
2023-07-05 17:35:22 +00:00
|
|
|
if shouldQueue {
|
2024-02-29 09:51:38 +00:00
|
|
|
return nil, m.Queue(signer, community, processedDescription.Clock, payload)
|
2023-07-05 17:35:22 +00:00
|
|
|
}
|
|
|
|
} else {
|
2023-10-19 16:52:57 +00:00
|
|
|
// only queue if already known control node is different than the signer
|
|
|
|
// and if the clock is greater
|
|
|
|
shouldQueue = shouldQueue && !common.IsPubKeyEqual(community.ControlNode(), signer) &&
|
2024-02-29 09:51:38 +00:00
|
|
|
community.config.CommunityDescription.Clock < processedDescription.Clock
|
2023-10-19 16:52:57 +00:00
|
|
|
if shouldQueue {
|
2024-02-29 09:51:38 +00:00
|
|
|
return nil, m.Queue(signer, community, processedDescription.Clock, payload)
|
2023-07-05 17:35:22 +00:00
|
|
|
}
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2023-12-07 16:27:14 +00:00
|
|
|
if hasTokenOwnership && verifiedOwner != nil {
|
2023-10-19 16:52:57 +00:00
|
|
|
// Override verified owner
|
2024-01-08 15:57:57 +00:00
|
|
|
m.logger.Info("updating verified owner",
|
|
|
|
zap.String("communityID", community.IDString()),
|
|
|
|
zap.String("verifiedOwner", common.PubkeyToHex(verifiedOwner)),
|
|
|
|
zap.String("signer", common.PubkeyToHex(signer)),
|
|
|
|
zap.String("controlNode", common.PubkeyToHex(community.ControlNode())),
|
|
|
|
)
|
2023-07-05 17:35:22 +00:00
|
|
|
|
2023-12-07 16:27:14 +00:00
|
|
|
// If we are not the verified owner anymore, drop the private key
|
|
|
|
if !common.IsPubKeyEqual(verifiedOwner, &m.identity.PublicKey) {
|
|
|
|
community.config.PrivateKey = nil
|
|
|
|
}
|
2023-07-05 17:35:22 +00:00
|
|
|
|
2023-12-07 16:27:14 +00:00
|
|
|
// new control node will be set in the 'UpdateCommunityDescription'
|
|
|
|
if !common.IsPubKeyEqual(verifiedOwner, signer) {
|
2023-10-19 16:52:57 +00:00
|
|
|
return nil, ErrNotAuthorized
|
|
|
|
}
|
2023-12-07 16:27:14 +00:00
|
|
|
} else if !common.IsPubKeyEqual(community.ControlNode(), signer) {
|
2023-06-14 14:15:46 +00:00
|
|
|
return nil, ErrNotAuthorized
|
|
|
|
}
|
|
|
|
|
2024-02-29 09:51:38 +00:00
|
|
|
r, err := m.handleCommunityDescriptionMessageCommon(community, processedDescription, payload, verifiedOwner)
|
2024-01-23 16:56:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
r.FailedToDecrypt = failedToDecrypt
|
|
|
|
return r, nil
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2024-02-29 09:51:38 +00:00
|
|
|
func (m *Manager) NewHashRatchetKeys(keys []*encryption.HashRatchetInfo) error {
|
|
|
|
return m.persistence.InvalidateDecryptedCommunityCacheForKeys(keys)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) preprocessDescription(id types.HexBytes, description *protobuf.CommunityDescription) ([]*CommunityPrivateDataFailedToDecrypt, *protobuf.CommunityDescription, error) {
|
|
|
|
decryptedCommunity, err := m.persistence.GetDecryptedCommunityDescription(id, description.Clock)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
if decryptedCommunity != nil {
|
|
|
|
return nil, decryptedCommunity, nil
|
|
|
|
}
|
|
|
|
|
2024-01-23 16:56:51 +00:00
|
|
|
response, err := decryptDescription(id, m, description, m.logger)
|
2023-11-29 17:21:21 +00:00
|
|
|
if err != nil {
|
2024-02-29 09:51:38 +00:00
|
|
|
return response, description, err
|
2023-11-29 17:21:21 +00:00
|
|
|
}
|
|
|
|
|
2024-02-13 10:23:11 +00:00
|
|
|
upgradeTokenPermissions(description)
|
|
|
|
|
2023-11-29 17:21:21 +00:00
|
|
|
// Workaround for https://github.com/status-im/status-desktop/issues/12188
|
2024-06-27 09:36:18 +00:00
|
|
|
hydrateChannelsMembers(description)
|
2023-11-29 17:21:21 +00:00
|
|
|
|
2024-02-29 09:51:38 +00:00
|
|
|
return response, description, m.persistence.SaveDecryptedCommunityDescription(id, response, description)
|
2023-11-29 17:21:21 +00:00
|
|
|
}
|
|
|
|
|
2023-10-31 14:20:40 +00:00
|
|
|
func (m *Manager) handleCommunityDescriptionMessageCommon(community *Community, description *protobuf.CommunityDescription, payload []byte, newControlNode *ecdsa.PublicKey) (*CommunityResponse, error) {
|
2024-03-01 16:37:20 +00:00
|
|
|
prevClock := community.config.CommunityDescription.Clock
|
2024-07-05 08:38:12 +00:00
|
|
|
prevResendAccountsClock := community.config.CommunityDescription.ResendAccountsClock
|
|
|
|
|
2023-10-31 14:20:40 +00:00
|
|
|
changes, err := community.UpdateCommunityDescription(description, payload, newControlNode)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-01-16 08:08:56 +00:00
|
|
|
if err = m.handleCommunityTokensMetadata(community); err != nil {
|
2023-08-22 17:48:42 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-04-22 07:42:22 +00:00
|
|
|
hasCommunityArchiveInfo, err := m.persistence.HasCommunityArchiveInfo(community.ID())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cdMagnetlinkClock := community.config.CommunityDescription.ArchiveMagnetlinkClock
|
|
|
|
if !hasCommunityArchiveInfo {
|
|
|
|
err = m.persistence.SaveCommunityArchiveInfo(community.ID(), cdMagnetlinkClock, 0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
magnetlinkClock, err := m.persistence.GetMagnetlinkMessageClock(community.ID())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if cdMagnetlinkClock > magnetlinkClock {
|
|
|
|
err = m.persistence.UpdateMagnetlinkMessageClock(community.ID(), cdMagnetlinkClock)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-14 09:26:10 +00:00
|
|
|
pkString := common.PubkeyToHex(&m.identity.PublicKey)
|
2023-07-05 17:35:22 +00:00
|
|
|
if m.tokenManager != nil && description.CommunityTokensMetadata != nil && len(description.CommunityTokensMetadata) > 0 {
|
2023-08-22 09:32:27 +00:00
|
|
|
for _, tokenMetadata := range description.CommunityTokensMetadata {
|
|
|
|
if tokenMetadata.TokenType != protobuf.CommunityTokenType_ERC20 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for chainID, address := range tokenMetadata.ContractAddresses {
|
2023-09-11 11:45:30 +00:00
|
|
|
_ = m.tokenManager.FindOrCreateTokenByAddress(context.Background(), chainID, gethcommon.HexToAddress(address))
|
2023-08-22 09:32:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-11 10:32:51 +00:00
|
|
|
|
|
|
|
// If the community require membership, we set whether we should leave/join the community after a state change
|
2023-10-25 13:22:12 +00:00
|
|
|
if community.ManualAccept() || community.AutoAccept() {
|
2021-01-11 10:32:51 +00:00
|
|
|
if changes.HasNewMember(pkString) {
|
|
|
|
hasPendingRequest, err := m.persistence.HasPendingRequestsToJoinForUserAndCommunity(pkString, changes.Community.ID())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// If there's any pending request, we should join the community
|
|
|
|
// automatically
|
|
|
|
changes.ShouldMemberJoin = hasPendingRequest
|
|
|
|
}
|
|
|
|
|
2024-07-05 08:38:12 +00:00
|
|
|
if changes.HasMemberLeft(pkString) && community.Joined() {
|
|
|
|
softKick := !changes.IsMemberBanned(pkString) &&
|
|
|
|
(changes.ControlNodeChanged != nil || prevResendAccountsClock < community.Description().ResendAccountsClock)
|
|
|
|
|
2023-10-27 19:20:08 +00:00
|
|
|
// If we joined previously the community, that means we have been kicked
|
2024-07-05 08:38:12 +00:00
|
|
|
changes.MemberKicked = !softKick
|
|
|
|
// soft kick previously joined member on community owner change or on ResendAccountsClock change
|
|
|
|
changes.MemberSoftKicked = softKick
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-01 16:37:20 +00:00
|
|
|
if description.Clock > prevClock {
|
|
|
|
err = m.persistence.DeleteCommunityEvents(community.ID())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
community.config.EventsData = nil
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
2023-10-10 18:03:55 +00:00
|
|
|
// Set Joined if we are part of the member list
|
|
|
|
if !community.Joined() && community.hasMember(&m.identity.PublicKey) {
|
|
|
|
changes.ShouldMemberJoin = true
|
|
|
|
}
|
|
|
|
|
2020-12-17 14:36:09 +00:00
|
|
|
err = m.persistence.SaveCommunity(community)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
// We mark our requests as completed, though maybe we should mark
|
|
|
|
// any request for any user that has been added as completed
|
2023-10-04 19:02:17 +00:00
|
|
|
if err := m.markRequestToJoinAsAccepted(&m.identity.PublicKey, community); err != nil {
|
2021-01-11 10:32:51 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Check if there's a change and we should be joining
|
|
|
|
|
|
|
|
return &CommunityResponse{
|
|
|
|
Community: community,
|
|
|
|
Changes: changes,
|
|
|
|
}, nil
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2023-08-08 18:33:29 +00:00
|
|
|
func (m *Manager) signEvents(community *Community) error {
|
|
|
|
for i := range community.config.EventsData.Events {
|
|
|
|
communityEvent := &community.config.EventsData.Events[i]
|
|
|
|
if communityEvent.Signature == nil || len(communityEvent.Signature) == 0 {
|
2023-08-08 13:16:29 +00:00
|
|
|
err := communityEvent.Sign(m.identity)
|
2023-08-08 18:33:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
func (m *Manager) HandleCommunityEventsMessage(signer *ecdsa.PublicKey, message *protobuf.CommunityEventsMessage) (*CommunityResponse, error) {
|
2023-06-14 14:15:46 +00:00
|
|
|
if signer == nil {
|
|
|
|
return nil, errors.New("signer can't be nil")
|
|
|
|
}
|
|
|
|
|
2023-08-08 18:33:29 +00:00
|
|
|
eventsMessage, err := CommunityEventsMessageFromProtobuf(message)
|
2023-07-18 15:06:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(eventsMessage.CommunityID)
|
|
|
|
defer m.communityLock.Unlock(eventsMessage.CommunityID)
|
|
|
|
|
2023-09-25 11:26:17 +00:00
|
|
|
community, err := m.GetByID(eventsMessage.CommunityID)
|
2023-06-14 14:15:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-28 18:18:27 +00:00
|
|
|
if !community.IsPrivilegedMember(signer) {
|
|
|
|
return nil, errors.New("user has not permissions to send events")
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
originCommunity := community.CreateDeepCopy()
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
var lastlyAppliedEvents map[string]uint64
|
|
|
|
if community.IsControlNode() {
|
|
|
|
lastlyAppliedEvents, err = m.persistence.GetAppliedCommunityEvents(community.ID())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-08-08 13:16:29 +00:00
|
|
|
}
|
2024-02-19 09:52:22 +00:00
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
2024-05-10 15:56:40 +00:00
|
|
|
additionalCommunityResponse, err := m.handleCommunityEventsAndMetadata(community, eventsMessage, lastlyAppliedEvents)
|
2023-06-14 14:15:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
// Control node applies events and publish updated CommunityDescription
|
|
|
|
if community.IsControlNode() {
|
2024-02-19 09:52:22 +00:00
|
|
|
appliedEvents := map[string]uint64{}
|
|
|
|
if community.config.EventsData != nil {
|
|
|
|
for _, event := range community.config.EventsData.Events {
|
|
|
|
appliedEvents[event.EventTypeID()] = event.CommunityEventClock
|
|
|
|
}
|
|
|
|
}
|
2023-08-17 17:14:23 +00:00
|
|
|
community.config.EventsData = nil // clear events, they are already applied
|
|
|
|
community.increaseClock()
|
2023-11-29 17:21:21 +00:00
|
|
|
|
|
|
|
if m.keyDistributor != nil {
|
|
|
|
encryptionKeyActions := EvaluateCommunityEncryptionKeyActions(originCommunity, community)
|
|
|
|
err := m.keyDistributor.Generate(community, encryptionKeyActions)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
err = m.persistence.SaveCommunity(community)
|
2023-07-18 15:06:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
err = m.persistence.UpsertAppliedCommunityEvents(community.ID(), appliedEvents)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
m.publish(&Subscription{Community: community})
|
2023-07-18 15:06:12 +00:00
|
|
|
} else {
|
2023-08-17 17:14:23 +00:00
|
|
|
err = m.persistence.SaveCommunity(community)
|
2023-07-18 15:06:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-08-17 17:14:23 +00:00
|
|
|
err := m.persistence.SaveCommunityEvents(community)
|
2023-07-18 15:06:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
return &CommunityResponse{
|
2023-08-17 17:14:23 +00:00
|
|
|
Community: community,
|
|
|
|
Changes: EvaluateCommunityChanges(originCommunity, community),
|
2023-08-18 19:52:13 +00:00
|
|
|
RequestsToJoin: additionalCommunityResponse.RequestsToJoin,
|
2023-07-18 15:06:12 +00:00
|
|
|
}, nil
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2023-08-18 19:52:13 +00:00
|
|
|
func (m *Manager) handleAdditionalAdminChanges(community *Community) (*CommunityResponse, error) {
|
|
|
|
communityResponse := CommunityResponse{
|
|
|
|
RequestsToJoin: make([]*RequestToJoin, 0),
|
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
if !(community.IsControlNode() || community.HasPermissionToSendCommunityEvents()) {
|
|
|
|
// we're a normal user/member node, so there's nothing for us to do here
|
2023-08-18 19:52:13 +00:00
|
|
|
return &communityResponse, nil
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
if community.config.EventsData == nil {
|
|
|
|
return &communityResponse, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
handledMembers := map[string]struct{}{}
|
|
|
|
|
|
|
|
for i := len(community.config.EventsData.Events) - 1; i >= 0; i-- {
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
communityEvent := &community.config.EventsData.Events[i]
|
2024-02-19 09:52:22 +00:00
|
|
|
if _, handled := handledMembers[communityEvent.MemberToAction]; handled {
|
|
|
|
continue
|
|
|
|
}
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
switch communityEvent.Type {
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT:
|
2024-02-19 09:52:22 +00:00
|
|
|
handledMembers[communityEvent.MemberToAction] = struct{}{}
|
2023-08-18 19:52:13 +00:00
|
|
|
requestsToJoin, err := m.handleCommunityEventRequestAccepted(community, communityEvent)
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
if err != nil {
|
2023-08-18 19:52:13 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if requestsToJoin != nil {
|
|
|
|
communityResponse.RequestsToJoin = append(communityResponse.RequestsToJoin, requestsToJoin...)
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_REJECT:
|
2024-02-19 09:52:22 +00:00
|
|
|
handledMembers[communityEvent.MemberToAction] = struct{}{}
|
2023-08-18 19:52:13 +00:00
|
|
|
requestsToJoin, err := m.handleCommunityEventRequestRejected(community, communityEvent)
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
if err != nil {
|
2023-08-18 19:52:13 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if requestsToJoin != nil {
|
|
|
|
communityResponse.RequestsToJoin = append(communityResponse.RequestsToJoin, requestsToJoin...)
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
}
|
2023-08-18 19:52:13 +00:00
|
|
|
return &communityResponse, nil
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
2023-09-20 08:37:46 +00:00
|
|
|
func (m *Manager) saveOrUpdateRequestToJoin(communityID types.HexBytes, requestToJoin *RequestToJoin) (bool, error) {
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
updated := false
|
|
|
|
|
|
|
|
existingRequestToJoin, err := m.persistence.GetRequestToJoin(requestToJoin.ID)
|
|
|
|
if err != nil && err != sql.ErrNoRows {
|
|
|
|
return updated, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if existingRequestToJoin != nil {
|
|
|
|
// node already knows about this request to join, so let's compare clocks
|
|
|
|
// and update it if necessary
|
|
|
|
if existingRequestToJoin.Clock <= requestToJoin.Clock {
|
|
|
|
pk, err := common.HexToPubkey(existingRequestToJoin.PublicKey)
|
|
|
|
if err != nil {
|
|
|
|
return updated, err
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
err = m.persistence.SetRequestToJoinState(common.PubkeyToHex(pk), communityID, requestToJoin.State)
|
2023-06-14 14:15:46 +00:00
|
|
|
if err != nil {
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
return updated, err
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
updated = true
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
err := m.persistence.SaveRequestToJoin(requestToJoin)
|
|
|
|
if err != nil {
|
|
|
|
return updated, err
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
}
|
2023-09-20 08:37:46 +00:00
|
|
|
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
return updated, nil
|
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
2023-08-18 19:52:13 +00:00
|
|
|
func (m *Manager) handleCommunityEventRequestAccepted(community *Community, communityEvent *CommunityEvent) ([]*RequestToJoin, error) {
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
acceptedRequestsToJoin := make([]types.HexBytes, 0)
|
|
|
|
|
2023-08-18 19:52:13 +00:00
|
|
|
requestsToJoin := make([]*RequestToJoin, 0)
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
signer := communityEvent.MemberToAction
|
|
|
|
request := communityEvent.RequestToJoin
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
requestToJoin := &RequestToJoin{
|
2024-04-03 14:49:57 +00:00
|
|
|
PublicKey: signer,
|
|
|
|
Clock: request.Clock,
|
|
|
|
ENSName: request.EnsName,
|
|
|
|
CommunityID: request.CommunityId,
|
|
|
|
State: RequestToJoinStateAcceptedPending,
|
|
|
|
CustomizationColor: multiaccountscommon.IDToColorFallbackToBlue(request.CustomizationColor),
|
2024-02-19 09:52:22 +00:00
|
|
|
}
|
|
|
|
requestToJoin.CalculateID()
|
2023-08-15 15:27:01 +00:00
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
existingRequestToJoin, err := m.persistence.GetRequestToJoin(requestToJoin.ID)
|
|
|
|
if err != nil && err != sql.ErrNoRows {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
if existingRequestToJoin != nil {
|
|
|
|
alreadyProcessedByControlNode := existingRequestToJoin.State == RequestToJoinStateAccepted
|
|
|
|
if alreadyProcessedByControlNode || existingRequestToJoin.State == RequestToJoinStateCanceled {
|
|
|
|
return requestsToJoin, nil
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
}
|
2024-02-19 09:52:22 +00:00
|
|
|
}
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
requestUpdated, err := m.saveOrUpdateRequestToJoin(community.ID(), requestToJoin)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-08-18 19:52:13 +00:00
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
// If request to join exists in control node, add request to acceptedRequestsToJoin.
|
|
|
|
// Otherwise keep the request as RequestToJoinStateAcceptedPending,
|
|
|
|
// as privileged users don't have revealed addresses. This can happen if control node received
|
|
|
|
// community event message before user request to join.
|
|
|
|
if community.IsControlNode() && requestUpdated {
|
|
|
|
acceptedRequestsToJoin = append(acceptedRequestsToJoin, requestToJoin.ID)
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
}
|
2024-02-19 09:52:22 +00:00
|
|
|
|
|
|
|
requestsToJoin = append(requestsToJoin, requestToJoin)
|
|
|
|
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
if community.IsControlNode() {
|
|
|
|
m.publish(&Subscription{AcceptedRequestsToJoin: acceptedRequestsToJoin})
|
|
|
|
}
|
2023-08-18 19:52:13 +00:00
|
|
|
return requestsToJoin, nil
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
}
|
|
|
|
|
2023-08-18 19:52:13 +00:00
|
|
|
func (m *Manager) handleCommunityEventRequestRejected(community *Community, communityEvent *CommunityEvent) ([]*RequestToJoin, error) {
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
rejectedRequestsToJoin := make([]types.HexBytes, 0)
|
|
|
|
|
2023-08-18 19:52:13 +00:00
|
|
|
requestsToJoin := make([]*RequestToJoin, 0)
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
signer := communityEvent.MemberToAction
|
|
|
|
request := communityEvent.RequestToJoin
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
requestToJoin := &RequestToJoin{
|
2024-04-03 14:49:57 +00:00
|
|
|
PublicKey: signer,
|
|
|
|
Clock: request.Clock,
|
|
|
|
ENSName: request.EnsName,
|
|
|
|
CommunityID: request.CommunityId,
|
|
|
|
State: RequestToJoinStateDeclinedPending,
|
|
|
|
CustomizationColor: multiaccountscommon.IDToColorFallbackToBlue(request.CustomizationColor),
|
2024-02-19 09:52:22 +00:00
|
|
|
}
|
|
|
|
requestToJoin.CalculateID()
|
2023-08-15 15:27:01 +00:00
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
existingRequestToJoin, err := m.persistence.GetRequestToJoin(requestToJoin.ID)
|
|
|
|
if err != nil && err != sql.ErrNoRows {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
if existingRequestToJoin != nil {
|
|
|
|
alreadyProcessedByControlNode := existingRequestToJoin.State == RequestToJoinStateDeclined
|
|
|
|
if alreadyProcessedByControlNode || existingRequestToJoin.State == RequestToJoinStateCanceled {
|
|
|
|
return requestsToJoin, nil
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
2024-02-19 09:52:22 +00:00
|
|
|
}
|
2023-08-18 19:52:13 +00:00
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
requestUpdated, err := m.saveOrUpdateRequestToJoin(community.ID(), requestToJoin)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
2024-02-19 09:52:22 +00:00
|
|
|
// If request to join exists in control node, add request to rejectedRequestsToJoin.
|
|
|
|
// Otherwise keep the request as RequestToJoinStateDeclinedPending,
|
|
|
|
// as privileged users don't have revealed addresses. This can happen if control node received
|
|
|
|
// community event message before user request to join.
|
|
|
|
if community.IsControlNode() && requestUpdated {
|
|
|
|
rejectedRequestsToJoin = append(rejectedRequestsToJoin, requestToJoin.ID)
|
|
|
|
}
|
|
|
|
|
|
|
|
requestsToJoin = append(requestsToJoin, requestToJoin)
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
|
|
|
|
if community.IsControlNode() {
|
|
|
|
m.publish(&Subscription{RejectedRequestsToJoin: rejectedRequestsToJoin})
|
|
|
|
}
|
2023-08-18 19:52:13 +00:00
|
|
|
return requestsToJoin, nil
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2023-10-04 19:02:17 +00:00
|
|
|
// markRequestToJoinAsAccepted marks all the pending requests to join as completed
|
2021-01-11 10:32:51 +00:00
|
|
|
// if we are members
|
2023-10-04 19:02:17 +00:00
|
|
|
func (m *Manager) markRequestToJoinAsAccepted(pk *ecdsa.PublicKey, community *Community) error {
|
2021-01-11 10:32:51 +00:00
|
|
|
if community.HasMember(pk) {
|
|
|
|
return m.persistence.SetRequestToJoinState(common.PubkeyToHex(pk), community.ID(), RequestToJoinStateAccepted)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-10-28 08:41:20 +00:00
|
|
|
func (m *Manager) markRequestToJoinAsCanceled(pk *ecdsa.PublicKey, community *Community) error {
|
|
|
|
return m.persistence.SetRequestToJoinState(common.PubkeyToHex(pk), community.ID(), RequestToJoinStateCanceled)
|
|
|
|
}
|
|
|
|
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
func (m *Manager) markRequestToJoinAsAcceptedPending(pk *ecdsa.PublicKey, community *Community) error {
|
|
|
|
return m.persistence.SetRequestToJoinState(common.PubkeyToHex(pk), community.ID(), RequestToJoinStateAcceptedPending)
|
|
|
|
}
|
|
|
|
|
2023-04-21 09:18:47 +00:00
|
|
|
func (m *Manager) DeletePendingRequestToJoin(request *RequestToJoin) error {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(request.CommunityID)
|
|
|
|
defer m.communityLock.Unlock(request.CommunityID)
|
|
|
|
|
2023-04-21 09:18:47 +00:00
|
|
|
community, err := m.GetByID(request.CommunityID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.persistence.DeletePendingRequestToJoin(request.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-08-07 15:57:02 +00:00
|
|
|
if community.IsControlNode() {
|
|
|
|
err = m.saveAndPublish(community)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-04-21 09:18:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) UpdateClockInRequestToJoin(id types.HexBytes, clock uint64) error {
|
|
|
|
return m.persistence.UpdateClockInRequestToJoin(id, clock)
|
|
|
|
}
|
|
|
|
|
2023-07-19 12:14:42 +00:00
|
|
|
func (m *Manager) SetMuted(id types.HexBytes, muted bool) error {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(id)
|
|
|
|
defer m.communityLock.Unlock(id)
|
|
|
|
|
2023-07-19 12:14:42 +00:00
|
|
|
return m.persistence.SetMuted(id, muted)
|
2021-06-30 13:29:43 +00:00
|
|
|
}
|
|
|
|
|
2023-07-19 12:14:42 +00:00
|
|
|
func (m *Manager) MuteCommunityTill(communityID []byte, muteTill time.Time) error {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(communityID)
|
|
|
|
defer m.communityLock.Unlock(communityID)
|
|
|
|
|
2023-07-19 12:14:42 +00:00
|
|
|
return m.persistence.MuteCommunityTill(communityID, muteTill)
|
|
|
|
}
|
2022-10-28 08:41:20 +00:00
|
|
|
func (m *Manager) CancelRequestToJoin(request *requests.CancelRequestToJoinCommunity) (*RequestToJoin, *Community, error) {
|
|
|
|
dbRequest, err := m.persistence.GetRequestToJoin(request.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
community, err := m.GetByID(dbRequest.CommunityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
pk, err := common.HexToPubkey(dbRequest.PublicKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2023-02-07 18:22:49 +00:00
|
|
|
dbRequest.State = RequestToJoinStateCanceled
|
2022-10-28 08:41:20 +00:00
|
|
|
if err := m.markRequestToJoinAsCanceled(pk, community); err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return dbRequest, community, nil
|
|
|
|
}
|
|
|
|
|
2023-04-25 12:00:17 +00:00
|
|
|
func (m *Manager) CheckPermissionToJoin(id []byte, addresses []gethcommon.Address) (*CheckPermissionToJoinResponse, error) {
|
|
|
|
community, err := m.GetByID(id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-10-12 15:45:23 +00:00
|
|
|
return m.PermissionChecker.CheckPermissionToJoin(community, addresses)
|
2023-04-25 12:00:17 +00:00
|
|
|
}
|
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
func (m *Manager) accountsSatisfyPermissionsToJoin(
|
|
|
|
communityPermissionsPreParsedData map[protobuf.CommunityTokenPermission_Type]*PreParsedCommunityPermissionsData,
|
|
|
|
accountsAndChainIDs []*AccountChainIDsCombination) (bool, protobuf.CommunityMember_Roles, error) {
|
2023-07-20 20:33:47 +00:00
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
if m.accountsHasPrivilegedPermission(communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER], accountsAndChainIDs) {
|
2023-08-04 10:28:46 +00:00
|
|
|
return true, protobuf.CommunityMember_ROLE_TOKEN_MASTER, nil
|
|
|
|
}
|
2024-05-17 16:15:39 +00:00
|
|
|
if m.accountsHasPrivilegedPermission(communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_ADMIN], accountsAndChainIDs) {
|
2023-08-04 10:28:46 +00:00
|
|
|
return true, protobuf.CommunityMember_ROLE_ADMIN, nil
|
2023-07-20 20:33:47 +00:00
|
|
|
}
|
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
preParsedBecomeMemberPermissions := communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_MEMBER]
|
|
|
|
if preParsedBecomeMemberPermissions != nil {
|
|
|
|
permissionResponse, err := m.PermissionChecker.CheckPermissions(preParsedBecomeMemberPermissions, accountsAndChainIDs, true)
|
2023-07-20 20:33:47 +00:00
|
|
|
if err != nil {
|
2023-08-04 10:28:46 +00:00
|
|
|
return false, protobuf.CommunityMember_ROLE_NONE, err
|
2023-07-20 20:33:47 +00:00
|
|
|
}
|
|
|
|
|
2023-08-04 10:28:46 +00:00
|
|
|
return permissionResponse.Satisfied, protobuf.CommunityMember_ROLE_NONE, nil
|
2023-07-20 20:33:47 +00:00
|
|
|
}
|
|
|
|
|
2023-08-04 10:28:46 +00:00
|
|
|
return true, protobuf.CommunityMember_ROLE_NONE, nil
|
2023-07-20 20:33:47 +00:00
|
|
|
}
|
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
func (m *Manager) accountsSatisfyPermissionsToJoinChannels(
|
|
|
|
community *Community,
|
|
|
|
channelPermissionsPreParsedData map[string]*PreParsedCommunityPermissionsData,
|
|
|
|
accountsAndChainIDs []*AccountChainIDsCombination) (map[string]*protobuf.CommunityChat, map[string]*protobuf.CommunityChat, error) {
|
|
|
|
|
2024-03-09 21:36:40 +00:00
|
|
|
viewChats := make(map[string]*protobuf.CommunityChat)
|
|
|
|
viewAndPostChats := make(map[string]*protobuf.CommunityChat)
|
2023-06-23 10:49:26 +00:00
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
if len(channelPermissionsPreParsedData) == 0 {
|
|
|
|
for channelID, channel := range community.config.CommunityDescription.Chats {
|
|
|
|
viewAndPostChats[channelID] = channel
|
|
|
|
}
|
|
|
|
|
|
|
|
return viewChats, viewAndPostChats, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// check which permissions we satisfy and which not
|
|
|
|
channelPermissionsCheckResult, err := m.checkChannelsPermissions(channelPermissionsPreParsedData, accountsAndChainIDs, true)
|
|
|
|
if err != nil {
|
|
|
|
m.logger.Warn("check channel permission failed: %v", zap.Error(err))
|
|
|
|
return viewChats, viewAndPostChats, err
|
|
|
|
}
|
2024-03-01 17:15:38 +00:00
|
|
|
|
2024-03-09 21:36:40 +00:00
|
|
|
for channelID, channel := range community.config.CommunityDescription.Chats {
|
2024-05-17 16:15:39 +00:00
|
|
|
chatID := community.ChatID(channelID)
|
|
|
|
channelPermissionsCheckResult, exists := channelPermissionsCheckResult[chatID]
|
2024-03-01 17:15:38 +00:00
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
if !exists {
|
2024-03-09 21:36:40 +00:00
|
|
|
viewAndPostChats[channelID] = channel
|
|
|
|
continue
|
|
|
|
}
|
2024-03-01 17:15:38 +00:00
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
viewAndPostSatisfied, exists := channelPermissionsCheckResult[protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL]
|
|
|
|
if exists && viewAndPostSatisfied {
|
|
|
|
delete(viewChats, channelID)
|
|
|
|
viewAndPostChats[channelID] = channel
|
|
|
|
continue
|
2024-03-09 21:36:40 +00:00
|
|
|
}
|
2023-06-23 10:49:26 +00:00
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
viewOnlySatisfied, exists := channelPermissionsCheckResult[protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL]
|
|
|
|
if exists && viewOnlySatisfied {
|
|
|
|
if _, exists := viewAndPostChats[channelID]; !exists {
|
2024-03-09 21:36:40 +00:00
|
|
|
viewChats[channelID] = channel
|
2023-06-23 10:49:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-09 21:36:40 +00:00
|
|
|
return viewChats, viewAndPostChats, nil
|
2023-06-23 10:49:26 +00:00
|
|
|
}
|
|
|
|
|
2023-08-15 15:27:01 +00:00
|
|
|
func (m *Manager) AcceptRequestToJoin(dbRequest *RequestToJoin) (*Community, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(dbRequest.CommunityID)
|
|
|
|
defer m.communityLock.Unlock(dbRequest.CommunityID)
|
|
|
|
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
pk, err := common.HexToPubkey(dbRequest.PublicKey)
|
2021-01-11 10:32:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
community, err := m.GetByID(dbRequest.CommunityID)
|
2023-06-16 07:10:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
Check token funds when handling community requests to join
This adds checks to `HandleCommunityRequestToJoin` and
`AcceptRequestToJoinCommunity` that ensure a given user's revealed
wallet addresses own the token funds required by a community.
When community has token permissions of type `BECOME_MEMBER`, the
following happens when the owner receives a request:
1. Upon verifying provided wallet addresses by the requester, the owner
node accumulates all token funds related to the given wallets that
match the token criteria in the configured permissions
2. If the requester does not meet the necessary requirements, the
request to join will be declined. If the requester does have the
funds, he'll either be automatically accepted to the community, or
enters the next stage where an owner needs to manually accept the
request.
3. The the community does not automatically accept users, then the funds
check will happen again, when the owner tries to manually accept the
request. If the necessary funds do not exist at this stage, the
request will be declined
4. Upon accepting, whether automatically or manually, the owner adds the
requester's wallet addresses to the `CommunityDescription`, such that
they can be retrieved later when doing periodic checks or when
permissions have changed.
2023-03-16 14:35:33 +00:00
|
|
|
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
if community.IsControlNode() {
|
|
|
|
revealedAccounts, err := m.persistence.GetRequestToJoinRevealedAddresses(dbRequest.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-06-23 10:49:26 +00:00
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
accountsAndChainIDs := revealedAccountsToAccountsAndChainIDsCombination(revealedAccounts)
|
|
|
|
|
|
|
|
communityPermissionsPreParsedData, channelPermissionsPreParsedData := PreParsePermissionsData(community.tokenPermissions())
|
|
|
|
|
|
|
|
permissionsSatisfied, role, err := m.accountsSatisfyPermissionsToJoin(communityPermissionsPreParsedData, accountsAndChainIDs)
|
2023-06-23 10:49:26 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
if !permissionsSatisfied {
|
|
|
|
return community, ErrNoPermissionToJoin
|
|
|
|
}
|
|
|
|
|
|
|
|
memberRoles := []protobuf.CommunityMember_Roles{}
|
|
|
|
if role != protobuf.CommunityMember_ROLE_NONE {
|
|
|
|
memberRoles = []protobuf.CommunityMember_Roles{role}
|
|
|
|
}
|
|
|
|
|
2024-07-05 13:22:22 +00:00
|
|
|
_, err = community.AddMember(pk, memberRoles, dbRequest.Clock)
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
viewChannels, postChannels, err := m.accountsSatisfyPermissionsToJoinChannels(community, channelPermissionsPreParsedData, accountsAndChainIDs)
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-03-01 17:15:38 +00:00
|
|
|
for channelID := range viewChannels {
|
|
|
|
_, err = community.AddMemberToChat(channelID, pk, memberRoles, protobuf.CommunityMember_CHANNEL_ROLE_VIEWER)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for channelID := range postChannels {
|
|
|
|
_, err = community.AddMemberToChat(channelID, pk, memberRoles, protobuf.CommunityMember_CHANNEL_ROLE_POSTER)
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-11 19:55:52 +00:00
|
|
|
dbRequest.State = RequestToJoinStateAccepted
|
2023-10-04 19:02:17 +00:00
|
|
|
if err := m.markRequestToJoinAsAccepted(pk, community); err != nil {
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2022-07-01 13:54:02 +00:00
|
|
|
|
2023-12-07 16:27:14 +00:00
|
|
|
dbRequest.RevealedAccounts = revealedAccounts
|
2023-09-20 08:37:46 +00:00
|
|
|
if err = m.shareAcceptedRequestToJoinWithPrivilegedMembers(community, dbRequest); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// if accepted member has a privilege role, share with him requests to join
|
|
|
|
memberRole := community.MemberRole(pk)
|
|
|
|
if memberRole == protobuf.CommunityMember_ROLE_OWNER || memberRole == protobuf.CommunityMember_ROLE_ADMIN ||
|
|
|
|
memberRole == protobuf.CommunityMember_ROLE_TOKEN_MASTER {
|
|
|
|
|
|
|
|
newPrivilegedMember := make(map[protobuf.CommunityMember_Roles][]*ecdsa.PublicKey)
|
|
|
|
newPrivilegedMember[memberRole] = []*ecdsa.PublicKey{pk}
|
2024-07-05 13:43:03 +00:00
|
|
|
if err = m.ShareRequestsToJoinWithPrivilegedMembers(community, newPrivilegedMember); err != nil {
|
2023-09-20 08:37:46 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2023-10-04 19:02:17 +00:00
|
|
|
} else if community.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT) {
|
2024-02-19 09:52:22 +00:00
|
|
|
err := community.addNewCommunityEvent(community.ToCommunityRequestToJoinAcceptCommunityEvent(dbRequest.PublicKey, dbRequest.ToCommunityRequestToJoinProtobuf()))
|
2023-10-04 19:02:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-10-11 19:55:52 +00:00
|
|
|
dbRequest.State = RequestToJoinStateAcceptedPending
|
2023-10-04 19:02:17 +00:00
|
|
|
if err := m.markRequestToJoinAsAcceptedPending(pk, community); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return nil, ErrNotAuthorized
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.saveAndPublish(community)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-09-20 08:37:46 +00:00
|
|
|
}
|
|
|
|
|
2022-07-01 13:54:02 +00:00
|
|
|
return community, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) GetRequestToJoin(ID types.HexBytes) (*RequestToJoin, error) {
|
|
|
|
return m.persistence.GetRequestToJoin(ID)
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
|
2023-08-15 15:27:01 +00:00
|
|
|
func (m *Manager) DeclineRequestToJoin(dbRequest *RequestToJoin) (*Community, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(dbRequest.CommunityID)
|
|
|
|
defer m.communityLock.Unlock(dbRequest.CommunityID)
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
community, err := m.GetByID(dbRequest.CommunityID)
|
|
|
|
if err != nil {
|
2023-08-15 15:27:01 +00:00
|
|
|
return nil, err
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2023-10-04 19:02:17 +00:00
|
|
|
adminEventCreated, err := community.DeclineRequestToJoin(dbRequest)
|
2023-06-14 14:15:46 +00:00
|
|
|
if err != nil {
|
2023-08-15 15:27:01 +00:00
|
|
|
return nil, err
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2023-10-04 19:02:17 +00:00
|
|
|
requestToJoinState := RequestToJoinStateDeclined
|
|
|
|
if adminEventCreated {
|
|
|
|
requestToJoinState = RequestToJoinStateDeclinedPending // can only be declined by control node
|
|
|
|
}
|
|
|
|
|
2023-10-11 19:55:52 +00:00
|
|
|
dbRequest.State = requestToJoinState
|
2023-10-04 19:02:17 +00:00
|
|
|
err = m.persistence.SetRequestToJoinState(dbRequest.PublicKey, dbRequest.CommunityID, requestToJoinState)
|
2023-07-18 15:06:12 +00:00
|
|
|
if err != nil {
|
2023-08-15 15:27:01 +00:00
|
|
|
return nil, err
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
err = m.saveAndPublish(community)
|
|
|
|
if err != nil {
|
2023-08-15 15:27:01 +00:00
|
|
|
return nil, err
|
2023-06-14 14:15:46 +00:00
|
|
|
}
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2023-08-15 15:27:01 +00:00
|
|
|
return community, nil
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
|
2023-10-12 21:42:03 +00:00
|
|
|
func (m *Manager) shouldUserRetainDeclined(signer *ecdsa.PublicKey, community *Community, requestClock uint64) (bool, error) {
|
|
|
|
requestID := CalculateRequestID(common.PubkeyToHex(signer), types.HexBytes(community.IDString()))
|
|
|
|
request, err := m.persistence.GetRequestToJoin(requestID)
|
2023-03-23 18:48:46 +00:00
|
|
|
if err != nil {
|
2023-10-12 21:42:03 +00:00
|
|
|
if err == sql.ErrNoRows {
|
|
|
|
return false, nil
|
|
|
|
}
|
2023-03-23 18:48:46 +00:00
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2023-10-12 21:42:03 +00:00
|
|
|
return request.ShouldRetainDeclined(requestClock)
|
2023-03-23 18:48:46 +00:00
|
|
|
}
|
|
|
|
|
2022-10-28 08:41:20 +00:00
|
|
|
func (m *Manager) HandleCommunityCancelRequestToJoin(signer *ecdsa.PublicKey, request *protobuf.CommunityCancelRequestToJoin) (*RequestToJoin, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(request.CommunityId)
|
|
|
|
defer m.communityLock.Unlock(request.CommunityId)
|
|
|
|
|
2023-09-25 11:26:17 +00:00
|
|
|
community, err := m.GetByID(request.CommunityId)
|
2022-10-28 08:41:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-12-09 12:46:30 +00:00
|
|
|
previousRequestToJoin, err := m.GetRequestToJoinByPkAndCommunityID(signer, community.ID())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if request.Clock <= previousRequestToJoin.Clock {
|
|
|
|
return nil, ErrInvalidClock
|
|
|
|
}
|
|
|
|
|
2023-10-12 21:42:03 +00:00
|
|
|
retainDeclined, err := m.shouldUserRetainDeclined(signer, community, request.Clock)
|
2023-03-23 18:48:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-10-12 21:42:03 +00:00
|
|
|
if retainDeclined {
|
2023-03-23 18:48:46 +00:00
|
|
|
return nil, ErrCommunityRequestAlreadyRejected
|
|
|
|
}
|
|
|
|
|
2022-10-28 08:41:20 +00:00
|
|
|
err = m.markRequestToJoinAsCanceled(signer, community)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
requestToJoin, err := m.persistence.GetRequestToJoinByPk(common.PubkeyToHex(signer), community.ID(), RequestToJoinStateCanceled)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-12-09 12:46:30 +00:00
|
|
|
if community.HasMember(signer) {
|
|
|
|
_, err = community.RemoveUserFromOrg(signer)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.saveAndPublish(community)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-28 08:41:20 +00:00
|
|
|
return requestToJoin, nil
|
|
|
|
}
|
|
|
|
|
2023-10-12 21:42:03 +00:00
|
|
|
func (m *Manager) HandleCommunityRequestToJoin(signer *ecdsa.PublicKey, receiver *ecdsa.PublicKey, request *protobuf.CommunityRequestToJoin) (*Community, *RequestToJoin, error) {
|
2023-09-21 11:16:05 +00:00
|
|
|
community, err := m.GetByID(request.CommunityId)
|
2021-01-11 10:32:51 +00:00
|
|
|
if err != nil {
|
2023-10-12 21:42:03 +00:00
|
|
|
return nil, nil, err
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
2023-09-20 08:37:46 +00:00
|
|
|
|
2023-10-12 21:42:03 +00:00
|
|
|
err = community.ValidateRequestToJoin(signer, request)
|
2023-03-23 18:48:46 +00:00
|
|
|
if err != nil {
|
2023-10-12 21:42:03 +00:00
|
|
|
return nil, nil, err
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
|
2024-05-01 17:27:31 +00:00
|
|
|
nbPendingRequestsToJoin, err := m.persistence.GetNumberOfPendingRequestsToJoin(community.ID())
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
if nbPendingRequestsToJoin >= maxNbPendingRequestedMembers {
|
|
|
|
return nil, nil, errors.New("max number of requests to join reached")
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
requestToJoin := &RequestToJoin{
|
2024-04-03 14:49:57 +00:00
|
|
|
PublicKey: common.PubkeyToHex(signer),
|
|
|
|
Clock: request.Clock,
|
|
|
|
ENSName: request.EnsName,
|
|
|
|
CommunityID: request.CommunityId,
|
|
|
|
State: RequestToJoinStatePending,
|
|
|
|
RevealedAccounts: request.RevealedAccounts,
|
|
|
|
CustomizationColor: multiaccountscommon.IDToColorFallbackToBlue(request.CustomizationColor),
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
requestToJoin.CalculateID()
|
|
|
|
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
existingRequestToJoin, err := m.persistence.GetRequestToJoin(requestToJoin.ID)
|
|
|
|
if err != nil && err != sql.ErrNoRows {
|
2023-10-12 21:42:03 +00:00
|
|
|
return nil, nil, err
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
|
2023-10-12 21:42:03 +00:00
|
|
|
if existingRequestToJoin == nil {
|
|
|
|
err = m.SaveRequestToJoin(requestToJoin)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
retainDeclined, err := existingRequestToJoin.ShouldRetainDeclined(request.Clock)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
if retainDeclined {
|
|
|
|
return nil, nil, ErrCommunityRequestAlreadyRejected
|
|
|
|
}
|
|
|
|
|
|
|
|
switch existingRequestToJoin.State {
|
|
|
|
case RequestToJoinStatePending, RequestToJoinStateDeclined, RequestToJoinStateCanceled:
|
|
|
|
// Another request have been received, save request back to pending state
|
|
|
|
err = m.SaveRequestToJoin(requestToJoin)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2023-12-19 13:45:34 +00:00
|
|
|
case RequestToJoinStateAccepted:
|
|
|
|
// if member leaved the community and tries to request to join again
|
|
|
|
if !community.HasMember(signer) {
|
|
|
|
err = m.SaveRequestToJoin(requestToJoin)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
}
|
2023-10-04 19:02:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if community.IsControlNode() {
|
2023-10-12 21:42:03 +00:00
|
|
|
// verify if revealed addresses indeed belong to requester
|
|
|
|
for _, revealedAccount := range request.RevealedAccounts {
|
|
|
|
recoverParams := account.RecoverParams{
|
|
|
|
Message: types.EncodeHex(crypto.Keccak256(crypto.CompressPubkey(signer), community.ID(), requestToJoin.ID)),
|
|
|
|
Signature: types.EncodeHex(revealedAccount.Signature),
|
2023-06-19 10:33:48 +00:00
|
|
|
}
|
|
|
|
|
2023-10-12 21:42:03 +00:00
|
|
|
matching, err := m.accountsManager.CanRecover(recoverParams, types.HexToAddress(revealedAccount.Address))
|
2023-06-19 10:33:48 +00:00
|
|
|
if err != nil {
|
2023-10-12 21:42:03 +00:00
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
if !matching {
|
|
|
|
// if ownership of only one wallet address cannot be verified,
|
|
|
|
// we mark the request as cancelled and stop
|
|
|
|
requestToJoin.State = RequestToJoinStateDeclined
|
|
|
|
return community, requestToJoin, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save revealed addresses + signatures so they can later be added
|
|
|
|
// to the control node's local table of known revealed addresses
|
|
|
|
err = m.persistence.SaveRequestToJoinRevealedAddresses(requestToJoin.ID, requestToJoin.RevealedAccounts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if existingRequestToJoin != nil {
|
|
|
|
// request to join was already processed by privileged user
|
|
|
|
// and waits to get confirmation for its decision
|
|
|
|
if existingRequestToJoin.State == RequestToJoinStateDeclinedPending {
|
|
|
|
requestToJoin.State = RequestToJoinStateDeclined
|
|
|
|
return community, requestToJoin, nil
|
|
|
|
} else if existingRequestToJoin.State == RequestToJoinStateAcceptedPending {
|
|
|
|
requestToJoin.State = RequestToJoinStateAccepted
|
|
|
|
return community, requestToJoin, nil
|
2023-10-31 14:20:40 +00:00
|
|
|
|
|
|
|
} else if existingRequestToJoin.State == RequestToJoinStateAwaitingAddresses {
|
|
|
|
// community ownership changed, accept request automatically
|
|
|
|
requestToJoin.State = RequestToJoinStateAccepted
|
|
|
|
return community, requestToJoin, nil
|
2023-06-19 10:33:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-01 17:27:31 +00:00
|
|
|
// Check if we reached the limit, if we did, change the community setting to be On Request
|
|
|
|
if community.AutoAccept() && community.MembersCount() >= maxNbMembers {
|
|
|
|
community.EditPermissionAccess(protobuf.CommunityPermissions_MANUAL_ACCEPT)
|
|
|
|
err = m.saveAndPublish(community)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-04 19:02:17 +00:00
|
|
|
// If user is already a member, then accept request automatically
|
|
|
|
// It may happen when member removes itself from community and then tries to rejoin
|
|
|
|
// More specifically, CommunityRequestToLeave may be delivered later than CommunityRequestToJoin, or not delivered at all
|
2023-10-25 13:16:49 +00:00
|
|
|
acceptAutomatically := community.AutoAccept() || community.HasMember(signer)
|
2023-10-12 21:42:03 +00:00
|
|
|
if acceptAutomatically {
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
// Don't check permissions here,
|
|
|
|
// it will be done further in the processing pipeline.
|
|
|
|
requestToJoin.State = RequestToJoinStateAccepted
|
2023-10-12 21:42:03 +00:00
|
|
|
return community, requestToJoin, nil
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
}
|
2023-06-16 07:10:32 +00:00
|
|
|
}
|
|
|
|
|
2023-10-12 21:42:03 +00:00
|
|
|
return community, requestToJoin, nil
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
|
2023-08-18 11:39:59 +00:00
|
|
|
func (m *Manager) HandleCommunityEditSharedAddresses(signer *ecdsa.PublicKey, request *protobuf.CommunityEditSharedAddresses) error {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(request.CommunityId)
|
|
|
|
defer m.communityLock.Unlock(request.CommunityId)
|
|
|
|
|
2023-09-25 11:26:17 +00:00
|
|
|
community, err := m.GetByID(request.CommunityId)
|
2023-07-10 14:11:37 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-01-09 21:47:37 +00:00
|
|
|
|
2024-07-05 13:43:03 +00:00
|
|
|
if !community.IsControlNode() {
|
|
|
|
return ErrNotOwner
|
|
|
|
}
|
|
|
|
|
|
|
|
publicKey := common.PubkeyToHex(signer)
|
|
|
|
|
|
|
|
if err := community.ValidateEditSharedAddresses(publicKey, request); err != nil {
|
2023-07-10 14:11:37 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-07-05 13:43:03 +00:00
|
|
|
community.UpdateMemberLastUpdateClock(publicKey, request.Clock)
|
2023-07-10 14:11:37 +00:00
|
|
|
// verify if revealed addresses indeed belong to requester
|
|
|
|
for _, revealedAccount := range request.RevealedAccounts {
|
|
|
|
recoverParams := account.RecoverParams{
|
|
|
|
Message: types.EncodeHex(crypto.Keccak256(crypto.CompressPubkey(signer), community.ID())),
|
|
|
|
Signature: types.EncodeHex(revealedAccount.Signature),
|
|
|
|
}
|
|
|
|
|
|
|
|
matching, err := m.accountsManager.CanRecover(recoverParams, types.HexToAddress(revealedAccount.Address))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !matching {
|
|
|
|
// if ownership of only one wallet address cannot be verified we stop
|
|
|
|
return errors.New("wrong wallet address used")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-05 13:43:03 +00:00
|
|
|
err = m.handleCommunityEditSharedAddresses(publicKey, request.CommunityId, request.RevealedAccounts, request.Clock)
|
2023-07-10 14:11:37 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.persistence.SaveCommunity(community)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if community.IsControlNode() {
|
2023-07-10 14:11:37 +00:00
|
|
|
m.publish(&Subscription{Community: community})
|
|
|
|
}
|
|
|
|
|
2024-07-05 13:43:03 +00:00
|
|
|
subscriptionMsg := &CommunityPrivilegedMemberSyncMessage{
|
2024-07-19 10:34:34 +00:00
|
|
|
Receivers: community.GetTokenMasterMembers(),
|
2024-07-05 13:43:03 +00:00
|
|
|
CommunityPrivilegedUserSyncMessage: &protobuf.CommunityPrivilegedUserSyncMessage{
|
|
|
|
Type: protobuf.CommunityPrivilegedUserSyncMessage_CONTROL_NODE_MEMBER_EDIT_SHARED_ADDRESSES,
|
|
|
|
CommunityId: community.ID(),
|
|
|
|
SyncEditSharedAddresses: &protobuf.SyncCommunityEditSharedAddresses{
|
|
|
|
PublicKey: common.PubkeyToHex(signer),
|
|
|
|
EditSharedAddress: request,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
m.publish(&Subscription{CommunityPrivilegedMemberSyncMessage: subscriptionMsg})
|
|
|
|
|
2023-07-10 14:11:37 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-07-05 13:43:03 +00:00
|
|
|
func (m *Manager) handleCommunityEditSharedAddresses(publicKey string, communityID types.HexBytes, revealedAccounts []*protobuf.RevealedAccount, clock uint64) error {
|
|
|
|
requestToJoinID := CalculateRequestID(publicKey, communityID)
|
|
|
|
err := m.UpdateClockInRequestToJoin(requestToJoinID, clock)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.persistence.RemoveRequestToJoinRevealedAddresses(requestToJoinID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return m.persistence.SaveRequestToJoinRevealedAddresses(requestToJoinID, revealedAccounts)
|
|
|
|
}
|
|
|
|
|
2023-06-06 18:33:09 +00:00
|
|
|
func calculateChainIDsSet(accountsAndChainIDs []*AccountChainIDsCombination, requirementsChainIDs map[uint64]bool) []uint64 {
|
|
|
|
|
|
|
|
revealedAccountsChainIDs := make([]uint64, 0)
|
|
|
|
revealedAccountsChainIDsMap := make(map[uint64]bool)
|
|
|
|
|
|
|
|
// we want all chainIDs provided by revealed addresses that also exist
|
|
|
|
// in the token requirements
|
|
|
|
for _, accountAndChainIDs := range accountsAndChainIDs {
|
|
|
|
for _, chainID := range accountAndChainIDs.ChainIDs {
|
|
|
|
if requirementsChainIDs[chainID] && !revealedAccountsChainIDsMap[chainID] {
|
|
|
|
revealedAccountsChainIDsMap[chainID] = true
|
|
|
|
revealedAccountsChainIDs = append(revealedAccountsChainIDs, chainID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return revealedAccountsChainIDs
|
|
|
|
}
|
|
|
|
|
2023-07-13 17:26:17 +00:00
|
|
|
type CollectiblesByChain = map[uint64]map[gethcommon.Address]thirdparty.TokenBalancesPerContractAddress
|
2023-03-27 09:35:03 +00:00
|
|
|
|
2023-06-06 18:33:09 +00:00
|
|
|
func (m *Manager) GetOwnedERC721Tokens(walletAddresses []gethcommon.Address, tokenRequirements map[uint64]map[string]*protobuf.TokenCriteria, chainIDs []uint64) (CollectiblesByChain, error) {
|
2023-07-13 17:26:17 +00:00
|
|
|
if m.collectiblesManager == nil {
|
|
|
|
return nil, errors.New("no collectibles manager")
|
2023-06-06 18:33:09 +00:00
|
|
|
}
|
2023-03-27 09:35:03 +00:00
|
|
|
|
2023-11-14 17:16:39 +00:00
|
|
|
ctx := context.Background()
|
|
|
|
|
2023-07-13 17:26:17 +00:00
|
|
|
ownedERC721Tokens := make(CollectiblesByChain)
|
2023-03-27 09:35:03 +00:00
|
|
|
|
2023-06-06 18:33:09 +00:00
|
|
|
for chainID, erc721Tokens := range tokenRequirements {
|
2023-03-27 09:35:03 +00:00
|
|
|
|
2023-06-06 18:33:09 +00:00
|
|
|
skipChain := true
|
|
|
|
for _, cID := range chainIDs {
|
|
|
|
if chainID == cID {
|
|
|
|
skipChain = false
|
2023-03-27 09:35:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-06 18:33:09 +00:00
|
|
|
if skipChain {
|
|
|
|
continue
|
|
|
|
}
|
2023-03-27 09:35:03 +00:00
|
|
|
|
|
|
|
contractAddresses := make([]gethcommon.Address, 0)
|
|
|
|
for contractAddress := range erc721Tokens {
|
|
|
|
contractAddresses = append(contractAddresses, gethcommon.HexToAddress(contractAddress))
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, exists := ownedERC721Tokens[chainID]; !exists {
|
2023-07-13 17:26:17 +00:00
|
|
|
ownedERC721Tokens[chainID] = make(map[gethcommon.Address]thirdparty.TokenBalancesPerContractAddress)
|
2023-03-27 09:35:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, owner := range walletAddresses {
|
2023-11-14 17:16:39 +00:00
|
|
|
balances, err := m.collectiblesManager.FetchBalancesByOwnerAndContractAddress(ctx, walletcommon.ChainID(chainID), owner, contractAddresses)
|
2023-03-27 09:35:03 +00:00
|
|
|
if err != nil {
|
|
|
|
m.logger.Info("couldn't fetch owner assets", zap.Error(err))
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-07-13 17:26:17 +00:00
|
|
|
ownedERC721Tokens[chainID][owner] = balances
|
Check token funds when handling community requests to join
This adds checks to `HandleCommunityRequestToJoin` and
`AcceptRequestToJoinCommunity` that ensure a given user's revealed
wallet addresses own the token funds required by a community.
When community has token permissions of type `BECOME_MEMBER`, the
following happens when the owner receives a request:
1. Upon verifying provided wallet addresses by the requester, the owner
node accumulates all token funds related to the given wallets that
match the token criteria in the configured permissions
2. If the requester does not meet the necessary requirements, the
request to join will be declined. If the requester does have the
funds, he'll either be automatically accepted to the community, or
enters the next stage where an owner needs to manually accept the
request.
3. The the community does not automatically accept users, then the funds
check will happen again, when the owner tries to manually accept the
request. If the necessary funds do not exist at this stage, the
request will be declined
4. Upon accepting, whether automatically or manually, the owner adds the
requester's wallet addresses to the `CommunityDescription`, such that
they can be retrieved later when doing periodic checks or when
permissions have changed.
2023-03-16 14:35:33 +00:00
|
|
|
}
|
|
|
|
}
|
2023-06-06 18:33:09 +00:00
|
|
|
return ownedERC721Tokens, nil
|
Check token funds when handling community requests to join
This adds checks to `HandleCommunityRequestToJoin` and
`AcceptRequestToJoinCommunity` that ensure a given user's revealed
wallet addresses own the token funds required by a community.
When community has token permissions of type `BECOME_MEMBER`, the
following happens when the owner receives a request:
1. Upon verifying provided wallet addresses by the requester, the owner
node accumulates all token funds related to the given wallets that
match the token criteria in the configured permissions
2. If the requester does not meet the necessary requirements, the
request to join will be declined. If the requester does have the
funds, he'll either be automatically accepted to the community, or
enters the next stage where an owner needs to manually accept the
request.
3. The the community does not automatically accept users, then the funds
check will happen again, when the owner tries to manually accept the
request. If the necessary funds do not exist at this stage, the
request will be declined
4. Upon accepting, whether automatically or manually, the owner adds the
requester's wallet addresses to the `CommunityDescription`, such that
they can be retrieved later when doing periodic checks or when
permissions have changed.
2023-03-16 14:35:33 +00:00
|
|
|
}
|
|
|
|
|
2023-06-12 15:17:37 +00:00
|
|
|
func (m *Manager) CheckChannelPermissions(communityID types.HexBytes, chatID string, addresses []gethcommon.Address) (*CheckChannelPermissionsResponse, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(communityID)
|
|
|
|
defer m.communityLock.Unlock(communityID)
|
|
|
|
|
2023-06-12 15:17:37 +00:00
|
|
|
community, err := m.GetByID(communityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if chatID == "" {
|
|
|
|
return nil, errors.New(fmt.Sprintf("couldn't check channel permissions, invalid chat id: %s", chatID))
|
|
|
|
}
|
|
|
|
|
|
|
|
viewOnlyPermissions := community.ChannelTokenPermissionsByType(chatID, protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL)
|
|
|
|
viewAndPostPermissions := community.ChannelTokenPermissionsByType(chatID, protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL)
|
2024-05-17 16:15:39 +00:00
|
|
|
viewOnlyPreParsedPermissions := preParsedCommunityPermissionsData(viewOnlyPermissions)
|
|
|
|
viewAndPostPreParsedPermissions := preParsedCommunityPermissionsData(viewAndPostPermissions)
|
2023-06-12 15:17:37 +00:00
|
|
|
|
|
|
|
allChainIDs, err := m.tokenManager.GetAllChainIDs()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
accountsAndChainIDs := combineAddressesAndChainIDs(addresses, allChainIDs)
|
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
response, err := m.checkChannelPermissions(viewOnlyPreParsedPermissions, viewAndPostPreParsedPermissions, accountsAndChainIDs, false)
|
2023-06-22 06:54:58 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.persistence.SaveCheckChannelPermissionResponse(communityID.String(), chatID, response)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return response, nil
|
2023-06-12 15:17:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type CheckChannelPermissionsResponse struct {
|
|
|
|
ViewOnlyPermissions *CheckChannelViewOnlyPermissionsResult `json:"viewOnlyPermissions"`
|
|
|
|
ViewAndPostPermissions *CheckChannelViewAndPostPermissionsResult `json:"viewAndPostPermissions"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type CheckChannelViewOnlyPermissionsResult struct {
|
|
|
|
Satisfied bool `json:"satisfied"`
|
|
|
|
Permissions map[string]*PermissionTokenCriteriaResult `json:"permissions"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type CheckChannelViewAndPostPermissionsResult struct {
|
|
|
|
Satisfied bool `json:"satisfied"`
|
|
|
|
Permissions map[string]*PermissionTokenCriteriaResult `json:"permissions"`
|
|
|
|
}
|
|
|
|
|
2024-05-23 17:06:50 +00:00
|
|
|
func computeViewOnlySatisfied(hasViewOnlyPermissions bool, hasViewAndPostPermissions bool, checkedViewOnlySatisfied bool, checkedViewAndPostSatisified bool) bool {
|
2024-05-10 14:30:48 +00:00
|
|
|
if (hasViewAndPostPermissions && !hasViewOnlyPermissions) || (hasViewOnlyPermissions && hasViewAndPostPermissions && checkedViewAndPostSatisified) {
|
|
|
|
return checkedViewAndPostSatisified
|
|
|
|
} else {
|
|
|
|
return checkedViewOnlySatisfied
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-23 17:06:50 +00:00
|
|
|
func computeViewAndPostSatisfied(hasViewOnlyPermissions bool, hasViewAndPostPermissions bool, checkedViewAndPostSatisified bool) bool {
|
2024-05-10 14:30:48 +00:00
|
|
|
if hasViewOnlyPermissions && !hasViewAndPostPermissions {
|
|
|
|
return false
|
|
|
|
} else {
|
|
|
|
return checkedViewAndPostSatisified
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
func (m *Manager) checkChannelPermissions(viewOnlyPreParsedPermissions *PreParsedCommunityPermissionsData, viewAndPostPreParsedPermissions *PreParsedCommunityPermissionsData, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool) (*CheckChannelPermissionsResponse, error) {
|
2024-05-23 17:06:50 +00:00
|
|
|
viewOnlyPermissionsResponse, err := m.PermissionChecker.CheckPermissions(viewOnlyPreParsedPermissions, accountsAndChainIDs, shortcircuit)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
viewAndPostPermissionsResponse, err := m.PermissionChecker.CheckPermissions(viewAndPostPreParsedPermissions, accountsAndChainIDs, shortcircuit)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
hasViewOnlyPermissions := viewOnlyPreParsedPermissions != nil
|
|
|
|
hasViewAndPostPermissions := viewAndPostPreParsedPermissions != nil
|
|
|
|
|
|
|
|
return computeCheckChannelPermissionsResponse(hasViewOnlyPermissions, hasViewAndPostPermissions,
|
|
|
|
viewOnlyPermissionsResponse, viewAndPostPermissionsResponse),
|
|
|
|
nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func computeCheckChannelPermissionsResponse(hasViewOnlyPermissions bool, hasViewAndPostPermissions bool,
|
|
|
|
viewOnlyPermissionsResponse *CheckPermissionsResponse, viewAndPostPermissionsResponse *CheckPermissionsResponse) *CheckChannelPermissionsResponse {
|
2023-06-12 15:17:37 +00:00
|
|
|
|
|
|
|
response := &CheckChannelPermissionsResponse{
|
|
|
|
ViewOnlyPermissions: &CheckChannelViewOnlyPermissionsResult{
|
|
|
|
Satisfied: false,
|
|
|
|
Permissions: make(map[string]*PermissionTokenCriteriaResult),
|
|
|
|
},
|
|
|
|
ViewAndPostPermissions: &CheckChannelViewAndPostPermissionsResult{
|
|
|
|
Satisfied: false,
|
|
|
|
Permissions: make(map[string]*PermissionTokenCriteriaResult),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2024-05-23 17:06:50 +00:00
|
|
|
viewOnlySatisfied := !hasViewOnlyPermissions || viewOnlyPermissionsResponse.Satisfied
|
|
|
|
viewAndPostSatisfied := !hasViewAndPostPermissions || viewAndPostPermissionsResponse.Satisfied
|
2023-06-12 15:17:37 +00:00
|
|
|
|
2024-05-23 17:06:50 +00:00
|
|
|
response.ViewOnlyPermissions.Satisfied = computeViewOnlySatisfied(hasViewOnlyPermissions, hasViewAndPostPermissions,
|
|
|
|
viewOnlySatisfied, viewAndPostSatisfied)
|
|
|
|
if viewOnlyPermissionsResponse != nil {
|
|
|
|
response.ViewOnlyPermissions.Permissions = viewOnlyPermissionsResponse.Permissions
|
2023-06-12 15:17:37 +00:00
|
|
|
}
|
|
|
|
|
2024-05-23 17:06:50 +00:00
|
|
|
response.ViewAndPostPermissions.Satisfied = computeViewAndPostSatisfied(hasViewOnlyPermissions, hasViewAndPostPermissions,
|
|
|
|
viewAndPostSatisfied)
|
2023-06-12 15:17:37 +00:00
|
|
|
|
2024-05-23 17:06:50 +00:00
|
|
|
if viewAndPostPermissionsResponse != nil {
|
|
|
|
response.ViewAndPostPermissions.Permissions = viewAndPostPermissionsResponse.Permissions
|
|
|
|
}
|
2023-06-12 15:17:37 +00:00
|
|
|
|
2024-05-23 17:06:50 +00:00
|
|
|
return response
|
2023-06-12 15:17:37 +00:00
|
|
|
}
|
|
|
|
|
2023-06-13 12:50:15 +00:00
|
|
|
func (m *Manager) CheckAllChannelsPermissions(communityID types.HexBytes, addresses []gethcommon.Address) (*CheckAllChannelsPermissionsResponse, error) {
|
|
|
|
|
|
|
|
community, err := m.GetByID(communityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
channels := community.Chats()
|
|
|
|
|
|
|
|
allChainIDs, err := m.tokenManager.GetAllChainIDs()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
accountsAndChainIDs := combineAddressesAndChainIDs(addresses, allChainIDs)
|
|
|
|
|
2024-05-23 17:06:50 +00:00
|
|
|
_, channelsPermissionsPreParsedData := PreParsePermissionsData(community.tokenPermissions())
|
|
|
|
|
|
|
|
channelPermissionsCheckResult := make(map[string]map[protobuf.CommunityTokenPermission_Type]*CheckPermissionsResponse)
|
|
|
|
|
|
|
|
for permissionId, channelsPermissionPreParsedData := range channelsPermissionsPreParsedData {
|
|
|
|
permissionResponse, err := m.PermissionChecker.CheckPermissions(channelsPermissionPreParsedData, accountsAndChainIDs, false)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note: in `PreParsedCommunityPermissionsData` for channels there will be only one permission for channels
|
|
|
|
for _, chatId := range channelsPermissionPreParsedData.Permissions[0].ChatIds {
|
|
|
|
if _, exists := channelPermissionsCheckResult[chatId]; !exists {
|
|
|
|
channelPermissionsCheckResult[chatId] = make(map[protobuf.CommunityTokenPermission_Type]*CheckPermissionsResponse)
|
|
|
|
}
|
|
|
|
storedPermissionResponse, exists := channelPermissionsCheckResult[chatId][channelsPermissionPreParsedData.Permissions[0].Type]
|
|
|
|
if !exists {
|
|
|
|
channelPermissionsCheckResult[chatId][channelsPermissionPreParsedData.Permissions[0].Type] =
|
|
|
|
permissionResponse
|
|
|
|
} else {
|
|
|
|
channelPermissionsCheckResult[chatId][channelsPermissionPreParsedData.Permissions[0].Type].Permissions[permissionId] =
|
|
|
|
permissionResponse.Permissions[permissionId]
|
|
|
|
channelPermissionsCheckResult[chatId][channelsPermissionPreParsedData.Permissions[0].Type].Satisfied =
|
|
|
|
storedPermissionResponse.Satisfied || permissionResponse.Satisfied
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-13 12:50:15 +00:00
|
|
|
response := &CheckAllChannelsPermissionsResponse{
|
|
|
|
Channels: make(map[string]*CheckChannelPermissionsResponse),
|
|
|
|
}
|
|
|
|
|
|
|
|
for channelID := range channels {
|
2024-05-23 17:06:50 +00:00
|
|
|
chatId := community.ChatID(channelID)
|
|
|
|
|
|
|
|
channelCheckPermissionsResponse, exists := channelPermissionsCheckResult[chatId]
|
|
|
|
|
|
|
|
var channelPermissionsResponse *CheckChannelPermissionsResponse
|
|
|
|
if !exists {
|
|
|
|
channelPermissionsResponse = computeCheckChannelPermissionsResponse(false, false, nil, nil)
|
|
|
|
} else {
|
|
|
|
viewPermissionsResponse, viewExists := channelCheckPermissionsResponse[protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL]
|
|
|
|
postPermissionsResponse, postExists := channelCheckPermissionsResponse[protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL]
|
|
|
|
channelPermissionsResponse = computeCheckChannelPermissionsResponse(viewExists, postExists, viewPermissionsResponse, postPermissionsResponse)
|
2023-06-13 12:50:15 +00:00
|
|
|
}
|
2024-05-23 17:06:50 +00:00
|
|
|
|
|
|
|
err = m.persistence.SaveCheckChannelPermissionResponse(community.IDString(), chatId, channelPermissionsResponse)
|
2023-06-22 06:54:58 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-05-23 17:06:50 +00:00
|
|
|
response.Channels[chatId] = channelPermissionsResponse
|
2023-06-13 12:50:15 +00:00
|
|
|
}
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
2023-06-22 06:54:58 +00:00
|
|
|
func (m *Manager) GetCheckChannelPermissionResponses(communityID types.HexBytes) (*CheckAllChannelsPermissionsResponse, error) {
|
|
|
|
|
|
|
|
response, err := m.persistence.GetCheckChannelPermissionResponses(communityID.String())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &CheckAllChannelsPermissionsResponse{Channels: response}, nil
|
|
|
|
}
|
|
|
|
|
2023-06-13 12:50:15 +00:00
|
|
|
type CheckAllChannelsPermissionsResponse struct {
|
|
|
|
Channels map[string]*CheckChannelPermissionsResponse `json:"channels"`
|
|
|
|
}
|
|
|
|
|
2023-02-03 16:33:16 +00:00
|
|
|
func (m *Manager) HandleCommunityRequestToJoinResponse(signer *ecdsa.PublicKey, request *protobuf.CommunityRequestToJoinResponse) (*RequestToJoin, error) {
|
2024-09-25 18:47:06 +00:00
|
|
|
if len(request.CommunityDescriptionProtocolMessage) > 0 {
|
|
|
|
description, err := unmarshalCommunityDescriptionMessage(request.CommunityDescriptionProtocolMessage, signer)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
_, err = m.HandleCommunityDescriptionMessage(signer, description, request.CommunityDescriptionProtocolMessage, nil, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(request.CommunityId)
|
|
|
|
defer m.communityLock.Unlock(request.CommunityId)
|
|
|
|
|
2023-02-03 16:33:16 +00:00
|
|
|
pkString := common.PubkeyToHex(&m.identity.PublicKey)
|
2022-07-01 13:54:02 +00:00
|
|
|
|
2023-09-25 11:26:17 +00:00
|
|
|
community, err := m.GetByID(request.CommunityId)
|
2022-07-01 13:54:02 +00:00
|
|
|
if err != nil {
|
2023-02-03 16:33:16 +00:00
|
|
|
return nil, err
|
2022-07-01 13:54:02 +00:00
|
|
|
}
|
|
|
|
|
2024-04-17 14:53:51 +00:00
|
|
|
if community.Encrypted() && len(request.Grant) > 0 {
|
|
|
|
_, err = m.HandleCommunityGrant(community, request.Grant, request.Clock)
|
|
|
|
if err != nil && err != ErrGrantOlder && err != ErrGrantExpired {
|
|
|
|
m.logger.Error("Error handling a community grant", zap.Error(err))
|
|
|
|
}
|
2024-02-15 19:13:12 +00:00
|
|
|
}
|
|
|
|
|
2022-07-01 13:54:02 +00:00
|
|
|
if request.Accepted {
|
2023-10-04 19:02:17 +00:00
|
|
|
err = m.markRequestToJoinAsAccepted(&m.identity.PublicKey, community)
|
2023-02-03 16:33:16 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
err = m.persistence.SetRequestToJoinState(pkString, community.ID(), RequestToJoinStateDeclined)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-07-01 13:54:02 +00:00
|
|
|
}
|
2023-02-03 16:33:16 +00:00
|
|
|
|
|
|
|
return m.persistence.GetRequestToJoinByPkAndCommunityID(pkString, community.ID())
|
2022-07-01 13:54:02 +00:00
|
|
|
}
|
|
|
|
|
2022-08-22 10:10:31 +00:00
|
|
|
func (m *Manager) HandleCommunityRequestToLeave(signer *ecdsa.PublicKey, proto *protobuf.CommunityRequestToLeave) error {
|
|
|
|
requestToLeave := NewRequestToLeave(common.PubkeyToHex(signer), proto)
|
|
|
|
if err := m.persistence.SaveRequestToLeave(requestToLeave); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure corresponding requestToJoin clock is older than requestToLeave
|
|
|
|
requestToJoin, err := m.persistence.GetRequestToJoin(requestToLeave.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if requestToJoin.Clock > requestToLeave.Clock {
|
|
|
|
return ErrOldRequestToLeave
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-10-25 15:01:33 +00:00
|
|
|
func UnwrapCommunityDescriptionMessage(payload []byte) (*ecdsa.PublicKey, *protobuf.CommunityDescription, error) {
|
2020-11-18 09:16:51 +00:00
|
|
|
|
|
|
|
applicationMetadataMessage := &protobuf.ApplicationMetadataMessage{}
|
|
|
|
err := proto.Unmarshal(payload, applicationMetadataMessage)
|
|
|
|
if err != nil {
|
2023-07-05 17:35:22 +00:00
|
|
|
return nil, nil, err
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
if applicationMetadataMessage.Type != protobuf.ApplicationMetadataMessage_COMMUNITY_DESCRIPTION {
|
2023-07-05 17:35:22 +00:00
|
|
|
return nil, nil, ErrInvalidMessage
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
2023-11-15 15:58:15 +00:00
|
|
|
signer, err := utils.RecoverKey(applicationMetadataMessage)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
2023-07-05 17:35:22 +00:00
|
|
|
return nil, nil, err
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
description := &protobuf.CommunityDescription{}
|
|
|
|
|
|
|
|
err = proto.Unmarshal(applicationMetadataMessage.Payload, description)
|
2023-07-05 17:35:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return signer, description, nil
|
|
|
|
}
|
|
|
|
|
2023-05-29 17:57:05 +00:00
|
|
|
func (m *Manager) JoinCommunity(id types.HexBytes, forceJoin bool) (*Community, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(id)
|
|
|
|
defer m.communityLock.Unlock(id)
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
community, err := m.GetByID(id)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-05-29 17:57:05 +00:00
|
|
|
if !forceJoin && community.Joined() {
|
|
|
|
// Nothing to do, we are already joined
|
|
|
|
return community, ErrOrgAlreadyJoined
|
|
|
|
}
|
2020-12-17 14:36:09 +00:00
|
|
|
community.Join()
|
|
|
|
err = m.persistence.SaveCommunity(community)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-12-17 14:36:09 +00:00
|
|
|
return community, nil
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2022-09-20 19:57:39 +00:00
|
|
|
func (m *Manager) SpectateCommunity(id types.HexBytes) (*Community, error) {
|
2024-02-23 02:16:51 +00:00
|
|
|
m.communityLock.Lock(id)
|
|
|
|
defer m.communityLock.Unlock(id)
|
2024-04-16 15:06:27 +00:00
|
|
|
|
2022-09-20 19:57:39 +00:00
|
|
|
community, err := m.GetByID(id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
community.Spectate()
|
|
|
|
if err = m.persistence.SaveCommunity(community); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return community, nil
|
|
|
|
}
|
|
|
|
|
2022-03-21 14:18:36 +00:00
|
|
|
func (m *Manager) GetMagnetlinkMessageClock(communityID types.HexBytes) (uint64, error) {
|
|
|
|
return m.persistence.GetMagnetlinkMessageClock(communityID)
|
|
|
|
}
|
|
|
|
|
2023-11-29 15:28:04 +00:00
|
|
|
func (m *Manager) GetCommunityRequestToJoinClock(pk *ecdsa.PublicKey, communityID string) (uint64, error) {
|
2024-05-23 20:13:48 +00:00
|
|
|
communityIDBytes, err := types.DecodeHex(communityID)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
joinClock, err := m.persistence.GetRequestToJoinClockByPkAndCommunityID(common.PubkeyToHex(pk), communityIDBytes)
|
|
|
|
|
2023-11-29 15:28:04 +00:00
|
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
|
|
return 0, nil
|
|
|
|
} else if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
2024-05-23 20:13:48 +00:00
|
|
|
return joinClock, nil
|
2023-11-29 15:28:04 +00:00
|
|
|
}
|
|
|
|
|
2023-12-09 12:46:30 +00:00
|
|
|
func (m *Manager) GetRequestToJoinByPkAndCommunityID(pk *ecdsa.PublicKey, communityID []byte) (*RequestToJoin, error) {
|
|
|
|
return m.persistence.GetRequestToJoinByPkAndCommunityID(common.PubkeyToHex(pk), communityID)
|
|
|
|
}
|
|
|
|
|
2022-03-21 14:18:36 +00:00
|
|
|
func (m *Manager) UpdateCommunityDescriptionMagnetlinkMessageClock(communityID types.HexBytes, clock uint64) error {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(communityID)
|
|
|
|
defer m.communityLock.Unlock(communityID)
|
|
|
|
|
2022-03-21 14:18:36 +00:00
|
|
|
community, err := m.GetByIDString(communityID.String())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
community.config.CommunityDescription.ArchiveMagnetlinkClock = clock
|
|
|
|
return m.persistence.SaveCommunity(community)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) UpdateMagnetlinkMessageClock(communityID types.HexBytes, clock uint64) error {
|
|
|
|
return m.persistence.UpdateMagnetlinkMessageClock(communityID, clock)
|
|
|
|
}
|
|
|
|
|
2022-12-19 08:34:37 +00:00
|
|
|
func (m *Manager) UpdateLastSeenMagnetlink(communityID types.HexBytes, magnetlinkURI string) error {
|
|
|
|
return m.persistence.UpdateLastSeenMagnetlink(communityID, magnetlinkURI)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) GetLastSeenMagnetlink(communityID types.HexBytes) (string, error) {
|
|
|
|
return m.persistence.GetLastSeenMagnetlink(communityID)
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func (m *Manager) LeaveCommunity(id types.HexBytes) (*Community, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(id)
|
|
|
|
defer m.communityLock.Unlock(id)
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
community, err := m.GetByID(id)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-09-14 12:39:55 +00:00
|
|
|
|
2022-10-14 09:26:10 +00:00
|
|
|
community.RemoveOurselvesFromOrg(&m.identity.PublicKey)
|
2020-12-17 14:36:09 +00:00
|
|
|
community.Leave()
|
2021-11-12 10:23:59 +00:00
|
|
|
|
2022-09-14 12:39:55 +00:00
|
|
|
if err = m.persistence.SaveCommunity(community); err != nil {
|
2020-11-18 09:16:51 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2022-09-14 12:39:55 +00:00
|
|
|
|
2020-12-17 14:36:09 +00:00
|
|
|
return community, nil
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2024-02-26 12:33:07 +00:00
|
|
|
// Same as LeaveCommunity, but we have an option to stay spectating
|
|
|
|
func (m *Manager) KickedOutOfCommunity(id types.HexBytes, spectateMode bool) (*Community, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(id)
|
|
|
|
defer m.communityLock.Unlock(id)
|
|
|
|
|
2023-10-27 19:20:08 +00:00
|
|
|
community, err := m.GetByID(id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
community.RemoveOurselvesFromOrg(&m.identity.PublicKey)
|
|
|
|
community.Leave()
|
2024-02-26 12:33:07 +00:00
|
|
|
if spectateMode {
|
|
|
|
community.Spectate()
|
|
|
|
}
|
2023-10-27 19:20:08 +00:00
|
|
|
|
|
|
|
if err = m.persistence.SaveCommunity(community); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return community, nil
|
|
|
|
}
|
|
|
|
|
2022-09-21 10:50:56 +00:00
|
|
|
func (m *Manager) AddMemberOwnerToCommunity(communityID types.HexBytes, pk *ecdsa.PublicKey) (*Community, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(communityID)
|
|
|
|
defer m.communityLock.Unlock(communityID)
|
|
|
|
|
2022-07-01 13:54:02 +00:00
|
|
|
community, err := m.GetByID(communityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-07-05 13:22:22 +00:00
|
|
|
_, err = community.AddMember(pk, []protobuf.CommunityMember_Roles{protobuf.CommunityMember_ROLE_OWNER}, community.Clock())
|
2022-07-01 13:54:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.persistence.SaveCommunity(community)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
m.publish(&Subscription{Community: community})
|
|
|
|
return community, nil
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func (m *Manager) RemoveUserFromCommunity(id types.HexBytes, pk *ecdsa.PublicKey) (*Community, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(id)
|
|
|
|
defer m.communityLock.Unlock(id)
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
community, err := m.GetByID(id)
|
2020-12-21 15:10:52 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = community.RemoveUserFromOrg(pk)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
err = m.saveAndPublish(community)
|
2021-03-19 09:15:45 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return community, nil
|
|
|
|
}
|
|
|
|
|
2022-06-23 07:12:15 +00:00
|
|
|
func (m *Manager) UnbanUserFromCommunity(request *requests.UnbanUserFromCommunity) (*Community, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(request.CommunityID)
|
|
|
|
defer m.communityLock.Unlock(request.CommunityID)
|
|
|
|
|
2022-06-23 07:12:15 +00:00
|
|
|
id := request.CommunityID
|
|
|
|
publicKey, err := common.HexToPubkey(request.User.String())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
community, err := m.GetByID(id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = community.UnbanUserFromCommunity(publicKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
err = m.saveAndPublish(community)
|
2022-06-23 07:12:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-12-02 11:34:02 +00:00
|
|
|
return community, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) AddRoleToMember(request *requests.AddRoleToMember) (*Community, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(request.CommunityID)
|
|
|
|
defer m.communityLock.Unlock(request.CommunityID)
|
|
|
|
|
2022-12-02 11:34:02 +00:00
|
|
|
id := request.CommunityID
|
|
|
|
publicKey, err := common.HexToPubkey(request.User.String())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
community, err := m.GetByID(id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !community.hasMember(publicKey) {
|
|
|
|
return nil, ErrMemberNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = community.AddRoleToMember(publicKey, request.Role)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.persistence.SaveCommunity(community)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
m.publish(&Subscription{Community: community})
|
|
|
|
|
|
|
|
return community, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) RemoveRoleFromMember(request *requests.RemoveRoleFromMember) (*Community, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(request.CommunityID)
|
|
|
|
defer m.communityLock.Unlock(request.CommunityID)
|
|
|
|
|
2022-12-02 11:34:02 +00:00
|
|
|
id := request.CommunityID
|
|
|
|
publicKey, err := common.HexToPubkey(request.User.String())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
community, err := m.GetByID(id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !community.hasMember(publicKey) {
|
|
|
|
return nil, ErrMemberNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = community.RemoveRoleFromMember(publicKey, request.Role)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.persistence.SaveCommunity(community)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
m.publish(&Subscription{Community: community})
|
2022-06-23 07:12:15 +00:00
|
|
|
|
|
|
|
return community, nil
|
|
|
|
}
|
|
|
|
|
2021-03-19 09:15:45 +00:00
|
|
|
func (m *Manager) BanUserFromCommunity(request *requests.BanUserFromCommunity) (*Community, error) {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(request.CommunityID)
|
|
|
|
defer m.communityLock.Unlock(request.CommunityID)
|
|
|
|
|
2021-03-19 09:15:45 +00:00
|
|
|
id := request.CommunityID
|
|
|
|
|
|
|
|
publicKey, err := common.HexToPubkey(request.User.String())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
community, err := m.GetByID(id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-02-22 10:25:13 +00:00
|
|
|
_, err = community.BanUserFromCommunity(publicKey, &protobuf.CommunityBanInfo{DeleteAllMessages: request.DeleteAllMessages})
|
2021-03-19 09:15:45 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
err = m.saveAndPublish(community)
|
2020-12-21 15:10:52 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return community, nil
|
|
|
|
}
|
|
|
|
|
2023-11-30 12:25:31 +00:00
|
|
|
func (m *Manager) dbRecordBundleToCommunity(r *CommunityRecordBundle) (*Community, error) {
|
2023-11-29 17:21:21 +00:00
|
|
|
var descriptionEncryptor DescriptionEncryptor
|
|
|
|
if m.encryptor != nil {
|
|
|
|
descriptionEncryptor = m
|
|
|
|
}
|
|
|
|
|
2024-07-01 18:52:57 +00:00
|
|
|
initializer := func(community *Community) error {
|
2024-02-29 09:51:38 +00:00
|
|
|
_, description, err := m.preprocessDescription(community.ID(), community.config.CommunityDescription)
|
2023-11-29 17:21:21 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-02-29 09:51:38 +00:00
|
|
|
community.config.CommunityDescription = description
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
if community.config.EventsData != nil {
|
2024-09-25 14:05:58 +00:00
|
|
|
eventsDescription, err := unmarshalCommunityDescriptionMessage(community.config.EventsData.EventsBaseCommunityDescription, community.ControlNode())
|
2024-02-19 09:52:22 +00:00
|
|
|
if err != nil {
|
|
|
|
m.logger.Error("invalid EventsBaseCommunityDescription", zap.Error(err))
|
|
|
|
}
|
2024-04-03 12:51:28 +00:00
|
|
|
if eventsDescription != nil && eventsDescription.Clock == community.Clock() {
|
2024-02-19 09:52:22 +00:00
|
|
|
community.applyEvents()
|
|
|
|
}
|
2023-10-30 18:34:21 +00:00
|
|
|
}
|
|
|
|
|
2023-11-30 12:25:31 +00:00
|
|
|
if m.transport != nil && m.transport.WakuVersion() == 2 {
|
|
|
|
topic := community.PubsubTopic()
|
|
|
|
privKey, err := m.transport.RetrievePubsubTopicKey(topic)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
community.config.PubsubTopicPrivateKey = privKey
|
|
|
|
}
|
2023-09-25 11:26:17 +00:00
|
|
|
|
2023-11-30 12:25:31 +00:00
|
|
|
return nil
|
2024-07-01 18:52:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return recordBundleToCommunity(
|
|
|
|
r,
|
|
|
|
m.identity,
|
|
|
|
m.installationID,
|
|
|
|
m.logger,
|
|
|
|
m.timesource,
|
|
|
|
descriptionEncryptor,
|
|
|
|
m.mediaServer,
|
|
|
|
initializer,
|
|
|
|
)
|
2023-09-25 11:26:17 +00:00
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func (m *Manager) GetByID(id []byte) (*Community, error) {
|
2024-01-09 21:47:37 +00:00
|
|
|
community, err := m.persistence.GetByID(&m.identity.PublicKey, id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if community == nil {
|
|
|
|
return nil, ErrOrgNotFound
|
|
|
|
}
|
|
|
|
return community, nil
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
|
2020-11-18 09:16:51 +00:00
|
|
|
func (m *Manager) GetByIDString(idString string) (*Community, error) {
|
|
|
|
id, err := types.DecodeHex(idString)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-01-11 10:32:51 +00:00
|
|
|
return m.GetByID(id)
|
|
|
|
}
|
|
|
|
|
2023-12-22 12:37:37 +00:00
|
|
|
func (m *Manager) GetCommunityShard(communityID types.HexBytes) (*shard.Shard, error) {
|
|
|
|
return m.persistence.GetCommunityShard(communityID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) SaveCommunityShard(communityID types.HexBytes, shard *shard.Shard, clock uint64) error {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(communityID)
|
|
|
|
defer m.communityLock.Unlock(communityID)
|
|
|
|
|
2023-12-22 12:37:37 +00:00
|
|
|
return m.persistence.SaveCommunityShard(communityID, shard, clock)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) DeleteCommunityShard(communityID types.HexBytes) error {
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(communityID)
|
|
|
|
defer m.communityLock.Unlock(communityID)
|
|
|
|
|
2023-12-22 12:37:37 +00:00
|
|
|
return m.persistence.DeleteCommunityShard(communityID)
|
|
|
|
}
|
|
|
|
|
2023-08-29 18:56:30 +00:00
|
|
|
func (m *Manager) SaveRequestToJoinRevealedAddresses(requestID types.HexBytes, revealedAccounts []*protobuf.RevealedAccount) error {
|
|
|
|
return m.persistence.SaveRequestToJoinRevealedAddresses(requestID, revealedAccounts)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) RemoveRequestToJoinRevealedAddresses(requestID types.HexBytes) error {
|
|
|
|
return m.persistence.RemoveRequestToJoinRevealedAddresses(requestID)
|
|
|
|
}
|
|
|
|
|
2023-07-14 17:06:37 +00:00
|
|
|
func (m *Manager) SaveRequestToJoinAndCommunity(requestToJoin *RequestToJoin, community *Community) (*Community, *RequestToJoin, error) {
|
|
|
|
if err := m.persistence.SaveRequestToJoin(requestToJoin); err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
community.config.RequestedToJoinAt = uint64(time.Now().Unix())
|
|
|
|
community.AddRequestToJoin(requestToJoin)
|
|
|
|
|
2023-08-29 18:56:30 +00:00
|
|
|
// Save revealed addresses to our own table so that we can retrieve them later when editing
|
|
|
|
if err := m.SaveRequestToJoinRevealedAddresses(requestToJoin.ID, requestToJoin.RevealedAccounts); err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-14 17:06:37 +00:00
|
|
|
return community, requestToJoin, nil
|
|
|
|
}
|
|
|
|
|
2024-04-03 14:49:57 +00:00
|
|
|
func (m *Manager) CreateRequestToJoin(request *requests.RequestToJoinCommunity, customizationColor multiaccountscommon.CustomizationColor) *RequestToJoin {
|
2021-01-11 10:32:51 +00:00
|
|
|
clock := uint64(time.Now().Unix())
|
|
|
|
requestToJoin := &RequestToJoin{
|
2024-07-12 09:24:16 +00:00
|
|
|
PublicKey: common.PubkeyToHex(&m.identity.PublicKey),
|
|
|
|
Clock: clock,
|
|
|
|
ENSName: request.ENSName,
|
|
|
|
CommunityID: request.CommunityID,
|
|
|
|
State: RequestToJoinStatePending,
|
|
|
|
Our: true,
|
|
|
|
RevealedAccounts: make([]*protobuf.RevealedAccount, 0),
|
|
|
|
CustomizationColor: customizationColor,
|
|
|
|
ShareFutureAddresses: request.ShareFutureAddresses,
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
requestToJoin.CalculateID()
|
|
|
|
|
2023-10-20 06:21:41 +00:00
|
|
|
addSignature := len(request.Signatures) == len(request.AddressesToReveal)
|
|
|
|
for i := range request.AddressesToReveal {
|
|
|
|
revealedAcc := &protobuf.RevealedAccount{
|
|
|
|
Address: request.AddressesToReveal[i],
|
|
|
|
IsAirdropAddress: types.HexToAddress(request.AddressesToReveal[i]) == types.HexToAddress(request.AirdropAddress),
|
|
|
|
}
|
|
|
|
|
|
|
|
if addSignature {
|
|
|
|
revealedAcc.Signature = request.Signatures[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
requestToJoin.RevealedAccounts = append(requestToJoin.RevealedAccounts, revealedAcc)
|
|
|
|
}
|
|
|
|
|
|
|
|
return requestToJoin
|
2021-01-11 10:32:51 +00:00
|
|
|
}
|
|
|
|
|
2021-08-06 15:40:23 +00:00
|
|
|
func (m *Manager) SaveRequestToJoin(request *RequestToJoin) error {
|
|
|
|
return m.persistence.SaveRequestToJoin(request)
|
|
|
|
}
|
|
|
|
|
2022-10-28 08:41:20 +00:00
|
|
|
func (m *Manager) CanceledRequestsToJoinForUser(pk *ecdsa.PublicKey) ([]*RequestToJoin, error) {
|
|
|
|
return m.persistence.CanceledRequestsToJoinForUser(common.PubkeyToHex(pk))
|
|
|
|
}
|
|
|
|
|
2023-04-21 09:18:47 +00:00
|
|
|
func (m *Manager) PendingRequestsToJoin() ([]*RequestToJoin, error) {
|
|
|
|
return m.persistence.PendingRequestsToJoin()
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func (m *Manager) PendingRequestsToJoinForUser(pk *ecdsa.PublicKey) ([]*RequestToJoin, error) {
|
2023-10-31 14:20:40 +00:00
|
|
|
return m.persistence.RequestsToJoinForUserByState(common.PubkeyToHex(pk), RequestToJoinStatePending)
|
|
|
|
}
|
|
|
|
|
2021-01-11 10:32:51 +00:00
|
|
|
func (m *Manager) PendingRequestsToJoinForCommunity(id types.HexBytes) ([]*RequestToJoin, error) {
|
|
|
|
m.logger.Info("fetching pending invitations", zap.String("community-id", id.String()))
|
|
|
|
return m.persistence.PendingRequestsToJoinForCommunity(id)
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
|
|
|
|
2022-08-04 07:44:35 +00:00
|
|
|
func (m *Manager) DeclinedRequestsToJoinForCommunity(id types.HexBytes) ([]*RequestToJoin, error) {
|
|
|
|
m.logger.Info("fetching declined invitations", zap.String("community-id", id.String()))
|
|
|
|
return m.persistence.DeclinedRequestsToJoinForCommunity(id)
|
|
|
|
}
|
|
|
|
|
2022-10-28 08:41:20 +00:00
|
|
|
func (m *Manager) CanceledRequestsToJoinForCommunity(id types.HexBytes) ([]*RequestToJoin, error) {
|
|
|
|
m.logger.Info("fetching canceled invitations", zap.String("community-id", id.String()))
|
|
|
|
return m.persistence.CanceledRequestsToJoinForCommunity(id)
|
|
|
|
}
|
|
|
|
|
2023-06-14 14:15:46 +00:00
|
|
|
func (m *Manager) AcceptedRequestsToJoinForCommunity(id types.HexBytes) ([]*RequestToJoin, error) {
|
|
|
|
m.logger.Info("fetching canceled invitations", zap.String("community-id", id.String()))
|
|
|
|
return m.persistence.AcceptedRequestsToJoinForCommunity(id)
|
|
|
|
}
|
|
|
|
|
refactor: EventSenders forward RequestToJoin decision to control node
This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.
Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.
This commit changes this behaviour such that:
1. EventSenders can make a decision (accept, reject), but merely forward
their decision to the control node, which ultimately has to confirm
it
2. EventSenders are no longer removing or adding members to and from
communities
3. When an eventsender signaled a decision, the membership request will
enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
override that decision
This implementation is covered with a bunch of tests:
- Ensure that decision made by event sender is shared with other event
senders
- `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
confirmed decision by event senders
- `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
- `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
event senders
- `testEventSenderCannotOverrideRequestToJoinState()`
These test cases live in three test suites for different event sender
types respectively
- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`
In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).
Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.
This commit does not yet handle the case of event senders attempting to
kick and ban members.
Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
2023-08-02 12:04:47 +00:00
|
|
|
func (m *Manager) AcceptedPendingRequestsToJoinForCommunity(id types.HexBytes) ([]*RequestToJoin, error) {
|
|
|
|
return m.persistence.AcceptedPendingRequestsToJoinForCommunity(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) DeclinedPendingRequestsToJoinForCommunity(id types.HexBytes) ([]*RequestToJoin, error) {
|
|
|
|
return m.persistence.DeclinedPendingRequestsToJoinForCommunity(id)
|
2023-12-05 14:50:45 +00:00
|
|
|
}
|
2023-10-12 19:21:49 +00:00
|
|
|
|
2023-12-05 14:50:45 +00:00
|
|
|
func (m *Manager) AllNonApprovedCommunitiesRequestsToJoin() ([]*RequestToJoin, error) {
|
|
|
|
m.logger.Info("fetching all non-approved invitations for all communities")
|
|
|
|
return m.persistence.AllNonApprovedCommunitiesRequestsToJoin()
|
2023-10-12 19:21:49 +00:00
|
|
|
}
|
|
|
|
|
2023-10-31 14:20:40 +00:00
|
|
|
func (m *Manager) RequestsToJoinForCommunityAwaitingAddresses(id types.HexBytes) ([]*RequestToJoin, error) {
|
|
|
|
m.logger.Info("fetching ownership changed invitations", zap.String("community-id", id.String()))
|
|
|
|
return m.persistence.RequestsToJoinForCommunityAwaitingAddresses(id)
|
|
|
|
}
|
|
|
|
|
2024-03-01 17:15:38 +00:00
|
|
|
func (m *Manager) CanPost(pk *ecdsa.PublicKey, communityID string, chatID string, messageType protobuf.ApplicationMetadataMessage_Type) (bool, error) {
|
2021-01-11 10:32:51 +00:00
|
|
|
community, err := m.GetByIDString(communityID)
|
2020-11-18 09:16:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2024-03-01 17:15:38 +00:00
|
|
|
return community.CanPost(pk, chatID, messageType)
|
2020-11-18 09:16:51 +00:00
|
|
|
}
|
2021-08-06 15:40:23 +00:00
|
|
|
|
2022-05-27 09:14:40 +00:00
|
|
|
func (m *Manager) IsEncrypted(communityID string) (bool, error) {
|
|
|
|
community, err := m.GetByIDString(communityID)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return community.Encrypted(), nil
|
2023-06-23 10:49:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) IsChannelEncrypted(communityID string, chatID string) (bool, error) {
|
|
|
|
community, err := m.GetByIDString(communityID)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2022-05-27 09:14:40 +00:00
|
|
|
|
2023-10-26 15:09:43 +00:00
|
|
|
channelID := strings.TrimPrefix(chatID, communityID)
|
|
|
|
return community.ChannelEncrypted(channelID), nil
|
2022-05-27 09:14:40 +00:00
|
|
|
}
|
2023-06-23 10:49:26 +00:00
|
|
|
|
2023-08-18 11:39:59 +00:00
|
|
|
func (m *Manager) ShouldHandleSyncCommunity(community *protobuf.SyncInstallationCommunity) (bool, error) {
|
2021-08-06 15:40:23 +00:00
|
|
|
return m.persistence.ShouldHandleSyncCommunity(community)
|
|
|
|
}
|
|
|
|
|
2022-06-01 07:55:48 +00:00
|
|
|
func (m *Manager) ShouldHandleSyncCommunitySettings(communitySettings *protobuf.SyncCommunitySettings) (bool, error) {
|
|
|
|
return m.persistence.ShouldHandleSyncCommunitySettings(communitySettings)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) HandleSyncCommunitySettings(syncCommunitySettings *protobuf.SyncCommunitySettings) (*CommunitySettings, error) {
|
|
|
|
id, err := types.DecodeHex(syncCommunitySettings.CommunityId)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
settings, err := m.persistence.GetCommunitySettingsByID(id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if settings == nil {
|
|
|
|
settings = &CommunitySettings{
|
|
|
|
CommunityID: syncCommunitySettings.CommunityId,
|
|
|
|
HistoryArchiveSupportEnabled: syncCommunitySettings.HistoryArchiveSupportEnabled,
|
|
|
|
Clock: syncCommunitySettings.Clock,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if syncCommunitySettings.Clock > settings.Clock {
|
|
|
|
settings.CommunityID = syncCommunitySettings.CommunityId
|
|
|
|
settings.HistoryArchiveSupportEnabled = syncCommunitySettings.HistoryArchiveSupportEnabled
|
|
|
|
settings.Clock = syncCommunitySettings.Clock
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.persistence.SaveCommunitySettings(*settings)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return settings, nil
|
|
|
|
}
|
|
|
|
|
2021-08-06 15:40:23 +00:00
|
|
|
func (m *Manager) SetSyncClock(id []byte, clock uint64) error {
|
|
|
|
return m.persistence.SetSyncClock(id, clock)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) SetPrivateKey(id []byte, privKey *ecdsa.PrivateKey) error {
|
|
|
|
return m.persistence.SetPrivateKey(id, privKey)
|
|
|
|
}
|
|
|
|
|
2022-03-28 10:10:40 +00:00
|
|
|
func (m *Manager) GetSyncedRawCommunity(id []byte) (*RawCommunityRow, error) {
|
2021-08-06 15:40:23 +00:00
|
|
|
return m.persistence.getSyncedRawCommunity(id)
|
|
|
|
}
|
2022-03-08 15:25:00 +00:00
|
|
|
|
|
|
|
func (m *Manager) GetCommunitySettingsByID(id types.HexBytes) (*CommunitySettings, error) {
|
|
|
|
return m.persistence.GetCommunitySettingsByID(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) GetCommunitiesSettings() ([]CommunitySettings, error) {
|
|
|
|
return m.persistence.GetCommunitiesSettings()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) SaveCommunitySettings(settings CommunitySettings) error {
|
|
|
|
return m.persistence.SaveCommunitySettings(settings)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) CommunitySettingsExist(id types.HexBytes) (bool, error) {
|
|
|
|
return m.persistence.CommunitySettingsExist(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) DeleteCommunitySettings(id types.HexBytes) error {
|
|
|
|
return m.persistence.DeleteCommunitySettings(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) UpdateCommunitySettings(settings CommunitySettings) error {
|
|
|
|
return m.persistence.UpdateCommunitySettings(settings)
|
|
|
|
}
|
2022-03-09 09:58:05 +00:00
|
|
|
|
2023-07-05 17:35:22 +00:00
|
|
|
func (m *Manager) GetOwnedCommunitiesChatIDs() (map[string]bool, error) {
|
2023-10-19 22:06:09 +00:00
|
|
|
ownedCommunities, err := m.Controlled()
|
2022-03-09 09:58:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
chatIDs := make(map[string]bool)
|
2023-07-05 17:35:22 +00:00
|
|
|
for _, c := range ownedCommunities {
|
2022-03-09 09:58:05 +00:00
|
|
|
if c.Joined() {
|
|
|
|
for _, id := range c.ChatIDs() {
|
|
|
|
chatIDs[id] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return chatIDs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) StoreWakuMessage(message *types.Message) error {
|
|
|
|
return m.persistence.SaveWakuMessage(message)
|
|
|
|
}
|
2022-03-21 14:18:36 +00:00
|
|
|
|
2022-09-29 11:50:23 +00:00
|
|
|
func (m *Manager) StoreWakuMessages(messages []*types.Message) error {
|
|
|
|
return m.persistence.SaveWakuMessages(messages)
|
|
|
|
}
|
|
|
|
|
2022-03-21 14:18:36 +00:00
|
|
|
func (m *Manager) GetLatestWakuMessageTimestamp(topics []types.TopicType) (uint64, error) {
|
|
|
|
return m.persistence.GetLatestWakuMessageTimestamp(topics)
|
|
|
|
}
|
|
|
|
|
2023-08-15 17:42:40 +00:00
|
|
|
func (m *Manager) GetCommunityToken(communityID string, chainID int, address string) (*community_token.CommunityToken, error) {
|
|
|
|
return m.persistence.GetCommunityToken(communityID, chainID, address)
|
|
|
|
}
|
|
|
|
|
2024-03-14 08:39:06 +00:00
|
|
|
func (m *Manager) GetCommunityTokenByChainAndAddress(chainID int, address string) (*community_token.CommunityToken, error) {
|
|
|
|
return m.persistence.GetCommunityTokenByChainAndAddress(chainID, address)
|
|
|
|
}
|
|
|
|
|
2023-07-07 13:03:37 +00:00
|
|
|
func (m *Manager) GetCommunityTokens(communityID string) ([]*community_token.CommunityToken, error) {
|
2023-03-02 17:33:30 +00:00
|
|
|
return m.persistence.GetCommunityTokens(communityID)
|
2023-01-27 13:27:24 +00:00
|
|
|
}
|
|
|
|
|
2023-07-07 13:03:37 +00:00
|
|
|
func (m *Manager) GetAllCommunityTokens() ([]*community_token.CommunityToken, error) {
|
2023-04-26 08:48:10 +00:00
|
|
|
return m.persistence.GetAllCommunityTokens()
|
|
|
|
}
|
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
func (m *Manager) GetCommunityGrant(communityID string) ([]byte, uint64, error) {
|
|
|
|
return m.persistence.GetCommunityGrant(communityID)
|
|
|
|
}
|
|
|
|
|
2023-03-14 12:02:30 +00:00
|
|
|
func (m *Manager) ImageToBase64(uri string) string {
|
2023-07-05 17:35:22 +00:00
|
|
|
if uri == "" {
|
|
|
|
return ""
|
|
|
|
}
|
2023-03-14 12:02:30 +00:00
|
|
|
file, err := os.Open(uri)
|
|
|
|
if err != nil {
|
|
|
|
m.logger.Error(err.Error())
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
payload, err := ioutil.ReadAll(file)
|
|
|
|
if err != nil {
|
|
|
|
m.logger.Error(err.Error())
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
base64img, err := images.GetPayloadDataURI(payload)
|
|
|
|
if err != nil {
|
|
|
|
m.logger.Error(err.Error())
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return base64img
|
|
|
|
}
|
|
|
|
|
2023-07-07 13:03:37 +00:00
|
|
|
func (m *Manager) SaveCommunityToken(token *community_token.CommunityToken, croppedImage *images.CroppedImage) (*community_token.CommunityToken, error) {
|
|
|
|
|
2024-01-09 21:47:37 +00:00
|
|
|
_, err := m.GetByIDString(token.CommunityID)
|
2023-07-07 13:03:37 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-03 10:46:09 +00:00
|
|
|
if croppedImage != nil && croppedImage.ImagePath != "" {
|
|
|
|
bytes, err := images.OpenAndAdjustImage(*croppedImage, true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
base64img, err := images.GetPayloadDataURI(bytes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
token.Base64Image = base64img
|
2023-07-18 08:33:45 +00:00
|
|
|
} else if !images.IsPayloadDataURI(token.Base64Image) {
|
|
|
|
// if image is already base64 do not convert (owner and master tokens have already base64 image)
|
2023-07-03 10:46:09 +00:00
|
|
|
token.Base64Image = m.ImageToBase64(token.Base64Image)
|
|
|
|
}
|
2023-03-14 12:02:30 +00:00
|
|
|
|
!refactor: introduce `SaveCommunityToken()` and change `AddCommunityToken()`
**This is a breaking change!**
Prior to this commit we had `AddCommunityToken(token *communities,
croppedImage CroppedImage)` that we used to
1. add a `CommunityToken` to the user's database and
2. to create a `CommunityTokenMetadata` from it which is then added to
the community's `CommunityDescription` and published to its members
However, I've then discovered that we need to separate these two things,
such that we can deploy a community token, then add it to the database
only for tracking purposes, **then** add it to the community description
(and propagate to members) once we know that the deploy tx indeed went
through.
To implement this, this commit introduces a new API
`SaveCommunityToken(token *communities.CommunityToken, croppedImage
CroppedImage)` which adds the token to the database only and doesn't
touch the community description.
The `AddCommunityToken` API is then changed that it's exclusively used
for adding an already saved `CommunityToken` to the community
description so it can be published to members. Hence, the signature is
now `AddCommunityToken(communityID string, chainID int, address
string)`, which makes this a breaking change.
Clients that used `AddCommunityToken()` before now need to ensure that
they first call `SaveCommunityToken()` as `AddCommunityToken()` will
fail otherwise.
2023-07-25 11:35:17 +00:00
|
|
|
return token, m.persistence.AddCommunityToken(token)
|
|
|
|
}
|
|
|
|
|
2023-09-21 11:16:05 +00:00
|
|
|
func (m *Manager) AddCommunityToken(token *community_token.CommunityToken, clock uint64) (*Community, error) {
|
2023-08-15 17:42:40 +00:00
|
|
|
if token == nil {
|
|
|
|
return nil, errors.New("Token is absent in database")
|
|
|
|
}
|
!refactor: introduce `SaveCommunityToken()` and change `AddCommunityToken()`
**This is a breaking change!**
Prior to this commit we had `AddCommunityToken(token *communities,
croppedImage CroppedImage)` that we used to
1. add a `CommunityToken` to the user's database and
2. to create a `CommunityTokenMetadata` from it which is then added to
the community's `CommunityDescription` and published to its members
However, I've then discovered that we need to separate these two things,
such that we can deploy a community token, then add it to the database
only for tracking purposes, **then** add it to the community description
(and propagate to members) once we know that the deploy tx indeed went
through.
To implement this, this commit introduces a new API
`SaveCommunityToken(token *communities.CommunityToken, croppedImage
CroppedImage)` which adds the token to the database only and doesn't
touch the community description.
The `AddCommunityToken` API is then changed that it's exclusively used
for adding an already saved `CommunityToken` to the community
description so it can be published to members. Hence, the signature is
now `AddCommunityToken(communityID string, chainID int, address
string)`, which makes this a breaking change.
Clients that used `AddCommunityToken()` before now need to ensure that
they first call `SaveCommunityToken()` as `AddCommunityToken()` will
fail otherwise.
2023-07-25 11:35:17 +00:00
|
|
|
|
2024-04-16 15:06:27 +00:00
|
|
|
communityID, err := types.DecodeHex(token.CommunityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
m.communityLock.Lock(communityID)
|
|
|
|
defer m.communityLock.Unlock(communityID)
|
|
|
|
|
|
|
|
community, err := m.GetByID(communityID)
|
!refactor: introduce `SaveCommunityToken()` and change `AddCommunityToken()`
**This is a breaking change!**
Prior to this commit we had `AddCommunityToken(token *communities,
croppedImage CroppedImage)` that we used to
1. add a `CommunityToken` to the user's database and
2. to create a `CommunityTokenMetadata` from it which is then added to
the community's `CommunityDescription` and published to its members
However, I've then discovered that we need to separate these two things,
such that we can deploy a community token, then add it to the database
only for tracking purposes, **then** add it to the community description
(and propagate to members) once we know that the deploy tx indeed went
through.
To implement this, this commit introduces a new API
`SaveCommunityToken(token *communities.CommunityToken, croppedImage
CroppedImage)` which adds the token to the database only and doesn't
touch the community description.
The `AddCommunityToken` API is then changed that it's exclusively used
for adding an already saved `CommunityToken` to the community
description so it can be published to members. Hence, the signature is
now `AddCommunityToken(communityID string, chainID int, address
string)`, which makes this a breaking change.
Clients that used `AddCommunityToken()` before now need to ensure that
they first call `SaveCommunityToken()` as `AddCommunityToken()` will
fail otherwise.
2023-07-25 11:35:17 +00:00
|
|
|
if err != nil {
|
2023-08-15 17:42:40 +00:00
|
|
|
return nil, err
|
!refactor: introduce `SaveCommunityToken()` and change `AddCommunityToken()`
**This is a breaking change!**
Prior to this commit we had `AddCommunityToken(token *communities,
croppedImage CroppedImage)` that we used to
1. add a `CommunityToken` to the user's database and
2. to create a `CommunityTokenMetadata` from it which is then added to
the community's `CommunityDescription` and published to its members
However, I've then discovered that we need to separate these two things,
such that we can deploy a community token, then add it to the database
only for tracking purposes, **then** add it to the community description
(and propagate to members) once we know that the deploy tx indeed went
through.
To implement this, this commit introduces a new API
`SaveCommunityToken(token *communities.CommunityToken, croppedImage
CroppedImage)` which adds the token to the database only and doesn't
touch the community description.
The `AddCommunityToken` API is then changed that it's exclusively used
for adding an already saved `CommunityToken` to the community
description so it can be published to members. Hence, the signature is
now `AddCommunityToken(communityID string, chainID int, address
string)`, which makes this a breaking change.
Clients that used `AddCommunityToken()` before now need to ensure that
they first call `SaveCommunityToken()` as `AddCommunityToken()` will
fail otherwise.
2023-07-25 11:35:17 +00:00
|
|
|
}
|
|
|
|
|
2023-08-15 17:42:40 +00:00
|
|
|
if !community.MemberCanManageToken(&m.identity.PublicKey, token) {
|
|
|
|
return nil, ErrInvalidManageTokensPermission
|
!refactor: introduce `SaveCommunityToken()` and change `AddCommunityToken()`
**This is a breaking change!**
Prior to this commit we had `AddCommunityToken(token *communities,
croppedImage CroppedImage)` that we used to
1. add a `CommunityToken` to the user's database and
2. to create a `CommunityTokenMetadata` from it which is then added to
the community's `CommunityDescription` and published to its members
However, I've then discovered that we need to separate these two things,
such that we can deploy a community token, then add it to the database
only for tracking purposes, **then** add it to the community description
(and propagate to members) once we know that the deploy tx indeed went
through.
To implement this, this commit introduces a new API
`SaveCommunityToken(token *communities.CommunityToken, croppedImage
CroppedImage)` which adds the token to the database only and doesn't
touch the community description.
The `AddCommunityToken` API is then changed that it's exclusively used
for adding an already saved `CommunityToken` to the community
description so it can be published to members. Hence, the signature is
now `AddCommunityToken(communityID string, chainID int, address
string)`, which makes this a breaking change.
Clients that used `AddCommunityToken()` before now need to ensure that
they first call `SaveCommunityToken()` as `AddCommunityToken()` will
fail otherwise.
2023-07-25 11:35:17 +00:00
|
|
|
}
|
|
|
|
|
2023-02-20 11:57:33 +00:00
|
|
|
tokenMetadata := &protobuf.CommunityTokenMetadata{
|
|
|
|
ContractAddresses: map[uint64]string{uint64(token.ChainID): token.Address},
|
|
|
|
Description: token.Description,
|
|
|
|
Image: token.Base64Image,
|
|
|
|
Symbol: token.Symbol,
|
|
|
|
TokenType: token.TokenType,
|
2023-03-07 14:29:16 +00:00
|
|
|
Name: token.Name,
|
2023-06-14 07:47:54 +00:00
|
|
|
Decimals: uint32(token.Decimals),
|
2024-07-02 11:34:18 +00:00
|
|
|
Version: token.Version,
|
2023-02-20 11:57:33 +00:00
|
|
|
}
|
|
|
|
_, err = community.AddCommunityTokensMetadata(tokenMetadata)
|
|
|
|
if err != nil {
|
2023-08-15 17:42:40 +00:00
|
|
|
return nil, err
|
2023-02-20 11:57:33 +00:00
|
|
|
}
|
|
|
|
|
2023-08-22 17:48:42 +00:00
|
|
|
if community.IsControlNode() && (token.PrivilegesLevel == community_token.MasterLevel || token.PrivilegesLevel == community_token.OwnerLevel) {
|
|
|
|
permissionType := protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER
|
|
|
|
if token.PrivilegesLevel == community_token.MasterLevel {
|
|
|
|
permissionType = protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER
|
|
|
|
}
|
|
|
|
|
|
|
|
contractAddresses := make(map[uint64]string)
|
|
|
|
contractAddresses[uint64(token.ChainID)] = token.Address
|
|
|
|
|
|
|
|
tokenCriteria := &protobuf.TokenCriteria{
|
|
|
|
ContractAddresses: contractAddresses,
|
|
|
|
Type: protobuf.CommunityTokenType_ERC721,
|
|
|
|
Symbol: token.Symbol,
|
|
|
|
Name: token.Name,
|
|
|
|
Amount: "1",
|
2024-02-13 10:23:11 +00:00
|
|
|
AmountInWei: "1",
|
|
|
|
Decimals: uint64(0),
|
2023-08-22 17:48:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
request := &requests.CreateCommunityTokenPermission{
|
|
|
|
CommunityID: community.ID(),
|
|
|
|
Type: permissionType,
|
|
|
|
TokenCriteria: []*protobuf.TokenCriteria{tokenCriteria},
|
|
|
|
IsPrivate: true,
|
|
|
|
ChatIds: []string{},
|
|
|
|
}
|
|
|
|
|
|
|
|
community, _, err = m.createCommunityTokenPermission(request, community)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-10-19 11:03:41 +00:00
|
|
|
|
|
|
|
if token.PrivilegesLevel == community_token.OwnerLevel {
|
2023-10-31 14:20:40 +00:00
|
|
|
_, err = m.promoteSelfToControlNode(community, clock)
|
2023-09-21 11:16:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-10-19 11:03:41 +00:00
|
|
|
}
|
2023-08-22 17:48:42 +00:00
|
|
|
}
|
|
|
|
|
2023-08-15 17:42:40 +00:00
|
|
|
return community, m.saveAndPublish(community)
|
2023-01-27 13:27:24 +00:00
|
|
|
}
|
|
|
|
|
2023-07-07 13:03:37 +00:00
|
|
|
func (m *Manager) UpdateCommunityTokenState(chainID int, contractAddress string, deployState community_token.DeployState) error {
|
2023-06-02 08:07:00 +00:00
|
|
|
return m.persistence.UpdateCommunityTokenState(chainID, contractAddress, deployState)
|
|
|
|
}
|
|
|
|
|
2023-07-18 08:33:45 +00:00
|
|
|
func (m *Manager) UpdateCommunityTokenAddress(chainID int, oldContractAddress string, newContractAddress string) error {
|
|
|
|
return m.persistence.UpdateCommunityTokenAddress(chainID, oldContractAddress, newContractAddress)
|
|
|
|
}
|
|
|
|
|
2023-06-21 11:20:43 +00:00
|
|
|
func (m *Manager) UpdateCommunityTokenSupply(chainID int, contractAddress string, supply *bigint.BigInt) error {
|
2023-06-02 08:07:00 +00:00
|
|
|
return m.persistence.UpdateCommunityTokenSupply(chainID, contractAddress, supply)
|
2023-01-27 13:27:24 +00:00
|
|
|
}
|
2023-03-28 14:40:00 +00:00
|
|
|
|
2023-07-24 13:04:11 +00:00
|
|
|
func (m *Manager) RemoveCommunityToken(chainID int, contractAddress string) error {
|
|
|
|
return m.persistence.RemoveCommunityToken(chainID, contractAddress)
|
|
|
|
}
|
|
|
|
|
2023-03-28 14:40:00 +00:00
|
|
|
func (m *Manager) SetCommunityActiveMembersCount(communityID string, activeMembersCount uint64) error {
|
2024-04-16 15:06:27 +00:00
|
|
|
id, err := types.DecodeHex(communityID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
m.communityLock.Lock(id)
|
|
|
|
defer m.communityLock.Unlock(id)
|
|
|
|
|
|
|
|
community, err := m.GetByID(id)
|
2023-03-28 14:40:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
updated, err := community.SetActiveMembersCount(activeMembersCount)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if updated {
|
|
|
|
if err = m.persistence.SaveCommunity(community); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
m.publish(&Subscription{Community: community})
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2023-05-04 22:17:54 +00:00
|
|
|
|
2023-06-06 18:33:09 +00:00
|
|
|
func combineAddressesAndChainIDs(addresses []gethcommon.Address, chainIDs []uint64) []*AccountChainIDsCombination {
|
|
|
|
combinations := make([]*AccountChainIDsCombination, 0)
|
|
|
|
for _, address := range addresses {
|
|
|
|
combinations = append(combinations, &AccountChainIDsCombination{
|
|
|
|
Address: address,
|
|
|
|
ChainIDs: chainIDs,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return combinations
|
|
|
|
}
|
|
|
|
|
|
|
|
func revealedAccountsToAccountsAndChainIDsCombination(revealedAccounts []*protobuf.RevealedAccount) []*AccountChainIDsCombination {
|
|
|
|
accountsAndChainIDs := make([]*AccountChainIDsCombination, 0)
|
|
|
|
for _, revealedAccount := range revealedAccounts {
|
|
|
|
accountsAndChainIDs = append(accountsAndChainIDs, &AccountChainIDsCombination{
|
|
|
|
Address: gethcommon.HexToAddress(revealedAccount.Address),
|
|
|
|
ChainIDs: revealedAccount.ChainIds,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return accountsAndChainIDs
|
|
|
|
}
|
2023-06-14 14:15:46 +00:00
|
|
|
|
2024-05-17 16:15:39 +00:00
|
|
|
func (m *Manager) accountsHasPrivilegedPermission(preParsedCommunityPermissionData *PreParsedCommunityPermissionsData, accounts []*AccountChainIDsCombination) bool {
|
|
|
|
if preParsedCommunityPermissionData != nil {
|
|
|
|
permissionResponse, err := m.PermissionChecker.CheckPermissions(preParsedCommunityPermissionData, accounts, true)
|
2023-06-14 14:15:46 +00:00
|
|
|
if err != nil {
|
2023-08-04 10:28:46 +00:00
|
|
|
m.logger.Warn("check privileged permission failed: %v", zap.Error(err))
|
2023-06-14 14:15:46 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
return permissionResponse.Satisfied
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2024-10-18 02:25:34 +00:00
|
|
|
func (m *Manager) SaveAndPublish(community *Community) error {
|
|
|
|
return m.saveAndPublish(community)
|
|
|
|
}
|
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
func (m *Manager) saveAndPublish(community *Community) error {
|
|
|
|
err := m.persistence.SaveCommunity(community)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-07-26 12:16:50 +00:00
|
|
|
if community.IsControlNode() {
|
2023-07-18 15:06:12 +00:00
|
|
|
m.publish(&Subscription{Community: community})
|
|
|
|
return nil
|
2024-05-08 15:32:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if community.HasPermissionToSendCommunityEvents() {
|
2023-08-08 18:33:29 +00:00
|
|
|
err := m.signEvents(community)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = m.persistence.SaveCommunityEvents(community)
|
2023-07-18 15:06:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
m.publish(&Subscription{CommunityEventsMessage: community.toCommunityEventsMessage()})
|
2023-07-18 15:06:12 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2023-07-26 14:12:53 +00:00
|
|
|
|
2023-08-04 09:49:11 +00:00
|
|
|
func (m *Manager) GetRevealedAddresses(communityID types.HexBytes, memberPk string) ([]*protobuf.RevealedAccount, error) {
|
2024-04-25 01:54:25 +00:00
|
|
|
logger := m.logger.Named("GetRevealedAddresses")
|
|
|
|
|
2023-08-04 09:49:11 +00:00
|
|
|
requestID := CalculateRequestID(memberPk, communityID)
|
2024-04-25 01:54:25 +00:00
|
|
|
response, err := m.persistence.GetRequestToJoinRevealedAddresses(requestID)
|
|
|
|
|
|
|
|
revealedAddresses := make([]string, len(response))
|
|
|
|
for i, acc := range response {
|
|
|
|
revealedAddresses[i] = acc.Address
|
|
|
|
}
|
|
|
|
logger.Debug("Revealed addresses", zap.Any("Addresses:", revealedAddresses))
|
|
|
|
|
|
|
|
return response, err
|
2023-08-04 09:49:11 +00:00
|
|
|
}
|
|
|
|
|
2024-01-16 08:08:56 +00:00
|
|
|
func (m *Manager) handleCommunityTokensMetadata(community *Community) error {
|
2023-08-22 17:48:42 +00:00
|
|
|
communityID := community.IDString()
|
|
|
|
communityTokens := community.CommunityTokensMetadata()
|
|
|
|
|
|
|
|
if len(communityTokens) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
2023-07-07 13:03:37 +00:00
|
|
|
for _, tokenMetadata := range communityTokens {
|
|
|
|
for chainID, address := range tokenMetadata.ContractAddresses {
|
|
|
|
exists, err := m.persistence.HasCommunityToken(communityID, address, int(chainID))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !exists {
|
2023-09-21 12:40:58 +00:00
|
|
|
// Fetch community token to make sure it's stored in the DB, discard result
|
|
|
|
communityToken, err := m.FetchCommunityToken(community, tokenMetadata, chainID, address)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2023-07-07 13:03:37 +00:00
|
|
|
}
|
|
|
|
|
2023-10-04 08:26:38 +00:00
|
|
|
err = m.persistence.AddCommunityToken(communityToken)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-09-21 12:40:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2023-07-07 13:03:37 +00:00
|
|
|
|
2024-04-17 14:53:51 +00:00
|
|
|
func (m *Manager) HandleCommunityGrant(community *Community, grant []byte, clock uint64) (uint64, error) {
|
|
|
|
_, oldClock, err := m.GetCommunityGrant(community.IDString())
|
2024-02-15 19:13:12 +00:00
|
|
|
if err != nil {
|
2024-04-17 14:53:51 +00:00
|
|
|
return 0, err
|
2024-02-15 19:13:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if oldClock >= clock {
|
2024-04-17 14:53:51 +00:00
|
|
|
return 0, ErrGrantOlder
|
|
|
|
}
|
|
|
|
|
|
|
|
verifiedGrant, err := community.VerifyGrantSignature(grant)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !bytes.Equal(verifiedGrant.MemberId, crypto.CompressPubkey(&m.identity.PublicKey)) {
|
|
|
|
return 0, ErrGrantMemberPublicKeyIsDifferent
|
2024-02-15 19:13:12 +00:00
|
|
|
}
|
|
|
|
|
2024-04-17 14:53:51 +00:00
|
|
|
return clock - oldClock, m.persistence.SaveCommunityGrant(community.IDString(), grant, clock)
|
2024-02-15 19:13:12 +00:00
|
|
|
}
|
|
|
|
|
2023-09-21 12:40:58 +00:00
|
|
|
func (m *Manager) FetchCommunityToken(community *Community, tokenMetadata *protobuf.CommunityTokenMetadata, chainID uint64, contractAddress string) (*community_token.CommunityToken, error) {
|
|
|
|
communityID := community.IDString()
|
2023-08-22 17:48:42 +00:00
|
|
|
|
2023-09-21 12:40:58 +00:00
|
|
|
communityToken := &community_token.CommunityToken{
|
|
|
|
CommunityID: communityID,
|
|
|
|
Address: contractAddress,
|
|
|
|
TokenType: tokenMetadata.TokenType,
|
|
|
|
Name: tokenMetadata.Name,
|
|
|
|
Symbol: tokenMetadata.Symbol,
|
|
|
|
Description: tokenMetadata.Description,
|
|
|
|
Transferable: true,
|
|
|
|
RemoteSelfDestruct: false,
|
|
|
|
ChainID: int(chainID),
|
|
|
|
DeployState: community_token.Deployed,
|
|
|
|
Base64Image: tokenMetadata.Image,
|
|
|
|
Decimals: int(tokenMetadata.Decimals),
|
2024-07-02 11:34:18 +00:00
|
|
|
Version: tokenMetadata.Version,
|
2023-09-21 12:40:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch tokenMetadata.TokenType {
|
|
|
|
case protobuf.CommunityTokenType_ERC721:
|
|
|
|
contractData, err := m.communityTokensService.GetCollectibleContractData(chainID, contractAddress)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-07-07 13:03:37 +00:00
|
|
|
|
2023-09-21 12:40:58 +00:00
|
|
|
communityToken.Supply = contractData.TotalSupply
|
|
|
|
communityToken.Transferable = contractData.Transferable
|
|
|
|
communityToken.RemoteSelfDestruct = contractData.RemoteBurnable
|
|
|
|
communityToken.InfiniteSupply = contractData.InfiniteSupply
|
2023-08-22 17:48:42 +00:00
|
|
|
|
2023-09-21 12:40:58 +00:00
|
|
|
case protobuf.CommunityTokenType_ERC20:
|
|
|
|
contractData, err := m.communityTokensService.GetAssetContractData(chainID, contractAddress)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-07-07 13:03:37 +00:00
|
|
|
}
|
2023-09-21 12:40:58 +00:00
|
|
|
|
|
|
|
communityToken.Supply = contractData.TotalSupply
|
|
|
|
communityToken.InfiniteSupply = contractData.InfiniteSupply
|
2023-07-07 13:03:37 +00:00
|
|
|
}
|
2023-09-21 12:40:58 +00:00
|
|
|
|
|
|
|
communityToken.PrivilegesLevel = getPrivilegesLevel(chainID, contractAddress, community.TokenPermissions())
|
|
|
|
|
|
|
|
return communityToken, nil
|
2023-07-07 13:03:37 +00:00
|
|
|
}
|
2023-08-15 15:27:01 +00:00
|
|
|
|
2023-08-17 17:14:23 +00:00
|
|
|
func getPrivilegesLevel(chainID uint64, tokenAddress string, tokenPermissions map[string]*CommunityTokenPermission) community_token.PrivilegesLevel {
|
2023-08-22 17:48:42 +00:00
|
|
|
for _, permission := range tokenPermissions {
|
|
|
|
if permission.Type == protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER || permission.Type == protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER {
|
|
|
|
for _, tokenCriteria := range permission.TokenCriteria {
|
|
|
|
value, exist := tokenCriteria.ContractAddresses[chainID]
|
|
|
|
if exist && value == tokenAddress {
|
|
|
|
if permission.Type == protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER {
|
|
|
|
return community_token.OwnerLevel
|
|
|
|
}
|
|
|
|
return community_token.MasterLevel
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return community_token.CommunityLevel
|
|
|
|
}
|
|
|
|
|
2023-08-18 19:52:13 +00:00
|
|
|
func (m *Manager) ValidateCommunityPrivilegedUserSyncMessage(message *protobuf.CommunityPrivilegedUserSyncMessage) error {
|
2023-08-15 15:27:01 +00:00
|
|
|
if message == nil {
|
|
|
|
return errors.New("invalid CommunityPrivilegedUserSyncMessage message")
|
|
|
|
}
|
|
|
|
|
|
|
|
if message.CommunityId == nil || len(message.CommunityId) == 0 {
|
|
|
|
return errors.New("invalid CommunityId in CommunityPrivilegedUserSyncMessage message")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch message.Type {
|
|
|
|
case protobuf.CommunityPrivilegedUserSyncMessage_CONTROL_NODE_ACCEPT_REQUEST_TO_JOIN:
|
|
|
|
fallthrough
|
|
|
|
case protobuf.CommunityPrivilegedUserSyncMessage_CONTROL_NODE_REJECT_REQUEST_TO_JOIN:
|
|
|
|
if message.RequestToJoin == nil || len(message.RequestToJoin) == 0 {
|
|
|
|
return errors.New("invalid request to join in CommunityPrivilegedUserSyncMessage message")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, requestToJoinProto := range message.RequestToJoin {
|
|
|
|
if len(requestToJoinProto.CommunityId) == 0 {
|
|
|
|
return errors.New("no communityId in request to join in CommunityPrivilegedUserSyncMessage message")
|
|
|
|
}
|
|
|
|
}
|
2023-09-20 08:37:46 +00:00
|
|
|
case protobuf.CommunityPrivilegedUserSyncMessage_CONTROL_NODE_ALL_SYNC_REQUESTS_TO_JOIN:
|
|
|
|
if message.SyncRequestsToJoin == nil || len(message.SyncRequestsToJoin) == 0 {
|
|
|
|
return errors.New("invalid sync requests to join in CommunityPrivilegedUserSyncMessage message")
|
|
|
|
}
|
2024-07-05 13:43:03 +00:00
|
|
|
case protobuf.CommunityPrivilegedUserSyncMessage_CONTROL_NODE_MEMBER_EDIT_SHARED_ADDRESSES:
|
|
|
|
if message.SyncEditSharedAddresses == nil || len(message.CommunityId) == 0 ||
|
|
|
|
len(message.SyncEditSharedAddresses.PublicKey) == 0 || message.SyncEditSharedAddresses.EditSharedAddress == nil {
|
|
|
|
return errors.New("invalid edit shared adresses in CommunityPrivilegedUserSyncMessage message")
|
|
|
|
}
|
2023-08-15 15:27:01 +00:00
|
|
|
}
|
2023-09-20 08:37:46 +00:00
|
|
|
|
2023-08-15 15:27:01 +00:00
|
|
|
return nil
|
|
|
|
}
|
2023-08-22 17:48:42 +00:00
|
|
|
|
|
|
|
func (m *Manager) createCommunityTokenPermission(request *requests.CreateCommunityTokenPermission, community *Community) (*Community, *CommunityChanges, error) {
|
|
|
|
if community == nil {
|
|
|
|
return nil, nil, ErrOrgNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
tokenPermission := request.ToCommunityTokenPermission()
|
|
|
|
tokenPermission.Id = uuid.New().String()
|
2023-08-17 15:19:18 +00:00
|
|
|
changes, err := community.UpsertTokenPermission(&tokenPermission)
|
2023-08-22 17:48:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return community, changes, nil
|
2023-07-05 17:35:22 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2024-07-05 08:38:12 +00:00
|
|
|
func (m *Manager) RemoveUsersWithoutRevealedAccounts(community *Community, clock uint64) (*CommunityChanges, error) {
|
|
|
|
membersAccounts, err := m.persistence.GetCommunityRequestsToJoinRevealedAddresses(community.ID())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
myPk := common.PubkeyToHex(&m.identity.PublicKey)
|
|
|
|
membersToRemove := []string{}
|
|
|
|
for pk := range community.Members() {
|
|
|
|
if myPk == pk {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if _, exists := membersAccounts[pk]; !exists {
|
|
|
|
membersToRemove = append(membersToRemove, pk)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(membersToRemove) > 0 {
|
|
|
|
community.SetResendAccountsClock(clock)
|
|
|
|
}
|
|
|
|
|
|
|
|
return community.RemoveMembersFromOrg(membersToRemove), nil
|
|
|
|
}
|
|
|
|
|
2023-11-07 13:18:59 +00:00
|
|
|
func (m *Manager) PromoteSelfToControlNode(community *Community, clock uint64) (*CommunityChanges, error) {
|
2023-07-05 17:35:22 +00:00
|
|
|
if community == nil {
|
|
|
|
return nil, ErrOrgNotFound
|
|
|
|
}
|
|
|
|
|
2024-04-16 15:06:27 +00:00
|
|
|
m.communityLock.Lock(community.ID())
|
|
|
|
defer m.communityLock.Unlock(community.ID())
|
|
|
|
|
2023-10-31 14:20:40 +00:00
|
|
|
ownerChanged, err := m.promoteSelfToControlNode(community, clock)
|
2023-09-21 11:16:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-10-19 11:03:41 +00:00
|
|
|
|
2023-10-31 14:20:40 +00:00
|
|
|
if ownerChanged {
|
|
|
|
return community.RemoveAllUsersFromOrg(), m.saveAndPublish(community)
|
|
|
|
}
|
|
|
|
|
2024-07-05 08:38:12 +00:00
|
|
|
// if control node device was changed, check that we own all members revealed accounts
|
|
|
|
// members without revealed accounts will be soft kicked
|
|
|
|
changes, err := m.RemoveUsersWithoutRevealedAccounts(community, clock)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return changes, m.saveAndPublish(community)
|
2023-10-19 11:03:41 +00:00
|
|
|
}
|
|
|
|
|
2023-10-31 14:20:40 +00:00
|
|
|
func (m *Manager) promoteSelfToControlNode(community *Community, clock uint64) (bool, error) {
|
|
|
|
ownerChanged := false
|
2023-10-18 15:04:02 +00:00
|
|
|
community.setPrivateKey(m.identity)
|
|
|
|
if !community.ControlNode().Equal(&m.identity.PublicKey) {
|
2023-10-31 14:20:40 +00:00
|
|
|
ownerChanged = true
|
2023-10-18 15:04:02 +00:00
|
|
|
community.setControlNode(&m.identity.PublicKey)
|
2023-07-05 17:35:22 +00:00
|
|
|
}
|
2023-09-21 11:16:05 +00:00
|
|
|
|
|
|
|
// Mark this device as the control node
|
|
|
|
syncControlNode := &protobuf.SyncCommunityControlNode{
|
|
|
|
Clock: clock,
|
|
|
|
InstallationId: m.installationID,
|
|
|
|
}
|
2023-10-31 14:20:40 +00:00
|
|
|
|
2023-09-21 11:16:05 +00:00
|
|
|
err := m.SaveSyncControlNode(community.ID(), syncControlNode)
|
|
|
|
if err != nil {
|
2023-10-31 14:20:40 +00:00
|
|
|
return false, err
|
2023-09-21 11:16:05 +00:00
|
|
|
}
|
|
|
|
community.config.ControlDevice = true
|
|
|
|
|
2023-11-07 13:18:59 +00:00
|
|
|
if exists := community.HasMember(&m.identity.PublicKey); !exists {
|
|
|
|
ownerRole := []protobuf.CommunityMember_Roles{protobuf.CommunityMember_ROLE_OWNER}
|
2024-07-05 13:22:22 +00:00
|
|
|
_, err = community.AddMember(&m.identity.PublicKey, ownerRole, community.Clock())
|
2023-11-07 13:18:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for channelID := range community.Chats() {
|
2024-03-01 17:15:38 +00:00
|
|
|
_, err = community.AddMemberToChat(channelID, &m.identity.PublicKey, ownerRole, protobuf.CommunityMember_CHANNEL_ROLE_POSTER)
|
2023-11-07 13:18:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
_, err = community.AddRoleToMember(&m.identity.PublicKey, protobuf.CommunityMember_ROLE_OWNER)
|
|
|
|
}
|
|
|
|
|
2023-10-31 14:20:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2024-05-10 15:56:40 +00:00
|
|
|
err = m.handleCommunityEvents(community)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2023-09-21 11:16:05 +00:00
|
|
|
community.increaseClock()
|
|
|
|
|
2023-10-31 14:20:40 +00:00
|
|
|
return ownerChanged, nil
|
2023-08-22 17:48:42 +00:00
|
|
|
}
|
2023-09-20 08:37:46 +00:00
|
|
|
|
2024-05-10 15:56:40 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-07-05 13:43:03 +00:00
|
|
|
func (m *Manager) ShareRequestsToJoinWithPrivilegedMembers(community *Community, privilegedMembers map[protobuf.CommunityMember_Roles][]*ecdsa.PublicKey) error {
|
2024-08-07 15:57:02 +00:00
|
|
|
if len(privilegedMembers) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-09-20 08:37:46 +00:00
|
|
|
requestsToJoin, err := m.GetCommunityRequestsToJoinWithRevealedAddresses(community.ID())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var syncRequestsWithoutRevealedAccounts []*protobuf.SyncCommunityRequestsToJoin
|
|
|
|
var syncRequestsWithRevealedAccounts []*protobuf.SyncCommunityRequestsToJoin
|
|
|
|
for _, request := range requestsToJoin {
|
2024-08-07 15:57:02 +00:00
|
|
|
// if shared request to join is not approved by control node - do not send revealed accounts.
|
|
|
|
// revealed accounts will be sent as soon as control node accepts request to join
|
|
|
|
if request.State != RequestToJoinStateAccepted {
|
|
|
|
request.RevealedAccounts = []*protobuf.RevealedAccount{}
|
|
|
|
}
|
2023-09-20 08:37:46 +00:00
|
|
|
syncRequestsWithRevealedAccounts = append(syncRequestsWithRevealedAccounts, request.ToSyncProtobuf())
|
|
|
|
requestProtoWithoutAccounts := request.ToSyncProtobuf()
|
|
|
|
requestProtoWithoutAccounts.RevealedAccounts = []*protobuf.RevealedAccount{}
|
|
|
|
syncRequestsWithoutRevealedAccounts = append(syncRequestsWithoutRevealedAccounts, requestProtoWithoutAccounts)
|
|
|
|
}
|
|
|
|
|
|
|
|
syncMsgWithoutRevealedAccounts := &protobuf.CommunityPrivilegedUserSyncMessage{
|
|
|
|
Type: protobuf.CommunityPrivilegedUserSyncMessage_CONTROL_NODE_ALL_SYNC_REQUESTS_TO_JOIN,
|
|
|
|
CommunityId: community.ID(),
|
|
|
|
SyncRequestsToJoin: syncRequestsWithoutRevealedAccounts,
|
|
|
|
}
|
|
|
|
|
|
|
|
syncMsgWitRevealedAccounts := &protobuf.CommunityPrivilegedUserSyncMessage{
|
|
|
|
Type: protobuf.CommunityPrivilegedUserSyncMessage_CONTROL_NODE_ALL_SYNC_REQUESTS_TO_JOIN,
|
|
|
|
CommunityId: community.ID(),
|
|
|
|
SyncRequestsToJoin: syncRequestsWithRevealedAccounts,
|
|
|
|
}
|
|
|
|
|
2024-07-19 10:34:34 +00:00
|
|
|
subscriptionMsg := &CommunityPrivilegedMemberSyncMessage{}
|
2023-09-20 08:37:46 +00:00
|
|
|
|
2024-07-05 13:43:03 +00:00
|
|
|
for role, members := range privilegedMembers {
|
2023-09-20 08:37:46 +00:00
|
|
|
if len(members) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
subscriptionMsg.Receivers = members
|
|
|
|
|
|
|
|
switch role {
|
|
|
|
case protobuf.CommunityMember_ROLE_ADMIN:
|
|
|
|
subscriptionMsg.CommunityPrivilegedUserSyncMessage = syncMsgWithoutRevealedAccounts
|
|
|
|
case protobuf.CommunityMember_ROLE_OWNER:
|
2023-12-07 16:27:14 +00:00
|
|
|
continue
|
2023-09-20 08:37:46 +00:00
|
|
|
case protobuf.CommunityMember_ROLE_TOKEN_MASTER:
|
|
|
|
subscriptionMsg.CommunityPrivilegedUserSyncMessage = syncMsgWitRevealedAccounts
|
|
|
|
}
|
|
|
|
|
|
|
|
m.publish(&Subscription{CommunityPrivilegedMemberSyncMessage: subscriptionMsg})
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-10-01 14:35:56 +00:00
|
|
|
func (m *Manager) shareAcceptedRequestToJoinWithPrivilegedMembers(community *Community, requestToJoin *RequestToJoin) error {
|
|
|
|
pk, err := common.HexToPubkey(requestToJoin.PublicKey)
|
2023-09-20 08:37:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
acceptedRequestsToJoinWithoutRevealedAccounts := make(map[string]*protobuf.CommunityRequestToJoin)
|
|
|
|
acceptedRequestsToJoinWithRevealedAccounts := make(map[string]*protobuf.CommunityRequestToJoin)
|
|
|
|
|
2024-10-01 14:35:56 +00:00
|
|
|
acceptedRequestsToJoinWithRevealedAccounts[requestToJoin.PublicKey] = requestToJoin.ToCommunityRequestToJoinProtobuf()
|
|
|
|
|
|
|
|
revealedAccounts := requestToJoin.RevealedAccounts
|
|
|
|
requestToJoin.RevealedAccounts = make([]*protobuf.RevealedAccount, 0)
|
|
|
|
acceptedRequestsToJoinWithoutRevealedAccounts[requestToJoin.PublicKey] = requestToJoin.ToCommunityRequestToJoinProtobuf()
|
|
|
|
// Set back the revealed accounts
|
|
|
|
requestToJoin.RevealedAccounts = revealedAccounts
|
2023-09-20 08:37:46 +00:00
|
|
|
|
|
|
|
msgWithRevealedAccounts := &protobuf.CommunityPrivilegedUserSyncMessage{
|
|
|
|
Type: protobuf.CommunityPrivilegedUserSyncMessage_CONTROL_NODE_ACCEPT_REQUEST_TO_JOIN,
|
|
|
|
CommunityId: community.ID(),
|
|
|
|
RequestToJoin: acceptedRequestsToJoinWithRevealedAccounts,
|
|
|
|
}
|
|
|
|
|
|
|
|
msgWithoutRevealedAccounts := &protobuf.CommunityPrivilegedUserSyncMessage{
|
|
|
|
Type: protobuf.CommunityPrivilegedUserSyncMessage_CONTROL_NODE_ACCEPT_REQUEST_TO_JOIN,
|
|
|
|
CommunityId: community.ID(),
|
|
|
|
RequestToJoin: acceptedRequestsToJoinWithoutRevealedAccounts,
|
|
|
|
}
|
|
|
|
|
|
|
|
// do not sent to ourself and to the accepted user
|
|
|
|
skipMembers := make(map[string]struct{})
|
|
|
|
skipMembers[common.PubkeyToHex(&m.identity.PublicKey)] = struct{}{}
|
|
|
|
skipMembers[common.PubkeyToHex(pk)] = struct{}{}
|
|
|
|
|
2024-07-19 10:34:34 +00:00
|
|
|
subscriptionMsg := &CommunityPrivilegedMemberSyncMessage{}
|
2023-09-20 08:37:46 +00:00
|
|
|
|
|
|
|
fileredPrivilegedMembers := community.GetFilteredPrivilegedMembers(skipMembers)
|
|
|
|
for role, members := range fileredPrivilegedMembers {
|
|
|
|
if len(members) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
subscriptionMsg.Receivers = members
|
|
|
|
|
|
|
|
switch role {
|
|
|
|
case protobuf.CommunityMember_ROLE_ADMIN:
|
|
|
|
subscriptionMsg.CommunityPrivilegedUserSyncMessage = msgWithoutRevealedAccounts
|
|
|
|
case protobuf.CommunityMember_ROLE_OWNER:
|
|
|
|
fallthrough
|
|
|
|
case protobuf.CommunityMember_ROLE_TOKEN_MASTER:
|
|
|
|
subscriptionMsg.CommunityPrivilegedUserSyncMessage = msgWithRevealedAccounts
|
|
|
|
}
|
|
|
|
|
|
|
|
m.publish(&Subscription{CommunityPrivilegedMemberSyncMessage: subscriptionMsg})
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) GetCommunityRequestsToJoinWithRevealedAddresses(communityID types.HexBytes) ([]*RequestToJoin, error) {
|
|
|
|
return m.persistence.GetCommunityRequestsToJoinWithRevealedAddresses(communityID)
|
|
|
|
}
|
2023-09-22 17:57:27 +00:00
|
|
|
|
|
|
|
func (m *Manager) SaveCommunity(community *Community) error {
|
|
|
|
return m.persistence.SaveCommunity(community)
|
|
|
|
}
|
2023-08-29 13:17:37 +00:00
|
|
|
|
|
|
|
func (m *Manager) CreateCommunityTokenDeploymentSignature(ctx context.Context, chainID uint64, addressFrom string, communityID string) ([]byte, error) {
|
|
|
|
community, err := m.GetByIDString(communityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !community.IsControlNode() {
|
|
|
|
return nil, ErrNotControlNode
|
|
|
|
}
|
|
|
|
digest, err := m.communityTokensService.DeploymentSignatureDigest(chainID, addressFrom, communityID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return crypto.Sign(digest, community.PrivateKey())
|
|
|
|
}
|
2023-09-21 11:16:05 +00:00
|
|
|
|
|
|
|
func (m *Manager) GetSyncControlNode(id types.HexBytes) (*protobuf.SyncCommunityControlNode, error) {
|
|
|
|
return m.persistence.GetSyncControlNode(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) SaveSyncControlNode(id types.HexBytes, syncControlNode *protobuf.SyncCommunityControlNode) error {
|
|
|
|
return m.persistence.SaveSyncControlNode(id, syncControlNode.Clock, syncControlNode.InstallationId)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) SetSyncControlNode(id types.HexBytes, syncControlNode *protobuf.SyncCommunityControlNode) error {
|
|
|
|
existingSyncControlNode, err := m.GetSyncControlNode(id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if existingSyncControlNode == nil || existingSyncControlNode.Clock < syncControlNode.Clock {
|
|
|
|
return m.SaveSyncControlNode(id, syncControlNode)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2023-10-31 14:20:40 +00:00
|
|
|
|
|
|
|
func (m *Manager) GetCommunityRequestToJoinWithRevealedAddresses(pubKey string, communityID types.HexBytes) (*RequestToJoin, error) {
|
|
|
|
return m.persistence.GetCommunityRequestToJoinWithRevealedAddresses(pubKey, communityID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) SafeGetSignerPubKey(chainID uint64, communityID string) (string, error) {
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
return m.ownerVerifier.SafeGetSignerPubKey(ctx, chainID, communityID)
|
|
|
|
}
|
2023-11-14 21:06:33 +00:00
|
|
|
|
|
|
|
func (m *Manager) GetCuratedCommunities() (*CuratedCommunities, error) {
|
|
|
|
return m.persistence.GetCuratedCommunities()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) SetCuratedCommunities(communities *CuratedCommunities) error {
|
|
|
|
return m.persistence.SetCuratedCommunities(communities)
|
|
|
|
}
|
2023-12-15 11:55:32 +00:00
|
|
|
|
2023-11-29 17:21:21 +00:00
|
|
|
func (m *Manager) encryptCommunityDescriptionImpl(groupID []byte, d *protobuf.CommunityDescription) (string, []byte, error) {
|
|
|
|
payload, err := proto.Marshal(d)
|
|
|
|
if err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
encryptedPayload, ratchet, newSeqNo, err := m.encryptor.EncryptWithHashRatchet(groupID, payload)
|
2024-01-23 16:56:51 +00:00
|
|
|
if err == encryption.ErrNoEncryptionKey {
|
|
|
|
_, err := m.encryptor.GenerateHashRatchetKey(groupID)
|
|
|
|
if err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
encryptedPayload, ratchet, newSeqNo, err = m.encryptor.EncryptWithHashRatchet(groupID, payload)
|
|
|
|
if err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if err != nil {
|
2023-11-29 17:21:21 +00:00
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
keyID, err := ratchet.GetKeyID()
|
|
|
|
if err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
|
2024-05-08 14:49:41 +00:00
|
|
|
m.logger.Debug("encrypting community description",
|
|
|
|
zap.Any("community", d),
|
|
|
|
zap.String("groupID", types.Bytes2Hex(groupID)),
|
|
|
|
zap.String("keyID", types.Bytes2Hex(keyID)))
|
2024-01-23 16:56:51 +00:00
|
|
|
|
2023-11-29 17:21:21 +00:00
|
|
|
keyIDSeqNo := fmt.Sprintf("%s%d", hex.EncodeToString(keyID), newSeqNo)
|
|
|
|
|
|
|
|
return keyIDSeqNo, encryptedPayload, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) encryptCommunityDescription(community *Community, d *protobuf.CommunityDescription) (string, []byte, error) {
|
|
|
|
return m.encryptCommunityDescriptionImpl(community.ID(), d)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) encryptCommunityDescriptionChannel(community *Community, channelID string, d *protobuf.CommunityDescription) (string, []byte, error) {
|
|
|
|
return m.encryptCommunityDescriptionImpl([]byte(community.IDString()+channelID), d)
|
|
|
|
}
|
|
|
|
|
2024-02-22 08:08:58 +00:00
|
|
|
// TODO: add collectiblesManager to messenger intance
|
|
|
|
func (m *Manager) GetCollectiblesManager() CollectiblesManager {
|
|
|
|
return m.collectiblesManager
|
|
|
|
}
|
|
|
|
|
2024-01-23 16:56:51 +00:00
|
|
|
type DecryptCommunityResponse struct {
|
|
|
|
Decrypted bool
|
|
|
|
Description *protobuf.CommunityDescription
|
|
|
|
KeyID []byte
|
|
|
|
GroupID []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) decryptCommunityDescription(keyIDSeqNo string, d []byte) (*DecryptCommunityResponse, error) {
|
2023-11-29 17:21:21 +00:00
|
|
|
const hashHexLength = 64
|
|
|
|
if len(keyIDSeqNo) <= hashHexLength {
|
|
|
|
return nil, errors.New("invalid keyIDSeqNo")
|
|
|
|
}
|
|
|
|
|
|
|
|
keyID, err := hex.DecodeString(keyIDSeqNo[:hashHexLength])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
seqNo, err := strconv.ParseUint(keyIDSeqNo[hashHexLength:], 10, 32)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
decryptedPayload, err := m.encryptor.DecryptWithHashRatchet(keyID, uint32(seqNo), d)
|
2024-01-23 16:56:51 +00:00
|
|
|
if err == encryption.ErrNoRatchetKey {
|
|
|
|
return &DecryptCommunityResponse{
|
|
|
|
KeyID: keyID,
|
|
|
|
}, err
|
|
|
|
|
|
|
|
}
|
2023-11-29 17:21:21 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var description protobuf.CommunityDescription
|
|
|
|
err = proto.Unmarshal(decryptedPayload, &description)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-01-23 16:56:51 +00:00
|
|
|
decryptCommunityResponse := &DecryptCommunityResponse{
|
|
|
|
Decrypted: true,
|
|
|
|
KeyID: keyID,
|
|
|
|
Description: &description,
|
|
|
|
}
|
|
|
|
return decryptCommunityResponse, nil
|
2023-11-29 17:21:21 +00:00
|
|
|
}
|
|
|
|
|
2024-05-31 14:20:36 +00:00
|
|
|
// GetPersistence returns the instantiated *Persistence used by the Manager
|
|
|
|
func (m *Manager) GetPersistence() *Persistence {
|
|
|
|
return m.persistence
|
|
|
|
}
|
|
|
|
|
2023-12-15 11:55:32 +00:00
|
|
|
func ToLinkPreveiwThumbnail(image images.IdentityImage) (*common.LinkPreviewThumbnail, error) {
|
|
|
|
thumbnail := &common.LinkPreviewThumbnail{}
|
|
|
|
|
|
|
|
if image.IsEmpty() {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
width, height, err := images.GetImageDimensions(image.Payload)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to get image dimensions: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
dataURI, err := image.GetDataURI()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to get data uri: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
thumbnail.Width = width
|
|
|
|
thumbnail.Height = height
|
|
|
|
thumbnail.DataURI = dataURI
|
|
|
|
return thumbnail, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Community) ToStatusLinkPreview() (*common.StatusCommunityLinkPreview, error) {
|
|
|
|
communityLinkPreview := &common.StatusCommunityLinkPreview{}
|
|
|
|
if image, ok := c.Images()[images.SmallDimName]; ok {
|
|
|
|
thumbnail, err := ToLinkPreveiwThumbnail(images.IdentityImage{Payload: image.Payload})
|
|
|
|
if err != nil {
|
|
|
|
c.config.Logger.Warn("unfurling status link: failed to set community thumbnail", zap.Error(err))
|
|
|
|
}
|
|
|
|
communityLinkPreview.Icon = *thumbnail
|
|
|
|
}
|
|
|
|
|
|
|
|
if image, ok := c.Images()[images.BannerIdentityName]; ok {
|
|
|
|
thumbnail, err := ToLinkPreveiwThumbnail(images.IdentityImage{Payload: image.Payload})
|
|
|
|
if err != nil {
|
|
|
|
c.config.Logger.Warn("unfurling status link: failed to set community thumbnail", zap.Error(err))
|
|
|
|
}
|
|
|
|
communityLinkPreview.Banner = *thumbnail
|
|
|
|
}
|
|
|
|
|
|
|
|
communityLinkPreview.CommunityID = c.IDString()
|
|
|
|
communityLinkPreview.DisplayName = c.Name()
|
|
|
|
communityLinkPreview.Description = c.DescriptionText()
|
|
|
|
communityLinkPreview.MembersCount = uint32(c.MembersCount())
|
|
|
|
communityLinkPreview.Color = c.Color()
|
|
|
|
|
|
|
|
return communityLinkPreview, nil
|
|
|
|
}
|
2024-07-04 15:54:48 +00:00
|
|
|
|
|
|
|
func (m *Manager) determineChannelsForHRKeysRequest(c *Community, now int64) ([]string, error) {
|
|
|
|
result := []string{}
|
|
|
|
|
|
|
|
channelsWithMissingKeys := func() map[string]struct{} {
|
|
|
|
r := map[string]struct{}{}
|
|
|
|
for id := range c.Chats() {
|
|
|
|
if c.HasMissingEncryptionKey(id) {
|
|
|
|
r[id] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return r
|
|
|
|
}()
|
|
|
|
|
|
|
|
if len(channelsWithMissingKeys) == 0 {
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
requests, err := m.persistence.GetEncryptionKeyRequests(c.ID(), channelsWithMissingKeys)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for channelID := range channelsWithMissingKeys {
|
|
|
|
request, ok := requests[channelID]
|
|
|
|
if !ok {
|
|
|
|
// If there's no prior request, ask for encryption key now
|
|
|
|
result = append(result, channelID)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Exponential backoff formula: initial delay * 2^(requestCount - 1)
|
|
|
|
initialDelay := int64(10 * 60 * 1000) // 10 minutes in milliseconds
|
|
|
|
backoffDuration := initialDelay * (1 << (request.requestedCount - 1))
|
|
|
|
nextRequestTime := request.requestedAt + backoffDuration
|
|
|
|
|
|
|
|
if now >= nextRequestTime {
|
|
|
|
result = append(result, channelID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type CommunityWithChannelIDs struct {
|
|
|
|
Community *Community
|
|
|
|
ChannelIDs []string
|
|
|
|
}
|
|
|
|
|
|
|
|
// DetermineChannelsForHRKeysRequest identifies channels in a community that
|
|
|
|
// should ask for encryption keys based on their current state and past request records,
|
|
|
|
// as determined by exponential backoff.
|
|
|
|
func (m *Manager) DetermineChannelsForHRKeysRequest() ([]*CommunityWithChannelIDs, error) {
|
|
|
|
communities, err := m.Joined()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
result := []*CommunityWithChannelIDs{}
|
|
|
|
now := time.Now().UnixMilli()
|
|
|
|
|
|
|
|
for _, c := range communities {
|
|
|
|
if c.IsControlNode() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
channelsToRequest, err := m.determineChannelsForHRKeysRequest(c, now)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(channelsToRequest) > 0 {
|
|
|
|
result = append(result, &CommunityWithChannelIDs{
|
|
|
|
Community: c,
|
|
|
|
ChannelIDs: channelsToRequest,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) updateEncryptionKeysRequests(communityID types.HexBytes, channelIDs []string, now int64) error {
|
|
|
|
return m.persistence.UpdateAndPruneEncryptionKeyRequests(communityID, channelIDs, now)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) UpdateEncryptionKeysRequests(communityID types.HexBytes, channelIDs []string) error {
|
|
|
|
return m.updateEncryptionKeysRequests(communityID, channelIDs, time.Now().UnixMilli())
|
|
|
|
}
|
2024-09-25 14:08:16 +00:00
|
|
|
|
|
|
|
func unmarshalCommunityDescriptionMessage(signedDescription []byte, signerPubkey *ecdsa.PublicKey) (*protobuf.CommunityDescription, error) {
|
|
|
|
metadata := &protobuf.ApplicationMetadataMessage{}
|
|
|
|
|
|
|
|
err := proto.Unmarshal(signedDescription, metadata)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if metadata.Type != protobuf.ApplicationMetadataMessage_COMMUNITY_DESCRIPTION {
|
|
|
|
return nil, ErrInvalidMessage
|
|
|
|
}
|
|
|
|
|
|
|
|
signer, err := utils.RecoverKey(metadata)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if signer == nil {
|
|
|
|
return nil, errors.New("CommunityDescription does not contain the control node signature")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !signer.Equal(signerPubkey) {
|
|
|
|
return nil, errors.New("CommunityDescription was not signed by an owner")
|
|
|
|
}
|
|
|
|
|
|
|
|
description := &protobuf.CommunityDescription{}
|
|
|
|
|
|
|
|
err = proto.Unmarshal(metadata.Payload, description)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return description, nil
|
|
|
|
}
|