linera-io/linera-protocol · error · ChainError

Block proposal has size

Error message

Block proposal has size {0} which is too large

What it means

ProposedBlock::check_proposal_size (linera-chain/src/data_types/mod.rs:141-148) bcs-serializes the block and rejects it if it exceeds the committee policy's maximum_block_proposal_size — 13,000,000 bytes in the default production policy (linera-execution/src/policy.rs:326), u64::MAX in the test default (policy.rs:241). Both stage_block_execution (linera-core/src/chain_worker/state.rs:2424) and try_handle_block_proposal (state.rs:2529) enforce the limit, so oversized proposals never execute.

Solutions

  1. Split the block into several smaller proposals at the same or successive heights
  2. Publish large data as blobs through the blob-storage flow first and reference them by BlobId in the block instead of inlining the bytes
  3. If you operate the committee, raise maximum_block_proposal_size in the policy (genesis config or policy update path)

Example fix

// before: inlining a big blob's bytes into the block
let block = ProposedBlock { transactions: vec![Transaction::ExecuteOperation(
    Operation::system(SystemOperation::PublishDataBlob(blob_bytes)),
)], .. };

// after: publish via blob service, reference by id in (smaller) blocks
let blob_id = client.publish_data_blob(blob_bytes).await?; // stored out-of-band
let block = ProposedBlock { transactions: vec![Transaction::ExecuteOperation(
    Operation::system(SystemOperation::PublishBlob { blob_id }),
)], .. };
Defensive patterns

Strategy: validation

Validate before calling

// Cheap pre-submit mirror of check_proposal_size (data_types/mod.rs:141):
let size = bcs::serialized_size(&block)?;
let limit = usize::try_from(policy.maximum_block_proposal_size).unwrap_or(usize::MAX);
if size > limit {
    anyhow::bail!("block proposal is {size} bytes > limit {limit}; split it or use blobs");
}

Try / catch

match result {
    Err(ChainError::BlockProposalTooLarge(size)) => {
        // split the block, or move large payloads to the blob service and reference
        // BlobIds; retrying the identical block cannot succeed
    }
    other => other?,
}

Prevention

When it happens

Trigger: Blocks inlining large published blobs or many big transactions (check_proposal_size counts the whole serialized ProposedBlock); a committee that lowered maximum_block_proposal_size (it is configurable via genesis/policy, linera-service/src/cli/command.rs:317); tests deliberately setting tiny limits (e.g., fee_consumption.rs uses 61 bytes).

Common situations: Publishing large data (images, WASM bytecode, state blobs) inside the block instead of via the blob service; batch jobs packing thousands of operations into one block; a policy change shrinking limits while old batching logic remained.

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/193b556ecb3ecb83. Report an issue: GitHub.

Appendix: source

Thrown at linera-chain/src/data_types/mod.rs:143

    pub fn operations(&self) -> impl Iterator<Item = &Operation> {
        self.transactions.iter().filter_map(|tx| match tx {
            Transaction::ExecuteOperation(operation) => Some(operation),
            Transaction::ReceiveMessages(_) => None,
        })
    }

    /// Returns all incoming bundles in this block.
    pub fn incoming_bundles(&self) -> impl Iterator<Item = &IncomingBundle> {
        self.transactions.iter().filter_map(|tx| match tx {
            Transaction::ReceiveMessages(bundle) => Some(bundle),
            Transaction::ExecuteOperation(_) => None,
        })
    }

    /// Checks that the serialized size of this block does not exceed the given maximum.
    pub fn check_proposal_size(&self, maximum_block_proposal_size: u64) -> Result<(), ChainError> {
        let size = bcs::serialized_size(self)?;
        ensure!(
            size <= usize::try_from(maximum_block_proposal_size).unwrap_or(usize::MAX),
            ChainError::BlockProposalTooLarge(size)
        );
        Ok(())
    }
}

#[async_graphql::ComplexObject]
impl ProposedBlock {
    /// Metadata about the transactions in this block.
    async fn transaction_metadata(&self) -> Vec<TransactionMetadata> {
        self.transactions
            .iter()
            .map(TransactionMetadata::from_transaction)
            .collect()
    }
}

View on GitHub (pinned to 6c226ddcb3)