Peer, PeerGroup: migrate fastCatchupTime field to java.time API

This commit is contained in:
Andreas Schildbach 2023-03-04 14:52:19 +01:00
parent 838b12c046
commit 310b93be4e
6 changed files with 71 additions and 35 deletions

View File

@ -123,7 +123,7 @@ public class Peer extends PeerSocketHandler {
// Each wallet added to the peer will be notified of downloaded transaction data.
private final CopyOnWriteArrayList<Wallet> wallets;
// A time before which we only download block headers, after that point we download block bodies.
@GuardedBy("lock") private long fastCatchupTimeSecs;
@GuardedBy("lock") private Instant fastCatchupTime;
// Whether we are currently downloading headers only or block bodies. Starts at true. If the fast catchup time is
// set AND our best block is before that date, switch to false until block headers beyond that point have been
// received at which point it gets set to true again. This isn't relevant unless vDownloadData is true.
@ -239,7 +239,7 @@ public class Peer extends PeerSocketHandler {
this.vDownloadData = chain != null;
this.getDataFutures = new ConcurrentLinkedQueue<>();
this.getAddrFutures = new LinkedList<>();
this.fastCatchupTimeSecs = params.getGenesisBlock().getTimeSeconds();
this.fastCatchupTime = params.getGenesisBlock().getTimeInstant();
this.pendingPings = new CopyOnWriteArrayList<>();
this.vMinProtocolVersion = params.getProtocolVersionNum(NetworkParameters.ProtocolVersion.PONG);
this.wallets = new CopyOnWriteArrayList<>();
@ -617,7 +617,7 @@ public class Peer extends PeerSocketHandler {
// the chain if it pre-dates the fast catchup time. If we go past it, we can stop processing the headers and
// request the full blocks from that point on instead.
boolean downloadBlockBodies;
long fastCatchupTimeSecs;
Instant fastCatchupTime;
lock.lock();
try {
@ -626,7 +626,7 @@ public class Peer extends PeerSocketHandler {
log.warn("Received headers when Peer is not configured with a chain.");
return;
}
fastCatchupTimeSecs = this.fastCatchupTimeSecs;
fastCatchupTime = this.fastCatchupTime;
downloadBlockBodies = this.downloadBlockBodies;
} finally {
lock.unlock();
@ -639,7 +639,7 @@ public class Peer extends PeerSocketHandler {
// Process headers until we pass the fast catchup time, or are about to catch up with the head
// of the chain - always process the last block as a full/filtered block to kick us out of the
// fast catchup mode (in which we ignore new blocks).
boolean passedTime = header.getTimeSeconds() >= fastCatchupTimeSecs;
boolean passedTime = header.getTimeInstant().compareTo(fastCatchupTime) >= 0;
boolean reachedTop = blockChain.getBestChainHeight() >= vPeerVersionMessage.bestHeight;
if (!passedTime && !reachedTop) {
if (!vDownloadData) {
@ -661,7 +661,7 @@ public class Peer extends PeerSocketHandler {
try {
log.info(
"Passed the fast catchup time ({}) at height {}, discarding {} headers and requesting full blocks",
TimeUtils.dateTimeFormat(fastCatchupTimeSecs * 1000), blockChain.getBestChainHeight() + 1,
TimeUtils.dateTimeFormat(fastCatchupTime.toEpochMilli()), blockChain.getBestChainHeight() + 1,
m.getBlockHeaders().size() - i);
this.downloadBlockBodies = true;
// Prevent this request being seen as a duplicate.
@ -1313,27 +1313,47 @@ public class Peer extends PeerSocketHandler {
* isn't known until their headers are available and they are requested in chunks, so some headers may be downloaded
* twice using this scheme, but this optimization can still be a large win for newly created wallets.
*
* @param secondsSinceEpoch Time in seconds since the epoch or 0 to reset to always downloading block bodies.
* @param useFilteredBlocks whether to request filtered blocks if the protocol version allows for them
* @param fastCatchupTime time before which block bodies are skipped
*/
public void setDownloadParameters(long secondsSinceEpoch, boolean useFilteredBlocks) {
public void setFastDownloadParameters(boolean useFilteredBlocks, Instant fastCatchupTime) {
lock.lock();
try {
if (secondsSinceEpoch == 0) {
fastCatchupTimeSecs = params.getGenesisBlock().getTimeSeconds();
downloadBlockBodies = true;
} else {
fastCatchupTimeSecs = secondsSinceEpoch;
// If the given time is before the current chains head block time, then this has no effect (we already
// downloaded everything we need).
if (blockChain != null && fastCatchupTimeSecs > blockChain.getChainHead().getHeader().getTimeSeconds())
downloadBlockBodies = false;
}
this.fastCatchupTime = fastCatchupTime;
// If the given time is before the current chains head block time, then this has no effect (we already
// downloaded everything we need).
if (blockChain != null && this.fastCatchupTime.isAfter(blockChain.getChainHead().getHeader().getTimeInstant()))
downloadBlockBodies = false;
this.useFilteredBlocks = useFilteredBlocks;
} finally {
lock.unlock();
}
}
/**
* Always download full blocks.
* @param useFilteredBlocks whether to request filtered blocks if the protocol version allows for them
*/
public void setDownloadParameters(boolean useFilteredBlocks) {
lock.lock();
try {
this.fastCatchupTime = params.getGenesisBlock().getTimeInstant();
downloadBlockBodies = true;
this.useFilteredBlocks = useFilteredBlocks;
} finally {
lock.unlock();
}
}
/** @deprecated use {@link #setDownloadParameters(boolean)} or {@link #setFastDownloadParameters(boolean, Instant)} */
@Deprecated
public void setDownloadParameters(long fastCatchupTimeSecs, boolean useFilteredBlocks) {
if (fastCatchupTimeSecs > 0)
setFastDownloadParameters(useFilteredBlocks, Instant.ofEpochSecond(fastCatchupTimeSecs));
else
setDownloadParameters(useFilteredBlocks);
}
/**
* Links the given wallet to this peer. If you have multiple peers, you should use a {@link PeerGroup} to manage
* them and use the {@link PeerGroup#addWallet(Wallet)} method instead of registering the wallet with each peer

View File

@ -213,7 +213,7 @@ public class PeerGroup implements TransactionBroadcaster {
@GuardedBy("lock") private boolean useLocalhostPeerWhenPossible = true;
@GuardedBy("lock") private boolean ipv6Unreachable = false;
@GuardedBy("lock") private long fastCatchupTimeSecs;
@GuardedBy("lock") private Instant fastCatchupTime;
private final CopyOnWriteArrayList<Wallet> wallets;
private final CopyOnWriteArrayList<PeerFilterProvider> peerFilterProviders;
@ -424,7 +424,7 @@ public class PeerGroup implements TransactionBroadcaster {
Context.getOrCreate(); // create a context for convenience
this.params = params;
this.chain = chain;
fastCatchupTimeSecs = params.getGenesisBlock().getTimeSeconds();
fastCatchupTime = params.getGenesisBlock().getTimeInstant();
wallets = new CopyOnWriteArrayList<>();
peerFilterProviders = new CopyOnWriteArrayList<>();
@ -1711,7 +1711,7 @@ public class PeerGroup implements TransactionBroadcaster {
}
downloadPeer.setDownloadData(true);
if (chain != null)
downloadPeer.setDownloadParameters(fastCatchupTimeSecs, bloomFilterMerger.getLastFilter() != null);
downloadPeer.setFastDownloadParameters(bloomFilterMerger.getLastFilter() != null, fastCatchupTime);
}
} finally {
lock.unlock();
@ -1729,34 +1729,46 @@ public class PeerGroup implements TransactionBroadcaster {
* before starting block chain download.
* Do not use a {@code time > NOW - 1} block, as it will break some block download logic.
*/
public void setFastCatchupTimeSecs(long secondsSinceEpoch) {
public void setFastCatchupTime(Instant fastCatchupTime) {
lock.lock();
try {
checkState(chain == null || !chain.shouldVerifyTransactions(), "Fast catchup is incompatible with fully verifying");
fastCatchupTimeSecs = secondsSinceEpoch;
this.fastCatchupTime = fastCatchupTime;
if (downloadPeer != null) {
downloadPeer.setDownloadParameters(secondsSinceEpoch, bloomFilterMerger.getLastFilter() != null);
downloadPeer.setFastDownloadParameters(bloomFilterMerger.getLastFilter() != null, fastCatchupTime);
}
} finally {
lock.unlock();
}
}
/** @deprecated use {@link #setFastCatchupTime(Instant)} */
@Deprecated
public void setFastCatchupTimeSecs(long fastCatchupTimeSecs) {
setFastCatchupTime(Instant.ofEpochSecond(fastCatchupTimeSecs));
}
/**
* Returns the current fast catchup time. The contents of blocks before this time won't be downloaded as they
* cannot contain any interesting transactions. If you use {@link PeerGroup#addWallet(Wallet)} this just returns
* the min of the wallets earliest key times.
* @return a time in seconds since the epoch
*/
public long getFastCatchupTimeSecs() {
public Instant getFastCatchupTime() {
lock.lock();
try {
return fastCatchupTimeSecs;
return fastCatchupTime;
} finally {
lock.unlock();
}
}
/** @deprecated use {@link #getFastCatchupTime()} */
@Deprecated
public long getFastCatchupTimeSecs() {
return getFastCatchupTime().getEpochSecond();
}
protected void handlePeerDeath(final Peer peer, @Nullable Throwable exception) {
// Peer deaths can occur during startup if a connect attempt after peer discovery aborts immediately.
if (!isRunning()) return;

View File

@ -3576,7 +3576,7 @@ public class Wallet extends BaseTaggableObject
* of {@link ECKey#getCreationTimeSeconds()}. This can return zero if at least one key does
* not have that data (was created before key timestamping was implemented). <p>
*
* This method is most often used in conjunction with {@link PeerGroup#setFastCatchupTimeSecs(long)} in order to
* This method is most often used in conjunction with {@link PeerGroup#setFastCatchupTime(Instant)} in order to
* optimize chain download for new users of wallet apps. Backwards compatibility notice: if you get zero from this
* method, you can instead use the time of the first release of your software, as it's guaranteed no users will
* have wallets pre-dating this time. <p>

View File

@ -43,6 +43,7 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
@ -113,7 +114,7 @@ public class BitcoindComparisonTool {
}
log.info("bitcoind connected");
// Make sure bitcoind has no blocks
bitcoind.setDownloadParameters(0, false);
bitcoind.setDownloadParameters(false);
bitcoind.startBlockChainDownload();
connectedFuture.complete(null);
});

View File

@ -49,6 +49,7 @@ import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -430,23 +431,23 @@ public class PeerGroupTest extends TestWithPeerGroup {
// Check the fast catchup time was initialized to something around the current runtime minus a week.
// The wallet was already added to the peer in setup.
final int WEEK = 86400 * 7;
final long now = TimeUtils.currentTimeSeconds();
final Instant now = TimeUtils.currentTime().truncatedTo(ChronoUnit.SECONDS);
peerGroup.start();
assertTrue(peerGroup.getFastCatchupTimeSecs() > now - WEEK - 10000);
assertTrue(peerGroup.getFastCatchupTime().isAfter(now.minusSeconds(WEEK).minusSeconds(10000)));
Wallet w2 = Wallet.createDeterministic(UNITTEST, ScriptType.P2PKH);
ECKey key1 = new ECKey();
key1.setCreationTimeSeconds(now - 86400); // One day ago.
key1.setCreationTimeSeconds(now.getEpochSecond() - 86400); // One day ago.
w2.importKey(key1);
peerGroup.addWallet(w2);
peerGroup.waitForJobQueue();
assertEquals(peerGroup.getFastCatchupTimeSecs(), now - 86400 - WEEK);
assertEquals(peerGroup.getFastCatchupTime(), now.minusSeconds(86400).minusSeconds(WEEK));
// Adding a key to the wallet should update the fast catchup time, but asynchronously and in the background
// due to the need to avoid complicated lock inversions.
ECKey key2 = new ECKey();
key2.setCreationTimeSeconds(now - 100000);
key2.setCreationTimeSeconds(now.getEpochSecond() - 100000);
w2.importKey(key2);
peerGroup.waitForJobQueue();
assertEquals(peerGroup.getFastCatchupTimeSecs(), now - WEEK - 100000);
assertEquals(peerGroup.getFastCatchupTime(),now.minusSeconds(WEEK).minusSeconds(100000));
}
@Test

View File

@ -466,7 +466,9 @@ public class PeerTest extends TestWithNetworkConnections {
b4.solve();
// Request headers until the last 2 blocks.
peer.setDownloadParameters(TimeUtils.currentTimeSeconds() - (600*2) + 1, false);
peer.setFastDownloadParameters(
false, TimeUtils.currentTime().minusSeconds(600 * 2).plusSeconds(1)
);
peer.startBlockChainDownload();
GetHeadersMessage getheaders = (GetHeadersMessage) outbound(writeTarget);
BlockLocator expectedLocator = new BlockLocator();