chore: use lamport timestamp for communities
closes: status-im/status-desktop#11961
This commit is contained in:
parent
c88b6e53af
commit
9d374bcadc
|
@ -51,15 +51,20 @@ type EventsData struct {
|
|||
}
|
||||
|
||||
type Community struct {
|
||||
config *Config
|
||||
mutex sync.Mutex
|
||||
config *Config
|
||||
mutex sync.Mutex
|
||||
timesource common.TimeSource
|
||||
}
|
||||
|
||||
func New(config Config) (*Community, error) {
|
||||
func New(config Config, timesource common.TimeSource) (*Community, error) {
|
||||
if config.MemberIdentity == nil {
|
||||
return nil, errors.New("no member identity")
|
||||
}
|
||||
|
||||
if timesource == nil {
|
||||
return nil, errors.New("no timesource")
|
||||
}
|
||||
|
||||
if config.Logger == nil {
|
||||
logger, err := zap.NewDevelopment()
|
||||
if err != nil {
|
||||
|
@ -68,7 +73,7 @@ func New(config Config) (*Community, error) {
|
|||
config.Logger = logger
|
||||
}
|
||||
|
||||
community := &Community{config: &config}
|
||||
community := &Community{config: &config, timesource: timesource}
|
||||
community.initialize()
|
||||
return community, nil
|
||||
}
|
||||
|
@ -1820,7 +1825,16 @@ func (o *Community) RequestedToJoinAt() uint64 {
|
|||
}
|
||||
|
||||
func (o *Community) nextClock() uint64 {
|
||||
return o.config.CommunityDescription.Clock + 1
|
||||
// lamport timestamp
|
||||
clock := o.config.CommunityDescription.Clock
|
||||
timestamp := o.timesource.GetCurrentTime()
|
||||
if clock == 0 || clock < timestamp {
|
||||
clock = timestamp
|
||||
} else {
|
||||
clock = clock + 1
|
||||
}
|
||||
|
||||
return clock
|
||||
}
|
||||
|
||||
func (o *Community) CanManageUsersPublicKeys() ([]*ecdsa.PublicKey, error) {
|
||||
|
@ -1985,6 +1999,7 @@ func (o *Community) CreateDeepCopy() *Community {
|
|||
SyncedAt: o.config.SyncedAt,
|
||||
EventsData: o.config.EventsData,
|
||||
},
|
||||
timesource: o.timesource,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package communities
|
|||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
@ -13,27 +12,26 @@ import (
|
|||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
)
|
||||
|
||||
func createTestCommunity(identity *ecdsa.PrivateKey) *Community {
|
||||
return &Community{
|
||||
config: &Config{
|
||||
PrivateKey: identity,
|
||||
CommunityDescription: &protobuf.CommunityDescription{
|
||||
Members: map[string]*protobuf.CommunityMember{},
|
||||
Permissions: &protobuf.CommunityPermissions{},
|
||||
Identity: &protobuf.ChatIdentity{},
|
||||
Chats: map[string]*protobuf.CommunityChat{},
|
||||
BanList: []string{},
|
||||
Categories: map[string]*protobuf.CommunityCategory{},
|
||||
Encrypted: false,
|
||||
TokenPermissions: map[string]*protobuf.CommunityTokenPermission{},
|
||||
CommunityTokensMetadata: []*protobuf.CommunityTokenMetadata{},
|
||||
},
|
||||
ID: &identity.PublicKey,
|
||||
Joined: true,
|
||||
MemberIdentity: &identity.PublicKey,
|
||||
func createTestCommunity(identity *ecdsa.PrivateKey) (*Community, error) {
|
||||
config := Config{
|
||||
PrivateKey: identity,
|
||||
CommunityDescription: &protobuf.CommunityDescription{
|
||||
Members: map[string]*protobuf.CommunityMember{},
|
||||
Permissions: &protobuf.CommunityPermissions{},
|
||||
Identity: &protobuf.ChatIdentity{},
|
||||
Chats: map[string]*protobuf.CommunityChat{},
|
||||
BanList: []string{},
|
||||
Categories: map[string]*protobuf.CommunityCategory{},
|
||||
Encrypted: false,
|
||||
TokenPermissions: map[string]*protobuf.CommunityTokenPermission{},
|
||||
CommunityTokensMetadata: []*protobuf.CommunityTokenMetadata{},
|
||||
},
|
||||
mutex: sync.Mutex{},
|
||||
ID: &identity.PublicKey,
|
||||
Joined: true,
|
||||
MemberIdentity: &identity.PublicKey,
|
||||
}
|
||||
|
||||
return New(config, &TimeSourceStub{})
|
||||
}
|
||||
|
||||
func TestCommunityEncryptionKeyActionSuite(t *testing.T) {
|
||||
|
@ -79,7 +77,8 @@ func (s *CommunityEncryptionKeyActionSuite) SetupTest() {
|
|||
}
|
||||
|
||||
func (s *CommunityEncryptionKeyActionSuite) TestEncryptionKeyNone() {
|
||||
origin := createTestCommunity(s.identity)
|
||||
origin, err := createTestCommunity(s.identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// if there are no changes there should be no actions
|
||||
actions := EvaluateCommunityEncryptionKeyActions(origin, origin)
|
||||
|
@ -365,7 +364,8 @@ func (s *CommunityEncryptionKeyActionSuite) TestCommunityLevelKeyActions_Permiss
|
|||
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
origin := createTestCommunity(s.identity)
|
||||
origin, err := createTestCommunity(s.identity)
|
||||
s.Require().NoError(err)
|
||||
modified := origin.CreateDeepCopy()
|
||||
|
||||
for _, permission := range tc.originPermissions {
|
||||
|
@ -494,7 +494,9 @@ func (s *CommunityEncryptionKeyActionSuite) TestCommunityLevelKeyActions_Members
|
|||
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
origin := createTestCommunity(s.identity)
|
||||
origin, err := createTestCommunity(s.identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
for _, permission := range tc.permissions {
|
||||
_, err := origin.UpsertTokenPermission(permission)
|
||||
s.Require().NoError(err)
|
||||
|
@ -595,7 +597,8 @@ func (s *CommunityEncryptionKeyActionSuite) TestCommunityLevelKeyActions_Permiss
|
|||
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
origin := createTestCommunity(s.identity)
|
||||
origin, err := createTestCommunity(s.identity)
|
||||
s.Require().NoError(err)
|
||||
modified := origin.CreateDeepCopy()
|
||||
|
||||
for _, permission := range tc.originPermissions {
|
||||
|
@ -737,8 +740,10 @@ func (s *CommunityEncryptionKeyActionSuite) TestChannelLevelKeyActions() {
|
|||
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
origin := createTestCommunity(s.identity)
|
||||
_, err := origin.CreateChat(channelID, &protobuf.CommunityChat{
|
||||
origin, err := createTestCommunity(s.identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
_, err = origin.CreateChat(channelID, &protobuf.CommunityChat{
|
||||
Members: map[string]*protobuf.CommunityMember{},
|
||||
Permissions: &protobuf.CommunityPermissions{Access: protobuf.CommunityPermissions_NO_MEMBERSHIP},
|
||||
Identity: &protobuf.ChatIdentity{},
|
||||
|
@ -783,12 +788,13 @@ func (s *CommunityEncryptionKeyActionSuite) TestChannelLevelKeyActions() {
|
|||
}
|
||||
|
||||
func (s *CommunityEncryptionKeyActionSuite) TestNilOrigin() {
|
||||
newCommunity := createTestCommunity(s.identity)
|
||||
newCommunity, err := createTestCommunity(s.identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
channelID := "0x1234"
|
||||
chatID := types.EncodeHex(crypto.CompressPubkey(&s.identity.PublicKey)) + channelID
|
||||
|
||||
_, err := newCommunity.CreateChat(channelID, &protobuf.CommunityChat{
|
||||
_, err = newCommunity.CreateChat(channelID, &protobuf.CommunityChat{
|
||||
Members: map[string]*protobuf.CommunityMember{},
|
||||
Permissions: &protobuf.CommunityPermissions{Access: protobuf.CommunityPermissions_NO_MEMBERSHIP},
|
||||
Identity: &protobuf.ChatIdentity{},
|
||||
|
|
|
@ -3,6 +3,7 @@ package communities
|
|||
import (
|
||||
"crypto/ecdsa"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
|
@ -22,6 +23,13 @@ const testCategoryID1 = "category-id-1"
|
|||
const testCategoryName1 = "category-name-1"
|
||||
const testChatID2 = "chat-id-2"
|
||||
|
||||
type TimeSourceStub struct {
|
||||
}
|
||||
|
||||
func (t *TimeSourceStub) GetCurrentTime() uint64 {
|
||||
return uint64(time.Now().Unix())
|
||||
}
|
||||
|
||||
type CommunitySuite struct {
|
||||
suite.Suite
|
||||
|
||||
|
@ -219,6 +227,7 @@ func (s *CommunitySuite) TestDeleteChat() {
|
|||
|
||||
_, err := org.DeleteChat(testChatID1)
|
||||
s.Require().Equal(ErrNotAuthorized, err)
|
||||
change1Clock := org.Clock()
|
||||
|
||||
org.config.PrivateKey = s.identity
|
||||
org.config.ID = &s.identity.PublicKey
|
||||
|
@ -226,10 +235,11 @@ func (s *CommunitySuite) TestDeleteChat() {
|
|||
changes, err := org.DeleteChat(testChatID1)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(changes)
|
||||
change2Clock := org.Clock()
|
||||
|
||||
s.Require().Nil(org.Chats()[testChatID1])
|
||||
s.Require().Len(changes.ChatsRemoved, 1)
|
||||
s.Require().Equal(uint64(2), org.Clock())
|
||||
s.Require().Greater(change2Clock, change1Clock)
|
||||
}
|
||||
|
||||
func (s *CommunitySuite) TestRemoveUserFromChat() {
|
||||
|
@ -465,7 +475,7 @@ func (s *CommunitySuite) TestValidateRequestToJoin() {
|
|||
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
org, err := New(tc.config)
|
||||
org, err := New(tc.config, &TimeSourceStub{})
|
||||
s.Require().NoError(err)
|
||||
err = org.ValidateRequestToJoin(tc.signer, tc.request)
|
||||
s.Require().Equal(tc.err, err)
|
||||
|
@ -563,7 +573,7 @@ func (s *CommunitySuite) TestCanPost() {
|
|||
s.Run(tc.name, func() {
|
||||
var grant []byte
|
||||
var err error
|
||||
org, err := New(tc.config)
|
||||
org, err := New(tc.config, &TimeSourceStub{})
|
||||
s.Require().NoError(err)
|
||||
|
||||
if tc.grant == validGrant {
|
||||
|
@ -958,7 +968,7 @@ func (s *CommunitySuite) buildCommunity(owner *ecdsa.PublicKey) *Community {
|
|||
config.ID = owner
|
||||
config.CommunityDescription = s.buildCommunityDescription()
|
||||
|
||||
org, err := New(config)
|
||||
org, err := New(config, &TimeSourceStub{})
|
||||
s.Require().NoError(err)
|
||||
return org
|
||||
}
|
||||
|
|
|
@ -76,6 +76,7 @@ type Manager struct {
|
|||
logger *zap.Logger
|
||||
stdoutLogger *zap.Logger
|
||||
transport *transport.Transport
|
||||
timesource common.TimeSource
|
||||
quit chan struct{}
|
||||
torrentConfig *params.TorrentConfig
|
||||
torrentClient *torrent.Client
|
||||
|
@ -197,11 +198,15 @@ func WithCommunityTokensService(communityTokensService communitytokens.ServiceIn
|
|||
}
|
||||
}
|
||||
|
||||
func NewManager(identity *ecdsa.PrivateKey, db *sql.DB, encryptor *encryption.Protocol, logger *zap.Logger, verifier *ens.Verifier, transport *transport.Transport, torrentConfig *params.TorrentConfig, opts ...ManagerOption) (*Manager, error) {
|
||||
func NewManager(identity *ecdsa.PrivateKey, db *sql.DB, encryptor *encryption.Protocol, logger *zap.Logger, verifier *ens.Verifier, transport *transport.Transport, timesource common.TimeSource, torrentConfig *params.TorrentConfig, opts ...ManagerOption) (*Manager, error) {
|
||||
if identity == nil {
|
||||
return nil, errors.New("empty identity")
|
||||
}
|
||||
|
||||
if timesource == nil {
|
||||
return nil, errors.New("no timesource")
|
||||
}
|
||||
|
||||
var err error
|
||||
if logger == nil {
|
||||
if logger, err = zap.NewDevelopment(); err != nil {
|
||||
|
@ -226,12 +231,14 @@ func NewManager(identity *ecdsa.PrivateKey, db *sql.DB, encryptor *encryption.Pr
|
|||
identity: identity,
|
||||
quit: make(chan struct{}),
|
||||
transport: transport,
|
||||
timesource: timesource,
|
||||
torrentConfig: torrentConfig,
|
||||
torrentTasks: make(map[string]metainfo.Hash),
|
||||
historyArchiveDownloadTasks: make(map[string]*HistoryArchiveDownloadTask),
|
||||
persistence: &Persistence{
|
||||
logger: logger,
|
||||
db: db,
|
||||
logger: logger,
|
||||
db: db,
|
||||
timesource: timesource,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -647,7 +654,7 @@ func (m *Manager) CreateCommunity(request *requests.CreateCommunity, publish boo
|
|||
MemberIdentity: &m.identity.PublicKey,
|
||||
CommunityDescription: description,
|
||||
}
|
||||
community, err := New(config)
|
||||
community, err := New(config, m.timesource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1043,7 +1050,7 @@ func (m *Manager) ImportCommunity(key *ecdsa.PrivateKey) (*Community, error) {
|
|||
MemberIdentity: &m.identity.PublicKey,
|
||||
CommunityDescription: description,
|
||||
}
|
||||
community, err = New(config)
|
||||
community, err = New(config, m.timesource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1326,8 +1333,7 @@ func (m *Manager) HandleCommunityDescriptionMessage(signer *ecdsa.PublicKey, des
|
|||
MemberIdentity: &m.identity.PublicKey,
|
||||
ID: signer,
|
||||
}
|
||||
|
||||
community, err = New(config)
|
||||
community, err = New(config, m.timesource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ func (s *ManagerSuite) SetupTest() {
|
|||
key, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
s.Require().NoError(err)
|
||||
m, err := NewManager(key, db, nil, nil, nil, nil, nil)
|
||||
m, err := NewManager(key, db, nil, nil, nil, nil, &TimeSourceStub{}, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NoError(m.Start())
|
||||
s.manager = m
|
||||
|
@ -161,7 +161,7 @@ func (s *ManagerSuite) setupManagerForTokenPermissions() (*Manager, *testCollect
|
|||
WithTokenManager(tm),
|
||||
}
|
||||
|
||||
m, err := NewManager(key, db, nil, nil, nil, nil, nil, options...)
|
||||
m, err := NewManager(key, db, nil, nil, nil, nil, &TimeSourceStub{}, nil, options...)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NoError(m.Start())
|
||||
|
||||
|
|
|
@ -23,8 +23,9 @@ import (
|
|||
)
|
||||
|
||||
type Persistence struct {
|
||||
db *sql.DB
|
||||
logger *zap.Logger
|
||||
db *sql.DB
|
||||
logger *zap.Logger
|
||||
timesource common.TimeSource
|
||||
}
|
||||
|
||||
var ErrOldRequestToJoin = errors.New("old request to join")
|
||||
|
@ -148,7 +149,7 @@ func (p *Persistence) queryCommunities(memberIdentity *ecdsa.PublicKey, query st
|
|||
return nil, err
|
||||
}
|
||||
|
||||
org, err := unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, spectated, verified, muted, muteTill.Time, uint64(requestedToJoinAt.Int64), eventsBytes, eventsDescriptionBytes, p.logger)
|
||||
org, err := p.unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, spectated, verified, muted, muteTill.Time, uint64(requestedToJoinAt.Int64), eventsBytes, eventsDescriptionBytes, p.logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -207,7 +208,7 @@ func (p *Persistence) rowsToCommunities(memberIdentity *ecdsa.PublicKey, rows *s
|
|||
return nil, err
|
||||
}
|
||||
|
||||
comm, err = unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, spectated, verified, muted, muteTill.Time, uint64(rtjClock.Int64), eventsBytes, eventsDescriptionBytes, p.logger)
|
||||
comm, err = p.unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, spectated, verified, muted, muteTill.Time, uint64(rtjClock.Int64), eventsBytes, eventsDescriptionBytes, p.logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -280,10 +281,10 @@ func (p *Persistence) GetByID(memberIdentity *ecdsa.PublicKey, id []byte) (*Comm
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, spectated, verified, muted, muteTill.Time, uint64(requestedToJoinAt.Int64), eventsBytes, eventsDescriptionBytes, p.logger)
|
||||
return p.unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, spectated, verified, muted, muteTill.Time, uint64(requestedToJoinAt.Int64), eventsBytes, eventsDescriptionBytes, p.logger)
|
||||
}
|
||||
|
||||
func unmarshalCommunityFromDB(memberIdentity *ecdsa.PublicKey, publicKeyBytes, privateKeyBytes, wrappedCommunity []byte, joined,
|
||||
func (p *Persistence) unmarshalCommunityFromDB(memberIdentity *ecdsa.PublicKey, publicKeyBytes, privateKeyBytes, wrappedCommunity []byte, joined,
|
||||
spectated, verified, muted bool, muteTill time.Time, requestedToJoinAt uint64, eventsBytes []byte,
|
||||
eventsDescriptionBytes []byte, logger *zap.Logger) (*Community, error) {
|
||||
|
||||
|
@ -327,7 +328,7 @@ func unmarshalCommunityFromDB(memberIdentity *ecdsa.PublicKey, publicKeyBytes, p
|
|||
Spectated: spectated,
|
||||
EventsData: eventsData,
|
||||
}
|
||||
community, err := New(config)
|
||||
community, err := New(config, p.timesource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ func (s *PersistenceSuite) SetupTest() {
|
|||
err = sqlite.Migrate(db)
|
||||
s.NoError(err, "protocol migrate")
|
||||
|
||||
s.db = &Persistence{db: db}
|
||||
s.db = &Persistence{db: db, timesource: &TimeSourceStub{}}
|
||||
}
|
||||
|
||||
func (s *PersistenceSuite) TestSaveCommunity() {
|
||||
|
@ -259,7 +259,7 @@ func (s *PersistenceSuite) makeNewCommunity(identity *ecdsa.PrivateKey) *Communi
|
|||
MemberIdentity: &identity.PublicKey,
|
||||
PrivateKey: comPrivKey,
|
||||
ID: &comPrivKey.PublicKey,
|
||||
})
|
||||
}, &TimeSourceStub{})
|
||||
s.NoError(err, "New shouldn't give any error")
|
||||
|
||||
md, err := com.MarshaledDescription()
|
||||
|
|
|
@ -2635,7 +2635,7 @@ func (s *MessengerCommunitiesSuite) TestSyncCommunity_Leave() {
|
|||
|
||||
// Check that the joined community has the correct values
|
||||
s.Equal(community.ID(), aCom.ID())
|
||||
s.Equal(uint64(0x2), aCom.Clock())
|
||||
s.Equal(community.Clock(), aCom.Clock())
|
||||
s.Equal(community.PublicKey(), aCom.PublicKey())
|
||||
|
||||
// Check alicesOtherDevice receives the sync join message
|
||||
|
|
|
@ -461,7 +461,7 @@ func NewMessenger(
|
|||
managerOptions = append(managerOptions, communities.WithCommunityTokensService(c.communityTokensService))
|
||||
}
|
||||
|
||||
communitiesManager, err := communities.NewManager(identity, database, encryptionProtocol, logger, ensVerifier, transp, c.torrentConfig, managerOptions...)
|
||||
communitiesManager, err := communities.NewManager(identity, database, encryptionProtocol, logger, ensVerifier, transp, transp, c.torrentConfig, managerOptions...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue