diff --git a/core/src/main/java/org/bitcoinj/base/internal/TimeUtils.java b/core/src/main/java/org/bitcoinj/base/internal/TimeUtils.java index 3c7fcaa2b..9f482dce1 100644 --- a/core/src/main/java/org/bitcoinj/base/internal/TimeUtils.java +++ b/core/src/main/java/org/bitcoinj/base/internal/TimeUtils.java @@ -18,6 +18,7 @@ package org.bitcoinj.base.internal; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.util.Date; @@ -29,75 +30,68 @@ import java.util.TimeZone; */ public class TimeUtils { private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); - /** - * If non-null, overrides the return value of now(). - */ - private static volatile Date mockTime; + // Clock to be used for the return value of now() and currentTime() variants + private static volatile Clock clock = Clock.systemUTC(); /** - * Advances (or rewinds) the mock clock by the given number of seconds. - */ - public static Date rollMockClock(int seconds) { - return rollMockClockMillis(seconds * 1000); - } - - /** - * Advances (or rewinds) the mock clock by the given number of milliseconds. - */ - public static Date rollMockClockMillis(long millis) { - if (mockTime == null) - throw new IllegalStateException("You need to use setMockClock() first."); - mockTime = new Date(mockTime.getTime() + millis); - return mockTime; - } - - /** - * Sets the mock clock to the current time. + * Sets the mock clock to the current time as a fixed instant. */ public static void setMockClock() { - mockTime = new Date(); + setMockClock(Instant.now()); } /** - * Sets the mock clock to the given time (in seconds). + * Sets the mock clock to a fixed instant. + * @param fixedInstant a fixed instant */ - public static void setMockClock(long mockClockSeconds) { - mockTime = new Date(mockClockSeconds * 1000); + public static void setMockClock(Instant fixedInstant) { + clock = Clock.fixed(fixedInstant, UTC.toZoneId()); } /** - * Clears the mock clock and sleep + * Rolls an already set mock clock by the given duration. + * @param delta amount to roll the mock clock, can be negative + * @throws IllegalStateException if the mock clock isn't set */ - public static void resetMocking() { - mockTime = null; + public static void rollMockClock(Duration delta) { + if (clock.equals(Clock.systemUTC())) + throw new IllegalStateException("You need to use setMockClock() first."); + setMockClock(clock.instant().plus(delta)); + } + + /** + * Clears the mock clock and causes time to tick again. + */ + public static void clearMockClock() { + clock = Clock.systemUTC(); } /** * Returns the current time, or a mocked out equivalent. */ public static Date now() { - return mockTime != null ? mockTime : new Date(); + return Date.from(currentTime()); } /** * Returns the current time in milliseconds since the epoch, or a mocked out equivalent. */ public static long currentTimeMillis() { - return mockTime != null ? mockTime.getTime() : System.currentTimeMillis(); + return currentTime().toEpochMilli(); } /** * Returns the current time in seconds since the epoch, or a mocked out equivalent. */ public static long currentTimeSeconds() { - return currentTimeMillis() / 1000; + return currentTime().getEpochSecond(); } /** * Returns the current time as an Instant, or a mocked out equivalent. */ public static Instant currentTime() { - return Instant.ofEpochMilli(currentTimeMillis()); + return Instant.now(clock); } /** diff --git a/core/src/test/java/org/bitcoinj/base/internal/TimeUtilsTest.java b/core/src/test/java/org/bitcoinj/base/internal/TimeUtilsTest.java index 855f021d6..48159d21b 100644 --- a/core/src/test/java/org/bitcoinj/base/internal/TimeUtilsTest.java +++ b/core/src/test/java/org/bitcoinj/base/internal/TimeUtilsTest.java @@ -20,6 +20,8 @@ package org.bitcoinj.base.internal; import org.junit.Test; +import java.time.Duration; +import java.time.Instant; import java.util.Date; import static org.junit.Assert.assertEquals; @@ -35,9 +37,15 @@ public class TimeUtilsTest { } @Test - public void testRollMockClock() { - TimeUtils.setMockClock(25200); - assertEquals(new Date("Thu Jan 01 07:00:08 GMT 1970"), TimeUtils.rollMockClock(8)); - TimeUtils.resetMocking(); + public void setAndRollMockClock() { + TimeUtils.setMockClock(Instant.ofEpochSecond(25200)); + assertEquals(new Date("Thu Jan 01 07:00:00 GMT 1970"), TimeUtils.now()); + TimeUtils.rollMockClock(Duration.ofSeconds(8)); + assertEquals(new Date("Thu Jan 01 07:00:08 GMT 1970"), TimeUtils.now()); + } + + @Test(expected = IllegalStateException.class) + public void rollMockClock_uninitialized() { + TimeUtils.rollMockClock(Duration.ofMinutes(1)); } } diff --git a/core/src/test/java/org/bitcoinj/core/AbstractFullPrunedBlockChainTest.java b/core/src/test/java/org/bitcoinj/core/AbstractFullPrunedBlockChainTest.java index 2bea30661..426a5ec16 100644 --- a/core/src/test/java/org/bitcoinj/core/AbstractFullPrunedBlockChainTest.java +++ b/core/src/test/java/org/bitcoinj/core/AbstractFullPrunedBlockChainTest.java @@ -69,7 +69,7 @@ public abstract class AbstractFullPrunedBlockChainTest { @BeforeClass public static void setUpClass() { - TimeUtils.resetMocking(); + TimeUtils.clearMockClock(); PARAMS = new UnitTestParams() { @Override public int getInterval() { return 10000; diff --git a/core/src/test/java/org/bitcoinj/core/BlockChainTest.java b/core/src/test/java/org/bitcoinj/core/BlockChainTest.java index 038bc3c55..b069c058e 100644 --- a/core/src/test/java/org/bitcoinj/core/BlockChainTest.java +++ b/core/src/test/java/org/bitcoinj/core/BlockChainTest.java @@ -40,6 +40,7 @@ import org.junit.rules.ExpectedException; import java.math.BigInteger; import java.text.SimpleDateFormat; +import java.time.Duration; import java.time.Instant; import java.util.Date; import java.util.Locale; @@ -178,7 +179,7 @@ public class BlockChainTest { assertTrue(chain.add(newBlock)); prev = newBlock; // The fake chain should seem to be "fast" for the purposes of difficulty calculations. - TimeUtils.rollMockClock(2); + TimeUtils.rollMockClock(Duration.ofSeconds(2)); } // Now add another block that has no difficulty adjustment, it should be rejected. try { diff --git a/core/src/test/java/org/bitcoinj/core/TransactionTest.java b/core/src/test/java/org/bitcoinj/core/TransactionTest.java index 9939e6b85..626e62551 100644 --- a/core/src/test/java/org/bitcoinj/core/TransactionTest.java +++ b/core/src/test/java/org/bitcoinj/core/TransactionTest.java @@ -42,6 +42,7 @@ import java.io.IOException; import java.io.OutputStream; import java.math.BigInteger; import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.Calendar; import java.util.Date; import java.util.Iterator; @@ -147,7 +148,7 @@ public class TransactionTest { @Test public void testEstimatedLockTime_WhenParameterSignifiesBlockHeight() { int TEST_LOCK_TIME = 20; - Instant now = TimeUtils.currentTime(); + Instant now = TimeUtils.currentTime().truncatedTo(ChronoUnit.SECONDS); BlockChain mockBlockChain = createMock(BlockChain.class); EasyMock.expect(mockBlockChain.estimateBlockTimeInstant(TEST_LOCK_TIME)).andReturn(now); diff --git a/core/src/test/java/org/bitcoinj/store/SPVBlockStoreTest.java b/core/src/test/java/org/bitcoinj/store/SPVBlockStoreTest.java index e2470f877..669b12520 100644 --- a/core/src/test/java/org/bitcoinj/store/SPVBlockStoreTest.java +++ b/core/src/test/java/org/bitcoinj/store/SPVBlockStoreTest.java @@ -39,7 +39,6 @@ import java.math.BigInteger; import java.time.Duration; import java.time.Instant; import java.util.Collections; -import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -51,7 +50,7 @@ public class SPVBlockStoreTest { @BeforeClass public static void setUpClass() { - TimeUtils.resetMocking(); + TimeUtils.clearMockClock(); } @Before diff --git a/core/src/test/java/org/bitcoinj/store/WalletProtobufSerializerTest.java b/core/src/test/java/org/bitcoinj/store/WalletProtobufSerializerTest.java index 8514c00f8..638fe376c 100644 --- a/core/src/test/java/org/bitcoinj/store/WalletProtobufSerializerTest.java +++ b/core/src/test/java/org/bitcoinj/store/WalletProtobufSerializerTest.java @@ -97,7 +97,7 @@ public class WalletProtobufSerializerTest { @BeforeClass public static void setUpClass() { - TimeUtils.resetMocking(); + TimeUtils.clearMockClock(); Context.propagate(new Context()); } diff --git a/core/src/test/java/org/bitcoinj/testing/TestWithWallet.java b/core/src/test/java/org/bitcoinj/testing/TestWithWallet.java index 3f7930ec9..88d8e710a 100644 --- a/core/src/test/java/org/bitcoinj/testing/TestWithWallet.java +++ b/core/src/test/java/org/bitcoinj/testing/TestWithWallet.java @@ -62,7 +62,7 @@ public class TestWithWallet { @BeforeClass public static void setUpClass() throws Exception { - TimeUtils.resetMocking(); + TimeUtils.clearMockClock(); } public void setUp() throws Exception { diff --git a/core/src/test/java/org/bitcoinj/utils/VersionTallyTest.java b/core/src/test/java/org/bitcoinj/utils/VersionTallyTest.java index e81e6b2b9..a1b6e1a6b 100644 --- a/core/src/test/java/org/bitcoinj/utils/VersionTallyTest.java +++ b/core/src/test/java/org/bitcoinj/utils/VersionTallyTest.java @@ -42,7 +42,7 @@ public class VersionTallyTest { @BeforeClass public static void setUpClass() { - TimeUtils.resetMocking(); + TimeUtils.clearMockClock(); } @Before diff --git a/core/src/test/java/org/bitcoinj/wallet/BasicKeyChainTest.java b/core/src/test/java/org/bitcoinj/wallet/BasicKeyChainTest.java index c2709b205..f9431fb5f 100644 --- a/core/src/test/java/org/bitcoinj/wallet/BasicKeyChainTest.java +++ b/core/src/test/java/org/bitcoinj/wallet/BasicKeyChainTest.java @@ -28,6 +28,7 @@ import org.bitcoinj.wallet.listeners.AbstractKeyChainEventListener; import org.junit.Before; import org.junit.Test; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -69,7 +70,7 @@ public class BasicKeyChainTest { TimeUtils.setMockClock(); long now = TimeUtils.currentTimeSeconds(); final ECKey key1 = new ECKey(); - TimeUtils.rollMockClock(86400); + TimeUtils.rollMockClock(Duration.ofDays(1)); final ECKey key2 = new ECKey(); final ArrayList keys = Lists.newArrayList(key1, key2); @@ -198,7 +199,7 @@ public class BasicKeyChainTest { TimeUtils.setMockClock(); Date now = TimeUtils.now(); final ECKey key1 = new ECKey(); - TimeUtils.rollMockClock(5000); + TimeUtils.rollMockClock(Duration.ofSeconds(5000)); final ECKey key2 = new ECKey(); chain.importKeys(Arrays.asList(key1, key2)); List keys = chain.serializeToProtobuf(); @@ -278,7 +279,7 @@ public class BasicKeyChainTest { TimeUtils.setMockClock(); long now = TimeUtils.currentTimeSeconds(); final ECKey key1 = new ECKey(); - TimeUtils.rollMockClock(86400); + TimeUtils.rollMockClock(Duration.ofDays(1)); final ECKey key2 = new ECKey(); final List keys = Lists.newArrayList(key1, key2); assertEquals(2, chain.importKeys(keys)); diff --git a/core/src/test/java/org/bitcoinj/wallet/KeyChainGroupTest.java b/core/src/test/java/org/bitcoinj/wallet/KeyChainGroupTest.java index fa1a37c81..0d822d88c 100644 --- a/core/src/test/java/org/bitcoinj/wallet/KeyChainGroupTest.java +++ b/core/src/test/java/org/bitcoinj/wallet/KeyChainGroupTest.java @@ -38,6 +38,7 @@ import org.junit.Before; import org.junit.Test; import java.math.BigInteger; +import java.time.Duration; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicReference; @@ -256,11 +257,11 @@ public class KeyChainGroupTest { } private void encryption(boolean withImported) { - TimeUtils.rollMockClock(0); + TimeUtils.setMockClock(); long now = TimeUtils.currentTimeSeconds(); ECKey a = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); assertEquals(now, group.getEarliestKeyCreationTime()); - TimeUtils.rollMockClock(-86400); + TimeUtils.rollMockClock(Duration.ofDays(-1)); long yesterday = TimeUtils.currentTimeSeconds(); ECKey b = new ECKey(); @@ -403,9 +404,9 @@ public class KeyChainGroupTest { long now = TimeUtils.currentTimeSeconds(); // mock long yesterday = now - 86400; assertEquals(now, group.getEarliestKeyCreationTime()); - TimeUtils.rollMockClock(10000); + TimeUtils.rollMockClock(Duration.ofSeconds(10000)); group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); - TimeUtils.rollMockClock(10000); + TimeUtils.rollMockClock(Duration.ofSeconds(10000)); group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); // Check that all keys are assumed to be created at the same instant the seed is. assertEquals(now, group.getEarliestKeyCreationTime()); diff --git a/core/src/test/java/org/bitcoinj/wallet/WalletTest.java b/core/src/test/java/org/bitcoinj/wallet/WalletTest.java index e75075a8c..466f7092e 100644 --- a/core/src/test/java/org/bitcoinj/wallet/WalletTest.java +++ b/core/src/test/java/org/bitcoinj/wallet/WalletTest.java @@ -81,6 +81,7 @@ import java.io.File; import java.math.BigInteger; import java.net.InetAddress; import java.security.SecureRandom; +import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; @@ -1444,7 +1445,7 @@ public class WalletTest extends TestWithWallet { // Check the wallet can give us an ordered list of all received transactions. TimeUtils.setMockClock(); Transaction tx1 = sendMoneyToWallet(AbstractBlockChain.NewBlockType.BEST_CHAIN, COIN); - TimeUtils.rollMockClock(60 * 10); + TimeUtils.rollMockClock(Duration.ofMinutes(10)); Transaction tx2 = sendMoneyToWallet(AbstractBlockChain.NewBlockType.BEST_CHAIN, valueOf(0, 5)); // Check we got them back in order. List transactions = wallet.getTransactionsByTime(); @@ -1457,7 +1458,7 @@ public class WalletTest extends TestWithWallet { assertEquals(tx2, transactions.get(0)); // Create a spend five minutes later. - TimeUtils.rollMockClock(60 * 5); + TimeUtils.rollMockClock(Duration.ofMinutes(5)); Transaction tx3 = wallet.createSend(OTHER_ADDRESS, valueOf(0, 5)); // Does not appear in list yet. assertEquals(2, wallet.getTransactionsByTime().size()); @@ -1483,7 +1484,7 @@ public class WalletTest extends TestWithWallet { long now = TimeUtils.currentTimeSeconds(); wallet = Wallet.createDeterministic(TESTNET, ScriptType.P2PKH); assertEquals(now, wallet.getEarliestKeyCreationTime()); - TimeUtils.rollMockClock(60); + TimeUtils.rollMockClock(Duration.ofMinutes(1)); wallet.freshReceiveKey(); assertEquals(now, wallet.getEarliestKeyCreationTime()); } @@ -1494,7 +1495,7 @@ public class WalletTest extends TestWithWallet { long now = TimeUtils.currentTimeSeconds(); wallet = Wallet.createDeterministic(TESTNET, ScriptType.P2PKH); assertEquals(now, wallet.getEarliestKeyCreationTime()); - TimeUtils.rollMockClock(-120); + TimeUtils.rollMockClock(Duration.ofMinutes(-2)); wallet.addWatchedAddress(OTHER_ADDRESS); wallet.freshReceiveKey(); assertEquals(now - 120, wallet.getEarliestKeyCreationTime()); @@ -2916,7 +2917,7 @@ public class WalletTest extends TestWithWallet { assertFalse(wallet.isKeyRotating(key1)); // We got compromised! - TimeUtils.rollMockClock(1); + TimeUtils.rollMockClock(Duration.ofSeconds(1)); wallet.setKeyRotationTime(compromiseTime); assertTrue(wallet.isKeyRotating(key1)); wallet.doMaintenance(null, true); @@ -3010,7 +3011,7 @@ public class WalletTest extends TestWithWallet { DeterministicKey watchKey1 = wallet.getWatchingKey(); // A day later, we get compromised. - TimeUtils.rollMockClock(86400); + TimeUtils.rollMockClock(Duration.ofDays(1)); wallet.setKeyRotationTime(TimeUtils.currentTime()); List txns = wallet.doMaintenance(null, false).get(); @@ -3030,7 +3031,7 @@ public class WalletTest extends TestWithWallet { ECKey key = wallet.freshReceiveKey(); Address address = key.toAddress(ScriptType.P2PKH, BitcoinNetwork.TESTNET); TimeUtils.setMockClock(); - TimeUtils.rollMockClock(86400); + TimeUtils.rollMockClock(Duration.ofDays(1)); for (int i = 0; i < 800; i++) { sendMoneyToWallet(AbstractBlockChain.NewBlockType.BEST_CHAIN, CENT, address); } @@ -3038,7 +3039,7 @@ public class WalletTest extends TestWithWallet { MockTransactionBroadcaster broadcaster = new MockTransactionBroadcaster(wallet); Instant compromise = TimeUtils.currentTime().truncatedTo(ChronoUnit.SECONDS); - TimeUtils.rollMockClock(86400); + TimeUtils.rollMockClock(Duration.ofDays(1)); wallet.freshReceiveKey(); wallet.setKeyRotationTime(compromise); wallet.doMaintenance(null, true); 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 036f2b6ed..9f8bfe29c 100644 --- a/integration-test/src/test/java/org/bitcoinj/core/PeerTest.java +++ b/integration-test/src/test/java/org/bitcoinj/core/PeerTest.java @@ -452,15 +452,15 @@ public class PeerTest extends TestWithNetworkConnections { // This test is INCOMPLETE because it does not check we handle >2000 blocks correctly. Block b1 = createFakeBlock(blockStore, Block.BLOCK_HEIGHT_GENESIS).block; blockChain.add(b1); - TimeUtils.rollMockClock(60 * 10); // 10 minutes later. + TimeUtils.rollMockClock(Duration.ofMinutes(10)); // 10 minutes later. Block b2 = makeSolvedTestBlock(b1); b2.setTime(TimeUtils.currentTime()); b2.solve(); - TimeUtils.rollMockClock(60 * 10); // 10 minutes later. + TimeUtils.rollMockClock(Duration.ofMinutes(10)); // 10 minutes later. Block b3 = makeSolvedTestBlock(b2); b3.setTime(TimeUtils.currentTime()); b3.solve(); - TimeUtils.rollMockClock(60 * 10); + TimeUtils.rollMockClock(Duration.ofMinutes(10)); Block b4 = makeSolvedTestBlock(b3); b4.setTime(TimeUtils.currentTime()); b4.solve(); @@ -498,7 +498,7 @@ public class PeerTest extends TestWithNetworkConnections { inbound(writeTarget, b3); pingAndWait(writeTarget); closePeer(peer); - TimeUtils.resetMocking(); + TimeUtils.clearMockClock(); } @Test @@ -513,7 +513,7 @@ public class PeerTest extends TestWithNetworkConnections { assertEquals(Long.MAX_VALUE, peer.getPingTime()); assertFalse(future.isDone()); Ping pingMsg = (Ping) outbound(writeTarget); - TimeUtils.rollMockClock(5); + TimeUtils.rollMockClock(Duration.ofSeconds(5)); // The pong is returned. inbound(writeTarget, new Pong(pingMsg.getNonce())); pingAndWait(writeTarget); @@ -525,12 +525,12 @@ public class PeerTest extends TestWithNetworkConnections { // Do it again and make sure it affects the average. CompletableFuture future2 = peer.sendPing(); pingMsg = (Ping) outbound(writeTarget); - TimeUtils.rollMockClock(50); + TimeUtils.rollMockClock(Duration.ofSeconds(50)); inbound(writeTarget, new Pong(pingMsg.getNonce())); Duration elapsed2 = future2.get(); assertEquals(elapsed2.toMillis(), peer.getLastPingTime()); assertEquals(7250, peer.getPingTime()); - TimeUtils.resetMocking(); + TimeUtils.clearMockClock(); } @Test diff --git a/integration-test/src/test/java/org/bitcoinj/core/TransactionBroadcastTest.java b/integration-test/src/test/java/org/bitcoinj/core/TransactionBroadcastTest.java index 37f0ade3b..77f5fcfdc 100644 --- a/integration-test/src/test/java/org/bitcoinj/core/TransactionBroadcastTest.java +++ b/integration-test/src/test/java/org/bitcoinj/core/TransactionBroadcastTest.java @@ -80,7 +80,7 @@ public class TransactionBroadcastTest extends TestWithPeerGroup { @After public void tearDown() { super.tearDown(); - TimeUtils.resetMocking(); + TimeUtils.clearMockClock(); } @Test diff --git a/integration-test/src/test/java/org/bitcoinj/testing/TestWithPeerGroup.java b/integration-test/src/test/java/org/bitcoinj/testing/TestWithPeerGroup.java index fe41f37f7..8eef3adb4 100644 --- a/integration-test/src/test/java/org/bitcoinj/testing/TestWithPeerGroup.java +++ b/integration-test/src/test/java/org/bitcoinj/testing/TestWithPeerGroup.java @@ -39,6 +39,7 @@ import org.junit.rules.Timeout; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.time.Duration; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.Semaphore; @@ -121,7 +122,7 @@ public class TestWithPeerGroup extends TestWithNetworkConnections { if (!blockJobs) return super.schedule(command, delay, unit); return super.schedule(() -> { - TimeUtils.rollMockClockMillis(unit.toMillis(delay)); + TimeUtils.rollMockClock(Duration.ofMillis(unit.toMillis(delay))); command.run(); jobBlocks.acquireUninterruptibly(); }, 0 /* immediate */, unit);