aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bitcoin-qt.pro1
-rw-r--r--src/checkqueue.h206
-rw-r--r--src/init.cpp20
-rw-r--r--src/main.cpp40
-rw-r--r--src/main.h15
-rw-r--r--src/net.h1
-rw-r--r--src/test/test_bitcoin.cpp4
7 files changed, 281 insertions, 6 deletions
diff --git a/bitcoin-qt.pro b/bitcoin-qt.pro
index 2ca142add6..31780b5b84 100644
--- a/bitcoin-qt.pro
+++ b/bitcoin-qt.pro
@@ -156,6 +156,7 @@ HEADERS += src/qt/bitcoingui.h \
src/init.h \
src/irc.h \
src/mruset.h \
+ src/checkqueue.h \
src/json/json_spirit_writer_template.h \
src/json/json_spirit_writer.h \
src/json/json_spirit_value.h \
diff --git a/src/checkqueue.h b/src/checkqueue.h
new file mode 100644
index 0000000000..36141dd74b
--- /dev/null
+++ b/src/checkqueue.h
@@ -0,0 +1,206 @@
+// Copyright (c) 2012 The Bitcoin developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#ifndef CHECKQUEUE_H
+#define CHECKQUEUE_H
+
+#include <boost/thread/mutex.hpp>
+#include <boost/thread/locks.hpp>
+#include <boost/thread/condition_variable.hpp>
+
+#include <vector>
+#include <algorithm>
+
+template<typename T> class CCheckQueueControl;
+
+/** Queue for verifications that have to be performed.
+ * The verifications are represented by a type T, which must provide an
+ * operator(), returning a bool.
+ *
+ * One thread (the master) is assumed to push batches of verifications
+ * onto the queue, where they are processed by N-1 worker threads. When
+ * the master is done adding work, it temporarily joins the worker pool
+ * as an N'th worker, until all jobs are done.
+ */
+template<typename T> class CCheckQueue {
+private:
+ // Mutex to protect the inner state
+ boost::mutex mutex;
+
+ // Worker threads block on this when out of work
+ boost::condition_variable condWorker;
+
+ // Master thread blocks on this when out of work
+ boost::condition_variable condMaster;
+
+ // Quit method blocks on this until all workers are gone
+ boost::condition_variable condQuit;
+
+ // The queue of elements to be processed.
+ // As the order of booleans doesn't matter, it is used as a LIFO (stack)
+ std::vector<T> queue;
+
+ // The number of workers (including the master) that are idle.
+ int nIdle;
+
+ // The total number of workers (including the master).
+ int nTotal;
+
+ // The temporary evaluation result.
+ bool fAllOk;
+
+ // Number of verifications that haven't completed yet.
+ // This includes elements that are not anymore in queue, but still in
+ // worker's own batches.
+ unsigned int nTodo;
+
+ // Whether we're shutting down.
+ bool fQuit;
+
+ // The maximum number of elements to be processed in one batch
+ unsigned int nBatchSize;
+
+ // Internal function that does bulk of the verification work.
+ bool Loop(bool fMaster = false) {
+ boost::condition_variable &cond = fMaster ? condMaster : condWorker;
+ std::vector<T> vChecks;
+ vChecks.reserve(nBatchSize);
+ unsigned int nNow = 0;
+ bool fOk = true;
+ do {
+ {
+ boost::unique_lock<boost::mutex> lock(mutex);
+ // first do the clean-up of the previous loop run (allowing us to do it in the same critsect)
+ if (nNow) {
+ fAllOk &= fOk;
+ nTodo -= nNow;
+ if (nTodo == 0 && !fMaster)
+ // We processed the last element; inform the master he can exit and return the result
+ condMaster.notify_one();
+ } else {
+ // first iteration
+ nTotal++;
+ }
+ // logically, the do loop starts here
+ while (queue.empty()) {
+ if ((fMaster || fQuit) && nTodo == 0) {
+ nTotal--;
+ if (nTotal==0)
+ condQuit.notify_one();
+ bool fRet = fAllOk;
+ // reset the status for new work later
+ if (fMaster)
+ fAllOk = true;
+ // return the current status
+ return fRet;
+ }
+ nIdle++;
+ cond.wait(lock); // wait
+ nIdle--;
+ }
+ // Decide how many work units to process now.
+ // * Do not try to do everything at once, but aim for increasingly smaller batches so
+ // all workers finish approximately simultaneously.
+ // * Try to account for idle jobs which will instantly start helping.
+ // * Don't do batches smaller than 1 (duh), or larger than nBatchSize.
+ nNow = std::max(1U, std::min(nBatchSize, (unsigned int)queue.size() / (nTotal + nIdle + 1)));
+ vChecks.resize(nNow);
+ for (unsigned int i = 0; i < nNow; i++) {
+ // We want the lock on the mutex to be as short as possible, so swap jobs from the global
+ // queue to the local batch vector instead of copying.
+ vChecks[i].swap(queue.back());
+ queue.pop_back();
+ }
+ // Check whether we need to do work at all
+ fOk = fAllOk;
+ }
+ // execute work
+ BOOST_FOREACH(T &check, vChecks)
+ if (fOk)
+ fOk = check();
+ vChecks.clear();
+ } while(true);
+ }
+
+public:
+ // Create a new check queue
+ CCheckQueue(unsigned int nBatchSizeIn) :
+ nIdle(0), nTotal(0), fAllOk(true), nTodo(0), fQuit(false), nBatchSize(nBatchSizeIn) {}
+
+ // Worker thread
+ void Thread() {
+ Loop();
+ }
+
+ // Wait until execution finishes, and return whether all evaluations where succesful.
+ bool Wait() {
+ return Loop(true);
+ }
+
+ // Add a batch of checks to the queue
+ void Add(std::vector<T> &vChecks) {
+ boost::unique_lock<boost::mutex> lock(mutex);
+ BOOST_FOREACH(T &check, vChecks) {
+ queue.push_back(T());
+ check.swap(queue.back());
+ }
+ nTodo += vChecks.size();
+ if (vChecks.size() == 1)
+ condWorker.notify_one();
+ else if (vChecks.size() > 1)
+ condWorker.notify_all();
+ }
+
+ // Shut the queue down
+ void Quit() {
+ boost::unique_lock<boost::mutex> lock(mutex);
+ fQuit = true;
+ // No need to wake the master, as he will quit automatically when all jobs are
+ // done.
+ condWorker.notify_all();
+
+ while (nTotal > 0)
+ condQuit.wait(lock);
+ }
+
+ friend class CCheckQueueControl<T>;
+};
+
+/** RAII-style controller object for a CCheckQueue that guarantees the passed
+ * queue is finished before continuing.
+ */
+template<typename T> class CCheckQueueControl {
+private:
+ CCheckQueue<T> *pqueue;
+ bool fDone;
+
+public:
+ CCheckQueueControl(CCheckQueue<T> *pqueueIn) : pqueue(pqueueIn), fDone(false) {
+ // passed queue is supposed to be unused, or NULL
+ if (pqueue != NULL) {
+ assert(pqueue->nTotal == pqueue->nIdle);
+ assert(pqueue->nTodo == 0);
+ assert(pqueue->fAllOk == true);
+ }
+ }
+
+ bool Wait() {
+ if (pqueue == NULL)
+ return true;
+ bool fRet = pqueue->Wait();
+ fDone = true;
+ return fRet;
+ }
+
+ void Add(std::vector<T> &vChecks) {
+ if (pqueue != NULL)
+ pqueue->Add(vChecks);
+ }
+
+ ~CCheckQueueControl() {
+ if (!fDone)
+ Wait();
+ }
+};
+
+#endif
diff --git a/src/init.cpp b/src/init.cpp
index 557c23fc92..087750cf90 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -84,6 +84,10 @@ void Shutdown(void* parg)
fShutdown = true;
nTransactionsUpdated++;
bitdb.Flush(false);
+ {
+ LOCK(cs_main);
+ ThreadScriptCheckQuit();
+ }
StopNode();
{
LOCK(cs_main);
@@ -303,6 +307,7 @@ std::string HelpMessage()
" -checklevel=<n> " + _("How thorough the block verification is (0-6, default: 1)") + "\n" +
" -loadblock=<file> " + _("Imports blocks from external blk000??.dat file") + "\n" +
" -reindex " + _("Rebuild blockchain index from current blk000??.dat files") + "\n" +
+ " -par=N " + _("Set the number of script verification threads (1-16, 0=auto, default: 0)") + "\n" +
"\n" + _("Block creation options:") + "\n" +
" -blockminsize=<n> " + _("Set minimum block size in bytes (default: 0)") + "\n" +
@@ -484,6 +489,15 @@ bool AppInit2()
fDebug = GetBoolArg("-debug");
fBenchmark = GetBoolArg("-benchmark");
+ // -par=0 means autodetect, but nScriptCheckThreads==0 means no concurrency
+ nScriptCheckThreads = GetArg("-par", 0);
+ if (nScriptCheckThreads == 0)
+ nScriptCheckThreads = boost::thread::hardware_concurrency();
+ if (nScriptCheckThreads <= 1)
+ nScriptCheckThreads = 0;
+ else if (nScriptCheckThreads > MAX_SCRIPTCHECK_THREADS)
+ nScriptCheckThreads = MAX_SCRIPTCHECK_THREADS;
+
// -debug implies fDebug*
if (fDebug)
fDebugNet = true;
@@ -579,6 +593,12 @@ bool AppInit2()
if (fDaemon)
fprintf(stdout, "Bitcoin server starting\n");
+ if (nScriptCheckThreads) {
+ printf("Using %u threads for script verification\n", nScriptCheckThreads);
+ for (int i=0; i<nScriptCheckThreads-1; i++)
+ NewThread(ThreadScriptCheck, NULL);
+ }
+
int64 nStart;
// ********************************************************* Step 5: verify database integrity
diff --git a/src/main.cpp b/src/main.cpp
index 7b37d06442..18a1c59e14 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -10,6 +10,7 @@
#include "net.h"
#include "init.h"
#include "ui_interface.h"
+#include "checkqueue.h"
#include <boost/algorithm/string/replace.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
@@ -40,6 +41,7 @@ uint256 hashBestChain = 0;
CBlockIndex* pindexBest = NULL;
set<CBlockIndex*, CBlockIndexWorkComparator> setBlockIndexValid; // may contain all CBlockIndex*'s that have validness >=BLOCK_VALID_TRANSACTIONS, and must contain those who aren't failed
int64 nTimeBestReceived = 0;
+int nScriptCheckThreads = 0;
bool fImporting = false;
bool fReindex = false;
bool fBenchmark = false;
@@ -1360,10 +1362,13 @@ bool VerifySignature(const CCoins& txFrom, const CTransaction& txTo, unsigned in
return CScriptCheck(txFrom, txTo, nIn, flags, nHashType)();
}
-bool CTransaction::CheckInputs(CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags) const
+bool CTransaction::CheckInputs(CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, std::vector<CScriptCheck> *pvChecks) const
{
if (!IsCoinBase())
{
+ if (pvChecks)
+ pvChecks->reserve(vin.size());
+
// This doesn't trigger the DoS code on purpose; if it did, it would make it easier
// for an attacker to attempt to split the network.
if (!HaveInputs(inputs))
@@ -1416,8 +1421,12 @@ bool CTransaction::CheckInputs(CCoinsViewCache &inputs, bool fScriptChecks, unsi
const CCoins &coins = inputs.GetCoins(prevout.hash);
// Verify signature
- if (!VerifySignature(coins, *this, i, flags, 0))
- return DoS(100,error("CheckInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str()));
+ CScriptCheck check(coins, *this, i, flags, 0);
+ if (pvChecks) {
+ pvChecks->push_back(CScriptCheck());
+ check.swap(pvChecks->back());
+ } else if (!check())
+ return DoS(100,false);
}
}
}
@@ -1567,6 +1576,19 @@ void static FlushBlockFile()
bool FindUndoPos(int nFile, CDiskBlockPos &pos, unsigned int nAddSize);
+static CCheckQueue<CScriptCheck> scriptcheckqueue(128);
+
+void ThreadScriptCheck(void*) {
+ vnThreadsRunning[THREAD_SCRIPTCHECK]++;
+ RenameThread("bitcoin-scriptch");
+ scriptcheckqueue.Thread();
+ vnThreadsRunning[THREAD_SCRIPTCHECK]--;
+}
+
+void ThreadScriptCheckQuit() {
+ scriptcheckqueue.Quit();
+}
+
bool CBlock::ConnectBlock(CBlockIndex* pindex, CCoinsViewCache &view, bool fJustCheck)
{
// Check it again in case a previous version let a bad block in
@@ -1607,6 +1629,8 @@ bool CBlock::ConnectBlock(CBlockIndex* pindex, CCoinsViewCache &view, bool fJust
CBlockUndo blockundo;
+ CCheckQueueControl<CScriptCheck> control(fScriptChecks && nScriptCheckThreads ? &scriptcheckqueue : NULL);
+
int64 nStart = GetTimeMicros();
int64 nFees = 0;
int nInputs = 0;
@@ -1638,8 +1662,10 @@ bool CBlock::ConnectBlock(CBlockIndex* pindex, CCoinsViewCache &view, bool fJust
nFees += tx.GetValueIn(view)-tx.GetValueOut();
- if (!tx.CheckInputs(view, fScriptChecks, fStrictPayToScriptHash ? SCRIPT_VERIFY_P2SH : SCRIPT_VERIFY_NONE))
+ std::vector<CScriptCheck> vChecks;
+ if (!tx.CheckInputs(view, fScriptChecks, fStrictPayToScriptHash ? SCRIPT_VERIFY_P2SH : SCRIPT_VERIFY_NONE, nScriptCheckThreads ? &vChecks : NULL))
return false;
+ control.Add(vChecks);
}
CTxUndo txundo;
@@ -1656,6 +1682,12 @@ bool CBlock::ConnectBlock(CBlockIndex* pindex, CCoinsViewCache &view, bool fJust
if (vtx[0].GetValueOut() > GetBlockValue(pindex->nHeight, nFees))
return error("ConnectBlock() : coinbase pays too much (actual=%"PRI64d" vs limit=%"PRI64d")", vtx[0].GetValueOut(), GetBlockValue(pindex->nHeight, nFees));
+ if (!control.Wait())
+ return DoS(100, false);
+ int64 nTime2 = GetTimeMicros() - nStart;
+ if (fBenchmark)
+ printf("- Verify %u txins: %.2fms (%.3fms/txin)\n", nInputs - 1, 0.001 * nTime2, nInputs <= 1 ? 0 : 0.001 * nTime2 / (nInputs-1));
+
if (fJustCheck)
return true;
diff --git a/src/main.h b/src/main.h
index abd3eaf7d7..3b7621959a 100644
--- a/src/main.h
+++ b/src/main.h
@@ -53,6 +53,8 @@ inline bool MoneyRange(int64 nValue) { return (nValue >= 0 && nValue <= MAX_MONE
static const int COINBASE_MATURITY = 100;
/** Threshold for nLockTime: below this value it is interpreted as block number, otherwise as UNIX timestamp. */
static const unsigned int LOCKTIME_THRESHOLD = 500000000; // Tue Nov 5 00:53:20 1985 UTC
+/** Maximum number of script-checking threads allowed */
+static const int MAX_SCRIPTCHECK_THREADS = 16;
#ifdef USE_UPNP
static const int fHaveUPnP = true;
#else
@@ -90,6 +92,7 @@ extern unsigned char pchMessageStart[4];
extern bool fImporting;
extern bool fReindex;
extern bool fBenchmark;
+extern int nScriptCheckThreads;
extern unsigned int nCoinCacheSize;
// Settings
@@ -107,6 +110,7 @@ class CCoins;
class CTxUndo;
class CCoinsView;
class CCoinsViewCache;
+class CScriptCheck;
/** Register a wallet to receive updates from core */
void RegisterWallet(CWallet* pwalletIn);
@@ -136,6 +140,10 @@ bool ProcessMessages(CNode* pfrom);
bool SendMessages(CNode* pto, bool fSendTrickle);
/** Run the importer thread, which deals with reindexing, loading bootstrap.dat, and whatever is passed to -loadblock */
void ThreadImport(void *parg);
+/** Run an instance of the script checking thread */
+void ThreadScriptCheck(void* parg);
+/** Stop the script checking threads */
+void ThreadScriptCheckQuit();
/** Run the miner threads */
void GenerateBitcoins(bool fGenerate, CWallet* pwallet);
/** Generate a new block, without valid proof-of-work */
@@ -633,8 +641,11 @@ public:
bool HaveInputs(CCoinsViewCache &view) const;
// Check whether all inputs of this transaction are valid (no double spends, scripts & sigs, amounts)
- // This does not modify the UTXO set
- bool CheckInputs(CCoinsViewCache &view, bool fScriptChecks = true, unsigned int flags = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC) const;
+ // This does not modify the UTXO set. If pvChecks is not NULL, script checks are pushed onto it
+ // instead of being performed inline.
+ bool CheckInputs(CCoinsViewCache &view, bool fScriptChecks = true,
+ unsigned int flags = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC,
+ std::vector<CScriptCheck> *pvChecks = NULL) const;
// Apply the effects of this transaction on the UTXO set represented by view
bool UpdateCoins(CCoinsViewCache &view, CTxUndo &txundo, int nHeight, const uint256 &txhash) const;
diff --git a/src/net.h b/src/net.h
index e37953772e..81caf541b0 100644
--- a/src/net.h
+++ b/src/net.h
@@ -82,6 +82,7 @@ enum threadId
THREAD_DUMPADDRESS,
THREAD_RPCHANDLER,
THREAD_IMPORT,
+ THREAD_SCRIPTCHECK,
THREAD_MAX
};
diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp
index b98816d53d..f75b762f1f 100644
--- a/src/test/test_bitcoin.cpp
+++ b/src/test/test_bitcoin.cpp
@@ -33,9 +33,13 @@ struct TestingSetup {
pwalletMain = new CWallet("wallet.dat");
pwalletMain->LoadWallet(fFirstRun);
RegisterWallet(pwalletMain);
+ nScriptCheckThreads = 3;
+ for (int i=0; i < nScriptCheckThreads-1; i++)
+ NewThread(ThreadScriptCheck, NULL);
}
~TestingSetup()
{
+ ThreadScriptCheckQuit();
delete pwalletMain;
pwalletMain = NULL;
delete pcoinsTip;