Skip to content

feat(l1): fix EIP-8025 compliance#6516

Merged
avilagaston9 merged 4 commits intolambdaclass:mainfrom
jsign:jsign-ethrex-8025-fixes
Apr 27, 2026
Merged

feat(l1): fix EIP-8025 compliance#6516
avilagaston9 merged 4 commits intolambdaclass:mainfrom
jsign:jsign-ethrex-8025-fixes

Conversation

@jsign
Copy link
Copy Markdown
Contributor

@jsign jsign commented Apr 21, 2026

Summary

This branch fixes the EIP-8025 guest-program execution flow and aligns the SSZ-side NewPayloadRequest model with the Electra/CL spec.

At a high level, it adds a byte-oriented EIP-8025 execution entrypoint, moves execution requests into a typed SSZ container, and uses that typed representation to reconstruct the EL requests_hash correctly when converting a NewPayloadRequest into a block.

What changed

  • Added execution_program_eip8025_bytes, which decodes and executes the EIP-8025 wire format directly:
    • [ssz_len: u32 LE][ssz_bytes][rkyv_bytes]
  • Refactored the EIP-8025 validation flow so both entrypoints share the same logic for:
    • NewPayloadRequest -> Block conversion
    • block hash validation
    • blob versioned hash validation
    • stateless execution via execute_blocks
  • Updated the EIP-8025 SSZ types in ethrex-common to better match the Electra consensus spec:
    • ExecutionPayload no longer carries deposit/withdrawal/consolidation request lists
    • NewPayloadRequest.execution_requests is now a typed ExecutionRequests container
    • spec constants were renamed/normalized for clarity
    • the consolidation request limit was corrected to the Electra value
  • Added ExecutionRequests::to_encoded_requests() to convert the SSZ typed request container into the EIP-7685 encoded request list expected by compute_requests_hash
  • Re-exported the new EIP-8025 byte entrypoint from ethrex-guest-program

Behavior notes

  • Malformed EIP-8025 wire input still returns an error
  • For well-formed input, the byte-entrypoint now always computes the new_payload_request_root
  • If validation/execution fails after decoding, the byte-entrypoint returns ProgramOutput { valid: false, ... } instead of failing outright

Why this matters

This makes the EIP-8025 guest path closer to the consensus-side structure we actually need to prove against, while also giving us a direct raw-bytes execution entrypoint for the EIP-8025 wire format.

It also removes the mismatch between the SSZ request representation and the EL request-hash computation path by deriving requests_hash from a properly typed ExecutionRequests container.

External usage

I'm already using this PR branch into an ere-guests PR here: eth-act/ere-guests#39
This allowed ere-guests to basically remove a lot of wrapper code before calling the guest program regarding execution-payload->Block, output calculation, etc -- making now Ethrex own all that logic as expected.


// ── Spec limits (Electra) ──────────────────────────────────────────

/// MAX_TRANSACTIONS_PER_PAYLOAD (Electra).
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Some renamings to conform to the consensus-spec names, but also some fixes that I'll comment in particular.

Comment on lines -26 to -27
/// MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD (Electra).
const MAX_CONSOLIDATION_REQUESTS: usize = 1;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note that the previous 1 value wasn't correct, this was fixed below L30 with value 2 as defined in the specs.

Comment on lines -165 to -167
pub deposit_requests: SszList<DepositRequest, MAX_DEPOSIT_REQUESTS>,
pub withdrawal_requests: SszList<WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS>,
pub consolidation_requests: SszList<ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS>,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In the specs, ExecutionPayload does not have these three fields. They are actually part of the NewPayloadRequest.

/// SSZ `ExecutionRequests` container (Electra) — the typed EIP-7685 bundle
/// that the CL commits to alongside `ExecutionPayload`.
#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
pub struct ExecutionPayloadHeader {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This isn't strictly required so we can remove.

pub deposit_requests_root: [u8; 32],
pub withdrawal_requests_root: [u8; 32],
pub consolidation_requests_root: [u8; 32],
pub struct ExecutionRequests {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

See my next comment to understand this.

Comment on lines -217 to +223
pub execution_requests:
SszList<SszList<u8, MAX_EXECUTION_REQUEST_BYTES>, MAX_EXECUTION_REQUESTS>,
pub execution_requests: ExecutionRequests,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment on lines +83 to +92
/// Decode and execute the L1 stateless validation program from EIP-8025 wire
/// bytes.
///
/// The wire format is `[ssz_len: u32 LE][ssz_bytes][rkyv_bytes]`, matching
/// [`decode_eip8025`](super::decode_eip8025).
#[cfg(feature = "eip-8025")]
pub fn execution_program_eip8025_bytes(
bytes: &[u8],
crypto: Arc<dyn Crypto>,
) -> Result<ProgramOutput, ExecutionError> {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a new entrypoint which is aligned with the execution-specs.

In ere-guests now we use it to send raw bytes directly, removing a lot of "wrapper logic" we had in ere-guest guest program before calling Ethrex entry point.

@jsign jsign changed the title feat(guest l1): fix EIP-8025 compliance feat(l1): fix EIP-8025 compliance Apr 21, 2026
@jsign jsign marked this pull request as ready for review April 21, 2026 22:34
@jsign jsign requested a review from a team as a code owner April 21, 2026 22:34
Copilot AI review requested due to automatic review settings April 21, 2026 22:34
Copy link
Copy Markdown
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 completes the EIP-8025 guest-program execution path and aligns the SSZ NewPayloadRequest model with the Electra/CL spec, including deriving the EL requests_hash from a typed ExecutionRequests container.

Changes:

  • Added a raw-bytes EIP-8025 execution entrypoint ([ssz_len][ssz_bytes][rkyv_bytes]) and re-exported it for external consumers.
  • Refactored EIP-8025 validation/execution to share common logic for NewPayloadRequest -> Block conversion, hash checks, and stateless execution.
  • Updated SSZ types to match Electra and introduced ExecutionRequests::to_encoded_requests() for EIP-7685 requests_hash computation.

Reviewed changes

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

File Description
crates/guest-program/src/lib.rs Re-exports the new EIP-8025 bytes entrypoint under the L1 execution module.
crates/guest-program/src/l1/program.rs Adds the bytes entrypoint, centralizes EIP-8025 validation/execution, and adjusts request-hash reconstruction.
crates/guest-program/src/l1/mod.rs Re-exports execution_program_eip8025_bytes behind the eip-8025 feature.
crates/common/types/eip8025_ssz.rs Aligns SSZ containers with Electra and adds typed ExecutionRequests plus EIP-7685 encoding conversion.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +183 to +211
impl ExecutionRequests {
/// Produce the EIP-7685 encoded form: three `EncodedRequests` entries,
/// one per request type, each `[type_byte] ++ concat(ssz_encode(item))`.
///
/// The three request types are all fixed-size SSZ containers, so their
/// SSZ encoding is byte-for-byte the EL wire concatenation that
/// `compute_requests_hash` expects.
pub fn to_encoded_requests(&self) -> Vec<EncodedRequests> {
fn encode<T: SszEncode>(
type_byte: u8,
items: impl IntoIterator<Item = T>,
) -> EncodedRequests {
let mut buf = Vec::new();
buf.push(type_byte);
for item in items {
item.ssz_append(&mut buf);
}
EncodedRequests(Bytes::from(buf))
}

vec![
encode(DEPOSIT_REQUEST_TYPE, self.deposits.iter().cloned()),
encode(WITHDRAWAL_REQUEST_TYPE, self.withdrawals.iter().cloned()),
encode(
CONSOLIDATION_REQUEST_TYPE,
self.consolidations.iter().cloned(),
),
]
}
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

ExecutionRequests::to_encoded_requests() currently always returns 3 EncodedRequests entries, even when a request list is empty (yielding a 1-byte [type] entry). Elsewhere in the repo the EIP-7685 rules are enforced such that entries with empty request_data must be excluded (see validate_execution_requests, which rejects len() < 2). Consider emitting only non-empty request types (and/or filtering out EncodedRequests where is_empty() is true) so the helper is spec-compliant if reused beyond compute_requests_hash.

Copilot uses AI. Check for mistakes.
Comment on lines +95 to 105
let (new_payload_request, execution_witness) = super::decode_eip8025(bytes).map_err(|err| {
ExecutionError::Internal(format!("failed to decode EIP-8025 input: {err}"))
})?;

let request_root = new_payload_request.hash_tree_root(&Sha2Hasher);
let valid = validate_eip8025_execution(&new_payload_request, execution_witness, crypto).is_ok();

Ok(ProgramOutput {
new_payload_request_root: request_root,
valid: true,
valid,
})
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

The byte entrypoint sets valid based on validate_eip8025_execution(...).is_ok() but there’s no test covering the post-decode failure path (i.e., decoding succeeds but validation fails and the function must still return Ok(ProgramOutput { valid: false, ... })). Adding a test that builds a minimally valid wire payload with an intentionally-invalid block_hash (or versioned-hash mismatch) would lock in the intended behavior and prevent regressions back to returning an error.

Copilot uses AI. Check for mistakes.
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 21, 2026

Greptile Summary

This PR aligns the EIP-8025 SSZ types with the Electra consensus spec by moving execution requests out of ExecutionPayload into a typed ExecutionRequests container, adds a raw wire-format entrypoint (execution_program_eip8025_bytes), and shares the validation logic between both entrypoints via validate_eip8025_execution. The to_encoded_requests() method always emits 3 entries (one per request type), which is safe because compute_requests_hash already uses a > 1 length guard to skip empty-type entries, matching the EIP-7685 spec's get_execution_requests_list filter.

Confidence Score: 5/5

Safe to merge; all remaining findings are style/diagnostic P2 items with no correctness impact.

The SSZ type changes correctly mirror the Electra spec. compute_requests_hash's > 1 guard correctly handles the always-3-entries output of to_encoded_requests(). Request sizes verified in tests match EIP-6110/7002/7251 wire formats. MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD=2 is the correct Electra value. The only remaining findings are a stale comment and the intentional silent error conversion in the bytes entrypoint.

crates/guest-program/src/l1/program.rs — the is_ok() error swallowing in execution_program_eip8025_bytes may complicate future debugging.

Important Files Changed

Filename Overview
crates/common/types/eip8025_ssz.rs Removes deposit/withdrawal/consolidation from ExecutionPayload and moves them into a typed ExecutionRequests container; adds to_encoded_requests() that always emits 3 entries (handled correctly downstream by compute_requests_hash's > 1 guard); corrects MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD to 2 (Electra value); renames spec constants for clarity; removes now-unused ExecutionPayloadHeader and NewPayloadRequestHeader.
crates/guest-program/src/l1/program.rs Extracts shared validation logic into validate_eip8025_execution; adds execution_program_eip8025_bytes for the raw wire-format entrypoint; the bytes entrypoint silently converts all validation/execution errors to valid:false (P2: internal errors become indistinguishable from protocol invalidity).
crates/guest-program/src/l1/mod.rs Re-exports the new execution_program_eip8025_bytes function, gated correctly behind #[cfg(feature = "eip-8025")].
crates/guest-program/src/lib.rs Adds a conditional re-export of execution_program_eip8025_bytes in the public execution module, consistently double-gated on not(l2) and eip-8025.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Wire bytes\nssz_len ++ ssz_bytes ++ rkyv_bytes] --> B[decode_eip8025]
    B --> C[NewPayloadRequest\nExecutionRequests\nExecutionWitness]
    C --> D[hash_tree_root\n→ request_root]
    C --> E[validate_eip8025_execution]
    E --> F[new_payload_request_to_block]
    F --> G[execution_requests\n.to_encoded_requests]
    G --> H[compute_requests_hash\nskips len==1 entries]
    H --> I[Block]
    I --> J{block_hash match?}
    J -- No --> K[Err block_hash mismatch]
    J -- Yes --> L[validate_versioned_hashes]
    L --> M{hashes match?}
    M -- No --> N[Err versioned hashes mismatch]
    M -- Yes --> O[execute_blocks]
    O --> P{ok?}
    P -- No / execution_program --> Q[Err propagated]
    P -- No / eip8025_bytes --> R[ProgramOutput\nvalid=false]
    P -- Yes --> S[ProgramOutput\nvalid=true]
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: crates/guest-program/src/l1/program.rs
Line: 100

Comment:
**Silent error swallowing on validation failure**

`validate_eip8025_execution(…).is_ok()` converts all errors — including internal panics turned into `Err`, OOM, or logic bugs — to `valid: false`. A caller of `execution_program_eip8025_bytes` can never distinguish "this payload was legitimately invalid per the protocol" from "an unexpected internal error occurred during execution." At minimum, consider logging or propagating the error variant so that operator-level diagnostics are possible when this runs outside a strict zkVM context.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: crates/guest-program/src/l1/program.rs
Line: 72

Comment:
**Misleading "consuming" comment**

The comment says "before consuming the payload", but `hash_tree_root` takes `&self` — it borrows, not consumes, `new_payload_request`. The comment was meaningful when the old code moved the value into `new_payload_request_to_block`; it is now misleading.

```suggestion
    // Compute the hash_tree_root of the payload before validation.
    let request_root = new_payload_request.hash_tree_root(&Sha2Hasher);
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "refactor(l1): streamline EIP-8025 execut..." | Re-trigger Greptile

})?;

let request_root = new_payload_request.hash_tree_root(&Sha2Hasher);
let valid = validate_eip8025_execution(&new_payload_request, execution_witness, crypto).is_ok();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Silent error swallowing on validation failure

validate_eip8025_execution(…).is_ok() converts all errors — including internal panics turned into Err, OOM, or logic bugs — to valid: false. A caller of execution_program_eip8025_bytes can never distinguish "this payload was legitimately invalid per the protocol" from "an unexpected internal error occurred during execution." At minimum, consider logging or propagating the error variant so that operator-level diagnostics are possible when this runs outside a strict zkVM context.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/guest-program/src/l1/program.rs
Line: 100

Comment:
**Silent error swallowing on validation failure**

`validate_eip8025_execution(…).is_ok()` converts all errors — including internal panics turned into `Err`, OOM, or logic bugs — to `valid: false`. A caller of `execution_program_eip8025_bytes` can never distinguish "this payload was legitimately invalid per the protocol" from "an unexpected internal error occurred during execution." At minimum, consider logging or propagating the error variant so that operator-level diagnostics are possible when this runs outside a strict zkVM context.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread crates/guest-program/src/l1/program.rs Outdated
@@ -72,36 +72,36 @@ pub fn execution_program(
// Compute the hash_tree_root before consuming the payload.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Misleading "consuming" comment

The comment says "before consuming the payload", but hash_tree_root takes &self — it borrows, not consumes, new_payload_request. The comment was meaningful when the old code moved the value into new_payload_request_to_block; it is now misleading.

Suggested change
// Compute the hash_tree_root before consuming the payload.
// Compute the hash_tree_root of the payload before validation.
let request_root = new_payload_request.hash_tree_root(&Sha2Hasher);
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/guest-program/src/l1/program.rs
Line: 72

Comment:
**Misleading "consuming" comment**

The comment says "before consuming the payload", but `hash_tree_root` takes `&self` — it borrows, not consumes, `new_payload_request`. The comment was meaningful when the old code moved the value into `new_payload_request_to_block`; it is now misleading.

```suggestion
    // Compute the hash_tree_root of the payload before validation.
    let request_root = new_payload_request.hash_tree_root(&Sha2Hasher);
```

How can I resolve this? If you propose a fix, please make it concise.

Comment thread crates/guest-program/src/l1/program.rs Outdated
/// The wire format is `[ssz_len: u32 LE][ssz_bytes][rkyv_bytes]`, matching
/// [`decode_eip8025`](super::decode_eip8025).
#[cfg(feature = "eip-8025")]
pub fn execution_program_eip8025_bytes(
Copy link
Copy Markdown
Contributor

@avilagaston9 avilagaston9 Apr 23, 2026

Choose a reason for hiding this comment

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

Thanks for the PR! Looks good to me.

One thing: since execution_program is already under the eip-8025 feature flag, I'd keep only the bytes version and drop the typed one. I think there's no reason to maintain the typed entrypoint.

WDYT @ilitteri?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep, I was thinking about the same when making the PR but prefered to be conservative in removing existing APIs.

Leaving that decision to you guys!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I agree, let's keep the bytes version only.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sounds good. I'll adjust and reping here when ready.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

…nical execution_program

Signed-off-by: jsign <jsign.uy@gmail.com>
@jsign
Copy link
Copy Markdown
Contributor Author

jsign commented Apr 26, 2026

@ilitteri, is this mergeable? :)

@ilitteri
Copy link
Copy Markdown
Collaborator

@ilitteri, is this mergeable? :)

We need one more approval. I'd get it asap.

@avilagaston9 avilagaston9 added this pull request to the merge queue Apr 27, 2026
Merged via the queue into lambdaclass:main with commit 81484be Apr 27, 2026
53 checks passed
avilagaston9 added a commit that referenced this pull request Apr 27, 2026
Brings in main commits since the prior merge: #6516 EIP-8025 compliance
(Electra-aligned ExecutionRequests typed container in NewPayloadRequest,
MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD corrected from 1 to 2,
to_encoded_requests() helper for EIP-7685 bytes, removal of
ExecutionPayloadHeader/NewPayloadRequestHeader, new byte-oriented
execution_program entrypoint that decodes the wire format internally and
returns valid: false instead of erroring on post-decode failures), #6463
BAL withdrawal reverse check (DB->BAL direction so a malicious builder
can't omit a withdrawal recipient from the BAL), #6505 Kademlia k-bucket
revert (PeerTableServer::spawn no longer takes a node_id), plus snap-sync
observability + dashboards (#6470), pivot-update crash fix (#6475),
weighted peer selection (#6428), txpool_contentFrom/txpool_inspect RPC
(#6446), block-by-block exec fallback (#6464), Amsterdam EELS branch pin
(#6495), and rollup store SQLite v9->v10 migration (#6514).

Conflict resolutions:
- crates/common/types/stateless_ssz.rs: this branch had already moved
  the EIP-8025 SSZ types out of crates/common/types/eip8025_ssz.rs into
  stateless_ssz.rs and tucked the native-rollup containers below them.
  Kept that layout, applied #6516's content updates to the EIP-8025
  section (renamed spec-limit constants, ExecutionRequests typed
  container with to_encoded_requests, dropped header types and their
  tests), pulled in the EncodedRequests import, and kept both the new
  test_execution_requests_to_encoded_bytes and the branch's stateless
  round-trip tests.
- crates/guest-program/src/l1/program.rs: adopted #6516's new
  execution_program(bytes: &[u8], crypto) API with the internal
  decode_eip8025 call, the validate_eip8025_execution helper, and the
  decode-failure test. Rewrote all `eip-8025` feature gates as
  `experimental-devnet` and all `eip8025_ssz::` paths as
  `stateless_ssz::` to match this branch's renames.
- crates/guest-program/bin/{sp1,risc0,zisk,openvm}/src/main.rs: applied
  #6516's simplification (drop decode_eip8025 import, pass &input
  straight to execution_program) under the experimental-devnet feature
  gate. Also flipped the rkyv::rancor::Error import gate from the old
  `eip-8025` name to `experimental-devnet` so the non-devnet build still
  has the import it needs.
- crates/prover/src/backend/exec.rs: kept #6516's updated comment ("raw
  input bytes" instead of "(NewPayloadRequest, ExecutionWitness)") under
  the experimental-devnet feature gate.

Auto-merged regions checked: crates/vm/backends/levm/mod.rs picked up
all of #6463's Part B (DB->BAL) reverse check intact, and
cmd/ethrex/l2/initializers.rs picked up #6505's PeerTableServer::spawn
signature change. Verified cargo fmt --all clean, cargo check --workspace
clean, cargo check --workspace --tests clean, and cargo check -p
ethrex-guest-program --features experimental-devnet --tests clean.
avilagaston9 added a commit that referenced this pull request Apr 27, 2026
crates/guest-program/src/l1/program.rs::execution_program ("Decode and
execute the L1 stateless validation program from EIP-8025 wire bytes.")
— the merge resolution kept only the wire-format paragraph and dropped
that opening sentence by accident, so the function lost the high-level
"what does this do" line and the doc started straight into a format
description.

Also trim the "Stateless validation limits" preamble in
crates/common/types/stateless_ssz.rs down to just the section divider.
The block underneath was editorializing about how stateless.py uses
untyped Python lists and how only MAX_WITNESS_HEADERS has spec backing
— useful context the day the constants were chosen, but it doesn't
help anyone reading the file now and the per-constant docstrings below
are sufficient on their own.
avilagaston9 added a commit that referenced this pull request Apr 27, 2026
….py at the actual NativeRollup.sol/MPTProof.sol location so the Blockscout verification script can find its sources to flatten. The script lives at tooling/l2/dev/, so a single ".." resolves to tooling/l2/, and the previous "../crates/vm/levm/contracts" path was doubly wrong: it had only one parent step (giving tooling/l2/crates/vm/levm/contracts, which doesn't exist) and pointed at the pre-PR-#6516 location for the L1 native-rollup contracts. After the contracts were moved to crates/l2/contracts/src/nativeRollup/l1/ during the experimental-devnet unification, the script raised FileNotFoundError on flatten_source(), so Step 6 of the native-rollup deployment demo (verifying the deployed NativeRollup against a local Blockscout) couldn't run. Update the path to "../../../crates/l2/contracts/src/nativeRollup/l1" so Step 6 succeeds.
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.

6 participants