diff --git a/src/blockencodings.h b/src/blockencodings.h --- a/src/blockencodings.h +++ b/src/blockencodings.h @@ -6,6 +6,7 @@ #define BITCOIN_BLOCKENCODINGS_H #include +#include class Config; class CTxMemPool; @@ -14,29 +15,6 @@ // writing an actual formatter here. using TransactionCompression = DefaultFormatter; -class DifferenceFormatter { - uint64_t m_shift = 0; - -public: - template void Ser(Stream &s, I v) { - if (v < m_shift || v >= std::numeric_limits::max()) { - throw std::ios_base::failure("differential value overflow"); - } - WriteCompactSize(s, v - m_shift); - m_shift = uint64_t(v) + 1; - } - template void Unser(Stream &s, I &v) { - uint64_t n = ReadCompactSize(s); - m_shift += n; - if (m_shift < n || m_shift >= std::numeric_limits::max() || - m_shift < std::numeric_limits::min() || - m_shift > std::numeric_limits::max()) { - throw std::ios_base::failure("differential value overflow"); - } - v = I(m_shift++); - } -}; - class BlockTransactionsRequest { public: // A BlockTransactionsRequest message diff --git a/src/serialize.h b/src/serialize.h --- a/src/serialize.h +++ b/src/serialize.h @@ -749,6 +749,39 @@ }; }; +/** + * Helper for differentially encoded Compact Size integers in lists. + * + * Instead of using raw indexes, the number encoded is the difference between + * the current index and the previous index, minus one. For example, a first + * index of 0 implies a real index of 0, a second index of 0 thereafter refers + * to a real index of 1, etc. + * + * To be used with a VectorFormatter. + */ +class DifferenceFormatter { + uint64_t m_shift = 0; + +public: + template void Ser(Stream &s, I v) { + if (v < m_shift || v >= std::numeric_limits::max()) { + throw std::ios_base::failure("differential value overflow"); + } + WriteCompactSize(s, v - m_shift); + m_shift = uint64_t(v) + 1; + } + template void Unser(Stream &s, I &v) { + uint64_t n = ReadCompactSize(s); + m_shift += n; + if (m_shift < n || m_shift >= std::numeric_limits::max() || + m_shift < std::numeric_limits::min() || + m_shift > std::numeric_limits::max()) { + throw std::ios_base::failure("differential value overflow"); + } + v = I(m_shift++); + } +}; + /** * Forward declarations */ diff --git a/src/test/serialize_tests.cpp b/src/test/serialize_tests.cpp --- a/src/test/serialize_tests.cpp +++ b/src/test/serialize_tests.cpp @@ -452,4 +452,62 @@ BOOST_CHECK(methodtest3 == methodtest4); } +BOOST_AUTO_TEST_CASE(difference_formatter) { + VectorFormatter formatter; + + { + std::vector indicesIn{0, 1, 2, 5, 10, 20, 50, 100}; + std::vector indicesOut; + + CDataStream ss(SER_DISK, PROTOCOL_VERSION); + formatter.Ser(ss, indicesIn); + + // Check the stream is differentially encoded. Don't care about the + // prefixes and vector length here (assumed to be < 253). + const std::string streamStr = ss.str(); + const std::string differences = + HexStr(streamStr.substr(streamStr.size() - indicesIn.size())); + BOOST_CHECK_EQUAL(differences, "0000000204091d31"); + + formatter.Unser(ss, indicesOut); + BOOST_CHECK_EQUAL_COLLECTIONS(indicesIn.begin(), indicesIn.end(), + indicesOut.begin(), indicesOut.end()); + } + + { + std::vector indicesIn{1, 0}; + + CDataStream ss(SER_DISK, PROTOCOL_VERSION); + BOOST_CHECK_EXCEPTION(formatter.Ser(ss, indicesIn), + std::ios_base::failure, + HasReason("differential value overflow")); + } + + { + std::vector indicesOut; + + // Compute the number of MAX_SIZE increment we need to cause an overflow + const uint64_t overflow = + uint64_t(std::numeric_limits::max()) + 1; + BOOST_CHECK_GE(overflow, MAX_SIZE); + const uint64_t overflowIter = overflow / MAX_SIZE; + + // Make sure the iteration fits in an uint32_t + BOOST_CHECK_LE(overflowIter, std::numeric_limits::max()); + uint32_t remainder = uint32_t(overflow - (MAX_SIZE * overflowIter)); + + CDataStream ss(SER_DISK, PROTOCOL_VERSION); + WriteCompactSize(ss, overflowIter); + for (uint32_t i = 0; i < overflowIter; i++) { + WriteCompactSize(ss, MAX_SIZE); + } + // This is the overflow + WriteCompactSize(ss, remainder); + + BOOST_CHECK_EXCEPTION(formatter.Unser(ss, indicesOut), + std::ios_base::failure, + HasReason("differential value overflow")); + } +} + BOOST_AUTO_TEST_SUITE_END()