changeset 29654:034b7814ab95

main: add spentindex option
author Braydon Fuller <courier@braydon.com>
date Tue, 05 Apr 2016 15:53:38 -0400
parents 2e8c6d74f0aa
children 81c811cfcccf
files qa/pull-tester/rpc-tests.py qa/rpc-tests/spentindex.py src/init.cpp src/main.cpp src/main.h src/rpcmisc.cpp src/rpcserver.cpp src/rpcserver.h src/txdb.cpp src/txdb.h
diffstat 10 files changed, 268 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/qa/pull-tester/rpc-tests.py	Thu Apr 07 13:52:40 2016 -0400
+++ b/qa/pull-tester/rpc-tests.py	Tue Apr 05 15:53:38 2016 -0400
@@ -99,6 +99,7 @@
     'reindex.py',
     'addressindex.py',
     'timestampindex.py',
+    'spentindex.py',
     'decodescript.py',
     'p2p-fullblocktest.py',
     'blockchain.py',
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qa/rpc-tests/spentindex.py	Tue Apr 05 15:53:38 2016 -0400
@@ -0,0 +1,73 @@
+#!/usr/bin/env python2
+# Copyright (c) 2014-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.
+
+#
+# Test addressindex generation and fetching
+#
+
+import time
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+from test_framework.script import *
+from test_framework.mininode import *
+import binascii
+
+class SpentIndexTest(BitcoinTestFramework):
+
+    def setup_chain(self):
+        print("Initializing test directory "+self.options.tmpdir)
+        initialize_chain_clean(self.options.tmpdir, 4)
+
+    def setup_network(self):
+        self.nodes = []
+        # Nodes 0/1 are "wallet" nodes
+        self.nodes.append(start_node(0, self.options.tmpdir, ["-debug"]))
+        self.nodes.append(start_node(1, self.options.tmpdir, ["-debug", "-spentindex"]))
+        # Nodes 2/3 are used for testing
+        self.nodes.append(start_node(2, self.options.tmpdir, ["-debug", "-spentindex"]))
+        self.nodes.append(start_node(3, self.options.tmpdir, ["-debug", "-spentindex"]))
+        connect_nodes(self.nodes[0], 1)
+        connect_nodes(self.nodes[0], 2)
+        connect_nodes(self.nodes[0], 3)
+
+        self.is_network_split = False
+        self.sync_all()
+
+    def run_test(self):
+        print "Mining blocks..."
+        self.nodes[0].generate(105)
+        self.sync_all()
+
+        chain_height = self.nodes[1].getblockcount()
+        assert_equal(chain_height, 105)
+
+        # Check that
+        print "Testing spent index..."
+
+        privkey = "cSdkPxkAjA4HDr5VHgsebAPDEh9Gyub4HK8UJr2DFGGqKKy4K5sG"
+        address = "mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW"
+        addressHash = "0b2f0a0c31bfe0406b0ccc1381fdbe311946dadc".decode("hex")
+        scriptPubKey = CScript([OP_DUP, OP_HASH160, addressHash, OP_EQUALVERIFY, OP_CHECKSIG])
+        unspent = self.nodes[0].listunspent()
+        tx = CTransaction()
+        amount = unspent[0]["amount"] * 100000000
+        tx.vin = [CTxIn(COutPoint(int(unspent[0]["txid"], 16), unspent[0]["vout"]))]
+        tx.vout = [CTxOut(amount, scriptPubKey)]
+        tx.rehash()
+
+        signed_tx = self.nodes[0].signrawtransaction(binascii.hexlify(tx.serialize()).decode("utf-8"))
+        txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], True)
+        self.nodes[0].generate(1)
+        self.sync_all()
+
+        info = self.nodes[1].getspentinfo({"txid": unspent[0]["txid"], "index": unspent[0]["vout"]})
+        assert_equal(info["txid"], txid)
+        assert_equal(info["index"], 0)
+
+        print "Passed\n"
+
+
+if __name__ == '__main__':
+    SpentIndexTest().main()
--- a/src/init.cpp	Thu Apr 07 13:52:40 2016 -0400
+++ b/src/init.cpp	Tue Apr 05 15:53:38 2016 -0400
@@ -352,6 +352,7 @@
 
     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 += HelpMessageOpt("-timestampindex", strprintf(_("Maintain a timestamp index for block hashes, used to query blocks hashes by a range of timestamps (default: %u)"), DEFAULT_TIMESTAMPINDEX));
+    strUsage += HelpMessageOpt("-spentindex", strprintf(_("Maintain a full spent index, used to query the spending txid and input index for an outpoint (default: %u)"), DEFAULT_SPENTINDEX));
 
     strUsage += HelpMessageGroup(_("Connection options:"));
     strUsage += HelpMessageOpt("-addnode=<ip>", _("Add a node to connect to and attempt to keep the connection open"));
--- a/src/main.cpp	Thu Apr 07 13:52:40 2016 -0400
+++ b/src/main.cpp	Tue Apr 05 15:53:38 2016 -0400
@@ -68,6 +68,7 @@
 bool fTxIndex = false;
 bool fAddressIndex = false;
 bool fTimestampIndex = false;
+bool fSpentIndex = false;
 bool fHavePruned = false;
 bool fPruneMode = false;
 bool fIsBareMultisigStd = DEFAULT_PERMIT_BAREMULTISIG;
@@ -1454,6 +1455,17 @@
     return true;
 }
 
+bool GetSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value)
+{
+    if (!fSpentIndex)
+        return error("spent index not enabled");
+
+    if (!pblocktree->ReadSpentIndex(key, value))
+        return error("unable to get spent info");
+
+    return true;
+}
+
 bool GetAddressIndex(uint160 addressHash, int type,
                      std::vector<std::pair<CAddressIndexKey, CAmount> > &addressIndex, int start, int end)
 {
@@ -2058,6 +2070,7 @@
 
     std::vector<std::pair<CAddressIndexKey, CAmount> > addressIndex;
     std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > addressUnspentIndex;
+    std::vector<std::pair<CSpentIndexKey, CSpentIndexValue> > spentIndex;
 
     // undo transactions in reverse order
     for (int i = block.vtx.size() - 1; i >= 0; i--) {
@@ -2125,8 +2138,14 @@
                 if (!ApplyTxInUndo(undo, view, out))
                     fClean = false;
 
+                const CTxIn input = tx.vin[j];
+
+                if (fSpentIndex) {
+                    // undo and delete the spent index
+                    spentIndex.push_back(make_pair(CSpentIndexKey(input.prevout.hash, input.prevout.n), CSpentIndexValue()));
+                }
+
                 if (fAddressIndex) {
-                    const CTxIn input = tx.vin[j];
                     const CTxOut &prevout = view.GetOutputFor(tx.vin[j]);
                     if (prevout.scriptPubKey.IsPayToScriptHash()) {
                         vector<unsigned char> hashBytes(prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22);
@@ -2151,6 +2170,7 @@
                         continue;
                     }
                 }
+
             }
         }
     }
@@ -2435,6 +2455,7 @@
     blockundo.vtxundo.reserve(block.vtx.size() - 1);
     std::vector<std::pair<CAddressIndexKey, CAmount> > addressIndex;
     std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > addressUnspentIndex;
+    std::vector<std::pair<CSpentIndexKey, CSpentIndexValue> > spentIndex;
 
     for (unsigned int i = 0; i < block.vtx.size(); i++)
     {
@@ -2466,31 +2487,43 @@
                                  REJECT_INVALID, "bad-txns-nonfinal");
             }
 
-            if (fAddressIndex)
+            if (fAddressIndex || fSpentIndex)
             {
                 for (size_t j = 0; j < tx.vin.size(); j++) {
+
                     const CTxIn input = tx.vin[j];
-                    const CTxOut &prevout = view.GetOutputFor(tx.vin[j]);
-                    if (prevout.scriptPubKey.IsPayToScriptHash()) {
-                        vector<unsigned char> hashBytes(prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22);
-
-                        // record spending activity
-                        addressIndex.push_back(make_pair(CAddressIndexKey(2, uint160(hashBytes), pindex->nHeight, i, txhash, j, true), prevout.nValue * -1));
-
-                        // remove address from unspent index
-                        addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(2, uint160(hashBytes), input.prevout.hash, input.prevout.n), CAddressUnspentValue()));
-                    } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) {
-                        vector<unsigned char> hashBytes(prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23);
-
-                        // record spending activity
-                        addressIndex.push_back(make_pair(CAddressIndexKey(1, uint160(hashBytes), pindex->nHeight, i, txhash, j, true), prevout.nValue * -1));
-
-                        // remove address from unspent index
-                        addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, uint160(hashBytes), input.prevout.hash, input.prevout.n), CAddressUnspentValue()));
-
-                    } else {
-                        continue;
+
+                    if (fSpentIndex) {
+                        // add the spent index to determine the txid and input that spent an output
+                        spentIndex.push_back(make_pair(CSpentIndexKey(input.prevout.hash, input.prevout.n), CSpentIndexValue(txhash, j, pindex->nHeight)));
                     }
+
+                    if (fAddressIndex) {
+
+                        const CTxOut &prevout = view.GetOutputFor(tx.vin[j]);
+
+                        if (prevout.scriptPubKey.IsPayToScriptHash()) {
+                            vector<unsigned char> hashBytes(prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22);
+
+                            // record spending activity
+                            addressIndex.push_back(make_pair(CAddressIndexKey(2, uint160(hashBytes), pindex->nHeight, i, txhash, j, true), prevout.nValue * -1));
+
+                            // remove address from unspent index
+                            addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(2, uint160(hashBytes), input.prevout.hash, input.prevout.n), CAddressUnspentValue()));
+                        } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) {
+                            vector<unsigned char> hashBytes(prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23);
+
+                            // record spending activity
+                            addressIndex.push_back(make_pair(CAddressIndexKey(1, uint160(hashBytes), pindex->nHeight, i, txhash, j, true), prevout.nValue * -1));
+
+                            // remove address from unspent index
+                            addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, uint160(hashBytes), input.prevout.hash, input.prevout.n), CAddressUnspentValue()));
+
+                        } else {
+                            continue;
+                        }
+                    }
+
                 }
             }
 
@@ -2604,6 +2637,10 @@
         }
     }
 
+    if (fSpentIndex)
+        if (!pblocktree->UpdateSpentIndex(spentIndex))
+            return AbortNode(state, "Failed to write transaction index");
+
     if (fTimestampIndex)
         if (!pblocktree->WriteTimestampIndex(CTimestampIndexKey(pindex->nTime, pindex->GetBlockHash())))
             return AbortNode(state, "Failed to write timestamp index");
@@ -4007,6 +4044,10 @@
     pblocktree->ReadFlag("timestampindex", fTimestampIndex);
     LogPrintf("%s: timestamp index %s\n", __func__, fTimestampIndex ? "enabled" : "disabled");
 
+    // Check whether we have a spent index
+    pblocktree->ReadFlag("spentindex", fSpentIndex);
+    LogPrintf("%s: spent index %s\n", __func__, fSpentIndex ? "enabled" : "disabled");
+
     // Load pointer to end of best chain
     BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock());
     if (it == mapBlockIndex.end())
@@ -4176,6 +4217,9 @@
     fTimestampIndex = GetBoolArg("-timestampindex", DEFAULT_TIMESTAMPINDEX);
     pblocktree->WriteFlag("timestampindex", fTimestampIndex);
 
+    fSpentIndex = GetBoolArg("-spentindex", DEFAULT_SPENTINDEX);
+    pblocktree->WriteFlag("spentindex", fSpentIndex);
+
     LogPrintf("Initializing databases...\n");
 
     // Only add the genesis block if not reindexing (in which case we reuse the one already on disk)
--- a/src/main.h	Thu Apr 07 13:52:40 2016 -0400
+++ b/src/main.h	Tue Apr 05 15:53:38 2016 -0400
@@ -113,6 +113,7 @@
 static const bool DEFAULT_TXINDEX = false;
 static const bool DEFAULT_ADDRESSINDEX = false;
 static const bool DEFAULT_TIMESTAMPINDEX = false;
+static const bool DEFAULT_SPENTINDEX = false;
 static const unsigned int DEFAULT_BANSCORE_THRESHOLD = 100;
 
 static const bool DEFAULT_TESTSAFEMODE = false;
@@ -292,6 +293,69 @@
     std::vector<int> vHeightInFlight;
 };
 
+struct CSpentIndexKey {
+    uint256 txid;
+    unsigned int outputIndex;
+
+    ADD_SERIALIZE_METHODS;
+
+    template <typename Stream, typename Operation>
+    inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
+        READWRITE(txid);
+        READWRITE(outputIndex);
+    }
+
+    CSpentIndexKey(uint256 t, unsigned int i) {
+        txid = t;
+        outputIndex = i;
+    }
+
+    CSpentIndexKey() {
+        SetNull();
+    }
+
+    void SetNull() {
+        txid.SetNull();
+        outputIndex = 0;
+    }
+
+};
+
+struct CSpentIndexValue {
+    uint256 txid;
+    unsigned int inputIndex;
+    int blockHeight;
+
+    ADD_SERIALIZE_METHODS;
+
+    template <typename Stream, typename Operation>
+    inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
+        READWRITE(txid);
+        READWRITE(inputIndex);
+        READWRITE(blockHeight);
+    }
+
+    CSpentIndexValue(uint256 t, unsigned int i, int h) {
+        txid = t;
+        inputIndex = i;
+        blockHeight = h;
+    }
+
+    CSpentIndexValue() {
+        SetNull();
+    }
+
+    void SetNull() {
+        txid.SetNull();
+        inputIndex = 0;
+        blockHeight = 0;
+    }
+
+    bool IsNull() const {
+        return txid.IsNull();
+    }
+};
+
 struct CTimestampIndexIteratorKey {
     unsigned int timestamp;
 
@@ -676,6 +740,7 @@
 };
 
 bool GetTimestampIndex(const unsigned int &high, const unsigned int &low, std::vector<uint256> &hashes);
+bool GetSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value);
 bool GetAddressIndex(uint160 addressHash, int type,
                      std::vector<std::pair<CAddressIndexKey, CAmount> > &addressIndex,
                      int start = 0, int end = 0);
--- a/src/rpcmisc.cpp	Thu Apr 07 13:52:40 2016 -0400
+++ b/src/rpcmisc.cpp	Tue Apr 05 15:53:38 2016 -0400
@@ -701,3 +701,42 @@
     return result;
 
 }
+
+UniValue getspentinfo(const UniValue& params, bool fHelp)
+{
+
+    if (fHelp || params.size() != 1 || !params[0].isObject())
+        throw runtime_error(
+            "getspentinfo\n"
+            "\nReturns the txid and index where an output is spent.\n"
+            "\nResult\n"
+            "{\n"
+            "  \"txid\"  (string) The transaction id\n"
+            "  \"index\"  (number) The spending input index\n"
+            "  ,...\n"
+            "}\n"
+        );
+
+    UniValue txidValue = find_value(params[0].get_obj(), "txid");
+    UniValue indexValue = find_value(params[0].get_obj(), "index");
+
+    if (!txidValue.isStr() || !indexValue.isNum()) {
+        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid txid or index");
+    }
+
+    uint256 txid = ParseHashV(txidValue, "txid");
+    int outputIndex = indexValue.get_int();
+
+    CSpentIndexKey key(txid, outputIndex);
+    CSpentIndexValue value;
+
+    if (!GetSpentIndex(key, value)) {
+        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to get spent info");
+    }
+
+    UniValue obj(UniValue::VOBJ);
+    obj.push_back(Pair("txid", value.txid.GetHex()));
+    obj.push_back(Pair("index", (int)value.inputIndex));
+
+    return obj;
+}
--- a/src/rpcserver.cpp	Thu Apr 07 13:52:40 2016 -0400
+++ b/src/rpcserver.cpp	Tue Apr 05 15:53:38 2016 -0400
@@ -290,6 +290,7 @@
     { "blockchain",         "verifytxoutproof",       &verifytxoutproof,       true  },
     { "blockchain",         "gettxoutsetinfo",        &gettxoutsetinfo,        true  },
     { "blockchain",         "verifychain",            &verifychain,            true  },
+    { "blockchain",         "getspentinfo",           &getspentinfo,           true  },
 
     /* Mining */
     { "mining",             "getblocktemplate",       &getblocktemplate,       true  },
--- a/src/rpcserver.h	Thu Apr 07 13:52:40 2016 -0400
+++ b/src/rpcserver.h	Tue Apr 05 15:53:38 2016 -0400
@@ -271,6 +271,7 @@
 extern UniValue getchaintips(const UniValue& params, bool fHelp);
 extern UniValue invalidateblock(const UniValue& params, bool fHelp);
 extern UniValue reconsiderblock(const UniValue& params, bool fHelp);
+extern UniValue getspentinfo(const UniValue& params, bool fHelp);
 
 bool StartRPC();
 void InterruptRPC();
--- a/src/txdb.cpp	Thu Apr 07 13:52:40 2016 -0400
+++ b/src/txdb.cpp	Tue Apr 05 15:53:38 2016 -0400
@@ -24,6 +24,7 @@
 static const char DB_ADDRESSINDEX = 'a';
 static const char DB_ADDRESSUNSPENTINDEX = 'u';
 static const char DB_TIMESTAMPINDEX = 's';
+static const char DB_SPENTINDEX = 'p';
 static const char DB_BLOCK_INDEX = 'b';
 
 static const char DB_BEST_BLOCK = 'B';
@@ -166,6 +167,22 @@
     return WriteBatch(batch);
 }
 
+bool CBlockTreeDB::ReadSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value) {
+    return Read(make_pair(DB_SPENTINDEX, key), value);
+}
+
+bool CBlockTreeDB::UpdateSpentIndex(const std::vector<std::pair<CSpentIndexKey, CSpentIndexValue> >&vect) {
+    CDBBatch batch(&GetObfuscateKey());
+    for (std::vector<std::pair<CSpentIndexKey,CSpentIndexValue> >::const_iterator it=vect.begin(); it!=vect.end(); it++) {
+        if (it->second.IsNull()) {
+            batch.Erase(make_pair(DB_SPENTINDEX, it->first));
+        } else {
+            batch.Write(make_pair(DB_SPENTINDEX, it->first), it->second);
+        }
+    }
+    return WriteBatch(batch);
+}
+
 bool CBlockTreeDB::UpdateAddressUnspentIndex(const std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue > >&vect) {
     CDBBatch batch(&GetObfuscateKey());
     for (std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> >::const_iterator it=vect.begin(); it!=vect.end(); it++) {
--- a/src/txdb.h	Thu Apr 07 13:52:40 2016 -0400
+++ b/src/txdb.h	Tue Apr 05 15:53:38 2016 -0400
@@ -23,6 +23,8 @@
 struct CAddressIndexIteratorKey;
 struct CTimestampIndexKey;
 struct CTimestampIndexIteratorKey;
+struct CSpentIndexKey;
+struct CSpentIndexValue;
 class uint256;
 
 //! -dbcache default (MiB)
@@ -63,6 +65,8 @@
     bool ReadReindexing(bool &fReindex);
     bool ReadTxIndex(const uint256 &txid, CDiskTxPos &pos);
     bool WriteTxIndex(const std::vector<std::pair<uint256, CDiskTxPos> > &list);
+    bool ReadSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value);
+    bool UpdateSpentIndex(const std::vector<std::pair<CSpentIndexKey, CSpentIndexValue> >&vect);
     bool UpdateAddressUnspentIndex(const std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue > >&vect);
     bool ReadAddressUnspentIndex(uint160 addressHash, int type,
                                  std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > &vect);