Skip to content

Commit

Permalink
fix: check service did in w3filecoin (#1476)
Browse files Browse the repository at this point in the history
  • Loading branch information
vasco-santos committed May 23, 2024
1 parent c9bf33e commit 11b00bf
Show file tree
Hide file tree
Showing 8 changed files with 6,809 additions and 8,272 deletions.
13 changes: 13 additions & 0 deletions packages/filecoin-api/src/aggregator/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
QueueOperationFailed,
StoreOperationFailed,
UnexpectedState,
UnsupportedCapability,
} from '../errors.js'

/**
Expand All @@ -17,6 +18,12 @@ import {
* @returns {Promise<API.UcantoInterface.Result<API.PieceOfferSuccess, API.PieceOfferFailure> | API.UcantoInterface.JoinBuilder<API.PieceOfferSuccess>>}
*/
export const pieceOffer = async ({ capability }, context) => {
// Only service principal can perform offer
if (capability.with !== context.id.did()) {
return {
error: new UnsupportedCapability({ capability }),
}
}
const { piece, group } = capability.nb

// dedupe
Expand Down Expand Up @@ -60,6 +67,12 @@ export const pieceOffer = async ({ capability }, context) => {
* @returns {Promise<API.UcantoInterface.Result<API.PieceAcceptSuccess, API.PieceAcceptFailure> | API.UcantoInterface.JoinBuilder<API.PieceAcceptSuccess>>}
*/
export const pieceAccept = async ({ capability }, context) => {
// Only service principal can perform accept
if (capability.with !== context.id.did()) {
return {
error: new UnsupportedCapability({ capability }),
}
}
const { piece, group } = capability.nb

// Get inclusion proof for piece associated with this group
Expand Down
18 changes: 17 additions & 1 deletion packages/filecoin-api/src/dealer/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,24 @@ import * as DealerCaps from '@web3-storage/capabilities/filecoin/dealer'
import { DealTracker } from '@web3-storage/filecoin-client'
// eslint-disable-next-line no-unused-vars
import * as API from '../types.js'
import { StoreOperationFailed, DecodeBlockOperationFailed } from '../errors.js'
import {
StoreOperationFailed,
DecodeBlockOperationFailed,
UnsupportedCapability,
} from '../errors.js'

/**
* @param {API.Input<DealerCaps.aggregateOffer>} input
* @param {import('./api.js').ServiceContext} context
* @returns {Promise<API.UcantoInterface.Result<API.AggregateOfferSuccess, API.AggregateOfferFailure> | API.UcantoInterface.JoinBuilder<API.AggregateOfferSuccess>>}
*/
export const aggregateOffer = async ({ capability, invocation }, context) => {
// Only service principal can perform offer
if (capability.with !== context.id.did()) {
return {
error: new UnsupportedCapability({ capability }),
}
}
const issuer = invocation.issuer.did()
const { aggregate, pieces } = capability.nb

Expand Down Expand Up @@ -90,6 +100,12 @@ export const aggregateOffer = async ({ capability, invocation }, context) => {
* @returns {Promise<API.UcantoInterface.Result<API.AggregateAcceptSuccess, API.AggregateAcceptFailure>>}
*/
export const aggregateAccept = async ({ capability }, context) => {
// Only service principal can perform accept
if (capability.with !== context.id.did()) {
return {
error: new UnsupportedCapability({ capability }),
}
}
const { aggregate } = capability.nb

// Query current state
Expand Down
19 changes: 19 additions & 0 deletions packages/filecoin-api/src/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,22 @@ export class UnexpectedPiece extends Error {
return UnexpectedPieceErrorName
}
}

export const UnsupportedCapabilityErrorName = /** @type {const} */ ('UnsupportedCapability')
export class UnsupportedCapability extends Server.Failure {
/**
* @param {object} source
* @param {import('@ucanto/interface').Capability} source.capability
*/
constructor({ capability: { with: subject, can } }) {
super()

this.capability = { with: subject, can }
}
get name() {
return UnsupportedCapabilityErrorName
}
describe() {
return `${this.capability.with} does not have a "${this.capability.can}" capability provider`
}
}
14 changes: 14 additions & 0 deletions packages/filecoin-api/src/storefront/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
QueueOperationFailed,
StoreOperationFailed,
RecordNotFoundErrorName,
UnsupportedCapability,
} from '../errors.js'

/**
Expand Down Expand Up @@ -82,6 +83,13 @@ export const filecoinOffer = async ({ capability }, context) => {
* @returns {Promise<API.UcantoInterface.Result<API.FilecoinSubmitSuccess, API.FilecoinSubmitFailure> | API.UcantoInterface.JoinBuilder<API.FilecoinSubmitSuccess>>}
*/
export const filecoinSubmit = async ({ capability }, context) => {
// Only service principal can perform submit
if (capability.with !== context.id.did()) {
return {
error: new UnsupportedCapability({ capability }),
}
}

const { piece, content } = capability.nb
const group = context.id.did()

Expand Down Expand Up @@ -123,6 +131,12 @@ export const filecoinSubmit = async ({ capability }, context) => {
* @returns {Promise<API.UcantoInterface.Result<API.FilecoinAcceptSuccess, API.FilecoinAcceptFailure>>}
*/
export const filecoinAccept = async ({ capability }, context) => {
// Only service principal can perform accept
if (capability.with !== context.id.did()) {
return {
error: new UnsupportedCapability({ capability }),
}
}
const { piece } = capability.nb
const getPieceRes = await context.pieceStore.get({ piece })
if (getPieceRes.error) {
Expand Down
76 changes: 57 additions & 19 deletions packages/filecoin-api/test/services/aggregator.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { getStoreImplementations } from '../context/store-implementations.js'
import {
QueueOperationErrorName,
StoreOperationErrorName,
UnsupportedCapabilityErrorName,
} from '../../src/errors.js'

/**
Expand All @@ -36,7 +37,7 @@ import {
export const test = {
'piece/offer inserts piece into piece queue if not in piece store and returns effects':
async (assert, context) => {
const { storefront } = await getServiceContext()
const { storefront } = await getServiceContext(context.id)
const connection = connect({
id: context.id,
channel: createServer(context),
Expand Down Expand Up @@ -95,7 +96,7 @@ export const test = {
},
'piece/offer dedupes piece and returns effects without propagating message':
async (assert, context) => {
const { storefront } = await getServiceContext()
const { storefront } = await getServiceContext(context.id)
const connection = connect({
id: context.id,
channel: createServer(context),
Expand Down Expand Up @@ -157,7 +158,7 @@ export const test = {
},
'piece/offer fails if not able to verify piece store': wichMockableContext(
async (assert, context) => {
const { storefront } = await getServiceContext()
const { storefront } = await getServiceContext(context.id)
const connection = connect({
id: context.id,
channel: createServer(context),
Expand Down Expand Up @@ -190,7 +191,7 @@ export const test = {
),
'piece/offer fails if not able to add to piece queue': wichMockableContext(
async (assert, context) => {
const { storefront } = await getServiceContext()
const { storefront } = await getServiceContext(context.id)
const connection = connect({
id: context.id,
channel: createServer(context),
Expand Down Expand Up @@ -221,11 +222,42 @@ export const test = {
pieceQueue: new FailingQueue(),
})
),
'piece/accept must be invoked on service did': async (
assert,
context
) => {
const { agent, storefront } = await getServiceContext(context.id)
const connection = connect({
id: context.id,
channel: createServer(context),
})

// Generate piece for test
const group = storefront.did()
const { pieces } = await randomAggregate(100, 128)
const piece = pieces[0].link

// agent invocation instead of storefront
const pieceAcceptInv = Aggregator.pieceAccept.invoke({
issuer: agent,
audience: connection.id,
with: agent.did(),
nb: {
piece,
group,
},
})

const response = await pieceAcceptInv.execute(connection)
// Validate receipt
assert.ok(response.out.error)
assert.equal(response.out.error?.name, UnsupportedCapabilityErrorName)
},
'piece/accept issues receipt with data aggregation proof': async (
assert,
context
) => {
const { storefront } = await getServiceContext()
const { storefront, aggregator } = await getServiceContext(context.id)
const connection = connect({
id: context.id,
channel: createServer(context),
Expand Down Expand Up @@ -278,11 +310,11 @@ export const test = {
})
assert.ok(inclusionPutRes.ok)

// storefront invocation
// aggregator invocation
const pieceAcceptInv = Aggregator.pieceAccept.invoke({
issuer: storefront,
issuer: aggregator,
audience: connection.id,
with: storefront.did(),
with: aggregator.did(),
nb: {
piece,
group,
Expand Down Expand Up @@ -328,7 +360,7 @@ export const test = {
'piece/accept fails if not able to query inclusion store':
wichMockableContext(
async (assert, context) => {
const { storefront } = await getServiceContext()
const { storefront, aggregator } = await getServiceContext(context.id)
const connection = connect({
id: context.id,
channel: createServer(context),
Expand All @@ -339,11 +371,11 @@ export const test = {
const { pieces } = await randomAggregate(100, 128)
const piece = pieces[0].link

// storefront invocation
// aggregator invocation
const pieceAcceptInv = Aggregator.pieceAccept.invoke({
issuer: storefront,
issuer: aggregator,
audience: connection.id,
with: storefront.did(),
with: aggregator.did(),
nb: {
piece,
group,
Expand All @@ -364,7 +396,7 @@ export const test = {
'piece/accept fails if not able to read from aggregate store':
wichMockableContext(
async (assert, context) => {
const { storefront } = await getServiceContext()
const { storefront, aggregator } = await getServiceContext(context.id)
const connection = connect({
id: context.id,
channel: createServer(context),
Expand Down Expand Up @@ -394,11 +426,11 @@ export const test = {
})
assert.ok(inclusionPutRes.ok)

// storefront invocation
// aggregator invocation
const pieceAcceptInv = Aggregator.pieceAccept.invoke({
issuer: storefront,
issuer: aggregator,
audience: connection.id,
with: storefront.did(),
with: aggregator.did(),
nb: {
piece,
group,
Expand All @@ -418,10 +450,16 @@ export const test = {
),
}

async function getServiceContext() {
const storefront = await Signer.generate()
/**
* @param {Signer.Signer.Signer<`did:${string}:${string}`, Signer.Signer.Crypto.SigAlg>} serviceSigner
*/
async function getServiceContext(serviceSigner) {
const agent = await Signer.generate()
// Storefront and aggregator are today the same DID
const storefront = serviceSigner
const aggregator = serviceSigner

return { storefront }
return { agent, storefront, aggregator }
}

/**
Expand Down
Loading

0 comments on commit 11b00bf

Please sign in to comment.