diff --git a/build.gradle b/build.gradle index 74a59e7b0..c94f76f0b 100644 --- a/build.gradle +++ b/build.gradle @@ -15,4 +15,5 @@ allprojects { } group = 'com.dogecoin' + } diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 000000000..13af88293 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/build.gradle b/core/build.gradle index f9de088d0..be1e22574 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'java-library' +apply plugin: 'checkstyle' apply plugin: 'maven' version = '0.15-SNAPSHOT' @@ -40,6 +41,11 @@ task sourcesJar(type: Jar, dependsOn: classes) { from sourceSets.main.allSource } +tasks.withType(Checkstyle) { + // Generated, can ignore. + exclude 'com/dogecoin/protocols/payments/**' +} + artifacts { archives sourcesJar archives javadocJar diff --git a/core/src/main/java/com/dogecoin/dogecoinj/protocols/payments/PaymentProtocol.java b/core/src/main/java/com/dogecoin/dogecoinj/protocols/payments/PaymentProtocol.java index c13043b13..844ed7465 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/protocols/payments/PaymentProtocol.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/protocols/payments/PaymentProtocol.java @@ -1,13 +1,13 @@ /** * Copyright 2013 Google Inc. * Copyright 2014 Andreas Schildbach - * + *

* 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 - * + *

+ * 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. @@ -23,15 +23,37 @@ import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; import org.bitcoin.protocols.payments.Protos; -import org.bitcoinj.core.*; +import org.bitcoinj.core.Address; +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.Utils; import org.bitcoinj.crypto.X509Utils; import org.bitcoinj.script.ScriptBuilder; import javax.annotation.Nullable; import java.io.Serializable; -import java.security.*; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.cert.CertPath; +import java.security.cert.CertPathValidator; +import java.security.cert.CertPathValidatorException; import java.security.cert.Certificate; -import java.security.cert.*; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.CertificateParsingException; +import java.security.cert.PKIXCertPathValidatorResult; +import java.security.cert.PKIXParameters; +import java.security.cert.TrustAnchor; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; @@ -44,13 +66,31 @@ * protocol data does not flow over the Bitcoin P2P network or enter the block chain. It's instead for data that is only * of interest to the parties involved but isn't otherwise needed for consensus.

*/ -public class PaymentProtocol { +public final class PaymentProtocol { // MIME types as defined in DIP71. + + /** + * Payment request MIME (media) type, utilised in headers. + */ public static final String MIMETYPE_PAYMENTREQUEST = "application/vnd.doge.payment.request"; + + /** + * Payment MIME (media) type, utilised in headers. + */ public static final String MIMETYPE_PAYMENT = "application/vnd.doge.payment.payment"; + + /** + * Payment ACK MIME (media) type, utilised in headers. + */ public static final String MIMETYPE_PAYMENTACK = "application/vnd.doge.payment.ack"; + /** + * PaymentProtocol is a utility class and does not require a public constructor. + */ + private PaymentProtocol() { + } + /** * Create a payment request with one standard pay to address output. You may want to sign the request using * {@link #signPaymentRequest}. Use {@link Protos.PaymentRequest.Builder#build} to get the actual payment @@ -65,8 +105,11 @@ public class PaymentProtocol { * @return created payment request, in its builder form */ public static Protos.PaymentRequest.Builder createPaymentRequest(NetworkParameters params, - @Nullable Coin amount, Address toAddress, @Nullable String memo, @Nullable String paymentUrl, - @Nullable byte[] merchantData) { + @Nullable Coin amount, + Address toAddress, + @Nullable String memo, + @Nullable String paymentUrl, + @Nullable byte[] merchantData) { return createPaymentRequest(params, ImmutableList.of(createPayToAddressOutput(amount, toAddress)), memo, paymentUrl, merchantData); } @@ -74,7 +117,7 @@ public static Protos.PaymentRequest.Builder createPaymentRequest(NetworkParamete /** * Create a payment request. You may want to sign the request using {@link #signPaymentRequest}. Use * {@link Protos.PaymentRequest.Builder#build} to get the actual payment request. - * + * * @param params network parameters * @param outputs list of outputs to request coins to * @param memo arbitrary, user readable memo, or null if none @@ -83,18 +126,22 @@ public static Protos.PaymentRequest.Builder createPaymentRequest(NetworkParamete * @return created payment request, in its builder form */ public static Protos.PaymentRequest.Builder createPaymentRequest(NetworkParameters params, - List outputs, @Nullable String memo, @Nullable String paymentUrl, - @Nullable byte[] merchantData) { + List outputs, @Nullable String memo, @Nullable String paymentUrl, + @Nullable byte[] merchantData) { final Protos.PaymentDetails.Builder paymentDetails = Protos.PaymentDetails.newBuilder(); paymentDetails.setNetwork(params.getPaymentProtocolId()); - for (Protos.Output output : outputs) + for (Protos.Output output : outputs) { paymentDetails.addOutputs(output); - if (memo != null) + } + if (memo != null) { paymentDetails.setMemo(memo); - if (paymentUrl != null) + } + if (paymentUrl != null) { paymentDetails.setPaymentUrl(paymentUrl); - if (merchantData != null) + } + if (merchantData != null) { paymentDetails.setMerchantData(ByteString.copyFrom(merchantData)); + } paymentDetails.setTime(Utils.currentTimeSeconds()); final Protos.PaymentRequest.Builder paymentRequest = Protos.PaymentRequest.newBuilder(); @@ -104,10 +151,10 @@ public static Protos.PaymentRequest.Builder createPaymentRequest(NetworkParamete /** * Parse a payment request. - * + * * @param paymentRequest payment request to parse * @return instance of {@link PaymentSession}, used as a value object - * @throws PaymentProtocolException + * @throws PaymentProtocolException if payment request could not be verified */ public static PaymentSession parsePaymentRequest(Protos.PaymentRequest paymentRequest) throws PaymentProtocolException { @@ -116,7 +163,7 @@ public static PaymentSession parsePaymentRequest(Protos.PaymentRequest paymentRe /** * Sign the provided payment request. - * + * * @param paymentRequest Payment request to sign, in its builder form. * @param certificateChain Certificate chain to send with the payment request, ordered from client certificate to root * certificate. The root certificate itself may be omitted. @@ -126,8 +173,9 @@ public static void signPaymentRequest(Protos.PaymentRequest.Builder paymentReque X509Certificate[] certificateChain, PrivateKey privateKey) { try { final Protos.X509Certificates.Builder certificates = Protos.X509Certificates.newBuilder(); - for (final Certificate certificate : certificateChain) + for (final Certificate certificate : certificateChain) { certificates.addCertificate(ByteString.copyFrom(certificate.getEncoded())); + } paymentRequest.setPkiType("x509+sha256"); paymentRequest.setPkiData(certificates.build().toByteString()); @@ -135,10 +183,11 @@ public static void signPaymentRequest(Protos.PaymentRequest.Builder paymentReque final Protos.PaymentRequest paymentRequestToSign = paymentRequest.build(); final String algorithm; - if (privateKey.getAlgorithm().equalsIgnoreCase("RSA")) + if (privateKey.getAlgorithm().equalsIgnoreCase("RSA")) { algorithm = "SHA256withRSA"; - else + } else { throw new IllegalStateException(privateKey.getAlgorithm()); + } final Signature signature = Signature.getInstance(algorithm); signature.initSign(privateKey); @@ -153,40 +202,45 @@ public static void signPaymentRequest(Protos.PaymentRequest.Builder paymentReque /** * Uses the provided PKI method to find the corresponding public key and verify the provided signature. - * + * * @param paymentRequest Payment request to verify. * @param trustStore KeyStore of trusted root certificate authorities. * @return verification data, or null if no PKI method was specified in the {@link Protos.PaymentRequest}. * @throws PaymentProtocolException if payment request could not be verified. */ - public static @Nullable PkiVerificationData verifyPaymentRequestPki(Protos.PaymentRequest paymentRequest, KeyStore trustStore) + public static @Nullable + PkiVerificationData verifyPaymentRequestPki(Protos.PaymentRequest paymentRequest, KeyStore trustStore) throws PaymentProtocolException { List certs = null; try { final String pkiType = paymentRequest.getPkiType(); - if (pkiType.equals("none")) + if (pkiType.equals("none")) { // Nothing to verify. Everything is fine. Move along. return null; + } String algorithm; - if (pkiType.equals("x509+sha256")) + if (pkiType.equals("x509+sha256")) { algorithm = "SHA256withRSA"; - else if (pkiType.equals("x509+sha1")) + } else if (pkiType.equals("x509+sha1")) { algorithm = "SHA1withRSA"; - else + } else { throw new PaymentProtocolException.InvalidPkiType("Unsupported PKI type: " + pkiType); + } Protos.X509Certificates protoCerts = Protos.X509Certificates.parseFrom(paymentRequest.getPkiData()); - if (protoCerts.getCertificateCount() == 0) + if (protoCerts.getCertificateCount() == 0) { throw new PaymentProtocolException.InvalidPkiData("No certificates provided in message: server config error"); + } // Parse the certs and turn into a certificate chain object. Cert factories can parse both DER and base64. // The ordering of certificates is defined by the payment protocol spec to be the same as what the Java // crypto API requires - convenient! CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); certs = Lists.newArrayList(); - for (ByteString bytes : protoCerts.getCertificateList()) + for (ByteString bytes : protoCerts.getCertificateList()) { certs.add((X509Certificate) certificateFactory.generateCertificate(bytes.newInput())); + } CertPath path = certificateFactory.generateCertPath(certs); // Retrieves the most-trusted CAs from keystore. @@ -207,14 +261,16 @@ else if (pkiType.equals("x509+sha1")) Protos.PaymentRequest.Builder reqToCheck = paymentRequest.toBuilder(); reqToCheck.setSignature(ByteString.EMPTY); signature.update(reqToCheck.build().toByteArray()); - if (!signature.verify(paymentRequest.getSignature().toByteArray())) + if (!signature.verify(paymentRequest.getSignature().toByteArray())) { throw new PaymentProtocolException.PkiVerificationException("Invalid signature, this payment request is not valid."); + } // Signature verifies, get the names from the identity we just verified for presentation to the user. final X509Certificate cert = certs.get(0); String displayName = X509Utils.getDisplayNameFromCertificate(cert, true); - if (displayName == null) + if (displayName == null) { throw new PaymentProtocolException.PkiVerificationException("Could not extract name from certificate"); + } // Everything is peachy. Return some useful data to the caller. return new PkiVerificationData(displayName, publicKey, result.getTrustAnchor()); } catch (InvalidProtocolBufferException e) { @@ -246,14 +302,25 @@ else if (pkiType.equals("x509+sha1")) /** * Information about the X.509 signature's issuer and subject. */ - public static class PkiVerificationData { - /** Display name of the payment requestor, could be a domain name, email address, legal name, etc */ + public static final class PkiVerificationData { + /** + * Display name of the payment requestor, could be a domain name, email address, legal name, etc. + */ public final String displayName; - /** SSL public key that was used to sign. */ + + /** + * SSL public key that was used to sign. + */ public final PublicKey merchantSigningKey; - /** Object representing the CA that verified the merchant's ID */ + + /** + * Object representing the CA that verified the merchant's ID. + */ public final TrustAnchor rootAuthority; - /** String representing the display name of the CA that verified the merchant's ID */ + + /** + * String representing the display name of the CA that verified the merchant's ID. + */ public final String rootAuthorityName; private PkiVerificationData(@Nullable String displayName, PublicKey merchantSigningKey, @@ -281,7 +348,7 @@ public String toString() { /** * Create a payment message with one standard pay to address output. - * + * * @param transactions one or more transactions that satisfy the requested outputs. * @param refundAmount amount of coins to request as a refund, or null if no refund. * @param refundAddress address to refund coins to @@ -290,11 +357,12 @@ public String toString() { * @return created payment message */ public static Protos.Payment createPaymentMessage(List transactions, - @Nullable Coin refundAmount, @Nullable Address refundAddress, @Nullable String memo, - @Nullable byte[] merchantData) { + @Nullable Coin refundAmount, @Nullable Address refundAddress, @Nullable String memo, + @Nullable byte[] merchantData) { if (refundAddress != null) { - if (refundAmount == null) + if (refundAmount == null) { throw new IllegalArgumentException("Specify refund amount if refund address is specified."); + } return createPaymentMessage(transactions, ImmutableList.of(createPayToAddressOutput(refundAmount, refundAddress)), memo, merchantData); } else { @@ -304,7 +372,7 @@ public static Protos.Payment createPaymentMessage(List transactions /** * Create a payment message. This wraps up transaction data along with anything else useful for making a payment. - * + * * @param transactions transactions to include with the payment message * @param refundOutputs list of outputs to refund coins to, or null * @param memo arbitrary, user readable memo, or null if none @@ -312,35 +380,39 @@ public static Protos.Payment createPaymentMessage(List transactions * @return created payment message */ public static Protos.Payment createPaymentMessage(List transactions, - @Nullable List refundOutputs, @Nullable String memo, @Nullable byte[] merchantData) { + @Nullable List refundOutputs, @Nullable String memo, @Nullable byte[] merchantData) { Protos.Payment.Builder builder = Protos.Payment.newBuilder(); for (Transaction transaction : transactions) { transaction.verify(); builder.addTransactions(ByteString.copyFrom(transaction.unsafeBitcoinSerialize())); } if (refundOutputs != null) { - for (Protos.Output output : refundOutputs) + for (Protos.Output output : refundOutputs) { builder.addRefundTo(output); + } } - if (memo != null) + if (memo != null) { builder.setMemo(memo); - if (merchantData != null) + } + if (merchantData != null) { builder.setMerchantData(ByteString.copyFrom(merchantData)); + } return builder.build(); } /** * Parse transactions from payment message. - * + * * @param params network parameters (needed for transaction deserialization) * @param paymentMessage payment message to parse * @return list of transactions */ public static List parseTransactionsFromPaymentMessage(NetworkParameters params, - Protos.Payment paymentMessage) { + Protos.Payment paymentMessage) { final List transactions = new ArrayList(paymentMessage.getTransactionsCount()); - for (final ByteString transaction : paymentMessage.getTransactionsList()) + for (final ByteString transaction : paymentMessage.getTransactionsList()) { transactions.add(new Transaction(params, transaction.toByteArray())); + } return transactions; } @@ -348,7 +420,9 @@ public static List parseTransactionsFromPaymentMessage(NetworkParam * Message returned by the merchant in response to a Payment message. */ public static class Ack { - @Nullable private final String memo; + + @Nullable + private final String memo; Ack(@Nullable String memo) { this.memo = memo; @@ -359,14 +433,15 @@ public static class Ack { * as a notification (e.g. "Your payment was received and is being processed"). If none was provided, returns * null. */ - @Nullable public String getMemo() { + @Nullable + public String getMemo() { return memo; } } /** * Create a payment ack. - * + * * @param paymentMessage payment message to send with the ack * @param memo arbitrary, user readable memo, or null if none * @return created payment ack @@ -374,8 +449,9 @@ public static class Ack { public static Protos.PaymentACK createPaymentAck(Protos.Payment paymentMessage, @Nullable String memo) { final Protos.PaymentACK.Builder builder = Protos.PaymentACK.newBuilder(); builder.setPayment(paymentMessage); - if (memo != null) + if (memo != null) { builder.setMemo(memo); + } return builder.build(); } @@ -390,7 +466,7 @@ public static Ack parsePaymentAck(Protos.PaymentACK paymentAck) { /** * Create a standard pay to address output for usage in {@link #createPaymentRequest} and * {@link #createPaymentMessage}. - * + * * @param amount amount to pay, or null * @param address address to pay to * @return output @@ -398,8 +474,9 @@ public static Ack parsePaymentAck(Protos.PaymentACK paymentAck) { public static Protos.Output createPayToAddressOutput(@Nullable Coin amount, Address address) { Protos.Output.Builder output = Protos.Output.newBuilder(); if (amount != null) { - if (amount.compareTo(NetworkParameters.MAX_MONEY) > 0) + if (amount.compareTo(NetworkParameters.MAX_MONEY) > 0) { throw new IllegalArgumentException("Amount too big: " + amount); + } output.setAmount(amount.value); } else { output.setAmount(0); @@ -412,7 +489,8 @@ public static Protos.Output createPayToAddressOutput(@Nullable Coin amount, Addr * Value object to hold amount/script pairs. */ public static class Output implements Serializable { - public final @Nullable Coin amount; + public final @Nullable + Coin amount; public final byte[] scriptData; public Output(@Nullable Coin amount, byte[] scriptData) { diff --git a/core/src/main/java/com/dogecoin/dogecoinj/protocols/payments/PaymentSession.java b/core/src/main/java/com/dogecoin/dogecoinj/protocols/payments/PaymentSession.java index 2d2434461..b2019ca51 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/protocols/payments/PaymentSession.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/protocols/payments/PaymentSession.java @@ -2,9 +2,9 @@ * 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 - * + *

+ * 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. @@ -15,7 +15,12 @@ package com.dogecoin.dogecoinj.protocols.payments; import com.dogecoin.dogecoinj.protocols.payments.PaymentProtocol.PkiVerificationData; -import org.bitcoinj.core.*; +import org.bitcoinj.core.Address; +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionOutput; +import org.bitcoinj.core.VerificationException; import org.bitcoinj.wallet.SendRequest; import org.bitcoinj.crypto.TrustStoreLoader; import org.bitcoinj.params.MainNetParams; @@ -30,8 +35,13 @@ import javax.annotation.Nullable; -import java.io.*; -import java.net.*; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; import java.security.KeyStoreException; import java.util.ArrayList; import java.util.Date; @@ -77,7 +87,8 @@ public class PaymentSession { * Stores the calculated PKI verification data, or null if none is available. * Only valid after the session is created with the verifyPki parameter set to true. */ - @Nullable public final PkiVerificationData pkiVerificationData; + @Nullable + public final PkiVerificationData pkiVerificationData; /** *

Returns a future that will be notified with a PaymentSession object after it is fetched using the provided uri. @@ -117,8 +128,9 @@ public static ListenableFuture createFromBitcoinUri(final Bitcoi public static ListenableFuture createFromBitcoinUri(final BitcoinURI uri, final boolean verifyPki, @Nullable final TrustStoreLoader trustStoreLoader) throws PaymentProtocolException { String url = uri.getPaymentRequestUrl(); - if (url == null) + if (url == null) { throw new PaymentProtocolException.InvalidPaymentRequestURL("No payment request URL (r= parameter) in BitcoinURI " + uri); + } try { return fetchPaymentRequest(new URI(url), verifyPki, trustStoreLoader); } catch (URISyntaxException e) { @@ -143,6 +155,10 @@ public static ListenableFuture createFromUrl(final String url) t * If the payment request object specifies a PKI method, then the system trust store will * be used to verify the signature provided by the payment request. An exception is thrown by the future if the * signature cannot be verified. + * + * @param url specified payment url. + * @param verifyPki if pki should be validated + * @throws PaymentProtocolException if the specified url is invalid */ public static ListenableFuture createFromUrl(final String url, final boolean verifyPki) throws PaymentProtocolException { @@ -156,14 +172,20 @@ public static ListenableFuture createFromUrl(final String url, f * be used to verify the signature provided by the payment request. An exception is thrown by the future if the * signature cannot be verified. * If trustStoreLoader is null, the system default trust store is used. + * + * @param url specified payment url. + * @param verifyPki if pki should be validated + * @param trustStoreLoader specified trust store loader, otherwise system default. + * @throws PaymentProtocolException if the specified url is invalid */ public static ListenableFuture createFromUrl(final String url, final boolean verifyPki, @Nullable final TrustStoreLoader trustStoreLoader) throws PaymentProtocolException { - if (url == null) + if (url == null) { throw new PaymentProtocolException.InvalidPaymentRequestURL("null paymentRequestUrl"); + } try { return fetchPaymentRequest(new URI(url), verifyPki, trustStoreLoader); - } catch(URISyntaxException e) { + } catch (URISyntaxException e) { throw new PaymentProtocolException.InvalidPaymentRequestURL(e); } } @@ -172,7 +194,7 @@ private static ListenableFuture fetchPaymentRequest(final URI ur return executor.submit(new Callable() { @Override public PaymentSession call() throws Exception { - HttpURLConnection connection = (HttpURLConnection)uri.toURL().openConnection(); + HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection(); connection.setRequestProperty("Accept", PaymentProtocol.MIMETYPE_PAYMENTREQUEST); connection.setUseCaches(false); Protos.PaymentRequest paymentRequest = Protos.PaymentRequest.parseFrom(connection.getInputStream()); @@ -201,6 +223,11 @@ public PaymentSession(Protos.PaymentRequest request, boolean verifyPki) throws P * Creates a PaymentSession from the provided {@link Protos.PaymentRequest}. * If verifyPki is true, also validates the signature and throws an exception if it fails. * If trustStoreLoader is null, the system default trust store is used. + * + * @param request payment request. + * @param verifyPki if pki should be verified and signature should be validated. + * @param trustStoreLoader specified trust store loader, otherwise system default. + * @throws PaymentProtocolException if payment request should be verified and could not be verified. */ public PaymentSession(Protos.PaymentRequest request, boolean verifyPki, @Nullable final TrustStoreLoader trustStoreLoader) throws PaymentProtocolException { this.trustStoreLoader = trustStoreLoader != null ? trustStoreLoader : new TrustStoreLoader.DefaultTrustStoreLoader(); @@ -208,9 +235,7 @@ public PaymentSession(Protos.PaymentRequest request, boolean verifyPki, @Nullabl if (verifyPki) { try { pkiVerificationData = PaymentProtocol.verifyPaymentRequestPki(request, this.trustStoreLoader.getKeyStore()); - } catch (IOException x) { - throw new PaymentProtocolException(x); - } catch (KeyStoreException x) { + } catch (IOException | KeyStoreException x) { throw new PaymentProtocolException(x); } } else { @@ -233,11 +258,12 @@ public List getOutputs() { /** * Returns the memo included by the merchant in the payment request, or null if not found. */ - @Nullable public String getMemo() { - if (paymentDetails.hasMemo()) + @Nullable + public String getMemo() { + if (paymentDetails.hasMemo()) { return paymentDetails.getMemo(); - else - return null; + } + return null; } /** @@ -257,11 +283,12 @@ public Date getDate() { /** * Returns the expires time of the payment request, or null if none. */ - @Nullable public Date getExpires() { - if (paymentDetails.hasExpires()) + @Nullable + public Date getExpires() { + if (paymentDetails.hasExpires()) { return new Date(paymentDetails.getExpires() * 1000); - else - return null; + } + return null; } /** @@ -275,20 +302,23 @@ public boolean isExpired() { * Returns the payment url where the Payment message should be sent. * Returns null if no payment url was provided in the PaymentRequest. */ - public @Nullable String getPaymentUrl() { - if (paymentDetails.hasPaymentUrl()) + public @Nullable + String getPaymentUrl() { + if (paymentDetails.hasPaymentUrl()) { return paymentDetails.getPaymentUrl(); + } return null; } /** * Returns the merchant data included by the merchant in the payment request, or null if none. */ - @Nullable public byte[] getMerchantData() { - if (paymentDetails.hasMerchantData()) + @Nullable + public byte[] getMerchantData() { + if (paymentDetails.hasMerchantData()) { return paymentDetails.getMerchantData().toByteArray(); - else - return null; + } + return null; } /** @@ -296,8 +326,9 @@ public boolean isExpired() { */ public SendRequest getSendRequest() { Transaction tx = new Transaction(params); - for (Protos.Output output : paymentDetails.getOutputsList()) + for (Protos.Output output : paymentDetails.getOutputsList()) { tx.addOutput(new TransactionOutput(params, tx, Coin.valueOf(output.getAmount()), output.getScript().toByteArray())); + } return SendRequest.forTx(tx).fromPaymentDetails(paymentDetails); } @@ -312,13 +343,16 @@ public SendRequest getSendRequest() { * @param refundAddr will be used by the merchant to send money back if there was a problem. * @param memo is a message to include in the payment message sent to the merchant. */ - public @Nullable ListenableFuture sendPayment(List txns, @Nullable Address refundAddr, @Nullable String memo) + public @Nullable + ListenableFuture sendPayment(List txns, @Nullable Address refundAddr, @Nullable String memo) throws PaymentProtocolException, VerificationException, IOException { Protos.Payment payment = getPayment(txns, refundAddr, memo); - if (payment == null) + if (payment == null) { return null; - if (isExpired()) + } + if (isExpired()) { throw new PaymentProtocolException.Expired("PaymentRequest is expired"); + } URL url; try { url = new URL(paymentDetails.getPaymentUrl()); @@ -336,18 +370,27 @@ public SendRequest getSendRequest() { * @param refundAddr will be used by the merchant to send money back if there was a problem. * @param memo is a message to include in the payment message sent to the merchant. */ - public @Nullable Protos.Payment getPayment(List txns, @Nullable Address refundAddr, @Nullable String memo) - throws IOException, PaymentProtocolException.InvalidNetwork { + public @Nullable + Protos.Payment getPayment(List txns, @Nullable Address refundAddr, @Nullable String memo) + throws PaymentProtocolException.InvalidNetwork { if (paymentDetails.hasPaymentUrl()) { - for (Transaction tx : txns) - if (!tx.getParams().equals(params)) + for (Transaction tx : txns) { + if (!tx.getParams().equals(params)) { throw new PaymentProtocolException.InvalidNetwork(params.getPaymentProtocolId()); + } + } return PaymentProtocol.createPaymentMessage(txns, totalValue, refundAddr, memo, getMerchantData()); } else { return null; } } + /** + * Sends the payment to the merchant who generated the payment. + * @param url payment url + * @param payment payment + * @return payment response + */ @VisibleForTesting protected ListenableFuture sendPayment(final URL url, final Protos.Payment payment) { return executor.submit(new Callable() { @@ -377,53 +420,71 @@ public PaymentProtocol.Ack call() throws Exception { private void parsePaymentRequest(Protos.PaymentRequest request) throws PaymentProtocolException { try { - if (request == null) + if (request == null) { throw new PaymentProtocolException("request cannot be null"); - if (request.getPaymentDetailsVersion() != 1) + } + if (request.getPaymentDetailsVersion() != 1) { throw new PaymentProtocolException.InvalidVersion("Version 1 required. Received version " + request.getPaymentDetailsVersion()); + } paymentRequest = request; - if (!request.hasSerializedPaymentDetails()) + if (!request.hasSerializedPaymentDetails()) { throw new PaymentProtocolException("No PaymentDetails"); + } paymentDetails = Protos.PaymentDetails.newBuilder().mergeFrom(request.getSerializedPaymentDetails()).build(); - if (paymentDetails == null) + if (paymentDetails == null) { throw new PaymentProtocolException("Invalid PaymentDetails"); - if (!paymentDetails.hasNetwork()) + } + if (!paymentDetails.hasNetwork()) { params = MainNetParams.get(); - else + } else { params = NetworkParameters.fromPmtProtocolID(paymentDetails.getNetwork()); - if (params == null) + } + if (params == null) { throw new PaymentProtocolException.InvalidNetwork("Invalid network " + paymentDetails.getNetwork()); - if (paymentDetails.getOutputsCount() < 1) + } + if (paymentDetails.getOutputsCount() < 1) { throw new PaymentProtocolException.InvalidOutputs("No outputs"); + } for (Protos.Output output : paymentDetails.getOutputsList()) { - if (output.hasAmount()) + if (output.hasAmount()) { totalValue = totalValue.add(Coin.valueOf(output.getAmount())); + } } // 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 (totalValue.compareTo(NetworkParameters.MAX_MONEY) > 0) { throw new PaymentProtocolException.InvalidOutputs("The outputs are way too big."); + } } catch (InvalidProtocolBufferException e) { throw new PaymentProtocolException(e); } } - /** Returns the value of pkiVerificationData or null if it wasn't verified at construction time. */ - @Nullable public PkiVerificationData verifyPki() { + /** + * Returns the value of pkiVerificationData or null if it wasn't verified at construction time. + */ + @Nullable + public PkiVerificationData verifyPki() { return pkiVerificationData; } - /** Gets the params as read from the PaymentRequest.network field: main is the default if missing. */ + /** + * Gets the params as read from the PaymentRequest.network field: main is the default if missing. + */ public NetworkParameters getNetworkParameters() { return params; } - /** Returns the protobuf that this object was instantiated with. */ + /** + * Returns the protobuf that this object was instantiated with. + */ public Protos.PaymentRequest getPaymentRequest() { return paymentRequest; } - /** Returns the protobuf that describes the payment to be made. */ + /** + * Returns the protobuf that describes the payment to be made. + */ public Protos.PaymentDetails getPaymentDetails() { return paymentDetails; } diff --git a/core/src/main/java/com/dogecoin/dogecoinj/protocols/payments/package-info.java b/core/src/main/java/com/dogecoin/dogecoinj/protocols/payments/package-info.java index fb419735c..23fc16a6d 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/protocols/payments/package-info.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/protocols/payments/package-info.java @@ -2,4 +2,4 @@ * The BIP70 payment protocol wraps Bitcoin transactions and adds various useful features like memos, refund addresses * and authentication. */ -package com.dogecoin.dogecoinj.protocols.payments; \ No newline at end of file +package com.dogecoin.dogecoinj.protocols.payments; diff --git a/core/src/main/java/org/libdohj/params/AbstractDogecoinParams.java b/core/src/main/java/org/libdohj/params/AbstractDogecoinParams.java index 966f462c6..2a877958d 100644 --- a/core/src/main/java/org/libdohj/params/AbstractDogecoinParams.java +++ b/core/src/main/java/org/libdohj/params/AbstractDogecoinParams.java @@ -16,42 +16,37 @@ package org.libdohj.params; -import java.io.ByteArrayOutputStream; -import java.math.BigInteger; - -import org.bitcoinj.core.AltcoinBlock; -import org.bitcoinj.core.Block; -import org.bitcoinj.core.Coin; -import static org.bitcoinj.core.Coin.COIN; -import org.bitcoinj.core.NetworkParameters; -import org.bitcoinj.core.VerificationException; +import org.bitcoinj.core.*; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptOpCodes; import org.bitcoinj.store.BlockStore; import org.bitcoinj.store.BlockStoreException; import org.bitcoinj.utils.MonetaryFormat; - +import org.libdohj.core.AltcoinSerializer; +import org.libdohj.core.AuxPoWNetworkParameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.bitcoinj.core.Sha256Hash; -import org.bitcoinj.core.StoredBlock; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.core.TransactionInput; -import org.bitcoinj.core.TransactionOutput; -import org.bitcoinj.core.Utils; -import org.libdohj.core.AltcoinSerializer; -import org.libdohj.core.AuxPoWNetworkParameters; +import java.io.ByteArrayOutputStream; +import java.math.BigInteger; + +import static org.bitcoinj.core.Coin.COIN; /** * Common parameters for Dogecoin networks. */ public abstract class AbstractDogecoinParams extends NetworkParameters implements AuxPoWNetworkParameters { - /** Standard format for the DOGE denomination. */ + /** + * Standard format for the DOGE denomination. + */ public static final MonetaryFormat DOGE; - /** Standard format for the mDOGE denomination. */ + /** + * Standard format for the mDOGE denomination. + */ public static final MonetaryFormat MDOGE; - /** Standard format for the Koinu denomination. */ + /** + * Standard format for the Koinu denomination. + */ public static final MonetaryFormat KOINU; public static final int DIGISHIELD_BLOCK_HEIGHT = 145000; // Block height to use Digishield from @@ -62,11 +57,17 @@ public abstract class AbstractDogecoinParams extends NetworkParameters implement public static final int DOGE_INTERVAL = DOGE_TARGET_TIMESPAN / DOGE_TARGET_SPACING; public static final int DOGE_INTERVAL_NEW = DOGE_TARGET_TIMESPAN_NEW / DOGE_TARGET_SPACING; - /** Currency code for base 1 Dogecoin. */ + /** + * Currency code for base 1 Dogecoin. + */ public static final String CODE_DOGE = "DOGE"; - /** Currency code for base 1/1,000 Dogecoin. */ + /** + * Currency code for base 1/1,000 Dogecoin. + */ public static final String CODE_MDOGE = "mDOGE"; - /** Currency code for base 1/100,000,000 Dogecoin. */ + /** + * Currency code for base 1/100,000,000 Dogecoin. + */ public static final String CODE_KOINU = "Koinu"; private static final int BLOCK_MIN_VERSION_AUXPOW = 0x00620002; @@ -74,16 +75,20 @@ public abstract class AbstractDogecoinParams extends NetworkParameters implement static { DOGE = MonetaryFormat.BTC.noCode() - .code(0, CODE_DOGE) - .code(3, CODE_MDOGE) - .code(7, CODE_KOINU); + .code(0, CODE_DOGE) + .code(3, CODE_MDOGE) + .code(7, CODE_KOINU); MDOGE = DOGE.shift(3).minDecimals(2).optionalDecimals(2); KOINU = DOGE.shift(7).minDecimals(0).optionalDecimals(2); } - /** The string returned by getId() for the main, production network where people trade things. */ + /** + * The string returned by getId() for the main, production network where people trade things. + */ public static final String ID_DOGE_MAINNET = "org.dogecoin.production"; - /** The string returned by getId() for the testnet. */ + /** + * The string returned by getId() for the testnet. + */ public static final String ID_DOGE_TESTNET = "org.dogecoin.test"; protected final int newInterval; @@ -94,7 +99,7 @@ public abstract class AbstractDogecoinParams extends NetworkParameters implement public static final int DOGECOIN_PROTOCOL_VERSION_AUXPOW = 70003; public static final int DOGECOIN_PROTOCOL_VERSION_CURRENT = 70004; - private static final Coin BASE_SUBSIDY = COIN.multiply(500000); + private static final Coin BASE_SUBSIDY = COIN.multiply(500000); private static final Coin STABLE_SUBSIDY = COIN.multiply(10000); public AbstractDogecoinParams(final int setDiffChangeTarget) { @@ -147,7 +152,9 @@ public Coin getBlockSubsidy(final int height) { } } - /** How many blocks pass between difficulty adjustment periods. After new diff algo. */ + /** + * How many blocks pass between difficulty adjustment periods. After new diff algo. + */ public int getNewInterval() { return newInterval; } @@ -186,7 +193,8 @@ public boolean hasMaxMoney() { return false; } - /** Dogecoin: Normally minimum difficulty blocks can only occur in between + /** + * Dogecoin: Normally minimum difficulty blocks can only occur in between * retarget blocks. However, once we introduce Digishield every block is * a retarget, so we need to handle minimum difficulty on all blocks. */ @@ -205,7 +213,7 @@ private boolean allowDigishieldMinDifficultyForBlock(final StoredBlock pindexLas @Override public void checkDifficultyTransitions(StoredBlock storedPrev, Block nextBlock, BlockStore blockStore) - throws VerificationException, BlockStoreException { + throws VerificationException, BlockStoreException { try { final long newTargetCompact = calculateNewDifficultyTarget(storedPrev, nextBlock, blockStore); final long receivedTargetCompact = nextBlock.getDifficultyTarget(); @@ -224,14 +232,13 @@ public void checkDifficultyTransitions(StoredBlock storedPrev, Block nextBlock, * difficulty if the block interval is high enough. * * @throws CheckpointEncounteredException if a checkpoint is encountered while - * calculating difficulty target, and therefore no conclusive answer can - * be provided. + * calculating difficulty target, and therefore no conclusive answer can + * be provided. */ public long calculateNewDifficultyTarget(StoredBlock storedPrev, Block nextBlock, BlockStore blockStore) - throws VerificationException, BlockStoreException, CheckpointEncounteredException { + throws VerificationException, BlockStoreException, CheckpointEncounteredException { // Dogecoin: Special rules for minimum difficulty blocks with Digishield - if (allowDigishieldMinDifficultyForBlock(storedPrev, nextBlock)) - { + if (allowDigishieldMinDifficultyForBlock(storedPrev, nextBlock)) { // Special difficulty rule for testnet: // If the new block's timestamp is more than 2* nTargetSpacing minutes // then allow mining of a min-difficulty block. @@ -242,8 +249,8 @@ public long calculateNewDifficultyTarget(StoredBlock storedPrev, Block nextBlock final int previousHeight = storedPrev.getHeight(); final boolean digishieldAlgorithm = previousHeight + 1 >= this.getDigishieldBlockHeight(); final int retargetInterval = digishieldAlgorithm - ? this.getNewInterval() - : this.getInterval(); + ? this.getNewInterval() + : this.getInterval(); // Is this supposed to be a difficulty transition point? if ((storedPrev.getHeight() + 1) % retargetInterval != 0) { @@ -278,7 +285,7 @@ public long calculateNewDifficultyTarget(StoredBlock storedPrev, Block nextBlock // two weeks after the initial block chain download. StoredBlock cursor = storedPrev; int goBack = retargetInterval - 1; - if (cursor.getHeight()+1 != retargetInterval) + if (cursor.getHeight() + 1 != retargetInterval) goBack = retargetInterval; for (int i = 0; i < goBack; i++) { @@ -298,8 +305,8 @@ public long calculateNewDifficultyTarget(StoredBlock storedPrev, Block nextBlock Block blockIntervalAgo = cursor.getHeader(); return this.calculateNewDifficultyTargetInner(previousHeight, prev.getTimeSeconds(), - prev.getDifficultyTarget(), blockIntervalAgo.getTimeSeconds(), - nextBlock.getDifficultyTarget()); + prev.getDifficultyTarget(), blockIntervalAgo.getTimeSeconds(), + nextBlock.getDifficultyTarget()); } /** @@ -307,67 +314,60 @@ public long calculateNewDifficultyTarget(StoredBlock storedPrev, Block nextBlock * recalculation interval. Does not handle special cases such as testnet blocks * being setting the target to maximum for blocks after a long interval. * - * @param previousHeight height of the block immediately before the retarget. - * @param prev the block immediately before the retarget block. - * @param nextBlock the block the retarget happens at. + * @param previousHeight height of the block immediately before the retarget. + * @param prev the block immediately before the retarget block. + * @param nextBlock the block the retarget happens at. * @param blockIntervalAgo The last retarget block. * @return New difficulty target as compact bytes. */ protected long calculateNewDifficultyTargetInner(int previousHeight, final Block prev, - final Block nextBlock, final Block blockIntervalAgo) { + final Block nextBlock, final Block blockIntervalAgo) { return this.calculateNewDifficultyTargetInner(previousHeight, prev.getTimeSeconds(), - prev.getDifficultyTarget(), blockIntervalAgo.getTimeSeconds(), - nextBlock.getDifficultyTarget()); + prev.getDifficultyTarget(), blockIntervalAgo.getTimeSeconds(), + nextBlock.getDifficultyTarget()); } /** * Calculate the difficulty target expected for the next block after a normal * recalculation interval. - * - * @param previousHeight Height of the block immediately previous to the one we're calculating difficulty of. - * @param previousBlockTime Time of the block immediately previous to the one we're calculating difficulty of. + * + * @param previousHeight Height of the block immediately previous to the one we're calculating difficulty of. + * @param previousBlockTime Time of the block immediately previous to the one we're calculating difficulty of. * @param lastDifficultyTarget Compact difficulty target of the last retarget block. - * @param lastRetargetTime Time of the last difficulty retarget. + * @param lastRetargetTime Time of the last difficulty retarget. * @param nextDifficultyTarget The expected difficulty target of the next - * block, used for determining precision of the result. + * block, used for determining precision of the result. * @return New difficulty target as compact bytes. */ protected long calculateNewDifficultyTargetInner(int previousHeight, long previousBlockTime, - final long lastDifficultyTarget, final long lastRetargetTime, - final long nextDifficultyTarget) { + final long lastDifficultyTarget, final long lastRetargetTime, + final long nextDifficultyTarget) { final int height = previousHeight + 1; final boolean digishieldAlgorithm = height >= this.getDigishieldBlockHeight(); final int retargetTimespan = digishieldAlgorithm - ? this.getNewTargetTimespan() - : this.getTargetTimespan(); + ? this.getNewTargetTimespan() + : this.getTargetTimespan(); int actualTime = (int) (previousBlockTime - lastRetargetTime); final int minTimespan; final int maxTimespan; // Limit the adjustment step. - if (digishieldAlgorithm) - { + if (digishieldAlgorithm) { // Round towards zero to match the C++ implementation. if (actualTime < retargetTimespan) { - actualTime = (int)Math.ceil(retargetTimespan + (actualTime - retargetTimespan) / 8.0); + actualTime = (int) Math.ceil(retargetTimespan + (actualTime - retargetTimespan) / 8.0); } else { - actualTime = (int)Math.floor(retargetTimespan + (actualTime - retargetTimespan) / 8.0); + actualTime = (int) Math.floor(retargetTimespan + (actualTime - retargetTimespan) / 8.0); } minTimespan = retargetTimespan - (retargetTimespan / 4); maxTimespan = retargetTimespan + (retargetTimespan / 2); - } - else if (height > 10000) - { + } else if (height > 10000) { minTimespan = retargetTimespan / 4; maxTimespan = retargetTimespan * 4; - } - else if (height > 5000) - { + } else if (height > 5000) { minTimespan = retargetTimespan / 8; maxTimespan = retargetTimespan * 4; - } - else - { + } else { minTimespan = retargetTimespan / 16; maxTimespan = retargetTimespan * 4; } @@ -398,6 +398,11 @@ public int getDigishieldBlockHeight() { return DIGISHIELD_BLOCK_HEIGHT; } + /** + * Get chain identifier used to differentiate this chain from others, when constructing AuxPoW block headers. + * + * @return chain identifier + */ @Override public int getChainID() { return AUXPOW_CHAIN_ID; @@ -417,11 +422,23 @@ public BigInteger getBlockDifficulty(Block block) { return ((AltcoinBlock) block).getScryptHash().toBigInteger(); } + /** + * Get an {@link AltcoinSerializer} suitable for use with this blockchain (as in, understands any extended types it uses). + * + * @param parseRetain retain the backing byte array of a message for fast reserialization. + * @return altcoin serializer + */ @Override public AltcoinSerializer getSerializer(boolean parseRetain) { return new AltcoinSerializer(this, parseRetain); } + /** + * Get a protocol version number from a provided protocol version + * + * @param version protocol version + * @return protocol version number + */ @Override public int getProtocolVersionNum(final ProtocolVersion version) { switch (version) { @@ -438,8 +455,7 @@ public int getProtocolVersionNum(final ProtocolVersion version) { @Override public boolean isAuxPoWBlockVersion(long version) { - return version >= BLOCK_MIN_VERSION_AUXPOW - && (version & BLOCK_VERSION_FLAG_AUXPOW) > 0; + return version >= BLOCK_MIN_VERSION_AUXPOW && (version & BLOCK_VERSION_FLAG_AUXPOW) > 0; } /** @@ -451,17 +467,12 @@ public boolean isAuxPoWBlockVersion(long version) { */ protected int getTargetSpacing(int height) { final boolean digishieldAlgorithm = height >= this.getDigishieldBlockHeight(); - final int retargetInterval = digishieldAlgorithm - ? this.getNewInterval() - : this.getInterval(); - final int retargetTimespan = digishieldAlgorithm - ? this.getNewTargetTimespan() - : this.getTargetTimespan(); + final int retargetInterval = digishieldAlgorithm ? this.getNewInterval() : this.getInterval(); + final int retargetTimespan = digishieldAlgorithm ? this.getNewTargetTimespan() : this.getTargetTimespan(); return retargetTimespan / retargetInterval; } private static class CheckpointEncounteredException extends Exception { - private CheckpointEncounteredException() { } } diff --git a/namecoin/build.gradle b/namecoin/build.gradle index f1057562c..f0bd318c2 100644 --- a/namecoin/build.gradle +++ b/namecoin/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'java-library' +apply plugin: 'checkstyle' dependencies { implementation project(':core')