diff --git a/src/main/java/org/qortal/api/resource/CrossChainResource.java b/src/main/java/org/qortal/api/resource/CrossChainResource.java index 74136f50..9ac04308 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainResource.java @@ -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 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 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); diff --git a/src/main/java/org/qortal/crosschain/BTC.java b/src/main/java/org/qortal/crosschain/BTC.java index 88428262..65af8781 100644 --- a/src/main/java/org/qortal/crosschain/BTC.java +++ b/src/main/java/org/qortal/crosschain/BTC.java @@ -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 getUnspentOutputs(String base58Address) { diff --git a/src/test/java/org/qortal/test/btcacct/BuildP2SH.java b/src/test/java/org/qortal/test/btcacct/BuildP2SH.java index 25a95d8b..25f0430e 100644 --- a/src/test/java/org/qortal/test/btcacct/BuildP2SH.java +++ b/src/test/java/org/qortal/test/btcacct/BuildP2SH.java @@ -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) { diff --git a/src/test/java/org/qortal/test/btcacct/CheckP2SH.java b/src/test/java/org/qortal/test/btcacct/CheckP2SH.java index 8313d573..25f20c68 100644 --- a/src/test/java/org/qortal/test/btcacct/CheckP2SH.java +++ b/src/test/java/org/qortal/test/btcacct/CheckP2SH.java @@ -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 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")); diff --git a/src/test/java/org/qortal/test/btcacct/Redeem.java b/src/test/java/org/qortal/test/btcacct/Redeem.java index 091f2234..5a14906b 100644 --- a/src/test/java/org/qortal/test/btcacct/Redeem.java +++ b/src/test/java/org/qortal/test/btcacct/Redeem.java @@ -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 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); diff --git a/src/test/java/org/qortal/test/btcacct/Refund.java b/src/test/java/org/qortal/test/btcacct/Refund.java index a694ee14..b4ab94b5 100644 --- a/src/test/java/org/qortal/test/btcacct/Refund.java +++ b/src/test/java/org/qortal/test/btcacct/Refund.java @@ -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 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);