Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Feat(validator)/claimer #417

Open
wants to merge 33 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4e39a03
feat: claimer
Jan 22, 2025
e6b5d7e
feat: happy path tests
Jan 22, 2025
829bd77
feat: claimer tests
Jan 22, 2025
fc5020c
feat: cli path tests
Jan 22, 2025
e367173
feat: increased test coverage
Jan 22, 2025
c488c4a
chore: outscoped claim fetch
Feb 5, 2025
a91a9c6
fix: verifySnapshot time flag
Feb 5, 2025
4ef27a7
fix: happy path logs
Feb 5, 2025
4c7d59a
chore: env.dist update
Feb 5, 2025
89b6b0b
feat: multi network support
Mar 10, 2025
cffaa00
chore: refactor for networks
Mar 10, 2025
9a7cc65
feat(validator): devent network support
Mar 31, 2025
025345e
chore(validator): refactor for network support
Mar 31, 2025
3a2ff60
chore(validator): update subgraph query for multi network
Mar 31, 2025
f7ec86f
chore(validator): type params & refactor
Mar 31, 2025
7b9ffa0
chore(validator): update tests for refactor
Mar 31, 2025
cd47f5f
chore(validator): remove old watcher
Mar 31, 2025
6e34daa
fix(validator): existence check
Mar 31, 2025
d66c455
chore(validator): udpdate rpc in env example
Mar 31, 2025
e4c617f
chore(validator): add custom error
Mar 31, 2025
c54c899
chore(validator): pm2 bump & removed web3 dep
Mar 31, 2025
f47ec8a
chore(validator): add no claim required log
Mar 31, 2025
b2bf8d8
fix(validator): claimable epoch calculation fix
Mar 31, 2025
13d6e2b
feat(validator): subgraph fallback for contract logs
Apr 2, 2025
979394b
chore(validator): update tests for subgraph fallback
Apr 7, 2025
de4b1b6
chore(validator): refactor for subgraph fallback
Apr 7, 2025
ee90f37
fix(validator): snapshot sent query fix
Apr 7, 2025
c3d2a23
chore(validator): update pm2 config
Apr 7, 2025
4aa595a
chore(validator): remove legacy devnet code
Apr 7, 2025
28e6095
chore(validator): remove unsused vars
Apr 7, 2025
23937af
fix(validator): add default cases
Apr 7, 2025
c9dbe57
chore(validator): refactor into helper ftns
Apr 7, 2025
30b174d
fix(validator): sonar review fixes
Apr 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 9 additions & 20 deletions validator-cli/.env.dist
Original file line number Diff line number Diff line change
@@ -1,31 +1,20 @@
PRIVATE_KEY=

# Devnet RPCs
RPC_CHIADO=https://rpc.chiadochain.net
RPC_ARB_SEPOLIA=https://sepolia-rollup.arbitrum.io/rpc
RPC_SEPOLIA=
# Networks: devnet, testnet, mainnet
NETWORKS=devnet,testnet
Comment on lines +3 to +4
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify mainnet inclusion in NETWORKS variable

Line 3 mentions "devnet, testnet, mainnet" as supported networks, but line 4 only includes "devnet,testnet". If mainnet support is planned but not yet implemented, consider adding a comment to clarify this intention.


🏁 Script executed:

#!/bin/bash
# Check for mainnet references in the codebase
echo "Searching for mainnet references in the codebase:"
rg -i "mainnet" --type ts

Length of output: 4470


Clarify Mainnet Inclusion in .env.dist

The comment in the file lists the supported networks as "devnet, testnet, mainnet" but the NETWORKS variable only includes "devnet,testnet". Since multiple parts of the codebase (e.g., tests and configurations) reference mainnet, please either:

  • Update the NETWORKS variable to include mainnet if full support is intended, or
  • Add an inline comment in the file to clarify that mainnet support is not yet enabled (or is experimental), thereby explaining the discrepancy.


# Testnet or Mainnet RPCs

# RPCs
RPC_ARB=https://sepolia-rollup.arbitrum.io/rpc
RPC_ETH=
RPC_GNOSIS=https://rpc.chiadochain.net

# Testnet or Mainnet Addresses
# VEA Arbitrum to Ethereum
VEAINBOX_ARB_TO_ETH_ADDRESS=0xE12daFE59Bc3A996362d54b37DFd2BA9279cAd06
VEAOUTBOX_ARB_TO_ETH_ADDRESS=0x209BFdC6B7c66b63A8382196Ba3d06619d0F12c9
# VEA Arbitrum to GNOSIS
VEAINBOX_ARB_TO_GNOSIS_ADDRESS=0x854374483572FFcD4d0225290346279d0718240b
VEAOUTBOX_ARB_TO_GNOSIS_ADDRESS=0x2f1788F7B74e01c4C85578748290467A5f063B0b
VEAROUTER_ARB_TO_GNOSIS_ADDRESS=0x5BE03fDE7794Bc188416ba16932510Ed1277b193
GNOSIS_AMB_ADDRESS=0x8448E15d0e706C0298dECA99F0b4744030e59d7d

VEAOUTBOX_CHAIN_ID=421611
GNOSIS_AMB_ADDRESS=0x8448E15d0e706C0298dECA99F0b4744030e59d7d

# Devnet Addresses
VEAINBOX_ARBSEPOLIA_TO_SEPOLIA_ADDRESS=0x906dE43dBef27639b1688Ac46532a16dc07Ce410
VEAOUTBOX_ARBSEPOLIA_TO_SEPOLIA_ADDRESS=0x906dE43dBef27639b1688Ac46532a16dc07Ce410
VEAOUTBOX_CHAINS=11155111,421611

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

TRANSACTION_BATCHER_CONTRACT_ADDRESS_SEPOLIA=0xe7953da7751063d0a41ba727c32c762d3523ade8
TRANSACTION_BATCHER_CONTRACT_ADDRESS_CHIADO=0xcC0a08D4BCC5f91ee9a1587608f7a2975EA75d73
25 changes: 7 additions & 18 deletions validator-cli/ecosystem.config.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
module.exports = {
apps: [
{
name: "chiado-devnet",
script: "yarn",
args: "start-chiado-devnet",
interpreter: "/bin/bash",
name: "validator-cli",
script: "./src/watcher.ts",
interpreter: "../node_modules/.bin/ts-node",
interpreter_args: "--project tsconfig.json -r tsconfig-paths/register",
log_date_format: "YYYY-MM-DD HH:mm Z",
watch: false,
autorestart: false,
env: {
NODE_ENV: "development",
},
},
{
name: "start-sepolia-devnet",
script: "yarn",
args: "start-sepolia-devnet",
interpreter: "/bin/bash",
log_date_format: "YYYY-MM-DD HH:mm Z",
watch: false,
autorestart: false,
watch: true,
autorestart: true,
env: {
NODE_ENV: "development",
TS_NODE_PROJECT: "./tsconfig.json",
},
},
],
Expand Down
6 changes: 2 additions & 4 deletions validator-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@
"@kleros/vea-contracts": "workspace:^",
"@typechain/ethers-v6": "^0.5.1",
"dotenv": "^16.4.5",
"pm2": "^5.2.2",
"typescript": "^4.9.5",
"web3": "^4.16.0",
"web3-batched-send": "^1.0.3"
"pm2": "^6.0.5",
"typescript": "^4.9.5"
Comment on lines +25 to +26
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Updated PM2 and removed Web3 dependencies

  1. PM2 has been upgraded from ^5.2.2 to ^6.0.5, which aligns with the PR objective of adding PM2 support.
  2. The web3 and web3-batched-send dependencies have been removed, suggesting a shift in how the application interacts with the blockchain.

🏁 Script executed:

#!/bin/bash
# Check for any remaining web3 imports in the codebase that might break after removing these dependencies
rg -i "require\(['\"]web3['\"]" --type ts
rg -i "import.*from ['\"]web3['\"]" --type ts
rg -i "web3-batched-send" --type ts

Length of output: 277


Action Required: Remove Remaining Web3 References

The changes in validator-cli/package.json correctly update PM2 to ^6.0.5 and remove the Web3-related dependencies. However, verification revealed that the file relayer-cli/src/utils/relay.ts still imports:

  • import Web3 from "web3";
  • import initializeBatchedSend from "web3-batched-send";

Please update or remove these imports to ensure consistency with the dependency removals and to prevent potential runtime issues.

},
"devDependencies": {
"@types/jest": "^29.5.14",
Expand Down
234 changes: 234 additions & 0 deletions validator-cli/src/ArbToEth/claimer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import { ethers } from "ethers";
import { checkAndClaim, CheckAndClaimParams } from "./claimer";
import { ClaimHonestState } from "../utils/claim";
import { Network } from "../consts/bridgeRoutes";

describe("claimer", () => {
const NETWORK = Network.DEVNET;
let veaOutbox: any;
let veaInbox: any;
let veaInboxProvider: any;
let veaOutboxProvider: any;
let emitter: any;
let mockClaim: any;
let mockGetLatestClaimedEpoch: any;
let mockGetTransactionHandler: any;
let mockDeps: CheckAndClaimParams;

let mockTransactionHandler: any;
const mockTransactions = {
claimTxn: "0x111",
withdrawClaimDepositTxn: "0x222",
startVerificationTxn: "0x333",
verifySnapshotTxn: "0x444",
devnetAdvanceStateTxn: "0x555",
};
beforeEach(() => {
mockClaim = {
stateRoot: "0x1234",
claimer: "0xFa00D29d378EDC57AA1006946F0fc6230a5E3288",
timestampClaimed: 1234,
timestampVerification: 0,
blocknumberVerification: 0,
honest: 0,
challenger: ethers.ZeroAddress,
};
veaInbox = {
snapshots: jest.fn().mockResolvedValue(mockClaim.stateRoot),
};

veaOutbox = {
stateRoot: jest.fn().mockResolvedValue(mockClaim.stateRoot),
};
veaOutboxProvider = {
getBlock: jest.fn().mockResolvedValue({ number: 0, timestamp: 110 }),
};
emitter = {
emit: jest.fn(),
};

mockGetLatestClaimedEpoch = jest.fn();
mockGetTransactionHandler = jest.fn().mockReturnValue(function DummyTransactionHandler(params: any) {
// Return an object that matches our expected transaction handler.
return mockTransactionHandler;
});
mockDeps = {
chainId: 0,
claim: mockClaim,
network: NETWORK,
epoch: 10,
epochPeriod: 10,
veaInbox,
veaInboxProvider,
veaOutboxProvider,
veaOutbox,
transactionHandler: null,
emitter,
fetchLatestClaimedEpoch: mockGetLatestClaimedEpoch,
now: 110000, // (epoch+ 1) * epochPeriod * 1000 for claimable epoch
};

mockTransactionHandler = {
withdrawClaimDeposit: jest.fn().mockImplementation(() => {
mockTransactionHandler.transactions.withdrawClaimDepositTxn = mockTransactions.withdrawClaimDepositTxn;
return Promise.resolve();
}),
makeClaim: jest.fn().mockImplementation(() => {
mockTransactionHandler.transactions.claimTxn = mockTransactions.claimTxn;
return Promise.resolve();
}),
startVerification: jest.fn().mockImplementation(() => {
mockTransactionHandler.transactions.startVerificationTxn = mockTransactions.startVerificationTxn;
return Promise.resolve();
}),
verifySnapshot: jest.fn().mockImplementation(() => {
mockTransactionHandler.transactions.verifySnapshotTxn = mockTransactions.verifySnapshotTxn;
return Promise.resolve();
}),
transactions: {
claimTxn: "0x0",
withdrawClaimDepositTxn: "0x0",
startVerificationTxn: "0x0",
verifySnapshotTxn: "0x0",
},
};
});
afterEach(() => {
jest.clearAllMocks();
});
describe("checkAndClaim", () => {
beforeEach(() => {
mockTransactionHandler = {
withdrawClaimDeposit: jest.fn().mockImplementation(() => {
mockTransactionHandler.transactions.withdrawClaimDepositTxn = mockTransactions.withdrawClaimDepositTxn;
return Promise.resolve();
}),
makeClaim: jest.fn().mockImplementation(() => {
mockTransactionHandler.transactions.claimTxn = mockTransactions.claimTxn;
return Promise.resolve();
}),
startVerification: jest.fn().mockImplementation(() => {
mockTransactionHandler.transactions.startVerificationTxn = mockTransactions.startVerificationTxn;
return Promise.resolve();
}),
verifySnapshot: jest.fn().mockImplementation(() => {
mockTransactionHandler.transactions.verifySnapshotTxn = mockTransactions.verifySnapshotTxn;
return Promise.resolve();
}),
devnetAdvanceState: jest.fn().mockImplementation(() => {
mockTransactionHandler.transactions.devnetAdvanceStateTxn = mockTransactions.devnetAdvanceStateTxn;
return Promise.resolve();
}),
transactions: {
claimTxn: "0x0",
withdrawClaimDepositTxn: "0x0",
startVerificationTxn: "0x0",
verifySnapshotTxn: "0x0",
},
};
mockGetTransactionHandler = jest.fn().mockReturnValue(function DummyTransactionHandler(param: any) {
return mockTransactionHandler;
});
mockDeps.fetchTransactionHandler = mockGetTransactionHandler;
});
it("should return null if no claim is made for a passed epoch", async () => {
mockDeps.epoch = 7; // claimable epoch - 3
mockDeps.claim = null;

mockDeps.fetchTransactionHandler = mockGetTransactionHandler;
const result = await checkAndClaim(mockDeps);
expect(result).toBeNull();
});
it("should return null if no snapshot is saved on the inbox for a claimable epoch", async () => {
veaInbox.snapshots = jest.fn().mockResolvedValue(ethers.ZeroHash);
mockGetLatestClaimedEpoch = jest.fn().mockResolvedValue({
challenged: false,
stateroot: "0x1111",
});
mockDeps.claim = null;
mockDeps.fetchLatestClaimedEpoch = mockGetLatestClaimedEpoch;
const result = await checkAndClaim(mockDeps);
expect(result).toBeNull();
});
it("should return null if there are no new messages in the inbox", async () => {
veaInbox.snapshots = jest.fn().mockResolvedValue(mockClaim.stateRoot);
mockGetLatestClaimedEpoch = jest.fn().mockResolvedValue({
challenged: false,
stateroot: "0x1111",
});
mockDeps.claim = null;
mockDeps.fetchLatestClaimedEpoch = mockGetLatestClaimedEpoch;
const result = await checkAndClaim(mockDeps);
expect(result).toBeNull();
});
describe("devnet", () => {
beforeEach(() => {
mockDeps.network = Network.DEVNET;
});
it("should make a valid claim and advance state", async () => {
veaInbox.snapshots = jest.fn().mockResolvedValue("0x7890");
mockGetLatestClaimedEpoch = jest.fn().mockResolvedValue({
challenged: false,
stateroot: mockClaim.stateRoot,
});
mockDeps.transactionHandler = mockTransactionHandler;
mockDeps.fetchLatestClaimedEpoch = mockGetLatestClaimedEpoch;
mockDeps.claim = null;
mockDeps.veaInbox = veaInbox;
const result = await checkAndClaim(mockDeps);
expect(result.transactions.devnetAdvanceStateTxn).toBe(mockTransactions.devnetAdvanceStateTxn);
});
});
describe("testnet", () => {
beforeEach(() => {
mockDeps.network = Network.TESTNET;
});
it("should make a valid claim if no claim is made", async () => {
veaInbox.snapshots = jest.fn().mockResolvedValue("0x7890");
mockGetLatestClaimedEpoch = jest.fn().mockResolvedValue({
challenged: false,
stateroot: mockClaim.stateRoot,
});
mockDeps.transactionHandler = mockTransactionHandler;
mockDeps.fetchLatestClaimedEpoch = mockGetLatestClaimedEpoch;
mockDeps.claim = null;
mockDeps.veaInbox = veaInbox;
const result = await checkAndClaim(mockDeps);
expect(result.transactions.claimTxn).toBe(mockTransactions.claimTxn);
});
it("should make a valid claim if last claim was challenged", async () => {
veaInbox.snapshots = jest.fn().mockResolvedValue(mockClaim.stateRoot);
mockGetLatestClaimedEpoch = jest.fn().mockResolvedValue({
challenged: true,
stateroot: mockClaim.stateRoot,
});
mockDeps.transactionHandler = mockTransactionHandler;
mockDeps.fetchLatestClaimedEpoch = mockGetLatestClaimedEpoch;
mockDeps.claim = null;
mockDeps.veaInbox = veaInbox;
const result = await checkAndClaim(mockDeps);
expect(result.transactions.claimTxn).toEqual(mockTransactions.claimTxn);
});
it("should withdraw claim deposit if claimer is honest", async () => {
mockDeps.transactionHandler = mockTransactionHandler;
mockClaim.honest = ClaimHonestState.CLAIMER;
const result = await checkAndClaim(mockDeps);
expect(result.transactions.withdrawClaimDepositTxn).toEqual(mockTransactions.withdrawClaimDepositTxn);
});
it("should start verification if verification is not started", async () => {
mockDeps.transactionHandler = mockTransactionHandler;
mockClaim.honest = ClaimHonestState.NONE;
const result = await checkAndClaim(mockDeps);
expect(result.transactions.startVerificationTxn).toEqual(mockTransactions.startVerificationTxn);
});
it("should verify snapshot if verification is started", async () => {
mockDeps.transactionHandler = mockTransactionHandler;
mockClaim.honest = ClaimHonestState.NONE;
mockClaim.timestampVerification = 1234;
mockDeps.claim = mockClaim;
const result = await checkAndClaim(mockDeps);
expect(result.transactions.verifySnapshotTxn).toEqual(mockTransactions.verifySnapshotTxn);
});
});
});
});
Loading