0a917d330b
- Replace all hardcoded wallet mnemonics with env variable reads - Add comprehensive e2e test suite (tools/e2e-test/) - Add zagros validator management tools - Add subxt examples for mainnet operations - Update CRITICAL_STATE with zagros testnet and mainnet status - Fix people chain spec ID and chainspec build script
160 lines
5.6 KiB
JavaScript
160 lines
5.6 KiB
JavaScript
#!/usr/bin/env node
|
|
// Zagros Testnet: Reduce validator count from 21 to 4 via sudo
|
|
// Uses @pezkuwi/api (ESM)
|
|
|
|
import { ApiPromise, WsProvider } from '/home/mamostehp/pezkuwi-api/node_modules/@pezkuwi/api/build/index.js';
|
|
import { Keyring } from '/home/mamostehp/pezkuwi-api/node_modules/@pezkuwi/keyring/build/cjs/index.js';
|
|
import { cryptoWaitReady } from '/home/mamostehp/pezkuwi-api/node_modules/@pezkuwi/util-crypto/build/cjs/index.js';
|
|
|
|
const ZAGROS_RPC = 'ws://217.77.6.126:9948';
|
|
const SUDO_SEED = process.env.SUDO_MNEMONIC || '******';
|
|
const NEW_VALIDATOR_COUNT = 4;
|
|
|
|
async function main() {
|
|
console.log('=== ZAGROS VALIDATOR COUNT REDUCTION ===');
|
|
console.log(`Target: ${NEW_VALIDATOR_COUNT} validators`);
|
|
console.log(`RPC: ${ZAGROS_RPC}`);
|
|
console.log();
|
|
|
|
// Wait for crypto
|
|
await cryptoWaitReady();
|
|
|
|
// Create keyring and add sudo account
|
|
const keyring = new Keyring({ type: 'sr25519', ss58Format: 42 });
|
|
const sudo = keyring.addFromUri(SUDO_SEED);
|
|
console.log(`Sudo account: ${sudo.address}`);
|
|
|
|
// Connect to Zagros
|
|
const provider = new WsProvider(ZAGROS_RPC);
|
|
const api = await ApiPromise.create({
|
|
provider,
|
|
signedExtensions: {
|
|
AuthorizeCall: {
|
|
extrinsic: {},
|
|
payload: {}
|
|
}
|
|
}
|
|
});
|
|
|
|
console.log(`Connected to: ${(await api.rpc.system.chain()).toString()}`);
|
|
const version = await api.rpc.state.getRuntimeVersion();
|
|
console.log(`Runtime version: ${version.specVersion.toString()}`);
|
|
|
|
// Check current sudo key
|
|
const sudoKey = await api.query.sudo.key();
|
|
console.log(`On-chain sudo key: ${sudoKey.toString()}`);
|
|
console.log(`Our key matches: ${sudoKey.toString() === sudo.address}`);
|
|
console.log();
|
|
|
|
// Check current validator count
|
|
const currentCount = await api.query.staking.validatorCount();
|
|
console.log(`Current validator count: ${currentCount.toString()}`);
|
|
|
|
// Check current era
|
|
const currentEra = await api.query.staking.currentEra();
|
|
console.log(`Current era: ${currentEra.toString()}`);
|
|
console.log();
|
|
|
|
// Step 1: Set validator count to 4
|
|
console.log(`[1/2] Setting validator count to ${NEW_VALIDATOR_COUNT}...`);
|
|
const setValidatorCountCall = api.tx.staking.setValidatorCount(NEW_VALIDATOR_COUNT);
|
|
const sudoCall1 = api.tx.sudo.sudo(setValidatorCountCall);
|
|
|
|
try {
|
|
const result1 = await new Promise((resolve, reject) => {
|
|
sudoCall1.signAndSend(sudo, { nonce: -1 }, ({ status, events, dispatchError }) => {
|
|
if (dispatchError) {
|
|
if (dispatchError.isModule) {
|
|
const decoded = api.registry.findMetaError(dispatchError.asModule);
|
|
reject(new Error(`${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`));
|
|
} else {
|
|
reject(new Error(dispatchError.toString()));
|
|
}
|
|
}
|
|
if (status.isInBlock) {
|
|
console.log(` Included in block: ${status.asInBlock.toString()}`);
|
|
// Check for Sudid event
|
|
const sudidEvent = events.find(({ event }) =>
|
|
event.section === 'sudo' && event.method === 'Sudid'
|
|
);
|
|
if (sudidEvent) {
|
|
const result = sudidEvent.event.data[0];
|
|
if (result.isOk) {
|
|
console.log(' Sudo executed successfully!');
|
|
} else {
|
|
console.log(` Sudo dispatch error: ${result.asErr.toString()}`);
|
|
}
|
|
}
|
|
resolve(status.asInBlock.toString());
|
|
}
|
|
});
|
|
});
|
|
} catch (e) {
|
|
console.error(` ERROR: ${e.message}`);
|
|
await api.disconnect();
|
|
process.exit(1);
|
|
}
|
|
|
|
// Verify
|
|
const newCount = await api.query.staking.validatorCount();
|
|
console.log(` Validator count now: ${newCount.toString()}`);
|
|
console.log();
|
|
|
|
// Step 2: Force new era
|
|
console.log('[2/2] Forcing new era...');
|
|
const forceNewEraCall = api.tx.staking.forceNewEra();
|
|
const sudoCall2 = api.tx.sudo.sudo(forceNewEraCall);
|
|
|
|
try {
|
|
const result2 = await new Promise((resolve, reject) => {
|
|
sudoCall2.signAndSend(sudo, { nonce: -1 }, ({ status, events, dispatchError }) => {
|
|
if (dispatchError) {
|
|
if (dispatchError.isModule) {
|
|
const decoded = api.registry.findMetaError(dispatchError.asModule);
|
|
reject(new Error(`${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`));
|
|
} else {
|
|
reject(new Error(dispatchError.toString()));
|
|
}
|
|
}
|
|
if (status.isInBlock) {
|
|
console.log(` Included in block: ${status.asInBlock.toString()}`);
|
|
const sudidEvent = events.find(({ event }) =>
|
|
event.section === 'sudo' && event.method === 'Sudid'
|
|
);
|
|
if (sudidEvent) {
|
|
const result = sudidEvent.event.data[0];
|
|
if (result.isOk) {
|
|
console.log(' Sudo executed successfully!');
|
|
} else {
|
|
console.log(` Sudo dispatch error: ${result.asErr.toString()}`);
|
|
}
|
|
}
|
|
resolve(status.asInBlock.toString());
|
|
}
|
|
});
|
|
});
|
|
} catch (e) {
|
|
console.error(` ERROR: ${e.message}`);
|
|
await api.disconnect();
|
|
process.exit(1);
|
|
}
|
|
|
|
// Check forceEra storage
|
|
const forceEra = await api.query.staking.forceEra();
|
|
console.log(` ForceEra: ${forceEra.toString()}`);
|
|
console.log();
|
|
|
|
console.log('=== DONE ===');
|
|
console.log(`Validator count set to ${NEW_VALIDATOR_COUNT}`);
|
|
console.log('ForceNewEra triggered - new era will start at next session boundary');
|
|
console.log('GRANDPA should start finalizing once new authority set (4 validators) takes effect');
|
|
|
|
await api.disconnect();
|
|
process.exit(0);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error('Fatal error:', err);
|
|
process.exit(1);
|
|
});
|