Skip to content

Commit db6b450

Browse files
authored
chore: add is horizon ready flag to network monitor (#1118)
1 parent c49ebe8 commit db6b450

File tree

4 files changed

+193
-0
lines changed

4 files changed

+193
-0
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { createLogger, Logger } from '@graphprotocol/common-ts'
2+
import { NetworkMonitor } from '../monitor'
3+
import { getTestProvider } from '../../utils'
4+
import { resolveChainId } from '../../indexer-management'
5+
import { GraphNode } from '../../graph-node'
6+
import { SubgraphClient } from '../../subgraph-client'
7+
import { SubgraphFreshnessChecker } from '../../subgraphs'
8+
import { mockLogger, mockProvider } from '../../__tests__/subgraph.test'
9+
import { specification as spec } from '../../index'
10+
import {
11+
connectGraphHorizon,
12+
connectSubgraphService,
13+
} from '@graphprotocol/toolshed/deployments'
14+
import { Provider } from 'ethers'
15+
16+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
17+
declare const __LOG_LEVEL__: any
18+
19+
describe('Horizon readiness', () => {
20+
let logger: Logger
21+
let ethereum: Provider
22+
let graphNode: GraphNode
23+
let networkSubgraph: SubgraphClient
24+
let epochSubgraph: SubgraphClient
25+
let networkMonitor: NetworkMonitor
26+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
27+
let mockHorizonStaking: any
28+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
29+
let contracts: any
30+
31+
const setupMonitor = async () => {
32+
logger = createLogger({
33+
name: 'Horizon readiness tests',
34+
async: false,
35+
level: __LOG_LEVEL__ ?? 'error',
36+
})
37+
ethereum = getTestProvider('sepolia')
38+
39+
mockHorizonStaking = {
40+
getMaxThawingPeriod: jest.fn(),
41+
connect: jest.fn(),
42+
waitForDeployment: jest.fn(),
43+
interface: {},
44+
queryFilter: jest.fn(),
45+
}
46+
47+
const horizonContracts = {
48+
...connectGraphHorizon(5, ethereum),
49+
HorizonStaking: mockHorizonStaking,
50+
}
51+
contracts = {
52+
...horizonContracts,
53+
...connectSubgraphService(5, ethereum),
54+
}
55+
56+
const subgraphFreshnessChecker = new SubgraphFreshnessChecker(
57+
'Test Subgraph',
58+
mockProvider,
59+
10,
60+
10,
61+
mockLogger,
62+
1,
63+
)
64+
65+
networkSubgraph = await SubgraphClient.create({
66+
name: 'NetworkSubgraph',
67+
logger,
68+
endpoint: 'http://test-endpoint.xyz',
69+
deployment: undefined,
70+
subgraphFreshnessChecker,
71+
})
72+
73+
epochSubgraph = await SubgraphClient.create({
74+
name: 'EpochSubgraph',
75+
logger,
76+
endpoint: 'http://test-endpoint.xyz',
77+
subgraphFreshnessChecker,
78+
})
79+
80+
graphNode = new GraphNode(
81+
logger,
82+
'http://test-admin-endpoint.xyz',
83+
'https://test-query-endpoint.xyz',
84+
'http://test-status-endpoint.xyz',
85+
'https://test-ipfs-endpoint.xyz',
86+
)
87+
88+
const indexerOptions = spec.IndexerOptions.parse({
89+
address: '0xc61127cdfb5380df4214b0200b9a07c7c49d34f9',
90+
mnemonic:
91+
'word ivory whale diesel slab pelican voyage oxygen chat find tobacco sport',
92+
url: 'http://test-url.xyz',
93+
})
94+
95+
networkMonitor = new NetworkMonitor(
96+
resolveChainId('sepolia'),
97+
contracts,
98+
indexerOptions,
99+
logger,
100+
graphNode,
101+
networkSubgraph,
102+
ethereum,
103+
epochSubgraph,
104+
)
105+
}
106+
107+
beforeEach(setupMonitor)
108+
109+
describe('monitorIsHorizon', () => {
110+
beforeEach(() => {
111+
mockHorizonStaking.getMaxThawingPeriod.mockResolvedValue(0)
112+
})
113+
114+
test('should return false when getMaxThawingPeriod returns 0', async () => {
115+
const isHorizon = await networkMonitor.monitorIsHorizon(logger, {
116+
...contracts,
117+
HorizonStaking: mockHorizonStaking,
118+
})
119+
const value = await isHorizon.value()
120+
expect(value).toBe(false)
121+
})
122+
123+
test('should return true when getMaxThawingPeriod returns > 0', async () => {
124+
mockHorizonStaking.getMaxThawingPeriod.mockResolvedValue(1000)
125+
126+
const isHorizon = await networkMonitor.monitorIsHorizon(logger, {
127+
...contracts,
128+
HorizonStaking: mockHorizonStaking,
129+
})
130+
131+
const value = await isHorizon.value()
132+
console.log('Final value:', value)
133+
expect(value).toBe(true)
134+
})
135+
136+
test('should handle errors and maintain previous state', async () => {
137+
mockHorizonStaking.getMaxThawingPeriod.mockRejectedValue(
138+
new Error('Contract error'),
139+
)
140+
141+
const isHorizon = await networkMonitor.monitorIsHorizon(logger, {
142+
...contracts,
143+
HorizonStaking: mockHorizonStaking,
144+
})
145+
146+
const value = await isHorizon.value()
147+
expect(value).toBe(false)
148+
})
149+
})
150+
})

packages/indexer-common/src/indexer-management/monitor.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,43 @@ Please submit an issue at https://github.com/graphprotocol/block-oracle/issues/n
10861086
})
10871087
}
10881088

1089+
async monitorIsHorizon(
1090+
logger: Logger,
1091+
contracts: GraphHorizonContracts & SubgraphServiceContracts,
1092+
interval: number = 300_000,
1093+
): Promise<Eventual<boolean>> {
1094+
// Get initial value
1095+
1096+
const initialValue = await contracts.HorizonStaking.getMaxThawingPeriod()
1097+
.then((maxThawingPeriod) => maxThawingPeriod > 0)
1098+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
1099+
.catch((_) => false)
1100+
1101+
return sequentialTimerReduce(
1102+
{
1103+
logger,
1104+
milliseconds: interval,
1105+
},
1106+
async (isHorizon) => {
1107+
try {
1108+
logger.debug('Check if network is Horizon ready')
1109+
const maxThawingPeriod = await contracts.HorizonStaking.getMaxThawingPeriod()
1110+
return maxThawingPeriod > 0
1111+
} catch (err) {
1112+
logger.warn(
1113+
`Failed to check if network is Horizon ready, assuming it has not changed`,
1114+
{ err: indexerError(IndexerErrorCode.IE008, err), isHorizon },
1115+
)
1116+
return isHorizon
1117+
}
1118+
},
1119+
initialValue,
1120+
).map((isHorizon) => {
1121+
logger.info(isHorizon ? `Network is Horizon ready` : `Network is not Horizon ready`)
1122+
return isHorizon
1123+
})
1124+
}
1125+
10891126
async claimableAllocations(disputableEpoch: number): Promise<Allocation[]> {
10901127
try {
10911128
this.logger.debug('Fetch claimable allocations', {

packages/indexer-common/src/network.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export class Network {
5454
specification: spec.NetworkSpecification
5555
paused: Eventual<boolean>
5656
isOperator: Eventual<boolean>
57+
isHorizon: Eventual<boolean>
5758

5859
private constructor(
5960
logger: Logger,
@@ -68,6 +69,7 @@ export class Network {
6869
specification: spec.NetworkSpecification,
6970
paused: Eventual<boolean>,
7071
isOperator: Eventual<boolean>,
72+
isHorizon: Eventual<boolean>,
7173
) {
7274
this.logger = logger
7375
this.contracts = contracts
@@ -81,6 +83,7 @@ export class Network {
8183
this.specification = specification
8284
this.paused = paused
8385
this.isOperator = isOperator
86+
this.isHorizon = isHorizon
8487
}
8588

8689
static async create(
@@ -253,6 +256,8 @@ export class Network {
253256
wallet,
254257
)
255258

259+
const isHorizon = await networkMonitor.monitorIsHorizon(logger, contracts)
260+
256261
const transactionManager = new TransactionManager(
257262
networkProvider,
258263
wallet,
@@ -347,6 +352,7 @@ export class Network {
347352
specification,
348353
paused,
349354
isOperator,
355+
isHorizon,
350356
)
351357
}
352358

scripts/run-tests.sh

100644100755
File mode changed.

0 commit comments

Comments
 (0)