diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index 520c60837..aeda0da2c 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -38,6 +38,7 @@ testScripts=( 'txn_doublespend.py --mineblock' 'getchaintips.py' 'rawtransactions.py' + 'getrawtransaction_insight.py' 'rest.py' 'mempool_spendcoinbase.py' 'mempool_reorg.py' diff --git a/qa/rpc-tests/getrawtransaction_insight.py b/qa/rpc-tests/getrawtransaction_insight.py new file mode 100755 index 000000000..f18eed5d4 --- /dev/null +++ b/qa/rpc-tests/getrawtransaction_insight.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python2 +# Copyright (c) 2019 The Zcash developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +# +# Test the new fields added to the output of getrawtransaction +# RPC for the Insight Explorer by the new spentindex +# + +from test_framework.test_framework import BitcoinTestFramework + +from test_framework.util import assert_equal +from test_framework.util import initialize_chain_clean +from test_framework.util import start_nodes, stop_nodes, connect_nodes +from test_framework.util import wait_bitcoinds + +from test_framework.mininode import COIN + + +class GetrawtransactionTest(BitcoinTestFramework): + + def setup_chain(self): + print("Initializing test directory "+self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 4) + + def setup_network(self): + # -insightexplorer causes spentindex to be enabled (fSpentIndex = true) + + self.nodes = start_nodes(3, self.options.tmpdir, + [['-debug', '-txindex', '-experimentalfeatures', '-insightexplorer']]*3) + connect_nodes(self.nodes[0], 1) + connect_nodes(self.nodes[0], 2) + + self.is_network_split = False + self.sync_all() + + def run_test(self): + self.nodes[0].generate(105) + self.sync_all() + + chain_height = self.nodes[1].getblockcount() + assert_equal(chain_height, 105) + + # Test getrawtransaction changes and the getspentinfo RPC + + # send coinbase to address a + a = self.nodes[1].getnewaddress() + txid_a = self.nodes[0].sendtoaddress(a, 2) + self.sync_all() + self.nodes[0].generate(1) + self.sync_all() + + # send from a to b + # (the only utxo on node 1 is from address a) + b = self.nodes[2].getnewaddress() + txid_b = self.nodes[1].sendtoaddress(b, 1) + self.sync_all() + + # a to b transaction is not confirmed, so it has no height + tx_b = self.nodes[2].getrawtransaction(txid_b, 1) + assert('height' not in tx_b) + + self.sync_all() + tx_a = self.nodes[2].getrawtransaction(txid_a, 1) + + # txid_b is not yet confirmed, so these should not be set + assert('spentTxId' not in tx_a['vout'][0]) + assert('spentIndex' not in tx_a['vout'][0]) + assert('spentHeight' not in tx_a['vout'][0]) + + # confirm txid_b (a to b transaction) + self.nodes[0].generate(1) + self.sync_all() + + # Restart all nodes to ensure index files are saved to disk and recovered + stop_nodes(self.nodes) + wait_bitcoinds() + self.setup_network() + + # Check new fields added to getrawtransaction + tx_a = self.nodes[2].getrawtransaction(txid_a, 1) + assert_equal(tx_a['vin'][0]['value'], 10) # coinbase + assert_equal(tx_a['vin'][0]['valueSat'], 10*COIN) + # we want the non-change (payment) output + vout = filter(lambda o: o['value'] == 2, tx_a['vout']) + assert_equal(vout[0]['spentTxId'], txid_b) + assert_equal(vout[0]['spentIndex'], 0) + assert_equal(vout[0]['spentHeight'], 107) + assert_equal(tx_a['height'], 106) + + tx_b = self.nodes[2].getrawtransaction(txid_b, 1) + assert_equal(tx_b['vin'][0]['address'], a) + assert_equal(tx_b['vin'][0]['value'], 2) + assert_equal(tx_b['vin'][0]['valueSat'], 2*COIN) + # since this transaction's outputs haven't yet been + # spent, these fields should not be present + assert('spentTxId' not in tx_b['vout'][0]) + assert('spentIndex' not in tx_b['vout'][0]) + assert('spentHeight' not in tx_b['vout'][0]) + assert_equal(tx_b['height'], 107) + +if __name__ == '__main__': + GetrawtransactionTest().main() diff --git a/src/main.cpp b/src/main.cpp index eb87d2786..43e7f90e7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1607,6 +1607,14 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa return true; } +bool GetSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value) +{ + AssertLockHeld(cs_main); + if (!fSpentIndex) + return false; + return pblocktree->ReadSpentIndex(key, value); +} + /** 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) { @@ -2269,8 +2277,8 @@ static DisconnectResult DisconnectBlock(const CBlock& block, CValidationState& s if (fAddressIndex && updateIndices) { for (unsigned int k = tx.vout.size(); k-- > 0;) { const CTxOut &out = tx.vout[k]; - int const scriptType = out.scriptPubKey.Type(); - if (scriptType > 0) { + CScript::ScriptType scriptType = out.scriptPubKey.GetType(); + if (scriptType != CScript::UNKNOWN) { uint160 const addrHash = out.scriptPubKey.AddressHash(); // undo receiving activity @@ -2326,8 +2334,8 @@ static DisconnectResult DisconnectBlock(const CBlock& block, CValidationState& s const CTxIn input = tx.vin[j]; if (fAddressIndex && updateIndices) { const CTxOut &prevout = view.GetOutputFor(input); - int const scriptType = prevout.scriptPubKey.Type(); - if (scriptType > 0) { + CScript::ScriptType scriptType = prevout.scriptPubKey.GetType(); + if (scriptType != CScript::UNKNOWN) { uint160 const addrHash = prevout.scriptPubKey.AddressHash(); // undo spending activity @@ -2643,9 +2651,9 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin const CTxIn input = tx.vin[j]; const CTxOut &prevout = view.GetOutputFor(tx.vin[j]); - int const scriptType = prevout.scriptPubKey.Type(); + CScript::ScriptType scriptType = prevout.scriptPubKey.GetType(); const uint160 addrHash = prevout.scriptPubKey.AddressHash(); - if (fAddressIndex && scriptType > 0) { + if (fAddressIndex && scriptType != CScript::UNKNOWN) { // record spending activity addressIndex.push_back(make_pair( CAddressIndexKey(scriptType, addrHash, pindex->nHeight, i, hash, j, true), @@ -2695,8 +2703,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin 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) { + CScript::ScriptType scriptType = out.scriptPubKey.GetType(); + if (scriptType != CScript::UNKNOWN) { uint160 const addrHash = out.scriptPubKey.AddressHash(); // record receiving activity diff --git a/src/main.h b/src/main.h index 2b77875c4..cd2e39a09 100644 --- a/src/main.h +++ b/src/main.h @@ -437,6 +437,7 @@ public: ScriptError GetScriptError() const { return error; } }; +bool GetSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value); /** Functions for disk access for blocks */ bool WriteBlockToDisk(const CBlock& block, CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& messageStart); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 49768c247..80ff66ee7 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -147,7 +147,8 @@ UniValue TxShieldedOutputsToJSON(const CTransaction& tx) { void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) { - entry.push_back(Pair("txid", tx.GetHash().GetHex())); + const uint256 txid = tx.GetHash(); + entry.push_back(Pair("txid", txid.GetHex())); entry.push_back(Pair("overwintered", tx.fOverwintered)); entry.push_back(Pair("version", tx.nVersion)); if (tx.fOverwintered) { @@ -169,6 +170,20 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) o.push_back(Pair("asm", ScriptToAsmStr(txin.scriptSig, true))); o.push_back(Pair("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); in.push_back(Pair("scriptSig", o)); + + // Add address and value info if spentindex enabled + CSpentIndexValue spentInfo; + CSpentIndexKey spentKey(txin.prevout.hash, txin.prevout.n); + if (GetSpentIndex(spentKey, spentInfo)) { + in.push_back(Pair("value", ValueFromAmount(spentInfo.satoshis))); + in.push_back(Pair("valueSat", spentInfo.satoshis)); + + boost::optional dest = + DestFromAddressHash(spentInfo.addressType, spentInfo.addressHash); + if (dest) { + in.push_back(Pair("address", EncodeDestination(*dest))); + } + } } in.push_back(Pair("sequence", (int64_t)txin.nSequence)); vin.push_back(in); @@ -184,6 +199,15 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) UniValue o(UniValue::VOBJ); ScriptPubKeyToJSON(txout.scriptPubKey, o, true); out.push_back(Pair("scriptPubKey", o)); + + // Add spent information if spentindex is enabled + CSpentIndexValue spentInfo; + CSpentIndexKey spentKey(txid, i); + if (GetSpentIndex(spentKey, spentInfo)) { + out.push_back(Pair("spentTxId", spentInfo.txid.GetHex())); + out.push_back(Pair("spentIndex", (int)spentInfo.inputIndex)); + out.push_back(Pair("spentHeight", spentInfo.blockHeight)); + } vout.push_back(out); } entry.push_back(Pair("vout", vout)); @@ -208,12 +232,14 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) if (mi != mapBlockIndex.end() && (*mi).second) { CBlockIndex* pindex = (*mi).second; if (chainActive.Contains(pindex)) { + entry.push_back(Pair("height", pindex->nHeight)); entry.push_back(Pair("confirmations", 1 + chainActive.Height() - pindex->nHeight)); entry.push_back(Pair("time", pindex->GetBlockTime())); entry.push_back(Pair("blocktime", pindex->GetBlockTime())); - } - else + } else { + entry.push_back(Pair("height", -1)); entry.push_back(Pair("confirmations", 0)); + } } } } @@ -312,14 +338,14 @@ UniValue getrawtransaction(const UniValue& params, bool fHelp) + HelpExampleRpc("getrawtransaction", "\"mytxid\", 1") ); - LOCK(cs_main); - uint256 hash = ParseHashV(params[0], "parameter 1"); bool fVerbose = false; if (params.size() > 1) fVerbose = (params[1].get_int() != 0); + LOCK(cs_main); + CTransaction tx; uint256 hashBlock; if (!GetTransaction(hash, tx, Params().GetConsensus(), hashBlock, true)) diff --git a/src/script/script.cpp b/src/script/script.cpp index 9d13fd040..bcd1c6b2d 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -242,14 +242,14 @@ bool CScript::IsPushOnly() const } // insightexplorer -int CScript::Type() const +CScript::ScriptType CScript::GetType() const { if (this->IsPayToPublicKeyHash()) - return 1; + return CScript::P2PKH; if (this->IsPayToScriptHash()) - return 2; + return CScript::P2SH; // We don't know this script - return 0; + return CScript::UNKNOWN; } // insightexplorer diff --git a/src/script/script.h b/src/script/script.h index 6f8867611..2a511cd2e 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -567,10 +567,15 @@ public: */ unsigned int GetSigOpCount(const CScript& scriptSig) const; + // insightexplorer, there may be more script types in the future + enum ScriptType : int { + UNKNOWN = 0, + P2PKH = 1, + P2SH = 2, + }; bool IsPayToPublicKeyHash() const; bool IsPayToScriptHash() const; - - int Type() const; + ScriptType GetType() const; uint160 AddressHash() const; /** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */ diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 88cde3698..12df6d855 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -320,3 +320,16 @@ CScript GetScriptForMultisig(int nRequired, const std::vector& keys) bool IsValidDestination(const CTxDestination& dest) { return dest.which() != 0; } + +// insightexplorer +boost::optional DestFromAddressHash(int scriptType, uint160& addressHash) +{ + switch (scriptType) { + case CScript::P2PKH: + return CTxDestination(CKeyID(addressHash)); + case CScript::P2SH: + return CTxDestination(CScriptID(addressHash)); + default: + return boost::none; + } +} diff --git a/src/script/standard.h b/src/script/standard.h index 5dcff1c1a..40b3c329d 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -96,4 +96,7 @@ bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std:: CScript GetScriptForDestination(const CTxDestination& dest); CScript GetScriptForMultisig(int nRequired, const std::vector& keys); +// insightexplorer +boost::optional DestFromAddressHash(int scriptType, uint160& addressHash); + #endif // BITCOIN_SCRIPT_STANDARD_H