Reduce bitcoinj exposure to classes outside of org.qortal.crosschain package.

BTC.getBalance() now returns Long instead of Coin.

BTC.FORMAT.format(Coin) changed to BTC.format(Coin or long).

Added BTC.deriveP2shAddress(byte[] redeemScriptBytes).
This commit is contained in:
catbref 2020-06-11 14:16:49 +01:00
parent 04d691991a
commit 593b61ea4b
6 changed files with 46 additions and 34 deletions

View File

@ -498,17 +498,17 @@ public class CrossChainResource {
// Check P2SH is funded
Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString());
Long p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString());
if (p2shBalance == null)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN);
CrossChainBitcoinP2SHStatus p2shStatus = new CrossChainBitcoinP2SHStatus();
p2shStatus.bitcoinP2shAddress = p2shAddress.toString();
p2shStatus.bitcoinP2shBalance = BigDecimal.valueOf(p2shBalance.value, 8);
p2shStatus.bitcoinP2shBalance = BigDecimal.valueOf(p2shBalance, 8);
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString());
if (p2shBalance.value >= crossChainTradeData.expectedBitcoin && !fundingOutputs.isEmpty()) {
if (p2shBalance >= crossChainTradeData.expectedBitcoin && !fundingOutputs.isEmpty()) {
p2shStatus.canRedeem = now >= medianBlockTime * 1000L;
p2shStatus.canRefund = now >= crossChainTradeData.lockTime * 1000L;
}
@ -591,7 +591,7 @@ public class CrossChainResource {
// Check P2SH is funded
Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString());
Long p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString());
if (p2shBalance == null)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN);
@ -603,10 +603,10 @@ public class CrossChainResource {
if (!canRefund)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_TOO_SOON);
if (p2shBalance.value < crossChainTradeData.expectedBitcoin)
if (p2shBalance < crossChainTradeData.expectedBitcoin)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_BALANCE_ISSUE);
Coin refundAmount = p2shBalance.subtract(Coin.valueOf(refundRequest.bitcoinMinerFee.unscaledValue().longValue()));
Coin refundAmount = Coin.valueOf(p2shBalance - refundRequest.bitcoinMinerFee.unscaledValue().longValue());
org.bitcoinj.core.Transaction refundTransaction = BTCACCT.buildRefundTransaction(refundAmount, refundKey, fundingOutputs, redeemScriptBytes, crossChainTradeData.lockTime);
boolean wasBroadcast = BTC.getInstance().broadcastTransaction(refundTransaction);
@ -692,11 +692,11 @@ public class CrossChainResource {
long now = NTP.getTime();
// Check P2SH is funded
Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString());
Long p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString());
if (p2shBalance == null)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN);
if (p2shBalance.value < crossChainTradeData.expectedBitcoin)
if (p2shBalance < crossChainTradeData.expectedBitcoin)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_BALANCE_ISSUE);
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString());
@ -707,7 +707,7 @@ public class CrossChainResource {
if (!canRedeem)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_TOO_SOON);
Coin redeemAmount = p2shBalance.subtract(Coin.valueOf(redeemRequest.bitcoinMinerFee.unscaledValue().longValue()));
Coin redeemAmount = Coin.valueOf(p2shBalance - redeemRequest.bitcoinMinerFee.unscaledValue().longValue());
org.bitcoinj.core.Transaction redeemTransaction = BTCACCT.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, redeemRequest.secret);
boolean wasBroadcast = BTC.getInstance().broadcastTransaction(redeemTransaction);

View File

@ -8,6 +8,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
@ -16,13 +17,13 @@ import org.bitcoinj.params.RegTestParams;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.utils.MonetaryFormat;
import org.qortal.crypto.Crypto;
import org.qortal.settings.Settings;
import org.qortal.utils.BitTwiddling;
import org.qortal.utils.Pair;
public class BTC {
public static final MonetaryFormat FORMAT = new MonetaryFormat().minDecimals(8).postfixCode();
public static final long NO_LOCKTIME_NO_RBF_SEQUENCE = 0xFFFFFFFFL;
public static final long LOCKTIME_NO_RBF_SEQUENCE = NO_LOCKTIME_NO_RBF_SEQUENCE - 1;
public static final int HASH160_LENGTH = 20;
@ -30,6 +31,7 @@ public class BTC {
protected static final Logger LOGGER = LogManager.getLogger(BTC.class);
private static final int TIMESTAMP_OFFSET = 4 + 32 + 32;
private static final MonetaryFormat FORMAT = new MonetaryFormat().minDecimals(8).postfixCode();
public enum BitcoinNet {
MAIN {
@ -88,6 +90,20 @@ public class BTC {
// Actual useful methods for use by other classes
public static String format(Coin amount) {
return BTC.FORMAT.format(amount).toString();
}
public static String format(long amount) {
return format(Coin.valueOf(amount));
}
public String deriveP2shAddress(byte[] redeemScriptBytes) {
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
return p2shAddress.toString();
}
/** Returns median timestamp from latest 11 blocks, in seconds. */
public Integer getMedianBlockTime() {
Integer height = this.electrumX.getCurrentHeight();
@ -107,12 +123,8 @@ public class BTC {
return blockTimestamps.get(5);
}
public Coin getBalance(String base58Address) {
Long balance = this.electrumX.getBalance(addressToScript(base58Address));
if (balance == null)
return null;
return Coin.valueOf(balance);
public Long getBalance(String base58Address) {
return this.electrumX.getBalance(addressToScript(base58Address));
}
public List<TransactionOutput> getUnspentOutputs(String base58Address) {

View File

@ -98,7 +98,7 @@ public class BuildP2SH {
System.out.println(String.format("Bitcoin redeem amount: %s", bitcoinAmount.toPlainString()));
System.out.println(String.format("Redeem Bitcoin address: %s", redeemBitcoinAddress));
System.out.println(String.format("Redeem miner's fee: %s", BTC.FORMAT.format(bitcoinFee)));
System.out.println(String.format("Redeem miner's fee: %s", BTC.format(bitcoinFee)));
System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime));
System.out.println(String.format("Hash of secret: %s", HashCode.fromBytes(secretHash)));
@ -115,7 +115,7 @@ public class BuildP2SH {
// Fund P2SH
System.out.println(String.format("\nYou need to fund %s with %s (includes redeem/refund fee of %s)",
p2shAddress.toString(), BTC.FORMAT.format(bitcoinAmount), BTC.FORMAT.format(bitcoinFee)));
p2shAddress.toString(), BTC.format(bitcoinAmount), BTC.format(bitcoinFee)));
System.out.println("Once this is done, responder should run Respond to check P2SH funding and create AT");
} catch (DataException e) {

View File

@ -106,7 +106,7 @@ public class CheckP2SH {
System.out.println(String.format("Bitcoin redeem amount: %s", bitcoinAmount.toPlainString()));
System.out.println(String.format("Redeem Bitcoin address: %s", refundBitcoinAddress));
System.out.println(String.format("Redeem miner's fee: %s", BTC.FORMAT.format(bitcoinFee)));
System.out.println(String.format("Redeem miner's fee: %s", BTC.format(bitcoinFee)));
System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime));
System.out.println(String.format("Hash of secret: %s", HashCode.fromBytes(secretHash)));
@ -135,12 +135,12 @@ public class CheckP2SH {
System.out.println(String.format("Too soon (%s) to redeem based on median block time %s", LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneOffset.UTC), LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneOffset.UTC)));
// Check P2SH is funded
Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString());
Long p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString());
if (p2shBalance == null) {
System.err.println(String.format("Unable to check P2SH address %s balance", p2shAddress));
System.exit(2);
}
System.out.println(String.format("P2SH address %s balance: %s", p2shAddress, BTC.FORMAT.format(p2shBalance)));
System.out.println(String.format("P2SH address %s balance: %s", p2shAddress, BTC.format(p2shBalance)));
// Grab all P2SH funding transactions (just in case there are more than one)
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString());
@ -152,7 +152,7 @@ public class CheckP2SH {
System.out.println(String.format("Found %d output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : "")));
for (TransactionOutput fundingOutput : fundingOutputs)
System.out.println(String.format("Output %s:%d amount %s", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex(), BTC.FORMAT.format(fundingOutput.getValue())));
System.out.println(String.format("Output %s:%d amount %s", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex(), BTC.format(fundingOutput.getValue())));
if (fundingOutputs.isEmpty()) {
System.err.println(String.format("Can't redeem spent/unfunded P2SH"));

View File

@ -107,7 +107,7 @@ public class Redeem {
System.out.println("Confirm the following is correct based on the info you've given:");
System.out.println(String.format("Redeem PRIVATE key: %s", HashCode.fromBytes(redeemPrivateKey)));
System.out.println(String.format("Redeem miner's fee: %s", BTC.FORMAT.format(bitcoinFee)));
System.out.println(String.format("Redeem miner's fee: %s", BTC.format(bitcoinFee)));
System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime));
// New/derived info
@ -147,12 +147,12 @@ public class Redeem {
}
// Check P2SH is funded
Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString());
Long p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString());
if (p2shBalance == null) {
System.err.println(String.format("Unable to check P2SH address %s balance", p2shAddress));
System.exit(2);
}
System.out.println(String.format("P2SH address %s balance: %s BTC", p2shAddress, p2shBalance.toPlainString()));
System.out.println(String.format("P2SH address %s balance: %s", p2shAddress, BTC.format(p2shBalance)));
// Grab all P2SH funding transactions (just in case there are more than one)
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString());
@ -164,7 +164,7 @@ public class Redeem {
System.out.println(String.format("Found %d output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : "")));
for (TransactionOutput fundingOutput : fundingOutputs)
System.out.println(String.format("Output %s:%d amount %s", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex(), BTC.FORMAT.format(fundingOutput.getValue())));
System.out.println(String.format("Output %s:%d amount %s", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex(), BTC.format(fundingOutput.getValue())));
if (fundingOutputs.isEmpty()) {
System.err.println(String.format("Can't redeem spent/unfunded P2SH"));
@ -179,8 +179,8 @@ public class Redeem {
for (TransactionOutput fundingOutput : fundingOutputs)
System.out.println(String.format("Using output %s:%d for redeem", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex()));
Coin redeemAmount = p2shBalance.subtract(bitcoinFee);
System.out.println(String.format("Spending %s of output, with %s as mining fee", BTC.FORMAT.format(redeemAmount), BTC.FORMAT.format(bitcoinFee)));
Coin redeemAmount = Coin.valueOf(p2shBalance).subtract(bitcoinFee);
System.out.println(String.format("Spending %s of output, with %s as mining fee", BTC.format(redeemAmount), BTC.format(bitcoinFee)));
Transaction redeemTransaction = BTCACCT.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, secret);

View File

@ -110,7 +110,7 @@ public class Refund {
System.out.println(String.format("Redeem Bitcoin address: %s", redeemBitcoinAddress));
System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime));
System.out.println(String.format("P2SH address: %s", p2shAddress));
System.out.println(String.format("Refund miner's fee: %s", BTC.FORMAT.format(bitcoinFee)));
System.out.println(String.format("Refund miner's fee: %s", BTC.format(bitcoinFee)));
// New/derived info
@ -151,12 +151,12 @@ public class Refund {
}
// Check P2SH is funded
Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString());
Long p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString());
if (p2shBalance == null) {
System.err.println(String.format("Unable to check P2SH address %s balance", p2shAddress));
System.exit(2);
}
System.out.println(String.format("P2SH address %s balance: %s BTC", p2shAddress, p2shBalance.toPlainString()));
System.out.println(String.format("P2SH address %s balance: %s", p2shAddress, BTC.format(p2shBalance)));
// Grab all P2SH funding transactions (just in case there are more than one)
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString());
@ -168,7 +168,7 @@ public class Refund {
System.out.println(String.format("Found %d output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : "")));
for (TransactionOutput fundingOutput : fundingOutputs)
System.out.println(String.format("Output %s:%d amount %s", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex(), BTC.FORMAT.format(fundingOutput.getValue())));
System.out.println(String.format("Output %s:%d amount %s", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex(), BTC.format(fundingOutput.getValue())));
if (fundingOutputs.isEmpty()) {
System.err.println(String.format("Can't refund spent/unfunded P2SH"));
@ -183,8 +183,8 @@ public class Refund {
for (TransactionOutput fundingOutput : fundingOutputs)
System.out.println(String.format("Using output %s:%d for redeem", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex()));
Coin refundAmount = p2shBalance.subtract(bitcoinFee);
System.out.println(String.format("Spending %s of output, with %s as mining fee", BTC.FORMAT.format(refundAmount), BTC.FORMAT.format(bitcoinFee)));
Coin refundAmount = Coin.valueOf(p2shBalance).subtract(bitcoinFee);
System.out.println(String.format("Spending %s of output, with %s as mining fee", BTC.format(refundAmount), BTC.format(bitcoinFee)));
Transaction redeemTransaction = BTCACCT.buildRefundTransaction(refundAmount, refundKey, fundingOutputs, redeemScriptBytes, lockTime);