The problem was originally reported in https://github.com/bitcoin/bitcoin/issues/22450#issuecomment-880602891. This is a dedicated issue to track it independently because it is a different problem than the one reported in #22450 OP/description.

> If the same I2P address appears in multiple new buckets, then the first time this loop finds that address, it'll update the port and reposition it in that bucket. Any subsequent times that the address is encountered, it will already have had its port updated so the inner loop will `continue` ...

Thanks, @jnewbery!
*
     * vvNew, vvTried, mapInfo, mapAddr and vRandom are never encoded explicitly;
     * they are instead reconstructed from the other information.
     *
     * This format is more complex, but significantly smaller (at most 1.5 MiB), and supports
     * changes to the ADDRMAN_ parameters without breaking the on-disk structure.
     *
     * We don't use SERIALIZE_METHODS since the serialization and deserialization code has
     * very little in common.
     */
    template <typename Stream>
    void Serialize(Stream& s_) const
        EXCLUSIVE_LOCKS_REQUIRED(!cs)
    {
        LOCK(cs);
        
        OverrideStream<Stream> s(&s_, s_.GetType(), s_.GetVersion() | ADDRV2_FORMAT);
        s << static_cast<uint8_t>(FILE_FORMAT);
        
        
        static constexpr uint8_t lowest_compatible = Format::V3_BIP155;
        s << static_cast<uint8_t>(INCOMPATIBILITY_BASE + lowest_compatible);
        s << nKey;
        s << nNew;
        s << nTried;
        int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT ^ (1 << 30);
        s << nUBuckets;
        std::unordered_map<int, int> mapUnkIds;
        int nIds = 0;
        for (const auto& entry : mapInfo) {