From 7a3aa74c6ee23af5be461beab6f4ab4564f973d9 Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Sat, 11 Apr 2015 12:38:17 +0100 Subject: [PATCH] Added extension points for altcoin support via subclassing. --- .../org/bitcoinj/core/AbstractBlockChain.java | 6 +- .../main/java/org/bitcoinj/core/Block.java | 2 +- .../bitcoinj/core/FullPrunedBlockChain.java | 12 ++-- .../org/bitcoinj/core/NetworkParameters.java | 34 +++++++++ .../main/java/org/bitcoinj/core/Wallet.java | 4 +- .../params/AbstractBitcoinNetParams.java | 69 +++++++++++++++++++ .../org/bitcoinj/params/MainNetParams.java | 2 +- .../java/org/bitcoinj/params/Networks.java | 4 +- .../org/bitcoinj/params/TestNet2Params.java | 3 +- .../org/bitcoinj/params/TestNet3Params.java | 3 +- .../org/bitcoinj/params/UnitTestParams.java | 2 +- .../StoredPaymentChannelServerStates.java | 1 + .../protocols/payments/PaymentSession.java | 2 +- .../java/org/bitcoinj/uri/BitcoinURI.java | 21 ++++-- .../bitcoinj/wallet/DefaultCoinSelector.java | 1 + 15 files changed, 142 insertions(+), 24 deletions(-) create mode 100644 core/src/main/java/org/bitcoinj/params/AbstractBitcoinNetParams.java diff --git a/core/src/main/java/org/bitcoinj/core/AbstractBlockChain.java b/core/src/main/java/org/bitcoinj/core/AbstractBlockChain.java index 2cd928b5..33258b0f 100644 --- a/core/src/main/java/org/bitcoinj/core/AbstractBlockChain.java +++ b/core/src/main/java/org/bitcoinj/core/AbstractBlockChain.java @@ -829,9 +829,11 @@ public abstract class AbstractBlockChain { private static final Date testnetDiffDate = new Date(1329264000000L); /** - * Throws an exception if the blocks difficulty is not correct. + * Throws an exception if the block's difficulty is not correct. + * + * @throws VerificationException if the block's difficulty is not correct. */ - private void checkDifficultyTransitions(StoredBlock storedPrev, Block nextBlock) throws BlockStoreException, VerificationException { + protected void checkDifficultyTransitions(StoredBlock storedPrev, Block nextBlock) throws BlockStoreException, VerificationException { checkState(lock.isHeldByCurrentThread()); Block prev = storedPrev.getHeader(); diff --git a/core/src/main/java/org/bitcoinj/core/Block.java b/core/src/main/java/org/bitcoinj/core/Block.java index ea1288ad..3e17e397 100644 --- a/core/src/main/java/org/bitcoinj/core/Block.java +++ b/core/src/main/java/org/bitcoinj/core/Block.java @@ -643,7 +643,7 @@ public class Block extends Message { } /** Returns true if the hash of the block is OK (lower than difficulty target). */ - private boolean checkProofOfWork(boolean throwException) throws VerificationException { + protected boolean checkProofOfWork(boolean throwException) throws VerificationException { // This part is key - it is what proves the block was as difficult to make as it claims // to be. Note however that in the context of this function, the block can claim to be // as difficult as it wants to be .... if somebody was able to take control of our network diff --git a/core/src/main/java/org/bitcoinj/core/FullPrunedBlockChain.java b/core/src/main/java/org/bitcoinj/core/FullPrunedBlockChain.java index fa2dffff..4419ea41 100644 --- a/core/src/main/java/org/bitcoinj/core/FullPrunedBlockChain.java +++ b/core/src/main/java/org/bitcoinj/core/FullPrunedBlockChain.java @@ -297,12 +297,12 @@ public class FullPrunedBlockChain extends AbstractBlockChain { } // All values were already checked for being non-negative (as it is verified in Transaction.verify()) // but we check again here just for defence in depth. Transactions with zero output value are OK. - if (valueOut.signum() < 0 || valueOut.compareTo(NetworkParameters.MAX_MONEY) > 0) + if (valueOut.signum() < 0 || valueOut.compareTo(this.params.getMaxMoney()) > 0) throw new VerificationException("Transaction output value out of range"); if (isCoinBase) { coinbaseValue = valueOut; } else { - if (valueIn.compareTo(valueOut) < 0 || valueIn.compareTo(NetworkParameters.MAX_MONEY) > 0) + if (valueIn.compareTo(valueOut) < 0 || valueIn.compareTo(this.params.getMaxMoney()) > 0) throw new VerificationException("Transaction input value out of range"); totalFees = totalFees.add(valueIn.subtract(valueOut)); } @@ -314,7 +314,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain { listScriptVerificationResults.add(future); } } - if (totalFees.compareTo(NetworkParameters.MAX_MONEY) > 0 || block.getBlockInflation(height).add(totalFees).compareTo(coinbaseValue) < 0) + if (totalFees.compareTo(this.params.getMaxMoney()) > 0 || block.getBlockInflation(height).add(totalFees).compareTo(coinbaseValue) < 0) throw new VerificationException("Transaction fees out of range"); for (Future future : listScriptVerificationResults) { VerificationException e; @@ -427,12 +427,12 @@ public class FullPrunedBlockChain extends AbstractBlockChain { } // All values were already checked for being non-negative (as it is verified in Transaction.verify()) // but we check again here just for defence in depth. Transactions with zero output value are OK. - if (valueOut.signum() < 0 || valueOut.compareTo(NetworkParameters.MAX_MONEY) > 0) + if (valueOut.signum() < 0 || valueOut.compareTo(this.params.getMaxMoney()) > 0) throw new VerificationException("Transaction output value out of range"); if (isCoinBase) { coinbaseValue = valueOut; } else { - if (valueIn.compareTo(valueOut) < 0 || valueIn.compareTo(NetworkParameters.MAX_MONEY) > 0) + if (valueIn.compareTo(valueOut) < 0 || valueIn.compareTo(this.params.getMaxMoney()) > 0) throw new VerificationException("Transaction input value out of range"); totalFees = totalFees.add(valueIn.subtract(valueOut)); } @@ -444,7 +444,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain { listScriptVerificationResults.add(future); } } - if (totalFees.compareTo(NetworkParameters.MAX_MONEY) > 0 || + if (totalFees.compareTo(this.params.getMaxMoney()) > 0 || newBlock.getHeader().getBlockInflation(newBlock.getHeight()).add(totalFees).compareTo(coinbaseValue) < 0) throw new VerificationException("Transaction fees out of range"); txOutChanges = new TransactionOutputChanges(txOutsCreated, txOutsSpent); diff --git a/core/src/main/java/org/bitcoinj/core/NetworkParameters.java b/core/src/main/java/org/bitcoinj/core/NetworkParameters.java index dddc47aa..2f3bc1ed 100644 --- a/core/src/main/java/org/bitcoinj/core/NetworkParameters.java +++ b/core/src/main/java/org/bitcoinj/core/NetworkParameters.java @@ -22,6 +22,10 @@ import org.bitcoinj.net.discovery.*; import org.bitcoinj.params.*; import org.bitcoinj.script.*; +import org.bitcoinj.store.BlockStore; +import org.bitcoinj.store.BlockStoreException; +import org.bitcoinj.utils.MonetaryFormat; + import javax.annotation.*; import java.io.*; import java.math.*; @@ -374,4 +378,34 @@ public abstract class NetworkParameters implements Serializable { public int getBip32HeaderPriv() { return bip32HeaderPriv; } + + /** + * Returns the number of coins that will be produced in total, on this + * network. Where not applicable, a very large number of coins is returned + * instead (i.e. the main coin issue for Dogecoin). + */ + public abstract Coin getMaxMoney(); + + /** + * Any standard (ie pay-to-address) output smaller than this value will + * most likely be rejected by the network. + */ + public abstract Coin getMinNonDustOutput(); + + /** + * The monetary object for this currency. + */ + public abstract MonetaryFormat getMonetaryFormat(); + + /** + * Scheme part for URIs, for example "bitcoin". + */ + public abstract String getUriScheme(); + + /** + * Returns whether this network has a maximum number of coins (finite supply) or + * not. Always returns true for Bitcoin, but exists to be overriden for other + * networks. + */ + public abstract boolean hasMaxMoney(); } diff --git a/core/src/main/java/org/bitcoinj/core/Wallet.java b/core/src/main/java/org/bitcoinj/core/Wallet.java index 667f4df6..ed0a95f4 100644 --- a/core/src/main/java/org/bitcoinj/core/Wallet.java +++ b/core/src/main/java/org/bitcoinj/core/Wallet.java @@ -3080,7 +3080,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha try { checkNotNull(selector); List candidates = calculateAllSpendCandidates(true, false); - CoinSelection selection = selector.select(NetworkParameters.MAX_MONEY, candidates); + CoinSelection selection = selector.select(params.getMaxMoney(), candidates); return selection.valueGathered; } finally { lock.unlock(); @@ -3663,7 +3663,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha // of the total value we can currently spend as determined by the selector, and then subtracting the fee. checkState(req.tx.getOutputs().size() == 1, "Empty wallet TX must have a single output only."); CoinSelector selector = req.coinSelector == null ? coinSelector : req.coinSelector; - bestCoinSelection = selector.select(NetworkParameters.MAX_MONEY, candidates); + bestCoinSelection = selector.select(params.getMaxMoney(), candidates); candidates = null; // Selector took ownership and might have changed candidates. Don't access again. req.tx.getOutput(0).setValue(bestCoinSelection.valueGathered); log.info(" emptying {}", bestCoinSelection.valueGathered.toFriendlyString()); diff --git a/core/src/main/java/org/bitcoinj/params/AbstractBitcoinNetParams.java b/core/src/main/java/org/bitcoinj/params/AbstractBitcoinNetParams.java new file mode 100644 index 00000000..45d19f5b --- /dev/null +++ b/core/src/main/java/org/bitcoinj/params/AbstractBitcoinNetParams.java @@ -0,0 +1,69 @@ +/* + * Copyright 2013 Google Inc. + * + * 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.params; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.Sha256Hash; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.Utils; +import org.bitcoinj.utils.MonetaryFormat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.google.common.base.Preconditions.checkState; + +/** + * Parameters for Bitcoin-like networks. + */ +public abstract class AbstractBitcoinNetParams extends NetworkParameters { + /** + * Scheme part for Bitcoin URIs. + */ + public static final String BITCOIN_SCHEME = "bitcoin"; + + private static final Logger log = LoggerFactory.getLogger(AbstractBitcoinNetParams.class); + + public AbstractBitcoinNetParams() { + super(); + } + + @Override + public Coin getMaxMoney() { + return MAX_MONEY; + } + + @Override + public Coin getMinNonDustOutput() { + return Transaction.MIN_NONDUST_OUTPUT; + } + + @Override + public MonetaryFormat getMonetaryFormat() { + return new MonetaryFormat(); + } + + @Override + public String getUriScheme() { + return BITCOIN_SCHEME; + } + + @Override + public boolean hasMaxMoney() { + return true; + } +} diff --git a/core/src/main/java/org/bitcoinj/params/MainNetParams.java b/core/src/main/java/org/bitcoinj/params/MainNetParams.java index ca87170d..9fc5403c 100644 --- a/core/src/main/java/org/bitcoinj/params/MainNetParams.java +++ b/core/src/main/java/org/bitcoinj/params/MainNetParams.java @@ -27,7 +27,7 @@ import static com.google.common.base.Preconditions.*; /** * Parameters for the main production network on which people trade goods and services. */ -public class MainNetParams extends NetworkParameters { +public class MainNetParams extends AbstractBitcoinNetParams { public MainNetParams() { super(); interval = INTERVAL; diff --git a/core/src/main/java/org/bitcoinj/params/Networks.java b/core/src/main/java/org/bitcoinj/params/Networks.java index dc965da8..03082ad0 100644 --- a/core/src/main/java/org/bitcoinj/params/Networks.java +++ b/core/src/main/java/org/bitcoinj/params/Networks.java @@ -31,9 +31,9 @@ import java.util.Set; */ public class Networks { /** Registered networks */ - private static Set networks = ImmutableSet.of(TestNet3Params.get(), MainNetParams.get()); + private static Set networks = ImmutableSet.of(TestNet3Params.get(), MainNetParams.get()); - public static Set get() { + public static Set get() { return networks; } diff --git a/core/src/main/java/org/bitcoinj/params/TestNet2Params.java b/core/src/main/java/org/bitcoinj/params/TestNet2Params.java index 932d23a1..7b4b0855 100644 --- a/core/src/main/java/org/bitcoinj/params/TestNet2Params.java +++ b/core/src/main/java/org/bitcoinj/params/TestNet2Params.java @@ -16,7 +16,6 @@ package org.bitcoinj.params; -import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.Utils; import static com.google.common.base.Preconditions.checkState; @@ -25,7 +24,7 @@ import static com.google.common.base.Preconditions.checkState; * Parameters for the old version 2 testnet. This is not useful to you - it exists only because some unit tests are * based on it. */ -public class TestNet2Params extends NetworkParameters { +public class TestNet2Params extends AbstractBitcoinNetParams { public TestNet2Params() { super(); id = ID_TESTNET; diff --git a/core/src/main/java/org/bitcoinj/params/TestNet3Params.java b/core/src/main/java/org/bitcoinj/params/TestNet3Params.java index a295449e..498c9dfa 100644 --- a/core/src/main/java/org/bitcoinj/params/TestNet3Params.java +++ b/core/src/main/java/org/bitcoinj/params/TestNet3Params.java @@ -17,7 +17,6 @@ package org.bitcoinj.params; -import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.Utils; import static com.google.common.base.Preconditions.checkState; @@ -26,7 +25,7 @@ import static com.google.common.base.Preconditions.checkState; * Parameters for the testnet, a separate public instance of Bitcoin that has relaxed rules suitable for development * and testing of applications and new Bitcoin versions. */ -public class TestNet3Params extends NetworkParameters { +public class TestNet3Params extends AbstractBitcoinNetParams { public TestNet3Params() { super(); id = ID_TESTNET; diff --git a/core/src/main/java/org/bitcoinj/params/UnitTestParams.java b/core/src/main/java/org/bitcoinj/params/UnitTestParams.java index 0cc2e169..9ff393e9 100644 --- a/core/src/main/java/org/bitcoinj/params/UnitTestParams.java +++ b/core/src/main/java/org/bitcoinj/params/UnitTestParams.java @@ -24,7 +24,7 @@ import java.math.BigInteger; * Network parameters used by the bitcoinj unit tests (and potentially your own). This lets you solve a block using * {@link org.bitcoinj.core.Block#solve()} by setting difficulty to the easiest possible. */ -public class UnitTestParams extends NetworkParameters { +public class UnitTestParams extends AbstractBitcoinNetParams { // A simple static key/address for re-use in unit tests, to speed things up. public static ECKey TEST_KEY = new ECKey(); public static Address TEST_ADDRESS; diff --git a/core/src/main/java/org/bitcoinj/protocols/channels/StoredPaymentChannelServerStates.java b/core/src/main/java/org/bitcoinj/protocols/channels/StoredPaymentChannelServerStates.java index d83727e0..1005d0b2 100644 --- a/core/src/main/java/org/bitcoinj/protocols/channels/StoredPaymentChannelServerStates.java +++ b/core/src/main/java/org/bitcoinj/protocols/channels/StoredPaymentChannelServerStates.java @@ -193,6 +193,7 @@ public class StoredPaymentChannelServerStates implements WalletExtension { ServerState.StoredServerPaymentChannels.Builder builder = ServerState.StoredServerPaymentChannels.newBuilder(); for (StoredServerChannel channel : mapChannels.values()) { // First a few asserts to make sure things won't break + // TODO: Pull MAX_MONEY from network parameters checkState(channel.bestValueToMe.signum() >= 0 && channel.bestValueToMe.compareTo(NetworkParameters.MAX_MONEY) < 0); checkState(channel.refundTransactionUnlockTimeSecs > 0); checkNotNull(channel.myKey.getPrivKeyBytes()); diff --git a/core/src/main/java/org/bitcoinj/protocols/payments/PaymentSession.java b/core/src/main/java/org/bitcoinj/protocols/payments/PaymentSession.java index bb84d15d..cbc12108 100644 --- a/core/src/main/java/org/bitcoinj/protocols/payments/PaymentSession.java +++ b/core/src/main/java/org/bitcoinj/protocols/payments/PaymentSession.java @@ -400,7 +400,7 @@ public class PaymentSession { } // This won't ever happen in practice. It would only happen if the user provided outputs // that are obviously invalid. Still, we don't want to silently overflow. - if (totalValue.compareTo(NetworkParameters.MAX_MONEY) > 0) + if (params.hasMaxMoney() && totalValue.compareTo(params.getMaxMoney()) > 0) throw new PaymentProtocolException.InvalidOutputs("The outputs are way too big."); } catch (InvalidProtocolBufferException e) { throw new PaymentProtocolException(e); diff --git a/core/src/main/java/org/bitcoinj/uri/BitcoinURI.java b/core/src/main/java/org/bitcoinj/uri/BitcoinURI.java index 38c9175b..2e3aba05 100644 --- a/core/src/main/java/org/bitcoinj/uri/BitcoinURI.java +++ b/core/src/main/java/org/bitcoinj/uri/BitcoinURI.java @@ -20,6 +20,7 @@ import org.bitcoinj.core.Address; import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Coin; import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.params.AbstractBitcoinNetParams; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -91,6 +92,12 @@ public class BitcoinURI { public static final String FIELD_ADDRESS = "address"; public static final String FIELD_PAYMENT_REQUEST_URL = "r"; + /** + * URI for Bitcoin network. Use {@link org.bitcoinj.params.AbstractBitcoinNetParams.BITCOIN_SCHEME} if you specifically + * need Bitcoin, or use {@link org.bitcoinj.core.NetworkParams.getUriScheme()} to get the scheme + * from network parameters. + */ + @Deprecated public static final String BITCOIN_SCHEME = "bitcoin"; private static final String ENCODED_SPACE_CHARACTER = "%20"; private static final String AMPERSAND_SEPARATOR = "&"; @@ -124,6 +131,10 @@ public class BitcoinURI { checkNotNull(input); log.debug("Attempting to parse '{}' for {}", input, params == null ? "any" : params.getId()); + String scheme = null == params + ? AbstractBitcoinNetParams.BITCOIN_SCHEME + : params.getUriScheme(); + // Attempt to form the URI (fail fast syntax checking to official standards). URI uri; try { @@ -141,11 +152,13 @@ public class BitcoinURI { // For instance with : bitcoin:129mVqKUmJ9uwPxKJBnNdABbuaaNfho4Ha?amount=0.06&label=Tom%20%26%20Jerry // the & (%26) in Tom and Jerry gets interpreted as a separator and the label then gets parsed // as 'Tom ' instead of 'Tom & Jerry') + String blockchainInfoScheme = scheme + "://"; + String correctScheme = scheme + ":"; String schemeSpecificPart; - if (input.startsWith("bitcoin://")) { - schemeSpecificPart = input.substring("bitcoin://".length()); - } else if (input.startsWith("bitcoin:")) { - schemeSpecificPart = input.substring("bitcoin:".length()); + if (input.startsWith(blockchainInfoScheme)) { + schemeSpecificPart = input.substring(blockchainInfoScheme.length()); + } else if (input.startsWith(correctScheme)) { + schemeSpecificPart = input.substring(correctScheme.length()); } else { throw new BitcoinURIParseException("Unsupported URI scheme: " + uri.getScheme()); } diff --git a/core/src/main/java/org/bitcoinj/wallet/DefaultCoinSelector.java b/core/src/main/java/org/bitcoinj/wallet/DefaultCoinSelector.java index 888fa8c1..4a410d63 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DefaultCoinSelector.java +++ b/core/src/main/java/org/bitcoinj/wallet/DefaultCoinSelector.java @@ -25,6 +25,7 @@ public class DefaultCoinSelector implements CoinSelector { ArrayList sortedOutputs = new ArrayList(candidates); // When calculating the wallet balance, we may be asked to select all possible coins, if so, avoid sorting // them in order to improve performance. + // TODO: Take in network parameters when instanatiated, and then test against the current network. Or just have a boolean parameter for "give me everything" if (!target.equals(NetworkParameters.MAX_MONEY)) { sortOutputs(sortedOutputs); }