From 5cf710a7ed0ffbe27ec8f2d27985770e51f7ad68 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Fri, 13 Feb 2026 18:57:11 -0300 Subject: [PATCH 1/8] Replace string errors with custom errors in both OnChainProposer contracts and update the Rust side to detect revert reasons by their 4-byte ABI selector from the RPC error `data` field instead of string-matching on `message`. The non-based contract used opaque hex codes ("007", "00c") while the based contract used full strings. This caused a bug where l1_proof_sender's message.contains("Invalid TDX proof") never matched the non-based contract's codes, so invalid proofs were never deleted. Both contracts now use identical custom errors defined in their interfaces, which are cheaper (no string storage), self-documenting, and programmatically decodable. Solidity: define custom errors in both IOnChainProposer interfaces, replace ~60 require/revert string patterns with if/revert custom error patterns. Rust: add data field to RpcRequestError::RPCError, propagate revert data in all RPC error construction sites, define selector constants, and update l1_proof_sender and l1_proof_verifier to match on selectors. Also fixes the stale "00m" code match in l1_proof_verifier. --- .../l2/contracts/src/l1/OnChainProposer.sol | 204 ++++++------------ .../src/l1/based/OnChainProposer.sol | 190 +++++----------- .../l1/based/interfaces/IOnChainProposer.sol | 45 ++++ .../src/l1/interfaces/IOnChainProposer.sol | 44 ++++ crates/l2/networking/rpc/clients.rs | 2 + crates/l2/sequencer/l1_proof_sender.rs | 17 +- crates/l2/sequencer/l1_proof_verifier.rs | 11 +- crates/l2/sequencer/utils.rs | 8 + crates/networking/rpc/clients/eth/errors.rs | 6 +- crates/networking/rpc/clients/eth/mod.rs | 7 + 10 files changed, 244 insertions(+), 290 deletions(-) diff --git a/crates/l2/contracts/src/l1/OnChainProposer.sol b/crates/l2/contracts/src/l1/OnChainProposer.sol index ecbf2e53094..2fba77376bd 100644 --- a/crates/l2/contracts/src/l1/OnChainProposer.sol +++ b/crates/l2/contracts/src/l1/OnChainProposer.sol @@ -146,50 +146,32 @@ contract OnChainProposer is REQUIRE_SP1_PROOF = requireSp1Proof; REQUIRE_TDX_PROOF = requireTdxProof; - require( - !REQUIRE_RISC0_PROOF || r0verifier != address(0), - "OnChainProposer: missing RISC0 verifier address" - ); + if (REQUIRE_RISC0_PROOF && r0verifier == address(0)) + revert MissingRisc0Verifier(); RISC0_VERIFIER_ADDRESS = r0verifier; // In Aligned mode, SP1 proofs are verified through the AlignedProofAggregator, // not through a direct SP1 verifier contract, so we don't require sp1verifier. - require( - !REQUIRE_SP1_PROOF || aligned || sp1verifier != address(0), - "OnChainProposer: missing SP1 verifier address" - ); + if (REQUIRE_SP1_PROOF && !aligned && sp1verifier == address(0)) + revert MissingSp1Verifier(); SP1_VERIFIER_ADDRESS = sp1verifier; - require( - !REQUIRE_TDX_PROOF || tdxverifier != address(0), - "OnChainProposer: missing TDX verifier address" - ); + if (REQUIRE_TDX_PROOF && tdxverifier == address(0)) + revert MissingTdxVerifier(); TDX_VERIFIER_ADDRESS = tdxverifier; ALIGNED_MODE = aligned; ALIGNEDPROOFAGGREGATOR = alignedProofAggregator; // Aligned mode requires SP1 proofs to be enabled - require( - !aligned || requireSp1Proof, - "OnChainProposer: Aligned mode requires SP1 proof" - ); + if (aligned && !requireSp1Proof) revert AlignedModeRequiresSp1(); // Aligned mode does not support RISC0 proofs (not yet available in aggregation mode) - require( - !aligned || !requireRisc0Proof, - "OnChainProposer: Aligned mode does not support RISC0 proof" - ); - - require( - commitHash != bytes32(0), - "OnChainProposer: commit hash is zero" - ); - require( - !REQUIRE_SP1_PROOF || sp1Vk != bytes32(0), - "OnChainProposer: missing SP1 verification key" - ); - require( - !REQUIRE_RISC0_PROOF || risc0Vk != bytes32(0), - "OnChainProposer: missing RISC0 verification key" - ); + if (aligned && requireRisc0Proof) + revert AlignedModeDoesNotSupportRisc0(); + + if (commitHash == bytes32(0)) revert CommitHashIsZero(); + if (REQUIRE_SP1_PROOF && sp1Vk == bytes32(0)) + revert MissingSp1VerificationKey(); + if (REQUIRE_RISC0_PROOF && risc0Vk == bytes32(0)) + revert MissingRisc0VerificationKey(); verificationKeys[commitHash][SP1_VERIFIER_ID] = sp1Vk; verificationKeys[commitHash][RISC0_VERIFIER_ID] = risc0Vk; @@ -209,14 +191,8 @@ contract OnChainProposer is CHAIN_ID = chainId; - require( - bridge != address(0), - "001" // OnChainProposer: bridge is the zero address - ); - require( - bridge != address(this), - "000" // OnChainProposer: bridge is the contract address - ); + if (bridge == address(0)) revert BridgeIsZeroAddress(); + if (bridge == address(this)) revert BridgeIsContractAddress(); BRIDGE = bridge; OwnableUpgradeable.__Ownable_init(timelock_owner); @@ -227,10 +203,7 @@ contract OnChainProposer is bytes32 commit_hash, bytes32 new_vk ) public onlyOwner { - require( - commit_hash != bytes32(0), - "OnChainProposer: commit hash is zero" - ); + if (commit_hash == bytes32(0)) revert CommitHashIsZero(); // we don't want to restrict setting the vk to zero // as we may want to disable the version verificationKeys[commit_hash][SP1_VERIFIER_ID] = new_vk; @@ -242,10 +215,7 @@ contract OnChainProposer is bytes32 commit_hash, bytes32 new_vk ) public onlyOwner { - require( - commit_hash != bytes32(0), - "OnChainProposer: commit hash is zero" - ); + if (commit_hash == bytes32(0)) revert CommitHashIsZero(); // we don't want to restrict setting the vk to zero // as we may want to disable the version verificationKeys[commit_hash][RISC0_VERIFIER_ID] = new_vk; @@ -264,30 +234,21 @@ contract OnChainProposer is ICommonBridge.BalanceDiff[] calldata balanceDiffs, ICommonBridge.L2MessageRollingHash[] calldata l2MessageRollingHashes ) external override onlyOwner whenNotPaused { - // TODO: Refactor validation - require( - batchNumber == lastCommittedBatch + 1, - "002" // OnChainProposer: batchNumber is not the immediate successor of lastCommittedBatch - ); - require( - batchCommitments[batchNumber].newStateRoot == bytes32(0), - "003" // OnChainProposer: tried to commit an already committed batch - ); - require( - lastBlockHash != bytes32(0), - "004" // OnChainProposer: lastBlockHash cannot be zero - ); + if (batchNumber != lastCommittedBatch + 1) + revert BatchNumberNotSuccessor(); + if (batchCommitments[batchNumber].newStateRoot != bytes32(0)) + revert BatchAlreadyCommitted(); + if (lastBlockHash == bytes32(0)) revert LastBlockHashIsZero(); if (processedPrivilegedTransactionsRollingHash != bytes32(0)) { bytes32 claimedProcessedTransactions = ICommonBridge(BRIDGE) .getPendingTransactionsVersionedHash( uint16(bytes2(processedPrivilegedTransactionsRollingHash)) ); - require( - claimedProcessedTransactions == - processedPrivilegedTransactionsRollingHash, - "005" // OnChainProposer: invalid privileged transaction logs - ); + if ( + claimedProcessedTransactions != + processedPrivilegedTransactionsRollingHash + ) revert InvalidPrivilegedTransactionLogs(); } for (uint256 i = 0; i < l2MessageRollingHashes.length; i++) { @@ -297,10 +258,8 @@ contract OnChainProposer is l2MessageRollingHashes[i].chainId, uint16(bytes2(receivedRollingHash)) ); - require( - expectedRollingHash == receivedRollingHash, - "012" // OnChainProposer: invalid L2 message rolling hash - ); + if (expectedRollingHash != receivedRollingHash) + revert InvalidL2MessageRollingHash(); } if (withdrawalsLogsMerkleRoot != bytes32(0)) { @@ -313,29 +272,23 @@ contract OnChainProposer is // Blob is published in the (EIP-4844) transaction that calls this function. bytes32 blobVersionedHash = blobhash(0); if (VALIDIUM) { - require( - blobVersionedHash == 0, - "006" // L2 running as validium but blob was published - ); + if (blobVersionedHash != 0) revert ValidiumBlobPublished(); } else { - require( - blobVersionedHash != 0, - "007" // L2 running as rollup but blob was not published - ); + if (blobVersionedHash == 0) revert RollupBlobNotPublished(); } // Validate commit hash and corresponding verification keys are valid - require(commitHash != bytes32(0), "012"); + if (commitHash == bytes32(0)) revert CommitHashIsZero(); if ( REQUIRE_SP1_PROOF && verificationKeys[commitHash][SP1_VERIFIER_ID] == bytes32(0) ) { - revert("013"); // missing verification key for commit hash + revert MissingVerificationKeyForCommit(); } else if ( REQUIRE_RISC0_PROOF && verificationKeys[commitHash][RISC0_VERIFIER_ID] == bytes32(0) ) { - revert("013"); // missing verification key for commit hash + revert MissingVerificationKeyForCommit(); } batchCommitments[batchNumber] = BatchCommitmentInfo( @@ -369,19 +322,12 @@ contract OnChainProposer is //tdx bytes memory tdxSignature ) external override onlyOwner whenNotPaused { - require( - !ALIGNED_MODE, - "008" // Batch verification should be done via Aligned Layer. Call verifyBatchesAligned() instead. - ); + if (ALIGNED_MODE) revert UseAlignedVerification(); - require( - batchNumber == lastVerifiedBatch + 1, - "009" // OnChainProposer: batch already verified - ); - require( - batchCommitments[batchNumber].newStateRoot != bytes32(0), - "00a" // OnChainProposer: cannot verify an uncommitted batch - ); + if (batchNumber != lastVerifiedBatch + 1) + revert BatchAlreadyVerified(); + if (batchCommitments[batchNumber].newStateRoot == bytes32(0)) + revert BatchNotCommitted(); // The first 2 bytes are the number of privileged transactions. uint16 privileged_transaction_count = uint16( @@ -413,7 +359,7 @@ contract OnChainProposer is ICommonBridge(BRIDGE).hasExpiredPrivilegedTransactions() && batchCommitments[batchNumber].nonPrivilegedTransactions != 0 ) { - revert("00v"); // exceeded privileged transaction inclusion deadline, can't include non-privileged transactions + revert ExpiredPrivilegedTransactionDeadline(); } // Reconstruct public inputs from commitments @@ -432,9 +378,7 @@ contract OnChainProposer is sha256(publicInputs) ) {} catch { - revert( - "00c" // OnChainProposer: Invalid RISC0 proof failed proof verification - ); + revert InvalidRisc0Proof(); } } @@ -448,9 +392,7 @@ contract OnChainProposer is sp1ProofBytes ) {} catch { - revert( - "00e" // OnChainProposer: Invalid SP1 proof failed proof verification - ); + revert InvalidSp1Proof(); } } @@ -461,9 +403,7 @@ contract OnChainProposer is tdxSignature ) {} catch { - revert( - "00g" // OnChainProposer: Invalid TDX proof failed proof verification - ); + revert InvalidTdxProof(); } } @@ -486,41 +426,28 @@ contract OnChainProposer is bytes32[][] calldata sp1MerkleProofsList, bytes32[][] calldata risc0MerkleProofsList ) external override onlyOwner whenNotPaused { - require( - ALIGNED_MODE, - "00h" // Batch verification should be done via smart contract verifiers. Call verifyBatch() instead. - ); - require( - firstBatchNumber == lastVerifiedBatch + 1, - "00i" // OnChainProposer: incorrect first batch number - ); - require( - lastBatchNumber <= lastCommittedBatch, - "014" // OnChainProposer: last batch number exceeds last committed batch" - ); + if (!ALIGNED_MODE) revert UseSmartContractVerification(); + if (firstBatchNumber != lastVerifiedBatch + 1) + revert IncorrectFirstBatchNumber(); + if (lastBatchNumber > lastCommittedBatch) + revert LastBatchExceedsCommitted(); uint256 batchesToVerify = (lastBatchNumber - firstBatchNumber) + 1; if (REQUIRE_SP1_PROOF) { - require( - batchesToVerify == sp1MerkleProofsList.length, - "00j" // OnChainProposer: SP1 input/proof array length mismatch - ); + if (batchesToVerify != sp1MerkleProofsList.length) + revert Sp1ProofArrayLengthMismatch(); } if (REQUIRE_RISC0_PROOF) { - require( - batchesToVerify == risc0MerkleProofsList.length, - "00k" // OnChainProposer: Risc0 input/proof array length mismatch - ); + if (batchesToVerify != risc0MerkleProofsList.length) + revert Risc0ProofArrayLengthMismatch(); } uint256 batchNumber = firstBatchNumber; for (uint256 i = 0; i < batchesToVerify; i++) { - require( - batchCommitments[batchNumber].newStateRoot != bytes32(0), - "00l" // OnChainProposer: cannot verify an uncommitted batch - ); + if (batchCommitments[batchNumber].newStateRoot == bytes32(0)) + revert BatchNotCommitted(); // The first 2 bytes are the number of transactions. uint16 privileged_transaction_count = uint16( @@ -607,15 +534,9 @@ contract OnChainProposer is ); (bool callResult, bytes memory response) = ALIGNEDPROOFAGGREGATOR .staticcall(callData); - require( - callResult, - "00y" // OnChainProposer: call to ALIGNEDPROOFAGGREGATOR failed - ); + if (!callResult) revert AlignedAggregatorCallFailed(); bool proofVerified = abi.decode(response, (bool)); - require( - proofVerified, - "00z" // OnChainProposer: Aligned proof verification failed - ); + if (!proofVerified) revert AlignedProofVerificationFailed(); } /// @notice Constructs public inputs from committed batch data for proof verification. @@ -713,14 +634,9 @@ contract OnChainProposer is function revertBatch( uint256 batchNumber ) external override onlyOwner whenPaused { - require( - batchNumber > lastVerifiedBatch, - "010" // OnChainProposer: can't revert verified batch - ); - require( - batchNumber <= lastCommittedBatch, - "011" // OnChainProposer: no batches are being reverted - ); + if (batchNumber <= lastVerifiedBatch) + revert CannotRevertVerifiedBatch(); + if (batchNumber > lastCommittedBatch) revert NoBatchesToRevert(); // Remove batch commitments from batchNumber to lastCommittedBatch for (uint256 i = batchNumber; i <= lastCommittedBatch; i++) { diff --git a/crates/l2/contracts/src/l1/based/OnChainProposer.sol b/crates/l2/contracts/src/l1/based/OnChainProposer.sol index 2610efff3da..7016735bb51 100644 --- a/crates/l2/contracts/src/l1/based/OnChainProposer.sol +++ b/crates/l2/contracts/src/l1/based/OnChainProposer.sol @@ -103,11 +103,10 @@ contract OnChainProposer is public verificationKeys; modifier onlyLeaderSequencer() { - require( - msg.sender == - ISequencerRegistry(SEQUENCER_REGISTRY).leaderSequencer(), - "OnChainProposer: caller has no sequencing rights" - ); + if ( + msg.sender != + ISequencerRegistry(SEQUENCER_REGISTRY).leaderSequencer() + ) revert CallerHasNoSequencingRights(); _; } @@ -156,18 +155,11 @@ contract OnChainProposer is ALIGNED_MODE = aligned; ALIGNEDPROOFAGGREGATOR = alignedProofAggregator; - require( - commitHash != bytes32(0), - "OnChainProposer: commit hash is zero" - ); - require( - !REQUIRE_SP1_PROOF || sp1Vk != bytes32(0), - "OnChainProposer: missing SP1 verification key" - ); - require( - !REQUIRE_RISC0_PROOF || risc0Vk != bytes32(0), - "OnChainProposer: missing RISC0 verification key" - ); + if (commitHash == bytes32(0)) revert CommitHashIsZero(); + if (REQUIRE_SP1_PROOF && sp1Vk == bytes32(0)) + revert MissingSp1VerificationKey(); + if (REQUIRE_RISC0_PROOF && risc0Vk == bytes32(0)) + revert MissingRisc0VerificationKey(); verificationKeys[commitHash][SP1_VERIFIER_ID] = sp1Vk; verificationKeys[commitHash][RISC0_VERIFIER_ID] = risc0Vk; @@ -182,32 +174,19 @@ contract OnChainProposer is ); // Set the SequencerRegistry address - require( - SEQUENCER_REGISTRY == address(0), - "OnChainProposer: contract already initialized" - ); - require( - sequencer_registry != address(0), - "OnChainProposer: sequencer_registry is the zero address" - ); - require( - sequencer_registry != address(this), - "OnChainProposer: sequencer_registry is the contract address" - ); + if (SEQUENCER_REGISTRY != address(0)) revert AlreadyInitialized(); + if (sequencer_registry == address(0)) + revert SequencerRegistryIsZeroAddress(); + if (sequencer_registry == address(this)) + revert SequencerRegistryIsContractAddress(); SEQUENCER_REGISTRY = sequencer_registry; CHAIN_ID = chainId; OwnableUpgradeable.__Ownable_init(owner); - require( - bridge != address(0), - "OnChainProposer: bridge is the zero address" - ); - require( - bridge != address(this), - "OnChainProposer: bridge is the contract address" - ); + if (bridge == address(0)) revert BridgeIsZeroAddress(); + if (bridge == address(this)) revert BridgeIsContractAddress(); BRIDGE = bridge; emit VerificationKeyUpgraded("SP1", commitHash, sp1Vk); @@ -219,10 +198,7 @@ contract OnChainProposer is bytes32 commit_hash, bytes32 new_vk ) public onlyOwner { - require( - commit_hash != bytes32(0), - "OnChainProposer: commit hash is zero" - ); + if (commit_hash == bytes32(0)) revert CommitHashIsZero(); // we don't want to restrict setting the vk to zero // as we may want to disable the version verificationKeys[commit_hash][SP1_VERIFIER_ID] = new_vk; @@ -234,10 +210,7 @@ contract OnChainProposer is bytes32 commit_hash, bytes32 new_vk ) public onlyOwner { - require( - commit_hash != bytes32(0), - "OnChainProposer: commit hash is zero" - ); + if (commit_hash == bytes32(0)) revert CommitHashIsZero(); // we don't want to restrict setting the vk to zero // as we may want to disable the version verificationKeys[commit_hash][RISC0_VERIFIER_ID] = new_vk; @@ -255,32 +228,21 @@ contract OnChainProposer is bytes32 commitHash, bytes[] calldata //rlpEncodedBlocks ) external override onlyLeaderSequencer { - // TODO: Refactor validation - require( - batchNumber == lastCommittedBatch + 1, - "OnChainProposer: batchNumber is not the immediate successor of lastCommittedBatch" - ); - require( - batchCommitments[batchNumber].newStateRoot == bytes32(0), - "OnChainProposer: tried to commit an already committed batch" - ); - require( - lastBlockHash != bytes32(0), - "OnChainProposer: lastBlockHash cannot be zero" - ); - - // Check if commitment is equivalent to blob's KZG commitment. + if (batchNumber != lastCommittedBatch + 1) + revert BatchNumberNotSuccessor(); + if (batchCommitments[batchNumber].newStateRoot != bytes32(0)) + revert BatchAlreadyCommitted(); + if (lastBlockHash == bytes32(0)) revert LastBlockHashIsZero(); if (processedPrivilegedTransactionsRollingHash != bytes32(0)) { bytes32 claimedProcessedTransactions = ICommonBridge(BRIDGE) .getPendingTransactionsVersionedHash( uint16(bytes2(processedPrivilegedTransactionsRollingHash)) ); - require( - claimedProcessedTransactions == - processedPrivilegedTransactionsRollingHash, - "OnChainProposer: invalid privileged transactions log" - ); + if ( + claimedProcessedTransactions != + processedPrivilegedTransactionsRollingHash + ) revert InvalidPrivilegedTransactionLogs(); } if (withdrawalsLogsMerkleRoot != bytes32(0)) { ICommonBridge(BRIDGE).publishWithdrawals( @@ -290,28 +252,20 @@ contract OnChainProposer is } // Validate commit hash and corresponding verification keys are valid - require(commitHash != bytes32(0), "012"); - require( - (!REQUIRE_SP1_PROOF || - verificationKeys[commitHash][SP1_VERIFIER_ID] != bytes32(0)) && - (!REQUIRE_RISC0_PROOF || - verificationKeys[commitHash][RISC0_VERIFIER_ID] != - bytes32(0)), - "013" // missing verification key for commit hash - ); + if (commitHash == bytes32(0)) revert CommitHashIsZero(); + if ( + (REQUIRE_SP1_PROOF && + verificationKeys[commitHash][SP1_VERIFIER_ID] == bytes32(0)) || + (REQUIRE_RISC0_PROOF && + verificationKeys[commitHash][RISC0_VERIFIER_ID] == bytes32(0)) + ) revert MissingVerificationKeyForCommit(); // Blob is published in the (EIP-4844) transaction that calls this function. bytes32 blobVersionedHash = blobhash(0); if (VALIDIUM) { - require( - blobVersionedHash == 0, - "L2 running as validium but blob was published" - ); + if (blobVersionedHash != 0) revert ValidiumBlobPublished(); } else { - require( - blobVersionedHash != 0, - "L2 running as rollup but blob was not published" - ); + if (blobVersionedHash == 0) revert RollupBlobNotPublished(); } batchCommitments[batchNumber] = BatchCommitmentInfo( @@ -347,15 +301,10 @@ contract OnChainProposer is //tdx bytes memory tdxSignature ) external { - require( - !ALIGNED_MODE, - "Batch verification should be done via Aligned Layer. Call verifyBatchesAligned() instead." - ); + if (ALIGNED_MODE) revert UseAlignedVerification(); - require( - batchCommitments[batchNumber].newStateRoot != bytes32(0), - "OnChainProposer: cannot verify an uncommitted batch" - ); + if (batchCommitments[batchNumber].newStateRoot == bytes32(0)) + revert BatchNotCommitted(); // The first 2 bytes are the number of privileged transactions. uint16 privileged_transaction_count = uint16( @@ -374,9 +323,7 @@ contract OnChainProposer is ICommonBridge(BRIDGE).hasExpiredPrivilegedTransactions() && batchCommitments[batchNumber].nonPrivilegedTransactions != 0 ) { - revert( - "exceeded privileged transaction inclusion deadline, can't include non-privileged transactions" - ); + revert ExpiredPrivilegedTransactionDeadline(); } // Reconstruct public inputs from commitments @@ -394,9 +341,7 @@ contract OnChainProposer is sha256(publicInputs) ) {} catch { - revert( - "OnChainProposer: Invalid RISC0 proof failed proof verification" - ); + revert InvalidRisc0Proof(); } } @@ -410,9 +355,7 @@ contract OnChainProposer is sp1ProofBytes ) {} catch { - revert( - "OnChainProposer: Invalid SP1 proof failed proof verification" - ); + revert InvalidSp1Proof(); } } @@ -423,9 +366,7 @@ contract OnChainProposer is tdxSignature ) {} catch { - revert( - "OnChainProposer: Invalid TDX proof failed proof verification" - ); + revert InvalidTdxProof(); } } @@ -444,41 +385,28 @@ contract OnChainProposer is bytes32[][] calldata sp1MerkleProofsList, bytes32[][] calldata risc0MerkleProofsList ) external override { - require( - ALIGNED_MODE, - "Batch verification should be done via smart contract verifiers. Call verifyBatch() instead." - ); - require( - firstBatchNumber == lastVerifiedBatch + 1, - "OnChainProposer: incorrect first batch number" - ); - require( - lastBatchNumber <= lastCommittedBatch, - "OnChainProposer: last batch number exceeds last committed batch" - ); + if (!ALIGNED_MODE) revert UseSmartContractVerification(); + if (firstBatchNumber != lastVerifiedBatch + 1) + revert IncorrectFirstBatchNumber(); + if (lastBatchNumber > lastCommittedBatch) + revert LastBatchExceedsCommitted(); uint256 batchesToVerify = (lastBatchNumber - firstBatchNumber) + 1; if (REQUIRE_SP1_PROOF) { - require( - batchesToVerify == sp1MerkleProofsList.length, - "OnChainProposer: SP1 input/proof array length mismatch" - ); + if (batchesToVerify != sp1MerkleProofsList.length) + revert Sp1ProofArrayLengthMismatch(); } if (REQUIRE_RISC0_PROOF) { - require( - batchesToVerify == risc0MerkleProofsList.length, - "OnChainProposer: Risc0 input/proof array length mismatch" - ); + if (batchesToVerify != risc0MerkleProofsList.length) + revert Risc0ProofArrayLengthMismatch(); } uint256 batchNumber = firstBatchNumber; for (uint256 i = 0; i < batchesToVerify; i++) { - require( - batchCommitments[batchNumber].newStateRoot != bytes32(0), - "OnChainProposer: cannot verify an uncommitted batch" - ); + if (batchCommitments[batchNumber].newStateRoot == bytes32(0)) + revert BatchNotCommitted(); // The first 2 bytes are the number of privileged transactions. uint16 privileged_transaction_count = uint16( @@ -572,15 +500,9 @@ contract OnChainProposer is ); (bool callResult, bytes memory response) = ALIGNEDPROOFAGGREGATOR .staticcall(callData); - require( - callResult, - "OnChainProposer: call to ALIGNEDPROOFAGGREGATOR failed" - ); + if (!callResult) revert AlignedAggregatorCallFailed(); bool proofVerified = abi.decode(response, (bool)); - require( - proofVerified, - "OnChainProposer: Aligned proof verification failed" - ); + if (!proofVerified) revert AlignedProofVerificationFailed(); } /// @notice Allow owner to upgrade the contract. diff --git a/crates/l2/contracts/src/l1/based/interfaces/IOnChainProposer.sol b/crates/l2/contracts/src/l1/based/interfaces/IOnChainProposer.sol index 3b3964e405b..54e85e968c7 100644 --- a/crates/l2/contracts/src/l1/based/interfaces/IOnChainProposer.sol +++ b/crates/l2/contracts/src/l1/based/interfaces/IOnChainProposer.sol @@ -6,6 +6,51 @@ pragma solidity =0.8.31; /// @notice A OnChainProposer contract ensures the advancement of the L2. It is used /// by the proposer to commit batches of l2 blocks and verify proofs. interface IOnChainProposer { + // Initialization errors + error MissingRisc0Verifier(); + error MissingSp1Verifier(); + error MissingTdxVerifier(); + error AlignedModeRequiresSp1(); + error AlignedModeDoesNotSupportRisc0(); + error CommitHashIsZero(); + error MissingSp1VerificationKey(); + error MissingRisc0VerificationKey(); + error BridgeIsZeroAddress(); + error BridgeIsContractAddress(); + error AlreadyInitialized(); + error SequencerRegistryIsZeroAddress(); + error SequencerRegistryIsContractAddress(); + + // Commit errors + error BatchNumberNotSuccessor(); + error BatchAlreadyCommitted(); + error LastBlockHashIsZero(); + error InvalidPrivilegedTransactionLogs(); + error InvalidL2MessageRollingHash(); + error ValidiumBlobPublished(); + error RollupBlobNotPublished(); + error MissingVerificationKeyForCommit(); + + // Verify errors + error UseAlignedVerification(); + error UseSmartContractVerification(); + error BatchNotCommitted(); + error ExpiredPrivilegedTransactionDeadline(); + error InvalidRisc0Proof(); + error InvalidSp1Proof(); + error InvalidTdxProof(); + + // Aligned verify errors + error IncorrectFirstBatchNumber(); + error LastBatchExceedsCommitted(); + error Sp1ProofArrayLengthMismatch(); + error Risc0ProofArrayLengthMismatch(); + error AlignedAggregatorCallFailed(); + error AlignedProofVerificationFailed(); + + // Access control errors + error CallerHasNoSequencingRights(); + /// @notice The latest committed batch number. /// @return The latest committed batch number as a uint256. function lastCommittedBatch() external view returns (uint256); diff --git a/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol b/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol index 2f59b39ee77..7e47a010110 100644 --- a/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol +++ b/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol @@ -8,6 +8,50 @@ import {ICommonBridge} from "./ICommonBridge.sol"; /// @notice A OnChainProposer contract ensures the advancement of the L2. It is used /// by the proposer to commit batches of l2 blocks and verify proofs. interface IOnChainProposer { + // Initialization errors + error MissingRisc0Verifier(); + error MissingSp1Verifier(); + error MissingTdxVerifier(); + error AlignedModeRequiresSp1(); + error AlignedModeDoesNotSupportRisc0(); + error CommitHashIsZero(); + error MissingSp1VerificationKey(); + error MissingRisc0VerificationKey(); + error BridgeIsZeroAddress(); + error BridgeIsContractAddress(); + + // Commit errors + error BatchNumberNotSuccessor(); + error BatchAlreadyCommitted(); + error LastBlockHashIsZero(); + error InvalidPrivilegedTransactionLogs(); + error InvalidL2MessageRollingHash(); + error ValidiumBlobPublished(); + error RollupBlobNotPublished(); + error MissingVerificationKeyForCommit(); + + // Verify errors + error UseAlignedVerification(); + error UseSmartContractVerification(); + error BatchAlreadyVerified(); + error BatchNotCommitted(); + error ExpiredPrivilegedTransactionDeadline(); + error InvalidRisc0Proof(); + error InvalidSp1Proof(); + error InvalidTdxProof(); + + // Aligned verify errors + error IncorrectFirstBatchNumber(); + error LastBatchExceedsCommitted(); + error Sp1ProofArrayLengthMismatch(); + error Risc0ProofArrayLengthMismatch(); + error AlignedAggregatorCallFailed(); + error AlignedProofVerificationFailed(); + + // Revert errors + error CannotRevertVerifiedBatch(); + error NoBatchesToRevert(); + /// @notice The latest committed batch number. /// @return The latest committed batch number as a uint256. function lastCommittedBatch() external view returns (uint256); diff --git a/crates/l2/networking/rpc/clients.rs b/crates/l2/networking/rpc/clients.rs index d76babbf3c2..35c53d0101a 100644 --- a/crates/l2/networking/rpc/clients.rs +++ b/crates/l2/networking/rpc/clients.rs @@ -67,6 +67,7 @@ pub async fn get_batch_number(client: &EthClient) -> Result RpcResponse::Error(error_response) => Err(RpcRequestError::RPCError { method: "ethrex_batchNumber".to_string(), message: error_response.error.message, + data: error_response.error.data, } .into()), } @@ -151,6 +152,7 @@ pub async fn send_ethrex_transaction( RpcResponse::Error(error_response) => Err(RpcRequestError::RPCError { method: "ethrex_sendTransaction".to_string(), message: error_response.error.message, + data: error_response.error.data, } .into()), } diff --git a/crates/l2/sequencer/l1_proof_sender.rs b/crates/l2/sequencer/l1_proof_sender.rs index 74484ff63c2..ec13eb8bd4c 100644 --- a/crates/l2/sequencer/l1_proof_sender.rs +++ b/crates/l2/sequencer/l1_proof_sender.rs @@ -32,7 +32,10 @@ use tracing::{error, info, warn}; use super::{ configs::AlignedConfig, - utils::{random_duration, send_verify_tx}, + utils::{ + INVALID_RISC0_PROOF_SELECTOR, INVALID_SP1_PROOF_SELECTOR, INVALID_TDX_PROOF_SELECTOR, + random_duration, send_verify_tx, + }, }; use crate::{ @@ -435,20 +438,22 @@ impl L1ProofSender { let send_verify_tx_result = send_verify_tx(calldata, &self.eth_client, target_address, &self.signer).await; - if let Err(EthClientError::RpcRequestError(RpcRequestError::RPCError { message, .. })) = - send_verify_tx_result.as_ref() + if let Err(EthClientError::RpcRequestError(RpcRequestError::RPCError { + data: Some(data), + .. + })) = send_verify_tx_result.as_ref() { - if message.contains("Invalid TDX proof") { + if data.starts_with(INVALID_TDX_PROOF_SELECTOR) { warn!("Deleting invalid TDX proof"); self.rollup_store .delete_proof_by_batch_and_type(batch_number, ProverType::TDX) .await?; - } else if message.contains("Invalid RISC0 proof") { + } else if data.starts_with(INVALID_RISC0_PROOF_SELECTOR) { warn!("Deleting invalid RISC0 proof"); self.rollup_store .delete_proof_by_batch_and_type(batch_number, ProverType::RISC0) .await?; - } else if message.contains("Invalid SP1 proof") { + } else if data.starts_with(INVALID_SP1_PROOF_SELECTOR) { warn!("Deleting invalid SP1 proof"); self.rollup_store .delete_proof_by_batch_and_type(batch_number, ProverType::SP1) diff --git a/crates/l2/sequencer/l1_proof_verifier.rs b/crates/l2/sequencer/l1_proof_verifier.rs index 31c612bdfc5..7fad86a17a1 100644 --- a/crates/l2/sequencer/l1_proof_verifier.rs +++ b/crates/l2/sequencer/l1_proof_verifier.rs @@ -33,7 +33,7 @@ use crate::{ use super::{ configs::AlignedConfig, errors::SequencerError, - utils::{send_verify_tx, sleep_random}, + utils::{ALIGNED_PROOF_VERIFICATION_FAILED_SELECTOR, send_verify_tx, sleep_random}, }; const ALIGNED_VERIFY_FUNCTION_SIGNATURE: &str = @@ -251,10 +251,11 @@ impl L1ProofVerifier { let send_verify_tx_result = send_verify_tx(calldata, &self.eth_client, target_address, &self.l1_signer).await; - if let Err(EthClientError::RpcRequestError(RpcRequestError::RPCError { message, .. })) = - send_verify_tx_result.as_ref() - && message.contains("00m") - // Invalid Aligned proof + if let Err(EthClientError::RpcRequestError(RpcRequestError::RPCError { + data: Some(data), + .. + })) = send_verify_tx_result.as_ref() + && data.starts_with(ALIGNED_PROOF_VERIFICATION_FAILED_SELECTOR) { warn!("Deleting invalid ALIGNED proof"); for batch_number in first_batch_number..=last_batch_number { diff --git a/crates/l2/sequencer/utils.rs b/crates/l2/sequencer/utils.rs index ecbf7830272..b794031f6e5 100644 --- a/crates/l2/sequencer/utils.rs +++ b/crates/l2/sequencer/utils.rs @@ -22,6 +22,14 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tokio::time::sleep; use tracing::{info, warn}; +/// 4-byte ABI selectors for OnChainProposer custom errors. +/// These are the first 4 bytes of keccak256 of the error signature. +/// Used to detect specific revert reasons from the `data` field of RPC error responses. +pub const INVALID_RISC0_PROOF_SELECTOR: &str = "0x14add973"; +pub const INVALID_SP1_PROOF_SELECTOR: &str = "0x7ff849b5"; +pub const INVALID_TDX_PROOF_SELECTOR: &str = "0x62013a95"; +pub const ALIGNED_PROOF_VERIFICATION_FAILED_SELECTOR: &str = "0x44602025"; + pub async fn sleep_random(sleep_amount: u64) { sleep(random_duration(sleep_amount)).await; } diff --git a/crates/networking/rpc/clients/eth/errors.rs b/crates/networking/rpc/clients/eth/errors.rs index a929069c06e..12f31527dbc 100644 --- a/crates/networking/rpc/clients/eth/errors.rs +++ b/crates/networking/rpc/clients/eth/errors.rs @@ -10,7 +10,11 @@ pub enum RpcRequestError { source: serde_json::Error, }, #[error("{method}: {message}")] - RPCError { method: String, message: String }, + RPCError { + method: String, + message: String, + data: Option, + }, #[error("{method}: {source}")] ParseIntError { method: String, diff --git a/crates/networking/rpc/clients/eth/mod.rs b/crates/networking/rpc/clients/eth/mod.rs index 4deac8bf22c..2853315ccfd 100644 --- a/crates/networking/rpc/clients/eth/mod.rs +++ b/crates/networking/rpc/clients/eth/mod.rs @@ -208,6 +208,7 @@ impl EthClient { RpcResponse::Error(error_response) => Err(RpcRequestError::RPCError { method, message: error_response.error.message, + data: error_response.error.data, } .into()), } @@ -226,6 +227,7 @@ impl EthClient { RpcResponse::Error(error_response) => Err(RpcRequestError::RPCError { method, message: error_response.error.message, + data: error_response.error.data, } .into()), } @@ -315,6 +317,7 @@ impl EthClient { RpcResponse::Error(error_response) => Err(RpcRequestError::RPCError { method: "eth_estimateGas".to_string(), message: error_response.error.message, + data: error_response.error.data, } .into()), } @@ -407,6 +410,7 @@ impl EthClient { RpcResponse::Error(error_response) => Err(RpcRequestError::RPCError { method: "eth_getTransactionCount".to_string(), message: error_response.error.message, + data: error_response.error.data, } .into()), } @@ -453,6 +457,7 @@ impl EthClient { RpcResponse::Error(error_response) => Err(RpcRequestError::RPCError { method: "debug_getRawBlock".to_string(), message: error_response.error.message, + data: error_response.error.data, }), }; @@ -563,6 +568,7 @@ impl EthClient { RpcResponse::Error(error_response) => Err(RpcRequestError::RPCError { method: "eth_getCode".to_string(), message: error_response.error.message, + data: error_response.error.data, } .into()), } @@ -620,6 +626,7 @@ impl EthClient { RpcResponse::Error(error_response) => Err(RpcRequestError::RPCError { method: "eth_blobBaseFee".to_string(), message: error_response.error.message, + data: error_response.error.data, } .into()), } From c41ebf735f24eb7f76980b825a965c654eafa313 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Mon, 23 Feb 2026 15:57:56 -0300 Subject: [PATCH 2/8] Rename BatchAlreadyVerified to BatchNotSequential since the condition checks that batchNumber == lastVerifiedBatch + 1 (sequential ordering), not that a specific batch was already verified. --- crates/l2/contracts/src/l1/OnChainProposer.sol | 2 +- crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/l2/contracts/src/l1/OnChainProposer.sol b/crates/l2/contracts/src/l1/OnChainProposer.sol index 2fba77376bd..4e0cd31a5bc 100644 --- a/crates/l2/contracts/src/l1/OnChainProposer.sol +++ b/crates/l2/contracts/src/l1/OnChainProposer.sol @@ -325,7 +325,7 @@ contract OnChainProposer is if (ALIGNED_MODE) revert UseAlignedVerification(); if (batchNumber != lastVerifiedBatch + 1) - revert BatchAlreadyVerified(); + revert BatchNotSequential(); if (batchCommitments[batchNumber].newStateRoot == bytes32(0)) revert BatchNotCommitted(); diff --git a/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol b/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol index 7e47a010110..dc2870330ef 100644 --- a/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol +++ b/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol @@ -33,7 +33,7 @@ interface IOnChainProposer { // Verify errors error UseAlignedVerification(); error UseSmartContractVerification(); - error BatchAlreadyVerified(); + error BatchNotSequential(); error BatchNotCommitted(); error ExpiredPrivilegedTransactionDeadline(); error InvalidRisc0Proof(); From efaa4d5db4031b96ebbc6095b0db8b2dca4823b9 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Mon, 23 Feb 2026 17:13:23 -0300 Subject: [PATCH 3/8] Add test verifying that the hardcoded 4-byte ABI error selectors match the keccak256 hashes of their Solidity error signatures, preventing silent breakage if the error names ever change. --- crates/l2/sequencer/utils.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/crates/l2/sequencer/utils.rs b/crates/l2/sequencer/utils.rs index b794031f6e5..3dd0b09148d 100644 --- a/crates/l2/sequencer/utils.rs +++ b/crates/l2/sequencer/utils.rs @@ -30,6 +30,13 @@ pub const INVALID_SP1_PROOF_SELECTOR: &str = "0x7ff849b5"; pub const INVALID_TDX_PROOF_SELECTOR: &str = "0x62013a95"; pub const ALIGNED_PROOF_VERIFICATION_FAILED_SELECTOR: &str = "0x44602025"; +/// Computes the 4-byte ABI error selector for a Solidity custom error signature, +/// e.g. `"InvalidRisc0Proof()"` → `"0x14add973"`. +fn error_selector(signature: &str) -> String { + let hash = keccak(signature.as_bytes()); + format!("0x{}", hex::encode(&hash[..4])) +} + pub async fn sleep_random(sleep_amount: u64) { sleep(random_duration(sleep_amount)).await; } @@ -204,3 +211,28 @@ pub fn get_git_commit_hash() -> String { pub fn batch_checkpoint_name(batch_number: u64) -> String { format!("checkpoint_batch_{batch_number}") } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn error_selectors_match_solidity_signatures() { + assert_eq!( + error_selector("InvalidRisc0Proof()"), + INVALID_RISC0_PROOF_SELECTOR + ); + assert_eq!( + error_selector("InvalidSp1Proof()"), + INVALID_SP1_PROOF_SELECTOR + ); + assert_eq!( + error_selector("InvalidTdxProof()"), + INVALID_TDX_PROOF_SELECTOR + ); + assert_eq!( + error_selector("AlignedProofVerificationFailed()"), + ALIGNED_PROOF_VERIFICATION_FAILED_SELECTOR + ); + } +} From 04235f72f96a527c5a8efd440bd1a5e028357585 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Mon, 23 Feb 2026 17:43:16 -0300 Subject: [PATCH 4/8] Move error_selector helper inside the test module to fix the dead_code warning that caused Lint L2 CI to fail. --- crates/l2/sequencer/utils.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/l2/sequencer/utils.rs b/crates/l2/sequencer/utils.rs index 3dd0b09148d..907b85fff2f 100644 --- a/crates/l2/sequencer/utils.rs +++ b/crates/l2/sequencer/utils.rs @@ -30,13 +30,6 @@ pub const INVALID_SP1_PROOF_SELECTOR: &str = "0x7ff849b5"; pub const INVALID_TDX_PROOF_SELECTOR: &str = "0x62013a95"; pub const ALIGNED_PROOF_VERIFICATION_FAILED_SELECTOR: &str = "0x44602025"; -/// Computes the 4-byte ABI error selector for a Solidity custom error signature, -/// e.g. `"InvalidRisc0Proof()"` → `"0x14add973"`. -fn error_selector(signature: &str) -> String { - let hash = keccak(signature.as_bytes()); - format!("0x{}", hex::encode(&hash[..4])) -} - pub async fn sleep_random(sleep_amount: u64) { sleep(random_duration(sleep_amount)).await; } @@ -216,6 +209,13 @@ pub fn batch_checkpoint_name(batch_number: u64) -> String { mod tests { use super::*; + /// Computes the 4-byte ABI error selector for a Solidity custom error signature, + /// e.g. `"InvalidRisc0Proof()"` → `"0x14add973"`. + fn error_selector(signature: &str) -> String { + let hash = keccak(signature.as_bytes()); + format!("0x{}", hex::encode(&hash[..4])) + } + #[test] fn error_selectors_match_solidity_signatures() { assert_eq!( From 14e602ee94388229b35285484c605cdd4d7006e1 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Tue, 24 Feb 2026 13:01:22 -0300 Subject: [PATCH 5/8] Remove unnecessary l2 feature gate from error_selectors test module since it only depends on keccak and string constants, not L2 infrastructure. --- test/tests/l2/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/tests/l2/mod.rs b/test/tests/l2/mod.rs index 2b8d03180da..152900d07e1 100644 --- a/test/tests/l2/mod.rs +++ b/test/tests/l2/mod.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "l2")] mod error_selectors; #[cfg(feature = "l2")] mod integration_tests; From bd68e63d193e31788c8a42ed3ed2ff4957a9e4d1 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Tue, 24 Feb 2026 13:47:42 -0300 Subject: [PATCH 6/8] Convert remaining require statements in verifyBatches to custom errors and add missing BatchNotSequential, EmptyBatchArray, BatchArrayLengthMismatch declarations to both IOnChainProposer interfaces. The three requires in verifyBatches were introduced by main's multi-batch refactor and missed during merge conflict resolution. --- crates/l2/contracts/src/l1/OnChainProposer.sol | 13 ++++--------- .../l2/contracts/src/l1/based/OnChainProposer.sol | 13 ++++--------- .../src/l1/based/interfaces/IOnChainProposer.sol | 3 +++ .../src/l1/interfaces/IOnChainProposer.sol | 2 ++ 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/crates/l2/contracts/src/l1/OnChainProposer.sol b/crates/l2/contracts/src/l1/OnChainProposer.sol index 4f792e18f4a..b9d9a0f0734 100644 --- a/crates/l2/contracts/src/l1/OnChainProposer.sol +++ b/crates/l2/contracts/src/l1/OnChainProposer.sol @@ -418,16 +418,11 @@ contract OnChainProposer is bytes[] calldata sp1ProofsBytes, bytes[] calldata tdxSignatures ) external override onlyOwner whenNotPaused { - require( - !ALIGNED_MODE, - "008" // Batch verification should be done via Aligned Layer. Call verifyBatchesAligned() instead. - ); + if (ALIGNED_MODE) revert UseAlignedVerification(); uint256 batchCount = risc0BlockProofs.length; - require(batchCount > 0, "OnChainProposer: empty batch array"); - require( - sp1ProofsBytes.length == batchCount && tdxSignatures.length == batchCount, - "OnChainProposer: array length mismatch" - ); + if (batchCount == 0) revert EmptyBatchArray(); + if (sp1ProofsBytes.length != batchCount || tdxSignatures.length != batchCount) + revert BatchArrayLengthMismatch(); for (uint256 i = 0; i < batchCount; i++) { _verifyBatchInternal( firstBatchNumber + i, diff --git a/crates/l2/contracts/src/l1/based/OnChainProposer.sol b/crates/l2/contracts/src/l1/based/OnChainProposer.sol index 1e10e9db2fa..c2963777fe8 100644 --- a/crates/l2/contracts/src/l1/based/OnChainProposer.sol +++ b/crates/l2/contracts/src/l1/based/OnChainProposer.sol @@ -381,16 +381,11 @@ contract OnChainProposer is bytes[] calldata sp1ProofsBytes, bytes[] calldata tdxSignatures ) external { - require( - !ALIGNED_MODE, - "Batch verification should be done via Aligned Layer. Call verifyBatchesAligned() instead." - ); + if (ALIGNED_MODE) revert UseAlignedVerification(); uint256 batchCount = risc0BlockProofs.length; - require(batchCount > 0, "OnChainProposer: empty batch array"); - require( - sp1ProofsBytes.length == batchCount && tdxSignatures.length == batchCount, - "OnChainProposer: array length mismatch" - ); + if (batchCount == 0) revert EmptyBatchArray(); + if (sp1ProofsBytes.length != batchCount || tdxSignatures.length != batchCount) + revert BatchArrayLengthMismatch(); for (uint256 i = 0; i < batchCount; i++) { _verifyBatchInternal( firstBatchNumber + i, diff --git a/crates/l2/contracts/src/l1/based/interfaces/IOnChainProposer.sol b/crates/l2/contracts/src/l1/based/interfaces/IOnChainProposer.sol index 07d47843b7f..ef1ada3500b 100644 --- a/crates/l2/contracts/src/l1/based/interfaces/IOnChainProposer.sol +++ b/crates/l2/contracts/src/l1/based/interfaces/IOnChainProposer.sol @@ -34,7 +34,10 @@ interface IOnChainProposer { // Verify errors error UseAlignedVerification(); error UseSmartContractVerification(); + error BatchNotSequential(); error BatchNotCommitted(); + error EmptyBatchArray(); + error BatchArrayLengthMismatch(); error ExpiredPrivilegedTransactionDeadline(); error InvalidRisc0Proof(); error InvalidSp1Proof(); diff --git a/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol b/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol index 7cb2f441171..c26b0b68b8c 100644 --- a/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol +++ b/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol @@ -35,6 +35,8 @@ interface IOnChainProposer { error UseSmartContractVerification(); error BatchNotSequential(); error BatchNotCommitted(); + error EmptyBatchArray(); + error BatchArrayLengthMismatch(); error ExpiredPrivilegedTransactionDeadline(); error InvalidRisc0Proof(); error InvalidSp1Proof(); From 98dbdc0ca1b5e2b8b221a1c357213dfd78501f53 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Tue, 24 Feb 2026 13:52:58 -0300 Subject: [PATCH 7/8] Run cargo fmt to fix formatting in l1_proof_sender.rs and utils.rs. --- crates/l2/sequencer/l1_proof_sender.rs | 3 +-- crates/l2/sequencer/utils.rs | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/l2/sequencer/l1_proof_sender.rs b/crates/l2/sequencer/l1_proof_sender.rs index 7dbfbf6baec..2d870f42860 100644 --- a/crates/l2/sequencer/l1_proof_sender.rs +++ b/crates/l2/sequencer/l1_proof_sender.rs @@ -615,8 +615,7 @@ impl L1ProofSender { }) = err && let Some((batch_number, _)) = batches.first() { - self.try_delete_invalid_proof(data, *batch_number) - .await?; + self.try_delete_invalid_proof(data, *batch_number).await?; } } diff --git a/crates/l2/sequencer/utils.rs b/crates/l2/sequencer/utils.rs index 6a8103a12ae..b794031f6e5 100644 --- a/crates/l2/sequencer/utils.rs +++ b/crates/l2/sequencer/utils.rs @@ -204,4 +204,3 @@ pub fn get_git_commit_hash() -> String { pub fn batch_checkpoint_name(batch_number: u64) -> String { format!("checkpoint_batch_{batch_number}") } - From a8eeaa391b586c19f705bb3780ed6742c7a55245 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Tue, 24 Feb 2026 16:36:01 -0300 Subject: [PATCH 8/8] Include data field in RPCError display format so unknown revert selectors are visible in operator logs, making it easier to debug failed contract calls. --- crates/networking/rpc/clients/eth/errors.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/networking/rpc/clients/eth/errors.rs b/crates/networking/rpc/clients/eth/errors.rs index 12f31527dbc..9d22a95979c 100644 --- a/crates/networking/rpc/clients/eth/errors.rs +++ b/crates/networking/rpc/clients/eth/errors.rs @@ -9,7 +9,7 @@ pub enum RpcRequestError { method: String, source: serde_json::Error, }, - #[error("{method}: {message}")] + #[error("{method}: {message} (data: {data:?})")] RPCError { method: String, message: String,