Checks: multiple errors from a check. (#3990)

Co-authored-by: Catenocrypt <catenocrypt@users.noreply.github.com>
This commit is contained in:
Adam R 2020-09-16 14:52:10 +02:00 committed by GitHub
parent 53348b7fff
commit 4eb01f4b8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 126 additions and 108 deletions

View File

@ -16,9 +16,9 @@ import { formatSortJson } from "../common/json";
import * as bluebird from "bluebird";
import { copyFile } from "fs";
async function checkUpdateAllowDenyList(chain: string, checkOnly: boolean ): Promise<[boolean, string, string]> {
let errorMsg = "";
let warningMsg = "";
async function checkUpdateAllowDenyList(chain: string, checkOnly: boolean ): Promise<[boolean, string[], string[]]> {
let errorMsgs: string[] = [];
let warningMsgs: string[] = [];
const assets = getChainAssetsList(chain);
const allowlistPath = getChainAllowlistPath(chain);
@ -31,12 +31,12 @@ async function checkUpdateAllowDenyList(chain: string, checkOnly: boolean ): Pro
const commonElementsOrDuplicates = findCommonElementsOrDuplicates(currentAllowlist, currentDenylist);
if (commonElementsOrDuplicates && commonElementsOrDuplicates.length > 0) {
errorMsg += `Denylist and allowlist for chain ${chain} should have no common elements or duplicates, found ${commonElementsOrDuplicates.length} ${commonElementsOrDuplicates[0]}\n`;
errorMsgs.push(`Denylist and allowlist for chain ${chain} should have no common elements or duplicates, found ${commonElementsOrDuplicates.length} ${commonElementsOrDuplicates[0]}`);
}
const allowlistOrphan = arrayDiff(currentAllowlist, assets);
if (allowlistOrphan && allowlistOrphan.length > 0) {
// warning only
warningMsg += `Allowlist for chain ${chain} contains non-exitent assets, found ${allowlistOrphan.length}, ${allowlistOrphan[0]}\n`;
warningMsgs.push(`Allowlist for chain ${chain} contains non-exitent assets, found ${allowlistOrphan.length}, ${allowlistOrphan[0]}`);
}
const newDeny = makeUnique(currentDenylist.concat(allowlistOrphan));
@ -47,34 +47,34 @@ async function checkUpdateAllowDenyList(chain: string, checkOnly: boolean ): Pro
const wDiff1 = arrayDiffNocase(newAllow, currentAllowlist);
if (wDiff1.length > 0) {
// warning only
warningMsg += `Some elements are missing from allowlist for chain ${chain}: ${wDiff1.length} ${wDiff1[0]}\n`;
warningMsgs.push(`Some elements are missing from allowlist for chain ${chain}: ${wDiff1.length} ${wDiff1[0]}`);
}
const wDiff2 = arrayDiffNocase(currentAllowlist, newAllow);
if (wDiff2.length > 0) {
// warning only
warningMsg += `Some elements should be removed from allowlist for chain ${chain}: ${wDiff2.length} ${wDiff2[0]}\n`;
warningMsgs.push(`Some elements should be removed from allowlist for chain ${chain}: ${wDiff2.length} ${wDiff2[0]}`);
}
const bDiff1 = arrayDiffNocase(newDeny, currentDenylist);
if (bDiff1.length > 0) {
warningMsg += `Some elements are missing from denylist for chain ${chain}: ${bDiff1.length} ${bDiff1[0]}\n`;
warningMsgs.push(`Some elements are missing from denylist for chain ${chain}: ${bDiff1.length} ${bDiff1[0]}`);
}
const bDiff2 = arrayDiffNocase(currentDenylist, newDeny);
if (bDiff2.length > 0) {
warningMsg += `Some elements should be removed from denylist for chain ${chain}: ${bDiff2.length} ${bDiff2[0]}\n`;
warningMsgs.push(`Some elements should be removed from denylist for chain ${chain}: ${bDiff2.length} ${bDiff2[0]}`);
}
// additionally check for nice formatting, sorting:
const newAllowText = formatSortJson(newAllow);
const newDenyText = formatSortJson(newDeny);
if (newAllowText !== currentAllowlistText) {
warningMsg += `Allowlist for chain ${chain}: not formatted nicely \n`;
warningMsgs.push(`Allowlist for chain ${chain}: not formatted nicely `);
}
if (newDenyText !== currentDenylistText) {
warningMsg += `Denylist for chain ${chain}: not formatted nicely \n`;
warningMsgs.push(`Denylist for chain ${chain}: not formatted nicely `);
}
if (errorMsg.length > 0 || warningMsg.length > 0) {
if (errorMsgs.length > 0 || warningMsgs.length > 0) {
// sg wrong, may need to fix
if (!checkOnly) {
// update
@ -83,7 +83,7 @@ async function checkUpdateAllowDenyList(chain: string, checkOnly: boolean ): Pro
console.log(`Updated allow and denylists for chain ${chain}`);
}
}
return [(errorMsg.length == 0 && warningMsg.length == 0), errorMsg, warningMsg];
return [(errorMsgs.length == 0 && warningMsgs.length == 0), errorMsgs, warningMsgs];
}
export class Allowlist implements ActionInterface {
@ -98,11 +98,11 @@ export class Allowlist implements ActionInterface {
{
getName: () => { return `Allowlist and denylist for ${chain} should be consistent with assets`},
check: async () => {
const [isOK, errorMsg, warningMsg] = await checkUpdateAllowDenyList(chain, true);
const [isOK, errorMsgs, warningMsgs] = await checkUpdateAllowDenyList(chain, true);
if (!isOK) {
return [errorMsg, warningMsg];
return [errorMsgs, warningMsgs];
}
return ["", ""];
return [[], []];
}
}
);

View File

@ -106,16 +106,16 @@ export class BinanceAction implements ActionInterface {
{
getName: () => { return "Binance chain; assets must exist on chain"},
check: async () => {
var error: string = "";
var errors = [];
const tokenSymbols = await retrieveAssetSymbols();
const assets = readDirSync(getChainAssetsPath(Binance));
assets.forEach(asset => {
if (!(tokenSymbols.indexOf(asset) >= 0)) {
error += `Asset ${asset} missing on chain\n`;
errors.push(`Asset ${asset} missing on chain`);
}
});
console.log(` ${assets.length} assets checked.`);
return [error, ""];
return [errors, []];
}
},
];

View File

@ -11,22 +11,22 @@ export class CosmosAction implements ActionInterface {
{
getName: () => { return "Cosmos validator assets must have correct format"},
check: async () => {
var error: string = "";
var errors: string[] = [];
const assets = getChainValidatorsAssets(Cosmos);
const prefix = "cosmosvaloper1";
const expLength = 52;
assets.forEach(addr => {
if (!(addr.startsWith(prefix))) {
error += `Address ${addr} should start with '${prefix}'\n`;
errors.push(`Address ${addr} should start with '${prefix}'`);
}
if (addr.length != expLength) {
error += `Address ${addr} should have length ${expLength}\n`;
errors.push(`Address ${addr} should have length ${expLength}`);
}
if (!isLowerCase(addr)) {
error += `Address ${addr} should be in lowercase\n`;
errors.push(`Address ${addr} should be in lowercase`);
}
});
return [error, ""];
return [errors, []];
}
},
];

View File

@ -75,29 +75,29 @@ export class EthForks implements ActionInterface {
{
getName: () => { return `Folder structure for chain ${chain} (ethereum fork)`;},
check: async () => {
var error: string = "";
var errors: string[] = [];
const assetsFolder = getChainAssetsPath(chain);
const assetsList = getChainAssetsList(chain);
console.log(` Found ${assetsList.length} assets for chain ${chain}`);
await bluebird.each(assetsList, async (address) => {
const assetPath = `${assetsFolder}/${address}`;
if (!isPathExistsSync(assetPath)) {
error += `Expect directory at path: ${assetPath}\n`;
errors.push(`Expect directory at path: ${assetPath}`);
}
const inChecksum = toChecksum(address, chain);
if (address !== inChecksum) {
error += `Expect asset at path ${assetPath} in checksum: '${inChecksum}'\n`;
errors.push(`Expect asset at path ${assetPath} in checksum: '${inChecksum}'`);
}
const assetLogoPath = getChainAssetLogoPath(chain, address);
if (!isPathExistsSync(assetLogoPath)) {
error += `Missing file at path '${assetLogoPath}'\n`;
errors.push(`Missing file at path '${assetLogoPath}'`);
}
const [isInfoOK, infoMsg] = isAssetInfoOK(chain, address);
if (!isInfoOK) {
error += infoMsg + "\n";
errors.push(infoMsg);
}
});
return [error, ""];
return [errors, []];
}
}
);

View File

@ -27,54 +27,54 @@ export class FoldersFiles implements ActionInterface {
{
getName: () => { return "Repository root dir"},
check: async () => {
var error: string = "";
var errors: string[] = [];
const dirActualFiles = readDirSync(".");
dirActualFiles.forEach(file => {
if (!(rootDirAllowedFiles.indexOf(file) >= 0)) {
error += `File "${file}" should not be in root or added to predifined list\n`;
errors.push(`File "${file}" should not be in root or added to predifined list`);
}
});
return [error, ""];
return [errors, []];
}
},
{
getName: () => { return "Chain folders are lowercase, contain only predefined list of files"},
check: async () => {
var error: string = "";
var errors: string[] = [];
foundChains.forEach(chain => {
if (!isLowerCase(chain)) {
error += `Chain folder must be in lowercase "${chain}"\n`;
errors.push(`Chain folder must be in lowercase "${chain}"`);
}
getChainFolderFilesList(chain).forEach(file => {
if (!(chainFolderAllowedFiles.indexOf(file) >= 0)) {
error += `File '${file}' not allowed in chain folder: ${chain}\n`;
errors.push(`File '${file}' not allowed in chain folder: ${chain}`);
}
});
});
return [error, ""];
return [errors, []];
}
},
{
getName: () => { return "Chain folders have logo, and correct size"},
check: async () => {
var error: string = "";
var errors: string[] = [];
await bluebird.each(foundChains, async (chain) => {
const chainLogoPath = getChainLogoPath(chain);
if (!isPathExistsSync(chainLogoPath)) {
error += `File missing at path "${chainLogoPath}"\n`;
errors.push(`File missing at path "${chainLogoPath}"`);
}
const [isOk, error1] = await isLogoOK(chainLogoPath);
if (!isOk) {
error += error1 + "\n";
errors.push(error1);
}
});
return [error, ""];
return [errors, []];
}
},
{
getName: () => { return "Asset folders contain only predefined set of files"},
check: async () => {
var error: string = "";
var errors: string[] = [];
foundChains.forEach(chain => {
const assetsPath = getChainAssetsPath(chain);
if (isPathExistsSync(assetsPath)) {
@ -82,13 +82,13 @@ export class FoldersFiles implements ActionInterface {
const assetFiles = getChainAssetPath(chain, address)
readDirSync(assetFiles).forEach(assetFolderFile => {
if (!(assetFolderAllowedFiles.indexOf(assetFolderFile) >= 0)) {
error += `File '${assetFolderFile}' not allowed at this path: ${assetsPath}\n`;
errors.push(`File '${assetFolderFile}' not allowed at this path: ${assetsPath}`);
}
});
}) ;
}
});
return [error, ""];
return [errors, []];
}
},
];

View File

@ -1,8 +1,8 @@
// A single check step
export interface CheckStepInterface {
getName(): string;
// return [error, warning], null/"" on success
check(): Promise<[string, string]>;
// return [errors, warnings]
check(): Promise<[string[], string[]]>;
}
// An action for a check, fix, or update, or a combination.

View File

@ -12,17 +12,17 @@ export class JsonAction implements ActionInterface {
{
getName: () => { return "Check all JSON files to have valid content"},
check: async () => {
var error: string = "";
var errors: string[] = [];
const files = [
...findFiles(chainsPath, 'json'),
];
await bluebird.each(files, async file => {
if (!isValidJSON(file)) {
error += `${file} path contains invalid JSON\n`;
errors.push(`${file} path contains invalid JSON`);
}
});
return [error, ""];
return [errors, []];
}
},
];

View File

@ -11,22 +11,22 @@ export class KavaAction implements ActionInterface {
{
getName: () => { return "Kava validator assets must have correct format"},
check: async () => {
var error: string = "";
var errors: string[] = [];
const assets = getChainValidatorsAssets(Kava);
const prefix = "kavavaloper1";
const expLength = 50;
assets.forEach(addr => {
if (!(addr.startsWith(prefix))) {
error += `Address ${addr} should start with '${prefix}'\n`;
errors.push(`Address ${addr} should start with '${prefix}'`);
}
if (addr.length != expLength) {
error += `Address ${addr} should have length ${expLength}\n`;
errors.push(`Address ${addr} should have length ${expLength}`);
}
if (!isLowerCase(addr)) {
error += `Address ${addr} should be in lowercase\n`;
errors.push(`Address ${addr} should be in lowercase`);
}
});
return [error, ""];
return [errors, []];
}
},
];

View File

@ -16,12 +16,12 @@ import { checkResizeIfTooLarge } from "../common/image";
import { ActionInterface, CheckStepInterface } from "./interface";
// return name of large logo, or empty
async function checkDownsize(chains, checkOnly: boolean): Promise<string> {
async function checkDownsize(chains, checkOnly: boolean): Promise<string[]> {
console.log(`Checking all logos for size ...`);
let totalCountChecked: number = 0;
let totalCountTooLarge: number = 0;
let totalCountUpdated: number = 0;
let largePath = "";
let largePaths: string[] = [];
await bluebird.map(chains, async chain => {
let countChecked: number = 0;
let countTooLarge: number = 0;
@ -30,7 +30,7 @@ async function checkDownsize(chains, checkOnly: boolean): Promise<string> {
const path = getChainLogoPath(chain);
countChecked++;
const [tooLarge, updated] = await checkResizeIfTooLarge(path, checkOnly);
if (tooLarge) { largePath = path; }
if (tooLarge) { largePaths.push(path); }
countTooLarge += tooLarge ? 1 : 0;
countUpdated += updated ? 1 : 0;
@ -41,7 +41,7 @@ async function checkDownsize(chains, checkOnly: boolean): Promise<string> {
const path = getChainAssetLogoPath(chain, asset);
countChecked++;
const [tooLarge, updated] = await checkResizeIfTooLarge(path, checkOnly);
if (tooLarge) { largePath = path; }
if (tooLarge) { largePaths.push(path); }
countTooLarge += tooLarge ? 1 : 0;
countUpdated += updated ? 1 : 0;
})
@ -55,7 +55,7 @@ async function checkDownsize(chains, checkOnly: boolean): Promise<string> {
const path = getChainValidatorAssetLogoPath(chain, id);
countChecked++;
const [tooLarge, updated] = await checkResizeIfTooLarge(path, checkOnly);
if (tooLarge) { largePath = path; }
if (tooLarge) { largePaths.push(path); }
countTooLarge += tooLarge ? 1 : 0;
countUpdated += updated ? 1 : 0;
})
@ -65,11 +65,11 @@ async function checkDownsize(chains, checkOnly: boolean): Promise<string> {
totalCountTooLarge += countTooLarge;
totalCountUpdated += countUpdated;
if (countTooLarge > 0 || countUpdated > 0) {
console.log(`Checking logos on chain ${chain} completed, ${countChecked} checked, ${countTooLarge} too large, ${largePath}, ${countUpdated} logos updated`);
console.log(`Checking logos on chain ${chain} completed, ${countChecked} checked, ${countTooLarge} too large, ${largePaths}, ${countUpdated} logos updated`);
}
});
console.log(`Checking logos completed, ${totalCountChecked} logos checked, ${totalCountTooLarge} too large, ${totalCountUpdated} logos updated`);
return largePath;
return largePaths;
}
export class LogoSize implements ActionInterface {
@ -81,11 +81,9 @@ export class LogoSize implements ActionInterface {
getName: () => { return "Check that logos are not too large"},
check: async () => {
const foundChains = readDirSync(chainsPath);
var largePath = await checkDownsize(foundChains, true);
if (largePath.length > 0) {
return [`Found at least one logo that is too large: ${largePath}`, ""];
}
return ["", ""];
const largePaths = await checkDownsize(foundChains, true);
const errors: string[] = largePaths.map(p => `Logo too large: ${p}`);
return [errors, []];
}
},
];

View File

@ -11,22 +11,22 @@ export class TerraAction implements ActionInterface {
{
getName: () => { return "Terra validator assets must have correct format"},
check: async () => {
var error: string = "";
var errors: string[] = [];
const assets = getChainValidatorsAssets(Terra);
const prefix = "terravaloper1";
const expLength = 51;
assets.forEach(addr => {
if (!(addr.startsWith(prefix))) {
error += `Address ${addr} should start with '${prefix}'\n`;
errors.push(`Address ${addr} should start with '${prefix}'`);
}
if (addr.length != expLength) {
error += `Address ${addr} should have length ${expLength}\n`;
errors.push(`Address ${addr} should have length ${expLength}`);
}
if (!isLowerCase(addr)) {
error += `Address ${addr} should be in lowercase\n`;
errors.push(`Address ${addr} should be in lowercase`);
}
});
return [error, ""];
return [errors, []];
}
},
];

View File

@ -83,14 +83,14 @@ export class TezosAction implements ActionInterface {
{
getName: () => { return "Tezos validator assets must have correct format"},
check: async () => {
var error: string = "";
var errors: string[] = [];
const assets = getChainValidatorsAssets(Tezos);
assets.forEach(addr => {
if (!(eztz.crypto.checkAddress(addr))) {
error += `Address ${addr} must be valid Tezos address'\n`;
errors.push(`Address ${addr} must be valid Tezos address'`);
}
});
return [error, ""];
return [errors, []];
}
},
];

View File

@ -25,32 +25,32 @@ export class TronAction implements ActionInterface {
{
getName: () => { return "Tron assets should be TRC10 or TRC20, logo of correct size"; },
check: async () => {
var error: string = "";
var errors: string[] = [];
const path = getChainAssetsPath(Tron);
const assets = readDirSync(path);
await bluebird.each(assets, async (asset) => {
if (!isTRC10(asset) && !isTRC20(asset)) {
error += `Asset ${asset} at path '${path}' is not TRC10 nor TRC20\n`;
errors.push(`Asset ${asset} at path '${path}' is not TRC10 nor TRC20`);
}
const assetsLogoPath = getChainAssetLogoPath(Tron, asset);
if (!isPathExistsSync(assetsLogoPath)) {
error += `Missing file at path '${assetsLogoPath}'\n`;
errors.push(`Missing file at path '${assetsLogoPath}'`);
}
});
return [error, ""];
return [errors, []];
}
},
{
getName: () => { return "Tron validator assets must have correct format"},
check: async () => {
var error: string = "";
var errors: string[] = [];
const assets = getChainValidatorsAssets(Tron);
assets.forEach(addr => {
if (!(isTRC20(addr))) {
error += `Address ${addr} should be TRC20 address'\n`;
errors.push(`Address ${addr} should be TRC20 address'`);
}
});
return [error, ""];
return [errors, []];
}
}
];

View File

@ -32,30 +32,50 @@ const actionList: ActionInterface[] = [
new WavesAction()
];
const maxErrosFromOneCheck = 5;
async function checkStepList(steps: CheckStepInterface[]): Promise<[string[], string[]]> {
var errors: string[] = [];
var warnings: string[] = [];
var errorsAll: string[] = [];
var warningsAll: string[] = [];
await bluebird.each(steps, async (step) => {
try {
//console.log(` Running check step '${step.getName()}'...`);
const [error, warning] = await step.check();
if (error && error.length > 0) {
console.log(`- ${chalk.red('X')} '${step.getName()}': '${error}'`);
errors.push(`${step.getName()}: ${error}`);
const [errors, warnings] = await step.check();
if (errors && errors.length > 0) {
console.log(`- ${chalk.red('X')} '${step.getName()}': ${errors.length} errors`);
var cnt = 0;
errors.forEach(err => {
if (cnt < maxErrosFromOneCheck) {
console.log(` ${chalk.red('X')} '${err}'`);
errorsAll.push(err);
} else if (cnt == maxErrosFromOneCheck) {
console.log(` ${chalk.red('X')} ${errors.length} errors in total, omitting rest ...`);
}
cnt++;
});
}
if (warning && warning.length > 0) {
console.log(`- ${chalk.yellow('!')} '${step.getName()}': '${warning}'`);
warnings.push(`${step.getName()}: ${warning}`);
if (warnings && warnings.length > 0) {
console.log(`- ${chalk.yellow('!')} '${step.getName()}': ${warnings.length} warnings`);
var cnt = 0;
warnings.forEach(warn => {
if (cnt < maxErrosFromOneCheck) {
console.log(` ${chalk.yellow('!')} '${warn}'`);
warningsAll.push(warn);
} else if (cnt == maxErrosFromOneCheck) {
console.log(` ${chalk.red('X')} ${warnings.length} warnings in total, omitting rest ...`);
}
cnt++;
});
}
if (error.length == 0 && warning.length == 0) {
if (errors.length == 0 && warnings.length == 0) {
console.log(`- ${chalk.green('✓')} '${step.getName()}' OK`);
}
} catch (error) {
console.log(`- ${chalk.red('X')} '${step.getName()}': Caught error: ${error.message}`);
errors.push(`${step.getName()}: Exception: ${error.message}`);
errorsAll.push(`${step.getName()}: Exception: ${error.message}`);
}
});
return [errors, warnings];
return [errorsAll, warningsAll];
}
async function sanityCheckByActionList(actions: ActionInterface[]): Promise<[string[], string[]]> {

View File

@ -37,11 +37,11 @@ export class Validators implements ActionInterface {
var steps: CheckStepInterface[] = [
{
getName: () => { return "Make sure tests added for new staking chain"},
check: async (): Promise<[string, string]> => {
check: async (): Promise<[string[], string[]]> => {
if (stakingChains.length != 8) {
return [`Wrong number of staking chains ${stakingChains.length}`, ""];
return [[`Wrong number of staking chains ${stakingChains.length}`], []];
}
return ["", ""];
return [[], []];
}
},
];
@ -49,44 +49,44 @@ export class Validators implements ActionInterface {
steps.push(
{
getName: () => { return `Make sure chain ${chain} has valid list file, has logo`},
check: async () => {
check: async (): Promise<[string[], string[]]> => {
const validatorsListPath = getChainValidatorsListPath(chain);
if (!isValidJSON(validatorsListPath)) {
return [`Not valid Json file at path ${validatorsListPath}`, ""];
return [[`Not valid Json file at path ${validatorsListPath}`], []];
}
var error: string = "";
var errors: string[] = [];
const validatorsList = getChainValidatorsList(chain);
const chainValidatorsAssetsList = getChainValidatorsAssets(chain);
await bluebird.each(validatorsList, async (val: ValidatorModel) => {
if (!isValidatorHasAllKeys(val)) {
error += `Some key and/or type missing for validator ${JSON.stringify(val)}\n`;
errors.push(`Some key and/or type missing for validator ${JSON.stringify(val)}`);
}
const id = val.id;
const path = getChainValidatorAssetLogoPath(chain, id);
if (!isPathExistsSync(path)) {
error += `Chain ${chain} asset ${id} logo must be present at path ${path}\n`;
errors.push(`Chain ${chain} asset ${id} logo must be present at path ${path}`);
}
const [isOk, logoMsg] = await isLogoOK(path);
if (!isOk) {
error += logoMsg + "\n";
errors.push(logoMsg);
}
// Make sure validator has corresponding logo
if (!(chainValidatorsAssetsList.indexOf(id) >= 0)) {
error += `Expecting image asset for validator ${id} on chain ${chain}\n`;
errors.push(`Expecting image asset for validator ${id} on chain ${chain}`);
}
});
// Make sure validator asset logo has corresponding info
chainValidatorsAssetsList.forEach(valAssetLogoID => {
if (validatorsList.filter(v => v.id === valAssetLogoID).length != 1) {
error += `Expect validator logo ${valAssetLogoID} to have info\n`;
errors.push(`Expect validator logo ${valAssetLogoID} to have info`);
}
});
return [error, ""];
return [errors, []];
}
}
);

View File

@ -18,14 +18,14 @@ export class WavesAction implements ActionInterface {
{
getName: () => { return "Waves validator assets must have correct format"},
check: async () => {
var error: string = "";
var errors: string[] = [];
const assets = getChainValidatorsAssets(Waves);
assets.forEach(addr => {
if (!(isWavesAddress(addr))) {
error += `Address ${addr} should be a Waves address'\n`;
errors.push(`Address ${addr} should be a Waves address'`);
}
});
return [error, ""];
return [errors, []];
}
},
];