Skip to content

fix(sdk): return an error instead of panicking on a malformed network balance - #2994

Open
myetcd wants to merge 1 commit into
succinctlabs:mainfrom
myetcd:fix/sdk-malformed-network-balance
Open

myetcd wants to merge 1 commit into
succinctlabs:mainfrom
myetcd:fix/sdk-malformed-network-balance

Conversation

@myetcd

@myetcd myetcd commented Sep 30, 2026

Copy link
Copy Markdown

Motivation

NetworkClient::get_balance returns Result<U256> and propagates RPC failures with ?, but the final step unwraps a parse of a server-controlled string:

pub async fn get_balance(&self) -> Result<U256> {
    let response = self.get_balance_response().await?;
    Ok(U256::from_str(response.balance()).unwrap())
}

GetBalanceResponse::balance() returns the raw proto amount string, so a non-numeric, negative, whitespace-padded, or out-of-range response panics the caller's process instead of surfacing an error. Checked against alloy-primitives 1.5.7 (the version pinned in Cargo.lock), U256::from_str returns Err for "invalid", "1.5", "-1", " 12", and 10^78, so each of those panics at the unwrap(). A malformed or version-incompatible response is exactly the case where a caller wants a loggable error, not a panic mid-proving-pipeline.

The same check turned up a second, quieter problem: U256::from_str("") is Ok(0), so an empty amount does not panic — it silently reports a zero balance and hides the protocol error.

Solution

  • Add a parse_balance(&str) -> Result<U256> helper next to the existing parse_fulfillment_status (same module-level style and pub(super) visibility) that returns a contextual error carrying the offending value — invalid balance returned by prover network: "invalid" — and rejects blank responses explicitly, since U256::from_str maps "" to 0.
  • get_balance now delegates to it: parse_balance(response.balance()). The public signature is unchanged, and valid responses parse exactly as before.
  • Add a unit test covering valid values ("0", "1000", U256::MAX) and malformed ones ("", " ", "invalid", "1.5", "-1", an overflow value), asserting the error message contains the raw value.

This follows the convention already used in the same file: get_market_price_per_pgu parses its server-supplied string fields with .with_context(...), and parse_fulfillment_status was added for the same class of bug in #2909.

pub(super) fn parse_balance(raw: &str) -> Result<U256> {
    if raw.trim().is_empty() {
        return Err(anyhow::anyhow!("invalid balance returned by prover network: {raw:?}"));
    }
    U256::from_str(raw)
        .with_context(|| format!("invalid balance returned by prover network: {raw:?}"))
}

Validation

  • cargo check -p sp1-sdk --features network — clean
  • New test passes — balance_parser_handles_valid_and_invalid_values
  • cargo clippy -p sp1-sdk --features network --lib --tests — no diagnostics for client.rs
  • rustfmt --check crates/sdk/src/network/client.rs — clean
  • git diff --check — clean
test network::client::test::balance_parser_handles_valid_and_invalid_values ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 51 filtered out

Verified on toolchain 1.95.0 (the repo pins channel = "stable", which is not installed in my environment; 1.95.0 is above the workspace rust-version = "1.88"). The zkevm/* and sp1-gpu/* workspace members were excluded from the build because they resolve git dependencies that are unreachable from my network; neither is touched by this change.

PR Checklist

  • Added Tests
  • Added Documentation
  • Breaking changes

… balance

Signed-off-by: myetcd <mytech@139.com>

This branch has not been deployed

No deployments
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