Polygon PoS uses Bor, a fork of go-ethereum, where block production is organized into spans and sprints. A span is a fixed number of sprints during which a selected subset of validators produces blocks; within a span, producers rotate every sprint. The active validator set changes only at span boundaries, typically triggered by a state-sync or checkpoint event. You can read the current producer, validator set, and span/sprint state over RPC using Bor-specific methods like bor_getSnapshot, bor_getSigners, bor_getCurrentValidators, and bor_getCurrentProposer. This guide explains the mechanism, provides runnable code to inspect producer state, and covers troubleshooting for common RPC and sync issues.
How Polygon PoS Bor Organizes Block Production
Polygon PoS uses Bor, a fork of go-ethereum, as its block production layer. Bor combines a Proof-of-Stake validator set with a checkpointing mechanism that anchors finality to Ethereum. The active validator set is selected by staking on Ethereum, and block producers are drawn from this set. Block time is on the order of a couple of seconds, though the exact value is a documented parameter that you can verify on-chain.
To make block production predictable and efficient, Bor organizes producers into spans and sprints. A span is a fixed number of consecutive sprints during which a selected subset of validators is responsible for producing blocks. Within a span, producers rotate: a sprint is a run of consecutive blocks produced by a single validator, after which the next validator in the span takes over. This structure means that 'who produces the next block' is a function of the current span state, not a global round-robin.
The validator set changes only at span boundaries, typically triggered by a state-sync or checkpoint event. This design reduces the frequency of set changes and makes producer selection deterministic within a span. For a deeper look at how this fits into the broader Polygon network, see the Polygon network page.
- Span: a fixed number of sprints over which a selected validator subset produces blocks.
- Sprint: a run of consecutive blocks produced by a single validator.
- Validator set changes occur at span boundaries, often via state-sync or checkpoint events.
- Producer selection is derived from the validator set and the span/seed, making it computable from the set.
Reading Producer State with Bor-Specific RPC Methods
Bor exposes several non-standard JSON-RPC methods under the bor_ namespace. These methods are not part of the standard Ethereum JSON-RPC specification, so a plain Ethereum endpoint or a non-Bor chain will return a method-not-found error. The key methods for reading producer state are:
bor_getSnapshot(blockNumber) returns the current span/sprint snapshot, including the validator set, the current span and sprint numbers, and the producer. bor_getSigners(blockNumber) returns the list of signers for a given block. bor_getCurrentValidators() returns the current validator set. bor_getCurrentProposer() returns the address of the validator that will produce the next block.
You can also use standard eth_getBlockByNumber to fetch block metadata, including Bor-specific fields like the signer (often available as miner or via bor_getAuthor). To confirm your node is on the correct fork, check eth_syncing and compare block timestamps with a trusted source.
- bor_getSnapshot: returns span/sprint snapshot with validator set and producer.
- bor_getSigners: returns signers for a specific block.
- bor_getCurrentValidators: returns the current validator set.
- bor_getCurrentProposer: returns the next block producer.
- eth_getBlockByNumber: standard method for block metadata, including Bor-specific fields.
Practical Uses: Block Attribution, Stuck Validators, and Reorg Risk
Knowing the current producer and validator set is essential for block attribution. If your application needs to credit a block to a specific validator, you can use bor_getSigners or the block's miner field. This is useful for analytics, reward calculations, and monitoring validator performance.
Detecting a stuck validator is another common use case. If a validator fails to produce blocks during its sprint, you may see empty blocks or a gap in production. Upstream reports, such as a GitHub issue titled 'Bor sync wedges on empty block after producer restart', highlight that a producer restart can lead to an empty block that stalls sync. Monitoring bor_getCurrentProposer and block timestamps can help you detect such anomalies early.
Finally, understanding span boundaries helps estimate reorg depth versus finalization. Because the validator set changes only at span boundaries, reorgs that cross a span boundary are more disruptive. For more on finality concepts, see OP-Stack finality and safe/finalized block tags.
- Attribute blocks to producers using bor_getSigners or block miner field.
- Detect stuck validators by monitoring producer changes and block gaps.
- Estimate reorg risk around span boundaries where validator sets change.
Runnable Code: Fetching Snapshot and Cross-Checking Signers
The following Node.js script connects to a Bor RPC endpoint, fetches the snapshot at a given block, prints the current validators, sprint, span, and current producer, and then cross-checks with bor_getSigners for that block. Replace the RPC URL with your own endpoint.
This code uses the ethers library for JSON-RPC calls. Ensure you have it installed (npm install ethers). The script is self-contained and can be run directly with Node.js.
const { JsonRpcProvider } = require('ethers');
const RPC_URL = 'https://your-bor-rpc-endpoint';
const BLOCK_NUMBER = 'latest'; // or a specific block number
async function main() {
const provider = new JsonRpcProvider(RPC_URL);
// Fetch snapshot
const snapshot = await provider.send('bor_getSnapshot', [BLOCK_NUMBER]);
console.log('Snapshot:');
console.log(' Span:', snapshot.span ? snapshot.span.id : 'N/A');
console.log(' Sprint:', snapshot.sprint);
console.log(' Current Validators:', snapshot.validators);
console.log(' Current Producer:', snapshot.producer);
// Fetch signers for the same block
const signers = await provider.send('bor_getSigners', [BLOCK_NUMBER]);
console.log('Signers for block', BLOCK_NUMBER, ':', signers);
// Fetch current proposer
const proposer = await provider.send('bor_getCurrentProposer', []);
console.log('Current Proposer:', proposer);
// Fetch current validators
const validators = await provider.send('bor_getCurrentValidators', []);
console.log('Current Validators (bor_getCurrentValidators):', validators);
}
main().catch(console.error);Results Table: Measuring Producer State on Your Endpoint
Use the following table to record the results from your own RPC endpoint. Run the code above and fill in the values. This helps you verify that your endpoint is returning consistent Bor-specific data and that the producer matches the signer for the block.
If any field is missing or returns an error, check the troubleshooting section below. Note that bor_getSnapshot may return null for pruned or very old blocks; an archive node is required for historical snapshots. For more on archive nodes, see Polygon archive nodes and historical RPC.
- Block number: the block you queried.
- Span ID: from snapshot.span.id.
- Sprint number: from snapshot.sprint.
- Current validators: list from snapshot.validators or bor_getCurrentValidators.
- Current producer: from snapshot.producer or bor_getCurrentProposer.
- Signers: from bor_getSigners for the same block.
- Match? Does the producer appear in the signers list?
Common Failures and Troubleshooting Checklist
When reading Bor producer state over RPC, you may encounter several common issues. Here is a checklist to diagnose them:
Method not found: If you get a 'method not found' error for bor_ methods, your endpoint is likely not a Bor node or is a plain Ethereum endpoint. Ensure you are connected to a Polygon PoS RPC endpoint. For a list of providers, see Polygon RPC providers and nodes (RPC Assistant).
Null snapshot for old blocks: bor_getSnapshot may return null for blocks that have been pruned. Use an archive node for historical queries. See Polygon archive nodes and historical RPC.
Sprint/span boundary edge cases: At the exact boundary between sprints or spans, the snapshot may reflect the new producer or the old one depending on the block number. Always query the snapshot for the specific block you are interested in, and cross-check with bor_getSigners.
Misinterpreting snapshot as finality: The snapshot shows the current producer and validator set, but it does not indicate finality. Finality is achieved through checkpoints on Ethereum. Do not treat the snapshot as a finality signal.
Node behind chain tip: If your node is not synced, the snapshot may be stale. Check eth_syncing and compare block timestamps. See Detecting an RPC node behind the chain tip.
- Verify endpoint supports Bor-specific methods.
- Use archive node for historical snapshots.
- Query snapshot for the exact block number.
- Cross-check producer with signers.
- Monitor node sync status.
Limitations and Tradeoffs of Bor Producer State Over RPC
While Bor's RPC methods provide valuable insight into block production, they come with limitations. First, these methods are non-standard and may not be supported by all RPC providers. Even among Bor nodes, the availability of bor_getSnapshot for historical blocks depends on the node's pruning settings. Second, the snapshot data reflects the state at a given block, but it does not guarantee that the producer will successfully produce the next block; network conditions or validator downtime can cause deviations.
Third, relying solely on bor_getCurrentProposer for real-time monitoring can be misleading if your node is not fully synced. Always verify node health and sync status. For monitoring best practices, see Monitoring RPC endpoints and node health.
Finally, the validator set and span parameters are subject to governance and protocol upgrades. Always verify current values on-chain rather than assuming fixed numbers. For authoritative details, refer to the official Polygon documentation on Bor architecture and the Bor consensus package.
- Non-standard methods may not be available on all endpoints.
- Historical snapshots require archive nodes.
- Snapshot does not guarantee future block production.
- Node sync status affects data freshness.
- Protocol parameters can change via governance.
Next Steps: Integrating Producer State into Your Application
Now that you understand how to read Bor producer state, you can integrate this into your application. For block explorers or analytics dashboards, use bor_getSigners to attribute blocks to validators. For monitoring tools, poll bor_getCurrentProposer and bor_getCurrentValidators to detect set changes and validator downtime. For wallets or exchanges, use span boundaries to estimate reorg risk and adjust confirmation requirements.
To get started with a reliable RPC endpoint, explore OnFinality's Polygon network page and consider an API service plan that fits your needs. For pricing details, see RPC pricing. For more guides, visit the OnFinality Learn hub.
Remember to test your integration against multiple endpoints and verify results with the results table method described above. This ensures your application handles edge cases and provider differences gracefully.
- Use bor_getSigners for block attribution.
- Poll bor_getCurrentProposer for real-time monitoring.
- Adjust confirmation depth based on span boundaries.
- Choose a reliable RPC provider with Bor support.