diff --git a/src/init.cpp b/src/init.cpp index 0b3234566..16598ada4 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -350,6 +350,8 @@ std::string HelpMessage(HelpMessageMode mode) #endif strUsage += HelpMessageOpt("-txindex", strprintf(_("Maintain a full transaction index, used by the getrawtransaction rpc call (default: %u)"), DEFAULT_TXINDEX)); + strUsage += HelpMessageOpt("-addressindex", strprintf(_("Maintain a full address index, used to query for the balance, txids and unspent outputs for addresses (default: %u)"), DEFAULT_ADDRESSINDEX)); + strUsage += HelpMessageGroup(_("Connection options:")); strUsage += HelpMessageOpt("-addnode=", _("Add a node to connect to and attempt to keep the connection open")); strUsage += HelpMessageOpt("-banscore=", strprintf(_("Threshold for disconnecting misbehaving peers (default: %u)"), DEFAULT_BANSCORE_THRESHOLD)); diff --git a/src/main.cpp b/src/main.cpp index f85ac3317..adad65640 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -66,6 +66,7 @@ int nScriptCheckThreads = 0; bool fImporting = false; bool fReindex = false; bool fTxIndex = false; +bool fAddressIndex = false; bool fHavePruned = false; bool fPruneMode = false; bool fIsBareMultisigStd = DEFAULT_PERMIT_BAREMULTISIG; @@ -1438,6 +1439,17 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa return res; } +bool GetAddressIndex(uint160 addressHash, int type, std::vector > &addressIndex) +{ + if (!fAddressIndex) + return error("%s: address index not enabled"); + + if (!pblocktree->ReadAddressIndex(addressHash, type, addressIndex)) + return error("%s: unable to get txids for address"); + + return true; +} + /** Return transaction in tx, and if it was found inside a block, its hash is placed in hashBlock */ bool GetTransaction(const uint256 &hash, CTransaction &txOut, const Consensus::Params& consensusParams, uint256 &hashBlock, bool fAllowSlow) { @@ -2322,9 +2334,12 @@ 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; + for (unsigned int i = 0; i < block.vtx.size(); i++) { const CTransaction &tx = block.vtx[i]; + const uint256 txhash = tx.GetHash(); nInputs += tx.vin.size(); nSigOps += GetLegacySigOpCount(tx); @@ -2351,6 +2366,22 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin REJECT_INVALID, "bad-txns-nonfinal"); } + if (fAddressIndex) + { + for (size_t j = 0; j < tx.vin.size(); j++) { + const CTxOut &prevout = view.GetOutputFor(tx.vin[j]); + if (prevout.scriptPubKey.IsPayToScriptHash()) { + vector hashBytes(prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22); + addressIndex.push_back(make_pair(CAddressIndexKey(uint160(hashBytes), 2, txhash, j), prevout.nValue * -1)); + } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { + vector hashBytes(prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23); + addressIndex.push_back(make_pair(CAddressIndexKey(uint160(hashBytes), 1, txhash, j), prevout.nValue * -1)); + } else { + continue; + } + } + } + if (fStrictPayToScriptHash) { // Add in sigops done by pay-to-script-hash inputs; @@ -2372,6 +2403,24 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin control.Add(vChecks); } + if (fAddressIndex) { + for (unsigned int k = 0; k < tx.vout.size(); k++) { + const CTxOut &out = tx.vout[k]; + + if (out.scriptPubKey.IsPayToScriptHash()) { + vector hashBytes(out.scriptPubKey.begin()+2, out.scriptPubKey.begin()+22); + addressIndex.push_back(make_pair(CAddressIndexKey(uint160(hashBytes), 2, txhash, k), out.nValue)); + } else if (out.scriptPubKey.IsPayToPublicKeyHash()) { + vector hashBytes(out.scriptPubKey.begin()+3, out.scriptPubKey.begin()+23); + addressIndex.push_back(make_pair(CAddressIndexKey(uint160(hashBytes), 1, txhash, k), out.nValue)); + } else { + continue; + } + + } + } + + CTxUndo undoDummy; if (i > 0) { blockundo.vtxundo.push_back(CTxUndo()); @@ -2422,6 +2471,10 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin if (!pblocktree->WriteTxIndex(vPos)) return AbortNode(state, "Failed to write transaction index"); + if (fAddressIndex) + if (!pblocktree->WriteAddressIndex(addressIndex)) + return AbortNode(state, "Failed to write address index"); + // add this block to the view's block chain view.SetBestBlock(pindex->GetBlockHash()); @@ -3813,6 +3866,10 @@ bool static LoadBlockIndexDB() pblocktree->ReadFlag("txindex", fTxIndex); LogPrintf("%s: transaction index %s\n", __func__, fTxIndex ? "enabled" : "disabled"); + // Check whether we have an address index + pblocktree->ReadFlag("addressindex", fAddressIndex); + LogPrintf("%s: address index %s\n", __func__, fAddressIndex ? "enabled" : "disabled"); + // Load pointer to end of best chain BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock()); if (it == mapBlockIndex.end()) @@ -3973,6 +4030,11 @@ bool InitBlockIndex(const CChainParams& chainparams) // Use the provided setting for -txindex in the new database fTxIndex = GetBoolArg("-txindex", DEFAULT_TXINDEX); pblocktree->WriteFlag("txindex", fTxIndex); + + // Use the provided setting for -addressindex in the new database + fAddressIndex = GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX); + pblocktree->WriteFlag("addressindex", fAddressIndex); + 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 1a696dcd9..159099036 100644 --- a/src/main.h +++ b/src/main.h @@ -111,6 +111,7 @@ static const bool DEFAULT_PERMIT_BAREMULTISIG = true; static const unsigned int DEFAULT_BYTES_PER_SIGOP = 20; static const bool DEFAULT_CHECKPOINTS_ENABLED = true; static const bool DEFAULT_TXINDEX = false; +static const bool DEFAULT_ADDRESSINDEX = false; static const unsigned int DEFAULT_BANSCORE_THRESHOLD = 100; static const bool DEFAULT_TESTSAFEMODE = false; @@ -290,6 +291,42 @@ struct CNodeStateStats { std::vector vHeightInFlight; }; +struct CAddressIndexKey { + uint160 hashBytes; + unsigned int type; + uint256 txhash; + size_t index; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(hashBytes); + READWRITE(type); + READWRITE(txhash); + READWRITE(index); + } + + CAddressIndexKey(uint160 addressHash, unsigned int addressType, uint256 txid, size_t txindex) { + hashBytes = addressHash; + type = addressType; + txhash = txid; + index = txindex; + } + + CAddressIndexKey() { + SetNull(); + } + + void SetNull() { + hashBytes.SetNull(); + type = 0; + txhash.SetNull(); + index = 0; + } + +}; + struct CDiskTxPos : public CDiskBlockPos { unsigned int nTxOffset; // after header @@ -420,6 +457,7 @@ public: ScriptError GetScriptError() const { return error; } }; +bool GetAddressIndex(uint160 addressHash, int type, std::vector > &addressIndex); /** Functions for disk access for blocks */ bool WriteBlockToDisk(const CBlock& block, CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& messageStart); diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 9871c3fcc..aa762af88 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -396,3 +396,39 @@ UniValue setmocktime(const UniValue& params, bool fHelp) return NullUniValue; } + +UniValue getaddresstxids(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "getaddresstxids\n" + "\nReturns the txids for an address (requires addressindex to be enabled).\n" + "\nResult\n" + "[\n" + " \"transactionid\" (string) The transaction id\n" + " ,...\n" + "]\n" + ); + + CBitcoinAddress address(params[0].get_str()); + if (!address.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + + CKeyID keyID; + address.GetKeyID(keyID); + + int type = 1; // TODO + std::vector > addressIndex; + + LOCK(cs_main); + + if (!GetAddressIndex(keyID, type, addressIndex)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for address"); + + UniValue result(UniValue::VARR); + for (std::vector >::const_iterator it=addressIndex.begin(); it!=addressIndex.end(); it++) + result.push_back(it->first.txhash.GetHex()); + + return result; + +} diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index b3abeec4a..67da51ceb 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -313,6 +313,9 @@ static const CRPCCommand vRPCCommands[] = { "rawtransactions", "fundrawtransaction", &fundrawtransaction, false }, #endif + /* Address index */ + { "addressindex", "getaddresstxids", &getaddresstxids, false }, + /* Utility functions */ { "util", "createmultisig", &createmultisig, true }, { "util", "validateaddress", &validateaddress, true }, /* uses wallet if enabled */ diff --git a/src/rpcserver.h b/src/rpcserver.h index babf7c8d2..11f760051 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -166,6 +166,8 @@ extern std::string HelpExampleRpc(const std::string& methodname, const std::stri extern void EnsureWalletIsUnlocked(); extern UniValue getconnectioncount(const UniValue& params, bool fHelp); // in rpcnet.cpp +extern UniValue getaddresstxids(const UniValue& params, bool fHelp); + extern UniValue getpeerinfo(const UniValue& params, bool fHelp); extern UniValue ping(const UniValue& params, bool fHelp); extern UniValue addnode(const UniValue& params, bool fHelp); diff --git a/src/script/script.cpp b/src/script/script.cpp index 9f2809e59..3e4b72f5d 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -201,6 +201,17 @@ unsigned int CScript::GetSigOpCount(const CScript& scriptSig) const return subscript.GetSigOpCount(true); } +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: diff --git a/src/script/script.h b/src/script/script.h index d2a68a07b..9dd75a558 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -608,6 +608,8 @@ public: */ unsigned int GetSigOpCount(const CScript& scriptSig) const; + bool IsPayToPublicKeyHash() const; + bool IsPayToScriptHash() const; /** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */ diff --git a/src/txdb.cpp b/src/txdb.cpp index f99e11f26..153d7b84c 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -21,6 +21,7 @@ using namespace std; static const char DB_COINS = 'c'; static const char DB_BLOCK_FILES = 'f'; static const char DB_TXINDEX = 't'; +static const char DB_ADDRESSINDEX = 'a'; static const char DB_BLOCK_INDEX = 'b'; static const char DB_BEST_BLOCK = 'B'; @@ -163,6 +164,38 @@ bool CBlockTreeDB::WriteTxIndex(const std::vector return WriteBatch(batch); } +bool CBlockTreeDB::WriteAddressIndex(const std::vector >&vect) { + CDBBatch batch(&GetObfuscateKey()); + 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::ReadAddressIndex(uint160 addressHash, int type, std::vector > &addressIndex) { + + boost::scoped_ptr pcursor(NewIterator()); + + pcursor->Seek(make_pair(DB_ADDRESSINDEX, addressHash)); //TODO include type + + while (pcursor->Valid()) { + boost::this_thread::interruption_point(); + std::pair key; + if (pcursor->GetKey(key) && key.first == DB_ADDRESSINDEX && key.second.hashBytes == addressHash) { + CAmount nValue; + if (pcursor->GetValue(nValue)) { + addressIndex.push_back(make_pair(key.second, nValue)); + pcursor->Next(); + } else { + return error("failed to get address index value"); + } + } else { + break; + } + } + + return true; +} + 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 22e0c5704..3d2ace581 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -17,6 +17,7 @@ class CBlockFileInfo; class CBlockIndex; struct CDiskTxPos; +struct CAddressIndexKey; class uint256; //! -dbcache default (MiB) @@ -57,6 +58,8 @@ public: bool ReadReindexing(bool &fReindex); bool ReadTxIndex(const uint256 &txid, CDiskTxPos &pos); bool WriteTxIndex(const std::vector > &list); + bool WriteAddressIndex(const std::vector > &vect); + bool ReadAddressIndex(uint160 addressHash, int type, std::vector > &addressIndex); bool WriteFlag(const std::string &name, bool fValue); bool ReadFlag(const std::string &name, bool &fValue); bool LoadBlockIndexGuts();