Skip to content

Commit

Permalink
Do not sequence handle updates in slow path (#1292)
Browse files Browse the repository at this point in the history
* do not sequence handle updates in slow path

* build

* bail early if invite code doesn't exist
  • Loading branch information
devinivy authored Jul 6, 2023
1 parent 3ea892b commit 0d3a555
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 10 deletions.
1 change: 0 additions & 1 deletion .github/workflows/build-and-push-pds-aws.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ on:
push:
branches:
- main
- auth-triage-role
env:
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }}
USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_USERNAME }}
Expand Down
23 changes: 20 additions & 3 deletions packages/pds/src/api/com/atproto/admin/updateAccountHandle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
import * as ident from '@atproto/identifier'
import { Server } from '../../../../lexicon'
import AppContext from '../../../../context'
import { UserAlreadyExistsError } from '../../../../services/account'
import {
HandleSequenceToken,
UserAlreadyExistsError,
} from '../../../../services/account'
import { httpLogger } from '../../../../logger'

export default function (server: Server, ctx: AppContext) {
server.com.atproto.admin.updateAccountHandle({
Expand Down Expand Up @@ -48,17 +52,30 @@ export default function (server: Server, ctx: AppContext) {
throw new InvalidRequestError(`Account not found: ${did}`)
}

await ctx.db.transaction(async (dbTxn) => {
const seqHandleTok = await ctx.db.transaction(async (dbTxn) => {
let tok: HandleSequenceToken
try {
await ctx.services.account(dbTxn).updateHandle(did, handle)
tok = await ctx.services.account(dbTxn).updateHandle(did, handle)
} catch (err) {
if (err instanceof UserAlreadyExistsError) {
throw new InvalidRequestError(`Handle already taken: ${handle}`)
}
throw err
}
await ctx.plcClient.updateHandle(did, ctx.plcRotationKey, handle)
return tok
})

try {
await ctx.db.transaction(async (dbTxn) => {
await ctx.services.account(dbTxn).sequenceHandle(seqHandleTok)
})
} catch (err) {
httpLogger.error(
{ err, did, handle },
'failed to sequence handle update',
)
}
},
})
}
25 changes: 22 additions & 3 deletions packages/pds/src/api/com/atproto/identity/updateHandle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { InvalidRequestError } from '@atproto/xrpc-server'
import * as ident from '@atproto/identifier'
import { Server } from '../../../../lexicon'
import AppContext from '../../../../context'
import { UserAlreadyExistsError } from '../../../../services/account'
import {
HandleSequenceToken,
UserAlreadyExistsError,
} from '../../../../services/account'
import { httpLogger } from '../../../../logger'

export default function (server: Server, ctx: AppContext) {
server.com.atproto.identity.updateHandle({
Expand Down Expand Up @@ -43,17 +47,32 @@ export default function (server: Server, ctx: AppContext) {
}
}

await ctx.db.transaction(async (dbTxn) => {
const seqHandleTok = await ctx.db.transaction(async (dbTxn) => {
let tok: HandleSequenceToken
try {
await ctx.services.account(dbTxn).updateHandle(requester, handle)
tok = await ctx.services
.account(dbTxn)
.updateHandle(requester, handle)
} catch (err) {
if (err instanceof UserAlreadyExistsError) {
throw new InvalidRequestError(`Handle already taken: ${handle}`)
}
throw err
}
await ctx.plcClient.updateHandle(requester, ctx.plcRotationKey, handle)
return tok
})

try {
await ctx.db.transaction(async (dbTxn) => {
await ctx.services.account(dbTxn).sequenceHandle(seqHandleTok)
})
} catch (err) {
httpLogger.error(
{ err, did: requester, handle },
'failed to sequence handle update',
)
}
},
})
}
9 changes: 8 additions & 1 deletion packages/pds/src/api/com/atproto/server/createAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,20 @@ export const ensureCodeIsAvailable = async (
.if(withLock && db.dialect === 'pg', (qb) => qb.forUpdate().skipLocked())
.executeTakeFirst()

if (!invite || invite.disabled) {
throw new InvalidRequestError(
'Provided invite code not available',
'InvalidInviteCode',
)
}

const uses = await db.db
.selectFrom('invite_code_use')
.select(countAll.as('count'))
.where('code', '=', inviteCode)
.executeTakeFirstOrThrow()

if (!invite || invite.disabled || invite.availableUses <= uses.count) {
if (invite.availableUses <= uses.count) {
throw new InvalidRequestError(
'Provided invite code not available',
'InvalidInviteCode',
Expand Down
16 changes: 14 additions & 2 deletions packages/pds/src/services/account/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,12 @@ export class AccountService {
log.info({ handle, email, did }, 'registered user')
}

async updateHandle(did: string, handle: string) {
// @NOTE should always be paired with a sequenceHandle().
// the token output from this method should be passed to sequenceHandle().
async updateHandle(
did: string,
handle: string,
): Promise<HandleSequenceToken> {
const res = await this.db.db
.updateTable('did_handle')
.set({ handle })
Expand All @@ -164,7 +169,12 @@ export class AccountService {
if (res.numUpdatedRows < 1) {
throw new UserAlreadyExistsError()
}
const seqEvt = await sequencer.formatSeqHandleUpdate(did, handle)
return { did, handle }
}

async sequenceHandle(tok: HandleSequenceToken) {
this.db.assertTransaction()
const seqEvt = await sequencer.formatSeqHandleUpdate(tok.did, tok.handle)
await sequencer.sequenceEvt(this.db, seqEvt)
}

Expand Down Expand Up @@ -624,3 +634,5 @@ export class ListKeyset extends TimeCidKeyset<{
const matchNamespace = (namespace: string, fullname: string) => {
return fullname === namespace || fullname.startsWith(`${namespace}.`)
}

export type HandleSequenceToken = { did: string; handle: string }

0 comments on commit 0d3a555

Please sign in to comment.