Auto merge of #3926 - LarryRuane:3708-getaddressrpcs, r=str4d
add addressindex RPC for bitcore block explorer Addresses #3708
This commit is contained in:
commit
c68511b876
|
@ -57,6 +57,7 @@ testScripts=(
|
|||
'key_import_export.py'
|
||||
'nodehandling.py'
|
||||
'reindex.py'
|
||||
'addressindex.py'
|
||||
'decodescript.py'
|
||||
'blockchain.py'
|
||||
'disablewallet.py'
|
||||
|
|
|
@ -0,0 +1,357 @@
|
|||
#!/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 addressindex generation and fetching for insightexplorer
|
||||
#
|
||||
# RPCs tested here:
|
||||
#
|
||||
# getaddresstxids
|
||||
# getaddressbalance
|
||||
# getaddressdeltas
|
||||
# getaddressutxos
|
||||
# getaddressmempool
|
||||
#
|
||||
|
||||
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.util import (
|
||||
assert_equal,
|
||||
initialize_chain_clean,
|
||||
start_nodes,
|
||||
stop_nodes,
|
||||
connect_nodes,
|
||||
)
|
||||
|
||||
from test_framework.util import wait_bitcoinds
|
||||
|
||||
from test_framework.script import (
|
||||
CScript,
|
||||
OP_HASH160,
|
||||
OP_EQUAL,
|
||||
OP_DUP,
|
||||
OP_DROP,
|
||||
)
|
||||
|
||||
from test_framework.mininode import COIN, CTransaction
|
||||
from test_framework.mininode import CTxIn, CTxOut, COutPoint
|
||||
|
||||
from binascii import hexlify
|
||||
|
||||
|
||||
class AddressIndexTest(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 addressindex to be enabled (fAddressIndex = true)
|
||||
args = ('-debug', '-txindex', '-experimentalfeatures', '-insightexplorer')
|
||||
self.nodes = start_nodes(3, self.options.tmpdir, [args] * 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):
|
||||
|
||||
# helper functions
|
||||
def getaddresstxids(node_index, addresses, start, end):
|
||||
return self.nodes[node_index].getaddresstxids({
|
||||
'addresses': addresses,
|
||||
'start': start,
|
||||
'end': end
|
||||
})
|
||||
|
||||
def getaddressdeltas(node_index, addresses, start, end, chainInfo=None):
|
||||
params = {
|
||||
'addresses': addresses,
|
||||
'start': start,
|
||||
'end': end,
|
||||
}
|
||||
if chainInfo is not None:
|
||||
params.update({'chainInfo': chainInfo})
|
||||
return self.nodes[node_index].getaddressdeltas(params)
|
||||
|
||||
# default received value is the balance value
|
||||
def check_balance(node_index, address, expected_balance, expected_received=None):
|
||||
if isinstance(address, list):
|
||||
bal = self.nodes[node_index].getaddressbalance({'addresses': address})
|
||||
else:
|
||||
bal = self.nodes[node_index].getaddressbalance(address)
|
||||
assert_equal(bal['balance'], expected_balance)
|
||||
if expected_received is None:
|
||||
expected_received = expected_balance
|
||||
assert_equal(bal['received'], expected_received)
|
||||
|
||||
# begin test
|
||||
|
||||
self.nodes[0].generate(105)
|
||||
self.sync_all()
|
||||
assert_equal(self.nodes[0].getbalance(), 5 * 10)
|
||||
assert_equal(self.nodes[1].getblockcount(), 105)
|
||||
assert_equal(self.nodes[1].getbalance(), 0)
|
||||
|
||||
# only the oldest 5; subsequent are not yet mature
|
||||
unspent_txids = [ u['txid'] for u in self.nodes[0].listunspent() ]
|
||||
|
||||
# Currently our only unspents are coinbase transactions, choose any one
|
||||
tx = self.nodes[0].getrawtransaction(unspent_txids[0], 1)
|
||||
|
||||
# It just so happens that the first output is the mining reward,
|
||||
# which has type pay-to-public-key-hash, and the second output
|
||||
# is the founders' reward, which has type pay-to-script-hash.
|
||||
addr_p2pkh = tx['vout'][0]['scriptPubKey']['addresses'][0]
|
||||
addr_p2sh = tx['vout'][1]['scriptPubKey']['addresses'][0]
|
||||
|
||||
# Check that balances from mining are correct (105 blocks mined); in
|
||||
# regtest, all mining rewards from a single call to generate() are sent
|
||||
# to the same pair of addresses.
|
||||
check_balance(1, addr_p2pkh, 105 * 10 * COIN)
|
||||
check_balance(1, addr_p2sh, 105 * 2.5 * COIN)
|
||||
|
||||
# Multiple address arguments, results are the sum
|
||||
check_balance(1, [addr_p2sh, addr_p2pkh], 105 * 12.5 * COIN)
|
||||
|
||||
assert_equal(len(self.nodes[1].getaddresstxids(addr_p2pkh)), 105)
|
||||
assert_equal(len(self.nodes[1].getaddresstxids(addr_p2sh)), 105)
|
||||
|
||||
# only the oldest 5 transactions are in the unspent list,
|
||||
# dup addresses are ignored
|
||||
height_txids = getaddresstxids(1, [addr_p2pkh, addr_p2pkh], 1, 5)
|
||||
assert_equal(sorted(height_txids), sorted(unspent_txids))
|
||||
|
||||
height_txids = getaddresstxids(1, [addr_p2sh], 1, 5)
|
||||
assert_equal(sorted(height_txids), sorted(unspent_txids))
|
||||
|
||||
# each txid should appear only once
|
||||
height_txids = getaddresstxids(1, [addr_p2pkh, addr_p2sh], 1, 5)
|
||||
assert_equal(sorted(height_txids), sorted(unspent_txids))
|
||||
|
||||
# do some transfers, make sure balances are good
|
||||
txids_a1 = []
|
||||
addr1 = self.nodes[1].getnewaddress()
|
||||
expected = 0
|
||||
expected_deltas = [] # for checking getaddressdeltas (below)
|
||||
for i in range(5):
|
||||
# first transaction happens at height 105, mined in block 106
|
||||
txid = self.nodes[0].sendtoaddress(addr1, i + 1)
|
||||
txids_a1.append(txid)
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
expected += i + 1
|
||||
expected_deltas.append({
|
||||
'height': 106 + i,
|
||||
'satoshis': (i + 1) * COIN,
|
||||
'txid': txid,
|
||||
})
|
||||
check_balance(1, addr1, expected * COIN)
|
||||
assert_equal(sorted(self.nodes[0].getaddresstxids(addr1)), sorted(txids_a1))
|
||||
assert_equal(sorted(self.nodes[1].getaddresstxids(addr1)), sorted(txids_a1))
|
||||
|
||||
# Restart all nodes to ensure indices are saved to disk and recovered
|
||||
stop_nodes(self.nodes)
|
||||
wait_bitcoinds()
|
||||
self.setup_network()
|
||||
|
||||
bal = self.nodes[1].getaddressbalance(addr1)
|
||||
assert_equal(bal['balance'], expected * COIN)
|
||||
assert_equal(bal['received'], expected * COIN)
|
||||
assert_equal(sorted(self.nodes[0].getaddresstxids(addr1)), sorted(txids_a1))
|
||||
assert_equal(sorted(self.nodes[1].getaddresstxids(addr1)), sorted(txids_a1))
|
||||
|
||||
# Send 3 from addr1, but -- subtlety alert! -- addr1 at this
|
||||
# time has 4 UTXOs, with values 1, 2, 3, 4. Sending value 3 requires
|
||||
# using up the value 4 UTXO, because of the tx fee
|
||||
# (the 3 UTXO isn't quite large enough).
|
||||
#
|
||||
# The txid from sending *from* addr1 is also added to the list of
|
||||
# txids associated with that address (test will verify below).
|
||||
|
||||
addr2 = self.nodes[2].getnewaddress()
|
||||
txid = self.nodes[1].sendtoaddress(addr2, 3)
|
||||
self.sync_all()
|
||||
|
||||
# the one tx in the mempool refers to addresses addr1 and addr2,
|
||||
# check that duplicate addresses are processed correctly
|
||||
mempool = self.nodes[0].getaddressmempool({'addresses': [addr2, addr1, addr2]})
|
||||
assert_equal(len(mempool), 3)
|
||||
|
||||
# addr2 (first arg)
|
||||
assert_equal(mempool[0]['address'], addr2)
|
||||
assert_equal(mempool[0]['satoshis'], 3 * COIN)
|
||||
assert_equal(mempool[0]['txid'], txid)
|
||||
|
||||
# addr1 (second arg)
|
||||
assert_equal(mempool[1]['address'], addr1)
|
||||
assert_equal(mempool[1]['satoshis'], (-4) * COIN)
|
||||
assert_equal(mempool[1]['txid'], txid)
|
||||
|
||||
# addr2 (third arg)
|
||||
assert_equal(mempool[2]['address'], addr2)
|
||||
assert_equal(mempool[2]['satoshis'], 3 * COIN)
|
||||
assert_equal(mempool[2]['txid'], txid)
|
||||
|
||||
# a single address can be specified as a string (not json object)
|
||||
assert_equal([mempool[1]], self.nodes[0].getaddressmempool(addr1))
|
||||
|
||||
txids_a1.append(txid)
|
||||
expected_deltas.append({
|
||||
'height': 111,
|
||||
'satoshis': (-4) * COIN,
|
||||
'txid': txid,
|
||||
})
|
||||
self.sync_all() # ensure transaction is included in the next block
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# the send to addr2 tx is now in a mined block, no longer in the mempool
|
||||
mempool = self.nodes[0].getaddressmempool({'addresses': [addr2, addr1]})
|
||||
assert_equal(len(mempool), 0)
|
||||
|
||||
# Test DisconnectBlock() by invalidating the most recent mined block
|
||||
tip = self.nodes[1].getchaintips()[0]
|
||||
for i in range(3):
|
||||
node = self.nodes[i]
|
||||
# the value 4 UTXO is no longer in our balance
|
||||
check_balance(i, addr1, (expected - 4) * COIN, expected * COIN)
|
||||
check_balance(i, addr2, 3 * COIN)
|
||||
|
||||
assert_equal(node.getblockcount(), 111)
|
||||
node.invalidateblock(tip['hash'])
|
||||
assert_equal(node.getblockcount(), 110)
|
||||
|
||||
mempool = node.getaddressmempool({'addresses': [addr2, addr1]})
|
||||
assert_equal(len(mempool), 2)
|
||||
|
||||
check_balance(i, addr1, expected * COIN)
|
||||
check_balance(i, addr2, 0)
|
||||
|
||||
# now re-mine the addr1 to addr2 send
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
for node in self.nodes:
|
||||
assert_equal(node.getblockcount(), 111)
|
||||
|
||||
mempool = self.nodes[0].getaddressmempool({'addresses': [addr2, addr1]})
|
||||
assert_equal(len(mempool), 0)
|
||||
|
||||
# the value 4 UTXO is no longer in our balance
|
||||
check_balance(2, addr1, (expected - 4) * COIN, expected * COIN)
|
||||
|
||||
# Ensure the change from that transaction appears
|
||||
tx = self.nodes[0].getrawtransaction(txid, 1)
|
||||
change_vout = filter(lambda v: v['valueZat'] != 3 * COIN, tx['vout'])
|
||||
change = change_vout[0]['scriptPubKey']['addresses'][0]
|
||||
bal = self.nodes[2].getaddressbalance(change)
|
||||
assert(bal['received'] > 0)
|
||||
# the inequality is due to randomness in the tx fee
|
||||
assert(bal['received'] < (4 - 3) * COIN)
|
||||
assert_equal(bal['received'], bal['balance'])
|
||||
assert_equal(self.nodes[2].getaddresstxids(change), [txid])
|
||||
|
||||
# Further checks that limiting by height works
|
||||
|
||||
# various ranges
|
||||
for i in range(5):
|
||||
height_txids = getaddresstxids(1, [addr1], 106, 106 + i)
|
||||
assert_equal(height_txids, txids_a1[0:i+1])
|
||||
|
||||
height_txids = getaddresstxids(1, [addr1], 1, 108)
|
||||
assert_equal(height_txids, txids_a1[0:3])
|
||||
|
||||
# Further check specifying multiple addresses
|
||||
txids_all = list(txids_a1)
|
||||
txids_all += self.nodes[1].getaddresstxids(addr_p2pkh)
|
||||
txids_all += self.nodes[1].getaddresstxids(addr_p2sh)
|
||||
multitxids = self.nodes[1].getaddresstxids({
|
||||
'addresses': [addr1, addr_p2sh, addr_p2pkh]
|
||||
})
|
||||
# No dups in return list from getaddresstxids
|
||||
assert_equal(len(multitxids), len(set(multitxids)))
|
||||
|
||||
# set(txids_all) removes its (expected) duplicates
|
||||
assert_equal(set(multitxids), set(txids_all))
|
||||
|
||||
deltas = self.nodes[1].getaddressdeltas({'addresses': [addr1]})
|
||||
assert_equal(len(deltas), len(expected_deltas))
|
||||
for i in range(len(deltas)):
|
||||
assert_equal(deltas[i]['address'], addr1)
|
||||
assert_equal(deltas[i]['height'], expected_deltas[i]['height'])
|
||||
assert_equal(deltas[i]['satoshis'], expected_deltas[i]['satoshis'])
|
||||
assert_equal(deltas[i]['txid'], expected_deltas[i]['txid'])
|
||||
|
||||
# 106-111 is the full range (also the default)
|
||||
deltas_limited = getaddressdeltas(1, [addr1], 106, 111)
|
||||
assert_equal(deltas_limited, deltas)
|
||||
|
||||
# only the first element missing
|
||||
deltas_limited = getaddressdeltas(1, [addr1], 107, 111)
|
||||
assert_equal(deltas_limited, deltas[1:])
|
||||
|
||||
deltas_limited = getaddressdeltas(1, [addr1], 109, 109)
|
||||
assert_equal(deltas_limited, deltas[3:4])
|
||||
|
||||
# the full range (also the default)
|
||||
deltas_info = getaddressdeltas(1, [addr1], 106, 111, chainInfo=True)
|
||||
assert_equal(deltas_info['deltas'], deltas)
|
||||
|
||||
# check the additional items returned by chainInfo
|
||||
assert_equal(deltas_info['start']['height'], 106)
|
||||
block_hash = self.nodes[1].getblockhash(106)
|
||||
assert_equal(deltas_info['start']['hash'], block_hash)
|
||||
|
||||
assert_equal(deltas_info['end']['height'], 111)
|
||||
block_hash = self.nodes[1].getblockhash(111)
|
||||
assert_equal(deltas_info['end']['hash'], block_hash)
|
||||
|
||||
# Test getaddressutxos by comparing results with deltas
|
||||
utxos = self.nodes[1].getaddressutxos(addr1)
|
||||
|
||||
# The value 4 note was spent, so won't show up in the utxo list,
|
||||
# so for comparison, remove the 4 (and -4 for output) from the
|
||||
# deltas list
|
||||
deltas = self.nodes[1].getaddressdeltas({'addresses': [addr1]})
|
||||
deltas = filter(lambda d: abs(d['satoshis']) != 4 * COIN, deltas)
|
||||
assert_equal(len(utxos), len(deltas))
|
||||
for i in range(len(utxos)):
|
||||
assert_equal(utxos[i]['address'], addr1)
|
||||
assert_equal(utxos[i]['height'], deltas[i]['height'])
|
||||
assert_equal(utxos[i]['satoshis'], deltas[i]['satoshis'])
|
||||
assert_equal(utxos[i]['txid'], deltas[i]['txid'])
|
||||
|
||||
# Check that outputs with the same address in the same tx return one txid
|
||||
# (can't use createrawtransaction() as it combines duplicate addresses)
|
||||
addr = "t2LMJ6Arw9UWBMWvfUr2QLHM4Xd9w53FftS"
|
||||
addressHash = "97643ce74b188f4fb6bbbb285e067a969041caf2".decode('hex')
|
||||
scriptPubKey = CScript([OP_HASH160, addressHash, OP_EQUAL])
|
||||
# Add an unrecognized script type to vout[], a legal script that pays,
|
||||
# but won't modify the addressindex (since the address can't be extracted).
|
||||
# (This extra output has no effect on the rest of the test.)
|
||||
scriptUnknown = CScript([OP_HASH160, OP_DUP, OP_DROP, addressHash, OP_EQUAL])
|
||||
unspent = filter(lambda u: u['amount'] >= 4, self.nodes[0].listunspent())
|
||||
tx = CTransaction()
|
||||
tx.vin = [CTxIn(COutPoint(int(unspent[0]['txid'], 16), unspent[0]['vout']))]
|
||||
tx.vout = [
|
||||
CTxOut(1 * COIN, scriptPubKey),
|
||||
CTxOut(2 * COIN, scriptPubKey),
|
||||
CTxOut(7 * COIN, scriptUnknown),
|
||||
]
|
||||
tx = self.nodes[0].signrawtransaction(hexlify(tx.serialize()).decode('utf-8'))
|
||||
txid = self.nodes[0].sendrawtransaction(tx['hex'], True)
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
assert_equal(self.nodes[1].getaddresstxids(addr), [txid])
|
||||
check_balance(2, addr, 3 * COIN)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
AddressIndexTest().main()
|
|
@ -222,4 +222,66 @@ struct CAddressIndexIteratorHeightKey {
|
|||
}
|
||||
};
|
||||
|
||||
struct CMempoolAddressDelta
|
||||
{
|
||||
int64_t time;
|
||||
CAmount amount;
|
||||
uint256 prevhash;
|
||||
unsigned int prevout;
|
||||
|
||||
CMempoolAddressDelta(int64_t t, CAmount a, uint256 hash, unsigned int out) {
|
||||
time = t;
|
||||
amount = a;
|
||||
prevhash = hash;
|
||||
prevout = out;
|
||||
}
|
||||
|
||||
CMempoolAddressDelta(int64_t t, CAmount a) {
|
||||
time = t;
|
||||
amount = a;
|
||||
prevhash.SetNull();
|
||||
prevout = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct CMempoolAddressDeltaKey
|
||||
{
|
||||
int type;
|
||||
uint160 addressBytes;
|
||||
uint256 txhash;
|
||||
unsigned int index;
|
||||
int spending;
|
||||
|
||||
CMempoolAddressDeltaKey(int addressType, uint160 addressHash, uint256 hash, unsigned int i, int s) {
|
||||
type = addressType;
|
||||
addressBytes = addressHash;
|
||||
txhash = hash;
|
||||
index = i;
|
||||
spending = s;
|
||||
}
|
||||
|
||||
CMempoolAddressDeltaKey(int addressType, uint160 addressHash) {
|
||||
type = addressType;
|
||||
addressBytes = addressHash;
|
||||
txhash.SetNull();
|
||||
index = 0;
|
||||
spending = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct CMempoolAddressDeltaKeyCompare
|
||||
{
|
||||
bool operator()(const CMempoolAddressDeltaKey& a, const CMempoolAddressDeltaKey& b) const {
|
||||
if (a.type != b.type)
|
||||
return a.type < b.type;
|
||||
if (a.addressBytes != b.addressBytes)
|
||||
return a.addressBytes < b.addressBytes;
|
||||
if (a.txhash != b.txhash)
|
||||
return a.txhash < b.txhash;
|
||||
if (a.index != b.index)
|
||||
return a.index < b.index;
|
||||
return a.spending < b.spending;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // BITCOIN_ADDRESSINDEX_H
|
||||
|
|
31
src/main.cpp
31
src/main.cpp
|
@ -21,7 +21,6 @@
|
|||
#include "metrics.h"
|
||||
#include "net.h"
|
||||
#include "pow.h"
|
||||
#include "txdb.h"
|
||||
#include "txmempool.h"
|
||||
#include "ui_interface.h"
|
||||
#include "undo.h"
|
||||
|
@ -1600,6 +1599,11 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
|
|||
|
||||
// Store transaction in memory
|
||||
pool.addUnchecked(hash, entry, !IsInitialBlockDownload(Params()));
|
||||
|
||||
// Add memory address index
|
||||
if (fAddressIndex) {
|
||||
pool.addAddressIndex(entry, view);
|
||||
}
|
||||
}
|
||||
|
||||
SyncWithWallets(tx, NULL);
|
||||
|
@ -1615,6 +1619,31 @@ bool GetSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value)
|
|||
return pblocktree->ReadSpentIndex(key, value);
|
||||
}
|
||||
|
||||
bool GetAddressIndex(const uint160& addressHash, int type,
|
||||
std::vector<CAddressIndexDbEntry>& addressIndex,
|
||||
int start, int end)
|
||||
{
|
||||
if (!fAddressIndex)
|
||||
return error("address index not enabled");
|
||||
|
||||
if (!pblocktree->ReadAddressIndex(addressHash, type, addressIndex, start, end))
|
||||
return error("unable to get txids for address");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetAddressUnspent(const uint160& addressHash, int type,
|
||||
std::vector<CAddressUnspentDbEntry>& unspentOutputs)
|
||||
{
|
||||
if (!fAddressIndex)
|
||||
return error("address index not enabled");
|
||||
|
||||
if (!pblocktree->ReadAddressUnspentIndex(addressHash, type, unspentOutputs))
|
||||
return error("unable to get txids for address");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 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)
|
||||
{
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "script/standard.h"
|
||||
#include "sync.h"
|
||||
#include "tinyformat.h"
|
||||
#include "txdb.h"
|
||||
#include "txmempool.h"
|
||||
#include "uint256.h"
|
||||
#include "addressindex.h"
|
||||
|
@ -438,6 +439,11 @@ public:
|
|||
};
|
||||
|
||||
bool GetSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value);
|
||||
bool GetAddressIndex(const uint160& addressHash, int type,
|
||||
std::vector<CAddressIndexDbEntry> &addressIndex,
|
||||
int start = 0, int end = 0);
|
||||
bool GetAddressUnspent(const uint160& addressHash, int type,
|
||||
std::vector<CAddressUnspentDbEntry>& unspentOutputs);
|
||||
|
||||
/** Functions for disk access for blocks */
|
||||
bool WriteBlockToDisk(const CBlock& block, CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& messageStart);
|
||||
|
|
|
@ -99,6 +99,11 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "prioritisetransaction", 2 },
|
||||
{ "setban", 2 },
|
||||
{ "setban", 3 },
|
||||
{ "getaddresstxids", 0},
|
||||
{ "getaddressbalance", 0},
|
||||
{ "getaddressdeltas", 0},
|
||||
{ "getaddressutxos", 0},
|
||||
{ "getaddressmempool", 0},
|
||||
{ "zcrawjoinsplit", 1 },
|
||||
{ "zcrawjoinsplit", 2 },
|
||||
{ "zcrawjoinsplit", 3 },
|
||||
|
|
569
src/rpc/misc.cpp
569
src/rpc/misc.cpp
|
@ -11,6 +11,7 @@
|
|||
#include "netbase.h"
|
||||
#include "rpc/server.h"
|
||||
#include "timedata.h"
|
||||
#include "txmempool.h"
|
||||
#include "util.h"
|
||||
#ifdef ENABLE_WALLET
|
||||
#include "wallet/wallet.h"
|
||||
|
@ -483,6 +484,567 @@ UniValue setmocktime(const UniValue& params, bool fHelp)
|
|||
return NullUniValue;
|
||||
}
|
||||
|
||||
static bool getAddressFromIndex(
|
||||
int type, const uint160 &hash, std::string &address)
|
||||
{
|
||||
if (type == CScript::P2SH) {
|
||||
address = EncodeDestination(CScriptID(hash));
|
||||
} else if (type == CScript::P2PKH) {
|
||||
address = EncodeDestination(CKeyID(hash));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function accepts an address and returns in the output parameters
|
||||
// the version and raw bytes for the RIPEMD-160 hash.
|
||||
static bool getIndexKey(
|
||||
const CTxDestination& dest, uint160& hashBytes, int& type)
|
||||
{
|
||||
if (!IsValidDestination(dest)) {
|
||||
return false;
|
||||
}
|
||||
if (dest.type() == typeid(CKeyID)) {
|
||||
auto x = boost::get<CKeyID>(&dest);
|
||||
memcpy(&hashBytes, x->begin(), 20);
|
||||
type = CScript::P2PKH;
|
||||
return true;
|
||||
}
|
||||
if (dest.type() == typeid(CScriptID)) {
|
||||
auto x = boost::get<CScriptID>(&dest);
|
||||
memcpy(&hashBytes, x->begin(), 20);
|
||||
type = CScript::P2SH;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool getAddressesFromParams(
|
||||
const UniValue& params,
|
||||
std::vector<std::pair<uint160, int>> &addresses)
|
||||
{
|
||||
std::vector<std::string> param_addresses;
|
||||
if (params[0].isStr()) {
|
||||
param_addresses.push_back(params[0].get_str());
|
||||
} else if (params[0].isObject()) {
|
||||
UniValue addressValues = find_value(params[0].get_obj(), "addresses");
|
||||
if (!addressValues.isArray()) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
|
||||
"Addresses is expected to be an array");
|
||||
}
|
||||
for (const auto& it : addressValues.getValues()) {
|
||||
param_addresses.push_back(it.get_str());
|
||||
}
|
||||
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
|
||||
}
|
||||
for (const auto& it : param_addresses) {
|
||||
CTxDestination address = DecodeDestination(it);
|
||||
uint160 hashBytes;
|
||||
int type = 0;
|
||||
if (!getIndexKey(address, hashBytes, type)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
|
||||
}
|
||||
addresses.push_back(std::make_pair(hashBytes, type));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
UniValue getaddressmempool(const UniValue& params, bool fHelp)
|
||||
{
|
||||
std::string enableArg = "insightexplorer";
|
||||
bool fEnableGetAddressMempool = fExperimentalMode && fInsightExplorer;
|
||||
std::string disabledMsg = "";
|
||||
if (!fEnableGetAddressMempool) {
|
||||
disabledMsg = experimentalDisabledHelpMsg("getaddressmempool", enableArg);
|
||||
}
|
||||
if (fHelp || params.size() != 1)
|
||||
throw runtime_error(
|
||||
"getaddressmempool {\"addresses\": [\"taddr\", ...]}\n"
|
||||
"\nReturns all mempool deltas for an address.\n"
|
||||
+ disabledMsg +
|
||||
"\nArguments:\n"
|
||||
"{\n"
|
||||
" \"addresses\":\n"
|
||||
" [\n"
|
||||
" \"address\" (string) The base58check encoded address\n"
|
||||
" ,...\n"
|
||||
" ]\n"
|
||||
"}\n"
|
||||
"(or)\n"
|
||||
"\"address\" (string) The base58check encoded address\n"
|
||||
"\nResult:\n"
|
||||
"[\n"
|
||||
" {\n"
|
||||
" \"address\" (string) The base58check encoded address\n"
|
||||
" \"txid\" (string) The related txid\n"
|
||||
" \"index\" (number) The related input or output index\n"
|
||||
" \"satoshis\" (number) The difference of zatoshis\n"
|
||||
" \"timestamp\" (number) The time the transaction entered the mempool (seconds)\n"
|
||||
" \"prevtxid\" (string) The previous txid (if spending)\n"
|
||||
" \"prevout\" (string) The previous transaction output index (if spending)\n"
|
||||
" }\n"
|
||||
"]\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("getaddressmempool", "'{\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"]}'")
|
||||
+ HelpExampleRpc("getaddressmempool", "{\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"]}")
|
||||
);
|
||||
|
||||
if (!fEnableGetAddressMempool) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Error: getaddressmempool is disabled. "
|
||||
"Run './zcash-cli help getaddressmempool' for instructions on how to enable this feature.");
|
||||
}
|
||||
|
||||
std::vector<std::pair<uint160, int>> addresses;
|
||||
|
||||
if (!getAddressesFromParams(params, addresses)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
|
||||
}
|
||||
std::vector<std::pair<CMempoolAddressDeltaKey, CMempoolAddressDelta>> indexes;
|
||||
mempool.getAddressIndex(addresses, indexes);
|
||||
std::sort(indexes.begin(), indexes.end(),
|
||||
[](const std::pair<CMempoolAddressDeltaKey, CMempoolAddressDelta>& a,
|
||||
const std::pair<CMempoolAddressDeltaKey, CMempoolAddressDelta>& b) -> bool {
|
||||
return a.second.time < b.second.time;
|
||||
});
|
||||
|
||||
UniValue result(UniValue::VARR);
|
||||
|
||||
for (const auto& it : indexes) {
|
||||
std::string address;
|
||||
if (!getAddressFromIndex(it.first.type, it.first.addressBytes, address)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown address type");
|
||||
}
|
||||
UniValue delta(UniValue::VOBJ);
|
||||
delta.push_back(Pair("address", address));
|
||||
delta.push_back(Pair("txid", it.first.txhash.GetHex()));
|
||||
delta.push_back(Pair("index", (int)it.first.index));
|
||||
delta.push_back(Pair("satoshis", it.second.amount));
|
||||
delta.push_back(Pair("timestamp", it.second.time));
|
||||
if (it.second.amount < 0) {
|
||||
delta.push_back(Pair("prevtxid", it.second.prevhash.GetHex()));
|
||||
delta.push_back(Pair("prevout", (int)it.second.prevout));
|
||||
}
|
||||
result.push_back(delta);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
UniValue getaddressutxos(const UniValue& params, bool fHelp)
|
||||
{
|
||||
std::string enableArg = "insightexplorer";
|
||||
bool fEnableGetAddressUtxos = fExperimentalMode && fInsightExplorer;
|
||||
std::string disabledMsg = "";
|
||||
if (!fEnableGetAddressUtxos) {
|
||||
disabledMsg = experimentalDisabledHelpMsg("getaddressutxos", enableArg);
|
||||
}
|
||||
if (fHelp || params.size() != 1)
|
||||
throw runtime_error(
|
||||
"getaddressutxos {\"addresses\": [\"taddr\", ...], (\"chainInfo\": true|false)}\n"
|
||||
"\nReturns all unspent outputs for an address.\n"
|
||||
+ disabledMsg +
|
||||
"\nArguments:\n"
|
||||
"{\n"
|
||||
" \"addresses\":\n"
|
||||
" [\n"
|
||||
" \"address\" (string) The base58check encoded address\n"
|
||||
" ,...\n"
|
||||
" ],\n"
|
||||
" \"chainInfo\" (boolean, optional, default=false) Include chain info with results\n"
|
||||
"}\n"
|
||||
"(or)\n"
|
||||
"\"address\" (string) The base58check encoded address\n"
|
||||
"\nResult\n"
|
||||
"[\n"
|
||||
" {\n"
|
||||
" \"address\" (string) The address base58check encoded\n"
|
||||
" \"txid\" (string) The output txid\n"
|
||||
" \"height\" (number) The block height\n"
|
||||
" \"outputIndex\" (number) The output index\n"
|
||||
" \"script\" (string) The script hex encoded\n"
|
||||
" \"satoshis\" (number) The number of zatoshis of the output\n"
|
||||
" }, ...\n"
|
||||
"]\n\n"
|
||||
"(or, if chainInfo is true):\n\n"
|
||||
"{\n"
|
||||
" \"utxos\":\n"
|
||||
" [\n"
|
||||
" {\n"
|
||||
" \"address\" (string) The address base58check encoded\n"
|
||||
" \"txid\" (string) The output txid\n"
|
||||
" \"height\" (number) The block height\n"
|
||||
" \"outputIndex\" (number) The output index\n"
|
||||
" \"script\" (string) The script hex encoded\n"
|
||||
" \"satoshis\" (number) The number of zatoshis of the output\n"
|
||||
" }, ...\n"
|
||||
" ],\n"
|
||||
" \"hash\" (string) The block hash\n"
|
||||
" \"height\" (numeric) The block height\n"
|
||||
"}\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("getaddressutxos", "'{\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"], \"chainInfo\": true}'")
|
||||
+ HelpExampleRpc("getaddressutxos", "{\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"], \"chainInfo\": true}")
|
||||
);
|
||||
|
||||
if (!fEnableGetAddressUtxos) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Error: getaddressutxos is disabled. "
|
||||
"Run './zcash-cli help getaddressutxos' for instructions on how to enable this feature.");
|
||||
}
|
||||
|
||||
bool includeChainInfo = false;
|
||||
if (params[0].isObject()) {
|
||||
UniValue chainInfo = find_value(params[0].get_obj(), "chainInfo");
|
||||
if (!chainInfo.isNull()) {
|
||||
includeChainInfo = chainInfo.get_bool();
|
||||
}
|
||||
}
|
||||
std::vector<std::pair<uint160, int>> addresses;
|
||||
if (!getAddressesFromParams(params, addresses)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
|
||||
}
|
||||
std::vector<CAddressUnspentDbEntry> unspentOutputs;
|
||||
for (const auto& it : addresses) {
|
||||
if (!GetAddressUnspent(it.first, it.second, unspentOutputs)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for address");
|
||||
}
|
||||
}
|
||||
std::sort(unspentOutputs.begin(), unspentOutputs.end(),
|
||||
[](const CAddressUnspentDbEntry& a, const CAddressUnspentDbEntry& b) -> bool {
|
||||
return a.second.blockHeight < b.second.blockHeight;
|
||||
});
|
||||
|
||||
UniValue utxos(UniValue::VARR);
|
||||
for (const auto& it : unspentOutputs) {
|
||||
UniValue output(UniValue::VOBJ);
|
||||
std::string address;
|
||||
if (!getAddressFromIndex(it.first.type, it.first.hashBytes, address)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown address type");
|
||||
}
|
||||
|
||||
output.push_back(Pair("address", address));
|
||||
output.push_back(Pair("txid", it.first.txhash.GetHex()));
|
||||
output.push_back(Pair("outputIndex", (int)it.first.index));
|
||||
output.push_back(Pair("script", HexStr(it.second.script.begin(), it.second.script.end())));
|
||||
output.push_back(Pair("satoshis", it.second.satoshis));
|
||||
output.push_back(Pair("height", it.second.blockHeight));
|
||||
utxos.push_back(output);
|
||||
}
|
||||
|
||||
if (!includeChainInfo)
|
||||
return utxos;
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.push_back(Pair("utxos", utxos));
|
||||
|
||||
LOCK(cs_main); // for chainActive
|
||||
result.push_back(Pair("hash", chainActive.Tip()->GetBlockHash().GetHex()));
|
||||
result.push_back(Pair("height", (int)chainActive.Height()));
|
||||
return result;
|
||||
}
|
||||
|
||||
static void getHeightRange(const UniValue& params, int& start, int& end)
|
||||
{
|
||||
start = 0;
|
||||
end = 0;
|
||||
if (params[0].isObject()) {
|
||||
UniValue startValue = find_value(params[0].get_obj(), "start");
|
||||
UniValue endValue = find_value(params[0].get_obj(), "end");
|
||||
// If either is not specified, the other is ignored.
|
||||
if (!startValue.isNull() && !endValue.isNull()) {
|
||||
start = startValue.get_int();
|
||||
end = endValue.get_int();
|
||||
if (start <= 0 || end <= 0) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
|
||||
"Start and end are expected to be greater than zero");
|
||||
}
|
||||
if (end < start) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
|
||||
"End value is expected to be greater than start");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOCK(cs_main); // for chainActive
|
||||
if (start > chainActive.Height() || end > chainActive.Height()) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Start or end is outside chain range");
|
||||
}
|
||||
}
|
||||
|
||||
// Parse an address list then fetch the corresponding addressindex information.
|
||||
static void getAddressesInHeightRange(
|
||||
const UniValue& params,
|
||||
int start, int end,
|
||||
std::vector<std::pair<uint160, int>>& addresses,
|
||||
std::vector<std::pair<CAddressIndexKey, CAmount>> &addressIndex)
|
||||
{
|
||||
if (!getAddressesFromParams(params, addresses)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
|
||||
}
|
||||
for (const auto& it : addresses) {
|
||||
if (!GetAddressIndex(it.first, it.second, addressIndex, start, end)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
|
||||
"No information available for address");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UniValue getaddressdeltas(const UniValue& params, bool fHelp)
|
||||
{
|
||||
std::string enableArg = "insightexplorer";
|
||||
bool fEnableGetAddressDeltas = fExperimentalMode && fInsightExplorer;
|
||||
std::string disabledMsg = "";
|
||||
if (!fEnableGetAddressDeltas) {
|
||||
disabledMsg = experimentalDisabledHelpMsg("getaddressdeltas", enableArg);
|
||||
}
|
||||
if (fHelp || params.size() != 1)
|
||||
throw runtime_error(
|
||||
"getaddressdeltas {\"addresses\": [\"taddr\", ...], (\"start\": n), (\"end\": n), (\"chainInfo\": true|false)}\n"
|
||||
"\nReturns all changes for an address.\n"
|
||||
"\nReturns information about all changes to the given transparent addresses within the given (inclusive)\n"
|
||||
"\nblock height range, default is the full blockchain.\n"
|
||||
+ disabledMsg +
|
||||
"\nArguments:\n"
|
||||
"{\n"
|
||||
" \"addresses\":\n"
|
||||
" [\n"
|
||||
" \"address\" (string) The base58check encoded address\n"
|
||||
" ,...\n"
|
||||
" ]\n"
|
||||
" \"start\" (number, optional) The start block height\n"
|
||||
" \"end\" (number, optional) The end block height\n"
|
||||
" \"chainInfo\" (boolean, optional, default=false) Include chain info in results, only applies if start and end specified\n"
|
||||
"}\n"
|
||||
"(or)\n"
|
||||
"\"address\" (string) The base58check encoded address\n"
|
||||
"\nResult:\n"
|
||||
"[\n"
|
||||
" {\n"
|
||||
" \"satoshis\" (number) The difference of zatoshis\n"
|
||||
" \"txid\" (string) The related txid\n"
|
||||
" \"index\" (number) The related input or output index\n"
|
||||
" \"height\" (number) The block height\n"
|
||||
" \"address\" (string) The base58check encoded address\n"
|
||||
" }, ...\n"
|
||||
"]\n\n"
|
||||
"(or, if chainInfo is true):\n\n"
|
||||
"{\n"
|
||||
" \"deltas\":\n"
|
||||
" [\n"
|
||||
" {\n"
|
||||
" \"satoshis\" (number) The difference of zatoshis\n"
|
||||
" \"txid\" (string) The related txid\n"
|
||||
" \"index\" (number) The related input or output index\n"
|
||||
" \"height\" (number) The block height\n"
|
||||
" \"address\" (string) The address base58check encoded\n"
|
||||
" }, ...\n"
|
||||
" ],\n"
|
||||
" \"start\":\n"
|
||||
" {\n"
|
||||
" \"hash\" (string) The start block hash\n"
|
||||
" \"height\" (numeric) The height of the start block\n"
|
||||
" }\n"
|
||||
" \"end\":\n"
|
||||
" {\n"
|
||||
" \"hash\" (string) The end block hash\n"
|
||||
" \"height\" (numeric) The height of the end block\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("getaddressdeltas", "'{\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"], \"start\": 1000, \"end\": 2000, \"chainInfo\": true}'")
|
||||
+ HelpExampleRpc("getaddressdeltas", "{\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"], \"start\": 1000, \"end\": 2000, \"chainInfo\": true}")
|
||||
);
|
||||
|
||||
if (!fEnableGetAddressDeltas) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Error: getaddressdeltas is disabled. "
|
||||
"Run './zcash-cli help getaddressdeltas' for instructions on how to enable this feature.");
|
||||
}
|
||||
|
||||
int start = 0;
|
||||
int end = 0;
|
||||
getHeightRange(params, start, end);
|
||||
|
||||
std::vector<std::pair<uint160, int>> addresses;
|
||||
std::vector<std::pair<CAddressIndexKey, CAmount>> addressIndex;
|
||||
getAddressesInHeightRange(params, start, end, addresses, addressIndex);
|
||||
|
||||
bool includeChainInfo = false;
|
||||
if (params[0].isObject()) {
|
||||
UniValue chainInfo = find_value(params[0].get_obj(), "chainInfo");
|
||||
if (!chainInfo.isNull()) {
|
||||
includeChainInfo = chainInfo.get_bool();
|
||||
}
|
||||
}
|
||||
|
||||
UniValue deltas(UniValue::VARR);
|
||||
for (const auto& it : addressIndex) {
|
||||
std::string address;
|
||||
if (!getAddressFromIndex(it.first.type, it.first.hashBytes, address)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown address type");
|
||||
}
|
||||
|
||||
UniValue delta(UniValue::VOBJ);
|
||||
delta.push_back(Pair("address", address));
|
||||
delta.push_back(Pair("blockindex", (int)it.first.txindex));
|
||||
delta.push_back(Pair("height", it.first.blockHeight));
|
||||
delta.push_back(Pair("index", (int)it.first.index));
|
||||
delta.push_back(Pair("satoshis", it.second));
|
||||
delta.push_back(Pair("txid", it.first.txhash.GetHex()));
|
||||
deltas.push_back(delta);
|
||||
}
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
|
||||
if (!(includeChainInfo && start > 0 && end > 0)) {
|
||||
return deltas;
|
||||
}
|
||||
|
||||
UniValue startInfo(UniValue::VOBJ);
|
||||
UniValue endInfo(UniValue::VOBJ);
|
||||
{
|
||||
LOCK(cs_main); // for chainActive
|
||||
if (start > chainActive.Height() || end > chainActive.Height()) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Start or end is outside chain range");
|
||||
}
|
||||
startInfo.push_back(Pair("hash", chainActive[start]->GetBlockHash().GetHex()));
|
||||
endInfo.push_back(Pair("hash", chainActive[end]->GetBlockHash().GetHex()));
|
||||
}
|
||||
startInfo.push_back(Pair("height", start));
|
||||
endInfo.push_back(Pair("height", end));
|
||||
|
||||
result.push_back(Pair("deltas", deltas));
|
||||
result.push_back(Pair("start", startInfo));
|
||||
result.push_back(Pair("end", endInfo));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
UniValue getaddressbalance(const UniValue& params, bool fHelp)
|
||||
{
|
||||
std::string enableArg = "insightexplorer";
|
||||
bool fEnableGetAddressBalance = fExperimentalMode && fInsightExplorer;
|
||||
std::string disabledMsg = "";
|
||||
if (!fEnableGetAddressBalance) {
|
||||
disabledMsg = experimentalDisabledHelpMsg("getaddressbalance", enableArg);
|
||||
}
|
||||
if (fHelp || params.size() != 1)
|
||||
throw runtime_error(
|
||||
"getaddressbalance {\"addresses\": [\"taddr\", ...]}\n"
|
||||
"\nReturns the balance for addresses.\n"
|
||||
+ disabledMsg +
|
||||
"\nArguments:\n"
|
||||
"{\n"
|
||||
" \"addresses:\"\n"
|
||||
" [\n"
|
||||
" \"address\" (string) The base58check encoded address\n"
|
||||
" ,...\n"
|
||||
" ]\n"
|
||||
"}\n"
|
||||
"(or)\n"
|
||||
"\"address\" (string) The base58check encoded address\n"
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
" \"balance\" (string) The current balance in zatoshis\n"
|
||||
" \"received\" (string) The total number of zatoshis received (including change)\n"
|
||||
"}\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("getaddressbalance", "'{\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"]}'")
|
||||
+ HelpExampleRpc("getaddressbalance", "{\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"]}")
|
||||
);
|
||||
|
||||
if (!fEnableGetAddressBalance) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Error: getaddressbalance is disabled. "
|
||||
"Run './zcash-cli help getaddressbalance' for instructions on how to enable this feature.");
|
||||
}
|
||||
|
||||
std::vector<std::pair<uint160, int>> addresses;
|
||||
std::vector<std::pair<CAddressIndexKey, CAmount>> addressIndex;
|
||||
// this method doesn't take start and end block height params, so set
|
||||
// to zero (full range, entire blockchain)
|
||||
getAddressesInHeightRange(params, 0, 0, addresses, addressIndex);
|
||||
|
||||
CAmount balance = 0;
|
||||
CAmount received = 0;
|
||||
for (const auto& it : addressIndex) {
|
||||
if (it.second > 0) {
|
||||
received += it.second;
|
||||
}
|
||||
balance += it.second;
|
||||
}
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.push_back(Pair("balance", balance));
|
||||
result.push_back(Pair("received", received));
|
||||
return result;
|
||||
}
|
||||
|
||||
UniValue getaddresstxids(const UniValue& params, bool fHelp)
|
||||
{
|
||||
std::string enableArg = "insightexplorer";
|
||||
bool fEnableGetAddressTxids = fExperimentalMode && fInsightExplorer;
|
||||
std::string disabledMsg = "";
|
||||
if (!fEnableGetAddressTxids) {
|
||||
disabledMsg = experimentalDisabledHelpMsg("getaddresstxids", enableArg);
|
||||
}
|
||||
if (fHelp || params.size() != 1)
|
||||
throw runtime_error(
|
||||
"getaddresstxids {\"addresses\": [\"taddr\", ...], (\"start\": n), (\"end\": n)}\n"
|
||||
"\nReturns the txids for given transparent addresses within the given (inclusive)\n"
|
||||
"\nblock height range, default is the full blockchain.\n"
|
||||
+ disabledMsg +
|
||||
"\nArguments:\n"
|
||||
"{\n"
|
||||
" \"addresses\":\n"
|
||||
" [\n"
|
||||
" \"taddr\" (string) The base58check encoded address\n"
|
||||
" ,...\n"
|
||||
" ]\n"
|
||||
" \"start\" (number, optional) The start block height\n"
|
||||
" \"end\" (number, optional) The end block height\n"
|
||||
"}\n"
|
||||
"(or)\n"
|
||||
"\"address\" (string) The base58check encoded address\n"
|
||||
"\nResult:\n"
|
||||
"[\n"
|
||||
" \"transactionid\" (string) The transaction id\n"
|
||||
" ,...\n"
|
||||
"]\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("getaddresstxids", "'{\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"], \"start\": 1000, \"end\": 2000}'")
|
||||
+ HelpExampleRpc("getaddresstxids", "{\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"], \"start\": 1000, \"end\": 2000}")
|
||||
);
|
||||
|
||||
if (!fEnableGetAddressTxids) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Error: getaddresstxids is disabled. "
|
||||
"Run './zcash-cli help getaddresstxids' for instructions on how to enable this feature.");
|
||||
}
|
||||
|
||||
int start = 0;
|
||||
int end = 0;
|
||||
getHeightRange(params, start, end);
|
||||
|
||||
std::vector<std::pair<uint160, int>> addresses;
|
||||
std::vector<std::pair<CAddressIndexKey, CAmount>> addressIndex;
|
||||
getAddressesInHeightRange(params, start, end, addresses, addressIndex);
|
||||
|
||||
// This is an ordered set, sorted by height, so result also sorted by height.
|
||||
std::set<std::pair<int, std::string>> txids;
|
||||
|
||||
for (const auto& it : addressIndex) {
|
||||
const int height = it.first.blockHeight;
|
||||
const std::string txid = it.first.txhash.GetHex();
|
||||
// Duplicate entries (two addresses in same tx) are suppressed
|
||||
txids.insert(std::make_pair(height, txid));
|
||||
}
|
||||
UniValue result(UniValue::VARR);
|
||||
for (const auto& it : txids) {
|
||||
// only push the txid, not the height
|
||||
result.push_back(it.second);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static const CRPCCommand commands[] =
|
||||
{ // category name actor (function) okSafeMode
|
||||
// --------------------- ------------------------ ----------------------- ----------
|
||||
|
@ -492,6 +1054,13 @@ static const CRPCCommand commands[] =
|
|||
{ "util", "createmultisig", &createmultisig, true },
|
||||
{ "util", "verifymessage", &verifymessage, true },
|
||||
|
||||
/* 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 */
|
||||
|
||||
/* Not shown in help */
|
||||
{ "hidden", "setmocktime", &setmocktime, true },
|
||||
};
|
||||
|
|
|
@ -66,7 +66,9 @@ UniValue TxJoinSplitToJSON(const CTransaction& tx) {
|
|||
UniValue joinsplit(UniValue::VOBJ);
|
||||
|
||||
joinsplit.push_back(Pair("vpub_old", ValueFromAmount(jsdescription.vpub_old)));
|
||||
joinsplit.push_back(Pair("vpub_oldZat", jsdescription.vpub_old));
|
||||
joinsplit.push_back(Pair("vpub_new", ValueFromAmount(jsdescription.vpub_new)));
|
||||
joinsplit.push_back(Pair("vpub_newZat", jsdescription.vpub_new));
|
||||
|
||||
joinsplit.push_back(Pair("anchor", jsdescription.anchor.GetHex()));
|
||||
|
||||
|
@ -178,10 +180,10 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry)
|
|||
in.push_back(Pair("value", ValueFromAmount(spentInfo.satoshis)));
|
||||
in.push_back(Pair("valueSat", spentInfo.satoshis));
|
||||
|
||||
boost::optional<CTxDestination> dest =
|
||||
CTxDestination dest =
|
||||
DestFromAddressHash(spentInfo.addressType, spentInfo.addressHash);
|
||||
if (dest) {
|
||||
in.push_back(Pair("address", EncodeDestination(*dest)));
|
||||
if (IsValidDestination(dest)) {
|
||||
in.push_back(Pair("address", EncodeDestination(dest)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -217,6 +219,7 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry)
|
|||
|
||||
if (tx.fOverwintered && tx.nVersion >= SAPLING_TX_VERSION) {
|
||||
entry.push_back(Pair("valueBalance", ValueFromAmount(tx.valueBalance)));
|
||||
entry.push_back(Pair("valueBalanceZat", tx.valueBalance));
|
||||
UniValue vspenddesc = TxShieldedSpendsToJSON(tx);
|
||||
entry.push_back(Pair("vShieldedSpend", vspenddesc));
|
||||
UniValue voutputdesc = TxShieldedOutputsToJSON(tx);
|
||||
|
|
|
@ -248,7 +248,7 @@ CScript::ScriptType CScript::GetType() const
|
|||
return CScript::P2PKH;
|
||||
if (this->IsPayToScriptHash())
|
||||
return CScript::P2SH;
|
||||
// We don't know this script
|
||||
// We don't know this script type
|
||||
return CScript::UNKNOWN;
|
||||
}
|
||||
|
||||
|
@ -262,7 +262,7 @@ uint160 CScript::AddressHash() const
|
|||
else if (this->IsPayToScriptHash())
|
||||
start = 2;
|
||||
else {
|
||||
// unknown script type; return zeros
|
||||
// unknown script type; return zeros (this can happen)
|
||||
vector<unsigned char> hashBytes;
|
||||
hashBytes.resize(20);
|
||||
return uint160(hashBytes);
|
||||
|
|
|
@ -322,7 +322,7 @@ bool IsValidDestination(const CTxDestination& dest) {
|
|||
}
|
||||
|
||||
// insightexplorer
|
||||
boost::optional<CTxDestination> DestFromAddressHash(int scriptType, uint160& addressHash)
|
||||
CTxDestination DestFromAddressHash(int scriptType, uint160& addressHash)
|
||||
{
|
||||
switch (scriptType) {
|
||||
case CScript::P2PKH:
|
||||
|
@ -330,6 +330,9 @@ boost::optional<CTxDestination> DestFromAddressHash(int scriptType, uint160& add
|
|||
case CScript::P2SH:
|
||||
return CTxDestination(CScriptID(addressHash));
|
||||
default:
|
||||
return boost::none;
|
||||
// This probably won't ever happen, because it would mean that
|
||||
// the addressindex contains a type (say, 3) that we (currently)
|
||||
// don't recognize; maybe we "dropped support" for it?
|
||||
return CNoDestination();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,6 +97,6 @@ CScript GetScriptForDestination(const CTxDestination& dest);
|
|||
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys);
|
||||
|
||||
// insightexplorer
|
||||
boost::optional<CTxDestination> DestFromAddressHash(int scriptType, uint160& addressHash);
|
||||
CTxDestination DestFromAddressHash(int scriptType, uint160& addressHash);
|
||||
|
||||
#endif // BITCOIN_SCRIPT_STANDARD_H
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "rpc/client.h"
|
||||
|
||||
#include "key_io.h"
|
||||
#include "main.h"
|
||||
#include "netbase.h"
|
||||
#include "utilstrencodings.h"
|
||||
|
||||
|
@ -55,6 +56,19 @@ UniValue CallRPC(string args)
|
|||
}
|
||||
|
||||
|
||||
void CheckRPCThrows(std::string rpcString, std::string expectedErrorMessage) {
|
||||
try {
|
||||
CallRPC(rpcString);
|
||||
// Note: CallRPC catches (const UniValue& objError) and rethrows a runtime_error
|
||||
BOOST_FAIL("Should have caused an error");
|
||||
} catch (const std::runtime_error& e) {
|
||||
BOOST_CHECK_EQUAL(expectedErrorMessage, e.what());
|
||||
} catch(const std::exception& e) {
|
||||
BOOST_FAIL(std::string("Unexpected exception: ") + typeid(e).name() + ", message=\"" + e.what() + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(rpc_tests, TestingSetup)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(rpc_rawparams)
|
||||
|
@ -346,4 +360,65 @@ BOOST_AUTO_TEST_CASE(rpc_getnetworksolps)
|
|||
BOOST_CHECK_NO_THROW(CallRPC("getnetworksolps 120 -1"));
|
||||
}
|
||||
|
||||
// Test parameter processing (not functionality)
|
||||
BOOST_AUTO_TEST_CASE(rpc_insightexplorer)
|
||||
{
|
||||
CheckRPCThrows("getaddressmempool \"a\"",
|
||||
"Error: getaddressmempool is disabled. "
|
||||
"Run './zcash-cli help getaddressmempool' for instructions on how to enable this feature.");
|
||||
CheckRPCThrows("getaddressutxos \"a\"",
|
||||
"Error: getaddressutxos is disabled. "
|
||||
"Run './zcash-cli help getaddressutxos' for instructions on how to enable this feature.");
|
||||
CheckRPCThrows("getaddressdeltas \"a\"",
|
||||
"Error: getaddressdeltas is disabled. "
|
||||
"Run './zcash-cli help getaddressdeltas' for instructions on how to enable this feature.");
|
||||
CheckRPCThrows("getaddressbalance \"a\"",
|
||||
"Error: getaddressbalance is disabled. "
|
||||
"Run './zcash-cli help getaddressbalance' for instructions on how to enable this feature.");
|
||||
CheckRPCThrows("getaddresstxids \"a\"",
|
||||
"Error: getaddresstxids is disabled. "
|
||||
"Run './zcash-cli help getaddresstxids' for instructions on how to enable this feature.");
|
||||
|
||||
fExperimentalMode = true;
|
||||
fInsightExplorer = true;
|
||||
|
||||
// must be a legal mainnet address
|
||||
const string addr = "t1T3G72ToPuCDTiCEytrU1VUBRHsNupEBut";
|
||||
BOOST_CHECK_NO_THROW(CallRPC("getaddressmempool \"" + addr + "\""));
|
||||
BOOST_CHECK_NO_THROW(CallRPC("getaddressmempool {\"addresses\":[\"" + addr + "\"]}"));
|
||||
BOOST_CHECK_NO_THROW(CallRPC("getaddressmempool {\"addresses\":[\"" + addr + "\",\"" + addr + "\"]}"));
|
||||
|
||||
BOOST_CHECK_NO_THROW(CallRPC("getaddressutxos {\"addresses\":[],\"chainInfo\":true}"));
|
||||
CheckRPCThrows("getaddressutxos {}",
|
||||
"Addresses is expected to be an array");
|
||||
CheckRPCThrows("getaddressutxos {\"addressesmisspell\":[]}",
|
||||
"Addresses is expected to be an array");
|
||||
CheckRPCThrows("getaddressutxos {\"addresses\":[],\"chainInfo\":1}",
|
||||
"JSON value is not a boolean as expected");
|
||||
|
||||
BOOST_CHECK_NO_THROW(CallRPC("getaddressdeltas {\"addresses\":[]}"));
|
||||
CheckRPCThrows("getaddressdeltas {\"addresses\":[],\"start\":0,\"end\":0,\"chainInfo\":true}",
|
||||
"Start and end are expected to be greater than zero");
|
||||
CheckRPCThrows("getaddressdeltas {\"addresses\":[],\"start\":3,\"end\":2,\"chainInfo\":true}",
|
||||
"End value is expected to be greater than start");
|
||||
// in this test environment, only the genesis block (0) exists
|
||||
CheckRPCThrows("getaddressdeltas {\"addresses\":[],\"start\":2,\"end\":3,\"chainInfo\":true}",
|
||||
"Start or end is outside chain range");
|
||||
|
||||
BOOST_CHECK_NO_THROW(CallRPC("getaddressbalance {\"addresses\":[]}"));
|
||||
|
||||
BOOST_CHECK_NO_THROW(CallRPC("getaddresstxids {\"addresses\":[]}"));
|
||||
CheckRPCThrows("getaddresstxids {\"addresses\":[],\"start\":0,\"end\":0,\"chainInfo\":true}",
|
||||
"Start and end are expected to be greater than zero");
|
||||
CheckRPCThrows("getaddresstxids {\"addresses\":[],\"start\":3,\"end\":2,\"chainInfo\":true}",
|
||||
"End value is expected to be greater than start");
|
||||
// in this test environment, only the genesis block (0) exists
|
||||
CheckRPCThrows("getaddresstxids {\"addresses\":[],\"start\":2,\"end\":3,\"chainInfo\":true}",
|
||||
"Start or end is outside chain range");
|
||||
|
||||
// revert
|
||||
fExperimentalMode = false;
|
||||
fInsightExplorer = false;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
@ -1656,19 +1656,6 @@ BOOST_AUTO_TEST_CASE(rpc_z_shieldcoinbase_internals)
|
|||
|
||||
}
|
||||
|
||||
|
||||
void CheckRPCThrows(std::string rpcString, std::string expectedErrorMessage) {
|
||||
try {
|
||||
CallRPC(rpcString);
|
||||
// Note: CallRPC catches (const UniValue& objError) and rethrows a runtime_error
|
||||
BOOST_FAIL("Should have caused an error");
|
||||
} catch (const std::runtime_error& e) {
|
||||
BOOST_CHECK_EQUAL(expectedErrorMessage, e.what());
|
||||
} catch(const std::exception& e) {
|
||||
BOOST_FAIL(std::string("Unexpected exception: ") + typeid(e).name() + ", message=\"" + e.what() + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(rpc_z_mergetoaddress_parameters)
|
||||
{
|
||||
SelectParams(CBaseChainParams::TESTNET);
|
||||
|
|
|
@ -68,4 +68,7 @@ struct TestMemPoolEntryHelper
|
|||
TestMemPoolEntryHelper &SpendsCoinbase(bool _flag) { spendsCoinbase = _flag; return *this; }
|
||||
TestMemPoolEntryHelper &BranchId(uint32_t _branchId) { nBranchId = _branchId; return *this; }
|
||||
};
|
||||
|
||||
void CheckRPCThrows(std::string rpcString, std::string expectedErrorMessage);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -326,7 +326,7 @@ bool CBlockTreeDB::ReadAddressUnspentIndex(uint160 addressHash, int type, std::v
|
|||
if (!(pcursor->GetKey(key) && key.first == DB_ADDRESSUNSPENTINDEX && key.second.hashBytes == addressHash))
|
||||
break;
|
||||
CAddressUnspentValue nValue;
|
||||
if (pcursor->GetValue(nValue))
|
||||
if (!pcursor->GetValue(nValue))
|
||||
return error("failed to get address unspent value");
|
||||
unspentOutputs.push_back(make_pair(key.second, nValue));
|
||||
pcursor->Next();
|
||||
|
|
|
@ -121,6 +121,65 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry,
|
|||
return true;
|
||||
}
|
||||
|
||||
void CTxMemPool::addAddressIndex(const CTxMemPoolEntry &entry, const CCoinsViewCache &view)
|
||||
{
|
||||
LOCK(cs);
|
||||
const CTransaction& tx = entry.GetTx();
|
||||
std::vector<CMempoolAddressDeltaKey> inserted;
|
||||
|
||||
uint256 txhash = tx.GetHash();
|
||||
for (unsigned int j = 0; j < tx.vin.size(); j++) {
|
||||
const CTxIn input = tx.vin[j];
|
||||
const CTxOut &prevout = view.GetOutputFor(input);
|
||||
CScript::ScriptType type = prevout.scriptPubKey.GetType();
|
||||
if (type == CScript::UNKNOWN)
|
||||
continue;
|
||||
CMempoolAddressDeltaKey key(type, prevout.scriptPubKey.AddressHash(), txhash, j, 1);
|
||||
CMempoolAddressDelta delta(entry.GetTime(), prevout.nValue * -1, input.prevout.hash, input.prevout.n);
|
||||
mapAddress.insert(make_pair(key, delta));
|
||||
inserted.push_back(key);
|
||||
}
|
||||
|
||||
for (unsigned int j = 0; j < tx.vout.size(); j++) {
|
||||
const CTxOut &out = tx.vout[j];
|
||||
CScript::ScriptType type = out.scriptPubKey.GetType();
|
||||
if (type == CScript::UNKNOWN)
|
||||
continue;
|
||||
CMempoolAddressDeltaKey key(type, out.scriptPubKey.AddressHash(), txhash, j, 0);
|
||||
mapAddress.insert(make_pair(key, CMempoolAddressDelta(entry.GetTime(), out.nValue)));
|
||||
inserted.push_back(key);
|
||||
}
|
||||
|
||||
mapAddressInserted.insert(make_pair(txhash, inserted));
|
||||
}
|
||||
|
||||
void CTxMemPool::getAddressIndex(
|
||||
const std::vector<std::pair<uint160, int>>& addresses,
|
||||
std::vector<std::pair<CMempoolAddressDeltaKey, CMempoolAddressDelta>>& results)
|
||||
{
|
||||
LOCK(cs);
|
||||
for (const auto& it : addresses) {
|
||||
auto ait = mapAddress.lower_bound(CMempoolAddressDeltaKey(it.second, it.first));
|
||||
while (ait != mapAddress.end() && (*ait).first.addressBytes == it.first && (*ait).first.type == it.second) {
|
||||
results.push_back(*ait);
|
||||
ait++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CTxMemPool::removeAddressIndex(const uint256& txhash)
|
||||
{
|
||||
LOCK(cs);
|
||||
auto it = mapAddressInserted.find(txhash);
|
||||
|
||||
if (it != mapAddressInserted.end()) {
|
||||
std::vector<CMempoolAddressDeltaKey> keys = it->second;
|
||||
for (const auto& mit : keys) {
|
||||
mapAddress.erase(mit);
|
||||
}
|
||||
mapAddressInserted.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void CTxMemPool::remove(const CTransaction &origTx, std::list<CTransaction>& removed, bool fRecursive)
|
||||
{
|
||||
|
@ -172,6 +231,9 @@ void CTxMemPool::remove(const CTransaction &origTx, std::list<CTransaction>& rem
|
|||
mapTx.erase(hash);
|
||||
nTransactionsUpdated++;
|
||||
minerPolicyEstimator->removeTx(hash);
|
||||
// insightexplorer
|
||||
if (fAddressIndex)
|
||||
removeAddressIndex(hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "coins.h"
|
||||
#include "primitives/transaction.h"
|
||||
#include "sync.h"
|
||||
#include "addressindex.h"
|
||||
|
||||
#undef foreach
|
||||
#include "boost/multi_index_container.hpp"
|
||||
|
@ -152,6 +153,12 @@ public:
|
|||
|
||||
mutable CCriticalSection cs;
|
||||
indexed_transaction_set mapTx;
|
||||
|
||||
private:
|
||||
std::map<CMempoolAddressDeltaKey, CMempoolAddressDelta, CMempoolAddressDeltaKeyCompare> mapAddress;
|
||||
std::map<uint256, std::vector<CMempoolAddressDeltaKey> > mapAddressInserted;
|
||||
|
||||
public:
|
||||
std::map<COutPoint, CInPoint> mapNextTx;
|
||||
std::map<uint256, std::pair<double, CAmount> > mapDeltas;
|
||||
|
||||
|
@ -168,6 +175,12 @@ public:
|
|||
void setSanityCheck(double dFrequency = 1.0) { nCheckFrequency = static_cast<uint32_t>(dFrequency * 4294967295.0); }
|
||||
|
||||
bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, bool fCurrentEstimate = true);
|
||||
|
||||
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 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