Skip to content

feat: Fast upgrades: upgrade permit shares, pool, and pool manager - #11548

Open
frankdavid wants to merge 2 commits into
masterfrom
frankdavid/fast-upgrades-consensus1
Open

frankdavid wants to merge 2 commits into
masterfrom
frankdavid/fast-upgrades-consensus1

Conversation

@frankdavid

Copy link
Copy Markdown
Contributor

First step of the Phase-2 rolling-reboot consensus protocol, which adds the data types and the artifact mechanism.

  • Add UpgradePermitAction (Request / Authorize / Return). UpgradePermitAuthorizationRequest is the signed content, UpgradePermitAuthorizationShare is the gossiped artifact.
  • Add the in-memory UpgradePermitAuthPoolImpl which is modeled after other pools.
  • Add the ic-consensus-upgrade crate with UpgradePermitAuthPoolManager. It signs shares for requests in finalized blocks and validates gossiped shares.
  • Register the new proto file with the generator and add UpgradePermitAuthorizationRequest (signing) and UpgradePermitAuthorizationShare (pool-ID hashing) domain separators.

First step of the Phase-2 rolling-reboot consensus protocol, which adds the data types and the artifact mechanism.

* Add `UpgradePermitAction` (`Request` / `Authorize` / `Return`). `UpgradePermitAuthorizationRequest` is the signed content, `UpgradePermitAuthorizationShare` is the gossiped artifact.
* Add the in-memory `UpgradePermitAuthPoolImpl` which is modeled after other pools.
* Add the `ic-consensus-upgrade` crate with `UpgradePermitAuthPoolManager`. It signs shares for requests in finalized blocks and validates gossiped shares.
* Register the new proto file with the generator and add `UpgradePermitAuthorizationRequest` (signing) and `UpgradePermitAuthorizationShare` (pool-ID hashing) domain separators.
@zeropath-ai

zeropath-ai Bot commented Sep 11, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to f8f1910.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► rs/artifact_pool/src/upgrade_permit_auth_pool.rs
    Add UpgradePermitAuthPool implementation and logic
Enhancement ► rs/artifact_pool/src/lib.rs
    Expose upgrade_permit_auth_pool module publicly
Enhancement ► rs/consensus/mocks/src/lib.rs
    Include UpgradePermitAuthPoolImpl in mocks
Enhancement ► rs/consensus/upgrade/BUILD.bazel
    Add Bazel build for consensus upgrade crate
Enhancement ► rs/consensus/upgrade/Cargo.toml
    Add new ic-consensus-upgrade crate dependencies and config
Enhancement ► rs/consensus/upgrade/src/lib.rs
    Implement upgrade permit authentication pool manager scaffolding (core logic)
Enhancement ► rs/consensus/upgrade/src/pool_manager.rs
    Add PoolManager with signing, validation, and pruning logic for upgrade permit authorization shares
Enhancement ► rs/consensus/upgrade/src/ tests
    Add extensive unit tests for pool manager behavior
Enhancement ► rs/consensus/upgrade/src/lib.rs (new file path in crate)
Enhancement ► rs/consensus/utils/src/crypto.rs
    Extend ConsensusCrypto trait to support UpgradePermitAuthorizationRequest signature verification

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Consensus hash incompatibility, incomplete payload validation and production wiring, and incorrect retry and share-validation behavior are blocking issues.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Introduces Phase-2 upgrade-permit types, protobuf encoding, cryptographic domains, artifact pooling, and authorization-share management.

Changes:

  • Adds upgrade request, authorization, return, and signature-share types.
  • Adds payload serialization, artifact IDs, pool interfaces, and an in-memory pool.
  • Adds a pool manager for signing, validating, gossiping, and expiring shares.
File summaries
File Description
rs/types/types/src/crypto/sign.rs Adds request signing domain.
rs/types/types/src/crypto/hash/tests.rs Updates consensus hash vectors.
rs/types/types/src/crypto/hash/domain_separator.rs Adds upgrade domains.
rs/types/types/src/crypto/hash.rs Adds request/share hash domains.
rs/types/types/src/consensus/upgrade.rs Defines permit actions.
rs/types/types/src/consensus.rs Adds request/share types and protobuf conversion.
rs/types/types/src/batch/upgrade.rs Encodes and decodes upgrade payloads.
rs/types/types/src/batch.rs Extends batch payloads and messages.
rs/types/types/src/artifact.rs Defines authorization-share IDs.
rs/test_utilities/types/src/batch/payload.rs Initializes test payload field.
rs/state_machine_tests/src/lib.rs Initializes delivered upgrade actions.
rs/protobuf/src/gen/types/types.v1.rs Regenerates protobuf bindings.
rs/protobuf/generator/src/lib.rs Registers upgrade protobuf generation.
rs/protobuf/def/types/v1/upgrade.proto Defines upgrade wire messages.
rs/protobuf/def/types/v1/consensus.proto Adds block upgrade bytes.
rs/protobuf/def/types/v1/artifact.proto Adds upgrade artifact IDs.
rs/interfaces/src/upgrade.rs Adds pool and validation interfaces.
rs/interfaces/src/p2p/consensus.rs Corrects documentation spelling.
rs/interfaces/src/lib.rs Exports upgrade interfaces.
rs/interfaces/src/crypto.rs Extends the crypto interface.
rs/interfaces/src/consensus.rs Adds upgrade validation errors.
rs/interfaces/mocks/src/crypto.rs Adds upgrade crypto mocks.
rs/consensus/utils/src/crypto.rs Extends consensus crypto bounds.
rs/consensus/upgrade/src/pool_manager.rs Implements share lifecycle management.
rs/consensus/upgrade/src/lib.rs Implements membership and share validation.
rs/consensus/upgrade/Cargo.toml Defines the new crate.
rs/consensus/upgrade/BUILD.bazel Adds Bazel targets.
rs/consensus/src/consensus/payload_builder.rs Initializes upgrade test payloads.
rs/consensus/mocks/src/lib.rs Adds the pool to test dependencies.
rs/artifact_pool/src/upgrade_permit_auth_pool.rs Implements the in-memory pool.
rs/artifact_pool/src/lib.rs Exports the pool module.
Cargo.toml Registers the workspace crate.
Cargo.lock Locks the new crate dependencies.
Review details

Suppressed comments (2)

rs/consensus/upgrade/src/pool_manager.rs:77

  • Advancing last_scanned to the tip before processing makes every skipped or failed request permanent. In particular, a transient signing failure logged below is never retried on later polls, so this node may never contribute its required authorization share. Advance the cursor only past successfully processed work, or retain failed requests for retry.
        *last = tip;

rs/consensus/upgrade/src/lib.rs:77

  • This maps every signature-verification failure to AuthorizeInvalidShare, including transient crypto errors. The manager then permanently removes the share; established consensus code instead defers non-reproducible errors (for example rs/consensus/idkg/src/pre_signer.rs:834-845). Preserve the crypto error classification so only reproducible failures invalidate the artifact and transient failures are retried.
        .map_err(|_| InvalidUpgradePayloadReason::AuthorizeInvalidShare { signer })?;
  • Files reviewed: 32/33 changed files
  • Comments generated: 9
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +32 to +35
let registry_at_height: BTreeSet<NodeId> = membership
.get_nodes_at_version(block_registry_version)
.map(|nodes| nodes.into_iter().collect())
.unwrap_or_default();
Comment on lines +163 to +167
match validate_share(
share,
share.content.requestor,
share.content.request_height,
&membership,
pub canister_http: Vec<u8>,
pub query_stats: Vec<u8>,
pub chain_key: Vec<u8>,
pub upgrade: Vec<u8>,
assert_eq!(
hex::encode(hash.get_ref().0.as_slice()),
"764535296841f3db421a928cfadff3460be406d0182da64034eee623a9a97e99",
"c20a87578beb94df369dabfefc30c0d47d170c75d68236aae3b16335c0f21c4a",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it should be fine, please don't do that. The reason this fired seems to be because test_block above returns a data block, when it should really be a summary block (which is unaffected by this change).


/// Signs shares for requests in finalized blocks, validates gossiped shares,
/// and purges expired ones.
pub struct UpgradePermitAuthPoolManager {
Comment on lines +31 to +32
/// Requests we've already signed (node, request_height).
signed_requests: Mutex<BTreeSet<(NodeId, Height)>>,
Comment on lines +82 to +86
for height_num in start.get()..=tip.get() {
let height = Height::from(height_num);
let Ok(block) = chain.get_block_by_height(height) else {
continue;
};
//!
//! 1. **Request**: A block maker includes `UpgradePermitAction::Request` in
//! its block when it wants to reboot. Validators check outstanding requests
//! the allowed max parallel reboots.
for Signed<UpgradePermitAuthorizationRequest, BasicSignature<UpgradePermitAuthorizationRequest>>
{
fn domain(&self) -> String {
DomainSeparator::UpgradePermitAuthorizationShare.to_string()
Comment on lines +235 to +240
upgrade: if self.upgrade.is_empty() {
Vec::new()
} else {
bytes_to_upgrade_payload(&self.upgrade)
.map_err(IntoMessagesError::UpgradePayloadError)?
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think we need the condition here

canister_http,
query_stats,
chain_key,
upgrade: _,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also add a comment here if this test isn't applicable (like for xnet above)

consensus_pool_cache: Arc<dyn ConsensusBlockCache>,
membership: Arc<Membership>,
/// Requests we've already signed (node, request_height).
signed_requests: Mutex<BTreeSet<(NodeId, Height)>>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we just check the pool if a certain signature already exists?

Comment on lines +87 to +98
let payload = block.payload.as_ref();
if payload.is_summary() {
continue;
}
let upgrade_bytes = &payload.as_data().batch.upgrade;
if upgrade_bytes.is_empty() {
continue;
}
let Ok(actions) = bytes_to_upgrade_payload(upgrade_bytes) else {
continue;
};
let membership = self.block_membership(block);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't the plan to also add some data to the replicated state? I think that might simplify everything here. Usually we shouldn't need to look back at the blockchain and deserialize the payloads in the client (see for example the HTTP outcalls or chain-key signer implementations). There, the data flow is the following:

  1. A block maker adds some data to a block
  2. When the block is executed, this data triggers some changes to the replicated state
  3. The client/pool manager creates/validates new shares based on data in the replicated state

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants