diff --git a/doc/release-notes.md b/doc/release-notes.md index 91240196ba..9f493c1d24 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -1,48 +1,53 @@ Bitcoin ABC version 0.15.0 is now available from: This release includes the following features and fixes: - Low level RPC error code changes (D500 / backport of PR9853) - Reserve block space for high priority transactions (D485) Low-level RPC changes (D500) ---------------------------- - Error codes have been updated to be more accurate for the following error cases: - `getblock` now returns RPC_MISC_ERROR if the block can't be found on disk (for example if the block has been pruned). Previously returned RPC_INTERNAL_ERROR. - `pruneblockchain` now returns RPC_MISC_ERROR if the blocks cannot be pruned because the node is not in pruned mode. Previously returned RPC_METHOD_NOT_FOUND. - `pruneblockchain` now returns RPC_INVALID_PARAMETER if the blocks cannot be pruned because the supplied timestamp is too late. Previously returned RPC_INTERNAL_ERROR. - `pruneblockchain` now returns RPC_MISC_ERROR if the blocks cannot be pruned because the blockchain is too short. Previously returned RPC_INTERNAL_ERROR. - `setban` now returns RPC_CLIENT_INVALID_IP_OR_SUBNET if the supplied IP address or subnet is invalid. Previously returned RPC_CLIENT_NODE_ALREADY_ADDED. - `setban` now returns RPC_CLIENT_INVALID_IP_OR_SUBNET if the user tries to unban a node that has not previously been banned. Previously returned RPC_MISC_ERROR. - `removeprunedfunds` now returns RPC_WALLET_ERROR if bitcoind is unable to remove the transaction. Previously returned RPC_INTERNAL_ERROR. - `removeprunedfunds` now returns RPC_INVALID_PARAMETER if the transaction does not exist in the wallet. Previously returned RPC_INTERNAL_ERROR. - `fundrawtransaction` now returns RPC_INVALID_ADDRESS_OR_KEY if an invalid change address is provided. Previously returned RPC_INVALID_PARAMETER. - `fundrawtransaction` now returns RPC_WALLET_ERROR if bitcoind is unable to create the transaction. The error message provides further details. Previously returned RPC_INTERNAL_ERROR. + - The `gettxoutsetinfo` response now contains `disk_size` and `bogosize` instead of + `bytes_serialized`. The first is a more accurate estimate of actual disk usage, but + is not deterministic. The second is unrelated to disk usage, but is a + database-independent metric of UTXO set size: it counts every UTXO entry as 50 + the + length of its scriptPubKey. Reserve block space for high priority transactions (D485) --------------------------------------------------------- By default reserve 5% of the max generated block size parameter to hiprio transactions. Hence a `bitcoind` instance running with an unmodified configuration will reserve 100K for high priority transactions. The parameter name used for this configuration `blockprioritypercentage`. While introducing this new parameter we deprecated `blockprioritysize`(it was used to specify the amount of high prio reserved area in byte). A transaction is considered high priority if its priority is higher than this threshold: `COIN * 144 / 250`, where COIN is the value of a one bitcoin UTXO expressed in satoshis. Thus a transaction who as an input of 1 bitcoin and are 144 blocks old and whose size is 250 bytes is considered the priority cut-off. diff --git a/qa/rpc-tests/blockchain.py b/qa/rpc-tests/blockchain.py index e9c1e1ef13..dd6a928b74 100755 --- a/qa/rpc-tests/blockchain.py +++ b/qa/rpc-tests/blockchain.py @@ -1,110 +1,116 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2016 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. # # Test RPC calls related to blockchain state. Tests correspond to code in # rpc/blockchain.cpp. # from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.authproxy import JSONRPCException from test_framework.util import ( assert_equal, assert_raises, assert_is_hex_string, assert_is_hash_string, start_nodes, connect_nodes_bi, ) class BlockchainTest(BitcoinTestFramework): """ Test blockchain-related RPC calls: - gettxoutsetinfo - verifychain """ def __init__(self): super().__init__() self.setup_clean_chain = False self.num_nodes = 1 def run_test(self): self._test_gettxoutsetinfo() self._test_getblockheader() self.nodes[0].verifychain(4, 0) def _test_gettxoutsetinfo(self): node = self.nodes[0] res = node.gettxoutsetinfo() assert_equal(res['total_amount'], Decimal('8725.00000000')) assert_equal(res['transactions'], 200) assert_equal(res['height'], 200) assert_equal(res['txouts'], 200) - assert_equal(res['bytes_serialized'], 13924) + assert_equal(res['bogosize'], 17000), assert_equal(res['bestblock'], node.getblockhash(200)) + size = res['disk_size'] + assert size > 6400 + assert size < 64000 assert_equal(len(res['bestblock']), 64) assert_equal(len(res['hash_serialized']), 64) self.log.info( "Test that gettxoutsetinfo() works for blockchain with just the genesis block") b1hash = node.getblockhash(1) node.invalidateblock(b1hash) res2 = node.gettxoutsetinfo() assert_equal(res2['transactions'], 0) assert_equal(res2['total_amount'], Decimal('0')) assert_equal(res2['height'], 0) assert_equal(res2['txouts'], 0) + assert_equal(res2['bogosize'], 0), assert_equal(res2['bestblock'], node.getblockhash(0)) assert_equal(len(res2['hash_serialized']), 64) self.log.info( "Test that gettxoutsetinfo() returns the same result after invalidate/reconsider block") node.reconsiderblock(b1hash) res3 = node.gettxoutsetinfo() assert_equal(res['total_amount'], res3['total_amount']) assert_equal(res['transactions'], res3['transactions']) assert_equal(res['height'], res3['height']) assert_equal(res['txouts'], res3['txouts']) + assert_equal(res['bogosize'], res3['bogosize']) assert_equal(res['bestblock'], res3['bestblock']) assert_equal(res['hash_serialized'], res3['hash_serialized']) def _test_getblockheader(self): node = self.nodes[0] assert_raises( JSONRPCException, lambda: node.getblockheader('nonsense')) besthash = node.getbestblockhash() secondbesthash = node.getblockhash(199) header = node.getblockheader(besthash) assert_equal(header['hash'], besthash) assert_equal(header['height'], 200) assert_equal(header['confirmations'], 1) assert_equal(header['previousblockhash'], secondbesthash) assert_is_hex_string(header['chainwork']) assert_is_hash_string(header['hash']) assert_is_hash_string(header['previousblockhash']) assert_is_hash_string(header['merkleroot']) assert_is_hash_string(header['bits'], length=None) assert isinstance(header['time'], int) assert isinstance(header['mediantime'], int) assert isinstance(header['nonce'], int) assert isinstance(header['version'], int) assert isinstance(int(header['versionHex'], 16), int) assert isinstance(header['difficulty'], Decimal) + if __name__ == '__main__': BlockchainTest().main() diff --git a/src/coins.cpp b/src/coins.cpp index 3e8499ce41..40adf54000 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -1,382 +1,385 @@ // Copyright (c) 2012-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "coins.h" #include "consensus/consensus.h" #include "memusage.h" #include "random.h" #include /** * calculate number of bytes for the bitmask, and its number of non-zero bytes * each bit in the bitmask represents the availability of one output, but the * availabilities of the first two outputs are encoded separately */ void CCoins::CalcMaskSize(unsigned int &nBytes, unsigned int &nNonzeroBytes) const { unsigned int nLastUsedByte = 0; for (unsigned int b = 0; 2 + b * 8 < vout.size(); b++) { bool fZero = true; for (unsigned int i = 0; i < 8 && 2 + b * 8 + i < vout.size(); i++) { if (!vout[2 + b * 8 + i].IsNull()) { fZero = false; continue; } } if (!fZero) { nLastUsedByte = b + 1; nNonzeroBytes++; } } nBytes += nLastUsedByte; } bool CCoins::Spend(uint32_t nPos) { if (nPos >= vout.size() || vout[nPos].IsNull()) return false; vout[nPos].SetNull(); Cleanup(); return true; } bool CCoinsView::GetCoins(const uint256 &txid, CCoins &coins) const { return false; } bool CCoinsView::HaveCoins(const uint256 &txid) const { return false; } uint256 CCoinsView::GetBestBlock() const { return uint256(); } bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; } CCoinsViewCursor *CCoinsView::Cursor() const { return 0; } CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) {} bool CCoinsViewBacked::GetCoins(const uint256 &txid, CCoins &coins) const { return base->GetCoins_DONOTUSE(txid, coins); } bool CCoinsViewBacked::HaveCoins(const uint256 &txid) const { return base->HaveCoins_DONOTUSE(txid); } uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); } void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; } bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); } CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); } +size_t CCoinsViewBacked::EstimateSize() const { + return base->EstimateSize(); +} SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits::max())), k1(GetRand(std::numeric_limits::max())) {} CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), cachedCoinsUsage(0) {} size_t CCoinsViewCache::DynamicMemoryUsage() const { return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage; } CCoinsMap::iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const { CCoinsMap::iterator it = cacheCoins.find(txid); if (it != cacheCoins.end()) { return it; } CCoins tmp; if (!base->GetCoins_DONOTUSE(txid, tmp)) { return cacheCoins.end(); } CCoinsMap::iterator ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry())).first; tmp.swap(ret->second.coins); if (ret->second.coins.IsPruned()) { // The parent only has an empty entry for this txid; we can consider our // version as fresh. ret->second.flags = CCoinsCacheEntry::FRESH; } cachedCoinsUsage += ret->second.coins.DynamicMemoryUsage(); return ret; } bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const { CCoinsMap::const_iterator it = FetchCoins(txid); if (it != cacheCoins.end()) { coins = it->second.coins; return true; } return false; } void CCoinsViewCache::AddCoin(const COutPoint &outpoint, const Coin &coin, bool possible_overwrite) { assert(!coin.IsSpent()); if (coin.GetTxOut().scriptPubKey.IsUnspendable()) { return; } CCoinsMap::iterator it; bool inserted; std::tie(it, inserted) = cacheCoins.emplace( std::piecewise_construct, std::forward_as_tuple(outpoint.hash), std::tuple<>()); bool fresh = false; if (!inserted) { cachedCoinsUsage -= it->second.coins.DynamicMemoryUsage(); } if (!possible_overwrite) { if (it->second.coins.IsAvailable(outpoint.n)) { throw std::logic_error( "Adding new coin that replaces non-pruned entry"); } fresh = it->second.coins.IsPruned() && !(it->second.flags & CCoinsCacheEntry::DIRTY); } if (it->second.coins.vout.size() <= outpoint.n) { it->second.coins.vout.resize(outpoint.n + 1); } it->second.coins.vout[outpoint.n] = coin.GetTxOut(); it->second.coins.nHeight = coin.GetHeight(); it->second.coins.fCoinBase = coin.IsCoinBase(); it->second.flags |= CCoinsCacheEntry::DIRTY | (fresh ? CCoinsCacheEntry::FRESH : 0); cachedCoinsUsage += it->second.coins.DynamicMemoryUsage(); } void AddCoins(CCoinsViewCache &cache, const CTransaction &tx, int nHeight) { bool fCoinbase = tx.IsCoinBase(); const uint256 &txid = tx.GetHash(); for (size_t i = 0; i < tx.vout.size(); ++i) { // Pass fCoinbase as the possible_overwrite flag to AddCoin, in order to // correctly deal with the pre-BIP30 occurrances of duplicate coinbase // transactions. cache.AddCoin(COutPoint(txid, i), Coin(tx.vout[i], nHeight, fCoinbase), fCoinbase); } } bool CCoinsViewCache::SpendCoin(const COutPoint &outpoint, Coin *moveout) { CCoinsMap::iterator it = FetchCoins(outpoint.hash); if (it == cacheCoins.end()) { return false; } cachedCoinsUsage -= it->second.coins.DynamicMemoryUsage(); if (moveout && it->second.coins.IsAvailable(outpoint.n)) { *moveout = Coin(it->second.coins.vout[outpoint.n], it->second.coins.nHeight, it->second.coins.fCoinBase); } // Ignore return value: SpendCoin has no effect if no UTXO found. it->second.coins.Spend(outpoint.n); if (it->second.coins.IsPruned() && it->second.flags & CCoinsCacheEntry::FRESH) { cacheCoins.erase(it); } else { cachedCoinsUsage += it->second.coins.DynamicMemoryUsage(); it->second.flags |= CCoinsCacheEntry::DIRTY; } return true; } const CCoins *CCoinsViewCache::AccessCoins(const uint256 &txid) const { CCoinsMap::const_iterator it = FetchCoins(txid); if (it == cacheCoins.end()) { return nullptr; } return &it->second.coins; } static const Coin coinEmpty; const Coin CCoinsViewCache::AccessCoin(const COutPoint &outpoint) const { CCoinsMap::const_iterator it = FetchCoins(outpoint.hash); if (it == cacheCoins.end() || !it->second.coins.IsAvailable(outpoint.n)) { return coinEmpty; } return Coin(it->second.coins.vout[outpoint.n], it->second.coins.nHeight, it->second.coins.fCoinBase); } bool CCoinsViewCache::HaveCoins(const uint256 &txid) const { CCoinsMap::const_iterator it = FetchCoins(txid); // We're using vtx.empty() instead of IsPruned here for performance reasons, // as we only care about the case where a transaction was replaced entirely // in a reorganization (which wipes vout entirely, as opposed to spending // which just cleans individual outputs). return (it != cacheCoins.end() && !it->second.coins.vout.empty()); } bool CCoinsViewCache::HaveCoinInCache(const COutPoint &outpoint) const { CCoinsMap::const_iterator it = cacheCoins.find(outpoint.hash); return it != cacheCoins.end() && it->second.coins.IsAvailable(outpoint.n); } uint256 CCoinsViewCache::GetBestBlock() const { if (hashBlock.IsNull()) hashBlock = base->GetBestBlock(); return hashBlock; } void CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) { hashBlock = hashBlockIn; } bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn) { for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) { // Ignore non-dirty entries (optimization). if (it->second.flags & CCoinsCacheEntry::DIRTY) { CCoinsMap::iterator itUs = cacheCoins.find(it->first); if (itUs == cacheCoins.end()) { // The parent cache does not have an entry, while the child does // We can ignore it if it's both FRESH and pruned in the child if (!(it->second.flags & CCoinsCacheEntry::FRESH && it->second.coins.IsPruned())) { // Otherwise we will need to create it in the parent and // move the data up and mark it as dirty CCoinsCacheEntry &entry = cacheCoins[it->first]; entry.coins.swap(it->second.coins); cachedCoinsUsage += entry.coins.DynamicMemoryUsage(); entry.flags = CCoinsCacheEntry::DIRTY; // We can mark it FRESH in the parent if it was FRESH in the // child. Otherwise it might have just been flushed from the // parent's cache and already exist in the grandparent if (it->second.flags & CCoinsCacheEntry::FRESH) entry.flags |= CCoinsCacheEntry::FRESH; } } else { // Assert that the child cache entry was not marked FRESH if the // parent cache entry has unspent outputs. If this ever happens, // it means the FRESH flag was misapplied and there is a logic // error in the calling code. if ((it->second.flags & CCoinsCacheEntry::FRESH) && !itUs->second.coins.IsPruned()) throw std::logic_error("FRESH flag misapplied to cache " "entry for base transaction with " "spendable outputs"); // Found the entry in the parent cache if ((itUs->second.flags & CCoinsCacheEntry::FRESH) && it->second.coins.IsPruned()) { // The grandparent does not have an entry, and the child is // modified and being pruned. This means we can just delete // it from the parent. cachedCoinsUsage -= itUs->second.coins.DynamicMemoryUsage(); cacheCoins.erase(itUs); } else { // A normal modification. cachedCoinsUsage -= itUs->second.coins.DynamicMemoryUsage(); itUs->second.coins.swap(it->second.coins); cachedCoinsUsage += itUs->second.coins.DynamicMemoryUsage(); itUs->second.flags |= CCoinsCacheEntry::DIRTY; // NOTE: It is possible the child has a FRESH flag here in // the event the entry we found in the parent is pruned. But // we must not copy that FRESH flag to the parent as that // pruned state likely still needs to be communicated to the // grandparent. } } } CCoinsMap::iterator itOld = it++; mapCoins.erase(itOld); } hashBlock = hashBlockIn; return true; } bool CCoinsViewCache::Flush() { bool fOk = base->BatchWrite(cacheCoins, hashBlock); cacheCoins.clear(); cachedCoinsUsage = 0; return fOk; } void CCoinsViewCache::Uncache(const COutPoint &outpoint) { CCoinsMap::iterator it = cacheCoins.find(outpoint.hash); if (it != cacheCoins.end() && it->second.flags == 0) { cachedCoinsUsage -= it->second.coins.DynamicMemoryUsage(); cacheCoins.erase(it); } } unsigned int CCoinsViewCache::GetCacheSize() const { return cacheCoins.size(); } const CTxOut &CCoinsViewCache::GetOutputFor(const CTxIn &input) const { const CCoins *coins = AccessCoins(input.prevout.hash); assert(coins && coins->IsAvailable(input.prevout.n)); return coins->vout[input.prevout.n]; } CAmount CCoinsViewCache::GetValueIn(const CTransaction &tx) const { if (tx.IsCoinBase()) return 0; CAmount nResult = 0; for (unsigned int i = 0; i < tx.vin.size(); i++) nResult += GetOutputFor(tx.vin[i]).nValue; return nResult; } bool CCoinsViewCache::HaveInputs(const CTransaction &tx) const { if (tx.IsCoinBase()) { return true; } for (unsigned int i = 0; i < tx.vin.size(); i++) { const COutPoint &prevout = tx.vin[i].prevout; const CCoins *coins = AccessCoins(prevout.hash); if (!coins || !coins->IsAvailable(prevout.n)) { return false; } } return true; } double CCoinsViewCache::GetPriority(const CTransaction &tx, int nHeight, CAmount &inChainInputValue) const { inChainInputValue = 0; if (tx.IsCoinBase()) return 0.0; double dResult = 0.0; for (const CTxIn &txin : tx.vin) { const CCoins *coins = AccessCoins(txin.prevout.hash); assert(coins); if (!coins->IsAvailable(txin.prevout.n)) continue; if (coins->nHeight <= nHeight) { dResult += (double)(coins->vout[txin.prevout.n].nValue) * (nHeight - coins->nHeight); inChainInputValue += coins->vout[txin.prevout.n].nValue; } } return tx.ComputePriority(dResult); } CCoinsViewCursor::~CCoinsViewCursor() {} // TODO: merge with similar definition in undo.h. static const size_t MAX_OUTPUTS_PER_TX = MAX_TX_SIZE / ::GetSerializeSize(CTxOut(), SER_NETWORK, PROTOCOL_VERSION); const Coin AccessByTxid(const CCoinsViewCache &view, const uint256 &txid) { COutPoint iter(txid, 0); while (iter.n < MAX_OUTPUTS_PER_TX) { const Coin &alternate = view.AccessCoin(iter); if (!alternate.IsSpent()) { return alternate; } ++iter.n; } return coinEmpty; } diff --git a/src/coins.h b/src/coins.h index 68426e6bfe..dbdd006323 100644 --- a/src/coins.h +++ b/src/coins.h @@ -1,571 +1,575 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_COINS_H #define BITCOIN_COINS_H #include "compressor.h" #include "core_memusage.h" #include "hash.h" #include "memusage.h" #include "serialize.h" #include "uint256.h" #include #include #include /** * A UTXO entry. * * Serialized format: * - VARINT((coinbase ? 1 : 0) | (height << 1)) * - the non-spent CTxOut (via CTxOutCompressor) */ class Coin { //! Unspent transaction output. CTxOut out; //! Whether containing transaction was a coinbase and height at which the //! transaction was included into a block. uint32_t nHeightAndIsCoinBase; public: //! Empty constructor Coin() : nHeightAndIsCoinBase(0) {} //! Constructor from a CTxOut and height/coinbase information. Coin(CTxOut outIn, uint32_t nHeightIn, bool IsCoinbase) : out(std::move(outIn)), nHeightAndIsCoinBase((nHeightIn << 1) | IsCoinbase) {} uint32_t GetHeight() const { return nHeightAndIsCoinBase >> 1; } bool IsCoinBase() const { return nHeightAndIsCoinBase & 0x01; } bool IsSpent() const { return out.IsNull(); } CTxOut &GetTxOut() { return out; } const CTxOut &GetTxOut() const { return out; } void Clear() { out.SetNull(); nHeightAndIsCoinBase = 0; } template void Serialize(Stream &s) const { assert(!IsSpent()); ::Serialize(s, VARINT(nHeightAndIsCoinBase)); ::Serialize(s, CTxOutCompressor(REF(out))); } template void Unserialize(Stream &s) { ::Unserialize(s, VARINT(nHeightAndIsCoinBase)); ::Unserialize(s, REF(CTxOutCompressor(out))); } size_t DynamicMemoryUsage() const { return memusage::DynamicUsage(out.scriptPubKey); } }; /** * Pruned version of CTransaction: only retains metadata and unspent transaction outputs * * Serialized format: * - VARINT(nVersion) - DEPRECATED, always zero on new reccords. * - VARINT(nCode) * - unspentness bitvector, for vout[2] and further; least significant byte first * - the non-spent CTxOuts (via CTxOutCompressor) * - VARINT(nHeight) * * The nCode value consists of: * - bit 0: IsCoinBase() * - bit 1: vout[0] is not spent * - bit 2: vout[1] is not spent * - The higher bits encode N, the number of non-zero bytes in the following bitvector. * - In case both bit 1 and bit 2 are unset, they encode N-1, as there must be at * least one non-spent output). * * Example: 0104835800816115944e077fe7c803cfa57f29b36bf87c1d358bb85e * <><><--------------------------------------------><----> * | \ | / * version code vout[1] height * * - version = 1 * - code = 4 (vout[1] is not spent, and 0 non-zero bytes of bitvector follow) * - unspentness bitvector: as 0 non-zero bytes follow, it has length 0 * - vout[1]: 835800816115944e077fe7c803cfa57f29b36bf87c1d35 * * 8358: compact amount representation for 60000000000 (600 BCC) * * 00: special txout type pay-to-pubkey-hash * * 816115944e077fe7c803cfa57f29b36bf87c1d35: address uint160 * - height = 203998 * * * Example: 0109044086ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4eebbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa486af3b * <><><--><--------------------------------------------------><----------------------------------------------><----> * / \ \ | | / * version code unspentness vout[4] vout[16] height * * - version = 1 * - code = 9 (coinbase, neither vout[0] or vout[1] are unspent, * 2 (1, +1 because both bit 1 and bit 2 are unset) non-zero bitvector bytes follow) * - unspentness bitvector: bits 2 (0x04) and 14 (0x4000) are set, so vout[2+2] and vout[14+2] are unspent * - vout[4]: 86ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4ee * * 86ef97d579: compact amount representation for 234925952 (2.35 BCC) * * 00: special txout type pay-to-pubkey-hash * * 61b01caab50f1b8e9c50a5057eb43c2d9563a4ee: address uint160 * - vout[16]: bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4 * * bbd123: compact amount representation for 110397 (0.001 BCC) * * 00: special txout type pay-to-pubkey-hash * * 8c988f1a4a4de2161e0f50aac7f17e7f9555caa4: address uint160 * - height = 120891 * * @DISABLE FORMATING FOR THIS COMMENT@ */ class CCoins { public: //! whether transaction is a coinbase bool fCoinBase; //! unspent transaction outputs; spent outputs are .IsNull(); spent outputs //! at the end of the array are dropped std::vector vout; //! at which height this transaction was included in the active block chain int nHeight; void FromTx(const CTransaction &tx, int nHeightIn) { fCoinBase = tx.IsCoinBase(); vout = tx.vout; nHeight = nHeightIn; ClearUnspendable(); } //! construct a CCoins from a CTransaction, at a given height CCoins(const CTransaction &tx, int nHeightIn) { FromTx(tx, nHeightIn); } void Clear() { fCoinBase = false; std::vector().swap(vout); nHeight = 0; } //! empty constructor CCoins() : fCoinBase(false), vout(0), nHeight(0) {} //! remove spent outputs at the end of vout void Cleanup() { while (vout.size() > 0 && vout.back().IsNull()) { vout.pop_back(); } if (vout.empty()) { std::vector().swap(vout); } } void ClearUnspendable() { for (CTxOut &txout : vout) { if (txout.scriptPubKey.IsUnspendable()) { txout.SetNull(); } } Cleanup(); } void swap(CCoins &to) { std::swap(to.fCoinBase, fCoinBase); to.vout.swap(vout); std::swap(to.nHeight, nHeight); } //! equality test friend bool operator==(const CCoins &a, const CCoins &b) { // Empty CCoins objects are always equal. if (a.IsPruned() && b.IsPruned()) { return true; } return a.fCoinBase == b.fCoinBase && a.nHeight == b.nHeight && a.vout == b.vout; } friend bool operator!=(const CCoins &a, const CCoins &b) { return !(a == b); } void CalcMaskSize(unsigned int &nBytes, unsigned int &nNonzeroBytes) const; bool IsCoinBase() const { return fCoinBase; } template void Serialize(Stream &s) const { unsigned int nMaskSize = 0, nMaskCode = 0; CalcMaskSize(nMaskSize, nMaskCode); bool fFirst = vout.size() > 0 && !vout[0].IsNull(); bool fSecond = vout.size() > 1 && !vout[1].IsNull(); assert(fFirst || fSecond || nMaskCode); unsigned int nCode = 8 * (nMaskCode - (fFirst || fSecond ? 0 : 1)) + (fCoinBase ? 1 : 0) + (fFirst ? 2 : 0) + (fSecond ? 4 : 0); // version int nVersionDummy = 0; ::Serialize(s, VARINT(nVersionDummy)); // header code ::Serialize(s, VARINT(nCode)); // spentness bitmask for (unsigned int b = 0; b < nMaskSize; b++) { uint8_t chAvail = 0; for (size_t i = 0; i < 8 && 2 + b * 8 + i < vout.size(); i++) { if (!vout[2 + b * 8 + i].IsNull()) { chAvail |= (1 << i); } } ::Serialize(s, chAvail); } // txouts themself for (unsigned int i = 0; i < vout.size(); i++) { if (!vout[i].IsNull()) { ::Serialize(s, CTxOutCompressor(REF(vout[i]))); } } // coinbase height ::Serialize(s, VARINT(nHeight)); } template void Unserialize(Stream &s) { unsigned int nCode = 0; // version int nVersionDummy; ::Unserialize(s, VARINT(nVersionDummy)); // header code ::Unserialize(s, VARINT(nCode)); fCoinBase = nCode & 1; std::vector vAvail(2, false); vAvail[0] = (nCode & 2) != 0; vAvail[1] = (nCode & 4) != 0; unsigned int nMaskCode = (nCode / 8) + ((nCode & 6) != 0 ? 0 : 1); // spentness bitmask while (nMaskCode > 0) { uint8_t chAvail = 0; ::Unserialize(s, chAvail); for (unsigned int p = 0; p < 8; p++) { bool f = (chAvail & (1 << p)) != 0; vAvail.push_back(f); } if (chAvail != 0) { nMaskCode--; } } // txouts themself vout.assign(vAvail.size(), CTxOut()); for (unsigned int i = 0; i < vAvail.size(); i++) { if (vAvail[i]) { ::Unserialize(s, REF(CTxOutCompressor(vout[i]))); } } // coinbase height ::Unserialize(s, VARINT(nHeight)); Cleanup(); } //! mark a vout spent bool Spend(uint32_t nPos); //! check whether a particular output is still available bool IsAvailable(unsigned int nPos) const { return (nPos < vout.size() && !vout[nPos].IsNull()); } //! check whether the entire CCoins is spent //! note that only !IsPruned() CCoins can be serialized bool IsPruned() const { for (const CTxOut &out : vout) { if (!out.IsNull()) { return false; } } return true; } size_t DynamicMemoryUsage() const { size_t ret = memusage::DynamicUsage(vout); for (const CTxOut &out : vout) { ret += RecursiveDynamicUsage(out.scriptPubKey); } return ret; } }; class SaltedTxidHasher { private: /** Salt */ const uint64_t k0, k1; public: SaltedTxidHasher(); /** * This *must* return size_t. With Boost 1.46 on 32-bit systems the * unordered_map will behave unpredictably if the custom hasher returns a * uint64_t, resulting in failures when syncing the chain (#4634). * Note: This information above might be outdated as the unordered map * container type has meanwhile been switched to the C++ standard library * implementation. */ size_t operator()(const uint256 &txid) const { return SipHashUint256(k0, k1, txid); } }; struct CCoinsCacheEntry { CCoins coins; // The actual cached data. uint8_t flags; enum Flags { // This cache entry is potentially different from the version in the // parent view. DIRTY = (1 << 0), // The parent view does not have this entry (or it is pruned). FRESH = (1 << 1), /* Note that FRESH is a performance optimization with which we can erase coins that are fully spent if we know we do not need to flush the changes to the parent cache. It is always safe to not mark FRESH if that condition is not guaranteed. */ }; CCoinsCacheEntry() : coins(), flags(0) {} }; typedef std::unordered_map CCoinsMap; /** Cursor for iterating over CoinsView state */ class CCoinsViewCursor { public: CCoinsViewCursor(const uint256 &hashBlockIn) : hashBlock(hashBlockIn) {} virtual ~CCoinsViewCursor(); virtual bool GetKey(uint256 &key) const = 0; virtual bool GetValue(CCoins &coins) const = 0; /* Don't care about GetKeySize here */ virtual unsigned int GetValueSize() const = 0; virtual bool Valid() const = 0; virtual void Next() = 0; //! Get best block at the time this cursor was created const uint256 &GetBestBlock() const { return hashBlock; } private: uint256 hashBlock; }; /** Abstract view on the open txout dataset. */ class CCoinsView { protected: //! Retrieve the CCoins (unspent transaction outputs) for a given txid virtual bool GetCoins(const uint256 &txid, CCoins &coins) const; //! Just check whether we have data for a given txid. //! This may (but cannot always) return true for fully spent transactions virtual bool HaveCoins(const uint256 &txid) const; public: //! Transitional function to move from GetCoins to GetCoin. bool GetCoins_DONOTUSE(const uint256 &txid, CCoins &coins) const { return GetCoins(txid, coins); } //! Retrieve the Coin (unspent transaction output) for a given outpoint. bool GetCoin(const COutPoint &outpoint, Coin &coin) const { CCoins coins; if (!GetCoins(outpoint.hash, coins) || !coins.IsAvailable(outpoint.n)) { return false; } coin = Coin(coins.vout[outpoint.n], coins.nHeight, coins.fCoinBase); return true; } //! Transitional function to move from HaveCoins to HaveCoin. bool HaveCoins_DONOTUSE(const uint256 &txid) const { return HaveCoins(txid); } //! Just check whether we have data for a given outpoint. //! This may (but cannot always) return true for spent outputs. bool HaveCoin(const COutPoint &outpoint) const { CCoins coins; if (!GetCoins(outpoint.hash, coins)) { return false; } return coins.IsAvailable(outpoint.n); } //! Retrieve the block hash whose state this CCoinsView currently represents virtual uint256 GetBestBlock() const; //! Do a bulk modification (multiple CCoins changes + BestBlock change). //! The passed mapCoins can be modified. virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); //! Get a cursor to iterate over the whole state virtual CCoinsViewCursor *Cursor() const; //! As we use CCoinsViews polymorphically, have a virtual destructor virtual ~CCoinsView() {} + + //! Estimate database size (0 if not implemented) + virtual size_t EstimateSize() const { return 0; } }; /** CCoinsView backed by another CCoinsView */ class CCoinsViewBacked : public CCoinsView { protected: CCoinsView *base; bool GetCoins(const uint256 &txid, CCoins &coins) const; bool HaveCoins(const uint256 &txid) const; public: CCoinsViewBacked(CCoinsView *viewIn); uint256 GetBestBlock() const; void SetBackend(CCoinsView &viewIn); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); CCoinsViewCursor *Cursor() const; + size_t EstimateSize() const override; }; /** * CCoinsView that adds a memory cache for transactions to another CCoinsView */ class CCoinsViewCache : public CCoinsViewBacked { protected: /** * Make mutable so that we can "fill the cache" even from Get-methods * declared as "const". */ mutable uint256 hashBlock; mutable CCoinsMap cacheCoins; /* Cached dynamic memory usage for the inner CCoins objects. */ mutable size_t cachedCoinsUsage; bool GetCoins(const uint256 &txid, CCoins &coins) const; bool HaveCoins(const uint256 &txid) const; /** * Return a pointer to CCoins in the cache, or nullptr if not found. This is * more efficient than GetCoins. Modifications to other cache entries are * allowed while accessing the returned pointer. */ const CCoins *AccessCoins(const uint256 &txid) const; public: CCoinsViewCache(CCoinsView *baseIn); // Standard CCoinsView methods uint256 GetBestBlock() const; void SetBestBlock(const uint256 &hashBlock); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); /** * Check if we have the given utxo already loaded in this cache. * The semantics are the same as HaveCoin(), but no calls to the backing * CCoinsView are made. */ bool HaveCoinInCache(const COutPoint &outpoint) const; //! Transitional function to move from AccessCoins to AccessCoin. const CCoins *AccessCoins_DONOTUSE(const uint256 &txid) const { return AccessCoins(txid); } /** * Return a copy of a Coin in the cache, or a pruned one if not found. This * is more efficient than GetCoin. Modifications to other cache entries are * allowed while accessing the returned pointer. * TODO: return a reference instead of a value once underlying storage is * updated. */ const Coin AccessCoin(const COutPoint &output) const; /** * Add a coin. Set potential_overwrite to true if a non-pruned version may * already exist. */ void AddCoin(const COutPoint &outpoint, const Coin &coin, bool potential_overwrite); /** * Spend a coin. Pass moveto in order to get the deleted data. * If no unspent output exists for the passed outpoint, this call has no * effect. */ bool SpendCoin(const COutPoint &outpoint, Coin *moveto = nullptr); /** * Push the modifications applied to this cache to its base. * Failure to call this method before destruction will cause the changes to * be forgotten. If false is returned, the state of this cache (and its * backing view) will be undefined. */ bool Flush(); /** * Removes the UTXO with the given outpoint from the cache, if it is not * modified. */ void Uncache(const COutPoint &outpoint); //! Calculate the size of the cache (in number of transactions) unsigned int GetCacheSize() const; //! Calculate the size of the cache (in bytes) size_t DynamicMemoryUsage() const; /** * Amount of bitcoins coming in to a transaction * Note that lightweight clients may not know anything besides the hash of * previous transactions, so may not be able to calculate this. * * @param[in] tx transaction for which we are checking input total * @return Sum of value of all inputs (scriptSigs) */ CAmount GetValueIn(const CTransaction &tx) const; //! Check whether all prevouts of the transaction are present in the UTXO //! set represented by this view bool HaveInputs(const CTransaction &tx) const; /** * Return priority of tx at height nHeight. Also calculate the sum of the * values of the inputs that are already in the chain. These are the inputs * that will age and increase priority as new blocks are added to the chain. */ double GetPriority(const CTransaction &tx, int nHeight, CAmount &inChainInputValue) const; const CTxOut &GetOutputFor(const CTxIn &input) const; friend class CCoinsModifier; private: CCoinsMap::iterator FetchCoins(const uint256 &txid) const; /** * By making the copy constructor private, we prevent accidentally using it * when one intends to create a cache on top of a base cache. */ CCoinsViewCache(const CCoinsViewCache &); }; //! Utility function to add all of a transaction's outputs to a cache. // It assumes that overwrites are only possible for coinbase transactions. // TODO: pass in a boolean to limit these possible overwrites to known // (pre-BIP34) cases. void AddCoins(CCoinsViewCache &cache, const CTransaction &tx, int nHeight); //! Utility function to find any unspent output with a given txid. const Coin AccessByTxid(const CCoinsViewCache &cache, const uint256 &txid); #endif // BITCOIN_COINS_H diff --git a/src/dbwrapper.h b/src/dbwrapper.h index 6520f9ea61..e95b132889 100644 --- a/src/dbwrapper.h +++ b/src/dbwrapper.h @@ -1,278 +1,294 @@ // Copyright (c) 2012-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_DBWRAPPER_H #define BITCOIN_DBWRAPPER_H #include "clientversion.h" #include "serialize.h" #include "streams.h" #include "util.h" #include "utilstrencodings.h" #include "version.h" #include #include #include static const size_t DBWRAPPER_PREALLOC_KEY_SIZE = 64; static const size_t DBWRAPPER_PREALLOC_VALUE_SIZE = 1024; class dbwrapper_error : public std::runtime_error { public: dbwrapper_error(const std::string &msg) : std::runtime_error(msg) {} }; class CDBWrapper; /** * These should be considered an implementation detail of the specific database. */ namespace dbwrapper_private { /** * Handle database error by throwing dbwrapper_error exception. */ void HandleError(const leveldb::Status &status); /** * Work around circular dependency, as well as for testing in dbwrapper_tests. * Database obfuscation should be considered an implementation detail of the * specific database. */ const std::vector &GetObfuscateKey(const CDBWrapper &w); }; /** Batch of changes queued to be written to a CDBWrapper */ class CDBBatch { friend class CDBWrapper; private: const CDBWrapper &parent; leveldb::WriteBatch batch; CDataStream ssKey; CDataStream ssValue; public: /** * @param[in] _parent CDBWrapper that this batch is to be submitted to */ CDBBatch(const CDBWrapper &_parent) : parent(_parent), ssKey(SER_DISK, CLIENT_VERSION), ssValue(SER_DISK, CLIENT_VERSION){}; template void Write(const K &key, const V &value) { ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; leveldb::Slice slKey(ssKey.data(), ssKey.size()); ssValue.reserve(DBWRAPPER_PREALLOC_VALUE_SIZE); ssValue << value; ssValue.Xor(dbwrapper_private::GetObfuscateKey(parent)); leveldb::Slice slValue(ssValue.data(), ssValue.size()); batch.Put(slKey, slValue); ssKey.clear(); ssValue.clear(); } template void Erase(const K &key) { ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; leveldb::Slice slKey(ssKey.data(), ssKey.size()); batch.Delete(slKey); ssKey.clear(); } }; class CDBIterator { private: const CDBWrapper &parent; leveldb::Iterator *piter; public: /** * @param[in] _parent Parent CDBWrapper instance. * @param[in] _piter The original leveldb iterator. */ CDBIterator(const CDBWrapper &_parent, leveldb::Iterator *_piter) : parent(_parent), piter(_piter){}; ~CDBIterator(); bool Valid(); void SeekToFirst(); template void Seek(const K &key) { CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; leveldb::Slice slKey(ssKey.data(), ssKey.size()); piter->Seek(slKey); } void Next(); template bool GetKey(K &key) { leveldb::Slice slKey = piter->key(); try { CDataStream ssKey(slKey.data(), slKey.data() + slKey.size(), SER_DISK, CLIENT_VERSION); ssKey >> key; } catch (const std::exception &) { return false; } return true; } unsigned int GetKeySize() { return piter->key().size(); } template bool GetValue(V &value) { leveldb::Slice slValue = piter->value(); try { CDataStream ssValue(slValue.data(), slValue.data() + slValue.size(), SER_DISK, CLIENT_VERSION); ssValue.Xor(dbwrapper_private::GetObfuscateKey(parent)); ssValue >> value; } catch (const std::exception &) { return false; } return true; } unsigned int GetValueSize() { return piter->value().size(); } }; class CDBWrapper { friend const std::vector & dbwrapper_private::GetObfuscateKey(const CDBWrapper &w); private: //! custom environment this database is using (may be nullptr in case of //! default environment) leveldb::Env *penv; //! database options used leveldb::Options options; //! options used when reading from the database leveldb::ReadOptions readoptions; //! options used when iterating over values of the database leveldb::ReadOptions iteroptions; //! options used when writing to the database leveldb::WriteOptions writeoptions; //! options used when sync writing to the database leveldb::WriteOptions syncoptions; //! the database itself leveldb::DB *pdb; //! a key used for optional XOR-obfuscation of the database std::vector obfuscate_key; //! the key under which the obfuscation key is stored static const std::string OBFUSCATE_KEY_KEY; //! the length of the obfuscate key in number of bytes static const unsigned int OBFUSCATE_KEY_NUM_BYTES; std::vector CreateObfuscateKey() const; public: /** * @param[in] path Location in the filesystem where leveldb data will * be stored. * @param[in] nCacheSize Configures various leveldb cache settings. * @param[in] fMemory If true, use leveldb's memory environment. * @param[in] fWipe If true, remove all existing data. * @param[in] obfuscate If true, store data obfuscated via simple XOR. If * false, XOR * with a zero'd byte array. */ CDBWrapper(const boost::filesystem::path &path, size_t nCacheSize, bool fMemory = false, bool fWipe = false, bool obfuscate = false); ~CDBWrapper(); template bool Read(const K &key, V &value) const { CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; leveldb::Slice slKey(ssKey.data(), ssKey.size()); std::string strValue; leveldb::Status status = pdb->Get(readoptions, slKey, &strValue); if (!status.ok()) { if (status.IsNotFound()) return false; LogPrintf("LevelDB read failure: %s\n", status.ToString()); dbwrapper_private::HandleError(status); } try { CDataStream ssValue(strValue.data(), strValue.data() + strValue.size(), SER_DISK, CLIENT_VERSION); ssValue.Xor(obfuscate_key); ssValue >> value; } catch (const std::exception &) { return false; } return true; } template bool Write(const K &key, const V &value, bool fSync = false) { CDBBatch batch(*this); batch.Write(key, value); return WriteBatch(batch, fSync); } template bool Exists(const K &key) const { CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; leveldb::Slice slKey(ssKey.data(), ssKey.size()); std::string strValue; leveldb::Status status = pdb->Get(readoptions, slKey, &strValue); if (!status.ok()) { if (status.IsNotFound()) return false; LogPrintf("LevelDB read failure: %s\n", status.ToString()); dbwrapper_private::HandleError(status); } return true; } template bool Erase(const K &key, bool fSync = false) { CDBBatch batch(*this); batch.Erase(key); return WriteBatch(batch, fSync); } bool WriteBatch(CDBBatch &batch, bool fSync = false); // not available for LevelDB; provide for compatibility with BDB bool Flush() { return true; } bool Sync() { CDBBatch batch(*this); return WriteBatch(batch, true); } CDBIterator *NewIterator() { return new CDBIterator(*this, pdb->NewIterator(iteroptions)); } /** * Return true if the database managed by this class contains no entries. */ bool IsEmpty(); + + template + size_t EstimateSize(const K &key_begin, const K &key_end) const { + CDataStream ssKey1(SER_DISK, CLIENT_VERSION), + ssKey2(SER_DISK, CLIENT_VERSION); + ssKey1.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); + ssKey2.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); + ssKey1 << key_begin; + ssKey2 << key_end; + leveldb::Slice slKey1(ssKey1.data(), ssKey1.size()); + leveldb::Slice slKey2(ssKey2.data(), ssKey2.size()); + uint64_t size = 0; + leveldb::Range range(slKey1, slKey2); + pdb->GetApproximateSizes(&range, 1, &size); + return size; + } }; #endif // BITCOIN_DBWRAPPER_H diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 625ddaa7b9..a8a69f3b34 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1,1642 +1,1653 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "rpc/blockchain.h" #include "amount.h" #include "chain.h" #include "chainparams.h" #include "checkpoints.h" #include "coins.h" #include "config.h" #include "consensus/validation.h" #include "hash.h" #include "policy/policy.h" #include "primitives/transaction.h" #include "rpc/server.h" #include "streams.h" #include "sync.h" #include "txmempool.h" #include "util.h" #include "utilstrencodings.h" #include "validation.h" #include #include // boost::thread::interrupt #include #include struct CUpdatedBlock { uint256 hash; int height; }; static std::mutex cs_blockchange; static std::condition_variable cond_blockchange; static CUpdatedBlock latestblock; extern void TxToJSON(const CTransaction &tx, const uint256 hashBlock, UniValue &entry); void ScriptPubKeyToJSON(const CScript &scriptPubKey, UniValue &out, bool fIncludeHex); double GetDifficulty(const CBlockIndex *blockindex) { // Floating point number that is a multiple of the minimum difficulty, // minimum difficulty = 1.0. if (blockindex == nullptr) { if (chainActive.Tip() == nullptr) { return 1.0; } blockindex = chainActive.Tip(); } int nShift = (blockindex->nBits >> 24) & 0xff; double dDiff = double(0x0000ffff) / double(blockindex->nBits & 0x00ffffff); while (nShift < 29) { dDiff *= 256.0; nShift++; } while (nShift > 29) { dDiff /= 256.0; nShift--; } return dDiff; } UniValue blockheaderToJSON(const CBlockIndex *blockindex) { UniValue result(UniValue::VOBJ); result.push_back(Pair("hash", blockindex->GetBlockHash().GetHex())); int confirmations = -1; // Only report confirmations if the block is on the main chain if (chainActive.Contains(blockindex)) { confirmations = chainActive.Height() - blockindex->nHeight + 1; } result.push_back(Pair("confirmations", confirmations)); result.push_back(Pair("height", blockindex->nHeight)); result.push_back(Pair("version", blockindex->nVersion)); result.push_back( Pair("versionHex", strprintf("%08x", blockindex->nVersion))); result.push_back(Pair("merkleroot", blockindex->hashMerkleRoot.GetHex())); result.push_back(Pair("time", int64_t(blockindex->nTime))); result.push_back( Pair("mediantime", int64_t(blockindex->GetMedianTimePast()))); result.push_back(Pair("nonce", uint64_t(blockindex->nNonce))); result.push_back(Pair("bits", strprintf("%08x", blockindex->nBits))); result.push_back(Pair("difficulty", GetDifficulty(blockindex))); result.push_back(Pair("chainwork", blockindex->nChainWork.GetHex())); if (blockindex->pprev) { result.push_back(Pair("previousblockhash", blockindex->pprev->GetBlockHash().GetHex())); } CBlockIndex *pnext = chainActive.Next(blockindex); if (pnext) { result.push_back(Pair("nextblockhash", pnext->GetBlockHash().GetHex())); } return result; } UniValue blockToJSON(const CBlock &block, const CBlockIndex *blockindex, bool txDetails = false) { UniValue result(UniValue::VOBJ); result.push_back(Pair("hash", blockindex->GetBlockHash().GetHex())); int confirmations = -1; // Only report confirmations if the block is on the main chain if (chainActive.Contains(blockindex)) { confirmations = chainActive.Height() - blockindex->nHeight + 1; } result.push_back(Pair("confirmations", confirmations)); result.push_back(Pair( "size", (int)::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION))); result.push_back(Pair("height", blockindex->nHeight)); result.push_back(Pair("version", block.nVersion)); result.push_back(Pair("versionHex", strprintf("%08x", block.nVersion))); result.push_back(Pair("merkleroot", block.hashMerkleRoot.GetHex())); UniValue txs(UniValue::VARR); for (const auto &tx : block.vtx) { if (txDetails) { UniValue objTx(UniValue::VOBJ); TxToJSON(*tx, uint256(), objTx); txs.push_back(objTx); } else txs.push_back(tx->GetId().GetHex()); } result.push_back(Pair("tx", txs)); result.push_back(Pair("time", block.GetBlockTime())); result.push_back( Pair("mediantime", int64_t(blockindex->GetMedianTimePast()))); result.push_back(Pair("nonce", uint64_t(block.nNonce))); result.push_back(Pair("bits", strprintf("%08x", block.nBits))); result.push_back(Pair("difficulty", GetDifficulty(blockindex))); result.push_back(Pair("chainwork", blockindex->nChainWork.GetHex())); if (blockindex->pprev) { result.push_back(Pair("previousblockhash", blockindex->pprev->GetBlockHash().GetHex())); } CBlockIndex *pnext = chainActive.Next(blockindex); if (pnext) { result.push_back(Pair("nextblockhash", pnext->GetBlockHash().GetHex())); } return result; } UniValue getblockcount(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( "getblockcount\n" "\nReturns the number of blocks in the longest blockchain.\n" "\nResult:\n" "n (numeric) The current block count\n" "\nExamples:\n" + HelpExampleCli("getblockcount", "") + HelpExampleRpc("getblockcount", "")); } LOCK(cs_main); return chainActive.Height(); } UniValue getbestblockhash(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( "getbestblockhash\n" "\nReturns the hash of the best (tip) block in the " "longest blockchain.\n" "\nResult:\n" "\"hex\" (string) the block hash hex encoded\n" "\nExamples:\n" + HelpExampleCli("getbestblockhash", "") + HelpExampleRpc("getbestblockhash", "")); } LOCK(cs_main); return chainActive.Tip()->GetBlockHash().GetHex(); } void RPCNotifyBlockChange(bool ibd, const CBlockIndex *pindex) { if (pindex) { std::lock_guard lock(cs_blockchange); latestblock.hash = pindex->GetBlockHash(); latestblock.height = pindex->nHeight; } cond_blockchange.notify_all(); } UniValue waitfornewblock(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() > 1) { throw std::runtime_error( "waitfornewblock (timeout)\n" "\nWaits for a specific new block and returns " "useful info about it.\n" "\nReturns the current block on timeout or exit.\n" "\nArguments:\n" "1. timeout (int, optional, default=0) Time in " "milliseconds to wait for a response. 0 indicates " "no timeout.\n" "\nResult:\n" "{ (json object)\n" " \"hash\" : { (string) The blockhash\n" " \"height\" : { (int) Block height\n" "}\n" "\nExamples:\n" + HelpExampleCli("waitfornewblock", "1000") + HelpExampleRpc("waitfornewblock", "1000")); } int timeout = 0; if (request.params.size() > 0) { timeout = request.params[0].get_int(); } CUpdatedBlock block; { std::unique_lock lock(cs_blockchange); block = latestblock; if (timeout) { cond_blockchange.wait_for( lock, std::chrono::milliseconds(timeout), [&block] { return latestblock.height != block.height || latestblock.hash != block.hash || !IsRPCRunning(); }); } else { cond_blockchange.wait(lock, [&block] { return latestblock.height != block.height || latestblock.hash != block.hash || !IsRPCRunning(); }); } block = latestblock; } UniValue ret(UniValue::VOBJ); ret.push_back(Pair("hash", block.hash.GetHex())); ret.push_back(Pair("height", block.height)); return ret; } UniValue waitforblock(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( "waitforblock (timeout)\n" "\nWaits for a specific new block and returns useful info about " "it.\n" "\nReturns the current block on timeout or exit.\n" "\nArguments:\n" "1. \"blockhash\" (required, string) Block hash to wait for.\n" "2. timeout (int, optional, default=0) Time in milliseconds " "to wait for a response. 0 indicates no timeout.\n" "\nResult:\n" "{ (json object)\n" " \"hash\" : { (string) The blockhash\n" " \"height\" : { (int) Block height\n" "}\n" "\nExamples:\n" + HelpExampleCli("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4" "570b24c9ed7b4a8c619eb02596f8862\", " "1000") + HelpExampleRpc("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4" "570b24c9ed7b4a8c619eb02596f8862\", " "1000")); } int timeout = 0; uint256 hash = uint256S(request.params[0].get_str()); if (request.params.size() > 1) { timeout = request.params[1].get_int(); } CUpdatedBlock block; { std::unique_lock lock(cs_blockchange); if (timeout) { cond_blockchange.wait_for( lock, std::chrono::milliseconds(timeout), [&hash] { return latestblock.hash == hash || !IsRPCRunning(); }); } else { cond_blockchange.wait(lock, [&hash] { return latestblock.hash == hash || !IsRPCRunning(); }); } block = latestblock; } UniValue ret(UniValue::VOBJ); ret.push_back(Pair("hash", block.hash.GetHex())); ret.push_back(Pair("height", block.height)); return ret; } UniValue waitforblockheight(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( "waitforblockheight (timeout)\n" "\nWaits for (at least) block height and returns the height and " "hash\n" "of the current tip.\n" "\nReturns the current block on timeout or exit.\n" "\nArguments:\n" "1. height (required, int) Block height to wait for (int)\n" "2. timeout (int, optional, default=0) Time in milliseconds to " "wait for a response. 0 indicates no timeout.\n" "\nResult:\n" "{ (json object)\n" " \"hash\" : { (string) The blockhash\n" " \"height\" : { (int) Block height\n" "}\n" "\nExamples:\n" + HelpExampleCli("waitforblockheight", "\"100\", 1000") + HelpExampleRpc("waitforblockheight", "\"100\", 1000")); } int timeout = 0; int height = request.params[0].get_int(); if (request.params.size() > 1) { timeout = request.params[1].get_int(); } CUpdatedBlock block; { std::unique_lock lock(cs_blockchange); if (timeout) { cond_blockchange.wait_for( lock, std::chrono::milliseconds(timeout), [&height] { return latestblock.height >= height || !IsRPCRunning(); }); } else { cond_blockchange.wait(lock, [&height] { return latestblock.height >= height || !IsRPCRunning(); }); } block = latestblock; } UniValue ret(UniValue::VOBJ); ret.push_back(Pair("hash", block.hash.GetHex())); ret.push_back(Pair("height", block.height)); return ret; } UniValue getdifficulty(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error("getdifficulty\n" "\nReturns the proof-of-work difficulty as a " "multiple of the minimum difficulty.\n" "\nResult:\n" "n.nnn (numeric) the proof-of-work " "difficulty as a multiple of the minimum " "difficulty.\n" "\nExamples:\n" + HelpExampleCli("getdifficulty", "") + HelpExampleRpc("getdifficulty", "")); } LOCK(cs_main); return GetDifficulty(); } std::string EntryDescriptionString() { return " \"size\" : n, (numeric) transaction size.\n" " \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n" " \"modifiedfee\" : n, (numeric) transaction fee with fee " "deltas used for mining priority\n" " \"time\" : n, (numeric) local time transaction " "entered pool in seconds since 1 Jan 1970 GMT\n" " \"height\" : n, (numeric) block height when " "transaction entered pool\n" " \"startingpriority\" : n, (numeric) DEPRECATED. Priority when " "transaction entered pool\n" " \"currentpriority\" : n, (numeric) DEPRECATED. Transaction " "priority now\n" " \"descendantcount\" : n, (numeric) number of in-mempool " "descendant transactions (including this one)\n" " \"descendantsize\" : n, (numeric) virtual transaction size " "of in-mempool descendants (including this one)\n" " \"descendantfees\" : n, (numeric) modified fees (see above) " "of in-mempool descendants (including this one)\n" " \"ancestorcount\" : n, (numeric) number of in-mempool " "ancestor transactions (including this one)\n" " \"ancestorsize\" : n, (numeric) virtual transaction size " "of in-mempool ancestors (including this one)\n" " \"ancestorfees\" : n, (numeric) modified fees (see above) " "of in-mempool ancestors (including this one)\n" " \"depends\" : [ (array) unconfirmed transactions " "used as inputs for this transaction\n" " \"transactionid\", (string) parent transaction id\n" " ... ]\n"; } void entryToJSON(UniValue &info, const CTxMemPoolEntry &e) { AssertLockHeld(mempool.cs); info.push_back(Pair("size", (int)e.GetTxSize())); info.push_back(Pair("fee", ValueFromAmount(e.GetFee()))); info.push_back(Pair("modifiedfee", ValueFromAmount(e.GetModifiedFee()))); info.push_back(Pair("time", e.GetTime())); info.push_back(Pair("height", (int)e.GetHeight())); info.push_back(Pair("startingpriority", e.GetPriority(e.GetHeight()))); info.push_back( Pair("currentpriority", e.GetPriority(chainActive.Height()))); info.push_back(Pair("descendantcount", e.GetCountWithDescendants())); info.push_back(Pair("descendantsize", e.GetSizeWithDescendants())); info.push_back(Pair("descendantfees", e.GetModFeesWithDescendants())); info.push_back(Pair("ancestorcount", e.GetCountWithAncestors())); info.push_back(Pair("ancestorsize", e.GetSizeWithAncestors())); info.push_back(Pair("ancestorfees", e.GetModFeesWithAncestors())); const CTransaction &tx = e.GetTx(); std::set setDepends; for (const CTxIn &txin : tx.vin) { if (mempool.exists(txin.prevout.hash)) { setDepends.insert(txin.prevout.hash.ToString()); } } UniValue depends(UniValue::VARR); for (const std::string &dep : setDepends) { depends.push_back(dep); } info.push_back(Pair("depends", depends)); } UniValue mempoolToJSON(bool fVerbose = false) { if (fVerbose) { LOCK(mempool.cs); UniValue o(UniValue::VOBJ); for (const CTxMemPoolEntry &e : mempool.mapTx) { const uint256 &txid = e.GetTx().GetId(); UniValue info(UniValue::VOBJ); entryToJSON(info, e); o.push_back(Pair(txid.ToString(), info)); } return o; } else { std::vector vtxids; mempool.queryHashes(vtxids); UniValue a(UniValue::VARR); for (const uint256 &txid : vtxids) { a.push_back(txid.ToString()); } return a; } } UniValue getrawmempool(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() > 1) { throw std::runtime_error( "getrawmempool ( verbose )\n" "\nReturns all transaction ids in memory pool as a json array of " "string transaction ids.\n" "\nArguments:\n" "1. verbose (boolean, optional, default=false) True for a json " "object, false for array of transaction ids\n" "\nResult: (for verbose = false):\n" "[ (json array of string)\n" " \"transactionid\" (string) The transaction id\n" " ,...\n" "]\n" "\nResult: (for verbose = true):\n" "{ (json object)\n" " \"transactionid\" : { (json object)\n" + EntryDescriptionString() + " }, ...\n" "}\n" "\nExamples:\n" + HelpExampleCli("getrawmempool", "true") + HelpExampleRpc("getrawmempool", "true")); } bool fVerbose = false; if (request.params.size() > 0) { fVerbose = request.params[0].get_bool(); } return mempoolToJSON(fVerbose); } UniValue getmempoolancestors(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( "getmempoolancestors txid (verbose)\n" "\nIf txid is in the mempool, returns all in-mempool ancestors.\n" "\nArguments:\n" "1. \"txid\" (string, required) The transaction id " "(must be in mempool)\n" "2. verbose (boolean, optional, default=false) " "True for a json object, false for array of transaction ids\n" "\nResult (for verbose=false):\n" "[ (json array of strings)\n" " \"transactionid\" (string) The transaction id of an " "in-mempool ancestor transaction\n" " ,...\n" "]\n" "\nResult (for verbose=true):\n" "{ (json object)\n" " \"transactionid\" : { (json object)\n" + EntryDescriptionString() + " }, ...\n" "}\n" "\nExamples:\n" + HelpExampleCli("getmempoolancestors", "\"mytxid\"") + HelpExampleRpc("getmempoolancestors", "\"mytxid\"")); } bool fVerbose = false; if (request.params.size() > 1) { fVerbose = request.params[1].get_bool(); } uint256 hash = ParseHashV(request.params[0], "parameter 1"); LOCK(mempool.cs); CTxMemPool::txiter it = mempool.mapTx.find(hash); if (it == mempool.mapTx.end()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); } CTxMemPool::setEntries setAncestors; uint64_t noLimit = std::numeric_limits::max(); std::string dummy; mempool.CalculateMemPoolAncestors(*it, setAncestors, noLimit, noLimit, noLimit, noLimit, dummy, false); if (!fVerbose) { UniValue o(UniValue::VARR); for (CTxMemPool::txiter ancestorIt : setAncestors) { o.push_back(ancestorIt->GetTx().GetId().ToString()); } return o; } else { UniValue o(UniValue::VOBJ); for (CTxMemPool::txiter ancestorIt : setAncestors) { const CTxMemPoolEntry &e = *ancestorIt; const uint256 &_hash = e.GetTx().GetId(); UniValue info(UniValue::VOBJ); entryToJSON(info, e); o.push_back(Pair(_hash.ToString(), info)); } return o; } } UniValue getmempooldescendants(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( "getmempooldescendants txid (verbose)\n" "\nIf txid is in the mempool, returns all in-mempool descendants.\n" "\nArguments:\n" "1. \"txid\" (string, required) The transaction id " "(must be in mempool)\n" "2. verbose (boolean, optional, default=false) " "True for a json object, false for array of transaction ids\n" "\nResult (for verbose=false):\n" "[ (json array of strings)\n" " \"transactionid\" (string) The transaction id of an " "in-mempool descendant transaction\n" " ,...\n" "]\n" "\nResult (for verbose=true):\n" "{ (json object)\n" " \"transactionid\" : { (json object)\n" + EntryDescriptionString() + " }, ...\n" "}\n" "\nExamples:\n" + HelpExampleCli("getmempooldescendants", "\"mytxid\"") + HelpExampleRpc("getmempooldescendants", "\"mytxid\"")); } bool fVerbose = false; if (request.params.size() > 1) fVerbose = request.params[1].get_bool(); uint256 hash = ParseHashV(request.params[0], "parameter 1"); LOCK(mempool.cs); CTxMemPool::txiter it = mempool.mapTx.find(hash); if (it == mempool.mapTx.end()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); } CTxMemPool::setEntries setDescendants; mempool.CalculateDescendants(it, setDescendants); // CTxMemPool::CalculateDescendants will include the given tx setDescendants.erase(it); if (!fVerbose) { UniValue o(UniValue::VARR); for (CTxMemPool::txiter descendantIt : setDescendants) { o.push_back(descendantIt->GetTx().GetId().ToString()); } return o; } else { UniValue o(UniValue::VOBJ); for (CTxMemPool::txiter descendantIt : setDescendants) { const CTxMemPoolEntry &e = *descendantIt; const uint256 &_hash = e.GetTx().GetId(); UniValue info(UniValue::VOBJ); entryToJSON(info, e); o.push_back(Pair(_hash.ToString(), info)); } return o; } } UniValue getmempoolentry(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "getmempoolentry txid\n" "\nReturns mempool data for given transaction\n" "\nArguments:\n" "1. \"txid\" (string, required) " "The transaction id (must be in mempool)\n" "\nResult:\n" "{ (json object)\n" + EntryDescriptionString() + "}\n" "\nExamples:\n" + HelpExampleCli("getmempoolentry", "\"mytxid\"") + HelpExampleRpc("getmempoolentry", "\"mytxid\"")); } uint256 hash = ParseHashV(request.params[0], "parameter 1"); LOCK(mempool.cs); CTxMemPool::txiter it = mempool.mapTx.find(hash); if (it == mempool.mapTx.end()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); } const CTxMemPoolEntry &e = *it; UniValue info(UniValue::VOBJ); entryToJSON(info, e); return info; } UniValue getblockhash(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "getblockhash height\n" "\nReturns hash of block in best-block-chain at height provided.\n" "\nArguments:\n" "1. height (numeric, required) The height index\n" "\nResult:\n" "\"hash\" (string) The block hash\n" "\nExamples:\n" + HelpExampleCli("getblockhash", "1000") + HelpExampleRpc("getblockhash", "1000")); } LOCK(cs_main); int nHeight = request.params[0].get_int(); if (nHeight < 0 || nHeight > chainActive.Height()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range"); } CBlockIndex *pblockindex = chainActive[nHeight]; return pblockindex->GetBlockHash().GetHex(); } UniValue getblockheader(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( "getblockheader \"hash\" ( verbose )\n" "\nIf verbose is false, returns a string that is serialized, " "hex-encoded data for blockheader 'hash'.\n" "If verbose is true, returns an Object with information about " "blockheader .\n" "\nArguments:\n" "1. \"hash\" (string, required) The block hash\n" "2. verbose (boolean, optional, default=true) true for a " "json object, false for the hex encoded data\n" "\nResult (for verbose = true):\n" "{\n" " \"hash\" : \"hash\", (string) the block hash (same as " "provided)\n" " \"confirmations\" : n, (numeric) The number of confirmations, " "or -1 if the block is not on the main chain\n" " \"height\" : n, (numeric) The block height or index\n" " \"version\" : n, (numeric) The block version\n" " \"versionHex\" : \"00000000\", (string) The block version " "formatted in hexadecimal\n" " \"merkleroot\" : \"xxxx\", (string) The merkle root\n" " \"time\" : ttt, (numeric) The block time in seconds " "since epoch (Jan 1 1970 GMT)\n" " \"mediantime\" : ttt, (numeric) The median block time in " "seconds since epoch (Jan 1 1970 GMT)\n" " \"nonce\" : n, (numeric) The nonce\n" " \"bits\" : \"1d00ffff\", (string) The bits\n" " \"difficulty\" : x.xxx, (numeric) The difficulty\n" " \"chainwork\" : \"0000...1f3\" (string) Expected number of " "hashes required to produce the current chain (in hex)\n" " \"previousblockhash\" : \"hash\", (string) The hash of the " "previous block\n" " \"nextblockhash\" : \"hash\", (string) The hash of the " "next block\n" "}\n" "\nResult (for verbose=false):\n" "\"data\" (string) A string that is serialized, " "hex-encoded data for block 'hash'.\n" "\nExamples:\n" + HelpExampleCli("getblockheader", "\"00000000c937983704a73af28acdec3" "7b049d214adbda81d7e2a3dd146f6ed09" "\"") + HelpExampleRpc("getblockheader", "\"00000000c937983704a73af28acdec3" "7b049d214adbda81d7e2a3dd146f6ed09" "\"")); } LOCK(cs_main); std::string strHash = request.params[0].get_str(); uint256 hash(uint256S(strHash)); bool fVerbose = true; if (request.params.size() > 1) { fVerbose = request.params[1].get_bool(); } if (mapBlockIndex.count(hash) == 0) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } CBlockIndex *pblockindex = mapBlockIndex[hash]; if (!fVerbose) { CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION); ssBlock << pblockindex->GetBlockHeader(); std::string strHex = HexStr(ssBlock.begin(), ssBlock.end()); return strHex; } return blockheaderToJSON(pblockindex); } UniValue getblock(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( "getblock \"blockhash\" ( verbose )\n" "\nIf verbose is false, returns a string that is serialized, " "hex-encoded data for block 'hash'.\n" "If verbose is true, returns an Object with information about " "block .\n" "\nArguments:\n" "1. \"blockhash\" (string, required) The block hash\n" "2. verbose (boolean, optional, default=true) true " "for a json object, false for the hex encoded data\n" "\nResult (for verbose = true):\n" "{\n" " \"hash\" : \"hash\", (string) the block hash (same as " "provided)\n" " \"confirmations\" : n, (numeric) The number of confirmations, " "or -1 if the block is not on the main chain\n" " \"size\" : n, (numeric) The block size\n" " \"height\" : n, (numeric) The block height or index\n" " \"version\" : n, (numeric) The block version\n" " \"versionHex\" : \"00000000\", (string) The block version " "formatted in hexadecimal\n" " \"merkleroot\" : \"xxxx\", (string) The merkle root\n" " \"tx\" : [ (array of string) The transaction ids\n" " \"transactionid\" (string) The transaction id\n" " ,...\n" " ],\n" " \"time\" : ttt, (numeric) The block time in seconds " "since epoch (Jan 1 1970 GMT)\n" " \"mediantime\" : ttt, (numeric) The median block time in " "seconds since epoch (Jan 1 1970 GMT)\n" " \"nonce\" : n, (numeric) The nonce\n" " \"bits\" : \"1d00ffff\", (string) The bits\n" " \"difficulty\" : x.xxx, (numeric) The difficulty\n" " \"chainwork\" : \"xxxx\", (string) Expected number of hashes " "required to produce the chain up to this block (in hex)\n" " \"previousblockhash\" : \"hash\", (string) The hash of the " "previous block\n" " \"nextblockhash\" : \"hash\" (string) The hash of the " "next block\n" "}\n" "\nResult (for verbose=false):\n" "\"data\" (string) A string that is serialized, " "hex-encoded data for block 'hash'.\n" "\nExamples:\n" + HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d" "214adbda81d7e2a3dd146f6ed09\"") + HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d" "214adbda81d7e2a3dd146f6ed09\"")); } LOCK(cs_main); std::string strHash = request.params[0].get_str(); uint256 hash(uint256S(strHash)); bool fVerbose = true; if (request.params.size() > 1) { fVerbose = request.params[1].get_bool(); } if (mapBlockIndex.count(hash) == 0) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } CBlock block; CBlockIndex *pblockindex = mapBlockIndex[hash]; if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0) { throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)"); } if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) { // Block not found on disk. This could be because we have the block // header in our index but don't have the block (for example if a // non-whitelisted node sends us an unrequested long chain of valid // blocks, we add the headers to our index, but don't accept the block). throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk"); } if (!fVerbose) { CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()); ssBlock << block; std::string strHex = HexStr(ssBlock.begin(), ssBlock.end()); return strHex; } return blockToJSON(block, pblockindex); } struct CCoinsStats { int nHeight; uint256 hashBlock; uint64_t nTransactions; uint64_t nTransactionOutputs; - uint64_t nSerializedSize; + uint64_t nBogoSize; uint256 hashSerialized; + uint64_t nDiskSize; CAmount nTotalAmount; CCoinsStats() - : nHeight(0), nTransactions(0), nTransactionOutputs(0), - nSerializedSize(0), nTotalAmount(0) {} + : nHeight(0), nTransactions(0), nTransactionOutputs(0), nBogoSize(0), + nDiskSize(0), nTotalAmount(0) {} }; //! Calculate statistics about the unspent transaction output set static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats) { std::unique_ptr pcursor(view->Cursor()); CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); stats.hashBlock = pcursor->GetBestBlock(); { LOCK(cs_main); stats.nHeight = mapBlockIndex.find(stats.hashBlock)->second->nHeight; } ss << stats.hashBlock; CAmount nTotalAmount = 0; while (pcursor->Valid()) { boost::this_thread::interruption_point(); uint256 key; CCoins coins; if (pcursor->GetKey(key) && pcursor->GetValue(coins)) { stats.nTransactions++; ss << key; + ss << VARINT(coins.nHeight * 2 + coins.fCoinBase); for (size_t i = 0; i < coins.vout.size(); i++) { const CTxOut &out = coins.vout[i]; if (!out.IsNull()) { stats.nTransactionOutputs++; ss << VARINT(i + 1); ss << out; nTotalAmount += out.nValue; + stats.nBogoSize += + 32 /* txid */ + 4 /* vout index */ + + 4 /* height + coinbase */ + 8 /* amount */ + + 2 /* scriptPubKey len */ + + out.scriptPubKey.size() /* scriptPubKey */; } } - stats.nSerializedSize += 32 + pcursor->GetValueSize(); ss << VARINT(0); } else { return error("%s: unable to read value", __func__); } pcursor->Next(); } stats.hashSerialized = ss.GetHash(); stats.nTotalAmount = nTotalAmount; + stats.nDiskSize = view->EstimateSize(); return true; } UniValue pruneblockchain(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "pruneblockchain\n" "\nArguments:\n" "1. \"height\" (numeric, required) The block height to prune " "up to. May be set to a discrete height, or a unix timestamp\n" " to prune blocks whose block time is at least 2 " "hours older than the provided timestamp.\n" "\nResult:\n" "n (numeric) Height of the last block pruned.\n" "\nExamples:\n" + HelpExampleCli("pruneblockchain", "1000") + HelpExampleRpc("pruneblockchain", "1000")); } if (!fPruneMode) { throw JSONRPCError( RPC_MISC_ERROR, "Cannot prune blocks because node is not in prune mode."); } LOCK(cs_main); int heightParam = request.params[0].get_int(); if (heightParam < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative block height."); } // Height value more than a billion is too high to be a block height, and // too low to be a block time (corresponds to timestamp from Sep 2001). if (heightParam > 1000000000) { // Add a 2 hour buffer to include blocks which might have had old // timestamps CBlockIndex *pindex = chainActive.FindEarliestAtLeast(heightParam - 7200); if (!pindex) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Could not find block with at least the specified timestamp."); } heightParam = pindex->nHeight; } unsigned int height = (unsigned int)heightParam; unsigned int chainHeight = (unsigned int)chainActive.Height(); if (chainHeight < Params().PruneAfterHeight()) { throw JSONRPCError(RPC_MISC_ERROR, "Blockchain is too short for pruning."); } else if (height > chainHeight) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Blockchain is shorter than the attempted prune height."); } else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) { LogPrint("rpc", "Attempt to prune blocks close to the tip. Retaining " "the minimum number of blocks."); height = chainHeight - MIN_BLOCKS_TO_KEEP; } PruneBlockFilesManual(height); return uint64_t(height); } UniValue gettxoutsetinfo(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( "gettxoutsetinfo\n" "\nReturns statistics about the unspent transaction output set.\n" "Note this call may take some time.\n" "\nResult:\n" "{\n" " \"height\":n, (numeric) The current block height (index)\n" " \"bestblock\": \"hex\", (string) the best block hash hex\n" " \"transactions\": n, (numeric) The number of transactions\n" " \"txouts\": n, (numeric) The number of output " "transactions\n" - " \"bytes_serialized\": n, (numeric) The serialized size\n" + " \"bogosize\": n, (numeric) A database-independent " + "metric for UTXO set size\n" " \"hash_serialized\": \"hash\", (string) The serialized hash\n" + " \"disk_size\": n, (numeric) The estimated size of the " + "chainstate on disk\n" " \"total_amount\": x.xxx (numeric) The total amount\n" "}\n" "\nExamples:\n" + HelpExampleCli("gettxoutsetinfo", "") + HelpExampleRpc("gettxoutsetinfo", "")); } UniValue ret(UniValue::VOBJ); CCoinsStats stats; FlushStateToDisk(); if (GetUTXOStats(pcoinsTip, stats)) { ret.push_back(Pair("height", int64_t(stats.nHeight))); ret.push_back(Pair("bestblock", stats.hashBlock.GetHex())); ret.push_back(Pair("transactions", int64_t(stats.nTransactions))); ret.push_back(Pair("txouts", int64_t(stats.nTransactionOutputs))); - ret.push_back(Pair("bytes_serialized", int64_t(stats.nSerializedSize))); + ret.push_back(Pair("bogosize", int64_t(stats.nBogoSize))); ret.push_back(Pair("hash_serialized", stats.hashSerialized.GetHex())); + ret.push_back(Pair("disk_size", stats.nDiskSize)); ret.push_back( Pair("total_amount", ValueFromAmount(stats.nTotalAmount))); } else { throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); } return ret; } UniValue gettxout(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) { throw std::runtime_error( "gettxout \"txid\" n ( include_mempool )\n" "\nReturns details about an unspent transaction output.\n" "\nArguments:\n" "1. \"txid\" (string, required) The transaction id\n" "2. n (numeric, required) vout number\n" "3. include_mempool (boolean, optional) Whether to include the " "mempool\n" "\nResult:\n" "{\n" " \"bestblock\" : \"hash\", (string) the block hash\n" " \"confirmations\" : n, (numeric) The number of " "confirmations\n" " \"value\" : x.xxx, (numeric) The transaction value " "in " + CURRENCY_UNIT + "\n" " \"scriptPubKey\" : { (json object)\n" " \"asm\" : \"code\", (string) \n" " \"hex\" : \"hex\", (string) \n" " \"reqSigs\" : n, (numeric) Number of required " "signatures\n" " \"type\" : \"pubkeyhash\", (string) The type, eg pubkeyhash\n" " \"addresses\" : [ (array of string) array of " "bitcoin addresses\n" " \"address\" (string) bitcoin address\n" " ,...\n" " ]\n" " },\n" " \"version\" : n, (numeric) The version\n" " \"coinbase\" : true|false (boolean) Coinbase or not\n" "}\n" "\nExamples:\n" "\nGet unspent transactions\n" + HelpExampleCli("listunspent", "") + "\nView the details\n" + HelpExampleCli("gettxout", "\"txid\" 1") + "\nAs a json rpc call\n" + HelpExampleRpc("gettxout", "\"txid\", 1")); } LOCK(cs_main); UniValue ret(UniValue::VOBJ); std::string strHash = request.params[0].get_str(); uint256 hash(uint256S(strHash)); int n = request.params[1].get_int(); COutPoint out(hash, n); bool fMempool = true; if (request.params.size() > 2) { fMempool = request.params[2].get_bool(); } Coin coin; if (fMempool) { LOCK(mempool.cs); CCoinsViewMemPool view(pcoinsTip, mempool); if (!view.GetCoin(out, coin) || mempool.isSpent(out)) { // TODO: this should be done by the CCoinsViewMemPool return NullUniValue; } } else { if (!pcoinsTip->GetCoin(out, coin)) { return NullUniValue; } } BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock()); CBlockIndex *pindex = it->second; ret.push_back(Pair("bestblock", pindex->GetBlockHash().GetHex())); if (coin.GetHeight() == MEMPOOL_HEIGHT) { ret.push_back(Pair("confirmations", 0)); } else { ret.push_back(Pair("confirmations", int64_t(pindex->nHeight - coin.GetHeight() + 1))); } ret.push_back(Pair("value", ValueFromAmount(coin.GetTxOut().nValue))); UniValue o(UniValue::VOBJ); ScriptPubKeyToJSON(coin.GetTxOut().scriptPubKey, o, true); ret.push_back(Pair("scriptPubKey", o)); ret.push_back(Pair("coinbase", coin.IsCoinBase())); return ret; } UniValue verifychain(const Config &config, const JSONRPCRequest &request) { int nCheckLevel = GetArg("-checklevel", DEFAULT_CHECKLEVEL); int nCheckDepth = GetArg("-checkblocks", DEFAULT_CHECKBLOCKS); if (request.fHelp || request.params.size() > 2) { throw std::runtime_error( "verifychain ( checklevel nblocks )\n" "\nVerifies blockchain database.\n" "\nArguments:\n" "1. checklevel (numeric, optional, 0-4, default=" + strprintf("%d", nCheckLevel) + ") How thorough the block verification is.\n" "2. nblocks (numeric, optional, default=" + strprintf("%d", nCheckDepth) + ", 0=all) The number of blocks to check.\n" "\nResult:\n" "true|false (boolean) Verified or not\n" "\nExamples:\n" + HelpExampleCli("verifychain", "") + HelpExampleRpc("verifychain", "")); } LOCK(cs_main); if (request.params.size() > 0) { nCheckLevel = request.params[0].get_int(); } if (request.params.size() > 1) { nCheckDepth = request.params[1].get_int(); } return CVerifyDB().VerifyDB(config, Params(), pcoinsTip, nCheckLevel, nCheckDepth); } /** Implementation of IsSuperMajority with better feedback */ static UniValue SoftForkMajorityDesc(int version, CBlockIndex *pindex, const Consensus::Params &consensusParams) { UniValue rv(UniValue::VOBJ); bool activated = false; switch (version) { case 2: activated = pindex->nHeight >= consensusParams.BIP34Height; break; case 3: activated = pindex->nHeight >= consensusParams.BIP66Height; break; case 4: activated = pindex->nHeight >= consensusParams.BIP65Height; break; } rv.push_back(Pair("status", activated)); return rv; } static UniValue SoftForkDesc(const std::string &name, int version, CBlockIndex *pindex, const Consensus::Params &consensusParams) { UniValue rv(UniValue::VOBJ); rv.push_back(Pair("id", name)); rv.push_back(Pair("version", version)); rv.push_back( Pair("reject", SoftForkMajorityDesc(version, pindex, consensusParams))); return rv; } static UniValue BIP9SoftForkDesc(const Consensus::Params &consensusParams, Consensus::DeploymentPos id) { UniValue rv(UniValue::VOBJ); const ThresholdState thresholdState = VersionBitsTipState(consensusParams, id); switch (thresholdState) { case THRESHOLD_DEFINED: rv.push_back(Pair("status", "defined")); break; case THRESHOLD_STARTED: rv.push_back(Pair("status", "started")); break; case THRESHOLD_LOCKED_IN: rv.push_back(Pair("status", "locked_in")); break; case THRESHOLD_ACTIVE: rv.push_back(Pair("status", "active")); break; case THRESHOLD_FAILED: rv.push_back(Pair("status", "failed")); break; } if (THRESHOLD_STARTED == thresholdState) { rv.push_back(Pair("bit", consensusParams.vDeployments[id].bit)); } rv.push_back( Pair("startTime", consensusParams.vDeployments[id].nStartTime)); rv.push_back(Pair("timeout", consensusParams.vDeployments[id].nTimeout)); rv.push_back( Pair("since", VersionBitsTipStateSinceHeight(consensusParams, id))); return rv; } void BIP9SoftForkDescPushBack(UniValue &bip9_softforks, const std::string &name, const Consensus::Params &consensusParams, Consensus::DeploymentPos id) { // Deployments with timeout value of 0 are hidden. // A timeout value of 0 guarantees a softfork will never be activated. // This is used when softfork codes are merged without specifying the // deployment schedule. if (consensusParams.vDeployments[id].nTimeout > 0) { bip9_softforks.push_back( Pair(name, BIP9SoftForkDesc(consensusParams, id))); } } UniValue getblockchaininfo(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( "getblockchaininfo\n" "Returns an object containing various state info regarding " "blockchain processing.\n" "\nResult:\n" "{\n" " \"chain\": \"xxxx\", (string) current network name as " "defined in BIP70 (main, test, regtest)\n" " \"blocks\": xxxxxx, (numeric) the current number of " "blocks processed in the server\n" " \"headers\": xxxxxx, (numeric) the current number of " "headers we have validated\n" " \"bestblockhash\": \"...\", (string) the hash of the currently " "best block\n" " \"difficulty\": xxxxxx, (numeric) the current difficulty\n" " \"mediantime\": xxxxxx, (numeric) median time for the " "current best block\n" " \"verificationprogress\": xxxx, (numeric) estimate of " "verification progress [0..1]\n" " \"chainwork\": \"xxxx\" (string) total amount of work in " "active chain, in hexadecimal\n" " \"pruned\": xx, (boolean) if the blocks are subject " "to pruning\n" " \"pruneheight\": xxxxxx, (numeric) lowest-height complete " "block stored\n" " \"softforks\": [ (array) status of softforks in " "progress\n" " {\n" " \"id\": \"xxxx\", (string) name of softfork\n" " \"version\": xx, (numeric) block version\n" " \"reject\": { (object) progress toward " "rejecting pre-softfork blocks\n" " \"status\": xx, (boolean) true if threshold " "reached\n" " },\n" " }, ...\n" " ],\n" " \"bip9_softforks\": { (object) status of BIP9 " "softforks in progress\n" " \"xxxx\" : { (string) name of the softfork\n" " \"status\": \"xxxx\", (string) one of \"defined\", " "\"started\", \"locked_in\", \"active\", \"failed\"\n" " \"bit\": xx, (numeric) the bit (0-28) in the " "block version field used to signal this softfork (only for " "\"started\" status)\n" " \"startTime\": xx, (numeric) the minimum median " "time past of a block at which the bit gains its meaning\n" " \"timeout\": xx, (numeric) the median time past " "of a block at which the deployment is considered failed if not " "yet locked in\n" " \"since\": xx (numeric) height of the first " "block to which the status applies\n" " }\n" " }\n" "}\n" "\nExamples:\n" + HelpExampleCli("getblockchaininfo", "") + HelpExampleRpc("getblockchaininfo", "")); } LOCK(cs_main); UniValue obj(UniValue::VOBJ); obj.push_back(Pair("chain", Params().NetworkIDString())); obj.push_back(Pair("blocks", int(chainActive.Height()))); obj.push_back( Pair("headers", pindexBestHeader ? pindexBestHeader->nHeight : -1)); obj.push_back( Pair("bestblockhash", chainActive.Tip()->GetBlockHash().GetHex())); obj.push_back(Pair("difficulty", double(GetDifficulty()))); obj.push_back( Pair("mediantime", int64_t(chainActive.Tip()->GetMedianTimePast()))); obj.push_back( Pair("verificationprogress", GuessVerificationProgress(Params().TxData(), chainActive.Tip()))); obj.push_back(Pair("chainwork", chainActive.Tip()->nChainWork.GetHex())); obj.push_back(Pair("pruned", fPruneMode)); const Consensus::Params &consensusParams = Params().GetConsensus(); CBlockIndex *tip = chainActive.Tip(); UniValue softforks(UniValue::VARR); UniValue bip9_softforks(UniValue::VOBJ); softforks.push_back(SoftForkDesc("bip34", 2, tip, consensusParams)); softforks.push_back(SoftForkDesc("bip66", 3, tip, consensusParams)); softforks.push_back(SoftForkDesc("bip65", 4, tip, consensusParams)); BIP9SoftForkDescPushBack(bip9_softforks, "csv", consensusParams, Consensus::DEPLOYMENT_CSV); obj.push_back(Pair("softforks", softforks)); obj.push_back(Pair("bip9_softforks", bip9_softforks)); if (fPruneMode) { CBlockIndex *block = chainActive.Tip(); while (block && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) { block = block->pprev; } obj.push_back(Pair("pruneheight", block->nHeight)); } return obj; } /** Comparison function for sorting the getchaintips heads. */ struct CompareBlocksByHeight { bool operator()(const CBlockIndex *a, const CBlockIndex *b) const { // Make sure that unequal blocks with the same height do not compare // equal. Use the pointers themselves to make a distinction. if (a->nHeight != b->nHeight) { return (a->nHeight > b->nHeight); } return a < b; } }; UniValue getchaintips(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( "getchaintips\n" "Return information about all known tips in the block tree," " including the main chain as well as orphaned branches.\n" "\nResult:\n" "[\n" " {\n" " \"height\": xxxx, (numeric) height of the chain tip\n" " \"hash\": \"xxxx\", (string) block hash of the tip\n" " \"branchlen\": 0 (numeric) zero for main chain\n" " \"status\": \"active\" (string) \"active\" for the main " "chain\n" " },\n" " {\n" " \"height\": xxxx,\n" " \"hash\": \"xxxx\",\n" " \"branchlen\": 1 (numeric) length of branch " "connecting the tip to the main chain\n" " \"status\": \"xxxx\" (string) status of the chain " "(active, valid-fork, valid-headers, headers-only, invalid)\n" " }\n" "]\n" "Possible values for status:\n" "1. \"invalid\" This branch contains at least one " "invalid block\n" "2. \"headers-only\" Not all blocks for this branch are " "available, but the headers are valid\n" "3. \"valid-headers\" All blocks are available for this " "branch, but they were never fully validated\n" "4. \"valid-fork\" This branch is not part of the " "active chain, but is fully validated\n" "5. \"active\" This is the tip of the active main " "chain, which is certainly valid\n" "\nExamples:\n" + HelpExampleCli("getchaintips", "") + HelpExampleRpc("getchaintips", "")); } LOCK(cs_main); /** * Idea: the set of chain tips is chainActive.tip, plus orphan blocks which * do not have another orphan building off of them. * Algorithm: * - Make one pass through mapBlockIndex, picking out the orphan blocks, * and also storing a set of the orphan block's pprev pointers. * - Iterate through the orphan blocks. If the block isn't pointed to by * another orphan, it is a chain tip. * - add chainActive.Tip() */ std::set setTips; std::set setOrphans; std::set setPrevs; for (const std::pair &item : mapBlockIndex) { if (!chainActive.Contains(item.second)) { setOrphans.insert(item.second); setPrevs.insert(item.second->pprev); } } for (std::set::iterator it = setOrphans.begin(); it != setOrphans.end(); ++it) { if (setPrevs.erase(*it) == 0) { setTips.insert(*it); } } // Always report the currently active tip. setTips.insert(chainActive.Tip()); /* Construct the output array. */ UniValue res(UniValue::VARR); for (const CBlockIndex *block : setTips) { UniValue obj(UniValue::VOBJ); obj.push_back(Pair("height", block->nHeight)); obj.push_back(Pair("hash", block->phashBlock->GetHex())); const int branchLen = block->nHeight - chainActive.FindFork(block)->nHeight; obj.push_back(Pair("branchlen", branchLen)); std::string status; if (chainActive.Contains(block)) { // This block is part of the currently active chain. status = "active"; } else if (block->nStatus & BLOCK_FAILED_MASK) { // This block or one of its ancestors is invalid. status = "invalid"; } else if (block->nChainTx == 0) { // This block cannot be connected because full block data for it or // one of its parents is missing. status = "headers-only"; } else if (block->IsValid(BLOCK_VALID_SCRIPTS)) { // This block is fully validated, but no longer part of the active // chain. It was probably the active block once, but was // reorganized. status = "valid-fork"; } else if (block->IsValid(BLOCK_VALID_TREE)) { // The headers for this block are valid, but it has not been // validated. It was probably never part of the most-work chain. status = "valid-headers"; } else { // No clue. status = "unknown"; } obj.push_back(Pair("status", status)); res.push_back(obj); } return res; } UniValue mempoolInfoToJSON() { UniValue ret(UniValue::VOBJ); ret.push_back(Pair("size", (int64_t)mempool.size())); ret.push_back(Pair("bytes", (int64_t)mempool.GetTotalTxSize())); ret.push_back(Pair("usage", (int64_t)mempool.DynamicMemoryUsage())); size_t maxmempool = GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; ret.push_back(Pair("maxmempool", (int64_t)maxmempool)); ret.push_back( Pair("mempoolminfee", ValueFromAmount(mempool.GetMinFee(maxmempool).GetFeePerK()))); return ret; } UniValue getmempoolinfo(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( "getmempoolinfo\n" "\nReturns details on the active state of the TX memory pool.\n" "\nResult:\n" "{\n" " \"size\": xxxxx, (numeric) Current tx count\n" " \"bytes\": xxxxx, (numeric) Transaction size.\n" " \"usage\": xxxxx, (numeric) Total memory usage for " "the mempool\n" " \"maxmempool\": xxxxx, (numeric) Maximum memory usage " "for the mempool\n" " \"mempoolminfee\": xxxxx (numeric) Minimum fee for tx to " "be accepted\n" "}\n" "\nExamples:\n" + HelpExampleCli("getmempoolinfo", "") + HelpExampleRpc("getmempoolinfo", "")); } return mempoolInfoToJSON(); } UniValue preciousblock(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "preciousblock \"blockhash\"\n" "\nTreats a block as if it were received before others with the " "same work.\n" "\nA later preciousblock call can override the effect of an " "earlier one.\n" "\nThe effects of preciousblock are not retained across restarts.\n" "\nArguments:\n" "1. \"blockhash\" (string, required) the hash of the block to " "mark as precious\n" "\nResult:\n" "\nExamples:\n" + HelpExampleCli("preciousblock", "\"blockhash\"") + HelpExampleRpc("preciousblock", "\"blockhash\"")); } std::string strHash = request.params[0].get_str(); uint256 hash(uint256S(strHash)); CBlockIndex *pblockindex; { LOCK(cs_main); if (mapBlockIndex.count(hash) == 0) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } pblockindex = mapBlockIndex[hash]; } CValidationState state; PreciousBlock(config, state, pblockindex); if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason()); } return NullUniValue; } UniValue invalidateblock(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "invalidateblock \"blockhash\"\n" "\nPermanently marks a block as invalid, as if it " "violated a consensus rule.\n" "\nArguments:\n" "1. \"blockhash\" (string, required) the hash of " "the block to mark as invalid\n" "\nResult:\n" "\nExamples:\n" + HelpExampleCli("invalidateblock", "\"blockhash\"") + HelpExampleRpc("invalidateblock", "\"blockhash\"")); } std::string strHash = request.params[0].get_str(); uint256 hash(uint256S(strHash)); CValidationState state; { LOCK(cs_main); if (mapBlockIndex.count(hash) == 0) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } CBlockIndex *pblockindex = mapBlockIndex[hash]; InvalidateBlock(config, state, pblockindex); } if (state.IsValid()) { ActivateBestChain(config, state); } if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason()); } return NullUniValue; } UniValue reconsiderblock(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "reconsiderblock \"blockhash\"\n" "\nRemoves invalidity status of a block and its descendants, " "reconsider them for activation.\n" "This can be used to undo the effects of invalidateblock.\n" "\nArguments:\n" "1. \"blockhash\" (string, required) the hash of the block to " "reconsider\n" "\nResult:\n" "\nExamples:\n" + HelpExampleCli("reconsiderblock", "\"blockhash\"") + HelpExampleRpc("reconsiderblock", "\"blockhash\"")); } std::string strHash = request.params[0].get_str(); uint256 hash(uint256S(strHash)); { LOCK(cs_main); if (mapBlockIndex.count(hash) == 0) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } CBlockIndex *pblockindex = mapBlockIndex[hash]; ResetBlockFailureFlags(pblockindex); } CValidationState state; ActivateBestChain(config, state); if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason()); } return NullUniValue; } // clang-format off static const CRPCCommand commands[] = { // category name actor (function) okSafe argNames // ------------------- ------------------------ ---------------------- ------ ---------- { "blockchain", "getblockchaininfo", getblockchaininfo, true, {} }, { "blockchain", "getbestblockhash", getbestblockhash, true, {} }, { "blockchain", "getblockcount", getblockcount, true, {} }, { "blockchain", "getblock", getblock, true, {"blockhash","verbose"} }, { "blockchain", "getblockhash", getblockhash, true, {"height"} }, { "blockchain", "getblockheader", getblockheader, true, {"blockhash","verbose"} }, { "blockchain", "getchaintips", getchaintips, true, {} }, { "blockchain", "getdifficulty", getdifficulty, true, {} }, { "blockchain", "getmempoolancestors", getmempoolancestors, true, {"txid","verbose"} }, { "blockchain", "getmempooldescendants", getmempooldescendants, true, {"txid","verbose"} }, { "blockchain", "getmempoolentry", getmempoolentry, true, {"txid"} }, { "blockchain", "getmempoolinfo", getmempoolinfo, true, {} }, { "blockchain", "getrawmempool", getrawmempool, true, {"verbose"} }, { "blockchain", "gettxout", gettxout, true, {"txid","n","include_mempool"} }, { "blockchain", "gettxoutsetinfo", gettxoutsetinfo, true, {} }, { "blockchain", "pruneblockchain", pruneblockchain, true, {"height"} }, { "blockchain", "verifychain", verifychain, true, {"checklevel","nblocks"} }, { "blockchain", "preciousblock", preciousblock, true, {"blockhash"} }, /* Not shown in help */ { "hidden", "invalidateblock", invalidateblock, true, {"blockhash"} }, { "hidden", "reconsiderblock", reconsiderblock, true, {"blockhash"} }, { "hidden", "waitfornewblock", waitfornewblock, true, {"timeout"} }, { "hidden", "waitforblock", waitforblock, true, {"blockhash","timeout"} }, { "hidden", "waitforblockheight", waitforblockheight, true, {"height","timeout"} }, }; // clang-format on void RegisterBlockchainRPCCommands(CRPCTable &t) { for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) { t.appendCommand(commands[vcidx].name, &commands[vcidx]); } } diff --git a/src/txdb.cpp b/src/txdb.cpp index 9172bf442a..0a791dea59 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -1,229 +1,233 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "txdb.h" #include "chainparams.h" #include "hash.h" #include "pow.h" #include "uint256.h" #include #include static const char DB_COINS = 'c'; static const char DB_BLOCK_FILES = 'f'; static const char DB_TXINDEX = 't'; static const char DB_BLOCK_INDEX = 'b'; static const char DB_BEST_BLOCK = 'B'; static const char DB_FLAG = 'F'; static const char DB_REINDEX_FLAG = 'R'; static const char DB_LAST_BLOCK = 'l'; CCoinsViewDB::CCoinsViewDB(size_t nCacheSize, bool fMemory, bool fWipe) : db(GetDataDir() / "chainstate", nCacheSize, fMemory, fWipe, true) {} bool CCoinsViewDB::GetCoins(const uint256 &txid, CCoins &coins) const { return db.Read(std::make_pair(DB_COINS, txid), coins); } bool CCoinsViewDB::HaveCoins(const uint256 &txid) const { return db.Exists(std::make_pair(DB_COINS, txid)); } uint256 CCoinsViewDB::GetBestBlock() const { uint256 hashBestChain; if (!db.Read(DB_BEST_BLOCK, hashBestChain)) return uint256(); return hashBestChain; } bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { CDBBatch batch(db); size_t count = 0; size_t changed = 0; for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) { if (it->second.flags & CCoinsCacheEntry::DIRTY) { if (it->second.coins.IsPruned()) batch.Erase(std::make_pair(DB_COINS, it->first)); else batch.Write(std::make_pair(DB_COINS, it->first), it->second.coins); changed++; } count++; CCoinsMap::iterator itOld = it++; mapCoins.erase(itOld); } if (!hashBlock.IsNull()) batch.Write(DB_BEST_BLOCK, hashBlock); LogPrint( "coindb", "Committing %u changed transactions (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count); return db.WriteBatch(batch); } +size_t CCoinsViewDB::EstimateSize() const { + return db.EstimateSize(DB_COINS, char(DB_COINS + 1)); +} + CBlockTreeDB::CBlockTreeDB(size_t nCacheSize, bool fMemory, bool fWipe) : CDBWrapper(GetDataDir() / "blocks" / "index", nCacheSize, fMemory, fWipe) {} bool CBlockTreeDB::ReadBlockFileInfo(int nFile, CBlockFileInfo &info) { return Read(std::make_pair(DB_BLOCK_FILES, nFile), info); } bool CBlockTreeDB::WriteReindexing(bool fReindexing) { if (fReindexing) return Write(DB_REINDEX_FLAG, '1'); else return Erase(DB_REINDEX_FLAG); } bool CBlockTreeDB::ReadReindexing(bool &fReindexing) { fReindexing = Exists(DB_REINDEX_FLAG); return true; } bool CBlockTreeDB::ReadLastBlockFile(int &nFile) { return Read(DB_LAST_BLOCK, nFile); } CCoinsViewCursor *CCoinsViewDB::Cursor() const { CCoinsViewDBCursor *i = new CCoinsViewDBCursor( const_cast(&db)->NewIterator(), GetBestBlock()); /** * It seems that there are no "const iterators" for LevelDB. Since we only * need read operations on it, use a const-cast to get around that * restriction. */ i->pcursor->Seek(DB_COINS); // Cache key of first record if (i->pcursor->Valid()) { i->pcursor->GetKey(i->keyTmp); } else { // Make sure Valid() and GetKey() return false i->keyTmp.first = 0; } return i; } bool CCoinsViewDBCursor::GetKey(uint256 &key) const { // Return cached key if (keyTmp.first == DB_COINS) { key = keyTmp.second; return true; } return false; } bool CCoinsViewDBCursor::GetValue(CCoins &coins) const { return pcursor->GetValue(coins); } unsigned int CCoinsViewDBCursor::GetValueSize() const { return pcursor->GetValueSize(); } bool CCoinsViewDBCursor::Valid() const { return keyTmp.first == DB_COINS; } void CCoinsViewDBCursor::Next() { pcursor->Next(); if (!pcursor->Valid() || !pcursor->GetKey(keyTmp)) // Invalidate cached key after last record so that Valid() and GetKey() // return false keyTmp.first = 0; } bool CBlockTreeDB::WriteBatchSync( const std::vector> &fileInfo, int nLastFile, const std::vector &blockinfo) { CDBBatch batch(*this); for (std::vector>::const_iterator it = fileInfo.begin(); it != fileInfo.end(); it++) { batch.Write(std::make_pair(DB_BLOCK_FILES, it->first), *it->second); } batch.Write(DB_LAST_BLOCK, nLastFile); for (std::vector::const_iterator it = blockinfo.begin(); it != blockinfo.end(); it++) { batch.Write(std::make_pair(DB_BLOCK_INDEX, (*it)->GetBlockHash()), CDiskBlockIndex(*it)); } return WriteBatch(batch, true); } bool CBlockTreeDB::ReadTxIndex(const uint256 &txid, CDiskTxPos &pos) { return Read(std::make_pair(DB_TXINDEX, txid), pos); } bool CBlockTreeDB::WriteTxIndex( const std::vector> &vect) { CDBBatch batch(*this); for (std::vector>::const_iterator it = vect.begin(); it != vect.end(); it++) batch.Write(std::make_pair(DB_TXINDEX, it->first), it->second); return WriteBatch(batch); } bool CBlockTreeDB::WriteFlag(const std::string &name, bool fValue) { return Write(std::make_pair(DB_FLAG, name), fValue ? '1' : '0'); } bool CBlockTreeDB::ReadFlag(const std::string &name, bool &fValue) { char ch; if (!Read(std::make_pair(DB_FLAG, name), ch)) return false; fValue = ch == '1'; return true; } bool CBlockTreeDB::LoadBlockIndexGuts( std::function insertBlockIndex) { std::unique_ptr pcursor(NewIterator()); pcursor->Seek(std::make_pair(DB_BLOCK_INDEX, uint256())); // Load mapBlockIndex while (pcursor->Valid()) { boost::this_thread::interruption_point(); std::pair key; if (!pcursor->GetKey(key) || key.first != DB_BLOCK_INDEX) { break; } CDiskBlockIndex diskindex; if (!pcursor->GetValue(diskindex)) { return error("LoadBlockIndex() : failed to read value"); } // Construct block index object CBlockIndex *pindexNew = insertBlockIndex(diskindex.GetBlockHash()); pindexNew->pprev = insertBlockIndex(diskindex.hashPrev); pindexNew->nHeight = diskindex.nHeight; pindexNew->nFile = diskindex.nFile; pindexNew->nDataPos = diskindex.nDataPos; pindexNew->nUndoPos = diskindex.nUndoPos; pindexNew->nVersion = diskindex.nVersion; pindexNew->hashMerkleRoot = diskindex.hashMerkleRoot; pindexNew->nTime = diskindex.nTime; pindexNew->nBits = diskindex.nBits; pindexNew->nNonce = diskindex.nNonce; pindexNew->nStatus = diskindex.nStatus; pindexNew->nTx = diskindex.nTx; if (!CheckProofOfWork(pindexNew->GetBlockHash(), pindexNew->nBits, Params().GetConsensus())) return error("LoadBlockIndex(): CheckProofOfWork failed: %s", pindexNew->ToString()); pcursor->Next(); } return true; } diff --git a/src/txdb.h b/src/txdb.h index 29841d4903..93a96dc349 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -1,127 +1,129 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_TXDB_H #define BITCOIN_TXDB_H #include "chain.h" #include "coins.h" #include "dbwrapper.h" #include #include #include #include class CBlockIndex; class CCoinsViewDBCursor; class uint256; //! Compensate for extra memory peak (x1.5-x1.9) at flush time. static constexpr int DB_PEAK_USAGE_FACTOR = 2; //! No need to periodic flush if at least this much space still available. static constexpr int MAX_BLOCK_COINSDB_USAGE = 200 * DB_PEAK_USAGE_FACTOR; //! Always periodic flush if less than this much space still available. static constexpr int MIN_BLOCK_COINSDB_USAGE = 50 * DB_PEAK_USAGE_FACTOR; //! -dbcache default (MiB) static const int64_t nDefaultDbCache = 450; //! max. -dbcache (MiB) static const int64_t nMaxDbCache = sizeof(void *) > 4 ? 16384 : 1024; //! min. -dbcache (MiB) static const int64_t nMinDbCache = 4; //! Max memory allocated to block tree DB specific cache, if no -txindex (MiB) static const int64_t nMaxBlockDBCache = 2; //! Max memory allocated to block tree DB specific cache, if -txindex (MiB) // Unlike for the UTXO database, for the txindex scenario the leveldb cache make // a meaningful difference: // https://github.com/bitcoin/bitcoin/pull/8273#issuecomment-229601991 static const int64_t nMaxBlockDBAndTxIndexCache = 1024; //! Max memory allocated to coin DB specific cache (MiB) static const int64_t nMaxCoinsDBCache = 8; struct CDiskTxPos : public CDiskBlockPos { unsigned int nTxOffset; // after header ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { READWRITE(*(CDiskBlockPos *)this); READWRITE(VARINT(nTxOffset)); } CDiskTxPos(const CDiskBlockPos &blockIn, unsigned int nTxOffsetIn) : CDiskBlockPos(blockIn.nFile, blockIn.nPos), nTxOffset(nTxOffsetIn) {} CDiskTxPos() { SetNull(); } void SetNull() { CDiskBlockPos::SetNull(); nTxOffset = 0; } }; /** CCoinsView backed by the coin database (chainstate/) */ class CCoinsViewDB : public CCoinsView { protected: CDBWrapper db; public: CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); bool GetCoins(const uint256 &txid, CCoins &coins) const; bool HaveCoins(const uint256 &txid) const; uint256 GetBestBlock() const; bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); CCoinsViewCursor *Cursor() const; + + size_t EstimateSize() const override; }; /** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */ class CCoinsViewDBCursor : public CCoinsViewCursor { public: ~CCoinsViewDBCursor() {} bool GetKey(uint256 &key) const; bool GetValue(CCoins &coins) const; unsigned int GetValueSize() const; bool Valid() const; void Next(); private: CCoinsViewDBCursor(CDBIterator *pcursorIn, const uint256 &hashBlockIn) : CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {} std::unique_ptr pcursor; std::pair keyTmp; friend class CCoinsViewDB; }; /** Access to the block database (blocks/index/) */ class CBlockTreeDB : public CDBWrapper { public: CBlockTreeDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); private: CBlockTreeDB(const CBlockTreeDB &); void operator=(const CBlockTreeDB &); public: bool WriteBatchSync( const std::vector> &fileInfo, int nLastFile, const std::vector &blockinfo); bool ReadBlockFileInfo(int nFile, CBlockFileInfo &fileinfo); bool ReadLastBlockFile(int &nFile); bool WriteReindexing(bool fReindex); bool ReadReindexing(bool &fReindex); bool ReadTxIndex(const uint256 &txid, CDiskTxPos &pos); bool WriteTxIndex(const std::vector> &list); bool WriteFlag(const std::string &name, bool fValue); bool ReadFlag(const std::string &name, bool &fValue); bool LoadBlockIndexGuts( std::function insertBlockIndex); }; #endif // BITCOIN_TXDB_H