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
- Read the wrapped {e} to determine connection vs data cause
- Verify token table schema matches the binary encoding (address, symbol, name, decimals order and types)
- Reconnect/retry copy_tokens after transient network failures
- 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
- Run migrations before syncing so the token table matches the binary encoder
- Ensure chain partitions exist before copy_tokens
- Handle token fields that may be NULL (symbol/name) consistently with the encoder
- Monitor connection health; abort early rather than streaming into a dead connection
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
- Failed to write pool fee collect data: {e}
- Failed to write pool data: {e}
- Failed to start COPY operation: {e}
- Failed to finish COPY operation: {e}
- Failed to write COPY trailer: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/33af3104abebeb9e.
Report an issue: GitHub.