nautechsystems/nautilus_trader · error · anyhow::Error
Failed to write block data: {e}
Error message
Failed to write block data: {e} What it means
write_block_binary serializes one Block into PostgreSQL COPY BINARY row format and sends it with PgCopyIn::send; a send failure is wrapped as 'Failed to write block data'. Because the error propagates via `?`, the whole copy_blocks batch aborts. This is a mid-stream write failure — transport error or the server having already rejected the COPY session.
Source
Thrown at crates/adapters/blockchain/src/cache/copy.rs:432
let l1_gas_used_bytes = (l1_gas_used as i64).to_be_bytes();
row_data.write_all(&(l1_gas_used_bytes.len() as i32).to_be_bytes())?;
row_data.write_all(&l1_gas_used_bytes)?;
} else {
row_data.write_all(&(-1i32).to_be_bytes())?; // NULL value
}
if let Some(l1_fee_scalar) = block.l1_fee_scalar {
let l1_fee_scalar_bytes = (l1_fee_scalar as i64).to_be_bytes();
row_data.write_all(&(l1_fee_scalar_bytes.len() as i32).to_be_bytes())?;
row_data.write_all(&l1_fee_scalar_bytes)?;
} else {
row_data.write_all(&(-1i32).to_be_bytes())?; // NULL value
}
copy_in
.send(row_data)
.await
.map_err(|e| anyhow::anyhow!("Failed to write block data: {e}"))?;
Ok(())
}
/// Writes a single pool swap 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_swap_binary(
&self,
copy_in: &mut sqlx::postgres::PgCopyIn<sqlx::pool::PoolConnection<sqlx::Postgres>>,
chain_id: u32,
swap: &PoolSwap,
) -> anyhow::Result<()> {
use std::io::Write;
let mut row_data = Vec::new();
row_data.write_all(&19u16.to_be_bytes())?;View on GitHub (pinned to 18893faf8b)
Solutions
- Retry copy_blocks with a smaller batch size to isolate the failing block and survive transient drops
- Compare the fields written by write_block_binary to the COPY column list for block after recent migrations
- Check DB server logs for why the COPY session aborted
- Enable connection health checks (test_before_acquire) and re-run the backfill
Example fix
// before
handler.copy_blocks(chain_id, &all_blocks).await?;
// after
for chunk in all_blocks.chunks(500) {
handler.copy_blocks(chain_id, chunk).await?;
} Defensive patterns
Strategy: retry
Try / catch
let result = handler.copy_blocks(chain_id, &blocks).await;
match result {
Err(e) if e.to_string().contains("Failed to write block data") => {
tokio::time::sleep(Duration::from_secs(5)).await;
handler.copy_blocks(chain_id, &blocks).await?; // retry whole batch; COPY is atomic
}
other => other?,
} Prevention
- Chunk block batches (e.g. 500) so retries are cheap
- Monitor network stability for long backfills
- Recheck write_block_binary field layout after schema migrations
- Check server logs at the first sign of mid-stream aborts
When it happens
Trigger: Connection loss while streaming a large block batch, server abort of the COPY (e.g. previous row triggered an error surfaced on the next send), or serialization code writing bytes inconsistent with the declared column list.
Common situations: Multi-thousand-block backfills across an unreliable network/VPN; DB restart mid-backfill; after a migration changed the block table but write_block_binary still writes the old field layout.
Related errors
- Failed to write pool swap data: {e}
- Failed to write pool liquidity update data: {e}
- Failed to write COPY header: {e}
- Failed to load active execution intent: {e}
- Failed to start replacement transaction persistence: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/330f9c2a362bf316.
Report an issue: GitHub.