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

feat: /pieceCid support for roundabout redirect api #233

Merged
merged 19 commits into from
Sep 22, 2023
Merged

Conversation

olizilla
Copy link
Contributor

@olizilla olizilla commented Sep 19, 2023

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 an assert/equals content claim.

License: MIT

Add support for piece cids to the roundabout redirect to signed url api

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]>
@seed-deploy seed-deploy bot temporarily deployed to pr233 September 20, 2023 13:21 Inactive
Comment on lines +26 to +27
const pieceCid = CID.parse('bafkzcibbai3tdo4zvruj6zxo6wlt4suu3imi6to4vzmaojh4n475mdp5jcbtg')
const carCid = CID.parse('bagbaieratdhefxxpkhkae2ovil2tcs7pfr2grvabvvoykful7k2maeepox3q')
Copy link
Contributor Author

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: {} }

Copy link
Member

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

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: {} }

@seed-deploy
Copy link

seed-deploy bot commented Sep 20, 2023

View stack outputs

Comment on lines +1 to +8
// 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
Copy link
Contributor Author

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.

Copy link
Contributor

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

@vasco-santos vasco-santos mentioned this pull request Sep 20, 2023
1 task
Copy link
Contributor

@vasco-santos vasco-santos left a 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

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 })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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]>
Copy link
Contributor

@Gozala Gozala left a 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.

if (claim.type !== 'assert/equals') {
continue
}
const carCid = asCarCid(claim.content) ?? asCarCid(claim.equals)
Copy link
Contributor

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.

return toLambdaResponse(signedUrl)
const signer = getSigner(s3Client, BUCKET_NAME)

if (asPieceCid(cid) !== undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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)
}
}
Copy link
Contributor

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)
}

Copy link
Contributor

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.

Copy link
Contributor Author

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]>
@seed-deploy seed-deploy bot temporarily deployed to pr233 September 21, 2023 15:29 Inactive
License: MIT
Signed-off-by: Oli Evans <[email protected]>
Copy link
Contributor

@vasco-santos vasco-santos left a 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]>
@seed-deploy seed-deploy bot temporarily deployed to pr233 September 21, 2023 16:30 Inactive
Comment on lines +51 to +62
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)
})
Copy link
Contributor Author

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.

Copy link
Contributor

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

@olizilla olizilla merged commit 44c75cd into main Sep 22, 2023
1 check passed
@olizilla olizilla deleted the roundabout-piece branch September 22, 2023 08:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants