Skip to content

Commit 8e6b7d2

Browse files
author
Alan Shaw
authored
feat: use location claims (#98)
Simple change to allow freeway to make use of location claims. Note: still assumes all content is in carpark, this PR just extracts CAR CID from URL `pathname` and uses byte range to extract block.
1 parent 6178793 commit 8e6b7d2

File tree

5 files changed

+173
-72
lines changed

5 files changed

+173
-72
lines changed

package-lock.json

+71-69
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"@ipld/dag-cbor": "^9.0.8",
4141
"@ipld/dag-json": "^10.1.7",
4242
"@ipld/dag-pb": "^4.0.8",
43-
"@web3-storage/content-claims": "^4.0.1",
43+
"@web3-storage/content-claims": "^4.0.5",
4444
"@web3-storage/gateway-lib": "^4.1.1",
4545
"cardex": "^3.0.0",
4646
"dagula": "^7.3.0",
@@ -55,7 +55,7 @@
5555
"@ucanto/principal": "^8.1.0",
5656
"ava": "^5.3.1",
5757
"carbites": "^1.0.6",
58-
"carstream": "^1.1.1",
58+
"carstream": "^2.1.0",
5959
"dotenv": "^16.3.1",
6060
"esbuild": "^0.18.20",
6161
"files-from-path": "^0.2.6",

src/lib/dag-index/content-claims.js

+38
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
/* global ReadableStream */
22
import * as Link from 'multiformats/link'
33
import * as raw from 'multiformats/codecs/raw'
4+
import { base32 } from 'multiformats/bases/base32'
5+
import * as Digest from 'multiformats/hashes/digest'
6+
import { varint } from 'multiformats'
47
import * as Claims from '@web3-storage/content-claims/client'
58
import { MultihashIndexSortedReader } from 'cardex/multihash-index-sorted'
69
import { Map as LinkMap } from 'lnmap'
@@ -94,6 +97,18 @@ export class ContentClaimsIndex {
9497

9598
const claims = await Claims.read(cid, { serviceURL: this.#serviceURL })
9699
for (const claim of claims) {
100+
if (claim.type === 'assert/location' && claim.range?.length != null) {
101+
const origin = locationToShardCID(claim.location[0])
102+
if (!origin) continue
103+
104+
const { multihash } = cid
105+
// convert offset to CARv2 index offset (start of block header)
106+
const offset = claim.range.offset - (varint.encodingLength(cid.bytes.length + claim.range.length) + cid.bytes.length)
107+
108+
this.#cache.set(cid, { origin, multihash, digest: multihash.bytes, offset })
109+
continue
110+
}
111+
97112
// skip anything that is not a relation claim, since we know by
98113
// our naming convention that our CAR files are named after their hash
99114
// and we don't serve anything that we don't have in our own bucket.
@@ -158,3 +173,26 @@ const decodeIndex = async (origin, bytes) => {
158173
}
159174
return entries
160175
}
176+
177+
/**
178+
* Attempts to extract a CAR CID from a bucket location URL.
179+
*
180+
* @param {string} key
181+
*/
182+
const locationToShardCID = key => {
183+
const filename = String(key.split('/').at(-1))
184+
const [hash] = filename.split('.')
185+
try {
186+
// recent buckets encode CAR CID in filename
187+
const cid = Link.parse(hash).toV1()
188+
if (isCARLink(cid)) return cid
189+
throw new Error('not a CAR CID')
190+
} catch (err) {
191+
// older buckets base32 encode a CAR multihash <base32(car-multihash)>.car
192+
try {
193+
const digestBytes = base32.baseDecode(hash)
194+
const digest = Digest.decode(digestBytes)
195+
return Link.create(CAR.code, digest)
196+
} catch {}
197+
}
198+
}

test/helpers/content-claims.js

+37
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,43 @@ export const generateClaims = async (signer, dataCid, carCid, carStream, indexCi
117117
return claims
118118
}
119119

120+
/**
121+
* @param {import('@ucanto/interface').Signer} signer
122+
* @param {import('cardex/api').CARLink} carCid
123+
* @param {ReadableStream<Uint8Array>} carStream CAR file data
124+
*/
125+
export const generateLocationClaims = async (signer, carCid, carStream) => {
126+
/** @type {Claims} */
127+
const claims = new LinkMap()
128+
129+
await carStream
130+
.pipeThrough(new CARReaderStream())
131+
.pipeTo(new WritableStream({
132+
async write ({ cid, blockOffset, blockLength }) {
133+
const invocation = Assert.location.invoke({
134+
issuer: signer,
135+
audience: signer,
136+
with: signer.did(),
137+
nb: {
138+
content: cid,
139+
location: [
140+
/** @type {import('@ucanto/interface').URI<'http:'>} */
141+
(`http://localhost/${carCid}/${carCid}.car`)
142+
],
143+
range: { offset: blockOffset, length: blockLength }
144+
}
145+
})
146+
147+
const blocks = claims.get(cid) ?? []
148+
// @ts-expect-error
149+
blocks.push(await encode(invocation))
150+
claims.set(cid, blocks)
151+
}
152+
}))
153+
154+
return claims
155+
}
156+
120157
/**
121158
* Encode a claim to a block.
122159
* @param {import('@ucanto/interface').IssuedInvocationView} invocation

0 commit comments

Comments
 (0)