diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index cf2e388c174..5e1de9f0af4 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -660,11 +660,12 @@ bool BlockManager::ReadBlockUndo(CBlockUndo& blockundo, const CBlockIndex& index const FlatFilePos pos{WITH_LOCK(::cs_main, return index.GetUndoPos())}; // Open history file to read - AutoFile filein{OpenUndoFile(pos, true)}; - if (filein.IsNull()) { + AutoFile file{OpenUndoFile(pos, true)}; + if (file.IsNull()) { LogError("OpenUndoFile failed for %s while reading block undo", pos.ToString()); return false; } + BufferedReader filein{std::move(file)}; try { // Read block @@ -996,15 +997,14 @@ bool BlockManager::ReadBlock(CBlock& block, const FlatFilePos& pos) const block.SetNull(); // Open history file to read - AutoFile filein{OpenBlockFile(pos, /*fReadOnly=*/true)}; - if (filein.IsNull()) { - LogError("OpenBlockFile failed for %s while reading block", pos.ToString()); + std::vector block_data; + if (!ReadRawBlock(block_data, pos)) { return false; } try { // Read block - filein >> TX_WITH_WITNESS(block); + SpanReader{block_data} >> TX_WITH_WITNESS(block); } catch (const std::exception& e) { LogError("Deserialize or I/O error - %s at %s while reading block", e.what(), pos.ToString()); return false; diff --git a/src/streams.h b/src/streams.h index 81d95feaa8e..f19bef5df22 100644 --- a/src/streams.h +++ b/src/streams.h @@ -467,6 +467,8 @@ public: } }; +using DataBuffer = std::vector; + /** Wrapper around an AutoFile& that implements a ring buffer to * deserialize from. It guarantees the ability to rewind a given number of bytes. * @@ -481,7 +483,7 @@ private: uint64_t m_read_pos{0}; //!< how many bytes have been read from this uint64_t nReadLimit; //!< up to which position we're allowed to read uint64_t nRewind; //!< how many bytes we guarantee to rewind - std::vector vchBuf; //!< the buffer + DataBuffer vchBuf; //! read data from the source to fill the buffer bool Fill() { @@ -614,4 +616,46 @@ public: } }; +/** + * Wrapper that buffers reads from an underlying stream. + * Requires underlying stream to support read() and detail_fread() calls + * to support fixed-size and variable-sized reads, respectively. + */ +template +class BufferedReader +{ + S& m_src; + DataBuffer m_buf; + size_t m_buf_pos; + +public: + //! Requires stream ownership to prevent leaving the stream at an unexpected position after buffered reads. + explicit BufferedReader(S&& stream LIFETIMEBOUND, size_t size = 1 << 16) + requires std::is_rvalue_reference_v + : m_src{stream}, m_buf(size), m_buf_pos{size} {} + + void read(std::span dst) + { + if (const auto available{std::min(dst.size(), m_buf.size() - m_buf_pos)}) { + std::copy_n(m_buf.begin() + m_buf_pos, available, dst.begin()); + m_buf_pos += available; + dst = dst.subspan(available); + } + if (dst.size()) { + assert(m_buf_pos == m_buf.size()); + m_src.read(dst); + + m_buf_pos = 0; + m_buf.resize(m_src.detail_fread(m_buf)); + } + } + + template + BufferedReader& operator>>(T&& obj) + { + Unserialize(*this, obj); + return *this; + } +}; + #endif // BITCOIN_STREAMS_H diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index 1a44e66932c..6f47b98f297 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -2,6 +2,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include +#include #include #include #include @@ -553,6 +555,58 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file_rand) fs::remove(streams_test_filename); } +BOOST_AUTO_TEST_CASE(buffered_reader_matches_autofile_random_content) +{ + const size_t file_size{1 + m_rng.randrange(1 << 17)}; + const size_t buf_size{1 + m_rng.randrange(file_size)}; + const FlatFilePos pos{0, 0}; + + const FlatFileSeq test_file{m_args.GetDataDirBase(), "buffered_file_test_random", node::BLOCKFILE_CHUNK_SIZE}; + const std::vector obfuscation{m_rng.randbytes(8)}; + + // Write out the file with random content + { + AutoFile{test_file.Open(pos, /*read_only=*/false), obfuscation}.write(m_rng.randbytes(file_size)); + } + BOOST_CHECK_EQUAL(fs::file_size(test_file.FileName(pos)), file_size); + + { + AutoFile direct_file{test_file.Open(pos, /*read_only=*/true), obfuscation}; + + AutoFile buffered_file{test_file.Open(pos, /*read_only=*/true), obfuscation}; + BufferedReader buffered_reader{std::move(buffered_file), buf_size}; + + for (size_t total_read{0}; total_read < file_size;) { + const size_t read{Assert(std::min(1 + m_rng.randrange(m_rng.randbool() ? buf_size : 2 * buf_size), file_size - total_read))}; + + DataBuffer direct_file_buffer{read}; + direct_file.read(direct_file_buffer); + + DataBuffer buffered_buffer{read}; + buffered_reader.read(buffered_buffer); + + BOOST_CHECK_EQUAL_COLLECTIONS( + direct_file_buffer.begin(), direct_file_buffer.end(), + buffered_buffer.begin(), buffered_buffer.end() + ); + + total_read += read; + } + + { + DataBuffer excess_byte{1}; + BOOST_CHECK_EXCEPTION(direct_file.read(excess_byte), std::ios_base::failure, HasReason{"end of file"}); + } + + { + DataBuffer excess_byte{1}; + BOOST_CHECK_EXCEPTION(buffered_reader.read(excess_byte), std::ios_base::failure, HasReason{"end of file"}); + } + } + + fs::remove(test_file.FileName(pos)); +} + BOOST_AUTO_TEST_CASE(streams_hashed) { DataStream stream{};