Skip to content

Commit 79be2eb

Browse files
committed
test(host-listener): make slow-lane local validation deterministic
1 parent 99e8962 commit 79be2eb

File tree

4 files changed

+126
-16
lines changed

4 files changed

+126
-16
lines changed

coprocessor/fhevm-engine/host-listener/README.md

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Recommended (auditable) entrypoint:
5858

5959
```bash
6060
cd test-suite/fhevm
61-
./scripts/validate-slow-lane.sh --cap 1 --load-test operators
61+
./scripts/validate-slow-lane.sh --cap 2
6262
```
6363

6464
What this enforces:
@@ -76,7 +76,10 @@ Useful modes:
7676
./scripts/validate-slow-lane.sh --integration-only
7777

7878
# local stack only (assumes stack already up)
79-
./scripts/validate-slow-lane.sh --stack-only --cap 1 --load-test operators
79+
./scripts/validate-slow-lane.sh --stack-only --cap 2
80+
81+
# optional fallback to fhevm-cli test flows
82+
./scripts/validate-slow-lane.sh --stack-only --cap 1 --load-grep "" --load-test operators
8083
```
8184

8285
1. Start local stack
@@ -140,19 +143,19 @@ Important:
140143

141144
- Keep `-p fhevm` when restarting manually. Without it, containers can start on a different compose project/network and fail DNS (`host-node`, `db`).
142145

143-
4. Generate dependent load
146+
4. Generate deterministic dependent load
147+
148+
The validator defaults to a dedicated e2e case (`Slow lane deterministic contention`)
149+
that emits one heavy dependent chain and one light dependent chain.
144150

145-
Use a real host-contract flow so host-listener ingests real TFHE logs:
151+
Manual equivalent:
146152

147153
```bash
148-
cd test-suite/fhevm
149-
./fhevm-cli test erc20
154+
docker exec fhevm-test-suite-e2e-debug \
155+
./run-tests.sh -n staging -g "Slow lane deterministic contention"
150156
```
151157

152-
If this does not trigger slow-lane in local runs, use forcing mode for validation:
153-
154-
- set `--dependent-ops-max-per-chain=1` temporarily
155-
- rerun `./fhevm-cli test erc20`
158+
Recommended cap for this scenario: `2` (heavy chain > cap, light chain <= cap).
156159

157160
5. Validate acceptance criteria in DB
158161

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-License-Identifier: BSD-3-Clause-Clear
2+
3+
pragma solidity ^0.8.24;
4+
5+
import "@fhevm/solidity/lib/FHE.sol";
6+
import {E2ECoprocessorConfig} from "../E2ECoprocessorConfigLocal.sol";
7+
8+
contract SlowLaneContention is E2ECoprocessorConfig {
9+
euint64 public lastResult;
10+
11+
function runAddChain(externalEuint64 seed, bytes calldata inputProof, uint8 steps) external {
12+
euint64 value = FHE.fromExternal(seed, inputProof);
13+
14+
for (uint8 i = 0; i < steps; i++) {
15+
value = FHE.add(value, value);
16+
FHE.allowThis(value);
17+
}
18+
19+
lastResult = value;
20+
FHE.allowThis(lastResult);
21+
FHE.allow(lastResult, msg.sender);
22+
FHE.makePubliclyDecryptable(lastResult);
23+
}
24+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { expect } from 'chai';
2+
import { ethers } from 'hardhat';
3+
4+
import { createInstance } from '../instance';
5+
import { getSigner } from '../signers';
6+
7+
describe('Slow lane deterministic contention', function () {
8+
beforeEach(async function () {
9+
this.signer = await getSigner(119);
10+
11+
const contractFactory = await ethers.getContractFactory('SlowLaneContention');
12+
const contract = await contractFactory.connect(this.signer).deploy();
13+
await contract.waitForDeployment();
14+
15+
this.contractAddress = await contract.getAddress();
16+
this.contract = contract;
17+
this.instance = await createInstance();
18+
});
19+
20+
it('creates one heavy chain and one light chain', async function () {
21+
const heavyInput = this.instance.createEncryptedInput(this.contractAddress, this.signer.address);
22+
heavyInput.add64(1);
23+
const heavyEncryptedInput = await heavyInput.encrypt();
24+
25+
const heavyTx = await this.contract.runAddChain(
26+
heavyEncryptedInput.handles[0],
27+
heavyEncryptedInput.inputProof,
28+
8,
29+
);
30+
const heavyReceipt = await heavyTx.wait();
31+
expect(heavyReceipt?.status).to.eq(1);
32+
33+
const lightInput = this.instance.createEncryptedInput(this.contractAddress, this.signer.address);
34+
lightInput.add64(1);
35+
const lightEncryptedInput = await lightInput.encrypt();
36+
37+
const lightTx = await this.contract.runAddChain(
38+
lightEncryptedInput.handles[0],
39+
lightEncryptedInput.inputProof,
40+
1,
41+
);
42+
const lightReceipt = await lightTx.wait();
43+
expect(lightReceipt?.status).to.eq(1);
44+
});
45+
});

test-suite/fhevm/scripts/validate-slow-lane.sh

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ RUN_INTEGRATION=true
1414
RUN_STACK=true
1515
CAP=1
1616
LOAD_TEST="operators"
17+
LOAD_GREP="Slow lane deterministic contention"
1718
BOOTSTRAP_TIMEOUT_SECONDS=180
1819

1920
RED='\033[0;31m'
@@ -33,7 +34,8 @@ Options:
3334
--integration-only Run only deterministic Rust integration assertions.
3435
--stack-only Run only local-stack multi-listener SQL assertions.
3536
--cap N Slow-lane cap for stack scenario (default: 1).
36-
--load-test NAME fhevm-cli test to generate load (default: operators).
37+
--load-grep PATTERN run-tests grep for deterministic load (default: "Slow lane deterministic contention").
38+
--load-test NAME fhevm-cli test fallback when --load-grep is empty (default: operators).
3739
--bootstrap-timeout SEC Timeout for key-bootstrap gate (default: 180).
3840
-h, --help Show this help.
3941
EOF
@@ -128,9 +130,11 @@ wait_for_bootstrap() {
128130
local has_activate_key
129131
local has_fetched_keyset
130132
local has_key_material
133+
local sns_waiting_for_keys
131134

132135
has_activate_key="$(docker logs --since=20m coprocessor-gw-listener 2>&1 | rg -c "ActivateKey event successful" || true)"
133136
has_fetched_keyset="$(docker logs --since=20m coprocessor-sns-worker 2>&1 | rg -c "Fetched keyset" || true)"
137+
sns_waiting_for_keys="$(docker logs --since=2m coprocessor-sns-worker 2>&1 | rg -c "No keys available, retrying" || true)"
134138
has_key_material="$(db_query "
135139
SELECT COALESCE(bool_and(key_bytes > 0), false)
136140
FROM (
@@ -146,6 +150,10 @@ wait_for_bootstrap() {
146150
log "Bootstrap gate passed (ActivateKey + keyset + non-empty sns_pk)"
147151
return 0
148152
fi
153+
if [[ "${has_key_material}" == "t" && "${sns_waiting_for_keys}" == "0" ]]; then
154+
log "Bootstrap gate passed (non-empty sns_pk and sns-worker not waiting for keys)"
155+
return 0
156+
fi
149157
sleep 3
150158
done
151159

@@ -155,11 +163,28 @@ wait_for_bootstrap() {
155163
local retry_deadline=$((SECONDS + BOOTSTRAP_TIMEOUT_SECONDS))
156164
while (( SECONDS < retry_deadline )); do
157165
local has_fetched_keyset
166+
local has_key_material
167+
local sns_waiting_for_keys
158168
has_fetched_keyset="$(docker logs --since=20m coprocessor-sns-worker 2>&1 | rg -c "Fetched keyset" || true)"
169+
sns_waiting_for_keys="$(docker logs --since=2m coprocessor-sns-worker 2>&1 | rg -c "No keys available, retrying" || true)"
170+
has_key_material="$(db_query "
171+
SELECT COALESCE(bool_and(key_bytes > 0), false)
172+
FROM (
173+
SELECT COALESCE(SUM(octet_length(lo.data)), 0) AS key_bytes
174+
FROM tenants t
175+
LEFT JOIN pg_largeobject lo
176+
ON lo.loid = t.sns_pk
177+
GROUP BY t.tenant_id
178+
) s;
179+
")"
159180
if [[ "${has_fetched_keyset}" -gt 0 ]]; then
160181
log "Bootstrap recovered after gw-listener restart"
161182
return 0
162183
fi
184+
if [[ "${has_key_material}" == "t" && "${sns_waiting_for_keys}" == "0" ]]; then
185+
log "Bootstrap recovered from persisted key material state"
186+
return 0
187+
fi
163188
sleep 3
164189
done
165190

@@ -215,7 +240,11 @@ run_integration_assertions() {
215240
}
216241

217242
run_stack_assertions() {
218-
log "Running local-stack assertions (cap=${CAP}, load=${LOAD_TEST})"
243+
if [[ -n "${LOAD_GREP}" ]]; then
244+
log "Running local-stack assertions (cap=${CAP}, grep=${LOAD_GREP})"
245+
else
246+
log "Running local-stack assertions (cap=${CAP}, load=${LOAD_TEST})"
247+
fi
219248
wait_for_bootstrap
220249

221250
local before_block_height
@@ -224,10 +253,14 @@ run_stack_assertions() {
224253

225254
apply_listener_cap_override "${CAP}"
226255

227-
(
228-
cd "${FHEVM_DIR}"
229-
./fhevm-cli test "${LOAD_TEST}"
230-
)
256+
if [[ -n "${LOAD_GREP}" ]]; then
257+
docker exec fhevm-test-suite-e2e-debug ./run-tests.sh -n staging -g "${LOAD_GREP}"
258+
else
259+
(
260+
cd "${FHEVM_DIR}"
261+
./fhevm-cli test "${LOAD_TEST}"
262+
)
263+
fi
231264

232265
local counts
233266
counts="$(db_query "
@@ -287,6 +320,11 @@ while (( "$#" )); do
287320
LOAD_TEST="$2"
288321
shift 2
289322
;;
323+
--load-grep)
324+
[[ $# -ge 2 ]] || die "--load-grep requires a value"
325+
LOAD_GREP="$2"
326+
shift 2
327+
;;
290328
--bootstrap-timeout)
291329
[[ $# -ge 2 ]] || die "--bootstrap-timeout requires a value"
292330
BOOTSTRAP_TIMEOUT_SECONDS="$2"

0 commit comments

Comments
 (0)