linera-io/linera-protocol · error
Invalid address length: {s}
Error message
Invalid address length: {s} What it means
AccountOwner::from_str parses 0x-prefixed hex strings into one of three shapes: 64 hex chars → Address32 (a CryptoHash), 40 hex chars → Address20 (an EVM-style address), 2 hex chars → Reserved(u8). The 'Invalid address length' bail guards the 40-char branch when the decoded bytes are not exactly 20; since 40 hex characters always decode to 20 bytes, this arm is effectively a defensive unreachable check — malformed input more commonly surfaces as the adjacent 'Invalid address value' error or a hex decode error. Note this is a returned Err (anyhow::bail!), not a panic.
Source
Thrown at linera-base/src/identifiers.rs:1229
};
Ok(())
}
}
impl std::str::FromStr for AccountOwner {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(s) = s.strip_prefix("0x") {
if s.len() == 64 {
if let Ok(hash) = CryptoHash::from_str(s) {
return Ok(AccountOwner::Address32(hash));
}
} else if s.len() == 40 {
let address = hex::decode(s)?;
if address.len() != 20 {
anyhow::bail!("Invalid address length: {s}");
}
let address = <[u8; 20]>::try_from(address.as_slice()).unwrap();
return Ok(AccountOwner::Address20(address));
}
if s.len() == 2 {
let bytes = hex::decode(s)?;
if bytes.len() == 1 {
let value = u8::from_be_bytes(bytes.try_into().expect("one byte"));
return Ok(AccountOwner::Reserved(value));
}
}
}
anyhow::bail!("Invalid address value: {s}");
}
}
impl fmt::Display for ChainId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {View on GitHub (pinned to 6c226ddcb3)
Solutions
- Use exactly 40 hex characters (20 bytes) with the 0x prefix for EVM-style Address20 owners
- Use 64 hex characters for 32-byte Address32 owners and 2 hex characters for Reserved values
- Validate owner strings in tests with AccountOwner::from_str before shipping configs or scripts
Example fix
// before let owner: AccountOwner = "0x8da6bac7dc85b63b4326c28926a1c168786e3fc912".parse()?; // 41 hex chars // after let owner: AccountOwner = "0x8da6bac7dc85b63b4326c28926a1c168786e3fc9".parse()?; // 40 hex chars
Defensive patterns
Strategy: validation
Validate before calling
fn is_valid_account_owner(s: &str) -> bool {
let Some(hex) = s.strip_prefix("0x") else { return false };
match hex.len() {
64 => hex.chars().all(|c| c.is_ascii_hexdigit()), // Address32
40 => hex.chars().all(|c| c.is_ascii_hexdigit()), // Address20
2 => hex.chars().all(|c| c.is_ascii_hexdigit()), // Reserved(u8)
_ => false,
}
}
assert!(is_valid_account_owner(input), "not a valid AccountOwner: {input}"); Type guard
fn parse_account_owner(s: &str) -> Option<linera_base::identifiers::AccountOwner> {
s.parse().ok()
} Try / catch
match "0x8da...".parse::<AccountOwner>() {
Ok(owner) => use_owner(owner),
Err(e) => eprintln!("invalid AccountOwner (need 0x + 64/40/2 hex chars): {e:#}"),
} Prevention
- Always prefix owner addresses with 0x and use exactly 40 hex chars for EVM-style owners
- Parse owner strings at the system boundary (CLI/config/GraphQL) and reject early
- Round-trip test: owner.to_string().parse::<AccountOwner>().unwrap() for values you emit
When it happens
Trigger: Parsing an owner string whose hex length doesn't match the 64/40/2 forms (falls through to 'Invalid address value'), contains non-hex characters (hex decode error), or — for this specific message — any hypothetical path where a 40-char string decodes to a length other than 20 bytes. Callers hit it via AccountOwner::from_str in CLI parsing, GraphQL input, or config deserialization.
Common situations: Copying Ethereum addresses with checksummed mixed-case (hex::decode accepts mixed case, so usually fine) or with missing/extra digits; passing a 20-byte address where a 32-byte owner (CryptoHash) is expected or vice versa; forgetting the 0x prefix; feeding a contract address where an AccountOwner string is wanted.
Related errors
- Invalid address value: {s}
- Expected a tip hash string, but got {invalid_data:?} instead
- We should have one genesis config path and one optional name
- For Storage service, the formatting has to be service:endpoi
- We should have one endpoint and one namespace
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/03369bd7f455de8b.
Report an issue: GitHub.