Skip to content

Commit

Permalink
fix: record comparisons
Browse files Browse the repository at this point in the history
  • Loading branch information
Alan Shaw committed Nov 17, 2021
1 parent 6088ca3 commit 7dbd6c8
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 30 deletions.
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
},
"dependencies": {
"@aws-sdk/client-s3": "^3.28.0",
"@google-cloud/precise-date": "^2.0.4",
"@ipld/car": "^3.1.4",
"@ipld/dag-cbor": "^6.0.3",
"@ipld/dag-pb": "^2.0.2",
Expand Down
18 changes: 13 additions & 5 deletions packages/api/src/name.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as Digest from 'multiformats/hashes/digest'
import { base36 } from 'multiformats/bases/base36'
import { CID } from 'multiformats/cid'
import { keys } from 'libp2p-crypto'
import { PreciseDate } from '@google-cloud/precise-date'
import { HTTPError } from './errors.js'
import { JSONResponse } from './utils/json-response.js'

Expand Down Expand Up @@ -45,16 +46,23 @@ export async function namePost (request, env) {
throw new HTTPError(`invalid key code: ${keyCid.code}`, 400)
}

const rawRecord = await request.text()
const record = ipns.unmarshal(uint8arrays.fromString(rawRecord, 'base64pad'))
const record = await request.text()
const entry = ipns.unmarshal(uint8arrays.fromString(record, 'base64pad'))
const pubKey = keys.unmarshalPublicKey(Digest.decode(keyCid.multihash.bytes).bytes)

if (record.pubKey && !keys.unmarshalPublicKey(record.pubKey).equals(pubKey)) {
if (entry.pubKey && !keys.unmarshalPublicKey(entry.pubKey).equals(pubKey)) {
throw new HTTPError('embedded public key mismatch', 400)
}

await ipns.validate(pubKey, record)
await env.db.publishNameRecord(key, rawRecord, record.sequence)
await ipns.validate(pubKey, entry)

await env.db.publishNameRecord(
key,
record,
Boolean(entry.signatureV2),
entry.sequence,
new PreciseDate(uint8arrays.toString(entry.validity)).getFullTime()
)

return new JSONResponse({ id: key }, { status: 202 })
}
19 changes: 9 additions & 10 deletions packages/db/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -759,21 +759,20 @@ export class DBClient {
*
* @param {string} key
* @param {string} record Base 64 encoded serialized IPNS record.
* @param {boolean} hasV2Sig If the record has a v2 signature.
* @param {bigint} seqno Sequence number from the record.
* @param {bigint} validity Validity from the record in nanoseconds since 00:00, Jan 1 1970 UTC.
*/
async publishNameRecord (key, record, seqno) {
const { error } = await this._client
.from('name')
.upsert({
async publishNameRecord (key, record, hasV2Sig, seqno, validity) {
const { error } = await this._client.rpc('publish_name_record', {
data: {
key,
record,
has_v2_sig: hasV2Sig,
seqno: seqno.toString(),
updated_at: new Date().toISOString()
}, {
returning: 'minimal',
onConflict: 'key'
})
.lt('seqno', seqno.toString())
validity: validity.toString()
}
})

if (error) {
throw new DBError(error)
Expand Down
22 changes: 12 additions & 10 deletions packages/db/postgres/functions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -295,29 +295,31 @@ WHERE ae.cid_v1 = ANY (cids)
ORDER BY de.entry_last_updated
$$;

CREATE OR REPLACE FUNCTION publish_name_record(data json)
CREATE OR REPLACE FUNCTION publish_name_record(data json) RETURNS TEXT
LANGUAGE plpgsql
volatile
PARALLEL UNSAFE
AS
$$
BEGIN
INSERT INTO public.name (key, record, has_v2_sig, seqno, validity)
INSERT INTO name (key, record, has_v2_sig, seqno, validity)
VALUES (data ->> 'key',
data ->> 'record',
data ->> 'has_v2_sig',
(data ->> 'has_v2_sig')::BOOLEAN,
(data ->> 'seqno')::BIGINT,
(data ->> 'validity')::BIGINT)
ON CONFLICT (key) DO UPDATE
SET record = data ->> 'record',
has_v2_sig = data ->> 'has_v2_sig',
has_v2_sig = (data ->> 'has_v2_sig')::BOOLEAN,
seqno = (data ->> 'seqno')::BIGINT,
validity = (data ->> 'validity')::BIGINT)
validity = (data ->> 'validity')::BIGINT,
updated_at = TIMEZONE('utc'::TEXT, NOW())
WHERE
(data ->> 'has_v2_sig' = TRUE AND has_v2_sig = FALSE) OR
((data ->> 'seqno')::BIGINT > seqno) OR
((data ->> 'validity')::BIGINT > validity)
-- TODO:
-- (DECODE(data ->> 'record', 'base64'))
-- https://github.com/ipfs/go-ipns/blob/a8379aa25ef287ffab7c5b89bfaad622da7e976d/ipns.go#L325
((data ->> 'has_v2_sig')::BOOLEAN = TRUE AND name.has_v2_sig = FALSE) OR
((data ->> 'has_v2_sig')::BOOLEAN = name.has_v2_sig AND (data ->> 'seqno')::BIGINT > name.seqno) OR
((data ->> 'has_v2_sig')::BOOLEAN = name.has_v2_sig AND (data ->> 'seqno')::BIGINT = name.seqno AND (data ->> 'validity')::BIGINT > name.validity) OR
((data ->> 'has_v2_sig')::BOOLEAN = name.has_v2_sig AND (data ->> 'seqno')::BIGINT = name.seqno AND (data ->> 'validity')::BIGINT = name.validity AND DECODE(data ->> 'record', 'base64') > DECODE(name.record, 'base64'));
RETURN data ->> 'key';
END
$$;
9 changes: 4 additions & 5 deletions packages/db/postgres/tables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -202,18 +202,17 @@ CREATE TABLE IF NOT EXISTS migration_tracker
inserted_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL
);

CREATE TABLE IF NOT EXISTS public.name
CREATE TABLE IF NOT EXISTS name
(
-- base36 "libp2p-key" encoding of the public key
key TEXT PRIMARY KEY,
-- the serialized IPNS record - base64 encoded
record TEXT NOT NULL,
-- the following 3 fields are derived from the record, and used for
-- newness comparisons, see:
-- https://github.com/ipfs/go-ipns/blob/a8379aa25ef287ffab7c5b89bfaad622da7e976d/ipns.go#L325
-- next 3 fields are derived from the record & used for newness comparisons
-- see: https://github.com/ipfs/go-ipns/blob/a8379aa25ef287ffab7c5b89bfaad622da7e976d/ipns.go#L325
has_v2_sig BOOLEAN NOT NULL,
seqno BIGINT NOT NULL,
validity BIGINT NOT NULL, -- (nanosecond resolution so needs to be a bigint not timestamp)
validity BIGINT NOT NULL, -- nanoseconds since 00:00, Jan 1 1970 UTC
inserted_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL
);

0 comments on commit 7dbd6c8

Please sign in to comment.