@@ -5,6 +5,7 @@ import { compatPolicyForState } from "../compat/compat";
55import { DRIFT_CLEANUP_SQL , DRIFT_INSTALL_SQL , driftDatabaseName , parseDriftInstanceIndex , parsePositiveInteger } from "../drift" ;
66import { PreflightError } from "../errors" ;
77import { pause , shellEscape , unpause } from "../flow/up-flow" ;
8+ import { hostReachableRpcUrl } from "../utils/fs" ;
89import { run , runWithHeartbeat } from "../utils/process" ;
910import { loadState } from "../state/state" ;
1011import { topologyForState } from "../stack-spec/stack-spec" ;
@@ -222,6 +223,44 @@ const waitForDriftWarning = async (
222223 throw new PreflightError ( `drift warning was not observed after injecting handle ${ handleHex } ` ) ;
223224} ;
224225
226+ /** Topic hash for AddCiphertextMaterial(bytes32 indexed ctHandle, uint256 keyId, bytes32 ciphertextDigest, bytes32 snsCiphertextDigest, address coprocessorTxSender). */
227+ const ADD_CIPHERTEXT_MATERIAL_TOPIC = "0x7249a80e5b91709d2170511b960e8a92e1d5849d200f320524dfffd8b50308f7" ;
228+
229+ /** Queries on-chain AddCiphertextMaterial events and asserts the two submissions have divergent digests. */
230+ const assertOnChainDivergence = async (
231+ gatewayRpcUrl : string ,
232+ contractAddress : string ,
233+ handleHex : string ,
234+ ) => {
235+ const paddedHandle = `0x${ handleHex . toLowerCase ( ) . padStart ( 64 , "0" ) } ` ;
236+ const response = await fetch ( gatewayRpcUrl , {
237+ method : "POST" ,
238+ headers : { "content-type" : "application/json" } ,
239+ body : JSON . stringify ( {
240+ jsonrpc : "2.0" ,
241+ id : 1 ,
242+ method : "eth_getLogs" ,
243+ params : [ { fromBlock : "0x0" , toBlock : "latest" , address : contractAddress , topics : [ ADD_CIPHERTEXT_MATERIAL_TOPIC , paddedHandle ] } ] ,
244+ } ) ,
245+ } ) ;
246+ if ( ! response . ok ) {
247+ throw new PreflightError ( `eth_getLogs failed: ${ response . status } ${ response . statusText } ` ) ;
248+ }
249+ const payload = ( await response . json ( ) ) as { result ?: { data : string } [ ] } ;
250+ const logs = payload . result ?? [ ] ;
251+ if ( logs . length < 2 ) {
252+ throw new PreflightError ( `expected 2+ AddCiphertextMaterial events for handle 0x${ handleHex } , got ${ logs . length } ` ) ;
253+ }
254+ // data layout: keyId (32B) | ciphertextDigest (32B) | snsCiphertextDigest (32B) | coprocessorTxSender (32B)
255+ // ciphertextDigest starts at byte offset 32 (chars 66..130 in the 0x-prefixed hex)
256+ const digests = logs . map ( ( log ) => log . data . slice ( 66 , 130 ) ) ;
257+ const unique = new Set ( digests ) ;
258+ if ( unique . size < 2 ) {
259+ throw new PreflightError ( `on-chain AddCiphertextMaterial events show identical digests — drift not visible on chain` ) ;
260+ }
261+ console . log ( `[drift] on-chain divergence confirmed: ${ logs . length } submissions with ${ unique . size } distinct digest(s)` ) ;
262+ } ;
263+
225264/** Builds the `docker exec` argv used to run tests inside the test-suite container. */
226265export const buildTestContainerArgs = ( tail : string [ ] , extraExecArgs : string [ ] = [ ] ) => [
227266 "docker" ,
@@ -542,7 +581,7 @@ export const test = async (testName: string | undefined, options: TestOptions) =
542581 postgresPassword : postgres . postgresPassword ,
543582 } ) ;
544583 await runWithHeartbeat (
545- buildTestContainerArgs ( [ "./run-tests.sh" , "-n" , options . network , "-g" , grepPattern ] , [ "-e" , "EXPECT_CIPHERTEXT_DIVERGENCE=true " ] ) ,
584+ buildTestContainerArgs ( [ "./run-tests.sh" , "-n" , options . network , "-g" , grepPattern ] , [ "-e" , "GATEWAY_RPC_URL= " ] ) ,
546585 "test ciphertext-drift" ,
547586 ) ;
548587 const injectedHandleHex = await injector ;
@@ -552,6 +591,11 @@ export const test = async (testName: string | undefined, options: TestOptions) =
552591 pollIntervalSeconds : driftAlertPollIntervalSeconds ,
553592 } ) ;
554593 console . log ( `[drift] detected in ${ warning . container } for injected handle 0x${ injectedHandleHex } ` ) ;
594+ const ciphertextCommitsAddress = state . discovery ! . gateway . CIPHERTEXT_COMMITS_ADDRESS ;
595+ if ( ciphertextCommitsAddress ) {
596+ const gatewayRpcUrl = hostReachableRpcUrl ( state . discovery ! . endpoints . gateway . http ) ;
597+ await assertOnChainDivergence ( gatewayRpcUrl , ciphertextCommitsAddress , injectedHandleHex ) ;
598+ }
555599 } ) ;
556600 }
557601 if ( name === "multi-chain-isolation" ) {
0 commit comments