linera-io/linera-protocol · error
invalid topic: {e}
Error message
invalid topic: {e} What it means
One of the entries inside the topics list did not decode as a B256 (32-byte) value. Every Ethereum topic must be an RLP string of exactly 32 bytes; anything else — wrong length, a list, or a truncated tail — fails here.
Source
Thrown at linera-bridge/src/proof/mod.rs:580
let address = <Address as alloy_rlp::Decodable>::decode(&mut log_data_buf)
.map_err(|e| anyhow!("invalid log address: {e}"))?;
// Decode topics list
let topics_header = alloy_rlp::Header::decode(&mut log_data_buf)
.map_err(|e| anyhow!("invalid topics list RLP: {e}"))?;
ensure!(topics_header.list, "topics must be an RLP list");
ensure!(
log_data_buf.len() >= topics_header.payload_length,
"topics payload extends past log boundary"
);
let mut topics_data = &log_data_buf[..topics_header.payload_length];
log_data_buf = &log_data_buf[topics_header.payload_length..];
let mut topics = Vec::new();
while !topics_data.is_empty() {
let topic = <B256 as alloy_rlp::Decodable>::decode(&mut topics_data)
.map_err(|e| anyhow!("invalid topic: {e}"))?;
topics.push(topic);
}
// Decode log data (byte string)
let data_header = alloy_rlp::Header::decode(&mut log_data_buf)
.map_err(|e| anyhow!("invalid log data RLP: {e}"))?;
ensure!(
!data_header.list,
"log data must be a byte string, not a list"
);
ensure!(
log_data_buf.len() >= data_header.payload_length,
"log data extends past log boundary"
);
let log_bytes = log_data_buf[..data_header.payload_length].to_vec();
log_data_buf = &log_data_buf[data_header.payload_length..];
ensure!(View on GitHub (pinned to 6c226ddcb3)
Solutions
- Pre-validate with alloy_primitives::Log::decode, which enforces 32-byte topics exactly.
- Inspect the failing topic's RLP prefix: 0xa0 means a 32-byte string — anything else indicates a malformed topic.
- Fix the event-encoding side so indexed parameters are abi.encode()-padded to 32 bytes.
- Rebuild the receipt proof from the chain.
Example fix
// before: every remaining byte-range is treated as a topic
let topic = <B256 as alloy_rlp::Decodable>::decode(&mut topics_data)
.map_err(|e| anyhow!("invalid topic: {e}"))?;
// after: only accept logs whose topics are canonical 32-byte words
use alloy_rlp::Decodable;
anyhow::ensure!(
alloy_primitives::Log::decode(&mut &log_entry_bytes[..]).is_ok(),
"non-32-byte topic in log — rejecting entry"
); Defensive patterns
Strategy: validation
Validate before calling
use alloy_rlp::Decodable;
fn topics_are_canonical(rlp: &[u8]) -> bool {
alloy_primitives::Log::decode(&mut &rlp[..]).is_ok() // every topic must be 32 bytes
} Try / catch
Err(e) if e.to_string().contains("invalid topic") => reject_proof(e), // permanent data error Prevention
- Pad all indexed event parameters to 32 bytes (standard ABI encoding) in any custom prover.
- Treat any decode family error as proof rejection in bridge verification logic.
When it happens
Trigger: decode_log iterating topics_data: a topic shorter/longer than 32 bytes, a nested list where a string was expected, or a topics payload whose boundary leaves a partial topic at the end.
Common situations: Producers that encode topics with variable-length integers instead of fixed 32-byte words; a topic count/payload length mismatch in hand-built fixtures; bytes shifted by one after an off-by-one in the topics header.
Related errors
- invalid topics list RLP: {e}
- invalid log RLP: {e}
- invalid log address: {e}
- invalid log data RLP: {e}
- block header must be an RLP list
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/7f6396aa1ad68f69.
Report an issue: GitHub.