Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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/synapse-core/src/mocks/jsonrpc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ export const presets = {
getDataSetStorageProvider: () => [ADDRESSES.serviceProvider1, ADDRESSES.zero],
getDataSetLeafCount: () => [0n],
getScheduledRemovals: () => [[]],
getNextChallengeEpoch: () => [5000n],
},
serviceRegistry: {
registerProvider: () => [1n],
Expand Down
13 changes: 13 additions & 0 deletions packages/synapse-core/src/mocks/jsonrpc/pdp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type getActivePieces = ExtractAbiFunction<typeof Abis.pdp, 'getActivePiec
export type getDataSetStorageProvider = ExtractAbiFunction<typeof Abis.pdp, 'getDataSetStorageProvider'>
export type getDataSetLeafCount = ExtractAbiFunction<typeof Abis.pdp, 'getDataSetLeafCount'>
export type getScheduledRemovals = ExtractAbiFunction<typeof Abis.pdp, 'getScheduledRemovals'>
export type getNextChallengeEpoch = ExtractAbiFunction<typeof Abis.pdp, 'getNextChallengeEpoch'>

export interface PDPVerifierOptions {
dataSetLive?: (args: AbiToType<dataSetLive['inputs']>) => AbiToType<dataSetLive['outputs']>
Expand All @@ -25,6 +26,9 @@ export interface PDPVerifierOptions {
) => AbiToType<getDataSetStorageProvider['outputs']>
getDataSetLeafCount?: (args: AbiToType<getDataSetLeafCount['inputs']>) => AbiToType<getDataSetLeafCount['outputs']>
getScheduledRemovals?: (args: AbiToType<getScheduledRemovals['inputs']>) => AbiToType<getScheduledRemovals['outputs']>
getNextChallengeEpoch?: (
args: AbiToType<getNextChallengeEpoch['inputs']>
) => AbiToType<getNextChallengeEpoch['outputs']>
}

/**
Expand Down Expand Up @@ -111,6 +115,15 @@ export function pdpVerifierCallHandler(data: Hex, options: JSONRPCOptions): Hex
options.pdpVerifier.getScheduledRemovals(args)
)
}
case 'getNextChallengeEpoch': {
if (!options.pdpVerifier?.getNextChallengeEpoch) {
throw new Error('PDP Verifier: getNextChallengeEpoch is not defined')
}
return encodeAbiParameters(
Abis.pdp.find((abi) => abi.type === 'function' && abi.name === 'getNextChallengeEpoch')!.outputs,
options.pdpVerifier.getNextChallengeEpoch(args)
)
}
default: {
throw new Error(`PDP Verifier: unknown function: ${functionName} with args: ${args}`)
}
Expand Down
117 changes: 117 additions & 0 deletions packages/synapse-core/src/pdp-verifier/get-next-challenge-epoch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import type { Simplify } from 'type-fest'
import type {
Address,
Chain,
Client,
ContractFunctionParameters,
ContractFunctionReturnType,
ReadContractErrorType,
Transport,
} from 'viem'
import { readContract } from 'viem/actions'
import type { pdpVerifierAbi } from '../abis/generated.ts'
import { asChain } from '../chains.ts'
import type { ActionCallChain } from '../types.ts'

export namespace getNextChallengeEpoch {
export type OptionsType = {
/** The ID of the data set to get next challenge epoch for. */
dataSetId: bigint
/** PDP Verifier contract address. If not provided, the default is the PDP Verifier contract address for the chain. */
contractAddress?: Address
}

export type OutputType = bigint
/**
* `uint256`
*/
export type ContractOutputType = ContractFunctionReturnType<
typeof pdpVerifierAbi,
'pure' | 'view',
'getNextChallengeEpoch'
>

export type ErrorType = asChain.ErrorType | ReadContractErrorType
}

/**
* Get next challenge epoch
*
* @example
* ```ts
* import { getNextChallengeEpoch } from '@filoz/synapse-core/pdp-verifier'
* import { calibration } from '@filoz/synapse-core/chains'
* import { createPublicClient, http } from 'viem'
*
* const client = createPublicClient({
* chain: calibration,
* transport: http(),
* })
*
* const nextChallengeEpoch = await getNextChallengeEpoch(client, {
* dataSetId: 1n,
* })
* ```
*
* @param client - The client to use to get the active pieces.
* @param options - {@link getNextChallengeEpoch.OptionsType}
* @returns The next challenge epoch for the data set {@link getNextChallengeEpoch.OutputType}
* @throws Errors {@link getNextChallengeEpoch.ErrorType}
*/
export async function getNextChallengeEpoch(
client: Client<Transport, Chain>,
options: getNextChallengeEpoch.OptionsType
): Promise<getNextChallengeEpoch.OutputType> {
const data = await readContract(
client,
getNextChallengeEpochCall({
chain: client.chain,
dataSetId: options.dataSetId,
contractAddress: options.contractAddress,
})
)
return data
}

export namespace getNextChallengeEpochCall {
export type OptionsType = Simplify<getNextChallengeEpoch.OptionsType & ActionCallChain>
export type ErrorType = asChain.ErrorType
export type OutputType = ContractFunctionParameters<typeof pdpVerifierAbi, 'pure' | 'view', 'getNextChallengeEpoch'>
}

/**
* Create a call to the {@link getNextChallengeEpoch} function for use with the multicall or readContract function.
*
* @example
* ```ts
* import { getNextChallengeEpochCall } from '@filoz/synapse-core/pdp-verifier'
* import { calibration } from '@filoz/synapse-core/chains'
* import { createPublicClient, http } from 'viem'
* import { multicall } from 'viem/actions'
*
* const client = createPublicClient({
* chain: calibration,
* transport: http(),
* })
*
* const results = await multicall(client, {
* contracts: [
* getNextChallengeEpochCall({ chain: calibration, dataSetId: 1n }),
* getNextChallengeEpochCall({ chain: calibration, dataSetId: 101n }),
* ],
* })
* ```
*
* @param options - {@link getNextChallengeEpochCall.OptionsType}
* @returns The call to the getNextChallengeEpoch function {@link getNextChallengeEpochCall.OutputType}
* @throws Errors {@link getNextChallengeEpochCall.ErrorType}
*/
export function getNextChallengeEpochCall(options: getNextChallengeEpochCall.OptionsType) {
const chain = asChain(options.chain)
return {
abi: chain.contracts.pdp.abi,
address: options.contractAddress ?? chain.contracts.pdp.address,
functionName: 'getNextChallengeEpoch',
args: [options.dataSetId],
} satisfies getNextChallengeEpochCall.OutputType
}
1 change: 1 addition & 0 deletions packages/synapse-core/src/pdp-verifier/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export * from './get-data-set-leaf-count.ts'
export * from './get-data-set-listener.ts'
export * from './get-data-set-storage-provider.ts'
export * from './get-dataset-size.ts'
export * from './get-next-challenge-epoch.ts'
export * from './get-next-piece-id.ts'
export * from './get-pieces.ts'
export * from './get-scheduled-removals.ts'
Expand Down
161 changes: 62 additions & 99 deletions packages/synapse-sdk/src/storage/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1090,31 +1090,6 @@ export class StorageContext {
return hash
}

/**
* Check if a piece exists on this service provider.
*
* @param options - Options for the has piece operation
* @param options.pieceCid - The PieceCID (piece CID) to check
* @returns True if the piece exists on this provider, false otherwise
*/
async hasPiece(options: { pieceCid: string | PieceCID }): Promise<boolean> {
const { pieceCid } = options
const parsedPieceCID = Piece.asPieceCID(pieceCid)
if (parsedPieceCID == null) {
return false
}

try {
await SP.findPiece({
serviceURL: this._pdpEndpoint,
pieceCid: parsedPieceCID,
})
return true
} catch {
return false
}
}

Comment thread
juliangruber marked this conversation as resolved.
/**
* Check if a piece exists on this service provider and get its proof status.
* Also returns timing information about when the piece was last proven and when the next
Expand All @@ -1126,9 +1101,9 @@ export class StorageContext {
*
* @param options - Options for the piece status
* @param options.pieceCid - The PieceCID (piece CID) to check
* @returns Status information including existence, data set timing, and retrieval URL
* @returns Status information including data set timing and retrieval URL
*/
async pieceStatus(options: { pieceCid: string | PieceCID }): Promise<PieceStatus> {
async pieceStatus(options: { pieceCid: string | PieceCID }): Promise<PieceStatus | null> {
if (this.dataSetId == null) {
throw createError('StorageContext', 'pieceStatus', 'Data set not found')
}
Expand All @@ -1138,18 +1113,26 @@ export class StorageContext {
}

// Run multiple operations in parallel for better performance
const [exists, dataSetData, currentEpoch] = await Promise.all([
// Check if piece exists on provider
this.hasPiece({ pieceCid: parsedPieceCID }),
// Get data set data
SP.getDataSet({
serviceURL: this._pdpEndpoint,
const [activePieces, nextChallengeEpoch, currentEpoch, pdpConfig, providerInfo] = await Promise.all([
PDPVerifier.getActivePieces(this._client, {
dataSetId: this.dataSetId,
}),
PDPVerifier.getNextChallengeEpoch(this._client, {
dataSetId: this.dataSetId,
}),
// Get current epoch
getBlockNumber(this._client),
this._warmStorageService.getPDPConfig().catch((error) => {
console.debug('Failed to get PDP config:', error)
return null
}),
this.getProviderInfo().catch(() => null),
])

const pieceData = activePieces.pieces.find((piece) => piece.cid.equals(parsedPieceCID))
if (pieceData === undefined) {
return null
}

// Initialize return values
let retrievalUrl: string | null = null
let pieceId: bigint | undefined
Expand All @@ -1159,80 +1142,60 @@ export class StorageContext {
let hoursUntilChallengeWindow = 0
let isProofOverdue = false

// If piece exists, get provider info for retrieval URL and proving params in parallel
if (exists) {
const [providerInfo, pdpConfig] = await Promise.all([
// Get provider info for retrieval URL
this.getProviderInfo().catch(() => null),
dataSetData != null
? this._warmStorageService.getPDPConfig().catch((error) => {
console.debug('Failed to get PDP config:', error)
return null
})
: Promise.resolve(null),
])

// Set retrieval URL if we have provider info
if (providerInfo != null) {
retrievalUrl = createPieceUrlPDP({
cid: parsedPieceCID.toString(),
serviceURL: providerInfo.pdp.serviceURL,
})
}
// Set retrieval URL if we have provider info
if (providerInfo != null) {
retrievalUrl = createPieceUrlPDP({
cid: parsedPieceCID.toString(),
serviceURL: providerInfo.pdp.serviceURL,
})
}

// Process proof timing data if we have data set data and PDP config
if (dataSetData != null && pdpConfig != null) {
// Check if this PieceCID is in the data set
const pieceData = dataSetData.pieces.find((piece) => piece.pieceCid.toString() === parsedPieceCID.toString())

if (pieceData != null) {
pieceId = pieceData.pieceId

// Calculate timing based on nextChallengeEpoch
if (dataSetData.nextChallengeEpoch > 0) {
// nextChallengeEpoch is when the challenge window STARTS, not ends!
// The proving deadline is nextChallengeEpoch + challengeWindowSize
const challengeWindowStart = dataSetData.nextChallengeEpoch
const provingDeadline = challengeWindowStart + Number(pdpConfig.challengeWindowSize)

// Calculate when the next proof is due (end of challenge window)
nextProofDue = epochToDate(provingDeadline, this._chain.genesisTimestamp)

// Calculate last proven date (one proving period before next challenge)
const lastProvenDate = calculateLastProofDate(
dataSetData.nextChallengeEpoch,
Number(pdpConfig.maxProvingPeriod),
this._chain.genesisTimestamp
)
if (lastProvenDate != null) {
lastProven = lastProvenDate
}
// Process proof timing data if we have data set data and PDP config
if (pdpConfig != null) {
pieceId = pieceData.id

// Calculate timing based on nextChallengeEpoch
if (nextChallengeEpoch > 0n) {
// nextChallengeEpoch is when the challenge window STARTS, not ends!
// The proving deadline is nextChallengeEpoch + challengeWindowSize
const challengeWindowStart = nextChallengeEpoch
const provingDeadline = challengeWindowStart + pdpConfig.challengeWindowSize

// Calculate when the next proof is due (end of challenge window)
nextProofDue = epochToDate(Number(provingDeadline), this._chain.genesisTimestamp)

// Calculate last proven date (one proving period before next challenge)
const lastProvenDate = calculateLastProofDate(
Number(nextChallengeEpoch),
Number(pdpConfig.maxProvingPeriod),
this._chain.genesisTimestamp
)
if (lastProvenDate != null) {
lastProven = lastProvenDate
}

// Check if we're in the challenge window
inChallengeWindow = Number(currentEpoch) >= challengeWindowStart && Number(currentEpoch) < provingDeadline
// Check if we're in the challenge window
inChallengeWindow = Number(currentEpoch) >= challengeWindowStart && Number(currentEpoch) < provingDeadline

// Check if proof is overdue (past the proving deadline)
isProofOverdue = Number(currentEpoch) >= provingDeadline
// Check if proof is overdue (past the proving deadline)
isProofOverdue = Number(currentEpoch) >= provingDeadline

// Calculate hours until challenge window starts (only if before challenge window)
if (Number(currentEpoch) < challengeWindowStart) {
const timeUntil = timeUntilEpoch(challengeWindowStart, Number(currentEpoch))
hoursUntilChallengeWindow = timeUntil.hours
}
} else {
// If nextChallengeEpoch is 0, it might mean:
// 1. Proof was just submitted and system is updating
// 2. Data set is not active
// In case 1, we might have just proven, so set lastProven to very recent
// This is a temporary state and should resolve quickly
console.debug('Data set has nextChallengeEpoch=0, may have just been proven')
}
// Calculate hours until challenge window starts (only if before challenge window)
if (Number(currentEpoch) < challengeWindowStart) {
const timeUntil = timeUntilEpoch(Number(challengeWindowStart), Number(currentEpoch))
hoursUntilChallengeWindow = timeUntil.hours
}
} else {
// If nextChallengeEpoch is 0, it might mean:
// 1. Proof was just submitted and system is updating
// 2. Data set is not active
// In case 1, we might have just proven, so set lastProven to very recent
// This is a temporary state and should resolve quickly
console.debug('Data set has nextChallengeEpoch=0, may have just been proven')
}
}

return {
exists,
dataSetLastProven: lastProven,
dataSetNextProofDue: nextProofDue,
retrievalUrl,
Expand Down
2 changes: 1 addition & 1 deletion packages/synapse-sdk/src/storage/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ import type {
UploadCosts,
UploadResult,
} from '../types.ts'
import { combineMetadata, createError, METADATA_KEYS, SIZE_CONSTANTS, TIME_CONSTANTS } from '../utils/index.ts'
import { combineMetadata, createError, SIZE_CONSTANTS, TIME_CONSTANTS } from '../utils/index.ts'
import type { WarmStorageService } from '../warm-storage/index.ts'
import { StorageContext } from './context.ts'

Expand Down
Loading
Loading