From adde43853c0ef50b5fed5cddda4d7741c5c6f599 Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Mon, 16 Mar 2026 15:37:52 -0700 Subject: [PATCH 1/5] package-info.java: add missing package-info to package `o.b.examples` --- .../org/bitcoinj/examples/package-info.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 examples/src/main/java/org/bitcoinj/examples/package-info.java diff --git a/examples/src/main/java/org/bitcoinj/examples/package-info.java b/examples/src/main/java/org/bitcoinj/examples/package-info.java new file mode 100644 index 00000000000..b243d7e94ca --- /dev/null +++ b/examples/src/main/java/org/bitcoinj/examples/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright by the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * bitcoinj example applications including {@link org.bitcoinj.examples.ForwardingService} which + * is featured in the Getting Started tutorial. + * @see Getting Started + */ +package org.bitcoinj.examples; From 4fae3ed85f420cf0fe72ee62af23a9a82be48e7e Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Mon, 16 Mar 2026 16:40:36 -0700 Subject: [PATCH 2/5] FetchBlock, ForwardingService, GenerateLowSTests: annotate nullable fields and methods --- examples/src/main/java/org/bitcoinj/examples/FetchBlock.java | 3 ++- .../src/main/java/org/bitcoinj/examples/ForwardingService.java | 3 ++- .../src/main/java/org/bitcoinj/examples/GenerateLowSTests.java | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/src/main/java/org/bitcoinj/examples/FetchBlock.java b/examples/src/main/java/org/bitcoinj/examples/FetchBlock.java index 5008add8166..d2d60db9aec 100644 --- a/examples/src/main/java/org/bitcoinj/examples/FetchBlock.java +++ b/examples/src/main/java/org/bitcoinj/examples/FetchBlock.java @@ -30,6 +30,7 @@ import org.bitcoinj.store.BlockStore; import org.bitcoinj.store.MemoryBlockStore; import org.bitcoinj.utils.BriefLogFormatter; +import org.jspecify.annotations.Nullable; import picocli.CommandLine; import java.util.concurrent.Callable; @@ -44,7 +45,7 @@ @CommandLine.Command(usageHelpAutoWidth = true, sortOptions = false) public class FetchBlock implements Callable { @CommandLine.Parameters(index = "0", description = "The hash of the block to download.") - private String blockHashParam; + private @Nullable String blockHashParam; @CommandLine.Option(names = "--localhost", description = "Connect to the localhost node. Default: ${DEFAULT-VALUE}") private boolean localhost = true; @CommandLine.Option(names = "--help", usageHelp = true, description = "Displays program options.") diff --git a/examples/src/main/java/org/bitcoinj/examples/ForwardingService.java b/examples/src/main/java/org/bitcoinj/examples/ForwardingService.java index eba04019ac2..d9d9cd881bf 100644 --- a/examples/src/main/java/org/bitcoinj/examples/ForwardingService.java +++ b/examples/src/main/java/org/bitcoinj/examples/ForwardingService.java @@ -29,6 +29,7 @@ import org.bitcoinj.wallet.SendRequest; import org.bitcoinj.wallet.Wallet; import org.bitcoinj.wallet.listeners.WalletCoinsReceivedEventListener; +import org.jspecify.annotations.Nullable; import java.io.Closeable; import java.io.File; @@ -46,7 +47,7 @@ public class ForwardingService implements Closeable { static final int MAX_CONNECTIONS = 4; private final BitcoinNetwork network; private final Address forwardingAddress; - private volatile WalletAppKit kit; + private volatile @Nullable WalletAppKit kit; /* We need to save the listener object (created by a method reference) so we can remove it later */ private final WalletCoinsReceivedEventListener coinsReceivedListener = this::coinForwardingListener; diff --git a/examples/src/main/java/org/bitcoinj/examples/GenerateLowSTests.java b/examples/src/main/java/org/bitcoinj/examples/GenerateLowSTests.java index d0a10ecb501..a8e9ebae207 100644 --- a/examples/src/main/java/org/bitcoinj/examples/GenerateLowSTests.java +++ b/examples/src/main/java/org/bitcoinj/examples/GenerateLowSTests.java @@ -38,6 +38,7 @@ import org.bitcoinj.signers.TransactionSigner.ProposedTransaction; import org.bitcoinj.wallet.KeyBag; import org.bitcoinj.wallet.RedeemData; +import org.jspecify.annotations.Nullable; import java.io.IOException; import java.math.BigInteger; @@ -75,6 +76,7 @@ public ECKey findKeyFromPubKey(byte[] pubKey) { } @Override + @Nullable public RedeemData findRedeemDataFromScriptHash(byte[] scriptHash) { return null; } From b44df537e6cf00420d195d70529bb37573602163 Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Mon, 16 Mar 2026 16:41:34 -0700 Subject: [PATCH 3/5] FetchBlock: make implicit nullability assumption explicit --- examples/src/main/java/org/bitcoinj/examples/FetchBlock.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/src/main/java/org/bitcoinj/examples/FetchBlock.java b/examples/src/main/java/org/bitcoinj/examples/FetchBlock.java index d2d60db9aec..7ebfa3e4e3b 100644 --- a/examples/src/main/java/org/bitcoinj/examples/FetchBlock.java +++ b/examples/src/main/java/org/bitcoinj/examples/FetchBlock.java @@ -33,6 +33,7 @@ import org.jspecify.annotations.Nullable; import picocli.CommandLine; +import java.util.Objects; import java.util.concurrent.Callable; import java.util.concurrent.Future; @@ -60,6 +61,7 @@ public static void main(String[] args) throws Exception { @Override public Integer call() throws Exception { // Connect to testnet and find a peer + Objects.requireNonNull(blockHashParam); System.out.println("Connecting to node"); final Network network = BitcoinNetwork.TESTNET; final NetworkParameters params = NetworkParameters.of(network); From 6833cbd09b3d22d4160d4ccae8a6c981fdce0d4f Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Mon, 16 Mar 2026 16:42:14 -0700 Subject: [PATCH 4/5] PrintPeers: fix, refactor, make null-safe --- .../org/bitcoinj/examples/PrintPeers.java | 45 ++++++++++--------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/examples/src/main/java/org/bitcoinj/examples/PrintPeers.java b/examples/src/main/java/org/bitcoinj/examples/PrintPeers.java index e0b398669b1..d5e81a0d853 100644 --- a/examples/src/main/java/org/bitcoinj/examples/PrintPeers.java +++ b/examples/src/main/java/org/bitcoinj/examples/PrintPeers.java @@ -18,6 +18,7 @@ import org.bitcoinj.base.BitcoinNetwork; import org.bitcoinj.base.Network; +import org.bitcoinj.core.Context; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.Peer; import org.bitcoinj.core.PeerAddress; @@ -33,55 +34,55 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; /** * Prints a list of IP addresses obtained from DNS. */ public class PrintPeers { - private static List dnsPeers; + record PeerDnsResult(List dnsPeers, Duration duration) {} - private static void printElapsed(long start) { - long now = System.currentTimeMillis(); - System.out.println(String.format("Took %.2f seconds", (now - start) / 1000.0)); - } - - private static void printPeers(List addresses) { - for (InetSocketAddress address : addresses) { + private static void printPeers(PeerDnsResult result) { + for (InetSocketAddress address : result.dnsPeers()) { String hostAddress = address.getAddress().getHostAddress(); System.out.println(String.format("%s:%d", hostAddress, address.getPort())); } + System.out.println(String.format("DNS query took %.2f seconds", result.duration().toMillis() / 1000.0)); } - private static void printDNS(Network network) throws PeerDiscoveryException { + private static PeerDnsResult getPeers(Network network) throws PeerDiscoveryException { long start = System.currentTimeMillis(); DnsDiscovery dns = new DnsDiscovery(network); - dnsPeers = dns.getPeers(0, Duration.ofSeconds(10)); - printPeers(dnsPeers); - printElapsed(start); + List dnsPeers = dns.getPeers(0, Duration.ofSeconds(10)); + var duration = Duration.ofMillis(System.currentTimeMillis() - start); + return new PeerDnsResult(dnsPeers, duration); } public static void main(String[] args) throws Exception { - BriefLogFormatter.init(); + BriefLogFormatter.init(Level.WARNING); + Context.propagate(new Context()); final Network network = BitcoinNetwork.MAINNET; final NetworkParameters params = NetworkParameters.of(network); System.out.println("=== DNS ==="); - printDNS(network); + PeerDnsResult result = getPeers(network); + printPeers(result); System.out.println("=== Version/chain heights ==="); ArrayList addrs = new ArrayList<>(); - for (InetSocketAddress peer : dnsPeers) addrs.add(peer.getAddress()); + for (InetSocketAddress peer : result.dnsPeers()) addrs.add(peer.getAddress()); System.out.println("Scanning " + addrs.size() + " peers:"); final Object lock = new Object(); final long[] bestHeight = new long[1]; - List> futures = new ArrayList<>(); + List> futures = new ArrayList<>(); NioClientManager clientManager = new NioClientManager(); + clientManager.start().join(); for (final InetAddress addr : addrs) { InetSocketAddress address = new InetSocketAddress(addr, params.getPort()); final Peer peer = new Peer(params, new VersionMessage(params, 0), PeerAddress.simple(address), null); - final CompletableFuture future = new CompletableFuture<>(); + final CompletableFuture future = new CompletableFuture<>(); // Once the connection has completed version handshaking ... peer.addConnectedEventListener((p, peerCount) -> { // Check the chain height it claims to have. @@ -100,18 +101,22 @@ public static void main(String[] args) throws Exception { } } // Now finish the future and close the connection - future.complete(null); + future.complete(true); peer.close(); }); peer.addDisconnectedEventListener((p, peerCount) -> { - if (!future.isDone()) + if (!future.isDone()) { System.out.println("Failed to talk to " + addr); - future.complete(null); + future.complete(false); + } }); clientManager.openConnection(address, peer); futures.add(future); } // Wait for every tried connection to finish. CompletableFuture.allOf(futures.toArray( new CompletableFuture[0])).join(); + int successful = futures.stream().mapToInt(f -> f.join() ? 1 : 0).sum(); + int total = futures.size(); + System.out.printf("Successfully talked to %d of %d nodes.\n", successful, total); } } From 3b2ec0b42fec870fae26ac8a76008507da0dbc03 Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Mon, 16 Mar 2026 16:42:48 -0700 Subject: [PATCH 5/5] package-info.java: declare nullness expectations for package `o.b.examples` --- examples/src/main/java/org/bitcoinj/examples/package-info.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/src/main/java/org/bitcoinj/examples/package-info.java b/examples/src/main/java/org/bitcoinj/examples/package-info.java index b243d7e94ca..3145fa3ef45 100644 --- a/examples/src/main/java/org/bitcoinj/examples/package-info.java +++ b/examples/src/main/java/org/bitcoinj/examples/package-info.java @@ -19,4 +19,7 @@ * is featured in the Getting Started tutorial. * @see Getting Started */ +@NullMarked package org.bitcoinj.examples; + +import org.jspecify.annotations.NullMarked;