Skip to content

Commit 0424a18

Browse files
committed
chore: add is horizon ready flag to network monitor
1 parent c49ebe8 commit 0424a18

File tree

5 files changed

+229
-0
lines changed

5 files changed

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

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,47 @@ 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(
1122+
isHorizon
1123+
? `Network is Horizon ready`
1124+
: `Network is not Horizon ready`,
1125+
)
1126+
return isHorizon
1127+
})
1128+
}
1129+
10891130
async claimableAllocations(disputableEpoch: number): Promise<Allocation[]> {
10901131
try {
10911132
this.logger.debug('Fetch claimable allocations', {

packages/indexer-common/src/network.ts

Lines changed: 10 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,11 +256,17 @@ export class Network {
253256
wallet,
254257
)
255258

259+
const isHorizon = await networkMonitor.monitorIsHorizon(
260+
logger,
261+
contracts,
262+
)
263+
256264
const transactionManager = new TransactionManager(
257265
networkProvider,
258266
wallet,
259267
paused,
260268
isOperator,
269+
isHorizon,
261270
specification.transactionMonitoring,
262271
)
263272

@@ -347,6 +356,7 @@ export class Network {
347356
specification,
348357
paused,
349358
isOperator,
359+
isHorizon,
350360
)
351361
}
352362

packages/indexer-common/src/transactions.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export class TransactionManager {
3030
wallet: HDNodeWallet
3131
paused: Eventual<boolean>
3232
isOperator: Eventual<boolean>
33+
isHorizon: Eventual<boolean>
3334
specification: TransactionMonitoring
3435
adjustedGasIncreaseFactor: bigint
3536
adjustedBaseFeePerGasMax: number
@@ -39,12 +40,14 @@ export class TransactionManager {
3940
wallet: HDNodeWallet,
4041
paused: Eventual<boolean>,
4142
isOperator: Eventual<boolean>,
43+
isHorizon: Eventual<boolean>,
4244
specification: TransactionMonitoring,
4345
) {
4446
this.ethereum = ethereum
4547
this.wallet = wallet
4648
this.paused = paused
4749
this.isOperator = isOperator
50+
this.isHorizon = isHorizon
4851
this.specification = specification
4952
this.adjustedGasIncreaseFactor = parseUnits(
5053
specification.gasIncreaseFactor.toString(),
@@ -420,6 +423,46 @@ export class TransactionManager {
420423
})
421424
}
422425

426+
async monitorIsHorizon(
427+
logger: Logger,
428+
contracts: GraphHorizonContracts & SubgraphServiceContracts,
429+
interval: number = 300_000,
430+
): Promise<Eventual<boolean>> {
431+
// Get initial value
432+
const initialValue = await contracts.HorizonStaking.getMaxThawingPeriod()
433+
.then(maxThawingPeriod => maxThawingPeriod > 0)
434+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
435+
.catch(_ => false)
436+
437+
return sequentialTimerReduce(
438+
{
439+
logger,
440+
milliseconds: interval,
441+
},
442+
async (isHorizon) => {
443+
try {
444+
logger.debug('Check if network is Horizon ready')
445+
const maxThawingPeriod = await contracts.HorizonStaking.getMaxThawingPeriod()
446+
return maxThawingPeriod > 0
447+
} catch (err) {
448+
logger.warn(
449+
`Failed to check if network is Horizon ready, assuming it has not changed`,
450+
{ err: indexerError(IndexerErrorCode.IE008, err), isHorizon },
451+
)
452+
return isHorizon
453+
}
454+
},
455+
initialValue,
456+
).map((isHorizon) => {
457+
logger.info(
458+
isHorizon
459+
? `Network is Horizon ready`
460+
: `Network is not Horizon ready`,
461+
)
462+
return isHorizon
463+
})
464+
}
465+
423466
findEvent(
424467
eventType: string,
425468
contractInterface: Interface,

scripts/run-tests.sh

100644100755
File mode changed.

0 commit comments

Comments
 (0)