Auto merge of #3953 - LarryRuane:3708-getspentrpcs, r=daira
add getspent RPCs for bitcore block explorer Part of #3708.
This commit is contained in:
commit
12046373bc
|
@ -58,6 +58,7 @@ testScripts=(
|
|||
'nodehandling.py'
|
||||
'reindex.py'
|
||||
'addressindex.py'
|
||||
'spentindex.py'
|
||||
'decodescript.py'
|
||||
'blockchain.py'
|
||||
'disablewallet.py'
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -99,6 +99,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "prioritisetransaction", 2 },
|
||||
{ "setban", 2 },
|
||||
{ "setban", 3 },
|
||||
{ "getspentinfo", 0},
|
||||
{ "getaddresstxids", 0},
|
||||
{ "getaddressbalance", 0},
|
||||
{ "getaddressdeltas", 0},
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue