20
20
#include " history/HistoryManager.h"
21
21
#include " ledger/FlushAndRotateMetaDebugWork.h"
22
22
#include " ledger/LedgerHeaderUtils.h"
23
+ #include " ledger/LedgerManager.h"
23
24
#include " ledger/LedgerTxn.h"
24
25
#include " ledger/LedgerTxnEntry.h"
25
26
#include " ledger/LedgerTxnHeader.h"
27
+ #include " ledger/SharedModuleCacheCompiler.h"
26
28
#include " main/Application.h"
27
29
#include " main/Config.h"
28
30
#include " main/ErrorMessages.h"
31
+ #include " rust/RustBridge.h"
29
32
#include " transactions/MutableTransactionResult.h"
30
33
#include " transactions/OperationFrame.h"
31
34
#include " transactions/TransactionFrameBase.h"
41
44
#include " util/XDRCereal.h"
42
45
#include " util/XDRStream.h"
43
46
#include " work/WorkScheduler.h"
47
+ #include " xdr/Stellar-ledger-entries.h"
44
48
#include " xdrpp/printer.h"
45
49
50
+ #include < cstdint>
46
51
#include < fmt/format.h>
47
52
48
53
#include " xdr/Stellar-ledger-entries.h"
58
63
#include < Tracy.hpp>
59
64
60
65
#include < chrono>
66
+ #include < memory>
67
+ #include < mutex>
61
68
#include < optional>
62
69
#include < regex>
63
70
#include < sstream>
@@ -125,6 +132,27 @@ LedgerManager::ledgerAbbrev(LedgerHeaderHistoryEntry const& he)
125
132
return ledgerAbbrev (he.header , he.hash );
126
133
}
127
134
135
+ static std::vector<uint32_t >
136
+ getModuleCacheProtocols ()
137
+ {
138
+ std::vector<uint32_t > ledgerVersions;
139
+ for (uint32_t i = (uint32_t )REUSABLE_SOROBAN_MODULE_CACHE_PROTOCOL_VERSION;
140
+ i <= Config::CURRENT_LEDGER_PROTOCOL_VERSION; i++)
141
+ {
142
+ ledgerVersions.push_back (i);
143
+ }
144
+ auto extra = getenv (" SOROBAN_TEST_EXTRA_PROTOCOL" );
145
+ if (extra)
146
+ {
147
+ uint32_t proto = static_cast <uint32_t >(atoi (extra));
148
+ if (proto > 0 )
149
+ {
150
+ ledgerVersions.push_back (proto);
151
+ }
152
+ }
153
+ return ledgerVersions;
154
+ }
155
+
128
156
LedgerManagerImpl::LedgerManagerImpl (Application& app)
129
157
: mApp (app)
130
158
, mSorobanMetrics (app.getMetrics())
@@ -157,6 +185,8 @@ LedgerManagerImpl::LedgerManagerImpl(Application& app)
157
185
, mCatchupDuration (
158
186
app.getMetrics().NewTimer({" ledger" , " catchup" , " duration" }))
159
187
, mState (LM_BOOTING_STATE)
188
+ , mModuleCache (::rust_bridge::new_module_cache())
189
+ , mModuleCacheProtocols (getModuleCacheProtocols())
160
190
161
191
{
162
192
setupLedgerCloseMetaStream ();
@@ -405,6 +435,9 @@ LedgerManagerImpl::loadLastKnownLedger(bool restoreBucketlist)
405
435
updateNetworkConfig (ltx);
406
436
mSorobanNetworkConfigReadOnly = mSorobanNetworkConfigForApply ;
407
437
}
438
+
439
+ // Prime module cache with ledger content.
440
+ compileAllContractsInLedger (latestLedgerHeader->ledgerVersion );
408
441
}
409
442
410
443
Database&
@@ -560,6 +593,118 @@ LedgerManagerImpl::getSorobanMetrics()
560
593
return mSorobanMetrics ;
561
594
}
562
595
596
+ ::rust::Box<rust_bridge::SorobanModuleCache>
597
+ LedgerManagerImpl::getModuleCache ()
598
+ {
599
+ std::lock_guard<std::recursive_mutex> guard (mLedgerStateMutex );
600
+ finishAnyPendingCompilation ();
601
+ return mModuleCache ->shallow_clone ();
602
+ }
603
+
604
+ void
605
+ LedgerManagerImpl::finishAnyPendingCompilation ()
606
+ {
607
+ std::lock_guard<std::recursive_mutex> guard (mLedgerStateMutex );
608
+ if (mCompiler )
609
+ {
610
+ auto newCache = mCompiler ->wait ();
611
+ mSorobanMetrics .mModuleCacheRebuildBytes .set_count (
612
+ (int64)mCompiler ->getBytesCompiled ());
613
+ mSorobanMetrics .mModuleCacheNumEntries .set_count (
614
+ (int64)mCompiler ->getContractsCompiled ());
615
+ mSorobanMetrics .mModuleCacheRebuildTime .Update (
616
+ mCompiler ->getCompileTime ());
617
+ mModuleCache .swap (newCache);
618
+ mCompiler .reset ();
619
+ mApp .getAppConnector ().setModuleCache (mModuleCache ->shallow_clone ());
620
+ }
621
+ }
622
+
623
+ void
624
+ LedgerManagerImpl::compileAllContractsInLedger (uint32_t minLedgerVersion)
625
+ {
626
+ startCompilingAllContracts (minLedgerVersion);
627
+ finishAnyPendingCompilation ();
628
+ }
629
+
630
+ void
631
+ LedgerManagerImpl::startCompilingAllContracts (uint32_t minLedgerVersion)
632
+ {
633
+ std::lock_guard<std::recursive_mutex> guard (mLedgerStateMutex );
634
+ // Always stop a previous compilation before starting a new one. Can only
635
+ // have one running at any time.
636
+ finishAnyPendingCompilation ();
637
+ std::vector<uint32_t > versions;
638
+ for (auto const & v : mModuleCacheProtocols )
639
+ {
640
+ if (v >= minLedgerVersion)
641
+ {
642
+ versions.push_back (v);
643
+ }
644
+ }
645
+ mCompiler = std::make_unique<SharedModuleCacheCompiler>(mApp , versions);
646
+ mCompiler ->start ();
647
+ }
648
+
649
+ void
650
+ LedgerManagerImpl::maybeRebuildModuleCache (uint32_t minLedgerVersion)
651
+ {
652
+ std::lock_guard<std::recursive_mutex> guard (mLedgerStateMutex );
653
+ // There is (currently) a grow-only arena underlying the module cache, so as
654
+ // entries are uploaded and evicted that arena will still grow. To cap this
655
+ // growth, we periodically rebuild the module cache from scratch.
656
+ //
657
+ // We could pick various size caps, but we want to avoid rebuilding
658
+ // spuriously when there just happens to be "a fairly large" cache due to
659
+ // having a fairly large live BL. I.e. we want to allow it to get as big as
660
+ // we can -- or as big as the "natural" BL-limits-dictated size -- while
661
+ // still rebuilding fairly often in DoS-attempt scenarios or just generally
662
+ // if there's regular upload/expiry churn that would otherwise cause
663
+ // unbounded growth.
664
+ //
665
+ // Unfortunately we do not know exactly how much memory is used by each byte
666
+ // of contract we compile, and the size estimates from the cost model have
667
+ // to assume a worst case which is almost a factor of _40_ larger than the
668
+ // byte-size of the contracts. So for example if we assume 100MB of
669
+ // contracts, the cost model says we ought to budget for 4GB of memory, just
670
+ // in case _all 100MB of contracts_ are "the worst case contract" that's
671
+ // just a continuous stream of function definitions.
672
+ //
673
+ // So: we take this multiplier, times the size of the contracts we _last_
674
+ // drew from the BL when doing a full recompile, times two, as a cap on the
675
+ // _current_ (post-rebuild, currently-growing) cache's budget-tracked
676
+ // memory. This should avoid rebuilding spuriously, while still treating
677
+ // events that double the size of the contract-set in the live BL as an
678
+ // event that warrants a rebuild.
679
+
680
+ // We try to fish the current cost multiplier out of the soroban network
681
+ // config's memory cost model, but fall back to a conservative default in
682
+ // case there is no mem cost param for VmInstantiation (This should never
683
+ // happen but just in case).
684
+ uint64_t linearTerm = 5000 ;
685
+
686
+ // linearTerm is in 1/128ths in the cost model, to reduce rounding error.
687
+ uint64_t scale = 128 ;
688
+
689
+ auto const & cfg = getSorobanNetworkConfigForApply ();
690
+ auto const & memParams = cfg.memCostParams ();
691
+ if (memParams.size () > (size_t )stellar::VmInstantiation)
692
+ {
693
+ auto const & param = memParams[(size_t )stellar::VmInstantiation];
694
+ linearTerm = param.linearTerm ;
695
+ }
696
+ auto lastBytesCompiled = mSorobanMetrics .mModuleCacheRebuildBytes .count ();
697
+ uint64_t limit = 2 * lastBytesCompiled * linearTerm / scale;
698
+ if (mModuleCache ->get_mem_bytes_consumed () > limit)
699
+ {
700
+ CLOG_DEBUG (Ledger,
701
+ " Rebuilding module cache: worst-case estimate {} "
702
+ " model-bytes consumed of {} limit" ,
703
+ mModuleCache ->get_mem_bytes_consumed (), limit);
704
+ startCompilingAllContracts (minLedgerVersion);
705
+ }
706
+ }
707
+
563
708
void
564
709
LedgerManagerImpl::publishSorobanMetrics ()
565
710
{
@@ -803,6 +948,9 @@ LedgerManagerImpl::closeLedger(LedgerCloseData const& ledgerData,
803
948
return ;
804
949
}
805
950
951
+ // Complete any pending wasm-module-compilation before closing the ledger.
952
+ finishAnyPendingCompilation ();
953
+
806
954
#ifdef BUILD_TESTS
807
955
mLastLedgerTxMeta .clear ();
808
956
#endif
@@ -1149,11 +1297,14 @@ LedgerManagerImpl::setLastClosedLedger(
1149
1297
advanceLedgerPointers (advanceLedgerStateSnapshot (lastClosed.header , has));
1150
1298
1151
1299
LedgerTxn ltx2 (mApp .getLedgerTxnRoot ());
1152
- if ( protocolVersionStartsFrom ( ltx2.loadHeader ().current ().ledgerVersion ,
1153
- SOROBAN_PROTOCOL_VERSION))
1300
+ auto lv = ltx2.loadHeader ().current ().ledgerVersion ;
1301
+ if ( protocolVersionStartsFrom (lv, SOROBAN_PROTOCOL_VERSION))
1154
1302
{
1155
- mApp . getLedgerManager (). updateNetworkConfig (ltx2);
1303
+ updateNetworkConfig (ltx2);
1156
1304
}
1305
+ // This should not be additionally conditionalized on lv >= anything,
1306
+ // since we want to support SOROBAN_TEST_EXTRA_PROTOCOL > lv.
1307
+ compileAllContractsInLedger (lv);
1157
1308
}
1158
1309
1159
1310
void
@@ -1804,6 +1955,8 @@ LedgerManagerImpl::transferLedgerEntriesToBucketList(
1804
1955
ledgerCloseMeta->populateEvictedEntries (evictedState);
1805
1956
}
1806
1957
1958
+ evictFromModuleCache (lh.ledgerVersion , evictedState);
1959
+
1807
1960
ltxEvictions.commit ();
1808
1961
}
1809
1962
@@ -1814,6 +1967,8 @@ LedgerManagerImpl::transferLedgerEntriesToBucketList(
1814
1967
ltx.getAllEntries (initEntries, liveEntries, deadEntries);
1815
1968
if (blEnabled)
1816
1969
{
1970
+ addAnyContractsToModuleCache (lh.ledgerVersion , initEntries);
1971
+ addAnyContractsToModuleCache (lh.ledgerVersion , liveEntries);
1817
1972
mApp .getBucketManager ().addLiveBatch (mApp , lh, initEntries, liveEntries,
1818
1973
deadEntries);
1819
1974
}
@@ -1872,6 +2027,75 @@ LedgerManagerImpl::ledgerClosed(
1872
2027
res = advanceLedgerStateSnapshot (lh, has);
1873
2028
});
1874
2029
2030
+ if (protocolVersionStartsFrom (
2031
+ initialLedgerVers, REUSABLE_SOROBAN_MODULE_CACHE_PROTOCOL_VERSION))
2032
+ {
2033
+ maybeRebuildModuleCache (initialLedgerVers);
2034
+ }
2035
+
1875
2036
return res;
1876
2037
}
2038
+
2039
+ void
2040
+ LedgerManagerImpl::evictFromModuleCache (uint32_t ledgerVersion,
2041
+ EvictedStateVectors const & evictedState)
2042
+ {
2043
+ std::vector<Hash> keys;
2044
+ for (auto const & key : evictedState.deletedKeys )
2045
+ {
2046
+ if (key.type () == CONTRACT_CODE)
2047
+ {
2048
+ keys.emplace_back (key.contractCode ().hash );
2049
+ }
2050
+ }
2051
+ for (auto const & entry : evictedState.archivedEntries )
2052
+ {
2053
+ if (entry.data .type () == CONTRACT_CODE)
2054
+ {
2055
+ Hash const & hash = entry.data .contractCode ().hash ;
2056
+ keys.emplace_back (hash);
2057
+ }
2058
+ }
2059
+ if (keys.size () > 0 )
2060
+ {
2061
+ CLOG_DEBUG (Ledger, " evicting {} modules from module cache" ,
2062
+ keys.size ());
2063
+ for (auto const & hash : keys)
2064
+ {
2065
+ CLOG_DEBUG (Ledger, " evicting {} from module cache" , binToHex (hash));
2066
+ ::rust::Slice<uint8_t const > slice{hash.data (), hash.size ()};
2067
+ mModuleCache ->evict_contract_code (slice);
2068
+ mSorobanMetrics .mModuleCacheNumEntries .dec ();
2069
+ }
2070
+ }
2071
+ }
2072
+
2073
+ void
2074
+ LedgerManagerImpl::addAnyContractsToModuleCache (
2075
+ uint32_t ledgerVersion, std::vector<LedgerEntry> const & le)
2076
+ {
2077
+ for (auto const & e : le)
2078
+ {
2079
+ if (e.data .type () == CONTRACT_CODE)
2080
+ {
2081
+ for (auto const & v : mModuleCacheProtocols )
2082
+ {
2083
+ if (v >= ledgerVersion)
2084
+ {
2085
+ auto const & wasm = e.data .contractCode ().code ;
2086
+ CLOG_DEBUG (Ledger,
2087
+ " compiling wasm {} for protocol {} module cache" ,
2088
+ binToHex (sha256 (wasm)), v);
2089
+ auto slice =
2090
+ rust::Slice<const uint8_t >(wasm.data (), wasm.size ());
2091
+ mSorobanMetrics .mModuleCacheNumEntries .inc ();
2092
+ auto timer =
2093
+ mSorobanMetrics .mModuleCompilationTime .TimeScope ();
2094
+ mModuleCache ->compile (v, slice);
2095
+ }
2096
+ }
2097
+ }
2098
+ }
2099
+ }
2100
+
1877
2101
}
0 commit comments