Multithreaded amount commitment and range proof checking

This commit is contained in:
instagibbs 2016-07-06 12:06:55 +02:00 committed by Gregory Sanders
parent 56aa82fc2d
commit 33609ed3ea
4 changed files with 140 additions and 67 deletions

View file

@ -41,7 +41,8 @@ private:
//! 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;
//! This should really be a vector of unique_ptr's, but that's C++11.
std::vector<T*> queue;
//! The number of workers (including the master) that are idle.
int nIdle;
@ -69,7 +70,7 @@ private:
bool Loop(bool fMaster = false)
{
boost::condition_variable& cond = fMaster ? condMaster : condWorker;
std::vector<T> vChecks;
std::vector<T*> vChecks;
vChecks.reserve(nBatchSize);
unsigned int nNow = 0;
bool fOk = true;
@ -108,20 +109,18 @@ private:
// * 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();
}
vChecks.clear();
vChecks.insert(vChecks.end(), queue.end() - nNow, queue.end());
queue.resize(queue.size() - nNow);
// Check whether we need to do work at all
fOk = fAllOk;
}
// execute work
BOOST_FOREACH (T& check, vChecks)
BOOST_FOREACH (T* check, vChecks) {
if (fOk)
fOk = check();
fOk = (*check)();
delete check;
}
vChecks.clear();
} while (true);
}
@ -142,14 +141,11 @@ public:
return Loop(true);
}
//! Add a batch of checks to the queue
void Add(std::vector<T>& vChecks)
//! Add a batch of checks to the queue and takes ownership of them
void Add(const std::vector<T*> vChecks)
{
boost::unique_lock<boost::mutex> lock(mutex);
BOOST_FOREACH (T& check, vChecks) {
queue.push_back(T());
check.swap(queue.back());
}
queue.insert(queue.end(), vChecks.begin(), vChecks.end());
nTodo += vChecks.size();
if (vChecks.size() == 1)
condWorker.notify_one();
@ -159,6 +155,7 @@ public:
~CCheckQueue()
{
assert(queue.empty());
}
bool IsIdle()
@ -199,7 +196,7 @@ public:
return fRet;
}
void Add(std::vector<T>& vChecks)
void Add(std::vector<T*> vChecks)
{
if (pqueue != NULL)
pqueue->Add(vChecks);

View file

@ -1122,10 +1122,9 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state)
return true;
}
//static Secp256k1Ctx init_context_on_load;
//extern secp256k1_context* secp256k1_bitcoin_verify_context;
namespace {
static secp256k1_context* secp256k1_ctx_verify_amounts = NULL;
static secp256k1_context* secp256k1_ctx_verify_amounts;
class Secp256k1Ctx
{
@ -1144,12 +1143,86 @@ public:
secp256k1_ctx_verify_amounts = NULL;
}
};
static Secp256k1Ctx instance_of_secp256k1ctx;
bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, const CAmount& excess)
/** Closure representing one output range check. */
class CRangeCheck : public CCheck
{
private:
const CTxOutValue* val;
public:
CRangeCheck(const CTxOutValue* val_) : val(val_) {}
bool operator()();
};
/** Closure representing a transaction amount balance check. */
class CBalanceCheck : public CCheck
{
private:
std::vector<unsigned char> vchData;
std::vector<unsigned char *> vpchCommitsIn, vpchCommitsOut;
CAmount nPlainAmount;
public:
CBalanceCheck(std::vector<unsigned char>& vchData_, std::vector<unsigned char*>& vpchCommitsIn_, std::vector<unsigned char*>& vpchCommitsOut_, const CAmount& nPlainAmount_) : nPlainAmount(nPlainAmount_) {
vchData.swap(vchData_);
vpchCommitsIn.swap(vpchCommitsIn_);
vpchCommitsOut.swap(vpchCommitsOut_);
}
bool operator()();
};
// Destroys check, or passes its ownership to the queue.
static inline bool QueueCheck(std::vector<CCheck*>* queue, CCheck* check)
{
if (queue != NULL) {
queue->push_back(check);
return true;
}
bool ret = (*check)();
delete check;
return ret;
}
bool CRangeCheck::operator()()
{
if (val->IsAmount()) {
return true;
}
uint64_t min_value, max_value;
if (!secp256k1_rangeproof_verify(secp256k1_ctx_verify_amounts, &min_value, &max_value, &val->vchCommitment[0], val->vchRangeproof.data(), val->vchRangeproof.size())) {
fAmountError = true;
return false;
}
return true;
};
bool CBalanceCheck::operator()()
{
if (!secp256k1_pedersen_verify_tally(secp256k1_ctx_verify_amounts, vpchCommitsIn.data(), vpchCommitsIn.size(), vpchCommitsOut.data(), vpchCommitsOut.size(), nPlainAmount)) {
fAmountError = true;
return false;
}
return true;
}
} // namespace
bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, const CAmount& excess, std::vector<CCheck*>* pvChecks)
{
bool fNeedNoRangeProof = false;
CAmount nPlainAmount = excess;
{
std::vector<unsigned char> vchData;
std::vector<unsigned char *> vpchCommitsIn, vpchCommitsOut;
bool fNullRangeproof = false;
@ -1200,21 +1273,25 @@ bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, const C
if (vpchCommitsIn.size() + vpchCommitsOut.size() == 0)
return (nPlainAmount == 0);
if (!secp256k1_pedersen_verify_tally(secp256k1_ctx_verify_amounts, vpchCommitsIn.data(), vpchCommitsIn.size(), vpchCommitsOut.data(), vpchCommitsOut.size(), nPlainAmount))
fNeedNoRangeProof = ((!vpchCommitsIn.empty()) && vpchCommitsOut.size() == 1 && nPlainAmount <= 0 && fNullRangeproof);
if (!QueueCheck(pvChecks, new CBalanceCheck(vchData, vpchCommitsIn, vpchCommitsOut, nPlainAmount))) {
return false;
}
}
// Rangeproof is optional in this case
if ((!vpchCommitsIn.empty()) && vpchCommitsOut.size() == 1 && nPlainAmount <= 0 && fNullRangeproof)
if (fNeedNoRangeProof)
return true;
uint64_t min_value, max_value;
for (size_t i = 0; i < tx.vout.size(); ++i)
{
const CTxOutValue& val = tx.vout[i].nValue;
if (val.IsAmount())
continue;
if (!secp256k1_rangeproof_verify(secp256k1_ctx_verify_amounts, &min_value, &max_value, &val.vchCommitment[0], val.vchRangeproof.data(), val.vchRangeproof.size()))
if (!QueueCheck(pvChecks, new CRangeCheck(&val))) {
return false;
}
}
return true;
@ -2211,7 +2288,7 @@ int GetSpendHeight(const CCoinsViewCache& inputs)
}
namespace Consensus {
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, std::set<std::pair<uint256, COutPoint> >& setWithdrawsSpent)
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, std::set<std::pair<uint256, COutPoint> >& setWithdrawsSpent, std::vector<CCheck*> *pvChecks)
{
// 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.
@ -2259,7 +2336,7 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
if (!MoneyRange(nTxFee))
return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange");
if (!VerifyAmounts(inputs, tx, nTxFee))
if (!VerifyAmounts(inputs, tx, nTxFee, pvChecks))
return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-belowout", false,
strprintf("value in (%s) < value out", FormatMoney(nValueIn)));
@ -2267,11 +2344,11 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
}
}// namespace Consensus
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, PrecomputedTransactionData& txdata, set<pair<uint256, COutPoint> >& setWithdrawsSpent, std::vector<CScriptCheck> *pvChecks)
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, PrecomputedTransactionData& txdata, set<pair<uint256, COutPoint> >& setWithdrawsSpent, std::vector<CCheck*> *pvChecks)
{
if (!tx.IsCoinBase())
{
if (!Consensus::CheckTxInputs(tx, state, inputs, GetSpendHeight(inputs), setWithdrawsSpent))
if (!Consensus::CheckTxInputs(tx, state, inputs, GetSpendHeight(inputs), setWithdrawsSpent, pvChecks))
return false;
if (pvChecks)
@ -2295,11 +2372,8 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
assert(coins);
// Verify signature
CScriptCheck check(*coins, tx, i, prevValueIn, flags, cacheStore, &txdata);
if (pvChecks) {
pvChecks->push_back(CScriptCheck());
check.swap(pvChecks->back());
} else if (!check()) {
CCheck* check = new CScriptCheck(*coins, tx, i, prevValueIn, flags, cacheStore, &txdata);
if (!QueueCheck(pvChecks, check)) {
if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) {
// Check whether the failure was caused by a
// non-mandatory script verification check, such as
@ -2310,7 +2384,7 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
CScriptCheck check2(*coins, tx, i, prevValueIn,
flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheStore, &txdata);
if (check2())
return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError())));
return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check->GetScriptError())));
}
// Failures of other flags indicate a transaction that is
// invalid in new blocks, e.g. a invalid P2SH. We DoS ban
@ -2319,10 +2393,10 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
// as to the correct behavior - we may want to continue
// peering with non-upgraded nodes even after soft-fork
// super-majority signaling has occurred.
if (check.GetScriptError() == SCRIPT_ERR_WITHDRAW_VERIFY_BLOCKCONFIRMED)
return state.Invalid(false, REJECT_SCRIPT, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError())));
if (check->GetScriptError() == SCRIPT_ERR_WITHDRAW_VERIFY_BLOCKCONFIRMED)
return state.Invalid(false, REJECT_SCRIPT, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check->GetScriptError())));
else
return state.DoS(100,false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError())));
return state.DoS(100,false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check->GetScriptError())));
}
const CTxOutValue& value = coins->vout[tx.vin[i].prevout.n].nValue;
if (value.IsAmount())
@ -2554,7 +2628,7 @@ void static FlushBlockFile(bool fFinalize = false)
bool FindUndoPos(CValidationState &state, int nFile, CDiskBlockPos &pos, unsigned int nAddSize);
static CCheckQueue<CScriptCheck> scriptcheckqueue(128);
static CCheckQueue<CCheck> scriptcheckqueue(128);
void ThreadScriptCheck() {
RenameThread("bitcoin-scriptch");
@ -2838,7 +2912,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
CBlockUndo blockundo;
CCheckQueueControl<CScriptCheck> control(fScriptChecks && nScriptCheckThreads ? &scriptcheckqueue : NULL);
CCheckQueueControl<CCheck> control(fScriptChecks && nScriptCheckThreads ? &scriptcheckqueue : NULL);
std::vector<uint256> vOrphanErase;
std::vector<int> prevheights;
@ -2908,7 +2982,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
if (!MoneyRange(nFees))
return state.DoS(100, error("ConnectBlock(): total tx fee overflowed"), REJECT_INVALID, "bad-txns-fee-outofrange");
std::vector<CScriptCheck> vChecks;
std::vector<CCheck*> vChecks;
bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */
if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, txdata[i], setWithdrawsSpent == NULL ? setWithdrawsSpentDummy : *setWithdrawsSpent, nScriptCheckThreads ? &vChecks : NULL))
return error("ConnectBlock(): CheckInputs on %s failed with %s",
@ -2937,7 +3011,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, chainparams.GetConsensus());
if (!MoneyRange(blockReward))
return state.DoS(100, error("ConnectBlock(): total block reward overflowed"), REJECT_INVALID, "bad-blockreward-outofrange");
if (VerifyAmounts(view, block.vtx[0], -blockReward))
if (!VerifyAmounts(view, block.vtx[0], -blockReward))
return state.DoS(100,
error("ConnectBlock(): coinbase pays too much (limit=%d)",
blockReward),

View file

@ -34,7 +34,7 @@ class CBlockTreeDB;
class CBloomFilter;
class CChainParams;
class CInv;
class CScriptCheck;
class CCheck;
class CTxMemPool;
class CValidationInterface;
class CValidationState;
@ -353,7 +353,7 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i
*/
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &view, bool fScriptChecks,
unsigned int flags, bool cacheStore, PrecomputedTransactionData& txdata, std::set<std::pair<uint256, COutPoint> >& setWithdrawsSpent,
std::vector<CScriptCheck> *pvChecks = NULL);
std::vector<CCheck*> *pvChecks = NULL);
/** Apply the effects of this transaction on the UTXO set represented by view */
void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight);
@ -368,7 +368,7 @@ namespace Consensus {
* This does not modify the UTXO set. This does not check scripts and sigs.
* Preconditions: tx.IsCoinBase() is false.
*/
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, std::set<std::pair<uint256, COutPoint> >& setWithdrawsSpent);
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, std::set<std::pair<uint256, COutPoint> >& setWithdrawsSpent, std::vector<CCheck*> *pvChecks);
} // namespace Consensus
@ -378,9 +378,10 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
* @param[in] view CCoinsViewCache to find necessary outputs
* @param[in] tx transaction for which we are checking totals
* @param[in] excess additional amount to consider as input value (eg fees), can be negative
* @param[in] pvChecks multithreaded rangeproof and commitment checker
* @return True if totals are identical
*/
bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, const CAmount& excess);
bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, const CAmount& excess, std::vector<CCheck*>* pvChecks = NULL);
/**
@ -426,7 +427,23 @@ bool CheckSequenceLocks(const CTransaction &tx, int flags, LockPoints* lp = NULL
* Closure representing one script verification
* Note that this stores references to the spending transaction
*/
class CScriptCheck
class CCheck
{
protected:
ScriptError error;
bool fAmountError;
public:
CCheck() : error(SCRIPT_ERR_UNKNOWN_ERROR), fAmountError(false) {}
virtual ~CCheck() {}
virtual bool operator()() = 0;
ScriptError GetScriptError() const { return error; }
bool IsAmountError() const { return fAmountError; }
};
class CScriptCheck : public CCheck
{
private:
CScript scriptPubKey;
@ -436,31 +453,16 @@ private:
unsigned int nIn;
unsigned int nFlags;
bool cacheStore;
ScriptError error;
PrecomputedTransactionData *txdata;
public:
CScriptCheck(): amount(0), amountPreviousInput(-1), ptxTo(0), nIn(0), nFlags(0), cacheStore(false), error(SCRIPT_ERR_UNKNOWN_ERROR) {}
CScriptCheck(const CCoins& txFromIn, const CTransaction& txToIn, unsigned int nInIn, const CTxOutValue& amountPreviousInputIn, unsigned int nFlagsIn, bool cacheIn, PrecomputedTransactionData* txdataIn) :
scriptPubKey(txFromIn.vout[txToIn.vin[nInIn].prevout.n].scriptPubKey), amount(txFromIn.vout[txToIn.vin[nInIn].prevout.n].nValue),
amountPreviousInput(amountPreviousInputIn),
ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn), cacheStore(cacheIn), error(SCRIPT_ERR_UNKNOWN_ERROR), txdata(txdataIn) { }
ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn), cacheStore(cacheIn), txdata(txdataIn) { }
bool operator()();
void swap(CScriptCheck &check) {
scriptPubKey.swap(check.scriptPubKey);
std::swap(ptxTo, check.ptxTo);
std::swap(amount, check.amount);
std::swap(amountPreviousInput, check.amountPreviousInput);
std::swap(nIn, check.nIn);
std::swap(nFlags, check.nFlags);
std::swap(cacheStore, check.cacheStore);
std::swap(error, check.error);
std::swap(txdata, check.txdata);
}
ScriptError GetScriptError() const { return error; }
};

View file

@ -497,7 +497,7 @@ BOOST_AUTO_TEST_CASE(test_big_witness_transaction) {
CScriptCheck *checks[mtx.vin.size()];
for(uint32_t i = 0; i < mtx.vin.size(); i++) {
std::vector<CScriptCheck*> vChecks;
checks[i] = new CScriptCheck(coins, tx, i, 0, 0, 0, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, false, &txdata);
checks[i] = new CScriptCheck(coins, tx, i, 0, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, false, &txdata);
vChecks.push_back(checks[i]);
control.Add(vChecks);
}