# grandpa_roundState - Moonbase RPC Method

Returns the state of the current GRANDPA finality round on Moonbase Alpha when the endpoint exposes validator-round internals. GRANDPA (GHOST-based Recursive ANcestor Deriving Prefix Agreement) is the finality gadget used by many Substrate-based chains to provide deterministic finality, but some public endpoints do not surface `grandpa_roundState` and instead return a method-not-found style error.

> **Why Moonbase?** Build on the Moonbeam testnet for risk-free EVM dApp development and deployment testing with free testnet tokens, full EVM compatibility, XCM testing capabilities, and identical feature set to Moonbeam mainnet.

## When to Use This Method

`grandpa_roundState` is essential for Moonbeam developers, dApp testers, and teams validating cross-chain integrations:

- **Finality Monitoring** -- Track whether GRANDPA rounds are progressing normally or stalling on Moonbase, critical for EVM dApp testing, XCM integration validation, and pre-mainnet deployment verification
- **Consensus Health Checks** -- Detect finality delays by comparing prevote/precommit counts against the supermajority threshold weight
- **Validator Participation Analysis** -- Monitor which validators are actively voting and whether the authority set has sufficient online weight
- **Authority Set Tracking** -- Observe `setId` changes after validator set rotations to verify smooth authority transitions
- **Capability Detection** -- Confirm whether the shared endpoint exposes GRANDPA round internals before you build monitoring around them

## Best Practices

- Primarily used for network monitoring and consensus debugging
- Returns `prevotes` and `precommits` from active validators
- Response may be large on networks with many validators
- Most applications should use `chain_getFinalizedHead` instead for finality tracking

## Request Parameters

This method accepts no parameters.

## Request Example

```json
{
  "jsonrpc": "2.0",
  "method": "grandpa_roundState",
  "params": [],
  "id": 1
}
```

## Response Fields

- `setId` (`u64, required`): The current GRANDPA authority set ID; increments when the validator set changes
- `best` (`RoundState, required`): State of the best (most recent) active round
- `background` (`Vec<RoundState>, required`): Background rounds that are still being tracked (typically the previous round)
- `round` (`u64, required`): The round number
- `totalWeight` (`u64, required`): Total combined weight of all authorities in this set
- `thresholdWeight` (`u64, required`): Minimum weight required for a supermajority (2/3 + 1 of totalWeight)
- `prevotes` (`Votes, required`): Current prevote state for this round
- `precommits` (`Votes, required`): Current precommit state for this round
- `currentWeight` (`u64, required`): Total weight of votes received so far
- `missing` (`Vec<AuthorityId>, required`): List of authority public keys that have not yet voted

## Successful Response

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "setId": 4821,
    "best": {
      "round": 19384,
      "totalWeight": 297,
      "thresholdWeight": 199,
      "prevotes": {
        "currentWeight": 297,
        "missing": []
      },
      "precommits": {
        "currentWeight": 264,
        "missing": [
          "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
          "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty"
        ]
      }
    },
    "background": []
  }
}
```

## Code Examples

cURL
JavaScript
Python
Rust

```bash
curl -X POST https://api-moonbase-alpha.n.dwellir.com/YOUR_API_KEY \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "grandpa_roundState",
    "params": [],
    "id": 1
  }'
```

```javascript
// Using @polkadot/api
import { ApiPromise, WsProvider } from '@polkadot/api';

const provider = new WsProvider('wss://api-moonbase-alpha.n.dwellir.com/YOUR_API_KEY');
const api = await ApiPromise.create({ provider });

try {
  const roundState = await api.rpc.grandpa.roundState();
  const best = roundState.best;

  console.log('Authority set ID:', roundState.setId.toString());
  console.log('Round:', best.round.toString());
  console.log('Total weight:', best.totalWeight.toString());
  console.log('Threshold weight:', best.thresholdWeight.toString());
  console.log('Prevote weight:', best.prevotes.currentWeight.toString());
  console.log('Precommit weight:', best.precommits.currentWeight.toString());
  console.log('Missing precommits:', best.precommits.missing.length);
} catch (error) {
  console.log('grandpa_roundState unsupported:', error.message);
}

await api.disconnect();

// Using fetch (HTTP JSON-RPC)
const response = await fetch('https://api-moonbase-alpha.n.dwellir.com/YOUR_API_KEY', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    jsonrpc: '2.0',
    method: 'grandpa_roundState',
    params: [],
    id: 1
  })
});

const payload = await response.json();
if (payload.error) {
  console.log('grandpa_roundState unsupported:', payload.error.message);
} else {
  console.log('Set ID:', payload.result.setId);
  console.log('Best round:', payload.result.best.round);
  console.log('Prevote progress:', payload.result.best.prevotes.currentWeight, '/', payload.result.best.thresholdWeight);
  console.log('Precommit progress:', payload.result.best.precommits.currentWeight, '/', payload.result.best.thresholdWeight);
}
```

```python
import requests

def get_grandpa_round_state():
    response = requests.post(
        'https://api-moonbase-alpha.n.dwellir.com/YOUR_API_KEY',
        json={
            'jsonrpc': '2.0',
            'method': 'grandpa_roundState',
            'params': [],
            'id': 1
        }
    )
    return response.json()['result']

try:
    state = get_grandpa_round_state()
    best = state['best']

    print(f"Authority set ID: {state['setId']}")
    print(f"Round: {best['round']}")
    print(f"Prevote: {best['prevotes']['currentWeight']}/{best['thresholdWeight']}")
    print(f"Precommit: {best['precommits']['currentWeight']}/{best['thresholdWeight']}")
    print(f"Missing precommit voters: {len(best['precommits']['missing'])}")
except KeyError:
    print('grandpa_roundState unsupported on this endpoint')

# Using substrate-interface
from substrateinterface import SubstrateInterface

substrate = SubstrateInterface(url='wss://api-moonbase-alpha.n.dwellir.com/YOUR_API_KEY')
response = substrate.rpc_request('grandpa_roundState', [])
if 'error' in response:
    print(f"grandpa_roundState unsupported: {response['error']['message']}")
else:
    print(f"Set ID: {response['result']['setId']}, Round: {response['result']['best']['round']}")
```

```rust
use serde_json::json;

#[derive(Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct RoundState {
    set_id: u64,
    best: BestRound,
    background: Vec<BestRound>,
}

#[derive(Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct BestRound {
    round: u64,
    total_weight: u64,
    threshold_weight: u64,
    prevotes: Votes,
    precommits: Votes,
}

#[derive(Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct Votes {
    current_weight: u64,
    missing: Vec<String>,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest::Client::new();
    let response = client
        .post("https://api-moonbase-alpha.n.dwellir.com/YOUR_API_KEY")
        .json(&json!({
            "jsonrpc": "2.0",
            "method": "grandpa_roundState",
            "params": [],
            "id": 1
        }))
        .send()
        .await?;

    let body: serde_json::Value = response.json().await?;
    if body.get("error").is_some() {
        println!("grandpa_roundState unsupported: {}", body["error"]["message"]);
        return Ok(());
    }

    let state: RoundState = serde_json::from_value(body["result"].clone())?;

    println!("Set ID: {}", state.set_id);
    println!("Round: {}", state.best.round);
    println!("Prevote: {}/{}", state.best.prevotes.current_weight, state.best.threshold_weight);
    println!("Precommit: {}/{}", state.best.precommits.current_weight, state.best.threshold_weight);
    println!("Missing precommit voters: {}", state.best.precommits.missing.len());

    Ok(())
}
```

## Common Use Cases

### 1. Finality Health Monitoring

Periodically check whether GRANDPA rounds are progressing and alert on stalls:

```javascript
import { ApiPromise, WsProvider } from '@polkadot/api';

async function monitorFinality(api, intervalMs = 10000) {
  let lastRound = 0;
  let lastSetId = 0;
  let stallCount = 0;

  setInterval(async () => {
    const state = await api.rpc.grandpa.roundState();
    const best = state.best;
    const round = best.round.toNumber();
    const setId = state.setId.toNumber();
    const prevoteProgress = best.prevotes.currentWeight.toNumber();
    const precommitProgress = best.precommits.currentWeight.toNumber();
    const threshold = best.thresholdWeight.toNumber();

    if (setId !== lastSetId) {
      console.log(`Authority set changed: ${lastSetId} -> ${setId}`);
      lastSetId = setId;
    }

    if (round === lastRound) {
      stallCount++;
      if (stallCount >= 3) {
        console.warn(`GRANDPA round ${round} stalled for ${stallCount} checks`);
        console.warn(`  Prevotes: ${prevoteProgress}/${threshold}`);
        console.warn(`  Precommits: ${precommitProgress}/${threshold}`);
        console.warn(`  Missing voters: ${best.precommits.missing.length}`);
      }
    } else {
      stallCount = 0;
      console.log(`Round ${round} | prevotes=${prevoteProgress}/${threshold} precommits=${precommitProgress}/${threshold}`);
    }

    lastRound = round;
  }, intervalMs);
}
```

### 2. Validator Participation Report

Generate a report of which validators are consistently missing votes:

```javascript
async function trackMissingVoters(api, samples = 20, delayMs = 6000) {
  const missingCounts = {};

  for (let i = 0; i < samples; i++) {
    const state = await api.rpc.grandpa.roundState();
    const missing = state.best.precommits.missing;

    missing.forEach((authority) => {
      const key = authority.toString();
      missingCounts[key] = (missingCounts[key] || 0) + 1;
    });

    await new Promise((resolve) => setTimeout(resolve, delayMs));
  }

  // Sort by most frequently missing
  const sorted = Object.entries(missingCounts)
    .sort(([, a], [, b]) => b - a);

  console.log('Validator participation report:');
  sorted.forEach(([authority, count]) => {
    const missRate = ((count / samples) * 100).toFixed(1);
    console.log(`  ${authority}: missed ${count}/${samples} (${missRate}%)`);
  });

  return sorted;
}
```

### 3. Supported-Fallback Check

If the endpoint does not expose GRANDPA round internals, fall back to finalized-head tracking:

```javascript
async function getFinalitySignal(api) {
  try {
    return { supported: true, roundState: await api.rpc.grandpa.roundState() };
  } catch (error) {
    return {
      supported: false,
      finalizedHead: (await api.rpc.chain.getFinalizedHead()).toHex(),
      message: error.message
    };
  }
}
```

### 3. Finality Lag Detection

Compare the finalized head with the best block to measure finality lag:

```javascript
async function getFinalityLag(api) {
  const [roundState, finalizedHash, bestHeader] = await Promise.all([
    api.rpc.grandpa.roundState(),
    api.rpc.chain.getFinalizedHead(),
    api.rpc.chain.getHeader()
  ]);

  const finalizedHeader = await api.rpc.chain.getHeader(finalizedHash);
  const bestNumber = bestHeader.number.toNumber();
  const finalizedNumber = finalizedHeader.number.toNumber();
  const lag = bestNumber - finalizedNumber;

  return {
    bestBlock: bestNumber,
    finalizedBlock: finalizedNumber,
    lagBlocks: lag,
    grandpaRound: roundState.best.round.toNumber(),
    setId: roundState.setId.toNumber(),
    prevoteReached: roundState.best.prevotes.currentWeight.toNumber() >= roundState.best.thresholdWeight.toNumber(),
    precommitReached: roundState.best.precommits.currentWeight.toNumber() >= roundState.best.thresholdWeight.toNumber()
  };
}
```

## Understanding GRANDPA Rounds

GRANDPA achieves finality through a two-phase voting protocol:

1. **Prevote Phase** -- Each authority broadcasts a prevote for the highest block they consider best. Once prevotes reach the `thresholdWeight` (supermajority), the protocol derives the highest block that is an ancestor of all supermajority prevotes.

2. **Precommit Phase** -- Authorities that observe a supermajority of prevotes issue precommits for the block derived in the prevote phase. When precommits reach the threshold, that block and all its ancestors are finalized.

3. **Authority Sets** -- The `setId` increments each time the authority set changes (e.g., after a session rotation). A new authority set starts a new round sequence from round 1.

| Concept             | Description                                                                                   |
| ------------------- | --------------------------------------------------------------------------------------------- |
| **totalWeight**     | Sum of all authority weights in the current set                                               |
| **thresholdWeight** | `⌊totalWeight × 2/3⌋ + 1` -- minimum for supermajority                                        |
| **Healthy round**   | `prevotes.currentWeight >= thresholdWeight` AND `precommits.currentWeight >= thresholdWeight` |
| **Stalled round**   | Neither prevotes nor precommits reach threshold for an extended period                        |

## Related Methods

- [`chain_getFinalizedHead`](https://www.dwellir.com/docs/moonbase-alpha/chain_getFinalizedHead) -- Get the hash of the latest finalized block
- [`chain_subscribeFinalizedHeads`](https://www.dwellir.com/docs/moonbase-alpha/chain_subscribeFinalizedHeads) -- Subscribe to new finalized block headers
- `grandpa_proveFinality` -- Get a finality proof for a specific block number
- [`beefy_getFinalizedHead`](https://www.dwellir.com/docs/moonbase-alpha/beefy_getFinalizedHead) -- Get the latest BEEFY finalized block (if BEEFY is enabled)
- [`system_health`](https://www.dwellir.com/docs/moonbase-alpha/system_health) -- Check overall node health including sync and peer status
