Skip to content

Commit 787ff42

Browse files
authored
Merge pull request #417 from kleros/feat(validator)/claimer
Feat(validator)/claimer
2 parents 3b49b3b + e64c8a8 commit 787ff42

29 files changed

+1913
-1114
lines changed

validator-cli/.env.dist

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,20 @@
11
PRIVATE_KEY=
22

3-
# Devnet RPCs
4-
RPC_CHIADO=https://rpc.chiadochain.net
5-
RPC_ARB_SEPOLIA=https://sepolia-rollup.arbitrum.io/rpc
6-
RPC_SEPOLIA=
3+
# Networks: devnet, testnet, mainnet
4+
NETWORKS=devnet,testnet
75

8-
# Testnet or Mainnet RPCs
6+
7+
# RPCs
98
RPC_ARB=https://sepolia-rollup.arbitrum.io/rpc
109
RPC_ETH=
1110
RPC_GNOSIS=https://rpc.chiadochain.net
1211

13-
# Testnet or Mainnet Addresses
14-
# VEA Arbitrum to Ethereum
15-
VEAINBOX_ARB_TO_ETH_ADDRESS=0xE12daFE59Bc3A996362d54b37DFd2BA9279cAd06
16-
VEAOUTBOX_ARB_TO_ETH_ADDRESS=0x209BFdC6B7c66b63A8382196Ba3d06619d0F12c9
17-
# VEA Arbitrum to GNOSIS
18-
VEAINBOX_ARB_TO_GNOSIS_ADDRESS=0x854374483572FFcD4d0225290346279d0718240b
19-
VEAOUTBOX_ARB_TO_GNOSIS_ADDRESS=0x2f1788F7B74e01c4C85578748290467A5f063B0b
20-
VEAROUTER_ARB_TO_GNOSIS_ADDRESS=0x5BE03fDE7794Bc188416ba16932510Ed1277b193
21-
GNOSIS_AMB_ADDRESS=0x8448E15d0e706C0298dECA99F0b4744030e59d7d
2212

23-
VEAOUTBOX_CHAIN_ID=421611
13+
GNOSIS_AMB_ADDRESS=0x8448E15d0e706C0298dECA99F0b4744030e59d7d
2414

25-
# Devnet Addresses
26-
VEAINBOX_ARBSEPOLIA_TO_SEPOLIA_ADDRESS=0x906dE43dBef27639b1688Ac46532a16dc07Ce410
27-
VEAOUTBOX_ARBSEPOLIA_TO_SEPOLIA_ADDRESS=0x906dE43dBef27639b1688Ac46532a16dc07Ce410
15+
VEAOUTBOX_CHAINS=11155111,421611
2816

17+
# vescan subgraph endpoints
18+
VEAINBOX_SUBGRAPH=https://api.studio.thegraph.com/query/user/inbox-arb-sep/version/latest
19+
VEAOUTBOX_SUBGRAPH=https://api.studio.thegraph.com/query/user/outbox-arb-sep/version/latest
2920

30-
TRANSACTION_BATCHER_CONTRACT_ADDRESS_SEPOLIA=0xe7953da7751063d0a41ba727c32c762d3523ade8
31-
TRANSACTION_BATCHER_CONTRACT_ADDRESS_CHIADO=0xcC0a08D4BCC5f91ee9a1587608f7a2975EA75d73

validator-cli/ecosystem.config.js

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,16 @@
11
module.exports = {
22
apps: [
33
{
4-
name: "chiado-devnet",
5-
script: "yarn",
6-
args: "start-chiado-devnet",
7-
interpreter: "/bin/bash",
4+
name: "validator-cli",
5+
script: "./src/watcher.ts",
6+
interpreter: "../node_modules/.bin/ts-node",
7+
interpreter_args: "--project tsconfig.json -r tsconfig-paths/register",
88
log_date_format: "YYYY-MM-DD HH:mm Z",
9-
watch: false,
10-
autorestart: false,
11-
env: {
12-
NODE_ENV: "development",
13-
},
14-
},
15-
{
16-
name: "start-sepolia-devnet",
17-
script: "yarn",
18-
args: "start-sepolia-devnet",
19-
interpreter: "/bin/bash",
20-
log_date_format: "YYYY-MM-DD HH:mm Z",
21-
watch: false,
22-
autorestart: false,
9+
watch: true,
10+
autorestart: true,
2311
env: {
2412
NODE_ENV: "development",
13+
TS_NODE_PROJECT: "./tsconfig.json",
2514
},
2615
},
2716
],

validator-cli/package.json

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@
1010
},
1111
"scripts": {
1212
"start": "npx ts-node ./src/watcher.ts",
13-
"start-chiado-devnet": "npx ts-node ./src/devnet/arbToChiado/happyPath.ts",
14-
"start-sepolia-devnet": "npx ts-node ./src/devnet/arbToSepolia/happyPath.ts",
15-
"start-sepolia-testnet": "npx ts-node ./src/ArbToEth/watcherArbToEth.ts",
16-
"start-arbitrum-to-gnosis": "npx ts-node ./src/ArbToEth/watcherArbToGnosis.ts",
1713
"test": "jest --coverage"
1814
},
1915
"dependencies": {
@@ -22,10 +18,8 @@
2218
"@kleros/vea-contracts": "workspace:^",
2319
"@typechain/ethers-v6": "^0.5.1",
2420
"dotenv": "^16.4.5",
25-
"pm2": "^5.2.2",
26-
"typescript": "^4.9.5",
27-
"web3": "^4.16.0",
28-
"web3-batched-send": "^1.0.3"
21+
"pm2": "^6.0.5",
22+
"typescript": "^4.9.5"
2923
},
3024
"devDependencies": {
3125
"@types/jest": "^29.5.14",
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
import { ethers } from "ethers";
2+
import { checkAndClaim, CheckAndClaimParams } from "./claimer";
3+
import { ClaimHonestState } from "../utils/claim";
4+
import { Network } from "../consts/bridgeRoutes";
5+
6+
describe("claimer", () => {
7+
const NETWORK = Network.DEVNET;
8+
let veaOutbox: any;
9+
let veaInbox: any;
10+
let veaInboxProvider: any;
11+
let veaOutboxProvider: any;
12+
let emitter: any;
13+
let mockClaim: any;
14+
let mockGetLatestClaimedEpoch: any;
15+
let mockGetTransactionHandler: any;
16+
let mockDeps: CheckAndClaimParams;
17+
18+
let mockTransactionHandler: any;
19+
const mockTransactions = {
20+
claimTxn: "0x111",
21+
withdrawClaimDepositTxn: "0x222",
22+
startVerificationTxn: "0x333",
23+
verifySnapshotTxn: "0x444",
24+
devnetAdvanceStateTxn: "0x555",
25+
};
26+
beforeEach(() => {
27+
mockClaim = {
28+
stateRoot: "0x1234",
29+
claimer: "0xFa00D29d378EDC57AA1006946F0fc6230a5E3288",
30+
timestampClaimed: 1234,
31+
timestampVerification: 0,
32+
blocknumberVerification: 0,
33+
honest: 0,
34+
challenger: ethers.ZeroAddress,
35+
};
36+
veaInbox = {
37+
snapshots: jest.fn().mockResolvedValue(mockClaim.stateRoot),
38+
};
39+
40+
veaOutbox = {
41+
stateRoot: jest.fn().mockResolvedValue(mockClaim.stateRoot),
42+
};
43+
veaOutboxProvider = {
44+
getBlock: jest.fn().mockResolvedValue({ number: 0, timestamp: 110 }),
45+
};
46+
emitter = {
47+
emit: jest.fn(),
48+
};
49+
50+
mockGetLatestClaimedEpoch = jest.fn();
51+
mockGetTransactionHandler = jest.fn().mockReturnValue(function DummyTransactionHandler(params: any) {
52+
// Return an object that matches our expected transaction handler.
53+
return mockTransactionHandler;
54+
});
55+
mockDeps = {
56+
chainId: 0,
57+
claim: mockClaim,
58+
network: NETWORK,
59+
epoch: 10,
60+
epochPeriod: 10,
61+
veaInbox,
62+
veaInboxProvider,
63+
veaOutboxProvider,
64+
veaOutbox,
65+
transactionHandler: null,
66+
emitter,
67+
fetchLatestClaimedEpoch: mockGetLatestClaimedEpoch,
68+
now: 110000, // (epoch+ 1) * epochPeriod * 1000 for claimable epoch
69+
};
70+
71+
mockTransactionHandler = {
72+
withdrawClaimDeposit: jest.fn().mockImplementation(() => {
73+
mockTransactionHandler.transactions.withdrawClaimDepositTxn = mockTransactions.withdrawClaimDepositTxn;
74+
return Promise.resolve();
75+
}),
76+
makeClaim: jest.fn().mockImplementation(() => {
77+
mockTransactionHandler.transactions.claimTxn = mockTransactions.claimTxn;
78+
return Promise.resolve();
79+
}),
80+
startVerification: jest.fn().mockImplementation(() => {
81+
mockTransactionHandler.transactions.startVerificationTxn = mockTransactions.startVerificationTxn;
82+
return Promise.resolve();
83+
}),
84+
verifySnapshot: jest.fn().mockImplementation(() => {
85+
mockTransactionHandler.transactions.verifySnapshotTxn = mockTransactions.verifySnapshotTxn;
86+
return Promise.resolve();
87+
}),
88+
transactions: {
89+
claimTxn: "0x0",
90+
withdrawClaimDepositTxn: "0x0",
91+
startVerificationTxn: "0x0",
92+
verifySnapshotTxn: "0x0",
93+
},
94+
};
95+
});
96+
afterEach(() => {
97+
jest.clearAllMocks();
98+
});
99+
describe("checkAndClaim", () => {
100+
beforeEach(() => {
101+
mockTransactionHandler = {
102+
withdrawClaimDeposit: jest.fn().mockImplementation(() => {
103+
mockTransactionHandler.transactions.withdrawClaimDepositTxn = mockTransactions.withdrawClaimDepositTxn;
104+
return Promise.resolve();
105+
}),
106+
makeClaim: jest.fn().mockImplementation(() => {
107+
mockTransactionHandler.transactions.claimTxn = mockTransactions.claimTxn;
108+
return Promise.resolve();
109+
}),
110+
startVerification: jest.fn().mockImplementation(() => {
111+
mockTransactionHandler.transactions.startVerificationTxn = mockTransactions.startVerificationTxn;
112+
return Promise.resolve();
113+
}),
114+
verifySnapshot: jest.fn().mockImplementation(() => {
115+
mockTransactionHandler.transactions.verifySnapshotTxn = mockTransactions.verifySnapshotTxn;
116+
return Promise.resolve();
117+
}),
118+
devnetAdvanceState: jest.fn().mockImplementation(() => {
119+
mockTransactionHandler.transactions.devnetAdvanceStateTxn = mockTransactions.devnetAdvanceStateTxn;
120+
return Promise.resolve();
121+
}),
122+
transactions: {
123+
claimTxn: "0x0",
124+
withdrawClaimDepositTxn: "0x0",
125+
startVerificationTxn: "0x0",
126+
verifySnapshotTxn: "0x0",
127+
},
128+
};
129+
mockGetTransactionHandler = jest.fn().mockReturnValue(function DummyTransactionHandler(param: any) {
130+
return mockTransactionHandler;
131+
});
132+
mockDeps.fetchTransactionHandler = mockGetTransactionHandler;
133+
});
134+
it("should return null if no claim is made for a passed epoch", async () => {
135+
mockDeps.epoch = 7; // claimable epoch - 3
136+
mockDeps.claim = null;
137+
138+
mockDeps.fetchTransactionHandler = mockGetTransactionHandler;
139+
const result = await checkAndClaim(mockDeps);
140+
expect(result).toBeNull();
141+
});
142+
it("should return null if no snapshot is saved on the inbox for a claimable epoch", async () => {
143+
veaInbox.snapshots = jest.fn().mockResolvedValue(ethers.ZeroHash);
144+
mockGetLatestClaimedEpoch = jest.fn().mockResolvedValue({
145+
challenged: false,
146+
stateroot: "0x1111",
147+
});
148+
mockDeps.claim = null;
149+
mockDeps.fetchLatestClaimedEpoch = mockGetLatestClaimedEpoch;
150+
const result = await checkAndClaim(mockDeps);
151+
expect(result).toBeNull();
152+
});
153+
it("should return null if there are no new messages in the inbox", async () => {
154+
veaInbox.snapshots = jest.fn().mockResolvedValue(mockClaim.stateRoot);
155+
mockGetLatestClaimedEpoch = jest.fn().mockResolvedValue({
156+
challenged: false,
157+
stateroot: "0x1111",
158+
});
159+
mockDeps.claim = null;
160+
mockDeps.fetchLatestClaimedEpoch = mockGetLatestClaimedEpoch;
161+
const result = await checkAndClaim(mockDeps);
162+
expect(result).toBeNull();
163+
});
164+
describe("devnet", () => {
165+
beforeEach(() => {
166+
mockDeps.network = Network.DEVNET;
167+
});
168+
it("should make a valid claim and advance state", async () => {
169+
veaInbox.snapshots = jest.fn().mockResolvedValue("0x7890");
170+
mockGetLatestClaimedEpoch = jest.fn().mockResolvedValue({
171+
challenged: false,
172+
stateroot: mockClaim.stateRoot,
173+
});
174+
mockDeps.transactionHandler = mockTransactionHandler;
175+
mockDeps.fetchLatestClaimedEpoch = mockGetLatestClaimedEpoch;
176+
mockDeps.claim = null;
177+
mockDeps.veaInbox = veaInbox;
178+
const result = await checkAndClaim(mockDeps);
179+
expect(result.transactions.devnetAdvanceStateTxn).toBe(mockTransactions.devnetAdvanceStateTxn);
180+
});
181+
});
182+
describe("testnet", () => {
183+
beforeEach(() => {
184+
mockDeps.network = Network.TESTNET;
185+
});
186+
it("should make a valid claim if no claim is made", async () => {
187+
veaInbox.snapshots = jest.fn().mockResolvedValue("0x7890");
188+
mockGetLatestClaimedEpoch = jest.fn().mockResolvedValue({
189+
challenged: false,
190+
stateroot: mockClaim.stateRoot,
191+
});
192+
mockDeps.transactionHandler = mockTransactionHandler;
193+
mockDeps.fetchLatestClaimedEpoch = mockGetLatestClaimedEpoch;
194+
mockDeps.claim = null;
195+
mockDeps.veaInbox = veaInbox;
196+
const result = await checkAndClaim(mockDeps);
197+
expect(result.transactions.claimTxn).toBe(mockTransactions.claimTxn);
198+
});
199+
it("should make a valid claim if last claim was challenged", async () => {
200+
veaInbox.snapshots = jest.fn().mockResolvedValue(mockClaim.stateRoot);
201+
mockGetLatestClaimedEpoch = jest.fn().mockResolvedValue({
202+
challenged: true,
203+
stateroot: mockClaim.stateRoot,
204+
});
205+
mockDeps.transactionHandler = mockTransactionHandler;
206+
mockDeps.fetchLatestClaimedEpoch = mockGetLatestClaimedEpoch;
207+
mockDeps.claim = null;
208+
mockDeps.veaInbox = veaInbox;
209+
const result = await checkAndClaim(mockDeps);
210+
expect(result.transactions.claimTxn).toEqual(mockTransactions.claimTxn);
211+
});
212+
it("should withdraw claim deposit if claimer is honest", async () => {
213+
mockDeps.transactionHandler = mockTransactionHandler;
214+
mockClaim.honest = ClaimHonestState.CLAIMER;
215+
const result = await checkAndClaim(mockDeps);
216+
expect(result.transactions.withdrawClaimDepositTxn).toEqual(mockTransactions.withdrawClaimDepositTxn);
217+
});
218+
it("should start verification if verification is not started", async () => {
219+
mockDeps.transactionHandler = mockTransactionHandler;
220+
mockClaim.honest = ClaimHonestState.NONE;
221+
const result = await checkAndClaim(mockDeps);
222+
expect(result.transactions.startVerificationTxn).toEqual(mockTransactions.startVerificationTxn);
223+
});
224+
it("should verify snapshot if verification is started", async () => {
225+
mockDeps.transactionHandler = mockTransactionHandler;
226+
mockClaim.honest = ClaimHonestState.NONE;
227+
mockClaim.timestampVerification = 1234;
228+
mockDeps.claim = mockClaim;
229+
const result = await checkAndClaim(mockDeps);
230+
expect(result.transactions.verifySnapshotTxn).toEqual(mockTransactions.verifySnapshotTxn);
231+
});
232+
});
233+
});
234+
});

0 commit comments

Comments
 (0)