Auto merge of #3972 - LarryRuane:3708-getblockhashesrpc, r=str4d

3708 getblockhashesrpc

Part of #3708
This commit is contained in:
Homu 2019-08-09 08:00:17 -07:00
commit 5fd1f5afc0
9 changed files with 246 additions and 20 deletions

View File

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

94
qa/rpc-tests/timestampindex.py Executable file
View File

@ -0,0 +1,94 @@
#!/usr/bin/env python
# Copyright (c) 2019 The Zcash developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
#
# Test timestampindex generation and fetching for insightexplorer
import sys; assert sys.version_info < (3,), ur"This script does not run under Python 3. Please use Python 2.7.x."
import time
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
initialize_chain_clean,
start_nodes,
stop_nodes,
connect_nodes,
wait_bitcoinds,
)
class TimestampIndexTest(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):
blockhashes = []
print "Mining blocks..."
for _ in range(8):
blockhashes.extend(self.nodes[0].generate(1))
time.sleep(1)
self.sync_all()
times = [self.nodes[1].getblock(b)['time'] for b in blockhashes]
assert_equal(blockhashes, self.nodes[1].getblockhashes(times[0]+100, 0))
# test various ranges; the api returns blocks have times LESS THAN
# 'high' (first argument), not less than or equal, hence the +1
assert_equal(blockhashes[0:8], self.nodes[1].getblockhashes(times[8-1]+1, times[0]))
assert_equal(blockhashes[2:6], self.nodes[1].getblockhashes(times[6-1]+1, times[2]))
assert_equal(blockhashes[5:8], self.nodes[1].getblockhashes(times[8-1]+1, times[5]))
assert_equal(blockhashes[6:7], self.nodes[1].getblockhashes(times[7-1]+1, times[6]))
assert_equal(blockhashes[4:6], self.nodes[1].getblockhashes(times[6-1]+1, times[4]))
assert_equal(blockhashes[1:1], self.nodes[1].getblockhashes(times[1-1]+1, times[1]))
# Restart all nodes to ensure indices are saved to disk and recovered
stop_nodes(self.nodes)
wait_bitcoinds()
self.setup_network()
# generating multiple blocks within the same second should
# result in timestamp index entries with unique times
# (not realistic but there is logic to ensure this)
blockhashes = self.nodes[0].generate(10)
self.sync_all()
firsttime = self.nodes[1].getblock(blockhashes[0])['time']
assert_equal(blockhashes, self.nodes[1].getblockhashes(firsttime+10+1, firsttime))
# the api can also return 'logical' times, which is the key of the
# timestamp index (the content being blockhash). Logical times are
# block times when possible, but since keys must be unique, and the
# previous 10 block were generated in much less than 10 seconds,
# each logical time should be one greater than the previous.
results = self.nodes[1].getblockhashes(
firsttime+10+1, firsttime,
{'logicalTimes': True})
ltimes = [r['logicalts'] for r in results]
assert_equal(ltimes, range(firsttime, firsttime+10))
# there's also a flag to exclude orphaned blocks; results should
# be the same in this test
assert_equal(
results,
self.nodes[1].getblockhashes(
firsttime+10+1, firsttime,
{'logicalTimes': True, 'noOrphans': True}))
if __name__ == '__main__':
TimestampIndexTest().main()

View File

@ -1617,11 +1617,23 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
return true;
}
bool GetTimestampIndex(unsigned int high, unsigned int low, bool fActiveOnly,
std::vector<std::pair<uint256, unsigned int> > &hashes)
{
if (!fTimestampIndex)
return error("Timestamp index not enabled");
if (!pblocktree->ReadTimestampIndex(high, low, fActiveOnly, hashes))
return error("Unable to get hashes for timestamps");
return true;
}
bool GetSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value)
{
AssertLockHeld(cs_main);
if (!fSpentIndex)
return false;
return error("spent index not enabled");
if (mempool.getSpentIndex(key, value))
return true;
return pblocktree->ReadSpentIndex(key, value);
@ -4450,9 +4462,10 @@ bool static LoadBlockIndexDB()
// insightexplorer
// Check whether block explorer features are enabled
pblocktree->ReadFlag("insightexplorer", fInsightExplorer);
LogPrintf("%s: insight explorer %s\n", __func__, fAddressIndex ? "enabled" : "disabled");
LogPrintf("%s: insight explorer %s\n", __func__, fInsightExplorer ? "enabled" : "disabled");
fAddressIndex = fInsightExplorer;
fSpentIndex = fInsightExplorer;
fTimestampIndex = fInsightExplorer;
// Fill in-memory data
BOOST_FOREACH(const PAIRTYPE(uint256, CBlockIndex*)& item, mapBlockIndex)

View File

@ -151,6 +151,9 @@ extern bool fAddressIndex;
// Maintain a full spent index, used to query the spending txid and input index for an outpoint
extern bool fSpentIndex;
// Maintain a full timestamp index, used to query for blocks within a time range
extern bool fTimestampIndex;
// END insightexplorer
extern bool fIsBareMultisigStd;
@ -445,6 +448,8 @@ bool GetAddressIndex(const uint160& addressHash, int type,
int start = 0, int end = 0);
bool GetAddressUnspent(const uint160& addressHash, int type,
std::vector<CAddressUnspentDbEntry>& unspentOutputs);
bool GetTimestampIndex(unsigned int high, unsigned int low, bool fActiveOnly,
std::vector<std::pair<uint256, unsigned int> > &hashes);
/** Functions for disk access for blocks */
bool WriteBlockToDisk(const CBlock& block, CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& messageStart);

View File

@ -190,6 +190,7 @@ UniValue blockToDeltasJSON(const CBlock& block, const CBlockIndex* blockindex)
if (IsValidDestination(dest)) {
delta.push_back(Pair("address", EncodeDestination(dest)));
}
delta.push_back(Pair("address", EncodeDestination(dest)));
delta.push_back(Pair("satoshis", out.nValue));
delta.push_back(Pair("index", (int)k));
@ -414,7 +415,7 @@ UniValue getblockdeltas(const UniValue& params, bool fHelp)
if (fHelp || params.size() != 1)
throw runtime_error(
"getblockdeltas \"blockhash\"\n"
"\nReturns the txid and index where an output is spent.\n"
"\nReturns information about the given block and its transactions.\n"
+ disabledMsg +
"\nArguments:\n"
"1. \"hash\" (string, required) The block hash\n"
@ -425,12 +426,12 @@ UniValue getblockdeltas(const UniValue& params, bool fHelp)
" \"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"
" \"merkleroot\": \"hash\", (hexstring) block Merkle root\n"
" \"deltas\": [\n"
" {\n"
" \"txid\": \"hash\", (string) transaction ID\n"
" \"index\": n, (numeric) tx index in block\n"
" \"inputs\": [\n"
" \"txid\": \"hash\", (hexstring) transaction ID\n"
" \"index\": n, (numeric) The offset of the tx in the block\n"
" \"inputs\": [ (array of json objects)\n"
" {\n"
" \"address\": \"taddr\", (string) transparent address\n"
" \"satoshis\": n, (numeric) negative of spend amount\n"
@ -439,7 +440,7 @@ UniValue getblockdeltas(const UniValue& params, bool fHelp)
" \"prevout\": n (numeric) source utxo index\n"
" }, ...\n"
" ],\n"
" \"outputs\": [\n"
" \"outputs\": [ (array of json objects)\n"
" {\n"
" \"address\": \"taddr\", (string) transparent address\n"
" \"satoshis\": n, (numeric) amount\n"
@ -448,14 +449,14 @@ UniValue getblockdeltas(const UniValue& params, bool fHelp)
" ]\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"
" \"time\" : n, (numeric) The block version\n"
" \"mediantime\": n, (numeric) The most recent blocks' ave time\n"
" \"nonce\" : \"nonce\", (hex string) The nonce\n"
" \"bits\" : \"1d00ffff\", (hex string) The bits\n"
" \"difficulty\": n, (numeric) the current difficulty\n"
" \"chainwork\": \"xxxx\" (hex string) total amount of work in active chain\n"
" \"previousblockhash\" : \"hash\",(hex string) The hash of the previous block\n"
" \"nextblockhash\" : \"hash\" (hex string) The hash of the next block\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("getblockdeltas", "00227e566682aebd6a7a5b772c96d7a999cadaebeaf1ce96f4191a3aad58b00b")
@ -485,6 +486,89 @@ UniValue getblockdeltas(const UniValue& params, bool fHelp)
return blockToDeltasJSON(block, pblockindex);
}
// insightexplorer
UniValue getblockhashes(const UniValue& params, bool fHelp)
{
std::string enableArg = "insightexplorer";
bool fEnableGetBlockHashes = fExperimentalMode && fInsightExplorer;
std::string disabledMsg = "";
if (!fEnableGetBlockHashes) {
disabledMsg = experimentalDisabledHelpMsg("getblockhashes", enableArg);
}
if (fHelp || params.size() < 2)
throw runtime_error(
"getblockhashes high low ( {\"noOrphans\": true|false, \"logicalTimes\": true|false} )\n"
"\nReturns array of hashes of blocks within the timestamp range provided,\n"
"\ngreater or equal to low, less than high.\n"
+ disabledMsg +
"\nArguments:\n"
"1. high (numeric, required) The newer block timestamp\n"
"2. low (numeric, required) The older block timestamp\n"
"3. options (string, optional) A json object\n"
" {\n"
" \"noOrphans\": true|false (boolean) will only include blocks on the main chain\n"
" \"logicalTimes\": true|false (boolean) will include logical timestamps with hashes\n"
" }\n"
"\nResult:\n"
"[\n"
" \"xxxx\" (hex string) The block hash\n"
"]\n"
"or\n"
"[\n"
" {\n"
" \"blockhash\": \"xxxx\" (hex string) The block hash\n"
" \"logicalts\": n (numeric) The logical timestamp\n"
" }\n"
"]\n"
"\nExamples:\n"
+ HelpExampleCli("getblockhashes", "1558141697 1558141576")
+ HelpExampleRpc("getblockhashes", "1558141697, 1558141576")
+ HelpExampleCli("getblockhashes", "1558141697 1558141576 '{\"noOrphans\":false, \"logicalTimes\":true}'")
);
if (!fEnableGetBlockHashes) {
throw JSONRPCError(RPC_MISC_ERROR, "Error: getblockhashes is disabled. "
"Run './zcash-cli help getblockhashes' for instructions on how to enable this feature.");
}
unsigned int high = params[0].get_int();
unsigned int low = params[1].get_int();
bool fActiveOnly = false;
bool fLogicalTS = false;
if (params.size() > 2) {
UniValue noOrphans = find_value(params[2].get_obj(), "noOrphans");
if (!noOrphans.isNull())
fActiveOnly = noOrphans.get_bool();
UniValue returnLogical = find_value(params[2].get_obj(), "logicalTimes");
if (!returnLogical.isNull())
fLogicalTS = returnLogical.get_bool();
}
std::vector<std::pair<uint256, unsigned int> > blockHashes;
{
LOCK(cs_main);
if (!GetTimestampIndex(high, low, fActiveOnly, blockHashes)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"No information available for block hashes");
}
}
UniValue result(UniValue::VARR);
for (std::vector<std::pair<uint256, unsigned int> >::const_iterator it=blockHashes.begin();
it!=blockHashes.end(); it++) {
if (fLogicalTS) {
UniValue item(UniValue::VOBJ);
item.push_back(Pair("blockhash", it->first.GetHex()));
item.push_back(Pair("logicalts", (int)it->second));
result.push_back(item);
} else {
result.push_back(it->first.GetHex());
}
}
return result;
}
UniValue getblockhash(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
@ -1220,6 +1304,7 @@ static const CRPCCommand commands[] =
// insightexplorer
{ "blockchain", "getblockdeltas", &getblockdeltas, false },
{ "blockchain", "getblockhashes", &getblockhashes, true },
/* Not shown in help */
{ "hidden", "invalidateblock", &invalidateblock, true },

View File

@ -105,6 +105,10 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "getaddressdeltas", 0},
{ "getaddressutxos", 0},
{ "getaddressmempool", 0},
{ "getblockhashes", 0},
{ "getblockhashes", 1},
{ "getblockhashes", 2},
{ "getblockdeltas", 0},
{ "zcrawjoinsplit", 1 },
{ "zcrawjoinsplit", 2 },
{ "zcrawjoinsplit", 3 },

View File

@ -360,7 +360,8 @@ BOOST_AUTO_TEST_CASE(rpc_getnetworksolps)
BOOST_CHECK_NO_THROW(CallRPC("getnetworksolps 120 -1"));
}
// Test parameter processing (not functionality)
// Test parameter processing (not functionality).
// These tests also ensure that src/rpc/client.cpp has the correct entries.
BOOST_AUTO_TEST_CASE(rpc_insightexplorer)
{
CheckRPCThrows("getaddressmempool \"a\"",
@ -384,9 +385,17 @@ BOOST_AUTO_TEST_CASE(rpc_insightexplorer)
CheckRPCThrows("getblockdeltas \"a\"",
"Error: getblockdeltas is disabled. "
"Run './zcash-cli help getblockdeltas' for instructions on how to enable this feature.");
CheckRPCThrows("getblockhashes 0 0",
"Error: getblockhashes is disabled. "
"Run './zcash-cli help getblockhashes' for instructions on how to enable this feature.");
// During startup of the real system, fInsightExplorer ("-insightexplorer")
// automatically enables the next three, but not here, must explicitly enable.
fExperimentalMode = true;
fInsightExplorer = true;
fAddressIndex = true;
fSpentIndex = true;
fTimestampIndex = true;
// must be a legal mainnet address
const string addr = "t1T3G72ToPuCDTiCEytrU1VUBRHsNupEBut";
@ -430,12 +439,27 @@ BOOST_AUTO_TEST_CASE(rpc_insightexplorer)
CheckRPCThrows("getspentinfo {\"txid\":\"hello\",\"index\":0}",
"txid must be hexadecimal string (not 'hello')");
CheckRPCThrows("getblockdeltas \"00040fe8ec8471911baa1db1266ea15dd06b4a8a5c453883c000b031973dce08\"",
// only the mainnet genesis block exists
BOOST_CHECK_NO_THROW(CallRPC("getblockdeltas \"00040fe8ec8471911baa1db1266ea15dd06b4a8a5c453883c000b031973dce08\""));
// damage the block hash (change last digit)
CheckRPCThrows("getblockdeltas \"00040fe8ec8471911baa1db1266ea15dd06b4a8a5c453883c000b031973dce09\"",
"Block not found");
BOOST_CHECK_NO_THROW(CallRPC("getblockhashes 1477641360 1477641360"));
BOOST_CHECK_NO_THROW(CallRPC("getblockhashes 1477641360 1477641360 {\"noOrphans\":true,\"logicalTimes\":true}"));
// Unfortunately, an unknown or mangled key is ignored
BOOST_CHECK_NO_THROW(CallRPC("getblockhashes 1477641360 1477641360 {\"AAAnoOrphans\":true,\"logicalTimes\":true}"));
CheckRPCThrows("getblockhashes 1477641360 1477641360 {\"noOrphans\":true,\"logicalTimes\":1}",
"JSON value is not a boolean as expected");
CheckRPCThrows("getblockhashes 1477641360 1477641360 {\"noOrphans\":True,\"logicalTimes\":false}",
"Error parsing JSON:{\"noOrphans\":True,\"logicalTimes\":false}");
// revert
fExperimentalMode = false;
fInsightExplorer = false;
fAddressIndex = false;
fSpentIndex = false;
fTimestampIndex = false;
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -399,7 +399,7 @@ bool CBlockTreeDB::WriteTimestampIndex(const CTimestampIndexKey &timestampIndex)
return WriteBatch(batch);
}
bool CBlockTreeDB::ReadTimestampIndex(const unsigned int &high, const unsigned int &low,
bool CBlockTreeDB::ReadTimestampIndex(unsigned int high, unsigned int low,
const bool fActiveOnly, std::vector<std::pair<uint256, unsigned int> > &hashes)
{
boost::scoped_ptr<CDBIterator> pcursor(NewIterator());

View File

@ -125,7 +125,7 @@ public:
bool ReadSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value);
bool UpdateSpentIndex(const std::vector<CSpentIndexDbEntry> &vect);
bool WriteTimestampIndex(const CTimestampIndexKey &timestampIndex);
bool ReadTimestampIndex(const unsigned int &high, const unsigned int &low,
bool ReadTimestampIndex(unsigned int high, unsigned int low,
const bool fActiveOnly, std::vector<std::pair<uint256, unsigned int> > &vect);
bool WriteTimestampBlockIndex(const CTimestampBlockIndexKey &blockhashIndex,
const CTimestampBlockIndexValue &logicalts);