mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-11-02 13:37:24 +00:00
Restructure libdohj around Maven modules
This commit is contained in:
102
core/pom.xml
Normal file
102
core/pom.xml
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
6012
core/src/main/java/com/dogecoin/protocols/payments/Protos.java
Normal file
6012
core/src/main/java/com/dogecoin/protocols/payments/Protos.java
Normal file
File diff suppressed because it is too large
Load Diff
305
core/src/main/java/org/bitcoinj/core/AltcoinBlock.java
Normal file
305
core/src/main/java/org/bitcoinj/core/AltcoinBlock.java
Normal 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();
|
||||
}
|
||||
}
|
||||
469
core/src/main/java/org/bitcoinj/core/AuxPoW.java
Normal file
469
core/src/main/java/org/bitcoinj/core/AuxPoW.java
Normal 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;
|
||||
}
|
||||
}
|
||||
222
core/src/main/java/org/bitcoinj/core/MerkleBranch.java
Normal file
222
core/src/main/java/org/bitcoinj/core/MerkleBranch.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
49
core/src/main/java/org/libdohj/core/AltcoinSerializer.java
Normal file
49
core/src/main/java/org/libdohj/core/AltcoinSerializer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
35
core/src/main/java/org/libdohj/core/ScryptHash.java
Normal file
35
core/src/main/java/org/libdohj/core/ScryptHash.java
Normal 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);
|
||||
}
|
||||
}
|
||||
33
core/src/main/java/org/libdohj/core/Utils.java
Normal file
33
core/src/main/java/org/libdohj/core/Utils.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 { }
|
||||
}
|
||||
118
core/src/main/java/org/libdohj/params/DogecoinMainNetParams.java
Normal file
118
core/src/main/java/org/libdohj/params/DogecoinMainNetParams.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
121
core/src/main/java/org/libdohj/params/LitecoinMainNetParams.java
Normal file
121
core/src/main/java/org/libdohj/params/LitecoinMainNetParams.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
5
core/src/main/java/org/libdohj/params/package-info.java
Normal file
5
core/src/main/java/org/libdohj/params/package-info.java
Normal 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;
|
||||
BIN
core/src/main/resources/org.dogecoin.production.checkpoints
Normal file
BIN
core/src/main/resources/org.dogecoin.production.checkpoints
Normal file
Binary file not shown.
BIN
core/src/main/resources/org.dogecoin.test.checkpoints
Normal file
BIN
core/src/main/resources/org.dogecoin.test.checkpoints
Normal file
Binary file not shown.
BIN
core/src/main/resources/org.litecoin.production.checkpoints
Normal file
BIN
core/src/main/resources/org.litecoin.production.checkpoints
Normal file
Binary file not shown.
47
core/src/paymentrequest.proto
Normal file
47
core/src/paymentrequest.proto
Normal 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
|
||||
}
|
||||
69
core/src/test/java/org/bitcoinj/core/AltcoinBlockTest.java
Normal file
69
core/src/test/java/org/bitcoinj/core/AltcoinBlockTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
429
core/src/test/java/org/bitcoinj/core/AuxPoWTest.java
Normal file
429
core/src/test/java/org/bitcoinj/core/AuxPoWTest.java
Normal 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));
|
||||
}
|
||||
}
|
||||
187
core/src/test/java/org/bitcoinj/core/DogecoinBlockTest.java
Normal file
187
core/src/test/java/org/bitcoinj/core/DogecoinBlockTest.java
Normal 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));
|
||||
}
|
||||
}
|
||||
41
core/src/test/java/org/bitcoinj/core/LitecoinBlockTest.java
Normal file
41
core/src/test/java/org/bitcoinj/core/LitecoinBlockTest.java
Normal 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));
|
||||
}
|
||||
}
|
||||
61
core/src/test/java/org/bitcoinj/core/MerkleBranchTest.java
Normal file
61
core/src/test/java/org/bitcoinj/core/MerkleBranchTest.java
Normal 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));
|
||||
}
|
||||
}
|
||||
35
core/src/test/java/org/bitcoinj/core/Util.java
Normal file
35
core/src/test/java/org/bitcoinj/core/Util.java
Normal 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;
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
BIN
core/src/test/resources/org/bitcoinj/core/auxpow_header.bin
Normal file
BIN
core/src/test/resources/org/bitcoinj/core/auxpow_header.bin
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
core/src/test/resources/org/bitcoinj/core/dogecoin_block1.bin
Normal file
BIN
core/src/test/resources/org/bitcoinj/core/dogecoin_block1.bin
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
core/src/test/resources/org/bitcoinj/core/litecoin_block1.bin
Normal file
BIN
core/src/test/resources/org/bitcoinj/core/litecoin_block1.bin
Normal file
Binary file not shown.
BIN
core/src/test/resources/org/libdohj/params/dogecoin_block239.bin
Normal file
BIN
core/src/test/resources/org/libdohj/params/dogecoin_block239.bin
Normal file
Binary file not shown.
BIN
core/src/test/resources/org/libdohj/params/dogecoin_block479.bin
Normal file
BIN
core/src/test/resources/org/libdohj/params/dogecoin_block479.bin
Normal file
Binary file not shown.
BIN
core/src/test/resources/org/libdohj/params/dogecoin_block480.bin
Normal file
BIN
core/src/test/resources/org/libdohj/params/dogecoin_block480.bin
Normal file
Binary file not shown.
BIN
core/src/test/resources/org/libdohj/params/dogecoin_block719.bin
Normal file
BIN
core/src/test/resources/org/libdohj/params/dogecoin_block719.bin
Normal file
Binary file not shown.
BIN
core/src/test/resources/org/libdohj/params/dogecoin_block720.bin
Normal file
BIN
core/src/test/resources/org/libdohj/params/dogecoin_block720.bin
Normal file
Binary file not shown.
Reference in New Issue
Block a user