Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: ttl and caching for ipns urls #34

Merged
merged 6 commits into from
Mar 28, 2024
Merged
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
11 changes: 7 additions & 4 deletions packages/verified-fetch/src/utils/parse-url-string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,15 @@ function matchURLString (urlString: string): MatchUrlGroups {
*
* @see https://github.com/ipfs/js-ipns/blob/16e0e10682fa9a663e0bb493a44d3e99a5200944/src/index.ts#L200
* @see https://github.com/ipfs/js-ipns/pull/308
* @returns the ttl in seconds
*/
function calculateTtl (resolveResult?: IPNSResolveResult | DNSLinkResolveResult): number | undefined {
if (resolveResult == null) {
return undefined
}
const dnsLinkTtl = (resolveResult as DNSLinkResolveResult).answer?.TTL
const ipnsTtlNs = (resolveResult as IPNSResolveResult).record?.ttl
// For some reason, ipns "nanoseconds" are 1e-8 of a second, instead of 1e-9.
const ipnsTtl = ipnsTtlNs != null ? Number(ipnsTtlNs / BigInt(1e8)) : undefined
const ipnsTtl = ipnsTtlNs != null ? Number(ipnsTtlNs / BigInt(1e9)) : undefined
2color marked this conversation as resolved.
Show resolved Hide resolved
return dnsLinkTtl ?? ipnsTtl
}

Expand Down Expand Up @@ -214,11 +214,14 @@ export async function parseUrlString ({ urlString, ipns, logger }: ParseUrlStrin
throw new AggregateError(errors, `Invalid resource. Cannot determine CID from URL "${urlString}"`)
}

const ttl = calculateTtl(resolveResult)
let ttl = calculateTtl(resolveResult)

if (resolveResult != null) {
// use the ttl for the resolved resouce for the cache, but fallback to 2 minutes if not available
ipnsCache.set(cidOrPeerIdOrDnsLink, resolveResult, ttl ?? 60 * 1000 * 2)
ttl = ttl ?? 60 * 2
log.trace('caching %s resolved to %s with TTL: %s', cidOrPeerIdOrDnsLink, cid, ttl)
// convert ttl from seconds to ms for the cache
ipnsCache.set(cidOrPeerIdOrDnsLink, resolveResult, ttl * 1000)
}

// parse query string
Expand Down
4 changes: 2 additions & 2 deletions packages/verified-fetch/src/utils/tlru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export class TLRU<T> {
return undefined
}

set (key: string, value: T, ttl: number): void {
this.lru.set(key, { value, expire: Date.now() + ttl })
set (key: string, value: T, ttlMs: number): void {
this.lru.set(key, { value, expire: Date.now() + ttlMs })
}

has (key: string): boolean {
Expand Down
5 changes: 4 additions & 1 deletion packages/verified-fetch/test/cache-control-header.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ describe('cache-control header', () => {
expect(resp.headers.get('Cache-Control')).to.not.containIgnoreCase('immutable')
})

it('should return the correct max-age in the cache-control header for an IPNS name', async () => {
// Skipping until https://github.com/ipfs/js-ipns/issues/310 is resolved
// Note that the source of the error is from the `name.publish` call rather than the max-age value
2color marked this conversation as resolved.
Show resolved Hide resolved
// in the cache control header.
it.skip('should return the correct max-age in the cache-control header for an IPNS name', async () => {
const obj = {
hello: 'world'
}
Expand Down
34 changes: 34 additions & 0 deletions packages/verified-fetch/test/utils/parse-url-string.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,40 @@ describe('parseUrlString', () => {
})
})

describe('TTL', () => {
const oneHourInSeconds = 3600
const oneHourInNanoseconds = BigInt(3600 * 1e9)

it('should return the correct TTL from the DNS Answer ', async () => {
ipns.resolveDNSLink.withArgs('newdomain.com').resolves({
cid: CID.parse('QmQJ8fxavY54CUsxMSx9aE9Rdcmvhx8awJK2jzJp4iAqCr'),
path: '',
answer: {
TTL: oneHourInSeconds,
type: 16,
name: 'n/a',
data: 'n/a'
}
})

const result = await parseUrlString({ urlString: 'ipns://newdomain.com/', ipns, logger })
expect(result.ttl).to.equal(oneHourInSeconds)
})

it('should return the correct TTL from the IPNS answer', async () => {
const testPeerId = await createEd25519PeerId()

ipns.resolve.withArgs(matchPeerId(testPeerId)).resolves({
cid: CID.parse('QmQJ8fxavY54CUsxMSx9aE9Rdcmvhx8awJK2jzJp4iAqCr'),
path: '',
record: ipnsRecordStub({ peerId: testPeerId, ttl: oneHourInNanoseconds })
})

const result = await parseUrlString({ urlString: `ipns://${testPeerId}`, ipns, logger })
expect(result.ttl).to.equal(oneHourInSeconds)
})
})

describe('/ipfs/<CID> URLs', () => {
it('should parse an IPFS Path with a CID only', async () => {
await assertMatchUrl(
Expand Down
Loading