Skip to content

Conversation

@maflcko
Copy link
Member

@maflcko maflcko commented Dec 28, 2020

The fuzz tests have several problems:

  • The array passed to the fuzz engine to pick net_permission_flags is outdated
  • The process_message* targets has the service flags as well as connection type hardcoded, limiting potential coverage
  • The service flags deserialization from the fuzz engine doesn't allow for easy "exact matches". The fuzz engine has to explore a 64-bit space to hit an "exact match" (only one bit set)

Fix all issues in the commits in this pull

@practicalswift
Copy link
Contributor

Concept ACK

Nice improvements!

@DrahtBot DrahtBot added the Tests label Dec 28, 2020
@DrahtBot
Copy link
Contributor

DrahtBot commented Dec 29, 2020

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Conflicts

Reviewers, this pull request conflicts with the following ones:

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

Copy link
Contributor

@vasild vasild left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK fa9ab9a8c

The chance of 5 random commits to all start with the same two letters is 0.00000002% 👀 😮

Comment on lines +33 to +40
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it is possible to get a warning if a new enum value is added, but this array is forgotten to be updated? I think not :/

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is ok as it is, and I am just wondering if this can be improved wrt minimizing the chance of the actual enum and the "all" arrays going out of sync over time. Maybe one of the below would help:

o Add a comment next to the enum definition "don't forget to update ALL_... in src/test/util/net.h if you change this enum".

o Define the array immediately below the enum itself.

o Define both in one struct
struct Color
{
    enum E : uint8_t {
        RED,
        GREEN,
        BLUE,
    };

    static constexpr E all[]{
        RED,
        GREEN,
        BLUE,
    };
};

...
    std::cout << Color::BLUE << std::endl;

    for (auto& c : Color::all) {
        std::cout << c << std::endl;
    }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we could start using kMaxValue as in the Chromium project. That way we could ​assert that the last element of the ALL_* array is equal to the enum's kMaxValue.

Another benefit from using kMaxValue is that we could start using FuzzedDataProvider::ConsumeEnum when fuzzing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that is another way. enum Network uses this. It only works as long as enum values are left at their defaults:

* Keep these sequential starting from 0 and `NET_MAX` as the last entry.

and also the dummy "max" value has to be explicitly handled in switch without default:

bitcoin/src/netaddress.cpp

Lines 145 to 146 in f1f26b8

case NET_MAX:
assert(false);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For strong enums, I was thinking about a macro ENUM_CLASS_ALL that writes the enum class and std::array that holds all enum types of that class. Thus, we could keep using PickValueInArray for strong enums. FuzzedDataProvider::ConsumeEnum is an alternative for strong enums that does exactly the same from the perspective of the fuzz engine.

For weak enums, ConsumeWeakEnum is probably the best bet. I tried kMaxValue and it performed strictly worse in all scenarios, in one it was unable to find a crash at all. And it can't possibly work for 64-bit enums. Someone should report those bugs to google. See also the benchmark: #20789 (comment)

@maflcko
Copy link
Member Author

maflcko commented Dec 31, 2020

Force pushed to fix a bug. Should be trivial to re-ACK

Copy link
Contributor

@vasild vasild left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK fa

@maflcko
Copy link
Member Author

maflcko commented Dec 31, 2020

Benchmarks

The goal is to benchmark the complexity of finding a strong and weak match of an enum type.
Strong match means the fuzz engine finds an exact value match. (built-in operator== for integers)
Weak match means the fuzz engine finds a matching flag. (built-in operator& for integers)

To reproduce:

  • For all benchmarks clang version 11.0.0 without sanitizers is used.
  • Only the fuzz target process_message_inv is used.
  • -use_value_profile=1 is set at runtime.
  • All patches are on top of this pull request.

Strong (scoped) enum class

While C++ doesn't restrict how scoped enum classes are used, in our code base, scoped enum classes avoid operator&, so this benchmark only considers an exact match.

diffs

The following diff was used for "kMaxValue":

diff --git a/src/net.h b/src/net.h
index 6316c73eee..a4930b229c 100644
--- a/src/net.h
+++ b/src/net.h
@@ -178,6 +178,7 @@ enum class ConnectionType {
      * AddrMan is empty.
      */
     ADDR_FETCH,
+    kMaxValue=ADDR_FETCH,
 };
 
 class NetEventsInterface;
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index c5ea2dc85f..964d4e1899 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -2702,6 +2702,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat
                     return;
                 } else if (!fAlreadyHave && !m_chainman.ActiveChainstate().IsInitialBlockDownload()) {
                     AddTxAnnouncement(pfrom, gtxid, current_time);
+                    assert(!pfrom.IsFeelerConn());
                 }
             } else {
                 LogPrint(BCLog::NET, "Unknown inv type \"%s\" received from peer=%d\n", inv.ToString(), pfrom.GetId());
diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h
index 465452c394..9e345d0c73 100644
--- a/src/test/fuzz/util.h
+++ b/src/test/fuzz/util.h
@@ -307,7 +307,7 @@ auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<N
     const uint64_t local_host_nonce = fuzzed_data_provider.ConsumeIntegral<uint64_t>();
     const CAddress addr_bind = ConsumeAddress(fuzzed_data_provider);
     const std::string addr_name = fuzzed_data_provider.ConsumeRandomLengthString(64);
-    const ConnectionType conn_type = fuzzed_data_provider.PickValueInArray(ALL_CONNECTION_TYPES);
+    const ConnectionType conn_type = fuzzed_data_provider.ConsumeEnum<ConnectionType>();
     const bool inbound_onion = fuzzed_data_provider.ConsumeBool();
     if constexpr (ReturnUniquePtr) {
         return std::make_unique<CNode>(node_id, local_services, my_starting_height, socket, address, keyed_net_group, local_host_nonce, addr_bind, addr_name, conn_type, inbound_onion);

The following diff was used for "ArrayEnum":

diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index c5ea2dc85f..964d4e1899 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -2702,6 +2702,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat
                     return;
                 } else if (!fAlreadyHave && !m_chainman.ActiveChainstate().IsInitialBlockDownload()) {
                     AddTxAnnouncement(pfrom, gtxid, current_time);
+                    assert(!pfrom.IsFeelerConn());
                 }
             } else {
                 LogPrint(BCLog::NET, "Unknown inv type \"%s\" received from peer=%d\n", inv.ToString(), pfrom.GetId());
kMaxValue (ConsumeEnum) ArrayEnum (PickValueInArray)
fdp_enum fdp_arr

No difference should be observed because both calls to the fuzz engine are compiled down to the same ConsumeIntegralInRange. Given the limited experimental data, this seems to hold in practice.

Weak enum

Weak match (operator&)

diffs

Diff for "kMaxValue":

diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index c5ea2dc85f..2acd336c1b 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -2702,6 +2702,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat
                     return;
                 } else if (!fAlreadyHave && !m_chainman.ActiveChainstate().IsInitialBlockDownload()) {
                     AddTxAnnouncement(pfrom, gtxid, current_time);
+                    assert(!(pfrom.GetLocalServices() & NODE_NETWORK_LIMITED));
                 }
             } else {
                 LogPrint(BCLog::NET, "Unknown inv type \"%s\" received from peer=%d\n", inv.ToString(), pfrom.GetId());
diff --git a/src/protocol.h b/src/protocol.h
index 8af34f58bd..ae863b13d0 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -287,6 +287,7 @@ enum ServiceFlags : uint64_t {
     // serving the last 288 (2 day) blocks
     // See BIP159 for details on how this is implemented.
     NODE_NETWORK_LIMITED = (1 << 10),
+    kMaxValue=NODE_NETWORK_LIMITED,
 
     // Bits 24-31 are reserved for temporary experiments. Just pick a bit that
     // isn't getting used, or one not being used much, and notify the
diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h
index 465452c394..7f9a72b585 100644
--- a/src/test/fuzz/util.h
+++ b/src/test/fuzz/util.h
@@ -299,7 +299,7 @@ template <bool ReturnUniquePtr = false>
 auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<NodeId>& node_id_in = nullopt) noexcept
 {
     const NodeId node_id = node_id_in.value_or(fuzzed_data_provider.ConsumeIntegral<NodeId>());
-    const ServiceFlags local_services = ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS);
+    const ServiceFlags local_services = fuzzed_data_provider.ConsumeEnum<ServiceFlags>();
     const int my_starting_height = fuzzed_data_provider.ConsumeIntegral<int>();
     const SOCKET socket = INVALID_SOCKET;
     const CAddress address = ConsumeAddress(fuzzed_data_provider);

Diff for "ArrayEnum":

diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index c5ea2dc85f..2acd336c1b 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -2702,6 +2702,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat
                     return;
                 } else if (!fAlreadyHave && !m_chainman.ActiveChainstate().IsInitialBlockDownload()) {
                     AddTxAnnouncement(pfrom, gtxid, current_time);
+                    assert(!(pfrom.GetLocalServices() & NODE_NETWORK_LIMITED));
                 }
             } else {
                 LogPrint(BCLog::NET, "Unknown inv type \"%s\" received from peer=%d\n", inv.ToString(), pfrom.GetId());
kMaxValue (ConsumeEnum) ArrayEnum (ConsumeWeakEnum)
fdp_enum_weak_match our_weak_weak_match

Exact match (operator==)

diffs

Diff used for "kMaxValue":

diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index c5ea2dc85f..be2a9fac3d 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -2702,6 +2702,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat
                     return;
                 } else if (!fAlreadyHave && !m_chainman.ActiveChainstate().IsInitialBlockDownload()) {
                     AddTxAnnouncement(pfrom, gtxid, current_time);
+                    assert(!(pfrom.GetLocalServices() == NODE_NETWORK_LIMITED));
                 }
             } else {
                 LogPrint(BCLog::NET, "Unknown inv type \"%s\" received from peer=%d\n", inv.ToString(), pfrom.GetId());
diff --git a/src/protocol.h b/src/protocol.h
index 8af34f58bd..ae863b13d0 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -287,6 +287,7 @@ enum ServiceFlags : uint64_t {
     // serving the last 288 (2 day) blocks
     // See BIP159 for details on how this is implemented.
     NODE_NETWORK_LIMITED = (1 << 10),
+    kMaxValue=NODE_NETWORK_LIMITED,
 
     // Bits 24-31 are reserved for temporary experiments. Just pick a bit that
     // isn't getting used, or one not being used much, and notify the
diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h
index 465452c394..7f9a72b585 100644
--- a/src/test/fuzz/util.h
+++ b/src/test/fuzz/util.h
@@ -299,7 +299,7 @@ template <bool ReturnUniquePtr = false>
 auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<NodeId>& node_id_in = nullopt) noexcept
 {
     const NodeId node_id = node_id_in.value_or(fuzzed_data_provider.ConsumeIntegral<NodeId>());
-    const ServiceFlags local_services = ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS);
+    const ServiceFlags local_services = fuzzed_data_provider.ConsumeEnum<ServiceFlags>();
     const int my_starting_height = fuzzed_data_provider.ConsumeIntegral<int>();
     const SOCKET socket = INVALID_SOCKET;
     const CAddress address = ConsumeAddress(fuzzed_data_provider);

Diff used for "ArrayEnum":

diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index c5ea2dc85f..be2a9fac3d 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -2702,6 +2702,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat
                     return;
                 } else if (!fAlreadyHave && !m_chainman.ActiveChainstate().IsInitialBlockDownload()) {
                     AddTxAnnouncement(pfrom, gtxid, current_time);
+                    assert(!(pfrom.GetLocalServices() == NODE_NETWORK_LIMITED));
                 }
             } else {
                 LogPrint(BCLog::NET, "Unknown inv type \"%s\" received from peer=%d\n", inv.ToString(), pfrom.GetId());
kMaxValue (ConsumeEnum) ArrayEnum (ConsumeWeakEnum)
fdp_enum_exact_match our_weak_exact_match

Double weak match (low bits)

diffs

Diff for "kMaxValue":

diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index c5ea2dc85f..8946a750a8 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -2702,6 +2702,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat
                     return;
                 } else if (!fAlreadyHave && !m_chainman.ActiveChainstate().IsInitialBlockDownload()) {
                     AddTxAnnouncement(pfrom, gtxid, current_time);
+                    assert(!HasAllDesirableServiceFlags(pfrom.GetLocalServices()));
                 }
             } else {
                 LogPrint(BCLog::NET, "Unknown inv type \"%s\" received from peer=%d\n", inv.ToString(), pfrom.GetId());
diff --git a/src/protocol.h b/src/protocol.h
index 8af34f58bd..ae863b13d0 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -287,6 +287,7 @@ enum ServiceFlags : uint64_t {
     // serving the last 288 (2 day) blocks
     // See BIP159 for details on how this is implemented.
     NODE_NETWORK_LIMITED = (1 << 10),
+    kMaxValue=NODE_NETWORK_LIMITED,
 
     // Bits 24-31 are reserved for temporary experiments. Just pick a bit that
     // isn't getting used, or one not being used much, and notify the
diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h
index 465452c394..7f9a72b585 100644
--- a/src/test/fuzz/util.h
+++ b/src/test/fuzz/util.h
@@ -299,7 +299,7 @@ template <bool ReturnUniquePtr = false>
 auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<NodeId>& node_id_in = nullopt) noexcept
 {
     const NodeId node_id = node_id_in.value_or(fuzzed_data_provider.ConsumeIntegral<NodeId>());
-    const ServiceFlags local_services = ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS);
+    const ServiceFlags local_services = fuzzed_data_provider.ConsumeEnum<ServiceFlags>();
     const int my_starting_height = fuzzed_data_provider.ConsumeIntegral<int>();
     const SOCKET socket = INVALID_SOCKET;
     const CAddress address = ConsumeAddress(fuzzed_data_provider);

Diff for ArrayEnum:

diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index c5ea2dc85f..8946a750a8 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -2702,6 +2702,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat
                     return;
                 } else if (!fAlreadyHave && !m_chainman.ActiveChainstate().IsInitialBlockDownload()) {
                     AddTxAnnouncement(pfrom, gtxid, current_time);
+                    assert(!HasAllDesirableServiceFlags(pfrom.GetLocalServices()));
                 }
             } else {
                 LogPrint(BCLog::NET, "Unknown inv type \"%s\" received from peer=%d\n", inv.ToString(), pfrom.GetId());
kMaxValue (ConsumeEnum) ArrayEnum (ConsumeWeakEnum)
fdp_enum_double our_enum_double

Double weak match (high bits)

diffs

Diff for "kMaxValue":

diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index c5ea2dc85f..d0e0167746 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -2702,6 +2702,8 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat
                     return;
                 } else if (!fAlreadyHave && !m_chainman.ActiveChainstate().IsInitialBlockDownload()) {
                     AddTxAnnouncement(pfrom, gtxid, current_time);
+                    constexpr auto crash_target = NODE_NETWORK_LIMITED | NODE_WITNESS;
+                    assert(!((pfrom.GetLocalServices() & crash_target) == crash_target));
                 }
             } else {
                 LogPrint(BCLog::NET, "Unknown inv type \"%s\" received from peer=%d\n", inv.ToString(), pfrom.GetId());
diff --git a/src/protocol.h b/src/protocol.h
index 8af34f58bd..ae863b13d0 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -287,6 +287,7 @@ enum ServiceFlags : uint64_t {
     // serving the last 288 (2 day) blocks
     // See BIP159 for details on how this is implemented.
     NODE_NETWORK_LIMITED = (1 << 10),
+    kMaxValue=NODE_NETWORK_LIMITED,
 
     // Bits 24-31 are reserved for temporary experiments. Just pick a bit that
     // isn't getting used, or one not being used much, and notify the
diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h
index 465452c394..7f9a72b585 100644
--- a/src/test/fuzz/util.h
+++ b/src/test/fuzz/util.h
@@ -299,7 +299,7 @@ template <bool ReturnUniquePtr = false>
 auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<NodeId>& node_id_in = nullopt) noexcept
 {
     const NodeId node_id = node_id_in.value_or(fuzzed_data_provider.ConsumeIntegral<NodeId>());
-    const ServiceFlags local_services = ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS);
+    const ServiceFlags local_services = fuzzed_data_provider.ConsumeEnum<ServiceFlags>();
     const int my_starting_height = fuzzed_data_provider.ConsumeIntegral<int>();
     const SOCKET socket = INVALID_SOCKET;
     const CAddress address = ConsumeAddress(fuzzed_data_provider);

Diff for ArrayEnum:

diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index c5ea2dc85f..e8c6b00ad7 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -2702,6 +2702,8 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat
                     return;
                 } else if (!fAlreadyHave && !m_chainman.ActiveChainstate().IsInitialBlockDownload()) {
                     AddTxAnnouncement(pfrom, gtxid, current_time);
+                    constexpr auto crash_target = NODE_NETWORK_LIMITED | NODE_WITNESS;
+                    assert(!(pfrom.GetLocalServices()&crash_target==crash_target));
                 }
             } else {
                 LogPrint(BCLog::NET, "Unknown inv type \"%s\" received from peer=%d\n", inv.ToString(), pfrom.GetId());
kMaxValue (ConsumeEnum) ArrayEnum (ConsumeWeakEnum)
n/a (aborted without any crash after 5.2*10^6 iterations) our_enum_double_high

@practicalswift
Copy link
Contributor

cr ACK fadbf6a83040b7a8b9f91e27b61adfaf4c5df3fb

Thanks for providing benchmarks!

@maflcko
Copy link
Member Author

maflcko commented Dec 31, 2020

@vasild btw for the merge script to pick up your ack, you'll have to provide at least the first 6 chars of the git hash ;)

@maflcko maflcko force-pushed the 2012-fuzzRefactor branch from fadbf6a to fac2b6c Compare January 1, 2021 11:10
@vasild
Copy link
Contributor

vasild commented Jan 1, 2021

ACK fac2b6c

Both fadbf6a83 and fac2b6c compile fine with clang 12.0.0.

@MarcoFalke so you were lucky enough to get 15 straight commit ids all starting with fa. I guess you have to have 16777216 times more CPUluck in order to make a commit with arbitrary contents that starts with fac2b6c5 :-D

Happy New Year! 🎉

@mzumsande
Copy link
Contributor

ACK fac2b6c

Code looks good to me, built locally and did some testing, as in the benchmarks.

As for the worse performance of kMaxValue, I think calling this a "bug" that should be reported is too hard: FuzzedDataProvider::ConsumeEnum() does have a disclaimer "The enum must start at 0 and be contiguous" so it's just not designed for bitmasks such as enum ServiceFlags. Also, considering that all ConsumeEnum() does is to call ConsumeIntegralInRange between 0 and kMaxValue, it is clear that NODE_NETWORK_LIMITED | NODE_WITNESS, which is > kMaxValue in the benchmark "Double weak match (high bits)" can't possibly ever hit.

@maflcko
Copy link
Member Author

maflcko commented Jan 2, 2021

Oh, this is missing from the documentation in the declaration:

// Returns a value chosen from the given enum.
template <typename T> T ConsumeEnum();

It is only mentioned in the implementation:

// Returns an enum value. The enum must start at 0 and be contiguous. It must
// also contain |kMaxValue| aliased to its largest (inclusive) value. Such as:
// enum class Foo { SomeValue, OtherValue, kMaxValue = OtherValue };
template <typename T> T FuzzedDataProvider::ConsumeEnum() {
static_assert(std::is_enum<T>::value, "|T| must be an enum type.");
return static_cast<T>(
ConsumeIntegralInRange<uint32_t>(0, static_cast<uint32_t>(T::kMaxValue)));
}

@maflcko
Copy link
Member Author

maflcko commented Jan 2, 2021

Rebased due to trivial conflict in adjacent line

@jonatack
Copy link
Member

jonatack commented Jan 2, 2021

Concept ACK, first read-through looks very good.

@maflcko maflcko force-pushed the 2012-fuzzRefactor branch from fa58fe3 to eeee43b Compare January 2, 2021 14:08
@maflcko
Copy link
Member Author

maflcko commented Jan 2, 2021

Rebased after removal of my_starting_height

@maflcko
Copy link
Member Author

maflcko commented Jan 7, 2021

Btw, should be (relatively) easy to re-ACK 🙏

@mzumsande
Copy link
Contributor

ACK eeee43b after rebase.

@maflcko maflcko merged commit 3a6acd1 into bitcoin:master Jan 7, 2021
@maflcko maflcko deleted the 2012-fuzzRefactor branch January 7, 2021 16:10
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Jan 7, 2021
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Aug 16, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants