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:
commit
bb58c8ec2e
|
@ -38,6 +38,7 @@ testScripts=(
|
|||
'txn_doublespend.py --mineblock'
|
||||
'getchaintips.py'
|
||||
'rawtransactions.py'
|
||||
'getrawtransaction_insight.py'
|
||||
'rest.py'
|
||||
'mempool_spendcoinbase.py'
|
||||
'mempool_reorg.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()
|
24
src/main.cpp
24
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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<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));
|
||||
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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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). */
|
||||
|
|
|
@ -320,3 +320,16 @@ CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys)
|
|||
bool IsValidDestination(const CTxDestination& dest) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,4 +96,7 @@ bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::
|
|||
CScript GetScriptForDestination(const CTxDestination& dest);
|
||||
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys);
|
||||
|
||||
// insightexplorer
|
||||
boost::optional<CTxDestination> DestFromAddressHash(int scriptType, uint160& addressHash);
|
||||
|
||||
#endif // BITCOIN_SCRIPT_STANDARD_H
|
||||
|
|
Loading…
Reference in New Issue