feat: ensure unique control node across devices

closes: status-im/status-desktop#11962
This commit is contained in:
Patryk Osmaczko 2023-09-21 13:16:05 +02:00 committed by osmaczko
parent 624996a7e9
commit 345851c396
19 changed files with 1044 additions and 867 deletions

View file

@ -4,7 +4,7 @@ import (
"github.com/status-im/status-go/protocol/protobuf"
)
func (o *Community) ToSyncInstallationCommunityProtobuf(clock uint64, communitySettings *CommunitySettings) (*protobuf.SyncInstallationCommunity, error) {
func (o *Community) ToSyncInstallationCommunityProtobuf(clock uint64, communitySettings *CommunitySettings, syncControlNode *protobuf.SyncCommunityControlNode) (*protobuf.SyncInstallationCommunity, error) {
wrappedCommunity, err := o.ToProtocolMessageBytes()
if err != nil {
return nil, err
@ -35,5 +35,6 @@ func (o *Community) ToSyncInstallationCommunityProtobuf(clock uint64, communityS
Muted: o.Muted(),
RequestsToJoin: rtjs,
Settings: settings,
ControlNode: syncControlNode,
}, nil
}

View file

@ -30,6 +30,7 @@ const signatureLength = 65
type Config struct {
PrivateKey *ecdsa.PrivateKey
ControlNode *ecdsa.PublicKey
ControlDevice bool // whether this device is control node
CommunityDescription *protobuf.CommunityDescription
CommunityDescriptionProtocolMessage []byte // community in a wrapped & signed (by owner) protocol message
ID *ecdsa.PublicKey
@ -1086,11 +1087,11 @@ func (o *Community) ValidateEditSharedAddresses(signer *ecdsa.PublicKey, request
// We treat control node as an owner with community key
func (o *Community) IsControlNode() bool {
return o.config.PrivateKey != nil && o.config.PrivateKey.PublicKey.Equal(o.ControlNode())
return o.config.PrivateKey != nil && o.config.PrivateKey.PublicKey.Equal(o.ControlNode()) && o.config.ControlDevice
}
func (o *Community) IsOwnerWithoutCommunityKey() bool {
return o.config.PrivateKey == nil && o.IsMemberOwner(o.config.MemberIdentity)
func (o *Community) IsOwner() bool {
return o.IsMemberOwner(o.config.MemberIdentity)
}
func (o *Community) IsTokenMaster() bool {
@ -2077,6 +2078,7 @@ func (o *Community) CreateDeepCopy() *Community {
config: &Config{
PrivateKey: o.config.PrivateKey,
ControlNode: o.config.ControlNode,
ControlDevice: o.config.ControlDevice,
CommunityDescription: proto.Clone(o.config.CommunityDescription).(*protobuf.CommunityDescription),
CommunityDescriptionProtocolMessage: o.config.CommunityDescriptionProtocolMessage,
ID: o.config.ID,

View file

@ -28,6 +28,7 @@ func createTestCommunity(identity *ecdsa.PrivateKey) (*Community, error) {
},
ID: &identity.PublicKey,
ControlNode: &identity.PublicKey,
ControlDevice: true,
Joined: true,
MemberIdentity: &identity.PublicKey,
}

View file

@ -838,6 +838,7 @@ func (s *CommunitySuite) newConfig(identity *ecdsa.PrivateKey, description *prot
CommunityDescription: description,
PrivateKey: identity,
ControlNode: &identity.PublicKey,
ControlDevice: true,
}
}

View file

@ -79,6 +79,7 @@ type Manager struct {
ensVerifier *ens.Verifier
ownerVerifier OwnerVerifier
identity *ecdsa.PrivateKey
installationID string
accountsManager account.Manager
tokenManager TokenManager
collectiblesManager CollectiblesManager
@ -211,7 +212,7 @@ type OwnerVerifier interface {
SafeGetSignerPubKey(ctx context.Context, chainID uint64, communityID string) (string, error)
}
func NewManager(identity *ecdsa.PrivateKey, db *sql.DB, encryptor *encryption.Protocol, logger *zap.Logger, ensverifier *ens.Verifier, ownerVerifier OwnerVerifier, transport *transport.Transport, timesource common.TimeSource, torrentConfig *params.TorrentConfig, opts ...ManagerOption) (*Manager, error) {
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, torrentConfig *params.TorrentConfig, opts ...ManagerOption) (*Manager, error) {
if identity == nil {
return nil, errors.New("empty identity")
}
@ -242,6 +243,7 @@ func NewManager(identity *ecdsa.PrivateKey, db *sql.DB, encryptor *encryption.Pr
stdoutLogger: stdoutLogger,
encryptor: encryptor,
identity: identity,
installationID: installationID,
ownerVerifier: ownerVerifier,
quit: make(chan struct{}),
transport: transport,
@ -596,7 +598,7 @@ func (m *Manager) publish(subscription *Subscription) {
}
func (m *Manager) All() ([]*Community, error) {
communities, err := m.persistence.AllCommunities(&m.identity.PublicKey)
communities, err := m.persistence.AllCommunities(&m.identity.PublicKey, m.installationID)
if err != nil {
return nil, err
}
@ -658,7 +660,7 @@ func (m *Manager) GetStoredDescriptionForCommunities(communityIDs []types.HexByt
}
func (m *Manager) Joined() ([]*Community, error) {
communities, err := m.persistence.JoinedCommunities(&m.identity.PublicKey)
communities, err := m.persistence.JoinedCommunities(&m.identity.PublicKey, m.installationID)
if err != nil {
return nil, err
}
@ -674,7 +676,7 @@ func (m *Manager) Joined() ([]*Community, error) {
}
func (m *Manager) Spectated() ([]*Community, error) {
communities, err := m.persistence.SpectatedCommunities(&m.identity.PublicKey)
communities, err := m.persistence.SpectatedCommunities(&m.identity.PublicKey, m.installationID)
if err != nil {
return nil, err
}
@ -690,7 +692,7 @@ func (m *Manager) Spectated() ([]*Community, error) {
}
func (m *Manager) JoinedAndPendingCommunitiesWithRequests() ([]*Community, error) {
communities, err := m.persistence.JoinedAndPendingCommunitiesWithRequests(&m.identity.PublicKey)
communities, err := m.persistence.JoinedAndPendingCommunitiesWithRequests(&m.identity.PublicKey, m.installationID)
if err != nil {
return nil, err
}
@ -706,7 +708,7 @@ func (m *Manager) JoinedAndPendingCommunitiesWithRequests() ([]*Community, error
}
func (m *Manager) DeletedCommunities() ([]*Community, error) {
communities, err := m.persistence.DeletedCommunities(&m.identity.PublicKey)
communities, err := m.persistence.DeletedCommunities(&m.identity.PublicKey, m.installationID)
if err != nil {
return nil, err
}
@ -722,7 +724,7 @@ func (m *Manager) DeletedCommunities() ([]*Community, error) {
}
func (m *Manager) ControlledCommunities() ([]*Community, error) {
communities, err := m.persistence.CommunitiesWithPrivateKey(&m.identity.PublicKey)
communities, err := m.persistence.CommunitiesWithPrivateKey(&m.identity.PublicKey, m.installationID)
if err != nil {
return nil, err
}
@ -738,7 +740,7 @@ func (m *Manager) ControlledCommunities() ([]*Community, error) {
}
func (m *Manager) Owned() ([]*Community, error) {
communities, err := m.persistence.CommunitiesWithPrivateKey(&m.identity.PublicKey)
communities, err := m.persistence.CommunitiesWithPrivateKey(&m.identity.PublicKey, m.installationID)
if err != nil {
return nil, err
}
@ -783,6 +785,7 @@ func (m *Manager) CreateCommunity(request *requests.CreateCommunity, publish boo
ID: &key.PublicKey,
PrivateKey: key,
ControlNode: &key.PublicKey,
ControlDevice: true,
Logger: m.logger,
Joined: true,
MemberIdentity: &m.identity.PublicKey,
@ -802,6 +805,16 @@ func (m *Manager) CreateCommunity(request *requests.CreateCommunity, publish boo
return nil, err
}
// 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
}
if publish {
m.publish(&Subscription{Community: community})
}
@ -1192,7 +1205,7 @@ func (m *Manager) ExportCommunity(id types.HexBytes) (*ecdsa.PrivateKey, error)
return community.config.PrivateKey, nil
}
func (m *Manager) ImportCommunity(key *ecdsa.PrivateKey) (*Community, error) {
func (m *Manager) ImportCommunity(key *ecdsa.PrivateKey, clock uint64) (*Community, error) {
communityID := crypto.CompressPubkey(&key.PublicKey)
community, err := m.GetByID(communityID)
@ -1201,14 +1214,29 @@ func (m *Manager) ImportCommunity(key *ecdsa.PrivateKey) (*Community, error) {
}
if community == nil {
description := &protobuf.CommunityDescription{
Permissions: &protobuf.CommunityPermissions{},
createCommunityRequest := requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_ON_REQUEST,
Name: "unknown imported",
}
description, err := createCommunityRequest.ToCommunityDescription()
if err != nil {
return nil, err
}
err = ValidateCommunityDescription(description)
if err != nil {
return nil, err
}
description.Clock = 1
description.ID = types.EncodeHex(communityID)
config := Config{
ID: &key.PublicKey,
PrivateKey: key,
ControlNode: &key.PublicKey,
ControlDevice: true,
Logger: m.logger,
Joined: true,
MemberIdentity: &m.identity.PublicKey,
@ -1221,6 +1249,7 @@ func (m *Manager) ImportCommunity(key *ecdsa.PrivateKey) (*Community, error) {
} else {
community.config.PrivateKey = key
community.config.ControlDevice = true
}
community.Join()
@ -1229,6 +1258,16 @@ func (m *Manager) ImportCommunity(key *ecdsa.PrivateKey) (*Community, error) {
return nil, err
}
// 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
}
return community, nil
}
@ -2352,7 +2391,7 @@ func (m *Manager) HandleCommunityCancelRequestToJoin(signer *ecdsa.PublicKey, re
}
func (m *Manager) HandleCommunityRequestToJoin(signer *ecdsa.PublicKey, receiver *ecdsa.PublicKey, request *protobuf.CommunityRequestToJoin) (*Community, *RequestToJoin, error) {
community, err := m.persistence.GetByID(&m.identity.PublicKey, request.CommunityId)
community, err := m.GetByID(request.CommunityId)
if err != nil {
return nil, nil, err
}
@ -3394,7 +3433,7 @@ func initializeCommunity(community *Community) error {
}
func (m *Manager) GetByID(id []byte) (*Community, error) {
community, err := m.persistence.GetByID(&m.identity.PublicKey, id)
community, err := m.persistence.GetByID(&m.identity.PublicKey, m.installationID, id)
if err != nil {
return nil, err
}
@ -4640,7 +4679,7 @@ func (m *Manager) SaveCommunityToken(token *community_token.CommunityToken, crop
return token, m.persistence.AddCommunityToken(token)
}
func (m *Manager) AddCommunityToken(token *community_token.CommunityToken) (*Community, error) {
func (m *Manager) AddCommunityToken(token *community_token.CommunityToken, clock uint64) (*Community, error) {
if token == nil {
return nil, errors.New("Token is absent in database")
}
@ -4703,7 +4742,10 @@ func (m *Manager) AddCommunityToken(token *community_token.CommunityToken) (*Com
}
if token.PrivilegesLevel == community_token.OwnerLevel {
m.promoteSelfToControlNode(community)
err = m.promoteSelfToControlNode(community, clock)
if err != nil {
return nil, err
}
}
}
@ -5026,7 +5068,7 @@ func (m *Manager) createCommunityTokenPermission(request *requests.CreateCommuni
}
func (m *Manager) PromoteSelfToControlNode(communityID types.HexBytes) (*Community, error) {
func (m *Manager) PromoteSelfToControlNode(communityID types.HexBytes, clock uint64) (*Community, error) {
community, err := m.GetByID(communityID)
if err != nil {
return nil, err
@ -5036,16 +5078,34 @@ func (m *Manager) PromoteSelfToControlNode(communityID types.HexBytes) (*Communi
return nil, ErrOrgNotFound
}
m.promoteSelfToControlNode(community)
err = m.promoteSelfToControlNode(community, clock)
if err != nil {
return nil, err
}
return community, m.saveAndPublish(community)
}
func (m *Manager) promoteSelfToControlNode(community *Community) {
func (m *Manager) promoteSelfToControlNode(community *Community, clock uint64) error {
community.setPrivateKey(m.identity)
if !community.ControlNode().Equal(&m.identity.PublicKey) {
community.setControlNode(&m.identity.PublicKey)
}
// 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 err
}
community.config.ControlDevice = true
community.increaseClock()
return nil
}
func (m *Manager) shareRequestsToJoinWithNewPrivilegedMembers(community *Community, newPrivilegedMembers map[protobuf.CommunityMember_Roles][]*ecdsa.PublicKey) error {
@ -5183,3 +5243,24 @@ func (m *Manager) CreateCommunityTokenDeploymentSignature(ctx context.Context, c
}
return crypto.Sign(digest, community.PrivateKey())
}
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
}

View file

@ -55,7 +55,7 @@ func (s *ManagerSuite) buildManager(ownerVerifier OwnerVerifier) *Manager {
key, err := crypto.GenerateKey()
s.Require().NoError(err)
s.Require().NoError(err)
m, err := NewManager(key, db, nil, nil, nil, ownerVerifier, nil, &TimeSourceStub{}, nil)
m, err := NewManager(key, "", db, nil, nil, nil, ownerVerifier, nil, &TimeSourceStub{}, nil)
s.Require().NoError(err)
s.Require().NoError(m.Start())
return m
@ -169,7 +169,7 @@ func (s *ManagerSuite) setupManagerForTokenPermissions() (*Manager, *testCollect
WithTokenManager(tm),
}
m, err := NewManager(key, db, nil, nil, nil, nil, nil, &TimeSourceStub{}, nil, options...)
m, err := NewManager(key, "", db, nil, nil, nil, nil, nil, &TimeSourceStub{}, nil, options...)
s.Require().NoError(err)
s.Require().NoError(m.Start())
@ -279,7 +279,6 @@ func (s *ManagerSuite) TestRetrieveCollectibles() {
}
func (s *ManagerSuite) TestCreateCommunity() {
request := &requests.CreateCommunity{
Name: "status",
Description: "token membership description",
@ -302,6 +301,7 @@ func (s *ManagerSuite) TestCreateCommunity() {
s.Require().Equal(community.ID(), actualCommunity.ID())
s.Require().Equal(community.PrivateKey(), actualCommunity.PrivateKey())
s.Require().True(community.IsControlNode())
s.Require().True(proto.Equal(community.config.CommunityDescription, actualCommunity.config.CommunityDescription))
}

View file

@ -33,10 +33,11 @@ var ErrOldRequestToLeave = errors.New("old request to leave")
const OR = " OR "
const communitiesBaseQuery = `
SELECT c.id, c.private_key, c.control_node, c.description, c.joined, c.spectated, c.verified, c.muted, c.muted_till, r.clock, ae.raw_events, ae.raw_description, c.shard_cluster, c.shard_index
SELECT c.id, c.private_key, c.control_node, c.description, c.joined, c.spectated, c.verified, c.muted, c.muted_till, r.clock, ae.raw_events, ae.raw_description, c.shard_cluster, c.shard_index, ccn.installation_id
FROM communities_communities c
LEFT JOIN communities_requests_to_join r ON c.id = r.community_id AND r.public_key = ?
LEFT JOIN communities_events ae ON c.id = ae.id`
LEFT JOIN communities_events ae ON c.id = ae.id
LEFT JOIN communities_control_node ccn ON c.id = ccn.community_id`
func (p *Persistence) SaveCommunity(community *Community) error {
id := community.ID()
@ -127,8 +128,7 @@ func (p *Persistence) ShouldHandleSyncCommunity(community *protobuf.SyncInstalla
}
}
func (p *Persistence) queryCommunities(memberIdentity *ecdsa.PublicKey, query string) (response []*Community, err error) {
func (p *Persistence) queryCommunities(memberIdentity *ecdsa.PublicKey, installationID string, query string) (response []*Community, err error) {
rows, err := p.db.Query(query, common.PubkeyToHex(memberIdentity))
if err != nil {
return nil, err
@ -149,10 +149,11 @@ func (p *Persistence) queryCommunities(memberIdentity *ecdsa.PublicKey, query st
var joined, spectated, verified, muted bool
var muteTill sql.NullTime
var cluster, index, requestedToJoinAt sql.NullInt64
var installationIDStr sql.NullString
// Community events specific fields
var eventsBytes, eventsDescriptionBytes []byte
err := rows.Scan(&publicKeyBytes, &privateKeyBytes, &controlNodeBytes, &descriptionBytes, &joined, &spectated, &verified, &muted, &muteTill, &requestedToJoinAt, &eventsBytes, &eventsDescriptionBytes, &cluster, &index)
err := rows.Scan(&publicKeyBytes, &privateKeyBytes, &controlNodeBytes, &descriptionBytes, &joined, &spectated, &verified, &muted, &muteTill, &requestedToJoinAt, &eventsBytes, &eventsDescriptionBytes, &cluster, &index, &installationIDStr)
if err != nil {
return nil, err
}
@ -169,7 +170,9 @@ func (p *Persistence) queryCommunities(memberIdentity *ecdsa.PublicKey, query st
indexValue = &v
}
org, err := p.unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, controlNodeBytes, descriptionBytes, joined, spectated, verified, muted, muteTill.Time, uint64(requestedToJoinAt.Int64), eventsBytes, eventsDescriptionBytes, clusterValue, indexValue, p.logger)
isControlDevice := installationIDStr.Valid && installationIDStr.String == installationID
org, err := p.unmarshalCommunityFromDB(memberIdentity, isControlDevice, publicKeyBytes, privateKeyBytes, controlNodeBytes, descriptionBytes, joined, spectated, verified, muted, muteTill.Time, uint64(requestedToJoinAt.Int64), eventsBytes, eventsDescriptionBytes, clusterValue, indexValue, p.logger)
if err != nil {
return nil, err
}
@ -180,21 +183,21 @@ func (p *Persistence) queryCommunities(memberIdentity *ecdsa.PublicKey, query st
}
func (p *Persistence) AllCommunities(memberIdentity *ecdsa.PublicKey) ([]*Community, error) {
return p.queryCommunities(memberIdentity, communitiesBaseQuery)
func (p *Persistence) AllCommunities(memberIdentity *ecdsa.PublicKey, installationID string) ([]*Community, error) {
return p.queryCommunities(memberIdentity, installationID, communitiesBaseQuery)
}
func (p *Persistence) JoinedCommunities(memberIdentity *ecdsa.PublicKey) ([]*Community, error) {
func (p *Persistence) JoinedCommunities(memberIdentity *ecdsa.PublicKey, installationID string) ([]*Community, error) {
query := communitiesBaseQuery + ` WHERE c.joined`
return p.queryCommunities(memberIdentity, query)
return p.queryCommunities(memberIdentity, installationID, query)
}
func (p *Persistence) SpectatedCommunities(memberIdentity *ecdsa.PublicKey) ([]*Community, error) {
func (p *Persistence) SpectatedCommunities(memberIdentity *ecdsa.PublicKey, installationID string) ([]*Community, error) {
query := communitiesBaseQuery + ` WHERE c.spectated`
return p.queryCommunities(memberIdentity, query)
return p.queryCommunities(memberIdentity, installationID, query)
}
func (p *Persistence) rowsToCommunities(memberIdentity *ecdsa.PublicKey, rows *sql.Rows) (comms []*Community, err error) {
func (p *Persistence) rowsToCommunities(memberIdentity *ecdsa.PublicKey, installationID string, rows *sql.Rows) (comms []*Community, err error) {
defer func() {
if err != nil {
// Don't shadow original error
@ -213,6 +216,7 @@ func (p *Persistence) rowsToCommunities(memberIdentity *ecdsa.PublicKey, rows *s
var joined, spectated, verified, muted bool
var muteTill sql.NullTime
var cluster, index sql.NullInt64
var installationIDStr sql.NullString
// Request to join specific fields
var rtjID, rtjCommunityID []byte
@ -224,7 +228,7 @@ func (p *Persistence) rowsToCommunities(memberIdentity *ecdsa.PublicKey, rows *s
err = rows.Scan(
&publicKeyBytes, &privateKeyBytes, &controlNodeBytes, &descriptionBytes, &joined, &spectated, &verified, &muted, &muteTill,
&rtjID, &rtjPublicKey, &rtjClock, &rtjENSName, &rtjChatID, &rtjCommunityID, &rtjState, &eventsBytes, &eventsDescriptionBytes, &cluster, &index)
&rtjID, &rtjPublicKey, &rtjClock, &rtjENSName, &rtjChatID, &rtjCommunityID, &rtjState, &eventsBytes, &eventsDescriptionBytes, &cluster, &index, &installationIDStr)
if err != nil {
return nil, err
}
@ -241,7 +245,9 @@ func (p *Persistence) rowsToCommunities(memberIdentity *ecdsa.PublicKey, rows *s
indexValue = &v
}
comm, err = p.unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, controlNodeBytes, descriptionBytes, joined, spectated, verified, muted, muteTill.Time, uint64(rtjClock.Int64), eventsBytes, eventsDescriptionBytes, clusterValue, indexValue, p.logger)
isControlDevice := installationIDStr.Valid && installationIDStr.String == installationID
comm, err = p.unmarshalCommunityFromDB(memberIdentity, isControlDevice, publicKeyBytes, privateKeyBytes, controlNodeBytes, descriptionBytes, joined, spectated, verified, muted, muteTill.Time, uint64(rtjClock.Int64), eventsBytes, eventsDescriptionBytes, clusterValue, indexValue, p.logger)
if err != nil {
return nil, err
}
@ -256,13 +262,14 @@ func (p *Persistence) rowsToCommunities(memberIdentity *ecdsa.PublicKey, rows *s
return comms, nil
}
func (p *Persistence) JoinedAndPendingCommunitiesWithRequests(memberIdentity *ecdsa.PublicKey) (comms []*Community, err error) {
func (p *Persistence) JoinedAndPendingCommunitiesWithRequests(memberIdentity *ecdsa.PublicKey, installationID string) (comms []*Community, err error) {
query := `SELECT
c.id, c.private_key, c.control_node, c.description, c.joined, c.spectated, c.verified, c.muted, c.muted_till,
r.id, r.public_key, r.clock, r.ens_name, r.chat_id, r.community_id, r.state, ae.raw_events, ae.raw_description, c.shard_cluster, c.shard_index
r.id, r.public_key, r.clock, r.ens_name, r.chat_id, r.community_id, r.state, ae.raw_events, ae.raw_description, c.shard_cluster, c.shard_index, ccn.installation_id
FROM communities_communities c
LEFT JOIN communities_requests_to_join r ON c.id = r.community_id AND r.public_key = ?
LEFT JOIN communities_events ae ON c.id = ae.id
LEFT JOIN communities_control_node ccn ON c.id = ccn.community_id
WHERE c.Joined OR r.state = ?`
rows, err := p.db.Query(query, common.PubkeyToHex(memberIdentity), RequestToJoinStatePending)
@ -270,16 +277,17 @@ WHERE c.Joined OR r.state = ?`
return nil, err
}
return p.rowsToCommunities(memberIdentity, rows)
return p.rowsToCommunities(memberIdentity, installationID, rows)
}
func (p *Persistence) DeletedCommunities(memberIdentity *ecdsa.PublicKey) (comms []*Community, err error) {
func (p *Persistence) DeletedCommunities(memberIdentity *ecdsa.PublicKey, installationID string) (comms []*Community, err error) {
query := `SELECT
c.id, c.private_key, c.control_node, c.description, c.joined, c.spectated, c.verified, c.muted, c.muted_till,
r.id, r.public_key, r.clock, r.ens_name, r.chat_id, r.community_id, r.state, ae.raw_events, ae.raw_description, c.shard_cluster, c.shard_index
r.id, r.public_key, r.clock, r.ens_name, r.chat_id, r.community_id, r.state, ae.raw_events, ae.raw_description, c.shard_cluster, c.shard_index, ccn.installation_id
FROM communities_communities c
LEFT JOIN communities_requests_to_join r ON c.id = r.community_id AND r.public_key = ?
LEFT JOIN communities_events ae ON c.id = ae.id
LEFT JOIN communities_control_node ccn ON c.id = ccn.community_id
WHERE NOT c.Joined AND (r.community_id IS NULL or r.state != ?)`
rows, err := p.db.Query(query, common.PubkeyToHex(memberIdentity), RequestToJoinStatePending)
@ -287,15 +295,15 @@ WHERE NOT c.Joined AND (r.community_id IS NULL or r.state != ?)`
return nil, err
}
return p.rowsToCommunities(memberIdentity, rows)
return p.rowsToCommunities(memberIdentity, installationID, rows)
}
func (p *Persistence) CommunitiesWithPrivateKey(memberIdentity *ecdsa.PublicKey) ([]*Community, error) {
func (p *Persistence) CommunitiesWithPrivateKey(memberIdentity *ecdsa.PublicKey, installationID string) ([]*Community, error) {
query := communitiesBaseQuery + ` WHERE c.private_key IS NOT NULL`
return p.queryCommunities(memberIdentity, query)
return p.queryCommunities(memberIdentity, installationID, query)
}
func (p *Persistence) GetByID(memberIdentity *ecdsa.PublicKey, id []byte) (*Community, error) {
func (p *Persistence) GetByID(memberIdentity *ecdsa.PublicKey, installationID string, id []byte) (*Community, error) {
var publicKeyBytes, privateKeyBytes, controlNodeBytes, descriptionBytes []byte
var joined bool
var spectated bool
@ -303,11 +311,12 @@ func (p *Persistence) GetByID(memberIdentity *ecdsa.PublicKey, id []byte) (*Comm
var muted bool
var muteTill sql.NullTime
var requestedToJoinAt, cluster, index sql.NullInt64
var installationIDStr sql.NullString
// Community events specific fields
var eventsBytes, eventsDescriptionBytes []byte
err := p.db.QueryRow(communitiesBaseQuery+` WHERE c.id = ?`, common.PubkeyToHex(memberIdentity), id).Scan(&publicKeyBytes, &privateKeyBytes, &controlNodeBytes, &descriptionBytes, &joined, &spectated, &verified, &muted, &muteTill, &requestedToJoinAt, &eventsBytes, &eventsDescriptionBytes, &cluster, &index)
err := p.db.QueryRow(communitiesBaseQuery+` WHERE c.id = ?`, common.PubkeyToHex(memberIdentity), id).Scan(&publicKeyBytes, &privateKeyBytes, &controlNodeBytes, &descriptionBytes, &joined, &spectated, &verified, &muted, &muteTill, &requestedToJoinAt, &eventsBytes, &eventsDescriptionBytes, &cluster, &index, &installationIDStr)
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
@ -326,10 +335,12 @@ func (p *Persistence) GetByID(memberIdentity *ecdsa.PublicKey, id []byte) (*Comm
indexValue = &v
}
return p.unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, controlNodeBytes, descriptionBytes, joined, spectated, verified, muted, muteTill.Time, uint64(requestedToJoinAt.Int64), eventsBytes, eventsDescriptionBytes, clusterValue, indexValue, p.logger)
isControlDevice := installationIDStr.Valid && installationIDStr.String == installationID
return p.unmarshalCommunityFromDB(memberIdentity, isControlDevice, publicKeyBytes, privateKeyBytes, controlNodeBytes, descriptionBytes, joined, spectated, verified, muted, muteTill.Time, uint64(requestedToJoinAt.Int64), eventsBytes, eventsDescriptionBytes, clusterValue, indexValue, p.logger)
}
func (p *Persistence) unmarshalCommunityFromDB(memberIdentity *ecdsa.PublicKey, publicKeyBytes, privateKeyBytes, controlNodeBytes, wrappedCommunity []byte, joined,
func (p *Persistence) unmarshalCommunityFromDB(memberIdentity *ecdsa.PublicKey, isControlDevice bool, publicKeyBytes, privateKeyBytes, controlNodeBytes, wrappedCommunity []byte, joined,
spectated, verified, muted bool, muteTill time.Time, requestedToJoinAt uint64, eventsBytes []byte,
eventsDescriptionBytes []byte, cluster *uint, index *uint, logger *zap.Logger) (*Community, error) {
@ -377,6 +388,7 @@ func (p *Persistence) unmarshalCommunityFromDB(memberIdentity *ecdsa.PublicKey,
config := Config{
PrivateKey: privateKey,
ControlNode: controlNode,
ControlDevice: isControlDevice,
CommunityDescription: description,
MemberIdentity: memberIdentity,
CommunityDescriptionProtocolMessage: wrappedCommunity,
@ -1515,3 +1527,36 @@ func (p *Persistence) DeleteCommunitiesToValidateByCommunityID(communityID []byt
_, err := p.db.Exec(`DELETE FROM communities_validate_signer WHERE id = ?`, communityID)
return err
}
func (p *Persistence) GetSyncControlNode(communityID types.HexBytes) (*protobuf.SyncCommunityControlNode, error) {
result := &protobuf.SyncCommunityControlNode{}
err := p.db.QueryRow(`
SELECT clock, installation_id
FROM communities_control_node
WHERE community_id = ?
`, communityID).Scan(&result.Clock, &result.InstallationId)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
return result, nil
}
func (p *Persistence) SaveSyncControlNode(communityID types.HexBytes, clock uint64, installationID string) error {
_, err := p.db.Exec(
`INSERT INTO communities_control_node (
community_id,
clock,
installation_id
) VALUES (?, ?, ?)`,
communityID,
clock,
installationID,
)
return err
}

View file

@ -47,7 +47,7 @@ func (s *PersistenceSuite) TestSaveCommunity() {
s.Require().NoError(err)
// there is one community inserted by default
communities, err := s.db.AllCommunities(&id.PublicKey)
communities, err := s.db.AllCommunities(&id.PublicKey, "")
s.Require().NoError(err)
s.Require().Len(communities, 1)
@ -55,6 +55,7 @@ func (s *PersistenceSuite) TestSaveCommunity() {
config: &Config{
PrivateKey: id,
ControlNode: &id.PublicKey,
ControlDevice: true,
ID: &id.PublicKey,
Joined: true,
Spectated: true,
@ -66,7 +67,7 @@ func (s *PersistenceSuite) TestSaveCommunity() {
}
s.Require().NoError(s.db.SaveCommunity(&community))
communities, err = s.db.AllCommunities(&id.PublicKey)
communities, err = s.db.AllCommunities(&id.PublicKey, "")
s.Require().NoError(err)
s.Require().Len(communities, 2)
s.Equal(types.HexBytes(crypto.CompressPubkey(&id.PublicKey)), communities[1].ID())
@ -199,7 +200,7 @@ func (s *PersistenceSuite) TestJoinedAndPendingCommunitiesWithRequests() {
// Add a new community that we have joined
com := s.makeNewCommunity(identity)
com.Join()
sc, err := com.ToSyncInstallationCommunityProtobuf(clock, nil)
sc, err := com.ToSyncInstallationCommunityProtobuf(clock, nil, nil)
s.Require().NoError(err, "Community.ToSyncInstallationCommunityProtobuf shouldn't give any error")
err = s.db.saveRawCommunityRow(fromSyncCommunityProtobuf(sc))
s.Require().NoError(err, "saveRawCommunityRow")
@ -219,7 +220,7 @@ func (s *PersistenceSuite) TestJoinedAndPendingCommunitiesWithRequests() {
err = s.db.SaveRequestToJoin(rtj)
s.Require().NoError(err, "SaveRequestToJoin shouldn't give any error")
comms, err := s.db.JoinedAndPendingCommunitiesWithRequests(&identity.PublicKey)
comms, err := s.db.JoinedAndPendingCommunitiesWithRequests(&identity.PublicKey, "")
s.Require().NoError(err, "JoinedAndPendingCommunitiesWithRequests shouldn't give any error")
s.Len(comms, 2, "Should have 2 communities")
@ -260,6 +261,7 @@ func (s *PersistenceSuite) makeNewCommunity(identity *ecdsa.PrivateKey) *Communi
MemberIdentity: &identity.PublicKey,
PrivateKey: comPrivKey,
ControlNode: &comPrivKey.PublicKey,
ControlDevice: true,
ID: &comPrivKey.PublicKey,
}, &TimeSourceStub{})
s.NoError(err, "New shouldn't give any error")

View file

@ -434,36 +434,15 @@ func joinCommunity(s *suite.Suite, community *communities.Community, owner *Mess
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusPending)
// Retrieve and accept join request
err = tt.RetryWithBackOff(func() error {
response, err := owner.RetrieveAll()
if err != nil {
return err
}
if len(response.Communities()) == 0 {
return errors.New("no communities in response (accept join request)")
}
if !response.Communities()[0].HasMember(&user.identity.PublicKey) {
return errors.New("user not accepted")
}
return nil
})
_, err = WaitOnMessengerResponse(owner, func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && r.Communities()[0].HasMember(&user.identity.PublicKey)
}, "user not accepted")
s.Require().NoError(err)
// Retrieve join request response
err = tt.RetryWithBackOff(func() error {
response, err := user.RetrieveAll()
if err != nil {
return err
}
if len(response.Communities()) == 0 {
return errors.New("no communities in response (join request response)")
}
if !response.Communities()[0].HasMember(&user.identity.PublicKey) {
return errors.New("user not a member")
}
return nil
})
_, err = WaitOnMessengerResponse(user, func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && r.Communities()[0].HasMember(&user.identity.PublicKey)
}, "user not accepted")
s.Require().NoError(err)
}

View file

@ -1,7 +1,6 @@
package protocol
import (
"context"
"crypto/ecdsa"
"testing"
"time"
@ -20,7 +19,6 @@ import (
"github.com/status-im/status-go/protocol/tt"
"github.com/status-im/status-go/services/communitytokens"
"github.com/status-im/status-go/services/wallet/bigint"
"github.com/status-im/status-go/transactions"
"github.com/status-im/status-go/waku"
)
@ -105,7 +103,6 @@ func (s *MessengerCommunitiesSignersSuite) joinCommunity(controlNode *Messenger,
// Both John and Bob accepts the changes
func (s *MessengerCommunitiesSignersSuite) TestControlNodeUpdateSigner() {
// Create a community
// Transfer ownership
// Process message
@ -117,60 +114,12 @@ func (s *MessengerCommunitiesSignersSuite) TestControlNodeUpdateSigner() {
s.joinCommunity(s.john, community, s.bob)
s.joinCommunity(s.john, community, s.alice)
// john as control node publishes community update
johnDescr := "john's description"
response, err := s.john.EditCommunity(&requests.EditCommunity{
CommunityID: community.ID(),
CreateCommunity: requests.CreateCommunity{
Name: community.Name(),
Description: johnDescr,
Color: community.Color(),
Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP,
},
})
s.Require().NoError(err)
s.Require().Equal(johnDescr, response.Communities()[0].Description().Identity.Description)
// bob accepts community update
_, err = WaitOnMessengerResponse(
s.bob,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && r.Communities()[0].Description().Identity.Description == johnDescr
},
"no communities",
)
s.Require().NoError(err)
// alice accepts community update
_, err = WaitOnMessengerResponse(
s.alice,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && r.Communities()[0].Description().Identity.Description == johnDescr
},
"no communities",
)
s.Require().NoError(err)
// Alice will be transferred the ownership token, and alice will let others know
// john mints owner token
var chainID uint64 = 1
communityAddress := "community-address"
tokenAddress := "token-address"
tokenName := "tokenName"
tokenSymbol := "TSM"
// Update mock
// The signer for the community returned by the contracts should be alice
s.collectiblesServiceMock.SetSignerPubkeyForCommunity(community.ID(), common.PubkeyToHex(&s.alice.identity.PublicKey))
s.collectiblesServiceMock.SetMockCollectibleContractData(chainID, tokenAddress,
&communitytokens.CollectibleContractData{TotalSupply: &bigint.BigInt{}})
community, err = s.alice.communitiesManager.PromoteSelfToControlNode(community.ID())
s.Require().NoError(err)
s.Require().True(community.IsControlNode())
// Create community token
_, err = s.alice.SaveCommunityToken(&token.CommunityToken{
_, err := s.john.SaveCommunityToken(&token.CommunityToken{
TokenType: protobuf.CommunityTokenType_ERC721,
CommunityID: community.IDString(),
Address: tokenAddress,
@ -182,15 +131,46 @@ func (s *MessengerCommunitiesSignersSuite) TestControlNodeUpdateSigner() {
}, nil)
s.Require().NoError(err)
err = s.alice.AddCommunityToken(community.IDString(), int(chainID), tokenAddress)
// john adds minted owner token to community
err = s.john.AddCommunityToken(community.IDString(), int(chainID), tokenAddress)
s.Require().NoError(err)
// make alice the control node
transaction := transactions.SendTxArgs{}
_, err = s.alice.SetCommunitySignerPubKey(context.Background(), community.ID(), chainID, communityAddress, transaction, "password", common.PubkeyToHex(&s.alice.identity.PublicKey))
// update mock - the signer for the community returned by the contracts should be john
s.collectiblesServiceMock.SetSignerPubkeyForCommunity(community.ID(), common.PubkeyToHex(&s.john.identity.PublicKey))
s.collectiblesServiceMock.SetMockCollectibleContractData(chainID, tokenAddress,
&communitytokens.CollectibleContractData{TotalSupply: &bigint.BigInt{}})
// bob accepts community update
_, err = WaitOnSignaledMessengerResponse(
s.bob,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && len(r.Communities()[0].CommunityTokensMetadata()) == 1
},
"no communities",
)
s.Require().NoError(err)
// john accepts community update
// alice accepts community update
_, err = WaitOnSignaledMessengerResponse(
s.alice,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && len(r.Communities()[0].CommunityTokensMetadata()) == 1
},
"no communities",
)
s.Require().NoError(err)
// Alice will be transferred the ownership token, and alice will let others know
// update mock - the signer for the community returned by the contracts should be alice
s.collectiblesServiceMock.SetSignerPubkeyForCommunity(community.ID(), common.PubkeyToHex(&s.alice.identity.PublicKey))
s.collectiblesServiceMock.SetMockCollectibleContractData(chainID, tokenAddress,
&communitytokens.CollectibleContractData{TotalSupply: &bigint.BigInt{}})
community, err = s.alice.PromoteSelfToControlNode(community.ID())
s.Require().NoError(err)
s.Require().True(community.IsControlNode())
// john accepts community update from alice (new control node)
_, err = WaitOnSignaledMessengerResponse(
s.john,
func(r *MessengerResponse) bool {
@ -207,12 +187,11 @@ func (s *MessengerCommunitiesSignersSuite) TestControlNodeUpdateSigner() {
s.Require().True(common.IsPubKeyEqual(johnCommunity.ControlNode(), &s.alice.identity.PublicKey))
s.Require().False(johnCommunity.IsControlNode())
// We check the control node is correctly set on bob
// bob accepts community update from alice (new control node)
_, err = WaitOnSignaledMessengerResponse(
s.bob,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && r.Communities()[0].IDString() == community.IDString()
},
"no communities",
)

View file

@ -39,7 +39,7 @@ func TestMessengerCommunitiesSuite(t *testing.T) {
type MessengerCommunitiesSuite struct {
suite.Suite
admin *Messenger
owner *Messenger
bob *Messenger
alice *Messenger
// If one wants to send messages between different instances of Messenger,
@ -57,13 +57,13 @@ func (s *MessengerCommunitiesSuite) SetupTest() {
s.shh = gethbridge.NewGethWakuWrapper(shh)
s.Require().NoError(shh.Start())
s.admin = s.newMessenger()
s.owner = s.newMessenger()
s.bob = s.newMessenger()
s.alice = s.newMessenger()
s.admin.communitiesManager.RekeyInterval = 50 * time.Millisecond
s.owner.communitiesManager.RekeyInterval = 50 * time.Millisecond
_, err := s.admin.Start()
_, err := s.owner.Start()
s.Require().NoError(err)
_, err = s.bob.Start()
s.Require().NoError(err)
@ -72,7 +72,7 @@ func (s *MessengerCommunitiesSuite) SetupTest() {
}
func (s *MessengerCommunitiesSuite) TearDownTest() {
s.Require().NoError(s.admin.Shutdown())
s.Require().NoError(s.owner.Shutdown())
s.Require().NoError(s.bob.Shutdown())
s.Require().NoError(s.alice.Shutdown())
_ = s.logger.Sync()
@ -381,16 +381,16 @@ func (s *MessengerCommunitiesSuite) TestJoinCommunity() {
}
func (s *MessengerCommunitiesSuite) createCommunity() (*communities.Community, *Chat) {
return createCommunity(&s.Suite, s.admin)
return createCommunity(&s.Suite, s.owner)
}
func (s *MessengerCommunitiesSuite) advertiseCommunityTo(community *communities.Community, user *Messenger) {
advertiseCommunityTo(&s.Suite, community, s.admin, user)
advertiseCommunityTo(&s.Suite, community, s.owner, user)
}
func (s *MessengerCommunitiesSuite) joinCommunity(community *communities.Community, user *Messenger) {
request := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
joinCommunity(&s.Suite, community, s.admin, user, request)
joinCommunity(&s.Suite, community, s.owner, user, request)
}
func (s *MessengerCommunitiesSuite) TestCommunityContactCodeAdvertisement() {
@ -456,7 +456,7 @@ func (s *MessengerCommunitiesSuite) TestPostToCommunityChat() {
var response *MessengerResponse
// Pull message and make sure org is received
err = tt.RetryWithBackOff(func() error {
response, err = s.admin.RetrieveAll()
response, err = s.owner.RetrieveAll()
if err != nil {
return err
}
@ -495,35 +495,35 @@ func (s *MessengerCommunitiesSuite) TestImportCommunity() {
ChatIDs: []string{},
}
response, err := s.admin.CreateCommunityCategory(category)
response, err := s.owner.CreateCommunityCategory(category)
s.Require().NoError(err)
community = response.Communities()[0]
privateKey, err := s.admin.ExportCommunity(community.ID())
s.advertiseCommunityTo(community, s.bob)
s.joinCommunity(community, s.bob)
privateKey, err := s.owner.ExportCommunity(community.ID())
s.Require().NoError(err)
_, err = s.alice.ImportCommunity(ctx, privateKey)
s.Require().NoError(err)
// Invite user on admin side
s.advertiseCommunityTo(community, s.bob)
s.joinCommunity(community, s.bob)
// Pull message and make sure org is received
err = tt.RetryWithBackOff(func() error {
response, err = s.alice.RetrieveAll()
if err != nil {
return err
}
if len(response.Communities()) == 0 {
return errors.New("community not received")
}
if !response.Communities()[0].IsControlNode() {
return errors.New("isn't admin despite import")
}
return nil
newDescription := "new description set post import"
_, err = s.alice.EditCommunity(&requests.EditCommunity{
CommunityID: community.ID(),
CreateCommunity: requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_ON_REQUEST,
Name: community.Name(),
Color: community.Color(),
Description: newDescription,
},
})
s.Require().NoError(err)
// bob receives new description
_, err = WaitOnMessengerResponse(s.bob, func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && r.Communities()[0].DescriptionText() == newDescription
}, "new description not received")
s.Require().NoError(err)
}
@ -550,7 +550,7 @@ func (s *MessengerCommunitiesSuite) TestRemovePrivateKey() {
s.Require().Len(response.Communities(), 1)
community = response.Communities()[0]
s.Require().True(community.IsOwnerWithoutCommunityKey())
s.Require().True(community.IsOwner())
s.Require().False(community.IsControlNode())
}
@ -1932,7 +1932,7 @@ func (s *MessengerCommunitiesSuite) TestLeaveAndRejoinCommunity() {
s.joinCommunity(community, s.alice)
s.joinCommunity(community, s.bob)
joinedCommunities, err := s.admin.communitiesManager.Joined()
joinedCommunities, err := s.owner.communitiesManager.Joined()
s.Require().NoError(err)
s.Require().Equal(3, joinedCommunities[0].MembersCount())
@ -1959,7 +1959,7 @@ func (s *MessengerCommunitiesSuite) TestLeaveAndRejoinCommunity() {
if response.Communities()[0].MembersCount() != 2 {
communityMembersError = fmt.Errorf("invalid number of members: %d", response.Communities()[0].MembersCount())
} else if !response.Communities()[0].HasMember(&s.admin.identity.PublicKey) {
} else if !response.Communities()[0].HasMember(&s.owner.identity.PublicKey) {
communityMembersError = errors.New("admin removed from community")
} else if !response.Communities()[0].HasMember(&s.bob.identity.PublicKey) {
communityMembersError = errors.New("bob removed from community")
@ -1970,7 +1970,7 @@ func (s *MessengerCommunitiesSuite) TestLeaveAndRejoinCommunity() {
return communityMembersError
}
err = tt.RetryWithBackOff(func() error {
return verifyCommunityMembers(s.admin)
return verifyCommunityMembers(s.owner)
})
s.Require().NoError(err)
err = tt.RetryWithBackOff(func() error {
@ -1978,7 +1978,7 @@ func (s *MessengerCommunitiesSuite) TestLeaveAndRejoinCommunity() {
})
s.Require().NoError(err)
joinedCommunities, err = s.admin.communitiesManager.Joined()
joinedCommunities, err = s.owner.communitiesManager.Joined()
s.Require().NoError(err)
s.Require().Equal(2, joinedCommunities[0].MembersCount())
@ -1995,7 +1995,7 @@ func (s *MessengerCommunitiesSuite) TestLeaveAndRejoinCommunity() {
// alice can rejoin
s.joinCommunity(community, s.alice)
joinedCommunities, err = s.admin.communitiesManager.Joined()
joinedCommunities, err = s.owner.communitiesManager.Joined()
s.Require().NoError(err)
s.Require().Equal(3, joinedCommunities[0].MembersCount())
@ -2174,7 +2174,7 @@ func (s *MessengerCommunitiesSuite) TestBanUser() {
s.advertiseCommunityTo(community, s.alice)
s.joinCommunity(community, s.alice)
response, err := s.admin.BanUserFromCommunity(
response, err := s.owner.BanUserFromCommunity(
context.Background(),
&requests.BanUserFromCommunity{
CommunityID: community.ID(),
@ -2189,7 +2189,7 @@ func (s *MessengerCommunitiesSuite) TestBanUser() {
s.Require().False(community.HasMember(&s.alice.identity.PublicKey))
s.Require().True(community.IsBanned(&s.alice.identity.PublicKey))
response, err = s.admin.UnbanUserFromCommunity(
response, err = s.owner.UnbanUserFromCommunity(
&requests.UnbanUserFromCommunity{
CommunityID: community.ID(),
User: common.PubkeyToHexBytes(&s.alice.identity.PublicKey),
@ -2439,9 +2439,12 @@ func (s *MessengerCommunitiesSuite) TestSyncCommunity() {
s.Equal(newCommunity.InvitationOnly(), tnc.InvitationOnly())
s.True(newCommunity.IsControlNode())
s.False(newCommunity.IsOwnerWithoutCommunityKey())
s.True(tnc.IsOwnerWithoutCommunityKey())
s.True(newCommunity.IsOwner())
// Even though synced device have the private key, it is not the control node
// There can be only one control node
s.False(tnc.IsControlNode())
s.True(tnc.IsOwner())
}
// TestSyncCommunity_RequestToJoin tests more complex pairing and syncing scenario where one paired device
@ -2766,6 +2769,39 @@ func (s *MessengerCommunitiesSuite) TestSyncCommunity_Leave() {
s.Equal(aCom, aoCom)
}
func (s *MessengerCommunitiesSuite) TestSyncCommunity_ImportCommunity() {
// Owner creates community
community, _ := s.createCommunity()
s.Require().True(community.IsControlNode())
// New device is created & paired
ownersOtherDevice := s.createOtherDevice(s.owner)
PairDevices(&s.Suite, ownersOtherDevice, s.owner)
PairDevices(&s.Suite, s.owner, ownersOtherDevice)
privateKey, err := s.owner.ExportCommunity(community.ID())
s.Require().NoError(err)
// New device imports the community (before it is received via sync message)
ctx := context.Background()
response, err := ownersOtherDevice.ImportCommunity(ctx, privateKey)
s.Require().NoError(err)
s.Require().Len(response.Communities(), 1)
s.Require().Equal(community.IDString(), response.Communities()[0].IDString())
// New device becomes the control node
s.Require().True(response.Communities()[0].IsControlNode())
// Old device is no longer the control node
_, err = WaitOnMessengerResponse(s.owner, func(response *MessengerResponse) bool {
if len(response.Communities()) != 1 {
return false
}
c := response.Communities()[0]
return c.IDString() == community.IDString() && !c.IsControlNode()
}, "community not synced")
s.Require().NoError(err)
}
func (s *MessengerCommunitiesSuite) TestSetMutePropertyOnChatsByCategory() {
// Create a community
createCommunityReq := &requests.CreateCommunity{
@ -3096,7 +3132,7 @@ func (s *MessengerCommunitiesSuite) TestCommunityBanUserRequestToJoin() {
s.advertiseCommunityTo(community, s.alice)
s.joinCommunity(community, s.alice)
response, err := s.admin.BanUserFromCommunity(
response, err := s.owner.BanUserFromCommunity(
context.Background(),
&requests.BanUserFromCommunity{
CommunityID: community.ID(),
@ -3139,7 +3175,7 @@ func (s *MessengerCommunitiesSuite) TestCommunityBanUserRequestToJoin() {
s.Require().NoError(err)
messageState := s.admin.buildMessageState()
messageState := s.owner.buildMessageState()
messageState.CurrentMessageState = &CurrentMessageState{}
messageState.CurrentMessageState.PublicKey = &s.alice.identity.PublicKey
@ -3147,7 +3183,7 @@ func (s *MessengerCommunitiesSuite) TestCommunityBanUserRequestToJoin() {
statusMessage := v1protocol.StatusMessage{
Dst: community.PublicKey(),
}
err = s.admin.HandleCommunityRequestToJoin(messageState, requestToJoinProto, &statusMessage)
err = s.owner.HandleCommunityRequestToJoin(messageState, requestToJoinProto, &statusMessage)
s.Require().ErrorContains(err, "can't request access")
}
@ -3178,12 +3214,12 @@ func (s *MessengerCommunitiesSuite) TestHandleImport() {
wrappedPayload, err := v1protocol.WrapMessageV1(
encodedPayload,
protobuf.ApplicationMetadataMessage_CHAT_MESSAGE,
s.admin.identity,
s.owner.identity,
)
s.Require().NoError(err)
message := &types.Message{}
message.Sig = crypto.FromECDSAPub(&s.admin.identity.PublicKey)
message.Sig = crypto.FromECDSAPub(&s.owner.identity.PublicKey)
message.Payload = wrappedPayload
filter := s.alice.transport.FilterByChatID(chat.ID)
@ -3217,7 +3253,7 @@ func (s *MessengerCommunitiesSuite) TestGetCommunityIdFromKey() {
func (s *MessengerCommunitiesSuite) TestStartCommunityRekeyLoop() {
// Create a new community
response, err := s.admin.CreateCommunity(
response, err := s.owner.CreateCommunity(
&requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP,
Name: "status",
@ -3231,7 +3267,7 @@ func (s *MessengerCommunitiesSuite) TestStartCommunityRekeyLoop() {
s.Require().Len(response.Communities(), 1)
// Check community is present in the DB and has default values we care about
c, err := s.admin.GetCommunityByID(response.Communities()[0].ID())
c, err := s.owner.GetCommunityByID(response.Communities()[0].ID())
s.Require().NoError(err)
s.Require().False(c.Encrypted())
// TODO some check that there are no keys for the community. Alt for s.Require().Zero(c.RekeyedAt().Unix())

View file

@ -465,7 +465,7 @@ func NewMessenger(
managerOptions = append(managerOptions, communities.WithCommunityTokensService(c.communityTokensService))
}
communitiesManager, err := communities.NewManager(identity, database, encryptionProtocol, logger, ensVerifier, c.communityTokensService, transp, transp, c.torrentConfig, managerOptions...)
communitiesManager, err := communities.NewManager(identity, installationID, database, encryptionProtocol, logger, ensVerifier, c.communityTokensService, transp, transp, c.torrentConfig, managerOptions...)
if err != nil {
return nil, err
}
@ -2963,7 +2963,12 @@ func (m *Messenger) syncCommunity(ctx context.Context, community *communities.Co
return err
}
syncMessage, err := community.ToSyncInstallationCommunityProtobuf(clock, communitySettings)
syncControlNode, err := m.communitiesManager.GetSyncControlNode(community.ID())
if err != nil {
return err
}
syncMessage, err := community.ToSyncInstallationCommunityProtobuf(clock, communitySettings, syncControlNode)
if err != nil {
return err
}

View file

@ -305,7 +305,12 @@ func (m *Messenger) backupCommunities(ctx context.Context, clock uint64) ([]*pro
return nil, err
}
syncMessage, err := c.ToSyncInstallationCommunityProtobuf(clock, settings)
syncControlNode, err := m.communitiesManager.GetSyncControlNode(c.ID())
if err != nil {
return nil, err
}
syncMessage, err := c.ToSyncInstallationCommunityProtobuf(clock, settings, syncControlNode)
if err != nil {
return nil, err
}

View file

@ -2287,7 +2287,9 @@ func (m *Messenger) ExportCommunity(id types.HexBytes) (*ecdsa.PrivateKey, error
}
func (m *Messenger) ImportCommunity(ctx context.Context, key *ecdsa.PrivateKey) (*MessengerResponse, error) {
community, err := m.communitiesManager.ImportCommunity(key)
clock, _ := m.getLastClockWithRelatedChat()
community, err := m.communitiesManager.ImportCommunity(key, clock)
if err != nil {
return nil, err
}
@ -2324,6 +2326,12 @@ func (m *Messenger) ImportCommunity(ctx context.Context, key *ecdsa.PrivateKey)
return nil, err
}
// Notify other clients we are the control node now
err = m.syncCommunity(context.Background(), community, m.dispatchMessage)
if err != nil {
return nil, err
}
if m.torrentClientReady() {
var communities []*communities.Community
communities = append(communities, community)
@ -3157,8 +3165,15 @@ func (m *Messenger) handleSyncInstallationCommunity(messageState *ReceivedMessag
}
}
id := crypto.CompressPubkey(orgPubKey)
savedCommunity, err := m.communitiesManager.GetByID(id)
if syncCommunity.ControlNode != nil {
err = m.communitiesManager.SetSyncControlNode(syncCommunity.Id, syncCommunity.ControlNode)
if err != nil {
logger.Debug("m.SetSyncControlNode", zap.Error(err))
return err
}
}
savedCommunity, err := m.communitiesManager.GetByID(syncCommunity.Id)
if err != nil {
return err
}
@ -4771,7 +4786,13 @@ func (m *Messenger) AddCommunityToken(communityID string, chainID int, address s
return err
}
_, err = m.communitiesManager.AddCommunityToken(communityToken)
clock, _ := m.getLastClockWithRelatedChat()
community, err := m.communitiesManager.AddCommunityToken(communityToken, clock)
if err != nil {
return err
}
err = m.syncCommunity(context.Background(), community, m.dispatchMessage)
if err != nil {
return err
}
@ -5105,15 +5126,22 @@ func (m *Messenger) SetCommunitySignerPubKey(ctx context.Context, communityID []
if m.communityTokensService == nil {
return "", errors.New("tokens service not initialized")
}
transactionHash, err := m.communityTokensService.SetSignerPubKey(ctx, chainID, contractAddress, txArgs, password, newSignerPubKey)
if err != nil {
return "", err
}
_, err = m.communitiesManager.PromoteSelfToControlNode(communityID)
if err != nil {
return "", err
}
return transactionHash, nil
return m.communityTokensService.SetSignerPubKey(ctx, chainID, contractAddress, txArgs, password, newSignerPubKey)
}
func (m *Messenger) PromoteSelfToControlNode(communityID types.HexBytes) (*communities.Community, error) {
clock, _ := m.getLastClockWithRelatedChat()
community, err := m.communitiesManager.PromoteSelfToControlNode(communityID, clock)
if err != nil {
return nil, err
}
err = m.syncCommunity(context.Background(), community, m.dispatchMessage)
if err != nil {
return nil, err
}
return community, nil
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,18 @@
CREATE TABLE communities_control_node (
community_id BLOB NOT NULL PRIMARY KEY ON CONFLICT REPLACE,
clock INT NOT NULL,
installation_id VARCHAR NOT NULL
);
INSERT INTO
communities_control_node (community_id, clock, installation_id)
SELECT
c.id,
1 AS clock,
s.installation_id
FROM
communities_communities AS c
JOIN shhext_config AS s
WHERE
c.private_key IS NOT NULL
AND c.private_key != '';

View file

@ -45,7 +45,7 @@ func (x SyncTrustedUser_TrustStatus) String() string {
}
func (SyncTrustedUser_TrustStatus) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{30, 0}
return fileDescriptor_d61ab7221f0b5518, []int{31, 0}
}
type SyncVerificationRequest_VerificationStatus int32
@ -79,7 +79,7 @@ func (x SyncVerificationRequest_VerificationStatus) String() string {
}
func (SyncVerificationRequest_VerificationStatus) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{31, 0}
return fileDescriptor_d61ab7221f0b5518, []int{32, 0}
}
type SyncContactRequestDecision_DecisionStatus int32
@ -104,7 +104,7 @@ func (x SyncContactRequestDecision_DecisionStatus) String() string {
}
func (SyncContactRequestDecision_DecisionStatus) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{32, 0}
return fileDescriptor_d61ab7221f0b5518, []int{33, 0}
}
// `FetchingBackedUpDataDetails` is used to describe how many messages a single backup data structure consists of
@ -1074,6 +1074,7 @@ type SyncInstallationCommunity struct {
Encrypted bool `protobuf:"varint,10,opt,name=encrypted,proto3" json:"encrypted,omitempty"`
Spectated bool `protobuf:"varint,11,opt,name=spectated,proto3" json:"spectated,omitempty"`
EncryptionKeys []byte `protobuf:"bytes,12,opt,name=encryption_keys,json=encryptionKeys,proto3" json:"encryption_keys,omitempty"`
ControlNode *SyncCommunityControlNode `protobuf:"bytes,13,opt,name=control_node,json=controlNode,proto3" json:"control_node,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -1189,6 +1190,13 @@ func (m *SyncInstallationCommunity) GetEncryptionKeys() []byte {
return nil
}
func (m *SyncInstallationCommunity) GetControlNode() *SyncCommunityControlNode {
if m != nil {
return m.ControlNode
}
return nil
}
type SyncCommunityRequestsToJoin struct {
Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
PublicKey string `protobuf:"bytes,2,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
@ -1284,6 +1292,56 @@ func (m *SyncCommunityRequestsToJoin) GetRevealedAccounts() []*RevealedAccount {
return nil
}
type SyncCommunityControlNode struct {
// Lamport timestamp of control node change
Clock uint64 `protobuf:"varint,1,opt,name=clock,proto3" json:"clock,omitempty"`
// The device id of the control node
// Empty if there is no control node
InstallationId string `protobuf:"bytes,2,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *SyncCommunityControlNode) Reset() { *m = SyncCommunityControlNode{} }
func (m *SyncCommunityControlNode) String() string { return proto.CompactTextString(m) }
func (*SyncCommunityControlNode) ProtoMessage() {}
func (*SyncCommunityControlNode) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{10}
}
func (m *SyncCommunityControlNode) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SyncCommunityControlNode.Unmarshal(m, b)
}
func (m *SyncCommunityControlNode) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_SyncCommunityControlNode.Marshal(b, m, deterministic)
}
func (m *SyncCommunityControlNode) XXX_Merge(src proto.Message) {
xxx_messageInfo_SyncCommunityControlNode.Merge(m, src)
}
func (m *SyncCommunityControlNode) XXX_Size() int {
return xxx_messageInfo_SyncCommunityControlNode.Size(m)
}
func (m *SyncCommunityControlNode) XXX_DiscardUnknown() {
xxx_messageInfo_SyncCommunityControlNode.DiscardUnknown(m)
}
var xxx_messageInfo_SyncCommunityControlNode proto.InternalMessageInfo
func (m *SyncCommunityControlNode) GetClock() uint64 {
if m != nil {
return m.Clock
}
return 0
}
func (m *SyncCommunityControlNode) GetInstallationId() string {
if m != nil {
return m.InstallationId
}
return ""
}
type SyncChat struct {
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
ChatType uint32 `protobuf:"varint,2,opt,name=chat_type,json=chatType,proto3" json:"chat_type,omitempty"`
@ -1301,7 +1359,7 @@ func (m *SyncChat) Reset() { *m = SyncChat{} }
func (m *SyncChat) String() string { return proto.CompactTextString(m) }
func (*SyncChat) ProtoMessage() {}
func (*SyncChat) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{10}
return fileDescriptor_d61ab7221f0b5518, []int{11}
}
func (m *SyncChat) XXX_Unmarshal(b []byte) error {
@ -1391,7 +1449,7 @@ func (m *MembershipUpdateEvents) Reset() { *m = MembershipUpdateEvents{}
func (m *MembershipUpdateEvents) String() string { return proto.CompactTextString(m) }
func (*MembershipUpdateEvents) ProtoMessage() {}
func (*MembershipUpdateEvents) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{11}
return fileDescriptor_d61ab7221f0b5518, []int{12}
}
func (m *MembershipUpdateEvents) XXX_Unmarshal(b []byte) error {
@ -1494,7 +1552,7 @@ func (m *SyncChatRemoved) Reset() { *m = SyncChatRemoved{} }
func (m *SyncChatRemoved) String() string { return proto.CompactTextString(m) }
func (*SyncChatRemoved) ProtoMessage() {}
func (*SyncChatRemoved) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{12}
return fileDescriptor_d61ab7221f0b5518, []int{13}
}
func (m *SyncChatRemoved) XXX_Unmarshal(b []byte) error {
@ -1541,7 +1599,7 @@ func (m *SyncChatMessagesRead) Reset() { *m = SyncChatMessagesRead{} }
func (m *SyncChatMessagesRead) String() string { return proto.CompactTextString(m) }
func (*SyncChatMessagesRead) ProtoMessage() {}
func (*SyncChatMessagesRead) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{13}
return fileDescriptor_d61ab7221f0b5518, []int{14}
}
func (m *SyncChatMessagesRead) XXX_Unmarshal(b []byte) error {
@ -1588,7 +1646,7 @@ func (m *SyncActivityCenterRead) Reset() { *m = SyncActivityCenterRead{}
func (m *SyncActivityCenterRead) String() string { return proto.CompactTextString(m) }
func (*SyncActivityCenterRead) ProtoMessage() {}
func (*SyncActivityCenterRead) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{14}
return fileDescriptor_d61ab7221f0b5518, []int{15}
}
func (m *SyncActivityCenterRead) XXX_Unmarshal(b []byte) error {
@ -1635,7 +1693,7 @@ func (m *SyncActivityCenterAccepted) Reset() { *m = SyncActivityCenterAc
func (m *SyncActivityCenterAccepted) String() string { return proto.CompactTextString(m) }
func (*SyncActivityCenterAccepted) ProtoMessage() {}
func (*SyncActivityCenterAccepted) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{15}
return fileDescriptor_d61ab7221f0b5518, []int{16}
}
func (m *SyncActivityCenterAccepted) XXX_Unmarshal(b []byte) error {
@ -1682,7 +1740,7 @@ func (m *SyncActivityCenterDismissed) Reset() { *m = SyncActivityCenterD
func (m *SyncActivityCenterDismissed) String() string { return proto.CompactTextString(m) }
func (*SyncActivityCenterDismissed) ProtoMessage() {}
func (*SyncActivityCenterDismissed) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{16}
return fileDescriptor_d61ab7221f0b5518, []int{17}
}
func (m *SyncActivityCenterDismissed) XXX_Unmarshal(b []byte) error {
@ -1729,7 +1787,7 @@ func (m *SyncActivityCenterDeleted) Reset() { *m = SyncActivityCenterDel
func (m *SyncActivityCenterDeleted) String() string { return proto.CompactTextString(m) }
func (*SyncActivityCenterDeleted) ProtoMessage() {}
func (*SyncActivityCenterDeleted) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{17}
return fileDescriptor_d61ab7221f0b5518, []int{18}
}
func (m *SyncActivityCenterDeleted) XXX_Unmarshal(b []byte) error {
@ -1776,7 +1834,7 @@ func (m *SyncActivityCenterUnread) Reset() { *m = SyncActivityCenterUnre
func (m *SyncActivityCenterUnread) String() string { return proto.CompactTextString(m) }
func (*SyncActivityCenterUnread) ProtoMessage() {}
func (*SyncActivityCenterUnread) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{18}
return fileDescriptor_d61ab7221f0b5518, []int{19}
}
func (m *SyncActivityCenterUnread) XXX_Unmarshal(b []byte) error {
@ -1823,7 +1881,7 @@ func (m *SyncActivityCenterNotificationState) Reset() { *m = SyncActivit
func (m *SyncActivityCenterNotificationState) String() string { return proto.CompactTextString(m) }
func (*SyncActivityCenterNotificationState) ProtoMessage() {}
func (*SyncActivityCenterNotificationState) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{19}
return fileDescriptor_d61ab7221f0b5518, []int{20}
}
func (m *SyncActivityCenterNotificationState) XXX_Unmarshal(b []byte) error {
@ -1874,7 +1932,7 @@ func (m *SyncBookmark) Reset() { *m = SyncBookmark{} }
func (m *SyncBookmark) String() string { return proto.CompactTextString(m) }
func (*SyncBookmark) ProtoMessage() {}
func (*SyncBookmark) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{20}
return fileDescriptor_d61ab7221f0b5518, []int{21}
}
func (m *SyncBookmark) XXX_Unmarshal(b []byte) error {
@ -1951,7 +2009,7 @@ func (m *SyncEnsUsernameDetail) Reset() { *m = SyncEnsUsernameDetail{} }
func (m *SyncEnsUsernameDetail) String() string { return proto.CompactTextString(m) }
func (*SyncEnsUsernameDetail) ProtoMessage() {}
func (*SyncEnsUsernameDetail) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{21}
return fileDescriptor_d61ab7221f0b5518, []int{22}
}
func (m *SyncEnsUsernameDetail) XXX_Unmarshal(b []byte) error {
@ -2012,7 +2070,7 @@ func (m *SyncClearHistory) Reset() { *m = SyncClearHistory{} }
func (m *SyncClearHistory) String() string { return proto.CompactTextString(m) }
func (*SyncClearHistory) ProtoMessage() {}
func (*SyncClearHistory) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{22}
return fileDescriptor_d61ab7221f0b5518, []int{23}
}
func (m *SyncClearHistory) XXX_Unmarshal(b []byte) error {
@ -2064,7 +2122,7 @@ func (m *SyncProfilePicture) Reset() { *m = SyncProfilePicture{} }
func (m *SyncProfilePicture) String() string { return proto.CompactTextString(m) }
func (*SyncProfilePicture) ProtoMessage() {}
func (*SyncProfilePicture) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{23}
return fileDescriptor_d61ab7221f0b5518, []int{24}
}
func (m *SyncProfilePicture) XXX_Unmarshal(b []byte) error {
@ -2146,7 +2204,7 @@ func (m *SyncProfilePictures) Reset() { *m = SyncProfilePictures{} }
func (m *SyncProfilePictures) String() string { return proto.CompactTextString(m) }
func (*SyncProfilePictures) ProtoMessage() {}
func (*SyncProfilePictures) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{24}
return fileDescriptor_d61ab7221f0b5518, []int{25}
}
func (m *SyncProfilePictures) XXX_Unmarshal(b []byte) error {
@ -2207,7 +2265,7 @@ func (m *SyncAccount) Reset() { *m = SyncAccount{} }
func (m *SyncAccount) String() string { return proto.CompactTextString(m) }
func (*SyncAccount) ProtoMessage() {}
func (*SyncAccount) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{25}
return fileDescriptor_d61ab7221f0b5518, []int{26}
}
func (m *SyncAccount) XXX_Unmarshal(b []byte) error {
@ -2361,7 +2419,7 @@ func (m *SyncKeypair) Reset() { *m = SyncKeypair{} }
func (m *SyncKeypair) String() string { return proto.CompactTextString(m) }
func (*SyncKeypair) ProtoMessage() {}
func (*SyncKeypair) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{26}
return fileDescriptor_d61ab7221f0b5518, []int{27}
}
func (m *SyncKeypair) XXX_Unmarshal(b []byte) error {
@ -2473,7 +2531,7 @@ func (m *SyncAccountsPositions) Reset() { *m = SyncAccountsPositions{} }
func (m *SyncAccountsPositions) String() string { return proto.CompactTextString(m) }
func (*SyncAccountsPositions) ProtoMessage() {}
func (*SyncAccountsPositions) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{27}
return fileDescriptor_d61ab7221f0b5518, []int{28}
}
func (m *SyncAccountsPositions) XXX_Unmarshal(b []byte) error {
@ -2526,7 +2584,7 @@ func (m *SyncSavedAddress) Reset() { *m = SyncSavedAddress{} }
func (m *SyncSavedAddress) String() string { return proto.CompactTextString(m) }
func (*SyncSavedAddress) ProtoMessage() {}
func (*SyncSavedAddress) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{28}
return fileDescriptor_d61ab7221f0b5518, []int{29}
}
func (m *SyncSavedAddress) XXX_Unmarshal(b []byte) error {
@ -2616,7 +2674,7 @@ func (m *SyncCommunitySettings) Reset() { *m = SyncCommunitySettings{} }
func (m *SyncCommunitySettings) String() string { return proto.CompactTextString(m) }
func (*SyncCommunitySettings) ProtoMessage() {}
func (*SyncCommunitySettings) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{29}
return fileDescriptor_d61ab7221f0b5518, []int{30}
}
func (m *SyncCommunitySettings) XXX_Unmarshal(b []byte) error {
@ -2671,7 +2729,7 @@ func (m *SyncTrustedUser) Reset() { *m = SyncTrustedUser{} }
func (m *SyncTrustedUser) String() string { return proto.CompactTextString(m) }
func (*SyncTrustedUser) ProtoMessage() {}
func (*SyncTrustedUser) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{30}
return fileDescriptor_d61ab7221f0b5518, []int{31}
}
func (m *SyncTrustedUser) XXX_Unmarshal(b []byte) error {
@ -2732,7 +2790,7 @@ func (m *SyncVerificationRequest) Reset() { *m = SyncVerificationRequest
func (m *SyncVerificationRequest) String() string { return proto.CompactTextString(m) }
func (*SyncVerificationRequest) ProtoMessage() {}
func (*SyncVerificationRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{31}
return fileDescriptor_d61ab7221f0b5518, []int{32}
}
func (m *SyncVerificationRequest) XXX_Unmarshal(b []byte) error {
@ -2829,7 +2887,7 @@ func (m *SyncContactRequestDecision) Reset() { *m = SyncContactRequestDe
func (m *SyncContactRequestDecision) String() string { return proto.CompactTextString(m) }
func (*SyncContactRequestDecision) ProtoMessage() {}
func (*SyncContactRequestDecision) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{32}
return fileDescriptor_d61ab7221f0b5518, []int{33}
}
func (m *SyncContactRequestDecision) XXX_Unmarshal(b []byte) error {
@ -2888,7 +2946,7 @@ func (m *BackedUpProfile) Reset() { *m = BackedUpProfile{} }
func (m *BackedUpProfile) String() string { return proto.CompactTextString(m) }
func (*BackedUpProfile) ProtoMessage() {}
func (*BackedUpProfile) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{33}
return fileDescriptor_d61ab7221f0b5518, []int{34}
}
func (m *BackedUpProfile) XXX_Unmarshal(b []byte) error {
@ -2963,7 +3021,7 @@ func (m *RawMessage) Reset() { *m = RawMessage{} }
func (m *RawMessage) String() string { return proto.CompactTextString(m) }
func (*RawMessage) ProtoMessage() {}
func (*RawMessage) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{34}
return fileDescriptor_d61ab7221f0b5518, []int{35}
}
func (m *RawMessage) XXX_Unmarshal(b []byte) error {
@ -3012,7 +3070,7 @@ func (m *SyncRawMessage) Reset() { *m = SyncRawMessage{} }
func (m *SyncRawMessage) String() string { return proto.CompactTextString(m) }
func (*SyncRawMessage) ProtoMessage() {}
func (*SyncRawMessage) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{35}
return fileDescriptor_d61ab7221f0b5518, []int{36}
}
func (m *SyncRawMessage) XXX_Unmarshal(b []byte) error {
@ -3070,7 +3128,7 @@ func (m *SyncKeycard) Reset() { *m = SyncKeycard{} }
func (m *SyncKeycard) String() string { return proto.CompactTextString(m) }
func (*SyncKeycard) ProtoMessage() {}
func (*SyncKeycard) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{36}
return fileDescriptor_d61ab7221f0b5518, []int{37}
}
func (m *SyncKeycard) XXX_Unmarshal(b []byte) error {
@ -3145,7 +3203,7 @@ func (m *SyncSocialLinks) Reset() { *m = SyncSocialLinks{} }
func (m *SyncSocialLinks) String() string { return proto.CompactTextString(m) }
func (*SyncSocialLinks) ProtoMessage() {}
func (*SyncSocialLinks) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{37}
return fileDescriptor_d61ab7221f0b5518, []int{38}
}
func (m *SyncSocialLinks) XXX_Unmarshal(b []byte) error {
@ -3193,7 +3251,7 @@ func (m *SyncAccountCustomizationColor) Reset() { *m = SyncAccountCustom
func (m *SyncAccountCustomizationColor) String() string { return proto.CompactTextString(m) }
func (*SyncAccountCustomizationColor) ProtoMessage() {}
func (*SyncAccountCustomizationColor) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{38}
return fileDescriptor_d61ab7221f0b5518, []int{39}
}
func (m *SyncAccountCustomizationColor) XXX_Unmarshal(b []byte) error {
@ -3252,6 +3310,7 @@ func init() {
proto.RegisterType((*SyncInstallationAccount)(nil), "protobuf.SyncInstallationAccount")
proto.RegisterType((*SyncInstallationCommunity)(nil), "protobuf.SyncInstallationCommunity")
proto.RegisterType((*SyncCommunityRequestsToJoin)(nil), "protobuf.SyncCommunityRequestsToJoin")
proto.RegisterType((*SyncCommunityControlNode)(nil), "protobuf.SyncCommunityControlNode")
proto.RegisterType((*SyncChat)(nil), "protobuf.SyncChat")
proto.RegisterType((*MembershipUpdateEvents)(nil), "protobuf.MembershipUpdateEvents")
proto.RegisterType((*SyncChatRemoved)(nil), "protobuf.SyncChatRemoved")
@ -3288,203 +3347,206 @@ func init() {
}
var fileDescriptor_d61ab7221f0b5518 = []byte{
// 3168 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x3a, 0x4b, 0x6f, 0x1c, 0xc7,
0xd1, 0xde, 0x07, 0x97, 0xbb, 0xb5, 0xcb, 0x87, 0x5a, 0x94, 0xb4, 0xa2, 0x24, 0x98, 0x1a, 0x7d,
0x86, 0xe5, 0x0f, 0x0e, 0x9d, 0xc8, 0x4e, 0x1c, 0xf8, 0x01, 0x87, 0x22, 0xe9, 0x88, 0x7a, 0x50,
0x4c, 0x93, 0x94, 0x9d, 0x20, 0xc0, 0xb8, 0x35, 0xd3, 0xe2, 0xb6, 0xb9, 0x3b, 0x33, 0x99, 0xee,
0x25, 0xb3, 0x3e, 0x04, 0xc8, 0xcd, 0xc7, 0xdc, 0x7c, 0x0b, 0x7c, 0xc8, 0x21, 0xc8, 0x31, 0xb7,
0xfc, 0x80, 0x04, 0x39, 0xe6, 0x9e, 0xfc, 0x81, 0xfc, 0x83, 0x5c, 0x02, 0x04, 0x55, 0xdd, 0x33,
0x3b, 0xb3, 0x0f, 0x59, 0x3c, 0x71, 0xaa, 0xba, 0xba, 0xba, 0xba, 0xeb, 0x5d, 0x4b, 0x58, 0x4a,
0x84, 0x4a, 0x55, 0x74, 0xb2, 0x99, 0xa4, 0xb1, 0x89, 0x59, 0x93, 0xfe, 0x3c, 0x1f, 0xbe, 0x58,
0xbf, 0x1c, 0xf4, 0x84, 0xf1, 0x55, 0x28, 0x23, 0xa3, 0xcc, 0xc8, 0x2e, 0xaf, 0x5f, 0xd6, 0xa3,
0x28, 0xf0, 0xb5, 0x34, 0x46, 0x45, 0x27, 0xda, 0x21, 0x3d, 0x91, 0x24, 0x7d, 0x15, 0x08, 0xa3,
0xe2, 0xc8, 0x1f, 0x48, 0x23, 0x42, 0x61, 0x84, 0x3f, 0x90, 0x5a, 0x8b, 0x13, 0xe9, 0x68, 0x2e,
0x05, 0xf1, 0x60, 0x30, 0x8c, 0x94, 0x51, 0xd2, 0x6d, 0xf3, 0x04, 0xdc, 0xf8, 0x54, 0x9a, 0xa0,
0xa7, 0xa2, 0x93, 0xfb, 0x22, 0x38, 0x95, 0xe1, 0x71, 0xb2, 0x23, 0x8c, 0xd8, 0x91, 0x46, 0xa8,
0xbe, 0x66, 0xaf, 0x43, 0x9b, 0xf8, 0x44, 0xc3, 0xc1, 0x73, 0x99, 0x76, 0x2b, 0x1b, 0x95, 0xbb,
0x4b, 0x1c, 0x10, 0xb5, 0x4f, 0x18, 0x76, 0x1b, 0x3a, 0x26, 0x36, 0xa2, 0x9f, 0x51, 0x54, 0x89,
0xa2, 0x4d, 0x38, 0x4b, 0xe2, 0xfd, 0x77, 0x11, 0x1a, 0xc8, 0x7b, 0x98, 0xb0, 0x35, 0x58, 0x08,
0xfa, 0x71, 0x70, 0x4a, 0x8c, 0xea, 0xdc, 0x02, 0x6c, 0x19, 0xaa, 0x2a, 0xa4, 0x9d, 0x2d, 0x5e,
0x55, 0x21, 0xfb, 0x04, 0x9a, 0x41, 0x1c, 0x19, 0x11, 0x18, 0xdd, 0xad, 0x6d, 0xd4, 0xee, 0xb6,
0xef, 0xdd, 0xd9, 0xcc, 0x5e, 0x64, 0xf3, 0x70, 0x14, 0x05, 0x7b, 0x91, 0x36, 0xa2, 0xdf, 0xa7,
0xbb, 0x6e, 0x5b, 0xca, 0x67, 0xf7, 0x78, 0xbe, 0x89, 0xed, 0x42, 0xbb, 0x70, 0xd3, 0x6e, 0xfd,
0xbb, 0x79, 0x58, 0xe2, 0x11, 0x2f, 0xee, 0x63, 0x4f, 0x61, 0x25, 0x63, 0xe9, 0xde, 0xa3, 0xbb,
0xb0, 0x51, 0xb9, 0xdb, 0xbe, 0xf7, 0xc6, 0x98, 0xd5, 0x4b, 0x1e, 0x8f, 0x4f, 0xee, 0x66, 0xc7,
0xc0, 0x0a, 0xfc, 0x33, 0x9e, 0x8d, 0x8b, 0xf0, 0x9c, 0xc1, 0x80, 0xbd, 0x0b, 0x8b, 0x49, 0x1a,
0xbf, 0x50, 0x7d, 0xd9, 0x5d, 0x24, 0x5e, 0xd7, 0xc7, 0xbc, 0x32, 0x1e, 0x07, 0x96, 0x80, 0x67,
0x94, 0xec, 0x09, 0x2c, 0xbb, 0xcf, 0x4c, 0x8e, 0xe6, 0x45, 0xe4, 0x98, 0xd8, 0xcc, 0xde, 0x81,
0x45, 0x67, 0x90, 0xdd, 0x16, 0xf1, 0xb9, 0x52, 0x7e, 0xee, 0x43, 0xbb, 0xc8, 0x33, 0x2a, 0x7c,
0xdc, 0xcc, 0x82, 0x33, 0x01, 0xe0, 0x42, 0x8f, 0x3b, 0xb1, 0x1b, 0x25, 0x38, 0x95, 0x23, 0x74,
0xa4, 0x6e, 0x7b, 0x96, 0x04, 0x8f, 0xec, 0x22, 0xcf, 0xa8, 0xf0, 0x05, 0xdc, 0x67, 0x26, 0x40,
0xe7, 0x42, 0x2f, 0x50, 0xde, 0xcc, 0xb6, 0x60, 0xf5, 0x5c, 0x98, 0xa0, 0xf7, 0x34, 0xea, 0x8f,
0xb6, 0x82, 0x20, 0x1e, 0x46, 0xa6, 0xbb, 0x34, 0x4b, 0x10, 0xb7, 0xc8, 0xa7, 0xc8, 0x99, 0x0f,
0xd7, 0x26, 0x71, 0x99, 0x68, 0xcb, 0x17, 0x11, 0x6d, 0x1e, 0x17, 0x76, 0x17, 0x16, 0x30, 0xa0,
0xe8, 0xee, 0x0a, 0xb9, 0x04, 0x2b, 0x0b, 0xb6, 0xdd, 0x13, 0x86, 0x5b, 0x02, 0xb6, 0x07, 0x1d,
0xfa, 0xc8, 0xce, 0x5f, 0xbd, 0xc8, 0xf9, 0xa5, 0xad, 0xde, 0x1f, 0x17, 0xa0, 0xf3, 0x64, 0xd8,
0x37, 0x2a, 0xbb, 0x26, 0x83, 0x7a, 0x24, 0x06, 0x92, 0x82, 0x40, 0x8b, 0xd3, 0x37, 0xbb, 0x09,
0x2d, 0xa3, 0x06, 0x52, 0x1b, 0x31, 0x48, 0x28, 0x14, 0xd4, 0xf8, 0x18, 0x81, 0xab, 0x36, 0x06,
0x06, 0x71, 0xd4, 0xad, 0xd1, 0xb6, 0x31, 0x82, 0x7d, 0x02, 0x10, 0xc4, 0xfd, 0x38, 0xf5, 0x7b,
0x42, 0xf7, 0x9c, 0xb7, 0x6f, 0x8c, 0x25, 0x2d, 0x9e, 0xbd, 0xb9, 0x8d, 0x84, 0x0f, 0x84, 0xee,
0xf1, 0x56, 0x90, 0x7d, 0xb2, 0xeb, 0x18, 0x70, 0x90, 0x81, 0x0a, 0xc9, 0xc3, 0x6b, 0x7c, 0x91,
0xe0, 0xbd, 0x90, 0xbd, 0x09, 0x2b, 0xa7, 0x72, 0x14, 0x88, 0x34, 0xf4, 0x5d, 0x8c, 0x26, 0x7f,
0x6d, 0x91, 0xfa, 0x11, 0x7d, 0x60, 0xb1, 0xec, 0x1a, 0x99, 0x9f, 0x3f, 0x54, 0x21, 0x39, 0x61,
0x8b, 0x37, 0x4e, 0xe5, 0xe8, 0x58, 0x85, 0xec, 0x23, 0x68, 0xa8, 0x81, 0x38, 0x91, 0xe8, 0x60,
0x28, 0xd9, 0xff, 0xcd, 0x91, 0x6c, 0xcf, 0x05, 0xf9, 0x3d, 0x24, 0xe6, 0x6e, 0x0f, 0x7b, 0x07,
0x2e, 0x07, 0x43, 0x6d, 0xe2, 0x81, 0xfa, 0xca, 0x86, 0x76, 0x12, 0x8c, 0x7c, 0xac, 0xc5, 0x59,
0x69, 0x89, 0xae, 0xc6, 0x3e, 0x80, 0xeb, 0x33, 0x36, 0xf8, 0x36, 0xec, 0x02, 0x85, 0xdd, 0x6b,
0xd3, 0xdb, 0xb6, 0x71, 0x79, 0xfd, 0x36, 0xb4, 0xf2, 0xf7, 0xc1, 0x58, 0xad, 0xa2, 0x50, 0xfe,
0xba, 0x5b, 0xd9, 0xa8, 0xdd, 0xad, 0x71, 0x0b, 0xac, 0xff, 0xb3, 0x02, 0x4b, 0x25, 0x49, 0x8b,
0x17, 0xaf, 0x94, 0x2e, 0x9e, 0xa9, 0xb9, 0x5a, 0x50, 0x73, 0x17, 0x16, 0x13, 0x31, 0xea, 0xc7,
0x22, 0x24, 0x35, 0x76, 0x78, 0x06, 0xe2, 0x71, 0xe7, 0x2a, 0x34, 0xa8, 0x3f, 0x54, 0x80, 0x05,
0xd8, 0x55, 0x68, 0xf4, 0xa4, 0x3a, 0xe9, 0x19, 0xa7, 0x17, 0x07, 0xb1, 0x75, 0x68, 0x62, 0xf4,
0xd1, 0xea, 0x2b, 0x49, 0xfa, 0xa8, 0xf1, 0x1c, 0x66, 0x77, 0x60, 0x29, 0xa5, 0x2f, 0xdf, 0x88,
0xf4, 0x44, 0x1a, 0xd2, 0x47, 0x8d, 0x77, 0x2c, 0xf2, 0x88, 0x70, 0xe3, 0x4c, 0xd4, 0x2c, 0x64,
0x22, 0xef, 0x9b, 0x2a, 0x5c, 0x7e, 0x1c, 0x07, 0xa2, 0xef, 0xb4, 0x7a, 0xe0, 0x84, 0xfb, 0x21,
0xd4, 0x4f, 0xe5, 0x48, 0xd3, 0x53, 0xb4, 0xef, 0xdd, 0x1e, 0x6b, 0x70, 0x06, 0xf1, 0xe6, 0x23,
0x39, 0xe2, 0x44, 0xce, 0x3e, 0x80, 0xce, 0x00, 0x55, 0x2c, 0x5c, 0x38, 0xa8, 0x92, 0x13, 0x5d,
0x9d, 0x6d, 0x00, 0xbc, 0x44, 0x8b, 0x37, 0x4c, 0x84, 0xd6, 0xe7, 0x71, 0x1a, 0x3a, 0x8b, 0xcf,
0x61, 0x7c, 0x45, 0xf4, 0xb0, 0x47, 0x72, 0x44, 0xaf, 0xd5, 0xe2, 0x19, 0xc8, 0xee, 0xe6, 0xe6,
0xea, 0x84, 0xb2, 0x29, 0xab, 0xc5, 0x27, 0xd1, 0xeb, 0xdf, 0x83, 0x1a, 0x6e, 0x98, 0xe5, 0x8b,
0x0c, 0xea, 0x98, 0xe1, 0x49, 0xdc, 0x0e, 0xa7, 0x6f, 0xef, 0x2f, 0x15, 0xb8, 0x52, 0xba, 0xac,
0x94, 0xe9, 0x03, 0xd9, 0xef, 0xc7, 0xe8, 0x21, 0xce, 0x33, 0xfc, 0x33, 0x99, 0x6a, 0x15, 0x47,
0xc4, 0x6c, 0x81, 0x2f, 0x3b, 0xf4, 0x33, 0x8b, 0x45, 0x43, 0x49, 0xa4, 0x24, 0x27, 0xb3, 0x9c,
0x1b, 0x08, 0xee, 0x85, 0x54, 0x64, 0xc8, 0x33, 0x15, 0x48, 0x9f, 0x44, 0xb1, 0xb7, 0x05, 0x8b,
0xda, 0x47, 0x81, 0xc6, 0x04, 0x66, 0x94, 0x48, 0x77, 0x67, 0x47, 0x70, 0x34, 0x4a, 0x28, 0x7a,
0x68, 0x75, 0x12, 0x09, 0x33, 0x4c, 0x25, 0x5d, 0xb8, 0xc3, 0xc7, 0x08, 0xef, 0x0f, 0x15, 0x58,
0xc3, 0xf8, 0x86, 0xa2, 0x17, 0xd3, 0xfe, 0x9c, 0x72, 0xe4, 0x4d, 0x58, 0x51, 0x05, 0x2a, 0x3f,
0xaf, 0x4d, 0x96, 0x8b, 0xe8, 0x92, 0xdc, 0x24, 0x56, 0x6d, 0x4a, 0xac, 0xec, 0x71, 0xeb, 0x65,
0x0f, 0xc8, 0x9e, 0x69, 0x81, 0x6a, 0xa5, 0x0c, 0xf4, 0x7e, 0xdb, 0x80, 0xeb, 0x73, 0xab, 0x1b,
0xf6, 0x7d, 0x58, 0xeb, 0x0b, 0x6d, 0xfc, 0x61, 0x12, 0x0a, 0x23, 0x43, 0xbf, 0x8f, 0xca, 0xe8,
0x8f, 0x9c, 0xe8, 0x0c, 0xd7, 0x8e, 0xed, 0xd2, 0x63, 0xbb, 0x32, 0x55, 0x56, 0xdd, 0x81, 0x25,
0x97, 0xb4, 0x7d, 0x0a, 0x2e, 0x4e, 0xe0, 0x8e, 0x43, 0x5a, 0x6f, 0xbe, 0x0e, 0x4d, 0x19, 0x69,
0xbf, 0x20, 0xf6, 0xa2, 0x8c, 0x34, 0x69, 0xe1, 0x36, 0x74, 0x8a, 0x12, 0x90, 0xf8, 0x75, 0xde,
0x2e, 0x9c, 0x8c, 0x2f, 0xa2, 0x47, 0xda, 0xc8, 0x81, 0x6f, 0xc4, 0x09, 0x56, 0x36, 0x35, 0x7c,
0x11, 0x8b, 0x3a, 0x12, 0x27, 0x9a, 0xbd, 0x01, 0xcb, 0x24, 0xb8, 0x1f, 0xa9, 0xe0, 0x94, 0x0e,
0xb1, 0xc1, 0x72, 0x89, 0xb0, 0xfb, 0x0e, 0x89, 0x8a, 0x11, 0x61, 0x28, 0x43, 0x8a, 0x73, 0x4d,
0x6e, 0x01, 0x7c, 0xba, 0xe7, 0xa8, 0x21, 0x19, 0x52, 0x20, 0x6b, 0xf2, 0x0c, 0x44, 0xfa, 0xc1,
0x10, 0x65, 0x6a, 0x5b, 0x7a, 0x02, 0x90, 0x3e, 0x95, 0x83, 0xf8, 0x4c, 0x86, 0x94, 0xd9, 0x9b,
0x3c, 0x03, 0xd9, 0x06, 0x74, 0x7a, 0x42, 0xfb, 0xc4, 0xd6, 0x1f, 0x6a, 0xca, 0xd3, 0x4d, 0x0e,
0x3d, 0xa1, 0xb7, 0x10, 0x75, 0x4c, 0x71, 0xf7, 0x4c, 0xa6, 0xea, 0x45, 0x56, 0x51, 0x6b, 0x23,
0xcc, 0xd0, 0xa6, 0xe1, 0x1a, 0x67, 0xc5, 0xa5, 0x43, 0x5a, 0xa1, 0x42, 0x38, 0x1d, 0x6a, 0x93,
0x51, 0xae, 0x10, 0x65, 0x9b, 0x70, 0x8e, 0xe4, 0x63, 0xb8, 0xe1, 0x2a, 0x42, 0x3f, 0x95, 0xbf,
0x1a, 0x4a, 0x6d, 0xac, 0x16, 0x69, 0x8b, 0xa4, 0x14, 0x5b, 0xe3, 0x5d, 0x47, 0xc2, 0x2d, 0x05,
0x29, 0x13, 0xf7, 0xcb, 0xf9, 0xdb, 0xad, 0x0d, 0x5f, 0x9a, 0xbb, 0x9d, 0x82, 0x3b, 0xfb, 0x04,
0x6e, 0x4e, 0x6e, 0xc7, 0xe7, 0x30, 0xd2, 0x1d, 0xcf, 0x68, 0xff, 0xf5, 0xf2, 0x7e, 0x4e, 0x14,
0xf6, 0xfc, 0xf9, 0x0c, 0xac, 0x00, 0x97, 0xe7, 0x33, 0xb0, 0x12, 0xdc, 0x86, 0x4e, 0xa8, 0x74,
0xd2, 0x17, 0x23, 0x6b, 0x5f, 0x6b, 0xa4, 0xfa, 0xb6, 0xc3, 0xa1, 0x8d, 0x79, 0xe7, 0x70, 0x6d,
0xd2, 0x05, 0xb2, 0xaa, 0x61, 0xb6, 0xb3, 0x4e, 0x19, 0x75, 0x75, 0x86, 0x51, 0x4f, 0x5a, 0x6e,
0x6d, 0xca, 0x72, 0xbd, 0xbf, 0xd6, 0x66, 0x39, 0x9f, 0x6b, 0x0b, 0xbe, 0xb3, 0x6f, 0xe9, 0x38,
0x07, 0x6b, 0x27, 0xa9, 0x3a, 0x13, 0x46, 0xfa, 0xa7, 0x72, 0x64, 0x13, 0xdc, 0xfd, 0x6a, 0xb7,
0xc2, 0xc1, 0xa1, 0x31, 0xe0, 0x6e, 0x60, 0xd0, 0xd0, 0x41, 0xaa, 0x12, 0x3c, 0x82, 0x7c, 0xac,
0xc3, 0x8b, 0x28, 0xcc, 0x79, 0x5f, 0xc6, 0x2a, 0x72, 0x1e, 0xd6, 0xe4, 0x0e, 0xc2, 0x8c, 0x60,
0xed, 0x4e, 0x86, 0x94, 0xf3, 0x9a, 0x3c, 0x87, 0xc7, 0x0e, 0xb0, 0x58, 0x74, 0x80, 0xa7, 0xb0,
0xea, 0x34, 0xa5, 0x7d, 0x13, 0xfb, 0xc8, 0xc7, 0x15, 0x21, 0x6f, 0x4c, 0x54, 0x7e, 0x79, 0x03,
0xe4, 0xc8, 0x8f, 0xe2, 0x87, 0xb1, 0x8a, 0xf8, 0x72, 0x5a, 0x82, 0xd9, 0x87, 0xd0, 0xcc, 0xca,
0x6e, 0x57, 0xe6, 0xbf, 0x3e, 0x87, 0x91, 0xab, 0xf7, 0x35, 0xcf, 0x37, 0x60, 0x90, 0x96, 0x51,
0x90, 0x8e, 0x12, 0x93, 0x3b, 0xf0, 0x18, 0x41, 0x21, 0x3c, 0x91, 0x81, 0x11, 0x63, 0x37, 0x1e,
0x23, 0x30, 0x26, 0x3b, 0x52, 0x74, 0x46, 0xca, 0xc5, 0x1d, 0x7a, 0xb9, 0xe5, 0x31, 0xfa, 0x91,
0x1c, 0x69, 0xcc, 0xe0, 0x37, 0x5e, 0x72, 0x23, 0xa7, 0xb3, 0x4a, 0xae, 0xb3, 0x5b, 0x00, 0xc9,
0xf0, 0x79, 0x5f, 0x05, 0xa4, 0x32, 0x6b, 0x3c, 0x2d, 0x8b, 0x41, 0x6d, 0xe5, 0x8a, 0xaf, 0x15,
0x15, 0xff, 0x92, 0x20, 0x79, 0xcd, 0xa6, 0xe6, 0xac, 0x92, 0x6c, 0xf1, 0x06, 0x82, 0x7b, 0x21,
0xda, 0x60, 0xd6, 0xba, 0x8d, 0x70, 0xb5, 0x61, 0x15, 0x9f, 0xe3, 0xf6, 0x48, 0x89, 0xd6, 0x15,
0x17, 0xed, 0x61, 0x04, 0xb0, 0x4f, 0xe1, 0x52, 0x2a, 0xcf, 0xa4, 0xe8, 0xcb, 0xd0, 0x77, 0xc5,
0x41, 0x56, 0x4a, 0x16, 0xfa, 0x3c, 0xee, 0x48, 0xf2, 0xe6, 0x22, 0x2d, 0x23, 0xb4, 0xf7, 0xef,
0x0a, 0x34, 0xb3, 0x2a, 0xbf, 0xf0, 0x0c, 0x36, 0x37, 0xdc, 0x80, 0x16, 0x89, 0x4d, 0x89, 0xcc,
0xf6, 0xf0, 0x4d, 0x44, 0x94, 0xd2, 0x58, 0xad, 0x90, 0xc6, 0x3e, 0x87, 0xab, 0x03, 0x89, 0xed,
0xbd, 0xee, 0xa9, 0xc4, 0x3a, 0xd1, 0xee, 0x99, 0x44, 0xd1, 0xa6, 0xeb, 0xef, 0x99, 0x74, 0x7c,
0xce, 0x7e, 0x34, 0x7f, 0x11, 0x18, 0x75, 0x26, 0x33, 0xf3, 0xb7, 0xd0, 0x58, 0x15, 0x8d, 0xa2,
0x2a, 0x66, 0x1a, 0xbe, 0xf7, 0x75, 0x15, 0xae, 0xce, 0x3e, 0x76, 0x8e, 0x2b, 0x33, 0xa8, 0x17,
0xae, 0x4e, 0xdf, 0x98, 0x3e, 0x9c, 0x88, 0x34, 0x85, 0x68, 0xf1, 0x0c, 0x9c, 0x99, 0xd7, 0x5f,
0x5a, 0x82, 0x14, 0xcd, 0xa2, 0x51, 0x32, 0x0b, 0x06, 0xf5, 0x17, 0x69, 0x3c, 0x70, 0x69, 0x90,
0xbe, 0x31, 0x8b, 0xa6, 0xe2, 0xdc, 0xcf, 0x0a, 0xe5, 0x26, 0x31, 0x83, 0x54, 0x9c, 0x1f, 0x8c,
0x6b, 0xe5, 0x62, 0x1b, 0x60, 0x01, 0x2a, 0xd8, 0x29, 0x04, 0x02, 0x6d, 0xb0, 0x80, 0xf7, 0x3e,
0xac, 0xe4, 0xbd, 0x9d, 0xcb, 0x7e, 0xaf, 0x34, 0x85, 0xf1, 0x3e, 0xb2, 0x45, 0x13, 0x6e, 0x7c,
0x62, 0xa7, 0x48, 0x9a, 0x4b, 0xf1, 0xaa, 0xbb, 0x7f, 0x02, 0x57, 0x6d, 0xaf, 0x6b, 0xd4, 0x99,
0x32, 0xa3, 0x6d, 0x19, 0x19, 0x99, 0xbe, 0x64, 0xff, 0x2a, 0xd4, 0x54, 0xa8, 0xbb, 0xd5, 0x8d,
0xda, 0xdd, 0x0e, 0xc7, 0x4f, 0x6f, 0x07, 0xd6, 0xa7, 0x39, 0x6c, 0x05, 0x81, 0xa4, 0x70, 0xf1,
0xaa, 0x5c, 0x76, 0x6d, 0x38, 0x28, 0x73, 0xd9, 0x51, 0x7a, 0xa0, 0xb4, 0xbe, 0x00, 0x9b, 0x6d,
0x9b, 0x1d, 0x26, 0xd8, 0xc8, 0xbe, 0xbc, 0x88, 0x2c, 0xf7, 0xa1, 0x3b, 0xcd, 0xe4, 0x38, 0x4a,
0x2f, 0xf2, 0x2a, 0x3e, 0xdc, 0x99, 0xe6, 0xb1, 0x1f, 0x9b, 0x52, 0x39, 0x22, 0x31, 0xac, 0x65,
0x85, 0xa2, 0x30, 0x8e, 0x67, 0xcb, 0x61, 0xb6, 0x0c, 0x06, 0x30, 0xac, 0x7f, 0xb4, 0x94, 0x11,
0xe9, 0xac, 0xc9, 0x17, 0x7b, 0x42, 0x1f, 0x4a, 0x19, 0x79, 0xdf, 0x56, 0xa0, 0x83, 0x27, 0xdc,
0x8f, 0xe3, 0xd3, 0x81, 0x48, 0x4f, 0xe7, 0x4b, 0x36, 0x4c, 0xfb, 0x4e, 0xe1, 0xf8, 0x39, 0x33,
0x4a, 0xdc, 0x80, 0x16, 0x59, 0xa1, 0x8f, 0xb4, 0xd6, 0x5b, 0x9a, 0x84, 0x38, 0x4e, 0xfb, 0xc5,
0xf2, 0x6c, 0xa1, 0x5c, 0x9e, 0xdd, 0x02, 0x08, 0xed, 0xdb, 0xa2, 0xf4, 0xd6, 0xdf, 0x5b, 0x0e,
0xb3, 0x65, 0xbc, 0xdf, 0xc0, 0x15, 0x94, 0x70, 0x37, 0xd2, 0xc7, 0x5a, 0xa6, 0x78, 0x90, 0x1d,
0x35, 0xcc, 0x11, 0x75, 0x1d, 0x9a, 0x43, 0x47, 0xe7, 0xe4, 0xcd, 0x61, 0xea, 0xfc, 0x7b, 0x42,
0x51, 0x91, 0x6f, 0x43, 0xfc, 0x22, 0xc1, 0x7b, 0xa5, 0xea, 0xb1, 0x5e, 0x12, 0xcf, 0x7b, 0x08,
0xab, 0xe4, 0x19, 0x7d, 0x29, 0xd2, 0x07, 0x4a, 0x9b, 0x38, 0x1d, 0x15, 0x1d, 0xbc, 0x52, 0x72,
0xf0, 0x5b, 0x00, 0x01, 0x12, 0xda, 0xbb, 0x54, 0xed, 0x5d, 0x1c, 0x66, 0xcb, 0x78, 0x7f, 0xaf,
0x00, 0xa3, 0xde, 0xc4, 0xd6, 0x2b, 0x07, 0x2a, 0xa0, 0x78, 0x31, 0xab, 0x2d, 0x2b, 0xf4, 0xce,
0xd5, 0x39, 0xbd, 0x73, 0x8d, 0xc2, 0xd7, 0x54, 0xef, 0x5c, 0x27, 0x74, 0xd6, 0x3b, 0xdf, 0x80,
0x16, 0xd5, 0x4b, 0xd4, 0x3c, 0xdb, 0x1e, 0x84, 0x9a, 0xe7, 0xc3, 0x99, 0xcd, 0x73, 0x83, 0x08,
0xe6, 0x34, 0xcf, 0x8b, 0xc5, 0xe6, 0xb9, 0x07, 0x97, 0xa7, 0x6f, 0xa2, 0xe7, 0xcf, 0x07, 0x7e,
0x0c, 0xcd, 0xc4, 0x11, 0x91, 0x85, 0xb7, 0xef, 0xdd, 0x2c, 0x17, 0x13, 0x65, 0x4e, 0x3c, 0xa7,
0xf6, 0xfe, 0x51, 0x83, 0x76, 0x61, 0x92, 0x36, 0x47, 0xef, 0x5d, 0x58, 0x14, 0x61, 0x98, 0x4a,
0xad, 0xb3, 0xf7, 0x72, 0x60, 0x51, 0xa4, 0x5a, 0x49, 0xa4, 0x72, 0x35, 0x60, 0x6b, 0xb3, 0x42,
0x35, 0xc0, 0xa0, 0x9e, 0x08, 0xd3, 0x73, 0x99, 0x9d, 0xbe, 0x73, 0x4d, 0x35, 0x0a, 0x9a, 0x2a,
0xce, 0x93, 0x16, 0x5d, 0x83, 0xee, 0xe6, 0x49, 0x6b, 0xb0, 0x20, 0x07, 0xf1, 0x97, 0x8a, 0xa2,
0x7a, 0x8b, 0x5b, 0x00, 0x55, 0x75, 0x2e, 0xfa, 0x7d, 0x69, 0x5c, 0xc3, 0xe3, 0x20, 0x64, 0x8e,
0x66, 0xe4, 0xaa, 0x25, 0xfa, 0x26, 0xb5, 0xaa, 0x30, 0x94, 0x91, 0xab, 0x92, 0x1c, 0xf4, 0x92,
0x6e, 0x67, 0x1d, 0x9a, 0x49, 0xac, 0x15, 0xd5, 0x9b, 0x4b, 0x76, 0x58, 0x92, 0xc1, 0xec, 0x3d,
0xb8, 0x92, 0xa4, 0x71, 0x78, 0x90, 0xca, 0x17, 0x32, 0x4d, 0x65, 0xb8, 0x4d, 0xd6, 0xbf, 0x63,
0x3b, 0x9d, 0x16, 0x9f, 0xbd, 0x88, 0xbb, 0x8c, 0xd4, 0x66, 0x7a, 0xd7, 0x8a, 0xdd, 0x35, 0x73,
0x11, 0xe5, 0x88, 0x13, 0x99, 0x8a, 0xe7, 0x7d, 0xdb, 0xec, 0xb4, 0x78, 0x0e, 0x7b, 0xbf, 0x73,
0x2a, 0x75, 0x53, 0xda, 0x39, 0x2a, 0x2d, 0x28, 0xae, 0x3a, 0x73, 0xd6, 0x54, 0x2b, 0x8f, 0x31,
0x0a, 0xe3, 0x02, 0x9b, 0xd3, 0xb1, 0x05, 0x91, 0xa9, 0x3a, 0x93, 0xa1, 0x4f, 0x69, 0x77, 0xc1,
0xb5, 0x20, 0x16, 0xf7, 0x29, 0x66, 0xdf, 0x0f, 0x61, 0xdd, 0x36, 0x0b, 0x5a, 0x86, 0x3e, 0x2d,
0xb8, 0x31, 0x00, 0x0d, 0xc3, 0x6c, 0x30, 0xba, 0x46, 0xad, 0x83, 0x96, 0xe1, 0x4e, 0xbe, 0xbe,
0x87, 0xcb, 0xb6, 0x01, 0x8e, 0x82, 0x8c, 0xbd, 0x55, 0x3e, 0x58, 0x14, 0x71, 0xff, 0x01, 0x34,
0x27, 0x8a, 0xb8, 0x39, 0xd3, 0xe1, 0x9c, 0x0c, 0xb7, 0xb8, 0xe1, 0x0d, 0x16, 0xdd, 0xb5, 0x99,
0x93, 0x6d, 0x5c, 0xe5, 0x39, 0x59, 0xd1, 0x16, 0xa0, 0x6c, 0x0b, 0x6f, 0xc1, 0xea, 0xc4, 0x3c,
0x53, 0x93, 0x1d, 0x75, 0xa6, 0x26, 0x44, 0xde, 0x17, 0x36, 0xcc, 0x66, 0x05, 0xe4, 0x81, 0x33,
0x99, 0x79, 0x25, 0x54, 0xf1, 0x66, 0xd5, 0x57, 0xba, 0x99, 0xf7, 0x9f, 0x8a, 0x8d, 0xa4, 0x87,
0xe2, 0x4c, 0x86, 0x5b, 0xce, 0x39, 0x0b, 0x6e, 0x5b, 0x29, 0xbb, 0xed, 0xac, 0x81, 0xe2, 0x4d,
0x68, 0xbd, 0x10, 0x67, 0xf1, 0x30, 0x55, 0xc6, 0x6a, 0xbf, 0xc9, 0xc7, 0x88, 0x97, 0xa4, 0x98,
0xdb, 0xd0, 0xb1, 0xe9, 0xd0, 0x2f, 0x46, 0xb2, 0xb6, 0xc5, 0xd9, 0x76, 0xf5, 0xff, 0xe1, 0x92,
0xcd, 0x0d, 0xba, 0x17, 0xa7, 0x86, 0xaa, 0x7d, 0xed, 0xdc, 0x76, 0x85, 0x16, 0x0e, 0x11, 0x8f,
0x55, 0xbf, 0xc6, 0x74, 0x28, 0x23, 0xed, 0xea, 0x31, 0xfc, 0x44, 0x53, 0x55, 0xda, 0x47, 0x47,
0x70, 0x2a, 0x68, 0x28, 0x7d, 0x24, 0xb5, 0x79, 0x58, 0x6f, 0xd6, 0x57, 0x17, 0xbc, 0x6f, 0x2a,
0xf6, 0x75, 0xa7, 0x1a, 0xa6, 0x39, 0xaf, 0x3b, 0xd9, 0x3e, 0xd8, 0x37, 0x28, 0xb5, 0x0f, 0xbb,
0xf0, 0x7a, 0xcf, 0x66, 0x23, 0x5f, 0xa4, 0x41, 0x4f, 0x9d, 0x49, 0x5f, 0x0f, 0x93, 0x04, 0x65,
0x97, 0x11, 0x3a, 0x59, 0xe8, 0x1e, 0xe8, 0xa6, 0x23, 0xdb, 0xb2, 0x54, 0x87, 0x96, 0x68, 0xd7,
0xd2, 0x78, 0x7f, 0xae, 0xd8, 0x8a, 0xf1, 0x28, 0x1d, 0x6a, 0x23, 0x43, 0x4c, 0xb1, 0xaf, 0xf8,
0xbb, 0xdd, 0xc7, 0xd0, 0x70, 0xc3, 0x0f, 0x3c, 0x67, 0x79, 0xb2, 0xc9, 0x2c, 0x30, 0xdc, 0x3c,
0x1a, 0x8f, 0x45, 0xb8, 0xdb, 0xe4, 0x7d, 0x00, 0xed, 0x02, 0x9a, 0xb5, 0x61, 0xf1, 0x78, 0xff,
0xd1, 0xfe, 0xd3, 0xcf, 0xf6, 0x57, 0x5f, 0x43, 0xe0, 0x88, 0x1f, 0x1f, 0x1e, 0xed, 0xee, 0xac,
0x56, 0xd8, 0x25, 0x58, 0x3a, 0xde, 0x27, 0xf0, 0xb3, 0xa7, 0xfc, 0xe8, 0xc1, 0xcf, 0x57, 0xab,
0xde, 0xb7, 0x35, 0x3b, 0x38, 0x78, 0x56, 0x18, 0xcc, 0xb8, 0xce, 0x6f, 0x7e, 0xc5, 0x4f, 0x2e,
0x5a, 0x2d, 0x14, 0xde, 0xcb, 0x50, 0x35, 0xb1, 0x8b, 0x21, 0x55, 0x13, 0xa3, 0x71, 0x05, 0x3d,
0x8c, 0xc4, 0xd1, 0x49, 0x16, 0x46, 0xc6, 0x08, 0x54, 0x89, 0x6b, 0x8f, 0x6d, 0x6e, 0x77, 0xf3,
0xb0, 0x1c, 0xb7, 0x45, 0x43, 0xdc, 0x54, 0xea, 0x24, 0x8e, 0x74, 0x96, 0x20, 0x72, 0x18, 0x73,
0x4d, 0x2a, 0x93, 0xbe, 0xb2, 0x9b, 0xad, 0xfd, 0xb5, 0x1c, 0x66, 0xcb, 0x30, 0x39, 0x7b, 0x00,
0xd5, 0xa4, 0x97, 0x7d, 0xaf, 0xfc, 0xb2, 0x33, 0x6e, 0xbd, 0xf9, 0x6c, 0x6a, 0x44, 0x35, 0x73,
0x6c, 0x65, 0x75, 0xd8, 0xca, 0xeb, 0xf6, 0xcf, 0x81, 0x4d, 0xef, 0x9c, 0xd2, 0xc5, 0xc1, 0xee,
0xfe, 0xce, 0xde, 0xfe, 0x4f, 0x57, 0x2b, 0xac, 0x03, 0xcd, 0xad, 0xed, 0xed, 0xdd, 0x03, 0xd4,
0x4c, 0x15, 0xa1, 0x9d, 0xdd, 0xed, 0xc7, 0x7b, 0xfb, 0xbb, 0x3b, 0xab, 0x35, 0x84, 0xb6, 0xb7,
0xf6, 0xb7, 0x77, 0x1f, 0xef, 0xee, 0xac, 0xd6, 0xbd, 0x7f, 0x55, 0x6c, 0x41, 0xbf, 0x5d, 0x9a,
0x0f, 0xed, 0xc8, 0x40, 0xe9, 0xf9, 0xb3, 0xd8, 0x9b, 0xd0, 0x72, 0xef, 0xb9, 0x97, 0x59, 0xda,
0x18, 0xc1, 0x7e, 0x09, 0x2b, 0xa1, 0xdb, 0xef, 0x97, 0x2c, 0xef, 0xdd, 0xc9, 0xa9, 0xc4, 0xac,
0x23, 0x37, 0xb3, 0x0f, 0xf7, 0x3c, 0xcb, 0x61, 0x09, 0xf6, 0xde, 0x86, 0xe5, 0x32, 0x45, 0xe9,
0xb2, 0xaf, 0x95, 0x2e, 0x5b, 0xf1, 0xfe, 0x56, 0x85, 0x95, 0x89, 0x1f, 0x5b, 0xe7, 0x97, 0x3e,
0x93, 0x93, 0xb0, 0xea, 0xd4, 0x24, 0x8c, 0xbd, 0x0d, 0xac, 0x48, 0xe2, 0x17, 0xc7, 0x10, 0xab,
0x05, 0x42, 0x1b, 0xab, 0x8a, 0xb5, 0x54, 0xfd, 0x22, 0xb5, 0x14, 0xfb, 0x08, 0x3a, 0x3a, 0x0e,
0x94, 0xe8, 0xfb, 0x7d, 0x15, 0x9d, 0x66, 0xbf, 0x70, 0x5f, 0x9f, 0xf8, 0xf5, 0x96, 0x28, 0x1e,
0x23, 0x01, 0x6f, 0xeb, 0x31, 0xc0, 0x7e, 0x06, 0x6b, 0x32, 0xd2, 0x7e, 0x56, 0x4f, 0xfb, 0x61,
0xfe, 0x9b, 0x76, 0x6d, 0x7a, 0x38, 0x34, 0x55, 0xb0, 0x73, 0x26, 0x27, 0x51, 0xda, 0xd3, 0x00,
0x5c, 0x9c, 0xbb, 0x96, 0xb3, 0x58, 0xf4, 0x56, 0xca, 0x45, 0xef, 0x23, 0x68, 0xbb, 0xff, 0x6e,
0x38, 0xca, 0x3a, 0xf7, 0xe5, 0x7b, 0x6f, 0x8d, 0x4f, 0xdc, 0x1a, 0xff, 0x3f, 0xc4, 0x13, 0xf7,
0xef, 0x10, 0x8e, 0xe9, 0x26, 0x6e, 0xe0, 0xc5, 0xdd, 0xde, 0x9f, 0x2a, 0xb0, 0x8c, 0x22, 0x16,
0x4e, 0xfe, 0x11, 0x75, 0xe1, 0x59, 0xeb, 0xeb, 0x7e, 0xfa, 0x59, 0x2b, 0x4c, 0x5c, 0xf2, 0x45,
0x5e, 0x24, 0x64, 0xf7, 0x60, 0x4d, 0x0f, 0x9f, 0x67, 0x59, 0xf3, 0xa1, 0x8e, 0xa3, 0xfb, 0x23,
0x23, 0xb3, 0x1a, 0x74, 0xe6, 0x1a, 0x7b, 0x1b, 0x2e, 0x65, 0x63, 0xb2, 0xf1, 0x06, 0xfb, 0x03,
0xd9, 0xf4, 0x82, 0xf7, 0xfb, 0x4a, 0x5e, 0x2b, 0x61, 0xc2, 0xa6, 0x5e, 0x2c, 0x37, 0x31, 0xfc,
0x9c, 0x99, 0x29, 0xaf, 0x42, 0xc3, 0x0d, 0xcf, 0x6d, 0x16, 0x70, 0x50, 0xd1, 0x48, 0xeb, 0x25,
0x23, 0xbd, 0x09, 0x2d, 0x97, 0x79, 0x25, 0x9a, 0x05, 0xb6, 0xa0, 0x63, 0x44, 0xa9, 0xa8, 0xb4,
0x45, 0x51, 0x0e, 0x7b, 0x5f, 0xd8, 0x0c, 0x52, 0xb0, 0x1a, 0xf6, 0xfe, 0x84, 0x99, 0x4d, 0x3d,
0xe7, 0x98, 0xb8, 0x6c, 0x61, 0x79, 0x5c, 0xa8, 0x16, 0x7b, 0x8d, 0xaf, 0x2b, 0x70, 0xab, 0x50,
0x53, 0x6c, 0x4f, 0xff, 0x0e, 0xfa, 0x1d, 0x1d, 0xf0, 0x9c, 0xdf, 0x55, 0xab, 0x73, 0x7f, 0x57,
0x9d, 0xd7, 0x33, 0xdc, 0x5f, 0xfa, 0x45, 0x7b, 0xf3, 0x9d, 0x0f, 0xb3, 0x7b, 0x3c, 0x6f, 0xd0,
0xd7, 0xbb, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0xaa, 0x46, 0x88, 0x22, 0xd3, 0x23, 0x00, 0x00,
// 3208 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x3a, 0x4b, 0x6f, 0x1c, 0xc7,
0xd1, 0xde, 0x07, 0xf7, 0x51, 0xbb, 0x7c, 0xa8, 0x45, 0x49, 0x2b, 0x4a, 0x82, 0xa9, 0xd1, 0x67,
0x58, 0xfe, 0xe0, 0x8f, 0xfe, 0x3e, 0xd9, 0x5f, 0x1c, 0xf8, 0x01, 0x87, 0x22, 0xe9, 0x88, 0x7a,
0x50, 0x4c, 0x93, 0x94, 0xed, 0x20, 0xc0, 0xb8, 0x39, 0xd3, 0xe2, 0x8e, 0x39, 0x3b, 0x33, 0x99,
0xee, 0x25, 0xb3, 0x3e, 0x04, 0xc8, 0xcd, 0x87, 0x1c, 0x72, 0xf3, 0x2d, 0xf0, 0x21, 0x87, 0x20,
0xc7, 0xdc, 0xf2, 0x07, 0x82, 0x1c, 0x73, 0x4f, 0xfe, 0x40, 0xfe, 0x41, 0x2e, 0x01, 0x82, 0xaa,
0xee, 0x99, 0x9d, 0xd9, 0x07, 0x2d, 0x22, 0x27, 0x4e, 0x55, 0x57, 0x57, 0x57, 0x77, 0xbd, 0x6b,
0x09, 0x8b, 0x89, 0x08, 0xd2, 0x20, 0x3a, 0xd9, 0x48, 0xd2, 0x58, 0xc7, 0xac, 0x45, 0x7f, 0x8e,
0x87, 0x2f, 0xd7, 0xae, 0x7a, 0x7d, 0xa1, 0xdd, 0xc0, 0x97, 0x91, 0x0e, 0xf4, 0xc8, 0x2c, 0xaf,
0x5d, 0x55, 0xa3, 0xc8, 0x73, 0x95, 0xd4, 0x3a, 0x88, 0x4e, 0x94, 0x45, 0x3a, 0x22, 0x49, 0xc2,
0xc0, 0x13, 0x3a, 0x88, 0x23, 0x77, 0x20, 0xb5, 0xf0, 0x85, 0x16, 0xee, 0x40, 0x2a, 0x25, 0x4e,
0xa4, 0xa5, 0xb9, 0xe2, 0xc5, 0x83, 0xc1, 0x30, 0x0a, 0x74, 0x20, 0xed, 0x36, 0x47, 0xc0, 0xad,
0x4f, 0xa5, 0xf6, 0xfa, 0x41, 0x74, 0xf2, 0x50, 0x78, 0xa7, 0xd2, 0x3f, 0x4a, 0xb6, 0x85, 0x16,
0xdb, 0x52, 0x8b, 0x20, 0x54, 0xec, 0x75, 0xe8, 0x10, 0x9f, 0x68, 0x38, 0x38, 0x96, 0x69, 0xaf,
0xb2, 0x5e, 0xb9, 0xbf, 0xc8, 0x01, 0x51, 0x7b, 0x84, 0x61, 0x77, 0xa1, 0xab, 0x63, 0x2d, 0xc2,
0x8c, 0xa2, 0x4a, 0x14, 0x1d, 0xc2, 0x19, 0x12, 0xe7, 0x5f, 0x4d, 0x68, 0x20, 0xef, 0x61, 0xc2,
0x56, 0x61, 0xc1, 0x0b, 0x63, 0xef, 0x94, 0x18, 0xd5, 0xb9, 0x01, 0xd8, 0x12, 0x54, 0x03, 0x9f,
0x76, 0xb6, 0x79, 0x35, 0xf0, 0xd9, 0x27, 0xd0, 0xf2, 0xe2, 0x48, 0x0b, 0x4f, 0xab, 0x5e, 0x6d,
0xbd, 0x76, 0xbf, 0xf3, 0xe0, 0xde, 0x46, 0xf6, 0x22, 0x1b, 0x07, 0xa3, 0xc8, 0xdb, 0x8d, 0x94,
0x16, 0x61, 0x48, 0x77, 0xdd, 0x32, 0x94, 0x2f, 0x1e, 0xf0, 0x7c, 0x13, 0xdb, 0x81, 0x4e, 0xe1,
0xa6, 0xbd, 0xfa, 0xf7, 0xf3, 0x30, 0xc4, 0x23, 0x5e, 0xdc, 0xc7, 0x9e, 0xc3, 0x72, 0xc6, 0xd2,
0xbe, 0x47, 0x6f, 0x61, 0xbd, 0x72, 0xbf, 0xf3, 0xe0, 0x8d, 0x31, 0xab, 0x0b, 0x1e, 0x8f, 0x4f,
0xee, 0x66, 0x47, 0xc0, 0x0a, 0xfc, 0x33, 0x9e, 0x8d, 0xcb, 0xf0, 0x9c, 0xc1, 0x80, 0xbd, 0x0b,
0xcd, 0x24, 0x8d, 0x5f, 0x06, 0xa1, 0xec, 0x35, 0x89, 0xd7, 0xcd, 0x31, 0xaf, 0x8c, 0xc7, 0xbe,
0x21, 0xe0, 0x19, 0x25, 0x7b, 0x06, 0x4b, 0xf6, 0x33, 0x93, 0xa3, 0x75, 0x19, 0x39, 0x26, 0x36,
0xb3, 0x77, 0xa0, 0x69, 0x0d, 0xb2, 0xd7, 0x26, 0x3e, 0xd7, 0xca, 0xcf, 0x7d, 0x60, 0x16, 0x79,
0x46, 0x85, 0x8f, 0x9b, 0x59, 0x70, 0x26, 0x00, 0x5c, 0xea, 0x71, 0x27, 0x76, 0xa3, 0x04, 0xa7,
0x72, 0x84, 0x8e, 0xd4, 0xeb, 0xcc, 0x92, 0xe0, 0x89, 0x59, 0xe4, 0x19, 0x15, 0xbe, 0x80, 0xfd,
0xcc, 0x04, 0xe8, 0x5e, 0xea, 0x05, 0xca, 0x9b, 0xd9, 0x26, 0xac, 0x9c, 0x0b, 0xed, 0xf5, 0x9f,
0x47, 0xe1, 0x68, 0xd3, 0xf3, 0xe2, 0x61, 0xa4, 0x7b, 0x8b, 0xb3, 0x04, 0xb1, 0x8b, 0x7c, 0x8a,
0x9c, 0xb9, 0x70, 0x63, 0x12, 0x97, 0x89, 0xb6, 0x74, 0x19, 0xd1, 0xe6, 0x71, 0x61, 0xf7, 0x61,
0x01, 0x03, 0x8a, 0xea, 0x2d, 0x93, 0x4b, 0xb0, 0xb2, 0x60, 0x5b, 0x7d, 0xa1, 0xb9, 0x21, 0x60,
0xbb, 0xd0, 0xa5, 0x8f, 0xec, 0xfc, 0x95, 0xcb, 0x9c, 0x5f, 0xda, 0xea, 0xfc, 0x7e, 0x01, 0xba,
0xcf, 0x86, 0xa1, 0x0e, 0xb2, 0x6b, 0x32, 0xa8, 0x47, 0x62, 0x20, 0x29, 0x08, 0xb4, 0x39, 0x7d,
0xb3, 0xdb, 0xd0, 0xd6, 0xc1, 0x40, 0x2a, 0x2d, 0x06, 0x09, 0x85, 0x82, 0x1a, 0x1f, 0x23, 0x70,
0xd5, 0xc4, 0x40, 0x2f, 0x8e, 0x7a, 0x35, 0xda, 0x36, 0x46, 0xb0, 0x4f, 0x00, 0xbc, 0x38, 0x8c,
0x53, 0xb7, 0x2f, 0x54, 0xdf, 0x7a, 0xfb, 0xfa, 0x58, 0xd2, 0xe2, 0xd9, 0x1b, 0x5b, 0x48, 0xf8,
0x48, 0xa8, 0x3e, 0x6f, 0x7b, 0xd9, 0x27, 0xbb, 0x89, 0x01, 0x07, 0x19, 0x04, 0x3e, 0x79, 0x78,
0x8d, 0x37, 0x09, 0xde, 0xf5, 0xd9, 0x9b, 0xb0, 0x7c, 0x2a, 0x47, 0x9e, 0x48, 0x7d, 0xd7, 0xc6,
0x68, 0xf2, 0xd7, 0x36, 0xa9, 0x1f, 0xd1, 0xfb, 0x06, 0xcb, 0x6e, 0x90, 0xf9, 0xb9, 0xc3, 0xc0,
0x27, 0x27, 0x6c, 0xf3, 0xc6, 0xa9, 0x1c, 0x1d, 0x05, 0x3e, 0xfb, 0x08, 0x1a, 0xc1, 0x40, 0x9c,
0x48, 0x74, 0x30, 0x94, 0xec, 0xbf, 0xe6, 0x48, 0xb6, 0x6b, 0x83, 0xfc, 0x2e, 0x12, 0x73, 0xbb,
0x87, 0xbd, 0x03, 0x57, 0xbd, 0xa1, 0xd2, 0xf1, 0x20, 0xf8, 0xda, 0x84, 0x76, 0x12, 0x8c, 0x7c,
0xac, 0xcd, 0x59, 0x69, 0x89, 0xae, 0xc6, 0x3e, 0x80, 0x9b, 0x33, 0x36, 0xb8, 0x26, 0xec, 0x02,
0x85, 0xdd, 0x1b, 0xd3, 0xdb, 0xb6, 0x70, 0x79, 0xed, 0x2e, 0xb4, 0xf3, 0xf7, 0xc1, 0x58, 0x1d,
0x44, 0xbe, 0xfc, 0x45, 0xaf, 0xb2, 0x5e, 0xbb, 0x5f, 0xe3, 0x06, 0x58, 0xfb, 0x5b, 0x05, 0x16,
0x4b, 0x92, 0x16, 0x2f, 0x5e, 0x29, 0x5d, 0x3c, 0x53, 0x73, 0xb5, 0xa0, 0xe6, 0x1e, 0x34, 0x13,
0x31, 0x0a, 0x63, 0xe1, 0x93, 0x1a, 0xbb, 0x3c, 0x03, 0xf1, 0xb8, 0xf3, 0xc0, 0xd7, 0xa8, 0x3f,
0x54, 0x80, 0x01, 0xd8, 0x75, 0x68, 0xf4, 0x65, 0x70, 0xd2, 0xd7, 0x56, 0x2f, 0x16, 0x62, 0x6b,
0xd0, 0xc2, 0xe8, 0xa3, 0x82, 0xaf, 0x25, 0xe9, 0xa3, 0xc6, 0x73, 0x98, 0xdd, 0x83, 0xc5, 0x94,
0xbe, 0x5c, 0x2d, 0xd2, 0x13, 0xa9, 0x49, 0x1f, 0x35, 0xde, 0x35, 0xc8, 0x43, 0xc2, 0x8d, 0x33,
0x51, 0xab, 0x90, 0x89, 0x9c, 0x6f, 0xab, 0x70, 0xf5, 0x69, 0xec, 0x89, 0xd0, 0x6a, 0x75, 0xdf,
0x0a, 0xf7, 0xff, 0x50, 0x3f, 0x95, 0x23, 0x45, 0x4f, 0xd1, 0x79, 0x70, 0x77, 0xac, 0xc1, 0x19,
0xc4, 0x1b, 0x4f, 0xe4, 0x88, 0x13, 0x39, 0xfb, 0x00, 0xba, 0x03, 0x54, 0xb1, 0xb0, 0xe1, 0xa0,
0x4a, 0x4e, 0x74, 0x7d, 0xb6, 0x01, 0xf0, 0x12, 0x2d, 0xde, 0x30, 0x11, 0x4a, 0x9d, 0xc7, 0xa9,
0x6f, 0x2d, 0x3e, 0x87, 0xf1, 0x15, 0xd1, 0xc3, 0x9e, 0xc8, 0x11, 0xbd, 0x56, 0x9b, 0x67, 0x20,
0xbb, 0x9f, 0x9b, 0xab, 0x15, 0xca, 0xa4, 0xac, 0x36, 0x9f, 0x44, 0xaf, 0xfd, 0x0f, 0xd4, 0x70,
0xc3, 0x2c, 0x5f, 0x64, 0x50, 0xc7, 0x0c, 0x4f, 0xe2, 0x76, 0x39, 0x7d, 0x3b, 0x7f, 0xaa, 0xc0,
0xb5, 0xd2, 0x65, 0xa5, 0x4c, 0x1f, 0xc9, 0x30, 0x8c, 0xd1, 0x43, 0xac, 0x67, 0xb8, 0x67, 0x32,
0x55, 0x41, 0x1c, 0x11, 0xb3, 0x05, 0xbe, 0x64, 0xd1, 0x2f, 0x0c, 0x16, 0x0d, 0x25, 0x91, 0x92,
0x9c, 0xcc, 0x70, 0x6e, 0x20, 0xb8, 0xeb, 0x53, 0x91, 0x21, 0xcf, 0x02, 0x4f, 0xba, 0x24, 0x8a,
0xb9, 0x2d, 0x18, 0xd4, 0x1e, 0x0a, 0x34, 0x26, 0xd0, 0xa3, 0x44, 0xda, 0x3b, 0x5b, 0x82, 0xc3,
0x51, 0x42, 0xd1, 0x43, 0x05, 0x27, 0x91, 0xd0, 0xc3, 0x54, 0xd2, 0x85, 0xbb, 0x7c, 0x8c, 0x70,
0x7e, 0x57, 0x81, 0x55, 0x8c, 0x6f, 0x28, 0x7a, 0x31, 0xed, 0xcf, 0x29, 0x47, 0xde, 0x84, 0xe5,
0xa0, 0x40, 0xe5, 0xe6, 0xb5, 0xc9, 0x52, 0x11, 0x5d, 0x92, 0x9b, 0xc4, 0xaa, 0x4d, 0x89, 0x95,
0x3d, 0x6e, 0xbd, 0xec, 0x01, 0xd9, 0x33, 0x2d, 0x50, 0xad, 0x94, 0x81, 0xce, 0xaf, 0x1a, 0x70,
0x73, 0x6e, 0x75, 0xc3, 0xfe, 0x17, 0x56, 0x43, 0xa1, 0xb4, 0x3b, 0x4c, 0x7c, 0xa1, 0xa5, 0xef,
0x86, 0xa8, 0x8c, 0x70, 0x64, 0x45, 0x67, 0xb8, 0x76, 0x64, 0x96, 0x9e, 0x9a, 0x95, 0xa9, 0xb2,
0xea, 0x1e, 0x2c, 0xda, 0xa4, 0xed, 0x52, 0x70, 0xb1, 0x02, 0x77, 0x2d, 0xd2, 0x78, 0xf3, 0x4d,
0x68, 0xc9, 0x48, 0xb9, 0x05, 0xb1, 0x9b, 0x32, 0x52, 0xa4, 0x85, 0xbb, 0xd0, 0x2d, 0x4a, 0x40,
0xe2, 0xd7, 0x79, 0xa7, 0x70, 0x32, 0xbe, 0x88, 0x1a, 0x29, 0x2d, 0x07, 0xae, 0x16, 0x27, 0x58,
0xd9, 0xd4, 0xf0, 0x45, 0x0c, 0xea, 0x50, 0x9c, 0x28, 0xf6, 0x06, 0x2c, 0x91, 0xe0, 0x6e, 0x14,
0x78, 0xa7, 0x74, 0x88, 0x09, 0x96, 0x8b, 0x84, 0xdd, 0xb3, 0x48, 0x54, 0x8c, 0xf0, 0x7d, 0xe9,
0x53, 0x9c, 0x6b, 0x71, 0x03, 0xe0, 0xd3, 0x1d, 0xa3, 0x86, 0xa4, 0x4f, 0x81, 0xac, 0xc5, 0x33,
0x10, 0xe9, 0x07, 0x43, 0x94, 0xa9, 0x63, 0xe8, 0x09, 0x40, 0xfa, 0x54, 0x0e, 0xe2, 0x33, 0xe9,
0x53, 0x66, 0x6f, 0xf1, 0x0c, 0x64, 0xeb, 0xd0, 0xed, 0x0b, 0xe5, 0x12, 0x5b, 0x77, 0xa8, 0x28,
0x4f, 0xb7, 0x38, 0xf4, 0x85, 0xda, 0x44, 0xd4, 0x11, 0xc5, 0xdd, 0x33, 0x99, 0x06, 0x2f, 0xb3,
0x8a, 0x5a, 0x69, 0xa1, 0x87, 0x26, 0x0d, 0xd7, 0x38, 0x2b, 0x2e, 0x1d, 0xd0, 0x0a, 0x15, 0xc2,
0xe9, 0x50, 0xe9, 0x8c, 0x72, 0x99, 0x28, 0x3b, 0x84, 0xb3, 0x24, 0x1f, 0xc3, 0x2d, 0x5b, 0x11,
0xba, 0xa9, 0xfc, 0xf9, 0x50, 0x2a, 0x6d, 0xb4, 0x48, 0x5b, 0x24, 0xa5, 0xd8, 0x1a, 0xef, 0x59,
0x12, 0x6e, 0x28, 0x48, 0x99, 0xb8, 0x5f, 0xce, 0xdf, 0x6e, 0x6c, 0xf8, 0xca, 0xdc, 0xed, 0x14,
0xdc, 0xd9, 0x27, 0x70, 0x7b, 0x72, 0x3b, 0x3e, 0x87, 0x96, 0xf6, 0x78, 0x46, 0xfb, 0x6f, 0x96,
0xf7, 0x73, 0xa2, 0x30, 0xe7, 0xcf, 0x67, 0x60, 0x04, 0xb8, 0x3a, 0x9f, 0x81, 0x91, 0xe0, 0x2e,
0x74, 0xfd, 0x40, 0x25, 0xa1, 0x18, 0x19, 0xfb, 0x5a, 0x25, 0xd5, 0x77, 0x2c, 0x0e, 0x6d, 0xcc,
0x39, 0x87, 0x1b, 0x93, 0x2e, 0x90, 0x55, 0x0d, 0xb3, 0x9d, 0x75, 0xca, 0xa8, 0xab, 0x33, 0x8c,
0x7a, 0xd2, 0x72, 0x6b, 0x53, 0x96, 0xeb, 0xfc, 0xba, 0x3e, 0xcb, 0xf9, 0x6c, 0x5b, 0xf0, 0xbd,
0x7d, 0x4b, 0xd7, 0x3a, 0x58, 0x27, 0x49, 0x83, 0x33, 0xa1, 0xa5, 0x7b, 0x2a, 0x47, 0x26, 0xc1,
0x3d, 0xac, 0xf6, 0x2a, 0x1c, 0x2c, 0x1a, 0x03, 0xee, 0x3a, 0x06, 0x0d, 0xe5, 0xa5, 0x41, 0x82,
0x47, 0x90, 0x8f, 0x75, 0x79, 0x11, 0x85, 0x39, 0xef, 0xab, 0x38, 0x88, 0xac, 0x87, 0xb5, 0xb8,
0x85, 0x30, 0x23, 0x18, 0xbb, 0x93, 0x3e, 0xe5, 0xbc, 0x16, 0xcf, 0xe1, 0xb1, 0x03, 0x34, 0x8b,
0x0e, 0xf0, 0x1c, 0x56, 0xac, 0xa6, 0x94, 0xab, 0x63, 0x17, 0xf9, 0xd8, 0x22, 0xe4, 0x8d, 0x89,
0xca, 0x2f, 0x6f, 0x80, 0x2c, 0xf9, 0x61, 0xfc, 0x38, 0x0e, 0x22, 0xbe, 0x94, 0x96, 0x60, 0xf6,
0x21, 0xb4, 0xb2, 0xb2, 0xdb, 0x96, 0xf9, 0xaf, 0xcf, 0x61, 0x64, 0xeb, 0x7d, 0xc5, 0xf3, 0x0d,
0x18, 0xa4, 0x65, 0xe4, 0xa5, 0xa3, 0x44, 0xe7, 0x0e, 0x3c, 0x46, 0x50, 0x08, 0x4f, 0xa4, 0xa7,
0xc5, 0xd8, 0x8d, 0xc7, 0x08, 0x8c, 0xc9, 0x96, 0x14, 0x9d, 0x91, 0x72, 0x71, 0x97, 0x5e, 0x6e,
0x69, 0x8c, 0x7e, 0x82, 0x29, 0x77, 0x07, 0xba, 0x68, 0x80, 0x69, 0x1c, 0xba, 0x51, 0xec, 0x4b,
0x5b, 0x81, 0x3b, 0x73, 0xa4, 0xdc, 0x32, 0xa4, 0x7b, 0xb1, 0x2f, 0xb1, 0xf5, 0xcb, 0x01, 0x2c,
0x04, 0x6e, 0x5d, 0xf0, 0x30, 0x56, 0xf5, 0x95, 0x5c, 0xf5, 0x77, 0x00, 0x92, 0xe1, 0x71, 0x18,
0x78, 0xa4, 0x79, 0x63, 0x83, 0x6d, 0x83, 0x41, 0xa5, 0xe7, 0xf6, 0x53, 0x2b, 0xda, 0xcf, 0x05,
0xb1, 0xf6, 0x86, 0xc9, 0xf0, 0x59, 0x41, 0xda, 0xe6, 0x0d, 0x04, 0x77, 0x7d, 0x34, 0xe5, 0xac,
0x03, 0x1c, 0xe1, 0x6a, 0xc3, 0xd8, 0x4f, 0x8e, 0xdb, 0x25, 0x5b, 0x30, 0x1e, 0xdd, 0x34, 0x87,
0x11, 0xc0, 0x3e, 0x85, 0x2b, 0xa9, 0x3c, 0x93, 0x22, 0x94, 0xbe, 0x6b, 0x6b, 0x8c, 0xac, 0x22,
0x2d, 0xb4, 0x8b, 0xdc, 0x92, 0xe4, 0x3d, 0x4a, 0x5a, 0x46, 0x28, 0xe7, 0x0b, 0xe8, 0xcd, 0x7b,
0xc2, 0xff, 0x30, 0x9f, 0x3a, 0xff, 0xa8, 0x40, 0x2b, 0xeb, 0x43, 0x0a, 0x2f, 0x6c, 0xb2, 0xd7,
0x2d, 0x68, 0xd3, 0x8b, 0x50, 0xaa, 0x35, 0x53, 0x86, 0x16, 0x22, 0x4a, 0x89, 0xb6, 0x56, 0x48,
0xb4, 0x9f, 0xc3, 0xf5, 0x81, 0x1c, 0x1c, 0xcb, 0x54, 0xf5, 0x83, 0xc4, 0xb8, 0xf9, 0xce, 0x99,
0xc4, 0x5b, 0x4f, 0x77, 0x08, 0x33, 0xe9, 0xf8, 0x9c, 0xfd, 0xe8, 0xa0, 0xc2, 0xd3, 0xc1, 0x99,
0xcc, 0x1c, 0xd4, 0x40, 0xe3, 0xeb, 0x37, 0x8a, 0xd7, 0x9f, 0xe9, 0x9a, 0xce, 0x37, 0x55, 0xb8,
0x3e, 0xfb, 0xd8, 0x39, 0xaf, 0xc8, 0xa0, 0x5e, 0xb8, 0x3a, 0x7d, 0x63, 0x82, 0xb3, 0x22, 0xd2,
0x9c, 0xa4, 0xcd, 0x33, 0x70, 0x66, 0xe5, 0x71, 0x61, 0x91, 0x54, 0xb4, 0xb8, 0x46, 0xc9, 0xe2,
0x18, 0xd4, 0x5f, 0xa6, 0xf1, 0xc0, 0x26, 0x6a, 0xfa, 0xc6, 0x3c, 0x9f, 0x8a, 0x73, 0x37, 0x2b,
0xe5, 0x5b, 0xc4, 0x0c, 0x52, 0x71, 0xbe, 0x3f, 0xae, 0xe6, 0x8b, 0x8d, 0x8a, 0x01, 0xa8, 0xa5,
0xa0, 0x20, 0x0d, 0xb4, 0xc1, 0x00, 0xce, 0xfb, 0xb0, 0x9c, 0x77, 0x9f, 0x36, 0x3f, 0xbf, 0xd2,
0x9c, 0xc8, 0xf9, 0xc8, 0x94, 0x75, 0xb8, 0xf1, 0x99, 0x99, 0x73, 0x29, 0x2e, 0xc5, 0xab, 0xee,
0xfe, 0x11, 0x5c, 0x37, 0xdd, 0xb8, 0x0e, 0xce, 0xd0, 0x8e, 0x65, 0xa4, 0x65, 0x7a, 0xc1, 0xfe,
0x15, 0xa8, 0x05, 0xbe, 0xea, 0x55, 0xd7, 0x6b, 0xf7, 0xbb, 0x1c, 0x3f, 0x9d, 0x6d, 0x58, 0x9b,
0xe6, 0xb0, 0xe9, 0x79, 0x92, 0x02, 0xda, 0xab, 0x72, 0xd9, 0x31, 0x91, 0xa6, 0xcc, 0x65, 0x3b,
0x50, 0x83, 0x40, 0xa9, 0x4b, 0xb0, 0xd9, 0x32, 0xf9, 0x6b, 0x82, 0x8d, 0x0c, 0xe5, 0x65, 0x64,
0x79, 0x68, 0x9c, 0xbb, 0xcc, 0xe4, 0x28, 0x4a, 0x2f, 0xf3, 0x2a, 0x2e, 0xdc, 0x9b, 0xe6, 0xb1,
0x17, 0xeb, 0x52, 0xc1, 0x24, 0x31, 0x62, 0x66, 0xa5, 0xac, 0xd0, 0x96, 0x67, 0xdb, 0x62, 0x36,
0x35, 0xc6, 0x46, 0xac, 0xd0, 0x94, 0x94, 0x11, 0xe9, 0xac, 0xc5, 0x9b, 0x7d, 0xa1, 0x0e, 0xa4,
0x8c, 0x9c, 0xef, 0x2a, 0xd0, 0xc5, 0x13, 0x1e, 0xc6, 0xf1, 0xe9, 0x40, 0xa4, 0xa7, 0xf3, 0x25,
0x1b, 0xa6, 0xa1, 0x55, 0x38, 0x7e, 0xce, 0x8c, 0x12, 0xb7, 0xa0, 0x4d, 0x56, 0xe8, 0x22, 0xad,
0xf1, 0x96, 0x16, 0x21, 0x8e, 0xd2, 0xb0, 0x58, 0x40, 0x2e, 0x94, 0x0b, 0xc8, 0x3b, 0x00, 0xbe,
0x79, 0x5b, 0x94, 0xde, 0xf8, 0x7b, 0xdb, 0x62, 0x36, 0xb5, 0xf3, 0x4b, 0xb8, 0x86, 0x12, 0xee,
0x44, 0xea, 0x48, 0xc9, 0x14, 0x0f, 0x32, 0xc3, 0x90, 0x39, 0xa2, 0xae, 0x41, 0x6b, 0x68, 0xe9,
0xac, 0xbc, 0x39, 0x4c, 0xb3, 0x89, 0xbe, 0x08, 0x28, 0x6c, 0x9a, 0xec, 0xd1, 0x24, 0x78, 0xb7,
0x54, 0xdf, 0xd6, 0x4b, 0xe2, 0x39, 0x8f, 0x61, 0x85, 0x3c, 0x23, 0x94, 0x22, 0x7d, 0x14, 0x28,
0x1d, 0xa7, 0xa3, 0xa2, 0x83, 0x57, 0x4a, 0x0e, 0x7e, 0x07, 0xc0, 0x43, 0x42, 0x73, 0x97, 0xaa,
0xb9, 0x8b, 0xc5, 0x6c, 0x6a, 0xe7, 0x2f, 0x15, 0x60, 0xd4, 0x3d, 0x99, 0x8a, 0x6a, 0x3f, 0xf0,
0x28, 0x5e, 0xcc, 0x6a, 0x1c, 0x0b, 0xdd, 0x7d, 0x75, 0x4e, 0x77, 0x5f, 0xa3, 0xf0, 0x35, 0xd5,
0xdd, 0xd7, 0x09, 0x9d, 0x75, 0xf7, 0xb7, 0xa0, 0x4d, 0x15, 0x1d, 0xb5, 0xf7, 0xa6, 0x4b, 0xa2,
0xf6, 0xfe, 0x60, 0x66, 0x7b, 0xdf, 0x20, 0x82, 0x39, 0xed, 0x7d, 0xb3, 0xd8, 0xde, 0xf7, 0xe1,
0xea, 0xf4, 0x4d, 0xd4, 0xfc, 0x09, 0xc6, 0x0f, 0xa1, 0x95, 0x58, 0x22, 0xb2, 0xf0, 0xce, 0x83,
0xdb, 0xe5, 0x42, 0xa2, 0xcc, 0x89, 0xe7, 0xd4, 0xce, 0x5f, 0x6b, 0xd0, 0x29, 0xcc, 0xfa, 0xe6,
0xe8, 0xbd, 0x07, 0x4d, 0xe1, 0xfb, 0xa9, 0x54, 0x2a, 0x7b, 0x2f, 0x0b, 0x16, 0x45, 0xaa, 0x95,
0x44, 0x2a, 0x17, 0x1a, 0xa6, 0x7a, 0x2c, 0x14, 0x1a, 0x0c, 0xea, 0x89, 0xd0, 0x7d, 0x5b, 0x34,
0xd0, 0x77, 0xae, 0xa9, 0x46, 0x41, 0x53, 0xc5, 0x89, 0x57, 0xd3, 0x8e, 0x10, 0xec, 0xc4, 0x6b,
0x15, 0x16, 0xe4, 0x20, 0xfe, 0x2a, 0xa0, 0xa8, 0xde, 0xe6, 0x06, 0x40, 0x55, 0x9d, 0x8b, 0x30,
0x94, 0xda, 0xb6, 0x64, 0x16, 0x42, 0xe6, 0x68, 0x46, 0xb6, 0x9e, 0xa3, 0x6f, 0x52, 0x6b, 0xe0,
0xfb, 0x32, 0xb2, 0x75, 0x9c, 0x85, 0x2e, 0xe8, 0xc7, 0xd6, 0xa0, 0x95, 0xc4, 0x2a, 0xa0, 0x8a,
0x78, 0xd1, 0x8c, 0x73, 0x32, 0x98, 0xbd, 0x07, 0xd7, 0x92, 0x34, 0xf6, 0xf7, 0x53, 0xf9, 0x52,
0xa6, 0xa9, 0xf4, 0xb7, 0xc8, 0xfa, 0xb7, 0x4d, 0x2f, 0xd6, 0xe6, 0xb3, 0x17, 0x71, 0x97, 0x96,
0x4a, 0x4f, 0xef, 0x5a, 0x36, 0xbb, 0x66, 0x2e, 0xa2, 0x1c, 0x71, 0x22, 0x53, 0x71, 0x1c, 0x9a,
0x76, 0xac, 0xcd, 0x73, 0xd8, 0xf9, 0x8d, 0x55, 0xa9, 0x9d, 0x23, 0xcf, 0x51, 0x69, 0x41, 0x71,
0xd5, 0x99, 0xd3, 0xb0, 0x5a, 0x79, 0xd0, 0x52, 0x18, 0x68, 0x98, 0x9c, 0x8e, 0x4d, 0x92, 0x4c,
0x83, 0x33, 0xe9, 0xbb, 0x94, 0x76, 0x17, 0x6c, 0x93, 0x64, 0x70, 0x9f, 0x62, 0xf6, 0xfd, 0x10,
0xd6, 0x4c, 0x3b, 0xa3, 0xa4, 0xef, 0xd2, 0x82, 0x2d, 0xac, 0x68, 0x5c, 0x67, 0x82, 0xd1, 0x0d,
0x6a, 0x6e, 0x94, 0xf4, 0xb7, 0xf3, 0xf5, 0x5d, 0x5c, 0x36, 0x2d, 0x7a, 0xe4, 0x65, 0xec, 0x8d,
0xf2, 0xc1, 0xa0, 0x88, 0xfb, 0xff, 0x41, 0x6b, 0xa2, 0x3e, 0x9c, 0x33, 0xbf, 0xce, 0xc9, 0x70,
0x8b, 0x1d, 0x2f, 0x61, 0x5b, 0x50, 0x9b, 0x39, 0x7b, 0xc7, 0x55, 0x9e, 0x93, 0x15, 0x6d, 0x01,
0xca, 0xb6, 0xf0, 0x16, 0xac, 0x4c, 0x4c, 0x5c, 0x15, 0xd9, 0x51, 0x77, 0x6a, 0x86, 0xe5, 0x7c,
0x69, 0xc2, 0x6c, 0x56, 0x9b, 0xee, 0x5b, 0x93, 0x99, 0x57, 0x42, 0x15, 0x6f, 0x56, 0x7d, 0xa5,
0x9b, 0x39, 0xff, 0xac, 0x98, 0x48, 0x7a, 0x20, 0xce, 0xa4, 0xbf, 0x69, 0x9d, 0xb3, 0xe0, 0xb6,
0x95, 0xb2, 0xdb, 0xce, 0x1a, 0x79, 0xde, 0x86, 0xf6, 0x4b, 0x71, 0x16, 0x0f, 0xd3, 0x40, 0x1b,
0xed, 0xb7, 0xf8, 0x18, 0x71, 0x41, 0x8a, 0xb9, 0x0b, 0x5d, 0x93, 0x0e, 0xdd, 0x62, 0x24, 0xeb,
0x18, 0x9c, 0x69, 0xa8, 0xff, 0x1b, 0xae, 0x98, 0xdc, 0xa0, 0xfa, 0x71, 0xaa, 0xa9, 0x91, 0x50,
0xd6, 0x6d, 0x97, 0x69, 0xe1, 0x00, 0xf1, 0xd8, 0x50, 0x28, 0x4c, 0x87, 0x32, 0x52, 0xb6, 0x1e,
0xc3, 0x4f, 0x34, 0xd5, 0x40, 0xb9, 0xe8, 0x08, 0x56, 0x05, 0x8d, 0x40, 0x1d, 0x4a, 0xa5, 0x1f,
0xd7, 0x5b, 0xf5, 0x95, 0x05, 0xe7, 0xdb, 0x8a, 0x79, 0xdd, 0xa9, 0x96, 0x6e, 0xce, 0xeb, 0x4e,
0x76, 0x26, 0xe6, 0x0d, 0x4a, 0x9d, 0xc9, 0x0e, 0xbc, 0xde, 0x37, 0xd9, 0xc8, 0x15, 0xa9, 0xd7,
0x0f, 0xce, 0xa4, 0xab, 0x86, 0x49, 0x82, 0xb2, 0xcb, 0x08, 0x9d, 0xcc, 0xb7, 0x0f, 0x74, 0xdb,
0x92, 0x6d, 0x1a, 0xaa, 0x03, 0x43, 0xb4, 0x63, 0x68, 0x9c, 0x3f, 0x56, 0x4c, 0xc5, 0x78, 0x98,
0x0e, 0x95, 0x96, 0x3e, 0xa6, 0xd8, 0x57, 0xfc, 0x65, 0xf1, 0x63, 0x68, 0xd8, 0xf1, 0x0c, 0x9e,
0xb3, 0x34, 0xd9, 0x06, 0x17, 0x18, 0x6e, 0x1c, 0x8e, 0x07, 0x37, 0xdc, 0x6e, 0x72, 0x3e, 0x80,
0x4e, 0x01, 0xcd, 0x3a, 0xd0, 0x3c, 0xda, 0x7b, 0xb2, 0xf7, 0xfc, 0xb3, 0xbd, 0x95, 0xd7, 0x10,
0x38, 0xe4, 0x47, 0x07, 0x87, 0x3b, 0xdb, 0x2b, 0x15, 0x76, 0x05, 0x16, 0x8f, 0xf6, 0x08, 0xfc,
0xec, 0x39, 0x3f, 0x7c, 0xf4, 0xc5, 0x4a, 0xd5, 0xf9, 0xae, 0x66, 0x46, 0x1b, 0x2f, 0x0a, 0xa3,
0x23, 0xdb, 0x54, 0xce, 0xaf, 0xf8, 0xc9, 0x45, 0xab, 0x85, 0xc2, 0x7b, 0x09, 0xaa, 0x3a, 0xb6,
0x31, 0xa4, 0xaa, 0x63, 0x34, 0x2e, 0xaf, 0x8f, 0x91, 0x38, 0x3a, 0xc9, 0xc2, 0xc8, 0x18, 0x81,
0x2a, 0xb1, 0x0d, 0xbc, 0xc9, 0xed, 0x76, 0x62, 0x97, 0xe3, 0x36, 0x69, 0xcc, 0x9c, 0x4a, 0x95,
0xc4, 0x91, 0xca, 0x12, 0x44, 0x0e, 0x63, 0xae, 0x49, 0x65, 0x12, 0x06, 0x66, 0xb3, 0xb1, 0xbf,
0xb6, 0xc5, 0x6c, 0x6a, 0x26, 0x67, 0x8f, 0xc8, 0x5a, 0xf4, 0xb2, 0xef, 0x95, 0x5f, 0x76, 0xc6,
0xad, 0x37, 0x5e, 0x4c, 0x0d, 0xd1, 0x66, 0x0e, 0xd6, 0x8c, 0x0e, 0xdb, 0x79, 0xdd, 0xfe, 0x39,
0xb0, 0xe9, 0x9d, 0x53, 0xba, 0xd8, 0xdf, 0xd9, 0xdb, 0xde, 0xdd, 0xfb, 0xf1, 0x4a, 0x85, 0x75,
0xa1, 0xb5, 0xb9, 0xb5, 0xb5, 0xb3, 0x8f, 0x9a, 0xa9, 0x22, 0xb4, 0xbd, 0xb3, 0xf5, 0x74, 0x77,
0x6f, 0x67, 0x7b, 0xa5, 0x86, 0xd0, 0xd6, 0xe6, 0xde, 0xd6, 0xce, 0xd3, 0x9d, 0xed, 0x95, 0xba,
0xf3, 0xf7, 0x8a, 0x29, 0xe8, 0xb7, 0x4a, 0x13, 0xac, 0x6d, 0xe9, 0x05, 0x6a, 0xfe, 0xb4, 0xf8,
0x36, 0xb4, 0xed, 0x7b, 0xee, 0x66, 0x96, 0x36, 0x46, 0xb0, 0x9f, 0xc1, 0xb2, 0x6f, 0xf7, 0xbb,
0x25, 0xcb, 0x7b, 0x77, 0x72, 0x22, 0x31, 0xeb, 0xc8, 0x8d, 0xec, 0xc3, 0x3e, 0xcf, 0x92, 0x5f,
0x82, 0x9d, 0xb7, 0x61, 0xa9, 0x4c, 0x51, 0xba, 0xec, 0x6b, 0xa5, 0xcb, 0x56, 0x9c, 0x3f, 0x57,
0x61, 0x79, 0xe2, 0xe7, 0xe0, 0xf9, 0xa5, 0xcf, 0xe4, 0xac, 0xae, 0x3a, 0x35, 0xab, 0x63, 0x6f,
0x03, 0x2b, 0x92, 0xb8, 0xc5, 0x09, 0xc7, 0x4a, 0x81, 0xd0, 0xc4, 0xaa, 0x62, 0x2d, 0x55, 0xbf,
0x4c, 0x2d, 0xc5, 0x3e, 0x82, 0xae, 0x8a, 0xbd, 0x40, 0x84, 0x6e, 0x18, 0x44, 0xa7, 0xd9, 0x6f,
0xf0, 0x37, 0x27, 0x7e, 0x5f, 0x26, 0x8a, 0xa7, 0x48, 0xc0, 0x3b, 0x6a, 0x0c, 0xb0, 0x9f, 0xc0,
0xaa, 0x8c, 0x94, 0x9b, 0xd5, 0xd3, 0xae, 0x9f, 0xff, 0xea, 0x5e, 0x9b, 0x1e, 0x5f, 0x4d, 0x15,
0xec, 0x9c, 0xc9, 0x49, 0x94, 0x72, 0x14, 0x00, 0x17, 0xe7, 0xb6, 0xe5, 0x2c, 0x16, 0xbd, 0x95,
0x72, 0xd1, 0xfb, 0x04, 0x3a, 0xf6, 0xff, 0x2f, 0x0e, 0xb3, 0xce, 0x7d, 0xe9, 0xc1, 0x5b, 0xe3,
0x13, 0x37, 0xc7, 0xff, 0xb1, 0xf1, 0xcc, 0xfe, 0xc3, 0x86, 0x65, 0xba, 0x81, 0x1b, 0x78, 0x71,
0xb7, 0xf3, 0x87, 0x0a, 0x2c, 0xa1, 0x88, 0x85, 0x93, 0x7f, 0x40, 0x5d, 0x78, 0xd6, 0xfa, 0xda,
0x1f, 0xa7, 0x56, 0x0b, 0xc3, 0x9c, 0x7c, 0x91, 0x17, 0x09, 0xd9, 0x03, 0x58, 0x55, 0xc3, 0xe3,
0x2c, 0x6b, 0x3e, 0x56, 0x71, 0xf4, 0x70, 0xa4, 0x65, 0x56, 0x83, 0xce, 0x5c, 0x63, 0x6f, 0xc3,
0x95, 0x6c, 0x90, 0x37, 0xde, 0x60, 0x7e, 0xc2, 0x9b, 0x5e, 0x70, 0x7e, 0x5b, 0xc9, 0x6b, 0x25,
0x4c, 0xd8, 0xd4, 0x8b, 0xe5, 0x26, 0x86, 0x9f, 0x33, 0x33, 0xe5, 0x75, 0x68, 0xd8, 0xf1, 0xbe,
0xc9, 0x02, 0x16, 0x2a, 0x1a, 0x69, 0xbd, 0x64, 0xa4, 0xb7, 0xa1, 0x6d, 0x33, 0xaf, 0x44, 0xb3,
0xc0, 0x16, 0x74, 0x8c, 0x28, 0x15, 0x95, 0xa6, 0x28, 0xca, 0x61, 0xe7, 0x4b, 0x93, 0x41, 0x0a,
0x56, 0xc3, 0xde, 0x9f, 0x30, 0xb3, 0xa9, 0xe7, 0x1c, 0x13, 0x97, 0x2d, 0x2c, 0x8f, 0x0b, 0xd5,
0x62, 0xaf, 0xf1, 0x4d, 0x05, 0xee, 0x14, 0x6a, 0x8a, 0xad, 0xe9, 0x5f, 0x6a, 0xbf, 0xa7, 0x03,
0x9e, 0xf3, 0xcb, 0x6f, 0x75, 0xee, 0x2f, 0xbf, 0xf3, 0x7a, 0x86, 0x87, 0x8b, 0x3f, 0xed, 0x6c,
0xbc, 0xf3, 0x61, 0x76, 0x8f, 0xe3, 0x06, 0x7d, 0xbd, 0xfb, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff,
0x33, 0x1f, 0xc7, 0x0a, 0x75, 0x24, 0x00, 0x00,
}

View file

@ -136,6 +136,7 @@ message SyncInstallationCommunity {
bool encrypted = 10;
bool spectated = 11;
bytes encryption_keys = 12;
SyncCommunityControlNode control_node = 13;
}
message SyncCommunityRequestsToJoin {
@ -149,6 +150,15 @@ message SyncCommunityRequestsToJoin {
repeated RevealedAccount revealed_accounts = 8;
}
message SyncCommunityControlNode {
// Lamport timestamp of control node change
uint64 clock = 1;
// The device id of the control node
// Empty if there is no control node
string installation_id = 2;
}
message SyncChat {
string id = 1;
uint32 chat_type = 2;

View file

@ -7,12 +7,12 @@ import (
"github.com/stretchr/testify/suite"
"github.com/status-im/status-go/appdatabase"
"github.com/status-im/status-go/protocol/communities/token"
"github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/protocol/sqlite"
"github.com/status-im/status-go/services/wallet/bigint"
"github.com/status-im/status-go/t/helpers"
"github.com/status-im/status-go/walletdatabase"
)
func TestDatabaseSuite(t *testing.T) {
@ -82,7 +82,7 @@ func (s *DatabaseSuite) setupDatabase(db *sql.DB) error {
func (s *DatabaseSuite) SetupTest() {
s.db = nil
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
db, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
s.NoError(err, "creating sqlite db instance")
err = sqlite.Migrate(db)