{"record":{"id":"484d95503b8b44ea","repo":"linera-io/linera-protocol","slug":"wronground","errorCode":"WrongRound","errorMessage":"Round number should be {0:?}","messagePattern":"Round number should be (.+?)","errorType":"validation","errorClass":"ChainError","httpStatus":null,"severity":"warning","filePath":"linera-chain/src/manager.rs","lineNumber":308,"sourceCode":"        if let Some(old_proposal) = self.proposed.get() {\n            if old_proposal.content == proposal.content {\n                return Ok(Outcome::Skip); // We have already seen this proposal; nothing to do.\n            }\n        }\n        // When a block is certified, incrementing its height must succeed.\n        ensure!(\n            new_block.height < BlockHeight::MAX,\n            ChainError::BlockHeightOverflow\n        );\n        let current_round = self.current_round();\n        match new_round {\n            // The proposal from the fast round may still be relevant as a locking block, so\n            // we don't compare against the current round here.\n            Round::Fast => {}\n            Round::MultiLeader(_) | Round::SingleLeader(0) => {\n                // If the fast round has not timed out yet, only a super owner is allowed to open\n                // a later round by making a proposal.\n                ensure!(\n                    self.is_super(&proposal.owner()) || !current_round.is_fast(),\n                    ChainError::WrongRound(current_round)\n                );\n                // After the fast round, proposals older than the current round are obsolete.\n                ensure!(\n                    new_round >= current_round,\n                    ChainError::InsufficientRound(new_round)\n                );\n            }\n            Round::SingleLeader(_) | Round::Validator(_) => {\n                // After the first single-leader round, only proposals from the current round are relevant.\n                ensure!(\n                    new_round == current_round,\n                    ChainError::WrongRound(current_round)\n                );\n            }\n        }\n        // The round of our validation votes is only allowed to increase.","sourceCodeStart":290,"sourceCodeEnd":326,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-chain/src/manager.rs#L290-L326","documentation":"In check_proposed_block, a MultiLeader(_) or SingleLeader(0) proposal is rejected while the chain's current round is still Round::Fast and the proposer is not a super owner. This protects the fast round from being preempted: ordinary owners must wait for the fast round to time out before opening a later round; only super owners may bypass it.","triggerScenarios":"Calling try_handle_block_proposal with a MultiLeader(0) or higher MultiLeader proposal when manager.current_round() is Round::Fast and ownership does not list proposal.owner() as a super owner.","commonSituations":"Client built its proposal from stale chain info and missed that the chain is still in the fast round; a leader retrying immediately after block confirmation instead of waiting out the fast-round timeout; tests that skip round bookkeeping.","solutions":["Wait for the fast round to time out, fetch fresh chain info, and rebuild the proposal for the new current round","Have a super owner sign and submit the proposal if the fast round genuinely must be preempted","Re-query the chain (ChainInfoQuery / request_leader_timeout) to learn the current round and ownership before proposing"],"exampleFix":"// before: proposal built from stale state, submitted while fast round is open\nlet proposal = BlockProposal::new(Round::MultiLeader(0), block, ...);\nclient.submit_proposal(proposal).await?; // WrongRound(Fast)\n\n// after: wait for the fast-round timeout, then propose in the fresh current round\nlet info = client.chain_info(chain_id).await?;\nlet round = wait_for_next_round(&client, info).await?; // uses request_leader_timeout\nlet proposal = BlockProposal::new(round, block, ...);\nclient.submit_proposal(proposal).await?;","handlingStrategy":"validation","validationCode":"// Fetch fresh state before proposing in a post-fast round.\nlet info = client.chain_info(chain_id).await?;\nlet current = info.manager.current_round;\nlet is_super = info.manager.ownership.is_super_owner(&owner);\nif current.is_fast() && !is_super {\n    // Fast round still open: wait for its timeout instead of submitting.\n    return wait_for_round_change(&client, chain_id).await;\n}\nlet round = current.max(Round::MultiLeader(0));\nlet proposal = BlockProposal::new(round, block, /* ... */);","typeGuard":"fn is_wrong_round(e: &ChainError) -> bool {\n    matches!(e, ChainError::WrongRound(_))\n}","tryCatchPattern":"match client.submit_proposal(proposal).await {\n    Err(e) if matches!(e, ref x if x.is_wrong_round()) => {\n        // Re-query chain info and rebuild the proposal at the returned round.\n    }\n    other => other?,\n}","preventionTips":["Derive the proposal round from a fresh ChainInfoResponse, never from cached state","Track the fast-round timeout before attempting multi-leader proposals","Route preemption through a super owner only when intentional"],"tags":["consensus","round","block-proposal","fast-round","linera"],"backgroundTag":"consensus-round-mismatch","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}