Skip to content

Commit b5a56d8

Browse files
committed
Implements the new quorum intersection checker
- Rust bridge and wrapper for `FbasAnalyzer` (stellar/stellar-quorum-analyzer) - A new adaptor class `RustQuorumCheckerAdaptor` that wraps the Rust calls - Async interruption handling - A new config flag `USE_QUORUM_INTERSECTION_CHECKER_V2` to toggle between old and new solver - Update all tests - Add static counters for invocation metrics
1 parent 8d8266f commit b5a56d8

21 files changed

+713
-75
lines changed

Builds/VisualStudio/stellar-core.vcxproj

+2
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,7 @@ exit /b 0
521521
<ClCompile Include="..\..\src\herder\PendingEnvelopes.cpp" />
522522
<ClCompile Include="..\..\src\herder\QuorumIntersectionCheckerImpl.cpp" />
523523
<ClCompile Include="..\..\src\herder\QuorumTracker.cpp" />
524+
<ClCompile Include="..\..\src\herder\RustQuorumCheckerAdaptor.cpp" />
524525
<ClCompile Include="..\..\src\herder\SurgePricingUtils.cpp" />
525526
<ClCompile Include="..\..\src\herder\test\HerderTests.cpp" />
526527
<ClCompile Include="..\..\src\herder\test\PendingEnvelopesTests.cpp" />
@@ -976,6 +977,7 @@ exit /b 0
976977
<ClInclude Include="..\..\src\herder\QuorumIntersectionChecker.h" />
977978
<ClInclude Include="..\..\src\herder\QuorumIntersectionCheckerImpl.h" />
978979
<ClInclude Include="..\..\src\herder\QuorumTracker.h" />
980+
<ClInclude Include="..\..\src\herder\RustQuorumCheckerAdaptor.h" />
979981
<ClInclude Include="..\..\src\herder\SurgePricingUtils.h" />
980982
<ClInclude Include="..\..\src\herder\test\TestTxSetUtils.h" />
981983
<ClInclude Include="..\..\src\herder\TransactionQueue.h" />

Builds/VisualStudio/stellar-core.vcxproj.filters

+6
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,9 @@
696696
<ClCompile Include="..\..\src\herder\QuorumTracker.cpp">
697697
<Filter>herder</Filter>
698698
</ClCompile>
699+
<ClCompile Include="..\..\src\herder\RustQuorumCheckerAdaptor.cpp">
700+
<Filter>herder</Filter>
701+
</ClCompile>
699702
<ClCompile Include="..\..\src\herder\SurgePricingUtils.cpp">
700703
<Filter>herder</Filter>
701704
</ClCompile>
@@ -1892,6 +1895,9 @@
18921895
<ClInclude Include="..\..\src\herder\QuorumTracker.h">
18931896
<Filter>herder</Filter>
18941897
</ClInclude>
1898+
<ClInclude Include="..\..\src\herder\RustQuorumCheckerAdaptor.h">
1899+
<Filter>herder</Filter>
1900+
</ClInclude>
18951901
<ClInclude Include="..\..\src\herder\SurgePricingUtils.h">
18961902
<Filter>herder</Filter>
18971903
</ClInclude>

Cargo.lock

+45
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/versioning-soroban.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ We are leveraging Rust's support for linking together multiple copies of "the
139139
same" library (soroban) with different versions, but we are doing so somewhat
140140
against the grain of how cargo normally wants to do it.
141141

142-
Do do this "the normal way", we would just list the different versions of the
142+
To do this "the normal way", we would just list the different versions of the
143143
soroban crate in `Cargo.toml`, and then when we built it cargo would attempt to
144144
resolve all the dependencies and transitive-dependencies of all those soroban
145145
versions into a hopefully-minimal set of crates and download, compile and link
@@ -165,7 +165,7 @@ This has one minor and one major problem:
165165
p22 module on foo 0.1, cargo will bump _both_ to foo 0.2, which _changes_
166166
the semantics of the p22 module.
167167

168-
- We initially though a way out of this is to add redundant exact-version
168+
- We initially thought a way out of this is to add redundant exact-version
169169
dependencies (like `foo = "=0.2"`) to `Cargo.toml` for
170170
`soroban-env-host` but there turn out to be both a minor and a major
171171
problem with that too.

src/herder/HerderImpl.cpp

+78-11
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "herder/HerderUtils.h"
1414
#include "herder/LedgerCloseData.h"
1515
#include "herder/QuorumIntersectionChecker.h"
16+
#include "herder/RustQuorumCheckerAdaptor.h"
1617
#include "herder/TxSetFrame.h"
1718
#include "herder/TxSetUtils.h"
1819
#include "ledger/LedgerManager.h"
@@ -261,6 +262,13 @@ HerderImpl::newSlotExternalized(bool synchronous, StellarValue const& value)
261262
safelyProcessSCPQueue(synchronous);
262263
}
263264

265+
void
266+
HerderImpl::interrupt_quorum_checker()
267+
{
268+
mLastQuorumMapIntersectionState.mInterruptFlag = true;
269+
mLastQuorumMapIntersectionState.mInterrupt->fire();
270+
}
271+
264272
void
265273
HerderImpl::shutdown()
266274
{
@@ -273,7 +281,7 @@ HerderImpl::shutdown()
273281
// avoid a long pause joining worker threads.
274282
CLOG_DEBUG(Herder,
275283
"Shutdown interrupting quorum transitive closure analysis.");
276-
mLastQuorumMapIntersectionState.mInterruptFlag = true;
284+
interrupt_quorum_checker();
277285
}
278286
mTransactionQueue.shutdown();
279287
if (mSorobanTransactionQueue)
@@ -1883,7 +1891,7 @@ HerderImpl::checkAndMaybeReanalyzeQuorumMap()
18831891
CLOG_DEBUG(Herder, "Transitive closure of quorum has "
18841892
"changed, interrupting existing "
18851893
"analysis.");
1886-
mLastQuorumMapIntersectionState.mInterruptFlag = true;
1894+
interrupt_quorum_checker();
18871895
}
18881896
}
18891897
else
@@ -1897,33 +1905,76 @@ HerderImpl::checkAndMaybeReanalyzeQuorumMap()
18971905
auto& cfg = mApp.getConfig();
18981906
releaseAssert(threadIsMain());
18991907
auto seed = gRandomEngine();
1900-
auto qic = QuorumIntersectionChecker::create(
1901-
qmap, cfg, mLastQuorumMapIntersectionState.mInterruptFlag, seed);
1908+
19021909
auto ledger = trackingConsensusLedgerIndex();
19031910
auto nNodes = qmap.size();
19041911
auto& hState = mLastQuorumMapIntersectionState;
19051912
auto& app = mApp;
1906-
auto worker = [curr, ledger, nNodes, qic, qmap, cfg, seed, &app,
1907-
&hState] {
1913+
auto worker = [curr, ledger, nNodes, qmap, cfg, seed, &app, &hState] {
19081914
try
19091915
{
19101916
ZoneScoped;
1911-
bool ok = qic->networkEnjoysQuorumIntersection();
1912-
auto split = qic->getPotentialSplit();
1917+
bool useV2 = app.getConfig().USE_QUORUM_INTERSECTION_CHECKER_V2;
1918+
bool ok = false;
1919+
std::pair<std::vector<PublicKey>, std::vector<PublicKey>> split;
1920+
if (useV2)
1921+
{
1922+
ok = RustQuorumCheckerAdaptor::
1923+
networkEnjoysQuorumIntersection(
1924+
qmap, cfg, *hState.mInterrupt, split);
1925+
}
1926+
else
1927+
{
1928+
auto qic = QuorumIntersectionChecker::create(
1929+
qmap, cfg, hState.mInterruptFlag, seed);
1930+
ok = qic->networkEnjoysQuorumIntersection();
1931+
split = qic->getPotentialSplit();
1932+
}
19131933
std::set<std::set<PublicKey>> critical;
19141934
if (ok)
19151935
{
19161936
// Only bother calculating the _critical_ groups if we're
19171937
// intersecting; if not intersecting we should finish ASAP
19181938
// and raise an alarm.
1919-
critical = QuorumIntersectionChecker::
1920-
getIntersectionCriticalGroups(
1921-
qmap, cfg, hState.mInterruptFlag, seed);
1939+
if (useV2)
1940+
{
1941+
auto cb =
1942+
[&hState](
1943+
QuorumIntersectionChecker::QuorumSetMap const&
1944+
qSetMap,
1945+
std::optional<Config> const& config) -> bool {
1946+
std::pair<std::vector<PublicKey>,
1947+
std::vector<PublicKey>>
1948+
potential_split;
1949+
return RustQuorumCheckerAdaptor::
1950+
networkEnjoysQuorumIntersection(
1951+
qSetMap, config, *hState.mInterrupt,
1952+
potential_split);
1953+
};
1954+
critical = QuorumIntersectionChecker::
1955+
getIntersectionCriticalGroups(qmap, cfg, cb);
1956+
}
1957+
else
1958+
{
1959+
auto cb =
1960+
[&hState, seed](
1961+
QuorumIntersectionChecker::QuorumSetMap const&
1962+
qSetMap,
1963+
std::optional<Config> const& config) -> bool {
1964+
auto checker = QuorumIntersectionChecker::create(
1965+
qSetMap, config, hState.mInterruptFlag, seed,
1966+
/*quiet=*/true);
1967+
return checker->networkEnjoysQuorumIntersection();
1968+
};
1969+
critical = QuorumIntersectionChecker::
1970+
getIntersectionCriticalGroups(qmap, cfg, cb);
1971+
}
19221972
}
19231973
app.postOnMainThread(
19241974
[ok, curr, ledger, nNodes, split, critical, &hState] {
19251975
hState.mRecalculating = false;
19261976
hState.mInterruptFlag = false;
1977+
hState.mInterrupt->reset();
19271978
hState.mNumNodes = nNodes;
19281979
hState.mLastCheckLedger = ledger;
19291980
hState.mLastCheckQuorumMapHash = curr;
@@ -1945,10 +1996,26 @@ HerderImpl::checkAndMaybeReanalyzeQuorumMap()
19451996
[&hState] {
19461997
hState.mRecalculating = false;
19471998
hState.mInterruptFlag = false;
1999+
hState.mInterrupt->reset();
19482000
hState.mCheckingQuorumMapHash = Hash{};
19492001
},
19502002
"QuorumIntersectionChecker interrupted");
19512003
}
2004+
catch (const RustQuorumCheckerError& e)
2005+
{
2006+
CLOG_DEBUG(Herder,
2007+
"Quorum transitive closure analysis failed due to "
2008+
"Rust solver error: {}",
2009+
e.what());
2010+
app.postOnMainThread(
2011+
[&hState] {
2012+
hState.mRecalculating = false;
2013+
hState.mInterruptFlag = false;
2014+
hState.mInterrupt->reset();
2015+
hState.mCheckingQuorumMapHash = Hash{};
2016+
},
2017+
"QuorumIntersectionChecker rust error");
2018+
}
19522019
};
19532020
mApp.postOnBackgroundThread(worker, "QuorumIntersectionChecker");
19542021
}

src/herder/HerderImpl.h

+10-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "herder/PendingEnvelopes.h"
1010
#include "herder/TransactionQueue.h"
1111
#include "herder/Upgrades.h"
12+
#include "rust/RustBridge.h"
1213
#include "util/Timer.h"
1314
#include "util/UnorderedMap.h"
1415
#include "util/XDROperators.h"
@@ -30,7 +31,7 @@ constexpr uint32 const SOROBAN_TRANSACTION_QUEUE_SIZE_MULTIPLIER = 2;
3031
class Application;
3132
class LedgerManager;
3233
class HerderSCPDriver;
33-
34+
class InterruptGuard;
3435
/*
3536
* Is in charge of receiving transactions from the network.
3637
*/
@@ -338,7 +339,13 @@ class HerderImpl : public Herder
338339
Hash mLastCheckQuorumMapHash{};
339340
Hash mCheckingQuorumMapHash{};
340341
bool mRecalculating{false};
342+
343+
// for v1 (QuorumIntersectionChecker)
341344
std::atomic<bool> mInterruptFlag{false};
345+
// for v2 (rust quorum checker)
346+
rust::Box<rust_bridge::quorum_checker::Interrupt> mInterrupt{
347+
rust_bridge::quorum_checker::new_interrupt()};
348+
342349
std::pair<std::vector<PublicKey>, std::vector<PublicKey>>
343350
mPotentialSplit{};
344351
std::set<std::set<PublicKey>> mIntersectionCriticalNodes{};
@@ -357,6 +364,8 @@ class HerderImpl : public Herder
357364
};
358365
QuorumMapIntersectionState mLastQuorumMapIntersectionState;
359366

367+
void interrupt_quorum_checker();
368+
360369
State mState;
361370
void setState(State st);
362371

src/herder/HerderUtils.cpp

+49-1
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
44

55
#include "herder/HerderUtils.h"
6+
#include "crypto/KeyUtils.h"
7+
#include "main/Config.h"
8+
#include "rust/RustVecXdrMarshal.h"
69
#include "scp/Slot.h"
710
#include "xdr/Stellar-ledger.h"
811
#include <algorithm>
9-
#include <xdrpp/marshal.h>
1012

1113
namespace stellar
1214
{
@@ -40,4 +42,50 @@ getStellarValues(SCPStatement const& statement)
4042

4143
return result;
4244
}
45+
46+
// Render `id` as a short, human readable string. If `cfg` has a value, this
47+
// function uses `cfg` to render the string. Otherwise, it returns the first 5
48+
// hex values `id`.
49+
std::string
50+
toShortString(std::optional<Config> const& cfg, NodeID const& id)
51+
{
52+
if (cfg)
53+
{
54+
return cfg->toShortString(id);
55+
}
56+
else
57+
{
58+
return KeyUtils::toShortString(id).substr(0, 5);
59+
}
60+
}
61+
62+
QuorumIntersectionChecker::QuorumSetMap
63+
toQuorumIntersectionMap(QuorumTracker::QuorumMap const& qmap)
64+
{
65+
QuorumIntersectionChecker::QuorumSetMap ret;
66+
for (auto const& elem : qmap)
67+
{
68+
ret[elem.first] = elem.second.mQuorumSet;
69+
}
70+
return ret;
71+
}
72+
73+
std::pair<std::vector<PublicKey>, std::vector<PublicKey>>
74+
toQuorumSplitNodeIDs(QuorumSplit& split)
75+
{
76+
std::vector<NodeID> leftNodes;
77+
leftNodes.reserve(split.left.size());
78+
for (const auto& str : split.left)
79+
{
80+
leftNodes.push_back(KeyUtils::fromStrKey<NodeID>(std::string(str)));
81+
}
82+
std::vector<NodeID> rightNodes;
83+
rightNodes.reserve(split.right.size());
84+
for (const auto& str : split.right)
85+
{
86+
rightNodes.push_back(KeyUtils::fromStrKey<NodeID>(std::string(str)));
87+
}
88+
return std::make_pair(std::move(leftNodes), std::move(rightNodes));
89+
}
90+
4391
}

0 commit comments

Comments
 (0)