diff --git a/protocol/communities/community.go b/protocol/communities/community.go index ba6cd6673..8ddb2dc01 100644 --- a/protocol/communities/community.go +++ b/protocol/communities/community.go @@ -40,7 +40,7 @@ type Config struct { RequestsToJoin []*RequestToJoin MemberIdentity *ecdsa.PublicKey SyncedAt uint64 - RekeyedAt *time.Time + RekeyedAt time.Time EventsData *EventsData } diff --git a/protocol/communities/persistence.go b/protocol/communities/persistence.go index 9b9b03b86..7aeac6bb6 100644 --- a/protocol/communities/persistence.go +++ b/protocol/communities/persistence.go @@ -31,7 +31,7 @@ var ErrOldRequestToLeave = errors.New("old request to leave") const OR = " OR " const communitiesBaseQuery = ` - SELECT c.id, c.private_key, c.description, c.joined, c.spectated, c.verified, c.muted, c.muted_till, c.rekeyed, r.clock, ae.raw_events, ae.raw_description + SELECT c.id, c.private_key, c.description, c.joined, c.spectated, c.verified, c.muted, c.muted_till, c.rekeyed_at, r.clock, ae.raw_events, ae.raw_description 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` @@ -143,12 +143,12 @@ func (p *Persistence) queryCommunities(memberIdentity *ecdsa.PublicKey, query st // Community events specific fields var eventsBytes, eventsDescriptionBytes []byte - err := rows.Scan(&publicKeyBytes, &privateKeyBytes, &descriptionBytes, &joined, &spectated, &verified, &muted, &muteTill, &requestedToJoinAt, &rekeyedAt, &eventsBytes, &eventsDescriptionBytes) + err := rows.Scan(&publicKeyBytes, &privateKeyBytes, &descriptionBytes, &joined, &spectated, &verified, &muted, &muteTill, &rekeyedAt, &requestedToJoinAt, &eventsBytes, &eventsDescriptionBytes) if err != nil { return nil, err } - org, err := unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, spectated, verified, muted, muteTill.Time, &rekeyedAt.Time, uint64(requestedToJoinAt.Int64), eventsBytes, eventsDescriptionBytes, p.logger) + org, err := unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, spectated, verified, muted, muteTill.Time, rekeyedAt.Time, uint64(requestedToJoinAt.Int64), eventsBytes, eventsDescriptionBytes, p.logger) if err != nil { return nil, err } @@ -190,8 +190,7 @@ func (p *Persistence) rowsToCommunities(memberIdentity *ecdsa.PublicKey, rows *s // Community specific fields var publicKeyBytes, privateKeyBytes, descriptionBytes []byte var joined, spectated, verified, muted bool - var muteTill sql.NullTime - var rekeyedAt sql.NullTime + var muteTill, rekeyedAt sql.NullTime // Request to join specific fields var rtjID, rtjCommunityID []byte @@ -202,13 +201,13 @@ func (p *Persistence) rowsToCommunities(memberIdentity *ecdsa.PublicKey, rows *s var eventsBytes, eventsDescriptionBytes []byte err = rows.Scan( - &publicKeyBytes, &privateKeyBytes, &descriptionBytes, &joined, &spectated, &verified, &muted, &muteTill, + &publicKeyBytes, &privateKeyBytes, &descriptionBytes, &joined, &spectated, &verified, &muted, &muteTill, &rekeyedAt, &rtjID, &rtjPublicKey, &rtjClock, &rtjENSName, &rtjChatID, &rtjCommunityID, &rtjState, &eventsBytes, &eventsDescriptionBytes) if err != nil { return nil, err } - comm, err = unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, spectated, verified, muted, muteTill.Time, &rekeyedAt.Time, uint64(rtjClock.Int64), eventsBytes, eventsDescriptionBytes, p.logger) + comm, err = unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, spectated, verified, muted, muteTill.Time, rekeyedAt.Time, uint64(rtjClock.Int64), eventsBytes, eventsDescriptionBytes, p.logger) if err != nil { return nil, err } @@ -225,7 +224,7 @@ func (p *Persistence) rowsToCommunities(memberIdentity *ecdsa.PublicKey, rows *s func (p *Persistence) JoinedAndPendingCommunitiesWithRequests(memberIdentity *ecdsa.PublicKey) (comms []*Community, err error) { query := `SELECT -c.id, c.private_key, c.description, c.joined, c.spectated, c.verified, c.muted, c.muted_till, +c.id, c.private_key, c.description, c.joined, c.spectated, c.verified, c.muted, c.muted_till, c.rekeyed_at, r.id, r.public_key, r.clock, r.ens_name, r.chat_id, r.community_id, r.state, ae.raw_events, ae.raw_description FROM communities_communities c LEFT JOIN communities_requests_to_join r ON c.id = r.community_id AND r.public_key = ? @@ -242,7 +241,7 @@ WHERE c.Joined OR r.state = ?` func (p *Persistence) DeletedCommunities(memberIdentity *ecdsa.PublicKey) (comms []*Community, err error) { query := `SELECT -c.id, c.private_key, c.description, c.joined, c.spectated, c.verified, c.muted, c.muted_till, +c.id, c.private_key, c.description, c.joined, c.spectated, c.verified, c.muted, c.muted_till, c.rekeyed_at, r.id, r.public_key, r.clock, r.ens_name, r.chat_id, r.community_id, r.state, ae.raw_events, ae.raw_description FROM communities_communities c LEFT JOIN communities_requests_to_join r ON c.id = r.community_id AND r.public_key = ? @@ -271,23 +270,23 @@ func (p *Persistence) GetByID(memberIdentity *ecdsa.PublicKey, id []byte) (*Comm var muteTill sql.NullTime var rekeyed sql.NullTime var requestedToJoinAt sql.NullInt64 + var rekeyedAt sql.NullTime // Community events specific fields var eventsBytes, eventsDescriptionBytes []byte - err := p.db.QueryRow(communitiesBaseQuery+` WHERE c.id = ?`, common.PubkeyToHex(memberIdentity), id).Scan(&publicKeyBytes, &privateKeyBytes, &descriptionBytes, &joined, &spectated, &verified, &muted, &muteTill, &rekeyed, &requestedToJoinAt, &eventsBytes, &eventsDescriptionBytes) - + err := p.db.QueryRow(communitiesBaseQuery+` WHERE c.id = ?`, common.PubkeyToHex(memberIdentity), id).Scan(&publicKeyBytes, &privateKeyBytes, &descriptionBytes, &joined, &spectated, &verified, &muted, &muteTill, &rekeyedAt, &requestedToJoinAt, &eventsBytes, &eventsDescriptionBytes) if err == sql.ErrNoRows { return nil, nil } else if err != nil { return nil, err } - return unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, spectated, verified, muted, muteTill.Time, &rekeyed.Time, uint64(requestedToJoinAt.Int64), eventsBytes, eventsDescriptionBytes, p.logger) + return unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, spectated, verified, muted, muteTill.Time, rekeyed.Time, uint64(requestedToJoinAt.Int64), eventsBytes, eventsDescriptionBytes, p.logger) } func unmarshalCommunityFromDB(memberIdentity *ecdsa.PublicKey, publicKeyBytes, privateKeyBytes, descriptionBytes []byte, joined, - spectated, verified, muted bool, muteTill time.Time, rekeyedAt *time.Time, requestedToJoinAt uint64, eventsBytes []byte, + spectated, verified, muted bool, muteTill time.Time, rekeyedAt time.Time, requestedToJoinAt uint64, eventsBytes []byte, eventsDescriptionBytes []byte, logger *zap.Logger) (*Community, error) { var privateKey *ecdsa.PrivateKey @@ -1273,11 +1272,17 @@ func decodeEventsData(eventsBytes []byte, eventsDescriptionBytes []byte) (*Event }, nil } -func (p *Persistence) GetRekeyedAtClock(id []byte, time *time.Time) error { - _, err := p.db.Exec(`UPDATE communities_communities SET rekeyed_at = ? WHERE id = ? AND rekeyed_at < ?`, time, id, time) - return err +// GetRekeyedAtClock returns the rekeyed_at time of a given community +func (p *Persistence) GetRekeyedAtClock(id []byte) (*time.Time, error) { + rekeyedAt := time.Time{} + err := p.db.QueryRow(`SELECT rekeyed_at FROM communities_communities WHERE id = ?`, id).Scan(&rekeyedAt) + if err != nil { + return nil, err + } + return &rekeyedAt, nil } +// SetRekeyedAtClock sets the rekeyed_at time value of a given community func (p *Persistence) SetRekeyedAtClock(id []byte, time *time.Time) error { _, err := p.db.Exec(`UPDATE communities_communities SET rekeyed_at = ? WHERE id = ? AND rekeyed_at < ?`, time, id, time) return err diff --git a/protocol/communities/persistence_test.go b/protocol/communities/persistence_test.go index 18a47c2dd..1a83257f2 100644 --- a/protocol/communities/persistence_test.go +++ b/protocol/communities/persistence_test.go @@ -471,3 +471,42 @@ func (s *PersistenceSuite) TestSaveCheckChannelPermissionResponse() { s.Require().Equal(responses[chatID].ViewAndPostPermissions.Permissions["one"].Criteria, []bool{true, true, true, true}) s.Require().Equal(responses[chatID].ViewAndPostPermissions.Permissions["two"].Criteria, []bool{false}) } + +func (s *PersistenceSuite) TestGetRekeyedAtClock() { + key, err := crypto.GenerateKey() + s.Require().NoError(err) + + // there is one community inserted by default + communities, err := s.db.AllCommunities(&key.PublicKey) + s.Require().NoError(err) + s.Require().Len(communities, 1) + + community := Community{ + config: &Config{ + PrivateKey: key, + ID: &key.PublicKey, + Joined: true, + Spectated: true, + Verified: true, + CommunityDescription: &protobuf.CommunityDescription{}, + }, + } + s.Require().NoError(s.db.SaveCommunity(&community)) + + communities, err = s.db.AllCommunities(&key.PublicKey) + s.Require().NoError(err) + s.Require().Len(communities, 2) + s.Equal(types.HexBytes(crypto.CompressPubkey(&key.PublicKey)), communities[1].ID()) + s.True(communities[1].Joined()) + s.True(communities[1].Spectated()) + s.True(communities[1].Verified()) + s.Zero(communities[1].config.RekeyedAt.Unix()) + + now := time.Now() + err = s.db.SetRekeyedAtClock(communities[1].ID(), &now) + s.NoError(err) + + then, err := s.db.GetRekeyedAtClock(communities[0].ID()) + s.NoError(err) + now.Equal(*then) +} diff --git a/protocol/communities/persistence_test_helpers.go b/protocol/communities/persistence_test_helpers.go index 2288eee46..4ec115c3a 100644 --- a/protocol/communities/persistence_test_helpers.go +++ b/protocol/communities/persistence_test_helpers.go @@ -2,6 +2,7 @@ package communities import ( "database/sql" + "time" "github.com/status-im/status-go/protocol/protobuf" ) @@ -15,9 +16,11 @@ type RawCommunityRow struct { Verified bool SyncedAt uint64 Muted bool + RekeyedAt time.Time } func fromSyncCommunityProtobuf(syncCommProto *protobuf.SyncCommunity) RawCommunityRow { + // TODO handle rekeyedAt value return RawCommunityRow{ ID: syncCommProto.Id, Description: syncCommProto.Description, @@ -31,9 +34,7 @@ func fromSyncCommunityProtobuf(syncCommProto *protobuf.SyncCommunity) RawCommuni func (p *Persistence) scanRowToStruct(rowScan func(dest ...interface{}) error) (*RawCommunityRow, error) { rcr := new(RawCommunityRow) - - syncedAt := sql.NullTime{} - muteTill := sql.NullTime{} + var syncedAt, muteTill, rekeyedAt sql.NullTime err := rowScan( &rcr.ID, @@ -44,11 +45,15 @@ func (p *Persistence) scanRowToStruct(rowScan func(dest ...interface{}) error) ( &rcr.Spectated, &rcr.Muted, &muteTill, + &rekeyedAt, &syncedAt, ) if syncedAt.Valid { rcr.SyncedAt = uint64(syncedAt.Time.Unix()) } + if rekeyedAt.Valid { + rcr.RekeyedAt = rekeyedAt.Time + } if err != nil { return nil, err diff --git a/protocol/migrations/sqlite/1683799017_add_rekeyed_at_column_to_communities.up.sql b/protocol/migrations/sqlite/1683799017_add_rekeyed_at_column_to_communities.up.sql new file mode 100644 index 000000000..2e58fe14b --- /dev/null +++ b/protocol/migrations/sqlite/1683799017_add_rekeyed_at_column_to_communities.up.sql @@ -0,0 +1 @@ +ALTER TABLE communities_communities ADD COLUMN rekeyed_at TIMESTAMP DEFAULT 0 NOT NULL;