chore: use lamport timestamp for communities

closes: status-im/status-desktop#11961
This commit is contained in:
Patryk Osmaczko 2023-09-28 17:37:03 +02:00 committed by osmaczko
parent c88b6e53af
commit 9d374bcadc
9 changed files with 95 additions and 57 deletions

View File

@ -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,
}
}

View File

@ -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{},

View File

@ -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
}

View File

@ -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
}

View File

@ -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())

View File

@ -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
}

View File

@ -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()

View File

@ -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

View File

@ -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
}