Commit 5c28b25
authored
refactor(services): unified approveService + TEE commitment-root storage (#119)
Collapse five `approveServiceWith*` entrypoints into one
`approveService(ApprovalParams)`. Move TEE commitment storage from a
per-operator dynamic array to a single keccak256 root + full data
emitted as `TeeCommitmentsRecorded`. BLS stays opt-in via zero pubkey.
RFQ paths (`createServiceFromQuotes`, `extendServiceFromQuotes`) are
unchanged — they already used the resource-commitment hash pattern.
WHY THIS CHANGE
The matrix of approval entrypoints was strict superset growth: each new
optional capability (commitments → BLS → BLS+commitments → TEE) doubled
the surface and left the protocol with a 5-selector explosion that any
new feature would extend further. Every variant duplicated the auth
gates inline, making it easy to drop a check on one path and ship.
The TEE commitment work also stored the full struct array per-operator
on activation — a per-(request, operator) cap of 8 commits × 3 storage
slots × 20K cold SSTORE ≈ 480K gas per operator just to copy commits
forward. With multi-operator services that gas scales linearly in
operator count and burns the activator's tx alone, since "last operator
pays for everyone" is the default activation pattern. The audit flagged
this as a HIGH operator-economics concern.
ARCHITECTURE
Single entrypoint:
approveService(ApprovalParams { requestId, securityCommitments,
blsPubkey, blsPopSignature,
teeCommitments }) external
Empty / zero fields are no-ops. The contract dispatches:
- securityCommitments empty + requirements present → auto-fill
protocol-default TNT commitment (only when that's the lone
requirement) or revert
- blsPubkey == [0,0,0,0] → operator skips BLS
- teeCommitments empty → operator skips TEE binding
Order of operations (fail-fast, validate-before-write):
1. _requireApprovingOperator (auth gate, no SSTORE)
2. _validateSecurityCommitments OR auto-fill default TNT
3. _validateTeeCommitments + compute keccak root
4. BLS proof-of-possession verify (if registering)
5. SSTORE security commits / TEE root / BLS pubkey
6. Mark approved, emit, manager-hook, activate-if-threshold
TEE storage shape:
mapping(uint64 => mapping(address => bytes32)) _requestTeeCommitmentRoot
mapping(uint64 => mapping(address => bytes32)) _serviceTeeCommitmentRoot
Activation: O(operators) — one bytes32 SSTORE per operator that
supplied a non-empty TEE array. Down from O(operators × commits × 3
slots). Pre-refactor 3-operators × 8-commits activator-gas was 2.89M;
post-refactor expected well under 1M (gas test enforces < 1.5M).
Slashing pattern: contract stores root only; slasher / provisioning
oracle supplies the original commitment array as a witness, verifies
`keccak256(abi.encode(witness)) == getTeeCommitmentRoot(serviceId, op)`
before treating the witness as authoritative. Same pattern already
used by `_serviceResourceCommitmentHash` for RFQ resource commits.
RFQ PATHS UNCHANGED
`createServiceFromQuotes` and `extendServiceFromQuotes` keep the
signed-quote acceptance flow exactly as it was. Quotes carry resource
commitments (already hash-stored) and security commitments. TEE is NOT
in the EIP-712 quote shape — TEE commitments require a request-derived
nonce that doesn't exist until the request is created, so they can't
be pre-signed in a quote. Manual approval remains the only path that
binds TEE commitments today; if RFQ + TEE is wanted later, extend the
QuoteDetails type at that point.
BLS IS OPT-IN
The protocol must accept any operator. Operators that don't register a
BLS pubkey can still approve services and run workloads — they simply
cannot participate in aggregated job-result signing on services that
choose to use BLS aggregation. The `JobsAggregation` path reverts with
`OperatorBlsPubkeyNotRegistered` only when an operator who didn't
register tries to participate in a BLS aggregate. No exclusion at
approval, no penalty otherwise.
API IMPACT
- TangleServicesFacet selectors: 10 → 6
Removed: approveService(uint64,uint8), approveServiceWithCommitments,
approveServiceWithBls, approveServiceWithCommitmentsAndBls,
approveServiceWithTeeCommitments, getTeeCommitment
Kept: approveService(ApprovalParams), rejectService,
getOperatorBlsPubkey, blsPopMessage,
getTeeCommitmentRoot (replaces getTeeCommitment),
teeNonceFor
- ITangleServices interface trimmed to match.
TEST SURFACE
Replaced TeeCommitmentApprovalTest.t.sol (16 cases) and
TeeCommitmentHardenTest.t.sol (7 cases) with one tight
ServicesApprovalTest.t.sol (~14 cases). Every test names a specific
failure mode; no compiler-bug theater. Coverage:
Happy paths
- single-operator + single TEE commit, root persists
- minimal approval, no optional fields
- mixed TEE / non-TEE operators, roots independent
- slasher witness verification (honest matches, tampered rejects)
Adversarial — TEE validation
- DirectTdx rejected; Unset enum sentinel rejected
- zero expectedMeasurement rejected
- cross-request replay rejected (nonce request-derived)
- past expiry rejected; >MAX_TTL rejected; at-cap accepted
- TooManyTeeCommitments (cap = 8) rejected
Adversarial — auth ordering
- unauthorized caller fails BEFORE storage write
- double approval rejected
Activation gas measurement
- 3 ops × 8 commits gas, asserted < 1.5M (was 2.89M pre-refactor)
CALL-SITE MIGRATION
~50 call sites in test/ and script/ migrated to the unified entrypoint
via `_approve / _approveWithCommitments / _approveWithBls` helpers
hoisted into BlueprintDefinitionHelper (visible in BaseTest,
TestHarness, and InvariantFuzz). One inline ApprovalParams construction
in three scripts where the helper isn't reachable.
DOES NOT TOUCH
- fix/slashing-correctness branch / Slashing.sol / SlashingLib.sol
- RFQ flows (QuotesCreate, QuotesExtend, TangleQuotesFacet)
- BLS aggregation path (JobsAggregation.sol)
- Heartbeat, payments, blueprint registry, MBSM, beacon, oracles1 parent e44681d commit 5c28b25
37 files changed
Lines changed: 829 additions & 1329 deletions
File tree
- script
- src
- core
- facets/tangle
- interfaces
- libraries
- test
- blueprints
- fuzz
- rewards
- support
- tangle
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
53 | | - | |
| 53 | + | |
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
475 | 475 | | |
476 | 476 | | |
477 | 477 | | |
478 | | - | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
479 | 487 | | |
480 | 488 | | |
481 | 489 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
913 | 913 | | |
914 | 914 | | |
915 | 915 | | |
916 | | - | |
| 916 | + | |
917 | 917 | | |
918 | 918 | | |
919 | 919 | | |
| |||
926 | 926 | | |
927 | 927 | | |
928 | 928 | | |
929 | | - | |
| 929 | + | |
930 | 930 | | |
931 | 931 | | |
932 | 932 | | |
| |||
1251 | 1251 | | |
1252 | 1252 | | |
1253 | 1253 | | |
1254 | | - | |
| 1254 | + | |
1255 | 1255 | | |
1256 | 1256 | | |
1257 | 1257 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
373 | 373 | | |
374 | 374 | | |
375 | 375 | | |
376 | | - | |
377 | | - | |
378 | | - | |
379 | | - | |
380 | | - | |
381 | | - | |
382 | | - | |
383 | | - | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
384 | 389 | | |
385 | 390 | | |
386 | 391 | | |
| |||
0 commit comments