From da8b9ce434d2b234215dad2bf53dd6392ea12645 Mon Sep 17 00:00:00 2001 From: Andreas Schildbach Date: Fri, 10 Mar 2023 10:35:17 +0100 Subject: [PATCH] Wallet: migrate `lastBlockSeenTime` field to `java.time` API --- .../bitcoinj/wallet/DefaultRiskAnalysis.java | 12 ++-- .../main/java/org/bitcoinj/wallet/Wallet.java | 58 ++++++++++++------- .../java/org/bitcoinj/wallet/WalletFiles.java | 10 ++-- .../wallet/WalletProtobufSerializer.java | 8 +-- .../wallet/DefaultRiskAnalysisTest.java | 3 +- .../java/org/bitcoinj/wallet/WalletTest.java | 2 +- 6 files changed, 57 insertions(+), 36 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/wallet/DefaultRiskAnalysis.java b/core/src/main/java/org/bitcoinj/wallet/DefaultRiskAnalysis.java index a47366c5f..dc2863a17 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DefaultRiskAnalysis.java +++ b/core/src/main/java/org/bitcoinj/wallet/DefaultRiskAnalysis.java @@ -31,7 +31,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; +import java.time.Instant; import java.util.List; +import java.util.Optional; import static com.google.common.base.Preconditions.checkState; @@ -91,21 +93,23 @@ public class DefaultRiskAnalysis implements RiskAnalysis { if (wallet == null) return null; + final int height = wallet.getLastBlockSeenHeight(); - final long time = wallet.getLastBlockSeenTimeSecs(); - if (time == 0) + final Optional time = wallet.getLastBlockSeenTimeInstant(); + if (!time.isPresent()) return null; // If the transaction has a lock time specified in blocks, we consider that if the tx would become final in the // next block it is not risky (as it would confirm normally). final int adjustedHeight = height + 1; + final long timeSecs = time.get().getEpochSecond(); - if (!tx.isFinal(adjustedHeight, time)) { + if (!tx.isFinal(adjustedHeight, timeSecs)) { nonFinal = tx; return Result.NON_FINAL; } for (Transaction dep : dependencies) { - if (!dep.isFinal(adjustedHeight, time)) { + if (!dep.isFinal(adjustedHeight, timeSecs)) { nonFinal = dep; return Result.NON_FINAL; } diff --git a/core/src/main/java/org/bitcoinj/wallet/Wallet.java b/core/src/main/java/org/bitcoinj/wallet/Wallet.java index 2acd0e031..5177b97de 100644 --- a/core/src/main/java/org/bitcoinj/wallet/Wallet.java +++ b/core/src/main/java/org/bitcoinj/wallet/Wallet.java @@ -249,7 +249,7 @@ public class Wallet extends BaseTaggableObject @Nullable private Sha256Hash lastBlockSeenHash; private int lastBlockSeenHeight; - private long lastBlockSeenTimeSecs; + @Nullable private Instant lastBlockSeenTime; private final List> changeListeners = new CopyOnWriteArrayList<>(); @@ -2447,7 +2447,7 @@ public class Wallet extends BaseTaggableObject // Store the new block hash. setLastBlockSeenHash(newBlockHash); setLastBlockSeenHeight(block.getHeight()); - setLastBlockSeenTimeSecs(block.getHeader().getTimeSeconds()); + setLastBlockSeenTime(block.getHeader().getTimeInstant()); // Notify all the BUILDING transactions of the new block. // This is so that they can update their depth. Set transactions = getTransactions(true); @@ -3292,7 +3292,7 @@ public class Wallet extends BaseTaggableObject clearTransactions(); lastBlockSeenHash = null; lastBlockSeenHeight = -1; // Magic value for 'never'. - lastBlockSeenTimeSecs = 0; + lastBlockSeenTime = null; saveLater(); maybeQueueOnWalletChanged(); } finally { @@ -3510,9 +3510,10 @@ public class Wallet extends BaseTaggableObject builder.append(" ").append(unspent.size()).append(" unspent\n"); builder.append(" ").append(spent.size()).append(" spent\n"); builder.append(" ").append(dead.size()).append(" dead\n"); - final Date lastBlockSeenTime = getLastBlockSeenTime(); builder.append("Last seen best block: ").append(getLastBlockSeenHeight()).append(" (") - .append(lastBlockSeenTime == null ? "time unknown" : TimeUtils.dateTimeFormat(lastBlockSeenTime)) + .append(getLastBlockSeenTimeInstant() + .map(instant -> TimeUtils.dateTimeFormat(instant.toEpochMilli())) + .orElse("time unknown")) .append("): ").append(getLastBlockSeenHash()).append('\n'); final KeyCrypter crypter = keyChainGroup.getKeyCrypter(); if (crypter != null) @@ -3667,45 +3668,58 @@ public class Wallet extends BaseTaggableObject } } - public void setLastBlockSeenTimeSecs(long timeSecs) { + public void setLastBlockSeenTime(Instant time) { lock.lock(); try { - lastBlockSeenTimeSecs = timeSecs; + lastBlockSeenTime = checkNotNull(time); } finally { lock.unlock(); } } + public void clearLastBlockSeenTime() { + lock.lock(); + try { + lastBlockSeenTime = null; + } finally { + lock.unlock(); + } + } + + /** @deprecated use {@link #setLastBlockSeenTime(Instant)} or {@link #clearLastBlockSeenTime()} */ + @Deprecated + public void setLastBlockSeenTimeSecs(long timeSecs) { + checkArgument(timeSecs > 0); + setLastBlockSeenTime(Instant.ofEpochSecond(timeSecs)); + } + /** - * Returns the UNIX time in seconds since the epoch extracted from the last best seen block header. This timestamp + * Returns time extracted from the last best seen block header, or empty. This timestamp * is not the local time at which the block was first observed by this application but rather what the block * (i.e. miner) self declares. It is allowed to have some significant drift from the real time at which the block * was found, although most miners do use accurate times. If this wallet is old and does not have a recorded * time then this method returns zero. */ - public long getLastBlockSeenTimeSecs() { + public Optional getLastBlockSeenTimeInstant() { lock.lock(); try { - return lastBlockSeenTimeSecs; + return Optional.ofNullable(lastBlockSeenTime); } finally { lock.unlock(); } } - /** - * Returns a {@link Date} representing the time extracted from the last best seen block header. This timestamp - * is not the local time at which the block was first observed by this application but rather what the block - * (i.e. miner) self declares. It is allowed to have some significant drift from the real time at which the block - * was found, although most miners do use accurate times. If this wallet is old and does not have a recorded - * time then this method returns null. - */ + /** @deprecated use {@link #getLastBlockSeenTimeInstant()} */ + @Deprecated + public long getLastBlockSeenTimeSecs() { + return getLastBlockSeenTimeInstant().map(Instant::getEpochSecond).orElse((long) 0); + } + + /** @deprecated use {@link #getLastBlockSeenTimeInstant()} */ + @Deprecated @Nullable public Date getLastBlockSeenTime() { - final long secs = getLastBlockSeenTimeSecs(); - if (secs == 0) - return null; - else - return new Date(secs * 1000); + return getLastBlockSeenTimeInstant().map(Date::from).orElse(null); } /** diff --git a/core/src/main/java/org/bitcoinj/wallet/WalletFiles.java b/core/src/main/java/org/bitcoinj/wallet/WalletFiles.java index 7160414f5..9e08f85da 100644 --- a/core/src/main/java/org/bitcoinj/wallet/WalletFiles.java +++ b/core/src/main/java/org/bitcoinj/wallet/WalletFiles.java @@ -94,10 +94,11 @@ public class WalletFiles { // Some other scheduled request already beat us to it. return null; } - Date lastBlockSeenTime = wallet.getLastBlockSeenTime(); log.info("Background saving wallet; last seen block is height {}, date {}, hash {}", wallet.getLastBlockSeenHeight(), - lastBlockSeenTime != null ? TimeUtils.dateTimeFormat(lastBlockSeenTime) : "unknown", + wallet.getLastBlockSeenTimeInstant() + .map(time -> TimeUtils.dateTimeFormat(time.toEpochMilli())) + .orElse("unknown"), wallet.getLastBlockSeenHash()); saveNowInternal(); return null; @@ -128,9 +129,10 @@ public class WalletFiles { // but they will serialize (using different temp files). if (executor.isShutdown()) return; - Date lastBlockSeenTime = wallet.getLastBlockSeenTime(); log.info("Saving wallet; last seen block is height {}, date {}, hash {}", wallet.getLastBlockSeenHeight(), - lastBlockSeenTime != null ? TimeUtils.dateTimeFormat(lastBlockSeenTime) : "unknown", + wallet.getLastBlockSeenTimeInstant() + .map(time -> TimeUtils.dateTimeFormat(time.toEpochMilli())) + .orElse("unknown"), wallet.getLastBlockSeenHash()); saveNowInternal(); } diff --git a/core/src/main/java/org/bitcoinj/wallet/WalletProtobufSerializer.java b/core/src/main/java/org/bitcoinj/wallet/WalletProtobufSerializer.java index 89ac5d6f9..3dcdc9bef 100644 --- a/core/src/main/java/org/bitcoinj/wallet/WalletProtobufSerializer.java +++ b/core/src/main/java/org/bitcoinj/wallet/WalletProtobufSerializer.java @@ -201,8 +201,8 @@ public class WalletProtobufSerializer { walletBuilder.setLastSeenBlockHash(hashToByteString(lastSeenBlockHash)); walletBuilder.setLastSeenBlockHeight(wallet.getLastBlockSeenHeight()); } - if (wallet.getLastBlockSeenTimeSecs() > 0) - walletBuilder.setLastSeenBlockTimeSecs(wallet.getLastBlockSeenTimeSecs()); + wallet.getLastBlockSeenTimeInstant().ifPresent( + time -> walletBuilder.setLastSeenBlockTimeSecs(time.getEpochSecond())); // Populate the scrypt parameters. KeyCrypter keyCrypter = wallet.getKeyCrypter(); @@ -525,7 +525,7 @@ public class WalletProtobufSerializer { // Should mirror Wallet.reset() wallet.setLastBlockSeenHash(null); wallet.setLastBlockSeenHeight(-1); - wallet.setLastBlockSeenTimeSecs(0); + wallet.clearLastBlockSeenTime(); } else { // Read all transactions and insert into the txMap. for (Protos.Transaction txProto : walletProto.getTransactionList()) { @@ -550,7 +550,7 @@ public class WalletProtobufSerializer { wallet.setLastBlockSeenHeight(walletProto.getLastSeenBlockHeight()); } // Will default to zero if not present. - wallet.setLastBlockSeenTimeSecs(walletProto.getLastSeenBlockTimeSecs()); + wallet.setLastBlockSeenTime(Instant.ofEpochSecond(walletProto.getLastSeenBlockTimeSecs())); if (walletProto.hasKeyRotationTime()) { wallet.setKeyRotationTime(Instant.ofEpochSecond(walletProto.getKeyRotationTime())); diff --git a/core/src/test/java/org/bitcoinj/wallet/DefaultRiskAnalysisTest.java b/core/src/test/java/org/bitcoinj/wallet/DefaultRiskAnalysisTest.java index 72b0323e3..b68994f0e 100644 --- a/core/src/test/java/org/bitcoinj/wallet/DefaultRiskAnalysisTest.java +++ b/core/src/test/java/org/bitcoinj/wallet/DefaultRiskAnalysisTest.java @@ -37,6 +37,7 @@ import org.bitcoinj.wallet.DefaultRiskAnalysis.RuleViolation; import org.junit.Before; import org.junit.Test; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -61,7 +62,7 @@ public class DefaultRiskAnalysisTest { Context.propagate(new Context()); wallet = Wallet.createDeterministic(MAINNET, ScriptType.P2PKH); wallet.setLastBlockSeenHeight(1000); - wallet.setLastBlockSeenTimeSecs(TIMESTAMP); + wallet.setLastBlockSeenTime(Instant.ofEpochSecond(TIMESTAMP)); } @Test(expected = IllegalStateException.class) diff --git a/core/src/test/java/org/bitcoinj/wallet/WalletTest.java b/core/src/test/java/org/bitcoinj/wallet/WalletTest.java index 48c6725d1..5006249bd 100644 --- a/core/src/test/java/org/bitcoinj/wallet/WalletTest.java +++ b/core/src/test/java/org/bitcoinj/wallet/WalletTest.java @@ -1538,7 +1538,7 @@ public class WalletTest extends TestWithWallet { // Receive a block on the best chain - this should set the last block seen hash. chain.add(b10); assertEquals(b10.getHash(), wallet.getLastBlockSeenHash()); - assertEquals(b10.getTimeSeconds(), wallet.getLastBlockSeenTimeSecs()); + assertEquals(b10.getTimeInstant(), wallet.getLastBlockSeenTimeInstant().get()); assertEquals(1, wallet.getLastBlockSeenHeight()); // Receive a block on the side chain - this should not change the last block seen hash. chain.add(b11);