-
Notifications
You must be signed in to change notification settings - Fork 7
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
feat: /pieceCid
support for roundabout redirect api
#233
Conversation
Add support for piece cids to the roundabout redirect to signed url api License: MIT Signed-off-by: Oli Evans <[email protected]>
License: MIT Signed-off-by: Oli Evans <[email protected]>
todo: currently fails as content-claims `read` fn expects web transform stream to be available globally License: MIT Signed-off-by: Oli Evans <[email protected]>
- CarReadableStream expects global `TransformStream` - ucanto client expects global `fetch` License: MIT Signed-off-by: Oli Evans <[email protected]>
License: MIT Signed-off-by: Oli Evans <[email protected]>
License: MIT Signed-off-by: Oli Evans <[email protected]>
License: MIT Signed-off-by: Oli Evans <[email protected]>
const pieceCid = CID.parse('bafkzcibbai3tdo4zvruj6zxo6wlt4suu3imi6to4vzmaojh4n475mdp5jcbtg') | ||
const carCid = CID.parse('bagbaieratdhefxxpkhkae2ovil2tcs7pfr2grvabvvoykful7k2maeepox3q') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CIDs are the ones generated by the previous test.
Added to the content-claims api manually
❯ ./bin.js equals bafkzcibbai3tdo4zvruj6zxo6wlt4suu3imi6to4vzmaojh4n475mdp5jcbtg bagbaieratdhefxxpkhkae2ovil2tcs7pfr2grvabvvoykful7k2maeepox3q > equals-demo.claim
❯ ./bin.js write equals-demo.claim
{ ok: {} }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: the claims added using the CLI will expire and then not be retrievable. I will open an issue for setting it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed! a never expiring claim has now been written
❯ ./bin.js equals bafkzcibbai3tdo4zvruj6zxo6wlt4suu3imi6to4vzmaojh4n475mdp5jcbtg bagbaieratdhefxxpkhkae2ovil2tcs7pfr2grvabvvoykful7k2maeepox3q -e never -o equals-demo-2.claim
bafyreibb5x5sw2kw6kcr7pjrho5evli7e3n4uohc63ek3vh5dxvw42feeq
❯ ./bin.js inspect equals-demo-2.claim
{
iss: 'did:web:claims.web3.storage',
aud: 'did:web:claims.web3.storage',
v: '0.9.1',
s: {
'/': {
bytes: '7aEDQJOpYFb4BilDcbMMK0YuLnkeCqVpFadt/ZGnaVEkb6aiQ2KzXUYkyBxyGhixA6GePjSrf21hDXxd79Wck6gklQQ'
}
},
exp: null,
att: [
{
can: 'assert/equals',
nb: {
content: {
'/': 'bafkzcibbai3tdo4zvruj6zxo6wlt4suu3imi6to4vzmaojh4n475mdp5jcbtg'
},
equals: {
'/': 'bagbaieratdhefxxpkhkae2ovil2tcs7pfr2grvabvvoykful7k2maeepox3q'
}
},
with: 'did:web:claims.web3.storage'
}
],
prf: [],
'/': 'bafyreibb5x5sw2kw6kcr7pjrho5evli7e3n4uohc63ek3vh5dxvw42feeq'
}
❯ ./bin.js write equals-demo-2.claim
(node:29766) ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
{ ok: {} }
View stack outputs
|
// NOTE: shim globals needed by content-claims client deps that would be present in nodejs v18. | ||
// TODO: migrate to sst v2 and nodejs v18+ | ||
import { TransformStream, WritableStream, CountQueuingStrategy } from 'node:stream/web' | ||
import { fetch } from 'undici' | ||
globalThis.TransformStream = TransformStream | ||
globalThis.CountQueuingStrategy = CountQueuingStrategy | ||
globalThis.WritableStream = WritableStream | ||
globalThis.fetch = fetch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
migrating to sst v2 is not trivial, so this seems to be the least worst workaround I can think of for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mentioned in #152 to guarantee we do not forget to take these
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
THis looks great 🙌🏼
Wonder if we could extend the integration tests as follow up? 😛
We know a piece has been written into the DB in https://github.com/web3-storage/w3infra/blob/main/test/integration.test.js#L247 . We could do a request to Roundabout for that pieceCID in same way we test roundabout with real infra in https://github.com/web3-storage/w3infra/blob/main/test/roundabout.test.js#L17
roundabout/functions/redirect.js
Outdated
const cars = await findEquivalentCarCids(cid) | ||
for (const carCid of cars) { | ||
// getUrl returns undefined if we don't have that car, so keep trying till we find a good one. | ||
const signedUrl = await signer.getUrl(`${carCid}/${carCid}.car`, { expiresIn }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const signedUrl = await signer.getUrl(`${carCid}/${carCid}.car`, { expiresIn }) | |
const key = `${carCid}/${carCid}.car` | |
const signedUrl = await signer.getUrl(key, { expiresIn }) |
nit
License: MIT Signed-off-by: Oli Evans <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! I made some suggestions on how to make it bit easier for noob to read and understand it, but all those are just optional.
Additionally I would love if we provided support for PieceV1 CIDs, by which I mean either responding with status code Upgrade Required 426 (although not sure if that is right use of the code) or Unsupported Media Type 415 or perhaps Bad Request 400.
Either way above is probably better done as a followup.
roundabout/piece.js
Outdated
if (claim.type !== 'assert/equals') { | ||
continue | ||
} | ||
const carCid = asCarCid(claim.content) ?? asCarCid(claim.equals) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm bit lost here, I was expecting that claim.content
will be set to piece
here, although I might be misinterpreting what fetchClaims
supposed to do.
That is to say I would appreciate code comment above this line telling me what's going on so reader does not has to guess. It also may be worth noting what are the expectations in regards to fetchClaims
function.
roundabout/functions/redirect.js
Outdated
return toLambdaResponse(signedUrl) | ||
const signer = getSigner(s3Client, BUCKET_NAME) | ||
|
||
if (asPieceCid(cid) !== undefined) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (asPieceCid(cid) !== undefined) { | |
// If request CID is of Filecoin Piece we attempt to find a payload CAR | |
// and redirect to it. | |
if (asPieceCid(cid) !== undefined) { |
if (signedUrl) { | ||
return toLambdaResponse(signedUrl) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I personally prefer more linear logic so I don't have to maintain stack in my head, e.g. in this case return here would make it easier to follow as I don't have to consider next if block.
Better yet would be to refactor logic paths into separate functions e.g.
const resolvePiece = async (cid, { signer, expiresIn }) => {
const piece = asPieceCid(cid)
if (piece) {
const cars = await findEquivalentCarCids(piece)
// Attempt to find a CAR that we can resolve
for (const car of cars) {
const url = await signer.getUrl(`${car}/${car}.car`, { expiresIn })
if (url) {
return url
}
}
// We don't have a CAR for this piece CID
return null
} else {
return null
}
}
const resolveContentArcive = async (cid, {signer, expiresIn}) => {
// ...
}
export async function redirectCarGet(request) {
// ...
return await resolvePiece(cid, {signer, expiresIn}) ??
await resolveContentArcive(cid, { signer, expiresIn }) ??
await notFound(cid)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: It also might be a good idea to return 404 with some error message explaining that we could not find a CAR for the piece. That way e.g. if user provides PieceV1 CID they'll be able to tell that we simply do not support those versions as opposed to us not having a content for them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer to have less state to think about too. I've refactored this to be clearer.
License: MIT Signed-off-by: Oli Evans <[email protected]>
License: MIT Signed-off-by: Oli Evans <[email protected]>
License: MIT Signed-off-by: Oli Evans <[email protected]>
License: MIT Signed-off-by: Oli Evans <[email protected]>
License: MIT Signed-off-by: Oli Evans <[email protected]>
License: MIT Signed-off-by: Oli Evans <[email protected]>
License: MIT Signed-off-by: Oli Evans <[email protected]>
License: MIT Signed-off-by: Oli Evans <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✨
License: MIT Signed-off-by: Oli Evans <[email protected]>
License: MIT Signed-off-by: Oli Evans <[email protected]>
test('asPieceCidv2', t => { | ||
const bytes = new Uint8Array(MIN_PAYLOAD_SIZE) | ||
const piece = Piece.fromPayload(bytes) | ||
const pieceCidV2 = piece.link | ||
const pieceCidV1 = CID.createV1(Piece.FilCommitmentUnsealed, Digest.create(Piece.Sha256Trunc254Padded, piece.root)) | ||
const carCid = CID.createV1(CAR_CODE, sha256.digest(bytes)) | ||
const rawCid = CID.createV1(Raw.code, sha256.digest(bytes)) | ||
t.is(asPieceCidV2(pieceCidV1), undefined) | ||
t.is(asPieceCidV2(pieceCidV2), pieceCidV2) | ||
t.is(asPieceCidV2(carCid), undefined) | ||
t.is(asPieceCidV2(rawCid), undefined) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Gozala here is how i'm (ab)using Piece from data-segment to test my checks for v2 and v1 piece cids.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks fine to me, although you could do const pieceCidV1 = piece.toInfo().link
instead of manually constructing it
Upgrade roundabout
/:cid
endpoint to handle v2 PieceCIDs.When passed a v2 piece cid, try and find an equivalent CAR CID for it from the content-claims api.
Redirect to a pre-signed URL for the car in R2 if we CAR CID that we are storing, (as roundabout does currently), otherwise 404.
This is good place for this logic, as consumers already expect to get a CAR back from requests to roundabout.
/piece/:pieceCID
was considered, but that would clash with the spec for fetching pieces, where the response body should be the padded piece itself https://github.com/filecoin-project/FIPs/blob/master/FRCs/frc-0066.md (here we return the equivalent, unpadded car, as asserted by anassert/equals
content claim.License: MIT