Auto merge of #3972 - LarryRuane:3708-getblockhashesrpc, r=str4d
3708 getblockhashesrpc Part of #3708
This commit is contained in:
commit
5fd1f5afc0
|
@ -59,6 +59,7 @@ testScripts=(
|
|||
'reindex.py'
|
||||
'addressindex.py'
|
||||
'spentindex.py'
|
||||
'timestampindex.py'
|
||||
'decodescript.py'
|
||||
'blockchain.py'
|
||||
'disablewallet.py'
|
||||
|
|
|
@ -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()
|
17
src/main.cpp
17
src/main.cpp
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -399,7 +399,7 @@ bool CBlockTreeDB::WriteTimestampIndex(const CTimestampIndexKey ×tampIndex)
|
|||
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());
|
||||
|
|
|
@ -125,7 +125,7 @@ public:
|
|||
bool ReadSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value);
|
||||
bool UpdateSpentIndex(const std::vector<CSpentIndexDbEntry> &vect);
|
||||
bool WriteTimestampIndex(const CTimestampIndexKey ×tampIndex);
|
||||
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);
|
||||
|
|
Loading…
Reference in New Issue