From 72c092acfa4082a397f76fb2b20e514e2bb53995 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:13:41 +0100 Subject: [PATCH] fix: rewards message test flakiness (#377) ## Summary This PR removes sources of flakiness in the rewards message E2E test by: 1. Selecting a validator whose Ethereum key is not shared with the relayers 2. Using specific block numbers for balance reads to avoid stale RPC data ## Changes - Select a non-relayer validator from rewardPoints.individual when claiming rewards - Remove the conditional updateSolochainAddressForValidator block (mapping is set during registration) - Read balances at specific block numbers to ensure deterministic comparisons --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com> --- test/suites/rewards-message.test.ts | 69 +++++++++++++++-------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/test/suites/rewards-message.test.ts b/test/suites/rewards-message.test.ts index a39c71ee..651f1082 100644 --- a/test/suites/rewards-message.test.ts +++ b/test/suites/rewards-message.test.ts @@ -1,5 +1,5 @@ import { beforeAll, describe, expect, it } from "bun:test"; -import { CROSS_CHAIN_TIMEOUTS, logger } from "utils"; +import { ANVIL_FUNDED_ACCOUNTS, CROSS_CHAIN_TIMEOUTS, logger } from "utils"; import { type Address, decodeEventLog, type Hex, isAddressEqual, padHex } from "viem"; import validatorSet from "../configs/validator-set.json"; import { BaseTestSuite } from "../framework"; @@ -135,7 +135,22 @@ describe("Rewards Message Flow", () => { expect(rewardPoints).toBeDefined(); expect(rewardPoints.total).toBeGreaterThan(0); - const [validatorAccount, points] = rewardPoints.individual[0]; + const relayerEthAccount = ANVIL_FUNDED_ACCOUNTS[1].publicKey.toLowerCase(); + const pick = rewardPoints.individual.find(([account]: [any, any]) => { + const v = validatorSet.validators.find( + (cfg) => cfg.solochainAddress.toLowerCase() === String(account).toLowerCase() + ); + return v && v.publicKey.toLowerCase() !== relayerEthAccount; + }); + const [validatorAccount, points] = (pick ?? rewardPoints.individual[0]) as [any, any]; + const match = validatorSet.validators.find( + (v) => v.solochainAddress.toLowerCase() === String(validatorAccount).toLowerCase() + ); + if (!match) { + throw new Error( + `Validator config not found for solochain address ${String(validatorAccount)}` + ); + } // Generate merkle proof via runtime API const merkleProof = await dhApi.apis.ExternalValidatorsRewardsApi.generate_rewards_merkle_proof( @@ -146,33 +161,9 @@ describe("Rewards Message Flow", () => { // Get validator credentials and create operator wallet const factory = suite.getConnectorFactory(); - const match = validatorSet.validators.find( - (v) => v.solochainAddress.toLowerCase() === String(validatorAccount).toLowerCase() - ); const operatorWallet = factory.createWalletClient(match!.privateKey as `0x${string}`); const resolvedOperator: Address = operatorWallet.account.address; - // Ensure the ServiceManager maps the operator ETH address to the solochain address - const expectedSolochain = String(validatorAccount) as Address; - const mappedSolochain = (await publicClient.readContract({ - address: serviceManager.address as Address, - abi: serviceManager.abi, - functionName: "validatorEthAddressToSolochainAddress", - args: [resolvedOperator] - })) as Address; - - if (mappedSolochain.toLowerCase() !== expectedSolochain.toLowerCase()) { - const updateTx = await operatorWallet.writeContract({ - address: serviceManager.address as Address, - abi: serviceManager.abi, - functionName: "updateSolochainAddressForValidator", - args: [expectedSolochain], - chain: null - }); - const updateReceipt = await publicClient.waitForTransactionReceipt({ hash: updateTx }); - expect(updateReceipt.status).toBe("success"); - } - // Ensure claim not already recorded const claimedBefore = (await publicClient.readContract({ address: rewardsRegistry.address, @@ -182,10 +173,17 @@ describe("Rewards Message Flow", () => { })) as boolean; expect(claimedBefore).toBe(false); - // Record balances for validation - const operatorBalanceBefore = await publicClient.getBalance({ address: resolvedOperator }); + // Record balances at a specific block to avoid stale RPC reads + const balanceBlockNumber = Number(await publicClient.getBlockNumber()); + const operatorBalanceBefore = await publicClient.getBalance({ + address: resolvedOperator, + blockNumber: balanceBlockNumber + }); const registryBalanceBefore = BigInt( - await publicClient.getBalance({ address: rewardsRegistry.address as Address }) + await publicClient.getBalance({ + address: rewardsRegistry.address as Address, + blockNumber: balanceBlockNumber + }) ); // Submit claim transaction @@ -238,14 +236,19 @@ describe("Rewards Message Flow", () => { expect(claimedAfter).toBe(true); // Validate RewardsRegistry balance decrease matches claimed rewards - const registryBalanceAfter = BigInt( - await publicClient.getBalance({ address: rewardsRegistry.address as Address }) - ); + const claimBlockNumber = Number(claimReceipt.blockNumber); + const registryBalanceAfter = await publicClient.getBalance({ + address: rewardsRegistry.address as Address, + blockNumber: claimBlockNumber + }); expect(registryBalanceBefore - registryBalanceAfter).toEqual(claimArgs.rewardsAmount); expect(claimArgs.rewardsAmount).toEqual(BigInt(points)); // Validate operator received rewards (accounting for gas) - const operatorBalanceAfter = await publicClient.getBalance({ address: resolvedOperator }); + const operatorBalanceAfter = await publicClient.getBalance({ + address: resolvedOperator, + blockNumber: claimBlockNumber + }); const gasCost = BigInt(claimReceipt.gasUsed) * BigInt(claimReceipt.effectiveGasPrice); const netBalanceChange = BigInt(operatorBalanceAfter) - BigInt(operatorBalanceBefore); // Operator balance should have changed by: rewards - gasCost