From be84f678b925d7c8ae1b8fdd84480b372d528bcd Mon Sep 17 00:00:00 2001
From: Lee Clagett <code@leeclagett.com>
Date: Tue, 3 Nov 2020 13:58:14 -0500
Subject: [PATCH] Switch to Dandelion++ fluff mode if no out connections for
 stem mode

---
 src/cryptonote_protocol/levin_notify.cpp |  10 +--
 tests/unit_tests/levin.cpp               | 109 +++++++++++++++++++++++
 2 files changed, 113 insertions(+), 6 deletions(-)

diff --git a/src/cryptonote_protocol/levin_notify.cpp b/src/cryptonote_protocol/levin_notify.cpp
index dbd11e7d0..9d349d3df 100644
--- a/src/cryptonote_protocol/levin_notify.cpp
+++ b/src/cryptonote_protocol/levin_notify.cpp
@@ -524,12 +524,7 @@ namespace levin
         if (!zone_ || !core_ || txs_.empty())
           return;
 
-        if (zone_->fluffing)
-        {
-          core_->on_transactions_relayed(epee::to_span(txs_), relay_method::fluff);
-          fluff_notify::run(std::move(zone_), epee::to_span(txs_), source_);
-        }
-        else // forward tx in stem
+        if (!zone_->fluffing)
         {
           core_->on_transactions_relayed(epee::to_span(txs_), relay_method::stem);
           for (int tries = 2; 0 < tries; tries--)
@@ -549,6 +544,9 @@ namespace levin
 
           MERROR("Unable to send transaction(s) via Dandelion++ stem");
         }
+
+        core_->on_transactions_relayed(epee::to_span(txs_), relay_method::fluff);
+        fluff_notify::run(std::move(zone_), epee::to_span(txs_), source_);
       }
     };
 
diff --git a/tests/unit_tests/levin.cpp b/tests/unit_tests/levin.cpp
index f8f1ac2da..28032fa50 100644
--- a/tests/unit_tests/levin.cpp
+++ b/tests/unit_tests/levin.cpp
@@ -613,6 +613,61 @@ TEST_F(levin_notify, stem_without_padding)
     }
 }
 
+TEST_F(levin_notify, stem_no_outs_without_padding)
+{
+    cryptonote::levin::notify notifier = make_notifier(0, true, false);
+
+    for (unsigned count = 0; count < 10; ++count)
+        add_connection(true);
+
+    {
+        const auto status = notifier.get_status();
+        EXPECT_FALSE(status.has_noise);
+        EXPECT_FALSE(status.connections_filled);
+    }
+    notifier.new_out_connection();
+    io_service_.poll();
+
+    std::vector<cryptonote::blobdata> txs(2);
+    txs[0].resize(100, 'f');
+    txs[1].resize(200, 'e');
+
+    std::vector<cryptonote::blobdata> sorted_txs = txs;
+    std::sort(sorted_txs.begin(), sorted_txs.end());
+
+    ASSERT_EQ(10u, contexts_.size());
+
+    auto context = contexts_.begin();
+    EXPECT_TRUE(notifier.send_txs(txs, context->get_id(), cryptonote::relay_method::stem));
+
+    io_service_.reset();
+    ASSERT_LT(0u, io_service_.poll());
+    EXPECT_EQ(txs, events_.take_relayed(cryptonote::relay_method::fluff));
+    if (events_.has_stem_txes())
+        EXPECT_EQ(txs, events_.take_relayed(cryptonote::relay_method::stem));
+
+
+    notifier.run_fluff();
+    ASSERT_LT(0u, io_service_.poll());
+
+    std::size_t send_count = 0;
+    EXPECT_EQ(0u, context->process_send_queue());
+    for (++context; context != contexts_.end(); ++context)
+    {
+        send_count += context->process_send_queue();
+    }
+
+    EXPECT_EQ(9u, send_count);
+    ASSERT_EQ(9u, receiver_.notified_size());
+    for (unsigned count = 0; count < 9u; ++count)
+    {
+        auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second;
+        EXPECT_EQ(sorted_txs, notification.txs);
+        EXPECT_TRUE(notification._.empty());
+        EXPECT_TRUE(notification.dandelionpp_fluff);
+    }
+}
+
 TEST_F(levin_notify, local_without_padding)
 {
     cryptonote::levin::notify notifier = make_notifier(0, true, false);
@@ -928,6 +983,60 @@ TEST_F(levin_notify, stem_with_padding)
     }
 }
 
+TEST_F(levin_notify, stem_no_outs_with_padding)
+{
+    cryptonote::levin::notify notifier = make_notifier(0, true, true);
+
+    for (unsigned count = 0; count < 10; ++count)
+        add_connection(true);
+
+    {
+        const auto status = notifier.get_status();
+        EXPECT_FALSE(status.has_noise);
+        EXPECT_FALSE(status.connections_filled);
+    }
+    notifier.new_out_connection();
+    io_service_.poll();
+
+    std::vector<cryptonote::blobdata> txs(2);
+    txs[0].resize(100, 'f');
+    txs[1].resize(200, 'e');
+
+    std::vector<cryptonote::blobdata> sorted_txs = txs;
+    std::sort(sorted_txs.begin(), sorted_txs.end());
+
+    ASSERT_EQ(10u, contexts_.size());
+
+    auto context = contexts_.begin();
+    EXPECT_TRUE(notifier.send_txs(txs, context->get_id(), cryptonote::relay_method::stem));
+
+    io_service_.reset();
+    ASSERT_LT(0u, io_service_.poll());
+    EXPECT_EQ(txs, events_.take_relayed(cryptonote::relay_method::fluff));
+    if (events_.has_stem_txes())
+        EXPECT_EQ(txs, events_.take_relayed(cryptonote::relay_method::stem));
+
+    notifier.run_fluff();
+    ASSERT_LT(0u, io_service_.poll());
+
+    std::size_t send_count = 0;
+    EXPECT_EQ(0u, context->process_send_queue());
+    for (++context; context != contexts_.end(); ++context)
+    {
+        send_count += context->process_send_queue();
+    }
+
+    EXPECT_EQ(9u, send_count);
+    ASSERT_EQ(9u, receiver_.notified_size());
+    for (unsigned count = 0; count < 9u; ++count)
+    {
+        auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second;
+        EXPECT_EQ(sorted_txs, notification.txs);
+        EXPECT_FALSE(notification._.empty());
+        EXPECT_TRUE(notification.dandelionpp_fluff);
+    }
+}
+
 TEST_F(levin_notify, local_with_padding)
 {
     cryptonote::levin::notify notifier = make_notifier(0, true, true);