Skip to content

Instantly share code, notes, and snippets.

@NotoriousPyro
Last active May 21, 2024 22:36
Show Gist options
  • Save NotoriousPyro/ac00492c3cbdd6374513124fd543eec4 to your computer and use it in GitHub Desktop.
Save NotoriousPyro/ac00492c3cbdd6374513124fd543eec4 to your computer and use it in GitHub Desktop.
Example database for caching Solana AddressLookupTableAddresses in memory using lmdb.js
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