diff --git a/core/src/main/java/org/bitcoinj/core/Peer.java b/core/src/main/java/org/bitcoinj/core/Peer.java index 01e2a57af..8aea1c333 100644 --- a/core/src/main/java/org/bitcoinj/core/Peer.java +++ b/core/src/main/java/org/bitcoinj/core/Peer.java @@ -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 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 diff --git a/core/src/main/java/org/bitcoinj/core/PeerGroup.java b/core/src/main/java/org/bitcoinj/core/PeerGroup.java index c8ae95968..f3a9e2fd2 100644 --- a/core/src/main/java/org/bitcoinj/core/PeerGroup.java +++ b/core/src/main/java/org/bitcoinj/core/PeerGroup.java @@ -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 wallets; private final CopyOnWriteArrayList 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; diff --git a/core/src/main/java/org/bitcoinj/wallet/Wallet.java b/core/src/main/java/org/bitcoinj/wallet/Wallet.java index d6656119e..ee97ff190 100644 --- a/core/src/main/java/org/bitcoinj/wallet/Wallet.java +++ b/core/src/main/java/org/bitcoinj/wallet/Wallet.java @@ -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).

* - * 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.

diff --git a/core/src/test/java/org/bitcoinj/core/BitcoindComparisonTool.java b/core/src/test/java/org/bitcoinj/core/BitcoindComparisonTool.java index e44821762..d57fcc04e 100644 --- a/core/src/test/java/org/bitcoinj/core/BitcoindComparisonTool.java +++ b/core/src/test/java/org/bitcoinj/core/BitcoindComparisonTool.java @@ -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); }); diff --git a/integration-test/src/test/java/org/bitcoinj/core/PeerGroupTest.java b/integration-test/src/test/java/org/bitcoinj/core/PeerGroupTest.java index c0b01add1..a0418cd3b 100644 --- a/integration-test/src/test/java/org/bitcoinj/core/PeerGroupTest.java +++ b/integration-test/src/test/java/org/bitcoinj/core/PeerGroupTest.java @@ -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 diff --git a/integration-test/src/test/java/org/bitcoinj/core/PeerTest.java b/integration-test/src/test/java/org/bitcoinj/core/PeerTest.java index 516f48c74..83e641c23 100644 --- a/integration-test/src/test/java/org/bitcoinj/core/PeerTest.java +++ b/integration-test/src/test/java/org/bitcoinj/core/PeerTest.java @@ -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();