forked from storacha/w3up
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add usage/report capability (storacha#1079)
Adds a capability allowing agents to request a usage report for a given period. The report includes the size of the space at the start of the period and at the end of the period, and additionally the events that caused the space to change size. This should allow us to display current storage usage for a space as well as graph space size change over time. --------- Co-authored-by: Benjamin Goering <171782+gobengo@users.noreply.github.com> Co-authored-by: Vasco Santos <santos.vasco10@gmail.com>
- Loading branch information
1 parent
b1f860b
commit 6418b4b
Showing
25 changed files
with
563 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { capability, ok, Schema } from '@ucanto/validator' | ||
import { and, equal, equalWith, SpaceDID } from './utils.js' | ||
|
||
/** | ||
* Capability can only be delegated (but not invoked) allowing audience to | ||
* be derived any `usage/` prefixed capability for the (memory) space identified | ||
* by DID in the `with` field. | ||
*/ | ||
export const usage = capability({ | ||
can: 'usage/*', | ||
/** DID of the (memory) space where usage is derived. */ | ||
with: SpaceDID, | ||
derives: equalWith, | ||
}) | ||
|
||
/** | ||
* Capability can be invoked by an agent to retrieve usage data for a space in | ||
* a given period. | ||
*/ | ||
export const report = capability({ | ||
can: 'usage/report', | ||
with: SpaceDID, | ||
nb: Schema.struct({ | ||
/** Period to retrieve events between. */ | ||
period: Schema.struct({ | ||
/** Time in seconds after Unix epoch (inclusive). */ | ||
from: Schema.integer().greaterThan(-1), | ||
/** Time in seconds after Unix epoch (exclusive). */ | ||
to: Schema.integer().greaterThan(-1), | ||
}), | ||
}), | ||
derives: (child, parent) => { | ||
return ( | ||
and(equalWith(child, parent)) || | ||
and( | ||
equal(child.nb.period?.from, parent.nb.period?.from, 'period.from') | ||
) || | ||
and(equal(child.nb.period?.to, parent.nb.period?.to, 'period.to')) || | ||
ok({}) | ||
) | ||
}, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
import assert from 'assert' | ||
import { access } from '@ucanto/validator' | ||
import { Verifier } from '@ucanto/principal' | ||
import * as Usage from '../../src/usage.js' | ||
import * as Capability from '../../src/top.js' | ||
import { | ||
alice, | ||
service as w3, | ||
mallory as account, | ||
bob, | ||
} from '../helpers/fixtures.js' | ||
import { validateAuthorization } from '../helpers/utils.js' | ||
|
||
const top = async () => | ||
Capability.top.delegate({ | ||
issuer: account, | ||
audience: alice, | ||
with: account.did(), | ||
}) | ||
|
||
const usage = async () => | ||
Usage.usage.delegate({ | ||
issuer: account, | ||
audience: alice, | ||
with: account.did(), | ||
proofs: [await top()], | ||
}) | ||
|
||
describe('usage capabilities', function () { | ||
it('usage/report can be derived from *', async () => { | ||
const period = { from: 0, to: 1 } | ||
const report = Usage.report.invoke({ | ||
issuer: alice, | ||
audience: w3, | ||
with: account.did(), | ||
nb: { period }, | ||
proofs: [await top()], | ||
}) | ||
|
||
const result = await access(await report.delegate(), { | ||
capability: Usage.report, | ||
principal: Verifier, | ||
authority: w3, | ||
validateAuthorization, | ||
}) | ||
|
||
if (result.error) { | ||
assert.fail(result.error.message) | ||
} | ||
|
||
assert.deepEqual(result.ok.audience.did(), w3.did()) | ||
assert.equal(result.ok.capability.can, 'usage/report') | ||
assert.deepEqual(result.ok.capability.nb, { period }) | ||
}) | ||
|
||
it('usage/report can be derived from usage/*', async () => { | ||
const period = { from: 2, to: 3 } | ||
const report = Usage.report.invoke({ | ||
issuer: alice, | ||
audience: w3, | ||
with: account.did(), | ||
nb: { period }, | ||
proofs: [await usage()], | ||
}) | ||
|
||
const result = await access(await report.delegate(), { | ||
capability: Usage.report, | ||
principal: Verifier, | ||
authority: w3, | ||
validateAuthorization, | ||
}) | ||
|
||
if (result.error) { | ||
assert.fail(result.error.message) | ||
} | ||
|
||
assert.deepEqual(result.ok.audience.did(), w3.did()) | ||
assert.equal(result.ok.capability.can, 'usage/report') | ||
assert.deepEqual(result.ok.capability.nb, { period }) | ||
}) | ||
|
||
it('usage/report can be derived from usage/* derived from *', async () => { | ||
const period = { from: 3, to: 4 } | ||
const usage = await Usage.report.delegate({ | ||
issuer: alice, | ||
audience: bob, | ||
with: account.did(), | ||
proofs: [await top()], | ||
}) | ||
|
||
const report = Usage.report.invoke({ | ||
issuer: bob, | ||
audience: w3, | ||
with: account.did(), | ||
nb: { period }, | ||
proofs: [usage], | ||
}) | ||
|
||
const result = await access(await report.delegate(), { | ||
capability: Usage.report, | ||
principal: Verifier, | ||
authority: w3, | ||
validateAuthorization, | ||
}) | ||
|
||
if (result.error) { | ||
assert.fail(result.error.message) | ||
} | ||
|
||
assert.deepEqual(result.ok.audience.did(), w3.did()) | ||
assert.equal(result.ok.capability.can, 'usage/report') | ||
assert.deepEqual(result.ok.capability.nb, { period }) | ||
}) | ||
|
||
it('usage/report sholud fail when escalating period constraint', async () => { | ||
const period = { from: 5, to: 6 } | ||
const delegation = await Usage.report.delegate({ | ||
issuer: alice, | ||
audience: bob, | ||
with: account.did(), | ||
nb: { period }, | ||
proofs: [await top()], | ||
}) | ||
|
||
{ | ||
const report = Usage.report.invoke({ | ||
issuer: bob, | ||
audience: w3, | ||
with: account.did(), | ||
nb: { period: { from: period.from + 1, to: period.to } }, | ||
proofs: [delegation], | ||
}) | ||
|
||
const result = await access(await report.delegate(), { | ||
capability: Usage.report, | ||
principal: Verifier, | ||
authority: w3, | ||
validateAuthorization, | ||
}) | ||
|
||
assert.ok(result.error) | ||
assert( | ||
result.error.message.includes( | ||
`${period.from + 1} violates imposed period.from constraint ${ | ||
period.from | ||
}` | ||
) | ||
) | ||
} | ||
|
||
{ | ||
const report = Usage.report.invoke({ | ||
issuer: bob, | ||
audience: w3, | ||
with: account.did(), | ||
nb: { period: { from: period.from, to: period.to + 1 } }, | ||
proofs: [delegation], | ||
}) | ||
|
||
const result = await access(await report.delegate(), { | ||
capability: Usage.report, | ||
principal: Verifier, | ||
authority: w3, | ||
validateAuthorization, | ||
}) | ||
|
||
assert.ok(result.error) | ||
assert( | ||
result.error.message.includes( | ||
`${period.to + 1} violates imposed period.to constraint ${period.to}` | ||
) | ||
) | ||
} | ||
}) | ||
|
||
it('usage/report period from must be an int', async () => { | ||
const period = { from: 5.5, to: 6 } | ||
const proofs = [await top()] | ||
assert.throws(() => { | ||
Usage.report.invoke({ | ||
issuer: alice, | ||
audience: w3, | ||
with: account.did(), | ||
nb: { period }, | ||
proofs, | ||
}) | ||
}, /Expected value of type integer instead got 5\.5/) | ||
}) | ||
|
||
it('usage/report period to must be an int', async () => { | ||
const period = { from: 5, to: 6.6 } | ||
const proofs = [await top()] | ||
assert.throws(() => { | ||
Usage.report.invoke({ | ||
issuer: alice, | ||
audience: w3, | ||
with: account.did(), | ||
nb: { period }, | ||
proofs, | ||
}) | ||
}, /Expected value of type integer instead got 6\.6/) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.