Skip to content

Commit 7f98636

Browse files
hugomrdiasrvagg
authored andcommitted
feat: better curio error and polling (#344)
closes #331
1 parent 109dbd8 commit 7f98636

File tree

18 files changed

+390
-389
lines changed

18 files changed

+390
-389
lines changed

packages/synapse-core/package.json

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@
3232
"types": "./dist/src/chains.d.ts",
3333
"default": "./dist/src/chains.js"
3434
},
35-
"./curio": {
36-
"types": "./dist/src/curio.d.ts",
37-
"default": "./dist/src/curio.js"
35+
"./sp": {
36+
"types": "./dist/src/sp.d.ts",
37+
"default": "./dist/src/sp.js"
3838
},
3939
"./erc20": {
4040
"types": "./dist/src/erc20.d.ts",
@@ -67,15 +67,19 @@
6767
"./errors": {
6868
"types": "./dist/src/errors/index.d.ts",
6969
"default": "./dist/src/errors/index.js"
70+
},
71+
"./piece": {
72+
"types": "./dist/src/piece.d.ts",
73+
"default": "./dist/src/piece.js"
7074
}
7175
},
7276
"typesVersions": {
7377
"*": {
7478
"chains": [
7579
"./dist/src/chains"
7680
],
77-
"curio": [
78-
"./dist/src/curio"
81+
"sp": [
82+
"./dist/src/sp"
7983
],
8084
"erc20": [
8185
"./dist/src/erc20"
@@ -100,6 +104,9 @@
100104
],
101105
"errors": [
102106
"./dist/src/errors/index"
107+
],
108+
"piece": [
109+
"./dist/src/piece"
103110
]
104111
}
105112
},
@@ -142,7 +149,7 @@
142149
"@web3-storage/data-segment": "^5.3.0",
143150
"dnum": "^2.15.0",
144151
"filsnap-adapter": "^3.3.8",
145-
"iso-web": "^1.4.0",
152+
"iso-web": "^1.4.2",
146153
"multiformats": "^13.4.1",
147154
"ox": "^0.9.12"
148155
},

packages/synapse-core/src/errors/base.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,22 @@ export class SynapseError extends Error {
1818

1919
override name = 'SynapseError'
2020
override cause?: Error
21-
21+
details?: string
2222
shortMessage: string
2323

2424
constructor(message: string, options?: SynapseErrorOptions) {
2525
const details =
2626
options?.cause instanceof Error ? options.cause.message : options?.details ? options.details : undefined
2727

28-
const msg = [message || 'An error occurred.', '', ...(details ? [`Details: ${details}`] : [])].join('\n')
28+
const msg = [
29+
message || 'An error occurred.',
30+
...(details ? [''] : []),
31+
...(details ? [`Details: ${details}`] : []),
32+
].join('\n')
2933
super(msg, options)
3034

3135
this.cause = options?.cause ?? undefined
36+
this.details = details ?? undefined
3237
this.shortMessage = message
3338
}
3439

packages/synapse-core/src/errors/pdp.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { decodePDPError } from '../utils/decode-pdp-errors.ts'
22
import { isSynapseError, SynapseError } from './base.ts'
33

4-
export class InvalidPDPLocationHeaderError extends SynapseError {
5-
override name: 'InvalidPDPLocationHeaderError' = 'InvalidPDPLocationHeaderError'
4+
export class LocationHeaderError extends SynapseError {
5+
override name: 'LocationHeaderError' = 'LocationHeaderError'
66

7-
constructor(location: string) {
8-
super(`Invalid PDP location header format: ${location}`)
7+
constructor(location?: string | null) {
8+
super(`Location header format is invalid: ${location ?? '<none>'}`)
99
}
1010

11-
static override is(value: unknown): value is InvalidPDPLocationHeaderError {
12-
return isSynapseError(value) && value.name === 'InvalidPDPLocationHeaderError'
11+
static override is(value: unknown): value is LocationHeaderError {
12+
return isSynapseError(value) && value.name === 'LocationHeaderError'
1313
}
1414
}
1515

@@ -47,9 +47,8 @@ export class GetDataSetError extends SynapseError {
4747
override name: 'GetDataSetError' = 'GetDataSetError'
4848

4949
constructor(error: string) {
50-
const decodedError = decodePDPError(error)
51-
super(`Failed to get data set.`, {
52-
details: decodedError,
50+
super(error ? 'Failed to get data set.' : 'Data set not found.', {
51+
details: error ? decodePDPError(error) : undefined,
5352
})
5453
}
5554

@@ -63,7 +62,7 @@ export class PostPieceError extends SynapseError {
6362

6463
constructor(error: string) {
6564
const decodedError = decodePDPError(error)
66-
super(`Failed to post piece.`, {
65+
super(`Failed to create upload session.`, {
6766
details: decodedError,
6867
})
6968
}

packages/synapse-core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
export * as abis from './abis/index.ts'
1313
export * as chains from './chains.ts'
1414
export * from './constants.ts'
15-
export * as curio from './curio.ts'
1615
export * as erc20 from './erc20.ts'
1716
export * as errors from './errors/index.ts'
1817
export * as pay from './pay/index.ts'
1918
export * as sessionKey from './session-key/index.ts'
19+
export * as curio from './sp.ts'
2020
export * as typedData from './typed-data/index.ts'
2121
export * as usdfc from './usdfc.ts'
2222
export * from './utils/decode-pdp-errors.ts'

packages/synapse-core/src/piece.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,15 @@ export function getSize(pieceCid: PieceCID): number {
4848
}
4949

5050
export function parse(pieceCid: string): PieceCID {
51-
const cid = CID.parse(pieceCid).toV1()
52-
if (!isPieceCID(cid)) {
53-
throw new Error('Invalid PieceCID: input must be a valid PieceCIDv2')
51+
try {
52+
const cid = CID.parse(pieceCid).toV1()
53+
if (!isPieceCID(cid)) {
54+
throw new Error('Invalid PieceCID: input must be a valid PieceCIDv2')
55+
}
56+
return cid
57+
} catch {
58+
throw new Error(`Invalid CID string: ${pieceCid}`)
5459
}
55-
return cid
5660
}
5761

5862
/**

packages/synapse-core/src/curio.ts renamed to packages/synapse-core/src/sp.ts

Lines changed: 41 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
/**
2-
* Synapse Core - Curio HTTP Operations
2+
* Synapse Core - Service Provider HTTP Operations
33
*
44
* @example
55
* ```ts
6-
* import * as Curio from '@filoz/synapse-core/curio'
6+
* import * as SP from '@filoz/synapse-core/sp'
77
* ```
88
*
99
* @packageDocumentation
1010
*/
1111

12-
import { HttpError, request } from 'iso-web/http'
13-
import type { Simplify as Curio } from 'type-fest'
14-
import type { Address, Hex } from 'viem'
12+
import { HttpError, request, TimeoutError } from 'iso-web/http'
13+
import type { Simplify } from 'type-fest'
14+
import { type Address, type Hex, isHex } from 'viem'
1515
import {
1616
AddPiecesError,
1717
CreateDataSetError,
1818
FindPieceError,
1919
GetDataSetError,
20-
InvalidPDPLocationHeaderError,
20+
LocationHeaderError,
2121
PollDataSetCreationStatusError,
2222
PollForAddPiecesStatusError,
2323
PostPieceError,
@@ -27,10 +27,15 @@ import type { PieceCID } from './piece.ts'
2727
import * as Piece from './piece.ts'
2828
import { createPieceUrl } from './utils/piece-url.ts'
2929

30-
const TIMEOUT = 180000
31-
const RETRIES = Infinity
32-
const FACTOR = 1
33-
const MIN_TIMEOUT = 4000 // interval between retries in milliseconds
30+
let TIMEOUT = 180000
31+
export const RETRIES = Infinity
32+
export const FACTOR = 1
33+
export const MIN_TIMEOUT = 4000 // interval between retries in milliseconds
34+
35+
// Just for testing purposes
36+
export function setTimeout(timeout: number) {
37+
TIMEOUT = timeout
38+
}
3439

3540
/**
3641
* The options for the create data set on PDP API.
@@ -76,14 +81,14 @@ export async function createDataSet(options: PDPCreateDataSetOptions) {
7681
throw response.error
7782
}
7883

79-
const location = response.result.headers.get('Location') ?? ''
80-
const hash = location.split('/').pop()
81-
if (!hash) {
82-
throw new InvalidPDPLocationHeaderError(location)
84+
const location = response.result.headers.get('Location')
85+
const hash = location?.split('/').pop()
86+
if (!location || !hash || !isHex(hash)) {
87+
throw new LocationHeaderError(location)
8388
}
8489

8590
return {
86-
hash: hash as `0x${string}`,
91+
txHash: hash,
8792
statusUrl: new URL(location, options.endpoint).toString(),
8893
}
8994
}
@@ -156,12 +161,14 @@ export type GetDataSetOptions = {
156161
export type GetDataSetResponse = {
157162
id: number
158163
nextChallengeEpoch: number
159-
pieces: CurioPiece[]
164+
pieces: SPPiece[]
160165
}
161166

162-
export type CurioPiece = {
167+
export type SPPiece = {
163168
pieceCid: string
164169
pieceId: number
170+
subPieceCid: string
171+
subPieceOffset: number
165172
}
166173

167174
/**
@@ -184,6 +191,7 @@ export async function getDataSet(options: GetDataSetOptions) {
184191
}
185192
throw response.error
186193
}
194+
187195
return response.result
188196
}
189197

@@ -195,8 +203,8 @@ export type GetPiecesForDataSetOptions = {
195203
cdn: boolean
196204
}
197205

198-
export type CurioPieceWithUrl = Curio<
199-
CurioPiece & {
206+
export type SPPieceWithUrl = Simplify<
207+
SPPiece & {
200208
pieceUrl: string
201209
}
202210
>
@@ -254,7 +262,6 @@ export async function uploadPiece(options: UploadPieceOptions) {
254262
headers: {
255263
'Content-Type': 'application/json',
256264
},
257-
timeout: TIMEOUT,
258265
})
259266

260267
if (response.error) {
@@ -272,10 +279,10 @@ export async function uploadPiece(options: UploadPieceOptions) {
272279
}
273280

274281
// Extract upload ID from Location header
275-
const location = response.result.headers.get('Location') ?? ''
276-
const uploadUuid = location.split('/').pop()
277-
if (uploadUuid == null) {
278-
throw new InvalidPDPLocationHeaderError(location)
282+
const location = response.result.headers.get('Location')
283+
const uploadUuid = location?.split('/').pop()
284+
if (!location || !uploadUuid) {
285+
throw new LocationHeaderError(location)
279286
}
280287

281288
const uploadResponse = await request.put(new URL(`pdp/piece/upload/${uploadUuid}`, options.endpoint), {
@@ -284,6 +291,7 @@ export async function uploadPiece(options: UploadPieceOptions) {
284291
'Content-Type': 'application/octet-stream',
285292
'Content-Length': options.data.length.toString(),
286293
},
294+
timeout: false,
287295
})
288296

289297
if (uploadResponse.error) {
@@ -319,22 +327,21 @@ export async function findPiece(options: FindPieceOptions): Promise<PieceCID> {
319327
const params = new URLSearchParams({ pieceCid: pieceCid.toString() })
320328

321329
const response = await request.json.get<{ pieceCid: string }>(new URL(`pdp/piece?${params.toString()}`, endpoint), {
322-
onResponse(response) {
323-
if (!response.ok) {
324-
throw new Error(`Piece not found: ${pieceCid.toString()}`)
325-
}
326-
},
327330
retry: {
331+
statusCodes: [404],
328332
retries: RETRIES,
329333
factor: FACTOR,
330334
},
331335
timeout: TIMEOUT,
332336
})
333337

334338
if (response.error) {
335-
if (response.error instanceof HttpError) {
339+
if (HttpError.is(response.error)) {
336340
throw new FindPieceError(await response.error.response.text())
337341
}
342+
if (TimeoutError.is(response.error)) {
343+
throw new FindPieceError('Timeout waiting for piece to be found')
344+
}
338345
throw response.error
339346
}
340347
const data = response.result
@@ -344,7 +351,6 @@ export async function findPiece(options: FindPieceOptions): Promise<PieceCID> {
344351
export type AddPiecesOptions = {
345352
endpoint: string
346353
dataSetId: bigint
347-
clientDataSetId: bigint
348354
nextPieceId: bigint
349355
pieces: PieceCID[]
350356
extraData: Hex
@@ -358,7 +364,6 @@ export type AddPiecesOptions = {
358364
* @param options - The options for the add pieces.
359365
* @param options.endpoint - The endpoint of the PDP API.
360366
* @param options.dataSetId - The ID of the data set.
361-
* @param options.clientDataSetId - The ID of the client data set.
362367
* @param options.nextPieceId - The next piece ID.
363368
* @param options.pieces - The pieces to add.
364369
* @param options.extraData - The extra data for the add pieces.
@@ -386,10 +391,10 @@ export async function addPieces(options: AddPiecesOptions) {
386391
}
387392
throw response.error
388393
}
389-
const location = response.result.headers.get('Location') ?? ''
390-
const txHash = location.split('/').pop()
391-
if (!txHash) {
392-
throw new InvalidPDPLocationHeaderError(location)
394+
const location = response.result.headers.get('Location')
395+
const txHash = location?.split('/').pop()
396+
if (!location || !txHash || !isHex(txHash)) {
397+
throw new LocationHeaderError(location)
393398
}
394399

395400
return {

packages/synapse-core/src/utils/decode-pdp-errors.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export function decodePDPError(error: string) {
5555
} else if (extractedContent?.startsWith('Error(')) {
5656
return `\n${extractedContent.replace('Error(', '').replace(')', '')}`
5757
} else {
58-
return `Curio PDP\n${error}`
58+
return `Service Provider PDP\n${error}`
5959
}
6060
}
6161

@@ -80,9 +80,9 @@ function formatPDPError(error: { abiItem: AbiError; args: readonly unknown[] | u
8080
: undefined
8181

8282
return [
83-
errorWithParams ? `Error: ${errorWithParams}` : '',
83+
errorWithParams ? `${errorWithParams}` : '',
8484
formattedArgs && formattedArgs !== '()'
85-
? ` ${[...Array(error.errorName?.length ?? 0).keys()].map(() => ' ').join('')}${formattedArgs}`
85+
? `${[...Array(error.errorName?.length ?? 0).keys()].map(() => ' ').join('')}${formattedArgs}`
8686
: '',
8787
].join('\n')
8888
}

packages/synapse-core/src/warm-storage/data-sets.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { type Account, type Address, type Chain, type Client, isAddressEqual, ty
33
import { multicall, readContract } from 'viem/actions'
44
import type * as Abis from '../abis/index.ts'
55
import { getChain } from '../chains.ts'
6-
import * as PDP from '../curio.ts'
6+
import * as PDP from '../sp.ts'
77
import { signCreateDataSet } from '../typed-data/sign-create-dataset.ts'
88
import { datasetMetadataObjectToEntry, type MetadataObject, metadataArrayToObject } from '../utils/metadata.ts'
99
import type { PDPProvider } from './providers.ts'

packages/synapse-core/src/warm-storage/upload.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Account, Chain, Client, Transport } from 'viem'
22
import { readContract } from 'viem/actions'
33
import { getChain } from '../chains.ts'
4-
import * as PDP from '../curio.ts'
4+
import * as PDP from '../sp.ts'
55
import { signAddPieces } from '../typed-data/sign-add-pieces.ts'
66
import { pieceMetadataObjectToEntry } from '../utils/metadata.ts'
77

@@ -55,7 +55,6 @@ export async function upload(client: Client<Transport, Chain, Account>, options:
5555

5656
const addPieces = await PDP.addPieces({
5757
dataSetId: options.dataSetId,
58-
clientDataSetId: dataSet.clientDataSetId,
5958
nextPieceId: nextPieceId,
6059
pieces: uploadResponses.map((response) => response.pieceCid),
6160
endpoint: provider[0].serviceURL,

0 commit comments

Comments
 (0)