nautechsystems/nautilus_trader · error · anyhow::Error

Failed to write token data: {e}

Error message

Failed to write token data: {e}

What it means

write_token_binary serializes one token row (including decimals with its byte-length prefix) and sends it to the server via the COPY binary sink. This error wraps the send failure. Tokens are copied via copy_tokens, so any sink error surfaces here per row.

Source

Thrown at crates/adapters/blockchain/src/cache/copy.rs:805

        row_data.write_all(&(address_bytes.len() as i32).to_be_bytes())?;
        row_data.write_all(&address_bytes)?;

        let name_bytes = token.name.as_bytes();
        row_data.write_all(&(name_bytes.len() as i32).to_be_bytes())?;
        row_data.write_all(name_bytes)?;

        let symbol_bytes = token.symbol.as_bytes();
        row_data.write_all(&(symbol_bytes.len() as i32).to_be_bytes())?;
        row_data.write_all(symbol_bytes)?;

        let decimals_bytes = (i32::from(token.decimals)).to_be_bytes();
        row_data.write_all(&(decimals_bytes.len() as i32).to_be_bytes())?;
        row_data.write_all(&decimals_bytes)?;

        copy_in
            .send(row_data)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to write token data: {e}"))?;
        Ok(())
    }

    /// Writes a single pool in PostgreSQL binary format.
    ///
    /// Each row in binary format consists of:
    /// - 2-byte field count
    /// - For each field: 4-byte length followed by data (or -1 for NULL)
    async fn write_pool_binary(
        &self,
        copy_in: &mut sqlx::postgres::PgCopyIn<sqlx::pool::PoolConnection<sqlx::Postgres>>,
        chain_id: u32,
        pool: &Pool,
    ) -> anyhow::Result<()> {
        use std::io::Write;
        let mut row_data = Vec::new();

        row_data.write_all(&14u16.to_be_bytes())?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Read the wrapped {e} to determine connection vs data cause
  2. Verify token table schema matches the binary encoding (address, symbol, name, decimals order and types)
  3. Reconnect/retry copy_tokens after transient network failures
  4. Ensure partitions for the chain's token table exist before copying

Example fix

// before: assumes connection is always alive across a long copy
db.copy_tokens(chain, &tokens).await?;
// after: retry with backoff on transient failure
for attempt in 0..3 {
    match db.copy_tokens(chain, &tokens).await {
        Ok(()) => break,
        Err(e) if attempt < 2 && is_transient(&e) => tokio::time::sleep(backoff(attempt)).await,
        Err(e) => return Err(e),
    }
}
Defensive patterns

Strategy: retry

Validate before calling

// confirm token table shape before copy
let cols: Vec<(String,)> = sqlx::query_as(
    "SELECT column_name FROM information_schema.columns WHERE table_name='token' ORDER BY ordinal_position"
).fetch_all(&pool).await?;
assert_eq!(cols.len(), EXPECTED_TOKEN_COLUMNS, "token schema drift");

Try / catch

match db.copy_tokens(chain, &tokens).await {
    Err(e) if is_transient(&e) => {
        tokio::time::sleep(RETRY_DELAY).await;
        db.copy_tokens(chain, &tokens).await?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: copy_in.send(row_data) fails during copy_tokens — connection drop, server-side COPY abort, or bytes not matching the token table's expected binary layout (e.g. wrong decimals length prefix).

Common situations: Schema migration changed column order/types for tokens; connection pool closed mid-sync; proxy/firewall severing long-running COPY connections.

Related errors


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