From 51f1d69e87e9fe2eab0b3af44a3b066e0fd24aeb Mon Sep 17 00:00:00 2001
From: Sean Gilligan
Date: Wed, 3 Aug 2022 09:05:47 -0700
Subject: [PATCH] Address: deprecate fromString, replace with AddressParser
This change migrates from using `NetworkParameters` to `Network` for specifying the network and
also decouples from static methods in `Address` with an interface/implementation approach.
Note that there are 3 use cases for address parsing:
1. Any network is allowed - AddressParser.parseAddressAnyNetwork(String)
2. Parse for a specified network - AddressParser.parseAddress(String, Network)
3. Parse for a previously-specified (context dependent) network - AddressParser.Strict.parseAddress(String)
In most use cases, an AddressParser instance can be accessed through the Wallet,
which already knows the Network type and in this context validation for network makes
sense. This is why `Wallet` is implementing `AddressParser.Strict`
BitcoinURI allocates its own DefaultAddressParser for now, as do some other tests
and examples that don't have access to a Wallet
In the future DefaultAddressParser may be replaced by something loaded
via the ServiceLoader mechanism or other dynamically configured mechanism.
---
.../main/java/org/bitcoinj/core/Address.java | 21 +++----
.../java/org/bitcoinj/core/AddressParser.java | 55 +++++++++++++++++++
.../bitcoinj/core/DefaultAddressParser.java | 48 ++++++++++++++++
.../java/org/bitcoinj/core/LegacyAddress.java | 3 +-
.../java/org/bitcoinj/uri/BitcoinURI.java | 7 ++-
.../main/java/org/bitcoinj/wallet/Wallet.java | 19 ++++++-
.../core/AddressComparatorSortTest.java | 4 +-
.../org/bitcoinj/examples/DoubleSpend.java | 5 +-
.../bitcoinj/examples/ForwardingService.java | 8 ++-
.../org/bitcoinj/examples/PrivateKeys.java | 5 +-
.../org/bitcoinj/examples/SendRequest.java | 2 +-
.../controls/BitcoinAddressValidator.java | 12 ++--
.../wallettemplate/SendMoneyController.java | 4 +-
.../org/bitcoinj/wallettool/WalletTool.java | 6 +-
14 files changed, 161 insertions(+), 38 deletions(-)
create mode 100644 core/src/main/java/org/bitcoinj/core/AddressParser.java
create mode 100644 core/src/main/java/org/bitcoinj/core/DefaultAddressParser.java
diff --git a/core/src/main/java/org/bitcoinj/core/Address.java b/core/src/main/java/org/bitcoinj/core/Address.java
index 4799f4200..5adbc4956 100644
--- a/core/src/main/java/org/bitcoinj/core/Address.java
+++ b/core/src/main/java/org/bitcoinj/core/Address.java
@@ -30,10 +30,11 @@ import static com.google.common.base.Preconditions.checkNotNull;
/**
* Base class for addresses, e.g. native segwit addresses ({@link SegwitAddress}) or legacy addresses ({@link LegacyAddress}).
*
- * Use {@link #fromString(NetworkParameters, String)} to conveniently construct any kind of address from its textual
+ * Use an implementation of {@link AddressParser#parseAddress(String, BitcoinNetwork)} to conveniently construct any kind of address from its textual
* form.
*/
public abstract class Address implements Comparable
{
+ protected static final AddressParser addressParser = new DefaultAddressParser();
protected final NetworkParameters params;
protected final byte[] bytes;
@@ -60,22 +61,14 @@ public abstract class Address implements Comparable {
* if the given string doesn't parse or the checksum is invalid
* @throws AddressFormatException.WrongNetwork
* if the given string is valid but not for the expected network (eg testnet vs mainnet)
+ * @deprecated Use {@link org.bitcoinj.wallet.Wallet#parseAddress(String)} or {@link AddressParser#parseAddress(String, BitcoinNetwork)}
*/
+ @Deprecated
public static Address fromString(@Nullable NetworkParameters params, String str)
throws AddressFormatException {
- try {
- return LegacyAddress.fromBase58(params, str);
- } catch (AddressFormatException.WrongNetwork x) {
- throw x;
- } catch (AddressFormatException x) {
- try {
- return SegwitAddress.fromBech32(params, str);
- } catch (AddressFormatException.WrongNetwork x2) {
- throw x;
- } catch (AddressFormatException x2) {
- throw new AddressFormatException(str);
- }
- }
+ return (params != null)
+ ? addressParser.parseAddress(str, params.network())
+ : addressParser.parseAddressAnyNetwork(str);
}
/**
diff --git a/core/src/main/java/org/bitcoinj/core/AddressParser.java b/core/src/main/java/org/bitcoinj/core/AddressParser.java
new file mode 100644
index 000000000..0d0febfec
--- /dev/null
+++ b/core/src/main/java/org/bitcoinj/core/AddressParser.java
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+package org.bitcoinj.core;
+
+import org.bitcoinj.base.BitcoinNetwork;
+import org.bitcoinj.base.exceptions.AddressFormatException;
+
+
+// TODO: Move this class to o.b.base
+/**
+ * Interface for parsing and validating address strings.
+ */
+public interface AddressParser {
+ /**
+ * Parse an address that could be for any network
+ * @param addressString string representation of address
+ * @return A validated address object
+ * @throws AddressFormatException invalid address string
+ */
+ Address parseAddressAnyNetwork(String addressString) throws AddressFormatException;
+
+ /**
+ * Parse an address and validate for specified network
+ * @param addressString string representation of address
+ * @param network the network the address string must represent
+ * @return A validated address object
+ * @throws AddressFormatException invalid address string or not valid for specified network
+ */
+ Address parseAddress(String addressString, BitcoinNetwork network) throws AddressFormatException;
+
+ @FunctionalInterface
+ interface Strict {
+ /**
+ * Parse an address in a strict context (e.g. the network must be valid)
+ * @param addressString string representation of address
+ * @return A validated address object
+ * @throws AddressFormatException invalid address string or not valid for network (provided by context)
+ */
+ Address parseAddress(String addressString) throws AddressFormatException;
+ }
+}
diff --git a/core/src/main/java/org/bitcoinj/core/DefaultAddressParser.java b/core/src/main/java/org/bitcoinj/core/DefaultAddressParser.java
new file mode 100644
index 000000000..7dd37bf61
--- /dev/null
+++ b/core/src/main/java/org/bitcoinj/core/DefaultAddressParser.java
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+package org.bitcoinj.core;
+
+import org.bitcoinj.base.BitcoinNetwork;
+import org.bitcoinj.base.exceptions.AddressFormatException;
+
+/**
+ * Address Parser that knows about the address types supported by bitcoinj core.
+ */
+public class DefaultAddressParser implements AddressParser {
+ @Override
+ public Address parseAddressAnyNetwork(String addressString) throws AddressFormatException {
+ return parseAddress(addressString, null);
+ }
+
+ @Override
+ public Address parseAddress(String addressString, BitcoinNetwork network) throws AddressFormatException {
+ NetworkParameters params = (network != null) ? NetworkParameters.of(network) : null;
+ try {
+ return LegacyAddress.fromBase58(params, addressString);
+ } catch (AddressFormatException.WrongNetwork x) {
+ throw x;
+ } catch (AddressFormatException x) {
+ try {
+ return SegwitAddress.fromBech32(params, addressString);
+ } catch (AddressFormatException.WrongNetwork x2) {
+ throw x;
+ } catch (AddressFormatException x2) {
+ throw new AddressFormatException(addressString);
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/org/bitcoinj/core/LegacyAddress.java b/core/src/main/java/org/bitcoinj/core/LegacyAddress.java
index d472ed4b5..07fd2f534 100644
--- a/core/src/main/java/org/bitcoinj/core/LegacyAddress.java
+++ b/core/src/main/java/org/bitcoinj/core/LegacyAddress.java
@@ -190,8 +190,7 @@ public class LegacyAddress extends Address {
*/
@Deprecated
public static NetworkParameters getParametersFromAddress(String address) throws AddressFormatException {
- // TODO: Provide a `Network`-based mechanism for resolving "alt addresses"
- return NetworkParameters.fromAddress(LegacyAddress.fromBase58(null, address));
+ return NetworkParameters.fromAddress(Address.addressParser.parseAddressAnyNetwork(address));
}
@Override
diff --git a/core/src/main/java/org/bitcoinj/uri/BitcoinURI.java b/core/src/main/java/org/bitcoinj/uri/BitcoinURI.java
index 7ce6ba80b..6f28df501 100644
--- a/core/src/main/java/org/bitcoinj/uri/BitcoinURI.java
+++ b/core/src/main/java/org/bitcoinj/uri/BitcoinURI.java
@@ -21,6 +21,8 @@ import org.bitcoinj.base.Network;
import org.bitcoinj.core.Address;
import org.bitcoinj.base.exceptions.AddressFormatException;
import org.bitcoinj.base.Coin;
+import org.bitcoinj.core.AddressParser;
+import org.bitcoinj.core.DefaultAddressParser;
import org.bitcoinj.core.NetworkParameters;
import javax.annotation.Nullable;
@@ -78,6 +80,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
* @see BIP 0021
*/
public class BitcoinURI {
+ private AddressParser addressParser = new DefaultAddressParser();
// Not worth turning into an enum
public static final String FIELD_MESSAGE = "message";
public static final String FIELD_LABEL = "label";
@@ -177,7 +180,9 @@ public class BitcoinURI {
if (!addressToken.isEmpty()) {
// Attempt to parse the addressToken as a Bitcoin address for this network
try {
- Address address = Address.fromString(params, addressToken);
+ Address address = (params != null)
+ ? addressParser.parseAddress(addressToken, params.network())
+ : addressParser.parseAddressAnyNetwork(addressToken);
putWithValidation(FIELD_ADDRESS, address);
} catch (final AddressFormatException e) {
throw new BitcoinURIParseException("Bad address", e);
diff --git a/core/src/main/java/org/bitcoinj/wallet/Wallet.java b/core/src/main/java/org/bitcoinj/wallet/Wallet.java
index 86003d17b..994c58426 100644
--- a/core/src/main/java/org/bitcoinj/wallet/Wallet.java
+++ b/core/src/main/java/org/bitcoinj/wallet/Wallet.java
@@ -26,14 +26,17 @@ import com.google.protobuf.ByteString;
import net.jcip.annotations.GuardedBy;
import org.bitcoinj.base.BitcoinNetwork;
import org.bitcoinj.base.Network;
+import org.bitcoinj.base.exceptions.AddressFormatException;
import org.bitcoinj.base.utils.StreamUtils;
import org.bitcoinj.core.AbstractBlockChain;
import org.bitcoinj.core.Address;
import org.bitcoinj.base.Base58;
+import org.bitcoinj.core.AddressParser;
import org.bitcoinj.core.BlockChain;
import org.bitcoinj.core.BloomFilter;
import org.bitcoinj.base.Coin;
import org.bitcoinj.core.Context;
+import org.bitcoinj.core.DefaultAddressParser;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.FilteredBlock;
import org.bitcoinj.core.InsufficientMoneyException;
@@ -174,8 +177,11 @@ import static com.google.common.base.Preconditions.checkState;
* for more information about this.
*/
public class Wallet extends BaseTaggableObject
- implements NewBestBlockListener, TransactionReceivedInBlockListener, PeerFilterProvider, KeyBag, TransactionBag, ReorganizeListener {
+ implements NewBestBlockListener, TransactionReceivedInBlockListener, PeerFilterProvider,
+ KeyBag, TransactionBag, ReorganizeListener, AddressParser.Strict {
private static final Logger log = LoggerFactory.getLogger(Wallet.class);
+ private static final AddressParser addressParser = new DefaultAddressParser();
+
// Ordering: lock > keyChainGroupLock. KeyChainGroup is protected separately to allow fast querying of current receive address
// even if the wallet itself is busy e.g. saving or processing a big reorg. Useful for reducing UI latency.
protected final ReentrantLock lock = Threading.lock(Wallet.class);
@@ -494,6 +500,17 @@ public class Wallet extends BaseTaggableObject
return params;
}
+ /**
+ * Parse an address string using all formats this wallet knows about for the wallet's network type
+ * @param addressString Address string to parse
+ * @return A validated address
+ * @throws AddressFormatException if invalid string
+ */
+ @Override
+ public Address parseAddress(String addressString) throws AddressFormatException {
+ return addressParser.parseAddress(addressString, params.network());
+ }
+
/**
* Gets the active keychains via {@link KeyChainGroup#getActiveKeyChains(long)}.
*/
diff --git a/core/src/test/java/org/bitcoinj/core/AddressComparatorSortTest.java b/core/src/test/java/org/bitcoinj/core/AddressComparatorSortTest.java
index bc9295576..df93ae8cd 100644
--- a/core/src/test/java/org/bitcoinj/core/AddressComparatorSortTest.java
+++ b/core/src/test/java/org/bitcoinj/core/AddressComparatorSortTest.java
@@ -31,6 +31,8 @@ import static org.junit.Assert.assertEquals;
* the default comparators.
*/
public class AddressComparatorSortTest {
+ private static final AddressParser addressParser = new DefaultAddressParser();
+
/**
* A manually sorted list of address for verifying sorting with our default comparator.
* See {@link Address#compareTo}.
@@ -48,7 +50,7 @@ public class AddressComparatorSortTest {
// Test net, Segwit
"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
"tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx"
- ).map(s -> Address.fromString(null, s))
+ ).map(addressParser::parseAddressAnyNetwork)
.collect(StreamUtils.toUnmodifiableList());
@Test
diff --git a/examples/src/main/java/org/bitcoinj/examples/DoubleSpend.java b/examples/src/main/java/org/bitcoinj/examples/DoubleSpend.java
index 5dcb64b64..a6ced5c61 100644
--- a/examples/src/main/java/org/bitcoinj/examples/DoubleSpend.java
+++ b/examples/src/main/java/org/bitcoinj/examples/DoubleSpend.java
@@ -46,8 +46,9 @@ public class DoubleSpend {
System.out.println(kit.wallet());
kit.wallet().getBalanceFuture(COIN, Wallet.BalanceType.AVAILABLE).get();
- Transaction tx1 = kit.wallet().createSend(Address.fromString(kit.params(), "bcrt1qsmf9envp5dphlu6my2tpwfmce0793jvpvlg5ez"), CENT);
- Transaction tx2 = kit.wallet().createSend(Address.fromString(kit.params(), "bcrt1qsmf9envp5dphlu6my2tpwfmce0793jvpvlg5ez"), CENT.add(SATOSHI.multiply(10)));
+ Address destinationAddress = kit.wallet().parseAddress("bcrt1qsmf9envp5dphlu6my2tpwfmce0793jvpvlg5ez");
+ Transaction tx1 = kit.wallet().createSend(destinationAddress, CENT);
+ Transaction tx2 = kit.wallet().createSend(destinationAddress, CENT.add(SATOSHI.multiply(10)));
final Peer peer = kit.peerGroup().getConnectedPeers().get(0);
peer.addPreMessageReceivedEventListener(Threading.SAME_THREAD,
(peer1, m) -> {
diff --git a/examples/src/main/java/org/bitcoinj/examples/ForwardingService.java b/examples/src/main/java/org/bitcoinj/examples/ForwardingService.java
index 7c36255bb..8ae214bf6 100644
--- a/examples/src/main/java/org/bitcoinj/examples/ForwardingService.java
+++ b/examples/src/main/java/org/bitcoinj/examples/ForwardingService.java
@@ -21,9 +21,10 @@ import org.bitcoinj.base.ScriptType;
import org.bitcoinj.base.Sha256Hash;
import org.bitcoinj.core.Address;
import org.bitcoinj.base.Coin;
+import org.bitcoinj.core.AddressParser;
import org.bitcoinj.core.Context;
+import org.bitcoinj.core.DefaultAddressParser;
import org.bitcoinj.core.InsufficientMoneyException;
-import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionBroadcast;
import org.bitcoinj.core.TransactionConfidence;
@@ -69,13 +70,14 @@ public class ForwardingService implements AutoCloseable {
// Figure out which network we should connect to. Each network gets its own set of files.
Address address;
BitcoinNetwork network;
+ AddressParser addressParser = new DefaultAddressParser();
if (args.length >= 2) {
// Verify address belongs to network
network = BitcoinNetwork.fromString(args[1]).orElseThrow();
- address = Address.fromString(NetworkParameters.of(network), args[0]);
+ address = addressParser.parseAddress(args[0], network);
} else {
// Infer network from address
- address = Address.fromString(null, args[0]);
+ address = addressParser.parseAddressAnyNetwork(args[0]);
network = address.network();
}
diff --git a/examples/src/main/java/org/bitcoinj/examples/PrivateKeys.java b/examples/src/main/java/org/bitcoinj/examples/PrivateKeys.java
index 671d788f9..5d0e86af3 100644
--- a/examples/src/main/java/org/bitcoinj/examples/PrivateKeys.java
+++ b/examples/src/main/java/org/bitcoinj/examples/PrivateKeys.java
@@ -60,13 +60,14 @@ public class PrivateKeys {
key = ECKey.fromPrivate(privKey);
}
System.out.println("Address from private key is: " + SegwitAddress.fromKey(params, key).toString());
- // And the address ...
- Address destination = Address.fromString(params, args[1]);
// Import the private key to a fresh wallet.
Wallet wallet = Wallet.createDeterministic(params, ScriptType.P2PKH);
wallet.importKey(key);
+ // And the address ...
+ Address destination = wallet.parseAddress(args[1]);
+
// Find the transactions that involve those coins.
final MemoryBlockStore blockStore = new MemoryBlockStore(params);
BlockChain chain = new BlockChain(params, wallet, blockStore);
diff --git a/examples/src/main/java/org/bitcoinj/examples/SendRequest.java b/examples/src/main/java/org/bitcoinj/examples/SendRequest.java
index 29acaa85d..6bdaeb518 100644
--- a/examples/src/main/java/org/bitcoinj/examples/SendRequest.java
+++ b/examples/src/main/java/org/bitcoinj/examples/SendRequest.java
@@ -48,7 +48,7 @@ public class SendRequest {
// To which address you want to send the coins?
// The Address class represents a Bitcoin address.
- Address to = Address.fromString(kit.params(), "bcrt1qspfueag7fvty7m8htuzare3xs898zvh30fttu2");
+ Address to = kit.wallet().parseAddress("bcrt1qspfueag7fvty7m8htuzare3xs898zvh30fttu2");
System.out.println("Send money to: " + to.toString());
// There are different ways to create and publish a SendRequest. This is probably the easiest one.
diff --git a/wallettemplate/src/main/java/org/bitcoinj/walletfx/controls/BitcoinAddressValidator.java b/wallettemplate/src/main/java/org/bitcoinj/walletfx/controls/BitcoinAddressValidator.java
index 619de55b3..3536b52cb 100644
--- a/wallettemplate/src/main/java/org/bitcoinj/walletfx/controls/BitcoinAddressValidator.java
+++ b/wallettemplate/src/main/java/org/bitcoinj/walletfx/controls/BitcoinAddressValidator.java
@@ -16,13 +16,12 @@
package org.bitcoinj.walletfx.controls;
-import org.bitcoinj.base.BitcoinNetwork;
import org.bitcoinj.core.Address;
import org.bitcoinj.base.exceptions.AddressFormatException;
-import org.bitcoinj.core.NetworkParameters;
import javafx.scene.Node;
import javafx.scene.control.TextField;
+import org.bitcoinj.core.AddressParser;
import org.bitcoinj.walletfx.utils.TextFieldValidator;
/**
@@ -30,11 +29,12 @@ import org.bitcoinj.walletfx.utils.TextFieldValidator;
* if the address is invalid for those params, and enable/disable the nodes.
*/
public class BitcoinAddressValidator {
- private BitcoinNetwork network;
+ private final AddressParser.Strict parser;
private Node[] nodes;
- public BitcoinAddressValidator(BitcoinNetwork network, TextField field, Node... nodes) {
- this.network = network;
+
+ public BitcoinAddressValidator(AddressParser.Strict parser, TextField field, Node... nodes) {
+ this.parser = parser;
this.nodes = nodes;
// Handle the red highlighting, but don't highlight in red just when the field is empty because that makes
@@ -52,7 +52,7 @@ public class BitcoinAddressValidator {
private boolean testAddr(String text) {
try {
- Address.fromString(NetworkParameters.of(network), text);
+ parser.parseAddress(text);
return true;
} catch (AddressFormatException e) {
return false;
diff --git a/wallettemplate/src/main/java/wallettemplate/SendMoneyController.java b/wallettemplate/src/main/java/wallettemplate/SendMoneyController.java
index f7c152483..e79dc15b2 100644
--- a/wallettemplate/src/main/java/wallettemplate/SendMoneyController.java
+++ b/wallettemplate/src/main/java/wallettemplate/SendMoneyController.java
@@ -69,7 +69,7 @@ public class SendMoneyController implements OverlayController
!WTUtils.didThrow(() -> checkState(Coin.parseCoin(text).compareTo(balance) <= 0)));
amountEdit.setText(balance.toPlainString());
@@ -84,7 +84,7 @@ public class SendMoneyController implements OverlayController {
if (selectAddrStr != null) {
Address selectAddr;
try {
- selectAddr = Address.fromString(params, selectAddrStr);
+ selectAddr = wallet.parseAddress(selectAddrStr);
} catch (AddressFormatException x) {
System.err.println("Could not parse given address, or wrong network: " + selectAddrStr);
return 1;
@@ -761,7 +761,7 @@ public class WalletTool implements Callable {
addr = null;
} else {
// Treat as an address.
- addr = Address.fromString(params, destination);
+ addr = wallet.parseAddress(destination);
key = null;
}
}
@@ -1228,7 +1228,7 @@ public class WalletTool implements Callable {
key = wallet.findKeyFromPubKey(HEX.decode(pubKeyStr));
} else {
try {
- Address address = Address.fromString(wallet.getParams(), addrStr);
+ Address address = wallet.parseAddress(addrStr);
key = wallet.findKeyFromAddress(address);
} catch (AddressFormatException e) {
System.err.println(addrStr + " does not parse as a Bitcoin address of the right network parameters.");