Repository: bitcoin/bitcoin (language: cpp)

Issue:
Avoid InitError when downgrading peers.dat
See https://github.com/bitcoin/bitcoin/pull/22762#issuecomment-951063826

This can be fixed by creating a new exception type and catching it in the right place.

#### Useful skills:

* C++ exceptions
* Addrman serialization
* Functional tests

#### Want to work on this issue?

For guidance on contributing, please read [CONTRIBUTING.md](https://github.com/bitcoin/bitcoin/blob/master/CONTRIBUTING.md) before opening your pull request.



Discussion / hints:

Fix patch:
diff --git a/src/addrdb.cpp b/src/addrdb.cpp
index 94518b88d8670..9f3e2e7219ed4 100644
--- a/src/addrdb.cpp
+++ b/src/addrdb.cpp
@@ -196,6 +196,15 @@ std::optional<bilingual_str> LoadAddrman(const std::vector<bool>& asmap, const A
         addrman = std::make_unique<AddrMan>(asmap, /* deterministic */ false, /* consistency_check_ratio */ check_addrman);
         LogPrintf("Creating peers.dat because the file was not found (%s)\n", fs::quoted(fs::PathToString(path_addr)));
         DumpPeerAddresses(args, *addrman);
+    } catch (const InvalidAddrManVersionError&) {
+        if (!RenameOver(path_addr, (fs::path)path_addr + ".bak")) {
+            addrman = nullptr;
+            return strprintf(_("Failed to rename invalid peers.dat file. Please move or delete it and try again."));
+        }
+        // Addrman can be in an inconsistent state after failure, reset it
+        addrman = std::make_unique<AddrMan>(asmap, /* deterministic */ false, /* consistency_check_ratio */ check_addrman);
+        LogPrintf("Creating new peers.dat because the file version was not compatible (%s). Original backed up to peers.dat.bak\n", fs::quoted(fs::PathToString(path_addr)));
+        DumpPeerAddresses(args, *addrman);
     } catch (const std::exception& e) {
         addrman = nullptr;
         return strprintf(_("Invalid or corrupt peers.dat (%s). If you believe this is a bug, please report it to %s. As a workaround, you can move the file (%s) out of the way (rename, move, or delete) to have a new one created on the next start."),
diff --git a/src/addrman.cpp b/src/addrman.cpp
index 3a845b5b6eb3e..f91a9799346ce 100644
--- a/src/addrman.cpp
+++ b/src/addrman.cpp
@@ -248,7 +248,7 @@ void AddrManImpl::Unserialize(Stream& s_)
     s >> compat;
     const uint8_t lowest_compatible = compat - INCOMPATIBILITY_BASE;
     if (lowest_compatible > FILE_FORMAT) {
-        throw std::ios_base::failure(strprintf(
+        throw InvalidAddrManVersionError(strprintf(
             "Unsupported format of addrman database: %u. It is compatible with formats >=%u, "
             "but the maximum supported by this version of %s is %u.",
             uint8_t{format}, uint8_t{lowest_compatible}, PACKAGE_NAME, uint8_t{FILE_FORMAT}));
diff --git a/src/addrman.h b/src/addrman.h
index 0646ef368d00b..472282833b0cb 100644
--- a/src/addrman.h
+++ b/src/addrman.h
@@ -17,6 +17,12 @@
 #include <utility>
 #include <vector>
 
+class InvalidAddrManVersionError : public std::ios_base::failure
+{
+public:
+    InvalidAddrManVersionError(std::string msg) : std::ios_base::failure(msg) { }
+};
+
 class AddrManImpl;
 
 /** Default for -checkaddrman */


Test patch:
diff --git a/test/functional/feature_addrman.py b/test/functional/feature_addrman.py
index 14a4f8abb778b..0fdefaa9c303a 100755
--- a/test/functional/feature_addrman.py
+++ b/test/functional/feature_addrman.py
@@ -68,17 +68,16 @@ def run_test(self):
             self.start_node(0, extra_args=["-checkaddrman=1"])
         assert_equal(self.nodes[0].getnodeaddresses(), [])
 
-        self.log.info("Check that addrman from future cannot be read")
+        self.log.info("Check that addrman from future is overwritten with new addrman")
         self.stop_node(0)
         write_addrman(peers_dat, lowest_compatible=111)
-        self.nodes[0].assert_start_raises_init_error(
-            expected_msg=init_error(
-                "Unsupported format of addrman database: 1. It is compatible with "
-                "formats >=111, but the maximum supported by this version of "
-                f"{self.config['environment']['PACKAGE_NAME']} is 4.: (.+)"
-            ),
-            match=ErrorMatch.FULL_REGEX,
-        )
+        assert_equal(os.path.exists(peers_dat + ".bak"), False)
+        with self.nodes[0].assert_debug_log([
+                f'Creating new peers.dat because the file version was not compatible ("{peers_dat}"). Original backed up to peers.dat.bak',
+        ]):
+            self.start_node(0)
+        assert_equal(self.nodes[0].getnodeaddresses(), [])
+        assert_equal(os.path.exists(peers_dat + ".bak"), True)
 
         self.log.info("Check that corrupt addrman cannot be read (EOF)")
         self.stop_node(0)