Skip to content

Commit c615f84

Browse files
authored
Merge pull request #422 from kleros/feat/save-snapshot
Feat/added save snapshot in validator-cli
2 parents 787ff42 + 5da2d6f commit c615f84

File tree

11 files changed

+477
-36
lines changed

11 files changed

+477
-36
lines changed

validator-cli/src/ArbToEth/transactionHandler.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { MockEmitter, defaultEmitter } from "../utils/emitter";
1010
import { BotEvents } from "../utils/botEvents";
1111
import { ClaimNotSetError } from "../utils/errors";
1212
import { getBridgeConfig, Network } from "../consts/bridgeRoutes";
13+
import { saveSnapshot } from "src/utils/snapshot";
1314

1415
describe("ArbToEthTransactionHandler", () => {
1516
const chainId = 11155111;
@@ -39,6 +40,7 @@ describe("ArbToEthTransactionHandler", () => {
3940
};
4041
veaInbox = {
4142
sendSnapshot: jest.fn(),
43+
saveSnapshot: jest.fn(),
4244
};
4345
claim = {
4446
stateRoot: "0x1234",
@@ -145,6 +147,35 @@ describe("ArbToEthTransactionHandler", () => {
145147
});
146148
});
147149

150+
describe("saveSnapshot", () => {
151+
let transactionHandler: ArbToEthTransactionHandler;
152+
beforeEach(() => {
153+
transactionHandler = new ArbToEthTransactionHandler(transactionHandlerParams);
154+
});
155+
156+
it("should save snapshot and set pending saveSnapshotTxn", async () => {
157+
jest.spyOn(transactionHandler, "checkTransactionStatus").mockResolvedValue(0);
158+
veaInbox.saveSnapshot.mockResolvedValue({ hash: "0x1234" });
159+
await transactionHandler.saveSnapshot();
160+
expect(veaInbox.saveSnapshot).toHaveBeenCalled();
161+
expect(transactionHandler.transactions.saveSnapshotTxn).toEqual({
162+
hash: "0x1234",
163+
broadcastedTimestamp: expect.any(Number),
164+
});
165+
});
166+
167+
it("should not save snapshot if a saveSnapshot transaction is pending", async () => {
168+
jest.spyOn(transactionHandler, "checkTransactionStatus").mockResolvedValue(1);
169+
transactionHandler.transactions.saveSnapshotTxn = { hash: "0x1234", broadcastedTimestamp: 1000 };
170+
await transactionHandler.saveSnapshot();
171+
expect(veaInbox.saveSnapshot).not.toHaveBeenCalled();
172+
expect(transactionHandler.transactions.saveSnapshotTxn).toEqual({
173+
hash: "0x1234",
174+
broadcastedTimestamp: 1000,
175+
});
176+
});
177+
});
178+
148179
// Happy path (claimer)
149180
describe("makeClaim", () => {
150181
let transactionHandler: ArbToEthTransactionHandler;

validator-cli/src/ArbToEth/transactionHandler.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export type Transactions = {
4343
verifySnapshotTxn: Transaction | null;
4444
challengeTxn: Transaction | null;
4545
withdrawChallengeDepositTxn: Transaction | null;
46+
saveSnapshotTxn: Transaction | null;
4647
sendSnapshotTxn: Transaction | null;
4748
executeSnapshotTxn: Transaction | null;
4849
devnetAdvanceStateTxn?: Transaction | null;
@@ -82,6 +83,7 @@ export class ArbToEthTransactionHandler {
8283
verifySnapshotTxn: null,
8384
challengeTxn: null,
8485
withdrawChallengeDepositTxn: null,
86+
saveSnapshotTxn: null,
8587
sendSnapshotTxn: null,
8688
executeSnapshotTxn: null,
8789
};
@@ -257,7 +259,7 @@ export class ArbToEthTransactionHandler {
257259
}
258260

259261
/**
260-
* Withdraw the claim deposit.
262+
* Withdraw the claim deposit from VeaOutbox(ETH).
261263
*
262264
*/
263265
public async withdrawClaimDeposit() {
@@ -335,7 +337,7 @@ export class ArbToEthTransactionHandler {
335337
}
336338

337339
/**
338-
* Withdraw the challenge deposit.
340+
* Withdraw the challenge deposit from VeaOutbox(ETH).
339341
*
340342
*/
341343
public async withdrawChallengeDeposit() {
@@ -360,6 +362,28 @@ export class ArbToEthTransactionHandler {
360362
};
361363
}
362364

365+
/**
366+
* Save a snapshot on VeaInbox(Arb).
367+
*/
368+
public async saveSnapshot() {
369+
this.emitter.emit(BotEvents.SAVING_SNAPSHOT, this.epoch);
370+
const currentTime = Date.now();
371+
const transactionStatus = await this.checkTransactionStatus(
372+
this.transactions.saveSnapshotTxn,
373+
ContractType.INBOX,
374+
currentTime
375+
);
376+
if (transactionStatus != TransactionStatus.NOT_MADE && transactionStatus != TransactionStatus.EXPIRED) {
377+
return;
378+
}
379+
const saveSnapshotTxn = await this.veaInbox.saveSnapshot();
380+
this.emitter.emit(BotEvents.TXN_MADE, saveSnapshotTxn.hash, this.epoch, "Save Snapshot");
381+
this.transactions.saveSnapshotTxn = {
382+
hash: saveSnapshotTxn.hash,
383+
broadcastedTimestamp: currentTime,
384+
};
385+
}
386+
363387
/**
364388
* Send a snapshot from the VeaInbox(ARB) to the VeaOutox(ETH).
365389
*/

validator-cli/src/ArbToEth/transactionHandlerDevnet.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export class ArbToEthDevnetTransactionHandler extends ArbToEthTransactionHandler
2525
verifySnapshotTxn: null,
2626
challengeTxn: null,
2727
withdrawChallengeDepositTxn: null,
28+
saveSnapshotTxn: null,
2829
sendSnapshotTxn: null,
2930
executeSnapshotTxn: null,
3031
devnetAdvanceStateTxn: null,

validator-cli/src/utils/botConfig.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ interface BotPathParams {
1818
* @param defaultPath - default path to use if not specified in the command line arguments
1919
* @returns BotPaths - the bot path (BotPaths)
2020
*/
21-
export function getBotPath({ cliCommand, defaultPath = BotPaths.BOTH }: BotPathParams): number {
21+
export function getBotPath({ cliCommand, defaultPath = BotPaths.BOTH }: BotPathParams): {
22+
path: number;
23+
toSaveSnapshot: boolean;
24+
} {
2225
const args = cliCommand.slice(2);
2326
const pathFlag = args.find((arg) => arg.startsWith("--path="));
2427

@@ -33,8 +36,9 @@ export function getBotPath({ cliCommand, defaultPath = BotPaths.BOTH }: BotPathP
3336
if (path && !(path in pathMapping)) {
3437
throw new InvalidBotPathError();
3538
}
36-
37-
return path ? pathMapping[path] : defaultPath;
39+
const saveSnapshotFlag = args.find((a) => a.startsWith("--saveSnapshot"));
40+
const toSaveSnapshot = saveSnapshotFlag ? true : false;
41+
return path ? { path: pathMapping[path], toSaveSnapshot } : { path: defaultPath, toSaveSnapshot };
3842
}
3943

4044
export interface NetworkConfig {

validator-cli/src/utils/botEvents.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ export enum BotEvents {
1313
NO_SNAPSHOT = "no_snapshot",
1414
CLAIM_EPOCH_PASSED = "claim_epoch_passed",
1515

16+
// Snapshot state
17+
SAVING_SNAPSHOT = "saving_snapshot",
18+
SNAPSHOT_WAITING = "snapshot_saving",
19+
1620
// Claim state
1721
CLAIMING = "claiming",
1822
STARTING_VERIFICATION = "starting_verification",

validator-cli/src/utils/epochHandler.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,26 +49,23 @@ const setEpochRange = ({
4949
return veaEpochOutboxCheckClaimsRangeArray;
5050
};
5151

52+
const getLatestChallengeableEpoch = (epochPeriod: number, now: number = Date.now()): number => {
53+
return Math.floor(now / 1000 / epochPeriod) - 2;
54+
};
55+
5256
/**
53-
* Checks if a new epoch has started.
57+
* Get the block number corresponding to a given epoch.
5458
*
55-
* @param currentVerifiableEpoch - The current verifiable epoch number
59+
* @param epoch - The epoch number
5660
* @param epochPeriod - The epoch period in seconds
57-
* @param now - The current time in milliseconds (optional, defaults to Date.now())
61+
* @param provider - The JSON-RPC provider
5862
*
59-
* @returns The updated epoch number
60-
*
61-
* @example
62-
* currentEpoch = checkForNewEpoch(currentEpoch, 7200);
63+
* @returns The block number corresponding to the given epoch
6364
*/
64-
const getLatestChallengeableEpoch = (epochPeriod: number, now: number = Date.now()): number => {
65-
return Math.floor(now / 1000 / epochPeriod) - 2;
66-
};
67-
6865
const getBlockFromEpoch = async (epoch: number, epochPeriod: number, provider: JsonRpcProvider): Promise<number> => {
6966
const epochTimestamp = epoch * epochPeriod;
7067
const latestBlock = await provider.getBlock("latest");
71-
const baseBlock = await provider.getBlock(latestBlock.number - 1000);
68+
const baseBlock = await provider.getBlock(latestBlock.number - 500);
7269
const secPerBlock = (latestBlock.timestamp - baseBlock.timestamp) / (latestBlock.number - baseBlock.number);
7370
const blockFallBack = Math.floor((latestBlock.timestamp - epochTimestamp) / secPerBlock);
7471
return latestBlock.number - blockFallBack;

validator-cli/src/utils/graphQueries.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ type VerificationData = {
7878
startTxHash: string | null;
7979
};
8080

81+
/**
82+
* Fetches the verification data for a given claim (used for claimer - happy path)
83+
* @param claimId
84+
* @returns VerificationData
85+
*/
8186
const getVerificationForClaim = async (claimId: string): Promise<VerificationData | undefined> => {
8287
try {
8388
const subgraph = process.env.VEAOUTBOX_SUBGRAPH;
@@ -97,6 +102,11 @@ const getVerificationForClaim = async (claimId: string): Promise<VerificationDat
97102
}
98103
};
99104

105+
/**
106+
* Fetches the challenger data for a given claim (used for validator - unhappy path)
107+
* @param claimId
108+
* @returns challenger address
109+
* */
100110
const getChallengerForClaim = async (claimId: string): Promise<{ challenger: string } | undefined> => {
101111
try {
102112
const subgraph = process.env.VEAOUTBOX_SUBGRAPH;
@@ -121,6 +131,11 @@ type SenSnapshotResponse = {
121131
}[];
122132
};
123133

134+
/**
135+
* Fetches the snapshot data for a given epoch (used for validator - happy path)
136+
* @param epoch
137+
* @returns snapshot data
138+
*/
124139
const getSnapshotSentForEpoch = async (epoch: number, veaInbox: any): Promise<{ txHash: string }> => {
125140
try {
126141
const subgraph = process.env.VEAINBOX_SUBGRAPH;
@@ -143,11 +158,40 @@ const getSnapshotSentForEpoch = async (epoch: number, veaInbox: any): Promise<{
143158
}
144159
};
145160

161+
type SnapshotSavedResponse = {
162+
snapshots: {
163+
messages: {
164+
id: string;
165+
}[];
166+
}[];
167+
};
168+
169+
/**
170+
* Fetches the last message saved for a given inbox (used for validator - happy path)
171+
* @param veaInbox
172+
* @returns message id
173+
*/
174+
const getLastMessageSaved = async (veaInbox: string): Promise<string> => {
175+
const subgraph = process.env.VEAINBOX_SUBGRAPH;
176+
const result: SnapshotSavedResponse = await request(
177+
`${subgraph}`,
178+
`{
179+
snapshots(first:2, orderBy:timestamp,orderDirection:desc, where:{inbox:"${veaInbox}"}) {
180+
messages(first: 1,orderBy:timestamp,orderDirection:desc){
181+
id
182+
}
183+
}
184+
}`
185+
);
186+
return result.snapshots[1].messages[0].id;
187+
};
188+
146189
export {
147190
getClaimForEpoch,
148191
getLastClaimedEpoch,
149192
getVerificationForClaim,
150193
getChallengerForClaim,
151194
getSnapshotSentForEpoch,
195+
getLastMessageSaved,
152196
ClaimData,
153197
};

validator-cli/src/utils/logger.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,14 @@ export const configurableInitialize = (emitter: EventEmitter) => {
7777
console.log(`Transaction(${transaction}) is expired`);
7878
});
7979

80+
// Snapshot state logs
81+
emitter.on(BotEvents.SAVING_SNAPSHOT, (epoch: number) => {
82+
console.log(`Saving snapshot for epoch ${epoch}`);
83+
});
84+
emitter.on(BotEvents.SNAPSHOT_WAITING, (time: number) => {
85+
console.log(`Waiting for saving snapshot, time left: ${time}`);
86+
});
87+
8088
// Claim state logs
8189
// claim()
8290
emitter.on(BotEvents.CLAIMING, (epoch: number) => {

0 commit comments

Comments
 (0)