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(val-947): Forta alerts for SR1.5 #588

Merged
merged 30 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2b659de
feat(val-947): Forta alerts for SR1.5
AlexandrMov Aug 6, 2024
6c84eb4
feat(val-947): CSM support
AlexandrMov Aug 16, 2024
225e2e3
feat(val-947): accounting oracle changes
AlexandrMov Aug 17, 2024
da2dfbf
feat(val-947): batch processing
AlexandrMov Aug 23, 2024
185a713
chore(val-947): refactor modules
AlexandrMov Aug 23, 2024
a514048
Merge branch 'main' into feature/val-947-forta-alerts-for-sr15-1
AlexandrMov Sep 16, 2024
eb91d11
fix(val-947): simple dvt alerts
AlexandrMov Sep 16, 2024
eb24648
chore(val-947): linter
AlexandrMov Sep 16, 2024
77cf7db
fix(val-947): event signature fixed
AlexandrMov Sep 30, 2024
4e06db9
fix(val-947): e2e tests
AlexandrMov Oct 2, 2024
e890104
feat(val-947): sanity checker
AlexandrMov Oct 2, 2024
67995d7
feat(val-947): changes for gov bot
AlexandrMov Oct 5, 2024
0e2f115
feat(val-947): csm and roles
AlexandrMov Oct 6, 2024
0db87bc
fix(val-947): new addresses
AlexandrMov Oct 10, 2024
a7226a7
fix(val-947): sanity checker abi
AlexandrMov Oct 15, 2024
ada8061
feat(val-1125): Create alert for insufficient balance for Council Dae…
AlexandrMov Oct 16, 2024
9dffc29
chore: readme
AlexandrMov Oct 16, 2024
50567d8
fix(val-1125): trigger alert once in a week
AlexandrMov Oct 16, 2024
954029f
chore(val-1125): split into simple functions
AlexandrMov Oct 16, 2024
2061ff0
fix(val-1125): one week replaced with one day
AlexandrMov Oct 17, 2024
86b58fc
fix(val-947): add envs to pipelines
AlexandrMov Oct 18, 2024
8995c30
Merge branch 'feature/val-1125-create-alert-for-insufficient-balance-…
AlexandrMov Oct 20, 2024
7137324
feat(val-1125): add alerts for guardians
AlexandrMov Oct 20, 2024
1bef95d
fix(val-947): tests timeout
AlexandrMov Oct 20, 2024
1b4408a
fix(val-947): tests
AlexandrMov Oct 20, 2024
51cf999
fix(val-947): tests
AlexandrMov Oct 20, 2024
7074570
fix: alerts for gov-bot moved into #622
AlexandrMov Oct 21, 2024
60e2a94
fix: VETTED-KEYS-SET not for CSM
AlexandrMov Oct 25, 2024
3964ea9
fix: split tests
AlexandrMov Oct 29, 2024
9858823
chore: workflows
AlexandrMov Nov 6, 2024
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,15 @@ describe("agent-node-operators-registry e2e tests", () => {
},
TEST_TIMEOUT,
);

it(
"should process tx with added simple dvt clusters",
async () => {
const findings = await runTransaction(
"0xc3338fc9ef0419109b90dcc318ac89bcdb235a1fe7b9960611a7d0666c1e8170",
);
expect(findings).toMatchSnapshot();
},
TEST_TIMEOUT,
);
});
2 changes: 1 addition & 1 deletion ethereum-validators-set/src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const STAKING_ROUTER_ADDRESS =
export const CURATED_NODE_OPERATORS_REGISTRY_ADDRESS =
"0x55032650b14df07b85bf18a3a3ec8e0af2e028d5";
export const SIMPLEDVT_NODE_OPERATORS_REGISTRY_ADDRESS =
"0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433";
"0xae7b191a31f627b4eb1d4dac64eab9976995b433";
export const DEPOSIT_SECURITY_ADDRESS =
"0xc77f8768774e1c9244beed705c4354f2113cfc09";
export const EL_REWARDS_VAULT_ADDRESS =
Expand Down
4 changes: 0 additions & 4 deletions ethereum-validators-set/src/common/interfaces.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { ethers } from "forta-agent";
import NODE_OPERATORS_REGISTRY_ABI from "../../abi/NodeOperatorsRegistry.json";
import { ethersProvider } from "../../ethers";
import {
NodeOperatorFullInfo,
NodeOperatorSummary,
NodeOperatorModuleParams,
} from "./interfaces";

export abstract class BaseRegistryModule<
ModuleParams extends NodeOperatorModuleParams,
> {
public nodeOperatorNames = new Map<number, NodeOperatorFullInfo>();
public readonly contract: ethers.Contract;
protected readonly batchSize = 20;

constructor(
public readonly params: ModuleParams,
protected readonly stakingRouter: ethers.Contract,
) {
this.contract = new ethers.Contract(
this.params.moduleAddress,
NODE_OPERATORS_REGISTRY_ABI,
ethersProvider,
);
}

async initialize(currentBlock: number) {
return await this.updateNodeOperatorsInfo(currentBlock);
}

getOperatorName(nodeOperatorId: string): string {
return (
this.nodeOperatorNames.get(Number(nodeOperatorId))?.name ?? "undefined"
);
}

abstract fetchOperatorName(
operatorId: string,
block: number,
): Promise<string>;

async updateNodeOperatorsInfo(block: number) {
const operatorsMap = await this.fetchAllNodeOperatorSummaries(block);

await Promise.all(
Array.from(operatorsMap.keys()).map(async (operatorId: string) => {
const name = await this.fetchOperatorName(operatorId, block);
const rewardAddress =
this.nodeOperatorNames.get(Number(operatorId))?.rewardAddress || "";
this.nodeOperatorNames.set(Number(operatorId), {
name,
rewardAddress,
});
}),
);

return operatorsMap;
}

protected async fetchAllNodeOperatorSummaries(
currentBlock: number,
): Promise<Map<string, NodeOperatorSummary>> {
const operatorsMap = new Map<string, NodeOperatorSummary>();
const [operatorsCountBN] =
await this.contract.functions.getNodeOperatorsCount({
blockTag: currentBlock,
});
const operatorsCount = operatorsCountBN.toNumber();

for (let offset = 0; offset < operatorsCount; offset += this.batchSize) {
const batchSize = Math.min(this.batchSize, operatorsCount - offset);
const summaries = await Promise.all(
Array.from({ length: batchSize }, (_, i) =>
this.contract.functions.getNodeOperatorSummary(offset + i, {
blockTag: currentBlock,
}),
),
);

summaries.forEach((summary, index) => {
operatorsMap.set(String(offset + index), summary);
});
}

return operatorsMap;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { BaseRegistryModule } from "./base-node-operators-registry";
import { NodeOperatorModuleParams } from "./interfaces";

export class CommunityRegistryModule extends BaseRegistryModule<NodeOperatorModuleParams> {
async fetchOperatorName(operatorId: string, _block: number): Promise<string> {
return `CSM Operator #${operatorId}`;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { BaseRegistryModule } from "./base-node-operators-registry";
import { NodeOperatorModuleParams } from "./interfaces";

export class CuratedRegistryModule extends BaseRegistryModule<NodeOperatorModuleParams> {
async fetchOperatorName(operatorId: string, block: number): Promise<string> {
const { name } = await this.contract.getNodeOperator(operatorId, true, {
blockTag: block,
});
return name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export interface NodeOperatorModuleParams {
moduleId: number;
moduleAddress: string;
moduleName: string;
alertPrefix: string;
}

export interface NodeOperatorFullInfo {
name: string;
rewardAddress: string;
}

export interface NodeOperatorSummary {
targetLimitMode: number;
targetValidatorsCount: number;
stuckValidatorsCount: number;
refundedValidatorsCount: number;
stuckPenaltyEndTimestamp: number;
totalExitedValidators: number;
totalDepositedValidators: number;
depositableValidatorsCount: number;
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const {
ACCOUNTING_ORACLE_ADDRESS,
MAX_ORACLE_REPORT_MAIN_DATA_SUBMIT_DELAY,
MAX_ORACLE_REPORT_EXTRA_DATA_SUBMIT_AFTER_MAIN_DATA_DELAY,
MAX_ORACLE_REPORT_EXTRA_DATA_DELAY_BETWEEN_SUBMITING,
ACCOUNTING_ORACLE_EVENTS_OF_NOTICE,
} = requireWithTier<typeof Constants>(
module,
Expand Down Expand Up @@ -76,7 +77,10 @@ export async function initialize(
return {};
}

async function getReportMainDataSubmits(blockFrom: number, blockTo: number) {
export async function getReportMainDataSubmits(
blockFrom: number,
blockTo: number,
) {
const accountingOracle = new ethers.Contract(
ACCOUNTING_ORACLE_ADDRESS,
ACCOUNTING_ORACLE_ABI,
Expand All @@ -93,7 +97,10 @@ async function getReportMainDataSubmits(blockFrom: number, blockTo: number) {
);
}

async function getReportExtraDataSubmits(blockFrom: number, blockTo: number) {
export async function getReportExtraDataSubmits(
blockFrom: number,
blockTo: number,
) {
const accountingOracle = new ethers.Contract(
ACCOUNTING_ORACLE_ADDRESS,
ACCOUNTING_ORACLE_ABI,
Expand All @@ -118,13 +125,13 @@ export async function handleBlock(blockEvent: BlockEvent) {
return findings;
}

async function handleMainDataReportSubmitted(
export async function handleMainDataReportSubmitted(
blockEvent: BlockEvent,
findings: Finding[],
) {
const now = blockEvent.block.timestamp;
const reportMainDataSubmitDelay =
now - lastReportMainDataSubmitTimestamp ?? 0;
now - (lastReportMainDataSubmitTimestamp ?? 0);

if (
(reportMainDataSubmitDelay > MAX_ORACLE_REPORT_MAIN_DATA_SUBMIT_DELAY &&
Expand All @@ -133,8 +140,6 @@ async function handleMainDataReportSubmitted(
MAX_ORACLE_REPORT_EXTRA_DATA_SUBMIT_AFTER_MAIN_DATA_DELAY &&
now - lastReportExtraDataSubmitOverdueTimestamp >= TRIGGER_PERIOD)
) {
// fetch events history 1 more time to be sure that there were actually no reports during last 25 hours
// needed to handle situation with the missed TX with prev report
const mainDataSubmits = await getReportMainDataSubmits(
blockEvent.blockNumber - Math.ceil((24 * ONE_HOUR) / SECONDS_PER_SLOT),
blockEvent.blockNumber - 1,
Expand All @@ -148,9 +153,40 @@ async function handleMainDataReportSubmitted(
blockEvent.blockNumber - 1,
);
if (extraDataSubmits.length > 0) {
lastReportExtraDataSubmitTimestamp = (
await extraDataSubmits[extraDataSubmits.length - 1].getBlock()
).timestamp;
const lastExtraDataSubmit =
extraDataSubmits[extraDataSubmits.length - 1];
const lastExtraDataSubmitBlock = await lastExtraDataSubmit.getBlock();
lastReportExtraDataSubmitTimestamp = lastExtraDataSubmitBlock.timestamp;

const timeSinceLastExtraSubmit =
now - lastReportExtraDataSubmitTimestamp;
const itemsProcessed =
parseInt(lastExtraDataSubmit.args?.itemsProcessed) ?? 0;
const itemsCount = parseInt(lastExtraDataSubmit.args?.itemsCount) ?? 0;

if (
timeSinceLastExtraSubmit >
MAX_ORACLE_REPORT_EXTRA_DATA_DELAY_BETWEEN_SUBMITING &&
itemsProcessed !== itemsCount
) {
findings.push(
Finding.fromObject({
name: "🚨 Accounting Oracle: report EXTRA data submit incomplete",
description: `Time since last extra data submit: ${formatDelay(
timeSinceLastExtraSubmit,
)}. Items processed: ${itemsProcessed}/${itemsCount}.`,
alertId: "ACCOUNTING-ORACLE-EXTRA-DATA-INCOMPLETE",
severity: FindingSeverity.High,
type: FindingType.Degraded,
metadata: {
timeSinceLastExtraSubmit: `${timeSinceLastExtraSubmit}`,
itemProcessed: `${itemsProcessed}`,
itemCount: `${itemsCount}`,
},
}),
);
lastReportExtraDataSubmitOverdueTimestamp = now;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const MAX_REPORT_SUBMIT_SKIP_BLOCKS_MEDIUM = Math.floor(
export const MAX_ORACLE_REPORT_MAIN_DATA_SUBMIT_DELAY = ONE_DAY + 15 * 60; // 24h 15m
export const MAX_ORACLE_REPORT_EXTRA_DATA_SUBMIT_AFTER_MAIN_DATA_DELAY =
20 * 60; // 20m
export const MAX_ORACLE_REPORT_EXTRA_DATA_DELAY_BETWEEN_SUBMITING = 10 * 60; // 10m

export const MIN_ORACLE_BALANCE_INFO = 0.3; // 0.3 ETH

Expand Down
Loading
Loading