diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillStoreJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillStoreJob.java index 7c37cfc18..3279f437c 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillStoreJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillStoreJob.java @@ -12,6 +12,8 @@ import java.util.Set; import net.i2p.data.DataStructure; import net.i2p.data.Hash; +import net.i2p.data.LeaseSet; +import net.i2p.data.RouterInfo; import net.i2p.router.Job; import net.i2p.router.RouterContext; @@ -45,10 +47,23 @@ class FloodfillStoreJob extends StoreJob { @Override protected void succeed() { super.succeed(); - if (_state != null) - getContext().jobQueue().addJob(new FloodfillVerifyStoreJob(getContext(), _state.getTarget(), _facade)); + if (_state != null) { + // Get the time stamp from the data we sent, so the Verify job can meke sure that + // it finds something stamped with that time or newer. + long published = 0; + boolean isRouterInfo = false; + DataStructure data = _state.getData(); + if (data instanceof RouterInfo) { + published = ((RouterInfo) data).getPublished(); + isRouterInfo = true; + } else if (data instanceof LeaseSet) { + published = ((LeaseSet) data).getEarliestLeaseDate(); + } + getContext().jobQueue().addJob(new FloodfillVerifyStoreJob(getContext(), _state.getTarget(), + published, isRouterInfo, _facade)); + } } @Override public String getName() { return "Floodfill netDb store"; } -} \ No newline at end of file +} diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillVerifyStoreJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillVerifyStoreJob.java index d541f781f..e368b4567 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillVerifyStoreJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillVerifyStoreJob.java @@ -5,6 +5,8 @@ import java.util.List; import net.i2p.data.DataStructure; import net.i2p.data.Hash; +import net.i2p.data.LeaseSet; +import net.i2p.data.RouterInfo; import net.i2p.data.i2np.DatabaseLookupMessage; import net.i2p.data.i2np.DatabaseSearchReplyMessage; import net.i2p.data.i2np.DatabaseStoreMessage; @@ -29,21 +31,33 @@ public class FloodfillVerifyStoreJob extends JobImpl { private FloodfillNetworkDatabaseFacade _facade; private long _expiration; private long _sendTime; + private long _published; + private boolean _isRouterInfo; private static final int VERIFY_TIMEOUT = 10*1000; - public FloodfillVerifyStoreJob(RouterContext ctx, Hash key, FloodfillNetworkDatabaseFacade facade) { + public FloodfillVerifyStoreJob(RouterContext ctx, Hash key, long published, boolean isRouterInfo, FloodfillNetworkDatabaseFacade facade) { super(ctx); _key = key; + _published = published; + _isRouterInfo = isRouterInfo; _log = ctx.logManager().getLog(getClass()); _facade = facade; // wait 10 seconds before trying to verify the store getTiming().setStartAfter(ctx.clock().now() + VERIFY_TIMEOUT); - getContext().statManager().createRateStat("netDb.floodfillVerifyOK", "How long a floodfill verify takes when it succeeds", "NetworkDatabase", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); - getContext().statManager().createRateStat("netDb.floodfillVerifyFail", "How long a floodfill verify takes when it fails", "NetworkDatabase", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); - getContext().statManager().createRateStat("netDb.floodfillVerifyTimeout", "How long a floodfill verify takes when it times out", "NetworkDatabase", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); + getContext().statManager().createRateStat("netDb.floodfillVerifyOK", "How long a floodfill verify takes when it succeeds", "NetworkDatabase", new long[] { 60*60*1000 }); + getContext().statManager().createRateStat("netDb.floodfillVerifyFail", "How long a floodfill verify takes when it fails", "NetworkDatabase", new long[] { 60*60*1000 }); + getContext().statManager().createRateStat("netDb.floodfillVerifyTimeout", "How long a floodfill verify takes when it times out", "NetworkDatabase", new long[] { 60*60*1000 }); } public String getName() { return "Verify netdb store"; } + + /** + * Wait 10 seconds, then query a random floodfill for the leaseset or routerinfo + * that we just stored to a (hopefully different) floodfill peer. + * + * If it fails (after waiting up to another 10 seconds), resend the data. + * If the queried data is older than what we stored, that counts as a fail. + **/ public void runJob() { _target = pickTarget(); if (_target == null) return; @@ -118,20 +132,29 @@ public class FloodfillVerifyStoreJob extends JobImpl { public void runJob() { long delay = getContext().clock().now() - _sendTime; if (_message instanceof DatabaseStoreMessage) { - // store ok, w00t! - // Hmm should we verify it's as recent as the one we sent??? - getContext().profileManager().dbLookupSuccessful(_target, delay); - getContext().statManager().addRateData("netDb.floodfillVerifyOK", delay, 0); - } else { - // store failed, boo, hiss! - if (_message instanceof DatabaseSearchReplyMessage) { - // assume 0 old, all new, 0 invalid, 0 dup - getContext().profileManager().dbLookupReply(_target, 0, - ((DatabaseSearchReplyMessage)_message).getNumReplies(), 0, 0, delay); + // Verify it's as recent as the one we sent + boolean success = false; + DatabaseStoreMessage dsm = (DatabaseStoreMessage)_message; + if (_isRouterInfo && dsm.getValueType() == DatabaseStoreMessage.KEY_TYPE_ROUTERINFO) + success = dsm.getRouterInfo().getPublished() >= _published; + else if ((!_isRouterInfo) && dsm.getValueType() == DatabaseStoreMessage.KEY_TYPE_LEASESET) + success = dsm.getLeaseSet().getEarliestLeaseDate() >= _published; + if (success) { + // store ok, w00t! + getContext().profileManager().dbLookupSuccessful(_target, delay); + getContext().statManager().addRateData("netDb.floodfillVerifyOK", delay, 0); + return; } - getContext().statManager().addRateData("netDb.floodfillVerifyFail", delay, 0); - resend(); + if (_log.shouldLog(Log.WARN)) + _log.warn("Verify failed - older"); + } else if (_message instanceof DatabaseSearchReplyMessage) { + // assume 0 old, all new, 0 invalid, 0 dup + getContext().profileManager().dbLookupReply(_target, 0, + ((DatabaseSearchReplyMessage)_message).getNumReplies(), 0, 0, delay); } + // store failed, boo, hiss! + getContext().statManager().addRateData("netDb.floodfillVerifyFail", delay, 0); + resend(); } public void setMessage(I2NPMessage message) { _message = message; } }