Skip to content

Commit

Permalink
fix: issue #513 in eth-steth: Using blockNumber - 1 instead of block.…
Browse files Browse the repository at this point in the history
…parentHash is a subject to reorg false alarms
  • Loading branch information
sergeyWh1te committed Apr 9, 2024
1 parent 8b2cce5 commit 29b906e
Show file tree
Hide file tree
Showing 19 changed files with 156 additions and 122 deletions.
8 changes: 4 additions & 4 deletions ethereum-steth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@
"stake": "forta-agent stake",
"test": "jest",
"generate-types": "typechain --target=ethers-v5 --out-dir=./src/generated ./src/abi/*",
"eslint:lint": "eslint ./src ./tests",
"eslint:format": "eslint ./src ./tests --fix",
"prettier:check": "prettier --check ./src ./tests",
"prettier:format": "prettier --write ./src ./tests README.md",
"eslint:lint": "eslint ./src",
"eslint:format": "eslint ./src --fix",
"prettier:check": "prettier --check ./src",
"prettier:format": "prettier --write ./src README.md",
"lint": "yarn run prettier:check && yarn run eslint:lint",
"format": "yarn run eslint:format && yarn run prettier:format",
"postinstall": "yarn generate-types"
Expand Down
15 changes: 11 additions & 4 deletions ethereum-steth/src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { TransactionEvent } from 'forta-agent/dist/sdk/transaction.event'
import { Metadata } from './entity/metadata'
import Version from './utils/version'
import { ETH_DECIMALS } from './utils/constants'
import { BlockDto } from './entity/events'

export function initialize(): Initialize {
const metadata: Metadata = {
Expand Down Expand Up @@ -138,11 +139,17 @@ export const handleBlock = (): HandleBlock => {
out.push(...findingsAsync)
}

const blockDto: BlockDto = {
number: blockEvent.block.number,
timestamp: blockEvent.block.timestamp,
parentHash: blockEvent.block.parentHash,
}

const [bufferedEthFindings, withdrawalsFindings, gateSealFindings, vaultFindings] = await Promise.all([
app.StethOperationSrv.handleBlock(blockEvent),
app.WithdrawalsSrv.handleBlock(blockEvent),
app.GateSealSrv.handleBlock(blockEvent),
app.VaultSrv.handleBlock(blockEvent),
app.StethOperationSrv.handleBlock(blockDto),
app.WithdrawalsSrv.handleBlock(blockDto),
app.GateSealSrv.handleBlock(blockDto),
app.VaultSrv.handleBlock(blockDto),
])

out.push(...bufferedEthFindings, ...withdrawalsFindings, ...gateSealFindings, ...vaultFindings)
Expand Down
15 changes: 9 additions & 6 deletions ethereum-steth/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,19 @@ export class App {
return knex(config)
}

public static async getInstance(): Promise<Container> {
public static async getInstance(rpcUrl?: string): Promise<Container> {
if (!App.instance) {
const db = App.getConnection()

const drpcProvider = `https://eth.drpc.org`
const mainnet = 1
const drcpClient = new ethers.providers.JsonRpcProvider(drpcProvider, mainnet)

const etherscanKey = Buffer.from('SVZCSjZUSVBXWUpZSllXSVM0SVJBSlcyNjRITkFUUjZHVQ==', 'base64').toString('utf-8')
const ethersProvider = getEthersProvider()
let ethersProvider = getEthersProvider()
if (rpcUrl !== undefined) {
ethersProvider = drcpClient
}
ethersProvider.formatter = new FormatterWithEIP1898()

const etherscanProvider = new ethers.providers.EtherscanProvider(ethersProvider.network, etherscanKey)
Expand All @@ -96,10 +103,6 @@ export class App {

const lidoContact = Lido__factory.connect(address.LIDO_STETH_ADDRESS, ethersProvider)

const drpcProvider = `https://eth.drpc.org`

const mainnet = 1
const drcpClient = new ethers.providers.JsonRpcProvider(drpcProvider, mainnet)
const wdQueueContact = WithdrawalQueueERC721__factory.connect(address.WITHDRAWALS_QUEUE_ADDRESS, drcpClient)

const gateSealContact = GateSeal__factory.connect(address.GATE_SEAL_DEFAULT_ADDRESS, ethersProvider)
Expand Down
26 changes: 25 additions & 1 deletion ethereum-steth/src/clients/eth_provider.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { App } from '../app'
import * as E from 'fp-ts/Either'
import { Address } from '../utils/constants'
import { Address, ETH_DECIMALS } from '../utils/constants'
import { GateSeal } from '../entity/gate_seal'
import { JsonRpcProvider } from '@ethersproject/providers'
import { ethers } from 'forta-agent'

describe('eth provider tests', () => {
let ethProvider: JsonRpcProvider
const mainnet = 1
const drpcProvider = 'https://eth.drpc.org/'

beforeAll(async () => {
ethProvider = new ethers.providers.JsonRpcProvider(drpcProvider, mainnet)
})

test('getWithdrawalStatuses should return 1750 withdrawal statuses', async () => {
const app = await App.getInstance()

Expand Down Expand Up @@ -40,4 +50,18 @@ describe('eth provider tests', () => {

expect(resp.right).toEqual(expected)
}, 120_000)

test('getBalanceByBlockHash is ok', async () => {
const app = await App.getInstance(drpcProvider)

const blockNumber = 19_140_476
const block = await ethProvider.getBlock(blockNumber)

const resp = await app.ethClient.getBalanceByBlockHash(Address.WITHDRAWALS_QUEUE_ADDRESS, block.parentHash)
if (E.isLeft(resp)) {
throw resp.left.message
}

expect(resp.right.dividedBy(ETH_DECIMALS).toNumber()).toEqual(16_619.29059680177)
}, 120_000)
})
4 changes: 1 addition & 3 deletions ethereum-steth/src/clients/eth_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,7 @@ export class ETHProvider implements IGateSealClient, IStethClient, IVaultClient,
try {
const out = await retryAsync<EtherBigNumber>(
async (): Promise<EtherBigNumber> => {
return await this.jsonRpcProvider.getBalance(address, {
blockHash: blockHash,
} as never)
return await this.jsonRpcProvider.getBalance(address, blockHash)
},
{ delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 },
)
Expand Down
6 changes: 6 additions & 0 deletions ethereum-steth/src/entity/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ export type EventOfNotice = {
severity: FindingSeverity
type: FindingType
}

export type BlockDto = {
number: number
timestamp: number
parentHash: string
}
1 change: 1 addition & 0 deletions ethereum-steth/src/generated/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*
21 changes: 11 additions & 10 deletions ethereum-steth/src/services/gate-seal/GateSeal.srv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { etherscanAddress } from '../../utils/string'
import { Logger } from 'winston'
import { networkAlert } from '../../utils/errors'
import { IGateSealClient } from './contract'
import { BlockEvent, filterLog, Finding, FindingSeverity, FindingType } from 'forta-agent'
import { filterLog, Finding, FindingSeverity, FindingType } from 'forta-agent'
import { BlockDto } from '../../entity/events'

const ONE_HOUR = 60 * 60
const ONE_DAY = 24 * ONE_HOUR
Expand Down Expand Up @@ -102,13 +103,13 @@ export class GateSealSrv {
return this.name
}

public async handleBlock(blockEvent: BlockEvent): Promise<Finding[]> {
public async handleBlock(blockDto: BlockDto): Promise<Finding[]> {
const start = new Date().getTime()
const findings: Finding[] = []

const [pauseRoleFindings, expiryGateSealFindings] = await Promise.all([
this.handlePauseRole(blockEvent),
this.handleExpiryGateSeal(blockEvent),
this.handlePauseRole(blockDto),
this.handleExpiryGateSeal(blockDto),
])

findings.push(...pauseRoleFindings, ...expiryGateSealFindings)
Expand All @@ -117,14 +118,14 @@ export class GateSealSrv {
return findings
}

public async handlePauseRole(blockEvent: BlockEvent): Promise<Finding[]> {
public async handlePauseRole(blockDto: BlockDto): Promise<Finding[]> {
const out: Finding[] = []
if (this.gateSealAddress === undefined) {
return []
}

const currentBlockTimestamp = blockEvent.block.timestamp
const status = await this.ethProvider.checkGateSeal(blockEvent.block.number, this.gateSealAddress)
const currentBlockTimestamp = blockDto.timestamp
const status = await this.ethProvider.checkGateSeal(blockDto.number, this.gateSealAddress)
if (E.isLeft(status)) {
if (status.left === GateSealExpiredErr) {
const f = Finding.fromObject({
Expand Down Expand Up @@ -182,13 +183,13 @@ export class GateSealSrv {
return out
}

public async handleExpiryGateSeal(blockEvent: BlockEvent): Promise<Finding[]> {
public async handleExpiryGateSeal(blockDto: BlockDto): Promise<Finding[]> {
if (this.gateSealAddress === undefined) {
return []
}

const currentBlockTimestamp = blockEvent.block.timestamp
const expiryTimestamp = await this.ethProvider.getExpiryTimestamp(blockEvent.block.number)
const currentBlockTimestamp = blockDto.timestamp
const expiryTimestamp = await this.ethProvider.getExpiryTimestamp(blockDto.number)

if (E.isLeft(expiryTimestamp)) {
return [
Expand Down
20 changes: 14 additions & 6 deletions ethereum-steth/src/services/gate-seal/gate-seal.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { Finding, FindingSeverity, FindingType, getEthersProvider } from 'forta-agent'
import { etherBlockToFortaBlockEvent } from '../../../tests/e2e/utils'
import { App } from '../../app'
import { Address } from '../../utils/constants'
import * as E from 'fp-ts/Either'
import { GateSeal } from '../../entity/gate_seal'
import { expect } from '@jest/globals'
import { BlockDto } from '../../entity/events'

describe('GateSeal srv e2e tests', () => {
describe('GateSeal srv functional tests', () => {
const ethProvider = getEthersProvider()

test('handle pause role true', async () => {
const app = await App.getInstance()

const blockNumber = 19113580
const block = await ethProvider.getBlock(blockNumber)
const blockDto: BlockDto = {
number: block.number,
timestamp: block.timestamp,
parentHash: block.parentHash,
}

const initErr = await app.GateSealSrv.initialize(blockNumber)
if (initErr instanceof Error) {
Expand All @@ -33,8 +38,7 @@ describe('GateSeal srv e2e tests', () => {
}
expect(status.right).toEqual(expected)

const blockEvent = etherBlockToFortaBlockEvent(block)
const result = await app.GateSealSrv.handlePauseRole(blockEvent)
const result = await app.GateSealSrv.handlePauseRole(blockDto)

expect(result.length).toEqual(0)
}, 120_000)
Expand All @@ -51,8 +55,12 @@ describe('GateSeal srv e2e tests', () => {

const neededBlock = 19_172_615
const block = await ethProvider.getBlock(neededBlock)
const blockEvent = etherBlockToFortaBlockEvent(block)
const result = await app.GateSealSrv.handleExpiryGateSeal(blockEvent)
const blockDto: BlockDto = {
number: block.number,
timestamp: block.timestamp,
parentHash: block.parentHash,
}
const result = await app.GateSealSrv.handleExpiryGateSeal(blockDto)

const expected = Finding.fromObject({
alertId: 'GATE-SEAL-IS-ABOUT-TO-BE-EXPIRED',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
import { ethers, Finding, FindingSeverity, FindingType, getEthersProvider, Network, Transaction } from 'forta-agent'
import { App } from '../../src/app'
import { App } from '../../app'
import { JsonRpcProvider } from '@ethersproject/providers'
import { createTransactionEvent, etherBlockToFortaBlockEvent } from './utils'
import { createTransactionEvent } from '../../utils/forta'
import BigNumber from 'bignumber.js'
import { BlockDto } from '../../entity/events'

const TEST_TIMEOUT = 60_000 // ms

describe('agent-steth-ops e2e tests', () => {
describe('Steth.srv functional tests', () => {
let ethProvider: JsonRpcProvider

beforeAll(async () => {
ethProvider = getEthersProvider()
})

test(
'should process block with low staking limit (10%)',
'LOW-STAKING-LIMIT',
async () => {
const app = await App.getInstance()
const blockNumber = 16704075
const blockNumber = 16_704_075
const block = await ethProvider.getBlock(blockNumber)

const blockEvent = etherBlockToFortaBlockEvent(block)
const blockDto: BlockDto = {
number: block.number,
timestamp: block.timestamp,
parentHash: block.parentHash,
}

const result = await app.StethOperationSrv.handleBlock(blockEvent)
const result = await app.StethOperationSrv.handleBlock(blockDto)

const expected = Finding.fromObject({
alertId: 'LOW-STAKING-LIMIT',
Expand All @@ -43,15 +48,19 @@ describe('agent-steth-ops e2e tests', () => {
)

test(
'should process block with huge buffered ETH amount and low deposit executor balance',
'LOW-DEPOSIT-EXECUTOR-BALANCE',
async () => {
const app = await App.getInstance()
const blockNumber = 17241600
const block = await ethProvider.getBlock(blockNumber)

const blockEvent = etherBlockToFortaBlockEvent(block)
const blockDto: BlockDto = {
number: block.number,
timestamp: block.timestamp,
parentHash: block.parentHash,
}

const result = await app.StethOperationSrv.handleDepositExecutorBalance(blockEvent.block.number, block.timestamp)
const result = await app.StethOperationSrv.handleDepositExecutorBalance(blockDto.number, blockDto.timestamp)

const expected = Finding.fromObject({
alertId: 'LOW-DEPOSIT-EXECUTOR-BALANCE',
Expand Down Expand Up @@ -127,7 +136,7 @@ describe('agent-steth-ops e2e tests', () => {
)

test(
'should process tx with transferred ownership of Insurance fund',
'Insurance fund',
async () => {
const app = await App.getInstance()
const txHash = '0x91c7c2f33faf3b5fb097138c1d49c1d4e83f99e1c3b346b3cad35a5928c03b3a'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { StethOperationCache } from './StethOperation.cache'
import { ETH_DECIMALS } from '../../utils/constants'
import * as E from 'fp-ts/Either'
import { BlockEvent, Finding, FindingSeverity, FindingType } from 'forta-agent'
import { Finding, FindingSeverity, FindingType } from 'forta-agent'
import { EventOfNotice } from '../../entity/events'
import { elapsedTime } from '../../utils/time'
import { IStethClient, TransactionEventContract } from './contracts'
import { Logger } from 'winston'
import { alertId_token_rebased } from '../../utils/events/lido_events'
import { networkAlert } from '../../utils/errors'
import { BlockDto } from '../../entity/events'

// Formula: (60 * 60 * 72) / 13 = 19_938
const HISTORY_BLOCK_OFFSET: number = Math.floor((60 * 60 * 72) / 13)
Expand Down Expand Up @@ -101,15 +102,15 @@ export class StethOperationSrv {
return this.name
}

public async handleBlock(blockEvent: BlockEvent) {
public async handleBlock(blockDto: BlockDto) {
const start = new Date().getTime()
const findings: Finding[] = []

const [bufferedEthFindings, depositorBalanceFindings, stakingLimitFindings] = await Promise.all([
this.handleBufferedEth(blockEvent.block.number, blockEvent.block.timestamp),
this.handleDepositExecutorBalance(blockEvent.block.number, blockEvent.block.timestamp),
this.handleStakingLimit(blockEvent.block.number, blockEvent.block.timestamp),
this.handleShareRateChange(blockEvent.block.number),
this.handleBufferedEth(blockDto.number, blockDto.timestamp),
this.handleDepositExecutorBalance(blockDto.number, blockDto.timestamp),
this.handleStakingLimit(blockDto.number, blockDto.timestamp),
this.handleShareRateChange(blockDto.number),
])

findings.push(...bufferedEthFindings, ...depositorBalanceFindings, ...stakingLimitFindings)
Expand Down
Loading

0 comments on commit 29b906e

Please sign in to comment.