Skip to content

Commit

Permalink
fix: different timestamps calculated for the same transaction dependi…
Browse files Browse the repository at this point in the history
…ng on the RPC method
  • Loading branch information
amalcaraz committed Oct 7, 2024
1 parent c4b64d5 commit b5f9408
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 44 deletions.
46 changes: 32 additions & 14 deletions packages/solana/src/sdk/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
GetSupplyRpcResult,
GetVoteAccounts,
} from './schema.js'
import { SolanaAccountTransactionHistoryStorage } from '../services/fetcher/src/dal/accountTransactionHistory.js'

export interface SolanaPaginationKey {
signature: string
Expand Down Expand Up @@ -80,9 +81,11 @@ export class SolanaRPC {
protected rateLimit = false
protected genesisBlockTimestamp = 1584368940000 // 2020-03-16T14:29:00.000Z
protected firstBlockWithTimestamp = 39824214 // blockTime is 1602083250 (2020-10-07T15:07:30.000Z)
// first block invalid timestamp 1584368940

constructor(options: SolanaRPCOptions) {
constructor(
protected options: SolanaRPCOptions,
protected accountSignatureDAL?: SolanaAccountTransactionHistoryStorage,
) {
this.connection = new Connection(options.url, {
rateLimit: options.rateLimit,
})
Expand Down Expand Up @@ -246,8 +249,10 @@ export class SolanaRPC {
},
)

return txs.map((tx) =>
this.completeTransactionsInfo(tx, options?.swallowErrors),
return Promise.all(
txs.map((tx) =>
this.completeTransactionsInfo(tx, options?.swallowErrors),
),
)
}

Expand Down Expand Up @@ -445,10 +450,10 @@ export class SolanaRPC {
return newSig
}

protected completeTransactionsInfo<
protected async completeTransactionsInfo<
S extends boolean,
R = S extends false ? SolanaRawTransaction : SolanaRawTransaction | null,
>(tx: RawParsedTransactionWithMeta | null, swallowErrors?: S): R {
>(tx: RawParsedTransactionWithMeta | null, swallowErrors?: S): Promise<R> {
if (!tx) {
if (!swallowErrors) throw new Error('Invalid null tx')
return tx as R
Expand All @@ -458,7 +463,19 @@ export class SolanaRPC {

newTx.id = tx.transaction.signatures[0]
newTx.signature = newTx.id
newTx.timestamp = this.calculateBlockTimestamp(newTx.slot, newTx.blockTime)

let timestamp: number | undefined

if (this.accountSignatureDAL) {
const sigInfo = await this.accountSignatureDAL.get([newTx.signature])
timestamp = sigInfo?.timestamp
}

if (!timestamp) {
timestamp = this.calculateBlockTimestamp(newTx.slot, newTx.blockTime)
}

newTx.timestamp = timestamp

return newTx as R
}
Expand All @@ -480,26 +497,27 @@ export class SolanaRPC {
// @note: Genesis block has blockTime === 1584368940 which is wrong
// Replace it with genesisBlockTimestamp to don't cause problems querying entities by time range
// @note: Also before block 39824214 the blockTime is 0 so aproximate it taking into account there is
// a new slot each 400ms (using 444ms * firstBlockWithTimestamp = 2020-10-07T06:08:11.016Z which is near but still lower than the real blockTime 2020-10-07T15:07:30.000Z)
// a new slot each 400ms
protected getAproximatedTimestamp(slot: number): number {
return this.genesisBlockTimestamp + slot * 444
return this.genesisBlockTimestamp + slot * 400
}
}

export class SolanaRPCRoundRobin {
protected i = 0
protected rpcClients: SolanaRPC[] = []

constructor(rpcs: (string | SolanaRPC)[], rateLimit = false) {
constructor(
rpcs: (string | SolanaRPC)[],
rateLimit = false,
accountSignatureDAL: SolanaAccountTransactionHistoryStorage,
) {
const dedup = rpcs
.filter((rpc) => !!rpc)
.map((rpc) => {
return rpc instanceof SolanaRPC
? rpc
: new SolanaRPC({
url: rpc,
rateLimit,
})
: new SolanaRPC({ url: rpc, rateLimit }, accountSignatureDAL)
})
.reduce((acc, curr) => {
const url = curr.getConnection().endpoint
Expand Down
66 changes: 36 additions & 30 deletions packages/solana/src/services/fetcher/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,26 @@ import { SolanaRPC, SolanaRPCRoundRobin } from '../../sdk/client.js'
// import { SolanaAccountState } from './src/types.js'


function getClusterConfig(blockchainId: BlockchainId): {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function solanaDalsFetcherFactory(basePath: string) {
return {
accountSignatureDAL: createSolanaAccountTransactionHistoryDAL(basePath),
// accountStateDAL: createAccountStateDAL<SolanaAccountState>(basePath, true),
transactionHistoryFetcherStateDAL: createFetcherStateDAL(basePath, 'fetcher_state_transaction_history'),
transactionHistoryPendingAccountDAL: createPendingAccountDAL(basePath, 'fetcher_pending_account_transaction_history'),
// accountStatePendingAccountDAL: createPendingAccountDAL(basePath, 'fetcher_pending_account_account_state'),
pendingEntityDAL: createPendingEntityDAL(basePath, IndexableEntityType.Transaction),
pendingEntityCacheDAL: createPendingEntityCacheDAL(basePath, IndexableEntityType.Transaction),
pendingEntityFetchDAL: createPendingEntityFetchDAL(basePath, IndexableEntityType.Transaction),
rawTransactionDAL: createRawEntityDAL<SolanaRawTransaction>(basePath, IndexableEntityType.Transaction, true),

}
}

function getClusterConfig(
blockchainId: BlockchainId,
DALs: ReturnType<typeof solanaDalsFetcherFactory>,
): {
historyRpcRR: SolanaRPCRoundRobin
historyRpc: SolanaRPC,
privateRpcRR: SolanaRPCRoundRobin
Expand All @@ -39,7 +58,7 @@ function getClusterConfig(blockchainId: BlockchainId): {
const historicUrlList = historicUrls.split(',')
const historicRateLimit = historicRateLimitStr === 'true'

const historyRpcRR = new SolanaRPCRoundRobin(historicUrlList, historicRateLimit)
const historyRpcRR = new SolanaRPCRoundRobin(historicUrlList, historicRateLimit, DALs.accountSignatureDAL)
const historyRpc = historyRpcRR.getProxy()


Expand All @@ -52,9 +71,8 @@ function getClusterConfig(blockchainId: BlockchainId): {
const privateUrlList = privateUrls.split(',')
const privateRateLimit = privateRateLimitStr === 'true'

privateRpcRR = new SolanaRPCRoundRobin(privateUrlList, privateRateLimit)
privateRpcRR = new SolanaRPCRoundRobin(privateUrlList, privateRateLimit, DALs.accountSignatureDAL)
privateRpc = privateRpcRR.getProxy()

}

return {
Expand All @@ -73,15 +91,14 @@ export async function solanaFetcherFactory(
): Promise<BlockchainFetcherI> {
if (basePath) await Utils.ensurePath(basePath)

const DALs = solanaDalsFetcherFactory(basePath)

const {
historyRpcRR,
historyRpc,
privateRpcRR,
privateRpc
} = getClusterConfig(blockchainId)


const {
historyRpcRR,
historyRpc,
privateRpcRR,
privateRpc
} = getClusterConfig(blockchainId, DALs)

// @note: Force resolve DNS and cache it before starting fetcher
await Promise.allSettled(
Expand All @@ -95,36 +112,25 @@ export async function solanaFetcherFactory(
}),
)

// DALs
const accountSignatureDAL = createSolanaAccountTransactionHistoryDAL(basePath)
// const accountStateDAL = createAccountStateDAL<SolanaAccountState>(basePath, true)
const transactionHistoryFetcherStateDAL = createFetcherStateDAL(basePath, 'fetcher_state_transaction_history')
const transactionHistoryPendingAccountDAL = createPendingAccountDAL(basePath, 'fetcher_pending_account_transaction_history')
// const accountStatePendingAccountDAL = createPendingAccountDAL(basePath, 'fetcher_pending_account_account_state')
const pendingEntityDAL = createPendingEntityDAL(basePath, IndexableEntityType.Transaction)
const pendingEntityCacheDAL = createPendingEntityCacheDAL(basePath, IndexableEntityType.Transaction)
const pendingEntityFetchDAL = createPendingEntityFetchDAL(basePath, IndexableEntityType.Transaction)
const rawTransactionDAL = createRawEntityDAL<SolanaRawTransaction>(basePath, IndexableEntityType.Transaction, true)

const transactionHistoryFetcher = new SolanaTransactionHistoryFetcher(
blockchainId,
privateRpc,
historyRpc,
transactionHistoryFetcherStateDAL,
DALs.transactionHistoryFetcherStateDAL,
fetcherClient,
transactionHistoryPendingAccountDAL,
accountSignatureDAL,
DALs.transactionHistoryPendingAccountDAL,
DALs.accountSignatureDAL,
)

const transactionFetcher = new SolanaTransactionFetcher(
blockchainId,
privateRpc,
historyRpc,
broker,
pendingEntityDAL,
pendingEntityCacheDAL,
pendingEntityFetchDAL,
rawTransactionDAL,
DALs.pendingEntityDAL,
DALs.pendingEntityCacheDAL,
DALs.pendingEntityFetchDAL,
DALs.rawTransactionDAL,
)

// const accountStateFetcherMain = new SolanaStateFetcher(
Expand Down

0 comments on commit b5f9408

Please sign in to comment.