-
Notifications
You must be signed in to change notification settings - Fork 22
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: write invocations and receipts into ucan log #592
Conversation
@@ -73,6 +74,11 @@ export interface RouteContext { | |||
uploadApi: ConnectionView | |||
} | |||
|
|||
interface UCANLog { | |||
logInvocations: (car: Uint8Array) => Promise<void> | |||
logReceipt: (receipt: Uint8Array) => Promise<void> |
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.
Is there a reason to allow logging more than one invocations at a time, but only one receipt at a time?
Any downsides to this being logReceipts
? It seems nice to allow for logging them in batches
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.
Invocations are run concurrently so in order to log receipts together we'd have to await for all of them to finish before we can log receipts. It seems like logging receipt right after invocation is complete is a better idea than having to await for the other invocations.
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.
we'd have to await for all of them to finish before we can log receipts
Yikes. I definitely don't want to do that.
It seems like logging receipt right after invocation is complete is a better idea than having to await for the other invocations.
logReceipts
could still be called several times as receipts are available (even one at a time). I'm only suggesting that the API afford for batching, not that we wait for all invocations to create only one batch.
@Gozala set those and made you org owner |
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 is looking good! Thanks for tackling this @Gozala
I agree we should move this into ucanto
as follow up. Let me work on the changes in w3infra side before approving to make sure everything fits nicely
export {} | ||
|
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 like it shouldn't be needed?
ran: invocation.cid, | ||
out, | ||
fx: { fork: [] }, | ||
meta: {}, |
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.
Currently there is no way to identify which invocation capability was the root for this receipt without opening the CAR file. This is not ideal from a stream of such events.
We should be able to filter out the events we want to handle per capability so that we do not need to read all the CARs to get to know about them. For example, let's consider a stream consumer (with batches of 20) that tracks number of store/add
successful invocations. This consumer would be required to go through all the receipts received, read all the CAR files from the invocations and check the capability. If from these 20 CARs there are no store/add
operations to address, all this processing was useless.
Thinking about adding capability from invocation in meta
as a good way to flag this. We would read CARs mentioned in receipts with capability we care about (and could still validate that capability is really what is given in meta
).
Thoughts?
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.
Currently there is no way to identify which invocation capability was the root for this receipt without opening the CAR file. This is not ideal from a stream of such events.
It's not that bad...right? It's a local bucket so won't be costly. Can we see how it pans out and change if necessary? I'd rather not rely on something in a meta
field...
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.
...or maybe a better idea - the UCAN log service should validate that the receipt belongs to a stored invocation. It shoudl read the invocation and annotate what it sends to the stream with what you need.
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.
...or maybe a better idea - the UCAN log service should validate that the receipt belongs to a stored invocation. It shoudl read the invocation and annotate what it sends to the stream with what you need.
I think this should be good yes, I will report back once I try it out
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.
It's not that bad...right? It's a local bucket so won't be costly. Can we see how it pans out and change if necessary? I'd rather not rely on something in a meta field...
Depends, if we could use Kinesis filters already that would be a bumper if we could not filter out consumers right away. But your solution above should work well
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.
LGTM - thanks @Gozala
packages/access-api/package.json
Outdated
@@ -8,6 +8,7 @@ | |||
"private": true, | |||
"scripts": { | |||
"lint": "tsc --build && eslint '**/*.{js,ts}' && prettier --check '**/*.{js,ts,yml,json}' --ignore-path ../../.gitignore", | |||
"fix:lint": "eslint --fix '**/*.{js,ts}'", |
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.
"fix:lint": "eslint --fix '**/*.{js,ts}'", | |
"lint:fix": "eslint --fix '**/*.{js,ts}'", |
ran: invocation.cid, | ||
out, | ||
fx: { fork: [] }, | ||
meta: {}, |
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.
Currently there is no way to identify which invocation capability was the root for this receipt without opening the CAR file. This is not ideal from a stream of such events.
It's not that bad...right? It's a local bucket so won't be costly. Can we see how it pans out and change if necessary? I'd rather not rely on something in a meta
field...
headers['content-type'] === 'application/car' ? [CAR, CBOR] : [json, json] | ||
|
||
const invocations = await decoder.decode({ | ||
body, | ||
headers: Object.fromEntries(request.headers.entries()), |
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.
headers: Object.fromEntries(request.headers.entries()), | |
headers, |
@@ -24,6 +24,8 @@ on: | |||
required: true | |||
LOGTAIL_TOKEN: | |||
required: true | |||
UCAN_LOG_BASIC_AUTH: |
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.
can this include a description of the format? e.g. in the past I've wondered if it should be base64 encoded or not. If not commented, I assume yes? (i.e. I'd assume it's the value in an http header after 'Basic '.
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.
Value after Basic encoded as base64
async logInvocations(car) { | ||
await pRetry( | ||
() => | ||
fetch(this.url, { |
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.
do we want to retry on http response status 502 and/or 503?
I think those cases would not reject the fetch promise, so wouldn't retry here.
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.
LGTM
@Gozala feel free to merge. If so, please replace secrets for now to not perform the HTTP Requests until we merge storacha/w3infra#172
method: 'POST', | ||
headers: { | ||
Authorization: `Basic ${this.auth}`, | ||
'Content-Type': 'application/invocations+car', |
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.
Is this defined anywhere?
nit: If it's ad-hoc, application/vnd.
... might be appropriate.
/** | ||
* @param {object} input | ||
* @param {URL} input.url | ||
* @param {string} [input.auth] |
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.
it could be helpful to include a comment on what kind of string this is. e.g. should it be base64, base64url, what kind of auth
(http basic?)
/** | ||
* @param {Uint8Array} car | ||
*/ | ||
async logInvocations(car) { |
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.
do tests run in this implementation?
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.
none of my comments should block
…ts (#172) This PR includes two significant changes: - `upload-api` ucanto server now follows same pattern as `access-api` proposal from @Gozala's storacha/w3up#592 - saves CAR file with UCAN invocations before executing them - sends one receipt per invocation when they are executed - modifies `POST /ucan` endpoint to accept both receipts and CAR invocations as discussed sync yesterday - it identifies by ContentType wether is a CAR of invocations or a Receipt (`processUcanLogRequest`) - for CAR of invocations, everything is quite similar as before: - parse CAR from bytes, persist CAR and send a view of it to Kinesis - more than persisting the CARs, we also persist a map of invocation CIDs to CARs where they live in same fashion as we do with DUDEWHERE - kinesis now also receives a type property, which can either be Invocation, or receipt like the actual endpoint - NOTE: removed `proofs` as they can get quite big and we already talked about getting rid of it before - for receipts: - we parse the receipt CBOR, verify if given receipt is from an Invocation that we previously got. If so, we can persist the Receipt - Kinesis stream receives quite similar content to the invocation. Main differences are that a receipt type is used, and we also include the `inovcationCid`. In other words, we provide value from invocation so that Kinesis consumers can know about `invocation` and `nb` from invocation to filter out and account metrics Other notes: - `upload-api` hooks in the same functions for integrating with UCAN Stream as the `/ucan` endpoint, avoiding the HTTP calls - Storing receipts and invocations Bucket keys were decided yesterday. - Like storacha/w3up#592, we aim to get to ucanto part of the code that we currently inline to create the receipts - Will do a follow up PR for metric consumers to update to new world
🤖 I have created a release *beep* *boop* --- ## [5.0.0](access-api-v4.11.0...access-api-v5.0.0) (2023-03-23) ### ⚠ BREAKING CHANGES * implement new account-based multi-device flow ([#433](#433)) * upgrade capabilities to latest ucanto ([#463](#463)) ### Features * access-api handles provider/add invocations ([#462](#462)) ([5fb56f7](5fb56f7)) * access-api serves access/claim invocations ([#456](#456)) ([baacf35](baacf35)) * access/authorize confirmation email click results in a delegation back to the issuer did:key so that access/claim works ([#460](#460)) ([a466a7d](a466a7d)) * allow multiple providers ([#595](#595)) ([96c5a2e](96c5a2e)) * define `access/confirm` handler and use it in ucanto-test-utils registerSpaces + validate-email handler ([#530](#530)) ([b1bbc90](b1bbc90)) * handle access/delegate invocations without error ([#427](#427)) ([4f0bd1c](4f0bd1c)) * if POST /validate-email?mode=authorize catches error w/ too big qr code ([#516](#516)) ([d0df525](d0df525)) * implement new account-based multi-device flow ([#433](#433)) ([1ddc6a0](1ddc6a0)) * includes proofs chains in the delegated authorization chain ([#467](#467)) ([5144293](5144293)) * move access-api delegation bytes out of d1 and into r2 ([#578](#578)) ([4510c9a](4510c9a)) * move validation flow to a Durable Object to make it ⏩ fast ⏩ fast ⏩ fast ⏩ ([#449](#449)) ([02d7552](02d7552)) * provision provider type is now the DID of the w3s service ([#528](#528)) ([6a72855](6a72855)) * space/info will not error for spaces that have had storage provider added via provider/add ([#510](#510)) ([ea4e872](ea4e872)) * upgrade capabilities to latest ucanto ([#463](#463)) ([2d786ee](2d786ee)) * upgrade to new ucanto ([#498](#498)) ([dcb41a9](dcb41a9)) * write invocations and receipts into ucan log ([#592](#592)) ([754bf52](754bf52)) ### Bug Fixes * access/delegate checks hasStorageProvider(space) in a way that provider/add allows access/delegate ([#483](#483)) ([f4c640d](f4c640d)) * adjust migration 0005 to keep delegations table but create new used delegations_v2 ([#469](#469)) ([a205ad1](a205ad1)) * adjust migration 0005 to not do a drop table and instead rename delegations -> delegations_old and create a new delegations ([#468](#468)) ([6c8242d](6c8242d)) * allow injecting email ([#466](#466)) ([e19847f](e19847f)) * DbDelegationsStorage#find throws UnexpectedDelegation w/ { row } if failed bytesToDelegations ([#476](#476)) ([a6dafcb](a6dafcb)) * DbProvisionsStorage putMany doesnt error on cid col conflict ([#517](#517)) ([c1fea63](c1fea63)) * delegations model tries to handle if row.bytes is Array not Buffer (e.g. cloudflare) ([#478](#478)) ([030e7b7](030e7b7)) ### Miscellaneous Chores * **access-client:** release 11.0.0-rc.0 ([#573](#573)) ([be4386d](be4386d)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
Fixes #581 ## Overview - Integrates changes from #579 - I have added `undici` to [pnpm overrides](https://pnpm.io/package_json) - Gets rid of `raw` route in favor of `root` which now uses decoder/encoder based of content type - Defines `UCANLog` interface for sending invocations & receipts to kenisis - As per #579 we probably want to use CF Queue for failure tolerance which can be a followup improvement - Type fixes to unblock some changes - You can't do `@implements {import('./bindings').UCANLog}` nor you can type alias and do implements because it no longer an interface. Solution is to do `import * as API from "./bindings.js"` in which case you can `@implements {API.UCANLog}`, which is why end up changing `bindings.d.ts` to `bindings.ts` + `bindings.js`. - Turns out `.d.ts` files aren't type checked so ☝️ uncovered bunch of errors I had to fix ## Notes - I'm not happy how this reaches deep into ucanto & I'd like to uplift changes around encode / decode / execute into ucanto. Once that is done I'll make a followup change here to replace some of these with ucanto exposed functionality - It also would make it easier to integrate things in the upload-api - I do not seem to have privileges to add vars and secrets in this, so someone with enough privileges should add `UCAN_LOG_URL` or` UCAN_LOG_BASIC_AUTH` so someone needs to do it to make this work. - I end up sending invocations and receipts to the same endpoint but different content-type header which I think makes sense. - Also unlike #579 I've decided to decouple endpoint from upload endpoint as they're logically unrelated.
🤖 I have created a release *beep* *boop* --- ## [5.0.0](access-api-v4.11.0...access-api-v5.0.0) (2023-03-23) ### ⚠ BREAKING CHANGES * implement new account-based multi-device flow ([#433](#433)) * upgrade capabilities to latest ucanto ([#463](#463)) ### Features * access-api handles provider/add invocations ([#462](#462)) ([46da0df](46da0df)) * access-api serves access/claim invocations ([#456](#456)) ([2ec16e9](2ec16e9)) * access/authorize confirmation email click results in a delegation back to the issuer did:key so that access/claim works ([#460](#460)) ([fc62691](fc62691)) * allow multiple providers ([#595](#595)) ([aba57b3](aba57b3)) * define `access/confirm` handler and use it in ucanto-test-utils registerSpaces + validate-email handler ([#530](#530)) ([a08b513](a08b513)) * handle access/delegate invocations without error ([#427](#427)) ([db01d07](db01d07)) * if POST /validate-email?mode=authorize catches error w/ too big qr code ([#516](#516)) ([ab83b19](ab83b19)) * implement new account-based multi-device flow ([#433](#433)) ([6152e55](6152e55)) * includes proofs chains in the delegated authorization chain ([#467](#467)) ([743a72f](743a72f)) * move access-api delegation bytes out of d1 and into r2 ([#578](#578)) ([3029e4a](3029e4a)) * move validation flow to a Durable Object to make it ⏩ fast ⏩ fast ⏩ fast ⏩ ([#449](#449)) ([3868d97](3868d97)) * provision provider type is now the DID of the w3s service ([#528](#528)) ([4cd6cd9](4cd6cd9)) * space/info will not error for spaces that have had storage provider added via provider/add ([#510](#510)) ([362024f](362024f)) * upgrade capabilities to latest ucanto ([#463](#463)) ([e375ae4](e375ae4)) * upgrade to new ucanto ([#498](#498)) ([790750d](790750d)) * write invocations and receipts into ucan log ([#592](#592)) ([d52a281](d52a281)) ### Bug Fixes * access/delegate checks hasStorageProvider(space) in a way that provider/add allows access/delegate ([#483](#483)) ([1d3d562](1d3d562)) * adjust migration 0005 to keep delegations table but create new used delegations_v2 ([#469](#469)) ([d90825a](d90825a)) * adjust migration 0005 to not do a drop table and instead rename delegations -> delegations_old and create a new delegations ([#468](#468)) ([89f2acd](89f2acd)) * allow injecting email ([#466](#466)) ([b4b0173](b4b0173)) * DbDelegationsStorage#find throws UnexpectedDelegation w/ { row } if failed bytesToDelegations ([#476](#476)) ([660f773](660f773)) * DbProvisionsStorage putMany doesnt error on cid col conflict ([#517](#517)) ([8c6dea8](8c6dea8)) * delegations model tries to handle if row.bytes is Array not Buffer (e.g. cloudflare) ([#478](#478)) ([02c0c28](02c0c28)) ### Miscellaneous Chores * **access-client:** release 11.0.0-rc.0 ([#573](#573)) ([29daa02](29daa02)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
Fixes #581
Overview
undici
to pnpm overridesraw
route in favor ofroot
which now uses decoder/encoder based of content typeUCANLog
interface for sending invocations & receipts to kenisis@implements {import('./bindings').UCANLog}
nor you can type alias and do implements because it no longer an interface. Solution is to doimport * as API from "./bindings.js"
in which case you can@implements {API.UCANLog}
, which is why end up changingbindings.d.ts
tobindings.ts
+bindings.js
..d.ts
files aren't type checked so ☝️ uncovered bunch of errors I had to fixNotes
UCAN_LOG_URL
orUCAN_LOG_BASIC_AUTH
so someone needs to do it to make this work.