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

fix(egress/record): rename capability #1572

Merged
merged 7 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 29 additions & 0 deletions packages/capabilities/src/space.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,32 @@ export const allocate = capability({
}
},
})

/**
* The capability grants permission for all content serve operations that fall under the "space/content/serve" namespace.
* It can be derived from any of the `space/*` capability that has matching `with`.
*/

export const contentServe = capability({
can: 'space/content/serve/*',
with: SpaceDID,
derives: equalWith,
})

/**
* Capability can be invoked by an agent to record egress data for a given resource.
* It can be derived from any of the `space/content/serve/*` capability that has matching `with`.
*/
export const egressRecord = capability({
can: 'space/content/serve/egress/record',
with: SpaceDID,
nb: Schema.struct({
/** CID of the resource that was served. */
resource: Schema.link(),
/** Amount of bytes served. */
bytes: Schema.integer().greaterThan(0),
/** Timestamp of the event in seconds after Unix epoch. */
servedAt: Schema.integer().greaterThan(-1),
}),
derives: equalWith,
})
16 changes: 11 additions & 5 deletions packages/capabilities/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
ProofData,
uint64,
} from '@web3-storage/data-segment'
import { space, info } from './space.js'
import * as SpaceCaps from './space.js'
import * as provider from './provider.js'
import { top } from './top.js'
import * as BlobCaps from './blob.js'
Expand Down Expand Up @@ -131,8 +131,14 @@ export type UsageReport = InferInvokedCapability<typeof UsageCaps.report>
export type UsageReportSuccess = Record<ProviderDID, UsageData>
export type UsageReportFailure = Ucanto.Failure

export type EgressRecord = InferInvokedCapability<typeof UsageCaps.record>
export type EgressRecordSuccess = Unit
export type EgressRecord = InferInvokedCapability<typeof SpaceCaps.egressRecord>
export type EgressRecordSuccess = {
space: SpaceDID
resource: UnknownLink
bytes: number
servedAt: ISO8601Date
cause: UnknownLink
}
export type EgressRecordFailure = ConsumerNotFound | Ucanto.Failure

export interface UsageData {
Expand Down Expand Up @@ -276,8 +282,8 @@ export interface RateLimitListSuccess {
export type RateLimitListFailure = Ucanto.Failure

// Space
export type Space = InferInvokedCapability<typeof space>
export type SpaceInfo = InferInvokedCapability<typeof info>
export type Space = InferInvokedCapability<typeof SpaceCaps.space>
export type SpaceInfo = InferInvokedCapability<typeof SpaceCaps.info>

// filecoin
export interface DealMetadata {
Expand Down
17 changes: 0 additions & 17 deletions packages/capabilities/src/usage.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,3 @@ export const report = capability({
)
},
})

/**
* Capability can be invoked by an agent to record usage data for a given resource.
*/
export const record = capability({
can: 'usage/record',
with: SpaceDID,
nb: Schema.struct({
/** CID of the resource that was served. */
resource: Schema.link(),
/** Amount of bytes served. */
bytes: Schema.integer().greaterThan(0),
/** Timestamp of the event in seconds after Unix epoch. */
servedAt: Schema.integer().greaterThan(-1),
}),
derives: equalWith,
})
9 changes: 9 additions & 0 deletions packages/capabilities/test/helpers/fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,12 @@ export const service = Signer.parse(
export const readmeCID = parseLink(
'bafybeihqfdg2ereoijjoyrqzr2x2wsasqm2udurforw7pa3tvbnxhojao4'
)

export const gateway = Signer.parse(
'MgCaNpGXCEX0+BxxE4SjSStrxU9Ru/Im+HGNQ/JJx3lDoI+0B3NWjWW3G8OzjbazZjanjM3kgfcZbvpyxv20jHtmcTtg=' // random key
).withDID('did:web:w3s.link')

/** did:key:z6MktYxTNoCxrXhK9oS5PdzutujTJ5DaS3FWYxNpRTXwrH6h */
export const space = Signer.parse(
'MgCYBaaeyfAHFNt5+M07rY9pPLnmhyxvMEj5jdyAN0ajSlO0B0Xk2fW+t/EsB2nqWraDmB7N0NiTXKZaVBbOpCMtCktI=' // random key
)
4 changes: 3 additions & 1 deletion packages/upload-api/src/space.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ import * as Provider from '@ucanto/server'
import * as API from './types.js'

import { info } from './space/info.js'
import { provide as provideRecordEgress } from './space/record.js'
import { createService as createBlobService } from './blob.js'
import { createService as createIndexService } from './index.js'

/**
* @param {API.SpaceServiceContext & API.BlobServiceContext & API.IndexServiceContext} ctx
* @param {API.SpaceServiceContext & API.BlobServiceContext & API.IndexServiceContext & API.UsageServiceContext} ctx
*/
export const createService = (ctx) => ({
info: Provider.provide(Space.info, (input) => info(input, ctx)),
blob: createBlobService(ctx),
index: createIndexService(ctx),
content: { serve: { egress: { record: provideRecordEgress(ctx) } } },
})
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import * as API from '../types.js'
import * as Provider from '@ucanto/server'
import { Usage } from '@web3-storage/capabilities'
import { Space } from '@web3-storage/capabilities'

/** @param {API.UsageServiceContext} context */
/** @param {API.SpaceServiceContext & API.UsageServiceContext} context */
export const provide = (context) =>
Provider.provide(Usage.record, (input) => record(input, context))
Provider.provide(Space.egressRecord, (input) => egressRecord(input, context))

/**
* @param {API.Input<Usage.record>} input
* @param {API.UsageServiceContext} context
* @param {API.Input<Space.egressRecord>} input
* @param {API.SpaceServiceContext & API.UsageServiceContext} context
* @returns {Promise<API.Result<API.EgressRecordSuccess, API.EgressRecordFailure>>}
*/
const record = async ({ capability, invocation }, context) => {
const egressRecord = async ({ capability, invocation }, context) => {
const provider = /** @type {`did:web:${string}`} */ (
invocation.audience.did()
)
Expand Down
2 changes: 0 additions & 2 deletions packages/upload-api/src/usage.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { provide as provideReport } from './usage/report.js'
import { provide as provideRecord } from './usage/record.js'

/** @param {import('./types.js').UsageServiceContext} context */
export const createService = (context) => ({
report: provideReport(context),
record: provideRecord(context),
})
14 changes: 9 additions & 5 deletions packages/upload-api/test/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,15 @@ export const mallory = ed25519.parse(
'MgCYtH0AvYxiQwBG6+ZXcwlXywq9tI50G2mCAUJbwrrahkO0B0elFYkl3Ulf3Q3A/EvcVY0utb4etiSE8e6pi4H0FEmU='
)

export const w3 = ed25519
.parse(
'MgCYKXoHVy7Vk4/QjcEGi+MCqjntUiasxXJ8uJKY0qh11e+0Bs8WsdqGK7xothgrDzzWD0ME7ynPjz2okXDh8537lId8='
)
.withDID('did:web:test.web3.storage')
export const w3Signer = ed25519.parse(
'MgCYKXoHVy7Vk4/QjcEGi+MCqjntUiasxXJ8uJKY0qh11e+0Bs8WsdqGK7xothgrDzzWD0ME7ynPjz2okXDh8537lId8='
)
export const w3 = w3Signer.withDID('did:web:test.web3.storage')

export const gatewaySigner = ed25519.parse(
'MgCaNpGXCEX0+BxxE4SjSStrxU9Ru/Im+HGNQ/JJx3lDoI+0B3NWjWW3G8OzjbazZjanjM3kgfcZbvpyxv20jHtmcTtg='
)
export const gateway = gatewaySigner.withDID('did:web:w3s.link')

/**
* Creates a server for the given service.
Expand Down
69 changes: 69 additions & 0 deletions packages/w3up-client/src/capability/space.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Base } from '../base.js'
import { Space as SpaceCapabilities } from '@web3-storage/capabilities'
import * as API from '../types.js'

/**
* Client for interacting with the `space/*` capabilities.
Expand All @@ -17,4 +19,71 @@ export class SpaceClient extends Base {
async info(space, options) {
return await this._agent.getSpaceInfo(space, options)
}

/**
* Record egress data for a served resource.
* It will execute the capability invocation to find the customer and then record the egress data for the resource.
*
* Required delegated capabilities:
* - `space/content/serve/egress/record`
*
* @param {object} egressData
* @param {import('../types.js').SpaceDID} egressData.space
* @param {API.UnknownLink} egressData.resource
* @param {number} egressData.bytes
* @param {string} egressData.servedAt
* @param {object} [options]
* @param {string} [options.nonce]
* @param {API.Delegation[]} [options.proofs]
* @returns {Promise<API.EgressRecordSuccess>}
*/
async egressRecord(egressData, options) {
const out = await egressRecord(
{ agent: this.agent },
{ ...egressData },
{ ...options }
)

if (!out.ok) {
throw new Error(
`failed ${SpaceCapabilities.egressRecord.can} invocation`,
{
cause: out.error,
}
)
}

return /** @type {API.EgressRecordSuccess} */ (out.ok)
}
}

/**
* Record egress data for a resource from a given space.
*
* @param {{agent: API.Agent}} client
* @param {object} egressData
* @param {API.SpaceDID} egressData.space
* @param {API.UnknownLink} egressData.resource
* @param {number} egressData.bytes
* @param {string} egressData.servedAt
* @param {object} options
* @param {string} [options.nonce]
* @param {API.Delegation[]} [options.proofs]
*/
export const egressRecord = async (
{ agent },
{ space, resource, bytes, servedAt },
{ nonce, proofs = [] }
) => {
const receipt = await agent.invokeAndExecute(SpaceCapabilities.egressRecord, {
with: space,
proofs,
nonce,
nb: {
resource,
bytes,
servedAt: Math.floor(new Date(servedAt).getTime() / 1000),
},
})
return receipt.out
}
63 changes: 0 additions & 63 deletions packages/w3up-client/src/capability/usage.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,37 +31,6 @@ export class UsageClient extends Base {

return out.ok
}

/**
* Record egress data for a served resource.
* It will execute the capability invocation to find the customer and then record the egress data for the resource.
*
* Required delegated capabilities:
* - `usage/record`
*
* @param {import('../types.js').SpaceDID} space
* @param {object} egressData
* @param {API.UnknownLink} egressData.resource
* @param {number} egressData.bytes
* @param {string} egressData.servedAt
* @param {object} [options]
* @param {string} [options.nonce]
*/
async record(space, egressData, options) {
const out = await record(
{ agent: this.agent },
{ space, ...egressData },
{ ...options }
)
/* c8 ignore next 5 */
if (!out.ok) {
throw new Error(`failed ${UsageCapabilities.record.can} invocation`, {
cause: out.error,
})
}

return out.ok
}
}

/**
Expand Down Expand Up @@ -92,35 +61,3 @@ export const report = async (
})
return receipt.out
}

/**
* Record egress data for a resource from a given space.
*
* @param {{agent: API.Agent}} client
* @param {object} egressData
* @param {API.SpaceDID} egressData.space
* @param {API.UnknownLink} egressData.resource
* @param {number} egressData.bytes
* @param {string} egressData.servedAt
* @param {object} options
* @param {string} [options.nonce]
* @param {API.Delegation[]} [options.proofs]
* @returns {Promise<API.Result<API.Unit, API.EgressRecordFailure>>}
*/
export const record = async (
{ agent },
{ space, resource, bytes, servedAt },
{ nonce, proofs = [] }
) => {
const receipt = await agent.invokeAndExecute(UsageCapabilities.record, {
with: space,
proofs,
nonce,
nb: {
resource,
bytes,
servedAt: Math.floor(new Date(servedAt).getTime() / 1000),
},
})
return receipt.out
}
Loading
Loading