From 36b0fe5b7181f39cac7e64824722b85242de0ebf Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 22 Sep 2025 12:39:23 -0400 Subject: [PATCH 1/2] improve(SDKProvider): Retry when `getBlockTime(slot)` returns null When `null` is returned, retrying should succeed sometimes --- src/arch/svm/SpokeUtils.ts | 42 +++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/arch/svm/SpokeUtils.ts b/src/arch/svm/SpokeUtils.ts index 7826de81e..76693ea1e 100644 --- a/src/arch/svm/SpokeUtils.ts +++ b/src/arch/svm/SpokeUtils.ts @@ -165,6 +165,27 @@ async function _callGetTimestampForSlotWithRetry( // @note: getBlockTime receives a slot number, not a block number. let _timestamp: bigint; + const retryCall = async () => { + const slot = slotNumber.toString(); + // Implement exponential backoff with jitter where the # of seconds to wait is = 2^retryAttempt + jitter + // e.g. First two retry delays are ~1.5s and ~2.5s. + const delaySeconds = 2 ** retryAttempt + Math.random(); + + if (retryAttempt >= maxRetries) { + throw new Error(`Timeout on SVM getBlockTime() for slot ${slot} after ${retryAttempt} retry attempts`); + } + logger?.debug({ + at: "getTimestampForSlot", + message: `Retrying getBlockTime() after ${delaySeconds} seconds for retry attempt #${retryAttempt}`, + slot, + retryAttempt, + maxRetries, + delaySeconds, + }); + await delay(delaySeconds); + return _callGetTimestampForSlotWithRetry(provider, slotNumber, ++retryAttempt, maxRetries, logger); + }; + try { _timestamp = await provider.getBlockTime(slotNumber).send(); } catch (err) { @@ -180,22 +201,7 @@ async function _callGetTimestampForSlotWithRetry( return undefined; case SVM_BLOCK_NOT_AVAILABLE: { - // Implement exponential backoff with jitter where the # of seconds to wait is = 2^retryAttempt + jitter - // e.g. First two retry delays are ~1.5s and ~2.5s. - const delaySeconds = 2 ** retryAttempt + Math.random(); - if (retryAttempt >= maxRetries) { - throw new Error(`Timeout on SVM getBlockTime() for slot ${slot} after ${retryAttempt} retry attempts`); - } - logger?.debug({ - at: "getTimestampForSlot", - message: `Retrying getBlockTime() after ${delaySeconds} seconds for retry attempt #${retryAttempt}`, - slot, - retryAttempt, - maxRetries, - delaySeconds, - }); - await delay(delaySeconds); - return _callGetTimestampForSlotWithRetry(provider, slotNumber, ++retryAttempt, maxRetries, logger); + return await retryCall(); } default: @@ -211,6 +217,10 @@ async function _callGetTimestampForSlotWithRetry( } } + // If the timestamp is null, retry the call because the provider returned an unexpected null. + if (_timestamp === null) { + return await retryCall(); + } const timestamp = Number(_timestamp); assert(BigInt(timestamp) === _timestamp, `Unexpected SVM block timestamp: ${_timestamp}`); // No truncation. From 2c105a87712bfb9b50eee7df9d1069c79a6b32ba Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 22 Sep 2025 12:42:30 -0400 Subject: [PATCH 2/2] Update SpokeUtils.ts --- src/arch/svm/SpokeUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arch/svm/SpokeUtils.ts b/src/arch/svm/SpokeUtils.ts index 76693ea1e..ba09e5b0b 100644 --- a/src/arch/svm/SpokeUtils.ts +++ b/src/arch/svm/SpokeUtils.ts @@ -217,7 +217,7 @@ async function _callGetTimestampForSlotWithRetry( } } - // If the timestamp is null, retry the call because the provider returned an unexpected null. + // If the timestamp is null, then the timestamp is not available yet for the block so retrying might help. if (_timestamp === null) { return await retryCall(); }