Skip to content

Improve batch wire format#69

Merged
RogerPodacter merged 3 commits intobatchesfrom
improve_wire_format
Oct 1, 2025
Merged

Improve batch wire format#69
RogerPodacter merged 3 commits intobatchesfrom
improve_wire_format

Conversation

@RogerPodacter
Copy link
Member

@RogerPodacter RogerPodacter commented Oct 1, 2025

Note

Switches batches to a new V2 wire format (magic + chain_id/version/role/length [+signature] + RLP tx list), renames FORCED to PERMISSIONLESS, and updates parser, verifier, sequencer, DB, and tests accordingly.

  • Protocol/Constants
    • Change MAGIC_PREFIX to ASCII "unstoppable sequencing" hex and add header sizing/offsets (MAGIC_SIZE, CHAIN_ID_SIZE, VERSION_SIZE, ROLE_SIZE, LENGTH_SIZE, HEADER_SIZE, SIGNATURE_SIZE).
    • Rename role FORCED -> PERMISSIONLESS; keep PRIORITY.
  • Parser (FacetBatchParser)
    • Parse new wire format: [MAGIC][CHAIN_ID:8][VERSION:1][ROLE:1][LENGTH:4][RLP_TX_LIST][SIGNATURE?].
    • Early chain ID check; remove l1_block_number param from parse_payload.
    • Compute content_hash from CHAIN_ID+VERSION+ROLE+RLP_TX_LIST(+SIGNATURE); decode RLP tx list; support priority signature presence.
    • Update ParsedBatch: fields reflect new semantics (no target_l1_block, updated role method, content_hash meaning, chain_id from header, source excludes events).
  • Signature Verification (BatchSignatureVerifier)
    • Verify over keccak256(signed_data) of header+tx list; normalize v (accept 0/1 or 27/28); improved error handling helper.
  • Sequencer
    • BatchMaker: emit new wire format, include role and optional signature; update content hash; remove target_l1_block usage in schema and inserts; update magic prefix.
    • DB (schema.ts): drop target_l1_block column usages in inserts and logic.
    • L1 Monitor: update magic prefix; change dropped-tx heuristic to 10 minutes after mined post attempt (from 100 blocks).
  • Models
    • StandardL2Transaction: raise DecodeError on Secp256k1::DeserializationError during EIP-1559 recovery.
  • Collector
    • Adjust calls to parse_payload (no block number); parse from calldata/blobs only.
  • Tests
    • Overhaul specs to build/parse new wire format; add signature verifier tests; rename expectations to PERMISSIONLESS; remove reliance on target_l1_block; update helpers and integration tests.

Written by Cursor Bugbot for commit 9a88f84. This will update automatically on new commits. Configure here.

@RogerPodacter RogerPodacter requested a review from Copilot October 1, 2025 16:00
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR updates the Facet batch wire format to improve parsing efficiency and simplify the protocol. The changes move from an RLP-nested structure to a streamlined binary header format.

  • Simplifies batch structure by using a binary header instead of nested RLP encoding
  • Updates role terminology from "FORCED" to "PERMISSIONLESS" for clarity
  • Removes target L1 block requirement from batches

Reviewed Changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
spec/support/blob_test_helper.rb Updates test helper to create batches in new wire format
spec/services/facet_block_builder_spec.rb Updates test batch creation to use new role and remove target block
spec/services/facet_batch_parser_spec.rb Comprehensive test updates for new wire format parsing
spec/services/facet_batch_collector_spec.rb Updates batch payload creation for new format
spec/services/blob_aggregation_spec.rb Updates blob tests to use new wire format
spec/services/batch_signature_verifier_spec.rb New test file for signature verification in wire format
spec/models/standard_l2_transaction_signature_recovery_spec.rb Updates error handling for signature recovery
spec/mixed_transaction_types_spec.rb Updates batch creation and debugging for new format
spec/integration/forced_tx_filtering_spec.rb Updates forced batch test to use permissionless role
spec/integration/blob_end_to_end_spec.rb Updates end-to-end blob tests for new format
sequencer/src/l1/monitor.ts Changes dropped transaction detection from block-based to time-based
sequencer/src/db/schema.ts Removes target_l1_block field from batch schema
sequencer/src/batch/maker.ts Updates batch creation to use new wire format
app/services/facet_batch_parser.rb Major refactor to parse new binary header format
app/services/facet_batch_collector.rb Removes l1_block_number parameter from parser calls
app/services/batch_signature_verifier.rb Simplifies signature verification for new format
app/models/standard_l2_transaction.rb Adds error handling for signature deserialization
app/models/parsed_batch.rb Updates model to remove target_l1_block and extra_data
app/models/facet_batch_constants.rb Adds wire format constants and updates role names

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

12345, # l1_block_number
0, # l1_tx_index
FacetBatchConstants::Source::BLOB,
{ versioned_hash: "0x" + "a" * 64 }
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parser.parse_payload call is missing the source_details parameter in the method signature but provides it as the fourth argument. According to the updated signature in facet_batch_parser.rb, source_details is now the fourth parameter with a default value.

Suggested change
{ versioned_hash: "0x" + "a" * 64 }
source_details: { versioned_hash: "0x" + "a" * 64 }

Copilot uses AI. Check for mistakes.
Comment on lines +160 to +174
# Create valid wire format batch
magic = FacetBatchConstants::MAGIC_PREFIX.to_bin
chain_id_bytes = [chain_id].pack('Q>') # uint64 big-endian
version_byte = [FacetBatchConstants::VERSION].pack('C') # uint8
role_byte = [role].pack('C') # uint8
length_bytes = [rlp_tx_list.length].pack('N') # uint32 big-endian

batch = magic + chain_id_bytes + version_byte + role_byte + length_bytes + rlp_tx_list

# Add signature for priority batches
if role == FacetBatchConstants::Role::PRIORITY && signature
batch += signature
end

batch
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signature parameter is optional but the logic only adds it if role is PRIORITY AND signature is provided. This could lead to incomplete priority batches if signature is not provided. Consider either requiring signature for priority batches or handling the case explicitly.

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +54
const role = 0; // 0 = permissionless
const signature: Hex | undefined = undefined;
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded role value should use the constant from FacetBatchConstants rather than a magic number. Consider defining these role constants in TypeScript or importing them.

Copilot uses AI. Check for mistakes.
Comment on lines 46 to 49
offset = index + FacetBatchConstants::HEADER_SIZE + length
# Add signature size if priority batch
role_offset = index + FacetBatchConstants::ROLE_OFFSET
role = data[role_offset, FacetBatchConstants::ROLE_SIZE].unpack1('C')
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code reads the role after already reading the length to skip the batch. Since we're skipping due to wrong chain_id, we don't need to check the role first - we can read it once and use it for both validation and offset calculation.

Suggested change
offset = index + FacetBatchConstants::HEADER_SIZE + length
# Add signature size if priority batch
role_offset = index + FacetBatchConstants::ROLE_OFFSET
role = data[role_offset, FacetBatchConstants::ROLE_SIZE].unpack1('C')
role_offset = index + FacetBatchConstants::ROLE_OFFSET
role = data[role_offset, FacetBatchConstants::ROLE_SIZE].unpack1('C')
offset = index + FacetBatchConstants::HEADER_SIZE + length

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +49
unless [0, 1].include?(v_normalised)
error_class = defined?(Eth::Signature::SignatureError) ? Eth::Signature::SignatureError : StandardError
raise error_class, "Invalid recovery id #{raw_v}"
end
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error class selection logic is repeated in the signature_error? method. Consider extracting this into a helper method or using a consistent approach for signature error handling.

Copilot uses AI. Check for mistakes.
cursor[bot]

This comment was marked as outdated.

@RogerPodacter RogerPodacter requested a review from Copilot October 1, 2025 17:40
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

spec/services/facet_batch_parser_spec.rb:1

  • [nitpick] The wire format construction logic is duplicated across multiple test helper methods. Consider extracting this into a shared test utility method to reduce code duplication and ensure consistency.
require 'rails_helper'

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

// Build new wire format: [MAGIC:22][CHAIN_ID:8][VERSION:1][ROLE:1][LENGTH:4][RLP_TX_LIST]
const rlpSize = size(rlpTxList);
const parts: Hex[] = [
this.FACET_MAGIC_PREFIX, // MAGIC: 12 bytes
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says '12 bytes' on line 153 but the magic prefix is actually 22 bytes based on the ASCII string 'unstoppable sequencing'.

Suggested change
this.FACET_MAGIC_PREFIX, // MAGIC: 12 bytes
this.FACET_MAGIC_PREFIX, // MAGIC: 22 bytes

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +77
def signature_error?(error)
return true if defined?(Eth::Signature::SignatureError) && error.is_a?(Eth::Signature::SignatureError)

if defined?(Secp256k1) && Secp256k1.const_defined?(:Error)
return true if error.is_a?(Secp256k1.const_get(:Error))
end

false
end
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The signature error detection logic is complex and fragile due to dynamic constant checking. Consider defining specific error classes or using a more explicit error handling approach.

Copilot uses AI. Check for mistakes.
@RogerPodacter
Copy link
Member Author

bugbot run

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no bugs!


@RogerPodacter RogerPodacter merged commit 271e854 into batches Oct 1, 2025
3 checks passed
@RogerPodacter RogerPodacter deleted the improve_wire_format branch October 1, 2025 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant