Merge bitcoin/bitcoin#26909: net: prevent peers.dat corruptions by only serializing once

5eabb61b23 addrdb: Only call Serialize() once (Martin Zumsande)
da6c7aeca3 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 5eabb61b23
  naumenkogs:
    utACK 5eabb61b23

Tree-SHA512: f19ad37c37088d3a9825c60de2efe85cc2b7a21b79b9156024d33439e021443ef977a5f8424a7981bcc13d73d11e30eaa82de60e14d88b3568a67b03e02b153b
This commit is contained in:
MarcoFalke 2023-01-19 16:02:54 +01:00
commit b5c88a5479
No known key found for this signature in database
GPG Key ID: CE2B75697E69A548
4 changed files with 43 additions and 6 deletions

View File

@ -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());
}

View File

@ -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);

View File

@ -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)

View File

@ -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()