mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-12-13 20:36:21 +01:00
blockstorage: allow reading partial block data from storage
It will allow fetching specific transactions using an external index, following https://github.com/bitcoin/bitcoin/pull/32541#issuecomment-3267485313. No logging takes place in case of an invalid offset/size (to avoid spamming the log), by using a new `ReadRawError::BadPartRange` error variant. Co-authored-by: Hodlinator <172445034+hodlinator@users.noreply.github.com> Co-authored-by: Lőrinc <pap.lorinc@gmail.com>
This commit is contained in:
@@ -1048,7 +1048,7 @@ bool BlockManager::ReadBlock(CBlock& block, const CBlockIndex& index) const
|
||||
return ReadBlock(block, block_pos, index.GetBlockHash());
|
||||
}
|
||||
|
||||
BlockManager::ReadRawBlockResult BlockManager::ReadRawBlock(const FlatFilePos& pos) const
|
||||
BlockManager::ReadRawBlockResult BlockManager::ReadRawBlock(const FlatFilePos& pos, std::optional<std::pair<size_t, size_t>> block_part) const
|
||||
{
|
||||
if (pos.nPos < STORAGE_HEADER_BYTES) {
|
||||
// If nPos is less than STORAGE_HEADER_BYTES, we can't read the header that precedes the block data
|
||||
@@ -1081,6 +1081,15 @@ BlockManager::ReadRawBlockResult BlockManager::ReadRawBlock(const FlatFilePos& p
|
||||
return util::Unexpected{ReadRawError::IO};
|
||||
}
|
||||
|
||||
if (block_part) {
|
||||
const auto [offset, size]{*block_part};
|
||||
if (size == 0 || offset >= blk_size || size > blk_size - offset) {
|
||||
return util::Unexpected{ReadRawError::BadPartRange}; // Avoid logging - offset/size come from untrusted REST input
|
||||
}
|
||||
filein.seek(offset, SEEK_CUR);
|
||||
blk_size = size;
|
||||
}
|
||||
|
||||
std::vector<std::byte> data(blk_size); // Zeroing of memory is intentional here
|
||||
filein.read(data);
|
||||
return data;
|
||||
|
||||
@@ -172,6 +172,7 @@ std::ostream& operator<<(std::ostream& os, const BlockfileCursor& cursor);
|
||||
|
||||
enum class ReadRawError {
|
||||
IO,
|
||||
BadPartRange,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -460,7 +461,7 @@ public:
|
||||
/** Functions for disk access for blocks */
|
||||
bool ReadBlock(CBlock& block, const FlatFilePos& pos, const std::optional<uint256>& expected_hash) const;
|
||||
bool ReadBlock(CBlock& block, const CBlockIndex& index) const;
|
||||
ReadRawBlockResult ReadRawBlock(const FlatFilePos& pos) const;
|
||||
ReadRawBlockResult ReadRawBlock(const FlatFilePos& pos, std::optional<std::pair<size_t, size_t>> block_part = std::nullopt) const;
|
||||
|
||||
bool ReadBlockUndo(CBlockUndo& blockundo, const CBlockIndex& index) const;
|
||||
|
||||
|
||||
@@ -420,6 +420,7 @@ static bool rest_block(const std::any& context,
|
||||
if (!block_data) {
|
||||
switch (block_data.error()) {
|
||||
case node::ReadRawError::IO: return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "I/O error reading " + hashStr);
|
||||
case node::ReadRawError::BadPartRange: break; // can happen only when reading a block part
|
||||
}
|
||||
assert(false);
|
||||
}
|
||||
|
||||
@@ -138,6 +138,68 @@ BOOST_FIXTURE_TEST_CASE(blockmanager_block_data_availability, TestChain100Setup)
|
||||
BOOST_CHECK(!blockman.CheckBlockDataAvailability(tip, *last_pruned_block));
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(blockmanager_block_data_part, TestChain100Setup)
|
||||
{
|
||||
LOCK(::cs_main);
|
||||
auto& chainman{m_node.chainman};
|
||||
auto& blockman{chainman->m_blockman};
|
||||
const CBlockIndex& tip{*chainman->ActiveTip()};
|
||||
const FlatFilePos tip_block_pos{tip.GetBlockPos()};
|
||||
|
||||
auto block{blockman.ReadRawBlock(tip_block_pos)};
|
||||
BOOST_REQUIRE(block);
|
||||
BOOST_REQUIRE_GE(block->size(), 200);
|
||||
|
||||
const auto expect_part{[&](size_t offset, size_t size) {
|
||||
auto res{blockman.ReadRawBlock(tip_block_pos, std::pair{offset, size})};
|
||||
BOOST_CHECK(res);
|
||||
const auto& part{res.value()};
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(part.begin(), part.end(), block->begin() + offset, block->begin() + offset + size);
|
||||
}};
|
||||
|
||||
expect_part(0, 20);
|
||||
expect_part(0, block->size() - 1);
|
||||
expect_part(0, block->size() - 10);
|
||||
expect_part(0, block->size());
|
||||
expect_part(1, block->size() - 1);
|
||||
expect_part(10, 20);
|
||||
expect_part(block->size() - 1, 1);
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(blockmanager_block_data_part_error, TestChain100Setup)
|
||||
{
|
||||
LOCK(::cs_main);
|
||||
auto& chainman{m_node.chainman};
|
||||
auto& blockman{chainman->m_blockman};
|
||||
const CBlockIndex& tip{*chainman->ActiveTip()};
|
||||
const FlatFilePos tip_block_pos{tip.GetBlockPos()};
|
||||
|
||||
auto block{blockman.ReadRawBlock(tip_block_pos)};
|
||||
BOOST_REQUIRE(block);
|
||||
BOOST_REQUIRE_GE(block->size(), 200);
|
||||
|
||||
const auto expect_part_error{[&](size_t offset, size_t size) {
|
||||
auto res{blockman.ReadRawBlock(tip_block_pos, std::pair{offset, size})};
|
||||
BOOST_CHECK(!res);
|
||||
BOOST_CHECK_EQUAL(res.error(), node::ReadRawError::BadPartRange);
|
||||
}};
|
||||
|
||||
expect_part_error(0, 0);
|
||||
expect_part_error(0, block->size() + 1);
|
||||
expect_part_error(0, std::numeric_limits<size_t>::max());
|
||||
expect_part_error(1, block->size());
|
||||
expect_part_error(2, block->size() - 1);
|
||||
expect_part_error(block->size() - 1, 2);
|
||||
expect_part_error(block->size() - 2, 3);
|
||||
expect_part_error(block->size() + 1, 0);
|
||||
expect_part_error(block->size() + 1, 1);
|
||||
expect_part_error(block->size() + 2, 2);
|
||||
expect_part_error(block->size(), 0);
|
||||
expect_part_error(block->size(), 1);
|
||||
expect_part_error(std::numeric_limits<size_t>::max(), 1);
|
||||
expect_part_error(std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max());
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(blockmanager_readblock_hash_mismatch, TestingSetup)
|
||||
{
|
||||
CBlockIndex index;
|
||||
|
||||
Reference in New Issue
Block a user