From 6cf0e50b56b39eda3cd2cf91e99a594f36e01618 Mon Sep 17 00:00:00 2001 From: Larry Ruane Date: Tue, 19 Mar 2019 13:28:02 -0600 Subject: [PATCH 1/2] add -addressindex changes for bitcore insight block explorer --- src/addressindex.h | 225 ++++++++++++++++++++++++++++++++++++++++++ src/init.cpp | 17 ++++ src/main.cpp | 144 ++++++++++++++++++++++++++- src/main.h | 20 ++-- src/script/script.cpp | 42 ++++++++ src/script/script.h | 6 ++ src/serialize.h | 11 +++ src/txdb.cpp | 83 ++++++++++++++++ src/txdb.h | 21 ++++ 9 files changed, 558 insertions(+), 11 deletions(-) create mode 100644 src/addressindex.h diff --git a/src/addressindex.h b/src/addressindex.h new file mode 100644 index 000000000..e83c9fc2f --- /dev/null +++ b/src/addressindex.h @@ -0,0 +1,225 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_ADDRESSINDEX_H +#define BITCOIN_ADDRESSINDEX_H + +#include "uint256.h" +#include "amount.h" +#include "script/script.h" + +struct CAddressUnspentKey { + unsigned int type; + uint160 hashBytes; + uint256 txhash; + size_t index; + + size_t GetSerializeSize(int nType, int nVersion) const { + return 57; + } + template + void Serialize(Stream& s) const { + ser_writedata8(s, type); + hashBytes.Serialize(s); + txhash.Serialize(s); + ser_writedata32(s, index); + } + template + void Unserialize(Stream& s) { + type = ser_readdata8(s); + hashBytes.Unserialize(s); + txhash.Unserialize(s); + index = ser_readdata32(s); + } + + CAddressUnspentKey(unsigned int addressType, uint160 addressHash, uint256 txid, size_t indexValue) { + type = addressType; + hashBytes = addressHash; + txhash = txid; + index = indexValue; + } + + CAddressUnspentKey() { + SetNull(); + } + + void SetNull() { + type = 0; + hashBytes.SetNull(); + txhash.SetNull(); + index = 0; + } +}; + +struct CAddressUnspentValue { + CAmount satoshis; + CScript script; + int blockHeight; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(satoshis); + READWRITE(*(CScriptBase*)(&script)); + READWRITE(blockHeight); + } + + CAddressUnspentValue(CAmount sats, CScript scriptPubKey, int height) { + satoshis = sats; + script = scriptPubKey; + blockHeight = height; + } + + CAddressUnspentValue() { + SetNull(); + } + + void SetNull() { + satoshis = -1; + script.clear(); + blockHeight = 0; + } + + bool IsNull() const { + return (satoshis == -1); + } +}; + +struct CAddressIndexKey { + unsigned int type; + uint160 hashBytes; + int blockHeight; + unsigned int txindex; + uint256 txhash; + size_t index; + bool spending; + + size_t GetSerializeSize(int nType, int nVersion) const { + return 66; + } + template + void Serialize(Stream& s) const { + ser_writedata8(s, type); + hashBytes.Serialize(s); + // Heights are stored big-endian for key sorting in LevelDB + ser_writedata32be(s, blockHeight); + ser_writedata32be(s, txindex); + txhash.Serialize(s); + ser_writedata32(s, index); + char f = spending; + ser_writedata8(s, f); + } + template + void Unserialize(Stream& s) { + type = ser_readdata8(s); + hashBytes.Unserialize(s); + blockHeight = ser_readdata32be(s); + txindex = ser_readdata32be(s); + txhash.Unserialize(s); + index = ser_readdata32(s); + char f = ser_readdata8(s); + spending = f; + } + + CAddressIndexKey(unsigned int addressType, uint160 addressHash, int height, int blockindex, + uint256 txid, size_t indexValue, bool isSpending) { + type = addressType; + hashBytes = addressHash; + blockHeight = height; + txindex = blockindex; + txhash = txid; + index = indexValue; + spending = isSpending; + } + + CAddressIndexKey() { + SetNull(); + } + + void SetNull() { + type = 0; + hashBytes.SetNull(); + blockHeight = 0; + txindex = 0; + txhash.SetNull(); + index = 0; + spending = false; + } + +}; + +struct CAddressIndexIteratorKey { + unsigned int type; + uint160 hashBytes; + + size_t GetSerializeSize(int nType, int nVersion) const { + return 21; + } + template + void Serialize(Stream& s) const { + ser_writedata8(s, type); + hashBytes.Serialize(s); + } + template + void Unserialize(Stream& s) { + type = ser_readdata8(s); + hashBytes.Unserialize(s); + } + + CAddressIndexIteratorKey(unsigned int addressType, uint160 addressHash) { + type = addressType; + hashBytes = addressHash; + } + + CAddressIndexIteratorKey() { + SetNull(); + } + + void SetNull() { + type = 0; + hashBytes.SetNull(); + } +}; + +struct CAddressIndexIteratorHeightKey { + unsigned int type; + uint160 hashBytes; + int blockHeight; + + size_t GetSerializeSize(int nType, int nVersion) const { + return 25; + } + template + void Serialize(Stream& s) const { + ser_writedata8(s, type); + hashBytes.Serialize(s); + ser_writedata32be(s, blockHeight); + } + template + void Unserialize(Stream& s) { + type = ser_readdata8(s); + hashBytes.Unserialize(s); + blockHeight = ser_readdata32be(s); + } + + CAddressIndexIteratorHeightKey(unsigned int addressType, uint160 addressHash, int height) { + type = addressType; + hashBytes = addressHash; + blockHeight = height; + } + + CAddressIndexIteratorHeightKey() { + SetNull(); + } + + void SetNull() { + type = 0; + hashBytes.SetNull(); + blockHeight = 0; + } +}; + +#endif // BITCOIN_ADDRESSINDEX_H diff --git a/src/init.cpp b/src/init.cpp index f9da81e87..ec459e36b 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -841,6 +841,8 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) return InitError(_("RPC method z_mergetoaddress requires -experimentalfeatures.")); } else if (mapArgs.count("-savesproutr1cs")) { return InitError(_("Saving the Sprout R1CS requires -experimentalfeatures.")); + } else if (mapArgs.count("-insightexplorer")) { + return InitError(_("Insight explorer requires -experimentalfeatures.")); } } @@ -1442,6 +1444,15 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) int64_t nBlockTreeDBCache = nTotalCache / 8; if (nBlockTreeDBCache > (1 << 21) && !GetBoolArg("-txindex", false)) nBlockTreeDBCache = (1 << 21); // block tree db cache shouldn't be larger than 2 MiB + + // https://github.com/bitpay/bitcoin/commit/c91d78b578a8700a45be936cb5bb0931df8f4b87#diff-c865a8939105e6350a50af02766291b7R1233 + if (GetBoolArg("-insightexplorer", false)) { + if (!GetBoolArg("-txindex", false)) { + return InitError(_("-insightexplorer requires -txindex.")); + } + // increase cache if additional indices are needed + nBlockTreeDBCache = nTotalCache * 3 / 4; + } nTotalCache -= nBlockTreeDBCache; int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache nTotalCache -= nCoinDBCache; @@ -1503,6 +1514,12 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) break; } + // Check for changed -insightexplorer state + if (fInsightExplorer != GetBoolArg("-insightexplorer", false)) { + strLoadError = _("You need to rebuild the database using -reindex to change -insightexplorer"); + break; + } + // Check for changed -prune state. What we are concerned about is a user who has pruned blocks // in the past, but is now trying to run unpruned. if (fHavePruned && !fPruneMode) { diff --git a/src/main.cpp b/src/main.cpp index d0d8db976..4c47cfa0c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -67,6 +67,8 @@ bool fExperimentalMode = false; bool fImporting = false; bool fReindex = false; bool fTxIndex = false; +bool fInsightExplorer = false; // insightexplorer +bool fAddressIndex = false; // insightexplorer bool fHavePruned = false; bool fPruneMode = false; bool fIsBareMultisigStd = true; @@ -2226,8 +2228,11 @@ enum DisconnectResult }; /** Undo the effects of this block (with given index) on the UTXO set represented by coins. - * When UNCLEAN or FAILED is returned, view is left in an indeterminate state. */ -DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view) + * When UNCLEAN or FAILED is returned, view is left in an indeterminate state. + * The addressIndex will be updated if requested. + */ +static DisconnectResult DisconnectBlock(const CBlock& block, CValidationState& state, + const CBlockIndex* pindex, CCoinsViewCache& view, const bool fUpdateAddressIndex) { assert(pindex->GetBlockHash() == view.GetBestBlock()); @@ -2248,11 +2253,35 @@ DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, error("DisconnectBlock(): block and undo data inconsistent"); return DISCONNECT_FAILED; } + std::vector addressIndex; + std::vector addressUnspentIndex; // undo transactions in reverse order for (int i = block.vtx.size() - 1; i >= 0; i--) { const CTransaction &tx = block.vtx[i]; - uint256 hash = tx.GetHash(); + uint256 const hash = tx.GetHash(); + + // insightexplorer + // https://github.com/bitpay/bitcoin/commit/017f548ea6d89423ef568117447e61dd5707ec42#diff-7ec3c68a81efff79b6ca22ac1f1eabbaR2236 + if (fAddressIndex && fUpdateAddressIndex) { + for (unsigned int k = tx.vout.size(); k-- > 0;) { + const CTxOut &out = tx.vout[k]; + int const scriptType = out.scriptPubKey.Type(); + if (scriptType > 0) { + uint160 const addrHash = out.scriptPubKey.AddressHash(); + + // undo receiving activity + addressIndex.push_back(make_pair( + CAddressIndexKey(scriptType, addrHash, pindex->nHeight, i, hash, k, false), + out.nValue)); + + // undo unspent index + addressUnspentIndex.push_back(make_pair( + CAddressUnspentKey(scriptType, addrHash, hash, k), + CAddressUnspentValue())); + } + } + } // Check that all outputs are available and match the outputs in the block itself // exactly. @@ -2288,6 +2317,27 @@ DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, const CTxInUndo &undo = txundo.vprevout[j]; if (!ApplyTxInUndo(undo, view, out)) fClean = false; + + // insightexplorer + // https://github.com/bitpay/bitcoin/commit/017f548ea6d89423ef568117447e61dd5707ec42#diff-7ec3c68a81efff79b6ca22ac1f1eabbaR2304 + if (fAddressIndex && fUpdateAddressIndex) { + const CTxIn input = tx.vin[j]; + const CTxOut &prevout = view.GetOutputFor(input); + int const scriptType = prevout.scriptPubKey.Type(); + if (scriptType > 0) { + uint160 const addrHash = prevout.scriptPubKey.AddressHash(); + + // undo spending activity + addressIndex.push_back(make_pair( + CAddressIndexKey(scriptType, addrHash, pindex->nHeight, i, hash, j, true), + prevout.nValue * -1)); + + // restore unspent index + addressUnspentIndex.push_back(make_pair( + CAddressUnspentKey(scriptType, addrHash, input.prevout.hash, input.prevout.n), + CAddressUnspentValue(prevout.nValue, prevout.scriptPubKey, undo.nHeight))); + } + } } } } @@ -2309,6 +2359,17 @@ DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, // move best block pointer to prevout block view.SetBestBlock(pindex->pprev->GetBlockHash()); + // insightexplorer + if (fAddressIndex && fUpdateAddressIndex) { + if (!pblocktree->EraseAddressIndex(addressIndex)) { + AbortNode(state, "Failed to delete address index"); + return DISCONNECT_FAILED; + } + if (!pblocktree->UpdateAddressUnspentIndex(addressUnspentIndex)) { + AbortNode(state, "Failed to write address unspent index"); + return DISCONNECT_FAILED; + } + } return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN; } @@ -2505,6 +2566,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin std::vector > vPos; vPos.reserve(block.vtx.size()); blockundo.vtxundo.reserve(block.vtx.size() - 1); + std::vector addressIndex; + std::vector addressUnspentIndex; // Construct the incremental merkle tree at the current // block position, @@ -2535,6 +2598,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin for (unsigned int i = 0; i < block.vtx.size(); i++) { const CTransaction &tx = block.vtx[i]; + const uint256 hash = tx.GetHash(); nInputs += tx.vin.size(); nSigOps += GetLegacySigOpCount(tx); @@ -2553,6 +2617,30 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin return state.DoS(100, error("ConnectBlock(): JoinSplit requirements not met"), REJECT_INVALID, "bad-txns-joinsplit-requirements-not-met"); + // insightexplorer + // https://github.com/bitpay/bitcoin/commit/017f548ea6d89423ef568117447e61dd5707ec42#diff-7ec3c68a81efff79b6ca22ac1f1eabbaR2597 + if (fAddressIndex) { + for (size_t j = 0; j < tx.vin.size(); j++) { + + const CTxIn input = tx.vin[j]; + const CTxOut &prevout = view.GetOutputFor(tx.vin[j]); + int const scriptType = prevout.scriptPubKey.Type(); + if (scriptType > 0) { + uint160 const addrHash = prevout.scriptPubKey.AddressHash(); + + // record spending activity + addressIndex.push_back(make_pair( + CAddressIndexKey(scriptType, addrHash, pindex->nHeight, i, hash, j, true), + prevout.nValue * -1)); + + // remove address from unspent index + addressUnspentIndex.push_back(make_pair( + CAddressUnspentKey(scriptType, addrHash, input.prevout.hash, input.prevout.n), + CAddressUnspentValue())); + } + } + } + // Add in sigops done by pay-to-script-hash inputs; // this is to prevent a "rogue miner" from creating // an incredibly-expensive-to-validate block. @@ -2575,6 +2663,28 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin control.Add(vChecks); } + // insightexplorer + // https://github.com/bitpay/bitcoin/commit/017f548ea6d89423ef568117447e61dd5707ec42#diff-7ec3c68a81efff79b6ca22ac1f1eabbaR2656 + if (fAddressIndex) { + for (unsigned int k = 0; k < tx.vout.size(); k++) { + const CTxOut &out = tx.vout[k]; + int const scriptType = out.scriptPubKey.Type(); + if (scriptType > 0) { + uint160 const addrHash = out.scriptPubKey.AddressHash(); + + // record receiving activity + addressIndex.push_back(make_pair( + CAddressIndexKey(scriptType, addrHash, pindex->nHeight, i, hash, k, false), + out.nValue)); + + // record unspent output + addressUnspentIndex.push_back(make_pair( + CAddressUnspentKey(scriptType, addrHash, hash, k), + CAddressUnspentValue(out.nValue, out.scriptPubKey, pindex->nHeight))); + } + } + } + CTxUndo undoDummy; if (i > 0) { blockundo.vtxundo.push_back(CTxUndo()); @@ -2666,6 +2776,16 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin if (!pblocktree->WriteTxIndex(vPos)) return AbortNode(state, "Failed to write transaction index"); + // insightexplorer + if (fAddressIndex) { + if (!pblocktree->WriteAddressIndex(addressIndex)) { + return AbortNode(state, "Failed to write address index"); + } + if (!pblocktree->UpdateAddressUnspentIndex(addressUnspentIndex)) { + return AbortNode(state, "Failed to write address unspent index"); + } + } + // add this block to the view's block chain view.SetBestBlock(pindex->GetBlockHash()); @@ -2860,7 +2980,8 @@ bool static DisconnectTip(CValidationState &state, bool fBare = false) { int64_t nStart = GetTimeMicros(); { CCoinsViewCache view(pcoinsTip); - if (DisconnectBlock(block, pindexDelete, view) != DISCONNECT_OK) + // insightexplorer: update indices (true) + if (DisconnectBlock(block, state, pindexDelete, view, true) != DISCONNECT_OK) return error("DisconnectTip(): DisconnectBlock %s failed", pindexDelete->GetBlockHash().ToString()); assert(view.Flush()); } @@ -4195,6 +4316,12 @@ bool static LoadBlockIndexDB() pblocktree->ReadFlag("txindex", fTxIndex); LogPrintf("%s: transaction index %s\n", __func__, fTxIndex ? "enabled" : "disabled"); + // insightexplorer + // Check whether block explorer features are enabled + pblocktree->ReadFlag("insightexplorer", fInsightExplorer); + LogPrintf("%s: insight explorer %s\n", __func__, fAddressIndex ? "enabled" : "disabled"); + fAddressIndex = fInsightExplorer; + // Fill in-memory data BOOST_FOREACH(const PAIRTYPE(uint256, CBlockIndex*)& item, mapBlockIndex) { @@ -4283,7 +4410,8 @@ bool CVerifyDB::VerifyDB(CCoinsView *coinsview, int nCheckLevel, int nCheckDepth } // check level 3: check for inconsistencies during memory-only disconnect of tip blocks if (nCheckLevel >= 3 && pindex == pindexState && (coins.DynamicMemoryUsage() + pcoinsTip->DynamicMemoryUsage()) <= nCoinCacheUsage) { - DisconnectResult res = DisconnectBlock(block, pindex, coins); + // insightexplorer: do not update indices (false) + DisconnectResult res = DisconnectBlock(block, state, pindex, coins, false); if (res == DISCONNECT_FAILED) { return error("VerifyDB(): *** irrecoverable inconsistency in block data at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); } @@ -4530,6 +4658,12 @@ bool InitBlockIndex() { // Use the provided setting for -txindex in the new database fTxIndex = GetBoolArg("-txindex", false); pblocktree->WriteFlag("txindex", fTxIndex); + + // Use the provided setting for -insightexplorer in the new database + fInsightExplorer = GetBoolArg("-insightexplorer", false); + pblocktree->WriteFlag("insightexplorer", fInsightExplorer); + fAddressIndex = fInsightExplorer; + LogPrintf("Initializing databases...\n"); // Only add the genesis block if not reindexing (in which case we reuse the one already on disk) diff --git a/src/main.h b/src/main.h index ce489d9ae..3b8c6aff9 100644 --- a/src/main.h +++ b/src/main.h @@ -26,6 +26,7 @@ #include "tinyformat.h" #include "txmempool.h" #include "uint256.h" +#include "addressindex.h" #include #include @@ -131,6 +132,19 @@ extern bool fImporting; extern bool fReindex; extern int nScriptCheckThreads; extern bool fTxIndex; + +// START insightexplorer +extern bool fInsightExplorer; + +// The following flags enable specific indices (DB tables), but are not exposed as +// separate command-line options; instead they are enabled by experimental feature "-insightexplorer" +// and are always equal to the overall controlling flag, fInsightExplorer. + +// Maintain a full address index, used to query for the balance, txids and unspent outputs for addresses +extern bool fAddressIndex; + +// END insightexplorer + extern bool fIsBareMultisigStd; extern bool fCheckBlockIndex; extern bool fCheckpointsEnabled; @@ -449,12 +463,6 @@ bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex); /** Functions for validating blocks and updating the block tree */ -/** Undo the effects of this block (with given index) on the UTXO set represented by coins. - * In case pfClean is provided, operation will try to be tolerant about errors, and *pfClean - * will be true if no problems were found. Otherwise, the return value will be false in case - * of problems. Note that in any case, coins may be modified. */ -bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& coins, bool* pfClean = NULL); - /** Apply the effects of this block (with given index) on the UTXO set represented by coins */ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& coins, bool fJustCheck = false); diff --git a/src/script/script.cpp b/src/script/script.cpp index d5234cae8..32fe34b59 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -201,6 +201,19 @@ unsigned int CScript::GetSigOpCount(const CScript& scriptSig) const return subscript.GetSigOpCount(true); } +// insightexplorer +// https://github.com/bitpay/bitcoin/commit/017f548ea6d89423ef568117447e61dd5707ec42#diff-f7ca24fb80ddba0f291cb66344ca6fcbR204 +bool CScript::IsPayToPublicKeyHash() const +{ + // Extra-fast test for pay-to-pubkey-hash CScripts: + return (this->size() == 25 && + (*this)[0] == OP_DUP && + (*this)[1] == OP_HASH160 && + (*this)[2] == 0x14 && + (*this)[23] == OP_EQUALVERIFY && + (*this)[24] == OP_CHECKSIG); +} + bool CScript::IsPayToScriptHash() const { // Extra-fast test for pay-to-script-hash CScripts: @@ -227,3 +240,32 @@ bool CScript::IsPushOnly() const } return true; } + +// insightexplorer +int CScript::Type() const +{ + if (this->IsPayToPublicKeyHash()) + return 1; + if (this->IsPayToScriptHash()) + return 2; + // We don't know this script + return 0; +} + +// insightexplorer +uint160 CScript::AddressHash() const +{ + // where the address bytes begin depends on the script type + int start; + if (this->IsPayToPublicKeyHash()) + start = 3; + else if (this->IsPayToScriptHash()) + start = 2; + else { + // unknown script type; return an empty vector + vector hashBytes; + return uint160(hashBytes); + } + vector hashBytes(this->begin()+start, this->begin()+start+20); + return uint160(hashBytes); +} diff --git a/src/script/script.h b/src/script/script.h index 541ece1f5..6f8867611 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -18,6 +18,8 @@ #include #include +#include "uint256.h" + static const unsigned int MAX_SCRIPT_ELEMENT_SIZE = 520; // bytes // Maximum script length in bytes @@ -565,8 +567,12 @@ public: */ unsigned int GetSigOpCount(const CScript& scriptSig) const; + bool IsPayToPublicKeyHash() const; bool IsPayToScriptHash() const; + int Type() const; + uint160 AddressHash() const; + /** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */ bool IsPushOnly() const; diff --git a/src/serialize.h b/src/serialize.h index a945650d6..ac2db6edd 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -110,6 +110,11 @@ template inline void ser_writedata32(Stream &s, uint32_t obj) obj = htole32(obj); s.write((char*)&obj, 4); } +template inline void ser_writedata32be(Stream &s, uint32_t obj) +{ + obj = htobe32(obj); + s.write((char*)&obj, 4); +} template inline void ser_writedata64(Stream &s, uint64_t obj) { obj = htole64(obj); @@ -133,6 +138,12 @@ template inline uint32_t ser_readdata32(Stream &s) s.read((char*)&obj, 4); return le32toh(obj); } +template inline uint32_t ser_readdata32be(Stream &s) +{ + uint32_t obj; + s.read((char*)&obj, 4); + return be32toh(obj); +} template inline uint64_t ser_readdata64(Stream &s) { uint64_t obj; diff --git a/src/txdb.cpp b/src/txdb.cpp index 8749ffcc6..162a79014 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -35,6 +35,10 @@ static const char DB_FLAG = 'F'; static const char DB_REINDEX_FLAG = 'R'; static const char DB_LAST_BLOCK = 'l'; +// insightexplorer +static const char DB_ADDRESSINDEX = 'd'; +static const char DB_ADDRESSUNSPENTINDEX = 'u'; + CCoinsViewDB::CCoinsViewDB(std::string dbName, size_t nCacheSize, bool fMemory, bool fWipe) : db(GetDataDir() / dbName, nCacheSize, fMemory, fWipe) { } @@ -293,6 +297,85 @@ bool CBlockTreeDB::WriteTxIndex(const std::vector return WriteBatch(batch); } +// START insightexplorer +// https://github.com/bitpay/bitcoin/commit/017f548ea6d89423ef568117447e61dd5707ec42#diff-81e4f16a1b5d5b7ca25351a63d07cb80R183 +bool CBlockTreeDB::UpdateAddressUnspentIndex(const std::vector &vect) +{ + CDBBatch batch(*this); + for (std::vector::const_iterator it=vect.begin(); it!=vect.end(); it++) { + if (it->second.IsNull()) { + batch.Erase(make_pair(DB_ADDRESSUNSPENTINDEX, it->first)); + } else { + batch.Write(make_pair(DB_ADDRESSUNSPENTINDEX, it->first), it->second); + } + } + return WriteBatch(batch); +} + +bool CBlockTreeDB::ReadAddressUnspentIndex(uint160 addressHash, int type, std::vector &unspentOutputs) +{ + boost::scoped_ptr pcursor(NewIterator()); + + pcursor->Seek(make_pair(DB_ADDRESSUNSPENTINDEX, CAddressIndexIteratorKey(type, addressHash))); + + while (pcursor->Valid()) { + boost::this_thread::interruption_point(); + std::pair key; + if (!(pcursor->GetKey(key) && key.first == DB_ADDRESSUNSPENTINDEX && key.second.hashBytes == addressHash)) + break; + CAddressUnspentValue nValue; + if (pcursor->GetValue(nValue)) + return error("failed to get address unspent value"); + unspentOutputs.push_back(make_pair(key.second, nValue)); + pcursor->Next(); + } + return true; +} + +bool CBlockTreeDB::WriteAddressIndex(const std::vector &vect) { + CDBBatch batch(*this); + for (std::vector::const_iterator it=vect.begin(); it!=vect.end(); it++) + batch.Write(make_pair(DB_ADDRESSINDEX, it->first), it->second); + return WriteBatch(batch); +} + +bool CBlockTreeDB::EraseAddressIndex(const std::vector &vect) { + CDBBatch batch(*this); + for (std::vector::const_iterator it=vect.begin(); it!=vect.end(); it++) + batch.Erase(make_pair(DB_ADDRESSINDEX, it->first)); + return WriteBatch(batch); +} + +bool CBlockTreeDB::ReadAddressIndex( + uint160 addressHash, int type, + std::vector &addressIndex, + int start, int end) +{ + boost::scoped_ptr pcursor(NewIterator()); + + if (start > 0 && end > 0) { + pcursor->Seek(make_pair(DB_ADDRESSINDEX, CAddressIndexIteratorHeightKey(type, addressHash, start))); + } else { + pcursor->Seek(make_pair(DB_ADDRESSINDEX, CAddressIndexIteratorKey(type, addressHash))); + } + + while (pcursor->Valid()) { + boost::this_thread::interruption_point(); + std::pair key; + if (!(pcursor->GetKey(key) && key.first == DB_ADDRESSINDEX && key.second.hashBytes == addressHash)) + break; + if (end > 0 && key.second.blockHeight > end) + break; + CAmount nValue; + if (!pcursor->GetValue(nValue)) + return error("failed to get address index value"); + addressIndex.push_back(make_pair(key.second, nValue)); + pcursor->Next(); + } + return true; +} +// END insightexplorer + bool CBlockTreeDB::WriteFlag(const std::string &name, bool fValue) { return Write(std::make_pair(DB_FLAG, name), fValue ? '1' : '0'); } diff --git a/src/txdb.h b/src/txdb.h index a415ad2bb..305e93e9c 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -17,6 +17,18 @@ class CBlockFileInfo; class CBlockIndex; struct CDiskTxPos; + +// START insightexplorer +struct CAddressUnspentKey; +struct CAddressUnspentValue; +struct CAddressIndexKey; +struct CAddressIndexIteratorKey; +struct CAddressIndexIteratorHeightKey; + +typedef std::pair CAddressUnspentDbEntry; +typedef std::pair CAddressIndexDbEntry; +// END insightexplorer + class uint256; //! -dbcache default (MiB) @@ -70,6 +82,15 @@ public: bool ReadReindexing(bool &fReindex); bool ReadTxIndex(const uint256 &txid, CDiskTxPos &pos); bool WriteTxIndex(const std::vector > &list); + + // START insightexplorer + bool UpdateAddressUnspentIndex(const std::vector &vect); + bool ReadAddressUnspentIndex(uint160 addressHash, int type, std::vector &vect); + bool WriteAddressIndex(const std::vector &vect); + bool EraseAddressIndex(const std::vector &vect); + bool ReadAddressIndex(uint160 addressHash, int type, std::vector &addressIndex, int start = 0, int end = 0); + // END insightexplorer + bool WriteFlag(const std::string &name, bool fValue); bool ReadFlag(const std::string &name, bool &fValue); bool LoadBlockIndexGuts(); From ec81c709644147a8924ae2a9e1d38d31d6c30c81 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Tue, 4 Oct 2016 15:03:13 -0400 Subject: [PATCH 2/2] tests: adds unit test for IsPayToPublicKeyHash method --- src/Makefile.test.include | 1 + src/test/script_P2PKH_tests.cpp | 59 +++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/test/script_P2PKH_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 91526ceb6..1eb6b262e 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -86,6 +86,7 @@ BITCOIN_TESTS =\ test/sanity_tests.cpp \ test/scheduler_tests.cpp \ test/script_P2SH_tests.cpp \ + test/script_P2PKH_tests.cpp \ test/script_tests.cpp \ test/scriptnum_tests.cpp \ test/serialize_tests.cpp \ diff --git a/src/test/script_P2PKH_tests.cpp b/src/test/script_P2PKH_tests.cpp new file mode 100644 index 000000000..3a7dc1660 --- /dev/null +++ b/src/test/script_P2PKH_tests.cpp @@ -0,0 +1,59 @@ +// Copyright (c) 2012-2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "script/script.h" +#include "test/test_bitcoin.h" + +#include + +using namespace std; + +BOOST_FIXTURE_TEST_SUITE(script_P2PKH_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(IsPayToPublicKeyHash) +{ + // Test CScript::IsPayToPublicKeyHash() + uint160 dummy; + CScript p2pkh; + p2pkh << OP_DUP << OP_HASH160 << ToByteVector(dummy) << OP_EQUALVERIFY << OP_CHECKSIG; + BOOST_CHECK(p2pkh.IsPayToPublicKeyHash()); + + static const unsigned char direct[] = { + OP_DUP, OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUALVERIFY, OP_CHECKSIG + }; + BOOST_CHECK(CScript(direct, direct+sizeof(direct)).IsPayToPublicKeyHash()); + + static const unsigned char notp2pkh1[] = { + OP_DUP, OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUALVERIFY, OP_CHECKSIG, OP_CHECKSIG + }; + BOOST_CHECK(!CScript(notp2pkh1, notp2pkh1+sizeof(notp2pkh1)).IsPayToPublicKeyHash()); + + static const unsigned char p2sh[] = { + OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUAL + }; + BOOST_CHECK(!CScript(p2sh, p2sh+sizeof(p2sh)).IsPayToPublicKeyHash()); + + static const unsigned char extra[] = { + OP_DUP, OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUALVERIFY, OP_CHECKSIG, OP_CHECKSIG + }; + BOOST_CHECK(!CScript(extra, extra+sizeof(extra)).IsPayToPublicKeyHash()); + + static const unsigned char missing[] = { + OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUALVERIFY, OP_CHECKSIG, OP_RETURN + }; + BOOST_CHECK(!CScript(missing, missing+sizeof(missing)).IsPayToPublicKeyHash()); + + static const unsigned char missing2[] = { + OP_DUP, OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + }; + BOOST_CHECK(!CScript(missing2, missing2+sizeof(missing)).IsPayToPublicKeyHash()); + + static const unsigned char tooshort[] = { + OP_DUP, OP_HASH160, 2, 0,0, OP_EQUALVERIFY, OP_CHECKSIG + }; + BOOST_CHECK(!CScript(tooshort, tooshort+sizeof(direct)).IsPayToPublicKeyHash()); + +} + +BOOST_AUTO_TEST_SUITE_END()