一键导入
subsystem-summary-of-scp
read this skill for a token-efficient summary of the scp subsystem
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
read this skill for a token-efficient summary of the scp subsystem
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
extending yourself with a new reusable skill by interviewing the user
analyzing a change to determine what tests are needed and adding them to the test suite
modifying build configuration to enable/disable variants, switch compilers or flags, or otherwise prepare for a build
reviewing a change for semantic correctness, simplicity, design consistency, and completeness
reviewing a git diff for small localized coding mistakes that can be fixed without high-level understanding
how to run make correctly to get a good build, and otherwise understand the build system
| name | subsystem-summary-of-scp |
| description | read this skill for a token-efficient summary of the scp subsystem |
The SCP (Stellar Consensus Protocol) subsystem implements the federated Byzantine agreement protocol used by stellar-core to reach consensus on ledger values. It is a self-contained library with a clean driver interface (SCPDriver) that decouples protocol logic from networking, persistence, and application-specific validation. The protocol operates in two main stages: nomination (proposing and filtering candidate values) and balloting (converging on a single value through prepare/confirm/externalize phases).
SCP class; entry point for receiving envelopes, nominating values, managing slots.NominationProtocol and BallotProtocol instances.SCPThe top-level protocol object. One instance per node.
Members:
mDriver (SCPDriver&) — Reference to the application-provided driver.mLocalNode (shared_ptr<LocalNode>) — The local node descriptor with its quorum set.mKnownSlots (map<uint64, shared_ptr<Slot>>) — Map from slot index to Slot objects. Slots are created lazily on first access via getSlot().Key Methods:
receiveEnvelope(SCPEnvelopeWrapperPtr) — Main entry point. Routes envelope to the appropriate Slot::processEnvelope().nominate(slotIndex, value, previousValue) — Initiates nomination for a slot (must be validator).stopNomination(slotIndex) — Stops nomination for a slot.purgeSlots(maxSlotIndex, slotToKeep) — Removes old slots below maxSlotIndex except slotToKeep.getSlot(slotIndex, create) — Lazily creates and retrieves Slot from mKnownSlots.setStateFromEnvelope(slotIndex, e) — Restores state from a previously emitted envelope (crash recovery).processCurrentState(slotIndex, f, forceSelf) — Iterates over latest messages for a slot.processSlotsAscendingFrom / processSlotsDescendingFrom — Iteration helpers over known slots.getExternalizingState(slotIndex) — Returns envelopes that contributed to externalization.getState(node, slotIndex) — Computes QuorumInfoNodeState for a node, checking up to NUM_SLOTS_TO_CHECK_FOR_REPORTING (2) recent slots.getJsonQuorumInfo(id, summary, fullKeys, index) — JSON diagnostic info categorizing quorum nodes as AGREE/MISSING/DELAYED/DISAGREE.envToStr(envelope/statement) — Formats SCP envelopes/statements as human-readable strings for logging.SCPDriver (Abstract)The application-facing interface. Stellar-core's HerderSCPDriver implements this.
Pure Virtual Methods (must be implemented):
signEnvelope(SCPEnvelope&) — Sign an outgoing envelope.getQSet(Hash) — Retrieve a quorum set by hash (return nullptr for unknown/invalid).emitEnvelope(SCPEnvelope) — Broadcast an envelope to the network.getHashOf(vector<opaque_vec<>>) — Compute a hash of serialized data.combineCandidates(slotIndex, candidates) — Produce a composite value from candidate set (used when transitioning from nomination to balloting).hasUpgrades(Value) — Check if a value contains protocol upgrades.stripAllUpgrades(Value) — Remove all upgrades from a value.getUpgradeNominationTimeoutLimit() — Max nomination timeouts before stripping upgrades.setupTimer(slotIndex, timerID, timeout, cb) / stopTimer(slotIndex, timerID) — Timer management.computeTimeout(roundNumber, isNomination) — Compute timeout for a round.Virtual Methods with Defaults:
validateValue(slotIndex, value, nomination) — Returns kMaybeValidValue by default. Three levels: kInvalidValue, kMaybeValidValue, kFullyValidatedValue.extractValidValue(slotIndex, value) — Extract a valid variant from an invalid value (returns nullptr by default).wrapEnvelope(e) / wrapValue(v) — Factory methods for SCPEnvelopeWrapper / ValueWrapper (allow subclasses to add metadata).computeHashNode(slotIndex, prev, isPriority, roundNumber, nodeID) — Hash for nomination leader election.computeValueHash(slotIndex, prev, roundNumber, value) — Hash for value ordering during nomination.getNodeWeight(nodeID, qset, isLocalNode) — Compute weight of a node within a quorum set (normalized 0–UINT64_MAX). Local node always gets UINT64_MAX. For other nodes, weight is threshold/total * leafWeight recursively through inner sets.valueExternalized, nominatingValue, updatedCandidateValue, startedBallotProtocol, acceptedBallotPrepared, confirmedBallotPrepared, acceptedCommit, ballotDidHearFromQuorum.SlotPer-slot state container. Each slot tracks one consensus round (one ledger sequence number).
Members:
mSlotIndex (uint64 const) — The slot/ledger index.mSCP (SCP&) — Back-reference to the owning SCP instance.mBallotProtocol (BallotProtocol) — Owns ballot protocol state (value, not pointer).mNominationProtocol (NominationProtocol) — Owns nomination protocol state (value, not pointer).mStatementsHistory (vector<HistoricalStatement>) — Debug log of all statements seen.mFullyValidated (bool) — True if all values processed by this slot have been fully validated.mGotVBlocking (bool) — True once messages from a v-blocking set have been received.Key Methods:
processEnvelope(envelope, self) — Dispatches to NominationProtocol or BallotProtocol based on statement type (SCP_ST_NOMINATE vs ballot types).nominate(value, previousValue, timedout) — Delegates to NominationProtocol::nominate().bumpState(value, force) — Delegates to BallotProtocol::bumpState().federatedAccept(voted, accepted, envs) — Checks if a statement should be accepted: true if either (a) a v-blocking set accepted it, or (b) a quorum voted-or-accepted it.federatedRatify(voted, envs) — Checks if a statement is ratified: true if a quorum voted for it.getQuorumSetFromStatement(st) — Retrieves quorum set for a statement; for EXTERNALIZE statements, returns the singleton {nodeID} set.createEnvelope(statement) — Wraps a statement into a signed envelope.getCompanionQuorumSetHashFromStatement(st) — Static; extracts the quorum set hash from any statement type (note: EXTERNALIZE uses commitQuorumSetHash).maybeSetGotVBlocking() — Checks if messages from a v-blocking set have been received.NominationProtocolImplements the nomination phase of SCP. Votes for values, promotes them through federated accept and ratify, and produces candidate values.
Members:
mSlot (Slot&) — Back-reference.mRoundNumber (int32) — Current nomination round (incremented on each nominate() call).mVotes (ValueWrapperPtrSet, paper variable X) — Values this node has voted to nominate.mAccepted (ValueWrapperPtrSet, paper variable Y) — Values accepted as nominated.mCandidates (ValueWrapperPtrSet, paper variable Z) — Values confirmed nominated (candidates).mLatestNominations (map<NodeID, SCPEnvelopeWrapperPtr>, paper variable N) — Latest nomination envelope per node.mLastEnvelope (SCPEnvelopeWrapperPtr) — Last envelope emitted by this node.mRoundLeaders (set<NodeID>) — Nodes with highest priority this round.mNominationStarted (bool) — Whether nominate() has been called.mLatestCompositeCandidate (ValueWrapperPtr) — Latest composite candidate value (from combineCandidates).mPreviousValue (Value) — Value from the previous slot (used for leader hashing).mTimerExpCount (uint32_t) — Number of timer expirations (used for reporting and upgrade timeout logic).Key Methods:
nominate(value, previousValue, timedout) — Main entry. Increments round, updates leaders, adds votes from leaders, optionally adds own value (if self is leader). Strips upgrades after exceeding getUpgradeNominationTimeoutLimit(). Arms a timer to re-invoke itself on timeout. Stops nominating once candidates exist.processEnvelope(envelope) — Processes a nomination message from another node. For each voted value, checks federatedAccept(voted, accepted); if accepted, adds to mAccepted. For each accepted value, checks federatedRatify(accepted); if ratified, promotes to mCandidates. When candidates found, stops timer, calls combineCandidates, and triggers bumpState on the ballot protocol.updateRoundLeaders() — Computes which nodes have priority this round using hashNode(priority). Includes self. Fast-forwards rounds if no node has priority.getNewValueFromNomination(nom) — Extracts the highest-hash value from a nomination that the local node doesn't already have, preferring accepted values. Validates or extracts valid values.emitNomination() — Creates and emits a SCP_ST_NOMINATE statement containing current votes and accepted values.hashNode(isPriority, nodeID) / hashValue(value) — Delegate to SCPDriver::computeHashNode / computeValueHash.getNodePriority(nodeID, qset) — Computes priority: if hashNode(N, nodeID) <= weight, returns hashNode(P, nodeID), else 0.stripUpgrades(value) — Calls SCPDriver::stripAllUpgrades to remove upgrades from a value when timeouts exceed threshold.stopNomination() — Sets mNominationStarted = false.getState(node, selfAlreadyMovedOn) — Categorizes node as AGREE/DELAYED/DISAGREE/MISSING based on accepted value comparison.isNewerStatement(old, new) — A nomination statement is newer if its votes and accepted sets are (non-strict) supersets with at least one being strictly larger.BallotProtocolImplements the ballot phase of SCP with three sub-phases: PREPARE, CONFIRM, EXTERNALIZE.
Members:
mSlot (Slot&) — Back-reference.mHeardFromQuorum (bool) — Whether a quorum at the current ballot counter has been heard.mPhase (SCPPhase) — Current phase: SCP_PHASE_PREPARE, SCP_PHASE_CONFIRM, or SCP_PHASE_EXTERNALIZE.mCurrentBallot (SCPBallotWrapperUPtr, paper variable b) — Current ballot.mPrepared (SCPBallotWrapperUPtr, paper variable p) — Highest accepted-prepared ballot.mPreparedPrime (SCPBallotWrapperUPtr, paper variable p') — Second-highest accepted-prepared ballot, incompatible with p.mHighBallot (SCPBallotWrapperUPtr, paper variable h) — Highest confirmed-prepared ballot.mCommit (SCPBallotWrapperUPtr, paper variable c) — Commit ballot.mLatestEnvelopes (map<NodeID, SCPEnvelopeWrapperPtr>, paper variable M) — Latest ballot envelope per node.mValueOverride (ValueWrapperPtr, paper variable z) — Value override set when h is confirmed prepared; ensures this value is used for subsequent ballots.mCurrentMessageLevel (int) — Recursion depth counter for advanceSlot, capped at MAX_ADVANCE_SLOT_RECURSION (50).mTimerExpCount (uint32_t) — Number of ballot timer expirations.mLastEnvelope / mLastEnvelopeEmit — Track last generated and last emitted envelopes.Inner Class: SCPBallotWrapper
Pairs a ValueWrapperPtr with an SCPBallot to keep shared ownership of the value. Used via unique_ptr<SCPBallotWrapper> (SCPBallotWrapperUPtr).
Key Methods — State Machine (advanceSlot):
advanceSlot(hint) — The core state machine driver. Called after each envelope is recorded. Sequentially attempts each progression step:
attemptAcceptPrepared(hint) — Steps 1,5: Check if any ballot can be federatedAccept-ed as prepared.attemptConfirmPrepared(hint) — Steps 2,3,8: Check if any prepared ballot can be federatedRatify-ed (confirmed prepared). If so, sets h, c, and the value override.attemptAcceptCommit(hint) — Steps 4,6,8: Check if commit can be federatedAccept-ed. Transitions PREPARE→CONFIRM phase.attemptConfirmCommit(hint) — Steps 7,8: Check if commit can be federatedRatify-ed. Transitions to EXTERNALIZE phase, calls valueExternalized.attemptBump() — Step 9: If a v-blocking subset has higher counters, bump local counter to the minimum counter that eliminates this condition.
After all attempts complete, calls checkHeardFromQuorum() and sendLatestEnvelope().Key Methods — State Setters:
setAcceptPrepared(ballot) — Updates p/p', clears c if p/p' conflict with h.setConfirmPrepared(newC, newH) — Sets h, optionally c; sets mValueOverride; updates current ballot if needed.setAcceptCommit(c, h) — Sets c/h; transitions phase to CONFIRM if in PREPARE; updates current ballot.setConfirmCommit(c, h) — Transitions to EXTERNALIZE phase; calls valueExternalized; stops nomination.Key Methods — Ballot Management:
bumpState(value, force) — Creates a new ballot at counter+1 (or 1 if no current ballot). Uses mValueOverride if set.bumpToBallot(ballot, check) — Low-level ballot update; resets h/c if incompatible; resets mHeardFromQuorum.updateCurrentValue(ballot) — Updates current ballot with checks; calls bumpToBallot.abandonBallot(n) — Bumps to ballot counter n (or counter+1 if n=0) using latest composite candidate value.ballotProtocolTimerExpired() — Increments timer count, calls abandonBallot(0).startBallotProtocolTimer() / stopBallotProtocolTimer() — Manage the ballot protocol timer.Key Methods — Predicates and Helpers:
isNewerStatement(old, new) — Total ordering: PREPARE < CONFIRM < EXTERNALIZE; within same type, lexicographic on (b, p, p', h).isStatementSane(st, self) — Validates structural invariants of each statement type (counter > 0, c ≤ h ≤ b, etc.).hasPreparedBallot(ballot, st) — Checks if a statement implies ballot is prepared.commitPredicate(ballot, interval, st) — Checks if a statement commits ballot within [interval.first, interval.second].compareBallots(b1, b2) — Orders ballots by (counter, value). Returns -1/0/1.areBallotsCompatible(b1, b2) — True if b1.value == b2.value.areBallotsLessAndCompatible/Incompatible — Combined comparisons.getPrepareCandidates(hint) — Collects ballots from all known envelopes that might be prepared.getCommitBoundariesFromStatements(ballot) — Collects counter boundaries for commit interval search.findExtendedInterval(candidate, boundaries, pred) — Scans boundaries top-down to find the widest [low,high] interval satisfying the predicate.validateValues(st) — Validates all values in a statement; returns the minimum validation level.checkHeardFromQuorum() — Checks if a quorum at the current ballot counter has been heard; starts/stops timer accordingly; invokes ballotDidHearFromQuorum callback.emitCurrentStateStatement() — Creates statement for current phase, processes it self, emits if newer.checkInvariants() — Debug assertions: in CONFIRM/EXTERNALIZE, b/p/c/h must all be set; p' < p and incompatible; h ≤ b and compatible; c ≤ h.createStatement(type) — Constructs SCPStatement from local state for the given phase type.LocalNodeRepresents the local node in the SCP network. Holds the node's identity, quorum set, and provides static methods for quorum/v-blocking checks.
Members:
mNodeID (NodeID const) — This node's public key.mIsValidator (bool const) — Whether this node is a validator.mQSet (SCPQuorumSet) — This node's quorum set (normalized on construction).mQSetHash (Hash) — Hash of the quorum set.mSingleQSet (shared_ptr<SCPQuorumSet>) — Singleton quorum set {{mNodeID}}, used during EXTERNALIZE.gSingleQSetHash (Hash) — Hash of the singleton quorum set.mDriver (SCPDriver&) — Back-reference.Key Static Methods:
forAllNodes(qset, proc) — Recursively iterates all nodes in a quorum set; short-circuits on proc returning false.isQuorumSlice(qSet, nodeSet) — Tests if nodeSet contains a quorum slice for this quorum set (threshold validators + inner sets satisfied).isVBlocking(qSet, nodeSet/map, filter) — Tests if a set of nodes forms a v-blocking set. Condition: nodeSet size ≥ total - threshold + 1 (enough to block every quorum slice).isQuorum(qSet, map, qfun, filter) — Iterative quorum check with transitivity: filters nodes, then repeatedly removes nodes whose quorum slice isn't satisfied until fixpoint, then checks if local node's slice is still satisfied.findClosestVBlocking(qset, nodes, excluded) — Finds the minimum set of nodes from nodes needed to form a v-blocking set (used for failure analysis in diagnostics).getSingletonQSet(nodeID) — Returns {threshold:1, validators:[nodeID]}.toJson / fromJson — Serialize/deserialize quorum sets to/from JSON.QuorumSetUtilsFunctions:
isQuorumSetSane(qSet, extraChecks, errString) — Validates a quorum set: nesting depth ≤ 4, threshold ≥ 1, threshold ≤ entries, no duplicate nodes, total nodes 1–1000. With extraChecks, also validates threshold ≥ v-blocking size (≥51% effective).normalizeQSet(qSet, idToRemove) — Normalizes a quorum set: removes idToRemove (adjusting threshold), merges singleton inner sets into parent's validators, simplifies {t:1, {inner}} to inner, then lexicographically sorts validators and inner sets.ValueWrapper — Immutable wrapper around Value (XDR opaque byte vector). Non-copyable, non-movable. Shared via ValueWrapperPtr (shared_ptr<ValueWrapper>).SCPEnvelopeWrapper — Immutable wrapper around SCPEnvelope. Non-copyable, non-movable. Shared via SCPEnvelopeWrapperPtr.ValueWrapperPtrSet — set<ValueWrapperPtr, WrappedValuePtrComparator> ordered by underlying value bytes.Goal: Agree on a set of candidate values to propose for balloting.
Flow:
SCP::nominate() is called by the application with a proposed value.NominationProtocol::nominate() increments round, computes round leaders via updateRoundLeaders(), and adds values from leaders' latest nominations to votes (X).NOMINATION_TIMER is armed to re-invoke nominate() with timedout=true for the next round.processEnvelope():
federatedAccept(voted_for, accepted) holds, move to Y (accepted).federatedRatify(accepted) holds, move to Z (candidates). Stops the nomination timer.combineCandidates() produces a composite value and bumpState() initiates the ballot protocol.Leader Election: Uses hashNode(priority, nodeID) with node weight from quorum set. The node(s) with highest priority hash value are leaders. Self is always included. Rounds fast-forward if no node has priority.
Goal: Converge on a prepared ballot.
State: mPhase = SCP_PHASE_PREPARE. Working variables: b (current ballot), p (highest accepted-prepared), p' (second highest, incompatible with p).
Transitions:
attemptAcceptPrepared): If federatedAccept(vote_to_prepare(ballot), accept_prepared(ballot)), update p (and p' if needed). If p/p' conflict with h, clear c.attemptConfirmPrepared): If federatedRatify(prepared(ballot)), set h (highest confirmed-prepared) and optionally c (lowest confirmed-prepared). Set mValueOverride to lock in h's value. Transition to CONFIRM if commit is also accepted.Goal: Converge on a committed ballot.
State: mPhase = SCP_PHASE_CONFIRM. Requires b, p, c, h all set. p' is cleared.
Transitions:
attemptAcceptCommit): If federatedAccept(vote_to_commit, accept_commit) for an interval [c, h], set c/h, transition PREPARE→CONFIRM.attemptConfirmCommit): If federatedRatify(commit(ballot, [c,h])), transition to EXTERNALIZE.attemptBump): If v-blocking set has higher ballot counters, bump to lowest counter that eliminates this condition.Goal: Finalize — consensus is reached.
State: mPhase = SCP_PHASE_EXTERNALIZE. Set by setConfirmCommit().
Actions:
EXTERNALIZE statement.Slot::stopNomination().SCPDriver::valueExternalized(slotIndex, value).NOMINATION_TIMER = 0)NominationProtocol::nominate() after each round.SCPDriver::computeTimeout(roundNumber, isNomination=true).nominate() with timedout=true, advancing to next round with new leaders.BALLOT_PROTOCOL_TIMER = 1)checkHeardFromQuorum() when a quorum is first heard at the current ballot counter.SCPDriver::computeTimeout(ballotCounter, isNomination=false).ballotProtocolTimerExpired() increments mTimerExpCount and calls abandonBallot(0), which bumps to the next ballot counter.advanceSlot RecursionadvanceSlot(hint), which may emit new statements that re-enter advanceSlot.MAX_ADVANCE_SLOT_RECURSION = 50 to prevent infinite loops.sendLatestEnvelope) is deferred to the outermost advanceSlot call (mCurrentMessageLevel == 0).SCP
├── mLocalNode: shared_ptr<LocalNode>
└── mKnownSlots: map<uint64, shared_ptr<Slot>>
└── Slot
├── mBallotProtocol: BallotProtocol (value member)
│ ├── mCurrentBallot: unique_ptr<SCPBallotWrapper>
│ ├── mPrepared: unique_ptr<SCPBallotWrapper>
│ ├── mPreparedPrime: unique_ptr<SCPBallotWrapper>
│ ├── mHighBallot: unique_ptr<SCPBallotWrapper>
│ ├── mCommit: unique_ptr<SCPBallotWrapper>
│ ├── mLatestEnvelopes: map<NodeID, SCPEnvelopeWrapperPtr>
│ └── mValueOverride: ValueWrapperPtr
└── mNominationProtocol: NominationProtocol (value member)
├── mVotes: ValueWrapperPtrSet (X)
├── mAccepted: ValueWrapperPtrSet (Y)
├── mCandidates: ValueWrapperPtrSet (Z)
├── mLatestNominations: map<NodeID, SCPEnvelopeWrapperPtr>
└── mLatestCompositeCandidate: ValueWrapperPtr
SCPDriver is referenced (not owned) by SCP and LocalNode. The application owns the SCPDriver and SCP instances.
SCP::receiveEnvelope(envelope) → Slot::processEnvelope(envelope, self=false)SCP_ST_NOMINATE → NominationProtocol::processEnvelope()SCP_ST_PREPARE/CONFIRM/EXTERNALIZE → BallotProtocol::processEnvelope()SCPDriver::validateValue().advanceSlot() which attempts all state transitionsemitCurrentStateStatement() (ballot) or emitNomination() (nomination).SCPDriver::signEnvelope().processEnvelope with self=true) to ensure consistency.SCPDriver::emitEnvelope() is called to broadcast.NominationProtocol confirms candidates (Z non-empty), it calls combineCandidates() to produce a composite value.Slot::bumpState(compositeValue, force=false) which delegates to BallotProtocol::bumpState().(1, compositeValue) and emits a PREPARE statement.Both protocols use two primitives provided by Slot:
federatedAccept(voted, accepted, envs) — True if: (a) a v-blocking set of nodes accepted the statement, OR (b) a quorum of nodes voted-or-accepted it.federatedRatify(voted, envs) — True if a quorum of nodes voted for the statement.These delegate to LocalNode::isVBlocking() and LocalNode::isQuorum() with the local node's quorum set and the relevant envelope maps.