Skip to content

Commit

Permalink
chore: update based on storacha/specs#117
Browse files Browse the repository at this point in the history
  • Loading branch information
vasco-santos committed Apr 12, 2024
1 parent 92500c6 commit c88f634
Show file tree
Hide file tree
Showing 14 changed files with 121 additions and 114 deletions.
8 changes: 4 additions & 4 deletions packages/capabilities/src/blob.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ export const blob = capability({
/**
* Blob description for being ingested by the service.
*/
export const blobModel = Schema.struct({
export const content = Schema.struct({
/**
* A multihash digest of the blob payload bytes, uniquely identifying blob.
*/
content: Schema.bytes(),
digest: Schema.bytes(),
/**
* Size of the Blob file to be stored. Service will provision write target
* for this exact size. Attempt to write a larger Blob file will fail.
Expand All @@ -51,7 +51,7 @@ export const blobModel = Schema.struct({
/**
* `blob/add` capability allows agent to store a Blob into a (memory) space
* identified by did:key in the `with` field. Agent must precompute Blob locally
* and provide it's multihash and size using `nb.content` and `nb.size` fields, allowing
* and provide it's multihash and size using `nb.blob` field, allowing
* a service to provision a write location for the agent to PUT or POST desired
* Blob into.
*/
Expand All @@ -66,7 +66,7 @@ export const add = capability({
/**
* Blob to allocate on the space.
*/
blob: blobModel,
blob: content,
}),
derives: equalBlob,
})
Expand Down
17 changes: 15 additions & 2 deletions packages/capabilities/src/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* @module
*/
import { capability, Schema, ok } from '@ucanto/validator'
import { blobModel } from './blob.js'
import { content } from './blob.js'
import { equal, equalBody, equalWith, SpaceDID, and } from './utils.js'

/**
Expand All @@ -28,7 +28,20 @@ export const put = capability({
/**
* BodyBlob to allocate on the space.
*/
body: blobModel,
body: content,
// TODO: what should be used?
/**
* HTTP(S) location that can receive blob content via HTTP PUT request.
*/
// url: Schema.struct({
// 'ucan/await': Schema.unknown(),
// }).optional(),
// /**
// * HTTP headers.
// */
// headers: Schema.struct({
// 'ucan/await': Schema.unknown(),
// }).optional(),
/**
* HTTP(S) location that can receive blob content via HTTP PUT request.
*/
Expand Down
9 changes: 4 additions & 5 deletions packages/capabilities/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,14 +454,14 @@ export type BlobAccept = InferInvokedCapability<typeof W3sBlobCaps.accept>

export type BlobMultihash = Uint8Array
export interface BlobModel {
content: BlobMultihash
digest: BlobMultihash
size: number
}

// Blob add
export interface BlobAddSuccess {
location: {
'ucan/await': ['.out.ok.claim', Link]
site: {
'ucan/await': ['.out.ok.site', Link]
}
}

Expand All @@ -484,7 +484,6 @@ export interface BlobListItem {
// Blob allocate
export interface BlobAllocateSuccess {
size: number
expiresAt?: ISO8601Date
address?: BlobAddress
}

Expand All @@ -508,7 +507,7 @@ export type BlobAllocateFailure =

// Blob accept
export interface BlobAcceptSuccess {
claim: Link
site: Link
}

// TODO: We should type the store errors and add them here, instead of Ucanto.Failure
Expand Down
20 changes: 10 additions & 10 deletions packages/capabilities/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export const equalLink = (claimed, delegated) => {
}

/**
* @template {Types.ParsedCapability<"blob/add"|"blob/remove"|"web3.storage/blob/allocate"|"web3.storage/blob/accept", Types.URI<'did:'>, {blob: { content: Uint8Array, size: number }}>} T
* @template {Types.ParsedCapability<"blob/add"|"blob/remove"|"web3.storage/blob/allocate"|"web3.storage/blob/accept", Types.URI<'did:'>, {blob: { digest: Uint8Array, size: number }}>} T
* @param {T} claimed
* @param {T} delegated
* @returns {Types.Result<{}, Types.Failure>}
Expand All @@ -99,13 +99,13 @@ export const equalBlob = (claimed, delegated) => {
`Expected 'with: "${delegated.with}"' instead got '${claimed.with}'`
)
} else if (
delegated.nb.blob.content &&
!equals(delegated.nb.blob.content, claimed.nb.blob.content)
delegated.nb.blob.digest &&
!equals(delegated.nb.blob.digest, claimed.nb.blob.digest)
) {
return fail(
`Link ${
claimed.nb.blob.content ? `${claimed.nb.blob.content}` : ''
} violates imposed ${delegated.nb.blob.content} constraint.`
claimed.nb.blob.digest ? `${claimed.nb.blob.digest}` : ''
} violates imposed ${delegated.nb.blob.digest} constraint.`
)
} else if (
claimed.nb.blob.size !== undefined &&
Expand All @@ -122,7 +122,7 @@ export const equalBlob = (claimed, delegated) => {
}

/**
* @template {Types.ParsedCapability<"http/put", Types.URI<'did:'>, {body: { content: Uint8Array, size: number }}>} T
* @template {Types.ParsedCapability<"http/put", Types.URI<'did:'>, {body: { digest: Uint8Array, size: number }}>} T
* @param {T} claimed
* @param {T} delegated
* @returns {Types.Result<{}, Types.Failure>}
Expand All @@ -133,13 +133,13 @@ export const equalBody = (claimed, delegated) => {
`Expected 'with: "${delegated.with}"' instead got '${claimed.with}'`
)
} else if (
delegated.nb.body.content &&
!equals(delegated.nb.body.content, claimed.nb.body.content)
delegated.nb.body.digest &&
!equals(delegated.nb.body.digest, claimed.nb.body.digest)
) {
return fail(
`Link ${
claimed.nb.body.content ? `${claimed.nb.body.content}` : ''
} violates imposed ${delegated.nb.body.content} constraint.`
claimed.nb.body.digest ? `${claimed.nb.body.digest}` : ''
} violates imposed ${delegated.nb.body.digest} constraint.`
)
} else if (
claimed.nb.body.size !== undefined &&
Expand Down
15 changes: 10 additions & 5 deletions packages/capabilities/src/web3.storage/blob.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { capability, Schema, Link, ok, fail } from '@ucanto/validator'
import { blobModel } from '../blob.js'
import { content } from '../blob.js'
import {
equalBlob,
equalWith,
Expand Down Expand Up @@ -41,7 +41,7 @@ export const allocate = capability({
/**
* Blob to allocate on the space.
*/
blob: blobModel,
blob: content,
/**
* The Link for an Add Blob task, that caused an allocation
*/
Expand Down Expand Up @@ -77,12 +77,17 @@ export const accept = capability({
/**
* Blob to accept.
*/
blob: blobModel,
// TODO: @gozala suggested we could use content length from `httt/put` to figure out size and not need to pass the blob here
blob: content,
/**
* Expiration..
* Expiration of location site.
*/
exp: Schema.integer(),
/**
* DID of the user space where allocation took place
*/
// TODO: space
// space: SpaceDID,
// TODO: _put?
}),
derives: (claim, from) => {
const result = equalBlob(claim, from)
Expand Down
17 changes: 8 additions & 9 deletions packages/upload-api/src/blob/accept.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ export function blobAcceptProvider(context) {
handler: async ({ capability }) => {
const { blob } = capability.nb
// If blob is not stored, we must fail
const hasBlob = await context.blobsStorage.has(blob.content)
const hasBlob = await context.blobsStorage.has(blob.digest)
if (hasBlob.error) {
return {
error: new BlobItemNotFound(),
}
}

const digest = new Digest(sha256.code, 32, blob.content, blob.content)
// TODO: we need to support multihash in claims, or specify hardcoded codec
const digest = new Digest(sha256.code, 32, blob.digest, blob.digest)
const content = createLink(CAR.code, digest)
const w3link = `https://w3s.link/ipfs/${content.toString()}`

Expand All @@ -41,22 +42,20 @@ export function blobAcceptProvider(context) {
content,
location: [
// @ts-expect-error Type 'string' is not assignable to type '`${string}:${string}`'
w3link
]
w3link,
],
},
expiration: Infinity,
})
.delegate()
// TODO: we need to support multihash in claims, or specify hardcoded codec

// Create result object
/** @type {API.OkBuilder<API.BlobAcceptSuccess, API.BlobAcceptFailure>} */
const result = Server.ok({
claim: locationClaim.cid
site: locationClaim.cid,
})

return result
.fork(locationClaim)
}
return result.fork(locationClaim)
},
})
}
28 changes: 15 additions & 13 deletions packages/upload-api/src/blob/add.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ export function blobAddProvider(context) {
}
}

// We derive principal from the content multihash to be an audience
// of the `http/put` invocation. That way anyone with blob content
// We derive principal from the blob multihash to be an audience
// of the `http/put` invocation. That way anyone with blob digest
// could perform the invocation and issue receipt by deriving same
// principal
const blobProvider = await ed25519.derive(blob.content.slice(0, 32))
const blobProvider = await ed25519.derive(blob.digest.slice(0, 32))
const facts = [
{
keys: blobProvider.toArchive(),
Expand All @@ -67,6 +67,8 @@ export function blobAddProvider(context) {
nb: {
blob,
exp: Number.MAX_SAFE_INTEGER,
// TODO:
// space
},
expiration: Infinity,
})
Expand All @@ -76,26 +78,26 @@ export function blobAddProvider(context) {
])

// Get receipt for `blob/allocate` if available, or schedule invocation if not
const allocatedGetRes = await allocationsStorage.get(space, blob.content)
const allocatedGetRes = await allocationsStorage.get(space, blob.digest)
let blobAllocateReceipt
/** @type {API.BlobAddress | undefined} */
let blobAllocateOutAddress
// If already allocated, just get the allocate receipt
// and the addresses if still pending to receive blob
if (allocatedGetRes.ok) {
// TODO: Check expires
// TODO: Check expires?
const receiptGet = await context.receiptsStorage.get(allocatefx.link())
if (receiptGet.error) {
return receiptGet
}
blobAllocateReceipt = receiptGet.ok

// Check if despite allocated, the blob is still not stored
const blobHasRes = await context.blobsStorage.has(blob.content)
const blobHasRes = await context.blobsStorage.has(blob.digest)
if (blobHasRes.error) {
return blobHasRes
// If still not stored, keep the allocate address to signal to the client
// that bytes MUST be sent through the `http/put` effect
// If still not stored, keep the allocate address to signal to the client
// that bytes MUST be sent through the `http/put` effect
} else if (!blobHasRes.ok) {
// @ts-expect-error receipt type is unknown
blobAllocateOutAddress = blobAllocateReceipt.out.ok.address
Expand All @@ -110,8 +112,8 @@ export function blobAddProvider(context) {
error: new AwaitError({
cause: allocateRes.out.error,
at: 'ucan/wait',
reference: ['.out.ok', allocatefx.cid]
})
reference: ['.out.ok', allocatefx.cid],
}),
}
}
// If this is a new allocation, `http/put` effect should be returned with address
Expand Down Expand Up @@ -144,8 +146,8 @@ export function blobAddProvider(context) {
// Create result object
/** @type {API.OkBuilder<API.BlobAddSuccess, API.BlobAddFailure>} */
const result = Server.ok({
location: {
'ucan/await': ['.out.ok.claim', acceptfx.link()],
site: {
'ucan/await': ['.out.ok.site', acceptfx.link()],
},
})

Expand All @@ -159,7 +161,7 @@ export function blobAddProvider(context) {
nb: {
body: blob,
url: blobAllocateOutAddress.url,
headers: blobAllocateOutAddress.headers
headers: blobAllocateOutAddress.headers,
},
facts,
expiration: Infinity,
Expand Down
7 changes: 3 additions & 4 deletions packages/upload-api/src/blob/allocate.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function blobAllocateProvider(context) {
// added to the space and there is no allocation change.
// If record exists but is expired, it can be re-written
if (allocationInsert.error.name === 'RecordKeyConflict') {
// TODO: Updates to new URL and expiration if expired?
// TODO: Should we return the same anyway and read the store to get address?
return {
ok: { size: 0 },
}
Expand All @@ -68,7 +68,7 @@ export function blobAllocateProvider(context) {
// Get presigned URL for the write target
const expiresIn = 60 * 60 * 24 // 1 day
const createUploadUrl = await context.blobsStorage.createUploadUrl(
blob.content,
blob.digest,
blob.size,
expiresIn
)
Expand All @@ -79,7 +79,7 @@ export function blobAllocateProvider(context) {
}

// Check if blob already exists
const hasBlobStore = await context.blobsStorage.has(blob.content)
const hasBlobStore = await context.blobsStorage.has(blob.digest)
if (hasBlobStore.error) {
return hasBlobStore
}
Expand All @@ -100,7 +100,6 @@ export function blobAllocateProvider(context) {
ok: {
size: blob.size,
address,
expiresAt: (new Date(Date.now() + expiresIn)).toISOString()
},
}
}
Expand Down
12 changes: 6 additions & 6 deletions packages/upload-api/src/blob/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@ export class AwaitError extends Failure {
* @param {import('@ucanto/interface').Failure} source.cause - error that caused referenced `await` to fail
*/
constructor({ at, reference, cause }) {
super()
this.at = at
this.reference = reference
this.cause = cause
super()
this.at = at
this.reference = reference
this.cause = cause
}
describe() {
const [selector, task] = this.reference
return `Awaited (${selector} ${task}) reference at ${this.at} has failed:\n${this.cause}`
const [selector, task] = this.reference
return `Awaited (${selector} ${task}) reference at ${this.at} has failed:\n${this.cause}`
}
get name() {
return AwaitErrorName
Expand Down
Loading

0 comments on commit c88f634

Please sign in to comment.