nautechsystems/nautilus_trader · error

Sealed transaction payload is truncated

Error message

Sealed transaction payload is truncated

What it means

parse_envelope requires the envelope to contain at least the fixed header (version byte + key id + nonce) plus the AES-GCM authentication tag (ENVELOPE_HEADER_LEN + TAG_LEN). If the byte slice is shorter, it cannot contain a complete sealed envelope and is rejected before any field slicing occurs.

Source

Thrown at crates/adapters/blockchain/src/execution/sealing.rs:408

pub(crate) fn envelope_key_id(envelope: &[u8]) -> anyhow::Result<[u8; KEY_ID_LEN]> {
    Ok(parse_envelope(envelope)?.key_id)
}

struct ParsedEnvelope<'a> {
    key_id: [u8; KEY_ID_LEN],
    nonce: &'a [u8],
    ciphertext_and_tag: &'a [u8],
}

fn parse_envelope(envelope: &[u8]) -> anyhow::Result<ParsedEnvelope<'_>> {
    anyhow::ensure!(
        envelope.len() <= MAX_SEALED_TRANSACTION_BYTES,
        "Sealed transaction payload is {} bytes, exceeding the {} byte limit",
        envelope.len(),
        MAX_SEALED_TRANSACTION_BYTES
    );
    anyhow::ensure!(
        envelope.len() >= ENVELOPE_HEADER_LEN + TAG_LEN,
        "Sealed transaction payload is truncated"
    );
    anyhow::ensure!(
        envelope[0] == ENVELOPE_VERSION,
        "Unsupported signed transaction payload envelope version {}",
        envelope[0]
    );

    let key_id = envelope[1..1 + KEY_ID_LEN]
        .try_into()
        .expect("fixed key ID slice length");
    let nonce_start = 1 + KEY_ID_LEN;
    let ciphertext_start = nonce_start + NONCE_LEN;
    Ok(ParsedEnvelope {
        key_id,
        nonce: &envelope[nonce_start..ciphertext_start],
        ciphertext_and_tag: &envelope[ciphertext_start..],

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check envelope.len() >= ENVELOPE_HEADER_LEN + TAG_LEN before calling unseal and log the actual length
  2. Re-fetch or re-seal the transaction; the stored payload is irrecoverably incomplete
  3. Fix the persistence layer that truncated the bytes (column size, blob write, serialization)

Example fix

// before
unseal(&stored_bytes, ...)?;
// after
if stored_bytes.len() < ENVELOPE_HEADER_LEN + TAG_LEN {
    return Err(anyhow::anyhow!("stored sealed tx is {} bytes, minimum is {}", stored_bytes.len(), ENVELOPE_HEADER_LEN + TAG_LEN));
}
unseal(&stored_bytes, ...)?;
Defensive patterns

Strategy: validation

Validate before calling

fn has_minimum_envelope_len(envelope: &[u8]) -> anyhow::Result<()> {
    anyhow::ensure!(
        envelope.len() >= ENVELOPE_HEADER_LEN + TAG_LEN,
        "envelope too short: {} bytes",
        envelope.len()
    );
    Ok(())
}

Type guard

fn is_complete_envelope(envelope: &[u8]) -> bool {
    envelope.len() >= ENVELOPE_HEADER_LEN + TAG_LEN
}

Try / catch

match unseal(&blob, deployment_id) {
    Ok(tx) => tx,
    Err(e) if e.to_string().contains("truncated") => {
        // treat payload as irrecoverable; re-seal from source of truth
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling unseal() or envelope_key_id() with a byte slice shorter than ENVELOPE_HEADER_LEN + TAG_LEN — e.g. a DB BLOB that was truncated, a partially written file, or slicing off the last 16 tag bytes.

Common situations: Database column truncation (e.g. storing bytes in a fixed-size or length-limited column); failed/interrupted write during persist; a caller passes the plaintext transaction instead of the sealed envelope.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/6c5c14d8c7246e1a. Report an issue: GitHub.