add spentindex RPC for bitcore block explorer

This commit is contained in:
Larry Ruane 2019-08-07 14:23:42 -06:00
parent c68511b876
commit 86b23f37ad
12 changed files with 537 additions and 19 deletions

View File

@ -58,6 +58,7 @@ testScripts=(
'nodehandling.py'
'reindex.py'
'addressindex.py'
'spentindex.py'
'decodescript.py'
'blockchain.py'
'disablewallet.py'

View File

@ -202,6 +202,11 @@ class AddressIndexTest(BitcoinTestFramework):
# a single address can be specified as a string (not json object)
assert_equal([mempool[1]], self.nodes[0].getaddressmempool(addr1))
tx = self.nodes[0].getrawtransaction(txid, 1)
assert_equal(tx['vin'][0]['address'], addr1)
assert_equal(tx['vin'][0]['value'], 4)
assert_equal(tx['vin'][0]['valueSat'], 4 * COIN)
txids_a1.append(txid)
expected_deltas.append({
'height': 111,

View File

@ -64,10 +64,11 @@ class GetrawtransactionTest(BitcoinTestFramework):
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])
# txid_b is not yet confirmed, so height is invalid (-1)
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'], -1)
# confirm txid_b (a to b transaction)
self.nodes[0].generate(1)

182
qa/rpc-tests/spentindex.py Executable file
View File

@ -0,0 +1,182 @@
#!/usr/bin/env python
# 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 spentindex generation and fetching
#
import sys; assert sys.version_info < (3,), ur"This script does not run under Python 3. Please use Python 2.7.x."
from test_framework.test_framework import BitcoinTestFramework
from test_framework.authproxy import JSONRPCException
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.util import fail
from test_framework.mininode import COIN
class SpentIndexTest(BitcoinTestFramework):
def setup_chain(self):
print("Initializing test directory "+self.options.tmpdir)
initialize_chain_clean(self.options.tmpdir, 3)
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 addr1
addr1 = self.nodes[1].getnewaddress()
txid1 = self.nodes[0].sendtoaddress(addr1, 2)
self.sync_all()
block_hash1 = self.nodes[0].generate(1)
self.sync_all()
# send from addr1 to addr2
# (the only utxo on node 1 is from address addr1)
addr2 = self.nodes[2].getnewaddress()
txid2 = self.nodes[1].sendtoaddress(addr2, 1)
self.sync_all()
# addr1 to addr2 transaction is not confirmed, so it has no height
tx2 = self.nodes[2].getrawtransaction(txid2, 1)
assert('height' not in tx2)
# confirm addr1 to addr2 transaction
block_hash2 = 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
tx1 = self.nodes[2].getrawtransaction(txid1, 1)
assert_equal(tx1['vin'][0]['value'], 10) # coinbase
assert_equal(tx1['vin'][0]['valueSat'], 10*COIN)
# we want the non-change (payment) output
vout = filter(lambda o: o['value'] == 2, tx1['vout'])
n = vout[0]['n']
assert_equal(vout[0]['spentTxId'], txid2)
assert_equal(vout[0]['spentIndex'], 0)
assert_equal(vout[0]['spentHeight'], 107)
assert_equal(tx1['height'], 106)
tx2 = self.nodes[2].getrawtransaction(txid2, 1)
assert_equal(tx2['vin'][0]['address'], addr1)
assert_equal(tx2['vin'][0]['value'], 2)
assert_equal(tx2['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 tx2['vout'][0])
assert('spentIndex' not in tx2['vout'][0])
assert('spentHeight' not in tx2['vout'][0])
assert_equal(tx2['height'], 107)
# Given a transaction output, getspentinfo() returns a reference
# to the (later, confirmed) transaction that spent that output,
# that is, the transaction that used this output as an input.
spentinfo = self.nodes[2].getspentinfo({'txid': txid1, 'index': n})
assert_equal(spentinfo['height'], 107)
assert_equal(spentinfo['index'], 0)
assert_equal(spentinfo['txid'], txid2)
# specifying an output that hasn't been spent should fail
try:
self.nodes[1].getspentinfo({'txid': txid2, 'index': 0})
fail('getspentinfo should have thrown an exception')
except JSONRPCException, e:
assert_equal(e.error['message'], "Unable to get spent info")
block_hash_next = self.nodes[0].generate(1)
self.sync_all()
# Test the getblockdeltas RPC
blockdeltas = self.nodes[2].getblockdeltas(block_hash1[0])
assert_equal(blockdeltas['confirmations'], 3)
assert_equal(blockdeltas['height'], 106)
assert_equal(blockdeltas['version'], 4)
assert_equal(blockdeltas['hash'], block_hash1[0])
assert_equal(blockdeltas['nextblockhash'], block_hash2[0])
deltas = blockdeltas['deltas']
# block contains two transactions, coinbase, and earlier coinbase to addr1
assert_equal(len(deltas), 2)
coinbase_tx = deltas[0]
assert_equal(coinbase_tx['index'], 0)
assert_equal(len(coinbase_tx['inputs']), 0)
assert_equal(len(coinbase_tx['outputs']), 2)
assert_equal(coinbase_tx['outputs'][0]['index'], 0)
assert_equal(coinbase_tx['outputs'][1]['index'], 1)
assert_equal(coinbase_tx['outputs'][1]['satoshis'], 2.5*COIN)
to_a_tx = deltas[1]
assert_equal(to_a_tx['index'], 1)
assert_equal(to_a_tx['txid'], txid1)
assert_equal(len(to_a_tx['inputs']), 1)
assert_equal(to_a_tx['inputs'][0]['index'], 0)
assert_equal(to_a_tx['inputs'][0]['prevout'], 0)
assert_equal(to_a_tx['inputs'][0]['satoshis'], -10*COIN)
assert_equal(len(to_a_tx['outputs']), 2)
# find the nonchange output, which is the payment to addr1
out = filter(lambda o: o['satoshis'] == 2*COIN, to_a_tx['outputs'])
assert_equal(len(out), 1)
assert_equal(out[0]['address'], addr1)
blockdeltas = self.nodes[2].getblockdeltas(block_hash2[0])
assert_equal(blockdeltas['confirmations'], 2)
assert_equal(blockdeltas['height'], 107)
assert_equal(blockdeltas['version'], 4)
assert_equal(blockdeltas['hash'], block_hash2[0])
assert_equal(blockdeltas['previousblockhash'], block_hash1[0])
assert_equal(blockdeltas['nextblockhash'], block_hash_next[0])
deltas = blockdeltas['deltas']
assert_equal(len(deltas), 2)
coinbase_tx = deltas[0]
assert_equal(coinbase_tx['index'], 0)
assert_equal(len(coinbase_tx['inputs']), 0)
assert_equal(len(coinbase_tx['outputs']), 2)
assert_equal(coinbase_tx['outputs'][0]['index'], 0)
assert_equal(coinbase_tx['outputs'][1]['index'], 1)
assert_equal(coinbase_tx['outputs'][1]['satoshis'], 2.5*COIN)
to_b_tx = deltas[1]
assert_equal(to_b_tx['index'], 1)
assert_equal(to_b_tx['txid'], txid2)
assert_equal(len(to_b_tx['inputs']), 1)
assert_equal(to_b_tx['inputs'][0]['index'], 0)
assert_equal(to_b_tx['inputs'][0]['prevtxid'], txid1)
assert_equal(to_b_tx['inputs'][0]['satoshis'], -2*COIN)
assert_equal(len(to_b_tx['outputs']), 2)
# find the nonchange output, which is the payment to addr2
out = filter(lambda o: o['satoshis'] == 1*COIN, to_b_tx['outputs'])
assert_equal(len(out), 1)
assert_equal(out[0]['address'], addr2)
if __name__ == '__main__':
SpentIndexTest().main()

View File

@ -1604,6 +1604,11 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
if (fAddressIndex) {
pool.addAddressIndex(entry, view);
}
// insightexplorer: Add memory spent index
if (fSpentIndex) {
pool.addSpentIndex(entry, view);
}
}
SyncWithWallets(tx, NULL);
@ -1616,6 +1621,8 @@ bool GetSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value)
AssertLockHeld(cs_main);
if (!fSpentIndex)
return false;
if (mempool.getSpentIndex(key, value))
return true;
return pblocktree->ReadSpentIndex(key, value);
}

View File

@ -8,6 +8,7 @@
#include "chainparams.h"
#include "checkpoints.h"
#include "consensus/validation.h"
#include "key_io.h"
#include "main.h"
#include "primitives/transaction.h"
#include "rpc/server.h"
@ -125,6 +126,94 @@ UniValue blockheaderToJSON(const CBlockIndex* blockindex)
return result;
}
// insightexplorer
UniValue blockToDeltasJSON(const CBlock& block, const CBlockIndex* blockindex)
{
UniValue result(UniValue::VOBJ);
result.push_back(Pair("hash", block.GetHash().GetHex()));
// Only report confirmations if the block is on the main chain
if (!chainActive.Contains(blockindex))
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block is an orphan");
int confirmations = chainActive.Height() - blockindex->nHeight + 1;
result.push_back(Pair("confirmations", confirmations));
result.push_back(Pair("size", (int)::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION)));
result.push_back(Pair("height", blockindex->nHeight));
result.push_back(Pair("version", block.nVersion));
result.push_back(Pair("merkleroot", block.hashMerkleRoot.GetHex()));
UniValue deltas(UniValue::VARR);
for (unsigned int i = 0; i < block.vtx.size(); i++) {
const CTransaction &tx = block.vtx[i];
const uint256 txhash = tx.GetHash();
UniValue entry(UniValue::VOBJ);
entry.push_back(Pair("txid", txhash.GetHex()));
entry.push_back(Pair("index", (int)i));
UniValue inputs(UniValue::VARR);
if (!tx.IsCoinBase()) {
for (size_t j = 0; j < tx.vin.size(); j++) {
const CTxIn input = tx.vin[j];
UniValue delta(UniValue::VOBJ);
CSpentIndexValue spentInfo;
CSpentIndexKey spentKey(input.prevout.hash, input.prevout.n);
if (!GetSpentIndex(spentKey, spentInfo)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Spent information not available");
}
CTxDestination dest = DestFromAddressHash(spentInfo.addressType, spentInfo.addressHash);
if (IsValidDestination(dest)) {
delta.push_back(Pair("address", EncodeDestination(dest)));
}
delta.push_back(Pair("satoshis", -1 * spentInfo.satoshis));
delta.push_back(Pair("index", (int)j));
delta.push_back(Pair("prevtxid", input.prevout.hash.GetHex()));
delta.push_back(Pair("prevout", (int)input.prevout.n));
inputs.push_back(delta);
}
}
entry.push_back(Pair("inputs", inputs));
UniValue outputs(UniValue::VARR);
for (unsigned int k = 0; k < tx.vout.size(); k++) {
const CTxOut &out = tx.vout[k];
UniValue delta(UniValue::VOBJ);
const uint160 addrhash = out.scriptPubKey.AddressHash();
CTxDestination dest;
if (out.scriptPubKey.IsPayToScriptHash()) {
dest = CScriptID(addrhash);
} else if (out.scriptPubKey.IsPayToPublicKeyHash()) {
dest = CKeyID(addrhash);
}
if (IsValidDestination(dest)) {
delta.push_back(Pair("address", EncodeDestination(dest)));
}
delta.push_back(Pair("satoshis", out.nValue));
delta.push_back(Pair("index", (int)k));
outputs.push_back(delta);
}
entry.push_back(Pair("outputs", outputs));
deltas.push_back(entry);
}
result.push_back(Pair("deltas", deltas));
result.push_back(Pair("time", block.GetBlockTime()));
result.push_back(Pair("mediantime", (int64_t)blockindex->GetMedianTimePast()));
result.push_back(Pair("nonce", block.nNonce.GetHex()));
result.push_back(Pair("bits", strprintf("%08x", block.nBits)));
result.push_back(Pair("difficulty", GetDifficulty(blockindex)));
result.push_back(Pair("chainwork", blockindex->nChainWork.GetHex()));
if (blockindex->pprev)
result.push_back(Pair("previousblockhash", blockindex->pprev->GetBlockHash().GetHex()));
CBlockIndex *pnext = chainActive.Next(blockindex);
if (pnext)
result.push_back(Pair("nextblockhash", pnext->GetBlockHash().GetHex()));
return result;
}
UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails = false)
{
UniValue result(UniValue::VOBJ);
@ -313,6 +402,89 @@ UniValue getrawmempool(const UniValue& params, bool fHelp)
return mempoolToJSON(fVerbose);
}
// insightexplorer
UniValue getblockdeltas(const UniValue& params, bool fHelp)
{
std::string enableArg = "insightexplorer";
bool enabled = fExperimentalMode && fInsightExplorer;
std::string disabledMsg = "";
if (!enabled) {
disabledMsg = experimentalDisabledHelpMsg("getblockdeltas", enableArg);
}
if (fHelp || params.size() != 1)
throw runtime_error(
"getblockdeltas \"blockhash\"\n"
"\nReturns the txid and index where an output is spent.\n"
+ disabledMsg +
"\nArguments:\n"
"1. \"hash\" (string, required) The block hash\n"
"\nResult:\n"
"{\n"
" \"hash\": \"hash\", (string) block ID\n"
" \"confirmations\": n, (numeric) number of confirmations\n"
" \"size\": n, (numeric) block size in bytes\n"
" \"height\": n, (numeric) block height\n"
" \"version\": n, (numeric) block version (e.g. 4)\n"
" \"merkleroot\": \"hash\", (string) block Merkle root\n"
" \"deltas\": [\n"
" {\n"
" \"txid\": \"hash\", (string) transaction ID\n"
" \"index\": n, (numeric) tx index in block\n"
" \"inputs\": [\n"
" {\n"
" \"address\": \"taddr\", (string) transparent address\n"
" \"satoshis\": n, (numeric) negative of spend amount\n"
" \"index\": n, (numeric) vin index\n"
" \"prevtxid\": \"hash\", (string) source utxo tx ID\n"
" \"prevout\": n (numeric) source utxo index\n"
" }, ...\n"
" ],\n"
" \"outputs\": [\n"
" {\n"
" \"address\": \"taddr\", (string) transparent address\n"
" \"satoshis\": n, (numeric) amount\n"
" \"index\": n (numeric) vout index\n"
" }, ...\n"
" ]\n"
" }, ...\n"
" ],\n"
" \"time\": n,\n"
" \"mediantime\": n,\n"
" \"nonce\": \"hexstring\",\n"
" \"bits\": \"hexstring\",\n"
" \"difficulty\": ,\n"
" \"chainwork\": \"hexstring\",\n"
" \"previousblockhash\": \"hash\",\n"
" \"nextblockhash\": \"hash\"\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("getblockdeltas", "00227e566682aebd6a7a5b772c96d7a999cadaebeaf1ce96f4191a3aad58b00b")
+ HelpExampleRpc("getblockdeltas", "\"00227e566682aebd6a7a5b772c96d7a999cadaebeaf1ce96f4191a3aad58b00b\"")
);
if (!enabled) {
throw JSONRPCError(RPC_MISC_ERROR, "Error: getblockdeltas is disabled. "
"Run './zcash-cli help getblockdeltas' for instructions on how to enable this feature.");
}
std::string strHash = params[0].get_str();
uint256 hash(uint256S(strHash));
if (mapBlockIndex.count(hash) == 0)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
CBlock block;
CBlockIndex* pblockindex = mapBlockIndex[hash];
if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0)
throw JSONRPCError(RPC_INTERNAL_ERROR, "Block not available (pruned data)");
if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus()))
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
return blockToDeltasJSON(block, pblockindex);
}
UniValue getblockhash(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
@ -1046,6 +1218,9 @@ static const CRPCCommand commands[] =
{ "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true },
{ "blockchain", "verifychain", &verifychain, true },
// insightexplorer
{ "blockchain", "getblockdeltas", &getblockdeltas, false },
/* Not shown in help */
{ "hidden", "invalidateblock", &invalidateblock, true },
{ "hidden", "reconsiderblock", &reconsiderblock, true },

View File

@ -99,6 +99,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "prioritisetransaction", 2 },
{ "setban", 2 },
{ "setban", 3 },
{ "getspentinfo", 0},
{ "getaddresstxids", 0},
{ "getaddressbalance", 0},
{ "getaddressdeltas", 0},

View File

@ -484,6 +484,7 @@ UniValue setmocktime(const UniValue& params, bool fHelp)
return NullUniValue;
}
// insightexplorer
static bool getAddressFromIndex(
int type, const uint160 &hash, std::string &address)
{
@ -520,6 +521,7 @@ static bool getIndexKey(
return false;
}
// insightexplorer
static bool getAddressesFromParams(
const UniValue& params,
std::vector<std::pair<uint160, int>> &addresses)
@ -552,12 +554,13 @@ static bool getAddressesFromParams(
return true;
}
// insightexplorer
UniValue getaddressmempool(const UniValue& params, bool fHelp)
{
std::string enableArg = "insightexplorer";
bool fEnableGetAddressMempool = fExperimentalMode && fInsightExplorer;
bool enabled = fExperimentalMode && fInsightExplorer;
std::string disabledMsg = "";
if (!fEnableGetAddressMempool) {
if (!enabled) {
disabledMsg = experimentalDisabledHelpMsg("getaddressmempool", enableArg);
}
if (fHelp || params.size() != 1)
@ -592,7 +595,7 @@ UniValue getaddressmempool(const UniValue& params, bool fHelp)
+ HelpExampleRpc("getaddressmempool", "{\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"]}")
);
if (!fEnableGetAddressMempool) {
if (!enabled) {
throw JSONRPCError(RPC_MISC_ERROR, "Error: getaddressmempool is disabled. "
"Run './zcash-cli help getaddressmempool' for instructions on how to enable this feature.");
}
@ -632,12 +635,13 @@ UniValue getaddressmempool(const UniValue& params, bool fHelp)
return result;
}
// insightexplorer
UniValue getaddressutxos(const UniValue& params, bool fHelp)
{
std::string enableArg = "insightexplorer";
bool fEnableGetAddressUtxos = fExperimentalMode && fInsightExplorer;
bool enabled = fExperimentalMode && fInsightExplorer;
std::string disabledMsg = "";
if (!fEnableGetAddressUtxos) {
if (!enabled) {
disabledMsg = experimentalDisabledHelpMsg("getaddressutxos", enableArg);
}
if (fHelp || params.size() != 1)
@ -688,7 +692,7 @@ UniValue getaddressutxos(const UniValue& params, bool fHelp)
+ HelpExampleRpc("getaddressutxos", "{\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"], \"chainInfo\": true}")
);
if (!fEnableGetAddressUtxos) {
if (!enabled) {
throw JSONRPCError(RPC_MISC_ERROR, "Error: getaddressutxos is disabled. "
"Run './zcash-cli help getaddressutxos' for instructions on how to enable this feature.");
}
@ -790,12 +794,13 @@ static void getAddressesInHeightRange(
}
}
// insightexplorer
UniValue getaddressdeltas(const UniValue& params, bool fHelp)
{
std::string enableArg = "insightexplorer";
bool fEnableGetAddressDeltas = fExperimentalMode && fInsightExplorer;
bool enabled = fExperimentalMode && fInsightExplorer;
std::string disabledMsg = "";
if (!fEnableGetAddressDeltas) {
if (!enabled) {
disabledMsg = experimentalDisabledHelpMsg("getaddressdeltas", enableArg);
}
if (fHelp || params.size() != 1)
@ -856,7 +861,7 @@ UniValue getaddressdeltas(const UniValue& params, bool fHelp)
+ HelpExampleRpc("getaddressdeltas", "{\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"], \"start\": 1000, \"end\": 2000, \"chainInfo\": true}")
);
if (!fEnableGetAddressDeltas) {
if (!enabled) {
throw JSONRPCError(RPC_MISC_ERROR, "Error: getaddressdeltas is disabled. "
"Run './zcash-cli help getaddressdeltas' for instructions on how to enable this feature.");
}
@ -920,12 +925,13 @@ UniValue getaddressdeltas(const UniValue& params, bool fHelp)
return result;
}
// insightexplorer
UniValue getaddressbalance(const UniValue& params, bool fHelp)
{
std::string enableArg = "insightexplorer";
bool fEnableGetAddressBalance = fExperimentalMode && fInsightExplorer;
bool enabled = fExperimentalMode && fInsightExplorer;
std::string disabledMsg = "";
if (!fEnableGetAddressBalance) {
if (!enabled) {
disabledMsg = experimentalDisabledHelpMsg("getaddressbalance", enableArg);
}
if (fHelp || params.size() != 1)
@ -953,7 +959,7 @@ UniValue getaddressbalance(const UniValue& params, bool fHelp)
+ HelpExampleRpc("getaddressbalance", "{\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"]}")
);
if (!fEnableGetAddressBalance) {
if (!enabled) {
throw JSONRPCError(RPC_MISC_ERROR, "Error: getaddressbalance is disabled. "
"Run './zcash-cli help getaddressbalance' for instructions on how to enable this feature.");
}
@ -978,12 +984,13 @@ UniValue getaddressbalance(const UniValue& params, bool fHelp)
return result;
}
// insightexplorer
UniValue getaddresstxids(const UniValue& params, bool fHelp)
{
std::string enableArg = "insightexplorer";
bool fEnableGetAddressTxids = fExperimentalMode && fInsightExplorer;
bool enabled = fExperimentalMode && fInsightExplorer;
std::string disabledMsg = "";
if (!fEnableGetAddressTxids) {
if (!enabled) {
disabledMsg = experimentalDisabledHelpMsg("getaddresstxids", enableArg);
}
if (fHelp || params.size() != 1)
@ -1014,7 +1021,7 @@ UniValue getaddresstxids(const UniValue& params, bool fHelp)
+ HelpExampleRpc("getaddresstxids", "{\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"], \"start\": 1000, \"end\": 2000}")
);
if (!fEnableGetAddressTxids) {
if (!enabled) {
throw JSONRPCError(RPC_MISC_ERROR, "Error: getaddresstxids is disabled. "
"Run './zcash-cli help getaddresstxids' for instructions on how to enable this feature.");
}
@ -1044,6 +1051,64 @@ UniValue getaddresstxids(const UniValue& params, bool fHelp)
return result;
}
// insightexplorer
UniValue getspentinfo(const UniValue& params, bool fHelp)
{
std::string enableArg = "insightexplorer";
bool enabled = fExperimentalMode && fInsightExplorer;
std::string disabledMsg = "";
if (!enabled) {
disabledMsg = experimentalDisabledHelpMsg("getspentinfo", enableArg);
}
if (fHelp || params.size() != 1 || !params[0].isObject())
throw runtime_error(
"getspentinfo {\"txid\": \"txidhex\", \"index\": n}\n"
"\nReturns the txid and index where an output is spent.\n"
+ disabledMsg +
"\nArguments:\n"
"{\n"
" \"txid\" (string) The hex string of the txid\n"
" \"index\" (number) The vout (output) index\n"
"}\n"
"\nResult:\n"
"{\n"
" \"txid\" (string) The transaction id\n"
" \"index\" (number) The spending (vin, input) index\n"
" ,...\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("getspentinfo", "'{\"txid\": \"0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9\", \"index\": 0}'")
+ HelpExampleRpc("getspentinfo", "{\"txid\": \"0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9\", \"index\": 0}")
);
if (!enabled) {
throw JSONRPCError(RPC_MISC_ERROR, "Error: getspentinfo is disabled. "
"Run './zcash-cli help getspentinfo' for instructions on how to enable this feature.");
}
UniValue txidValue = find_value(params[0].get_obj(), "txid");
UniValue indexValue = find_value(params[0].get_obj(), "index");
if (!txidValue.isStr())
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid txid, must be a string");
if (!indexValue.isNum())
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid index, must be an integer");
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));
obj.push_back(Pair("height", value.blockHeight));
return obj;
}
static const CRPCCommand commands[] =
{ // category name actor (function) okSafeMode
@ -1054,12 +1119,15 @@ static const CRPCCommand commands[] =
{ "util", "createmultisig", &createmultisig, true },
{ "util", "verifymessage", &verifymessage, true },
// START insightexplorer
/* Address index */
{ "addressindex", "getaddresstxids", &getaddresstxids, false }, /* insight explorer */
{ "addressindex", "getaddressbalance", &getaddressbalance, false }, /* insight explorer */
{ "addressindex", "getaddressdeltas", &getaddressdeltas, false }, /* insight explorer */
{ "addressindex", "getaddressutxos", &getaddressutxos, false }, /* insight explorer */
{ "addressindex", "getaddressmempool", &getaddressmempool, true }, /* insight explorer */
{ "blockchain", "getspentinfo", &getspentinfo, false }, /* insight explorer */
// END insightexplorer
/* Not shown in help */
{ "hidden", "setmocktime", &setmocktime, true },

View File

@ -197,6 +197,7 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry)
UniValue out(UniValue::VOBJ);
out.push_back(Pair("value", ValueFromAmount(txout.nValue)));
out.push_back(Pair("valueZat", txout.nValue));
out.push_back(Pair("valueSat", txout.nValue));
out.push_back(Pair("n", (int64_t)i));
UniValue o(UniValue::VOBJ);
ScriptPubKeyToJSON(txout.scriptPubKey, o, true);

View File

@ -378,6 +378,12 @@ BOOST_AUTO_TEST_CASE(rpc_insightexplorer)
CheckRPCThrows("getaddresstxids \"a\"",
"Error: getaddresstxids is disabled. "
"Run './zcash-cli help getaddresstxids' for instructions on how to enable this feature.");
CheckRPCThrows("getspentinfo {\"a\":1}",
"Error: getspentinfo is disabled. "
"Run './zcash-cli help getspentinfo' for instructions on how to enable this feature.");
CheckRPCThrows("getblockdeltas \"a\"",
"Error: getblockdeltas is disabled. "
"Run './zcash-cli help getblockdeltas' for instructions on how to enable this feature.");
fExperimentalMode = true;
fInsightExplorer = true;
@ -416,6 +422,17 @@ BOOST_AUTO_TEST_CASE(rpc_insightexplorer)
CheckRPCThrows("getaddresstxids {\"addresses\":[],\"start\":2,\"end\":3,\"chainInfo\":true}",
"Start or end is outside chain range");
// transaction does not exist:
CheckRPCThrows("getspentinfo {\"txid\":\"b4cc287e58f87cdae59417329f710f3ecd75a4ee1d2872b7248f50977c8493f3\",\"index\":0}",
"Unable to get spent info");
CheckRPCThrows("getspentinfo {\"txid\":\"b4cc287e58f87cdae59417329f710f3ecd75a4ee1d2872b7248f50977c8493f3\"}",
"Invalid index, must be an integer");
CheckRPCThrows("getspentinfo {\"txid\":\"hello\",\"index\":0}",
"txid must be hexadecimal string (not 'hello')");
CheckRPCThrows("getblockdeltas \"00040fe8ec8471911baa1db1266ea15dd06b4a8a5c453883c000b031973dce08\"",
"Block not found");
// revert
fExperimentalMode = false;
fInsightExplorer = false;

View File

@ -153,6 +153,7 @@ void CTxMemPool::addAddressIndex(const CTxMemPoolEntry &entry, const CCoinsViewC
mapAddressInserted.insert(make_pair(txhash, inserted));
}
// START insightexplorer
void CTxMemPool::getAddressIndex(
const std::vector<std::pair<uint160, int>>& addresses,
std::vector<std::pair<CMempoolAddressDeltaKey, CMempoolAddressDelta>>& results)
@ -181,6 +182,52 @@ void CTxMemPool::removeAddressIndex(const uint256& txhash)
}
}
void CTxMemPool::addSpentIndex(const CTxMemPoolEntry &entry, const CCoinsViewCache &view)
{
LOCK(cs);
const CTransaction& tx = entry.GetTx();
uint256 txhash = tx.GetHash();
std::vector<CSpentIndexKey> inserted;
for (unsigned int j = 0; j < tx.vin.size(); j++) {
const CTxIn input = tx.vin[j];
const CTxOut &prevout = view.GetOutputFor(input);
CSpentIndexKey key = CSpentIndexKey(input.prevout.hash, input.prevout.n);
CSpentIndexValue value = CSpentIndexValue(txhash, j, -1, prevout.nValue,
prevout.scriptPubKey.GetType(),
prevout.scriptPubKey.AddressHash());
mapSpent.insert(make_pair(key, value));
inserted.push_back(key);
}
mapSpentInserted.insert(make_pair(txhash, inserted));
}
bool CTxMemPool::getSpentIndex(const CSpentIndexKey &key, CSpentIndexValue &value)
{
LOCK(cs);
std::map<CSpentIndexKey, CSpentIndexValue, CSpentIndexKeyCompare>::iterator it = mapSpent.find(key);
if (it != mapSpent.end()) {
value = it->second;
return true;
}
return false;
}
void CTxMemPool::removeSpentIndex(const uint256 txhash)
{
LOCK(cs);
auto it = mapSpentInserted.find(txhash);
if (it != mapSpentInserted.end()) {
std::vector<CSpentIndexKey> keys = (*it).second;
for (std::vector<CSpentIndexKey>::iterator mit = keys.begin(); mit != keys.end(); mit++) {
mapSpent.erase(*mit);
}
mapSpentInserted.erase(it);
}
}
// END insightexplorer
void CTxMemPool::remove(const CTransaction &origTx, std::list<CTransaction>& removed, bool fRecursive)
{
// Remove transaction from memory pool
@ -231,9 +278,12 @@ void CTxMemPool::remove(const CTransaction &origTx, std::list<CTransaction>& rem
mapTx.erase(hash);
nTransactionsUpdated++;
minerPolicyEstimator->removeTx(hash);
// insightexplorer
if (fAddressIndex)
removeAddressIndex(hash);
if (fSpentIndex)
removeSpentIndex(hash);
}
}
}

View File

@ -13,6 +13,7 @@
#include "primitives/transaction.h"
#include "sync.h"
#include "addressindex.h"
#include "spentindex.h"
#undef foreach
#include "boost/multi_index_container.hpp"
@ -155,8 +156,11 @@ public:
indexed_transaction_set mapTx;
private:
// insightexplorer
std::map<CMempoolAddressDeltaKey, CMempoolAddressDelta, CMempoolAddressDeltaKeyCompare> mapAddress;
std::map<uint256, std::vector<CMempoolAddressDeltaKey> > mapAddressInserted;
std::map<CSpentIndexKey, CSpentIndexValue, CSpentIndexKeyCompare> mapSpent;
std::map<uint256, std::vector<CSpentIndexKey>> mapSpentInserted;
public:
std::map<COutPoint, CInPoint> mapNextTx;
@ -176,11 +180,17 @@ public:
bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, bool fCurrentEstimate = true);
// START insightexplorer
void addAddressIndex(const CTxMemPoolEntry &entry, const CCoinsViewCache &view);
void getAddressIndex(const std::vector<std::pair<uint160, int>>& addresses,
std::vector<std::pair<CMempoolAddressDeltaKey, CMempoolAddressDelta>>& results);
void removeAddressIndex(const uint256& txhash);
void addSpentIndex(const CTxMemPoolEntry &entry, const CCoinsViewCache &view);
bool getSpentIndex(const CSpentIndexKey &key, CSpentIndexValue &value);
void removeSpentIndex(const uint256 txhash);
// END insightexplorer
void remove(const CTransaction &tx, std::list<CTransaction>& removed, bool fRecursive = false);
void removeWithAnchor(const uint256 &invalidRoot, ShieldedType type);
void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags);