From 3e21cbd9daeebd29c52e6956894df5541024816c Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Mon, 12 Jan 2026 08:46:56 -0800 Subject: [PATCH 1/8] BasicKeyChainTest: don't mutate list in `importKeys()` For the check that duplicate keys are ignored, create a new list (keys2) rather than mutate (add a key) the first list (keys). This is functional style and also makes a forthcoming change to replace Guava `Lists.newArrayList()` smoother. --- .../test/java/org/bitcoinj/wallet/BasicKeyChainTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/test/java/org/bitcoinj/wallet/BasicKeyChainTest.java b/core/src/test/java/org/bitcoinj/wallet/BasicKeyChainTest.java index fb1cac93927..6c176ecc75d 100644 --- a/core/src/test/java/org/bitcoinj/wallet/BasicKeyChainTest.java +++ b/core/src/test/java/org/bitcoinj/wallet/BasicKeyChainTest.java @@ -74,7 +74,7 @@ public void importKeys() { final ECKey key1 = ECKey.random(); TimeUtils.rollMockClock(Duration.ofDays(1)); final ECKey key2 = ECKey.random(); - final ArrayList keys = Lists.newArrayList(key1, key2); + final List keys = Lists.newArrayList(key1, key2); // Import two keys, check the event is correct. assertEquals(2, chain.importKeys(keys)); @@ -84,11 +84,11 @@ public void importKeys() { assertEquals(now, chain.earliestKeyCreationTime()); // Check we ignore duplicates. final ECKey newKey = ECKey.random(); - keys.add(newKey); - assertEquals(1, chain.importKeys(keys)); + final List keys2 = Lists.newArrayList(key1, key2, newKey); + assertEquals(1, chain.importKeys(keys2)); assertTrue(onKeysAddedRan.getAndSet(false)); assertEquals(newKey, onKeysAdded.getAndSet(null).get(0)); - assertEquals(0, chain.importKeys(keys)); + assertEquals(0, chain.importKeys(keys2)); assertFalse(onKeysAddedRan.getAndSet(false)); assertNull(onKeysAdded.get()); From d521ad0c459e08dd12a6550e61da991147f1fd7a Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Mon, 12 Jan 2026 08:49:49 -0800 Subject: [PATCH 2/8] BasicKeyChainTest: remove use of `ArrayList` type Declare two local list variables as `List` instead of `ArrayList`. This reflects their actual usage, is better style, and smooths the path to removing Guava `List.newArrayList()`. --- .../src/test/java/org/bitcoinj/wallet/BasicKeyChainTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/test/java/org/bitcoinj/wallet/BasicKeyChainTest.java b/core/src/test/java/org/bitcoinj/wallet/BasicKeyChainTest.java index 6c176ecc75d..507c41e0b9e 100644 --- a/core/src/test/java/org/bitcoinj/wallet/BasicKeyChainTest.java +++ b/core/src/test/java/org/bitcoinj/wallet/BasicKeyChainTest.java @@ -32,7 +32,6 @@ import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -126,14 +125,14 @@ public void checkPasswordNoKeys() { @Test(expected = IllegalStateException.class) public void checkPasswordNotEncrypted() { - final ArrayList keys = Lists.newArrayList(ECKey.random(), ECKey.random()); + final List keys = Lists.newArrayList(ECKey.random(), ECKey.random()); chain.importKeys(keys); chain.checkPassword("test"); } @Test(expected = IllegalStateException.class) public void doubleEncryptFails() { - final ArrayList keys = Lists.newArrayList(ECKey.random(), ECKey.random()); + final List keys = Lists.newArrayList(ECKey.random(), ECKey.random()); chain.importKeys(keys); chain = chain.toEncrypted("foo"); chain.toEncrypted("foo"); From 7b20b88e362221cfc6fe70bcca890da48df39363 Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Tue, 13 Jan 2026 10:42:40 -0800 Subject: [PATCH 3/8] ECKey, DeterministicKey: remove deprecated methods `setCreationTimeSeconds()` --- .../java/org/bitcoinj/crypto/DeterministicKey.java | 11 ----------- core/src/main/java/org/bitcoinj/crypto/ECKey.java | 11 ----------- 2 files changed, 22 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/crypto/DeterministicKey.java b/core/src/main/java/org/bitcoinj/crypto/DeterministicKey.java index 1f7345beb09..6ba81818c0f 100644 --- a/core/src/main/java/org/bitcoinj/crypto/DeterministicKey.java +++ b/core/src/main/java/org/bitcoinj/crypto/DeterministicKey.java @@ -720,17 +720,6 @@ public void clearCreationTime() { super.clearCreationTime(); } - /** @deprecated use {@link #setCreationTime(Instant)} */ - @Deprecated - public void setCreationTimeSeconds(long creationTimeSecs) { - if (creationTimeSecs > 0) - setCreationTime(Instant.ofEpochSecond(creationTimeSecs)); - else if (creationTimeSecs == 0) - clearCreationTime(); - else - throw new IllegalArgumentException("Cannot set creation time to negative value: " + creationTimeSecs); - } - /** * Verifies equality of all fields but NOT the parent pointer (thus the same key derived in two separate hierarchy * objects will equal each other. diff --git a/core/src/main/java/org/bitcoinj/crypto/ECKey.java b/core/src/main/java/org/bitcoinj/crypto/ECKey.java index d13a6b7e16a..b35fa65cca8 100644 --- a/core/src/main/java/org/bitcoinj/crypto/ECKey.java +++ b/core/src/main/java/org/bitcoinj/crypto/ECKey.java @@ -1130,17 +1130,6 @@ public void clearCreationTime() { this.creationTime = null; } - /** @deprecated use {@link #setCreationTime(Instant)} */ - @Deprecated - public void setCreationTimeSeconds(long creationTimeSecs) { - if (creationTimeSecs > 0) - setCreationTime(Instant.ofEpochSecond(creationTimeSecs)); - else if (creationTimeSecs == 0) - clearCreationTime(); - else - throw new IllegalArgumentException("Cannot set creation time to negative value: " + creationTimeSecs); - } - /** * Create an encrypted private key with the keyCrypter and the AES key supplied. * This method returns a new encrypted key and leaves the original unchanged. From 2aa89ae16fd47a3844c47aa22700c8e75044e9fe Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Tue, 13 Jan 2026 15:14:02 -0800 Subject: [PATCH 4/8] TransactionConfidence: replace Guava `Iterators` with JDK `Collections` in `getBroadcastBy()` It also returns an immutable copy. --- .../main/java/org/bitcoinj/core/TransactionConfidence.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/TransactionConfidence.java b/core/src/main/java/org/bitcoinj/core/TransactionConfidence.java index 358900b8d17..15400bc30a2 100644 --- a/core/src/main/java/org/bitcoinj/core/TransactionConfidence.java +++ b/core/src/main/java/org/bitcoinj/core/TransactionConfidence.java @@ -18,7 +18,6 @@ package org.bitcoinj.core; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Iterators; import org.bitcoinj.base.Sha256Hash; import org.bitcoinj.base.internal.TimeUtils; import org.bitcoinj.utils.ListenerRegistration; @@ -328,9 +327,7 @@ public int numBroadcastPeers() { * Returns a snapshot of {@link PeerAddress}es that announced the transaction. */ public Set getBroadcastBy() { - Set broadcastBySet = new HashSet<>(); - Iterators.addAll(broadcastBySet, broadcastBy.listIterator()); - return broadcastBySet; + return Collections.unmodifiableSet(new HashSet<>(broadcastBy)); } /** Returns true if the given address has been seen via markBroadcastBy() */ From bee72172186d835fb3c7192c5d70ec8b034b347f Mon Sep 17 00:00:00 2001 From: nadavramon <159230565+nadavramon@users.noreply.github.com> Date: Wed, 14 Jan 2026 17:02:58 +0200 Subject: [PATCH 5/8] BitcoinSerializerTest, SendHeadersMessageTest: replace Guava `BaseEncoding.base16()` with `ByteUtils.parseHex()` --- .../test/java/org/bitcoinj/core/BitcoinSerializerTest.java | 6 ++---- .../java/org/bitcoinj/core/SendHeadersMessageTest.java | 7 +++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/core/src/test/java/org/bitcoinj/core/BitcoinSerializerTest.java b/core/src/test/java/org/bitcoinj/core/BitcoinSerializerTest.java index e8ccaafe263..c3e83a7d5cd 100644 --- a/core/src/test/java/org/bitcoinj/core/BitcoinSerializerTest.java +++ b/core/src/test/java/org/bitcoinj/core/BitcoinSerializerTest.java @@ -17,7 +17,6 @@ package org.bitcoinj.core; -import com.google.common.io.BaseEncoding; import org.bitcoinj.base.internal.ByteUtils; import org.bitcoinj.base.internal.TimeUtils; import org.bitcoinj.params.MainNetParams; @@ -40,12 +39,11 @@ import static org.junit.Assert.assertTrue; public class BitcoinSerializerTest { - private static final BaseEncoding HEX = BaseEncoding.base16().lowerCase(); private static final NetworkParameters MAINNET = MainNetParams.get(); private static final byte[] ADDRESS_MESSAGE_BYTES = ByteUtils.parseHex("f9beb4d96164647200000000000000001f000000" + "ed52399b01e215104d010000000000000000000000000000000000ffff0a000001208d"); - private static final byte[] TRANSACTION_MESSAGE_BYTES = HEX.withSeparator(" ", 2).decode( + private static final byte[] TRANSACTION_MESSAGE_BYTES = ByteUtils.parseHex(( "f9 be b4 d9 74 78 00 00 00 00 00 00 00 00 00 00" + "02 01 00 00 e2 93 cd be 01 00 00 00 01 6d bd db" + "08 5b 1d 8a f7 51 84 f0 bc 01 fa d5 8d 12 66 e9" + @@ -63,7 +61,7 @@ public class BitcoinSerializerTest { "cd 1c be a6 e7 45 8a 7a ba d5 12 a9 d9 ea 1a fb" + "22 5e 88 ac 80 fa e9 c7 00 00 00 00 19 76 a9 14" + "0e ab 5b ea 43 6a 04 84 cf ab 12 48 5e fd a0 b7" + - "8b 4e cc 52 88 ac 00 00 00 00"); + "8b 4e cc 52 88 ac 00 00 00 00").replaceAll("\\s", "")); @Test public void testAddr() throws Exception { diff --git a/core/src/test/java/org/bitcoinj/core/SendHeadersMessageTest.java b/core/src/test/java/org/bitcoinj/core/SendHeadersMessageTest.java index eafac639de0..e7cba189735 100644 --- a/core/src/test/java/org/bitcoinj/core/SendHeadersMessageTest.java +++ b/core/src/test/java/org/bitcoinj/core/SendHeadersMessageTest.java @@ -16,8 +16,8 @@ package org.bitcoinj.core; -import com.google.common.io.BaseEncoding; import org.bitcoinj.base.BitcoinNetwork; +import org.bitcoinj.base.internal.ByteUtils; import org.junit.Test; import java.nio.ByteBuffer; @@ -25,12 +25,11 @@ import static org.junit.Assert.assertTrue; public class SendHeadersMessageTest { - private static final BaseEncoding HEX = BaseEncoding.base16().lowerCase(); @Test public void decodeAndEncode() throws Exception { - byte[] message = HEX - .decode("00000000fabfb5da73656e646865616465727300000000005df6e0e2fabfb5da70696e670000000000000000080000009a" + byte[] message = ByteUtils.parseHex( + "00000000fabfb5da73656e646865616465727300000000005df6e0e2fabfb5da70696e670000000000000000080000009a" + "65b9cc9840c9729e4502b200000000000000000000000000000d000000000000000000000000000000000000000000000000007ad82" + "872c28ac782102f5361746f7368693a302e31342e312fe41d000001fabfb5da76657261636b000000000000000000005df6e0e2fabf" + "b5da616c65727400000000000000a80000001bf9aaea60010000000000000000000000ffffff7f00000000ffffff7ffeffff7f01fff" From 82b379ca00b839055e24d1d96fbf3350433f2b30 Mon Sep 17 00:00:00 2001 From: nadavramon <159230565+nadavramon@users.noreply.github.com> Date: Tue, 13 Jan 2026 22:24:10 +0200 Subject: [PATCH 6/8] Transaction: extract method `calculateVirtualTransactionSize()` from two places Also introduce constant for witness scale factor. --- .../java/org/bitcoinj/core/Transaction.java | 18 +++++++++++++++++- .../main/java/org/bitcoinj/wallet/Wallet.java | 4 +--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/Transaction.java b/core/src/main/java/org/bitcoinj/core/Transaction.java index 1705f7d0cbb..6ce943caabb 100644 --- a/core/src/main/java/org/bitcoinj/core/Transaction.java +++ b/core/src/main/java/org/bitcoinj/core/Transaction.java @@ -151,6 +151,22 @@ private static int sortableBlockHeight(Transaction tx) { */ public static final Coin DEFAULT_TX_FEE = Coin.valueOf(100_000); // 1 mBTC + /** + * The scale factor for Witness data in Segregated Witness transactions. + * @see BIP 141 + */ + public static final int WITNESS_SCALE_FACTOR = 4; + + /** + * Virtual transaction size is defined as Transaction weight / 4 (rounded up to the next integer). + * @param weight the transaction weight + * @return the virtual transaction size + * @see BIP 141 + */ + public static int calculateVirtualTransactionSize(int weight) { + return IntMath.divide(weight, WITNESS_SCALE_FACTOR, RoundingMode.CEILING); // round up + } + private final int protocolVersion; // These are bitcoin serialized. @@ -397,7 +413,7 @@ public int getWeight() { public int getVsize() { if (!hasWitnesses()) return this.messageSize(); - return IntMath.divide(getWeight(), 4, RoundingMode.CEILING); // round up + return calculateVirtualTransactionSize(getWeight()); } diff --git a/core/src/main/java/org/bitcoinj/wallet/Wallet.java b/core/src/main/java/org/bitcoinj/wallet/Wallet.java index 8796c34fc65..55b25a2e07b 100644 --- a/core/src/main/java/org/bitcoinj/wallet/Wallet.java +++ b/core/src/main/java/org/bitcoinj/wallet/Wallet.java @@ -19,7 +19,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ArrayListMultimap; -import com.google.common.math.IntMath; import com.google.protobuf.ByteString; import org.bitcoinj.core.internal.GuardedBy; import org.bitcoinj.base.BitcoinNetwork; @@ -5367,8 +5366,7 @@ private int estimateVirtualBytesForSigning(Script script) { } else if (ScriptPattern.isP2WPKH(script)) { ECKey key = findKeyFromPubKeyHash(ScriptPattern.extractHashFromP2WH(script), ScriptType.P2WPKH); Objects.requireNonNull(key, "Coin selection includes unspendable outputs"); - return IntMath.divide(script.getNumberOfBytesRequiredToSpend(key, null), 4, - RoundingMode.CEILING); // round up + return Transaction.calculateVirtualTransactionSize(script.getNumberOfBytesRequiredToSpend(key, null)); } else if (ScriptPattern.isP2SH(script)) { Script redeemScript = findRedeemDataFromScriptHash(ScriptPattern.extractHashFromP2SH(script)).redeemScript; Objects.requireNonNull(redeemScript, "Coin selection includes unspendable outputs"); From 220bc60fc6a42e73198a6e3ccf0bac8cf1995e18 Mon Sep 17 00:00:00 2001 From: nadavramon <159230565+nadavramon@users.noreply.github.com> Date: Tue, 13 Jan 2026 22:24:50 +0200 Subject: [PATCH 7/8] Transaction: replace Guava `IntMath.divide()` with standard Java math --- core/src/main/java/org/bitcoinj/core/Transaction.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/Transaction.java b/core/src/main/java/org/bitcoinj/core/Transaction.java index 6ce943caabb..cf48b06b647 100644 --- a/core/src/main/java/org/bitcoinj/core/Transaction.java +++ b/core/src/main/java/org/bitcoinj/core/Transaction.java @@ -18,7 +18,6 @@ package org.bitcoinj.core; import com.google.common.base.MoreObjects; -import com.google.common.math.IntMath; import org.bitcoinj.base.Address; import org.bitcoinj.base.Coin; import org.bitcoinj.base.Network; @@ -164,7 +163,7 @@ private static int sortableBlockHeight(Transaction tx) { * @see BIP 141 */ public static int calculateVirtualTransactionSize(int weight) { - return IntMath.divide(weight, WITNESS_SCALE_FACTOR, RoundingMode.CEILING); // round up + return (weight + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR; // round up } private final int protocolVersion; From 8d8af433f43bf48450dc7a16c21e4cac81a1d5cb Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Sun, 18 Jan 2026 11:33:37 -0800 Subject: [PATCH 8/8] PeerAddress: update Javadoc now that we are immutable and thread-safe --- core/src/main/java/org/bitcoinj/core/PeerAddress.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/bitcoinj/core/PeerAddress.java b/core/src/main/java/org/bitcoinj/core/PeerAddress.java index 825ddb3b355..8adfede0991 100644 --- a/core/src/main/java/org/bitcoinj/core/PeerAddress.java +++ b/core/src/main/java/org/bitcoinj/core/PeerAddress.java @@ -46,7 +46,7 @@ * A PeerAddress holds an IP address and port number representing the network location of * a peer in the Bitcoin P2P network. It exists primarily for serialization purposes. *

- * Instances of this class are not safe for use by multiple threads. + * This class is immutable and thread-safe. */ public class PeerAddress { @Nullable