Skip to content

chore(crates): Alloy Types with Preserved Git History#750

Merged
refcell merged 450 commits intomainfrom
alloy
Feb 18, 2026
Merged

chore(crates): Alloy Types with Preserved Git History#750
refcell merged 450 commits intomainfrom
alloy

Conversation

@refcell
Copy link
Copy Markdown
Contributor

@refcell refcell commented Feb 18, 2026

Summary

Pulls in alloy types with preserved git history.

mattsse and others added 30 commits November 6, 2024 20:41
move to consensus, make them reusable
      <!--
Thank you for your Pull Request. Please provide a description above and
review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide:
https://github.com/alloy-rs/core/blob/main/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and
building the
documentation.
-->

<!-- ** Please select "Allow edits from maintainers" in the PR Options
** -->

## Motivation

<!--
Explain the context and why you're making that change. What is the
problem
you're trying to solve? In some cases there is not a problem and this
can be
thought of as being the motivation for your change.
-->

## Solution

<!--
Summarize the solution and provide any necessary context needed to
understand
the code change.
-->

## PR Checklist

- [ ] Added Tests
- [ ] Added Documentation
- [ ] Breaking changes
<!--
Thank you for your Pull Request. Please provide a description above and
review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide:
https://github.com/alloy-rs/core/blob/main/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and
building the
documentation.
-->

<!-- ** Please select "Allow edits from maintainers" in the PR Options
** -->

## Motivation

Bug arbitrary impl

## Solution

Add missing `OpTxType::Eip7702` to `OpTxType::ALL` list

## PR Checklist

- [ ] Added Tests
- [ ] Added Documentation
- [ ] Breaking changes
<!--
Thank you for your Pull Request. Please provide a description above and
review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide:
https://github.com/alloy-rs/core/blob/main/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and
building the
documentation.
-->

<!-- ** Please select "Allow edits from maintainers" in the PR Options
** -->

## Motivation

Broken decoding for eip7702

## Solution

Adds conversion from u8 to `OpTxType::Eip7702`

## PR Checklist

- [ ] Added Tests
- [ ] Added Documentation
- [ ] Breaking changes
<!--
Thank you for your Pull Request. Please provide a description above and
review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide:
https://github.com/alloy-rs/core/blob/main/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and
building the
documentation.
-->

<!-- ** Please select "Allow edits from maintainers" in the PR Options
** -->

## Motivation

ref paradigmxyz/reth#12474

We need a separate `deposit_nonce` to account for deposit transaction
responses which have it while it's not present in inner envelope.

## Solution

Adds `nonce` field to `OptionalFields` helper. It would get deserialized
only if envelope did not consume it and serialized only if present and
inner envelope is a deposit

## PR Checklist

- [ ] Added Tests
- [ ] Added Documentation
- [ ] Breaking changes
<!--
Thank you for your Pull Request. Please provide a description above and
review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide:
https://github.com/alloy-rs/core/blob/main/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and
building the
documentation.
-->

<!-- ** Please select "Allow edits from maintainers" in the PR Options
** -->

## Motivation

ref https://t.me/paradigm_reth/36099

Makes envelope more consistend by making all variants hold a hash

## Solution

<!--
Summarize the solution and provide any necessary context needed to
understand
the code change.
-->

## PR Checklist

- [ ] Added Tests
- [ ] Added Documentation
- [ ] Breaking changes
adds encodeable+decodable+partialeq
### Description

Adds an examples for transforming `Frame`s into `Batch`es and the
reverse.
### Description

Implements encoding for batch types.

Steps in the direction to batch submission.
### Description

Since #259 adds `Batch` encoding, book examples can decode and encode
directly to and from the `Batch` types instead of the `SingleBatch`
type.
### Description

Removes public visibility from modules so crate docs don't show all the
`pub use mod::*` types as re-exported.

Also moves error types from `utils` into a new `errors` module.
### Description

Adds batch compression to the `ChannelOut`.
<!--
Thank you for your Pull Request. Please provide a description above and
review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide:
https://github.com/alloy-rs/core/blob/main/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and
building the
documentation.
-->

<!-- ** Please select "Allow edits from maintainers" in the PR Options
** -->

## Motivation

Protected bits are only present for legacy transactions and we should
respect transaction type when getting them in `full_txs`

## Solution

<!--
Summarize the solution and provide any necessary context needed to
understand
the code change.
-->

## PR Checklist

- [ ] Added Tests
- [ ] Added Documentation
- [ ] Breaking changes
### Description

Removes the `TryFrom` span batch for `RawSpanBatch`, inlining the
coercion into a `to_raw_span_batch` method.

Closes #266.
### Description

Adds zlib compression to the `ChannelOut`.

Closes #267
### Description

Re-introduces the `BatchReader` into `op-alloy-protocol`.

Batch compression already introduces the `brotli` and `miniz-oxzide`
deps.

The compression and decompression abstractions used here should be
improved for both directions: compression + decompression.
### Description

Now that `thiserror` supports `no_std`, we can use `thiserror` for error
types over `derive_more::Display`.

This PR updates the workspace to use `thiserror`.
### Description

Removes error impls using `thiserror` instead.
### Description

Removes re-exports from `op-alloy-genesis`.
### Description

Adds Holocene timestamps for OP Sepolia and Base Sepolia hardcoded
rollup configs.
### Description

Small doc touchup pr for `op-alloy` crate.
### Description

Cleans up `op-alloy-protocol` examples using the exported
`decompress_brotli` method.
### Description

Moves `OpTxType` from the `envelope` mod into it's own `tx_type` mod to
make it more digestible.

Also adds tests, including an `OpTxType::ALL` test that requires a
newly-added variant is added to the list.
### Description

Moves the `BatchTransaction` into the `batch` mod.
### Description

Upstreams a small `OpTxType` conversion to `alloy_primitives::U8` from
reth.

---------

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
@refcell refcell self-assigned this Feb 18, 2026
@refcell refcell added the A-repo Area: general repository changes label Feb 18, 2026
@cb-heimdall
Copy link
Copy Markdown
Collaborator

cb-heimdall commented Feb 18, 2026

✅ Heimdall Review Status

Requirement Status More Info
Reviews 1/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

@refcell refcell marked this pull request as draft February 18, 2026 20:39
@refcell
Copy link
Copy Markdown
Contributor Author

refcell commented Feb 18, 2026

Placing in draft to fix ci and some minor nits


impl From<OpTransactionFields> for OtherFields {
fn from(value: OpTransactionFields) -> Self {
serde_json::to_value(value).unwrap().try_into().unwrap()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Double .unwrap() in a From impl — this will panic if serialization or conversion fails. Since From impls are expected to be infallible, either make this truly infallible (restructure to avoid the serde round-trip) or change to a TryFrom impl with proper error handling.

Suggested change
serde_json::to_value(value).unwrap().try_into().unwrap()
serde_json::to_value(value).expect("OpTransactionFields serialization is infallible").try_into().expect("OpTransactionFields value is a valid OtherFields map")


impl From<OpTransactionReceiptFields> for OtherFields {
fn from(value: OpTransactionReceiptFields) -> Self {
serde_json::to_value(value).unwrap().try_into().unwrap()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same double-.unwrap() pattern as in transaction.rs:193. A From impl that panics on serialization failure is problematic. At a minimum, use .expect() with a message documenting why this is safe, or convert to TryFrom.

Suggested change
serde_json::to_value(value).unwrap().try_into().unwrap()
serde_json::to_value(value).expect("OpTransactionReceiptFields serialization is infallible").try_into().expect("OpTransactionReceiptFields value is a valid OtherFields map")

let mut sig = self.signature.as_bytes();
sig[64] = self.signature.v() as u8;
data.extend_from_slice(&sig[..]);
data.extend_from_slice(self.parent_beacon_block_root.as_ref().unwrap().as_slice());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

parent_beacon_block_root is Option<B256> and .unwrap() here will panic if it's None. Since this method already returns Result, this should propagate an error instead:

Suggested change
data.extend_from_slice(self.parent_beacon_block_root.as_ref().unwrap().as_slice());
data.extend_from_slice(self.parent_beacon_block_root.as_ref().ok_or(PayloadEnvelopeEncodeError::WrongVersion)?.as_slice());

(Or add a dedicated error variant like MissingParentBeaconBlockRootWrongVersion is a rough fit but avoids adding a new variant.)

let mut sig = self.signature.as_bytes();
sig[64] = self.signature.v() as u8;
data.extend_from_slice(&sig[..]);
data.extend_from_slice(self.parent_beacon_block_root.as_ref().unwrap().as_slice());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same .unwrap() on parent_beacon_block_root as encode_v3 — should return an error via ? instead of panicking.

}

/// Returns the transactions for the payload.
pub const fn transactions(&self) -> &Vec<Bytes> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Return &[Bytes] instead of &Vec<Bytes> — exposing &Vec<T> in a public API is a Rust anti-pattern (clippy ptr_arg). Same applies to withdrawals() in envelope.rs:321 and versioned_hashes() in sidecar.rs:108.

Suggested change
pub const fn transactions(&self) -> &Vec<Bytes> {
pub const fn transactions(&self) -> &[Bytes] {

@@ -0,0 +1,38 @@
#![allow(missing_docs)]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Per the project's CLAUDE.md: "Do not add #![allow(missing_docs)] or other allow-lints to suppress clippy warnings. Fix the underlying issue instead." Add doc comments to the public items in this file and remove this allow.

Note: The //! Various jsonrpsee docs module doc on line 3 doesn't compensate for missing docs on the individual trait methods.

Comment on lines +62 to +67
fn ssz_bytes_len(&self) -> usize {
let mut len = 0;
len += B256::ssz_fixed_len(); // parent_beacon_block_root is always 32 bytes
len += self.execution_payload.ssz_bytes_len();
len
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ssz_bytes_len is inconsistent with ssz_append: ssz_append conditionally skips parent_beacon_block_root for V1/V2 payloads, but ssz_bytes_len unconditionally includes B256::ssz_fixed_len(). This will cause callers that pre-allocate based on ssz_bytes_len to over-allocate for V1/V2 payloads, and the returned length won't match the actual encoded bytes.

Suggested change
fn ssz_bytes_len(&self) -> usize {
let mut len = 0;
len += B256::ssz_fixed_len(); // parent_beacon_block_root is always 32 bytes
len += self.execution_payload.ssz_bytes_len();
len
}
fn ssz_bytes_len(&self) -> usize {
let mut len = 0;
if !matches!(self.execution_payload, OpExecutionPayload::V1(_) | OpExecutionPayload::V2(_))
{
len += B256::ssz_fixed_len(); // parent_beacon_block_root is 32 bytes
}
len += self.execution_payload.ssz_bytes_len();
len
}

Comment on lines +66 to +71
) -> impl Iterator<
Item = alloy_rlp::Result<
alloy_eips::eip2718::WithEncoded<alloy_consensus::transaction::Recovered<T>>,
alloy_consensus::crypto::RecoveryError,
>,
> + '_
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

alloy_rlp::Result is a type alias Result<T, alloy_rlp::Error> (one type parameter). Using it with two type parameters here is likely a compile error when the k256 feature is enabled. This should be Result<..., RecoveryError> using core::result::Result:

Suggested change
) -> impl Iterator<
Item = alloy_rlp::Result<
alloy_eips::eip2718::WithEncoded<alloy_consensus::transaction::Recovered<T>>,
alloy_consensus::crypto::RecoveryError,
>,
> + '_
) -> impl Iterator<
Item = Result<
alloy_eips::eip2718::WithEncoded<alloy_consensus::transaction::Recovered<T>>,
alloy_consensus::crypto::RecoveryError,
>,
> + '_

Comment on lines +76 to +99
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
if bytes.len() < B256::ssz_fixed_len() {
return Err(ssz::DecodeError::InvalidByteLength {
len: bytes.len(),
expected: B256::ssz_fixed_len(),
});
}

// Decode parent_beacon_block_root
let parent_beacon_block_root = {
let root_bytes = &bytes[..B256::ssz_fixed_len()];
if root_bytes.iter().all(|&b| b == 0) {
None
} else {
Some(B256::from_slice(root_bytes))
}
};

// Decode payload
let execution_payload =
OpExecutionPayload::from_ssz_bytes(&bytes[B256::ssz_fixed_len()..])?;

Ok(Self { parent_beacon_block_root, execution_payload })
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SSZ decode for V1/V2 always tries to parse parent_beacon_block_root from the first 32 bytes, but ssz_append does not write it for V1/V2 payloads. This means from_ssz_bytes cannot correctly roundtrip V1/V2 payloads — it will consume 32 bytes of the payload data as the beacon root.

The decode logic needs to handle V1/V2 differently: either peek at the payload to determine the version first, or document that this SSZ codec only supports V3+ envelopes.

@refcell refcell marked this pull request as ready for review February 18, 2026 21:03
match ty {
OpTxType::Deposit => Err(vec!["not implemented for deposit tx"]),
_ => {
let ty = TxType::try_from(ty as u8).unwrap();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

.unwrap() on TxType::try_from() will panic if OpTxType has a variant whose u8 value doesn't map to a valid TxType. Since complete_type returns Result<(), Vec<&'static str>>, propagate the error instead:

Suggested change
let ty = TxType::try_from(ty as u8).unwrap();
let ty = TxType::try_from(ty as u8).map_err(|_| vec!["unsupported transaction type"])?;


fn build_unsigned(self) -> BuildResult<OpTypedTransaction, Base> {
if let Err((tx_type, missing)) = self.as_ref().missing_keys() {
let tx_type = OpTxType::try_from(tx_type as u8).unwrap();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same .unwrap() issue — build_unsigned returns BuildResult (a Result), so this conversion should propagate the error rather than panicking:

Suggested change
let tx_type = OpTxType::try_from(tx_type as u8).unwrap();
let tx_type = OpTxType::try_from(tx_type as u8).expect("TxType u8 value must be a valid OpTxType");

At minimum, use .expect() documenting the invariant. Ideally, convert the error via the BuildResult error path.

}

/// Return the withdrawals for the payload or attributes.
pub const fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Return &[Withdrawal] instead of &Vec<Withdrawal> — same ptr_arg anti-pattern already flagged on transactions(). Applies here and to versioned_hashes() in sidecar.rs:108.

Suggested change
pub const fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
pub const fn withdrawals(&self) -> Option<&[Withdrawal]> {

encode_jovian_extra_data(
params,
default_base_fee_params,
self.min_base_fee.unwrap(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This .unwrap() is safe because line 138 checks self.min_base_fee.is_none() and returns early, but it's fragile — a future refactor could break the invariant. Use the guarded value directly via if let or map to make this structurally safe:

self.eip_1559_params
    .zip(self.min_base_fee)
    .map(|(params, min_fee)| encode_jovian_extra_data(params, default_base_fee_params, min_fee))
    .ok_or(EIP1559ParamError::NoEIP1559Params)?

@github-actions
Copy link
Copy Markdown
Contributor

Review Summary

This PR adds 6 new base-alloy-* crates under crates/alloy/ porting OP Stack alloy types with preserved git history. The code is generally well-structured and follows workspace conventions. Below are the findings organized by severity.

Correctness Issues

  • SSZ encode/decode mismatch (envelope.rs): ssz_bytes_len unconditionally includes parent_beacon_block_root but ssz_append conditionally skips it for V1/V2. Similarly, from_ssz_bytes always reads 32 bytes for beacon root, breaking V1/V2 roundtrips. (existing comment)
  • .unwrap() on parent_beacon_block_root in encode_v3/encode_v4 (envelope.rs): These methods already return Result, so panicking on None is unnecessary. (existing comment)
  • Wrong Result type in recover_transactions (flashblock/payload.rs:67): alloy_rlp::Result takes one type parameter but is used with two. (existing comment)
  • .unwrap() in complete_type and build_unsigned (network/src/lib.rs:146,186): TxType::try_from() can fail; the surrounding functions return Result, so errors should be propagated. (new comment)

API Design

  • &Vec<T> return types: transactions() in payload/mod.rs:448, withdrawals() in envelope.rs:321, and versioned_hashes() in sidecar.rs:108 all return &Vec<T> instead of &[T]. (existing + new comments)
  • #![allow(missing_docs)] in rpc-jsonrpsee/src/traits.rs: Violates project lint policy. (existing comment)
  • From impls with double .unwrap() (transaction.rs:192, receipt.rs): Infallible From impls should use .expect() or be changed to TryFrom. (existing comment)

Robustness

  • get_jovian_extra_data fragile .unwrap() (attributes.rs:146): Relies on an early return 8 lines above. Should use .zip() or destructuring to make the invariant structural. (new comment)

Minor Observations (not commented)

  • pub(crate) on rlp_encode_fields, rlp_encoded_fields_length, tx_type in transaction/deposit.rs — project guidelines prefer pub for all types/functions within modules
  • pub mod interop and pub mod predeploys in consensus/src/lib.rs expose modules publicly but not all items are re-exported at the crate root via pub use
  • Cargo.toml dependency ordering is not strictly waterfall-sorted by line length in several crates

Overall the types are well-ported. The SSZ encode/decode mismatch for V1/V2 payloads is the most impactful issue and should be addressed before merge.

@refcell refcell added this pull request to the merge queue Feb 18, 2026
Merged via the queue into main with commit 72eada2 Feb 18, 2026
19 of 20 checks passed
@refcell refcell deleted the alloy branch February 18, 2026 21:39
@refcell refcell restored the alloy branch February 18, 2026 22:04
danyalprout pushed a commit that referenced this pull request Mar 5, 2026
* feat: u16a spec

Proposed specification for U16a. OptimismPortal functions for
super roots have been removed but the definitions remain to
simplify the process of adding the code back in later. Added
isFeatureEnabled logic to the SystemConfig as well as updates to
the pause function for chains that do not have the ETHLockbox
feature enabled.

* fix: lint

* fix: use split spec format

* fix: typo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-repo Area: general repository changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.