Prathamesh Musale
86259b35a6
Part of [Create a public laconicd testnet](https://www.notion.so/Create-a-public-laconicd-testnet-896a11bdd8094eff8f1b49c0be0ca3b8) Requires cerc-io/registry-sdk#22 Behaviour in different configuration scenarios: - Gas set, fees set to `Xalnt`: ```bash # Example gas: 500000 fees: 500000alnt gasPrice: ``` - `gasPrice` config ignored - tx rejected if given `fees` < `gas` * `min-gas-price` set by the node - tx fails mid-execution if it runs out of given `gas` - Fees not set, gas price set to `Xalnt`: ```bash # Example gas: fees: gasPrice: 1alnt ``` - `gas` config ignored - uses `auto` fee calculation using gas estimation with [default multiplier](https://git.vdb.to/cerc-io/registry-sdk/src/branch/main/src/constants.ts) (`2`) value from `registry-sdk` - tx rejected if given `gasPrice` < `min-gas-price` set by the node - tx fails mid-execution if it runs out of calculated gas - Fees set to a `X` (without `alnt` suffix), gas price set to `Yalnt`: ```bash # Example gas: fees: 1.8 gasPrice: 1alnt ``` - `gas` config ignored - uses `auto` fee calculation using gas estimation with `fees` as the multiplier - tx rejected if given `gasPrice` < `min-gas-price` set by the node - tx fails mid-execution if it runs out of calculated gas, can be retried with a higher gas estimation multiplier (`fees`) - Fees and gas price both not set: ```bash # Example gas: fees: gasPrice: ``` - `gas` config ignored - uses `auto` fee calculation using gas estimation - throws error: ```bash Gas price must be set in the client options when auto gas is used. ``` The provided config can be overridden with CLI args if required. Co-authored-by: IshaVenikar <ishavenikar7@gmail.com> Reviewed-on: #81 Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com> Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
220 lines
6.6 KiB
TypeScript
220 lines
6.6 KiB
TypeScript
import yargs from 'yargs';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import yaml from 'js-yaml';
|
|
import assert from 'assert';
|
|
import { hideBin } from 'yargs/helpers';
|
|
|
|
import { StdFee } from '@cosmjs/stargate';
|
|
import { Registry } from '@cerc-io/registry-sdk';
|
|
|
|
import { getConfig, getGasAndFees, getConnectionInfo, txOutput } from '../../src/util';
|
|
|
|
enum RecordType {
|
|
RepositoryRecord = 'RepositoryRecord',
|
|
ServiceRecord = 'ServiceRecord',
|
|
StackRecord = 'StackRecord',
|
|
SubgraphRecord = 'SubgraphRecord',
|
|
WatcherRecord = 'WatcherRecord',
|
|
}
|
|
|
|
const recordTypeToRecordField = new Map<string, string>([
|
|
[RecordType.WatcherRecord, 'watcher'],
|
|
[RecordType.SubgraphRecord, 'subgraph'],
|
|
[RecordType.ServiceRecord, 'service']
|
|
]);
|
|
|
|
let registry: Registry;
|
|
let fee: any;
|
|
let userKey: string;
|
|
let bondId: string;
|
|
|
|
async function main () {
|
|
const argv = getArgs();
|
|
const { records: recordsDir, config } = argv;
|
|
|
|
const { services: { registry: registryConfig } } = getConfig(config as string);
|
|
|
|
if (registryConfig.userKey == null) {
|
|
throw new Error('userKey not set in config');
|
|
}
|
|
|
|
if (registryConfig.bondId == null) {
|
|
throw new Error('bondId not set in config');
|
|
}
|
|
|
|
let rpcEndpoint, gqlEndpoint, chainId: string;
|
|
({ rpcEndpoint, gqlEndpoint, userKey, bondId, chainId } = getConnectionInfo(argv, registryConfig));
|
|
|
|
registry = new Registry(gqlEndpoint, rpcEndpoint, { chainId });
|
|
fee = getGasAndFees(argv, registryConfig);
|
|
|
|
await processDir(path.resolve(recordsDir));
|
|
}
|
|
|
|
async function processDir (directoryPath: string): Promise<void> {
|
|
const files = fs.readdirSync(directoryPath);
|
|
|
|
const dirHasRecords = await publishRecordsFromDir(directoryPath);
|
|
if (dirHasRecords) {
|
|
// Skip further recursion in the current dir
|
|
return;
|
|
}
|
|
|
|
// Recursively iterate through subdirectories
|
|
for (let i = 0; i < files.length; i++) {
|
|
const file = files[i];
|
|
const filePath = path.join(directoryPath, file);
|
|
|
|
if (fs.statSync(filePath).isDirectory()) {
|
|
await processDir(filePath);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function publishRecordsFromDir (recordsDir: string): Promise<boolean> {
|
|
// List record files
|
|
const files = fs.readdirSync(recordsDir);
|
|
const recordFiles = files.filter(file => ['.json', '.yaml', '.yml'].includes(path.extname(file).toLowerCase()));
|
|
|
|
if (recordFiles.length === 0) {
|
|
return false;
|
|
}
|
|
|
|
// Read record from each JSON file
|
|
console.log('**************************************');
|
|
console.log(`Publishing records from ${recordsDir}`);
|
|
|
|
let recordType;
|
|
for (let i = 0; i < recordFiles.length; i++) {
|
|
const file = recordFiles[i];
|
|
|
|
const filePath = path.resolve(recordsDir, file);
|
|
const record = await readRecord(filePath);
|
|
|
|
// Publish record
|
|
const result = await publishRecord(userKey, bondId, fee, record);
|
|
|
|
console.log(`Published record ${file}`);
|
|
txOutput(result, JSON.stringify(result, undefined, 2), '', false);
|
|
|
|
recordType = record.type;
|
|
}
|
|
|
|
// Check if deployment record files exist
|
|
const deploymentRecordsDir = path.resolve(recordsDir, 'deployments');
|
|
if (!fs.existsSync(deploymentRecordsDir) || !fs.statSync(deploymentRecordsDir).isDirectory()) {
|
|
return true;
|
|
}
|
|
console.log('--------------------------------------');
|
|
console.log(`Publishing deployment records from ${deploymentRecordsDir}`);
|
|
|
|
// List record files
|
|
const deploymentFiles = fs.readdirSync(deploymentRecordsDir);
|
|
const deploymentJsonFiles = deploymentFiles.filter(file => path.extname(file).toLowerCase() === '.json');
|
|
|
|
for (let i = 0; i < deploymentJsonFiles.length; i++) {
|
|
const file = deploymentJsonFiles[i];
|
|
|
|
const filePath = path.resolve(deploymentRecordsDir, file);
|
|
const deploymentRecord = await readRecord(filePath);
|
|
|
|
// Find record using name and given type
|
|
const recordName = deploymentRecord.name;
|
|
assert(recordType, 'recordType could not be resolved');
|
|
const queryResult = await registry.queryRecords({ type: recordType, name: recordName }, true);
|
|
if (queryResult.length === 0) {
|
|
throw new Error(`Record not found, type: ${recordType}, name: ${recordName}`);
|
|
}
|
|
|
|
// Assume the first query result
|
|
const recordId = queryResult[0].id;
|
|
|
|
// Set record field to record id
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
deploymentRecord[recordTypeToRecordField.get(recordType)!] = recordId;
|
|
|
|
// Publish record
|
|
const deploymentResult = await registry.setRecord({ privateKey: userKey, record: deploymentRecord, bondId }, userKey, fee);
|
|
|
|
console.log(`Published record ${file}`);
|
|
txOutput(deploymentResult, JSON.stringify(deploymentResult, undefined, 2), '', false);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
async function readRecord (filePath: string): Promise<any> {
|
|
let record;
|
|
try {
|
|
const fileExt = path.extname(filePath).toLowerCase();
|
|
const data = fs.readFileSync(filePath, 'utf8');
|
|
|
|
if (fileExt === '.json') {
|
|
// JSON file
|
|
record = JSON.parse(data);
|
|
} else {
|
|
// YAML file
|
|
({ record } = await yaml.load(data) as any);
|
|
|
|
// Convert sub-objects (other than arrays) to a JSON automatically.
|
|
for (const [k, v] of Object.entries(record)) {
|
|
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
|
record[k] = JSON.stringify(v);
|
|
}
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error(`Error reading file ${filePath}:`, err);
|
|
}
|
|
|
|
return record;
|
|
}
|
|
|
|
async function publishRecord (userKey: string, bondId: string, fee: StdFee, record: any): Promise<any> {
|
|
// Replace repository URL with record id (if type is one of RecordType)
|
|
if (record.repository && Object.values(RecordType).includes(record.type)) {
|
|
const repoUrl = record.repository;
|
|
|
|
const queryResult = await registry.queryRecords({ type: RecordType.RepositoryRecord, url: repoUrl }, true);
|
|
if (queryResult.length === 0) {
|
|
throw new Error(`Record not found, type: ${RecordType.RepositoryRecord}, url: ${repoUrl}`);
|
|
}
|
|
|
|
// Assume the first query result
|
|
const repoRecordId = queryResult[0].id;
|
|
|
|
// Replace repository URL with the repo record id
|
|
record.repository = repoRecordId;
|
|
}
|
|
|
|
return registry.setRecord({ privateKey: userKey, record, bondId }, userKey, fee);
|
|
}
|
|
|
|
function getArgs (): any {
|
|
return yargs(hideBin(process.argv)).parserConfiguration({
|
|
'parse-numbers': false
|
|
}).usage('Usage: $0 [options]')
|
|
.option('config', {
|
|
alias: 'c',
|
|
describe: 'Config',
|
|
type: 'string',
|
|
demandOption: true
|
|
})
|
|
.option('records', {
|
|
alias: 'r',
|
|
describe: 'Records dir path',
|
|
type: 'string',
|
|
demandOption: true
|
|
})
|
|
.help().argv;
|
|
}
|
|
|
|
main()
|
|
.catch(err => {
|
|
console.error(err);
|
|
})
|
|
.finally(() => {
|
|
console.log('Done');
|
|
});
|