Auto merge of #3863 - LarryRuane:3708-getrawtransaction, r=daira

add spentindex to getrawtransaction RPC results for bitcore block explorer

#3708 There are a few new `getrawtransaction` JSON result fields that the Insight block explorer depends on.
This commit is contained in:
Homu 2019-06-11 13:15:00 -07:00
commit bb58c8ec2e
9 changed files with 180 additions and 19 deletions

View File

@ -38,6 +38,7 @@ testScripts=(
'txn_doublespend.py --mineblock' 'txn_doublespend.py --mineblock'
'getchaintips.py' 'getchaintips.py'
'rawtransactions.py' 'rawtransactions.py'
'getrawtransaction_insight.py'
'rest.py' 'rest.py'
'mempool_spendcoinbase.py' 'mempool_spendcoinbase.py'
'mempool_reorg.py' 'mempool_reorg.py'

View File

@ -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()

View File

@ -1607,6 +1607,14 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
return true; 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 */ /** 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) 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) { if (fAddressIndex && updateIndices) {
for (unsigned int k = tx.vout.size(); k-- > 0;) { for (unsigned int k = tx.vout.size(); k-- > 0;) {
const CTxOut &out = tx.vout[k]; const CTxOut &out = tx.vout[k];
int const scriptType = out.scriptPubKey.Type(); CScript::ScriptType scriptType = out.scriptPubKey.GetType();
if (scriptType > 0) { if (scriptType != CScript::UNKNOWN) {
uint160 const addrHash = out.scriptPubKey.AddressHash(); uint160 const addrHash = out.scriptPubKey.AddressHash();
// undo receiving activity // undo receiving activity
@ -2326,8 +2334,8 @@ static DisconnectResult DisconnectBlock(const CBlock& block, CValidationState& s
const CTxIn input = tx.vin[j]; const CTxIn input = tx.vin[j];
if (fAddressIndex && updateIndices) { if (fAddressIndex && updateIndices) {
const CTxOut &prevout = view.GetOutputFor(input); const CTxOut &prevout = view.GetOutputFor(input);
int const scriptType = prevout.scriptPubKey.Type(); CScript::ScriptType scriptType = prevout.scriptPubKey.GetType();
if (scriptType > 0) { if (scriptType != CScript::UNKNOWN) {
uint160 const addrHash = prevout.scriptPubKey.AddressHash(); uint160 const addrHash = prevout.scriptPubKey.AddressHash();
// undo spending activity // undo spending activity
@ -2643,9 +2651,9 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
const CTxIn input = tx.vin[j]; const CTxIn input = tx.vin[j];
const CTxOut &prevout = view.GetOutputFor(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(); const uint160 addrHash = prevout.scriptPubKey.AddressHash();
if (fAddressIndex && scriptType > 0) { if (fAddressIndex && scriptType != CScript::UNKNOWN) {
// record spending activity // record spending activity
addressIndex.push_back(make_pair( addressIndex.push_back(make_pair(
CAddressIndexKey(scriptType, addrHash, pindex->nHeight, i, hash, j, true), CAddressIndexKey(scriptType, addrHash, pindex->nHeight, i, hash, j, true),
@ -2695,8 +2703,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
if (fAddressIndex) { if (fAddressIndex) {
for (unsigned int k = 0; k < tx.vout.size(); k++) { for (unsigned int k = 0; k < tx.vout.size(); k++) {
const CTxOut &out = tx.vout[k]; const CTxOut &out = tx.vout[k];
int const scriptType = out.scriptPubKey.Type(); CScript::ScriptType scriptType = out.scriptPubKey.GetType();
if (scriptType > 0) { if (scriptType != CScript::UNKNOWN) {
uint160 const addrHash = out.scriptPubKey.AddressHash(); uint160 const addrHash = out.scriptPubKey.AddressHash();
// record receiving activity // record receiving activity

View File

@ -437,6 +437,7 @@ public:
ScriptError GetScriptError() const { return error; } ScriptError GetScriptError() const { return error; }
}; };
bool GetSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value);
/** Functions for disk access for blocks */ /** Functions for disk access for blocks */
bool WriteBlockToDisk(const CBlock& block, CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& messageStart); bool WriteBlockToDisk(const CBlock& block, CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& messageStart);

View File

@ -147,7 +147,8 @@ UniValue TxShieldedOutputsToJSON(const CTransaction& tx) {
void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) 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("overwintered", tx.fOverwintered));
entry.push_back(Pair("version", tx.nVersion)); entry.push_back(Pair("version", tx.nVersion));
if (tx.fOverwintered) { 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("asm", ScriptToAsmStr(txin.scriptSig, true)));
o.push_back(Pair("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); o.push_back(Pair("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end())));
in.push_back(Pair("scriptSig", o)); 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<CTxDestination> dest =
DestFromAddressHash(spentInfo.addressType, spentInfo.addressHash);
if (dest) {
in.push_back(Pair("address", EncodeDestination(*dest)));
}
}
} }
in.push_back(Pair("sequence", (int64_t)txin.nSequence)); in.push_back(Pair("sequence", (int64_t)txin.nSequence));
vin.push_back(in); vin.push_back(in);
@ -184,6 +199,15 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry)
UniValue o(UniValue::VOBJ); UniValue o(UniValue::VOBJ);
ScriptPubKeyToJSON(txout.scriptPubKey, o, true); ScriptPubKeyToJSON(txout.scriptPubKey, o, true);
out.push_back(Pair("scriptPubKey", o)); 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); vout.push_back(out);
} }
entry.push_back(Pair("vout", vout)); 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) { if (mi != mapBlockIndex.end() && (*mi).second) {
CBlockIndex* pindex = (*mi).second; CBlockIndex* pindex = (*mi).second;
if (chainActive.Contains(pindex)) { 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("confirmations", 1 + chainActive.Height() - pindex->nHeight));
entry.push_back(Pair("time", pindex->GetBlockTime())); entry.push_back(Pair("time", pindex->GetBlockTime()));
entry.push_back(Pair("blocktime", pindex->GetBlockTime())); entry.push_back(Pair("blocktime", pindex->GetBlockTime()));
} } else {
else entry.push_back(Pair("height", -1));
entry.push_back(Pair("confirmations", 0)); entry.push_back(Pair("confirmations", 0));
}
} }
} }
} }
@ -312,14 +338,14 @@ UniValue getrawtransaction(const UniValue& params, bool fHelp)
+ HelpExampleRpc("getrawtransaction", "\"mytxid\", 1") + HelpExampleRpc("getrawtransaction", "\"mytxid\", 1")
); );
LOCK(cs_main);
uint256 hash = ParseHashV(params[0], "parameter 1"); uint256 hash = ParseHashV(params[0], "parameter 1");
bool fVerbose = false; bool fVerbose = false;
if (params.size() > 1) if (params.size() > 1)
fVerbose = (params[1].get_int() != 0); fVerbose = (params[1].get_int() != 0);
LOCK(cs_main);
CTransaction tx; CTransaction tx;
uint256 hashBlock; uint256 hashBlock;
if (!GetTransaction(hash, tx, Params().GetConsensus(), hashBlock, true)) if (!GetTransaction(hash, tx, Params().GetConsensus(), hashBlock, true))

View File

@ -242,14 +242,14 @@ bool CScript::IsPushOnly() const
} }
// insightexplorer // insightexplorer
int CScript::Type() const CScript::ScriptType CScript::GetType() const
{ {
if (this->IsPayToPublicKeyHash()) if (this->IsPayToPublicKeyHash())
return 1; return CScript::P2PKH;
if (this->IsPayToScriptHash()) if (this->IsPayToScriptHash())
return 2; return CScript::P2SH;
// We don't know this script // We don't know this script
return 0; return CScript::UNKNOWN;
} }
// insightexplorer // insightexplorer

View File

@ -567,10 +567,15 @@ public:
*/ */
unsigned int GetSigOpCount(const CScript& scriptSig) const; 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 IsPayToPublicKeyHash() const;
bool IsPayToScriptHash() const; bool IsPayToScriptHash() const;
ScriptType GetType() const;
int Type() const;
uint160 AddressHash() const; uint160 AddressHash() const;
/** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */ /** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */

View File

@ -320,3 +320,16 @@ CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys)
bool IsValidDestination(const CTxDestination& dest) { bool IsValidDestination(const CTxDestination& dest) {
return dest.which() != 0; return dest.which() != 0;
} }
// insightexplorer
boost::optional<CTxDestination> 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;
}
}

View File

@ -96,4 +96,7 @@ bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::
CScript GetScriptForDestination(const CTxDestination& dest); CScript GetScriptForDestination(const CTxDestination& dest);
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys); CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys);
// insightexplorer
boost::optional<CTxDestination> DestFromAddressHash(int scriptType, uint160& addressHash);
#endif // BITCOIN_SCRIPT_STANDARD_H #endif // BITCOIN_SCRIPT_STANDARD_H