Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
e17fbef
validation: skip coinsdb flush on prune-only pruned flushes during IBD
l0rinc Feb 9, 2026
71c86cb
consensus: avoid redundant utxo lookups in CheckTxInputs
l0rinc Feb 9, 2026
dc90f26
net_processing: skip txdownload maintenance during IBD
l0rinc Feb 9, 2026
1d78f91
validation: fill BIP68 prevheights during CheckTxInputs
l0rinc Feb 9, 2026
110b9ed
consensus: single-pass UTXO access in GetTransactionSigOpCost
l0rinc Feb 9, 2026
43191ac
util: allow caching outpoint hash codes
l0rinc Feb 9, 2026
4efed4b
validation: compute sigops cost during CheckTxInputs
l0rinc Feb 9, 2026
c6e389c
validation: avoid precomputing txdata when script checks are skipped
l0rinc Feb 9, 2026
eed04ed
node: scan block index once when pruning multiple files
l0rinc Feb 9, 2026
47e4396
coins: reuse outpoint when adding tx outputs
l0rinc Feb 9, 2026
c8c3372
tx: compute txid and wtxid in one serialization pass
l0rinc Feb 9, 2026
99f34a8
dbwrapper: bias coinsdb cache toward block cache
l0rinc Feb 9, 2026
4bbd0c7
dbwrapper: lower LevelDB block restart interval for IBD lookups
l0rinc Feb 9, 2026
cb6d6fc
dbwrapper: increase LevelDB bloom bits for chainstate lookups
l0rinc Feb 9, 2026
82e25c8
reindex: cut import info-log churn on no-op scans
l0rinc Feb 19, 2026
65a4d79
reindex: seek over known block payloads in import scan
l0rinc Feb 19, 2026
de6e8eb
blockstorage: hash undo while writing
l0rinc Feb 19, 2026
e3b6b58
blockstorage: drop blk/rev pages from OS cache during reindex
l0rinc Feb 19, 2026
e1dcdcf
kernel: raise max coinsdb cache
l0rinc Feb 19, 2026
b6910dd
dbwrapper: lower LevelDB block restart interval
l0rinc Feb 19, 2026
4471b5c
dbwrapper: increase LevelDB bloom filter bits
l0rinc Feb 19, 2026
020fd32
kernel: raise max block tree DB cache
l0rinc Feb 19, 2026
a855b56
blockstorage: reuse raw block buffer in ReadBlock
l0rinc Feb 19, 2026
142dde1
coins: skip unspendable outputs in AddCoins
l0rinc Feb 19, 2026
4d3fdb1
validation: avoid wiping UTXO cache on periodic 'large' flush
l0rinc Feb 19, 2026
df35617
kernel: default -dbcache 550 MiB and scale coinsdb cache
l0rinc Feb 19, 2026
cdafd00
util: mark SaltedOutpointHasher noexcept to save UTXO cache memory
l0rinc Feb 19, 2026
566d745
validation: avoid UTXO cache wipe on tiny size overshoot
l0rinc Feb 19, 2026
2d2f9e0
dbwrapper: reuse scratch string for Read/Exists
l0rinc Feb 19, 2026
bd83c97
dbwrapper: avoid DataStream copy for value deserialization
l0rinc Feb 19, 2026
b5ffab1
leveldb: avoid scratch alloc for mmap table reads
l0rinc Feb 19, 2026
e355fa0
dbwrapper: allocate more LevelDB cache to write buffers
l0rinc Feb 19, 2026
47250d6
dbwrapper: cap LevelDB write buffer by max_file_size
l0rinc Feb 19, 2026
168f10a
leveldb: prefer pread over mmap for table reads
l0rinc Feb 19, 2026
84c07e2
coins: use 1 MiB pool chunks for UTXO cache map
l0rinc Feb 19, 2026
85d307a
kernel: allocate more -dbcache to coinsdb LevelDB cache
l0rinc Feb 19, 2026
3367d87
coins: SpendCoin fast-path cached lookups
l0rinc Feb 19, 2026
5504618
dbwrapper: increase LevelDB bloom filter bits per key
l0rinc Feb 19, 2026
e548d61
dbwrapper: reuse key serialization buffer for reads
l0rinc Feb 19, 2026
fcc0240
coins: raise UTXO cache map load factor for density
l0rinc Feb 19, 2026
bdea9e0
dbwrapper: reuse key buffer for iterator Seek
l0rinc Feb 19, 2026
ad71b18
dbwrapper: reuse key buffers for EstimateSize
l0rinc Feb 19, 2026
42d9690
dbwrapper: avoid DataStream copy for iterator keys
l0rinc Feb 19, 2026
99289e2
dbwrapper: reuse buffer for iterator value deserialization
l0rinc Feb 19, 2026
912632a
leveldb: use 32-bit modulus for bloom bit positions
l0rinc Feb 19, 2026
024e6ee
validation: avoid CRITICAL flush on small coins cache overshoot
l0rinc Feb 19, 2026
9dbe85f
leveldb: hint random access for SSTable reads
l0rinc Feb 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions src/coins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ CCoinsViewCache::CCoinsViewCache(CCoinsView* baseIn, bool deterministic) :
CCoinsViewBacked(baseIn), m_deterministic(deterministic),
cacheCoins(0, SaltedOutpointHasher(/*deterministic=*/deterministic), CCoinsMap::key_equal{}, &m_cache_coins_memory_resource)
{
// Favor cache density once the UTXO set no longer fits in memory.
// A higher load factor reduces bucket array overhead and allows caching more coins
// for a fixed -dbcache at the cost of slightly more work per lookup.
cacheCoins.max_load_factor(2.0f);
m_sentinel.second.SelfRef(m_sentinel);
}

Expand Down Expand Up @@ -124,16 +128,26 @@ void CCoinsViewCache::EmplaceCoinInternalDANGER(COutPoint&& outpoint, Coin&& coi
void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool check_for_overwrite) {
bool fCoinbase = tx.IsCoinBase();
const Txid& txid = tx.GetHash();
// Reuse the outpoint to avoid copying the txid twice per output.
COutPoint outpoint{txid, 0};
for (size_t i = 0; i < tx.vout.size(); ++i) {
bool overwrite = check_for_overwrite ? cache.HaveCoin(COutPoint(txid, i)) : fCoinbase;
// Skip provably unspendable outputs early to avoid unnecessary cache work.
// AddCoin() will also check this, but doing it here avoids the overwrite
// lookup and Coin construction on common OP_RETURN outputs.
if (tx.vout[i].scriptPubKey.IsUnspendable()) continue;
outpoint.n = static_cast<uint32_t>(i);
bool overwrite = check_for_overwrite ? cache.HaveCoin(outpoint) : fCoinbase;
// Coinbase transactions can always be overwritten, in order to correctly
// deal with the pre-BIP30 occurrences of duplicate coinbase transactions.
cache.AddCoin(COutPoint(txid, i), Coin(tx.vout[i], nHeight, fCoinbase), overwrite);
cache.AddCoin(outpoint, Coin(tx.vout[i], nHeight, fCoinbase), overwrite);
}
}

bool CCoinsViewCache::SpendCoin(const COutPoint &outpoint, Coin* moveout) {
CCoinsMap::iterator it = FetchCoin(outpoint);
// SpendCoin is frequently called after an AccessCoin/HaveCoin on the same outpoint.
// Avoid the heavier try_emplace() path when the coin is already in the cache.
CCoinsMap::iterator it = cacheCoins.find(outpoint);
if (it == cacheCoins.end()) it = FetchCoin(outpoint);
if (it == cacheCoins.end()) return false;
cachedCoinsUsage -= it->second.coin.DynamicMemoryUsage();
TRACEPOINT(utxocache, spent,
Expand Down
4 changes: 3 additions & 1 deletion src/coins.h
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,9 @@ class CCoinsViewCache : public CCoinsViewBacked
* declared as "const".
*/
mutable uint256 hashBlock;
mutable CCoinsMapMemoryResource m_cache_coins_memory_resource{};
// Use larger pool chunks to reduce malloc/free churn when the cache grows large during
// reindex/IBD, while keeping the same total memory usage.
mutable CCoinsMapMemoryResource m_cache_coins_memory_resource{1 << 20};
/* The starting sentinel of the flagged entry circular doubly linked list. */
mutable CoinsCachePair m_sentinel;
mutable CCoinsMap cacheCoins;
Expand Down
51 changes: 38 additions & 13 deletions src/consensus/tx_verify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,33 +147,57 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i
if (tx.IsCoinBase())
return nSigOps;

if (flags & SCRIPT_VERIFY_P2SH) {
nSigOps += GetP2SHSigOpCount(tx, inputs) * WITNESS_SCALE_FACTOR;
}

for (unsigned int i = 0; i < tx.vin.size(); i++)
{
const bool fP2SH{static_cast<bool>(flags & SCRIPT_VERIFY_P2SH)};
for (unsigned int i = 0; i < tx.vin.size(); i++) {
const Coin& coin = inputs.AccessCoin(tx.vin[i].prevout);
assert(!coin.IsSpent());
const CTxOut &prevout = coin.out;
const CTxOut& prevout{coin.out};
if (fP2SH && prevout.scriptPubKey.IsPayToScriptHash()) {
nSigOps += prevout.scriptPubKey.GetSigOpCount(tx.vin[i].scriptSig) * WITNESS_SCALE_FACTOR;
}
nSigOps += CountWitnessSigOps(tx.vin[i].scriptSig, prevout.scriptPubKey, tx.vin[i].scriptWitness, flags);
}
return nSigOps;
}

bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee)
bool Consensus::CheckTxInputs(const CTransaction& tx,
TxValidationState& state,
const CCoinsViewCache& inputs,
int nSpendHeight,
CAmount& txfee,
std::vector<int>* prev_heights,
script_verify_flags flags,
int64_t* tx_sigops_cost)
{
// are the actual inputs available?
if (!inputs.HaveInputs(tx)) {
return state.Invalid(TxValidationResult::TX_MISSING_INPUTS, "bad-txns-inputs-missingorspent",
strprintf("%s: inputs missing/spent", __func__));
if (prev_heights) assert(prev_heights->size() == tx.vin.size());

int64_t sigops_cost{0};
if (tx_sigops_cost) {
// Compute sigops alongside input value checks to avoid re-walking the
// UTXO set (a major IBD bottleneck).
sigops_cost = GetLegacySigOpCount(tx) * WITNESS_SCALE_FACTOR;
}

CAmount nValueIn = 0;
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
const COutPoint &prevout = tx.vin[i].prevout;
const Coin& coin = inputs.AccessCoin(prevout);
assert(!coin.IsSpent());
// AccessCoin() returns an empty coin for missing inputs, so checking
// IsSpent() avoids a redundant HaveInputs() pass.
if (coin.IsSpent()) {
return state.Invalid(TxValidationResult::TX_MISSING_INPUTS, "bad-txns-inputs-missingorspent",
strprintf("%s: inputs missing/spent", __func__));
}
if (prev_heights) {
(*prev_heights)[i] = coin.nHeight;
}
if (tx_sigops_cost) {
const CTxOut& prev_tx_out{coin.out};
if (flags & SCRIPT_VERIFY_P2SH && prev_tx_out.scriptPubKey.IsPayToScriptHash()) {
sigops_cost += prev_tx_out.scriptPubKey.GetSigOpCount(tx.vin[i].scriptSig) * WITNESS_SCALE_FACTOR;
}
sigops_cost += CountWitnessSigOps(tx.vin[i].scriptSig, prev_tx_out.scriptPubKey, tx.vin[i].scriptWitness, flags);
}

// If prev is coinbase, check that it's matured
if (coin.IsCoinBase() && nSpendHeight - coin.nHeight < COINBASE_MATURITY) {
Expand Down Expand Up @@ -210,5 +234,6 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state,
}

txfee = txfee_aux;
if (tx_sigops_cost) *tx_sigops_cost = sigops_cost;
return true;
}
9 changes: 8 additions & 1 deletion src/consensus/tx_verify.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,16 @@ namespace Consensus {
* Check whether all inputs of this transaction are valid (no double spends and amounts)
* This does not modify the UTXO set. This does not check scripts and sigs.
* @param[out] txfee Set to the transaction fee if successful.
* @param[out] prev_heights If provided, it must be sized to tx.vin.size() and
* will be filled with the confirmation heights of the
* spent outputs (for use in BIP68 sequence locks).
* @param[out] tx_sigops_cost If provided, it will be filled with the sigops
* cost for this transaction (for use in block-level
* MAX_BLOCK_SIGOPS_COST checks).
* @param[in] flags Script verification flags used when calculating sigops cost.
* Preconditions: tx.IsCoinBase() is false.
*/
[[nodiscard]] bool CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee);
[[nodiscard]] bool CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee, std::vector<int>* prev_heights = nullptr, script_verify_flags flags = script_verify_flags{}, int64_t* tx_sigops_cost = nullptr);
} // namespace Consensus

/** Auxiliary functions for transaction validation (ideally should not be exposed) */
Expand Down
39 changes: 19 additions & 20 deletions src/dbwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,17 +139,25 @@ static void SetMaxOpenFiles(leveldb::Options *options) {
static leveldb::Options GetOptions(size_t nCacheSize)
{
leveldb::Options options;
options.block_cache = leveldb::NewLRUCache(nCacheSize / 2);
options.write_buffer_size = nCacheSize / 4; // up to two write buffers may be held in memory simultaneously
options.filter_policy = leveldb::NewBloomFilterPolicy(10);
options.max_file_size = std::max(options.max_file_size, DBWRAPPER_MAX_FILE_SIZE);
// During reindex/IBD, the chainstate DB is write-heavy and compaction can become a major IO
// bottleneck once the UTXO working set no longer fits in memory.
//
// LevelDB uses write_buffer_size as the memtable size, which also affects the size of
// level-0 files produced by memtable flushes. Keep it bounded by the target table file size
// to avoid producing oversized level-0 files that can increase overlap and compaction work.
const size_t max_write_buffer{options.max_file_size};
options.write_buffer_size = std::min(nCacheSize / 3, max_write_buffer); // up to two write buffers may be held in memory simultaneously
options.block_cache = leveldb::NewLRUCache(nCacheSize - options.write_buffer_size);
options.block_restart_interval = 4;
options.filter_policy = leveldb::NewBloomFilterPolicy(16);
options.compression = leveldb::kNoCompression;
options.info_log = new CBitcoinLevelDBLogger();
if (leveldb::kMajorVersion > 1 || (leveldb::kMajorVersion == 1 && leveldb::kMinorVersion >= 16)) {
// LevelDB versions before 1.16 consider short writes to be corruption. Only trigger error
// on corruption in later versions.
options.paranoid_checks = true;
}
options.max_file_size = std::max(options.max_file_size, DBWRAPPER_MAX_FILE_SIZE);
SetMaxOpenFiles(&options);
return options;
}
Expand Down Expand Up @@ -302,33 +310,24 @@ size_t CDBWrapper::DynamicMemoryUsage() const
return parsed.value();
}

std::optional<std::string> CDBWrapper::ReadImpl(std::span<const std::byte> key) const
bool CDBWrapper::ReadImpl(std::span<const std::byte> key, std::string& value) const
{
leveldb::Slice slKey(CharCast(key.data()), key.size());
std::string strValue;
leveldb::Status status = DBContext().pdb->Get(DBContext().readoptions, slKey, &strValue);
leveldb::Status status = DBContext().pdb->Get(DBContext().readoptions, slKey, &value);
if (!status.ok()) {
if (status.IsNotFound())
return std::nullopt;
return false;
LogError("LevelDB read failure: %s", status.ToString());
HandleError(status);
}
return strValue;
return true;
}

bool CDBWrapper::ExistsImpl(std::span<const std::byte> key) const
{
leveldb::Slice slKey(CharCast(key.data()), key.size());

std::string strValue;
leveldb::Status status = DBContext().pdb->Get(DBContext().readoptions, slKey, &strValue);
if (!status.ok()) {
if (status.IsNotFound())
return false;
LogError("LevelDB read failure: %s", status.ToString());
HandleError(status);
}
return true;
std::string& value{ScratchValueString()};
value.clear();
return ReadImpl(key, value);
}

size_t CDBWrapper::EstimateSizeImpl(std::span<const std::byte> key1, std::span<const std::byte> key2) const
Expand Down
55 changes: 42 additions & 13 deletions src/dbwrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ class CDBIterator
void SeekToFirst();

template<typename K> void Seek(const K& key) {
DataStream ssKey{};
static thread_local DataStream ssKey{};
ssKey.clear();
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
ssKey << key;
SeekImpl(ssKey);
Expand All @@ -153,7 +154,7 @@ class CDBIterator

template<typename K> bool GetKey(K& key) {
try {
DataStream ssKey{GetKeyImpl()};
SpanReader ssKey{GetKeyImpl()};
ssKey >> key;
} catch (const std::exception&) {
return false;
Expand All @@ -163,8 +164,12 @@ class CDBIterator

template<typename V> bool GetValue(V& value) {
try {
DataStream ssValue{GetValueImpl()};
dbwrapper_private::GetObfuscation(parent)(ssValue);
static thread_local DataStream ssValue{};
const auto sp_value{GetValueImpl()};
ssValue.clear();
ssValue.resize(sp_value.size());
std::memcpy(ssValue.data(), sp_value.data(), sp_value.size());
dbwrapper_private::GetObfuscation(parent)(std::span<std::byte>{ssValue.data(), ssValue.size()});
ssValue >> value;
} catch (const std::exception&) {
return false;
Expand All @@ -191,11 +196,29 @@ class CDBWrapper
//! obfuscation key storage key, null-prefixed to avoid collisions
inline static const std::string OBFUSCATION_KEY{"\000obfuscate_key", 14}; // explicit size to avoid truncation at leading \0

std::optional<std::string> ReadImpl(std::span<const std::byte> key) const;
bool ReadImpl(std::span<const std::byte> key, std::string& value) const;
bool ExistsImpl(std::span<const std::byte> key) const;
size_t EstimateSizeImpl(std::span<const std::byte> key1, std::span<const std::byte> key2) const;
auto& DBContext() const LIFETIMEBOUND { return *Assert(m_db_context); }

static DataStream& ScratchKeyStream() noexcept
{
static thread_local DataStream ssKey{};
return ssKey;
}

static DataStream& ScratchKeyStream2() noexcept
{
static thread_local DataStream ssKey{};
return ssKey;
}

static std::string& ScratchValueString() noexcept
{
static thread_local std::string value;
return value;
}

public:
CDBWrapper(const DBParams& params);
~CDBWrapper();
Expand All @@ -206,17 +229,19 @@ class CDBWrapper
template <typename K, typename V>
bool Read(const K& key, V& value) const
{
DataStream ssKey{};
DataStream& ssKey{ScratchKeyStream()};
ssKey.clear();
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
ssKey << key;
std::optional<std::string> strValue{ReadImpl(ssKey)};
if (!strValue) {
std::string& strValue{ScratchValueString()};
strValue.clear();
if (!ReadImpl(ssKey, strValue)) {
return false;
}
try {
std::span ssValue{MakeWritableByteSpan(*strValue)};
m_obfuscation(ssValue);
SpanReader{ssValue} >> value;
m_obfuscation(MakeWritableByteSpan(strValue));
SpanReader ssValue{MakeByteSpan(strValue)};
ssValue >> value;
} catch (const std::exception&) {
return false;
}
Expand All @@ -234,7 +259,8 @@ class CDBWrapper
template <typename K>
bool Exists(const K& key) const
{
DataStream ssKey{};
DataStream& ssKey{ScratchKeyStream()};
ssKey.clear();
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
ssKey << key;
return ExistsImpl(ssKey);
Expand Down Expand Up @@ -263,7 +289,10 @@ class CDBWrapper
template<typename K>
size_t EstimateSize(const K& key_begin, const K& key_end) const
{
DataStream ssKey1{}, ssKey2{};
DataStream& ssKey1{ScratchKeyStream()};
DataStream& ssKey2{ScratchKeyStream2()};
ssKey1.clear();
ssKey2.clear();
ssKey1.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
ssKey2.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
ssKey1 << key_begin;
Expand Down
2 changes: 2 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1333,6 +1333,8 @@ static ChainstateLoadResult InitAndLoadChainstate(

BlockManager::Options blockman_opts{
.chainparams = chainman_opts.chainparams,
.drop_os_cache = (do_reindex || do_reindex_chainstate) ||
(args.IsArgSet("-connect") && args.GetArgs("-connect").size() == 1 && args.GetArgs("-connect")[0] == "0"),
.blocks_dir = args.GetBlocksDirPath(),
.notifications = chainman_opts.notifications,
.block_tree_db_params = DBParams{
Expand Down
2 changes: 2 additions & 0 deletions src/kernel/blockmanager_opts.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ struct BlockManagerOpts {
bool use_xor{DEFAULT_XOR_BLOCKSDIR};
uint64_t prune_target{0};
bool fast_prune{false};
/** Hint to the OS that block/undo file pages can be dropped from cache after use. */
bool drop_os_cache{false};
const fs::path blocks_dir;
Notifications& notifications;
DBParams block_tree_db_params;
Expand Down
11 changes: 7 additions & 4 deletions src/kernel/caches.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
#include <algorithm>

//! Suggested default amount of cache reserved for the kernel (bytes)
static constexpr size_t DEFAULT_KERNEL_CACHE{450_MiB};
static constexpr size_t DEFAULT_KERNEL_CACHE{550_MiB};
//! Default LevelDB write batch size
static constexpr size_t DEFAULT_DB_CACHE_BATCH{32_MiB};

//! Max memory allocated to block tree DB specific cache (bytes)
static constexpr size_t MAX_BLOCK_DB_CACHE{2_MiB};
static constexpr size_t MAX_BLOCK_DB_CACHE{8_MiB};
//! Max memory allocated to coin DB specific cache (bytes)
static constexpr size_t MAX_COINS_DB_CACHE{8_MiB};
static constexpr size_t MAX_COINS_DB_CACHE{512_MiB};

namespace kernel {
struct CacheSizes {
Expand All @@ -29,7 +29,10 @@ struct CacheSizes {
{
block_tree_db = std::min(total_cache / 8, MAX_BLOCK_DB_CACHE);
total_cache -= block_tree_db;
coins_db = std::min(total_cache / 2, MAX_COINS_DB_CACHE);
// Prefer reserving most of the cache for the in-memory UTXO set, while still allowing
// the chainstate LevelDB cache (block cache + write buffers) to scale with -dbcache
// for IO-heavy startup/import/reindex scenarios.
coins_db = std::min(total_cache / 6, MAX_COINS_DB_CACHE);
total_cache -= coins_db;
coins = total_cache; // the rest goes to the coins cache
}
Expand Down
5 changes: 5 additions & 0 deletions src/leveldb/include/leveldb/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,11 @@ class LEVELDB_EXPORT RandomAccessFile {

virtual ~RandomAccessFile();

// Return true if this implementation may write into the caller-provided
// scratch buffer passed to Read(). Implementations backed by a stable mapping
// (e.g. mmap) can ignore scratch and return pointers into the mapping.
virtual bool RequiresScratch() const { return true; }

// Read up to "n" bytes from the file starting at "offset".
// "scratch[0..n-1]" may be written by this routine. Sets "*result"
// to the data that was read (including if fewer than "n" bytes were
Expand Down
Loading
Loading