mirror of
https://github.com/Xahau/xahaud.git
synced 2026-08-23 16:30:52 +00:00
refactor(rng): remove explicit-final proposal path
This commit is contained in:
@@ -331,7 +331,6 @@ struct FakeExtensions
|
||||
EstablishState estState_{EstablishState::ConvergingTx};
|
||||
std::chrono::steady_clock::time_point revealPhaseStart_{};
|
||||
std::chrono::steady_clock::time_point commitHashConflictStart_{};
|
||||
bool explicitFinalProposalSent_{false};
|
||||
bool entropySetPublished_{false};
|
||||
std::chrono::steady_clock::time_point entropyPublishStart_{};
|
||||
bool exportSigGateStarted_{false};
|
||||
@@ -348,14 +347,12 @@ struct FakeExtensions
|
||||
bool commitQuorum{true};
|
||||
bool minimumReveals{true};
|
||||
bool anyReveals{true};
|
||||
bool sendExplicitFinal{false};
|
||||
uint256 exportHash{makeHash("local-export-sig-set")};
|
||||
uint256 commitHash{makeHash("local-commit-set")};
|
||||
uint256 entropyHash{makeHash("local-entropy-set")};
|
||||
std::deque<uint256> exportHashSequence;
|
||||
std::deque<uint256> commitHashSequence;
|
||||
std::deque<uint256> entropyHashSequence;
|
||||
std::optional<FakeTxSet> explicitFinalTxSet;
|
||||
std::vector<uint256> fetchedExportSets;
|
||||
std::vector<uint256> fetchedEntropySets;
|
||||
std::vector<uint256> fetchedCommitSets;
|
||||
@@ -504,18 +501,6 @@ struct FakeExtensions
|
||||
fetchedExportSets.push_back(*hash);
|
||||
}
|
||||
|
||||
bool
|
||||
shouldSendExplicitFinalProposal() const
|
||||
{
|
||||
return sendExplicitFinal;
|
||||
}
|
||||
|
||||
std::optional<FakeTxSet>
|
||||
buildExplicitFinalProposalTxSet(FakeTxSet const&, LedgerIndex)
|
||||
{
|
||||
return explicitFinalTxSet;
|
||||
}
|
||||
|
||||
bool
|
||||
hasPendingExportSigs() const
|
||||
{
|
||||
@@ -1030,140 +1015,6 @@ class ConsensusExtensions_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testExplicitFinalProposalTxSetBuildsEntropyTxn()
|
||||
{
|
||||
testcase("explicit final proposal tx set builds entropy txn");
|
||||
|
||||
auto extractSingleEntropyTx =
|
||||
[](RCLTxSet const& set) -> std::shared_ptr<STTx const> {
|
||||
std::vector<std::shared_ptr<STTx const>> txs;
|
||||
set.map_->visitLeaves(
|
||||
[&](boost::intrusive_ptr<SHAMapItem const> const& item) {
|
||||
SerialIter sit(item->slice());
|
||||
txs.push_back(std::make_shared<STTx const>(sit));
|
||||
});
|
||||
if (txs.size() != 1 ||
|
||||
txs.front()->getTxnType() != ttCONSENSUS_ENTROPY)
|
||||
return {};
|
||||
return txs.front();
|
||||
};
|
||||
|
||||
using namespace jtx;
|
||||
Env env{
|
||||
*this, envconfig(validator, ""), supported_amendments(), nullptr};
|
||||
ConsensusExtensions ce{env.app(), activeNoopJournal()};
|
||||
auto const base = makeRCLTxSet(env.app(), {});
|
||||
auto const seq = env.closed()->seq() + 1;
|
||||
|
||||
auto synthetic = ce.buildExplicitFinalProposalTxSet(base, seq);
|
||||
BEAST_EXPECT(synthetic);
|
||||
if (!synthetic)
|
||||
return;
|
||||
|
||||
auto const txPtr = extractSingleEntropyTx(*synthetic);
|
||||
BEAST_EXPECT(txPtr);
|
||||
if (!txPtr)
|
||||
return;
|
||||
auto const& tx = *txPtr;
|
||||
BEAST_EXPECT(tx.getFieldU32(sfLedgerSequence) == seq);
|
||||
BEAST_EXPECT(
|
||||
tx.getFieldH256(sfDigest) ==
|
||||
sha512Half(std::string("standalone-entropy"), seq));
|
||||
BEAST_EXPECT(tx.getFieldU16(sfEntropyCount) == 20);
|
||||
BEAST_EXPECT(
|
||||
tx.getFieldU8(sfEntropyTier) == entropyTierValidatorQuorum);
|
||||
|
||||
auto duplicate = ce.buildExplicitFinalProposalTxSet(*synthetic, seq);
|
||||
BEAST_EXPECT(duplicate);
|
||||
if (duplicate)
|
||||
BEAST_EXPECT(duplicate->id() == synthetic->id());
|
||||
|
||||
Env nonStandaloneEnv{
|
||||
*this,
|
||||
envconfig(validator, ""),
|
||||
supported_amendments() | featureConsensusEntropy,
|
||||
nullptr};
|
||||
forceNonStandalone(nonStandaloneEnv.app());
|
||||
auto const ledger =
|
||||
nonStandaloneEnv.app().getLedgerMaster().getClosedLedger();
|
||||
auto const nonStandaloneBase = makeRCLTxSet(nonStandaloneEnv.app(), {});
|
||||
auto const nonStandaloneSeq = ledger->seq() + 1;
|
||||
|
||||
ConsensusExtensions zeroCe{nonStandaloneEnv.app(), activeNoopJournal()};
|
||||
zeroCe.onRoundStart(RCLCxLedger{ledger}, {});
|
||||
zeroCe.setEntropyFailed();
|
||||
auto zeroSynthetic = zeroCe.buildExplicitFinalProposalTxSet(
|
||||
nonStandaloneBase, nonStandaloneSeq);
|
||||
BEAST_EXPECT(zeroSynthetic);
|
||||
auto const zeroTx =
|
||||
zeroSynthetic ? extractSingleEntropyTx(*zeroSynthetic) : nullptr;
|
||||
BEAST_EXPECT(zeroTx);
|
||||
if (zeroTx)
|
||||
{
|
||||
// Tier 1 consensus_fallback digest over (prevLedgerHash, base set,
|
||||
// seq).
|
||||
auto const expectedFallback = sha512Half(
|
||||
HashPrefix::entropyFallback,
|
||||
ledger->info().hash,
|
||||
nonStandaloneBase.id(),
|
||||
nonStandaloneSeq);
|
||||
BEAST_EXPECT(zeroTx->getFieldH256(sfDigest) == expectedFallback);
|
||||
BEAST_EXPECT(zeroTx->getFieldH256(sfDigest) != uint256{});
|
||||
BEAST_EXPECT(zeroTx->getFieldU16(sfEntropyCount) == 0);
|
||||
BEAST_EXPECT(
|
||||
zeroTx->getFieldU8(sfEntropyTier) ==
|
||||
entropyTierConsensusFallback);
|
||||
}
|
||||
|
||||
auto const& valKeys = nonStandaloneEnv.app().getValidatorKeys();
|
||||
BEAST_EXPECT(valKeys.keys);
|
||||
if (!valKeys.keys)
|
||||
return;
|
||||
|
||||
auto const& publicKey = valKeys.keys->publicKey;
|
||||
auto const& secretKey = valKeys.keys->secretKey;
|
||||
auto const nodeId = valKeys.nodeID;
|
||||
auto const prevLedger = ledger->info().hash;
|
||||
auto const closeTime = NetClock::time_point{NetClock::duration{654}};
|
||||
auto const txSetHash = makeHash("explicit-final-nonzero-txset");
|
||||
auto const reveal = makeHash("explicit-final-nonzero-reveal");
|
||||
auto const viewLedger = makeUNLReportLedger(
|
||||
nonStandaloneEnv, std::vector<PublicKey>{publicKey});
|
||||
ConsensusExtensions revealCe{
|
||||
nonStandaloneEnv.app(), activeNoopJournal()};
|
||||
revealCe.cacheUNLReport(viewLedger);
|
||||
harvestCommitReveal(
|
||||
revealCe,
|
||||
nodeId,
|
||||
publicKey,
|
||||
secretKey,
|
||||
txSetHash,
|
||||
nonStandaloneSeq,
|
||||
closeTime,
|
||||
prevLedger,
|
||||
reveal);
|
||||
revealCe.buildEntropySet(nonStandaloneSeq);
|
||||
|
||||
auto revealSynthetic = revealCe.buildExplicitFinalProposalTxSet(
|
||||
nonStandaloneBase, nonStandaloneSeq);
|
||||
BEAST_EXPECT(revealSynthetic);
|
||||
auto const revealTx = revealSynthetic
|
||||
? extractSingleEntropyTx(*revealSynthetic)
|
||||
: nullptr;
|
||||
BEAST_EXPECT(revealTx);
|
||||
if (revealTx)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
revealTx->getFieldH256(sfDigest) ==
|
||||
expectedEntropy(publicKey, reveal));
|
||||
BEAST_EXPECT(revealTx->getFieldU16(sfEntropyCount) == 1);
|
||||
BEAST_EXPECT(
|
||||
revealTx->getFieldU8(sfEntropyTier) ==
|
||||
entropyTierValidatorQuorum);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testRuntimeConfigPolicyAccessors()
|
||||
{
|
||||
@@ -1175,20 +1026,15 @@ class ConsensusExtensions_test : public beast::unit_test::suite
|
||||
ConsensusExtensions ce{env.app(), activeNoopJournal()};
|
||||
|
||||
BEAST_EXPECT(!ce.bootstrapFastStartEnabled());
|
||||
BEAST_EXPECT(!ce.shouldSendExplicitFinalProposal());
|
||||
|
||||
ConfigVals cfg;
|
||||
cfg.bootstrapFastStart = true;
|
||||
cfg.explicitFinalProposal = true;
|
||||
env.app().getRuntimeConfig().setConfig("*", cfg);
|
||||
BEAST_EXPECT(ce.bootstrapFastStartEnabled());
|
||||
BEAST_EXPECT(ce.shouldSendExplicitFinalProposal());
|
||||
|
||||
cfg.bootstrapFastStart = false;
|
||||
cfg.explicitFinalProposal = false;
|
||||
env.app().getRuntimeConfig().setConfig("*", cfg);
|
||||
BEAST_EXPECT(!ce.bootstrapFastStartEnabled());
|
||||
BEAST_EXPECT(!ce.shouldSendExplicitFinalProposal());
|
||||
}
|
||||
|
||||
void
|
||||
@@ -2684,38 +2530,6 @@ class ConsensusExtensions_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(ext.fetchedEntropySets.size() == 1);
|
||||
}
|
||||
|
||||
void
|
||||
testRngExplicitFinalProposalPublishesSyntheticTxSet()
|
||||
{
|
||||
testcase("RNG explicit final proposal publishes synthetic tx set");
|
||||
|
||||
FakeExtensions ext;
|
||||
ext.rngOn = true;
|
||||
ext.exportOn = false;
|
||||
ext.estState_ = EstablishState::ConvergingReveal;
|
||||
ext.sendExplicitFinal = true;
|
||||
ext.explicitFinalTxSet = FakeTxSet{makeHash("explicit-final-tx-set")};
|
||||
|
||||
ExportTickHarness harness;
|
||||
auto const localHash = ext.entropyHash;
|
||||
harness.position.entropySetHash = localHash;
|
||||
ext.entropySetPublished_ = true;
|
||||
ext.entropyPublishStart_ = harness.start;
|
||||
harness.addEntropyPeer(1, localHash);
|
||||
harness.addEntropyPeer(2, localHash);
|
||||
harness.addEntropyPeer(3, localHash);
|
||||
harness.addEntropyPeer(4, localHash);
|
||||
|
||||
auto result = harness.tick(ext, std::chrono::milliseconds{100});
|
||||
BEAST_EXPECT(result.readyForAccept);
|
||||
BEAST_EXPECT(ext.explicitFinalProposalSent_);
|
||||
BEAST_EXPECT(
|
||||
harness.position.txSetHash == ext.explicitFinalTxSet->hash);
|
||||
BEAST_EXPECT(harness.position.entropySetHash == localHash);
|
||||
BEAST_EXPECT(harness.updates == 1);
|
||||
BEAST_EXPECT(harness.proposes == 1);
|
||||
}
|
||||
|
||||
void
|
||||
testExportSigGateAllowsAlignedQuorumDespiteMinorityConflict()
|
||||
{
|
||||
@@ -3177,7 +2991,6 @@ public:
|
||||
testActiveValidatorViewAppliesNegativeUNL();
|
||||
testActiveValidatorViewNullSourceAndExpectedProposers();
|
||||
testParticipantThreshold();
|
||||
testExplicitFinalProposalTxSetBuildsEntropyTxn();
|
||||
testRuntimeConfigPolicyAccessors();
|
||||
testDecoratePositionGeneratesCommitment();
|
||||
testOnPreBuildInjectsZeroEntropyFallback();
|
||||
@@ -3210,7 +3023,6 @@ public:
|
||||
testRngEntropyConflictTimeoutClearsHash();
|
||||
testRngEntropyConflictRefreshesHashBeforeWaiting();
|
||||
testRngEntropyConflictIgnoredWithQuorumAlignment();
|
||||
testRngExplicitFinalProposalPublishesSyntheticTxSet();
|
||||
testExportSigGateAllowsAlignedQuorumDespiteMinorityConflict();
|
||||
testExportSigGateAllowsQuorumDespiteMissingObservation();
|
||||
testExportSigGateFetchesAdvertisedPeerSets();
|
||||
|
||||
@@ -325,7 +325,6 @@ struct Peer
|
||||
EstablishState estState_{EstablishState::ConvergingTx};
|
||||
std::chrono::steady_clock::time_point revealPhaseStart_{};
|
||||
std::chrono::steady_clock::time_point commitHashConflictStart_{};
|
||||
bool explicitFinalProposalSent_{false};
|
||||
bool entropySetPublished_{false};
|
||||
std::chrono::steady_clock::time_point entropyPublishStart_{};
|
||||
bool exportSigGateStarted_{false};
|
||||
@@ -965,16 +964,6 @@ struct Peer
|
||||
return bootstrapFastStartEnabled_;
|
||||
}
|
||||
bool
|
||||
shouldSendExplicitFinalProposal() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::optional<TxSet>
|
||||
buildExplicitFinalProposalTxSet(TxSet const&, Ledger::Seq)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
bool
|
||||
hasPendingExportSigs() const
|
||||
{
|
||||
return enableExportConsensus_ && !pendingExportSigs_.empty();
|
||||
@@ -1010,7 +999,6 @@ struct Peer
|
||||
estState_ = EstablishState::ConvergingTx;
|
||||
revealPhaseStart_ = {};
|
||||
commitHashConflictStart_ = {};
|
||||
explicitFinalProposalSent_ = false;
|
||||
entropySetPublished_ = false;
|
||||
entropyPublishStart_ = {};
|
||||
exportSigGateStarted_ = false;
|
||||
|
||||
@@ -110,7 +110,6 @@ class RuntimeConfig_test : public beast::unit_test::suite
|
||||
ConfigVals peer;
|
||||
peer.sendDelayMs = 500;
|
||||
peer.sendDelayJitterMs = 25;
|
||||
peer.explicitFinalProposal = true;
|
||||
peer.rngPollMs = 75;
|
||||
peer.noExportSig = true;
|
||||
peer.messageCategories = std::set<std::size_t>{};
|
||||
@@ -122,8 +121,6 @@ class RuntimeConfig_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(merged.rngClaimDropPctX100 == 750);
|
||||
BEAST_EXPECT(merged.bootstrapFastStart.has_value());
|
||||
BEAST_EXPECT(*merged.bootstrapFastStart == false);
|
||||
BEAST_EXPECT(merged.explicitFinalProposal.has_value());
|
||||
BEAST_EXPECT(*merged.explicitFinalProposal == true);
|
||||
BEAST_EXPECT(merged.rngPollMs == 75);
|
||||
BEAST_EXPECT(merged.noExportSig.has_value());
|
||||
BEAST_EXPECT(*merged.noExportSig == true);
|
||||
@@ -138,7 +135,7 @@ class RuntimeConfig_test : public beast::unit_test::suite
|
||||
inactive.sendDropPctX100 = 0;
|
||||
inactive.rngClaimDropPctX100 = 0;
|
||||
BEAST_EXPECT(!inactive.active());
|
||||
inactive.explicitFinalProposal = false;
|
||||
inactive.bootstrapFastStart = false;
|
||||
BEAST_EXPECT(inactive.active());
|
||||
}
|
||||
|
||||
@@ -304,7 +301,6 @@ class RuntimeConfig_test : public beast::unit_test::suite
|
||||
EnvVarGuard jitter{"XAHAU_SEND_DELAY_JITTER_MS", "3"};
|
||||
EnvVarGuard drop{"XAHAU_SEND_DROP_PCT", "4.5"};
|
||||
EnvVarGuard rngDrop{"XAHAU_RNG_CLAIM_DROP_PCT", "6.25"};
|
||||
EnvVarGuard explicitFinal{"XAHAUD_EXPLICIT_FINAL_PROPOSAL", "off"};
|
||||
EnvVarGuard bootstrap{"XAHAUD_BOOTSTRAP_FAST_START", "yes"};
|
||||
EnvVarGuard rngPoll{"XAHAU_RNG_POLL_MS", "5"};
|
||||
EnvVarGuard noExportSig{"XAHAUD_NO_EXPORT_SIG", "0"};
|
||||
@@ -319,8 +315,6 @@ class RuntimeConfig_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(cfg->sendDelayJitterMs == 3);
|
||||
BEAST_EXPECT(cfg->sendDropPctX100 == 450);
|
||||
BEAST_EXPECT(cfg->rngClaimDropPctX100 == 625);
|
||||
BEAST_EXPECT(cfg->explicitFinalProposal.has_value());
|
||||
BEAST_EXPECT(*cfg->explicitFinalProposal == false);
|
||||
BEAST_EXPECT(cfg->bootstrapFastStart.has_value());
|
||||
BEAST_EXPECT(*cfg->bootstrapFastStart == true);
|
||||
BEAST_EXPECT(cfg->rngPollMs == 50);
|
||||
@@ -337,12 +331,12 @@ class RuntimeConfig_test : public beast::unit_test::suite
|
||||
"XAHAU_RUNTIME_CONFIG",
|
||||
R"({"*":{"send_delay_ms":100,"send_delay_jitter_ms":20,)"
|
||||
R"("send_drop_pct":1.25,"rng_claim_drop_pct":3.5,)"
|
||||
R"("explicit_final_proposal":true,"bootstrap_fast_start":false,)"
|
||||
R"("bootstrap_fast_start":false,)"
|
||||
R"("rng_poll_ms":5,"no_export_sig":true,)"
|
||||
R"("message_types":["proposal"]},)"
|
||||
R"("10.0.0.5:51235":{"send_delay_ms":200,)"
|
||||
R"("send_drop_pct":2.5,"rng_claim_drop_pct":4.5,)"
|
||||
R"("explicit_final_proposal":false,"bootstrap_fast_start":true,)"
|
||||
R"("bootstrap_fast_start":true,)"
|
||||
R"("rng_poll_ms":125,"no_export_sig":false,)"
|
||||
R"("message_types":[]}})"};
|
||||
|
||||
@@ -354,8 +348,6 @@ class RuntimeConfig_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(global->sendDelayJitterMs == 20);
|
||||
BEAST_EXPECT(global->sendDropPctX100 == 125);
|
||||
BEAST_EXPECT(global->rngClaimDropPctX100 == 350);
|
||||
BEAST_EXPECT(global->explicitFinalProposal.has_value());
|
||||
BEAST_EXPECT(*global->explicitFinalProposal == true);
|
||||
BEAST_EXPECT(global->bootstrapFastStart.has_value());
|
||||
BEAST_EXPECT(*global->bootstrapFastStart == false);
|
||||
BEAST_EXPECT(global->rngPollMs == 50);
|
||||
@@ -371,8 +363,6 @@ class RuntimeConfig_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(peer->sendDelayJitterMs == 20);
|
||||
BEAST_EXPECT(peer->sendDropPctX100 == 250);
|
||||
BEAST_EXPECT(peer->rngClaimDropPctX100 == 450);
|
||||
BEAST_EXPECT(peer->explicitFinalProposal.has_value());
|
||||
BEAST_EXPECT(*peer->explicitFinalProposal == false);
|
||||
BEAST_EXPECT(peer->bootstrapFastStart.has_value());
|
||||
BEAST_EXPECT(*peer->bootstrapFastStart == true);
|
||||
BEAST_EXPECT(peer->rngPollMs == 125);
|
||||
@@ -882,54 +872,6 @@ class RuntimeConfig_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testExplicitFinalProposalToggle()
|
||||
{
|
||||
testcase("explicit_final_proposal round-trips and merges");
|
||||
using namespace test::jtx;
|
||||
Env env{*this};
|
||||
|
||||
// Global default for this node: skip explicit final proposal.
|
||||
{
|
||||
Json::Value params;
|
||||
params["set"] = Json::objectValue;
|
||||
params["set"]["*"] = Json::objectValue;
|
||||
params["set"]["*"]["explicit_final_proposal"] = false;
|
||||
auto result = runtimeConfig(env, params);
|
||||
|
||||
auto const& global = result["configs"]["*"];
|
||||
BEAST_EXPECT(global["explicit_final_proposal"].asBool() == false);
|
||||
}
|
||||
|
||||
auto& rc = env.app().getRuntimeConfig();
|
||||
BEAST_EXPECT(rc.active());
|
||||
|
||||
// Global view is false.
|
||||
auto globalCfg = rc.getConfig("*");
|
||||
BEAST_EXPECT(globalCfg.has_value());
|
||||
BEAST_EXPECT(globalCfg->explicitFinalProposal.has_value());
|
||||
BEAST_EXPECT(*globalCfg->explicitFinalProposal == false);
|
||||
|
||||
// Per-peer override can re-enable.
|
||||
{
|
||||
Json::Value params;
|
||||
params["set"] = Json::objectValue;
|
||||
params["set"]["10.0.0.2:51235"] = Json::objectValue;
|
||||
params["set"]["10.0.0.2:51235"]["explicit_final_proposal"] = true;
|
||||
runtimeConfig(env, params);
|
||||
}
|
||||
|
||||
auto peerCfg = rc.getConfig("10.0.0.2:51235");
|
||||
BEAST_EXPECT(peerCfg.has_value());
|
||||
BEAST_EXPECT(peerCfg->explicitFinalProposal.has_value());
|
||||
BEAST_EXPECT(*peerCfg->explicitFinalProposal == true);
|
||||
|
||||
auto otherCfg = rc.getConfig("10.0.0.3:51235");
|
||||
BEAST_EXPECT(otherCfg.has_value());
|
||||
BEAST_EXPECT(otherCfg->explicitFinalProposal.has_value());
|
||||
BEAST_EXPECT(*otherCfg->explicitFinalProposal == false);
|
||||
}
|
||||
|
||||
void
|
||||
testPerPeerClearInheritedFilter()
|
||||
{
|
||||
@@ -1001,7 +943,6 @@ public:
|
||||
testRngClaimDropPct();
|
||||
testRngClaimDropPctClamping();
|
||||
testRngAndExportRuntimeToggles();
|
||||
testExplicitFinalProposalToggle();
|
||||
testPerPeerClearInheritedFilter();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -554,122 +554,6 @@ ConsensusExtensions::bootstrapFastStartEnabled() const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ConsensusExtensions::shouldSendExplicitFinalProposal() const
|
||||
{
|
||||
// Explicit-final-proposal policy is node-local and experimental.
|
||||
//
|
||||
// Default behavior is implicit finalization (no extra seq=4 proposal):
|
||||
// entropy pseudo-tx is injected in onAccept/buildLCL.
|
||||
//
|
||||
// We only enable explicit-final when operators intentionally opt in via
|
||||
// runtime config/env for measurement/diagnostics.
|
||||
//
|
||||
// TODO: remove the explicit-final proposal path. The implicit accept-time
|
||||
// injection path is the consensus path; explicit-final never found a robust
|
||||
// timing model and still carries separate experimental-only alignment
|
||||
// hazards. Do not promote this by just widening the gates.
|
||||
auto const cfg = app_.getRuntimeConfig().getConfig("*");
|
||||
if (cfg && cfg->explicitFinalProposal.has_value())
|
||||
return *cfg->explicitFinalProposal;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<RCLTxSet>
|
||||
ConsensusExtensions::buildExplicitFinalProposalTxSet(
|
||||
RCLTxSet const& txns,
|
||||
LedgerIndex seq)
|
||||
{
|
||||
JLOG(j_.debug()) << "RNGFINAL: build synthetic txSet"
|
||||
<< " baseTxSet=" << txns.id() << " seq=" << seq
|
||||
<< " commits=" << pendingCommits_.size()
|
||||
<< " reveals=" << pendingReveals_.size()
|
||||
<< " entropyFailed=" << (entropyFailed_ ? "yes" : "no");
|
||||
|
||||
// Shared deterministic selector over the AGREED entropySetMap_ — the same
|
||||
// one onPreBuild uses — NOT local pendingReveals_, which can diverge from
|
||||
// the agreed set at timeout boundaries. Routing explicit-final
|
||||
// (experimental, default-off) through it keeps this path byte-identical to
|
||||
// the implicit one. txns.id() is the BASE tx set hash for the fallback.
|
||||
//
|
||||
// TODO: delete this with the explicit-final proposal path; keep this helper
|
||||
// only while the runtime-config experiment still exists.
|
||||
auto const selection = selectEntropy(txns.id(), seq);
|
||||
uint256 const finalEntropy = selection.digest;
|
||||
std::uint8_t const entropyTier = selection.tier;
|
||||
std::uint16_t const entropyCount = selection.count;
|
||||
|
||||
JLOG(j_.debug()) << "RNGFINAL: entropy selected"
|
||||
<< " seq=" << seq
|
||||
<< " tier=" << static_cast<int>(entropyTier)
|
||||
<< " count=" << entropyCount << " digest=" << finalEntropy
|
||||
<< " baseTxSet=" << txns.id();
|
||||
|
||||
STTx tx(ttCONSENSUS_ENTROPY, [&](auto& obj) {
|
||||
obj.setFieldU32(sfLedgerSequence, seq);
|
||||
obj.setAccountID(sfAccount, AccountID{});
|
||||
obj.setFieldU32(sfSequence, 0);
|
||||
obj.setFieldAmount(sfFee, STAmount{});
|
||||
obj.setFieldH256(sfDigest, finalEntropy);
|
||||
obj.setFieldU16(sfEntropyCount, entropyCount);
|
||||
obj.setFieldU8(sfEntropyTier, entropyTier);
|
||||
});
|
||||
|
||||
auto const txID = tx.getTransactionID();
|
||||
// Value-based dedup (mirrors onPreBuild): there must never be two entropy
|
||||
// pseudo-txs. If one is already in the base set it must be the EXACT
|
||||
// pseudo-tx we would have produced (injection is deterministic, so the same
|
||||
// agreed inputs yield an identical txID); a present-but-different one is a
|
||||
// determinism violation to surface, not silently accept. Either way return
|
||||
// the base unchanged — explicit-final is best-effort and must not rewrite
|
||||
// an already-committed set.
|
||||
std::optional<uint256> presentID;
|
||||
txns.map_->visitLeaves(
|
||||
[&](boost::intrusive_ptr<SHAMapItem const> const& item) {
|
||||
if (presentID)
|
||||
return;
|
||||
try
|
||||
{
|
||||
SerialIter sit(item->slice());
|
||||
STTx const parsed{sit};
|
||||
if (parsed.getTxnType() == ttCONSENSUS_ENTROPY)
|
||||
presentID = parsed.getTransactionID();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
});
|
||||
if (presentID)
|
||||
{
|
||||
if (*presentID == txID)
|
||||
JLOG(j_.debug())
|
||||
<< "RNGFINAL: entropy pseudo-tx already in base txSet"
|
||||
<< " txHash=" << txID << " baseTxSet=" << txns.id()
|
||||
<< " action=skip-duplicate-verified";
|
||||
else
|
||||
JLOG(j_.error())
|
||||
<< "RNGFINAL: entropy pseudo-tx MISMATCH in base txSet"
|
||||
<< " reason=determinism-violation action=keep-base"
|
||||
<< " ourTxHash=" << txID << " presentTxHash=" << *presentID
|
||||
<< " baseTxSet=" << txns.id();
|
||||
return txns;
|
||||
}
|
||||
|
||||
RCLTxSet::MutableTxSet mutableTxSet{txns};
|
||||
Serializer ser(512);
|
||||
tx.add(ser);
|
||||
mutableTxSet.insert(RCLCxTx{make_shamapitem(txID, ser.slice())});
|
||||
auto syntheticSet = RCLTxSet{mutableTxSet};
|
||||
auto const hash = syntheticSet.id();
|
||||
app_.getInboundTransactions().giveSet(hash, syntheticSet.map_, false);
|
||||
|
||||
JLOG(j_.debug()) << "RNGFINAL: built synthetic txSet"
|
||||
<< " syntheticTxSet=" << hash << " baseTxSet=" << txns.id()
|
||||
<< " txHash=" << txID << " entropyCount=" << entropyCount;
|
||||
|
||||
return syntheticSet;
|
||||
}
|
||||
|
||||
uint256
|
||||
ConsensusExtensions::buildCommitSet(LedgerIndex seq)
|
||||
{
|
||||
@@ -1866,11 +1750,9 @@ ConsensusExtensions::onPreBuild(
|
||||
|
||||
//@@start rng-inject-entropy-selection
|
||||
// One deterministic selector over the AGREED entropySetMap_ chooses the
|
||||
// digest and its tier/count. onPreBuild and buildExplicitFinalProposalTxSet
|
||||
// share it, so neither the implicit vs explicit-final paths on one node nor
|
||||
// two different nodes can derive different entropy for the same agreed
|
||||
// round inputs. txSetHash is the BASE (pre-injection) consensus tx set
|
||||
// hash.
|
||||
// digest and its tier/count. Every node derives the same entropy for the
|
||||
// same agreed round inputs. txSetHash is the BASE (pre-injection)
|
||||
// consensus tx set hash.
|
||||
auto const selection = selectEntropy(txSetHash, seq);
|
||||
uint256 const finalEntropy = selection.digest;
|
||||
std::uint8_t const entropyTier = selection.tier;
|
||||
@@ -1898,11 +1780,6 @@ ConsensusExtensions::onPreBuild(
|
||||
// agree on the base transaction set first, then deterministically
|
||||
// derive/apply the entropy pseudo-tx for ledger construction.
|
||||
//
|
||||
// Explicit-final (seq=4 synthetic proposal) remains an optional
|
||||
// experiment for observability/perf testing and is default-off.
|
||||
// TBD (2026-03-03): revisit only with stronger evidence that explicit
|
||||
// publication can be made stable under tx-bearing, lossy networks.
|
||||
|
||||
//@@start rng-inject-pseudotx-core
|
||||
// Account Zero convention for pseudo-transactions (same as ttFEE, etc)
|
||||
STTx tx(ttCONSENSUS_ENTROPY, [&](auto& obj) {
|
||||
@@ -1917,13 +1794,13 @@ ConsensusExtensions::onPreBuild(
|
||||
|
||||
auto const txID = tx.getTransactionID();
|
||||
// Value-based dedup. There must never be two entropy pseudo-txs, but
|
||||
// when one is already present (explicit-final, or a peer's agreed
|
||||
// set) it must be VALIDATED as the exact pseudo-tx we would have
|
||||
// produced — not merely "same type". Injection is deterministic, so
|
||||
// every honest node derives the identical pseudo-tx (identical txID)
|
||||
// for the same agreed inputs. A present-but-different entropy pseudo-tx
|
||||
// is therefore a determinism violation (version skew or a divergent/
|
||||
// malicious peer) and must be surfaced, not silently trusted.
|
||||
// when one is already present in the agreed set it must be VALIDATED as
|
||||
// the exact pseudo-tx we would have produced — not merely "same type".
|
||||
// Injection is deterministic, so every honest node derives the
|
||||
// identical pseudo-tx (identical txID) for the same agreed inputs. A
|
||||
// present-but-different entropy pseudo-tx is therefore a determinism
|
||||
// violation (version skew or a divergent/malicious peer) and must be
|
||||
// surfaced, not silently trusted.
|
||||
auto const existing = std::find_if(
|
||||
retriableTxs.begin(), retriableTxs.end(), [](auto const& entry) {
|
||||
return entry.second->getTxnType() == ttCONSENSUS_ENTROPY;
|
||||
|
||||
@@ -101,7 +101,6 @@ public:
|
||||
EstablishState estState_{EstablishState::ConvergingTx};
|
||||
std::chrono::steady_clock::time_point revealPhaseStart_{};
|
||||
std::chrono::steady_clock::time_point commitHashConflictStart_{};
|
||||
bool explicitFinalProposalSent_{false};
|
||||
bool entropySetPublished_{false};
|
||||
std::chrono::steady_clock::time_point entropyPublishStart_{};
|
||||
bool exportSigGateStarted_{false};
|
||||
@@ -226,12 +225,6 @@ public:
|
||||
bool
|
||||
bootstrapFastStartEnabled() const;
|
||||
|
||||
bool
|
||||
shouldSendExplicitFinalProposal() const;
|
||||
|
||||
std::optional<RCLTxSet>
|
||||
buildExplicitFinalProposalTxSet(RCLTxSet const& txns, LedgerIndex seq);
|
||||
|
||||
uint256
|
||||
buildCommitSet(LedgerIndex seq);
|
||||
|
||||
@@ -502,7 +495,6 @@ public:
|
||||
estState_ = EstablishState::ConvergingTx;
|
||||
revealPhaseStart_ = {};
|
||||
commitHashConflictStart_ = {};
|
||||
explicitFinalProposalSent_ = false;
|
||||
entropySetPublished_ = false;
|
||||
entropyPublishStart_ = {};
|
||||
exportSigGateStarted_ = false;
|
||||
|
||||
@@ -210,12 +210,6 @@ If no entropy hash reaches the entropy gate threshold before the bounded
|
||||
deadline, the round must fall back to the Tier 1 consensus-bound digest. This
|
||||
is the safe degradation path, not a consensus failure.
|
||||
|
||||
> Known exception: the experimental, default-off explicit-final proposal path
|
||||
> counts alignment over the unfiltered proposer set (not the active view). It is
|
||||
> flagged in-code as an outstanding F1 gap, but the intended disposition is
|
||||
> removal rather than repair/promotion; it must not be enabled as a production
|
||||
> consensus path.
|
||||
|
||||
Examples with six active validators on a UNLReport-anchored view (validator_quorum
|
||||
threshold five, participant_aligned threshold four; six is the smallest view with
|
||||
a non-empty Tier 2 band and non-zero tolerated Byzantine count; at five validators
|
||||
@@ -253,10 +247,10 @@ after it updates the SLE. Hooks that need final entropy must treat open-ledger
|
||||
RNG results as previews.
|
||||
|
||||
The fallback digest derives from the BASE (pre-injection) tx set hash to avoid
|
||||
circularity, and entropy pseudo-tx deduplication is value-based: if an
|
||||
explicit-final synthetic set already contains the exact pseudo-tx, injection
|
||||
skips it; a present-but-different pseudo-tx is logged as a determinism
|
||||
violation and left in the agreed set.
|
||||
circularity, and entropy pseudo-tx deduplication is value-based: if the agreed
|
||||
set already contains the exact pseudo-tx, injection skips it; a
|
||||
present-but-different pseudo-tx is logged as a determinism violation and left in
|
||||
the agreed set.
|
||||
|
||||
## Sidecar Convergence Rules
|
||||
|
||||
|
||||
@@ -41,13 +41,6 @@ struct ConfigVals
|
||||
std::optional<int> sendDelayJitterMs;
|
||||
std::optional<int> sendDropPctX100; // 0-10000 (pct * 100, avoids float)
|
||||
std::optional<int> rngClaimDropPctX100; // 0-10000 (pct * 100)
|
||||
// Controls explicit final proposal broadcast in the RNG reveal phase.
|
||||
// true = attempt explicit-final proposal (experimental)
|
||||
// false = keep implicit mode (recommended default for production)
|
||||
//
|
||||
// NOTE: This knob is intentionally explicit opt-in. The consensus system
|
||||
// is fully functional without it via accept-time pseudo-tx injection.
|
||||
std::optional<bool> explicitFinalProposal;
|
||||
// Bootstrap fast start: seed prevRoundTime_ to 3s instead of 15s on first
|
||||
// round, auto-disables after stable quorum is observed.
|
||||
std::optional<bool> bootstrapFastStart;
|
||||
@@ -76,7 +69,6 @@ struct ConfigVals
|
||||
(sendDelayJitterMs && *sendDelayJitterMs > 0) ||
|
||||
(sendDropPctX100 && *sendDropPctX100 > 0) ||
|
||||
(rngClaimDropPctX100 && *rngClaimDropPctX100 > 0) ||
|
||||
explicitFinalProposal.has_value() ||
|
||||
bootstrapFastStart.has_value() || rngPollMs.has_value() ||
|
||||
noExportSig.has_value();
|
||||
}
|
||||
@@ -94,8 +86,6 @@ struct ConfigVals
|
||||
result.sendDropPctX100 = other.sendDropPctX100;
|
||||
if (other.rngClaimDropPctX100)
|
||||
result.rngClaimDropPctX100 = other.rngClaimDropPctX100;
|
||||
if (other.explicitFinalProposal.has_value())
|
||||
result.explicitFinalProposal = other.explicitFinalProposal;
|
||||
if (other.bootstrapFastStart.has_value())
|
||||
result.bootstrapFastStart = other.bootstrapFastStart;
|
||||
if (other.rngPollMs)
|
||||
|
||||
@@ -257,8 +257,6 @@ parseConfigVals(Json::Value const& v)
|
||||
if (v.isMember("rng_claim_drop_pct"))
|
||||
cfg.rngClaimDropPctX100 =
|
||||
static_cast<int>(v["rng_claim_drop_pct"].asDouble() * 100);
|
||||
if (v.isMember("explicit_final_proposal"))
|
||||
cfg.explicitFinalProposal = v["explicit_final_proposal"].asBool();
|
||||
if (v.isMember("bootstrap_fast_start"))
|
||||
cfg.bootstrapFastStart = v["bootstrap_fast_start"].asBool();
|
||||
if (v.isMember("rng_poll_ms"))
|
||||
@@ -346,11 +344,6 @@ RuntimeConfig::RuntimeConfig()
|
||||
global.sendDropPctX100 = static_cast<int>(std::atof(env) * 100);
|
||||
if (auto const* env = std::getenv("XAHAU_RNG_CLAIM_DROP_PCT"))
|
||||
global.rngClaimDropPctX100 = static_cast<int>(std::atof(env) * 100);
|
||||
// Explicit-final proposal is intentionally opt-in and defaults to
|
||||
// implicit behavior when unset.
|
||||
if (auto parsed =
|
||||
parseBoolEnv(std::getenv("XAHAUD_EXPLICIT_FINAL_PROPOSAL")))
|
||||
global.explicitFinalProposal = *parsed;
|
||||
if (auto parsed = parseBoolEnv(std::getenv("XAHAUD_BOOTSTRAP_FAST_START")))
|
||||
global.bootstrapFastStart = *parsed;
|
||||
if (auto const* env = std::getenv("XAHAU_RNG_POLL_MS"))
|
||||
|
||||
@@ -188,8 +188,6 @@ extensionsTick(Ext& ext, Ctx const& ctx)
|
||||
<< " participants=" << participants
|
||||
<< " peerPositions=" << ctx.peerPositions.size()
|
||||
<< " prevProposers=" << ctx.prevProposers
|
||||
<< " explicitFinalSent="
|
||||
<< (ext.explicitFinalProposalSent_ ? "yes" : "no")
|
||||
<< " closeTimeConsensus="
|
||||
<< (ctx.haveCloseTimeConsensus ? "yes" : "no")
|
||||
<< " txSet=" << ourPos;
|
||||
@@ -906,193 +904,6 @@ extensionsTick(Ext& ext, Ctx const& ctx)
|
||||
<< (entropyState.conflict ? "yes" : "no");
|
||||
}
|
||||
}
|
||||
|
||||
// Optional explicit final proposal (seq=4 style):
|
||||
// publish a synthetic tx-set hash that includes the
|
||||
// consensus-entropy pseudo-tx just before accept.
|
||||
//
|
||||
// IMPORTANT DESIGN NOTE (read before editing this block):
|
||||
//
|
||||
// This path is intentionally OPTIONAL and default-off. It
|
||||
// exists for diagnostics/perf experiments (for example, making
|
||||
// monitor visibility of the final pseudo-tx set more direct),
|
||||
// NOT as a required step for consensus correctness.
|
||||
//
|
||||
// Why so conservative?
|
||||
// - The main consensus engine still keys agreement on tx-set
|
||||
// hash.
|
||||
// - Updating our tx-set hash here creates a "late identity
|
||||
// change" in establish.
|
||||
// - Under lossy/reordered networks, peers can be slightly out
|
||||
// of
|
||||
// phase: some nodes may have switched to the synthetic hash
|
||||
// while others are still on the base hash.
|
||||
// - That can fragment agreement during a critical window (two
|
||||
// hashes in flight for one ledger), increase proposal
|
||||
// chatter, and trigger sync churn.
|
||||
//
|
||||
// Therefore this logic must remain best-effort only:
|
||||
// - Never required for liveness/safety.
|
||||
// - No extra wait tick is introduced.
|
||||
// - If gates are not met, we skip and continue to accept via
|
||||
// the
|
||||
// normal implicit path (accept-time pseudo-tx injection).
|
||||
//
|
||||
// TODO: remove this explicit-final proposal path. We did not find
|
||||
// a robust timing model that folds it into a guaranteed-safe
|
||||
// explicit final proposal across lossy/reordered links without
|
||||
// increasing churn. It remains opt-in only until deleted.
|
||||
{
|
||||
bool fullParticipantCoverage = false;
|
||||
bool entropyAligned = false;
|
||||
{
|
||||
// Guard against "early switch" churn:
|
||||
// require at least as many participants as the previous
|
||||
// round before attempting the explicit-final mutation.
|
||||
//
|
||||
// This is a heuristic to reduce risk, not a proof of
|
||||
// safety. We still keep the feature
|
||||
// optional/default-off.
|
||||
//
|
||||
// OUTSTANDING (F1): unlike the main entropy gate
|
||||
// (inspectTxConvergedSidecarPeers), this explicit-final
|
||||
// participant/alignment count is over the UNFILTERED
|
||||
// trusted-proposer set (ctx.peerPositions) plus an
|
||||
// unconditional local +1 -- NOT the active validator view.
|
||||
// A trusted-but-non-active proposer can pad both
|
||||
// `participants` and `alignedPeers`, inflating the counting
|
||||
// universe above originalViewSize and eroding the same
|
||||
// Tier-2 intersection margin that the F1 fix closed for the
|
||||
// main gate. Before this path is EVER enabled it must
|
||||
// filter peers through activeValidatorView()->containsNode
|
||||
// and gate the local +1 on local active-view membership
|
||||
// (mirror inspectTxConvergedSidecarPeers) if this code
|
||||
// survives long enough to be touched. Preferred disposition
|
||||
// is deletion, not promotion.
|
||||
auto const participants = ctx.peerPositions.size() + 1;
|
||||
auto const expectedParticipants = ctx.prevProposers + 1;
|
||||
fullParticipantCoverage =
|
||||
participants >= expectedParticipants;
|
||||
// Require a majority aligned on entropySetHash before
|
||||
// mutating tx-set hash. If this threshold is loosened,
|
||||
// the probability of hash fragmentation rises quickly.
|
||||
auto const requiredEntropyAligned =
|
||||
(expectedParticipants / 2) + 1;
|
||||
auto const ourPos = ctx.getPosition();
|
||||
if (ourPos.entropySetHash)
|
||||
{
|
||||
auto const expectedEntropy = *ourPos.entropySetHash;
|
||||
std::size_t alignedPeers = 0;
|
||||
bool conflict = false;
|
||||
for (auto const& [_, peerPos] : ctx.peerPositions)
|
||||
{
|
||||
auto const& peerPosition =
|
||||
peerPos.proposal().position();
|
||||
if (!peerPosition.entropySetHash)
|
||||
continue;
|
||||
if (*peerPosition.entropySetHash == expectedEntropy)
|
||||
{
|
||||
++alignedPeers;
|
||||
continue;
|
||||
}
|
||||
conflict = true;
|
||||
break;
|
||||
}
|
||||
|
||||
auto const alignedParticipants = alignedPeers + 1;
|
||||
entropyAligned = !conflict &&
|
||||
alignedParticipants >= requiredEntropyAligned;
|
||||
if (!entropyAligned)
|
||||
{
|
||||
JLOG(ext.j_.debug())
|
||||
<< "RNG: explicit-final entropy alignment "
|
||||
"insufficient"
|
||||
<< " buildSeq=" << buildSeq
|
||||
<< " alignedParticipants="
|
||||
<< alignedParticipants
|
||||
<< " required=" << requiredEntropyAligned
|
||||
<< " conflict=" << (conflict ? "yes" : "no");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(ext.j_.debug())
|
||||
<< "RNG: explicit-final waiting"
|
||||
<< " reason=missing-local-entropySetHash"
|
||||
<< " buildSeq=" << buildSeq;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.mode == ConsensusMode::proposing &&
|
||||
!ext.explicitFinalProposalSent_ &&
|
||||
ext.hasQuorumOfCommits() && revealConsensus &&
|
||||
fullParticipantCoverage && entropyAligned &&
|
||||
ext.shouldSendExplicitFinalProposal())
|
||||
{
|
||||
// One-shot per round. This avoids repeated mutations/
|
||||
// broadcasts from timer ticks, which can amplify
|
||||
// network chatter in the exact conditions
|
||||
// (loss/reordering) where this path is already fragile.
|
||||
auto const synthSet = ext.buildExplicitFinalProposalTxSet(
|
||||
ctx.getTxns(), buildSeq);
|
||||
ext.explicitFinalProposalSent_ = true;
|
||||
|
||||
if (synthSet)
|
||||
{
|
||||
auto const synthHash = synthSet->id();
|
||||
auto currentPos = ctx.getPosition();
|
||||
auto newPos = currentPos;
|
||||
newPos.updateTxSet(synthHash);
|
||||
|
||||
if (!(newPos == currentPos))
|
||||
{
|
||||
// WARNING:
|
||||
// This changes proposal tx-set identity late in
|
||||
// establish. Keep this path tightly gated and
|
||||
// optional. The canonical ledger path remains
|
||||
// the implicit accept-time injection logic.
|
||||
|
||||
// Maintain the invariant that our active
|
||||
// position's tx-set hash is present in
|
||||
// acquired_, otherwise gotTxSet can assert if
|
||||
// this set arrives back from the network.
|
||||
ctx.cacheAndShareTxSet(*synthSet);
|
||||
JLOG(ext.j_.debug())
|
||||
<< "RNG: cached explicit-final txSet"
|
||||
<< " buildSeq=" << buildSeq
|
||||
<< " txSet=" << synthHash;
|
||||
ctx.updatePosition(newPos);
|
||||
ctx.propose();
|
||||
JLOG(ext.j_.debug())
|
||||
<< "RNG: explicit final proposal"
|
||||
<< " buildSeq=" << buildSeq
|
||||
<< " txSet=" << synthHash;
|
||||
logRngDiag("rng-explicit-final-proposed");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
char const* reason = "disabled";
|
||||
if (ctx.mode != ConsensusMode::proposing)
|
||||
reason = "not-proposing";
|
||||
else if (ext.explicitFinalProposalSent_)
|
||||
reason = "already-sent";
|
||||
else if (!ext.hasQuorumOfCommits())
|
||||
reason = "no-commit-quorum";
|
||||
else if (!revealConsensus)
|
||||
reason = "reveal-timeout";
|
||||
else if (!fullParticipantCoverage)
|
||||
reason = "participant-gap";
|
||||
else if (!entropyAligned)
|
||||
reason = "entropy-not-aligned";
|
||||
JLOG(ext.j_.debug())
|
||||
<< "STALLDIAG: rng-explicit-final-skipped"
|
||||
<< " reason=" << reason << " buildSeq=" << buildSeq
|
||||
<< " mode=" << to_string(ctx.mode) << " sent="
|
||||
<< (ext.explicitFinalProposalSent_ ? "yes" : "no");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -62,9 +62,6 @@ doRuntimeConfig(RPC::JsonContext& context)
|
||||
pct = 100.0;
|
||||
cfg.rngClaimDropPctX100 = static_cast<int>(pct * 100);
|
||||
}
|
||||
if (v.isMember("explicit_final_proposal"))
|
||||
cfg.explicitFinalProposal =
|
||||
v["explicit_final_proposal"].asBool();
|
||||
if (v.isMember("bootstrap_fast_start"))
|
||||
cfg.bootstrapFastStart = v["bootstrap_fast_start"].asBool();
|
||||
if (v.isMember("rng_poll_ms"))
|
||||
@@ -131,8 +128,6 @@ doRuntimeConfig(RPC::JsonContext& context)
|
||||
entry["send_drop_pct"] = *cfg.sendDropPctX100 / 100.0;
|
||||
if (cfg.rngClaimDropPctX100)
|
||||
entry["rng_claim_drop_pct"] = *cfg.rngClaimDropPctX100 / 100.0;
|
||||
if (cfg.explicitFinalProposal.has_value())
|
||||
entry["explicit_final_proposal"] = *cfg.explicitFinalProposal;
|
||||
if (cfg.bootstrapFastStart.has_value())
|
||||
entry["bootstrap_fast_start"] = *cfg.bootstrapFastStart;
|
||||
if (cfg.rngPollMs)
|
||||
|
||||
Reference in New Issue
Block a user