-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/happy path(claimer) in validator-cli #396
base: dev
Are you sure you want to change the base?
Changes from 14 commits
a1b4008
295a2e7
2d2c850
25b88b6
759fd1f
a38d3ea
c4fa598
76dc985
f9a91e9
ad77cf4
371e8d0
7173b75
240f5f7
a25b473
cfa2e02
105a50a
edce69c
3f78877
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import type { Config } from "jest"; | ||
|
||
const config: Config = { | ||
preset: "ts-jest", | ||
testEnvironment: "node", | ||
collectCoverage: true, | ||
collectCoverageFrom: ["**/*.ts"], | ||
}; | ||
|
||
export default config; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
import { ethers } from "ethers"; | ||
import { checkAndClaim } from "./claimer"; | ||
import { ArbToEthTransactionHandler } from "./transactionHandler"; | ||
import { ClaimHonestState } from "../utils/claim"; | ||
import { start } from "pm2"; | ||
describe("claimer", () => { | ||
let veaOutbox: any; | ||
let veaInbox: any; | ||
let veaInboxProvider: any; | ||
let veaOutboxProvider: any; | ||
let emitter: any; | ||
let mockGetClaim: any; | ||
let mockClaim: any; | ||
let mockGetLatestClaimedEpoch: any; | ||
let mockDeps: any; | ||
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: 100 }), | ||
}; | ||
emitter = { | ||
emit: jest.fn(), | ||
}; | ||
|
||
mockGetClaim = jest.fn(); | ||
mockGetLatestClaimedEpoch = jest.fn(); | ||
mockDeps = { | ||
epoch: 10, | ||
epochPeriod: 10, | ||
veaInbox, | ||
veaInboxProvider, | ||
veaOutboxProvider, | ||
veaOutbox, | ||
transactionHandler: null, | ||
emitter, | ||
fetchClaim: mockGetClaim, | ||
fetchLatestClaimedEpoch: mockGetLatestClaimedEpoch, | ||
}; | ||
}); | ||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
describe("checkAndClaim", () => { | ||
let mockTransactionHandler: any; | ||
const mockTransactions = { | ||
claimTxn: "0x111", | ||
withdrawClaimDepositTxn: "0x222", | ||
startVerificationTxn: "0x333", | ||
verifySnapshotTxn: "0x444", | ||
}; | ||
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(); | ||
}), | ||
transactions: { | ||
claimTxn: "0x0", | ||
withdrawClaimDepositTxn: "0x0", | ||
startVerificationTxn: "0x0", | ||
verifySnapshotTxn: "0x0", | ||
}, | ||
}; | ||
}); | ||
it("should return null if no claim is made for a passed epoch", async () => { | ||
mockGetClaim = jest.fn().mockReturnValue(null); | ||
mockDeps.epoch = 7; // claimable epoch - 3 | ||
mockDeps.fetchClaim = mockGetClaim; | ||
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 () => { | ||
mockGetClaim = jest.fn().mockReturnValue(null); | ||
veaInbox.snapshots = jest.fn().mockResolvedValue(ethers.ZeroHash); | ||
mockGetLatestClaimedEpoch = jest.fn().mockResolvedValue({ | ||
challenged: false, | ||
stateroot: "0x1111", | ||
}); | ||
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 () => { | ||
mockGetClaim = jest.fn().mockReturnValue(null); | ||
veaInbox.snapshots = jest.fn().mockResolvedValue(mockClaim.stateRoot); | ||
mockGetLatestClaimedEpoch = jest.fn().mockResolvedValue({ | ||
challenged: false, | ||
stateroot: "0x1111", | ||
}); | ||
mockDeps.fetchLatestClaimedEpoch = mockGetLatestClaimedEpoch; | ||
const result = await checkAndClaim(mockDeps); | ||
expect(result).toBeNull(); | ||
}); | ||
it("should make a valid calim if no claim is made", async () => { | ||
mockGetClaim = jest.fn().mockReturnValue(null); | ||
veaInbox.snapshots = jest.fn().mockResolvedValue("0x7890"); | ||
mockGetLatestClaimedEpoch = jest.fn().mockResolvedValue({ | ||
challenged: false, | ||
stateroot: mockClaim.stateRoot, | ||
}); | ||
mockDeps.transactionHandler = mockTransactionHandler; | ||
mockDeps.fetchLatestClaimedEpoch = mockGetLatestClaimedEpoch; | ||
mockDeps.fetchClaim = mockGetClaim; | ||
mockDeps.veaInbox = veaInbox; | ||
const result = await checkAndClaim(mockDeps); | ||
expect(result.transactions.claimTxn).toBe(mockTransactions.claimTxn); | ||
}); | ||
it("should make a valid calim if last claim was challenged", async () => { | ||
mockGetClaim = jest.fn().mockReturnValue(null); | ||
veaInbox.snapshots = jest.fn().mockResolvedValue(mockClaim.stateRoot); | ||
mockGetLatestClaimedEpoch = jest.fn().mockResolvedValue({ | ||
challenged: true, | ||
stateroot: mockClaim.stateRoot, | ||
}); | ||
mockDeps.transactionHandler = mockTransactionHandler; | ||
mockDeps.fetchLatestClaimedEpoch = mockGetLatestClaimedEpoch; | ||
mockDeps.fetchClaim = mockGetClaim; | ||
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; | ||
|
||
mockGetClaim = jest.fn().mockResolvedValue(mockClaim); | ||
mockDeps.fetchClaim = mockGetClaim; | ||
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; | ||
mockGetClaim = jest.fn().mockResolvedValue(mockClaim); | ||
mockDeps.fetchClaim = mockGetClaim; | ||
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; | ||
mockGetClaim = jest.fn().mockResolvedValue(mockClaim); | ||
mockDeps.fetchClaim = mockGetClaim; | ||
const result = await checkAndClaim(mockDeps); | ||
expect(result.transactions.verifySnapshotTxn).toEqual(mockTransactions.verifySnapshotTxn); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,75 @@ | ||||||
import { EventEmitter } from "events"; | ||||||
import { ethers } from "ethers"; | ||||||
import { JsonRpcProvider } from "@ethersproject/providers"; | ||||||
import { getClaim, ClaimHonestState } from "../utils/claim"; | ||||||
import { getLastClaimedEpoch } from "../utils/graphQueries"; | ||||||
import { ArbToEthTransactionHandler } from "./transactionHandler"; | ||||||
import { BotEvents } from "../utils/botEvents"; | ||||||
interface checkAndClaimParams { | ||||||
epochPeriod: number; | ||||||
epoch: number; | ||||||
veaInbox: any; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Replace 'any' with specific contract types for 'veaInbox' and 'veaOutbox' Using Apply this diff to fix the issue: interface checkAndClaimParams {
epochPeriod: number;
epoch: number;
- veaInbox: any;
+ veaInbox: VeaInboxArbToEth;
veaInboxProvider: JsonRpcProvider;
- veaOutbox: any;
+ veaOutbox: VeaOutboxArbToEth;
veaOutboxProvider: JsonRpcProvider;
transactionHandler: ArbToEthTransactionHandler | null;
emitter: EventEmitter; Also applies to: 13-13 |
||||||
veaInboxProvider: JsonRpcProvider; | ||||||
veaOutbox: any; | ||||||
veaOutboxProvider: JsonRpcProvider; | ||||||
transactionHandler: ArbToEthTransactionHandler | null; | ||||||
emitter: EventEmitter; | ||||||
fetchClaim?: typeof getClaim; | ||||||
fetchLatestClaimedEpoch?: typeof getLastClaimedEpoch; | ||||||
} | ||||||
|
||||||
export async function checkAndClaim({ | ||||||
epoch, | ||||||
epochPeriod, | ||||||
veaInbox, | ||||||
veaInboxProvider, | ||||||
veaOutbox, | ||||||
veaOutboxProvider, | ||||||
transactionHandler, | ||||||
emitter, | ||||||
fetchClaim = getClaim, | ||||||
fetchLatestClaimedEpoch = getLastClaimedEpoch, | ||||||
}: checkAndClaimParams) { | ||||||
let outboxStateRoot = await veaOutbox.stateRoot(); | ||||||
const finalizedOutboxBlock = await veaOutboxProvider.getBlock("finalized"); | ||||||
const claimAbleEpoch = finalizedOutboxBlock.timestamp / epochPeriod; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure 'claimableEpoch' is an integer The calculation of Apply this diff to fix the issue: - const claimAbleEpoch = finalizedOutboxBlock.timestamp / epochPeriod;
+ const claimableEpoch = Math.floor(finalizedOutboxBlock.timestamp / epochPeriod); 📝 Committable suggestion
Suggested change
|
||||||
const claim = await fetchClaim(veaOutbox, veaOutboxProvider, epoch, finalizedOutboxBlock.number, "finalized"); | ||||||
if (!transactionHandler) { | ||||||
transactionHandler = new ArbToEthTransactionHandler( | ||||||
epoch, | ||||||
veaInbox, | ||||||
veaOutbox, | ||||||
veaInboxProvider, | ||||||
veaOutboxProvider, | ||||||
emitter, | ||||||
claim | ||||||
); | ||||||
} else { | ||||||
transactionHandler.claim = claim; | ||||||
} | ||||||
if (claim == null && epoch == claimAbleEpoch) { | ||||||
const [savedSnapshot, claimData] = await Promise.all([veaInbox.snapshots(epoch), fetchLatestClaimedEpoch()]); | ||||||
const newMessagesToBridge: boolean = savedSnapshot != outboxStateRoot && savedSnapshot != ethers.ZeroHash; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct the use of 'ethers.ZeroHash' to 'ethers.constants.HashZero' The property Apply this diff to fix the issue: - const newMessagesToBridge: boolean = savedSnapshot != outboxStateRoot && savedSnapshot != ethers.ZeroHash;
+ const newMessagesToBridge: boolean = savedSnapshot != outboxStateRoot && savedSnapshot != ethers.constants.HashZero; 📝 Committable suggestion
Suggested change
|
||||||
const lastClaimChallenged: boolean = claimData.challenged && savedSnapshot == outboxStateRoot; | ||||||
|
||||||
if (newMessagesToBridge || lastClaimChallenged) { | ||||||
await transactionHandler.makeClaim(savedSnapshot); | ||||||
return transactionHandler; | ||||||
} | ||||||
} else if (claim != null) { | ||||||
if (claim.honest == ClaimHonestState.CLAIMER) { | ||||||
await transactionHandler.withdrawClaimDeposit(); | ||||||
return transactionHandler; | ||||||
} else if (claim.honest == ClaimHonestState.NONE) { | ||||||
if (claim.timestampVerification == 0) { | ||||||
await transactionHandler.startVerification(finalizedOutboxBlock.timestamp); | ||||||
} else { | ||||||
await transactionHandler.verifySnapshot(finalizedOutboxBlock.timestamp); | ||||||
} | ||||||
return transactionHandler; | ||||||
} | ||||||
} else { | ||||||
emitter.emit(BotEvents.CLAIM_EPOCH_PASSED, epoch); | ||||||
} | ||||||
return null; | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Remove duplicate RPC URL configuration.
The Arbitrum RPC URL is defined twice:
RPC_ARB_SEPOLIA=https://sepolia-rollup.arbitrum.io/rpc
RPC_ARB=https://sepolia-rollup.arbitrum.io/rpc
This could lead to confusion. Consider removing one of them or clearly documenting the difference if both are needed.
Also applies to: 4-4