From 50f10e8cf1c6ae5971c36d56619bf467347c622f Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Mon, 15 Sep 2008 16:22:09 +0000
Subject: [PATCH]     * FloodOnlySearchJob:       - Ask non-floodfill peers if
 we don't know any floodfills       - Lookup hashes in the
 DatabaseSearchReplyMessage if we         don't know enough floodfills

---
 .../kademlia/FloodOnlySearchJob.java          | 130 ++++++++++++++++--
 1 file changed, 118 insertions(+), 12 deletions(-)

diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/FloodOnlySearchJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/FloodOnlySearchJob.java
index 4f3dc3e097..a7068ded55 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/FloodOnlySearchJob.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodOnlySearchJob.java
@@ -23,25 +23,40 @@ import net.i2p.util.Log;
  * Try sending a search to some floodfill peers, failing completely if we don't get
  * a match from one of those peers, with no fallback to the kademlia search
  *
+ * Exception (a semi-exception, since we still fail completely without fallback):
+ * If we don't know any floodfill peers, we ask a couple of peers at random,
+ * who will hopefully reply with some floodfill keys.
+ * We still fail without fallback, but we then spin off a job to
+ * ask that same random peer for the RouterInfos for those keys.
+ * If that job succeeds, the next search should work better.
+ *
+ * In addition, we follow the floodfill keys in the DSRM
+ * (DatabaseSearchReplyMessage) if we know less than 4 floodfills.
+ *
+ * These enhancements allow the router to bootstrap back into the network
+ * after it loses (or never had) floodfill references, as long as it
+ * knows one peer that is up.
+ *
  */
 class FloodOnlySearchJob extends FloodSearchJob {
-    private Log _log;
+    protected Log _log;
     private FloodfillNetworkDatabaseFacade _facade;
-    private Hash _key;
+    protected Hash _key;
     private List _onFind;
     private List _onFailed;
     private long _expiration;
-    private int _timeoutMs;
+    protected int _timeoutMs;
     private long _origExpiration;
     private boolean _isLease;
-    private volatile int _lookupsRemaining;
+    protected volatile int _lookupsRemaining;
     private volatile boolean _dead;
     private long _created;
+    private boolean _shouldProcessDSRM;
     
-    private List _out;
-    private MessageSelector _replySelector;
-    private ReplyJob _onReply;
-    private Job _onTimeout;
+    protected List _out;
+    protected MessageSelector _replySelector;
+    protected ReplyJob _onReply;
+    protected Job _onTimeout;
     public FloodOnlySearchJob(RouterContext ctx, FloodfillNetworkDatabaseFacade facade, Hash key, Job onFind, Job onFailed, int timeoutMs, boolean isLease) {
         super(ctx, facade, key, onFind, onFailed, timeoutMs, isLease);
         _log = ctx.logManager().getLog(FloodOnlySearchJob.class);
@@ -62,6 +77,7 @@ class FloodOnlySearchJob extends FloodSearchJob {
         _onReply = new FloodOnlyLookupMatchJob(getContext(), this);
         _onTimeout = new FloodOnlyLookupTimeoutJob(getContext(), this);
         _created = System.currentTimeMillis();
+        _shouldProcessDSRM = false;
     }
     void addDeferred(Job onFind, Job onFailed, long timeoutMs, boolean isLease) {
         if (_dead) {
@@ -72,15 +88,23 @@ class FloodOnlySearchJob extends FloodSearchJob {
         }
     }
     public long getExpiration() { return _expiration; }
+    public boolean shouldProcessDSRM() { return _shouldProcessDSRM; }
     private static final int CONCURRENT_SEARCHES = 2;
     public void runJob() {
         // pick some floodfill peers and send out the searches
         List floodfillPeers = _facade.getFloodfillPeers();
-        if (floodfillPeers == null) {
+        if (floodfillPeers.size() <= 3)
+            _shouldProcessDSRM = true;
+        if (floodfillPeers.size() <= 0) {
             if (_log.shouldLog(Log.ERROR))
                 _log.error("Running netDb searches against the floodfill peers, but we don't know any");
-            failed();
-            return;
+            floodfillPeers = new ArrayList(_facade.getAllRouters());
+            if (floodfillPeers.size() <= 0) {
+                if (_log.shouldLog(Log.ERROR))
+                    _log.error("We don't know any peers at all");
+                failed();
+                return;
+            }
         }
         OutNetMessage out = getContext().messageRegistry().registerPending(_replySelector, _onReply, _onTimeout, _timeoutMs);
         synchronized (_out) { _out.add(out); }
@@ -210,10 +234,12 @@ class FloodOnlyLookupTimeoutJob extends JobImpl {
 class FloodOnlyLookupMatchJob extends JobImpl implements ReplyJob {
     private Log _log;
     private FloodOnlySearchJob _search;
+    private DatabaseSearchReplyMessage _dsrm;
     public FloodOnlyLookupMatchJob(RouterContext ctx, FloodOnlySearchJob job) {
         super(ctx);
         _log = ctx.logManager().getLog(getClass());
         _search = job;
+        _dsrm = null;
     }
     public void runJob() { 
         if ( (getContext().netDb().lookupLeaseSetLocally(_search.getKey()) != null) ||
@@ -224,9 +250,16 @@ class FloodOnlyLookupMatchJob extends JobImpl implements ReplyJob {
         } else {
             int remaining = _search.getLookupsRemaining();
             if (_log.shouldLog(Log.INFO))
-                _log.info(_search.getJobId() + ": got a DatabasSearchReply when we were looking for " 
+                _log.info(_search.getJobId() + ": got a DatabaseSearchReply when we were looking for " 
                           + _search.getKey().toBase64() + ", with " + remaining + " outstanding searches");
             // netDb reply pointing us at other people
+            // Only process if we don't know enough floodfills
+            if (_search.shouldProcessDSRM() && _dsrm != null) {
+                if (_log.shouldLog(Log.INFO))
+                    _log.info(_search.getJobId() + ": Processing DatabaseSearchReply");
+                // Chase the hashes from the reply
+                getContext().jobQueue().addJob(new SingleLookupJob(getContext(), _dsrm));
+            }
             _search.failed();
         }
     }
@@ -234,6 +267,8 @@ class FloodOnlyLookupMatchJob extends JobImpl implements ReplyJob {
     public void setMessage(I2NPMessage message) {
         if (message instanceof DatabaseSearchReplyMessage) {
             // a dsrm is only passed in when there are no more lookups remaining
+            // If more than one peer sent one, we only process the last one
+            _dsrm = (DatabaseSearchReplyMessage) message;
             _search.failed();
             return;
         }
@@ -288,3 +323,74 @@ class FloodOnlyLookupSelector implements MessageSelector {
         return false;
     }   
 }
+
+/** Below here, only used to lookup the DSRM reply hashes when we are short of floodfills **/
+
+/**
+ * Ask the peer who sent us the DSRM for the RouterInfos.
+ * A simple version of SearchReplyJob in SearchJob.java.
+ * Skip the profile updates - this should be rare.
+ *
+ */
+class SingleLookupJob extends JobImpl {
+    private Log _log;
+    private DatabaseSearchReplyMessage _dsrm;
+    public SingleLookupJob(RouterContext ctx, DatabaseSearchReplyMessage dsrm) {
+        super(ctx);
+        _log = ctx.logManager().getLog(getClass());
+        _dsrm = dsrm;
+    }
+    public void runJob() { 
+        Hash from = _dsrm.getFromHash();
+        for (int i = 0; i < _dsrm.getNumReplies(); i++) {
+            Hash peer = _dsrm.getReply(i);
+            if (peer.equals(getContext().routerHash())) // us
+                continue;
+            if (getContext().netDb().lookupRouterInfoLocally(peer) == null)
+                getContext().jobQueue().addJob(new SingleSearchJob(getContext(), peer, from));
+        }
+    }
+    public String getName() { return "NetDb process DSRM"; }
+}
+
+/**
+ * Ask a single peer for a single key.
+ * This isn't really a flood-only search job at all, but we extend
+ * FloodOnlySearchJob so we can use the same selectors, etc.
+ *
+ */
+class SingleSearchJob extends FloodOnlySearchJob {
+    Hash _to;
+    OutNetMessage _onm;
+    public SingleSearchJob(RouterContext ctx, Hash key, Hash to) {
+        // warning, null FloodfillNetworkDatabaseFacade ...
+        // define our own failed() and success() below so _facade isn't used.
+        super(ctx, null, key, null, null, 5*1000, false);
+        _to = to;
+    }
+    public String getName() { return "NetDb search key from DSRM"; }
+    public boolean shouldProcessDSRM() { return false; } // don't loop
+    public void runJob() {
+        _onm = getContext().messageRegistry().registerPending(_replySelector, _onReply, _onTimeout, _timeoutMs);
+        DatabaseLookupMessage dlm = new DatabaseLookupMessage(getContext(), true);
+        TunnelInfo replyTunnel = getContext().tunnelManager().selectInboundTunnel();
+        TunnelInfo outTunnel = getContext().tunnelManager().selectOutboundTunnel();
+        if ( (replyTunnel == null) || (outTunnel == null) ) {
+            failed();
+            return;
+        }
+        dlm.setFrom(replyTunnel.getPeer(0));
+        dlm.setMessageExpiration(getContext().clock().now()+5*1000);
+        dlm.setReplyTunnel(replyTunnel.getReceiveTunnelId(0));
+        dlm.setSearchKey(_key);
+        
+        if (_log.shouldLog(Log.INFO))
+            _log.info(getJobId() + ": Single search for " + _key.toBase64() + " to " + _to.toBase64());
+        getContext().tunnelDispatcher().dispatchOutbound(dlm, outTunnel.getSendTunnelId(0), _to);
+        _lookupsRemaining = 1;
+    }
+    void failed() {
+        getContext().messageRegistry().unregisterPending(_onm);
+    }
+    void success() {}
+}
-- 
GitLab