@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
JavaScript
;
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