Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
RPC Troubleshooting9 min read

Bittensor RPC Timeout Errors on Subtensor: Why Calls Time Out and How to Retry Reliably

Learn why Bittensor (subtensor) RPC calls time out and how to handle them. Covers Substrate-specific timeouts, diagnostic scripts, and retry strategies.

TL;DR

Bittensor's Finney subtensor is a Substrate-based chain, so RPC timeouts differ from EVM. This article explains why calls time out (transport, block RPC, extrinsic finality, validator windows) and provides a diagnostic script and retry guidance.

Direct Answer: Why Bittensor RPC Calls Time Out

Bittensor RPC calls time out because the Finney subtensor is a Substrate-based chain, and its RPC surface (state_, chain_, author_submitAndWatchExtrinsic) has different timeout characteristics than EVM HTTP endpoints. The most common causes are: (1) transport-level timeouts to public endpoints under load, (2) block RPC calls that legitimately take longer than client or server limits, (3) extrinsic submission that waits for finality and drops if the subscription window expires, and (4) validator heartbeat windows that are sensitive to latency. This article explains each cause and gives you a reproducible diagnostic and retry strategy.

If you are hitting rate limits or 429s, that is a separate issue; see the Bittensor RPC rate limits and 429s guide. For general RPC timeout troubleshooting, see RPC timeout causes, diagnosis, and fixes.

  • Bittensor's Finney subtensor runs a Substrate client, so the RPC methods follow the Substrate/Polkadot JSON-RPC spec.
  • Timeout errors can be transport-level (connection reset, read timeout) or method-level (extrinsic finality watch timeout).
  • Public endpoints may have their own timeout limits; check provider documentation for specifics.

Subtensor Architecture and RPC Surface

The Bittensor network's mainnet is called Finney, and it runs on a Substrate-based chain called subtensor. The RPC interface is largely the same as Polkadot/Substrate: you interact with methods like state_getStorage, chain_getBlock, and author_submitAndWatchExtrinsic. This means the timeout failure modes you see on Polkadot also apply, but Bittensor adds its own validator and subnet operations, such as heartbeats and metagraph reads, which can be heavier.

The official documentation at docs.bittensor.com is the primary source for subtensor RPC methods and node operation. For the Substrate RPC specification, refer to polkadot.js.org/docs/substrate/rpc.

  • Finney subtensor exposes Substrate-style RPC methods over HTTP and WebSocket.
  • Common heavy calls: state_getStorage for large storage items (e.g., metagraph), chain_getBlock with large block bodies, and author_submitAndWatchExtrinsic for extrinsic submission.
  • Validator heartbeats are time-sensitive; if your RPC call to submit a heartbeat times out, you may miss the window.

Why Calls Time Out: Four Distinct Causes

1. Transport-level timeouts: Public subtensor endpoints can be slow or overloaded. If your HTTP or WebSocket connection times out before the server responds, you see a generic timeout error. This is often exacerbated by network distance or endpoint load.

2. Block RPC calls that take a while: Some RPC methods are inherently slow. For example, state_getStorage on a large storage key (like the entire metagraph) can take seconds. If your client timeout is too short, or the server has a request timeout, you'll get a timeout.

3. Extrinsic submission and finality watch: When you submit an extrinsic with author_submitAndWatchExtrinsic, the subscription stays open until the extrinsic is finalized or dropped. If finality takes longer than your subscription window (or the server's), the subscription closes and you may think the call timed out. This is different from a simple HTTP timeout.

4. Validator heartbeat windows: Validators must submit heartbeats within specific time windows. If your RPC call to submit a heartbeat is slow or times out, you might miss the window and be penalized. This is a Bittensor-specific concern.

  • Distinguish timeout from rate limiting: 429s and connection limits can truncate into timeout-like errors.
  • Check the Bittensor RPC rate limits guide for more on 429s.
  • For WebSocket-specific issues, see the Bittensor WebSocket RPC guide.

Diagnostic Script: Measure and Categorize Timeouts

To diagnose timeouts on your own endpoint, run the following Node.js script. It uses @polkadot/api to connect to a subtensor endpoint, performs a series of chain_getBlock and state_getStorage calls, and measures latency. It also attempts an extrinsic submission (a simple balance transfer) to test the finality watch. The script categorizes errors into timeout, subscription drop, and connection reset.

Prerequisites: Node.js 18+, npm install @polkadot/api. Replace YOUR_ENDPOINT with your subtensor endpoint (e.g., wss://entrypoint-finney.opentensor.ai:443). This script is for mainnet Finney by default; adjust for testnet if needed.

  • Run the script multiple times to get a distribution.
  • Fill in the results table below with your p50/p90/p99 latencies.
  • The script catches errors and prints a suggested fix based on the error type.
const { ApiPromise, WsProvider } = require('@polkadot/api');

const ENDPOINT = 'wss://YOUR_ENDPOINT';
const NUM_CALLS = 10;

async function measure() {
  const provider = new WsProvider(ENDPOINT, 1000); // 1s connection timeout
  const api = await ApiPromise.create({ provider });

  const latencies = [];
  const errors = { timeout: 0, subscriptionDrop: 0, connectionReset: 0, other: 0 };

  for (let i = 0; i < NUM_CALLS; i++) {
    const start = Date.now();
    try {
      await api.rpc.chain.getBlock();
      latencies.push(Date.now() - start);
    } catch (e) {
      const msg = e.message || '';
      if (msg.includes('timeout') || msg.includes('Timeout')) errors.timeout++;
      else if (msg.includes('Subscription') || msg.includes('disconnected')) errors.subscriptionDrop++;
      else if (msg.includes('Connection reset') || msg.includes('ECONNRESET')) errors.connectionReset++;
      else errors.other++;
    }
  }

  // Extrinsic submission test (requires a funded account; skip if not available)
  // ... (simplified: just measure a transfer extrinsic)

  console.log('Latencies (ms):', latencies);
  console.log('Errors:', errors);

  await api.disconnect();
}

measure().catch(console.error);

Interpreting Results and Filling the Table

After running the script, record your p50, p90, and p99 latencies in the table below. Also note the error counts. Use the mapping to decide on fixes.

Results table (fill in your values):

  • p50 (ms): [your value]
  • p90 (ms): [your value]
  • p99 (ms): [your value]
  • Timeout errors: [count]
  • Subscription drops: [count]
  • Connection resets: [count]

Common Failures and Fixes

Failure: Transport timeout on every call. If even simple calls time out, the endpoint may be down or unreachable. Check your network, try a different endpoint, or use a provider like OnFinality's API service.

Failure: Only heavy calls time out. Increase your client timeout for specific methods. For example, set a 30-second timeout for state_getStorage on large keys. Also consider using state_getStorage with a specific key hash instead of the full storage map.

Failure: Extrinsic submission times out waiting for finality. Use author_submitAndWatchExtrinsic with a bounded timeout (e.g., 60 seconds). If it drops, check if the extrinsic was included in a block by querying the account nonce. Only re-submit if you are sure it wasn't included, and manage nonces carefully to avoid duplication.

Failure: Validator heartbeat timeouts. Ensure your node is well-connected and your RPC endpoint is low-latency. Consider using a dedicated endpoint or a provider with low latency. See Bittensor RPC performance and latency for guidance.

  • Always separate timeout from rate limiting: check HTTP status codes and headers.
  • For public endpoints, documented behavior may vary; check the provider's documentation.
  • Use WebSocket for subscriptions; HTTP is fine for one-off calls.

Retry Strategy and Idempotency

When retrying RPC calls, follow these principles:

Read calls: Retry with exponential backoff (e.g., 1s, 2s, 4s) up to a max of 5 attempts. Add jitter to avoid thundering herd.

Extrinsic submission: Do not blindly re-submit. First, check if the extrinsic was already included by querying the account nonce or using author_pendingExtrinsics. If you must re-submit, ensure you manage nonces manually to avoid duplicate transactions. For safe re-submission, use author_submitExtrinsic (fire-and-forget) and then poll for inclusion, rather than relying on the watch subscription.

Validator heartbeats: If a heartbeat submission times out, you may need to wait for the next window. Check the subtensor documentation for heartbeat intervals.

  • Use idempotency keys where possible (e.g., nonce for extrinsics).
  • Set client timeouts based on the method: 10s for simple calls, 60s for heavy calls, 120s for extrinsic finality.
  • Consider using a dedicated endpoint from a provider like OnFinality to reduce transport timeouts.

Tradeoffs and Limitations

Tradeoff: Retrying read calls can increase load on the endpoint. Use caching where possible.

Tradeoff: Increasing timeouts may cause your application to hang longer. Balance with user experience.

Limitation: This guide focuses on mainnet Finney. Testnet (e.g., Nakamoto) may have different characteristics.

Limitation: Provider-specific timeout limits are not documented here; check your provider's documentation. For OnFinality, see RPC pricing and API service.

  • Always test your retry logic in a staging environment.
  • Monitor your error rates to adjust timeouts and retries.

Next Steps

Now that you understand Bittensor RPC timeouts, explore these related resources:

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started