Restructure libdohj around Maven modules

This commit is contained in:
Ross Nicoll
2016-06-27 21:06:44 +01:00
parent 6f92dc64bd
commit bacc348541
51 changed files with 312 additions and 72 deletions

102
core/pom.xml Normal file
View File

@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.libdohj</groupId>
<artifactId>libdohj-core</artifactId>
<version>0.14-SNAPSHOT</version>
<packaging>jar</packaging>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<!-- Dummy block to make Maven Central happy: authors list is in AUTHORS -->
<developers>
<developer>
<name>The libdohj team.</name>
<email>info@dogecoin.com</email>
</developer>
</developers>
<profiles>
<profile>
<id>update-protobuf</id>
<activation>
<property>
<name>updateProtobuf</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>compile-protoc</id>
<phase>generate-sources</phase>
<configuration>
<tasks>
<path id="proto.path">
<fileset dir="src">
<include name="**/*.proto"/>
</fileset>
</path>
<pathconvert pathsep=" " property="proto.files" refid="proto.path"/>
<exec executable="protoc" failonerror="true">
<arg value="--java_out=${project.basedir}/src/main/java"/>
<arg value="-I${project.basedir}/src"/>
<arg line="${proto.files}"/>
</exec>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>com.lambdaworks</groupId>
<artifactId>scrypt</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-core</artifactId>
<version>0.14.2</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
</properties>
<name>libdohj</name>
</project>

View File

@@ -0,0 +1,423 @@
/**
* 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
*
* 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 com.dogecoin.dogecoinj.protocols.payments;
import org.bitcoinj.core.*;
import org.bitcoinj.crypto.X509Utils;
import org.bitcoinj.script.ScriptBuilder;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.bitcoin.protocols.payments.Protos;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.List;
/**
* <p>Utility methods and constants for working with <a href="https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki">
* BIP 70 aka the payment protocol</a>. These are low level wrappers around the protocol buffers. If you're implementing
* a wallet app, look at {@link PaymentSession} for a higher level API that should simplify working with the protocol.</p>
*
* <p>BIP 70 defines a binary, protobuf based protocol that runs directly between sender and receiver of funds. Payment
* 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.</p>
*/
public class PaymentProtocol {
// MIME types as defined in DIP71.
public static final String MIMETYPE_PAYMENTREQUEST = "application/vnd.doge.payment.request";
public static final String MIMETYPE_PAYMENT = "application/vnd.doge.payment.payment";
public static final String MIMETYPE_PAYMENTACK = "application/vnd.doge.payment.ack";
/**
* 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
* request.
*
* @param params network parameters
* @param amount amount of coins to request, or null
* @param toAddress address to request coins to
* @param memo arbitrary, user readable memo, or null if none
* @param paymentUrl URL to send payment message to, or null if none
* @param merchantData arbitrary merchant data, or null if none
* @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) {
return createPaymentRequest(params, ImmutableList.of(createPayToAddressOutput(amount, toAddress)), memo,
paymentUrl, merchantData);
}
/**
* 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
* @param paymentUrl URL to send payment message to, or null if none
* @param merchantData arbitrary merchant data, or null if none
* @return created payment request, in its builder form
*/
public static Protos.PaymentRequest.Builder createPaymentRequest(NetworkParameters params,
List<Protos.Output> 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)
paymentDetails.addOutputs(output);
if (memo != null)
paymentDetails.setMemo(memo);
if (paymentUrl != null)
paymentDetails.setPaymentUrl(paymentUrl);
if (merchantData != null)
paymentDetails.setMerchantData(ByteString.copyFrom(merchantData));
paymentDetails.setTime(Utils.currentTimeSeconds());
final Protos.PaymentRequest.Builder paymentRequest = Protos.PaymentRequest.newBuilder();
paymentRequest.setSerializedPaymentDetails(paymentDetails.build().toByteString());
return paymentRequest;
}
/**
* Parse a payment request.
*
* @param paymentRequest payment request to parse
* @return instance of {@link PaymentSession}, used as a value object
* @throws PaymentProtocolException
*/
public static PaymentSession parsePaymentRequest(Protos.PaymentRequest paymentRequest)
throws PaymentProtocolException {
return new PaymentSession(paymentRequest, false, null);
}
/**
* 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.
* @param privateKey The key to sign with. Must match the public key from the first certificate of the certificate chain.
*/
public static void signPaymentRequest(Protos.PaymentRequest.Builder paymentRequest,
X509Certificate[] certificateChain, PrivateKey privateKey) {
try {
final Protos.X509Certificates.Builder certificates = Protos.X509Certificates.newBuilder();
for (final Certificate certificate : certificateChain)
certificates.addCertificate(ByteString.copyFrom(certificate.getEncoded()));
paymentRequest.setPkiType("x509+sha256");
paymentRequest.setPkiData(certificates.build().toByteString());
paymentRequest.setSignature(ByteString.EMPTY);
final Protos.PaymentRequest paymentRequestToSign = paymentRequest.build();
final String algorithm;
if (privateKey.getAlgorithm().equalsIgnoreCase("RSA"))
algorithm = "SHA256withRSA";
else
throw new IllegalStateException(privateKey.getAlgorithm());
final Signature signature = Signature.getInstance(algorithm);
signature.initSign(privateKey);
signature.update(paymentRequestToSign.toByteArray());
paymentRequest.setSignature(ByteString.copyFrom(signature.sign()));
} catch (final GeneralSecurityException x) {
// Should never happen so don't make users have to think about it.
throw new RuntimeException(x);
}
}
/**
* 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)
throws PaymentProtocolException {
List<X509Certificate> certs = null;
try {
final String pkiType = paymentRequest.getPkiType();
if (pkiType.equals("none"))
// Nothing to verify. Everything is fine. Move along.
return null;
String algorithm;
if (pkiType.equals("x509+sha256"))
algorithm = "SHA256withRSA";
else if (pkiType.equals("x509+sha1"))
algorithm = "SHA1withRSA";
else
throw new PaymentProtocolException.InvalidPkiType("Unsupported PKI type: " + pkiType);
Protos.X509Certificates protoCerts = Protos.X509Certificates.parseFrom(paymentRequest.getPkiData());
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())
certs.add((X509Certificate) certificateFactory.generateCertificate(bytes.newInput()));
CertPath path = certificateFactory.generateCertPath(certs);
// Retrieves the most-trusted CAs from keystore.
PKIXParameters params = new PKIXParameters(trustStore);
// Revocation not supported in the current version.
params.setRevocationEnabled(false);
// Now verify the certificate chain is correct and trusted. This let's us get an identity linked pubkey.
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) validator.validate(path, params);
PublicKey publicKey = result.getPublicKey();
// OK, we got an identity, now check it was used to sign this message.
Signature signature = Signature.getInstance(algorithm);
// Note that we don't use signature.initVerify(certs.get(0)) here despite it being the most obvious
// way to set it up, because we don't care about the constraints specified on the certificates: any
// cert that links a key to a domain name or other identity will do for us.
signature.initVerify(publicKey);
Protos.PaymentRequest.Builder reqToCheck = paymentRequest.toBuilder();
reqToCheck.setSignature(ByteString.EMPTY);
signature.update(reqToCheck.build().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)
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) {
// Data structures are malformed.
throw new PaymentProtocolException.InvalidPkiData(e);
} catch (CertificateException e) {
// The X.509 certificate data didn't parse correctly.
throw new PaymentProtocolException.PkiVerificationException(e);
} catch (NoSuchAlgorithmException e) {
// Should never happen so don't make users have to think about it. PKIX is always present.
throw new RuntimeException(e);
} catch (InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
} catch (CertPathValidatorException e) {
// The certificate chain isn't known or trusted, probably, the server is using an SSL root we don't
// know about and the user needs to upgrade to a new version of the software (or import a root cert).
throw new PaymentProtocolException.PkiVerificationException(e, certs);
} catch (InvalidKeyException e) {
// Shouldn't happen if the certs verified correctly.
throw new PaymentProtocolException.PkiVerificationException(e);
} catch (SignatureException e) {
// Something went wrong during hashing (yes, despite the name, this does not mean the sig was invalid).
throw new PaymentProtocolException.PkiVerificationException(e);
} catch (KeyStoreException e) {
throw new RuntimeException(e);
}
}
/**
* 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 final String displayName;
/** SSL public key that was used to sign. */
public final PublicKey merchantSigningKey;
/** 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 */
public final String rootAuthorityName;
private PkiVerificationData(@Nullable String displayName, PublicKey merchantSigningKey,
TrustAnchor rootAuthority) throws PaymentProtocolException.PkiVerificationException {
try {
this.displayName = displayName;
this.merchantSigningKey = merchantSigningKey;
this.rootAuthority = rootAuthority;
this.rootAuthorityName = X509Utils.getDisplayNameFromCertificate(rootAuthority.getTrustedCert(), true);
} catch (CertificateParsingException x) {
throw new PaymentProtocolException.PkiVerificationException(x);
}
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("displayName", displayName)
.add("rootAuthorityName", rootAuthorityName)
.add("merchantSigningKey", merchantSigningKey)
.add("rootAuthority", rootAuthority)
.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
* @param memo arbitrary, user readable memo, or null if none
* @param merchantData arbitrary merchant data, or null if none
* @return created payment message
*/
public static Protos.Payment createPaymentMessage(List<Transaction> transactions,
@Nullable Coin refundAmount, @Nullable Address refundAddress, @Nullable String memo,
@Nullable byte[] merchantData) {
if (refundAddress != 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 {
return createPaymentMessage(transactions, null, memo, merchantData);
}
}
/**
* 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
* @param merchantData arbitrary merchant data, or null if none
* @return created payment message
*/
public static Protos.Payment createPaymentMessage(List<Transaction> transactions,
@Nullable List<Protos.Output> 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)
builder.addRefundTo(output);
}
if (memo != null)
builder.setMemo(memo);
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<Transaction> parseTransactionsFromPaymentMessage(NetworkParameters params,
Protos.Payment paymentMessage) {
final List<Transaction> transactions = new ArrayList<Transaction>(paymentMessage.getTransactionsCount());
for (final ByteString transaction : paymentMessage.getTransactionsList())
transactions.add(new Transaction(params, transaction.toByteArray()));
return transactions;
}
/**
* Message returned by the merchant in response to a Payment message.
*/
public static class Ack {
@Nullable private final String memo;
Ack(@Nullable String memo) {
this.memo = memo;
}
/**
* Returns the memo included by the merchant in the payment ack. This message is typically displayed to the user
* as a notification (e.g. "Your payment was received and is being processed"). If none was provided, returns
* null.
*/
@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
*/
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)
builder.setMemo(memo);
return builder.build();
}
/**
* Parse payment ack into an object.
*/
public static Ack parsePaymentAck(Protos.PaymentACK paymentAck) {
final String memo = paymentAck.hasMemo() ? paymentAck.getMemo() : null;
return new Ack(memo);
}
/**
* 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
*/
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)
throw new IllegalArgumentException("Amount too big: " + amount);
output.setAmount(amount.value);
} else {
output.setAmount(0);
}
output.setScript(ByteString.copyFrom(ScriptBuilder.createOutputScript(address).getProgram()));
return output.build();
}
/**
* Value object to hold amount/script pairs.
*/
public static class Output implements Serializable {
public final @Nullable Coin amount;
public final byte[] scriptData;
public Output(@Nullable Coin amount, byte[] scriptData) {
this.amount = amount;
this.scriptData = scriptData;
}
}
}

View File

@@ -0,0 +1,107 @@
/**
* 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 com.dogecoin.dogecoinj.protocols.payments;
import java.security.cert.X509Certificate;
import java.util.List;
public class PaymentProtocolException extends Exception {
public PaymentProtocolException(String msg) {
super(msg);
}
public PaymentProtocolException(Exception e) {
super(e);
}
public static class Expired extends PaymentProtocolException {
public Expired(String msg) {
super(msg);
}
}
public static class InvalidPaymentRequestURL extends PaymentProtocolException {
public InvalidPaymentRequestURL(String msg) {
super(msg);
}
public InvalidPaymentRequestURL(Exception e) {
super(e);
}
}
public static class InvalidPaymentURL extends PaymentProtocolException {
public InvalidPaymentURL(Exception e) {
super(e);
}
public InvalidPaymentURL(String msg) {
super(msg);
}
}
public static class InvalidOutputs extends PaymentProtocolException {
public InvalidOutputs(String msg) {
super(msg);
}
}
public static class InvalidVersion extends PaymentProtocolException {
public InvalidVersion(String msg) {
super(msg);
}
}
public static class InvalidNetwork extends PaymentProtocolException {
public InvalidNetwork(String msg) {
super(msg);
}
}
public static class InvalidPkiType extends PaymentProtocolException {
public InvalidPkiType(String msg) {
super(msg);
}
}
public static class InvalidPkiData extends PaymentProtocolException {
public InvalidPkiData(String msg) {
super(msg);
}
public InvalidPkiData(Exception e) {
super(e);
}
}
public static class PkiVerificationException extends PaymentProtocolException {
public List<X509Certificate> certificates;
public PkiVerificationException(String msg) {
super(msg);
}
public PkiVerificationException(Exception e) {
super(e);
}
public PkiVerificationException(Exception e, List<X509Certificate> certificates) {
super(e);
this.certificates = certificates;
}
}
}

View File

@@ -0,0 +1,430 @@
/**
* 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 com.dogecoin.dogecoinj.protocols.payments;
import com.dogecoin.dogecoinj.protocols.payments.PaymentProtocol.PkiVerificationData;
import org.bitcoinj.core.*;
import org.bitcoinj.wallet.SendRequest;
import org.bitcoinj.crypto.TrustStoreLoader;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.uri.BitcoinURI;
import org.bitcoinj.utils.Threading;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.protobuf.InvalidProtocolBufferException;
import org.bitcoin.protocols.payments.Protos;
import javax.annotation.Nullable;
import java.io.*;
import java.net.*;
import java.security.KeyStoreException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
/**
* <p>Provides a standard implementation of the Payment Protocol (BIP 0070)</p>
*
* <p>A PaymentSession can be initialized from one of the following:</p>
*
* <ul>
* <li>A {@link BitcoinURI} object that conforms to BIP 0072</li>
* <li>A url where the {@link Protos.PaymentRequest} can be fetched</li>
* <li>Directly with a {@link Protos.PaymentRequest} object</li>
* </ul>
*
* <p>If initialized with a BitcoinURI or a url, a network request is made for the payment request object and a
* ListenableFuture is returned that will be notified with the PaymentSession object after it is downloaded.</p>
*
* <p>Once the PaymentSession is initialized, typically a wallet application will prompt the user to confirm that the
* amount and recipient are correct, perform any additional steps, and then construct a list of transactions to pass to
* the sendPayment method.</p>
*
* <p>Call sendPayment with a list of transactions that will be broadcast. A {@link Protos.Payment} message will be sent
* to the merchant if a payment url is provided in the PaymentRequest. NOTE: sendPayment does NOT broadcast the
* transactions to the bitcoin network. Instead it returns a ListenableFuture that will be notified when a
* {@link Protos.PaymentACK} is received from the merchant. Typically a wallet will show the message to the user
* as a confirmation message that the payment is now "processing" or that an error occurred, and then broadcast the
* tx itself later if needed.</p>
*
* @see <a href="https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki">BIP 0070</a>
*/
public class PaymentSession {
private static ListeningExecutorService executor = Threading.THREAD_POOL;
private NetworkParameters params;
private final TrustStoreLoader trustStoreLoader;
private Protos.PaymentRequest paymentRequest;
private Protos.PaymentDetails paymentDetails;
private Coin totalValue = Coin.ZERO;
/**
* 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;
/**
* <p>Returns a future that will be notified with a PaymentSession object after it is fetched using the provided uri.
* uri is a BIP-72-style BitcoinURI object that specifies where the {@link Protos.PaymentRequest} object may
* be fetched in the r= parameter.</p>
*
* <p>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.</p>
*/
public static ListenableFuture<PaymentSession> createFromBitcoinUri(final BitcoinURI uri) throws PaymentProtocolException {
return createFromBitcoinUri(uri, true, null);
}
/**
* Returns a future that will be notified with a PaymentSession object after it is fetched using the provided uri.
* uri is a BIP-72-style BitcoinURI object that specifies where the {@link Protos.PaymentRequest} object may
* be fetched in the r= parameter.
* If verifyPki is specified and 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.
*/
public static ListenableFuture<PaymentSession> createFromBitcoinUri(final BitcoinURI uri, final boolean verifyPki)
throws PaymentProtocolException {
return createFromBitcoinUri(uri, verifyPki, null);
}
/**
* Returns a future that will be notified with a PaymentSession object after it is fetched using the provided uri.
* uri is a BIP-72-style BitcoinURI object that specifies where the {@link Protos.PaymentRequest} object may
* be fetched in the r= parameter.
* If verifyPki is specified and 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.
* If trustStoreLoader is null, the system default trust store is used.
*/
public static ListenableFuture<PaymentSession> createFromBitcoinUri(final BitcoinURI uri, final boolean verifyPki, @Nullable final TrustStoreLoader trustStoreLoader)
throws PaymentProtocolException {
String url = uri.getPaymentRequestUrl();
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) {
throw new PaymentProtocolException.InvalidPaymentRequestURL(e);
}
}
/**
* Returns a future that will be notified with a PaymentSession object after it is fetched using the provided url.
* url is an address where the {@link Protos.PaymentRequest} object may be fetched.
* If verifyPki is specified and 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.
*/
public static ListenableFuture<PaymentSession> createFromUrl(final String url) throws PaymentProtocolException {
return createFromUrl(url, true, null);
}
/**
* Returns a future that will be notified with a PaymentSession object after it is fetched using the provided url.
* url is an address where the {@link Protos.PaymentRequest} object may be fetched.
* 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.
*/
public static ListenableFuture<PaymentSession> createFromUrl(final String url, final boolean verifyPki)
throws PaymentProtocolException {
return createFromUrl(url, verifyPki, null);
}
/**
* Returns a future that will be notified with a PaymentSession object after it is fetched using the provided url.
* url is an address where the {@link Protos.PaymentRequest} object may be fetched.
* 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.
* If trustStoreLoader is null, the system default trust store is used.
*/
public static ListenableFuture<PaymentSession> createFromUrl(final String url, final boolean verifyPki, @Nullable final TrustStoreLoader trustStoreLoader)
throws PaymentProtocolException {
if (url == null)
throw new PaymentProtocolException.InvalidPaymentRequestURL("null paymentRequestUrl");
try {
return fetchPaymentRequest(new URI(url), verifyPki, trustStoreLoader);
} catch(URISyntaxException e) {
throw new PaymentProtocolException.InvalidPaymentRequestURL(e);
}
}
private static ListenableFuture<PaymentSession> fetchPaymentRequest(final URI uri, final boolean verifyPki, @Nullable final TrustStoreLoader trustStoreLoader) {
return executor.submit(new Callable<PaymentSession>() {
@Override
public PaymentSession call() throws Exception {
HttpURLConnection connection = (HttpURLConnection)uri.toURL().openConnection();
connection.setRequestProperty("Accept", PaymentProtocol.MIMETYPE_PAYMENTREQUEST);
connection.setUseCaches(false);
Protos.PaymentRequest paymentRequest = Protos.PaymentRequest.parseFrom(connection.getInputStream());
return new PaymentSession(paymentRequest, verifyPki, trustStoreLoader);
}
});
}
/**
* Creates a PaymentSession from the provided {@link Protos.PaymentRequest}.
* Verifies PKI by default.
*/
public PaymentSession(Protos.PaymentRequest request) throws PaymentProtocolException {
this(request, true, null);
}
/**
* Creates a PaymentSession from the provided {@link Protos.PaymentRequest}.
* If verifyPki is true, also validates the signature and throws an exception if it fails.
*/
public PaymentSession(Protos.PaymentRequest request, boolean verifyPki) throws PaymentProtocolException {
this(request, verifyPki, null);
}
/**
* 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.
*/
public PaymentSession(Protos.PaymentRequest request, boolean verifyPki, @Nullable final TrustStoreLoader trustStoreLoader) throws PaymentProtocolException {
this.trustStoreLoader = trustStoreLoader != null ? trustStoreLoader : new TrustStoreLoader.DefaultTrustStoreLoader();
parsePaymentRequest(request);
if (verifyPki) {
try {
pkiVerificationData = PaymentProtocol.verifyPaymentRequestPki(request, this.trustStoreLoader.getKeyStore());
} catch (IOException x) {
throw new PaymentProtocolException(x);
} catch (KeyStoreException x) {
throw new PaymentProtocolException(x);
}
} else {
pkiVerificationData = null;
}
}
/**
* Returns the outputs of the payment request.
*/
public List<PaymentProtocol.Output> getOutputs() {
List<PaymentProtocol.Output> outputs = new ArrayList<PaymentProtocol.Output>(paymentDetails.getOutputsCount());
for (Protos.Output output : paymentDetails.getOutputsList()) {
Coin amount = output.hasAmount() ? Coin.valueOf(output.getAmount()) : null;
outputs.add(new PaymentProtocol.Output(amount, output.getScript().toByteArray()));
}
return outputs;
}
/**
* Returns the memo included by the merchant in the payment request, or null if not found.
*/
@Nullable public String getMemo() {
if (paymentDetails.hasMemo())
return paymentDetails.getMemo();
else
return null;
}
/**
* Returns the total amount of bitcoins requested.
*/
public Coin getValue() {
return totalValue;
}
/**
* Returns the date that the payment request was generated.
*/
public Date getDate() {
return new Date(paymentDetails.getTime() * 1000);
}
/**
* Returns the expires time of the payment request, or null if none.
*/
@Nullable public Date getExpires() {
if (paymentDetails.hasExpires())
return new Date(paymentDetails.getExpires() * 1000);
else
return null;
}
/**
* This should always be called before attempting to call sendPayment.
*/
public boolean isExpired() {
return paymentDetails.hasExpires() && System.currentTimeMillis() / 1000L > paymentDetails.getExpires();
}
/**
* 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())
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())
return paymentDetails.getMerchantData().toByteArray();
else
return null;
}
/**
* Returns a {@link Wallet.SendRequest} suitable for broadcasting to the network.
*/
public SendRequest getSendRequest() {
Transaction tx = new Transaction(params);
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);
}
/**
* Generates a Payment message and sends the payment to the merchant who sent the PaymentRequest.
* Provide transactions built by the wallet.
* NOTE: This does not broadcast the transactions to the bitcoin network, it merely sends a Payment message to the
* merchant confirming the payment.
* Returns an object wrapping PaymentACK once received.
* If the PaymentRequest did not specify a payment_url, returns null and does nothing.
* @param txns list of transactions to be included with the Payment message.
* @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<PaymentProtocol.Ack> sendPayment(List<Transaction> txns, @Nullable Address refundAddr, @Nullable String memo)
throws PaymentProtocolException, VerificationException, IOException {
Protos.Payment payment = getPayment(txns, refundAddr, memo);
if (payment == null)
return null;
if (isExpired())
throw new PaymentProtocolException.Expired("PaymentRequest is expired");
URL url;
try {
url = new URL(paymentDetails.getPaymentUrl());
} catch (MalformedURLException e) {
throw new PaymentProtocolException.InvalidPaymentURL(e);
}
return sendPayment(url, payment);
}
/**
* Generates a Payment message based on the information in the PaymentRequest.
* Provide transactions built by the wallet.
* If the PaymentRequest did not specify a payment_url, returns null.
* @param txns list of transactions to be included with the Payment message.
* @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<Transaction> txns, @Nullable Address refundAddr, @Nullable String memo)
throws IOException, PaymentProtocolException.InvalidNetwork {
if (paymentDetails.hasPaymentUrl()) {
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;
}
}
@VisibleForTesting
protected ListenableFuture<PaymentProtocol.Ack> sendPayment(final URL url, final Protos.Payment payment) {
return executor.submit(new Callable<PaymentProtocol.Ack>() {
@Override
public PaymentProtocol.Ack call() throws Exception {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", PaymentProtocol.MIMETYPE_PAYMENT);
connection.setRequestProperty("Accept", PaymentProtocol.MIMETYPE_PAYMENTACK);
connection.setRequestProperty("Content-Length", Integer.toString(payment.getSerializedSize()));
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
// Send request.
DataOutputStream outStream = new DataOutputStream(connection.getOutputStream());
payment.writeTo(outStream);
outStream.flush();
outStream.close();
// Get response.
Protos.PaymentACK paymentAck = Protos.PaymentACK.parseFrom(connection.getInputStream());
return PaymentProtocol.parsePaymentAck(paymentAck);
}
});
}
private void parsePaymentRequest(Protos.PaymentRequest request) throws PaymentProtocolException {
try {
if (request == null)
throw new PaymentProtocolException("request cannot be null");
if (request.getPaymentDetailsVersion() != 1)
throw new PaymentProtocolException.InvalidVersion("Version 1 required. Received version " + request.getPaymentDetailsVersion());
paymentRequest = request;
if (!request.hasSerializedPaymentDetails())
throw new PaymentProtocolException("No PaymentDetails");
paymentDetails = Protos.PaymentDetails.newBuilder().mergeFrom(request.getSerializedPaymentDetails()).build();
if (paymentDetails == null)
throw new PaymentProtocolException("Invalid PaymentDetails");
if (!paymentDetails.hasNetwork())
params = MainNetParams.get();
else
params = NetworkParameters.fromPmtProtocolID(paymentDetails.getNetwork());
if (params == null)
throw new PaymentProtocolException.InvalidNetwork("Invalid network " + paymentDetails.getNetwork());
if (paymentDetails.getOutputsCount() < 1)
throw new PaymentProtocolException.InvalidOutputs("No outputs");
for (Protos.Output output : paymentDetails.getOutputsList()) {
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)
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() {
return pkiVerificationData;
}
/** 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. */
public Protos.PaymentRequest getPaymentRequest() {
return paymentRequest;
}
/** Returns the protobuf that describes the payment to be made. */
public Protos.PaymentDetails getPaymentDetails() {
return paymentDetails;
}
}

View File

@@ -0,0 +1,5 @@
/**
* The BIP70 payment protocol wraps Bitcoin transactions and adds various useful features like memos, refund addresses
* and authentication.
*/
package com.dogecoin.dogecoinj.protocols.payments;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,305 @@
/**
* Copyright 2011 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
*
* 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.libdohj.core.AltcoinNetworkParameters;
import com.google.common.base.Preconditions;
import javax.annotation.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.util.BitSet;
import java.util.List;
import static org.bitcoinj.core.Coin.FIFTY_COINS;
import org.libdohj.core.ScryptHash;
import static org.libdohj.core.Utils.scryptDigest;
import static org.bitcoinj.core.Utils.reverseBytes;
import org.libdohj.core.AuxPoWNetworkParameters;
/**
* <p>A block is a group of transactions, and is one of the fundamental data structures of the Bitcoin system.
* It records a set of {@link Transaction}s together with some data that links it into a place in the global block
* chain, and proves that a difficult calculation was done over its contents. See
* <a href="http://www.bitcoin.org/bitcoin.pdf">the Bitcoin technical paper</a> for
* more detail on blocks. <p/>
*
* To get a block, you can either build one from the raw bytes you can get from another implementation, or request one
* specifically using {@link Peer#getBlock(Sha256Hash)}, or grab one from a downloaded {@link BlockChain}.
*/
public class AltcoinBlock extends org.bitcoinj.core.Block {
private static final int BYTE_BITS = 8;
private boolean auxpowParsed = false;
private boolean auxpowBytesValid = false;
/** AuxPoW header element, if applicable. */
@Nullable private AuxPoW auxpow;
/**
* Whether the chain this block belongs to support AuxPoW, used to avoid
* repeated instanceof checks. Initialised in parseTransactions()
*/
private boolean auxpowChain = false;
private ScryptHash scryptHash;
/** Special case constructor, used for the genesis node, cloneAsHeader and unit tests.
* @param params NetworkParameters object.
*/
public AltcoinBlock(final NetworkParameters params, final long version) {
super(params, version);
}
/** Special case constructor, used for the genesis node, cloneAsHeader and unit tests.
* @param params NetworkParameters object.
*/
public AltcoinBlock(final NetworkParameters params, final byte[] payloadBytes) {
this(params, payloadBytes, 0, params.getDefaultSerializer(), payloadBytes.length);
}
/**
* Construct a block object from the Bitcoin wire format.
* @param params NetworkParameters object.
* @param serializer the serializer to use for this message.
* @param length The length of message if known. Usually this is provided when deserializing of the wire
* as the length will be provided as part of the header. If unknown then set to Message.UNKNOWN_LENGTH
* @throws ProtocolException
*/
public AltcoinBlock(final NetworkParameters params, final byte[] payloadBytes,
final int offset, final MessageSerializer serializer, final int length)
throws ProtocolException {
super(params, payloadBytes, offset, serializer, length);
}
public AltcoinBlock(NetworkParameters params, byte[] payloadBytes, int offset,
Message parent, MessageSerializer serializer, int length)
throws ProtocolException {
super(params, payloadBytes, serializer, length);
}
/**
* Construct a block initialized with all the given fields.
* @param params Which network the block is for.
* @param version This should usually be set to 1 or 2, depending on if the height is in the coinbase input.
* @param prevBlockHash Reference to previous block in the chain or {@link Sha256Hash#ZERO_HASH} if genesis.
* @param merkleRoot The root of the merkle tree formed by the transactions.
* @param time UNIX time when the block was mined.
* @param difficultyTarget Number which this block hashes lower than.
* @param nonce Arbitrary number to make the block hash lower than the target.
* @param transactions List of transactions including the coinbase.
*/
public AltcoinBlock(NetworkParameters params, long version, Sha256Hash prevBlockHash, Sha256Hash merkleRoot, long time,
long difficultyTarget, long nonce, List<Transaction> transactions) {
super(params, version, prevBlockHash, merkleRoot, time, difficultyTarget, nonce, transactions);
}
private ScryptHash calculateScryptHash() {
try {
ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(HEADER_SIZE);
writeHeader(bos);
return new ScryptHash(reverseBytes(scryptDigest(bos.toByteArray())));
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.
} catch (GeneralSecurityException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
public AuxPoW getAuxPoW() {
return this.auxpow;
}
public void setAuxPoW(AuxPoW auxpow) {
this.auxpow = auxpow;
}
/**
* Returns the Scrypt hash of the block (which for a valid, solved block should be
* below the target). Big endian.
*/
public ScryptHash getScryptHash() {
if (scryptHash == null)
scryptHash = calculateScryptHash();
return scryptHash;
}
/**
* Returns the Scrypt hash of the block.
*/
public String getScryptHashAsString() {
return getScryptHash().toString();
}
@Override
public Coin getBlockInflation(int height) {
final AltcoinNetworkParameters altParams = (AltcoinNetworkParameters) params;
return altParams.getBlockSubsidy(height);
}
/**
* Get the chain ID (upper 16 bits) from an AuxPoW version number.
*/
public static long getChainID(final long rawVersion) {
return rawVersion >> 16;
}
/**
* Return chain ID from block version of an AuxPoW-enabled chain.
*/
public long getChainID() {
return getChainID(this.getRawVersion());
}
/**
* Return flags from block version of an AuxPoW-enabled chain.
*
* @return flags as a bitset.
*/
public BitSet getVersionFlags() {
final BitSet bitset = new BitSet(BYTE_BITS);
final int bits = (int) (this.getRawVersion() & 0xff00) >> 8;
for (int bit = 0; bit < BYTE_BITS; bit++) {
if ((bits & (1 << bit)) > 0) {
bitset.set(bit);
}
}
return bitset;
}
/**
* Return block version without applying any filtering (i.e. for AuxPoW blocks
* which structure version differently to pack in additional data).
*/
public final long getRawVersion() {
return super.getVersion();
}
/**
* Get the base version (i.e. Bitcoin-like version number) out of a packed
* AuxPoW version number (i.e. one that contains chain ID and feature flags).
*/
public static long getBaseVersion(final long rawVersion) {
return rawVersion & 0xff;
}
@Override
public long getVersion() {
// TODO: Can we cache the individual parts on parse?
if (this.params instanceof AltcoinNetworkParameters) {
// AuxPoW networks use the higher block version bits for flags and
// chain ID.
return getBaseVersion(super.getVersion());
} else {
return super.getVersion();
}
}
protected void parseAuxPoW() throws ProtocolException {
if (this.auxpowParsed)
return;
this.auxpow = null;
if (this.auxpowChain) {
final AuxPoWNetworkParameters auxpowParams = (AuxPoWNetworkParameters)this.params;
if (auxpowParams.isAuxPoWBlockVersion(this.getRawVersion())
&& payload.length >= 160) { // We have at least 2 headers in an Aux block. Workaround for StoredBlocks
this.auxpow = new AuxPoW(params, payload, cursor, this, serializer);
}
}
this.auxpowParsed = true;
this.auxpowBytesValid = serializer.isParseRetainMode();
}
@Override
protected void parseTransactions(final int offset) {
this.auxpowChain = params instanceof AuxPoWNetworkParameters;
parseAuxPoW();
if (null != this.auxpow) {
super.parseTransactions(offset + auxpow.getMessageSize());
optimalEncodingMessageSize += auxpow.getMessageSize();
} else {
super.parseTransactions(offset);
}
}
@Override
void writeHeader(OutputStream stream) throws IOException {
super.writeHeader(stream);
if (null != this.auxpow) {
this.auxpow.bitcoinSerialize(stream);
}
}
/** Returns a copy of the block, but without any transactions. */
@Override
public Block cloneAsHeader() {
AltcoinBlock block = new AltcoinBlock(params, getRawVersion());
super.copyBitcoinHeaderTo(block);
block.auxpow = auxpow;
return block;
}
/** Returns true if the hash of the block is OK (lower than difficulty target). */
protected boolean checkProofOfWork(boolean throwException) throws VerificationException {
if (params instanceof AltcoinNetworkParameters) {
BigInteger target = getDifficultyTargetAsInteger();
if (params instanceof AuxPoWNetworkParameters) {
final AuxPoWNetworkParameters auxParams = (AuxPoWNetworkParameters)this.params;
if (auxParams.isAuxPoWBlockVersion(getRawVersion()) && null != auxpow) {
return auxpow.checkProofOfWork(this.getHash(), target, throwException);
}
}
final AltcoinNetworkParameters altParams = (AltcoinNetworkParameters)this.params;
BigInteger h = altParams.getBlockDifficultyHash(this).toBigInteger();
if (h.compareTo(target) > 0) {
// Proof of work check failed!
if (throwException)
throw new VerificationException("Hash is higher than target: " + getHashAsString() + " vs "
+ target.toString(16));
else
return false;
}
return true;
} else {
return super.checkProofOfWork(throwException);
}
}
/**
* Checks the block data to ensure it follows the rules laid out in the network parameters. Specifically,
* throws an exception if the proof of work is invalid, or if the timestamp is too far from what it should be.
* This is <b>not</b> everything that is required for a block to be valid, only what is checkable independent
* of the chain and without a transaction index.
*
* @throws VerificationException
*/
@Override
public void verifyHeader() throws VerificationException {
super.verifyHeader();
}
}

View File

@@ -0,0 +1,469 @@
/**
* Copyright 2011 Google Inc.
* Copyright 2014 Andreas Schildbach
* Copyright 2015 J. Ross Nicoll
*
* 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.libdohj.core.AuxPoWNetworkParameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.*;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.*;
/**
* <p>An AuxPoW header wraps a block header from another coin, enabling the foreign
* chain's proof of work to be used for this chain as well. <b>Note: </b>
* NetworkParameters for AuxPoW networks <b>must</b> implement AltcoinNetworkParameters
* in order for AuxPoW to work.</p>
*/
public class AuxPoW extends ChildMessage {
public static final byte[] MERGED_MINING_HEADER = new byte[] {
(byte) 0xfa, (byte) 0xbe, "m".getBytes()[0], "m".getBytes()[0]
};
/**
* Maximum index of the merkle root hash in the coinbase transaction script,
* where no merged mining header is present.
*/
protected static final int MAX_INDEX_PC_BACKWARDS_COMPATIBILITY = 20;
private static final Logger log = LoggerFactory.getLogger(AuxPoW.class);
private static final long serialVersionUID = -8567546957352643140L;
private Transaction transaction;
private Sha256Hash hashBlock;
private MerkleBranch coinbaseBranch;
private MerkleBranch chainMerkleBranch;
private AltcoinBlock parentBlockHeader;
// Transactions can be encoded in a way that will use more bytes than is optimal
// (due to VarInts having multiple encodings)
// MAX_BLOCK_SIZE must be compared to the optimal encoding, not the actual encoding, so when parsing, we keep track
// of the size of the ideal encoding in addition to the actual message size (which Message needs) so that Blocks
// can properly keep track of optimal encoded size
private transient int optimalEncodingMessageSize;
public AuxPoW(NetworkParameters params, @Nullable Message parent) {
super(params);
transaction = new Transaction(params);
hashBlock = Sha256Hash.ZERO_HASH;
coinbaseBranch = new MerkleBranch(params, this);
chainMerkleBranch = new MerkleBranch(params, this);
parentBlockHeader = null;
}
/**
* Creates an AuxPoW header by reading payload starting from offset bytes in. Length of header is fixed.
* @param params NetworkParameters object.
* @param payload Bitcoin protocol formatted byte array containing message content.
* @param offset The location of the first payload byte within the array.
* @param parent The message element which contains this header.
* @param serializer the serializer to use for this message.
* @throws ProtocolException
*/
public AuxPoW(NetworkParameters params, byte[] payload, int offset, Message parent, MessageSerializer serializer)
throws ProtocolException {
super(params, payload, offset, parent, serializer, Message.UNKNOWN_LENGTH);
}
/**
* Creates an AuxPoW header by reading payload starting from offset bytes in. Length of header is fixed.
*
* @param params NetworkParameters object.
* @param payload Bitcoin protocol formatted byte array containing message content.
* @param parent The message element which contains this header.
* @param serializer the serializer to use for this message.
*/
public AuxPoW(NetworkParameters params, byte[] payload, @Nullable Message parent, MessageSerializer serializer)
throws ProtocolException {
super(params, payload, 0, parent, serializer, Message.UNKNOWN_LENGTH);
}
protected static int calcLength(byte[] buf, int offset) {
VarInt varint;
// jump past transaction
int cursor = offset + Transaction.calcLength(buf, offset);
// jump past header hash
cursor += 4;
// Coin base branch
cursor += MerkleBranch.calcLength(buf, offset);
// Block chain branch
cursor += MerkleBranch.calcLength(buf, offset);
// Block header
cursor += Block.HEADER_SIZE;
return cursor - offset + 4;
}
@Override
protected void parse() throws ProtocolException {
cursor = offset;
transaction = new Transaction(params, payload, cursor, this, serializer, Message.UNKNOWN_LENGTH);
cursor += transaction.getOptimalEncodingMessageSize();
optimalEncodingMessageSize = transaction.getOptimalEncodingMessageSize();
hashBlock = readHash();
optimalEncodingMessageSize += 32; // Add the hash size to the optimal encoding
coinbaseBranch = new MerkleBranch(params, this, payload, cursor, serializer);
cursor += coinbaseBranch.getOptimalEncodingMessageSize();
optimalEncodingMessageSize += coinbaseBranch.getOptimalEncodingMessageSize();
chainMerkleBranch = new MerkleBranch(params, this, payload, cursor, serializer);
cursor += chainMerkleBranch.getOptimalEncodingMessageSize();
optimalEncodingMessageSize += chainMerkleBranch.getOptimalEncodingMessageSize();
// Make a copy of JUST the contained block header, so the block parser doesn't try reading
// transactions past the end
byte[] blockBytes = Arrays.copyOfRange(payload, cursor, cursor + Block.HEADER_SIZE);
cursor += Block.HEADER_SIZE;
parentBlockHeader = new AltcoinBlock(params, blockBytes, 0, this, serializer, Block.HEADER_SIZE);
length = cursor - offset;
}
public int getOptimalEncodingMessageSize() {
if (optimalEncodingMessageSize != 0)
return optimalEncodingMessageSize;
optimalEncodingMessageSize = getMessageSize();
return optimalEncodingMessageSize;
}
@Override
public String toString() {
return toString(null);
}
/**
* A human readable version of the transaction useful for debugging. The format is not guaranteed to be stable.
* @param chain If provided, will be used to estimate lock times (if set). Can be null.
*/
public String toString(@Nullable AbstractBlockChain chain) {
return transaction.toString(chain);
}
@Override
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
transaction.bitcoinSerialize(stream);
stream.write(Utils.reverseBytes(hashBlock.getBytes()));
coinbaseBranch.bitcoinSerialize(stream);
chainMerkleBranch.bitcoinSerialize(stream);
parentBlockHeader.bitcoinSerializeToStream(stream);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AuxPoW input = (AuxPoW) o;
if (!transaction.equals(input.transaction)) return false;
if (!hashBlock.equals(input.hashBlock)) return false;
if (!coinbaseBranch.equals(input.coinbaseBranch)) return false;
if (!chainMerkleBranch.equals(input.chainMerkleBranch)) return false;
if (!parentBlockHeader.equals(input.parentBlockHeader)) return false;
return getHash().equals(input.getHash());
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + transaction.hashCode();
result = 31 * result + hashBlock.hashCode();
result = 31 * result + coinbaseBranch.hashCode();
result = 31 * result + chainMerkleBranch.hashCode();
result = 31 * result + parentBlockHeader.hashCode();
return result;
}
/**
* Get the block header from the parent blockchain. The hash of the header
* is the value which should match the difficulty target. Note that blocks are
* not necessarily part of the parent blockchain, they simply must be valid
* blocks at the difficulty of the child blockchain.
*/
public AltcoinBlock getParentBlockHeader() {
return parentBlockHeader;
}
/**
* Get the coinbase transaction from the AuxPoW header. This should contain a
* reference back to the block hash in its input scripts, to prove that the
* transaction was created after the block.
*/
public Transaction getCoinbase() {
return transaction;
}
/**
* Get the Merkle branch used to connect the AuXPow header with this blockchain.
*/
public MerkleBranch getChainMerkleBranch() {
return chainMerkleBranch;
}
/**
* Get the Merkle branch used to connect the coinbase transaction with this blockchain.
*/
public MerkleBranch getCoinbaseBranch() {
return coinbaseBranch;
}
/**
* Check the proof of work for this AuxPoW header meets the target
* difficulty.
*
* @param hashAuxBlock hash of the block the AuxPoW header is attached to.
* @param target the difficulty target after decoding from compact bits.
*/
protected boolean checkProofOfWork(Sha256Hash hashAuxBlock,
BigInteger target, boolean throwException) throws VerificationException {
if (!(params instanceof AuxPoWNetworkParameters)) {
if (throwException) {
// Should be impossible
throw new VerificationException("Network parameters are not an instance of AuxPoWNetworkParameters, AuxPoW support is not available.");
}
return false;
}
final AuxPoWNetworkParameters altcoinParams = (AuxPoWNetworkParameters) params;
if (0 != this.getCoinbaseBranch().getIndex()) {
if (throwException) {
// I don't like the message, but it correlates with what's in the reference client.
throw new VerificationException("AuxPow is not a generate");
}
return false;
}
if (!altcoinParams.isTestNet()
&& parentBlockHeader.getChainID() == altcoinParams.getChainID()) {
if (throwException) {
throw new VerificationException("Aux POW parent has our chain ID");
}
return false;
}
if (this.getChainMerkleBranch().size() > 30) {
if (throwException) {
throw new VerificationException("Aux POW chain merkle branch too long");
}
return false;
}
Sha256Hash nRootHash = getChainMerkleBranch().calculateMerkleRoot(hashAuxBlock);
final byte[] vchRootHash = nRootHash.getBytes();
// Check that the coinbase transaction is in the merkle tree of the
// parent block header
if (!getCoinbaseBranch().calculateMerkleRoot(getCoinbase().getHash()).equals(parentBlockHeader.getMerkleRoot())) {
if (throwException) {
throw new VerificationException("Aux POW merkle root incorrect");
}
return false;
}
if (this.getCoinbase().getInputs().isEmpty()) {
throw new VerificationException("Coinbase transaction has no inputs");
}
// Check that the chain merkle root is in the coinbase
final byte[] script = this.getCoinbase().getInput(0).getScriptBytes();
// Check that the same work is not submitted twice to our chain, by
// confirming that the child block hash is in the coinbase merkle tree
int pcHead = -1;
int pc = -1;
for (int scriptIdx = 0; scriptIdx < script.length; scriptIdx++) {
if (arrayMatch(script, scriptIdx, MERGED_MINING_HEADER)) {
// Enforce only one chain merkle root by checking that a single instance of the merged
// mining header exists just before.
if (pcHead >= 0) {
if (throwException) {
throw new VerificationException("Multiple merged mining headers in coinbase");
}
return false;
}
pcHead = scriptIdx;
} else if (arrayMatch(script, scriptIdx, vchRootHash)) {
pc = scriptIdx;
}
}
if (pc == -1) {
if (throwException) {
throw new VerificationException("Aux POW missing chain merkle root in parent coinbase");
}
return false;
}
if (pcHead != -1) {
if (pcHead + MERGED_MINING_HEADER.length != pc) {
if (throwException) {
throw new VerificationException("Merged mining header is not just before chain merkle root");
}
return false;
}
} else {
// For backward compatibility.
// Enforce only one chain merkle root by checking that it starts early in the coinbase.
// 8-12 bytes are enough to encode extraNonce and nBits.
if (pc > MAX_INDEX_PC_BACKWARDS_COMPATIBILITY) {
if (throwException) {
throw new VerificationException("Aux POW chain merkle root must start in the first 20 bytes of the parent coinbase");
}
return false;
}
}
// Ensure we are at a deterministic point in the merkle leaves by hashing
// a nonce and our chain ID and comparing to the index.
pc += vchRootHash.length;
if ((script.length - pc) < 8) {
if (throwException) {
throw new VerificationException("Aux POW missing chain merkle tree size and nonce in parent coinbase");
}
return false;
}
byte[] sizeBytes = Utils.reverseBytes(Arrays.copyOfRange(script, pc, pc + 4));
int branchSize = ByteBuffer.wrap(sizeBytes).getInt();
if (branchSize != (1 << getChainMerkleBranch().size())) {
if (throwException) {
throw new VerificationException("Aux POW merkle branch size does not match parent coinbase");
}
return false;
}
long nonce = getNonceFromScript(script, pc);
if (getChainMerkleBranch().getIndex() != getExpectedIndex(nonce, ((AuxPoWNetworkParameters) params).getChainID(), getChainMerkleBranch().size())) {
if (throwException) {
throw new VerificationException("Aux POW wrong index in chain merkle branch for chain ID "
+ ((AuxPoWNetworkParameters) params).getChainID() + ". Was "
+ getChainMerkleBranch().getIndex() + ", expected "
+ getExpectedIndex(nonce, ((AuxPoWNetworkParameters) params).getChainID(), getChainMerkleBranch().size()));
}
return false;
}
Sha256Hash hash = altcoinParams.getBlockDifficultyHash(getParentBlockHeader());
BigInteger hashVal = hash.toBigInteger();
if (hashVal.compareTo(target) > 0) {
// Proof of work check failed!
if (throwException) {
throw new VerificationException("Hash is higher than target: " + hash.toString() + " vs "
+ target.toString(16));
}
return false;
}
return true;
}
/**
* Get the nonce value from the coinbase transaction script.
*
* @param script the transaction script to extract the nonce from.
* @param pc offset of the merkle branch size within the script (this is 4
* bytes before the start of the nonce value). Range checks should be
* performed before calling this method.
* @return the nonce value.
*/
protected static long getNonceFromScript(final byte[] script, int pc) {
// Note that the nonce value is packed as platform order (typically
// little-endian) so we have to convert to big-endian for Java
final byte[] nonceBytes = Utils.reverseBytes(Arrays.copyOfRange(script, pc + 4, pc + 8));
return ByteBuffer.wrap(nonceBytes).getInt() & 0xffffffffl;
}
/**
* Get the expected index of the slot within the chain merkle tree.
*
* This prevents the same work from being used twice for the
* same chain while reducing the chance that two chains clash
* for the same slot.
*/
protected static int getExpectedIndex(final long nonce, final int chainId, final int merkleHeight) {
// Choose a pseudo-random slot in the chain merkle tree
// but have it be fixed for a size/nonce/chain combination.
// We do most of the maths with a signed 32 bit integer, as the operation is
// the same as the 32 unsigned integer that the reference version uses
int rand = (int) nonce;
rand = rand * 1103515245 + 12345;
rand += chainId;
rand = rand * 1103515245 + 12345;
// At this point, we need to flip the value to its positive version,
// so we switch to a 64 bit signed integer for the last calculations
long longRand = rand & 0xffffffffl;
longRand %= (1 << merkleHeight);
return (int) longRand;
}
public Transaction getTransaction() {
return transaction;
}
/**
* Test whether one array is at a specific offset within the other.
*
* @param script the longer array to test for containing another array.
* @param offset the offset to start at within the larger array.
* @param subArray the shorter array to test for presence in the longer array.
* @return true if the shorter array is present at the offset, false otherwise.
*/
static boolean arrayMatch(byte[] script, int offset, byte[] subArray) {
int matchIdx;
for (matchIdx = 0; matchIdx + offset < script.length && matchIdx < subArray.length; matchIdx++) {
if (script[offset + matchIdx] != subArray[matchIdx]) {
return false;
}
}
return matchIdx == subArray.length;
}
/**
* Set the merkle branch used to connect the coinbase transaction to the
* parent block header.
*/
public void setCoinbaseBranch(final MerkleBranch merkleBranch) {
this.coinbaseBranch = merkleBranch;
}
/**
* Set the parent chain block header.
*/
public void setParentBlockHeader(final AltcoinBlock header) {
this.parentBlockHeader = header;
}
}

View File

@@ -0,0 +1,222 @@
/**
* Copyright 2011 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
*
* 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 javax.annotation.Nullable;
import java.io.IOException;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A Merkle branch contains the hashes from a leaf of a Merkle tree
* up to its root, plus a bitset used to define how the hashes are applied.
* Given the hash of the leaf, this can be used to calculate the tree
* root. This is useful for proving that a leaf belongs to a given tree.
*
* TODO: Has a lot of similarity to PartialMerkleTree, should attempt to merge
* the two.
*/
public class MerkleBranch extends ChildMessage {
private static final long serialVersionUID = 2;
// Merkle branches can be encoded in a way that will use more bytes than is optimal
// (due to VarInts having multiple encodings)
// MAX_BLOCK_SIZE must be compared to the optimal encoding, not the actual encoding, so when parsing, we keep track
// of the size of the ideal encoding in addition to the actual message size (which Message needs) so that Blocks
// can properly keep track of optimal encoded size
private transient int optimalEncodingMessageSize;
private List<Sha256Hash> hashes;
private long index;
public MerkleBranch(NetworkParameters params, @Nullable ChildMessage parent) {
super(params);
setParent(parent);
this.hashes = new ArrayList<Sha256Hash>();
this.index = 0;
}
/**
* Deserializes an input message. This is usually part of a merkle branch message.
*/
public MerkleBranch(NetworkParameters params, @Nullable ChildMessage parent, byte[] payload, int offset) throws ProtocolException {
super(params, payload, offset);
setParent(parent);
}
/**
* Deserializes an input message. This is usually part of a merkle branch message.
* @param params NetworkParameters object.
* @param payload Bitcoin protocol formatted byte array containing message content.
* @param offset The location of the first payload byte within the array.
* @param serializer the serializer to use for this message.
* @throws ProtocolException
*/
public MerkleBranch(NetworkParameters params, ChildMessage parent, byte[] payload, int offset,
MessageSerializer serializer)
throws ProtocolException {
super(params, payload, offset, parent, serializer, UNKNOWN_LENGTH);
}
public MerkleBranch(NetworkParameters params, @Nullable ChildMessage parent,
final List<Sha256Hash> hashes, final long branchSideMask) {
super(params);
setParent(parent);
this.hashes = hashes;
this.index = branchSideMask;
}
public static int calcLength(byte[] buf, int offset) {
VarInt varint = new VarInt(buf, offset);
return ((int) varint.value) * 4 + 4;
}
@Override
protected void parse() throws ProtocolException {
cursor = offset;
final int hashCount = (int) readVarInt();
optimalEncodingMessageSize += VarInt.sizeOf(hashCount);
hashes = new ArrayList<Sha256Hash>(hashCount);
for (int hashIdx = 0; hashIdx < hashCount; hashIdx++) {
hashes.add(readHash());
}
optimalEncodingMessageSize += 32 * hashCount;
setIndex(readUint32());
optimalEncodingMessageSize += 4;
length = cursor - offset;
}
@Override
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
stream.write(new VarInt(hashes.size()).encode());
for (Sha256Hash hash: hashes) {
stream.write(Utils.reverseBytes(hash.getBytes()));
}
Utils.uint32ToByteStreamLE(index, stream);
}
/**
* Calculate the merkle branch root based on the supplied hashes and the given leaf hash.
* Used to verify that the given leaf and root are part of the same tree.
*/
public Sha256Hash calculateMerkleRoot(final Sha256Hash leaf) {
byte[] target = leaf.getReversedBytes();
long mask = index;
MessageDigest digest = Sha256Hash.newDigest();
for (Sha256Hash hash: hashes) {
digest.reset();
if ((mask & 1) == 0) { // 0 means it goes on the right
digest.update(target);
digest.update(hash.getReversedBytes());
} else {
digest.update(hash.getReversedBytes());
digest.update(target);
}
// Double-digest the values
target = digest.digest();
digest.reset();
target = digest.digest(target);
mask >>= 1;
}
return Sha256Hash.wrapReversed(target);
}
/**
* Get the hashes which make up this branch.
*/
public List<Sha256Hash> getHashes() {
return Collections.unmodifiableList(this.hashes);
}
/**
* Return the mask used to determine which side the hashes are applied on.
* Each bit represents a hash. Zero means it goes on the right, one means
* on the left.
*/
public long getIndex() {
return index;
}
/**
* @param hashes the hashes to set
*/
public void setHashes(List<Sha256Hash> hashes) {
this.hashes = hashes;
}
/**
* Set the mask used to determine the sides in which hashes are applied.
*/
public void setIndex(final long newIndex) {
assert newIndex >= 0;
this.index = newIndex;
}
/**
* Get the number of hashes in this branch.
*/
public int size() {
return hashes.size();
}
public int getOptimalEncodingMessageSize() {
if (optimalEncodingMessageSize != 0)
return optimalEncodingMessageSize;
if (optimalEncodingMessageSize != 0)
return optimalEncodingMessageSize;
optimalEncodingMessageSize = getMessageSize();
return optimalEncodingMessageSize;
}
/**
* Returns a human readable debug string.
*/
@Override
public String toString() {
return "Merkle branch";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MerkleBranch input = (MerkleBranch) o;
if (!hashes.equals(input.hashes)) return false;
if (index != input.index) return false;
return true;
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + hashes.hashCode();
result = 31 * result + (int) index;
return result;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2015 Ross Nicoll
*
* 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.libdohj.core;
import org.bitcoinj.core.Block;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Sha256Hash;
/**
*
* @author Ross Nicoll
*/
public interface AltcoinNetworkParameters {
/**
* Get the hash for the given block, for comparing against target difficulty.
* This provides an extension hook for networks which use a hash other than
* SHA256 twice (Bitcoin standard) for proof of work.
*/
Sha256Hash getBlockDifficultyHash(Block block);
public boolean isTestNet();
/**
* Get the subsidy (i.e. maximum number of coins that can be generated
* by the coinbase transaction) for a block at the given height.
*/
public Coin getBlockSubsidy(final int height);
}

View File

@@ -0,0 +1,49 @@
package org.libdohj.core;
import org.bitcoinj.core.*;
import org.bitcoinj.core.Utils;
/**
* @author jrn
*/
public class AltcoinSerializer extends BitcoinSerializer {
public AltcoinSerializer(NetworkParameters params, boolean parseRetain) {
super(params, parseRetain);
}
@Override
public Block makeBlock(final byte[] payloadBytes, final int offset, final int length) throws ProtocolException {
return new AltcoinBlock(getParameters(), payloadBytes, offset, this, length);
}
@Override
public FilteredBlock makeFilteredBlock(byte[] payloadBytes) throws ProtocolException {
long blockVersion = Utils.readUint32(payloadBytes, 0);
int headerSize = Block.HEADER_SIZE;
byte[] headerBytes = new byte[Block.HEADER_SIZE + 1];
System.arraycopy(payloadBytes, 0, headerBytes, 0, headerSize);
headerBytes[80] = 0; // Need to provide 0 transactions so the block header can be constructed
if (this.getParameters() instanceof AuxPoWNetworkParameters) {
final AuxPoWNetworkParameters auxPoWParams = (AuxPoWNetworkParameters) this.getParameters();
if (auxPoWParams.isAuxPoWBlockVersion(blockVersion)) {
final AltcoinBlock header = (AltcoinBlock) makeBlock(headerBytes, 0, Message.UNKNOWN_LENGTH);
final AuxPoW auxpow = new AuxPoW(this.getParameters(), payloadBytes, Block.HEADER_SIZE, null, this);
header.setAuxPoW(auxpow);
int pmtOffset = headerSize + auxpow.getMessageSize();
int pmtLength = payloadBytes.length - pmtOffset;
byte[] pmtBytes = new byte[pmtLength];
System.arraycopy(payloadBytes, pmtOffset, pmtBytes, 0, pmtLength);
PartialMerkleTree pmt = new PartialMerkleTree(this.getParameters(), pmtBytes, 0);
return new FilteredBlock(this.getParameters(), header, pmt);
}
}
// We are either not in AuxPoW mode, or the block is not an AuxPoW block.
return super.makeFilteredBlock(payloadBytes);
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2015 Ross Nicoll
*
* 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.libdohj.core;
/**
*
* @author Ross Nicoll
*/
public interface AuxPoWNetworkParameters extends AltcoinNetworkParameters {
boolean isAuxPoWBlockVersion(long version);
int getChainID();
}

View File

@@ -0,0 +1,35 @@
/**
* Copyright 2015 Ross Nicoll
*
* 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.libdohj.core;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Sha256Hash;
/**
* Scrypt hash. Currently extends Sha256Hash (so no real type safety is provided),
* but in time the two classes should have a common superclass rather than one
* extending the other directly.
*/
public class ScryptHash extends Sha256Hash {
public ScryptHash(byte[] rawHashBytes) {
super(rawHashBytes);
}
public ScryptHash(String hexString) {
super(hexString);
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright 2011 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
*
* 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.libdohj.core;
import com.lambdaworks.crypto.SCrypt;
import java.security.GeneralSecurityException;
/**
*
*/
public class Utils {
/**
* Calculates the Scrypt hash of the given byte range.
* The resulting hash is in small endian form.
*/
public static byte[] scryptDigest(byte[] input) throws GeneralSecurityException {
return SCrypt.scrypt(input, input, 1024, 1, 1, 32);
}
}

View File

@@ -0,0 +1,468 @@
/*
* 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.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.script.Script;
import org.bitcoinj.script.ScriptOpCodes;
import org.bitcoinj.store.BlockStore;
import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.utils.MonetaryFormat;
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;
/**
* Common parameters for Dogecoin networks.
*/
public abstract class AbstractDogecoinParams extends NetworkParameters implements AuxPoWNetworkParameters {
/** Standard format for the DOGE denomination. */
public static final MonetaryFormat DOGE;
/** Standard format for the mDOGE denomination. */
public static final MonetaryFormat MDOGE;
/** 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
public static final int AUXPOW_CHAIN_ID = 0x0062; // 98
public static final int DOGE_TARGET_TIMESPAN = 4 * 60 * 60; // 4 hours per difficulty cycle, on average.
public static final int DOGE_TARGET_TIMESPAN_NEW = 60; // 60s per difficulty cycle, on average. Kicks in after block 145k.
public static final int DOGE_TARGET_SPACING = 1 * 60; // 1 minute per block.
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. */
public static final String CODE_DOGE = "DOGE";
/** Currency code for base 1/1,000 Dogecoin. */
public static final String CODE_MDOGE = "mDOGE";
/** 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;
private static final int BLOCK_VERSION_FLAG_AUXPOW = 0x00000100;
static {
DOGE = MonetaryFormat.BTC.noCode()
.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. */
public static final String ID_DOGE_MAINNET = "org.dogecoin.production";
/** The string returned by getId() for the testnet. */
public static final String ID_DOGE_TESTNET = "org.dogecoin.test";
protected final int newInterval;
protected final int newTargetTimespan;
protected final int diffChangeTarget;
protected Logger log = LoggerFactory.getLogger(AbstractDogecoinParams.class);
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 STABLE_SUBSIDY = COIN.multiply(10000);
public AbstractDogecoinParams(final int setDiffChangeTarget) {
super();
genesisBlock = createGenesis(this);
interval = DOGE_INTERVAL;
newInterval = DOGE_INTERVAL_NEW;
targetTimespan = DOGE_TARGET_TIMESPAN;
newTargetTimespan = DOGE_TARGET_TIMESPAN_NEW;
maxTarget = Utils.decodeCompactBits(0x1e0fffffL);
diffChangeTarget = setDiffChangeTarget;
packetMagic = 0xc0c0c0c0;
bip32HeaderPub = 0x0488C42E; //The 4 byte header that serializes in base58 to "xpub". (?)
bip32HeaderPriv = 0x0488E1F4; //The 4 byte header that serializes in base58 to "xprv" (?)
}
private static AltcoinBlock createGenesis(NetworkParameters params) {
AltcoinBlock genesisBlock = new AltcoinBlock(params, Block.BLOCK_VERSION_GENESIS);
Transaction t = new Transaction(params);
try {
byte[] bytes = Utils.HEX.decode
("04ffff001d0104084e696e746f6e646f");
t.addInput(new TransactionInput(params, t, bytes));
ByteArrayOutputStream scriptPubKeyBytes = new ByteArrayOutputStream();
Script.writeBytes(scriptPubKeyBytes, Utils.HEX.decode
("040184710fa689ad5023690c80f3a49c8f13f8d45b8c857fbcbc8bc4a8e4d3eb4b10f4d4604fa08dce601aaf0f470216fe1b51850b4acf21b179c45070ac7b03a9"));
scriptPubKeyBytes.write(ScriptOpCodes.OP_CHECKSIG);
t.addOutput(new TransactionOutput(params, t, COIN.multiply(88), scriptPubKeyBytes.toByteArray()));
} catch (Exception e) {
// Cannot happen.
throw new RuntimeException(e);
}
genesisBlock.addTransaction(t);
return genesisBlock;
}
@Override
public Coin getBlockSubsidy(final int height) {
if (height < DIGISHIELD_BLOCK_HEIGHT) {
// Up until the Digishield hard fork, subsidy was based on the
// previous block hash. Rather than actually recalculating that, we
// simply use the maximum possible here, and let checkpoints enforce
// that new blocks with different values can't be mined
return BASE_SUBSIDY.shiftRight(height / getSubsidyDecreaseBlockCount()).multiply(2);
} else if (height < 600000) {
return BASE_SUBSIDY.shiftRight(height / getSubsidyDecreaseBlockCount());
} else {
return STABLE_SUBSIDY;
}
}
/** How many blocks pass between difficulty adjustment periods. After new diff algo. */
public int getNewInterval() {
return newInterval;
}
/**
* How much time in seconds is supposed to pass between "interval" blocks. If the actual elapsed time is
* significantly different from this value, the network difficulty formula will produce a different value.
* Dogecoin after block 145k uses 60 seconds.
*/
public int getNewTargetTimespan() {
return newTargetTimespan;
}
public MonetaryFormat getMonetaryFormat() {
return DOGE;
}
@Override
public Coin getMaxMoney() {
// TODO: Change to be Doge compatible
return MAX_MONEY;
}
@Override
public Coin getMinNonDustOutput() {
return Coin.COIN;
}
@Override
public String getUriScheme() {
return "dogecoin";
}
@Override
public boolean hasMaxMoney() {
return false;
}
/** 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.
*/
private boolean allowDigishieldMinDifficultyForBlock(final StoredBlock pindexLast, final Block pblock) {
// check if the chain allows minimum difficulty blocks
if (!this.allowMinDifficultyBlocks())
return false;
// check if the chain allows minimum difficulty blocks on recalc blocks
if (pindexLast.getHeight() < 157500)
return false;
// Allow for a minimum block time if the elapsed time > 2*nTargetSpacing
return (pblock.getTimeSeconds() > pindexLast.getHeader().getTimeSeconds() + this.getTargetSpacing(pindexLast.getHeight() + 1) * 2);
}
@Override
public void checkDifficultyTransitions(StoredBlock storedPrev, Block nextBlock, BlockStore blockStore)
throws VerificationException, BlockStoreException {
try {
final long newTargetCompact = calculateNewDifficultyTarget(storedPrev, nextBlock, blockStore);
final long receivedTargetCompact = nextBlock.getDifficultyTarget();
if (newTargetCompact != receivedTargetCompact)
throw new VerificationException("Network provided difficulty bits do not match what was calculated: " +
newTargetCompact + " vs " + receivedTargetCompact);
} catch (CheckpointEncounteredException ex) {
// Just have to take it on trust then
}
}
/**
* Get the difficulty target expected for the next block. This includes all
* the weird cases for Dogecoin such as testnet blocks which can be maximum
* 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.
*/
public long calculateNewDifficultyTarget(StoredBlock storedPrev, Block nextBlock, BlockStore blockStore)
throws VerificationException, BlockStoreException, CheckpointEncounteredException {
// Dogecoin: Special rules for minimum difficulty blocks with Digishield
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.
return Utils.encodeCompactBits(this.getMaxTarget());
}
final Block prev = storedPrev.getHeader();
final int previousHeight = storedPrev.getHeight();
final boolean digishieldAlgorithm = previousHeight + 1 >= this.getDigishieldBlockHeight();
final int retargetInterval = digishieldAlgorithm
? this.getNewInterval()
: this.getInterval();
// Is this supposed to be a difficulty transition point?
if ((storedPrev.getHeight() + 1) % retargetInterval != 0) {
if (this.allowMinDifficultyBlocks()) {
// Special difficulty rule for testnet:
// If the new block's timestamp is more than 2 minutes
// then allow mining of a min-difficulty block.
if (nextBlock.getTimeSeconds() > prev.getTimeSeconds() + getTargetSpacing(previousHeight + 1) * 2) {
return Utils.encodeCompactBits(maxTarget);
} else {
// Return the last non-special-min-difficulty-rules-block
StoredBlock cursor = storedPrev;
while (cursor.getHeight() % retargetInterval != 0
&& cursor.getHeader().getDifficultyTarget() == Utils.encodeCompactBits(this.getMaxTarget())) {
StoredBlock prevCursor = cursor.getPrev(blockStore);
if (prevCursor == null) {
break;
}
cursor = prevCursor;
}
return cursor.getHeader().getDifficultyTarget();
}
}
// No ... so check the difficulty didn't actually change.
return prev.getDifficultyTarget();
}
// We need to find a block far back in the chain. It's OK that this is expensive because it only occurs every
// two weeks after the initial block chain download.
StoredBlock cursor = storedPrev;
int goBack = retargetInterval - 1;
if (cursor.getHeight()+1 != retargetInterval)
goBack = retargetInterval;
for (int i = 0; i < goBack; i++) {
if (cursor == null) {
// This should never happen. If it does, it means we are following an incorrect or busted chain.
throw new VerificationException(
"Difficulty transition point but we did not find a way back to the genesis block.");
}
cursor = blockStore.get(cursor.getHeader().getPrevBlockHash());
}
//We used checkpoints...
if (cursor == null) {
log.debug("Difficulty transition: Hit checkpoint!");
throw new CheckpointEncounteredException();
}
Block blockIntervalAgo = cursor.getHeader();
return this.calculateNewDifficultyTargetInner(previousHeight, prev.getTimeSeconds(),
prev.getDifficultyTarget(), blockIntervalAgo.getTimeSeconds(),
nextBlock.getDifficultyTarget());
}
/**
* Calculate the difficulty target expected for the next block after a normal
* 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 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) {
return this.calculateNewDifficultyTargetInner(previousHeight, prev.getTimeSeconds(),
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 lastDifficultyTarget Compact difficulty target of the last retarget block.
* @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.
* @return New difficulty target as compact bytes.
*/
protected long calculateNewDifficultyTargetInner(int previousHeight, long previousBlockTime,
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();
int actualTime = (int) (previousBlockTime - lastRetargetTime);
final int minTimespan;
final int maxTimespan;
// Limit the adjustment step.
if (digishieldAlgorithm)
{
// Round towards zero to match the C++ implementation.
if (actualTime < retargetTimespan) {
actualTime = (int)Math.ceil(retargetTimespan + (actualTime - retargetTimespan) / 8.0);
} else {
actualTime = (int)Math.floor(retargetTimespan + (actualTime - retargetTimespan) / 8.0);
}
minTimespan = retargetTimespan - (retargetTimespan / 4);
maxTimespan = retargetTimespan + (retargetTimespan / 2);
}
else if (height > 10000)
{
minTimespan = retargetTimespan / 4;
maxTimespan = retargetTimespan * 4;
}
else if (height > 5000)
{
minTimespan = retargetTimespan / 8;
maxTimespan = retargetTimespan * 4;
}
else
{
minTimespan = retargetTimespan / 16;
maxTimespan = retargetTimespan * 4;
}
actualTime = Math.min(maxTimespan, Math.max(minTimespan, actualTime));
BigInteger newTarget = Utils.decodeCompactBits(lastDifficultyTarget);
newTarget = newTarget.multiply(BigInteger.valueOf(actualTime));
newTarget = newTarget.divide(BigInteger.valueOf(retargetTimespan));
if (newTarget.compareTo(this.getMaxTarget()) > 0) {
log.info("Difficulty hit proof of work limit: {}", newTarget.toString(16));
newTarget = this.getMaxTarget();
}
int accuracyBytes = (int) (nextDifficultyTarget >>> 24) - 3;
// The calculated difficulty is to a higher precision than received, so reduce here.
BigInteger mask = BigInteger.valueOf(0xFFFFFFL).shiftLeft(accuracyBytes * 8);
newTarget = newTarget.and(mask);
return Utils.encodeCompactBits(newTarget);
}
/**
* Get the block height from which the Digishield difficulty calculation
* algorithm is used.
*/
public int getDigishieldBlockHeight() {
return DIGISHIELD_BLOCK_HEIGHT;
}
@Override
public int getChainID() {
return AUXPOW_CHAIN_ID;
}
/**
* Whether this network has special rules to enable minimum difficulty blocks
* after a long interval between two blocks (i.e. testnet).
*/
public abstract boolean allowMinDifficultyBlocks();
/**
* Get the hash to use for a block.
*/
@Override
public Sha256Hash getBlockDifficultyHash(Block block) {
return ((AltcoinBlock) block).getScryptHash();
}
@Override
public AltcoinSerializer getSerializer(boolean parseRetain) {
return new AltcoinSerializer(this, parseRetain);
}
@Override
public int getProtocolVersionNum(final ProtocolVersion version) {
switch (version) {
case PONG:
case BLOOM_FILTER:
return version.getBitcoinProtocolVersion();
case CURRENT:
return DOGECOIN_PROTOCOL_VERSION_CURRENT;
case MINIMUM:
default:
return DOGECOIN_PROTOCOL_VERSION_AUXPOW;
}
}
@Override
public boolean isAuxPoWBlockVersion(long version) {
return version >= BLOCK_MIN_VERSION_AUXPOW
&& (version & BLOCK_VERSION_FLAG_AUXPOW) > 0;
}
/**
* Get the target time between individual blocks. Dogecoin uses this in its
* difficulty calculations, but most coins don't.
*
* @param height the block height to calculate at.
* @return the target spacing in seconds.
*/
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();
return retargetTimespan / retargetInterval;
}
private static class CheckpointEncounteredException extends Exception {
private CheckpointEncounteredException() {
}
}
}

View File

@@ -0,0 +1,320 @@
/*
* 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.libdohj.params;
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.Sha256Hash;
import org.bitcoinj.core.VerificationException;
import org.bitcoinj.store.BlockStore;
import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.Utils;
import org.bitcoinj.utils.MonetaryFormat;
import org.libdohj.core.AltcoinNetworkParameters;
import org.libdohj.core.AltcoinSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Common parameters for Litecoin networks.
*/
public abstract class AbstractLitecoinParams extends NetworkParameters implements AltcoinNetworkParameters {
/** Standard format for the LITE denomination. */
public static final MonetaryFormat LITE;
/** Standard format for the mLITE denomination. */
public static final MonetaryFormat MLITE;
/** Standard format for the Liteoshi denomination. */
public static final MonetaryFormat LITEOSHI;
public static final int LITE_TARGET_TIMESPAN = (int) (3.5 * 24 * 60 * 60); // 3.5 days
public static final int LITE_TARGET_SPACING = (int) (2.5 * 60); // 2.5 minutes
public static final int LITE_INTERVAL = LITE_TARGET_TIMESPAN / LITE_TARGET_SPACING;
/**
* The maximum number of coins to be generated
*/
public static final long MAX_LITECOINS = 21000000; // TODO: Needs to be 840000000
/**
* The maximum money to be generated
*/
public static final Coin MAX_LITECOIN_MONEY = COIN.multiply(MAX_LITECOINS);
/** Currency code for base 1 Litecoin. */
public static final String CODE_LITE = "LITE";
/** Currency code for base 1/1,000 Litecoin. */
public static final String CODE_MLITE = "mLITE";
/** Currency code for base 1/100,000,000 Litecoin. */
public static final String CODE_LITEOSHI = "Liteoshi";
static {
LITE = MonetaryFormat.BTC.noCode()
.code(0, CODE_LITE)
.code(3, CODE_MLITE)
.code(7, CODE_LITEOSHI);
MLITE = LITE.shift(3).minDecimals(2).optionalDecimals(2);
LITEOSHI = LITE.shift(7).minDecimals(0).optionalDecimals(2);
}
/** The string returned by getId() for the main, production network where people trade things. */
public static final String ID_LITE_MAINNET = "org.litecoin.production";
/** The string returned by getId() for the testnet. */
public static final String ID_LITE_TESTNET = "org.litecoin.test";
public static final int LITECOIN_PROTOCOL_VERSION_MINIMUM = 70002;
public static final int LITECOIN_PROTOCOL_VERSION_CURRENT = 70003;
private static final Coin BASE_SUBSIDY = COIN.multiply(50);
protected Logger log = LoggerFactory.getLogger(AbstractLitecoinParams.class);
public AbstractLitecoinParams() {
super();
interval = LITE_INTERVAL;
targetTimespan = LITE_TARGET_TIMESPAN;
maxTarget = Utils.decodeCompactBits(0x1e0fffffL);
packetMagic = 0xfbc0b6db;
bip32HeaderPub = 0x0488C42E; //The 4 byte header that serializes in base58 to "xpub". (?)
bip32HeaderPriv = 0x0488E1F4; //The 4 byte header that serializes in base58 to "xprv" (?)
}
@Override
public Coin getBlockSubsidy(final int height) {
return BASE_SUBSIDY.shiftRight(height / getSubsidyDecreaseBlockCount());
}
/**
* Get the hash to use for a block.
*/
@Override
public Sha256Hash getBlockDifficultyHash(Block block) {
return ((AltcoinBlock) block).getScryptHash();
}
public MonetaryFormat getMonetaryFormat() {
return LITE;
}
@Override
public Coin getMaxMoney() {
return MAX_LITECOIN_MONEY;
}
@Override
public Coin getMinNonDustOutput() {
return Coin.COIN;
}
@Override
public String getUriScheme() {
return "litecoin";
}
@Override
public boolean hasMaxMoney() {
return true;
}
@Override
public void checkDifficultyTransitions(StoredBlock storedPrev, Block nextBlock, BlockStore blockStore)
throws VerificationException, BlockStoreException {
try {
final long newTargetCompact = calculateNewDifficultyTarget(storedPrev, nextBlock, blockStore);
final long receivedTargetCompact = nextBlock.getDifficultyTarget();
if (newTargetCompact != receivedTargetCompact)
throw new VerificationException("Network provided difficulty bits do not match what was calculated: " +
newTargetCompact + " vs " + receivedTargetCompact);
} catch (CheckpointEncounteredException ex) {
// Just have to take it on trust then
}
}
/**
* Get the difficulty target expected for the next block. This includes all
* the weird cases for Litecoin such as testnet blocks which can be maximum
* 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.
*/
public long calculateNewDifficultyTarget(StoredBlock storedPrev, Block nextBlock, BlockStore blockStore)
throws VerificationException, BlockStoreException, CheckpointEncounteredException {
final Block prev = storedPrev.getHeader();
final int previousHeight = storedPrev.getHeight();
final int retargetInterval = this.getInterval();
// Is this supposed to be a difficulty transition point?
if ((storedPrev.getHeight() + 1) % retargetInterval != 0) {
if (this.allowMinDifficultyBlocks()) {
// Special difficulty rule for testnet:
// If the new block's timestamp is more than 5 minutes
// then allow mining of a min-difficulty block.
if (nextBlock.getTimeSeconds() > prev.getTimeSeconds() + getTargetSpacing() * 2) {
return Utils.encodeCompactBits(maxTarget);
} else {
// Return the last non-special-min-difficulty-rules-block
StoredBlock cursor = storedPrev;
while (cursor.getHeight() % retargetInterval != 0
&& cursor.getHeader().getDifficultyTarget() == Utils.encodeCompactBits(this.getMaxTarget())) {
StoredBlock prevCursor = cursor.getPrev(blockStore);
if (prevCursor == null) {
break;
}
cursor = prevCursor;
}
return cursor.getHeader().getDifficultyTarget();
}
}
// No ... so check the difficulty didn't actually change.
return prev.getDifficultyTarget();
}
// We need to find a block far back in the chain. It's OK that this is expensive because it only occurs every
// two weeks after the initial block chain download.
StoredBlock cursor = storedPrev;
int goBack = retargetInterval - 1;
// Litecoin: This fixes an issue where a 51% attack can change difficulty at will.
// Go back the full period unless it's the first retarget after genesis.
// Code based on original by Art Forz
if (cursor.getHeight()+1 != retargetInterval)
goBack = retargetInterval;
for (int i = 0; i < goBack; i++) {
if (cursor == null) {
// This should never happen. If it does, it means we are following an incorrect or busted chain.
throw new VerificationException(
"Difficulty transition point but we did not find a way back to the genesis block.");
}
cursor = blockStore.get(cursor.getHeader().getPrevBlockHash());
}
//We used checkpoints...
if (cursor == null) {
log.debug("Difficulty transition: Hit checkpoint!");
throw new CheckpointEncounteredException();
}
Block blockIntervalAgo = cursor.getHeader();
return this.calculateNewDifficultyTargetInner(previousHeight, prev.getTimeSeconds(),
prev.getDifficultyTarget(), blockIntervalAgo.getTimeSeconds(),
nextBlock.getDifficultyTarget());
}
/**
* Calculate the difficulty target expected for the next block after a normal
* 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 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) {
return this.calculateNewDifficultyTargetInner(previousHeight, prev.getTimeSeconds(),
prev.getDifficultyTarget(), blockIntervalAgo.getTimeSeconds(),
nextBlock.getDifficultyTarget());
}
/**
*
* @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 nextDifficultyTarget The expected difficulty target of the next
* 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 int retargetTimespan = this.getTargetTimespan();
int actualTime = (int) (previousBlockTime - lastRetargetTime);
final int minTimespan = retargetTimespan / 4;
final int maxTimespan = retargetTimespan * 4;
actualTime = Math.min(maxTimespan, Math.max(minTimespan, actualTime));
BigInteger newTarget = Utils.decodeCompactBits(lastDifficultyTarget);
newTarget = newTarget.multiply(BigInteger.valueOf(actualTime));
newTarget = newTarget.divide(BigInteger.valueOf(retargetTimespan));
if (newTarget.compareTo(this.getMaxTarget()) > 0) {
log.info("Difficulty hit proof of work limit: {}", newTarget.toString(16));
newTarget = this.getMaxTarget();
}
int accuracyBytes = (int) (nextDifficultyTarget >>> 24) - 3;
// The calculated difficulty is to a higher precision than received, so reduce here.
BigInteger mask = BigInteger.valueOf(0xFFFFFFL).shiftLeft(accuracyBytes * 8);
newTarget = newTarget.and(mask);
return Utils.encodeCompactBits(newTarget);
}
@Override
public AltcoinSerializer getSerializer(boolean parseRetain) {
return new AltcoinSerializer(this, parseRetain);
}
@Override
public int getProtocolVersionNum(final ProtocolVersion version) {
switch (version) {
case PONG:
case BLOOM_FILTER:
return version.getBitcoinProtocolVersion();
case CURRENT:
return LITECOIN_PROTOCOL_VERSION_CURRENT;
case MINIMUM:
default:
return LITECOIN_PROTOCOL_VERSION_MINIMUM;
}
}
/**
* Whether this network has special rules to enable minimum difficulty blocks
* after a long interval between two blocks (i.e. testnet).
*/
public boolean allowMinDifficultyBlocks() {
return this.isTestNet();
}
public int getTargetSpacing() {
return this.getTargetTimespan() / this.getInterval();
}
private static class CheckpointEncounteredException extends Exception { }
}

View File

@@ -0,0 +1,118 @@
/*
* 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.libdohj.params;
import org.bitcoinj.core.Sha256Hash;
import static com.google.common.base.Preconditions.checkState;
/**
* Parameters for the main Dogecoin production network on which people trade
* goods and services.
*/
public class DogecoinMainNetParams extends AbstractDogecoinParams {
public static final int MAINNET_MAJORITY_WINDOW = 2000;
public static final int MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED = 1900;
public static final int MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE = 1500;
protected static final int DIFFICULTY_CHANGE_TARGET = 145000;
public DogecoinMainNetParams() {
super(DIFFICULTY_CHANGE_TARGET);
dumpedPrivateKeyHeader = 158; //This is always addressHeader + 128
addressHeader = 30;
p2shHeader = 22;
acceptableAddressCodes = new int[] { addressHeader, p2shHeader };
port = 22556;
packetMagic = 0xc0c0c0c0;
// Note that while BIP44 makes HD wallets chain-agnostic, for legacy
// reasons we use a Doge-specific header for main net. At some point
// we'll add independent headers for BIP32 legacy and BIP44.
bip32HeaderPub = 0x02facafd; //The 4 byte header that serializes in base58 to "dgub".
bip32HeaderPriv = 0x02fac398; //The 4 byte header that serializes in base58 to "dgpv".
genesisBlock.setDifficultyTarget(0x1e0ffff0L);
genesisBlock.setTime(1386325540L);
genesisBlock.setNonce(99943L);
id = ID_DOGE_MAINNET;
subsidyDecreaseBlockCount = 100000;
spendableCoinbaseDepth = 100;
// Note this is an SHA256 hash, not a Scrypt hash. Scrypt hashes are only
// used in difficulty calculations.
String genesisHash = genesisBlock.getHashAsString();
checkState(genesisHash.equals("1a91e3dace36e2be3bf030a65679fe821aa1d6ef92e7c9902eb318182c355691"),
genesisHash);
majorityEnforceBlockUpgrade = MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE;
majorityRejectBlockOutdated = MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED;
majorityWindow = MAINNET_MAJORITY_WINDOW;
// This contains (at a minimum) the blocks which are not BIP30 compliant. BIP30 changed how duplicate
// transactions are handled. Duplicated transactions could occur in the case where a coinbase had the same
// extraNonce and the same outputs but appeared at different heights, and greatly complicated re-org handling.
// Having these here simplifies block connection logic considerably.
checkpoints.put( 0, Sha256Hash.wrap("1a91e3dace36e2be3bf030a65679fe821aa1d6ef92e7c9902eb318182c355691"));
checkpoints.put( 42279, Sha256Hash.wrap("8444c3ef39a46222e87584ef956ad2c9ef401578bd8b51e8e4b9a86ec3134d3a"));
checkpoints.put( 42400, Sha256Hash.wrap("557bb7c17ed9e6d4a6f9361cfddf7c1fc0bdc394af7019167442b41f507252b4"));
checkpoints.put(104679, Sha256Hash.wrap("35eb87ae90d44b98898fec8c39577b76cb1eb08e1261cfc10706c8ce9a1d01cf"));
checkpoints.put(128370, Sha256Hash.wrap("3f9265c94cab7dc3bd6a2ad2fb26c8845cb41cff437e0a75ae006997b4974be6"));
checkpoints.put(145000, Sha256Hash.wrap("cc47cae70d7c5c92828d3214a266331dde59087d4a39071fa76ddfff9b7bde72"));
checkpoints.put(165393, Sha256Hash.wrap("7154efb4009e18c1c6a6a79fc6015f48502bcd0a1edd9c20e44cd7cbbe2eeef1"));
checkpoints.put(186774, Sha256Hash.wrap("3c712c49b34a5f34d4b963750d6ba02b73e8a938d2ee415dcda141d89f5cb23a"));
checkpoints.put(199992, Sha256Hash.wrap("3408ff829b7104eebaf61fd2ba2203ef2a43af38b95b353e992ef48f00ebb190"));
checkpoints.put(225000, Sha256Hash.wrap("be148d9c5eab4a33392a6367198796784479720d06bfdd07bd547fe934eea15a"));
checkpoints.put(250000, Sha256Hash.wrap("0e4bcfe8d970979f7e30e2809ab51908d435677998cf759169407824d4f36460"));
checkpoints.put(270639, Sha256Hash.wrap("c587a36dd4f60725b9dd01d99694799bef111fc584d659f6756ab06d2a90d911"));
checkpoints.put(299742, Sha256Hash.wrap("1cc89c0c8a58046bf0222fe131c099852bd9af25a80e07922918ef5fb39d6742"));
checkpoints.put(323141, Sha256Hash.wrap("60c9f919f9b271add6ef5671e9538bad296d79f7fdc6487ba702bf2ba131d31d"));
checkpoints.put(339202, Sha256Hash.wrap("8c29048df5ae9df38a67ea9470fdd404d281a3a5c6f33080cd5bf14aa496ab03"));
checkpoints.put(350000, Sha256Hash.wrap("2bdcba23a47049e69c4fec4c425462e30f3d21d25223bde0ed36be4ea59a7075"));
checkpoints.put(370005, Sha256Hash.wrap("7be5af2c5bdcb79047dcd691ef613b82d4f1c20835677daed936de37a4782e15"));
checkpoints.put(371337, Sha256Hash.wrap("60323982f9c5ff1b5a954eac9dc1269352835f47c2c5222691d80f0d50dcf053"));
checkpoints.put(400002, Sha256Hash.wrap("a5021d69a83f39aef10f3f24f932068d6ff322c654d20562def3fac5703ce3aa"));
dnsSeeds = new String[] {
"seed.dogecoin.com",
"seed.multidoge.org",
"seed2.multidoge.org",
"seed.doger.dogecoin.com"
};
}
private static DogecoinMainNetParams instance;
public static synchronized DogecoinMainNetParams get() {
if (instance == null) {
instance = new DogecoinMainNetParams();
}
return instance;
}
@Override
public boolean allowMinDifficultyBlocks() {
return false;
}
@Override
public String getPaymentProtocolId() {
// TODO: CHANGE THIS
return ID_DOGE_MAINNET;
}
@Override
public boolean isTestNet() {
return false;
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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
*
* 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.libdohj.params;
import org.bitcoinj.core.Utils;
import org.spongycastle.util.encoders.Hex;
import static com.google.common.base.Preconditions.checkState;
/**
* Parameters for the Dogecoin testnet, a separate public network that has
* relaxed rules suitable for development and testing of applications and new
* Dogecoin versions.
*/
public class DogecoinTestNet3Params extends AbstractDogecoinParams {
public static final int TESTNET_MAJORITY_WINDOW = 1000;
public static final int TESTNET_MAJORITY_REJECT_BLOCK_OUTDATED = 750;
public static final int TESTNET_MAJORITY_ENFORCE_BLOCK_UPGRADE = 501;
protected static final int DIFFICULTY_CHANGE_TARGET = 145000;
public DogecoinTestNet3Params() {
super(DIFFICULTY_CHANGE_TARGET);
id = ID_DOGE_TESTNET;
packetMagic = 0xfcc1b7dc;
maxTarget = Utils.decodeCompactBits(0x1e0fffffL);
port = 44556;
addressHeader = 113;
p2shHeader = 196;
acceptableAddressCodes = new int[] { addressHeader, p2shHeader };
dumpedPrivateKeyHeader = 241;
genesisBlock.setTime(1391503289L);
genesisBlock.setDifficultyTarget(0x1e0ffff0L);
genesisBlock.setNonce(997879);
spendableCoinbaseDepth = 30;
subsidyDecreaseBlockCount = 100000;
String genesisHash = genesisBlock.getHashAsString();
checkState(genesisHash.equals("bb0a78264637406b6360aad926284d544d7049f45189db5664f3c4d07350559e"));
alertSigningKey = Hex.decode("042756726da3c7ef515d89212ee1705023d14be389e25fe15611585661b9a20021908b2b80a3c7200a0139dd2b26946606aab0eef9aa7689a6dc2c7eee237fa834");
majorityEnforceBlockUpgrade = TESTNET_MAJORITY_ENFORCE_BLOCK_UPGRADE;
majorityRejectBlockOutdated = TESTNET_MAJORITY_REJECT_BLOCK_OUTDATED;
majorityWindow = TESTNET_MAJORITY_WINDOW;
dnsSeeds = new String[] {
"testseed.jrn.me.uk"
};
// Note this is the same as the BIP32 testnet, as BIP44 makes HD wallets
// chain agnostic. Dogecoin mainnet has its own headers for legacy reasons.
bip32HeaderPub = 0x043587CF;
bip32HeaderPriv = 0x04358394;
}
private static DogecoinTestNet3Params instance;
public static synchronized DogecoinTestNet3Params get() {
if (instance == null) {
instance = new DogecoinTestNet3Params();
}
return instance;
}
@Override
public boolean allowMinDifficultyBlocks() {
return true;
}
@Override
public String getPaymentProtocolId() {
// TODO: CHANGE ME
return PAYMENT_PROTOCOL_ID_TESTNET;
}
@Override
public boolean isTestNet() {
return true;
}
}

View File

@@ -0,0 +1,121 @@
/*
* 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
*
* 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.libdohj.params;
import org.bitcoinj.core.Utils;
import org.spongycastle.util.encoders.Hex;
import static com.google.common.base.Preconditions.checkState;
import java.io.ByteArrayOutputStream;
import org.bitcoinj.core.AltcoinBlock;
import org.bitcoinj.core.Block;
import static org.bitcoinj.core.Coin.COIN;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptOpCodes;
/**
* Parameters for the Litecoin main production network on which people trade
* goods and services.
*/
public class LitecoinMainNetParams extends AbstractLitecoinParams {
public static final int MAINNET_MAJORITY_WINDOW = MainNetParams.MAINNET_MAJORITY_WINDOW;
public static final int MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED = MainNetParams.MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED;
public static final int MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE = MainNetParams.MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE;
public LitecoinMainNetParams() {
super();
id = ID_LITE_MAINNET;
// Genesis hash is 12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2
packetMagic = 0xfbc0b6db;
maxTarget = Utils.decodeCompactBits(0x1e0fffffL);
port = 9333;
addressHeader = 48;
p2shHeader = 5;
acceptableAddressCodes = new int[] { addressHeader, p2shHeader };
dumpedPrivateKeyHeader = 176;
this.genesisBlock = createGenesis(this);
spendableCoinbaseDepth = 100;
subsidyDecreaseBlockCount = 840000;
String genesisHash = genesisBlock.getHashAsString();
checkState(genesisHash.equals("12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2"));
alertSigningKey = Hex.decode("040184710fa689ad5023690c80f3a49c8f13f8d45b8c857fbcbc8bc4a8e4d3eb4b10f4d4604fa08dce601aaf0f470216fe1b51850b4acf21b179c45070ac7b03a9");
majorityEnforceBlockUpgrade = MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE;
majorityRejectBlockOutdated = MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED;
majorityWindow = MAINNET_MAJORITY_WINDOW;
dnsSeeds = new String[] {
"dnsseed.litecointools.com",
"dnsseed.litecoinpool.org",
"dnsseed.ltc.xurious.com",
"dnsseed.koin-project.com",
"dnsseed.weminemnc.com"
};
bip32HeaderPub = 0x0488B21E;
bip32HeaderPriv = 0x0488ADE4;
}
private static AltcoinBlock createGenesis(NetworkParameters params) {
AltcoinBlock genesisBlock = new AltcoinBlock(params, Block.BLOCK_VERSION_GENESIS);
Transaction t = new Transaction(params);
try {
byte[] bytes = Hex.decode
("04ffff001d0104404e592054696d65732030352f4f63742f32303131205374657665204a6f62732c204170706c65e280997320566973696f6e6172792c2044696573206174203536");
t.addInput(new TransactionInput(params, t, bytes));
ByteArrayOutputStream scriptPubKeyBytes = new ByteArrayOutputStream();
Script.writeBytes(scriptPubKeyBytes, Hex.decode
("040184710fa689ad5023690c80f3a49c8f13f8d45b8c857fbcbc8bc4a8e4d3eb4b10f4d4604fa08dce601aaf0f470216fe1b51850b4acf21b179c45070ac7b03a9"));
scriptPubKeyBytes.write(ScriptOpCodes.OP_CHECKSIG);
t.addOutput(new TransactionOutput(params, t, COIN.multiply(50), scriptPubKeyBytes.toByteArray()));
} catch (Exception e) {
// Cannot happen.
throw new RuntimeException(e);
}
genesisBlock.addTransaction(t);
genesisBlock.setTime(1317972665L);
genesisBlock.setDifficultyTarget(0x1e0ffff0L);
genesisBlock.setNonce(2084524493);
return genesisBlock;
}
private static LitecoinMainNetParams instance;
public static synchronized LitecoinMainNetParams get() {
if (instance == null) {
instance = new LitecoinMainNetParams();
}
return instance;
}
@Override
public String getPaymentProtocolId() {
return ID_LITE_MAINNET;
}
@Override
public boolean isTestNet() {
return false;
}
}

View File

@@ -0,0 +1,120 @@
/*
* 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
*
* 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.libdohj.params;
import org.bitcoinj.core.Utils;
import org.spongycastle.util.encoders.Hex;
import static com.google.common.base.Preconditions.checkState;
import java.io.ByteArrayOutputStream;
import org.bitcoinj.core.AltcoinBlock;
import org.bitcoinj.core.Block;
import static org.bitcoinj.core.Coin.COIN;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptOpCodes;
/**
* Parameters for the testnet, a separate public instance of Litecoin that has
* relaxed rules suitable for development and testing of applications and new
* Litecoin versions.
*/
public class LitecoinTestNet3Params extends AbstractLitecoinParams {
public static final int TESTNET_MAJORITY_WINDOW = 100;
public static final int TESTNET_MAJORITY_REJECT_BLOCK_OUTDATED = 75;
public static final int TESTNET_MAJORITY_ENFORCE_BLOCK_UPGRADE = 51;
public LitecoinTestNet3Params() {
super();
id = ID_LITE_TESTNET;
// Genesis hash is f5ae71e26c74beacc88382716aced69cddf3dffff24f384e1808905e0188f68f
packetMagic = 0xfcc1b7dc;
maxTarget = Utils.decodeCompactBits(0x1e0fffffL);
port = 19333;
addressHeader = 111;
p2shHeader = 196;
acceptableAddressCodes = new int[] { addressHeader, p2shHeader };
dumpedPrivateKeyHeader = 239;
this.genesisBlock = createGenesis(this);
spendableCoinbaseDepth = 30;
subsidyDecreaseBlockCount = 100000;
String genesisHash = genesisBlock.getHashAsString();
checkState(genesisHash.equals("f5ae71e26c74beacc88382716aced69cddf3dffff24f384e1808905e0188f68f"));
alertSigningKey = Hex.decode("0449623fc74489a947c4b15d579115591add020e53b3490bf47297dfa3762250625f8ecc2fb4fc59f69bdce8f7080f3167808276ed2c79d297054367566038aa82");
majorityEnforceBlockUpgrade = TESTNET_MAJORITY_ENFORCE_BLOCK_UPGRADE;
majorityRejectBlockOutdated = TESTNET_MAJORITY_REJECT_BLOCK_OUTDATED;
majorityWindow = TESTNET_MAJORITY_WINDOW;
dnsSeeds = new String[] {
"testnet-seed.litecointools.com",
"testnet-seed.ltc.xurious.com",
"dnsseed.wemine-testnet.com"
};
bip32HeaderPub = 0x043587CF;
bip32HeaderPriv = 0x04358394;
}
private static AltcoinBlock createGenesis(NetworkParameters params) {
AltcoinBlock genesisBlock = new AltcoinBlock(params, Block.BLOCK_VERSION_GENESIS);
Transaction t = new Transaction(params);
try {
byte[] bytes = Hex.decode
("04ffff001d0104404e592054696d65732030352f4f63742f32303131205374657665204a6f62732c204170706c65e280997320566973696f6e6172792c2044696573206174203536");
t.addInput(new TransactionInput(params, t, bytes));
ByteArrayOutputStream scriptPubKeyBytes = new ByteArrayOutputStream();
Script.writeBytes(scriptPubKeyBytes, Hex.decode
("040184710fa689ad5023690c80f3a49c8f13f8d45b8c857fbcbc8bc4a8e4d3eb4b10f4d4604fa08dce601aaf0f470216fe1b51850b4acf21b179c45070ac7b03a9"));
scriptPubKeyBytes.write(ScriptOpCodes.OP_CHECKSIG);
t.addOutput(new TransactionOutput(params, t, COIN.multiply(50), scriptPubKeyBytes.toByteArray()));
} catch (Exception e) {
// Cannot happen.
throw new RuntimeException(e);
}
genesisBlock.addTransaction(t);
genesisBlock.setTime(1317798646L);
genesisBlock.setDifficultyTarget(0x1e0ffff0L);
genesisBlock.setNonce(385270584);
return genesisBlock;
}
private static LitecoinTestNet3Params instance;
public static synchronized LitecoinTestNet3Params get() {
if (instance == null) {
instance = new LitecoinTestNet3Params();
}
return instance;
}
@Override
public String getPaymentProtocolId() {
return ID_LITE_TESTNET;
}
@Override
public boolean isTestNet() {
return true;
}
}

View File

@@ -0,0 +1,5 @@
/**
* Network parameters encapsulate some of the differences between different altcoin networks such as the main
* network, the testnet, regtest mode, unit testing params and so on.
*/
package org.libdohj.params;

Binary file not shown.

View File

@@ -0,0 +1,47 @@
//
// Simple Dogecoin Payment Protocol messages
// Derived fromthe Bitcoin Payment Protocol
//
// Use fields 100+ for extensions;
// to avoid conflicts, register extensions via pull-req at:
// https://github.com/dogecoin/dips
//
package payments;
option java_package = "com.dogecoin.protocols.payments";
option java_outer_classname = "Protos";
// Generalized form of "send payment to this/these dogecoin addresses"
message Output {
optional uint64 amount = 1 [default = 0]; // amount is integer-number-of-satoshis
required bytes script = 2; // usually one of the standard Script forms
}
message PaymentDetails {
optional string genesis = 1 [default = "1a91e3dace36e2be3bf030a65679fe821aa1d6ef92e7c9902eb318182c355691"]; // Hash of the network genesis block
repeated Output outputs = 2; // Where payment should be sent
required uint64 time = 3; // Timestamp; when payment request created
optional uint64 expires = 4; // Timestamp; when this request should be considered invalid
optional string memo = 5; // Human-readable description of request for the customer
optional string payment_url = 6; // URL to send Payment and get PaymentACK
optional bytes merchant_data = 7; // Arbitrary data to include in the Payment message
}
message PaymentRequest {
optional uint32 payment_details_version = 1 [default = 1];
optional string pki_type = 2 [default = "none"]; // none / x509+sha256 / x509+sha1
optional bytes pki_data = 3; // depends on pki_type
required bytes serialized_payment_details = 4; // PaymentDetails
optional bytes signature = 5; // pki-dependent signature
}
message X509Certificates {
repeated bytes certificate = 1; // DER-encoded X.509 certificate chain
}
message Payment {
optional bytes merchant_data = 1; // From PaymentDetails.merchant_data
repeated bytes transactions = 2; // Signed transactions that satisfy PaymentDetails.outputs
repeated Output refund_to = 3; // Where to send refunds, if a refund is necessary
optional string memo = 4; // Human-readable message for the merchant
}
message PaymentACK {
required Payment payment = 1; // Payment message that triggered this ACK
optional string memo = 2; // human-readable message for customer
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2015 jrn.
*
* 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 java.util.BitSet;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;
import org.libdohj.params.DogecoinMainNetParams;
/**
*
* @author jrn
*/
public class AltcoinBlockTest {
private final NetworkParameters params = DogecoinMainNetParams.get();
@Before
public void setUp() throws Exception {
Context context = new Context(params);
}
/**
* Test extraction of flags from the block version, for coins with AuxPoW
* support.
*/
@Test
public void testGetVersionFlags() {
AltcoinBlock block = new AltcoinBlock(params, 0L);
BitSet expected = new BitSet(8);
assertEquals(block.getVersionFlags(), expected);
// Set everything but the version flags
block = new AltcoinBlock(params, 0xffff00ff);
assertEquals(block.getVersionFlags(), expected);
// Set everything
block = new AltcoinBlock(params, 0xffffffff);
expected.set(0, 8);
assertEquals(block.getVersionFlags(), expected);
// Set only the version flags
block = new AltcoinBlock(params, 0x0000ff00);
assertEquals(block.getVersionFlags(), expected);
// Set some of the version flags
block = new AltcoinBlock(params, 0x00001700);
expected.clear(0, 8);
expected.set(0, 3);
expected.set(4);
assertEquals(block.getVersionFlags(), expected);
}
}

View File

@@ -0,0 +1,429 @@
package org.bitcoinj.core;
import java.util.Arrays;
import java.util.Collections;
import org.libdohj.core.AltcoinSerializer;
import org.libdohj.params.DogecoinMainNetParams;
import org.libdohj.params.DogecoinTestNet3Params;
import static org.bitcoinj.core.Util.getBytes;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
/**
* AuxPoW header parsing/serialization and validation
*/
public class AuxPoWTest {
static final NetworkParameters params = DogecoinMainNetParams.get();
private static final int MERKLE_ROOT_COINBASE_INDEX = 0;
@Before
public void setUp() throws Exception {
Context context = new Context(params);
}
/**
* Parse the AuxPoW header from Dogecoin block #403,931.
*/
@Test
public void parseAuxPoWHeader() throws Exception {
byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
MerkleBranch branch = auxpow.getCoinbaseBranch();
Sha256Hash expected = Sha256Hash.wrap("089b911f5e471c0e1800f3384281ebec5b372fbb6f358790a92747ade271ccdf");
assertEquals(expected, auxpow.getCoinbase().getHash());
assertEquals(3, auxpow.getCoinbaseBranch().size());
assertEquals(6, auxpow.getChainMerkleBranch().size());
expected = Sha256Hash.wrap("a22a9b01671d639fa6389f62ecf8ce69204c8ed41d5f1a745e0c5ba7116d5b4c");
assertEquals(expected, auxpow.getParentBlockHeader().getHash());
expected = Sha256Hash.wrap("f29cd14243ed542d9a0b495efcb9feca1b208bb5b717dc5ac04f068d2fef595a");
assertEquals(expected, auxpow.getParentBlockHeader().getMerkleRoot());
}
/**
* Test serializing the AuxPoW header from Dogecoin block #403,931.
*/
@Test
public void serializeAuxPoWHeader() throws Exception {
byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
byte[] expected = auxpowAsBytes;
byte[] actual = auxpow.bitcoinSerialize();
assertArrayEquals(expected, actual);
}
/**
* Validate the AuxPoW header from Dogecoin block #403,931.
*/
@Test
public void checkAuxPoWHeader() throws Exception {
byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
Utils.decodeCompactBits(0x1b06f8f0), true);
}
/**
* Validate the AuxPoW header with no explicit data header in the coinbase
* transaction. Namecoin block #19,414
*/
@Test
public void checkAuxPoWHeaderNoTxHeader() throws Exception {
// Emulate Namecoin block hashing for this test
final NetworkParameters namecoinLikeParams = new DogecoinTestNet3Params() {
@Override
public Sha256Hash getBlockDifficultyHash(Block block) {
// Namecoin uses SHA256 hashes
return block.getHash();
}
};
byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header_no_tx_header.bin"));
AuxPoW auxpow = new AuxPoW(namecoinLikeParams, auxpowAsBytes, (ChildMessage) null, namecoinLikeParams.getDefaultSerializer());
auxpow.checkProofOfWork(Sha256Hash.wrap("5fb89c3b18c27bc38d351d516177cbd3504c95ca0494cbbbbd52f2fb5f2ff1ec"),
Utils.decodeCompactBits(0x1b00b269), true);
}
@Rule
public ExpectedException expectedEx = ExpectedException.none();
/**
* Check that a non-generate AuxPoW transaction is rejected.
*/
@Test
public void shouldRejectNonGenerateAuxPoW() throws Exception {
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
auxpow.getCoinbaseBranch().setIndex(0x01);
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
expectedEx.expectMessage("AuxPow is not a generate");
auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
Utils.decodeCompactBits(0x1b06f8f0), true);
}
/**
* Check that block headers from the child chain are rejected as parent
* chain for AuxPoW, via checking of the chain IDs.
*/
@Test
public void shouldRejectOwnChainID() throws Exception {
byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block371337.bin"));
AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer();
final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload);
assertEquals(98, block.getChainID());
final AuxPoW auxpow = block.getAuxPoW();
assertNotNull(auxpow);
auxpow.setParentBlockHeader((AltcoinBlock)block.cloneAsHeader());
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
expectedEx.expectMessage("Aux POW parent has our chain ID");
auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
Utils.decodeCompactBits(0x1b06f8f0), true);
}
/**
* Check that where the merkle branch is far too long to use, it's rejected.
*/
@Test
public void shouldRejectVeryLongMerkleBranch() throws Exception {
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
auxpow.getChainMerkleBranch().setHashes(Arrays.asList(new Sha256Hash[32]));
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
expectedEx.expectMessage("Aux POW chain merkle branch too long");
auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
Utils.decodeCompactBits(0x1b06f8f0), true);
}
/**
* Later steps in AuxPoW validation depend on the contents of the coinbase
* transaction. Obviously that's useless if we don't check the coinbase
* transaction is actually part of the parent chain block, so first we test
* that the transaction hash is part of the merkle tree. This test modifies
* the transaction, invalidating the hash, to confirm that it's rejected.
*/
@Test
public void shouldRejectIfCoinbaseTransactionNotInMerkleBranch() throws Exception {
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
auxpow.getCoinbase().clearOutputs();
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
expectedEx.expectMessage("Aux POW merkle root incorrect");
auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
Utils.decodeCompactBits(0x1b06f8f0), true);
}
/**
* Ensure that in case of a malformed coinbase transaction (no inputs) it's
* caught and processed neatly.
*/
@Test
public void shouldRejectIfCoinbaseTransactionHasNoInputs() throws Exception {
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
// This will also break the difficulty check, but as that doesn't occur
// until the end, we can get away with it.
auxpow.getCoinbase().clearInputs();
updateMerkleRootToMatchCoinbase(auxpow);
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
expectedEx.expectMessage("Coinbase transaction has no inputs");
auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
Utils.decodeCompactBits(0x1b06f8f0), true);
}
/**
* Catch the case that the coinbase transaction does not contain details of
* the merged block. In this case we make the transaction script too short
* for it to do so.
*/
@Test
public void shouldRejectIfMergedMineHeaderMissing() throws Exception {
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
// This will also break the difficulty check, but as that doesn't occur
// until the end, we can get away with it.
final TransactionInput in = auxpow.getCoinbase().getInput(0);
final byte[] paddedScriptBytes = new byte[in.getScriptBytes().length + (AuxPoW.MAX_INDEX_PC_BACKWARDS_COMPATIBILITY + 4)];
Arrays.fill(paddedScriptBytes, (byte) 0);
System.arraycopy(in.getScriptBytes(), 8, paddedScriptBytes, (AuxPoW.MAX_INDEX_PC_BACKWARDS_COMPATIBILITY + 4), in.getScriptBytes().length - 8);
in.setScriptBytes(paddedScriptBytes);
updateMerkleRootToMatchCoinbase(auxpow);
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
expectedEx.expectMessage("Aux POW chain merkle root must start in the first 20 bytes of the parent coinbase");
auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
Utils.decodeCompactBits(0x1b06f8f0), true);
}
/**
* Catch the case that more than one merged mine header is present in the
* coinbase transaction (this is considered an attempt to confuse the parser).
*/
@Test
public void shouldRejectIfMergedMineHeaderDuplicated() throws Exception {
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
// This will also break the difficulty check, but as that doesn't occur
// until the end, we can get away with it.
final TransactionInput in = auxpow.getCoinbase().getInput(0);
final byte[] newBytes = Arrays.copyOf(in.getScriptBytes(), in.getScriptBytes().length + 4);
for (int byteIdx = 0; byteIdx < AuxPoW.MERGED_MINING_HEADER.length; byteIdx++) {
newBytes[newBytes.length - 4 + byteIdx] = AuxPoW.MERGED_MINING_HEADER[byteIdx];
}
in.setScriptBytes(newBytes);
updateMerkleRootToMatchCoinbase(auxpow);
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
expectedEx.expectMessage("Multiple merged mining headers in coinbase");
auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
Utils.decodeCompactBits(0x1b06f8f0), true);
}
/**
* Catch the case that the chain merkle branch is missing from the coinbase
* transaction. The chain merkle branch is used to prove that the block was
* mined for chain or chains including this one (i.e. random proof of work
* cannot be taken from any merged-mined blockchain and reused).
*/
@Test
public void shouldRejectIfCoinbaseMissingChainMerkleRoot() throws Exception {
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
// This will also break the difficulty check, but as that doesn't occur
// until the end, we can get away with it.
final TransactionInput in = auxpow.getCoinbase().getInput(0);
in.getScriptBytes()[8] = 0; // Break the first byte of the chain merkle root
updateMerkleRootToMatchCoinbase(auxpow);
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
expectedEx.expectMessage("Aux POW missing chain merkle root in parent coinbase");
auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
Utils.decodeCompactBits(0x1b06f8f0), true);
}
/**
* Catch the case that the chain merkle branch is not immediately after the
* merged mine header in the coinbase transaction (this is considered an
* attempt to confuse the parser).
*/
@Test
public void shouldRejectIfChainMerkleRootNotAfterHeader() throws Exception {
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
// This will also break the difficulty check, but as that doesn't occur
// until the end, we can get away with it.
final TransactionInput in = auxpow.getCoinbase().getInput(0);
final byte[] newBytes = Arrays.copyOf(in.getScriptBytes(), in.getScriptBytes().length + 1);
// Copy every byte after the merged-mine header forward one byte. We
// have to do this from the end of the array backwards to avoid overwriting
// the next byte to copy.
for (int byteIdx = newBytes.length - 1; byteIdx > 8; byteIdx--) {
newBytes[byteIdx] = newBytes[byteIdx - 1];
}
newBytes[8] = (byte) 0xff;
in.setScriptBytes(newBytes);
updateMerkleRootToMatchCoinbase(auxpow);
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
expectedEx.expectMessage("Merged mining header is not just before chain merkle root");
auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
Utils.decodeCompactBits(0x1b06f8f0), true);
}
/**
* Catch the case that the chain merkle branch is not immediately after the
* merged mine header in the coinbase transaction (this is considered an
* attempt to confuse the parser).
*/
@Test
public void shouldRejectIfScriptBytesTooShort() throws Exception {
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
// This will also break the difficulty check, but as that doesn't occur
// until the end, we can get away with it.
final TransactionInput in = auxpow.getCoinbase().getInput(0);
final byte[] newBytes = Arrays.copyOf(in.getScriptBytes(), in.getScriptBytes().length - 12);
in.setScriptBytes(newBytes);
updateMerkleRootToMatchCoinbase(auxpow);
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
expectedEx.expectMessage("Aux POW missing chain merkle tree size and nonce in parent coinbase");
auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
Utils.decodeCompactBits(0x1b06f8f0), true);
}
/**
* Catch the case that the chain merkle branch size in the coinbase transaction
* does not match the size of the merkle brach in the AuxPoW header.
*/
@Test
public void shouldRejectIfCoinbaseMerkleBranchSizeMismatch() throws Exception {
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
// This will also break the difficulty check, but as that doesn't occur
// until the end, we can get away with it.
final TransactionInput in = auxpow.getCoinbase().getInput(0);
in.getScriptBytes()[40] = 3; // Break the merkle branch length
updateMerkleRootToMatchCoinbase(auxpow);
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
expectedEx.expectMessage("Aux POW merkle branch size does not match parent coinbase");
auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
Utils.decodeCompactBits(0x1b06f8f0), true);
}
/**
* In order to ensure that the same work is not submitted more than once,
* confirm that the merkle branch index is correct for our chain ID.
*/
@Test
public void shouldRejectIfNonceIncorrect() throws Exception {
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
// This will also break the difficulty check, but as that doesn't occur
// until the end, we can get away with it.
final TransactionInput in = auxpow.getCoinbase().getInput(0);
in.getScriptBytes()[44] = (byte) 0xff; // Break the nonce value
updateMerkleRootToMatchCoinbase(auxpow);
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
expectedEx.expectMessage("Aux POW wrong index");
auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
Utils.decodeCompactBits(0x1b06f8f0), true);
}
/**
* Having validated the AuxPoW header, the last check is that the block hash
* meets the target difficulty.
*/
@Test
public void shouldRejectHashAboveTarget() throws Exception {
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
expectedEx.expectMessage("Hash is higher than target: 000000000003178bb23160cdbc81af53f47cae9f479acf1e69849da42fd5bfca vs 0");
auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
Utils.decodeCompactBits(0x00), true);
}
/**
* Fix up the merkle root of the parent block header to match the
* coinbase transaction.
*/
private void updateMerkleRootToMatchCoinbase(final AuxPoW auxpow) {
final Transaction coinbase = auxpow.getCoinbase();
final Sha256Hash revisedCoinbaseHash = coinbase.getHash();
// The coinbase hash is the single leaf node in the merkle tree,
// so to get the root we need to hash it with itself.
// Note that bytes are reversed for hashing
final Sha256Hash revisedMerkleRoot = Sha256Hash.wrapReversed(
Sha256Hash.hashTwice(revisedCoinbaseHash.getReversedBytes(), 0, 32, revisedCoinbaseHash.getReversedBytes(), 0, 32)
);
auxpow.getParentBlockHeader().setMerkleRoot(revisedMerkleRoot);
auxpow.setCoinbaseBranch(new MerkleBranch(params, auxpow,
Collections.singletonList(revisedCoinbaseHash), MERKLE_ROOT_COINBASE_INDEX));
}
/**
* Test extraction of a nonce value from the coinbase transaction pubscript.
* This test primarily exists to ensure that byte order is correct, and that
* a nonce value above Integer.MAX_VALUE is still returned as a positive
* integer.
*/
@Test
public void testGetNonceFromScript() {
final byte[] script = Utils.HEX.decode("03251d0de4b883e5bda9e7a59ee4bb99e9b1bcfabe6d6dc6c83f297ee373df0d826f3148f218e4e4eb349e0bba715ad793ccc2d6beb6df40000000f09f909f4d696e65642062792079616e6779616e676368656e00000000000000000000000000000000");
final int pc = 55;
final long expResult = 0x9f909ff0L;
final long result = AuxPoW.getNonceFromScript(script, pc);
assertEquals(expResult, result);
}
/**
* Test of getExpectedIndex method, of class AuxPoW.
*/
@Test
public void testGetExpectedIndex() {
final long nonce = 0x9f909ff0L;
final int chainId = 98;
final int merkleHeight = 6;
final int expResult = 40;
final int result = AuxPoW.getExpectedIndex(nonce, chainId, merkleHeight);
assertEquals(expResult, result);
}
/**
* Tests the array matching algorithm for not accepting part of the array when it is in the end of the script.
*/
@Test
public void testArrayMatch() {
byte[] script = Utils.HEX.decode("089b911f5e471c0e1800f3384281ebec5b372fbb6f358790a92747ade271ccdf");
byte[] prefix = Utils.HEX.decode("089b911f");
byte[] suffix = Utils.HEX.decode("e271ccdf");
byte[] anywhere = Utils.HEX.decode("384281eb");
byte[] overTheEnd = Utils.HEX.decode("e271ccdf000000");
assertTrue(AuxPoW.arrayMatch(script, 0, prefix));
assertTrue(AuxPoW.arrayMatch(script, 28, suffix));
assertTrue(AuxPoW.arrayMatch(script, 11, anywhere));
assertFalse(AuxPoW.arrayMatch(script, 28, overTheEnd));
}
}

View File

@@ -0,0 +1,187 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.bitcoinj.core;
import org.libdohj.core.AltcoinSerializer;
import java.io.IOException;
import java.math.BigInteger;
import org.libdohj.params.DogecoinMainNetParams;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
/**
*
* @author jrn
*/
public class DogecoinBlockTest {
private final NetworkParameters params = DogecoinMainNetParams.get();
@Before
public void setUp() throws Exception {
Context context = new Context(params);
}
@Test
public void shouldExtractChainID() {
final long baseVersion = 2;
final long flags = 1;
final long chainID = 98;
final long auxpowVersion = (chainID << 16) | (flags << 8) | baseVersion;
assertEquals(chainID, AltcoinBlock.getChainID(auxpowVersion));
}
@Test
public void shouldExtractBaseVersion() {
final long baseVersion = 2;
final long flags = 1;
final long chainID = 98;
final long auxpowVersion = (chainID << 16) | (flags << 8) | baseVersion;
assertEquals(baseVersion, AltcoinBlock.getBaseVersion(auxpowVersion));
}
@Test
public void shouldParseBlock1() throws IOException {
byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block1.bin"));
AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer();
final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload);
assertEquals("82bc68038f6034c0596b6e313729793a887fded6e92a31fbdf70863f89d9bea2", block.getHashAsString());
assertEquals(1, block.getTransactions().size());
assertEquals(0x1e0ffff0L, block.getDifficultyTarget());
}
/**
* Test the first hardfork block.
* @throws IOException
*/
@Test
public void shouldParseBlock250000() throws IOException {
byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block250000.bin"));
AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer();
final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload);
assertEquals(2469341065L, block.getNonce());
final AuxPoW auxpow = block.getAuxPoW();
assertNull(auxpow);
assertEquals(6, block.getTransactions().size());
assertEquals("0e4bcfe8d970979f7e30e2809ab51908d435677998cf759169407824d4f36460", block.getHashAsString());
}
/**
* Confirm parsing of the first merged-mine block.
*
* @throws IOException
*/
@Test
public void shouldParseBlock371337() throws IOException {
byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block371337.bin"));
AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer();
final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload);
assertEquals("60323982f9c5ff1b5a954eac9dc1269352835f47c2c5222691d80f0d50dcf053", block.getHashAsString());
assertEquals(0, block.getNonce());
// Check block version values
assertEquals(2, block.getVersion());
assertEquals(98, block.getChainID());
assertTrue(block.getVersionFlags().get(0));
final AuxPoW auxpow = block.getAuxPoW();
assertNotNull(auxpow);
final Transaction auxpowCoinbase = auxpow.getCoinbase();
assertEquals("e5422732b20e9e7ecc243427abbe296e9528d308bb111aae8d30c3465e442de8", auxpowCoinbase.getHashAsString());
final Block parentBlock = auxpow.getParentBlockHeader();
assertEquals("45df41e40aba5b2a03d08bd1202a1c02ef3954d8aa22ea6c5ae62fd00f290ea9", parentBlock.getHashAsString());
assertNull(parentBlock.getTransactions());
final MerkleBranch blockchainMerkleBranch = auxpow.getChainMerkleBranch();
Sha256Hash[] expected = new Sha256Hash[] {
Sha256Hash.wrap("b541c848bc001d07d2bdf8643abab61d2c6ae50d5b2495815339a4b30703a46f"),
Sha256Hash.wrap("78d6abe48cee514cf3496f4042039acb7e27616dcfc5de926ff0d6c7e5987be7"),
Sha256Hash.wrap("a0469413ce64d67c43902d54ee3a380eff12ded22ca11cbd3842e15d48298103")
};
assertArrayEquals(expected, blockchainMerkleBranch.getHashes().toArray(new Sha256Hash[blockchainMerkleBranch.size()]));
final MerkleBranch coinbaseMerkleBranch = auxpow.getCoinbaseBranch();
expected = new Sha256Hash[] {
Sha256Hash.wrap("cd3947cd5a0c26fde01b05a3aa3d7a38717be6ae11d27239365024db36a679a9"),
Sha256Hash.wrap("48f9e8fef3411944e27f49ec804462c9e124dca0954c71c8560e8a9dd218a452"),
Sha256Hash.wrap("d11293660392e7c51f69477a6130237c72ecee2d0c1d3dc815841734c370331a")
};
assertArrayEquals(expected, coinbaseMerkleBranch.getHashes().toArray(new Sha256Hash[coinbaseMerkleBranch.size()]));
assertEquals(6, block.getTransactions().size());
assertTrue(auxpow.checkProofOfWork(block.getHash(), block.getDifficultyTargetAsInteger(), false));
}
/**
* Confirm parsing of block with a nonce value above Integer.MAX_VALUE.
* See https://github.com/rnicoll/libdohj/pull/7
*
* @throws IOException
*/
@Test
public void shouldParseBlock748634() throws IOException {
byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block748634.bin"));
AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer();
final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload);
assertEquals("bd98a06391115285265c04984e8505229739f6ffa5d498929a91fbe7c281ea7b", block.getHashAsString());
assertEquals(0, block.getNonce());
// Check block version values
assertEquals(2, block.getVersion());
assertEquals(98, block.getChainID());
assertTrue(block.getVersionFlags().get(0));
final AuxPoW auxpow = block.getAuxPoW();
assertNotNull(auxpow);
assertTrue(auxpow.checkProofOfWork(block.getHash(), block.getDifficultyTargetAsInteger(), true));
}
/**
* Confirm parsing of block with a nonce value above Integer.MAX_VALUE.
* See https://github.com/rnicoll/libdohj/issues/5
*
* @throws IOException
*/
@Test
public void shouldParseBlock894863() throws IOException {
byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block894863.bin"));
AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer();
final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload);
assertEquals("93a207e6d227f4d60ee64fad584b47255f654b0b6378d78e774123dd66f4fef9", block.getHashAsString());
assertEquals(0, block.getNonce());
// Check block version values
assertEquals(2, block.getVersion());
assertEquals(98, block.getChainID());
assertTrue(block.getVersionFlags().get(0));
final AuxPoW auxpow = block.getAuxPoW();
assertNotNull(auxpow);
final Transaction auxpowCoinbase = auxpow.getCoinbase();
assertEquals("c84431cf41f592373cc70db07f6804f945202f5f7baad31a8bbab89aaecb7b8b", auxpowCoinbase.getHashAsString());
assertTrue(auxpow.checkProofOfWork(block.getHash(), block.getDifficultyTargetAsInteger(), true));
}
/**
* Confirm that checking proof of work on an AuxPoW block works.
*/
@Test
public void shouldCheckAuxPoWProofOfWork() throws IOException {
byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block371337.bin"));
AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer();
final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload);
assertEquals(true, block.checkProofOfWork(true));
}
}

View File

@@ -0,0 +1,41 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.bitcoinj.core;
import java.io.IOException;
import org.libdohj.core.AltcoinSerializer;
import org.libdohj.params.LitecoinMainNetParams;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
/**
*
* @author jrn
*/
public class LitecoinBlockTest {
private NetworkParameters params = LitecoinMainNetParams.get();
@Before
public void setUp() throws Exception {
Context context = new Context(params);
}
@Test
public void shouldParseBlock1() throws IOException {
byte[] payload = Util.getBytes(getClass().getResourceAsStream("litecoin_block1.bin"));
AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer();
final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload);
assertEquals("80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f", block.getHashAsString());
assertEquals(params.getGenesisBlock().getHash(), block.getPrevBlockHash());
assertEquals(1, block.getTransactions().size());
assertEquals(0x1e0ffff0L, block.getDifficultyTarget());
assertTrue(block.checkProofOfWork(false));
}
}

View File

@@ -0,0 +1,61 @@
package org.bitcoinj.core;
import static org.bitcoinj.core.AuxPoWTest.params;
import org.bitcoinj.params.TestNet3Params;
import org.junit.Test;
import static org.bitcoinj.core.Util.getBytes;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
/**
* Check merkle branch parsing and root calculation.
*/
public class MerkleBranchTest {
static final NetworkParameters params = TestNet3Params.get();
/**
* Parse the coinbase merkle branch from Dogecoin block #403,931.
*/
@Test
public void parseMerkleBranch() throws Exception {
byte[] branchAsBytes = getBytes(getClass().getResourceAsStream("auxpow_merkle_branch.bin"));
MerkleBranch branch = new MerkleBranch(params, (ChildMessage) null, branchAsBytes, 0);
Sha256Hash[] expected = new Sha256Hash[] {
Sha256Hash.wrap("be079078869399faccaa764c10e9df6e9981701759ad18e13724d9ca58831348"),
Sha256Hash.wrap("5f5bfb2c79541778499cab956a103887147f2ab5d4a717f32f9eeebd29e1f894"),
Sha256Hash.wrap("d8c6fe42ca25076159cd121a5e20c48c1bc53ab90730083e44a334566ea6bbcb")
};
assertArrayEquals(expected, branch.getHashes().toArray(new Sha256Hash[branch.size()]));
}
/**
* Parse the transaction merkle branch from Dogecoin block #403,931, then
* serialize it back again to verify serialization works.
*/
@Test
public void serializeMerkleBranch() throws Exception {
byte[] expected = getBytes(getClass().getResourceAsStream("auxpow_merkle_branch.bin"));
MerkleBranch branch = new MerkleBranch(params, (ChildMessage) null, expected, 0,
params.getDefaultSerializer());
byte[] actual = branch.bitcoinSerialize();
assertArrayEquals(expected, actual);
}
/**
* Calculate the AuxPoW merkle branch root from Dogecoin block #403,931.
*/
@Test
public void calculateRootBranch() throws Exception {
byte[] branchAsBytes = getBytes(getClass().getResourceAsStream("auxpow_merkle_branch2.bin"));
MerkleBranch branch = new MerkleBranch(params, (ChildMessage) null, branchAsBytes, 0);
Sha256Hash txId = Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609");
Sha256Hash expected = Sha256Hash.wrap("ce3040fdb7e37484f6a1ca4f8f5da81e6b7e404ec91102315a233e03a0c39c95");
assertEquals(expected, branch.calculateMerkleRoot(txId));
}
}

View File

@@ -0,0 +1,35 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.bitcoinj.core;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
*
* @author jrn
*/
public class Util {
public static byte[] getBytes(InputStream inputStream) throws IOException {
assert null != inputStream;
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int numberRead;
byte[] data = new byte[BUFFER_SIZE];
while ((numberRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, numberRead);
}
buffer.flush();
return buffer.toByteArray();
}
private static final int BUFFER_SIZE = 1024;
}

View File

@@ -0,0 +1,173 @@
/*
* Copyright 2015 J. Ross Nicoll
*
* 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.libdohj.params;
import java.io.IOException;
import org.bitcoinj.core.AltcoinBlock;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Context;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Util;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;
import org.libdohj.core.AltcoinSerializer;
/**
*
* @author Ross Nicoll
*/
public class AbstractDogecoinParamsTest {
private static final AbstractDogecoinParams params = DogecoinMainNetParams.get();
@Before
public void setUp() throws Exception {
Context context = new Context(params);
}
@Test
public void shouldCalculateBitcoinLikeDifficulty() {
int previousHeight = 239;
long previousBlockTime = 1386475638; // Block 239
long lastRetargetDifficulty = 0x1e0ffff0;
long lastRetargetTime = 1386474927; // Block 1
long nextDifficulty = 0x1e00ffff;
long newDifficulty =
params.calculateNewDifficultyTargetInner(previousHeight, previousBlockTime,
lastRetargetDifficulty, lastRetargetTime, nextDifficulty);
assertEquals(0x1e00ffff, newDifficulty);
previousHeight = 9599;
previousBlockTime = 1386954113;
lastRetargetDifficulty = 0x1c1a1206;
lastRetargetTime = 1386942008; // Block 9359
nextDifficulty = 0x1c15ea59;
newDifficulty =
params.calculateNewDifficultyTargetInner(previousHeight, previousBlockTime,
lastRetargetDifficulty, lastRetargetTime, nextDifficulty);
assertEquals(0x1c15ea59, newDifficulty);
}
/**
* Test block 720, where the time interval is below the minimum time interval
* (900 seconds).
*/
@Test
public void shouldConstrainActualTime() {
final int previousHeight = 719;
final long previousBlockTime = 1386476362; // Block 719
final long lastRetargetDifficulty = 0x1e00ffff;
final long lastRetargetTime = 1386475840; // Block 479
final long nextDifficulty = 0x1d0ffff0; // Block 720
final long newDifficulty =
params.calculateNewDifficultyTargetInner(previousHeight, previousBlockTime,
lastRetargetDifficulty, lastRetargetTime, nextDifficulty);
assertEquals(0x1d0ffff0, newDifficulty);
}
@Test
public void shouldCalculateDigishieldDifficulty() {
final int previousHeight = 145000;
final long previousBlockTime = 1395094679;
final long lastRetargetDifficulty = 0x1b499dfd;
final long lastRetargetTime = 1395094427;
final long nextDifficulty = 0x1b671062;
final long newDifficulty =
params.calculateNewDifficultyTargetInner(previousHeight, previousBlockTime,
lastRetargetDifficulty, lastRetargetTime, nextDifficulty);
assertEquals(0x1b671062, newDifficulty);
}
@Test
public void shouldCalculateDigishieldDifficultyRounding() {
// Test case for correct rounding of modulated time
final int previousHeight = 145001;
final long previousBlockTime = 1395094727;
final long lastRetargetDifficulty = 0x1b671062;
final long lastRetargetTime = 1395094679;
final long nextDifficulty = 0x1b6558a4;
final long newDifficulty =
params.calculateNewDifficultyTargetInner(previousHeight, previousBlockTime,
lastRetargetDifficulty, lastRetargetTime, nextDifficulty);
assertEquals(0x1b6558a4, newDifficulty);
}
@Test
public void shouldCalculateRetarget() throws IOException {
// Do a more in-depth test for the first retarget
byte[] payload;
AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer();
final AltcoinBlock block239;
final AltcoinBlock block479;
final AltcoinBlock block480;
final AltcoinBlock block719;
final AltcoinBlock block720;
payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block239.bin"));
block239 = (AltcoinBlock)serializer.makeBlock(payload);
payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block479.bin"));
block479 = (AltcoinBlock)serializer.makeBlock(payload);
payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block480.bin"));
block480 = (AltcoinBlock)serializer.makeBlock(payload);
payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block719.bin"));
block719 = (AltcoinBlock)serializer.makeBlock(payload);
payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block720.bin"));
block720 = (AltcoinBlock)serializer.makeBlock(payload);
assertEquals(Sha256Hash.wrap("f9533416310fc4484cf43405a858b06afc9763ad401d267c1835d77e7d225a4e"), block239.getHash());
assertEquals(Sha256Hash.wrap("ed83c923b532835f6597f70def42910aa9e06880e8a19b68f6b4a787f2b4b69f"), block479.getHash());
assertEquals(Sha256Hash.wrap("a0e6d1cdef02b394d31628c3281f67e8534bec74fda1a4294b58be80c3fdf3f3"), block480.getHash());
assertEquals(Sha256Hash.wrap("82e56e141ccfe019d475382d9a108ef86afeb297d95443dfd7250e57af805696"), block719.getHash());
assertEquals(Sha256Hash.wrap("6b34f1a7de1954beb0ddf100bb2b618ff0183b6ae2b4a9376721ef8e04ab3b39"), block720.getHash());
assertEquals(block480.getDifficultyTarget(), params.calculateNewDifficultyTargetInner(479, block479, block480, block239));
assertEquals(block720.getDifficultyTarget(), params.calculateNewDifficultyTargetInner(719, block719, block720, block479));
}
/**
* Confirm subsidy rules follow Dogecoin pattern.
*/
@Test
public void shouldCalculateSubsidy() {
assertEquals(Coin.COIN.multiply(1000000), params.getBlockSubsidy(0));
assertEquals(Coin.COIN.multiply(1000000), params.getBlockSubsidy(99999));
assertEquals(Coin.COIN.multiply(500000), params.getBlockSubsidy(100000));
assertEquals(Coin.COIN.multiply(500000), params.getBlockSubsidy(144999));
assertEquals(Coin.COIN.multiply(250000), params.getBlockSubsidy(145000));
assertEquals(Coin.COIN.multiply(250000), params.getBlockSubsidy(199999));
assertEquals(Coin.COIN.multiply(125000), params.getBlockSubsidy(200000));
assertEquals(Coin.COIN.multiply(125000), params.getBlockSubsidy(299999));
assertEquals(Coin.COIN.multiply(62500), params.getBlockSubsidy(300000));
assertEquals(Coin.COIN.multiply(62500), params.getBlockSubsidy(399999));
assertEquals(Coin.COIN.multiply(31250), params.getBlockSubsidy(400000));
assertEquals(Coin.COIN.multiply(31250), params.getBlockSubsidy(499999));
assertEquals(Coin.COIN.multiply(15625), params.getBlockSubsidy(500000));
assertEquals(Coin.COIN.multiply(15625), params.getBlockSubsidy(599999));
assertEquals(Coin.COIN.multiply(10000), params.getBlockSubsidy(600000));
assertEquals(Coin.COIN.multiply(10000), params.getBlockSubsidy(699999));
}
@Test
public void targetSpacingShouldBe60() {
// The getTargetSpacing() method only really exists for future expansion,
// and currently should always return 60 seconds
assertEquals(60, params.getTargetSpacing(0));
assertEquals(60, params.getTargetSpacing(1));
assertEquals(60, params.getTargetSpacing(params.getDigishieldBlockHeight() - 1));
assertEquals(60, params.getTargetSpacing(params.getDigishieldBlockHeight()));
assertEquals(60, params.getTargetSpacing(params.getDigishieldBlockHeight() + 1));
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2015 J. Ross Nicoll
*
* 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.libdohj.params;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Context;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;
/**
*
* @author Ross Nicoll
*/
public class AbstractLitecoinParamsTest {
private static final AbstractLitecoinParams params = LitecoinMainNetParams.get();
@Before
public void setUp() throws Exception {
Context context = new Context(params);
}
/**
* Confirm subsidy rules follow Litecoin pattern.
*/
@Test
public void shouldCalculateSubsidy() {
assertEquals(Coin.COIN.multiply(50), params.getBlockSubsidy(0));
assertEquals(Coin.COIN.multiply(50), params.getBlockSubsidy(839999));
assertEquals(Coin.COIN.multiply(25), params.getBlockSubsidy(840000));
assertEquals(Coin.COIN.multiply(25), params.getBlockSubsidy(1679999));
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2015 J. Ross Nicoll
*
* 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.libdohj.params;
import org.bitcoinj.core.Context;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;
/**
*
* @author Ross Nicoll
*/
public class LitecoinTestNet3ParamsTest {
private static final LitecoinTestNet3Params params = LitecoinTestNet3Params.get();
@Before
public void setUp() throws Exception {
Context context = new Context(params);
}
// Confirm subsidy rules follow Dogecoin pattern
@Test
public void shouldHaveCorrectValues() {
assertEquals(100, params.getMajorityWindow());
assertEquals(51, params.getMajorityEnforceBlockUpgrade());
assertEquals(75, params.getMajorityRejectBlockOutdated());
}
}