Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: automatic system alerts #141

Merged
merged 6 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
693 changes: 40 additions & 653 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions packages/app/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<the-header :class="$route?.name" />
<div class="container-app">
<NetworkDeprecated v-if="!currentNetwork.maintenance && currentNetwork.name === 'goerli'" />
<IssuesBanner v-if="!currentNetwork.maintenance && currentNetwork.name === 'mainnet'" />
<IndexerDelayAlert v-if="!currentNetwork.maintenance && currentNetwork.name === 'mainnet'" />
<MaintenanceView v-if="currentNetwork.maintenance" />
<router-view v-else />
</div>
Expand All @@ -14,7 +14,7 @@
<script setup lang="ts">
import { useTitle } from "@vueuse/core";

import IssuesBanner from "@/components/IssuesBanner.vue";
import IndexerDelayAlert from "@/components/IndexerDelayAlert.vue";
import NetworkDeprecated from "@/components/NetworkDeprecated.vue";
import TheFooter from "@/components/TheFooter.vue";
import TheHeader from "@/components/header/TheHeader.vue";
Expand Down
68 changes: 68 additions & 0 deletions packages/app/src/components/IndexerDelayAlert.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<template>
<SystemAlert v-if="isIndexerDelayed">
<span
v-if="latestTPS > MIN_TPS_TO_SHOW_HEAVY_LOAD_ALERT"
v-html="t('systemAlert.indexerDelayedDueToHeavyLoad', { indexerDelayInHours })"
/>
<span v-else v-html="t('systemAlert.indexerDelayed', { indexerDelayInHours })" />
</SystemAlert>
</template>

<script lang="ts" setup>
import { computed, ref } from "vue";
import { useI18n } from "vue-i18n";

import SystemAlert from "@/components/common/SystemAlert.vue";

import useBlocks from "@/composables/useBlocks";
import useContext from "@/composables/useContext";

import type { types } from "zksync-web3";

const { t } = useI18n();
const context = useContext();

const MIN_DELAY_TO_SHOW_ALERT = 900_000; // 15 mins
const MIN_TPS_TO_SHOW_HEAVY_LOAD_ALERT = 70;

const provider = context.getL2Provider();
const latestBlock = ref<types.Block | null>(null);
(async () => {
const block = await provider.getBlock("latest");
latestBlock.value = block;
})();

const { load: getLatestIndexedBlocks, data: latestIndexedBlocks } = useBlocks(context);
getLatestIndexedBlocks(1);

const indexerDelay = computed(() => {
if (!latestBlock.value?.number || !latestIndexedBlocks.value?.length) {
return 0;
}
const latestBlockDate = new Date(latestBlock.value.timestamp * 1000);
const latestIndexedBlockDate = new Date(latestIndexedBlocks.value[0].timestamp);
const delay = latestBlockDate.getTime() - latestIndexedBlockDate.getTime();
return delay;
});

const indexerDelayInHours = computed(() => (indexerDelay.value / (1000 * 60 * 60)).toFixed(1));

const isIndexerDelayed = computed(() => {
if (indexerDelay.value > MIN_DELAY_TO_SHOW_ALERT) {
return true;
}
return false;
});

const latestTPS = computed(() => {
if (!latestIndexedBlocks.value?.length) {
return 0;
}
const numberOfTransactions = latestIndexedBlocks.value.reduce((acc, block) => acc + block.size, 0);
const latestBlockDate = new Date(latestIndexedBlocks.value[0].timestamp);
const firstBlockDate = new Date(latestIndexedBlocks.value[latestIndexedBlocks.value.length - 1].timestamp);
const numberOfSeconds = Math.floor((latestBlockDate.getTime() - firstBlockDate.getTime()) / 1000);
const tps = numberOfSeconds ? numberOfTransactions / numberOfSeconds : 0;
return tps;
});
</script>
31 changes: 0 additions & 31 deletions packages/app/src/components/IssuesBanner.vue

This file was deleted.

26 changes: 6 additions & 20 deletions packages/app/src/components/NetworkDeprecated.vue
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
<template v-if="newNetworkUrl">
<div class="deprecated-alert">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M21.0907 18.7005C20.8457 19.1905 20.3449 19.5 19.7971 19.5H4.20288C3.65506 19.5 3.15426 19.1905 2.90927 18.7005C2.69455 18.2711 2.70694 17.7631 2.94232 17.3446L10.9455 3.11671C11.1598 2.73575 11.5629 2.5 12 2.5C12.4371 2.5 12.8402 2.73575 13.0545 3.11671L21.0577 17.3446C21.2931 17.7631 21.3054 18.2711 21.0907 18.7005Z"
stroke="#FFC81A"
/>
<circle cx="12" cy="16" r="1" fill="#FFC81A" />
<path d="M12 14V6" stroke="#FFC81A" />
</svg>
<SystemAlert>
<span
>We are ending our support of Goerli testnet. Please <a :href="newNetworkUrl!">use Sepolia</a>. For more info see
<a target="_blank" href="https://github.com/zkSync-Community-Hub/zkync-developers/discussions/228"
>this announcement</a
>.</span
>
</div>
</SystemAlert>
</template>

<script lang="ts" setup>
import { computed } from "vue";
import { useRoute } from "vue-router";

import SystemAlert from "@/components/common/SystemAlert.vue";

import useContext from "@/composables/useContext";

import { getWindowLocation } from "@/utils/helpers";
Expand All @@ -44,15 +38,7 @@ const newNetworkUrl = computed(() => {
</script>

<style scoped lang="scss">
.deprecated-alert {
@apply flex text-white rounded-2xl border border-amber-400/50 bg-amber-400/10 mb-6 py-3 px-4;

svg {
@apply mr-2 shrink-0;
}

a {
@apply text-inherit;
}
a {
@apply text-inherit;
}
</style>
22 changes: 22 additions & 0 deletions packages/app/src/components/common/SystemAlert.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<template>
<div class="system-alert">
<IconError class="icon" color="#FFC81A" />
<span class="system-alert-body">
<slot />
</span>
</div>
</template>

<script setup lang="ts">
import IconError from "@/components/icons/IconError.vue";
</script>

<style scoped lang="scss">
.system-alert {
@apply flex items-center text-white rounded-2xl border border-amber-400/50 bg-amber-400/10 mb-6 py-3 px-4;

.system-alert-body {
@apply ml-3 w-full;
}
}
</style>
8 changes: 0 additions & 8 deletions packages/app/src/components/header/TheHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -305,10 +305,6 @@ const hasContent = computed(() => {
.hero-banner-container {
@apply absolute left-0 top-full flex h-64 w-full items-end justify-end overflow-hidden bg-primary-900;

&.mainnet {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hack was needed to increase the height of the header background (which has a fixed size) when a warning message is shown. Otherwise the header text is pushed down and is not visible:
image
Having dynamic error messages we will also need to change the height of the background image dynamically or rework the whole thing.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We generally don't support mobile, but this should be fixed properly. Out of scope of this PR I'd say.

@apply h-[27rem] md:h-[23rem] lg:h-[20rem];
}

&.goerli {
@apply h-[25rem] md:h-[23rem] lg:h-[19rem];
}
Expand All @@ -320,10 +316,6 @@ const hasContent = computed(() => {
.home-banner {
@apply h-80;

&.mainnet {
@apply h-[32rem] md:h-[28rem] lg:h-[25rem];
}

&.goerli {
@apply h-[30rem] md:h-[27rem] lg:h-[24rem];
}
Expand Down
4 changes: 4 additions & 0 deletions packages/app/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -721,5 +721,9 @@
"callData": "calldata"
}
}
},
"systemAlert": {
"indexerDelayed": "Transaction indexing is {indexerDelayInHours} hours behind. Transactions are being processed normally and will gradually show up. You can also use other <a href=\"https://zksync.io/explore/\" style=\"color: inherit\">explorers</a> meanwhile.",
"indexerDelayedDueToHeavyLoad": "The network is under a heavy load at the moment and transaction indexing on the explorer is {indexerDelayInHours} hours behind. Transactions are being processed normally and will gradually show up. You can also use other <a href=\"https://zksync.io/explore/\" style=\"color: inherit\">explorers</a> meanwhile."
}
}
4 changes: 4 additions & 0 deletions packages/app/src/locales/uk.json
Original file line number Diff line number Diff line change
Expand Up @@ -438,5 +438,9 @@
"callData": "дані виклику"
}
}
},
"systemAlert": {
"indexerDelayed": "Індексація транзакцій відстає на {indexerDelayInHours} годин. Транзакції будуть поступово оброблені та відображені. Ви також можете скористатися іншими <a href=\"https://zksync.io/explore/\" style=\"color: inherit\">блок експлорерами</a> наразі.",
"indexerDelayedDueToHeavyLoad": "Мережа наразі перебуває під великим навантаженням, індексація транзакцій відстає на {indexerDelayInHours} годин. Транзакції будуть поступово оброблені та відображені. Ви також можете скористатися іншими <a href=\"https://zksync.io/explore/\" style=\"color: inherit\">блок експлорерами</a> наразі."
}
}
4 changes: 2 additions & 2 deletions packages/app/tests/e2e/features/copying.feature

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions packages/app/tests/e2e/src/steps/blockexplorer.steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,13 @@ Then("Clipboard contains {string} value", async function (this: ICustomWorld, te
await expect(result).toBe(text);
});

Then("Clipboard includes {string} value", async function (this: ICustomWorld, text: string) {
helper = new Helper(this);
result = await helper.getClipboardValue();

await expect(result.includes(text)).toBe(true);
});

Then("Clipboard value is not empty", async function (this: ICustomWorld) {
helper = new Helper(this);
result = await helper.getClipboardValue();
Expand Down
1 change: 0 additions & 1 deletion packages/worker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
"@willsoto/nestjs-prometheus": "^4.7.0",
"axios": "^1.4.0",
"ethers": "^5.7.1",
"firebase": "^10.7.1",
"nest-winston": "^1.7.0",
"pg": "^8.8.0",
"prom-client": "^14.1.0",
Expand Down
10 changes: 5 additions & 5 deletions packages/worker/src/health/health.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ describe("HealthController", () => {
});

describe("check", () => {
// it("checks health of the DB", async () => {
// await healthController.check();
// expect(dbHealthCheckerMock.pingCheck).toHaveBeenCalledTimes(1);
// expect(dbHealthCheckerMock.pingCheck).toHaveBeenCalledWith("database");
// });
it("checks health of the DB", async () => {
await healthController.check();
expect(dbHealthCheckerMock.pingCheck).toHaveBeenCalledTimes(1);
expect(dbHealthCheckerMock.pingCheck).toHaveBeenCalledWith("database");
});

it("checks health of the JSON RPC provider", async () => {
await healthController.check();
Expand Down
6 changes: 3 additions & 3 deletions packages/worker/src/health/health.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Logger, Controller, Get } from "@nestjs/common";
import { HealthCheckService, /*TypeOrmHealthIndicator,*/ HealthCheck, HealthCheckResult } from "@nestjs/terminus";
import { HealthCheckService, TypeOrmHealthIndicator, HealthCheck, HealthCheckResult } from "@nestjs/terminus";
import { JsonRpcHealthIndicator } from "./jsonRpcProvider.health";

@Controller(["health", "ready"])
Expand All @@ -8,7 +8,7 @@ export class HealthController {

constructor(
private readonly healthCheckService: HealthCheckService,
//private readonly dbHealthChecker: TypeOrmHealthIndicator,
private readonly dbHealthChecker: TypeOrmHealthIndicator,
private readonly jsonRpcHealthIndicator: JsonRpcHealthIndicator
) {
this.logger = new Logger(HealthController.name);
Expand All @@ -19,7 +19,7 @@ export class HealthController {
public async check(): Promise<HealthCheckResult> {
try {
return await this.healthCheckService.check([
//() => this.dbHealthChecker.pingCheck("database"),
() => this.dbHealthChecker.pingCheck("database"),
() => this.jsonRpcHealthIndicator.isHealthy("jsonRpcProvider"),
]);
} catch (error) {
Expand Down
43 changes: 0 additions & 43 deletions packages/worker/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,12 @@ import { ConfigService } from "@nestjs/config";
import logger from "./logger";
import { AppModule } from "./app.module";

import { initializeApp } from "firebase/app";
import { getFirestore, onSnapshot, doc } from "firebase/firestore";

const firebaseApp = initializeApp({
projectId: "scan-v2",
});
const db = getFirestore(firebaseApp);

const getConfig = async (instanceId: string): Promise<{ fromBlock: string; toBlock: string }> => {
return new Promise((resolve, reject) => {
const unsubscribe = onSnapshot(
doc(db, "config", "worker"),
(data) => {
if (data.exists()) {
const config = data.data() as Record<string, { fromBlock: string; toBlock: string }>;
if (config[instanceId] && config[instanceId].fromBlock && config[instanceId].toBlock) {
unsubscribe();
resolve(config[instanceId]);
}
}
},
(e) => {
logger.error(e);
reject(e);
}
);
});
};

async function bootstrap() {
process.on("uncaughtException", function (error) {
logger.error(error.message, error.stack, "UnhandledExceptions");
process.exit(1);
});

if (process.env.USE_REMOTE_CONFIG === "true") {
const config = await getConfig(process.env.HOSTNAME);
if (!config.fromBlock || !config.toBlock) {
throw new Error("Missing fromBlock or toBlock in config");
}
logger.log(`Using fromBlock: ${config.fromBlock}, toBlock: ${config.toBlock}`);
process.env.FROM_BLOCK = config.fromBlock;
process.env.TO_BLOCK = config.toBlock;
process.env.DISABLE_BATCHES_PROCESSING = "true";
process.env.DISABLE_COUNTERS_PROCESSING = "true";
process.env.DISABLE_OLD_BALANCES_CLEANER = "true";
process.env.DISABLE_BLOCKS_REVERT = "true";
}

const app = await NestFactory.create(AppModule, {
logger,
});
Expand Down
Loading