> subsystem-summary-of-scp
read this skill for a token-efficient summary of the scp subsystem
curl "https://skillshub.wtf/stellar/stellar-core/subsystem-summary-of-scp?format=md"SCP Subsystem — Technical Summary
Overview
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).
Key Files
- SCP.h / SCP.cpp — Top-level
SCPclass; entry point for receiving envelopes, nominating values, managing slots. - SCPDriver.h / SCPDriver.cpp — Abstract driver interface that the application implements; handles signing, validation, timers, quorum set retrieval, hashing, and event callbacks.
- Slot.h / Slot.cpp — Per-slot state container; owns
NominationProtocolandBallotProtocolinstances. - NominationProtocol.h / NominationProtocol.cpp — Nomination phase logic: voting for values, federated acceptance, candidate promotion.
- BallotProtocol.h / BallotProtocol.cpp — Ballot phase logic: prepare/confirm/externalize state machine, ballot bumping, timer management.
- LocalNode.h / LocalNode.cpp — Represents the local node; owns quorum set, provides quorum slice / v-blocking set checks.
- QuorumSetUtils.h / QuorumSetUtils.cpp — Quorum set sanity checking and normalization utilities.
Key Classes and Data Structures
SCP
The 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 toSlotobjects. Slots are created lazily on first access viagetSlot().
Key Methods:
receiveEnvelope(SCPEnvelopeWrapperPtr)— Main entry point. Routes envelope to the appropriateSlot::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 belowmaxSlotIndexexceptslotToKeep.getSlot(slotIndex, create)— Lazily creates and retrievesSlotfrommKnownSlots.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)— ComputesQuorumInfoNodeStatefor a node, checking up toNUM_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 (returnnullptrfor 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)— ReturnskMaybeValidValueby default. Three levels:kInvalidValue,kMaybeValidValue,kFullyValidatedValue.extractValidValue(slotIndex, value)— Extract a valid variant from an invalid value (returnsnullptrby default).wrapEnvelope(e)/wrapValue(v)— Factory methods forSCPEnvelopeWrapper/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 getsUINT64_MAX. For other nodes, weight isthreshold/total * leafWeightrecursively through inner sets.- Event callbacks:
valueExternalized,nominatingValue,updatedCandidateValue,startedBallotProtocol,acceptedBallotPrepared,confirmedBallotPrepared,acceptedCommit,ballotDidHearFromQuorum.
Slot
Per-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 owningSCPinstance.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 toNominationProtocolorBallotProtocolbased on statement type (SCP_ST_NOMINATEvs ballot types).nominate(value, previousValue, timedout)— Delegates toNominationProtocol::nominate().bumpState(value, force)— Delegates toBallotProtocol::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; forEXTERNALIZEstatements, 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:EXTERNALIZEusescommitQuorumSetHash).maybeSetGotVBlocking()— Checks if messages from a v-blocking set have been received.
NominationProtocol
Implements 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 eachnominate()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) — Whethernominate()has been called.mLatestCompositeCandidate(ValueWrapperPtr) — Latest composite candidate value (fromcombineCandidates).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 exceedinggetUpgradeNominationTimeoutLimit(). 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, checksfederatedAccept(voted, accepted); if accepted, adds tomAccepted. For each accepted value, checksfederatedRatify(accepted); if ratified, promotes tomCandidates. When candidates found, stops timer, callscombineCandidates, and triggersbumpStateon the ballot protocol.updateRoundLeaders()— Computes which nodes have priority this round usinghashNode(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 aSCP_ST_NOMINATEstatement containing current votes and accepted values.hashNode(isPriority, nodeID)/hashValue(value)— Delegate toSCPDriver::computeHashNode/computeValueHash.getNodePriority(nodeID, qset)— Computes priority: ifhashNode(N, nodeID) <= weight, returnshashNode(P, nodeID), else 0.stripUpgrades(value)— CallsSCPDriver::stripAllUpgradesto remove upgrades from a value when timeouts exceed threshold.stopNomination()— SetsmNominationStarted = 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.
BallotProtocol
Implements 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, orSCP_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 foradvanceSlot, capped atMAX_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, callsvalueExternalized.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, callscheckHeardFromQuorum()andsendLatestEnvelope().
Key Methods — State Setters:
setAcceptPrepared(ballot)— Updates p/p', clears c if p/p' conflict with h.setConfirmPrepared(newC, newH)— Sets h, optionally c; setsmValueOverride; 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; callsvalueExternalized; stops nomination.
Key Methods — Ballot Management:
bumpState(value, force)— Creates a new ballot at counter+1 (or 1 if no current ballot). UsesmValueOverrideif set.bumpToBallot(ballot, check)— Low-level ballot update; resets h/c if incompatible; resetsmHeardFromQuorum.updateCurrentValue(ballot)— Updates current ballot with checks; callsbumpToBallot.abandonBallot(n)— Bumps to ballot counter n (or counter+1 if n=0) using latest composite candidate value.ballotProtocolTimerExpired()— Increments timer count, callsabandonBallot(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 impliesballotis prepared.commitPredicate(ballot, interval, st)— Checks if a statement commitsballotwithin[interval.first, interval.second].compareBallots(b1, b2)— Orders ballots by (counter, value). Returns -1/0/1.areBallotsCompatible(b1, b2)— True ifb1.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; invokesballotDidHearFromQuorumcallback.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)— ConstructsSCPStatementfrom local state for the given phase type.
LocalNode
Represents 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 onprocreturning false.isQuorumSlice(qSet, nodeSet)— Tests ifnodeSetcontains 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:nodeSetsize ≥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 fromnodesneeded 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.
QuorumSetUtils
Functions:
isQuorumSetSane(qSet, extraChecks, errString)— Validates a quorum set: nesting depth ≤ 4, threshold ≥ 1, threshold ≤ entries, no duplicate nodes, total nodes 1–1000. WithextraChecks, also validates threshold ≥ v-blocking size (≥51% effective).normalizeQSet(qSet, idToRemove)— Normalizes a quorum set: removesidToRemove(adjusting threshold), merges singleton inner sets into parent's validators, simplifies{t:1, {inner}}toinner, then lexicographically sorts validators and inner sets.
Wrapper Types
ValueWrapper— Immutable wrapper aroundValue(XDR opaque byte vector). Non-copyable, non-movable. Shared viaValueWrapperPtr(shared_ptr<ValueWrapper>).SCPEnvelopeWrapper— Immutable wrapper aroundSCPEnvelope. Non-copyable, non-movable. Shared viaSCPEnvelopeWrapperPtr.ValueWrapperPtrSet—set<ValueWrapperPtr, WrappedValuePtrComparator>ordered by underlying value bytes.
SCP Protocol Phases
1. Nomination Phase
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 viaupdateRoundLeaders(), and adds values from leaders' latest nominations to votes (X).- If self is a leader, adds own proposed value to X (stripping upgrades if timeout limit exceeded).
- A
NOMINATION_TIMERis armed to re-invokenominate()withtimedout=truefor the next round. - When messages arrive via
processEnvelope():- For each voted value: if
federatedAccept(voted_for, accepted)holds, move to Y (accepted). - For each accepted value: if
federatedRatify(accepted)holds, move to Z (candidates). Stops the nomination timer.
- For each voted value: if
- When new candidates appear,
combineCandidates()produces a composite value andbumpState()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.
2. Ballot Phase — PREPARE
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:
- Accept Prepared (
attemptAcceptPrepared): IffederatedAccept(vote_to_prepare(ballot), accept_prepared(ballot)), update p (and p' if needed). If p/p' conflict with h, clear c. - Confirm Prepared (
attemptConfirmPrepared): IffederatedRatify(prepared(ballot)), set h (highest confirmed-prepared) and optionally c (lowest confirmed-prepared). SetmValueOverrideto lock in h's value. Transition to CONFIRM if commit is also accepted.
3. Ballot Phase — CONFIRM
Goal: Converge on a committed ballot.
State: mPhase = SCP_PHASE_CONFIRM. Requires b, p, c, h all set. p' is cleared.
Transitions:
- Accept Commit (
attemptAcceptCommit): IffederatedAccept(vote_to_commit, accept_commit)for an interval [c, h], set c/h, transition PREPARE→CONFIRM. - Confirm Commit (
attemptConfirmCommit): IffederatedRatify(commit(ballot, [c,h])), transition to EXTERNALIZE. - Bump (
attemptBump): If v-blocking set has higher ballot counters, bump to lowest counter that eliminates this condition. - Accept-prepared can still update p (for reporting purposes) in CONFIRM phase.
4. Ballot Phase — EXTERNALIZE
Goal: Finalize — consensus is reached.
State: mPhase = SCP_PHASE_EXTERNALIZE. Set by setConfirmCommit().
Actions:
- Emits
EXTERNALIZEstatement. - Stops nomination via
Slot::stopNomination(). - Invokes
SCPDriver::valueExternalized(slotIndex, value). - Incoming envelopes are still processed (recorded) if their value matches the committed value, but no further state transitions occur.
Key Control Flow and Timers
Nomination Timer (NOMINATION_TIMER = 0)
- Armed in
NominationProtocol::nominate()after each round. - Timeout computed by
SCPDriver::computeTimeout(roundNumber, isNomination=true). - On expiry: re-invokes
nominate()withtimedout=true, advancing to next round with new leaders. - Stopped when candidates are found (federated ratify of an accepted value).
Ballot Protocol Timer (BALLOT_PROTOCOL_TIMER = 1)
- Armed in
checkHeardFromQuorum()when a quorum is first heard at the current ballot counter. - Timeout computed by
SCPDriver::computeTimeout(ballotCounter, isNomination=false). - On expiry:
ballotProtocolTimerExpired()incrementsmTimerExpCountand callsabandonBallot(0), which bumps to the next ballot counter. - Stopped when quorum is no longer heard at current counter, or when externalized.
advanceSlot Recursion
- Processing an envelope triggers
advanceSlot(hint), which may emit new statements that re-enteradvanceSlot. - Recursion is capped at
MAX_ADVANCE_SLOT_RECURSION = 50to prevent infinite loops. - Envelope emission (
sendLatestEnvelope) is deferred to the outermostadvanceSlotcall (mCurrentMessageLevel == 0).
Ownership Relationships
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.
Key Data Flows
Inbound Envelope Processing
SCP::receiveEnvelope(envelope)→Slot::processEnvelope(envelope, self=false)- Slot dispatches based on statement type:
SCP_ST_NOMINATE→NominationProtocol::processEnvelope()SCP_ST_PREPARE/CONFIRM/EXTERNALIZE→BallotProtocol::processEnvelope()
- Protocol validates sanity, checks newness, validates values via
SCPDriver::validateValue(). - Records envelope, then:
- Nomination: checks federated accept/ratify on values
- Ballot: calls
advanceSlot()which attempts all state transitions
Outbound Envelope Emission
- Protocol state change triggers
emitCurrentStateStatement()(ballot) oremitNomination()(nomination). - Statement is created from current local state, wrapped in an envelope, signed via
SCPDriver::signEnvelope(). - The envelope is self-processed (fed back through
processEnvelopewithself=true) to ensure consistency. - If valid and newer than last emitted,
SCPDriver::emitEnvelope()is called to broadcast.
Nomination → Ballot Transition
- When
NominationProtocolconfirms candidates (Z non-empty), it callscombineCandidates()to produce a composite value. - It then calls
Slot::bumpState(compositeValue, force=false)which delegates toBallotProtocol::bumpState(). - This creates the first ballot
(1, compositeValue)and emits a PREPARE statement.
Federated Agreement Primitives
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.
> related_skills --same-repo
> validating-a-change
comprehensive validation of a change to ensure it is correct and ready for a pull request
> regenerating a technical summary of stellar-core
Instructions for regenerating the full set of subsystem and whole-system technical summary skill documents for stellar-core
> subsystem-summary-of-work
read this skill for a token-efficient summary of the work subsystem
> subsystem-summary-of-util
read this skill for a token-efficient summary of the util subsystem