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: filecoin info #1091

Merged
merged 2 commits into from
Nov 8, 2023
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
1 change: 1 addition & 0 deletions packages/capabilities/src/filecoin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export {
filecoinOffer as offer,
filecoinSubmit as submit,
filecoinAccept as accept,
filecoinInfo as info,
} from './storefront.js'
27 changes: 27 additions & 0 deletions packages/capabilities/src/filecoin/storefront.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,30 @@ export const filecoinAccept = capability({
)
},
})

/**
* Capability allowing an agent to _request_ info about a content piece in
* Filecoin deals.
*/
export const filecoinInfo = capability({
can: 'filecoin/info',
/**
* DID of the space the content is stored in.
*/
with: Schema.did(),
nb: Schema.struct({
/**
* CID of the piece.
*
* @see https://github.com/filecoin-project/FIPs/pull/758/files
*/
piece: PieceLink,
}),
derives: (claim, from) => {
return (
and(equalWith(claim, from)) ||
and(checkLink(claim.nb.piece, from.nb.piece, 'nb.piece')) ||
ok({})
)
},
})
1 change: 1 addition & 0 deletions packages/capabilities/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export const abilitiesAsStrings = [
Storefront.filecoinOffer.can,
Storefront.filecoinSubmit.can,
Storefront.filecoinAccept.can,
Storefront.filecoinInfo.can,
Aggregator.pieceOffer.can,
Aggregator.pieceAccept.can,
Dealer.aggregateOffer.can,
Expand Down
19 changes: 19 additions & 0 deletions packages/capabilities/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,21 @@ export interface ProofNotFound extends Ucanto.Failure {
name: 'ProofNotFound'
}

export interface FilecoinInfoSuccess {
piece: PieceLink
deals: FilecoinInfoAcceptedDeal[]
}
export interface FilecoinInfoAcceptedDeal
extends DataAggregationProof,
DealDetails {
aggregate: PieceLink
}

export type FilecoinInfoFailure =
| ContentNotFound
| InvalidContentPiece
| Ucanto.Failure

// filecoin aggregator
export interface PieceOfferSuccess {
/**
Expand Down Expand Up @@ -549,6 +564,9 @@ export type FilecoinSubmit = InferInvokedCapability<
export type FilecoinAccept = InferInvokedCapability<
typeof StorefrontCaps.filecoinAccept
>
export type FilecoinInfo = InferInvokedCapability<
typeof StorefrontCaps.filecoinInfo
>
export type PieceOffer = InferInvokedCapability<
typeof AggregatorCaps.pieceOffer
>
Expand Down Expand Up @@ -610,6 +628,7 @@ export type AbilitiesArray = [
FilecoinOffer['can'],
FilecoinSubmit['can'],
FilecoinAccept['can'],
FilecoinInfo['can'],
PieceOffer['can'],
PieceAccept['can'],
AggregateOffer['can'],
Expand Down
5 changes: 5 additions & 0 deletions packages/filecoin-api/src/storefront/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { PieceLink } from '@web3-storage/data-segment'
import {
AggregatorService,
StorefrontService,
DealTrackerService,
} from '@web3-storage/filecoin-client/types'
import {
Store,
Expand Down Expand Up @@ -64,6 +65,10 @@ export interface ServiceContext {
* Stores receipts for tasks.
*/
receiptStore: ReceiptStore
/**
* Deal tracker connection to find out available deals for an aggregate.
*/
dealTrackerService: ServiceConfig<DealTrackerService>
/**
* Service options.
*/
Expand Down
99 changes: 98 additions & 1 deletion packages/filecoin-api/src/storefront/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ import * as Client from '@ucanto/client'
import * as CAR from '@ucanto/transport/car'
import * as StorefrontCaps from '@web3-storage/capabilities/filecoin/storefront'
import * as AggregatorCaps from '@web3-storage/capabilities/filecoin/aggregator'
import { DealTracker } from '@web3-storage/filecoin-client'
// eslint-disable-next-line no-unused-vars
import * as API from '../types.js'
import { QueueOperationFailed, StoreOperationFailed } from '../errors.js'
import {
QueueOperationFailed,
StoreOperationFailed,
RecordNotFoundErrorName,
} from '../errors.js'

/**
* @param {API.Input<StorefrontCaps.filecoinOffer>} input
Expand Down Expand Up @@ -226,6 +231,94 @@ async function findDataAggregationProof({ taskStore, receiptStore }, task) {
}
}

/**
* @param {API.Input<StorefrontCaps.filecoinInfo>} input
* @param {import('./api.js').ServiceContext} context
* @returns {Promise<API.UcantoInterface.Result<API.FilecoinInfoSuccess, API.FilecoinInfoFailure> | API.UcantoInterface.JoinBuilder<API.FilecoinInfoSuccess>>}
*/
export const filecoinInfo = async ({ capability }, context) => {
const { piece } = capability.nb

// Get piece in store
const getPiece = await context.pieceStore.get({ piece })
if (getPiece.error && getPiece.error.name === RecordNotFoundErrorName) {
return {
error: getPiece.error,
}
} else if (getPiece.error) {
return { error: new StoreOperationFailed(getPiece.error.message) }
}

// Check if `piece/accept` receipt exists to get to know aggregate where it is included on a deal
const pieceAcceptInvocation = await StorefrontCaps.filecoinAccept
.invoke({
issuer: context.id,
audience: context.id,
with: context.id.did(),
nb: {
piece,
content: getPiece.ok.content,
},
expiration: Infinity,
})
.delegate()

const pieceAcceptReceiptGet = await context.receiptStore.get(
pieceAcceptInvocation.link()
)
if (pieceAcceptReceiptGet.error) {
/** @type {API.UcantoInterface.OkBuilder<API.FilecoinInfoSuccess, API.FilecoinInfoFailure>} */
const processingResult = Server.ok({
piece,
deals: [],
})
return processingResult
}

const pieceAcceptOut = /** @type {API.FilecoinAcceptSuccess} */ (
pieceAcceptReceiptGet.ok?.out.ok
)

// Query current info of aggregate from deal tracker
const info = await DealTracker.dealInfo(
context.dealTrackerService.invocationConfig,
pieceAcceptOut.aggregate,
{ connection: context.dealTrackerService.connection }
)

if (info.out.error) {
return {
error: info.out.error,
}
}
const deals = Object.entries(info.out.ok.deals || {})
if (!deals.length) {
// Should not happen if there is `piece/accept` receipt
return {
error: new Server.Failure(
`no deals were obtained for aggregate ${pieceAcceptOut.aggregate} where piece ${piece} is included`
),
}
}

/** @type {API.UcantoInterface.OkBuilder<API.FilecoinInfoSuccess, API.FilecoinInfoFailure>} */
const result = Server.ok({
piece,
deals: deals.map(([dealId, dealDetails]) => ({
aggregate: pieceAcceptOut.aggregate,
provider: dealDetails.provider,
inclusion: pieceAcceptOut.inclusion,
aux: {
dataType: 0n,
dataSource: {
dealID: BigInt(dealId),
},
},
})),
})
return result
}

export const ProofNotFoundName = /** @type {const} */ ('ProofNotFound')
export class ProofNotFound extends Server.Failure {
get reason() {
Expand Down Expand Up @@ -255,6 +348,10 @@ export function createService(context) {
capability: StorefrontCaps.filecoinAccept,
handler: (input) => filecoinAccept(input, context),
}),
info: Server.provideAdvanced({
capability: StorefrontCaps.filecoinInfo,
handler: (input) => filecoinInfo(input, context),
}),
},
}
}
Expand Down
94 changes: 94 additions & 0 deletions packages/filecoin-api/test/context/receipts.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Receipt } from '@ucanto/core'
import * as StorefrontCaps from '@web3-storage/capabilities/filecoin/storefront'
import * as AggregatorCaps from '@web3-storage/capabilities/filecoin/aggregator'
import * as DealerCaps from '@web3-storage/capabilities/filecoin/dealer'

Expand All @@ -12,6 +13,7 @@ import * as API from '../../src/types.js'
* @param {API.PieceLink} context.aggregate
* @param {string} context.group
* @param {API.PieceLink} context.piece
* @param {API.CARLink} context.content
* @param {import('@ucanto/interface').Block} context.piecesBlock
* @param {API.InclusionProof} context.inclusionProof
* @param {API.AggregateAcceptSuccess} context.aggregateAcceptStatus
Expand All @@ -23,10 +25,47 @@ export async function createInvocationsAndReceiptsForDealDataProofChain({
aggregate,
group,
piece,
content,
piecesBlock,
inclusionProof,
aggregateAcceptStatus,
}) {
const filecoinOfferInvocation = await StorefrontCaps.filecoinOffer
.invoke({
issuer: storefront,
audience: storefront,
with: storefront.did(),
nb: {
piece,
content,
},
expiration: Infinity,
})
.delegate()
const filecoinSubmitInvocation = await StorefrontCaps.filecoinSubmit
.invoke({
issuer: storefront,
audience: storefront,
with: storefront.did(),
nb: {
piece,
content,
},
expiration: Infinity,
})
.delegate()
const filecoinAcceptInvocation = await StorefrontCaps.filecoinAccept
.invoke({
issuer: storefront,
audience: storefront,
with: storefront.did(),
nb: {
piece,
content,
},
expiration: Infinity,
})
.delegate()
const pieceOfferInvocation = await AggregatorCaps.pieceOffer
.invoke({
issuer: storefront,
Expand Down Expand Up @@ -76,6 +115,55 @@ export async function createInvocationsAndReceiptsForDealDataProofChain({
expiration: Infinity,
})
.delegate()

// Receipts
const filecoinOfferReceipt = await Receipt.issue({
issuer: storefront,
ran: filecoinOfferInvocation.cid,
result: {
ok: /** @type {API.FilecoinOfferSuccess} */ ({
piece,
}),
},
fx: {
join: filecoinAcceptInvocation.cid,
fork: [filecoinSubmitInvocation.cid],
},
})

const filecoinSubmitReceipt = await Receipt.issue({
issuer: storefront,
ran: filecoinSubmitInvocation.cid,
result: {
ok: /** @type {API.FilecoinSubmitSuccess} */ ({
piece,
}),
},
fx: {
join: pieceOfferInvocation.cid,
fork: [],
},
})

const filecoinAcceptReceipt = await Receipt.issue({
issuer: storefront,
ran: filecoinAcceptInvocation.cid,
result: {
ok: /** @type {API.FilecoinAcceptSuccess} */ ({
piece,
aggregate,
inclusion: inclusionProof,
aux: {
...aggregateAcceptStatus,
},
}),
},
fx: {
join: undefined,
fork: [],
},
})

const pieceOfferReceipt = await Receipt.issue({
issuer: aggregator,
ran: pieceOfferInvocation.cid,
Expand Down Expand Up @@ -130,12 +218,18 @@ export async function createInvocationsAndReceiptsForDealDataProofChain({

return {
invocations: {
filecoinOfferInvocation,
filecoinSubmitInvocation,
filecoinAcceptInvocation,
pieceOfferInvocation,
pieceAcceptInvocation,
aggregateOfferInvocation,
aggregateAcceptInvocation,
},
receipts: {
filecoinOfferReceipt,
filecoinSubmitReceipt,
filecoinAcceptReceipt,
pieceOfferReceipt,
pieceAcceptReceipt,
aggregateOfferReceipt,
Expand Down
8 changes: 4 additions & 4 deletions packages/filecoin-api/test/context/store.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as API from '../../src/types.js'
import { RecordNotFound, StoreOperationFailed } from '../../src/errors.js'
import { StoreOperationFailed, RecordNotFound } from '../../src/errors.js'

/**
* @typedef {import('../../src/types.js').StorePutError} StorePutError
Expand Down Expand Up @@ -47,7 +47,7 @@ export class Store {
const t = this.getFn(this.items, item)
if (!t) {
return {
error: new RecordNotFound(),
error: new RecordNotFound('not found'),
}
}
return {
Expand Down Expand Up @@ -85,7 +85,7 @@ export class Store {
const t = this.queryFn(this.items, search)
if (!t) {
return {
error: new RecordNotFound(),
error: new RecordNotFound('not found'),
}
}
return {
Expand Down Expand Up @@ -123,7 +123,7 @@ export class UpdatableStore extends Store {
const t = this.updateFn(this.items, key, item)
if (!t) {
return {
error: new RecordNotFound(),
error: new RecordNotFound('not found'),
}
}
return {
Expand Down
Loading
Loading