Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
1fa0d70
Add a CValidationInterface::TransactionRemovedFromMempool
TheBlueMatt Feb 6, 2021
268be9c
Call TransactionRemovedFromMempool in the CScheduler thread
TheBlueMatt Jun 8, 2017
40ed4c4
Add CallFunctionInQueue to wait on validation interface queue drain
TheBlueMatt Jun 8, 2017
24a3ce4
Add CWallet::BlockUntilSyncedToCurrentChain()
TheBlueMatt Feb 6, 2021
f6df6e4
Add calls to CWallet::BlockUntilSyncedToCurrentChain() in RPCs
furszy Feb 6, 2021
31a8790
Use callbacks to cache whether wallet transactions are in mempool
TheBlueMatt Feb 7, 2021
c7ab490
Also call other wallet notify callbacks in scheduler thread
TheBlueMatt Feb 7, 2021
7d05997
Fix wallet RPC race by waiting for callbacks in sendrawtransaction
TheBlueMatt Feb 7, 2021
5f521fd
Give ZMQ consistent order with UpdatedBlockTip on scheduler thread
TheBlueMatt Oct 1, 2017
4d927b0
Add a dev notes document describing the new wallet RPC blocking
TheBlueMatt May 3, 2017
1c9fe10
RPC: listunspent remove redundant wallet check
furszy Feb 7, 2021
51dea23
net_processing move-only: decouple tier two get data request into its…
furszy Feb 8, 2021
10efbe5
net_processing: making PushTierTwoGetDataRequest return a bool in cas…
furszy Feb 8, 2021
da7c0f7
Refactor ProcessGetData avoiding to lock cs_main for its entire time.
furszy Feb 8, 2021
31c7974
Decouple block processing cs_main lock from the rest of inv get data …
furszy Feb 8, 2021
0c68e2f
Add an interface to get the queue depth out of CValidationInterface
TheBlueMatt Dec 4, 2017
cc91d44
Block ActivateBestChain to empty validationinterface queue
TheBlueMatt Feb 8, 2021
0c4642c
Add helper to wait for validation interface queue to catch up
TheBlueMatt Feb 8, 2021
596056c
[validation] Do not check for double spent zerocoins.
furszy Feb 8, 2021
67c754a
qa: Sync with validationinterface queue in sync_mempools
Feb 10, 2021
c3a281c
fix mempool_persist.py dump issue, missing sync with validation inter…
furszy Feb 11, 2021
0dfebf4
sapling_rpc_wallet_tests: remove unneeded cs_main and cs_wallet locks.
furszy Feb 18, 2021
296c956
wallet: guard null m_last_block_processed
furszy Feb 18, 2021
b9249c5
Miner: generate RPC, fix coinbase script key not marked as used
furszy Feb 19, 2021
4ed7024
fix invalid numbers in wallet_labels.py
furszy Feb 19, 2021
e6770c8
fixing invalid wallet_dump.py, generated PoW blocks use a P2PKH coinb…
furszy Feb 19, 2021
de3c7ae
fix wallet_upgrade.py test, wasn't counting the coinbase script.
furszy Feb 19, 2021
815667d
unit test framework: missing scheduler service loop start added.
furszy Feb 19, 2021
1ed753f
Fix wallet_tests.cpp, missing fInMempool flag set.
furszy Feb 20, 2021
d97ace9
[Test] notes_double_spend: sync wallet before check balances.
furszy Feb 20, 2021
1423dba
[bugfix] save feeDelta instead of priorityDelta in DumpMempool
morcos Jan 20, 2017
756d0fa
Handle rename failure in DumpMempool(...) by using RenameOver(...) re…
practicalswift Nov 27, 2020
65cf7e1
don't attempt mempool entry for wallet transactions on startup if alr…
instagibbs Feb 20, 2021
3ace13b
qa: Fix some tests to work on native windows
furszy Feb 20, 2021
f8cd371
[Miner] Sync wallet state before try to solve proof of stake.
furszy Feb 21, 2021
53497f0
Validation: DisconnectTip doesn't need to force a flush to disk.
furszy Feb 22, 2021
bfd9a15
test: sapling_fillblock.py sync mempool every 200 transactions instea…
furszy Feb 22, 2021
00cc6ec
dumpwallet: Add missing BlockUntilSyncedToCurrentChain
furszy Feb 23, 2021
ded2e8e
feature_dbcrash.py using blockmaxsize instead of blockmaxweight that …
furszy Feb 23, 2021
046386b
IsNoteSaplingChange: Add missing cs_wallet lock.
furszy Feb 23, 2021
3d11027
wallet:CreateCoinStake, solving IsSpent() missing cs_main lock.
furszy Feb 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions doc/developer-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -907,3 +907,16 @@ A few guidelines for introducing and reviewing new RPC interfaces:
- *Exception*: Using RPC method aliases may be appropriate in cases where a
new RPC is replacing a deprecated RPC, to avoid both RPCs confusingly
showing up in the command list.

- Wallet RPCs call BlockUntilSyncedToCurrentChain to maintain consistency with
`getblockchaininfo`'s state immediately prior to the call's execution. Wallet
RPCs whose behavior does *not* depend on the current chainstate may omit this
call.

- *Rationale*: In previous versions of Bitcoin Core, the wallet was always
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
- *Rationale*: In previous versions of Bitcoin Core, the wallet was always
- *Rationale*: In previous versions of PIVX Core, the wallet was always

in-sync with the chainstate (by virtue of them all being updated in the
same cs_main lock). In order to maintain the behavior that wallet RPCs
return results as of at least the highest best-known block an RPC
client may be aware of prior to entering a wallet RPC call, we must block
until the wallet is caught up to the chainstate as of the RPC call's entry.
This also makes the API much easier for RPC clients to reason about.
4 changes: 4 additions & 0 deletions src/blockassembler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ bool SolveProofOfStake(CBlock* pblock, CBlockIndex* pindexPrev, CWallet* pwallet
{
boost::this_thread::interruption_point();
pblock->nBits = GetNextWorkRequired(pindexPrev, pblock);

// Sync wallet before create coinstake
pwallet->BlockUntilSyncedToCurrentChain();

CMutableTransaction txCoinStake;
int64_t nTxNewTime = 0;
if (!pwallet->CreateCoinStake(*pwallet, pindexPrev, pblock->nBits, txCoinStake, nTxNewTime, availableCoins)) {
Expand Down
2 changes: 2 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ void PrepareShutdown()
// Disconnect all slots
UnregisterAllValidationInterfaces();
GetMainSignals().UnregisterBackgroundSignalScheduler();
GetMainSignals().UnregisterWithMempoolSignals(mempool);

#ifndef WIN32
try {
Expand Down Expand Up @@ -1264,6 +1265,7 @@ bool AppInitMain()
threadGroup.create_thread(std::bind(&TraceThread<CScheduler::Function>, "scheduler", serviceLoop));

GetMainSignals().RegisterBackgroundSignalScheduler(scheduler);
GetMainSignals().RegisterWithMempoolSignals(mempool);

// Initialize Sapling circuit parameters
LoadSaplingParams();
Expand Down
6 changes: 3 additions & 3 deletions src/miner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@
double dHashesPerSec = 0.0;
int64_t nHPSTimerStart = 0;

std::unique_ptr<CBlockTemplate> CreateNewBlockWithKey(CReserveKey& reservekey, CWallet* pwallet)
std::unique_ptr<CBlockTemplate> CreateNewBlockWithKey(CReserveKey* reservekey, CWallet* pwallet)
{
CPubKey pubkey;
if (!reservekey.GetReservedKey(pubkey))
if (!reservekey->GetReservedKey(pubkey))
return nullptr;

const int nHeightNext = chainActive.Tip()->nHeight + 1;
Expand Down Expand Up @@ -169,7 +169,7 @@ void BitcoinMiner(CWallet* pwallet, bool fProofOfStake)

std::unique_ptr<CBlockTemplate> pblocktemplate((fProofOfStake ?
BlockAssembler(Params(), DEFAULT_PRINTPRIORITY).CreateNewBlock(CScript(), pwallet, true, &availableCoins) :
CreateNewBlockWithKey(*opReservekey, pwallet)));
CreateNewBlockWithKey(opReservekey.get_ptr(), pwallet)));
if (!pblocktemplate) continue;
std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>(pblocktemplate->block);

Expand Down
2 changes: 1 addition & 1 deletion src/miner.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ static const bool DEFAULT_PRINTPRIORITY = false;
/** Run the miner threads */
void GenerateBitcoins(bool fGenerate, CWallet* pwallet, int nThreads);
/** Generate a new block, without valid proof-of-work */
std::unique_ptr<CBlockTemplate> CreateNewBlockWithKey(CReserveKey& reservekey, CWallet* pwallet);
std::unique_ptr<CBlockTemplate> CreateNewBlockWithKey(CReserveKey* reservekey, CWallet* pwallet);

void BitcoinMiner(CWallet* pwallet, bool fProofOfStake);
void ThreadStakeMinter();
Expand Down
345 changes: 192 additions & 153 deletions src/net_processing.cpp

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "utilmoneystr.h"
#include "utilstrencodings.h"
#include "hash.h"
#include "validationinterface.h"
#include "wallet/wallet.h"
#include "zpiv/zpivmodule.h"
#include "zpivchain.h"
Expand Down Expand Up @@ -48,6 +49,21 @@ static CUpdatedBlock latestblock;
extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry);
void ScriptPubKeyToJSON(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex);

UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() > 0) {
throw std::runtime_error(
"syncwithvalidationinterfacequeue\n"
"\nWaits for the validation interface queue to catch up on everything that was there when we entered this function.\n"
"\nExamples:\n"
+ HelpExampleCli("syncwithvalidationinterfacequeue","")
+ HelpExampleRpc("syncwithvalidationinterfacequeue","")
);
}
SyncWithValidationInterfaceQueue();
return NullUniValue;
}

double GetDifficulty(const CBlockIndex* blockindex)
{
// Floating point number that is a multiple of the minimum difficulty,
Expand Down Expand Up @@ -1448,6 +1464,7 @@ static const CRPCCommand commands[] =
{ "hidden", "waitfornewblock", &waitfornewblock, true },
{ "hidden", "waitforblock", &waitforblock, true },
{ "hidden", "waitforblockheight", &waitforblockheight, true },
{ "hidden", "syncwithvalidationinterfacequeue", &syncwithvalidationinterfacequeue, true },


};
Expand Down
13 changes: 11 additions & 2 deletions src/rpc/mining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,17 @@ UniValue generate(const JSONRPCRequest& request)

const Consensus::Params& consensus = Params().GetConsensus();
bool fPoS = consensus.NetworkUpgradeActive(nHeight + 1, Consensus::UPGRADE_POS);
std::unique_ptr<CReserveKey> reservekey;

if (fPoS) {
// If we are in PoS, wallet must be unlocked.
EnsureWalletIsUnlocked();
} else {
// Coinbase key
reservekey = MakeUnique<CReserveKey>(pwalletMain);
}

UniValue blockHashes(UniValue::VARR);
CReserveKey reservekey(pwalletMain);
unsigned int nExtraNonce = 0;

while (nHeight < nHeightEnd && !ShutdownRequested()) {
Expand All @@ -81,7 +84,7 @@ UniValue generate(const JSONRPCRequest& request)

std::unique_ptr<CBlockTemplate> pblocktemplate((fPoS ?
BlockAssembler(Params(), DEFAULT_PRINTPRIORITY).CreateNewBlock(CScript(), pwalletMain, true, &availableCoins) :
CreateNewBlockWithKey(reservekey, pwalletMain)));
CreateNewBlockWithKey(reservekey.get(), pwalletMain)));
if (!pblocktemplate.get()) break;
std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>(pblocktemplate->block);

Expand Down Expand Up @@ -114,6 +117,12 @@ UniValue generate(const JSONRPCRequest& request)
if (nGenerated == 0 || (!fPoS && nGenerated < nGenerate))
throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new blocks");

// mark key as used, only for PoW coinbases
if (reservekey) {
// Remove key from key pool
reservekey->KeepKey();
}

return blockHashes;
}
#endif // ENABLE_WALLET
Expand Down
27 changes: 25 additions & 2 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "core_io.h"
#include "init.h"
#include "keystore.h"
#include "validationinterface.h"
#include "net.h"
#include "policy/policy.h"
#include "primitives/transaction.h"
Expand All @@ -25,6 +26,7 @@
#include "wallet/wallet.h"
#endif

#include <future>
#include <stdint.h>

#include <univalue.h>
Expand Down Expand Up @@ -518,6 +520,10 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
if (!pwalletMain)
throw std::runtime_error("wallet not initialized");

// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwalletMain->BlockUntilSyncedToCurrentChain();

RPCTypeCheck(request.params, {UniValue::VSTR});

CTxDestination changeAddress = CNoDestination();
Expand Down Expand Up @@ -899,6 +905,8 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
"\nSend the transaction (signed hex)\n" + HelpExampleCli("sendrawtransaction", "\"signedhex\"") +
"\nAs a json rpc call\n" + HelpExampleRpc("sendrawtransaction", "\"signedhex\""));

std::promise<void> promise;

RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL});

// parse hex string from parameter
Expand All @@ -911,7 +919,8 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
if (request.params.size() > 1)
fOverrideFees = request.params[1].get_bool();

AssertLockNotHeld(cs_main);
{ // cs_main scope
LOCK(cs_main);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should indent everything by 4 spaces after the new opening scope brace

CCoinsViewCache& view = *pcoinsTip;
bool fHaveChain = false;
for (size_t o = 0; !fHaveChain && o < mtx.vout.size(); o++) {
Expand All @@ -923,7 +932,6 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
CValidationState state;
bool fMissingInputs;
{
LOCK(cs_main);
Comment on lines 934 to -926
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can remove the code-block now

if (!AcceptToMemoryPool(mempool, state, MakeTransactionRef(std::move(mtx)), true, &fMissingInputs, false, !fOverrideFees)) {
if (state.IsInvalid()) {
throw JSONRPCError(RPC_TRANSACTION_REJECTED, strprintf("%i: %s", state.GetRejectCode(), state.GetRejectReason()));
Expand All @@ -933,11 +941,25 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
}
throw JSONRPCError(RPC_TRANSACTION_ERROR, state.GetRejectReason());
}
} else {
// If wallet is enabled, ensure that the wallet has been made aware
// of the new transaction prior to returning. This prevents a race
// where a user might call sendrawtransaction with a transaction
// to/from their wallet, immediately call some wallet RPC, and get
// a stale result because callbacks have not yet been processed.
CallFunctionInValidationInterfaceQueue([&promise] {
promise.set_value();
});
}
}
} else if (fHaveChain) {
throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, "transaction already in block chain");
}

} // cs_main

promise.get_future().wait();

if(!g_connman)
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");

Expand All @@ -946,6 +968,7 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
{
pnode->PushInventory(inv);
});

return hashTx.GetHex();
}

Expand Down
2 changes: 1 addition & 1 deletion src/sapling/saplingscriptpubkeyman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@ CAmount SaplingScriptPubKeyMan::GetShieldedChange(const CWalletTx& wtx) const

bool SaplingScriptPubKeyMan::IsNoteSaplingChange(const SaplingOutPoint& op, libzcash::SaplingPaymentAddress address) const
{
LOCK(wallet->cs_KeyStore);
LOCK2(wallet->cs_wallet, wallet->cs_KeyStore);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can remove the cs_KeyStore lock here, as GetNullifiersForAddresses takes care of it via GetSaplingIncomingViewingKey.

std::set<libzcash::PaymentAddress> shieldedAddresses = {address};
std::set<std::pair<libzcash::PaymentAddress, uint256>> nullifierSet = GetNullifiersForAddresses(shieldedAddresses);
return IsNoteSaplingChange(nullifierSet, address, op);
Expand Down
5 changes: 5 additions & 0 deletions src/scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,8 @@ void SingleThreadedSchedulerClient::EmptyQueue() {
should_continue = !m_callbacks_pending.empty();
}
}

size_t SingleThreadedSchedulerClient::CallbacksPending() {
LOCK(m_cs_callbacks_pending);
return m_callbacks_pending.size();
}
2 changes: 2 additions & 0 deletions src/scheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ class SingleThreadedSchedulerClient {
// Processes all remaining queue members on the calling thread, blocking until queue is empty
// Must be called after the CScheduler has no remaining processing threads!
void EmptyQueue();

size_t CallbacksPending();
};

#endif
19 changes: 14 additions & 5 deletions src/test/librust/sapling_rpc_wallet_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_getbalance)
{
SelectParams(CBaseChainParams::TESTNET);

LOCK2(cs_main, pwalletMain->cs_wallet);
{
LOCK(pwalletMain->cs_wallet);
pwalletMain->SetMinVersion(FEATURE_SAPLING);
pwalletMain->SetupSPKM(false);
}

BOOST_CHECK_THROW(CallRPC("getshieldbalance too many args"), std::runtime_error);
BOOST_CHECK_THROW(CallRPC("getshieldbalance invalidaddress"), std::runtime_error);
Expand Down Expand Up @@ -249,7 +253,11 @@ BOOST_AUTO_TEST_CASE(rpc_shieldsendmany_parameters)
{
SelectParams(CBaseChainParams::TESTNET);

LOCK2(cs_main, pwalletMain->cs_wallet);
{
LOCK(pwalletMain->cs_wallet);
pwalletMain->SetMinVersion(FEATURE_SAPLING);
pwalletMain->SetupSPKM(false);
}

BOOST_CHECK_THROW(CallRPC("shieldsendmany"), std::runtime_error);
BOOST_CHECK_THROW(CallRPC("shieldsendmany toofewargs"), std::runtime_error);
Expand Down Expand Up @@ -299,7 +307,6 @@ BOOST_AUTO_TEST_CASE(rpc_shieldsendmany_parameters)
std::vector<char> v (2 * (ZC_MEMO_SIZE+1)); // x2 for hexadecimal string format
std::fill(v.begin(),v.end(), 'A');
std::string badmemo(v.begin(), v.end());
pwalletMain->SetupSPKM(false);
auto pa = pwalletMain->GenerateNewSaplingZKey();
std::string zaddr1 = KeyIO::EncodePaymentAddress(pa);
BOOST_CHECK_THROW(CallRPC(std::string("shieldsendmany yBYhwgzufrZ6F5VVuK9nEChENArq934mqC ")
Expand Down Expand Up @@ -543,8 +550,10 @@ BOOST_AUTO_TEST_CASE(rpc_listshieldunspent_parameters)
{
SelectParams(CBaseChainParams::TESTNET);

LOCK2(cs_main, pwalletMain->cs_wallet);
pwalletMain->SetupSPKM(false);
{
LOCK(pwalletMain->cs_wallet);
pwalletMain->SetupSPKM(false);
}

UniValue retValue;

Expand Down
7 changes: 7 additions & 0 deletions src/test/test_pivx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,15 @@ TestingSetup::TestingSetup()
fs::create_directories(pathTemp);
gArgs.ForceSetArg("-datadir", pathTemp.string());

// Start the lightweight task scheduler thread
CScheduler::Function serviceLoop = std::bind(&CScheduler::serviceQueue, &scheduler);
threadGroup.create_thread(std::bind(&TraceThread<CScheduler::Function>, "scheduler", serviceLoop));

// Note that because we don't bother running a scheduler thread here,
// callbacks via CValidationInterface are unreliable, but that's OK,
// our unit tests aren't testing multiple parts of the code at once.
Comment on lines +57 to 63
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit.
This comment doesn't make sense now.
Better remove it and replace the one at line 57 with

        // We have to run a scheduler thread to prevent ActivateBestChain
        // from blocking due to queue overrun.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, will tackle it in #2212.

GetMainSignals().RegisterBackgroundSignalScheduler(scheduler);
GetMainSignals().RegisterWithMempoolSignals(mempool);

// Ideally we'd move all the RPC tests to the functional testing framework
// instead of unit tests, but for now we need these here.
Expand Down Expand Up @@ -87,7 +92,9 @@ TestingSetup::~TestingSetup()
threadGroup.interrupt_all();
threadGroup.join_all();
GetMainSignals().FlushBackgroundCallbacks();
UnregisterAllValidationInterfaces();
GetMainSignals().UnregisterBackgroundSignalScheduler();
GetMainSignals().UnregisterWithMempoolSignals(mempool);
UnloadBlockIndex();
delete pcoinsTip;
delete pcoinsdbview;
Expand Down
3 changes: 3 additions & 0 deletions src/txmempool.h
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,9 @@ class CTxMemPool
// to track size/count of descendant transactions. First version of
// addUnchecked can be used to have it call CalculateMemPoolAncestors(), and
// then invoke the second version.
// Note that addUnchecked is ONLY called from ATMP outside of tests
// and any other callers may break wallet's in-mempool tracking (due to
// lack of CValidationInterface::TransactionAddedToMempool callbacks).
bool addUnchecked(const uint256& hash, const CTxMemPoolEntry& entry, bool fCurrentEstimate = true);
bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, setEntries &setAncestors, bool fCurrentEstimate = true);

Expand Down
Loading