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"
2023-03-01 23:56:03 +00:00
"net"
2022-03-21 14:18:36 +00:00
"os"
2022-04-22 07:42:22 +00:00
"sort"
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
2022-03-21 14:18:36 +00:00
"github.com/anacrolix/torrent"
"github.com/anacrolix/torrent/bencode"
"github.com/anacrolix/torrent/metainfo"
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"
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
)
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
2023-03-20 12:26:20 +00:00
var memberPermissionsCheckInterval = 1 * 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-03-06 08:33:52 +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
stdoutLogger * zap . Logger
transport * transport . Transport
timesource common . TimeSource
quit chan struct { }
torrentConfig * params . TorrentConfig
torrentClient * torrent . Client
walletConfig * params . WalletConfig
2024-03-14 08:39:06 +00:00
communityTokensService CommunityTokensServiceInterface
2024-03-06 08:33:52 +00:00
historyArchiveTasksWaitGroup sync . WaitGroup
historyArchiveTasks sync . Map // stores `chan struct{}`
membersReevaluationTasks sync . Map // stores `membersReevaluationTask`
2024-05-09 19:59:51 +00:00
forceMembersReevaluation map [ string ] chan struct { }
2024-03-06 08:33:52 +00:00
torrentTasks map [ string ] metainfo . Hash
historyArchiveDownloadTasks map [ string ] * HistoryArchiveDownloadTask
stopped bool
RekeyInterval time . Duration
PermissionChecker PermissionChecker
keyDistributor KeyDistributor
communityLock * CommunityLock
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
}
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 {
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 {
2023-06-06 18:33:09 +00:00
GetBalancesByChain ( ctx context . Context , accounts , tokens [ ] gethcommon . Address , chainIDs [ ] uint64 ) ( map [ uint64 ] map [ gethcommon . Address ] map [ gethcommon . Address ] * hexutil . Big , 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 )
}
2023-04-25 12:00:17 +00:00
type DefaultTokenManager struct {
tokenManager * token . Manager
}
func NewDefaultTokenManager ( tm * token . Manager ) * DefaultTokenManager {
return & DefaultTokenManager { tokenManager : tm }
}
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 ) {
2023-04-25 12:00:17 +00:00
networks , err := m . tokenManager . RPCClient . NetworkManager . Get ( false )
if err != nil {
return nil , err
}
2024-02-22 15:17:35 +00:00
areTestNetworksEnabled , err := m . tokenManager . RPCClient . NetworkManager . GetTestNetworksEnabled ( )
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-02-22 08:08:58 +00:00
GetCollectibleOwnership ( id thirdparty . CollectibleUniqueID ) ( [ ] thirdparty . AccountBalance , 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
}
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 )
}
2023-11-29 17:21:21 +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 , torrentConfig * params . TorrentConfig , 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" )
}
}
2022-10-06 20:51:04 +00:00
stdoutLogger , err := zap . NewDevelopment ( )
if err != nil {
return nil , errors . Wrap ( err , "failed to create archive 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 {
2023-05-05 18:13:55 +00:00
logger : logger ,
stdoutLogger : stdoutLogger ,
encryptor : encryptor ,
identity : identity ,
2023-09-21 11:16:05 +00:00
installationID : installationID ,
2023-07-05 17:35:22 +00:00
ownerVerifier : ownerVerifier ,
2023-05-05 18:13:55 +00:00
quit : make ( chan struct { } ) ,
transport : transport ,
2023-09-28 15:37:03 +00:00
timesource : timesource ,
2023-05-05 18:13:55 +00:00
torrentConfig : torrentConfig ,
torrentTasks : make ( map [ string ] metainfo . Hash ) ,
historyArchiveDownloadTasks : make ( map [ string ] * HistoryArchiveDownloadTask ) ,
2023-11-29 17:21:21 +00:00
keyDistributor : keyDistributor ,
2024-02-23 02:16:51 +00:00
communityLock : NewCommunityLock ( logger ) ,
2023-11-30 12:25:31 +00:00
}
manager . persistence = & Persistence {
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 {
2021-01-11 10:32:51 +00:00
2023-07-05 17:35:22 +00:00
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
}
2022-10-06 20:51:04 +00:00
func ( m * Manager ) LogStdout ( msg string , fields ... zap . Field ) {
m . stdoutLogger . Info ( msg , fields ... )
m . logger . Debug ( msg , fields ... )
}
2022-04-22 07:42:22 +00:00
type archiveMDSlice [ ] * archiveMetadata
type archiveMetadata struct {
hash string
from uint64
}
func ( md archiveMDSlice ) Len ( ) int {
return len ( md )
}
func ( md archiveMDSlice ) Swap ( i , j int ) {
md [ i ] , md [ j ] = md [ j ] , md [ i ]
}
func ( md archiveMDSlice ) Less ( i , j int ) bool {
return md [ i ] . from > md [ j ] . from
}
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
}
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-06 15:54:35 +00:00
return nil
}
2023-07-05 17:35:22 +00:00
2024-05-06 15:54:35 +00:00
func ( m * Manager ) SetOnline ( online bool ) {
if online {
if m . torrentConfig != nil && m . torrentConfig . Enabled && ! m . TorrentClientStarted ( ) {
err := m . StartTorrentClient ( )
if err != nil {
m . LogStdout ( "couldn't start torrent client" , zap . Error ( err ) )
}
2022-12-09 09:37:04 +00:00
}
2022-03-21 14:18:36 +00:00
}
2021-01-11 10:32:51 +00:00
}
func ( m * Manager ) runENSVerificationLoop ( ) {
go func ( ) {
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
}
}
} ( )
}
// 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 ( ) {
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
m . StopTorrentClient ( )
return nil
}
func ( m * Manager ) SetTorrentConfig ( config * params . TorrentConfig ) {
m . torrentConfig = config
}
2023-03-01 23:56:03 +00:00
// getTCPandUDPport will return the same port number given if != 0,
// otherwise, it will attempt to find a free random tcp and udp port using
// the same number for both protocols
func ( m * Manager ) getTCPandUDPport ( portNumber int ) ( int , error ) {
if portNumber != 0 {
return portNumber , nil
}
// Find free port
for i := 0 ; i < 10 ; i ++ {
2023-11-22 18:50:19 +00:00
port := func ( ) int {
tcpAddr , err := net . ResolveTCPAddr ( "tcp" , net . JoinHostPort ( "localhost" , "0" ) )
if err != nil {
m . logger . Warn ( "unable to resolve tcp addr: %v" , zap . Error ( err ) )
return 0
}
2023-03-01 23:56:03 +00:00
2023-11-22 18:50:19 +00:00
tcpListener , err := net . ListenTCP ( "tcp" , tcpAddr )
if err != nil {
m . logger . Warn ( "unable to listen on addr" , zap . Stringer ( "addr" , tcpAddr ) , zap . Error ( err ) )
return 0
}
defer tcpListener . Close ( )
2023-03-01 23:56:03 +00:00
2023-11-22 18:50:19 +00:00
port := tcpListener . Addr ( ) . ( * net . TCPAddr ) . Port
2023-03-01 23:56:03 +00:00
2023-11-22 18:50:19 +00:00
udpAddr , err := net . ResolveUDPAddr ( "udp" , net . JoinHostPort ( "localhost" , fmt . Sprintf ( "%d" , port ) ) )
if err != nil {
m . logger . Warn ( "unable to resolve udp addr: %v" , zap . Error ( err ) )
return 0
}
2023-03-01 23:56:03 +00:00
2023-11-22 18:50:19 +00:00
udpListener , err := net . ListenUDP ( "udp" , udpAddr )
if err != nil {
m . logger . Warn ( "unable to listen on addr" , zap . Stringer ( "addr" , udpAddr ) , zap . Error ( err ) )
return 0
}
defer udpListener . Close ( )
2023-03-01 23:56:03 +00:00
2023-11-22 18:50:19 +00:00
return port
} ( )
2023-03-01 23:56:03 +00:00
2023-11-22 18:50:19 +00:00
if port != 0 {
return port , nil
}
2023-03-01 23:56:03 +00:00
}
return 0 , fmt . Errorf ( "no free port found" )
}
2022-03-21 14:18:36 +00:00
func ( m * Manager ) StartTorrentClient ( ) error {
if m . torrentConfig == nil {
2023-03-01 23:56:03 +00:00
return fmt . Errorf ( "can't start torrent client: missing torrentConfig" )
2022-03-21 14:18:36 +00:00
}
if m . TorrentClientStarted ( ) {
return nil
}
2023-03-01 23:56:03 +00:00
port , err := m . getTCPandUDPport ( m . torrentConfig . Port )
if err != nil {
return err
}
2022-03-21 14:18:36 +00:00
config := torrent . NewDefaultClientConfig ( )
2023-03-01 23:56:03 +00:00
config . SetListenAddr ( ":" + fmt . Sprint ( port ) )
2022-03-21 14:18:36 +00:00
config . Seed = true
config . DataDir = m . torrentConfig . DataDir
if _ , err := os . Stat ( m . torrentConfig . DataDir ) ; os . IsNotExist ( err ) {
err := os . MkdirAll ( m . torrentConfig . DataDir , 0700 )
if err != nil {
return err
}
}
2023-03-01 23:56:03 +00:00
m . logger . Info ( "Starting torrent client" , zap . Any ( "port" , port ) )
2022-03-21 14:18:36 +00:00
// Instantiating the client will make it bootstrap and listen eagerly,
// so no go routine is needed here
client , err := torrent . NewClient ( config )
if err != nil {
return err
}
m . torrentClient = client
2020-11-18 09:16:51 +00:00
return nil
}
2022-03-21 14:18:36 +00:00
func ( m * Manager ) StopTorrentClient ( ) [ ] error {
if m . TorrentClientStarted ( ) {
m . StopHistoryArchiveTasksIntervals ( )
m . logger . Info ( "Stopping torrent client" )
errs := m . torrentClient . Close ( )
if len ( errs ) > 0 {
return errs
}
m . torrentClient = nil
}
return make ( [ ] error , 0 )
}
func ( m * Manager ) TorrentClientStarted ( ) bool {
return m . torrentClient != 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 ( ) ,
2022-10-14 09:26:10 +00:00
MemberIdentity : & m . identity . PublicKey ,
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
}
community , err := New ( config , m . timesource , descriptionEncryptor )
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-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-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
m . communityLock . Lock ( communityID )
defer m . communityLock . Unlock ( communityID )
community , err := m . GetByID ( communityID )
if err != nil {
return nil , nil , err
}
// TODO: Control node needs to be notified to do a permission check if TokenMasters did airdrop
// of the token which is using in a community permissions
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-17 16:15:39 +00:00
hasMemberPermissions := communityPermissionsPreParsedData [ protobuf . CommunityTokenPermission_BECOME_MEMBER ] != nil
if len ( channelPermissionsPreParsedData ) == 0 {
community . PopulateChannelsWithAllMembers ( )
}
2023-03-20 12:26:20 +00:00
2023-09-20 08:37:46 +00:00
newPrivilegedRoles := make ( map [ protobuf . CommunityMember_Roles ] [ ] * ecdsa . PublicKey )
newPrivilegedRoles [ protobuf . CommunityMember_ROLE_TOKEN_MASTER ] = [ ] * ecdsa . PublicKey { }
newPrivilegedRoles [ protobuf . CommunityMember_ROLE_ADMIN ] = [ ] * ecdsa . PublicKey { }
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
}
2023-09-20 08:37:46 +00:00
isCurrentRoleTokenMaster := community . IsMemberTokenMaster ( memberPubKey )
isCurrentRoleAdmin := community . IsMemberAdmin ( memberPubKey )
2023-08-04 09:49:11 +00:00
2024-05-17 16:15:39 +00:00
revealedAccount , exists := membersAccounts [ memberKey ]
memberHasWallet := exists
2023-06-14 14:15:46 +00:00
2023-08-04 10:28:46 +00:00
// Check if user has privilege role without sharing the account to controlNode
2023-06-14 14:15:46 +00:00
// or user treated as a member without wallet in closed community
2023-09-20 08:37:46 +00:00
if ! memberHasWallet && ( hasMemberPermissions || isCurrentRoleTokenMaster || isCurrentRoleAdmin ) {
2023-06-14 14:15:46 +00:00
_ , err = community . RemoveUserFromOrg ( memberPubKey )
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
}
2023-06-14 14:15:46 +00:00
continue
}
2023-03-20 10:36:32 +00:00
2024-05-17 16:15:39 +00:00
accountsAndChainIDs := revealedAccountsToAccountsAndChainIDsCombination ( revealedAccount )
2023-04-25 12:00:17 +00:00
2024-05-17 16:15:39 +00:00
isNewRoleTokenMaster , err := m . ReevaluatePrivilegedMember (
community ,
communityPermissionsPreParsedData [ protobuf . CommunityTokenPermission_BECOME_TOKEN_MASTER ] ,
accountsAndChainIDs ,
memberPubKey ,
2023-09-20 08:37:46 +00:00
protobuf . CommunityMember_ROLE_TOKEN_MASTER , isCurrentRoleTokenMaster )
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
}
2023-09-20 08:37:46 +00:00
if isNewRoleTokenMaster {
if ! isCurrentRoleTokenMaster {
newPrivilegedRoles [ protobuf . CommunityMember_ROLE_TOKEN_MASTER ] =
append ( newPrivilegedRoles [ protobuf . CommunityMember_ROLE_TOKEN_MASTER ] , memberPubKey )
}
2023-08-04 10:28:46 +00:00
// Skip further validation if user has TokenMaster permissions
continue
}
2024-05-17 16:15:39 +00:00
isNewRoleAdmin , err := m . ReevaluatePrivilegedMember (
community ,
communityPermissionsPreParsedData [ protobuf . CommunityTokenPermission_BECOME_ADMIN ] ,
accountsAndChainIDs ,
memberPubKey ,
2023-09-20 08:37:46 +00:00
protobuf . CommunityMember_ROLE_ADMIN , isCurrentRoleAdmin )
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
}
2023-09-20 08:37:46 +00:00
if isNewRoleAdmin {
if ! isCurrentRoleAdmin {
newPrivilegedRoles [ protobuf . CommunityMember_ROLE_ADMIN ] =
2024-03-19 17:14:24 +00:00
append ( newPrivilegedRoles [ protobuf . CommunityMember_ROLE_ADMIN ] , memberPubKey )
2023-09-20 08:37:46 +00:00
}
2023-08-04 10:28:46 +00:00
// Skip further validation if user has Admin permissions
2023-06-14 14:15:46 +00:00
continue
}
2023-06-23 10:49:26 +00:00
if hasMemberPermissions {
2024-05-17 16:15:39 +00:00
permissionResponse , err := m . PermissionChecker . CheckPermissions (
communityPermissionsPreParsedData [ protobuf . CommunityTokenPermission_BECOME_MEMBER ] ,
accountsAndChainIDs ,
true )
2023-06-23 10:49:26 +00:00
if err != nil {
2024-05-08 15:32:46 +00:00
return nil , nil , err
2023-06-23 10:49:26 +00:00
}
if ! permissionResponse . Satisfied {
_ , err = community . RemoveUserFromOrg ( memberPubKey )
if err != nil {
2024-05-08 15:32:46 +00:00
return nil , nil , err
2023-06-23 10:49:26 +00:00
}
// Skip channels validation if user has been removed
continue
}
2023-06-14 14:15:46 +00:00
}
2024-05-17 16:15:39 +00:00
err = m . reevaluateMemberChannelsPermissions ( community , memberPubKey , channelPermissionsPreParsedData , accountsAndChainIDs )
if err != nil {
return nil , nil , err
}
}
2023-06-23 10:49:26 +00:00
2024-05-17 16:15:39 +00:00
return community , newPrivilegedRoles , m . saveAndPublish ( community )
}
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 ,
channelPermissionsPreParsedData map [ string ] * PreParsedCommunityPermissionsData , accountsAndChainIDs [ ] * AccountChainIDsCombination ) error {
2023-06-23 10:49:26 +00:00
2024-05-17 16:15:39 +00:00
if len ( channelPermissionsPreParsedData ) == 0 {
return nil
}
// check which permissions we satisfy and which not
channelPermissionsCheckResult , err := m . checkChannelsPermissions ( channelPermissionsPreParsedData , accountsAndChainIDs , true )
if err != nil {
return err
}
2023-06-23 10:49:26 +00:00
2024-05-17 16:15:39 +00:00
for channelID := range community . Chats ( ) {
chatID := community . ChatID ( channelID )
isMemberAlreadyInChannel := community . IsMemberInChat ( memberPubKey , channelID )
2023-06-23 10:49:26 +00:00
2024-05-17 16:15:39 +00:00
channelPermissionsCheckResult , exists := channelPermissionsCheckResult [ chatID ]
2024-03-08 19:46:59 +00:00
2024-05-17 16:15:39 +00:00
// if channel permissions were removed member must be added back
if ! exists {
if ! isMemberAlreadyInChannel {
_ , err := community . AddMemberToChat ( channelID , memberPubKey , [ ] protobuf . CommunityMember_Roles { } , protobuf . CommunityMember_CHANNEL_ROLE_POSTER )
2023-06-23 10:49:26 +00:00
if err != nil {
2024-05-17 16:15:39 +00:00
return err
2023-06-23 10:49:26 +00:00
}
}
2024-05-17 16:15:39 +00:00
continue
}
viewAndPostSatisfied , viewAndPosPermissionExists := channelPermissionsCheckResult [ protobuf . CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL ]
viewOnlySatisfied , viewOnlyPermissionExists := channelPermissionsCheckResult [ protobuf . CommunityTokenPermission_CAN_VIEW_CHANNEL ]
satisfied := false
channelRole := protobuf . CommunityMember_CHANNEL_ROLE_VIEWER
if viewAndPosPermissionExists && viewAndPostSatisfied {
satisfied = viewAndPostSatisfied
channelRole = protobuf . CommunityMember_CHANNEL_ROLE_POSTER
} else if ! satisfied && viewOnlyPermissionExists {
satisfied = viewOnlySatisfied
}
if satisfied {
// Add the member back to the chat member list in case the role changed (it replaces the previous values)
_ , err := community . AddMemberToChat ( channelID , memberPubKey , [ ] protobuf . CommunityMember_Roles { } , channelRole )
if err != nil {
return err
}
} else if ! satisfied && isMemberAlreadyInChannel {
_ , err := community . RemoveUserFromChat ( memberPubKey , channelID )
if err != nil {
return err
}
2023-03-20 10:36:32 +00:00
}
2023-03-20 12:26:20 +00:00
}
2024-05-17 16:15:39 +00:00
return nil
}
2023-06-14 14:15:46 +00:00
2024-05-17 16:15:39 +00:00
func ( m * Manager ) checkChannelsPermissions ( channelsPermissionsPreParsedData map [ string ] * PreParsedCommunityPermissionsData , accountsAndChainIDs [ ] * AccountChainIDsCombination , shortcircuit bool ) ( map [ string ] map [ protobuf . CommunityTokenPermission_Type ] bool , error ) {
channelPermissionsCheckResult := make ( map [ string ] map [ protobuf . CommunityTokenPermission_Type ] bool )
for _ , channelsPermissionPreParsedData := range channelsPermissionsPreParsedData {
permissionResponse , err := m . PermissionChecker . CheckPermissions ( channelsPermissionPreParsedData , accountsAndChainIDs , true )
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-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-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 ( )
// Ensure reevaluation is performed not more often than once per minute
if ! force && task . lastSuccessTime . After ( time . Now ( ) . Add ( - 1 * time . Minute ) ) {
return false
}
if ! task . lastSuccessTime . Before ( time . Now ( ) . Add ( - memberPermissionsCheckInterval ) ) &&
! task . lastSuccessTime . Before ( task . onDemandRequestTime ) {
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-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 ( )
defer task . mutex . Unlock ( )
2024-05-08 15:32:46 +00:00
task . lastSuccessTime = time . Now ( )
2024-05-17 13:06:33 +00:00
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 {
return errors . New ( "reevaluation task doesn't exist" )
}
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
}
2023-09-20 08:37:46 +00:00
return m . shareRequestsToJoinWithNewPrivilegedMembers ( 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 ( ) ,
2022-10-14 09:26:10 +00:00
MemberIdentity : & m . identity . PublicKey ,
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
}
community , err = New ( config , m . timesource , descriptionEncryptor )
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
}
// Remove communityID prefix from chatID if exists
if strings . HasPrefix ( chatID , communityID . String ( ) ) {
chatID = strings . TrimPrefix ( chatID , communityID . String ( ) )
}
2023-06-14 14:15:46 +00:00
changes , err := community . DeleteChat ( chatID )
2021-07-30 17:05:44 +00:00
if err != nil {
return nil , nil , err
}
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
}
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
}
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 ,
MemberIdentity : & m . identity . PublicKey ,
2023-07-05 17:35:22 +00:00
ID : pubKey ,
ControlNode : signer ,
2023-11-17 23:35:29 +00:00
Shard : shard . FromProtobuff ( communityShard ) ,
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
}
community , err = New ( config , m . timesource , descriptionEncryptor )
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
hydrateChannelsMembers ( types . EncodeHex ( id ) , description )
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
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
}
if changes . HasMemberLeft ( pkString ) {
2023-10-27 19:20:08 +00:00
// If we joined previously the community, that means we have been kicked
changes . MemberKicked = community . Joined ( )
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
}
2023-07-18 15:06:12 +00:00
err = m . saveAndPublish ( community )
2023-04-21 09:18:47 +00:00
if err != nil {
return err
}
return nil
}
// UpdateClockInRequestToJoin method is used for testing
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 }
}
2023-08-04 09:49:11 +00:00
_ , err = community . AddMember ( pk , memberRoles )
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 }
if err = m . shareRequestsToJoinWithNewPrivilegedMembers ( community , newPrivilegedMember ) ; err != nil {
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
2023-07-10 14:11:37 +00:00
if err := community . ValidateEditSharedAddresses ( signer , request ) ; err != nil {
return err
}
// 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" )
}
}
2023-08-04 09:49:11 +00:00
requestToJoin := & RequestToJoin {
PublicKey : common . PubkeyToHex ( signer ) ,
CommunityID : community . ID ( ) ,
RevealedAccounts : request . RevealedAccounts ,
}
requestToJoin . CalculateID ( )
err = m . persistence . RemoveRequestToJoinRevealedAddresses ( requestToJoin . ID )
if err != nil {
return err
}
2023-08-29 18:56:30 +00:00
err = m . persistence . SaveRequestToJoinRevealedAddresses ( requestToJoin . ID , requestToJoin . RevealedAccounts )
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 } )
}
return nil
}
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-17 16:15:39 +00:00
func ( m * Manager ) checkChannelPermissions ( viewOnlyPreParsedPermissions * PreParsedCommunityPermissionsData , viewAndPostPreParsedPermissions * PreParsedCommunityPermissionsData , accountsAndChainIDs [ ] * AccountChainIDsCombination , shortcircuit bool ) ( * CheckChannelPermissionsResponse , error ) {
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-17 16:15:39 +00:00
viewOnlyPermissionsResponse , err := m . PermissionChecker . CheckPermissions ( viewOnlyPreParsedPermissions , accountsAndChainIDs , shortcircuit )
2023-06-12 15:17:37 +00:00
if err != nil {
return nil , err
}
2024-05-17 16:15:39 +00:00
viewAndPostPermissionsResponse , err := m . PermissionChecker . CheckPermissions ( viewAndPostPreParsedPermissions , accountsAndChainIDs , shortcircuit )
2023-06-12 15:17:37 +00:00
if err != nil {
return nil , err
}
2024-05-17 16:15:39 +00:00
hasViewOnlyPermissions := viewOnlyPreParsedPermissions != nil
hasViewAndPostPermissions := viewAndPostPreParsedPermissions != nil
2023-06-12 15:17:37 +00:00
if ( hasViewAndPostPermissions && ! hasViewOnlyPermissions ) || ( hasViewOnlyPermissions && hasViewAndPostPermissions && viewAndPostPermissionsResponse . Satisfied ) {
response . ViewOnlyPermissions . Satisfied = viewAndPostPermissionsResponse . Satisfied
} else {
response . ViewOnlyPermissions . Satisfied = viewOnlyPermissionsResponse . Satisfied
}
response . ViewOnlyPermissions . Permissions = viewOnlyPermissionsResponse . Permissions
2024-03-29 17:53:05 +00:00
if hasViewOnlyPermissions && ! hasViewAndPostPermissions {
2023-06-12 15:17:37 +00:00
response . ViewAndPostPermissions . Satisfied = false
} else {
response . ViewAndPostPermissions . Satisfied = viewAndPostPermissionsResponse . Satisfied
}
response . ViewAndPostPermissions . Permissions = viewAndPostPermissionsResponse . Permissions
return response , nil
}
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 )
response := & CheckAllChannelsPermissionsResponse {
Channels : make ( map [ string ] * CheckChannelPermissionsResponse ) ,
}
2024-05-17 16:15:39 +00:00
// TODO: optimize
2023-06-13 12:50:15 +00:00
for channelID := range channels {
viewOnlyPermissions := community . ChannelTokenPermissionsByType ( community . IDString ( ) + channelID , protobuf . CommunityTokenPermission_CAN_VIEW_CHANNEL )
viewAndPostPermissions := community . ChannelTokenPermissionsByType ( community . IDString ( ) + channelID , protobuf . CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL )
2024-05-17 16:15:39 +00:00
viewOnlyPreParsedPermissions := preParsedCommunityPermissionsData ( viewOnlyPermissions )
viewAndPostPreParsedPermissions := preParsedCommunityPermissionsData ( viewAndPostPermissions )
checkChannelPermissionsResponse , err := m . checkChannelPermissions ( viewOnlyPreParsedPermissions , viewAndPostPreParsedPermissions , accountsAndChainIDs , false )
2023-06-13 12:50:15 +00:00
if err != nil {
return nil , err
}
2023-06-22 06:54:58 +00:00
err = m . persistence . SaveCheckChannelPermissionResponse ( community . IDString ( ) , community . IDString ( ) + channelID , checkChannelPermissionsResponse )
if err != nil {
return nil , err
}
2023-06-13 12:50:15 +00:00
response . Channels [ community . IDString ( ) + channelID ] = checkChannelPermissionsResponse
}
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-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
}
communityDescriptionBytes , err := proto . Marshal ( request . Community )
if err != nil {
2023-02-03 16:33:16 +00:00
return nil , err
2022-07-01 13:54:02 +00:00
}
// We need to wrap `request.Community` in an `ApplicationMetadataMessage`
// of type `CommunityDescription` because `UpdateCommunityDescription` expects this.
//
// This is merely for marsheling/unmarsheling, hence we attaching a `Signature`
// is not needed.
metadataMessage := & protobuf . ApplicationMetadataMessage {
Payload : communityDescriptionBytes ,
Type : protobuf . ApplicationMetadataMessage_COMMUNITY_DESCRIPTION ,
}
appMetadataMsg , err := proto . Marshal ( metadataMessage )
if err != nil {
2023-02-03 16:33:16 +00:00
return nil , err
2022-07-01 13:54:02 +00:00
}
2023-11-23 15:58:43 +00:00
isControlNodeSigner := common . IsPubKeyEqual ( community . ControlNode ( ) , signer )
2023-08-22 17:48:42 +00:00
if ! isControlNodeSigner {
2023-06-14 14:15:46 +00:00
return nil , ErrNotAuthorized
}
2024-02-29 09:51:38 +00:00
_ , processedDescription , err := m . preprocessDescription ( community . ID ( ) , request . Community )
2023-11-29 17:21:21 +00:00
if err != nil {
return nil , err
}
2024-02-29 09:51:38 +00:00
_ , err = community . UpdateCommunityDescription ( processedDescription , appMetadataMsg , nil )
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-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
}
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
err = m . persistence . SaveCommunity ( community )
2023-07-18 15:06:12 +00:00
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
}
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 )
}
2022-10-25 22:06:20 +00:00
func ( m * Manager ) GetRequestToJoinIDByPkAndCommunityID ( pk * ecdsa . PublicKey , communityID [ ] byte ) ( [ ] byte , error ) {
return m . persistence . GetRequestToJoinIDByPkAndCommunityID ( common . PubkeyToHex ( pk ) , communityID )
}
2023-11-29 15:28:04 +00:00
func ( m * Manager ) GetCommunityRequestToJoinClock ( pk * ecdsa . PublicKey , communityID string ) ( uint64 , error ) {
request , err := m . persistence . GetRequestToJoinByPkAndCommunityID ( common . PubkeyToHex ( pk ) , [ ] byte ( communityID ) )
if errors . Is ( err , sql . ErrNoRows ) {
return 0 , nil
} else if err != nil {
return 0 , err
}
if request == nil || request . State != RequestToJoinStateAccepted {
return 0 , nil
}
return request . Clock , nil
}
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
}
2023-06-14 14:15:46 +00:00
_ , err = community . AddMember ( pk , [ ] protobuf . CommunityMember_Roles { protobuf . CommunityMember_ROLE_OWNER } )
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
}
return recordBundleToCommunity ( r , & m . identity . PublicKey , m . installationID , m . logger , m . timesource , descriptionEncryptor , 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 {
eventsDescription , err := validateAndGetEventsMessageCommunityDescription ( community . config . EventsData . EventsBaseCommunityDescription , community . ControlNode ( ) )
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
} )
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-04-03 14:49:57 +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 ,
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-12-09 12:46:30 +00:00
func ( m * Manager ) CanceledRequestToJoinForUserForCommunityID ( pk * ecdsa . PublicKey , communityID [ ] byte ) ( * RequestToJoin , error ) {
return m . persistence . CanceledRequestToJoinForUserForCommunityID ( common . PubkeyToHex ( pk ) , communityID )
}
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
}
2022-03-21 14:18:36 +00:00
func ( m * Manager ) GetCommunityChatsFilters ( communityID types . HexBytes ) ( [ ] * transport . Filter , error ) {
chatIDs , err := m . persistence . GetCommunityChatIDs ( communityID )
if err != nil {
return nil , err
}
filters := [ ] * transport . Filter { }
for _ , cid := range chatIDs {
filters = append ( filters , m . transport . FilterByChatID ( cid ) )
}
return filters , nil
}
func ( m * Manager ) GetCommunityChatsTopics ( communityID types . HexBytes ) ( [ ] types . TopicType , error ) {
filters , err := m . GetCommunityChatsFilters ( communityID )
if err != nil {
return nil , err
}
topics := [ ] types . TopicType { }
for _ , filter := range filters {
2023-05-22 21:38:02 +00:00
topics = append ( topics , filter . ContentTopic )
2022-03-21 14:18:36 +00:00
}
return topics , nil
}
2022-03-09 09:58:05 +00:00
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 )
}
func ( m * Manager ) GetOldestWakuMessageTimestamp ( topics [ ] types . TopicType ) ( uint64 , error ) {
return m . persistence . GetOldestWakuMessageTimestamp ( topics )
}
func ( m * Manager ) GetLastMessageArchiveEndDate ( communityID types . HexBytes ) ( uint64 , error ) {
return m . persistence . GetLastMessageArchiveEndDate ( communityID )
}
func ( m * Manager ) GetHistoryArchivePartitionStartTimestamp ( communityID types . HexBytes ) ( uint64 , error ) {
filters , err := m . GetCommunityChatsFilters ( communityID )
if err != nil {
2022-10-06 20:51:04 +00:00
m . LogStdout ( "failed to get community chats filters" , zap . Error ( err ) )
2022-03-21 14:18:36 +00:00
return 0 , err
}
if len ( filters ) == 0 {
// If we don't have chat filters, we likely don't have any chats
// associated to this community, which means there's nothing more
// to do here
return 0 , nil
}
topics := [ ] types . TopicType { }
for _ , filter := range filters {
2023-05-22 21:38:02 +00:00
topics = append ( topics , filter . ContentTopic )
2022-03-21 14:18:36 +00:00
}
lastArchiveEndDateTimestamp , err := m . GetLastMessageArchiveEndDate ( communityID )
if err != nil {
2022-10-06 20:51:04 +00:00
m . LogStdout ( "failed to get last archive end date" , zap . Error ( err ) )
2022-03-21 14:18:36 +00:00
return 0 , err
}
if lastArchiveEndDateTimestamp == 0 {
// If we don't have a tracked last message archive end date, it
// means we haven't created an archive before, which means
// the next thing to look at is the oldest waku message timestamp for
// this community
lastArchiveEndDateTimestamp , err = m . GetOldestWakuMessageTimestamp ( topics )
if err != nil {
2022-10-06 20:51:04 +00:00
m . LogStdout ( "failed to get oldest waku message timestamp" , zap . Error ( err ) )
2022-03-21 14:18:36 +00:00
return 0 , err
}
if lastArchiveEndDateTimestamp == 0 {
// This means there's no waku message stored for this community so far
// (even after requesting possibly missed messages), so no messages exist yet that can be archived
2022-10-06 20:51:04 +00:00
m . LogStdout ( "can't find valid `lastArchiveEndTimestamp`" )
2022-03-21 14:18:36 +00:00
return 0 , nil
}
}
return lastArchiveEndDateTimestamp , nil
}
2022-10-14 09:26:10 +00:00
func ( m * Manager ) CreateAndSeedHistoryArchive ( communityID types . HexBytes , topics [ ] types . TopicType , startDate time . Time , endDate time . Time , partition time . Duration , encrypt bool ) error {
2022-03-21 14:18:36 +00:00
m . UnseedHistoryArchiveTorrent ( communityID )
feat(CommunitiesManager): introduce `CreateHistoryArchiveTorrentFromMessages` API
Prior to this commit we had a `CreateHistoryArchiveTorrent()` API which
takes a `startDate`, an `endDate` and a `partition` to create a bunch of
message archives, given a certain time range.
The function expects the messages to live in the database, which means,
all messages that need to be archived have to be saved there at some
point.
This turns out to be an issue when importing communities from third
party services, where, sometimes, there are several thousands of messages
including attachment payloads, that have to be save to the database
first.
There are only two options to get the messages into the database:
1. Make one write operation with all messages - this slow, takes a long
time and blocks the database until done
2. Create message chunks and perform multiple write operations - this is
also slow, takes long but makes the database a bit more responsive as
it's many smaller operations instead of one big one
Option 2) turned out to not be super feasible either as sometimes,
inserting even a single such message can take up to 10 seconds
(depending on payload)
Which brings me to the third option.
**A third option** is to not store those imported messages as waku
message into the database, just to later query them again to create the
archives, but instead create the archives right away from all the
messages that have been loaded into memory.
This is significantly faster and doesn't block the database.
To make this possible, this commit introduces
a `CreateHistoryArchiveTorrentFromMessages()` API, and
a `CreateHistoryArchiveTorrentFromDB()` API which can be used for
different use cases.
2022-10-20 14:37:04 +00:00
_ , err := m . CreateHistoryArchiveTorrentFromDB ( communityID , topics , startDate , endDate , partition , encrypt )
2022-03-21 14:18:36 +00:00
if err != nil {
return err
}
return m . SeedHistoryArchiveTorrent ( communityID )
}
func ( m * Manager ) StartHistoryArchiveTasksInterval ( community * Community , interval time . Duration ) {
id := community . IDString ( )
2023-05-05 16:40:18 +00:00
if _ , exists := m . historyArchiveTasks . Load ( id ) ; exists {
2022-10-06 20:51:04 +00:00
m . LogStdout ( "history archive tasks interval already in progres" , zap . String ( "id" , id ) )
2022-03-21 14:18:36 +00:00
return
}
cancel := make ( chan struct { } )
2023-05-05 16:40:18 +00:00
m . historyArchiveTasks . Store ( id , cancel )
2022-03-21 14:18:36 +00:00
m . historyArchiveTasksWaitGroup . Add ( 1 )
ticker := time . NewTicker ( interval )
defer ticker . Stop ( )
2022-10-06 20:51:04 +00:00
m . LogStdout ( "starting history archive tasks interval" , zap . String ( "id" , id ) )
2022-03-21 14:18:36 +00:00
for {
select {
case <- ticker . C :
2022-10-06 20:51:04 +00:00
m . LogStdout ( "starting archive task..." , zap . String ( "id" , id ) )
2022-03-21 14:18:36 +00:00
lastArchiveEndDateTimestamp , err := m . GetHistoryArchivePartitionStartTimestamp ( community . ID ( ) )
if err != nil {
2022-10-06 20:51:04 +00:00
m . LogStdout ( "failed to get last archive end date" , zap . Error ( err ) )
2022-03-21 14:18:36 +00:00
continue
}
if lastArchiveEndDateTimestamp == 0 {
// This means there are no waku messages for this community,
// so nothing to do here
2022-10-06 20:51:04 +00:00
m . LogStdout ( "couldn't determine archive start date - skipping" )
2022-03-21 14:18:36 +00:00
continue
}
topics , err := m . GetCommunityChatsTopics ( community . ID ( ) )
if err != nil {
2022-10-06 20:51:04 +00:00
m . LogStdout ( "failed to get community chat topics " , zap . Error ( err ) )
2022-03-21 14:18:36 +00:00
continue
}
ts := time . Now ( ) . Unix ( )
to := time . Unix ( ts , 0 )
lastArchiveEndDate := time . Unix ( int64 ( lastArchiveEndDateTimestamp ) , 0 )
2022-10-14 09:26:10 +00:00
err = m . CreateAndSeedHistoryArchive ( community . ID ( ) , topics , lastArchiveEndDate , to , interval , community . Encrypted ( ) )
2022-03-21 14:18:36 +00:00
if err != nil {
2022-10-06 20:51:04 +00:00
m . LogStdout ( "failed to create and seed history archive" , zap . Error ( err ) )
2022-03-21 14:18:36 +00:00
continue
}
case <- cancel :
m . UnseedHistoryArchiveTorrent ( community . ID ( ) )
2023-05-05 16:40:18 +00:00
m . historyArchiveTasks . Delete ( id )
2022-03-21 14:18:36 +00:00
m . historyArchiveTasksWaitGroup . Done ( )
return
}
}
}
func ( m * Manager ) StopHistoryArchiveTasksIntervals ( ) {
2023-05-05 16:40:18 +00:00
m . historyArchiveTasks . Range ( func ( _ , task interface { } ) bool {
close ( task . ( chan struct { } ) ) // Need to cast to the chan
return true
} )
2022-03-21 14:18:36 +00:00
// Stoping archive interval tasks is async, so we need
// to wait for all of them to be closed before we shutdown
// the torrent client
m . historyArchiveTasksWaitGroup . Wait ( )
}
func ( m * Manager ) StopHistoryArchiveTasksInterval ( communityID types . HexBytes ) {
2023-05-05 16:40:18 +00:00
task , exists := m . historyArchiveTasks . Load ( communityID . String ( ) )
if exists {
2022-03-21 14:18:36 +00:00
m . logger . Info ( "Stopping history archive tasks interval" , zap . Any ( "id" , communityID . String ( ) ) )
2023-05-05 16:40:18 +00:00
close ( task . ( chan struct { } ) ) // Need to cast to the chan
2022-03-21 14:18:36 +00:00
}
}
type EncodedArchiveData struct {
padding int
bytes [ ] byte
}
feat(CommunitiesManager): introduce `CreateHistoryArchiveTorrentFromMessages` API
Prior to this commit we had a `CreateHistoryArchiveTorrent()` API which
takes a `startDate`, an `endDate` and a `partition` to create a bunch of
message archives, given a certain time range.
The function expects the messages to live in the database, which means,
all messages that need to be archived have to be saved there at some
point.
This turns out to be an issue when importing communities from third
party services, where, sometimes, there are several thousands of messages
including attachment payloads, that have to be save to the database
first.
There are only two options to get the messages into the database:
1. Make one write operation with all messages - this slow, takes a long
time and blocks the database until done
2. Create message chunks and perform multiple write operations - this is
also slow, takes long but makes the database a bit more responsive as
it's many smaller operations instead of one big one
Option 2) turned out to not be super feasible either as sometimes,
inserting even a single such message can take up to 10 seconds
(depending on payload)
Which brings me to the third option.
**A third option** is to not store those imported messages as waku
message into the database, just to later query them again to create the
archives, but instead create the archives right away from all the
messages that have been loaded into memory.
This is significantly faster and doesn't block the database.
To make this possible, this commit introduces
a `CreateHistoryArchiveTorrentFromMessages()` API, and
a `CreateHistoryArchiveTorrentFromDB()` API which can be used for
different use cases.
2022-10-20 14:37:04 +00:00
func ( m * Manager ) CreateHistoryArchiveTorrentFromMessages ( communityID types . HexBytes , messages [ ] * types . Message , topics [ ] types . TopicType , startDate time . Time , endDate time . Time , partition time . Duration , encrypt bool ) ( [ ] string , error ) {
return m . CreateHistoryArchiveTorrent ( communityID , messages , topics , startDate , endDate , partition , encrypt )
}
func ( m * Manager ) CreateHistoryArchiveTorrentFromDB ( communityID types . HexBytes , topics [ ] types . TopicType , startDate time . Time , endDate time . Time , partition time . Duration , encrypt bool ) ( [ ] string , error ) {
return m . CreateHistoryArchiveTorrent ( communityID , make ( [ ] * types . Message , 0 ) , topics , startDate , endDate , partition , encrypt )
}
func ( m * Manager ) CreateHistoryArchiveTorrent ( communityID types . HexBytes , msgs [ ] * types . Message , topics [ ] types . TopicType , startDate time . Time , endDate time . Time , partition time . Duration , encrypt bool ) ( [ ] string , error ) {
loadFromDB := len ( msgs ) == 0
2022-03-21 14:18:36 +00:00
from := startDate
to := from . Add ( partition )
if to . After ( endDate ) {
to = endDate
}
archiveDir := m . torrentConfig . DataDir + "/" + communityID . String ( )
torrentDir := m . torrentConfig . TorrentDir
2024-01-18 18:54:54 +00:00
indexPath := archiveDir + "/index"
dataPath := archiveDir + "/data"
2022-03-21 14:18:36 +00:00
wakuMessageArchiveIndexProto := & protobuf . WakuMessageArchiveIndex { }
wakuMessageArchiveIndex := make ( map [ string ] * protobuf . WakuMessageArchiveIndexMetadata )
2022-04-22 07:42:22 +00:00
archiveIDs := make ( [ ] string , 0 )
2022-03-21 14:18:36 +00:00
if _ , err := os . Stat ( archiveDir ) ; os . IsNotExist ( err ) {
err := os . MkdirAll ( archiveDir , 0700 )
if err != nil {
2022-04-22 07:42:22 +00:00
return archiveIDs , err
2022-03-21 14:18:36 +00:00
}
}
if _ , err := os . Stat ( torrentDir ) ; os . IsNotExist ( err ) {
err := os . MkdirAll ( torrentDir , 0700 )
if err != nil {
2022-04-22 07:42:22 +00:00
return archiveIDs , err
2022-03-21 14:18:36 +00:00
}
}
_ , err := os . Stat ( indexPath )
if err == nil {
2022-10-14 09:26:10 +00:00
wakuMessageArchiveIndexProto , err = m . LoadHistoryArchiveIndexFromFile ( m . identity , communityID )
2022-03-21 14:18:36 +00:00
if err != nil {
2022-04-22 07:42:22 +00:00
return archiveIDs , err
2022-03-21 14:18:36 +00:00
}
}
var offset uint64 = 0
for hash , metadata := range wakuMessageArchiveIndexProto . Archives {
offset = offset + metadata . Size
wakuMessageArchiveIndex [ hash ] = metadata
}
var encodedArchives [ ] * EncodedArchiveData
topicsAsByteArrays := topicsAsByteArrays ( topics )
m . publish ( & Subscription { CreatingHistoryArchivesSignal : & signal . CreatingHistoryArchivesSignal {
CommunityID : communityID . String ( ) ,
} } )
2022-10-06 20:51:04 +00:00
m . LogStdout ( "creating archives" ,
2022-03-21 14:18:36 +00:00
zap . Any ( "startDate" , startDate ) ,
zap . Any ( "endDate" , endDate ) ,
zap . Duration ( "partition" , partition ) ,
)
for {
if from . Equal ( endDate ) || from . After ( endDate ) {
break
}
2022-10-06 20:51:04 +00:00
m . LogStdout ( "creating message archive" ,
2022-03-21 14:18:36 +00:00
zap . Any ( "from" , from ) ,
zap . Any ( "to" , to ) ,
)
2022-10-06 20:51:04 +00:00
feat(CommunitiesManager): introduce `CreateHistoryArchiveTorrentFromMessages` API
Prior to this commit we had a `CreateHistoryArchiveTorrent()` API which
takes a `startDate`, an `endDate` and a `partition` to create a bunch of
message archives, given a certain time range.
The function expects the messages to live in the database, which means,
all messages that need to be archived have to be saved there at some
point.
This turns out to be an issue when importing communities from third
party services, where, sometimes, there are several thousands of messages
including attachment payloads, that have to be save to the database
first.
There are only two options to get the messages into the database:
1. Make one write operation with all messages - this slow, takes a long
time and blocks the database until done
2. Create message chunks and perform multiple write operations - this is
also slow, takes long but makes the database a bit more responsive as
it's many smaller operations instead of one big one
Option 2) turned out to not be super feasible either as sometimes,
inserting even a single such message can take up to 10 seconds
(depending on payload)
Which brings me to the third option.
**A third option** is to not store those imported messages as waku
message into the database, just to later query them again to create the
archives, but instead create the archives right away from all the
messages that have been loaded into memory.
This is significantly faster and doesn't block the database.
To make this possible, this commit introduces
a `CreateHistoryArchiveTorrentFromMessages()` API, and
a `CreateHistoryArchiveTorrentFromDB()` API which can be used for
different use cases.
2022-10-20 14:37:04 +00:00
var messages [ ] types . Message
if loadFromDB {
messages , err = m . persistence . GetWakuMessagesByFilterTopic ( topics , uint64 ( from . Unix ( ) ) , uint64 ( to . Unix ( ) ) )
if err != nil {
return archiveIDs , err
}
} else {
for _ , msg := range msgs {
if int64 ( msg . Timestamp ) >= from . Unix ( ) && int64 ( msg . Timestamp ) < to . Unix ( ) {
messages = append ( messages , * msg )
}
}
2022-03-21 14:18:36 +00:00
}
if len ( messages ) == 0 {
// No need to create an archive with zero messages
2022-10-06 20:51:04 +00:00
m . LogStdout ( "no messages in this partition" )
2022-03-21 14:18:36 +00:00
from = to
to = to . Add ( partition )
if to . After ( endDate ) {
to = endDate
}
continue
}
2023-01-16 14:17:19 +00:00
// Not only do we partition messages, we also chunk them
// roughly by size, such that each chunk will not exceed a given
// size and archive data doesn't get too big
messageChunks := make ( [ ] [ ] types . Message , 0 )
currentChunkSize := 0
currentChunk := make ( [ ] types . Message , 0 )
for _ , msg := range messages {
msgSize := len ( msg . Payload ) + len ( msg . Sig )
if msgSize > maxArchiveSizeInBytes {
// we drop messages this big
continue
}
if currentChunkSize + msgSize > maxArchiveSizeInBytes {
messageChunks = append ( messageChunks , currentChunk )
currentChunk = make ( [ ] types . Message , 0 )
currentChunkSize = 0
}
currentChunk = append ( currentChunk , msg )
currentChunkSize = currentChunkSize + msgSize
2022-03-21 14:18:36 +00:00
}
2023-01-16 14:17:19 +00:00
messageChunks = append ( messageChunks , currentChunk )
2022-03-21 14:18:36 +00:00
2023-01-16 14:17:19 +00:00
for _ , messages := range messageChunks {
wakuMessageArchive := m . createWakuMessageArchive ( from , to , messages , topicsAsByteArrays )
encodedArchive , err := proto . Marshal ( wakuMessageArchive )
2022-10-14 09:26:10 +00:00
if err != nil {
return archiveIDs , err
}
2023-01-16 14:17:19 +00:00
if encrypt {
messageSpec , err := m . encryptor . BuildHashRatchetMessage ( communityID , encodedArchive )
if err != nil {
return archiveIDs , err
}
encodedArchive , err = proto . Marshal ( messageSpec . Message )
if err != nil {
return archiveIDs , err
}
2022-10-14 09:26:10 +00:00
}
2023-01-16 14:17:19 +00:00
rawSize := len ( encodedArchive )
padding := 0
size := 0
2022-03-21 14:18:36 +00:00
2023-01-16 14:17:19 +00:00
if rawSize > pieceLength {
size = rawSize + pieceLength - ( rawSize % pieceLength )
padding = size - rawSize
} else {
padding = pieceLength - rawSize
size = rawSize + padding
}
2022-03-21 14:18:36 +00:00
2023-01-16 14:17:19 +00:00
wakuMessageArchiveIndexMetadata := & protobuf . WakuMessageArchiveIndexMetadata {
Metadata : wakuMessageArchive . Metadata ,
Offset : offset ,
Size : uint64 ( size ) ,
Padding : uint64 ( padding ) ,
}
2022-03-21 14:18:36 +00:00
2023-01-16 14:17:19 +00:00
wakuMessageArchiveIndexMetadataBytes , err := proto . Marshal ( wakuMessageArchiveIndexMetadata )
if err != nil {
return archiveIDs , err
}
archiveID := crypto . Keccak256Hash ( wakuMessageArchiveIndexMetadataBytes ) . String ( )
archiveIDs = append ( archiveIDs , archiveID )
wakuMessageArchiveIndex [ archiveID ] = wakuMessageArchiveIndexMetadata
encodedArchives = append ( encodedArchives , & EncodedArchiveData { bytes : encodedArchive , padding : padding } )
offset = offset + uint64 ( rawSize ) + uint64 ( padding )
2022-03-21 14:18:36 +00:00
}
from = to
to = to . Add ( partition )
if to . After ( endDate ) {
to = endDate
}
}
if len ( encodedArchives ) > 0 {
dataBytes := make ( [ ] byte , 0 )
for _ , encodedArchiveData := range encodedArchives {
dataBytes = append ( dataBytes , encodedArchiveData . bytes ... )
dataBytes = append ( dataBytes , make ( [ ] byte , encodedArchiveData . padding ) ... )
}
wakuMessageArchiveIndexProto . Archives = wakuMessageArchiveIndex
indexBytes , err := proto . Marshal ( wakuMessageArchiveIndexProto )
if err != nil {
2022-04-22 07:42:22 +00:00
return archiveIDs , err
2022-03-21 14:18:36 +00:00
}
2022-10-14 09:26:10 +00:00
if encrypt {
messageSpec , err := m . encryptor . BuildHashRatchetMessage ( communityID , indexBytes )
if err != nil {
return archiveIDs , err
}
indexBytes , err = proto . Marshal ( messageSpec . Message )
if err != nil {
return archiveIDs , err
}
}
2022-03-21 14:18:36 +00:00
err = os . WriteFile ( indexPath , indexBytes , 0644 ) // nolint: gosec
if err != nil {
2022-04-22 07:42:22 +00:00
return archiveIDs , err
2022-03-21 14:18:36 +00:00
}
2022-09-29 11:50:23 +00:00
file , err := os . OpenFile ( dataPath , os . O_WRONLY | os . O_CREATE | os . O_APPEND , 0644 )
if err != nil {
return archiveIDs , err
}
defer file . Close ( )
_ , err = file . Write ( dataBytes )
2022-03-21 14:18:36 +00:00
if err != nil {
2022-04-22 07:42:22 +00:00
return archiveIDs , err
2022-03-21 14:18:36 +00:00
}
metaInfo := metainfo . MetaInfo {
AnnounceList : defaultAnnounceList ,
}
metaInfo . SetDefaults ( )
2022-10-14 09:26:10 +00:00
metaInfo . CreatedBy = common . PubkeyToHex ( & m . identity . PublicKey )
2022-03-21 14:18:36 +00:00
info := metainfo . Info {
PieceLength : int64 ( pieceLength ) ,
}
err = info . BuildFromFilePath ( archiveDir )
if err != nil {
2022-04-22 07:42:22 +00:00
return archiveIDs , err
2022-03-21 14:18:36 +00:00
}
metaInfo . InfoBytes , err = bencode . Marshal ( info )
if err != nil {
2022-04-22 07:42:22 +00:00
return archiveIDs , err
2022-03-21 14:18:36 +00:00
}
metaInfoBytes , err := bencode . Marshal ( metaInfo )
if err != nil {
2022-04-22 07:42:22 +00:00
return archiveIDs , err
2022-03-21 14:18:36 +00:00
}
err = os . WriteFile ( m . torrentFile ( communityID . String ( ) ) , metaInfoBytes , 0644 ) // nolint: gosec
if err != nil {
2022-04-22 07:42:22 +00:00
return archiveIDs , err
2022-03-21 14:18:36 +00:00
}
2022-10-06 20:51:04 +00:00
m . LogStdout ( "torrent created" , zap . Any ( "from" , startDate . Unix ( ) ) , zap . Any ( "to" , endDate . Unix ( ) ) )
2022-03-21 14:18:36 +00:00
m . publish ( & Subscription {
HistoryArchivesCreatedSignal : & signal . HistoryArchivesCreatedSignal {
CommunityID : communityID . String ( ) ,
From : int ( startDate . Unix ( ) ) ,
To : int ( endDate . Unix ( ) ) ,
} ,
} )
} else {
2022-10-06 20:51:04 +00:00
m . LogStdout ( "no archives created" )
2022-03-21 14:18:36 +00:00
m . publish ( & Subscription {
NoHistoryArchivesCreatedSignal : & signal . NoHistoryArchivesCreatedSignal {
CommunityID : communityID . String ( ) ,
From : int ( startDate . Unix ( ) ) ,
To : int ( endDate . Unix ( ) ) ,
} ,
} )
}
lastMessageArchiveEndDate , err := m . persistence . GetLastMessageArchiveEndDate ( communityID )
if err != nil {
2022-04-22 07:42:22 +00:00
return archiveIDs , err
2022-03-21 14:18:36 +00:00
}
if lastMessageArchiveEndDate > 0 {
err = m . persistence . UpdateLastMessageArchiveEndDate ( communityID , uint64 ( from . Unix ( ) ) )
} else {
err = m . persistence . SaveLastMessageArchiveEndDate ( communityID , uint64 ( from . Unix ( ) ) )
}
if err != nil {
2022-04-22 07:42:22 +00:00
return archiveIDs , err
2022-03-21 14:18:36 +00:00
}
2022-04-22 07:42:22 +00:00
return archiveIDs , nil
2022-03-21 14:18:36 +00:00
}
func ( m * Manager ) SeedHistoryArchiveTorrent ( communityID types . HexBytes ) error {
m . UnseedHistoryArchiveTorrent ( communityID )
id := communityID . String ( )
torrentFile := m . torrentFile ( id )
metaInfo , err := metainfo . LoadFromFile ( torrentFile )
if err != nil {
return err
}
info , err := metaInfo . UnmarshalInfo ( )
if err != nil {
return err
}
hash := metaInfo . HashInfoBytes ( )
m . torrentTasks [ id ] = hash
if err != nil {
return err
}
torrent , err := m . torrentClient . AddTorrent ( metaInfo )
if err != nil {
return err
}
2023-12-14 09:52:10 +00:00
2022-03-21 14:18:36 +00:00
torrent . DownloadAll ( )
m . publish ( & Subscription {
HistoryArchivesSeedingSignal : & signal . HistoryArchivesSeedingSignal {
CommunityID : communityID . String ( ) ,
} ,
} )
2022-10-06 20:51:04 +00:00
magnetLink := metaInfo . Magnet ( nil , & info ) . String ( )
m . LogStdout ( "seeding torrent" , zap . String ( "id" , id ) , zap . String ( "magnetLink" , magnetLink ) )
2022-03-21 14:18:36 +00:00
return nil
}
func ( m * Manager ) UnseedHistoryArchiveTorrent ( communityID types . HexBytes ) {
id := communityID . String ( )
2022-09-29 11:50:23 +00:00
2022-03-21 14:18:36 +00:00
hash , exists := m . torrentTasks [ id ]
if exists {
torrent , ok := m . torrentClient . Torrent ( hash )
if ok {
m . logger . Debug ( "Unseeding and dropping torrent for community: " , zap . Any ( "id" , id ) )
torrent . Drop ( )
delete ( m . torrentTasks , id )
m . publish ( & Subscription {
HistoryArchivesUnseededSignal : & signal . HistoryArchivesUnseededSignal {
CommunityID : id ,
} ,
} )
}
}
}
func ( m * Manager ) IsSeedingHistoryArchiveTorrent ( communityID types . HexBytes ) bool {
id := communityID . String ( )
hash := m . torrentTasks [ id ]
torrent , ok := m . torrentClient . Torrent ( hash )
return ok && torrent . Seeding ( )
}
2022-12-12 09:22:37 +00:00
func ( m * Manager ) GetHistoryArchiveDownloadTask ( communityID string ) * HistoryArchiveDownloadTask {
return m . historyArchiveDownloadTasks [ communityID ]
}
2022-12-19 08:34:37 +00:00
func ( m * Manager ) DeleteHistoryArchiveDownloadTask ( communityID string ) {
delete ( m . historyArchiveDownloadTasks , communityID )
}
2022-12-12 09:22:37 +00:00
func ( m * Manager ) AddHistoryArchiveDownloadTask ( communityID string , task * HistoryArchiveDownloadTask ) {
m . historyArchiveDownloadTasks [ communityID ] = task
}
type HistoryArchiveDownloadTaskInfo struct {
TotalDownloadedArchivesCount int
TotalArchivesCount int
Cancelled bool
}
func ( m * Manager ) DownloadHistoryArchivesByMagnetlink ( communityID types . HexBytes , magnetlink string , cancelTask chan struct { } ) ( * HistoryArchiveDownloadTaskInfo , error ) {
2022-04-22 07:42:22 +00:00
id := communityID . String ( )
2022-12-12 09:22:37 +00:00
2022-04-22 07:42:22 +00:00
ml , err := metainfo . ParseMagnetUri ( magnetlink )
if err != nil {
return nil , err
}
m . logger . Debug ( "adding torrent via magnetlink for community" , zap . String ( "id" , id ) , zap . String ( "magnetlink" , magnetlink ) )
torrent , err := m . torrentClient . AddMagnet ( magnetlink )
if err != nil {
return nil , err
}
2022-12-12 09:22:37 +00:00
2022-12-19 08:34:37 +00:00
downloadTaskInfo := & HistoryArchiveDownloadTaskInfo {
TotalDownloadedArchivesCount : 0 ,
TotalArchivesCount : 0 ,
Cancelled : false ,
}
2022-04-22 07:42:22 +00:00
m . torrentTasks [ id ] = ml . InfoHash
timeout := time . After ( 20 * time . Second )
2022-10-06 20:51:04 +00:00
m . LogStdout ( "fetching torrent info" , zap . String ( "magnetlink" , magnetlink ) )
2022-04-22 07:42:22 +00:00
select {
case <- timeout :
2022-10-07 10:24:50 +00:00
return nil , ErrTorrentTimedout
2022-12-19 08:34:37 +00:00
case <- cancelTask :
m . LogStdout ( "cancelled fetching torrent info" )
downloadTaskInfo . Cancelled = true
return downloadTaskInfo , nil
2022-04-22 07:42:22 +00:00
case <- torrent . GotInfo ( ) :
2022-12-12 09:22:37 +00:00
2022-04-22 07:42:22 +00:00
files := torrent . Files ( )
i , ok := findIndexFile ( files )
if ! ok {
// We're dealing with a malformed torrent, so don't do anything
return nil , errors . New ( "malformed torrent data" )
}
indexFile := files [ i ]
indexFile . Download ( )
2022-10-06 20:51:04 +00:00
m . LogStdout ( "downloading history archive index" )
2022-04-22 07:42:22 +00:00
ticker := time . NewTicker ( 100 * time . Millisecond )
defer ticker . Stop ( )
for {
2022-09-29 11:50:23 +00:00
select {
2022-12-12 09:22:37 +00:00
case <- cancelTask :
m . LogStdout ( "cancelled downloading archive index" )
downloadTaskInfo . Cancelled = true
return downloadTaskInfo , nil
2022-09-29 11:50:23 +00:00
case <- ticker . C :
if indexFile . BytesCompleted ( ) == indexFile . Length ( ) {
2022-12-12 09:22:37 +00:00
2022-09-29 11:50:23 +00:00
index , err := m . LoadHistoryArchiveIndexFromFile ( m . identity , communityID )
if err != nil {
return nil , err
}
2022-04-22 07:42:22 +00:00
2022-12-12 09:22:37 +00:00
existingArchiveIDs , err := m . persistence . GetDownloadedMessageArchiveIDs ( communityID )
if err != nil {
return nil , err
}
2022-04-22 07:42:22 +00:00
2022-12-12 09:22:37 +00:00
if len ( existingArchiveIDs ) == len ( index . Archives ) {
m . LogStdout ( "download cancelled, no new archives" )
return downloadTaskInfo , nil
}
downloadTaskInfo . TotalDownloadedArchivesCount = len ( existingArchiveIDs )
downloadTaskInfo . TotalArchivesCount = len ( index . Archives )
archiveHashes := make ( archiveMDSlice , 0 , downloadTaskInfo . TotalArchivesCount )
2022-04-22 07:42:22 +00:00
2022-09-29 11:50:23 +00:00
for hash , metadata := range index . Archives {
archiveHashes = append ( archiveHashes , & archiveMetadata { hash : hash , from : metadata . Metadata . From } )
}
2022-04-22 07:42:22 +00:00
2022-09-29 11:50:23 +00:00
sort . Sort ( sort . Reverse ( archiveHashes ) )
2022-04-22 07:42:22 +00:00
2022-12-01 14:02:17 +00:00
m . publish ( & Subscription {
DownloadingHistoryArchivesStartedSignal : & signal . DownloadingHistoryArchivesStartedSignal {
CommunityID : communityID . String ( ) ,
} ,
} )
2022-09-29 11:50:23 +00:00
for _ , hd := range archiveHashes {
2022-12-12 09:22:37 +00:00
2022-09-29 11:50:23 +00:00
hash := hd . hash
2022-12-12 09:22:37 +00:00
hasArchive := false
for _ , existingHash := range existingArchiveIDs {
if existingHash == hash {
hasArchive = true
break
}
2022-09-29 11:50:23 +00:00
}
if hasArchive {
continue
}
2022-04-22 07:42:22 +00:00
2022-12-12 09:22:37 +00:00
metadata := index . Archives [ hash ]
2022-09-29 11:50:23 +00:00
startIndex := int ( metadata . Offset ) / pieceLength
endIndex := startIndex + int ( metadata . Size ) / pieceLength
2022-04-22 07:42:22 +00:00
2022-12-09 09:37:04 +00:00
downloadMsg := fmt . Sprintf ( "downloading data for message archive (%d/%d)" , downloadTaskInfo . TotalDownloadedArchivesCount + 1 , downloadTaskInfo . TotalArchivesCount )
m . LogStdout ( downloadMsg , zap . String ( "hash" , hash ) )
2022-09-29 11:50:23 +00:00
m . LogStdout ( "pieces (start, end)" , zap . Any ( "startIndex" , startIndex ) , zap . Any ( "endIndex" , endIndex - 1 ) )
torrent . DownloadPieces ( startIndex , endIndex )
piecesCompleted := make ( map [ int ] bool )
for i = startIndex ; i < endIndex ; i ++ {
piecesCompleted [ i ] = false
}
2022-04-22 07:42:22 +00:00
2022-09-29 11:50:23 +00:00
psc := torrent . SubscribePieceStateChanges ( )
downloadTicker := time . NewTicker ( 1 * time . Second )
defer downloadTicker . Stop ( )
downloadLoop :
2022-04-22 07:42:22 +00:00
for {
2022-09-29 11:50:23 +00:00
select {
case <- downloadTicker . C :
done := true
for i = startIndex ; i < endIndex ; i ++ {
piecesCompleted [ i ] = torrent . PieceState ( i ) . Complete
if ! piecesCompleted [ i ] {
done = false
}
}
if done {
psc . Close ( )
break downloadLoop
}
2022-12-12 09:22:37 +00:00
case <- cancelTask :
m . LogStdout ( "downloading archive data interrupted" )
downloadTaskInfo . Cancelled = true
return downloadTaskInfo , nil
2022-10-05 10:38:14 +00:00
}
2022-04-22 07:42:22 +00:00
}
2022-12-12 09:22:37 +00:00
downloadTaskInfo . TotalDownloadedArchivesCount ++
2022-09-29 11:50:23 +00:00
err = m . persistence . SaveMessageArchiveID ( communityID , hash )
if err != nil {
2022-12-12 09:22:37 +00:00
m . LogStdout ( "couldn't save message archive ID" , zap . Error ( err ) )
2022-09-29 11:50:23 +00:00
continue
2022-04-22 07:42:22 +00:00
}
2022-09-29 11:50:23 +00:00
m . publish ( & Subscription {
HistoryArchiveDownloadedSignal : & signal . HistoryArchiveDownloadedSignal {
CommunityID : communityID . String ( ) ,
From : int ( metadata . Metadata . From ) ,
To : int ( metadata . Metadata . To ) ,
} ,
} )
2022-04-22 07:42:22 +00:00
}
m . publish ( & Subscription {
2022-09-29 11:50:23 +00:00
HistoryArchivesSeedingSignal : & signal . HistoryArchivesSeedingSignal {
2022-04-22 07:42:22 +00:00
CommunityID : communityID . String ( ) ,
} ,
} )
2022-12-12 09:22:37 +00:00
m . LogStdout ( "finished downloading archives" )
return downloadTaskInfo , nil
2022-04-22 07:42:22 +00:00
}
}
}
}
}
2022-12-12 09:22:37 +00:00
func ( m * Manager ) GetMessageArchiveIDsToImport ( communityID types . HexBytes ) ( [ ] string , error ) {
return m . persistence . GetMessageArchiveIDsToImport ( communityID )
}
2023-01-13 15:40:39 +00:00
func ( m * Manager ) ExtractMessagesFromHistoryArchive ( communityID types . HexBytes , archiveID string ) ( [ ] * protobuf . WakuMessage , error ) {
2022-04-22 07:42:22 +00:00
id := communityID . String ( )
2022-10-14 09:26:10 +00:00
index , err := m . LoadHistoryArchiveIndexFromFile ( m . identity , communityID )
2022-04-22 07:42:22 +00:00
if err != nil {
return nil , err
}
2023-01-13 15:40:39 +00:00
dataFile , err := os . Open ( m . archiveDataFile ( id ) )
2022-04-22 07:42:22 +00:00
if err != nil {
return nil , err
}
2023-01-13 15:40:39 +00:00
defer dataFile . Close ( )
2022-04-22 07:42:22 +00:00
2023-01-13 15:40:39 +00:00
m . LogStdout ( "extracting messages from history archive" , zap . String ( "archive id" , archiveID ) )
metadata := index . Archives [ archiveID ]
2022-04-22 07:42:22 +00:00
2023-01-13 15:40:39 +00:00
_ , err = dataFile . Seek ( int64 ( metadata . Offset ) , 0 )
if err != nil {
m . LogStdout ( "failed to seek archive data file" , zap . Error ( err ) )
return nil , err
}
2022-04-22 07:42:22 +00:00
2023-01-13 15:40:39 +00:00
data := make ( [ ] byte , metadata . Size - metadata . Padding )
2023-06-01 20:02:34 +00:00
m . LogStdout ( "loading history archive data into memory" , zap . Float64 ( "data_size_MB" , float64 ( metadata . Size - metadata . Padding ) / 1024.0 / 1024.0 ) )
2023-01-13 15:40:39 +00:00
_ , err = dataFile . Read ( data )
if err != nil {
m . LogStdout ( "failed failed to read archive data" , zap . Error ( err ) )
return nil , err
}
2022-04-22 07:42:22 +00:00
2023-01-13 15:40:39 +00:00
archive := & protobuf . WakuMessageArchive { }
2022-10-14 09:26:10 +00:00
2023-01-13 15:40:39 +00:00
err = proto . Unmarshal ( data , archive )
if err != nil {
// The archive data might eb encrypted so we try to decrypt instead first
var protocolMessage encryption . ProtocolMessage
err := proto . Unmarshal ( data , & protocolMessage )
if err != nil {
m . LogStdout ( "failed to unmarshal protocol message" , zap . Error ( err ) )
return nil , err
2022-04-22 07:42:22 +00:00
}
2023-01-13 15:40:39 +00:00
pk , err := crypto . DecompressPubkey ( communityID )
if err != nil {
m . logger . Debug ( "failed to decompress community pubkey" , zap . Error ( err ) )
return nil , err
}
decryptedBytes , err := m . encryptor . HandleMessage ( m . identity , pk , & protocolMessage , make ( [ ] byte , 0 ) )
if err != nil {
m . LogStdout ( "failed to decrypt message archive" , zap . Error ( err ) )
return nil , err
}
err = proto . Unmarshal ( decryptedBytes . DecryptedMessage , archive )
if err != nil {
m . LogStdout ( "failed to unmarshal message archive data" , zap . Error ( err ) )
return nil , err
2022-04-22 07:42:22 +00:00
}
}
2023-01-13 15:40:39 +00:00
return archive . Messages , nil
2022-04-22 07:42:22 +00:00
}
2022-12-12 09:22:37 +00:00
func ( m * Manager ) SetMessageArchiveIDImported ( communityID types . HexBytes , hash string , imported bool ) error {
return m . persistence . SetMessageArchiveIDImported ( communityID , hash , imported )
}
2022-03-21 14:18:36 +00:00
func ( m * Manager ) GetHistoryArchiveMagnetlink ( communityID types . HexBytes ) ( string , error ) {
id := communityID . String ( )
torrentFile := m . torrentFile ( id )
metaInfo , err := metainfo . LoadFromFile ( torrentFile )
if err != nil {
return "" , err
}
info , err := metaInfo . UnmarshalInfo ( )
if err != nil {
return "" , err
}
return metaInfo . Magnet ( nil , & info ) . String ( ) , nil
}
func ( m * Manager ) createWakuMessageArchive ( from time . Time , to time . Time , messages [ ] types . Message , topics [ ] [ ] byte ) * protobuf . WakuMessageArchive {
var wakuMessages [ ] * protobuf . WakuMessage
for _ , msg := range messages {
topic := types . TopicTypeToByteArray ( msg . Topic )
wakuMessage := & protobuf . WakuMessage {
2022-09-28 12:45:34 +00:00
Sig : msg . Sig ,
Timestamp : uint64 ( msg . Timestamp ) ,
Topic : topic ,
Payload : msg . Payload ,
Padding : msg . Padding ,
Hash : msg . Hash ,
ThirdPartyId : msg . ThirdPartyID ,
2022-03-21 14:18:36 +00:00
}
wakuMessages = append ( wakuMessages , wakuMessage )
}
metadata := protobuf . WakuMessageArchiveMetadata {
From : uint64 ( from . Unix ( ) ) ,
To : uint64 ( to . Unix ( ) ) ,
ContentTopic : topics ,
}
wakuMessageArchive := & protobuf . WakuMessageArchive {
Metadata : & metadata ,
Messages : wakuMessages ,
}
return wakuMessageArchive
}
2022-10-14 09:26:10 +00:00
func ( m * Manager ) LoadHistoryArchiveIndexFromFile ( myKey * ecdsa . PrivateKey , communityID types . HexBytes ) ( * protobuf . WakuMessageArchiveIndex , error ) {
2022-03-21 14:18:36 +00:00
wakuMessageArchiveIndexProto := & protobuf . WakuMessageArchiveIndex { }
indexPath := m . archiveIndexFile ( communityID . String ( ) )
indexData , err := os . ReadFile ( indexPath )
if err != nil {
return nil , err
}
err = proto . Unmarshal ( indexData , wakuMessageArchiveIndexProto )
if err != nil {
return nil , err
}
2022-10-14 09:26:10 +00:00
if len ( wakuMessageArchiveIndexProto . Archives ) == 0 && len ( indexData ) > 0 {
// This means we're dealing with an encrypted index file, so we have to decrypt it first
var protocolMessage encryption . ProtocolMessage
err := proto . Unmarshal ( indexData , & protocolMessage )
if err != nil {
return nil , err
}
pk , err := crypto . DecompressPubkey ( communityID )
if err != nil {
return nil , err
}
decryptedBytes , err := m . encryptor . HandleMessage ( myKey , pk , & protocolMessage , make ( [ ] byte , 0 ) )
if err != nil {
return nil , err
}
err = proto . Unmarshal ( decryptedBytes . DecryptedMessage , wakuMessageArchiveIndexProto )
if err != nil {
return nil , err
}
}
2022-03-21 14:18:36 +00:00
return wakuMessageArchiveIndexProto , nil
}
2022-09-15 10:15:19 +00:00
func ( m * Manager ) TorrentFileExists ( communityID string ) bool {
_ , err := os . Stat ( m . torrentFile ( communityID ) )
return err == nil
}
2022-03-21 14:18:36 +00:00
func ( m * Manager ) torrentFile ( communityID string ) string {
return m . torrentConfig . TorrentDir + "/" + communityID + ".torrent"
}
func ( m * Manager ) archiveIndexFile ( communityID string ) string {
2024-01-18 18:54:54 +00:00
return m . torrentConfig . DataDir + "/" + communityID + "/index"
2022-03-21 14:18:36 +00:00
}
func ( m * Manager ) archiveDataFile ( communityID string ) string {
2024-01-18 18:54:54 +00:00
return m . torrentConfig . DataDir + "/" + communityID + "/data"
2022-03-21 14:18:36 +00:00
}
func topicsAsByteArrays ( topics [ ] types . TopicType ) [ ] [ ] byte {
var topicsAsByteArrays [ ] [ ] byte
for _ , t := range topics {
topic := types . TopicTypeToByteArray ( t )
topicsAsByteArrays = append ( topicsAsByteArrays , topic )
}
return topicsAsByteArrays
}
2022-04-22 07:42:22 +00:00
func findIndexFile ( files [ ] * torrent . File ) ( index int , ok bool ) {
for i , f := range files {
if f . DisplayPath ( ) == "index" {
return i , true
}
}
return 0 , false
}
2023-01-27 13:27:24 +00:00
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 ) ,
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
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-05-17 16:15:39 +00:00
func ( m * Manager ) ReevaluatePrivilegedMember ( community * Community , permissionsData * PreParsedCommunityPermissionsData ,
2023-08-04 10:28:46 +00:00
accountsAndChainIDs [ ] * AccountChainIDsCombination , memberPubKey * ecdsa . PublicKey ,
privilegedRole protobuf . CommunityMember_Roles , alreadyHasPrivilegedRole bool ) ( bool , error ) {
2024-05-17 16:15:39 +00:00
hasPrivilegedRolePermissions := permissionsData != nil
2023-08-04 10:28:46 +00:00
removeCurrentRole := false
if hasPrivilegedRolePermissions {
2024-05-17 16:15:39 +00:00
permissionResponse , err := m . PermissionChecker . CheckPermissions ( permissionsData , accountsAndChainIDs , true )
2023-08-04 10:28:46 +00:00
if err != nil {
2024-05-17 16:15:39 +00:00
m . logger . Warn ( "check privileged permission failed: %v" , zap . Error ( err ) )
2023-08-04 10:28:46 +00:00
return alreadyHasPrivilegedRole , err
} else if permissionResponse . Satisfied && ! alreadyHasPrivilegedRole {
_ , err = community . AddRoleToMember ( memberPubKey , privilegedRole )
if err != nil {
return alreadyHasPrivilegedRole , err
}
alreadyHasPrivilegedRole = true
} else if ! permissionResponse . Satisfied && alreadyHasPrivilegedRole {
removeCurrentRole = true
alreadyHasPrivilegedRole = false
}
}
// Remove privileged role if user does not pass role permissions check or
// Community does not have permissions but user has a role
if removeCurrentRole || ( ! hasPrivilegedRolePermissions && alreadyHasPrivilegedRole ) {
_ , err := community . RemoveRoleFromMember ( memberPubKey , privilegedRole )
if err != nil {
return alreadyHasPrivilegedRole , err
}
alreadyHasPrivilegedRole = false
}
if alreadyHasPrivilegedRole {
// Make sure privileged user is added to every channel
for channelID := range community . Chats ( ) {
if ! community . IsMemberInChat ( memberPubKey , channelID ) {
2024-03-01 17:15:38 +00:00
_ , err := community . AddMemberToChat ( channelID , memberPubKey , [ ] protobuf . CommunityMember_Roles { privilegedRole } , protobuf . CommunityMember_CHANNEL_ROLE_POSTER )
2023-08-04 10:28:46 +00:00
if err != nil {
return alreadyHasPrivilegedRole , err
}
}
}
}
return alreadyHasPrivilegedRole , nil
}
2023-07-07 13:03:37 +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 ) ,
}
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" )
}
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
}
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 )
}
return community . emptyCommunityChanges ( ) , 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 }
_ , err = community . AddMember ( & m . identity . PublicKey , ownerRole )
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
}
2023-09-20 08:37:46 +00:00
func ( m * Manager ) shareRequestsToJoinWithNewPrivilegedMembers ( community * Community , newPrivilegedMembers map [ protobuf . CommunityMember_Roles ] [ ] * ecdsa . PublicKey ) error {
requestsToJoin , err := m . GetCommunityRequestsToJoinWithRevealedAddresses ( community . ID ( ) )
if err != nil {
return err
}
var syncRequestsWithoutRevealedAccounts [ ] * protobuf . SyncCommunityRequestsToJoin
var syncRequestsWithRevealedAccounts [ ] * protobuf . SyncCommunityRequestsToJoin
for _ , request := range requestsToJoin {
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 ,
}
subscriptionMsg := & CommunityPrivilegedMemberSyncMessage {
CommunityPrivateKey : community . PrivateKey ( ) ,
}
for role , members := range newPrivilegedMembers {
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
}
func ( m * Manager ) shareAcceptedRequestToJoinWithPrivilegedMembers ( community * Community , requestsToJoin * RequestToJoin ) error {
pk , err := common . HexToPubkey ( requestsToJoin . PublicKey )
if err != nil {
return err
}
acceptedRequestsToJoinWithoutRevealedAccounts := make ( map [ string ] * protobuf . CommunityRequestToJoin )
acceptedRequestsToJoinWithRevealedAccounts := make ( map [ string ] * protobuf . CommunityRequestToJoin )
acceptedRequestsToJoinWithRevealedAccounts [ requestsToJoin . PublicKey ] = requestsToJoin . ToCommunityRequestToJoinProtobuf ( )
requestsToJoin . RevealedAccounts = make ( [ ] * protobuf . RevealedAccount , 0 )
acceptedRequestsToJoinWithoutRevealedAccounts [ requestsToJoin . PublicKey ] = requestsToJoin . ToCommunityRequestToJoinProtobuf ( )
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 { } { }
subscriptionMsg := & CommunityPrivilegedMemberSyncMessage {
CommunityPrivateKey : community . PrivateKey ( ) ,
}
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
}
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
}