11import { ContractDeployer , EthAddress , Fr , type Logger , TxStatus , type Wallet } from '@aztec/aztec.js' ;
22import { EthCheatCodes } from '@aztec/aztec/testing' ;
3- import type { GasPrice , L1BlobInputs , L1GasConfig , L1TxRequest , PublisherManager , TxUtilsState } from '@aztec/ethereum' ;
3+ import type { PublisherManager , ViemClient } from '@aztec/ethereum' ;
44import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs' ;
55import { times } from '@aztec/foundation/collection' ;
66import { SecretValue } from '@aztec/foundation/config' ;
7+ import { randomBytes } from '@aztec/foundation/crypto' ;
78import { StatefulTestContractArtifact } from '@aztec/noir-test-contracts.js/StatefulTest' ;
89import type { SequencerClient } from '@aztec/sequencer-client' ;
910import type { TestSequencerClient } from '@aztec/sequencer-client/test' ;
1011import type { AztecNodeAdmin , PXE } from '@aztec/stdlib/interfaces/client' ;
1112
1213import { jest } from '@jest/globals' ;
1314import 'jest-extended' ;
14- import type { Hex } from 'viem' ;
15+ import { type Hex , type TransactionSerialized , recoverTransactionAddress } from 'viem' ;
1516import { mnemonicToAccount } from 'viem/accounts' ;
1617
1718import { MNEMONIC } from './fixtures/fixtures.js' ;
@@ -92,19 +93,6 @@ describe('e2e_multi_eoa', () => {
9293
9394 afterAll ( ( ) => teardown ( ) ) ;
9495
95- const disableMining = async ( ) => {
96- await ethCheatCodes . setAutomine ( false ) ;
97- await ethCheatCodes . setIntervalMining ( 0 ) ;
98- logger . info ( 'Disabled Mining' ) ;
99- } ;
100-
101- // Helper to re-enable Anvil mining
102- const enableMining = async ( ) => {
103- await ethCheatCodes . setAutomine ( true ) ;
104- await ethCheatCodes . evmMine ( ) ;
105- logger . info ( 'Enabled Mining' ) ;
106- } ;
107-
10896 // This executes a test of publisher account rotation.
10997 // We try and publish a block with the expected publisher account.
11098 // We intercept the transaction and delete it from Anvil.
@@ -123,121 +111,54 @@ describe('e2e_multi_eoa', () => {
123111
124112 const l1Utils : L1TxUtilsWithBlobs [ ] = ( publisherManager as any ) . publishers ;
125113
126- // Intercept the required transactions
127- let transactionHashToDrop : Hex | undefined ;
128- let transactionHashToKeep : Hex | undefined ;
129- let cancelTransactionHashToDrop : Hex | undefined ;
130-
131- const originalSendFunctions = l1Utils . map ( l1Util => l1Util . sendTransaction . bind ( l1Util ) ) ;
132- const originalCancelFunctions = l1Utils . map ( l1Util => l1Util . attemptTxCancellation . bind ( l1Util ) ) ;
133-
134- // For the expected 'first' publisher, swap out the send function with one that gets the tx hash and drops it in anvil
135- const sendTxThatWeWillDrop = async (
136- request : L1TxRequest ,
137- _gasConfig ?: L1GasConfig ,
138- blobInputs ?: L1BlobInputs ,
139- stateChange ?: TxUtilsState ,
140- ) => {
141- await disableMining ( ) ;
142- const received = await originalSendFunctions [ expectedFirstSender ] ( request , _gasConfig , blobInputs , stateChange ) ;
143- transactionHashToDrop = received . txHash ;
144- logger . info ( `Dropping tx: ${ transactionHashToDrop } from Anvil` ) ;
145- await ethCheatCodes . dropTransaction ( transactionHashToDrop ) ;
146-
147- try {
148- await ethCheatCodes . publicClient . getTransaction ( {
149- hash : transactionHashToDrop ! ,
150- } ) ;
151- logger . error ( `Failed to drop transaction ${ transactionHashToDrop } from Anvil!!` ) ;
152- // eslint-disable-next-line @typescript-eslint/no-unused-vars
153- } catch ( _ ) {
154- // Should always get here
155- }
156-
157- await enableMining ( ) ;
158- return received ;
159- } ;
160- l1Utils [ expectedFirstSender ] . sendTransaction = jest . fn ( sendTxThatWeWillDrop ) ;
161-
162- // Also for the expected 'first' sender, drop any cancellations that may be sent
163- const sendCancelTxThatWeWillDrop = async (
164- currentTxHash : Hex ,
165- nonce : number ,
166- isBlobTx : boolean ,
167- previousGasPrice ?: GasPrice ,
168- attempts ?: number ,
169- ) => {
170- await disableMining ( ) ;
171- const received = await originalCancelFunctions [ expectedFirstSender ] (
172- currentTxHash ,
173- nonce ,
174- isBlobTx ,
175- previousGasPrice ,
176- attempts ,
177- ) ;
178- cancelTransactionHashToDrop = received ;
179- logger . info ( `Dropping cancel tx: ${ cancelTransactionHashToDrop } from Anvil` ) ;
180- await ethCheatCodes . dropTransaction ( cancelTransactionHashToDrop ) ;
181-
182- try {
183- await ethCheatCodes . publicClient . getTransaction ( {
184- hash : transactionHashToDrop ! ,
185- } ) ;
186- logger . error ( `Failed to drop transaction ${ cancelTransactionHashToDrop } from Anvil!!` ) ;
187- // eslint-disable-next-line @typescript-eslint/no-unused-vars
188- } catch ( _ ) {
189- // Should always get here
190- }
191-
192- await enableMining ( ) ;
193- return received ;
194- } ;
195- l1Utils [ expectedFirstSender ] . attemptTxCancellation = jest . fn ( sendCancelTxThatWeWillDrop ) ;
196-
197- // The 'second' sender should send the next block, we want this to succeed and we will verify against L1 later that
198- // the expected publisher was used
199- const sendTxSuccessfully = async (
200- request : L1TxRequest ,
201- _gasConfig ?: L1GasConfig ,
202- blobInputs ?: L1BlobInputs ,
203- stateChange ?: TxUtilsState ,
204- ) => {
205- const received = await originalSendFunctions [ expectedSecondSender ] (
206- request ,
207- _gasConfig ,
208- blobInputs ,
209- stateChange ,
210- ) ;
211- transactionHashToKeep = received . txHash ;
212- logger . info ( `Tx that we expect to mine: ${ transactionHashToKeep } ` ) ;
213- return received ;
214- } ;
215- l1Utils [ expectedSecondSender ] . sendTransaction = jest . fn ( sendTxSuccessfully ) ;
114+ const blockedSender = l1Utils [ expectedFirstSender ] . getSenderAddress ( ) ;
115+ const blockedTxs : Hex [ ] = [ ] ;
116+ const fallbackSender = l1Utils [ expectedSecondSender ] . getSenderAddress ( ) ;
117+ const fallbackTxs : Hex [ ] = [ ] ;
118+
119+ // NOTE: we only need to spy on a single client because all l1Utils use the same ViemClient instance
120+ const originalSendRawTransaction = l1Utils [ expectedFirstSender ] . client . sendRawTransaction ;
121+
122+ // auto-dispose of this spy at the end of this function
123+ using _ = jest
124+ . spyOn ( l1Utils [ expectedFirstSender ] . client , 'sendRawTransaction' )
125+ . mockImplementation ( async function ( this : ViemClient , arg ) {
126+ const signerAddress = EthAddress . fromString (
127+ await recoverTransactionAddress ( {
128+ serializedTransaction : arg . serializedTransaction as TransactionSerialized < 'eip1559' | 'eip4844' > ,
129+ } ) ,
130+ ) ;
131+
132+ if ( blockedSender . equals ( signerAddress ) ) {
133+ const txHash = randomEthTxHash ( ) ; // block this sender/ Its txs don't actually reach any L1 nodes
134+ blockedTxs . push ( txHash ) ;
135+ return txHash ;
136+ } else {
137+ const txHash = await originalSendRawTransaction . call ( this , arg ) ;
138+ if ( fallbackSender . equals ( signerAddress ) ) {
139+ fallbackTxs . push ( txHash ) ;
140+ }
141+ return txHash ;
142+ }
143+ } ) ;
216144
217145 const tx = deployMethodTx . send ( ) ;
218146 logger . info ( `L2 Tx sent with hash: ${ ( await tx . getTxHash ( ) ) . toString ( ) } ` ) ;
219147
220148 const receipt = await tx . wait ( ) ;
221149 expect ( receipt . status ) . toBe ( TxStatus . SUCCESS ) ;
222150
223- logger . info ( `Checking sender of transaction with hash ${ transactionHashToKeep } ` ) ;
151+ expect ( blockedTxs . length ) . toBeGreaterThan ( 0 ) ;
152+ expect ( fallbackTxs . length ) . toBeGreaterThan ( 0 ) ;
224153
154+ const transactionHashToKeep = fallbackTxs . at ( - 1 ) ! ;
225155 const l1Tx = await ethCheatCodes . publicClient . getTransaction ( {
226- hash : transactionHashToKeep ! ,
156+ hash : transactionHashToKeep ,
227157 } ) ;
228158 const senderEthAddress = EthAddress . fromString ( l1Tx . from ) ;
229159 const expectedSenderEthAddress = EthAddress . fromString ( sequencerKeysAndAddresses [ expectedSecondSender ] . address ) ;
230160 const areSame = senderEthAddress . equals ( expectedSenderEthAddress ) ;
231161 expect ( areSame ) . toBeTrue ( ) ;
232-
233- // Re-instate all modified functions
234- for ( let i = 0 ; i < l1Utils . length ; i ++ ) {
235- l1Utils [ i ] . sendTransaction = originalSendFunctions [ i ] ;
236- l1Utils [ i ] . attemptTxCancellation = originalCancelFunctions [ i ] ;
237- }
238-
239- // Ensure mining is switched on
240- await enableMining ( ) ;
241162 } ;
242163
243164 it ( 'publishers are rotated by the sequencer' , async ( ) => {
@@ -287,3 +208,7 @@ describe('e2e_multi_eoa', () => {
287208 } ) ;
288209 } ) ;
289210} ) ;
211+
212+ function randomEthTxHash ( ) : Hex {
213+ return `0x${ randomBytes ( 32 ) . toString ( 'hex' ) } ` ;
214+ }
0 commit comments