From 7f5a0d727843e3d1cc6d0e5d34901e5bca65120b Mon Sep 17 00:00:00 2001 From: Abdul Azeem Date: Wed, 8 Jul 2026 22:54:14 +0300 Subject: [PATCH 1/8] feat: Introduce `goldenTicketRetryInterval` network parameter --- src/@types/index.ts | 1 + src/config/index.ts | 1 + src/transactions/apply_change_network_param.ts | 3 +++ 3 files changed, 5 insertions(+) diff --git a/src/@types/index.ts b/src/@types/index.ts index fcc880a5..4d940118 100644 --- a/src/@types/index.ts +++ b/src/@types/index.ts @@ -934,6 +934,7 @@ export interface NetworkParameters { minTollUsdStr: string defaultTollUsdStr: string goldenTicketServerUrl: string + goldenTicketRetryInterval: number dao: { proposalFeeUsdStr: string voteThresholdUsdStr: string diff --git a/src/config/index.ts b/src/config/index.ts index 03bc91de..34ea0293 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -104,6 +104,7 @@ export const INITIAL_PARAMETERS: NetworkParameters = { minTollUsdStr: '0.2', defaultTollUsdStr: '0.2', goldenTicketServerUrl: 'http://localhost:3456/golden/ticket', + goldenTicketRetryInterval: 10 * ONE_MINUTE, messageRetentionDays: 7, messageMaxLength: 500, dao: { diff --git a/src/transactions/apply_change_network_param.ts b/src/transactions/apply_change_network_param.ts index d5888d11..4a588262 100644 --- a/src/transactions/apply_change_network_param.ts +++ b/src/transactions/apply_change_network_param.ts @@ -79,6 +79,9 @@ export function backfillNetworkAccount(network: NetworkAccount): void { if (network.current.goldenTicketServerUrl === undefined || network.current.goldenTicketServerUrl === null) { network.current.goldenTicketServerUrl = 'http://localhost:3456/golden/ticket' } + if (network.current.goldenTicketRetryInterval === undefined || network.current.goldenTicketRetryInterval === null) { + network.current.goldenTicketRetryInterval = 10 * config.ONE_MINUTE + } if (network.current.messageRetentionDays === undefined || network.current.messageRetentionDays === null) { network.current.messageRetentionDays = 7 } From 3f13b0b4124c000b50747e0b44b698cbc5c845f5 Mon Sep 17 00:00:00 2001 From: Abdul Azeem Date: Wed, 8 Jul 2026 22:54:15 +0300 Subject: [PATCH 2/8] feat: Define types for Golden Ticket fetch results --- src/transactions/admin_certificate.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/transactions/admin_certificate.ts b/src/transactions/admin_certificate.ts index 1885fb00..60c40c76 100644 --- a/src/transactions/admin_certificate.ts +++ b/src/transactions/admin_certificate.ts @@ -22,6 +22,13 @@ export interface AdminCertResponse { cached?: boolean } +export interface GoldenTicketFetchResult { + ticket?: AdminCert + error?: string + retryable: boolean + terminal: boolean +} + export type PutAdminCertRequest = AdminCert export interface PutAdminCertResult { From 24a5790c87a8949ca83821fb82da4e8ca2bb84cf Mon Sep 17 00:00:00 2001 From: Abdul Azeem Date: Wed, 8 Jul 2026 22:54:15 +0300 Subject: [PATCH 3/8] feat: Implement Golden Ticket error classification helpers --- src/transactions/admin_certificate.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/transactions/admin_certificate.ts b/src/transactions/admin_certificate.ts index 60c40c76..f3328ead 100644 --- a/src/transactions/admin_certificate.ts +++ b/src/transactions/admin_certificate.ts @@ -39,6 +39,27 @@ export interface PutAdminCertResult { export let adminCert: AdminCert = null export let isRequestedAdminCert: boolean = false +export function isTerminalGoldenTicketError(error?: string): boolean { + if (!error) return false + const normalizedError = error.toLowerCase() + return ( + normalizedError.includes('public key not registered') || + normalizedError.includes('inactive') || + normalizedError.includes('signature owner does not match registered public key') || + normalizedError.includes('invalid signature') || + normalizedError.includes('signature validation failed') + ) +} + +function createGoldenTicketFetchResult(error?: string): GoldenTicketFetchResult { + const terminal = isTerminalGoldenTicketError(error) + return { + error, + retryable: !terminal, + terminal, + } +} + function validatePutAdminCertRequest(req: PutAdminCertRequest, shardus: Shardus): PutAdminCertResult { const publicKey = shardus.crypto.getPublicKey() From 78fca278308a8277060bdc993891557369292f7b Mon Sep 17 00:00:00 2001 From: Abdul Azeem Date: Wed, 8 Jul 2026 22:54:16 +0300 Subject: [PATCH 4/8] refactor: Update `tryAndFetchGoldenTicket` signature --- src/transactions/admin_certificate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transactions/admin_certificate.ts b/src/transactions/admin_certificate.ts index f3328ead..6f6386e7 100644 --- a/src/transactions/admin_certificate.ts +++ b/src/transactions/admin_certificate.ts @@ -107,7 +107,7 @@ export async function putAdminCertificateHandler(req: Request, shardus: Shardus) return { success: true } } -export async function tryAndFetchGoldenTicket(publicKey: string, network: NetworkAccount, dapp: Shardus): Promise { +export async function tryAndFetchGoldenTicket(publicKey: string, network: NetworkAccount, dapp: Shardus): Promise { try { if (LiberdusFlags.VerboseLogs) console.log('Fetching golden ticket from', network.current.goldenTicketServerUrl, 'for publicKey', publicKey, 'node') const goldenTicketRequest: any = { From 586fd8f9c033e81fd82c88e9dc556aa93e0adcd1 Mon Sep 17 00:00:00 2001 From: Abdul Azeem Date: Wed, 8 Jul 2026 22:54:16 +0300 Subject: [PATCH 5/8] feat: Refactor `tryAndFetchGoldenTicket` to return structured results --- src/transactions/admin_certificate.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/transactions/admin_certificate.ts b/src/transactions/admin_certificate.ts index 6f6386e7..da39633a 100644 --- a/src/transactions/admin_certificate.ts +++ b/src/transactions/admin_certificate.ts @@ -121,15 +121,23 @@ export async function tryAndFetchGoldenTicket(publicKey: string, network: Networ if (LiberdusFlags.VerboseLogs) console.log('Golden Ticket request', signedGoldenTicketRequest) const response = await shardusPost(network.current.goldenTicketServerUrl, signedGoldenTicketRequest, { timeout: 5000 }) if (LiberdusFlags.VerboseLogs) console.log('Golden Ticket response', response.data) - if (response.data && response.data.success) { - return response.data.ticket + if (response.data && response.data.success && response.data.ticket) { + return { + ticket: response.data.ticket, + retryable: false, + terminal: false, + } } else { - console.error('No golden ticket received') - return null + const error = response.data?.error || 'No golden ticket received' + console.error(error) + return createGoldenTicketFetchResult(error) } } catch (error) { - console.error(`Error fetching golden ticket from - ${(error as Error).message}`) - return null + const axiosError = error as { message?: string; response?: { data?: AdminCertResponse; status?: number } } + const responseError = axiosError.response?.data?.error + const errorMessage = responseError || axiosError.message || 'Unknown Golden Ticket fetch error' + console.error(`Error fetching golden ticket from - ${errorMessage}`) + return createGoldenTicketFetchResult(errorMessage) } } export function setAdminCertificate(cert: AdminCert): void { From 41ffd9c66d7b45777070c8bc4f85da53aeb5132f Mon Sep 17 00:00:00 2001 From: Abdul Azeem Date: Wed, 8 Jul 2026 22:54:16 +0300 Subject: [PATCH 6/8] feat: Add state for Golden Ticket retry timing --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 74d84323..64ac88bb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -77,6 +77,7 @@ const daoPreCrackTxTypes = new Set([ let isReadyToJoinLatestValue = false let mustUseAdminCert = false +let nextGoldenTicketRetryAt = 0 const shardusSetup = (): void => { // SDK SETUP FUNCTIONS From a09572a7459ce738f5c4b3f00f2f161a0a5fab66 Mon Sep 17 00:00:00 2001 From: Abdul Azeem Date: Wed, 8 Jul 2026 22:54:17 +0300 Subject: [PATCH 7/8] feat: Integrate Golden Ticket fetch retry mechanism into `shardusSetup` --- src/index.ts | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/index.ts b/src/index.ts index 64ac88bb..f5f97199 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1566,27 +1566,41 @@ const shardusSetup = (): void => { isReadyToJoinLatestValue = false mustUseAdminCert = false + const hasExpiredGoldenTicket = adminCert && adminCert.certExp <= dapp.shardusGetTime() && adminCert?.goldenTicket === true // query admin cert from the golden ticket server - if (!isRequestedAdminCert && networkAccount && utils.isEqualOrNewerVersion('2.4.3', networkAccount.current.activeVersion)) { + if ( + (!isRequestedAdminCert || hasExpiredGoldenTicket) && + networkAccount && + utils.isEqualOrNewerVersion('2.4.3', networkAccount.current.activeVersion) && + dapp.shardusGetTime() >= nextGoldenTicketRetryAt + ) { try { - markRequestedAdminCert() // one-time request only - - const goldenTicket = await tryAndFetchGoldenTicket(publicKey, networkAccount, dapp) - if (goldenTicket) { - setAdminCertificate(goldenTicket) + const goldenTicketResult = await tryAndFetchGoldenTicket(publicKey, networkAccount, dapp) + if (goldenTicketResult.ticket) { + setAdminCertificate(goldenTicketResult.ticket) + markRequestedAdminCert() /* prettier-ignore */ - if (LiberdusFlags.VerboseLogs) console.log(`fetched golden ticket: ${Utils.safeStringify(goldenTicket)}`) + if (LiberdusFlags.VerboseLogs) console.log(`fetched golden ticket: ${Utils.safeStringify(goldenTicketResult.ticket)}`) nestedCountersInstance.countEvent('liberdus-staking', 'fetched golden ticket from server') console.log(`Admin certificate is set to `, adminCert) + } else if (goldenTicketResult.terminal) { + markRequestedAdminCert() + /* prettier-ignore */ + if (LiberdusFlags.VerboseLogs) console.log(`terminal golden ticket fetch error: ${goldenTicketResult.error}`) + nestedCountersInstance.countEvent('liberdus-staking', `terminal golden ticket fetch error: ${goldenTicketResult.error}`) } else { + const goldenTicketRetryInterval = networkAccount.current.goldenTicketRetryInterval || 10 * configs.ONE_MINUTE + nextGoldenTicketRetryAt = dapp.shardusGetTime() + goldenTicketRetryInterval /* prettier-ignore */ - if (LiberdusFlags.VerboseLogs) console.log(`no golden ticket available from server`) - nestedCountersInstance.countEvent('liberdus-staking', 'no golden ticket available from server') + if (LiberdusFlags.VerboseLogs) console.log(`no golden ticket available from server, retrying in ${goldenTicketRetryInterval}ms`) + nestedCountersInstance.countEvent('liberdus-staking', `no golden ticket available from server, retrying in ${goldenTicketRetryInterval}ms`) } } catch (e) { + const goldenTicketRetryInterval = networkAccount.current.goldenTicketRetryInterval || 10 * configs.ONE_MINUTE + nextGoldenTicketRetryAt = dapp.shardusGetTime() + goldenTicketRetryInterval /* prettier-ignore */ - if (logFlags.error) console.log(`Error fetching golden ticket: ${e.message}`) // non fatal + if (logFlags.error) console.log(`Error fetching golden ticket: ${e.message}; retrying in ${goldenTicketRetryInterval}ms`) // non fatal } } console.log('is AdminCert set to ', adminCert) From 292048eb041d6331962b2f0fb220599b0623a493 Mon Sep 17 00:00:00 2001 From: Abdul Azeem Date: Wed, 8 Jul 2026 22:54:17 +0300 Subject: [PATCH 8/8] test: Add unit tests for Golden Ticket fetch and error handling --- test/admin_certificate.test.ts | 104 +++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 test/admin_certificate.test.ts diff --git a/test/admin_certificate.test.ts b/test/admin_certificate.test.ts new file mode 100644 index 00000000..df3f1649 --- /dev/null +++ b/test/admin_certificate.test.ts @@ -0,0 +1,104 @@ +jest.mock('../src/utils/request', () => ({ + shardusPost: jest.fn(), +})) + +import { Shardus } from '@shardus/core' +import { shardusPost } from '../src/utils/request' +import { AdminCert, isTerminalGoldenTicketError, tryAndFetchGoldenTicket } from '../src/transactions/admin_certificate' +import { NetworkAccount } from '../src/@types' + +const mockShardusPost = shardusPost as jest.Mock + +const network = { + current: { + goldenTicketServerUrl: 'http://localhost:3456/golden/ticket', + }, +} as NetworkAccount + +const dapp = { + shardusGetTime: jest.fn(() => 123456), + signAsNode: jest.fn((request) => ({ ...request, sign: { owner: request.publicKey, sig: 'signature' } })), +} as unknown as Shardus + +const ticket: AdminCert = { + nominee: 'public-key', + certCreation: 123456, + certExp: 223456, + goldenTicket: true, + sign: { + owner: 'server', + sig: 'signature', + }, +} + +describe('Golden Ticket fetch', () => { + beforeEach(() => { + mockShardusPost.mockReset() + jest.spyOn(console, 'error').mockImplementation(() => undefined) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('returns a ticket on a successful Golden Ticket response', async () => { + mockShardusPost.mockResolvedValue({ data: { success: true, ticket } }) + + const result = await tryAndFetchGoldenTicket('public-key', network, dapp) + + expect(result).toEqual({ ticket, retryable: false, terminal: false }) + }) + + it('classifies timestamp errors from HTTP responses as retryable', async () => { + mockShardusPost.mockRejectedValue({ + message: 'Request failed with status code 409', + response: { + status: 409, + data: { + success: false, + error: 'Timestamp out of acceptable range. Difference: 10000ms, Tolerance: 5000ms', + }, + }, + }) + + const result = await tryAndFetchGoldenTicket('public-key', network, dapp) + + expect(result.retryable).toBe(true) + expect(result.terminal).toBe(false) + expect(result.error).toContain('Timestamp out of acceptable range') + }) + + it('classifies unregistered public keys as terminal', async () => { + mockShardusPost.mockRejectedValue({ + message: 'Request failed with status code 401', + response: { + status: 401, + data: { + success: false, + error: 'Public key not registered or inactive', + }, + }, + }) + + const result = await tryAndFetchGoldenTicket('public-key', network, dapp) + + expect(result.retryable).toBe(false) + expect(result.terminal).toBe(true) + expect(result.error).toBe('Public key not registered or inactive') + }) + + it('classifies network failures as retryable', async () => { + mockShardusPost.mockRejectedValue(new Error('connect ECONNREFUSED 127.0.0.1:3456')) + + const result = await tryAndFetchGoldenTicket('public-key', network, dapp) + + expect(result.retryable).toBe(true) + expect(result.terminal).toBe(false) + expect(result.error).toContain('ECONNREFUSED') + }) + + it('identifies terminal Golden Ticket validation errors', () => { + expect(isTerminalGoldenTicketError('Public key not registered or inactive')).toBe(true) + expect(isTerminalGoldenTicketError('Timestamp out of acceptable range')).toBe(false) + }) +})