Skip to content

Commit

Permalink
Merge pull request #1961 from cyrossignol/memory-block-index
Browse files Browse the repository at this point in the history
refactor: [Memory optimization] Separate chain trust from the block index
  • Loading branch information
jamescowens authored Nov 8, 2020
2 parents cfefef4 + 54caf8a commit 93f3867
Show file tree
Hide file tree
Showing 7 changed files with 403 additions and 83 deletions.
1 change: 1 addition & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ GRIDCOIN_CORE_H = \
gridcoin/scraper/http.h \
gridcoin/scraper/scraper.h \
gridcoin/scraper/scraper_net.h \
gridcoin/staking/chain_trust.h \
gridcoin/staking/difficulty.h \
gridcoin/staking/exceptions.h \
gridcoin/staking/kernel.h \
Expand Down
360 changes: 360 additions & 0 deletions src/gridcoin/staking/chain_trust.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,360 @@
// Copyright (c) 2014-2020 The Gridcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#pragma once

#include "arith_uint256.h"

#include <array>
#include <vector>

namespace GRC {
//!
//! \brief Calculates and stores chain trust values.
//!
//! Chain trust is a metric that represents the difficulty sum of a chain. We
//! calculate it for a particular block by adding that block's own difficulty
//! value to the total difficulty of all the blocks below it. The trust value
//! of a chain protects against some attack types by enabling nodes to select
//! the chain that required the most effort to produce. For Gridcoin, this is
//! the chain with the greatest amount of staked coins over time. A node will
//! reorganize to a competing chain only if the fork exhibits a greater chain
//! trust value.
//!
//! This class caches chain trust values to determine when a block produces a
//! chain with greater trust. It replaces the chain trust fields in the block
//! index to conserve memory.
//!
class ChainTrustCache
{
public:
ChainTrustCache()
: m_best_pindex(new CBlockIndex()) // Genesis bootstrap placeholder
{
}

//!
//! \brief Fill the cache from the block index and calculate the trust of
//! the best chain.
//!
//! \param genesis Blockchain index for the genesis block.
//! \param tip Blockchain index for the highest known block.
//!
void Initialize(const CBlockIndex* genesis, const CBlockIndex* tip)
{
int64_t start_time = GetTimeMillis();

if (m_best_pindex->phashBlock == nullptr) {
delete m_best_pindex; // Free genesis bootstrap placeholder
}

m_best_pindex = tip;
m_best_trust = 0;
m_long_cache.Reserve(tip);

for (const CBlockIndex* pindex = genesis; pindex; pindex = pindex->pnext) {
m_best_trust += pindex->GetBlockTrust();

if (m_long_cache.Expects(pindex)) {
m_long_cache.Push(m_best_trust);
}
}

LogPrintf(
"Gridcoin: Time to calculate chain trust %15" PRId64 "ms",
GetTimeMillis() - start_time);
}

//!
//! \brief Get the chain trust value of the current chain.
//!
//! \return Chain trust value at the current best block.
//!
arith_uint256 Best() const
{
return m_best_trust;
}

//!
//! \brief Determine whether a block exhibits greater chain trust than the
//! current best.
//!
//! \param pindex Points to the block index entry to compare.
//!
//! \return \c true if the specified block exhibits greater chain trust.
//!
bool Favors(const CBlockIndex* pindex) const
{
return GetTrust(pindex) > Best();
}

//!
//! \brief Calculate the chain trust value of the specified block.
//!
//! \param pindex Points to the block index entry to calculate trust for.
//!
//! \return Chain trust value for the specified block.
//!
arith_uint256 GetTrust(const CBlockIndex* pindex) const
{
arith_uint256 chain_trust;

if (pindex == nullptr) {
return chain_trust;
}

// First, look for a cached chain trust value near the chain tip:
//
if (pindex->nHeight >= m_best_pindex->nHeight
|| m_short_cache.MightContain(pindex))
{
for (; pindex != nullptr; pindex = pindex->pprev) {
if (pindex == m_best_pindex) {
return chain_trust + m_best_trust;
} else if (const auto trust_option = m_short_cache.Try(pindex)) {
return chain_trust + *trust_option;
}

chain_trust += pindex->GetBlockTrust();
}
}

// Otherwise, calculate chain trust up to one of the cached snapshots:
//
for (; pindex != nullptr; pindex = pindex->pprev) {
if (m_long_cache.Expects(pindex)) {
return chain_trust + m_long_cache.Fetch(pindex);
}

chain_trust += pindex->GetBlockTrust();
}

return chain_trust;
}

//!
//! \brief Remember the specified block as the tip of the best chain.
//!
//! \param pindex Points to the block index entry selected as the tip of
//! the chain.
//!
void SetBest(const CBlockIndex* pindex)
{
m_best_trust = pindex->GetBlockTrust() + GetTrust(pindex->pprev);
m_best_pindex = pindex;

if (m_long_cache.Expects(pindex)) {
m_long_cache.Store(pindex, m_best_trust);
} else {
m_short_cache.Store(pindex, m_best_trust);
}
}

private:
//!
//! \brief A circular buffer that caches chain trust values for blocks
//! recently-connected to the chain.
//!
//! This cache speeds up access to chain trust values for rapid changes to
//! the blocks at the chain tip. It retains the chain trust for forks that
//! occur to reduce the overhead of trust calculation triggered by a chain
//! reorganization.
//!
class ShortTrustCache
{
//!
//! \brief Number of entries to store in the cache.
//!
static constexpr size_t SIZE = 32;

public:
//!
//! \brief Determine whether the cache may contain the specified block.
//!
//! \param pindex Points the block index entry to check.
//!
//! \return \c true if the block height falls within the cached range.
//!
bool MightContain(const CBlockIndex* const pindex) const
{
for (const auto& slot : m_index_cache) {
if (slot != nullptr && slot->nHeight <= pindex->nHeight) {
return true;
}
}

return false;
}

//!
//! \brief Get the cached chain trust value for the specified block if
//! it exists in the cache.
//!
//! \param pindex Points to the block index entry to fetch trust for.
//!
//! \return Either contains a trust value for the block or does not.
//!
boost::optional<arith_uint256> Try(const CBlockIndex* const pindex) const
{
for (size_t i = 0; i < SIZE; ++i) {
if (m_index_cache[i] == pindex) {
return m_trust_cache[i];
}
}

return boost::none;
}

//!
//! \brief Store a trust value in the cache.
//!
//! \param pindex Points to the block index entry of the trust value.
//! \param trust Chain trust value to associate with the block.
//!
void Store(const CBlockIndex* const pindex, const arith_uint256 trust)
{
const size_t offset = m_position % SIZE;

m_index_cache[offset] = pindex;
m_trust_cache[offset] = trust;

++m_position;
}

private:
//!
//! \brief Circular buffer that stores block index entries of the chain
//! trust values in the cache.
//!
//! Since we scan the block index entries far more than we access the
//! trust values, the cache splits storage into two arrays to improve
//! CPU cache friendliness somewhat.
//!
std::array<const CBlockIndex*, SIZE> m_index_cache;

//!
//! \brief Circular buffer that stores chain trust values in the cache.
//!
std::array<arith_uint256, SIZE> m_trust_cache;

size_t m_position = 0; //!< Circular buffer counter
}; // ShortTrustCache

//!
//! \brief A persistent cache of historical chain trust snapshots created
//! at regular intervals in the blockchain.
//!
//! This cache reduces the effort needed to calculate the chain trust for
//! historical blocks by providing the aggregated trust for points in the
//! chain. A trust calculation can start with one of these points instead
//! of scanning the entire chain.
//!
class LongTrustCache
{
//!
//! \brief Height interval to store chain trust snapshots at.
//!
//! TODO: make this configurable for users and services that want a
//! more dense cache to improve "getblock" RPC performance.
//!
static constexpr size_t LONG_CACHE_INTERVAL = 10000;

public:
//!
//! \brief Determine whether the cache stores a chain trust snapshot
//! for the specified block.
//!
//! \param pindex Points to the block index entry to check.
//!
//! \return \c true if the block height matches the cache interval.
//!
bool Expects(const CBlockIndex* const pindex) const
{
return pindex->nHeight % LONG_CACHE_INTERVAL == 0;
}

//!
//! \brief Get the cached chain trust value for the specified block.
//!
//! \param pindex Points to a block index entry for an interval height.
//!
//! \return Chain trust value for the block.
//!
arith_uint256 Fetch(const CBlockIndex* const pindex) const
{
return m_cache[GetOffset(pindex)];
}

//!
//! \brief Store a trust value in the cache.
//!
//! \param pindex Points to the block index entry of the trust value.
//! \param trust Chain trust value to associate with the block.
//!
void Store(const CBlockIndex* const pindex, const arith_uint256 trust)
{
const size_t offset = GetOffset(pindex);

if (offset >= m_cache.size()) {
assert(offset == m_cache.size());
Reserve(pindex); // Just grow by 1. This doesn't happen often.
Push(trust);
} else {
m_cache[offset] = trust;

// Invalidate cached snapshots greater than this height:
m_cache.erase(m_cache.begin() + offset + 1, m_cache.end());
}
}

//!
//! \brief Reallocate storage for the cache if needed.
//!
//! \param pindex The height that determines the size of the cache.
//!
void Reserve(const CBlockIndex* const pindex)
{
m_cache.reserve(GetOffset(pindex) + 1);
}

//!
//! \brief Append a chain trust value directly to the end of the cache.
//!
//! \param trust Chain trust value for the block height at the tip of
//! the cache.
//!
void Push(const arith_uint256 trust)
{
m_cache.emplace_back(trust);
}

private:
//!
//! \brief Get the cache offset for the height of the specified block.
//!
//! \param pindex Block index entry with a height to map to an offset.
//!
//! \return Offset to store a trust value at for the specified block.
//!
static size_t GetOffset(const CBlockIndex* const pindex)
{
return pindex->nHeight / LONG_CACHE_INTERVAL;
}

//!
//! \brief Maps block heights to cached chain trust values.
//!
//! This stores chain trust values only for blocks that occur at each
//! height interval. We scale the height by the interval to determine
//! the offset in the container.
//!
std::vector<arith_uint256> m_cache;
}; // LongTrustCache

const CBlockIndex* m_best_pindex; //!< Block index entry for the chain tip.
arith_uint256 m_best_trust; //!< Chain trust value for the chain tip.
ShortTrustCache m_short_cache; //!< Caches chain trust at the chain tip.
LongTrustCache m_long_cache; //!< Caches chain trust at height intervals.
}; // ChainTrustCache
} // namespace GRC
Loading

0 comments on commit 93f3867

Please sign in to comment.