UNPKG

@j0nnyboi/amman

Version:

A modern mandatory toolbelt to help test solana SDK libraries and apps on a locally running validator.

168 lines 8.18 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createTemporarySnapshot = exports.mapPersistedAccountInfos = exports.accountInfoFromPersisted = exports.loadAccount = exports.AccountPersister = void 0; const amman_client_1 = require("@j0nnyboi/amman-client"); const web3_js_1 = require("@safecoin/web3.js"); const assert_1 = require("assert"); const fs_1 = require("fs"); const os_1 = require("os"); const path_1 = __importDefault(require("path")); const utils_1 = require("../utils"); const config_1 = require("../utils/config"); const fs_2 = require("../utils/fs"); const consts_1 = require("./consts"); const { logDebug, logTrace } = (0, utils_1.scopedLog)('persist'); class AccountPersister { constructor(targetDir, connection) { this.targetDir = targetDir; this.connection = connection; } // ----------------- // Account Infos // ----------------- async saveAccountInfo(address, accountInfo, subdir, label) { var _a; const persistedAccount = { pubkey: address.toBase58(), account: { lamports: accountInfo.lamports, data: [accountInfo.data.toString('base64'), 'base64'], owner: accountInfo.owner.toBase58(), executable: accountInfo.executable, rentEpoch: (_a = accountInfo.rentEpoch) !== null && _a !== void 0 ? _a : 0, }, }; return this.savePersistedAccountInfo(persistedAccount, subdir, label); } async savePersistedAccountInfo(persistedAccount, subdir, label) { logTrace('Saving account info', persistedAccount.pubkey, label !== null && label !== void 0 ? label : ''); logTrace(persistedAccount); (0, assert_1.strict)(!persistedAccount.account.executable, 'Can only save non-executable accounts'); const fulldir = subdir ? path_1.default.join(this.targetDir, subdir) : this.targetDir; await (0, fs_2.ensureDir)(fulldir); const accountPath = path_1.default.join(fulldir, `${label !== null && label !== void 0 ? label : persistedAccount.pubkey}.json`); await fs_1.promises.writeFile(accountPath, JSON.stringify(persistedAccount, null, 2)); return accountPath; } async saveAccount(address, connection, data) { connection = this._requireConnection('save Account', connection); const accountInfo = await connection.getAccountInfo(address, 'confirmed'); (0, assert_1.strict)(accountInfo != null, `Account not found at address ${address}`); if (data != null) { accountInfo.data = data; } return this.saveAccountInfo(address, accountInfo); } // ----------------- // Keypairs // ----------------- async saveKeypair(id, keypair, subdir) { logTrace('Saving keypair', id); const fulldir = subdir ? path_1.default.join(this.targetDir, subdir, consts_1.SNAPSHOT_KEYPAIRS_DIR) : path_1.default.join(this.targetDir, consts_1.SNAPSHOT_KEYPAIRS_DIR); await (0, fs_2.ensureDir)(fulldir); const keypairPath = path_1.default.join(fulldir, `${id}.json`); const json = JSON.stringify(Array.from(keypair.secretKey)); await fs_1.promises.writeFile(keypairPath, json, 'utf8'); return keypairPath; } // ----------------- // Snapshot // ----------------- async snapshot(snapshotLabel, addresses, // Keyed pubkey:label accountLabels, keypairs, accountOverrides = new Map()) { const snapshotRoot = this.targetDir; const connection = this._requireConnection('take snapshot'); const snapshotDir = path_1.default.join(snapshotRoot, snapshotLabel); await (0, fs_2.ensureDir)(snapshotDir, true); const subdir = path_1.default.join(snapshotLabel, consts_1.SNAPSHOT_ACCOUNTS_DIR); // 1. Save acconuts with known addresses await Promise.all(addresses // We are saving overrides below .filter((address) => !accountOverrides.has(address)) .map(async (address) => { // Save account info we pull from the validator const accountInfo = await connection.getAccountInfo(new web3_js_1.PublicKey(address), 'confirmed'); if (accountInfo == null || accountInfo.executable) return; return this.saveAccountInfo(new web3_js_1.PublicKey(address), accountInfo, subdir, accountLabels[address]); })); // 2. Save account overrides await Promise.all(Array.from(accountOverrides.values()).map((override) => { logTrace('Saving override account info %s', override.pubkey); return this.savePersistedAccountInfo(override, subdir, accountLabels[override.pubkey]); })); // 3. Save keypairs const seenKeypairIds = new Set(); await Promise.all(Array.from(keypairs.values()).map(async ({ keypair, id }) => { // The client ensures that keypair ids don't collide, but we add this // extra check here in order to guard against corrupted keypair files // due to being written to at the same time (filenames are ids) if (seenKeypairIds.has(id)) return; seenKeypairIds.add(id); return this.saveKeypair(id, keypair, snapshotLabel); })); return snapshotDir; } _requireConnection(task, connection) { connection !== null && connection !== void 0 ? connection : (connection = this.connection); (0, assert_1.strict)(connection != null, `Must instantiate persister with connection or provide it to ${task}`); return connection; } } exports.AccountPersister = AccountPersister; async function loadAccount(address, sourceDir, label) { logTrace('Loading account', address.toBase58(), label !== null && label !== void 0 ? label : ''); const accountPath = path_1.default.join(sourceDir !== null && sourceDir !== void 0 ? sourceDir : (0, config_1.fullAccountsDir)(), `${label !== null && label !== void 0 ? label : address.toBase58()}.json`); const json = await fs_1.promises.readFile(accountPath, 'utf8'); const persistedAccount = JSON.parse(json); return persistedAccount; } exports.loadAccount = loadAccount; function accountInfoFromPersisted(persisted) { const { executable, owner, data, lamports } = persisted.account; assert_1.strict.equal(data[1], 'base64', 'expected persisted account info data to be encoded as "base64"'); const accountInfo = { lamports, data: Buffer.from(data[0], 'base64'), owner: new web3_js_1.PublicKey(owner), executable, }; return { address: persisted.pubkey, accountInfo }; } exports.accountInfoFromPersisted = accountInfoFromPersisted; function mapPersistedAccountInfos(persisteds) { const map = new Map(); for (const persisted of persisteds) { const { address, accountInfo } = accountInfoFromPersisted(persisted); map.set(address, accountInfo); } return map; } exports.mapPersistedAccountInfos = mapPersistedAccountInfos; async function createTemporarySnapshot(addresses, // Keyed pubkey:label accountLabels, keypairs, accountOverrides = new Map()) { logDebug('Creating temporary snapshot'); logTrace(accountOverrides); const label = 'temporary'; const snapshotFolder = path_1.default.join((0, os_1.tmpdir)(), 'amman-snapshots'); const config = { snapshotFolder, load: label, }; const persister = new AccountPersister(snapshotFolder, new web3_js_1.Connection(amman_client_1.LOCALHOST, 'confirmed')); const snapshotDir = await persister.snapshot(label, addresses, accountLabels, keypairs, accountOverrides); function cleanupSnapshotDir() { return fs_1.promises.rm(snapshotDir, { recursive: true }); } return { config, cleanupSnapshotDir }; } exports.createTemporarySnapshot = createTemporarySnapshot; //# sourceMappingURL=persistence.js.map