diff options
author | MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> | 2023-01-19 16:02:54 +0100 |
---|---|---|
committer | MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> | 2023-01-19 16:03:08 +0100 |
commit | b5c88a547996776dbdc2e101bae9b67ac639fd02 (patch) | |
tree | 78389772458a6b8d9efe0ca91d8d140bef2be0a8 /src | |
parent | 05e3468fb3c084840a93aed31c67f062ef30ef58 (diff) | |
parent | 5eabb61b2386d00e93e6bbb2f493a56d1b326ad9 (diff) |
Merge bitcoin/bitcoin#26909: net: prevent peers.dat corruptions by only serializing once
5eabb61b2386d00e93e6bbb2f493a56d1b326ad9 addrdb: Only call Serialize() once (Martin Zumsande)
da6c7aeca38e1d0ab5839a374c26af0504d603fc hash: add HashedSourceWriter (Martin Zumsande)
Pull request description:
There have been various reports of corruption of `peers.dat` recently, see #26599.
As explained in [this post](https://github.com/bitcoin/bitcoin/issues/26599#issuecomment-1381082886) in more detail, the underlying issue is likely that we currently serialize `AddrMan` twice - once for the file stream, once for the hasher that helps create the checksum - and if `AddrMan` changes in between these two calls, the checksum doesn't match the data and the resulting `peers.dat` is corrupted.
This PR attempts to fix this by introducing and using `HashedSourceWriter` - a class that keeps a running hash while serializing data, similar to the existing `CHashVerifier` which does the analogous thing while unserializing data. Something like this was suggested before, see https://github.com/bitcoin/bitcoin/pull/10248#discussion_r120694343.
Fixes #26599 (not by changing the behavior in case of a crash, but by hopefully fixing the underlying cause of these corruptions).
ACKs for top commit:
sipa:
utACK 5eabb61b2386d00e93e6bbb2f493a56d1b326ad9
naumenkogs:
utACK 5eabb61b2386d00e93e6bbb2f493a56d1b326ad9
Tree-SHA512: f19ad37c37088d3a9825c60de2efe85cc2b7a21b79b9156024d33439e021443ef977a5f8424a7981bcc13d73d11e30eaa82de60e14d88b3568a67b03e02b153b
Diffstat (limited to 'src')
-rw-r--r-- | src/addrdb.cpp | 7 | ||||
-rw-r--r-- | src/addrman.cpp | 3 | ||||
-rw-r--r-- | src/hash.h | 25 | ||||
-rw-r--r-- | src/test/streams_tests.cpp | 14 |
4 files changed, 43 insertions, 6 deletions
diff --git a/src/addrdb.cpp b/src/addrdb.cpp index d95c07d6a8..7c954dbf3a 100644 --- a/src/addrdb.cpp +++ b/src/addrdb.cpp @@ -34,10 +34,9 @@ bool SerializeDB(Stream& stream, const Data& data) { // Write and commit header, data try { - CHashWriter hasher(stream.GetType(), stream.GetVersion()); - stream << Params().MessageStart() << data; - hasher << Params().MessageStart() << data; - stream << hasher.GetHash(); + HashedSourceWriter hashwriter{stream}; + hashwriter << Params().MessageStart() << data; + stream << hashwriter.GetHash(); } catch (const std::exception& e) { return error("%s: Serialize or I/O error - %s", __func__, e.what()); } diff --git a/src/addrman.cpp b/src/addrman.cpp index 91eedeebe1..f86bdfa3df 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -1178,8 +1178,7 @@ void AddrMan::Unserialize(Stream& s_) } // explicit instantiation -template void AddrMan::Serialize(CHashWriter& s) const; -template void AddrMan::Serialize(CAutoFile& s) const; +template void AddrMan::Serialize(HashedSourceWriter<CAutoFile>& s) const; template void AddrMan::Serialize(CDataStream& s) const; template void AddrMan::Unserialize(CAutoFile& s); template void AddrMan::Unserialize(CHashVerifier<CAutoFile>& s); diff --git a/src/hash.h b/src/hash.h index b18a031268..6500f1c709 100644 --- a/src/hash.h +++ b/src/hash.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_HASH_H #define BITCOIN_HASH_H +#include <attributes.h> #include <crypto/common.h> #include <crypto/ripemd160.h> #include <crypto/sha256.h> @@ -199,6 +200,30 @@ public: } }; +/** Writes data to an underlying source stream, while hashing the written data. */ +template <typename Source> +class HashedSourceWriter : public CHashWriter +{ +private: + Source& m_source; + +public: + explicit HashedSourceWriter(Source& source LIFETIMEBOUND) : CHashWriter{source.GetType(), source.GetVersion()}, m_source{source} {} + + void write(Span<const std::byte> src) + { + m_source.write(src); + CHashWriter::write(src); + } + + template <typename T> + HashedSourceWriter& operator<<(const T& obj) + { + ::Serialize(*this, obj); + return *this; + } +}; + /** Compute the 256-bit hash of an object's serialization. */ template<typename T> uint256 SerializeHash(const T& obj, int nType=SER_GETHASH, int nVersion=PROTOCOL_VERSION) diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index dce230ac10..03117f76ae 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -500,4 +500,18 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file_rand) fs::remove(streams_test_filename); } +BOOST_AUTO_TEST_CASE(streams_hashed) +{ + CDataStream stream(SER_NETWORK, INIT_PROTO_VERSION); + HashedSourceWriter hash_writer{stream}; + const std::string data{"bitcoin"}; + hash_writer << data; + + CHashVerifier hash_verifier{&stream}; + std::string result; + hash_verifier >> result; + BOOST_CHECK_EQUAL(data, result); + BOOST_CHECK_EQUAL(hash_writer.GetHash(), hash_verifier.GetHash()); +} + BOOST_AUTO_TEST_SUITE_END() |