Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion architecture.mmd
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,14 @@ graph TB
AccessRules["Access Rules Engine<br/>spam / traffic / geo filters"]
end

%% Captcha request records (sessions, powcaptchas, imagecaptchas a.k.a.
%% commitments) each carry a `requestType` discriminator
%% (session / powcaptcha / imagecaptcha / puzzlecaptcha) so every record
%% is self-describing ahead of consolidating them into one `requests`
%% collection. Phase 1: discriminator added; physical collections are
%% unchanged. "UserCommitment" record/type renamed to "ImageCaptcha".
subgraph Storage["Storage"]
MongoDB[(MongoDB<br/>captchas, commitments,<br/>clients, sessions)]
MongoDB[(MongoDB<br/>captchas, imagecaptchas,<br/>powcaptchas, clients, sessions<br/>+ requestType discriminator)]
Redis[(Redis<br/>session cache,<br/>write queue)]
Dataset[(Dataset<br/>labelled + unlabelled<br/>captcha images)]
end
Expand Down
14 changes: 7 additions & 7 deletions packages/database/src/databases/captcha.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ import { type Logger, getLogger } from "@prosopo/logger";
import {
type CaptchaProperties,
type ICaptchaDatabase,
type ImageCaptchaRecord,
type PoWCaptchaRecord,
StoredImageCaptchaRecordSchema,
StoredPoWCaptchaRecordSchema,
type StoredSession,
StoredSessionRecordSchema,
StoredUserCommitmentRecordSchema,
type Tables,
type UserCommitmentRecord,
} from "@prosopo/types-database";
import type { RootFilterQuery } from "mongoose";
import { MongoDatabase } from "../base/index.js";
Expand Down Expand Up @@ -51,7 +51,7 @@ const CAPTCHA_TABLES = [
{
collectionName: TableNames.commitment,
modelName: "UserCommitment",
schema: StoredUserCommitmentRecordSchema,
schema: StoredImageCaptchaRecordSchema,
},
];

Expand Down Expand Up @@ -126,7 +126,7 @@ export class CaptchaDatabase extends MongoDatabase implements ICaptchaDatabase {

async saveCaptchas(
sessionEvents: StoredSession[],
imageCaptchaEvents: UserCommitmentRecord[],
imageCaptchaEvents: ImageCaptchaRecord[],
powCaptchaEvents: PoWCaptchaRecord[],
) {
await this.connect();
Expand Down Expand Up @@ -206,7 +206,7 @@ export class CaptchaDatabase extends MongoDatabase implements ICaptchaDatabase {
filter: RootFilterQuery<CaptchaProperties> = {},
limit = 100,
): Promise<{
userCommitmentRecords: UserCommitmentRecord[];
imageCaptchaRecords: ImageCaptchaRecord[];
powCaptchaRecords: PoWCaptchaRecord[];
}> {
await this.connect();
Expand All @@ -215,15 +215,15 @@ export class CaptchaDatabase extends MongoDatabase implements ICaptchaDatabase {
const commitmentResults = await this.tables.commitment
.find(filter)
.limit(limit)
.lean<UserCommitmentRecord[]>();
.lean<ImageCaptchaRecord[]>();

const powCaptchaResults = await this.tables.powcaptcha
.find(filter)
.limit(limit)
.lean<PoWCaptchaRecord[]>();

return {
userCommitmentRecords: commitmentResults,
imageCaptchaRecords: commitmentResults,
powCaptchaRecords: powCaptchaResults,
};
} catch (error) {
Expand Down
6 changes: 3 additions & 3 deletions packages/database/src/databases/centralDbStreamer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

import { type Logger, getLogger } from "@prosopo/logger";
import type {
ImageCaptchaRecord,
PoWCaptchaRecord,
StoredSession,
UserCommitmentRecord,
} from "@prosopo/types-database";
import { CaptchaDatabase } from "./captcha.js";

Expand Down Expand Up @@ -154,7 +154,7 @@ export class CentralDbStreamer {
* Fire-and-forget: errors are logged, never thrown.
*/
streamImageRecord(
record: UserCommitmentRecord,
record: ImageCaptchaRecord,
markStored?: MarkStoredCallback,
): void {
const timestamp = this.getRecordTimestamp(record);
Expand All @@ -180,7 +180,7 @@ export class CentralDbStreamer {
* Stream an image captcha update by fetching the full record first.
*/
streamImageUpdate(
getFullRecord: () => Promise<UserCommitmentRecord | null>,
getFullRecord: () => Promise<ImageCaptchaRecord | null>,
markStored?: MarkStoredCallback,
): void {
getFullRecord()
Expand Down
49 changes: 30 additions & 19 deletions packages/database/src/databases/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
type PoWChallengeId,
type PuzzleCaptchaStored,
type RequestHeaders,
RequestType,
type ResultReason,
type ScheduledTaskNames,
type ScheduledTaskResult,
Expand Down Expand Up @@ -124,6 +125,12 @@ const PROVIDER_TABLES = [
},
{
collectionName: TableNames.powcaptcha,
// NOTE: model name is "PowCaptcha" (lowercase w). The private-DB reader
// (@prosopo/database-private PoWCaptchaData) registers "PoWCaptcha"
// (capital W); both pluralize to the same physical `powcaptchas`
// collection, so they coexist. This inconsistency is resolved when the
// records are merged into the unified `requests` collection — do not
// "fix" either string in isolation, as changing it moves the collection.
modelName: "PowCaptcha",
schema: PoWCaptchaRecordSchema,
},
Expand Down Expand Up @@ -669,7 +676,7 @@ export class ProviderDatabase
/**
* @description Store a Dapp User's captcha solution commitment
*/
async storeUserImageCaptchaSolution(
async storeImageCaptchaSolution(
captchas: CaptchaSolution[],
commit: UserCommitment,
): Promise<void> {
Expand Down Expand Up @@ -1239,7 +1246,7 @@ export class ProviderDatabase

/** @description Get serverChecked Dapp User image captcha commitments from the commitments table
*/
async getCheckedDappUserCommitments(): Promise<UserCommitmentRecord[]> {
async getCheckedImageCaptchas(): Promise<UserCommitmentRecord[]> {
const filter: {
[key in keyof Pick<UserCommitmentRecord, "serverChecked">]: boolean;
} = { [StoredStatusNames.serverChecked]: true };
Expand All @@ -1254,13 +1261,13 @@ export class ProviderDatabase
*
* Served by the `pendingStage_partial` index. Records have
* `pendingStage: true` set on insert and on every mutation (see
* `updateDappUserCommitment`, `markDappUserCommitmentsChecked`,
* `approveDappUserCommitment`, `disapproveDappUserCommitment`,
* `storePendingImageCommitment`). `markDappUserCommitmentsStored` clears
* `updateImageCaptcha`, `markImageCaptchasChecked`,
* `approveImageCaptcha`, `disapproveImageCaptcha`,
* `storePendingImageCommitment`). `markImageCaptchasStored` clears
* the flag after a successful stage, guarded by `lastUpdatedTimestamp`
* so an in-flight update isn't lost.
*/
async getUnstoredDappUserCommitments(
async getUnstoredImageCaptchas(
limit = 1000,
skip = 0,
): Promise<UserCommitmentRecord[]> {
Expand All @@ -1281,7 +1288,7 @@ export class ProviderDatabase
* fetch and mark-stored — those records will leave pendingStage set so
* the next sweep picks them up.
*/
async markDappUserCommitmentsStored(
async markImageCaptchasStored(
commitmentIds: Hash[],
asOfTimestamp: Date = new Date(),
): Promise<void> {
Expand All @@ -1300,7 +1307,7 @@ export class ProviderDatabase

/** @description Mark a list of captcha commits as checked
*/
async markDappUserCommitmentsChecked(commitmentIds: Hash[]): Promise<void> {
async markImageCaptchasChecked(commitmentIds: Hash[]): Promise<void> {
const updateDoc: Pick<
StoredCaptcha,
"serverChecked" | "lastUpdatedTimestamp" | "pendingStage"
Expand All @@ -1319,7 +1326,7 @@ export class ProviderDatabase

/** @description Update an image captcha commitment
*/
async updateDappUserCommitment(
async updateImageCaptcha(
commitmentId: Hash,
updates: Partial<UserCommitment>,
) {
Expand All @@ -1342,7 +1349,7 @@ export class ProviderDatabase
skip = 0,
): Promise<PoWCaptchaRecord[]> {
// Served by the `pendingStage_partial` index — see
// `getUnstoredDappUserCommitments` for the lifecycle of the flag.
// `getUnstoredImageCaptchas` for the lifecycle of the flag.
const docs = await this.tables?.powcaptcha
.find({ pendingStage: true })
.sort({ _id: 1 })
Expand All @@ -1355,7 +1362,7 @@ export class ProviderDatabase
/** @description Mark a list of PoW captcha commits as stored.
*
* `asOfTimestamp` defaults to "now" but the sweep should pass the time
* at which it fetched the batch. See markDappUserCommitmentsStored for
* at which it fetched the batch. See markImageCaptchasStored for
* the guard rationale.
*/
async markDappUserPoWCommitmentsStored(
Expand Down Expand Up @@ -1623,7 +1630,7 @@ export class ProviderDatabase
* @description Get session records that have not been stored yet.
*
* Served by the `pendingStage_partial` index — see
* `getUnstoredDappUserCommitments` for the lifecycle of the flag.
* `getUnstoredImageCaptchas` for the lifecycle of the flag.
* `checkAndRemoveSession` also flips the flag so consumed sessions
* propagate to the central DB via the next sweep.
* @param limit
Expand All @@ -1645,7 +1652,7 @@ export class ProviderDatabase
/** Mark a list of session records as stored.
*
* `asOfTimestamp` defaults to "now" but the sweep should pass the time
* at which it fetched the batch. See markDappUserCommitmentsStored for
* at which it fetched the batch. See markImageCaptchasStored for
* the guard rationale.
*/
async markSessionRecordsStored(
Expand Down Expand Up @@ -1688,6 +1695,10 @@ export class ProviderDatabase
});
}
const pendingRecord = {
// Stamp the discriminator explicitly: this placeholder is built by
// hand and written via `$set`, so we don't rely on the schema
// default applying on upsert-insert.
requestType: RequestType.imagecaptcha,
userAccount,
pending: true,
salt,
Expand All @@ -1701,7 +1712,7 @@ export class ProviderDatabase
// Deliberately NOT setting pendingStage here. Placeholder
// records have id: "" until the user submits a solution; if we
// flag them, the sweep picks them up via the partial index but
// then `markDappUserCommitmentsStored` runs
// then `markImageCaptchasStored` runs
// `{ id: { $in: ["", ...] } }` which collapses to a single ""
// bound and scans every empty-id row via the `id_-1` index —
// turning a cheap sweep into a fresh cache evictor. The real
Expand Down Expand Up @@ -1943,7 +1954,7 @@ export class ProviderDatabase
* @description Get dapp user commitment by user account
* @param commitmentId
*/
async getDappUserCommitmentById(
async getImageCaptchaById(
commitmentId: string,
): Promise<UserCommitmentRecord | undefined> {
const filter: Pick<UserCommitmentRecord, "id"> = { id: commitmentId };
Expand Down Expand Up @@ -1972,7 +1983,7 @@ export class ProviderDatabase
* @param {string} userAccount
* @param {string} dappAccount
*/
async getDappUserCommitmentByAccount(
async getImageCaptchaByAccount(
userAccount: string,
dappAccount: string,
): Promise<UserCommitmentRecord[]> {
Expand All @@ -1999,7 +2010,7 @@ export class ProviderDatabase
* @param {string[]} commitmentId
* @param coords
*/
async approveDappUserCommitment(
async approveImageCaptcha(
commitmentId: string,
coords?: [number, number][][],
): Promise<void> {
Expand Down Expand Up @@ -2047,7 +2058,7 @@ export class ProviderDatabase
* @param coords
* @param reason
*/
async disapproveDappUserCommitment(
async disapproveImageCaptcha(
commitmentId: string,
reason?: ResultReason,
coords?: [number, number][][],
Expand Down Expand Up @@ -2114,7 +2125,7 @@ export class ProviderDatabase
* @description Flag dapp users' commitments as used by calculated solution
* @param {string[]} commitmentIds
*/
async flagProcessedDappUserCommitments(commitmentIds: Hash[]): Promise<void> {
async flagProcessedImageCaptchas(commitmentIds: Hash[]): Promise<void> {
try {
const distinctCommitmentIds = [...new Set(commitmentIds)];
await this.tables?.commitment
Expand Down
Loading
Loading