Save group send endorsements

This commit is contained in:
Jamie Kyle 2024-05-20 11:15:39 -07:00 committed by GitHub
parent dea641bae4
commit 4253bed0bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 583 additions and 91 deletions

View file

@ -31,6 +31,7 @@ import type {
} from '../types/CallDisposition';
import type { CallLinkStateType, CallLinkType } from '../types/CallLink';
import type { AttachmentDownloadJobType } from '../types/AttachmentDownload';
import type { GroupSendEndorsementsData } from '../types/GroupSendEndorsements';
export type AdjacentMessagesByConversationOptionsType = Readonly<{
conversationId: string;
@ -526,6 +527,14 @@ export type DataInterface = {
serviceId: ServiceIdString
) => Promise<Array<ConversationType>>;
replaceAllEndorsementsForGroup: (
data: GroupSendEndorsementsData
) => Promise<void>;
deleteAllEndorsementsForGroup: (groupId: string) => Promise<void>;
getGroupSendCombinedEndorsementExpiration: (
groupId: string
) => Promise<number | null>;
getMessageCount: (conversationId?: string) => Promise<number>;
getStoryCount: (conversationId: string) => Promise<number>;
saveMessage: (

View file

@ -174,6 +174,11 @@ import {
updateCallLinkAdminKeyByRoomId,
updateCallLinkState,
} from './server/callLinks';
import {
replaceAllEndorsementsForGroup,
deleteAllEndorsementsForGroup,
getGroupSendCombinedEndorsementExpiration,
} from './server/groupEndorsements';
import { CallMode } from '../types/Calling';
import {
attachmentDownloadJobSchema,
@ -286,6 +291,10 @@ const dataInterface: ServerInterface = {
getAllConversationIds,
getAllGroupsInvolvingServiceId,
replaceAllEndorsementsForGroup,
deleteAllEndorsementsForGroup,
getGroupSendCombinedEndorsementExpiration,
searchMessages,
getMessageCount,
@ -4530,9 +4539,9 @@ function getAttachmentDownloadJob(
SELECT * FROM attachment_downloads
WHERE
messageId = ${job.messageId}
AND
AND
attachmentType = ${job.attachmentType}
AND
AND
digest = ${job.digest};
`;
@ -4570,7 +4579,7 @@ async function getNextAttachmentDownloadJobs({
})
AND
messageId IN (${sqlJoin(prioritizeMessageIds)})
-- for priority messages, let's load them oldest first; this helps, e.g. for stories where we
-- for priority messages, let's load them oldest first; this helps, e.g. for stories where we
-- want the oldest one first
ORDER BY receivedAt ASC
LIMIT ${limit}
@ -4681,11 +4690,11 @@ function removeAttachmentDownloadJobSync(
): void {
const [query, params] = sql`
DELETE FROM attachment_downloads
WHERE
WHERE
messageId = ${job.messageId}
AND
attachmentType = ${job.attachmentType}
AND
attachmentType = ${job.attachmentType}
AND
digest = ${job.digest};
`;
@ -6003,6 +6012,8 @@ async function removeAll(): Promise<void> {
DELETE FROM conversations;
DELETE FROM emojis;
DELETE FROM groupCallRingCancellations;
DELETE FROM groupSendCombinedEndorsement;
DELETE FROM groupSendMemberEndorsement;
DELETE FROM identityKeys;
DELETE FROM items;
DELETE FROM jobs;
@ -6053,6 +6064,8 @@ async function removeAllConfiguration(): Promise<void> {
db.transaction(() => {
db.exec(
`
DELETE FROM groupSendCombinedEndorsement;
DELETE FROM groupSendMemberEndorsement;
DELETE FROM identityKeys;
DELETE FROM jobs;
DELETE FROM kyberPreKeys;

View file

@ -0,0 +1,50 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { Database } from '@signalapp/better-sqlite3';
import type { LoggerType } from '../../types/Logging';
import { sql } from '../util';
export const version = 1050;
export function updateToSchemaVersion1050(
currentVersion: number,
db: Database,
logger: LoggerType
): void {
if (currentVersion >= 1050) {
return;
}
db.transaction(() => {
const [createTables] = sql`
DROP TABLE IF EXISTS groupSendCombinedEndorsement;
DROP TABLE IF EXISTS groupSendMemberEndorsement;
-- From GroupSendEndorsementsResponse->ReceivedEndorsements in libsignal
-- this is the combined endorsement for all group members
CREATE TABLE groupSendCombinedEndorsement (
groupId TEXT NOT NULL PRIMARY KEY, -- Only one endorsement per group
expiration INTEGER NOT NULL, -- Unix timestamp in seconds
endorsement BLOB NOT NULL
) STRICT;
-- From GroupSendEndorsementsResponse->ReceivedEndorsements in libsignal
-- these are the individual endorsements for each group member
CREATE TABLE groupSendMemberEndorsement (
groupId TEXT NOT NULL,
memberAci TEXT NOT NULL,
expiration INTEGER NOT NULL, -- Unix timestamp in seconds
endorsement BLOB NOT NULL,
PRIMARY KEY (groupId, memberAci) -- Only one endorsement per group member
) STRICT;
`;
db.exec(createTables);
db.pragma('user_version = 1050');
})();
logger.info('updateToSchemaVersion1050: success!');
}

View file

@ -79,10 +79,11 @@ import { updateToSchemaVersion1000 } from './1000-mark-unread-call-history-messa
import { updateToSchemaVersion1010 } from './1010-call-links-table';
import { updateToSchemaVersion1020 } from './1020-self-merges';
import { updateToSchemaVersion1030 } from './1030-unblock-event';
import { updateToSchemaVersion1040 } from './1040-undownloaded-backed-up-media';
import {
updateToSchemaVersion1040,
updateToSchemaVersion1050,
version as MAX_VERSION,
} from './1040-undownloaded-backed-up-media';
} from './1050-group-send-endorsements';
function updateToSchemaVersion1(
currentVersion: number,
@ -2029,6 +2030,7 @@ export const SCHEMA_VERSIONS = [
updateToSchemaVersion1020,
updateToSchemaVersion1030,
updateToSchemaVersion1040,
updateToSchemaVersion1050,
];
export class DBVersionFromFutureError extends Error {

View file

@ -0,0 +1,87 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { Database } from '@signalapp/better-sqlite3';
import type {
GroupSendCombinedEndorsementRecord,
GroupSendEndorsementsData,
GroupSendMemberEndorsementRecord,
} from '../../types/GroupSendEndorsements';
import { groupSendEndorsementExpirationSchema } from '../../types/GroupSendEndorsements';
import { getReadonlyInstance, getWritableInstance, prepare } from '../Server';
import { sql } from '../util';
/**
* We don't need to store more than one endorsement per group or per member.
*/
export async function replaceAllEndorsementsForGroup(
data: GroupSendEndorsementsData
): Promise<void> {
const db = await getWritableInstance();
db.transaction(() => {
const { combinedEndorsement, memberEndorsements } = data;
_replaceCombinedEndorsement(db, combinedEndorsement);
_replaceMemberEndorsements(db, memberEndorsements);
})();
}
function _replaceCombinedEndorsement(
db: Database,
combinedEndorsement: GroupSendCombinedEndorsementRecord
): void {
const { groupId, expiration, endorsement } = combinedEndorsement;
const [insertCombined, insertCombinedParams] = sql`
INSERT OR REPLACE INTO groupSendCombinedEndorsement
(groupId, expiration, endorsement)
VALUES (${groupId}, ${expiration}, ${endorsement});
`;
prepare<Array<unknown>>(db, insertCombined).run(insertCombinedParams);
}
function _replaceMemberEndorsements(
db: Database,
memberEndorsements: ReadonlyArray<GroupSendMemberEndorsementRecord>
) {
for (const memberEndorsement of memberEndorsements) {
const { groupId, memberAci, expiration, endorsement } = memberEndorsement;
const [replaceMember, replaceMemberParams] = sql`
INSERT OR REPLACE INTO groupSendMemberEndorsement
(groupId, memberAci, expiration, endorsement)
VALUES (${groupId}, ${memberAci}, ${expiration}, ${endorsement});
`;
prepare<Array<unknown>>(db, replaceMember).run(replaceMemberParams);
}
}
export async function deleteAllEndorsementsForGroup(
groupId: string
): Promise<void> {
const db = await getWritableInstance();
db.transaction(() => {
const [deleteCombined, deleteCombinedParams] = sql`
DELETE FROM groupSendCombinedEndorsement
WHERE groupId = ${groupId};
`;
const [deleteMembers, deleteMembersParams] = sql`
DELETE FROM groupSendMemberEndorsement
WHERE groupId = ${groupId};
`;
prepare<Array<unknown>>(db, deleteCombined).run(deleteCombinedParams);
prepare<Array<unknown>>(db, deleteMembers).run(deleteMembersParams);
})();
}
export async function getGroupSendCombinedEndorsementExpiration(
groupId: string
): Promise<number | null> {
const db = getReadonlyInstance();
const [selectGroup, selectGroupParams] = sql`
SELECT expiration FROM groupSendCombinedEndorsement
WHERE groupId = ${groupId};
`;
const value = prepare(db, selectGroup).pluck().get(selectGroupParams);
if (value == null) {
return null;
}
return groupSendEndorsementExpirationSchema.parse(value);
}

View file

@ -36,7 +36,12 @@ export function jsonToObject<T>(json: string): T {
return JSON.parse(json);
}
export type QueryTemplateParam = string | number | null | undefined;
export type QueryTemplateParam =
| Uint8Array
| string
| number
| null
| undefined;
export type QueryFragmentValue = QueryFragment | QueryTemplateParam;
export type QueryFragment = [
@ -148,7 +153,7 @@ export type QueryTemplate = [string, ReadonlyArray<QueryTemplateParam>];
*/
export function sql(
strings: TemplateStringsArray,
...values: ReadonlyArray<QueryFragment | QueryTemplateParam>
...values: Array<QueryFragment | QueryTemplateParam>
): QueryTemplate {
const [{ fragment }, params] = sqlFragment(strings, ...values);
return [fragment, params];