mirror of
https://github.com/Xahau/xahaud.git
synced 2026-08-23 00:10:54 +00:00
fix(consensus): guard keyless proposal authoring
This commit is contained in:
@@ -3160,6 +3160,51 @@ class ConsensusExtensions_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(ce.exportSigCollector().signatureCount(tx) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
testValidatorKeylessAuthoringNoops()
|
||||
{
|
||||
testcase("validator-keyless extension authoring no-ops");
|
||||
|
||||
using namespace jtx;
|
||||
Env env{
|
||||
*this,
|
||||
envconfig(),
|
||||
supported_amendments() | featureConsensusEntropy | featureExport,
|
||||
nullptr};
|
||||
auto const& valKeys = env.app().getValidatorKeys();
|
||||
BEAST_EXPECT(!valKeys.keys);
|
||||
|
||||
ConsensusExtensions ce{env.app(), activeNoopJournal()};
|
||||
auto const ledger = env.app().getLedgerMaster().getClosedLedger();
|
||||
|
||||
ExtendedPosition position{makeHash("keyless-authoring")};
|
||||
ce.decoratePosition(position, ledger, true);
|
||||
BEAST_EXPECT(!position.myCommitment);
|
||||
BEAST_EXPECT(ce.pendingCommitCount() == 0);
|
||||
|
||||
ce.generateEntropySecret();
|
||||
ce.selfSeedReveal();
|
||||
BEAST_EXPECT(ce.pendingRevealCount() == 0);
|
||||
|
||||
protocol::TMProposeSet prop;
|
||||
RCLCxPeerPos::Proposal proposal{
|
||||
ledger->info().hash,
|
||||
0,
|
||||
position,
|
||||
NetClock::time_point{},
|
||||
NetClock::time_point{},
|
||||
beast::zero};
|
||||
ce.setExportEnabledThisRound(true);
|
||||
ce.attachExportSignatures(prop, proposal);
|
||||
BEAST_EXPECT(prop.exportsignatures_size() == 0);
|
||||
|
||||
position.myCommitment = makeHash("keyless-commitment");
|
||||
position.myReveal = makeHash("keyless-reveal");
|
||||
ce.decorateMessage(prop, proposal, position, Buffer{});
|
||||
BEAST_EXPECT(ce.pendingRevealCount() == 0);
|
||||
BEAST_EXPECT(ce.pendingCommitCount() == 0);
|
||||
}
|
||||
|
||||
void
|
||||
testReplayedProposalHarvestsExportSigs()
|
||||
{
|
||||
@@ -3468,6 +3513,7 @@ public:
|
||||
testExportSigGateSkipsWhenExportDisabled();
|
||||
testParticipantDiagnosticsOnlyWhenExtensionEnabled();
|
||||
testExportDisabledRoundClearsCollector();
|
||||
testValidatorKeylessAuthoringNoops();
|
||||
testReplayedProposalHarvestsExportSigs();
|
||||
testWireProposalHarvestsExportSigs();
|
||||
testPublicHookNoopAndFailureBranches();
|
||||
|
||||
@@ -1009,6 +1009,9 @@ void
|
||||
ConsensusExtensions::selfSeedReveal()
|
||||
{
|
||||
auto const& valKeys = app_.getValidatorKeys();
|
||||
if (!valKeys.keys || valKeys.nodeID == beast::zero)
|
||||
return;
|
||||
|
||||
if (myEntropySecret_ != uint256{})
|
||||
{
|
||||
pendingReveals_[valKeys.nodeID] = myEntropySecret_;
|
||||
@@ -2446,11 +2449,22 @@ ConsensusExtensions::decoratePosition(
|
||||
return;
|
||||
}
|
||||
|
||||
auto const& valKeys = app_.getValidatorKeys();
|
||||
if (!valKeys.keys || valKeys.nodeID == beast::zero)
|
||||
{
|
||||
// Only validators author RNG sidecars. Observers still consume peer
|
||||
// sidecars and diagnostics, but never seed local commit/reveal state.
|
||||
JLOG(j_.debug()) << "RNG: decoratePosition skipped"
|
||||
<< " reason=no-validator-key"
|
||||
<< " prevLedgerSeq=" << prevLedger->info().seq
|
||||
<< " prevLedger=" << prevLedger->info().hash;
|
||||
return;
|
||||
}
|
||||
|
||||
setMode(ConsensusMode::proposing);
|
||||
cacheUNLReport(prevLedger);
|
||||
generateEntropySecret();
|
||||
|
||||
auto const& valKeys = app_.getValidatorKeys();
|
||||
pos.myCommitment = sha512Half(
|
||||
getEntropySecret(),
|
||||
valKeys.keys->publicKey,
|
||||
@@ -2505,6 +2519,15 @@ ConsensusExtensions::attachExportSignatures(
|
||||
if (!openLedger || !openLedger->rules().enabled(featureExport))
|
||||
return;
|
||||
|
||||
if (!valKeys.keys || valKeys.nodeID == beast::zero)
|
||||
{
|
||||
// Export signatures are validator attestations. Non-validator nodes may
|
||||
// relay proposals, but must not advertise locally authored signatures.
|
||||
JLOG(j_.debug()) << "Export: skipping proposal signatures"
|
||||
<< " reason=no-validator-key";
|
||||
return;
|
||||
}
|
||||
|
||||
auto const& valPK = valKeys.keys->publicKey;
|
||||
auto const& valSK = valKeys.keys->secretKey;
|
||||
// A locally configured validator may be trusted but not active for this
|
||||
@@ -2597,6 +2620,8 @@ ConsensusExtensions::decorateMessage(
|
||||
Buffer const& proposalSig)
|
||||
{
|
||||
auto const& valKeys = app_.getValidatorKeys();
|
||||
if (!valKeys.keys || valKeys.nodeID == beast::zero)
|
||||
return;
|
||||
|
||||
// Self-seed our own reveal so we count toward reveal quorum
|
||||
// (harvestRngData only sees peer proposals, not our own).
|
||||
|
||||
@@ -249,6 +249,15 @@ RCLConsensus::Adaptor::share(RCLCxTx const& tx)
|
||||
void
|
||||
RCLConsensus::Adaptor::propose(RCLCxPeerPos::Proposal const& proposal)
|
||||
{
|
||||
if (!validatorKeys_.keys)
|
||||
{
|
||||
// Proposal packets are signed consensus messages. Observing nodes can
|
||||
// follow consensus, but cannot safely author proposals without a
|
||||
// configured validator key.
|
||||
JLOG(j_.warn()) << "Skipping proposal without validator keys";
|
||||
return;
|
||||
}
|
||||
|
||||
JLOG(j_.trace()) << (proposal.isBowOut() ? "We bow out: " : "We propose: ")
|
||||
<< ripple::to_string(proposal.prevLedger()) << " -> "
|
||||
<< ripple::to_string(proposal.position());
|
||||
@@ -892,6 +901,14 @@ RCLConsensus::Adaptor::validate(
|
||||
RCLTxSet const& txns,
|
||||
bool proposing)
|
||||
{
|
||||
if (!validatorKeys_.keys)
|
||||
{
|
||||
// preStartRound normally prevents this path. Keep validate() itself
|
||||
// fail-closed so future call sites cannot dereference an observer key.
|
||||
JLOG(j_.warn()) << "Skipping validation without validator keys";
|
||||
return;
|
||||
}
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
auto validationTime = app_.timeKeeper().closeTime();
|
||||
|
||||
Reference in New Issue
Block a user