Skip to content

fix(types): make the arbitrary feature self-contained - #231

Open
mehmetkr-31 wants to merge 1 commit into
circlefin:mainfrom
mehmetkr-31:fix/arbitrary-derive-feature
Open

fix(types): make the arbitrary feature self-contained#231
mehmetkr-31 wants to merge 1 commit into
circlefin:mainfrom
mehmetkr-31:fix/arbitrary-derive-feature

Conversation

@mehmetkr-31

@mehmetkr-31 mehmetkr-31 commented Aug 6, 2026

Copy link
Copy Markdown

Fixes #233.

Summary

The arbitrary feature of arc-consensus-types does not build on its own:

$ cargo check -p arc-consensus-types --features arbitrary
error[E0433]: failed to resolve: could not find `Arbitrary` in `arbitrary`
  --> crates/types/src/address.rs:39:53
   |
39 | #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
   |                                                     ^^^^^^^^^ could not find `Arbitrary` in `arbitrary`

The feature gates derive(arbitrary::Arbitrary) on Address, but declares neither of the two things that derive needs:

  1. arbitrary/derive — the derive macro itself lives behind that feature, hence the E0433 above.
  2. alloy-primitives/arbitrary — without it the generated impl fails with the trait bound alloy_primitives::Address: Arbitrary<'_> is not satisfied, since the wrapped type has no Arbitrary impl of its own.

Why CI does not catch this

arc-consensus-db and arc-node-consensus both depend on alloy-rpc-types-engine with features = ["arbitrary"]. In a workspace build, feature unification turns on exactly what this crate omitted, so --all-features at the workspace level compiles and the gap stays hidden. It only surfaces when the crate is built alone — which is also how a consumer would build it, and crates/types inherits publish from the workspace rather than opting out.

Change

Declare both, following the pattern arc-evm and arc-node already use, where the arbitrary feature forwards to the alloy dependencies explicitly:

arbitrary = ["dep:arbitrary", "alloy-primitives/arbitrary"]
arbitrary = { workspace = true, optional = true, features = ["derive"] }

Testing

  • cargo check -p arc-consensus-types --features arbitrary — now compiles (this is the command that fails on main).
  • cargo check --workspace --all-features — still passes, no regression.
  • cargo test -p arc-consensus-types --features arbitrary — 192 tests pass.
  • cargo fmt --all --check — clean.
  • Cargo.lock is unchanged: arbitrary's derive feature and alloy-primitives/arbitrary are both already enabled somewhere in the graph, so no version resolution moves.

I left the workspace-level arbitrary = "1.3" in the root Cargo.toml alone deliberately — adding derive there would enable it for every consumer of the workspace dependency, and only this crate needs it.

`cargo check -p arc-consensus-types --features arbitrary` fails:

    error[E0433]: failed to resolve: could not find `Arbitrary` in `arbitrary`
      --> crates/types/src/address.rs:39
       | #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]

The feature gates `derive(arbitrary::Arbitrary)` on `Address`, but it
declared neither of the two things that derive needs:

- the `derive` feature of the `arbitrary` crate, which provides the
  derive macro itself;
- `alloy-primitives/arbitrary`, without which the generated impl fails
  with `the trait bound alloy_primitives::Address: Arbitrary<'_> is not
  satisfied`, since the wrapped type has no `Arbitrary` impl.

This is invisible in a workspace build: `arc-consensus-db` and
`arc-node-consensus` both depend on `alloy-rpc-types-engine` with
`features = ["arbitrary"]`, and feature unification turns on what this
crate omitted. Building the crate on its own is what exposes it.

Declare both, following the pattern already used by `arc-evm` and
`arc-node`, whose `arbitrary` features forward to their alloy
dependencies explicitly.

Cargo.lock is unchanged. `cargo check --workspace --all-features` still
passes, and `cargo test -p arc-consensus-types --features arbitrary`
passes (192 tests).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@osr21 osr21 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verified everything independently against main before reviewing:

  • The broken state is as described: arbitrary = ["dep:arbitrary"] with no derive and no alloy-primitives/arbitrary forwarding, while address.rs gates derive(arbitrary::Arbitrary) on the feature. Both missing pieces are real and both are needed — fixing only one moves the error from E0433 to the E0277 bound failure, exactly as the issue documents.
  • The arc-evm precedent holds: its arbitrary feature forwards to the alloy/revm/reth deps explicitly, so this change follows existing workspace convention rather than inventing one.
  • The masking analysis is the most valuable part of the PR and it's correct, with one detail worth making explicit: this only stays hidden because workspace-level commands (--workspace --all-features) unify features across all members. With resolver v2, cargo check -p <crate> resolves only that crate's own feature graph — which is why the standalone check is both the honest reproduction and a valid verification that the fix is complete (no unification bailing it out).

Two notes, neither blocking:

1. Placement of derive is equivalent to forwarding, with one subtle property worth knowing. Putting features = ["derive"] on the optional dependency declaration (rather than "arbitrary/derive" in the feature array) works because features on an optional dep only activate when the dep does. The one behavioral difference: if a second feature ever enables dep:arbitrary for a different purpose, it inherits derive whether it wants it or not, whereas the feature-array form keeps the choice per-feature. For a proc-macro feature on a fuzzing dep, that's a non-concern in practice — just noting it's a deliberate trade, and the current form is the more readable of the two.

2. This bug class is systematic, and there's a cheap guard. Feature-unification masking will re-occur for any crate whose optional features are exercised in CI only via workspace-wide builds — this crate just happens to be the first one caught building standalone. cargo hack check --each-feature -p <crate> (or workspace-wide cargo hack check --each-feature --workspace, which is the standard tool for exactly this) would catch the whole class in CI. Worth a follow-up issue rather than this PR, but without it the next #[cfg_attr(feature = ...)] derive added to any crate can silently regress the same way.

The restraint on the workspace-level arbitrary = "1.3" is also correct — pushing derive into the workspace declaration would force the proc-macro dependency on every consumer to fix one crate's declaration gap.

Minimal, correct, well-evidenced. Approving.

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

Labels

pending-import Merged PR awaiting reverse-sync to upstream

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: arc-consensus-types arbitrary feature does not build standalone

3 participants