Skip to content

Commit a5be37c

Browse files
kwvgPastaPastaPasta
authored andcommitted
refactor: remove CDeterministicMNManager global, move to NodeContext
1 parent cf90cf2 commit a5be37c

File tree

10 files changed

+70
-37
lines changed

10 files changed

+70
-37
lines changed

src/evo/deterministicmns.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@
2727
static const std::string DB_LIST_SNAPSHOT = "dmn_S3";
2828
static const std::string DB_LIST_DIFF = "dmn_D3";
2929

30-
std::unique_ptr<CDeterministicMNManager> deterministicMNManager;
31-
3230
uint64_t CDeterministicMN::GetInternalId() const
3331
{
3432
// can't get it if it wasn't set yet

src/evo/deterministicmns.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,4 @@ bool CheckProUpServTx(CDeterministicMNManager& dmnman, const CTransaction& tx, g
637637
bool CheckProUpRegTx(CDeterministicMNManager& dmnman, const CTransaction& tx, gsl::not_null<const CBlockIndex*> pindexPrev, TxValidationState& state, const CCoinsViewCache& view, bool check_sigs);
638638
bool CheckProUpRevTx(CDeterministicMNManager& dmnman, const CTransaction& tx, gsl::not_null<const CBlockIndex*> pindexPrev, TxValidationState& state, bool check_sigs);
639639

640-
extern std::unique_ptr<CDeterministicMNManager> deterministicMNManager;
641-
642640
#endif // BITCOIN_EVO_DETERMINISTICMNS_H

src/init.cpp

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -343,8 +343,8 @@ void PrepareShutdown(NodeContext& node)
343343
node.llmq_ctx.reset();
344344
}
345345
llmq::quorumSnapshotManager.reset();
346-
node.dmnman = nullptr;
347-
deterministicMNManager.reset();
346+
node.mempool->DisconnectManagers();
347+
node.dmnman.reset();
348348
node.cpoolman.reset();
349349
node.mnhf_manager.reset();
350350
node.evodb.reset();
@@ -1683,7 +1683,7 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
16831683
node.netfulfilledman = std::make_unique<CNetFulfilledRequestManager>();
16841684

16851685
assert(!node.govman);
1686-
node.govman = std::make_unique<CGovernanceManager>(*node.mn_metaman, *node.netfulfilledman, ::deterministicMNManager, node.mn_sync);
1686+
node.govman = std::make_unique<CGovernanceManager>(*node.mn_metaman, *node.netfulfilledman, node.dmnman, node.mn_sync);
16871687

16881688
assert(!node.sporkman);
16891689
node.sporkman = std::make_unique<CSporkManager>();
@@ -1725,15 +1725,15 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
17251725
fMasternodeMode = true;
17261726
{
17271727
// Create and register mn_activeman, will init later in ThreadImport
1728-
node.mn_activeman = std::make_unique<CActiveMasternodeManager>(keyOperator, *node.connman, ::deterministicMNManager);
1728+
node.mn_activeman = std::make_unique<CActiveMasternodeManager>(keyOperator, *node.connman, node.dmnman);
17291729
RegisterValidationInterface(node.mn_activeman.get());
17301730
}
17311731
}
17321732

17331733
assert(!node.peerman);
17341734
node.peerman = PeerManager::make(chainparams, *node.connman, *node.addrman, node.banman.get(),
17351735
*node.scheduler, chainman, *node.mempool, *node.mn_metaman, *node.mn_sync,
1736-
*node.govman, *node.sporkman, node.mn_activeman.get(), ::deterministicMNManager,
1736+
*node.govman, *node.sporkman, node.mn_activeman.get(), node.dmnman,
17371737
node.cj_ctx, node.llmq_ctx, ignores_incoming_txs);
17381738
RegisterValidationInterface(node.peerman.get());
17391739

@@ -1858,7 +1858,7 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
18581858
#endif
18591859

18601860
pdsNotificationInterface = new CDSNotificationInterface(
1861-
*node.connman, *node.mn_sync, *node.govman, node.mn_activeman.get(), ::deterministicMNManager, node.llmq_ctx, node.cj_ctx
1861+
*node.connman, *node.mn_sync, *node.govman, node.mn_activeman.get(), node.dmnman, node.llmq_ctx, node.cj_ctx
18621862
);
18631863
RegisterValidationInterface(pdsNotificationInterface);
18641864

@@ -1943,11 +1943,13 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
19431943
pblocktree.reset(new CBlockTreeDB(nBlockTreeDBCache, false, fReset));
19441944

19451945
// Same logic as above with pblocktree
1946-
deterministicMNManager.reset();
1947-
deterministicMNManager = std::make_unique<CDeterministicMNManager>(chainman.ActiveChainstate(), *node.connman, *node.evodb);
1948-
node.dmnman = deterministicMNManager.get();
1946+
node.dmnman.reset();
1947+
node.dmnman = std::make_unique<CDeterministicMNManager>(chainman.ActiveChainstate(), *node.connman, *node.evodb);
1948+
node.mempool->ConnectManagers(node.dmnman.get());
1949+
19491950
node.cpoolman.reset();
19501951
node.cpoolman = std::make_unique<CCreditPoolManager>(*node.evodb);
1952+
19511953
llmq::quorumSnapshotManager.reset();
19521954
llmq::quorumSnapshotManager.reset(new llmq::CQuorumSnapshotManager(*node.evodb));
19531955

src/node/context.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
#include <addrman.h>
88
#include <banman.h>
99
#include <coinjoin/context.h>
10-
#include <evo/creditpool.h>
1110
#include <evo/chainhelper.h>
11+
#include <evo/creditpool.h>
12+
#include <evo/deterministicmns.h>
1213
#include <evo/evodb.h>
1314
#include <evo/mnhftx.h>
1415
#include <governance/governance.h>

src/node/context.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ struct NodeContext {
7575
std::unique_ptr<CCreditPoolManager> cpoolman;
7676
std::unique_ptr<CEvoDB> evodb;
7777
std::unique_ptr<CChainstateHelper> chain_helper;
78+
std::unique_ptr<CDeterministicMNManager> dmnman;
7879
std::unique_ptr<CGovernanceManager> govman;
7980
std::unique_ptr<CJContext> cj_ctx;
8081
std::unique_ptr<CMasternodeMetaMan> mn_metaman;
@@ -83,7 +84,6 @@ struct NodeContext {
8384
std::unique_ptr<CNetFulfilledRequestManager> netfulfilledman;
8485
std::unique_ptr<CSporkManager> sporkman;
8586
std::unique_ptr<LLMQContext> llmq_ctx;
86-
CDeterministicMNManager* dmnman{nullptr};
8787

8888
//! Declare default constructor and destructor that are not inline, so code
8989
//! instantiating the NodeContext struct doesn't need to #include class

src/test/denialofservice_tests.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
6767
auto connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman);
6868
auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, nullptr, *m_node.scheduler,
6969
*m_node.chainman, *m_node.mempool, *m_node.mn_metaman, *m_node.mn_sync,
70-
*m_node.govman, *m_node.sporkman, /* mn_activeman = */ nullptr, ::deterministicMNManager,
70+
*m_node.govman, *m_node.sporkman, /* mn_activeman = */ nullptr, m_node.dmnman,
7171
m_node.cj_ctx, m_node.llmq_ctx, /* ignore_incoming_txs = */ false);
7272

7373
// Mock an outbound peer
@@ -138,7 +138,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management)
138138
auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman);
139139
auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, nullptr, *m_node.scheduler,
140140
*m_node.chainman, *m_node.mempool, *m_node.mn_metaman, *m_node.mn_sync,
141-
*m_node.govman, *m_node.sporkman, /* mn_activeman = */ nullptr, ::deterministicMNManager,
141+
*m_node.govman, *m_node.sporkman, /* mn_activeman = */ nullptr, m_node.dmnman,
142142
m_node.cj_ctx, m_node.llmq_ctx, /* ignore_incoming_txs = */ false);
143143

144144
constexpr int max_outbound_full_relay = MAX_OUTBOUND_FULL_RELAY_CONNECTIONS;
@@ -213,7 +213,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement)
213213
auto connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman);
214214
auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, banman.get(), *m_node.scheduler,
215215
*m_node.chainman, *m_node.mempool, *m_node.mn_metaman, *m_node.mn_sync,
216-
*m_node.govman, *m_node.sporkman, /* mn_activeman = */ nullptr, ::deterministicMNManager,
216+
*m_node.govman, *m_node.sporkman, /* mn_activeman = */ nullptr, m_node.dmnman,
217217
m_node.cj_ctx, m_node.llmq_ctx, /* ignore_incoming_txs = */ false);
218218

219219
banman->ClearBanned();
@@ -261,7 +261,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime)
261261
auto connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman);
262262
auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, banman.get(), *m_node.scheduler,
263263
*m_node.chainman, *m_node.mempool, *m_node.mn_metaman, *m_node.mn_sync,
264-
*m_node.govman, *m_node.sporkman, /* mn_activeman = */ nullptr, ::deterministicMNManager,
264+
*m_node.govman, *m_node.sporkman, /* mn_activeman = */ nullptr, m_node.dmnman,
265265
m_node.cj_ctx, m_node.llmq_ctx, /* ignore_incoming_txs = */ false);
266266

267267
banman->ClearBanned();

src/test/evo_deterministicmns_tests.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,9 @@ void FuncTestMempoolReorg(TestChainSetup& setup)
640640
SignTransaction(*(setup.m_node.mempool), tx_reg, setup.coinbaseKey);
641641

642642
CTxMemPool testPool;
643+
if (setup.m_node.dmnman) {
644+
testPool.ConnectManagers(setup.m_node.dmnman.get());
645+
}
643646
TestMemPoolEntryHelper entry;
644647
LOCK2(cs_main, testPool.cs);
645648

@@ -709,6 +712,9 @@ void FuncTestMempoolDualProregtx(TestChainSetup& setup)
709712
SignTransaction(*(setup.m_node.mempool), tx_reg2, setup.coinbaseKey);
710713

711714
CTxMemPool testPool;
715+
if (setup.m_node.dmnman) {
716+
testPool.ConnectManagers(setup.m_node.dmnman.get());
717+
}
712718
TestMemPoolEntryHelper entry;
713719
LOCK2(cs_main, testPool.cs);
714720

src/test/util/setup_common.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,9 @@ void DashTestSetup(NodeContext& node, const CChainParams& chainparams)
109109
{
110110
CChainState& chainstate = Assert(node.chainman)->ActiveChainstate();
111111

112-
::deterministicMNManager = std::make_unique<CDeterministicMNManager>(chainstate, *node.connman, *node.evodb);
113-
node.dmnman = ::deterministicMNManager.get();
112+
node.dmnman = std::make_unique<CDeterministicMNManager>(chainstate, *node.connman, *node.evodb);
113+
node.mempool->ConnectManagers(node.dmnman.get());
114+
114115
node.cj_ctx = std::make_unique<CJContext>(chainstate, *node.connman, *node.dmnman, *node.mn_metaman, *node.mempool,
115116
/* mn_activeman = */ nullptr, *node.mn_sync, /* relay_txes = */ true);
116117
#ifdef ENABLE_WALLET
@@ -131,8 +132,8 @@ void DashTestSetupClose(NodeContext& node)
131132
#ifdef ENABLE_WALLET
132133
node.coinjoin_loader.reset();
133134
#endif // ENABLE_WALLET
134-
node.dmnman = nullptr;
135-
::deterministicMNManager.reset();
135+
node.mempool->DisconnectManagers();
136+
node.dmnman.reset();
136137
node.cj_ctx.reset();
137138
}
138139

@@ -231,7 +232,7 @@ ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::ve
231232
m_node.mn_metaman = std::make_unique<CMasternodeMetaMan>();
232233
m_node.netfulfilledman = std::make_unique<CNetFulfilledRequestManager>();
233234
m_node.sporkman = std::make_unique<CSporkManager>();
234-
m_node.govman = std::make_unique<CGovernanceManager>(*m_node.mn_metaman, *m_node.netfulfilledman, ::deterministicMNManager, m_node.mn_sync);
235+
m_node.govman = std::make_unique<CGovernanceManager>(*m_node.mn_metaman, *m_node.netfulfilledman, m_node.dmnman, m_node.mn_sync);
235236
m_node.mn_sync = std::make_unique<CMasternodeSync>(*m_node.connman, *m_node.netfulfilledman, *m_node.govman);
236237

237238
// Start script-checking threads. Set g_parallel_script_checks to true so they are used.
@@ -283,7 +284,7 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
283284
m_node.banman = std::make_unique<BanMan>(GetDataDir() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME);
284285
m_node.peerman = PeerManager::make(chainparams, *m_node.connman, *m_node.addrman, m_node.banman.get(),
285286
*m_node.scheduler, *m_node.chainman, *m_node.mempool, *m_node.mn_metaman, *m_node.mn_sync,
286-
*m_node.govman, *m_node.sporkman, /* mn_activeman = */ nullptr, ::deterministicMNManager,
287+
*m_node.govman, *m_node.sporkman, /* mn_activeman = */ nullptr, m_node.dmnman,
287288
m_node.cj_ctx, m_node.llmq_ctx, /* ignore_incoming_txs = */ false);
288289
{
289290
CConnman::Options options;

src/txmempool.cpp

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,13 @@ CTxMemPool::CTxMemPool(CBlockPolicyEstimator* estimator, int check_ratio)
347347
_clear(); //lock free clear
348348
}
349349

350+
void CTxMemPool::ConnectManagers(CDeterministicMNManager* dmnman)
351+
{
352+
// Do not allow double-initialization
353+
assert(m_dmnman == nullptr);
354+
m_dmnman = Assert(dmnman);
355+
}
356+
350357
bool CTxMemPool::isSpent(const COutPoint& outpoint) const
351358
{
352359
LOCK(cs);
@@ -415,7 +422,7 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAnces
415422
// Invalid ProTxes should never get this far because transactions should be
416423
// fully checked by AcceptToMemoryPool() at this point, so we just assume that
417424
// everything is fine here.
418-
if (::deterministicMNManager) {
425+
if (m_dmnman) {
419426
addUncheckedProTx(newit, tx);
420427
}
421428
}
@@ -554,7 +561,7 @@ bool CTxMemPool::removeSpentIndex(const uint256 txhash)
554561

555562
void CTxMemPool::addUncheckedProTx(indexed_transaction_set::iterator& newit, const CTransaction& tx)
556563
{
557-
assert(::deterministicMNManager);
564+
assert(m_dmnman);
558565

559566
if (tx.nType == TRANSACTION_PROVIDER_REGISTER) {
560567
auto proTx = *Assert(GetTxPayload<CProRegTx>(tx));
@@ -577,15 +584,15 @@ void CTxMemPool::addUncheckedProTx(indexed_transaction_set::iterator& newit, con
577584
auto proTx = *Assert(GetTxPayload<CProUpRegTx>(tx));
578585
mapProTxRefs.emplace(proTx.proTxHash, tx.GetHash());
579586
mapProTxBlsPubKeyHashes.emplace(proTx.pubKeyOperator.GetHash(), tx.GetHash());
580-
auto dmn = Assert(deterministicMNManager->GetListAtChainTip().GetMN(proTx.proTxHash));
587+
auto dmn = Assert(m_dmnman->GetListAtChainTip().GetMN(proTx.proTxHash));
581588
newit->validForProTxKey = ::SerializeHash(dmn->pdmnState->pubKeyOperator);
582589
if (dmn->pdmnState->pubKeyOperator != proTx.pubKeyOperator) {
583590
newit->isKeyChangeProTx = true;
584591
}
585592
} else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_REVOKE) {
586593
auto proTx = *Assert(GetTxPayload<CProUpRevTx>(tx));
587594
mapProTxRefs.emplace(proTx.proTxHash, tx.GetHash());
588-
auto dmn = Assert(deterministicMNManager->GetListAtChainTip().GetMN(proTx.proTxHash));
595+
auto dmn = Assert(m_dmnman->GetListAtChainTip().GetMN(proTx.proTxHash));
589596
newit->validForProTxKey = ::SerializeHash(dmn->pdmnState->pubKeyOperator);
590597
if (dmn->pdmnState->pubKeyOperator.Get() != CBLSPublicKey()) {
591598
newit->isKeyChangeProTx = true;
@@ -623,7 +630,7 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason)
623630
} else
624631
vTxHashes.clear();
625632

626-
if (::deterministicMNManager) {
633+
if (m_dmnman) {
627634
removeUncheckedProTx(it->GetTx());
628635
}
629636

@@ -844,7 +851,7 @@ void CTxMemPool::removeProTxCollateralConflicts(const CTransaction &tx, const CO
844851

845852
void CTxMemPool::removeProTxSpentCollateralConflicts(const CTransaction &tx)
846853
{
847-
assert(::deterministicMNManager);
854+
assert(m_dmnman);
848855

849856
// Remove TXs that refer to a MN for which the collateral was spent
850857
auto removeSpentCollateralConflict = [&](const uint256& proTxHash) {
@@ -866,7 +873,7 @@ void CTxMemPool::removeProTxSpentCollateralConflicts(const CTransaction &tx)
866873
}
867874
}
868875
};
869-
auto mnList = deterministicMNManager->GetListAtChainTip();
876+
auto mnList = m_dmnman->GetListAtChainTip();
870877
for (const auto& in : tx.vin) {
871878
auto collateralIt = mapProTxCollaterals.find(in.prevout);
872879
if (collateralIt != mapProTxCollaterals.end()) {
@@ -983,7 +990,7 @@ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigne
983990
RemoveStaged(stage, true, MemPoolRemovalReason::BLOCK);
984991
}
985992
removeConflicts(*tx);
986-
if (::deterministicMNManager) {
993+
if (m_dmnman) {
987994
removeProTxConflicts(*tx);
988995
}
989996
ClearPrioritisation(tx->GetHash());
@@ -1259,7 +1266,7 @@ TxMempoolInfo CTxMemPool::info(const uint256& hash) const
12591266
}
12601267

12611268
bool CTxMemPool::existsProviderTxConflict(const CTransaction &tx) const {
1262-
assert(::deterministicMNManager);
1269+
assert(m_dmnman);
12631270

12641271
LOCK(cs);
12651272

@@ -1314,7 +1321,7 @@ bool CTxMemPool::existsProviderTxConflict(const CTransaction &tx) const {
13141321
auto& proTx = *opt_proTx;
13151322

13161323
// this method should only be called with validated ProTxs
1317-
auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(proTx.proTxHash);
1324+
auto dmn = m_dmnman->GetListAtChainTip().GetMN(proTx.proTxHash);
13181325
if (!dmn) {
13191326
LogPrint(BCLog::MEMPOOL, "%s: ERROR: Masternode is not in the list, proTxHash: %s\n", __func__, proTx.proTxHash.ToString());
13201327
return true; // i.e. failed to find validated ProTx == conflict
@@ -1336,7 +1343,7 @@ bool CTxMemPool::existsProviderTxConflict(const CTransaction &tx) const {
13361343
}
13371344
auto& proTx = *opt_proTx;
13381345
// this method should only be called with validated ProTxs
1339-
auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(proTx.proTxHash);
1346+
auto dmn = m_dmnman->GetListAtChainTip().GetMN(proTx.proTxHash);
13401347
if (!dmn) {
13411348
LogPrint(BCLog::MEMPOOL, "%s: ERROR: Masternode is not in the list, proTxHash: %s\n", __func__, proTx.proTxHash.ToString());
13421349
return true; // i.e. failed to find validated ProTx == conflict

src/txmempool.h

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ struct entry_time {};
361361
struct ancestor_score {};
362362

363363
class CBlockPolicyEstimator;
364+
class CDeterministicMNManager;
364365

365366
/**
366367
* Information about a mempool transaction.
@@ -472,6 +473,7 @@ class CTxMemPool
472473
const int m_check_ratio; //!< Value n means that 1 times in n we check.
473474
std::atomic<unsigned int> nTransactionsUpdated{0}; //!< Used by getblocktemplate to trigger CreateNewBlock() invocation
474475
CBlockPolicyEstimator* minerPolicyEstimator;
476+
CDeterministicMNManager* m_dmnman{nullptr};
475477

476478
uint64_t totalTxSize GUARDED_BY(cs); //!< sum of all mempool tx' byte sizes
477479
CAmount m_total_fee GUARDED_BY(cs); //!< sum of all mempool tx's fees (NOT modified fee)
@@ -599,6 +601,21 @@ class CTxMemPool
599601
*/
600602
explicit CTxMemPool(CBlockPolicyEstimator* estimator = nullptr, int check_ratio = 0);
601603

604+
/**
605+
* Set CDeterministicMNManager pointer.
606+
*
607+
* Separated from constructor as it's initialized after CTxMemPool
608+
* is created. Required for ProTx processing.
609+
*/
610+
void ConnectManagers(CDeterministicMNManager* dmnman);
611+
612+
/**
613+
* Reset CDeterministicMNManager pointer.
614+
*
615+
* @pre Must be called before CDeterministicMNManager is destroyed.
616+
*/
617+
void DisconnectManagers() { m_dmnman = nullptr; }
618+
602619
/**
603620
* If sanity-checking is turned on, check makes sure the pool is
604621
* consistent (does not contain two transactions that spend the same inputs,
@@ -759,7 +776,10 @@ class CTxMemPool
759776
TxMempoolInfo info(const uint256& hash) const;
760777
std::vector<TxMempoolInfo> infoAll() const;
761778

762-
/** @pre Caller must ensure that CDeterministicMNManager exists */
779+
/**
780+
* @pre Caller must ensure that CDeterministicMNManager exists and has been
781+
* set using ConnectManagers() for the CTxMemPool instance.
782+
*/
763783
bool existsProviderTxConflict(const CTransaction &tx) const;
764784

765785
size_t DynamicMemoryUsage() const;

0 commit comments

Comments
 (0)