Last active
May 21, 2024 22:36
-
-
Save NotoriousPyro/ac00492c3cbdd6374513124fd543eec4 to your computer and use it in GitHub Desktop.
Example database for caching Solana AddressLookupTableAddresses in memory using lmdb.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { RootDatabaseOptionsWithPath, open } from 'lmdb'; | |
import { addressLookupTableDBPath } from '../const'; | |
const _dbOptions: RootDatabaseOptionsWithPath = { | |
path: addressLookupTableDBPath, | |
cache: { // https://github.com/kriszyp/weak-lru-cache#weaklrucacheoptions-constructor | |
cacheSize: 16777216, // 16MB - maximum | |
clearKeptInterval: 100, | |
txnStartThreshold: 3 | |
}, | |
compression: false, | |
encoding: 'msgpack', | |
sharedStructuresKey: Symbol.for('Buffer'), | |
eventTurnBatching: false, // PERF: false. DEFAULT: true. | |
// LMDB options | |
noSync: false, // PERF: true. DEFAULT: false. NOTES: true = don't sync to disk, faster, could corrupt on crash. | |
noMemInit: true, // PERF: true. DEFAULT: false. NOTES: true = don't zero-out disk space, faster but could be risky if the data is sensitive. | |
remapChunks: false, // PERF: false. DEFAULT: true. NOTES: false = more ram usage, faster. | |
useWritemap: false, // PERF: true. DEFAULT: false. NOTES: true = reduce malloc and file writes, risk of corrupting data, slower on Windows, faster on Linux. | |
} | |
const _db = open(_dbOptions); | |
const AddressLookupTableDB = { | |
getMany: async (addresses: string[]): Promise<Buffer[]> => _db.getMany(addresses), | |
put: async (address: string, data: Buffer): Promise<boolean> => _db.put(address, data), | |
} | |
export default AddressLookupTableDB; | |
// then you just need some functions in a separate file which are using the database and have access to connection. | |
// Here's a cached and uncached version: | |
export async function* generateAccountInfos( | |
publicKeys: PublicKey[], | |
chunkSize: number = 100 | |
): AsyncGenerator<{ publicKey: PublicKey, accountInfo: AccountInfo<Buffer> }> { | |
for (let i = 0; i < publicKeys.length; i += chunkSize) { | |
const chunk = publicKeys.slice(i, i + chunkSize); | |
const accountInfos = await connection.getMultipleAccountsInfo(chunk); | |
for (const [index, accountInfo] of accountInfos.entries()) { | |
yield { publicKey: chunk[index], accountInfo } | |
} | |
} | |
} | |
export const getAccountInfos = async ( | |
publicKeys: PublicKey[], | |
chunkSize: number = 100 | |
): Promise<{ publicKey: PublicKey, accountInfo: AccountInfo<Buffer> }[]> => { | |
const accountInfos = []; | |
for await (const accountInfo of generateAccountInfos(publicKeys, chunkSize)) { | |
accountInfos.push(accountInfo); | |
} | |
return accountInfos; | |
} | |
export const getAddressLookupTableAccounts_Cached = async ( | |
addressLookupTableAddresses: string[] | |
): Promise<AddressLookupTableAccount[]> => { | |
const knownAccountsData = await AddressLookupTableDB.getMany(addressLookupTableAddresses); | |
const unknownAccounts = addressLookupTableAddresses.filter((__, index) => !knownAccountsData[index]); | |
const addressLookupTableAccountInfos = await getAccountInfos(await Promise.all(unknownAccounts.map(key => new PublicKey(key)))); | |
return await Promise.all(knownAccountsData.map( | |
// eslint-disable-next-line @typescript-eslint/require-await | |
async (data, index) => { | |
const addressLookupTableAddress = addressLookupTableAddresses[index]; | |
if (data) { // Cached | |
return new AddressLookupTableAccount({ | |
key: new PublicKey(addressLookupTableAddress), | |
state: AddressLookupTableAccount.deserialize(data), | |
}); | |
} | |
if (addressLookupTableAddress) { // Uncached | |
const {publicKey, accountInfo} = addressLookupTableAccountInfos.shift(); | |
if (accountInfo) { | |
void AddressLookupTableDB.put(addressLookupTableAddress, accountInfo.data); | |
return new AddressLookupTableAccount({ | |
key: publicKey, | |
state: AddressLookupTableAccount.deserialize(accountInfo.data), | |
}); | |
} | |
} | |
}) | |
); | |
}; | |
// Just use the version above, but this is in case something is broken you can revert to using non-cached | |
export const getAddressLookupTableAccounts_Uncached = async ( | |
addressLookupTableAddresses: string[] | |
): Promise<AddressLookupTableAccount[]> => { | |
const publicKeys = await Promise.all(addressLookupTableAddresses.map(async key => new PublicKey(key))); | |
const addressLookupTableAccountInfos = await connection.getMultipleAccountsInfo(publicKeys); | |
return await Promise.all( | |
addressLookupTableAccountInfos.map( | |
async (accountInfo, index) => { | |
const addressLookupTableAddress = addressLookupTableAddresses[index]; | |
if (addressLookupTableAddress) { | |
return new AddressLookupTableAccount({ | |
key: new PublicKey(addressLookupTableAddress), | |
state: AddressLookupTableAccount.deserialize(accountInfo.data), | |
}); | |
} | |
} | |
) | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment