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: integrate agent store for idempotence & invocation/receipt persistence #1444

Merged
merged 49 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
9aee035
feat: add blob add to upload-cli
joaosa Apr 30, 2024
48cde40
feat: use blob add on upload-cli
joaosa Apr 30, 2024
d5672c8
feat: modify tests to cover blob add on upload-client
joaosa Apr 30, 2024
0f1e592
chore: cleanup code and add current status
joaosa Apr 30, 2024
1ccb3c4
feat: revamp implementation
joaosa May 2, 2024
dc40432
chore: cleanup blob/add implementation
joaosa May 3, 2024
48a641e
chore: fix typo
joaosa May 3, 2024
99c1878
feat: expose blob add result types
joaosa May 3, 2024
8ae5f69
fix: have all test return types match the blob add response
joaosa May 3, 2024
2ff3748
fix: pass upload CAR and file tests
joaosa May 6, 2024
8fb6720
fix: pass upload-client tests
joaosa May 6, 2024
5642e01
feat: add client upload progress
joaosa May 6, 2024
1f600c3
chore: relock deps
joaosa May 6, 2024
2c73702
fix: blob add upload progress implementation
joaosa May 6, 2024
50929e6
chore: address linter errors
joaosa May 6, 2024
9391a86
fix: test blob upload on w3up-client tests
joaosa May 6, 2024
45507f2
chore: cleanup upload-client test code
joaosa May 6, 2024
0896614
fix: pass w3up-client upload tests
joaosa May 6, 2024
7552e9b
fix: missing import
joaosa May 6, 2024
4eee857
chore: appease prettier
joaosa May 6, 2024
24a99e9
fix: address w3up upload test expectations
joaosa May 7, 2024
c6aaa04
feat: implement blob/remove
joaosa May 8, 2024
65dfee1
feat: implement blob/list
joaosa May 8, 2024
21e2973
fix: pass blob protocol upload-client tests
joaosa May 8, 2024
f9c4043
chore: address coverage for upload-client blob
joaosa May 8, 2024
5196f9c
feat: add w3up-client upload-client blob boilerplate
joaosa May 8, 2024
fb21b1a
fix: error classes that called recursion
Gozala May 8, 2024
87915a9
fix: digest encoding
Gozala May 8, 2024
1c886f1
fix: invalid test assertion
Gozala May 8, 2024
c4c941a
fix: failing tests
Gozala May 9, 2024
e0b8619
feat: implement task & receipt store
Gozala May 13, 2024
c10ca5f
fix: address remaining issues
Gozala May 14, 2024
93faab5
chore: remove receipt storing logic
Gozala May 14, 2024
0afa904
Apply suggestions from code review
Gozala May 14, 2024
f5a960b
Merge remote-tracking branch 'origin/main' into feat/agent-store
Gozala May 14, 2024
cf7e1f7
chore: put comments about temp hack back in
Gozala May 14, 2024
c89e9ce
fix post-merge errors
Gozala May 14, 2024
49f275e
fix: enable all tests
Gozala May 14, 2024
b67bb83
fix: type check
Gozala May 14, 2024
8d4a484
chore: undo accidental changes
Gozala May 14, 2024
c8e0088
chore: disable failing test
Gozala May 14, 2024
a3382e4
fix: upload-client api misalignment
Gozala May 14, 2024
23c795f
fix: failing tests
Gozala May 15, 2024
6d513de
fix: failing test
Gozala May 15, 2024
4b196c6
fix: remove unused dep
Gozala May 15, 2024
98889fb
feat: add agent store tests
Gozala May 16, 2024
3c403f1
Apply suggestions from code review
Gozala May 16, 2024
1d6e557
Merge remote-tracking branch 'origin/main' into feat/agent-store
Gozala May 16, 2024
c8a83d3
fix: workaround c8 bug
Gozala May 16, 2024
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
4 changes: 3 additions & 1 deletion packages/access-client/src/agent-use-cases.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,19 @@ export async function requestAccess(access, account, capabilities) {
* @param {AccessAgent} access
* @param {API.DID} [audienceOfClaimedDelegations] - audience of claimed delegations. defaults to access.connection.id.did()
* @param {object} opts
* @param {string} [opts.nonce] - nonce to use for the claim
* @param {boolean} [opts.addProofs] - whether to addProof to access agent
* @returns
*/
export async function claimAccess(
access,
audienceOfClaimedDelegations = access.connection.id.did(),
{ addProofs = false } = {}
{ addProofs = false, nonce } = {}
) {
const res = await access.invokeAndExecute(Access.claim, {
audience: access.connection.id,
with: audienceOfClaimedDelegations,
nonce,
})
if (res.out.error) {
throw res.out.error
Expand Down
6 changes: 5 additions & 1 deletion packages/access-client/src/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,7 @@ export class Agent {
}),
issuer: this.issuer,
proofs: [...proofs],
nonce: options.nonce,
})

return /** @type {API.IssuedInvocationView<API.InferInvokedCapability<CAP>>} */ (
Expand All @@ -592,13 +593,16 @@ export class Agent {
* Get Space information from Access service
*
* @param {API.URI<"did:">} [space]
* @param {object} [options]
* @param {string} [options.nonce]
*/
async getSpaceInfo(space) {
async getSpaceInfo(space, options) {
const _space = space || this.currentSpace()
if (!_space) {
throw new Error('No space selected, you need pass a resource.')
}
const inv = await this.invokeAndExecute(Capabilities.info, {
...options,
with: _space,
})

Expand Down
2 changes: 1 addition & 1 deletion packages/filecoin-api/src/storefront/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
UpdatableAndQueryableStore,
Queue,
ServiceConfig,
StoreGetError
StoreGetError,
} from '../types.js'

export type PieceStore = UpdatableAndQueryableStore<
Expand Down
1 change: 0 additions & 1 deletion packages/upload-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@
"is-subset": "^0.1.1",
"mocha": "^10.2.0",
"one-webcrypto": "git://github.com/web3-storage/one-webcrypto",
"p-defer": "^4.0.1",
"typescript": "5.2.2"
},
"eslintConfig": {
Expand Down
91 changes: 89 additions & 2 deletions packages/upload-api/src/blob/accept.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ import * as DID from '@ipld/dag-ucan/did'
import * as W3sBlob from '@web3-storage/capabilities/web3.storage/blob'
import { Assert } from '@web3-storage/content-claims/capability'
import { create as createLink } from 'multiformats/link'
import { Invocation } from '@ucanto/core'
import * as Digest from 'multiformats/hashes/digest'
import { code as rawCode } from 'multiformats/codecs/raw'
import * as API from '../types.js'
import { AllocatedMemoryHadNotBeenWrittenTo } from './lib.js'
import {
AllocatedMemoryHadNotBeenWrittenTo,
UnsupportedCapability,
} from './lib.js'
import * as HTTP from '@web3-storage/capabilities/http'

/**
* @param {API.W3ServiceContext} context
Expand All @@ -16,6 +21,13 @@ export function blobAcceptProvider(context) {
return Server.provideAdvanced({
capability: W3sBlob.accept,
handler: async ({ capability }) => {
// Only service principal can perform an allocation
if (capability.with !== context.id.did()) {
return {
error: new UnsupportedCapability({ capability }),
}
}

const { blob, space } = capability.nb
// If blob is not stored, we must fail
const hasBlob = await context.blobsStorage.has(blob.digest)
Expand All @@ -29,7 +41,9 @@ export function blobAcceptProvider(context) {

const digest = Digest.decode(blob.digest)
const content = createLink(rawCode, digest)
const createUrl = await context.blobsStorage.createDownloadUrl(digest.bytes)
const createUrl = await context.blobsStorage.createDownloadUrl(
digest.bytes
)
if (createUrl.error) {
return createUrl
}
Expand All @@ -55,3 +69,76 @@ export function blobAcceptProvider(context) {
},
})
}

/**
* Polls `blob/accept` task whenever we receive a receipt. It may error if passed
* receipt is for `http/put` task that refers to the `blob/allocate` that we
* are unable to find.
*
* @param {API.ConcludeServiceContext} context
* @param {API.Receipt} receipt
* @returns {Promise<API.Result<{}, API.StorageGetError>>}
*/
export const poll = async (context, receipt) => {
const ran = Invocation.isInvocation(receipt.ran)
? { ok: receipt.ran }
: await context.agentStore.invocations.get(receipt.ran)

// If can not find an invocation for this receipt there is nothing to do here,
// if it was receipt for `http/put` we would have invocation record.
if (!ran.ok) {
return { ok: {} }
}

// Detect if this receipt is for an `http/put` invocation
const put = /** @type {?API.HTTPPut} */ (
ran.ok.capabilities.find(({ can }) => can === HTTP.put.can)
)

// If it's not an http/put invocation nothing to do here.
if (put == null) {
return { ok: {} }
}

// Otherwise we are going to lookup allocation corresponding to this http/put
// in order to issue blob/accept.
const [, allocation] = /** @type {API.UCANAwait} */ (put.nb.url)['ucan/await']
const result = await context.agentStore.invocations.get(allocation)
// If could not find blob/allocate invocation there is something wrong in
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: would be great to send this into a DLQ where we could be alerted. Probably an issue for now would be good, as we would also need to do the setup for DLQ monitoring

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could probably set this up as capability handler and dispatch to it, that way we would be able to throw here which will end up triggering catch handler of the server and get logged instead.

Does that sound like a better option ?

// the system and we return error so it could be propagated to the user. It is
// not a proper solution as user can not really do anything, but still seems
// better than silently ignoring, this way user has a chance to report a
// problem. Client test could potentially also catch errors.
if (result.error) {
return result
}

const [allocate] = /** @type {[API.BlobAllocate]} */ (result.ok.capabilities)

// If this is a receipt for the http/put we will perform blob/accept.
const blobAccept = await W3sBlob.accept.invoke({
issuer: context.id,
audience: context.id,
with: context.id.did(),
nb: {
blob: put.nb.body,
space: allocate.nb.space,
_put: {
'ucan/await': ['.out.ok', receipt.ran.link()],
},
},
// ⚠️ We need invocation to be deterministic which is why we use exact
// same as it is on allocation which will guarantee that expiry is the
// same regardless when we received `http/put` receipt.
//
// ℹ️ This works around the fact that we index receipts by invocation link
// as opposed to task link which would not care about the expiration.
expiration: result.ok.expiration,
})

// We do not care about the result we just want receipt to be issued and
// stored.
await blobAccept.execute(context.getServiceConnection())

Gozala marked this conversation as resolved.
Show resolved Hide resolved
return { ok: {} }
}
Loading
Loading