11import { TokenClient } from "../clients" ;
22import {
3+ blockExplorerLink ,
4+ CHAIN_IDs ,
35 EvmAddress ,
46 SvmAddress ,
57 winston ,
@@ -13,19 +15,23 @@ import {
1315 getSvmSignerFromEvmSigner ,
1416 waitForPubSub ,
1517 averageBlockTime ,
18+ getRedisCache ,
1619 getRedisPubSub ,
20+ Provider ,
1721} from "../utils" ;
1822import { spokePoolClientsToProviders } from "../common" ;
1923import { Dataworker } from "./Dataworker" ;
2024import { DataworkerConfig } from "./DataworkerConfig" ;
25+ import { generateValidationKey } from "./DataworkerUtils" ;
2126import {
2227 constructDataworkerClients ,
2328 constructSpokePoolClientsForFastDataworker ,
2429 getSpokePoolClientEventSearchConfigsForFastDataworker ,
2530 DataworkerClients ,
2631} from "./DataworkerClientHelper" ;
2732import { BalanceAllocator } from "../clients/BalanceAllocator" ;
28- import { PendingRootBundle , BundleData } from "../interfaces" ;
33+ import { PendingRootBundle , ProposedRootBundle , BundleData } from "../interfaces" ;
34+ import { Disputer } from "./Disputer" ;
2935
3036config ( ) ;
3137let logger : winston . Logger ;
@@ -80,23 +86,47 @@ function resolvePersonality(config: DataworkerConfig): string {
8086 return "Dataworker" ; // unknown
8187}
8288
89+ /**
90+ * Query the details of a proposal at a given block/tag.
91+ * @param provider Ethers provider instance.
92+ * @param chainId HubPool chain ID.
93+ * @param blockTag Block/tag to query at.
94+ */
95+ async function getProposal (
96+ provider : Provider ,
97+ chainId = CHAIN_IDs . MAINNET ,
98+ blockTag : number | "latest" = "latest"
99+ ) : Promise <
100+ Pick <
101+ ProposedRootBundle ,
102+ "poolRebalanceRoot" | "relayerRefundRoot" | "slowRelayRoot" | "challengePeriodEndTimestamp" | "proposer"
103+ > & { currentTime : number ; currentBlock : number }
104+ > {
105+ const { number : currentBlock , timestamp : currentTime } = await provider . getBlock ( blockTag ) ;
106+ const hubPool = getDeployedContract ( "HubPool" , chainId ) . connect ( provider ) ;
107+
108+ const { poolRebalanceRoot, relayerRefundRoot, slowRelayRoot, proposer, challengePeriodEndTimestamp } =
109+ await hubPool . rootBundleProposal ( { blockTag : currentBlock } ) ;
110+
111+ return {
112+ currentTime,
113+ currentBlock,
114+ poolRebalanceRoot,
115+ relayerRefundRoot,
116+ slowRelayRoot,
117+ challengePeriodEndTimestamp,
118+ proposer : EvmAddress . from ( proposer ) ,
119+ } ;
120+ }
121+
83122async function getChallengeRemaining (
84123 chainId : number ,
85124 challengeBuffer : number ,
86125 logger : winston . Logger
87126) : Promise < number > {
88127 const provider = await getProvider ( chainId ) ;
89- const latestBlock = await provider . getBlockNumber ( ) ;
90- const hubPool = getDeployedContract ( "HubPool" , chainId ) . connect ( provider ) ;
128+ const { currentBlock, currentTime, ...proposal } = await getProposal ( provider , chainId ) ;
91129
92- const [ proposal , currentTime ] = await Promise . all ( [
93- hubPool . rootBundleProposal ( {
94- blockTag : latestBlock ,
95- } ) ,
96- hubPool . getCurrentTime ( {
97- blockTag : latestBlock ,
98- } ) ,
99- ] ) ;
100130 const { challengePeriodEndTimestamp } = proposal ;
101131 const challengeRemaining = Math . max ( challengePeriodEndTimestamp + challengeBuffer - currentTime , 0 ) ;
102132 logger . debug ( {
@@ -105,8 +135,8 @@ async function getChallengeRemaining(
105135 challengeRemaining,
106136 challengeBuffer,
107137 challengePeriodEndTimestamp,
138+ currentBlock,
108139 currentTime,
109- blockTag : latestBlock ,
110140 } ) ;
111141
112142 return challengeRemaining ;
@@ -375,3 +405,60 @@ export async function runDataworker(_logger: winston.Logger, baseSigner: Signer)
375405 await disconnectRedisClients ( logger ) ;
376406 }
377407}
408+
409+ export async function runDisputerWatchdog ( logger : winston . Logger , signer : Signer ) : Promise < void > {
410+ const personality = "Disputer Watchdog" ;
411+ const at = "runDisputerWatchDog" ;
412+ const config = new DataworkerConfig ( process . env ) ;
413+ const { hubPoolChainId : hubChainId , sendingTransactionsEnabled : enabled } = config ;
414+
415+ const { DISPUTER_WATCHDOG_MIN_ATTESTATIONS = "3" , DISPUTER_WATCHDOG_CHALLENGE_LIMIT = "600" } = process . env ; // @todo Watchdog config.
416+ const minValidations = Number ( DISPUTER_WATCHDOG_MIN_ATTESTATIONS ) ;
417+ const challengeLimit = Number ( DISPUTER_WATCHDOG_CHALLENGE_LIMIT ) ;
418+
419+ const provider = await getProvider ( hubChainId , logger ) ;
420+ const hubPool = getDeployedContract ( "HubPool" , hubChainId ) . connect ( provider ) ;
421+ const disputer = new Disputer ( hubChainId , logger , hubPool , signer , ! enabled ) ;
422+
423+ logger . debug ( { at, message : "Starting Disputer Watchdog." } ) ;
424+
425+ try {
426+ await disputer . validate ( ) ;
427+ const redis = await getRedisCache ( logger ) ;
428+
429+ const { currentTime, currentBlock, ...proposal } = await getProposal ( provider , hubChainId ) ;
430+
431+ const getValidations = async ( ) => {
432+ const key = generateValidationKey ( proposal ) ;
433+ const result = await redis . get < string > ( key ) ;
434+ return Number ( result ) || 0 ; // Revert to 0 on isNaN(result)
435+ } ;
436+
437+ // @todo Validate that currentTime is not too different from host time.
438+ const challengeRemaining = proposal . challengePeriodEndTimestamp - currentTime ;
439+ if ( challengeRemaining <= 0 ) {
440+ logger . debug ( { at, message : "Proposal challenge window has elapsed, nothing to do..." } ) ;
441+ return ;
442+ }
443+
444+ const validations = await getValidations ( ) ;
445+ if ( challengeRemaining <= challengeLimit && validations < minValidations ) {
446+ const dispute = await disputer . dispute ( ) ;
447+ const message = enabled
448+ ? "Submitted HubPool root bundle dispute."
449+ : "Suppressed HubPool root bundle dispute due to configuration." ;
450+ const txn = isDefined ( dispute ) ? blockExplorerLink ( dispute . transactionHash , hubChainId ) : undefined ;
451+ logger . error ( { at, message, proposal, txn } ) ;
452+ } else {
453+ const waiting = challengeRemaining - challengeLimit ;
454+ const message =
455+ waiting > 0
456+ ? `Must wait an additional ${ waiting } seconds before evaluating validator attestations.`
457+ : "Current proposal has sufficient validator attestations." ;
458+ logger . debug ( { at, message, challengeLimit, validations, minValidations } ) ;
459+ }
460+ } finally {
461+ await disconnectRedisClients ( logger ) ;
462+ logger . debug ( { at, message : `Completed ${ personality } run.` } ) ;
463+ }
464+ }
0 commit comments