forked from Qortal/qortal
WIP: More defensive ElectrumX calls. Bring non-trade-bot API calls up to date
This commit is contained in:
parent
f9b726a75d
commit
b294f5e333
@ -8,10 +8,10 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class CrossChainCancelRequest {
|
||||
|
||||
@Schema(description = "AT creator's public key", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry")
|
||||
public byte[] creatorPublicKey;
|
||||
@Schema(description = "AT's trade public key", example = "K6wuddsBV3HzRrXFFezE7P5MoRXp5m3mEDokRDGZB6ry")
|
||||
public byte[] tradePublicKey;
|
||||
|
||||
@Schema(description = "Qortal AT address")
|
||||
@Schema(description = "Qortal trade AT address")
|
||||
public String atAddress;
|
||||
|
||||
public CrossChainCancelRequest() {
|
||||
|
@ -14,8 +14,11 @@ public class CrossChainSecretRequest {
|
||||
@Schema(description = "Qortal AT address")
|
||||
public String atAddress;
|
||||
|
||||
@Schema(description = "secret-A + secret-B (64 bytes)", example = "2gt2nSVBFknLfdU5buKtScLuTibkt9C3x6PZVqnA3AJ6BdEf3A9RbSj5Hn5QkvavdTTfmttNEaYEVw34TZdz135Q")
|
||||
public byte[] secret;
|
||||
@Schema(description = "secret-A (32 bytes)", example = "FHMzten4he9jZ4HGb4297Utj6F5g2w7serjq2EnAg2s1")
|
||||
public byte[] secretA;
|
||||
|
||||
@Schema(description = "secret-B (32 bytes)", example = "EN2Bgx3BcEMtxFCewmCVSMkfZjVKYhx3KEXC5A21KBGx")
|
||||
public byte[] secretB;
|
||||
|
||||
public CrossChainSecretRequest() {
|
||||
}
|
||||
|
@ -9,13 +9,13 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
public class CrossChainTradeRequest {
|
||||
|
||||
@Schema(description = "AT creator's public key", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry")
|
||||
public byte[] creatorPublicKey;
|
||||
public byte[] tradePublicKey;
|
||||
|
||||
@Schema(description = "Qortal AT address")
|
||||
public String atAddress;
|
||||
|
||||
@Schema(description = "Qortal address for trade partner/recipient")
|
||||
public String recipient;
|
||||
@Schema(description = "Signature of trading partner's MESSAGE transaction")
|
||||
public byte[] messageTransactionSignature;
|
||||
|
||||
public CrossChainTradeRequest() {
|
||||
}
|
||||
|
@ -68,6 +68,7 @@ import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.transaction.Transaction.TransactionType;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.transform.Transformer;
|
||||
@ -76,8 +77,6 @@ import org.qortal.transform.transaction.MessageTransactionTransformer;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
@Path("/crosschain")
|
||||
@Tag(name = "Cross-Chain")
|
||||
public class CrossChainResource {
|
||||
@ -224,7 +223,7 @@ public class CrossChainResource {
|
||||
summary = "Builds raw, unsigned MESSAGE transaction that sends cross-chain trade recipient address, triggering 'trade' mode",
|
||||
description = "Specify address of cross-chain AT that needs to be messaged, and address of Qortal recipient.<br>"
|
||||
+ "AT needs to be in 'offer' mode. Messages sent to an AT in 'trade' mode will be ignored, but still cost fees to send!<br>"
|
||||
+ "You need to sign output with same account as the AT creator otherwise the MESSAGE transaction will be invalid.",
|
||||
+ "You need to sign output with trade private key otherwise the MESSAGE transaction will be invalid.",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
@ -248,28 +247,52 @@ public class CrossChainResource {
|
||||
public String sendTradeRecipient(CrossChainTradeRequest tradeRequest) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
byte[] creatorPublicKey = tradeRequest.creatorPublicKey;
|
||||
byte[] tradePublicKey = tradeRequest.tradePublicKey;
|
||||
|
||||
if (creatorPublicKey == null || creatorPublicKey.length != Transformer.PUBLIC_KEY_LENGTH)
|
||||
if (tradePublicKey == null || tradePublicKey.length != Transformer.PUBLIC_KEY_LENGTH)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
|
||||
|
||||
if (tradeRequest.atAddress == null || !Crypto.isValidAtAddress(tradeRequest.atAddress))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||
|
||||
if (tradeRequest.recipient == null || !Crypto.isValidAddress(tradeRequest.recipient))
|
||||
if (tradeRequest.messageTransactionSignature == null || !Crypto.isValidAddress(tradeRequest.messageTransactionSignature))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
ATData atData = fetchAtDataWithChecking(repository, creatorPublicKey, tradeRequest.atAddress);
|
||||
ATData atData = fetchAtDataWithChecking(repository, tradeRequest.atAddress);
|
||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
||||
|
||||
if (crossChainTradeData.mode == CrossChainTradeData.Mode.TRADE)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
||||
// Does supplied public key match trade public key?
|
||||
if (tradePublicKey != null && !Crypto.toAddress(tradePublicKey).equals(crossChainTradeData.qortalCreatorTradeAddress))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
|
||||
|
||||
TransactionData transactionData = repository.getTransactionRepository().fromSignature(tradeRequest.messageTransactionSignature);
|
||||
if (transactionData == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSACTION_UNKNOWN);
|
||||
|
||||
if (transactionData.getType() != TransactionType.MESSAGE)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSACTION_INVALID);
|
||||
|
||||
MessageTransactionData messageTransactionData = (MessageTransactionData) transactionData;
|
||||
byte[] messageData = messageTransactionData.getData();
|
||||
BTCACCT.OfferMessageData offerMessageData = BTCACCT.extractOfferMessageData(messageData);
|
||||
if (offerMessageData == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSACTION_INVALID);
|
||||
|
||||
// Good to make MESSAGE
|
||||
|
||||
byte[] recipientAddressBytes = Bytes.ensureCapacity(Base58.decode(tradeRequest.recipient), 32, 0);
|
||||
byte[] messageTransactionBytes = buildAtMessage(repository, creatorPublicKey, tradeRequest.atAddress, recipientAddressBytes);
|
||||
byte[] aliceForeignPublicKeyHash = offerMessageData.recipientBitcoinPKH;
|
||||
byte[] hashOfSecretA = offerMessageData.hashOfSecretA;
|
||||
int lockTimeA = (int) offerMessageData.lockTimeA;
|
||||
|
||||
String aliceNativeAddress = Crypto.toAddress(messageTransactionData.getCreatorPublicKey());
|
||||
int lockTimeB = BTCACCT.calcLockTimeB(messageTransactionData.getTimestamp(), lockTimeA);
|
||||
|
||||
byte[] outgoingMessageData = BTCACCT.buildTradeMessage(aliceNativeAddress, aliceForeignPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB);
|
||||
byte[] messageTransactionBytes = buildAtMessage(repository, tradePublicKey, tradeRequest.atAddress, outgoingMessageData);
|
||||
|
||||
return Base58.encode(messageTransactionBytes);
|
||||
} catch (DataException e) {
|
||||
@ -315,11 +338,14 @@ public class CrossChainResource {
|
||||
if (secretRequest.atAddress == null || !Crypto.isValidAtAddress(secretRequest.atAddress))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||
|
||||
if (secretRequest.secret == null || secretRequest.secret.length != BTCACCT.SECRET_LENGTH * 2)
|
||||
if (secretRequest.secretA == null || secretRequest.secretA.length != BTCACCT.SECRET_LENGTH)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||
|
||||
if (secretRequest.secretB == null || secretRequest.secretB.length != BTCACCT.SECRET_LENGTH)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
ATData atData = fetchAtDataWithChecking(repository, null, secretRequest.atAddress); // null to skip creator check
|
||||
ATData atData = fetchAtDataWithChecking(repository, secretRequest.atAddress);
|
||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
||||
|
||||
if (crossChainTradeData.mode == CrossChainTradeData.Mode.OFFER)
|
||||
@ -334,7 +360,8 @@ public class CrossChainResource {
|
||||
|
||||
// Good to make MESSAGE
|
||||
|
||||
byte[] messageTransactionBytes = buildAtMessage(repository, recipientPublicKey, secretRequest.atAddress, secretRequest.secret);
|
||||
byte[] messageData = BTCACCT.buildRedeemMessage(secretRequest.secretA, secretRequest.secretB);
|
||||
byte[] messageTransactionBytes = buildAtMessage(repository, recipientPublicKey, secretRequest.atAddress, messageData);
|
||||
|
||||
return Base58.encode(messageTransactionBytes);
|
||||
} catch (DataException e) {
|
||||
@ -348,7 +375,7 @@ public class CrossChainResource {
|
||||
summary = "Builds raw, unsigned MESSAGE transaction that cancels cross-chain trade offer",
|
||||
description = "Specify address of cross-chain AT that needs to be cancelled.<br>"
|
||||
+ "AT needs to be in 'offer' mode. Messages sent to an AT in 'trade' mode will be ignored, but still cost fees to send!<br>"
|
||||
+ "You need to sign output with same account as the AT creator otherwise the MESSAGE transaction will be invalid.",
|
||||
+ "You need to sign output with trade's private key otherwise the MESSAGE transaction will be invalid.",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
@ -372,28 +399,31 @@ public class CrossChainResource {
|
||||
public String cancelTradeOffer(CrossChainCancelRequest cancelRequest) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
byte[] creatorPublicKey = cancelRequest.creatorPublicKey;
|
||||
byte[] tradePublicKey = cancelRequest.tradePublicKey;
|
||||
|
||||
if (creatorPublicKey == null || creatorPublicKey.length != Transformer.PUBLIC_KEY_LENGTH)
|
||||
if (tradePublicKey == null || tradePublicKey.length != Transformer.PUBLIC_KEY_LENGTH)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
|
||||
|
||||
if (cancelRequest.atAddress == null || !Crypto.isValidAtAddress(cancelRequest.atAddress))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
ATData atData = fetchAtDataWithChecking(repository, creatorPublicKey, cancelRequest.atAddress);
|
||||
ATData atData = fetchAtDataWithChecking(repository, cancelRequest.atAddress);
|
||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
||||
|
||||
if (crossChainTradeData.mode == CrossChainTradeData.Mode.TRADE)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
||||
// Does supplied public key match trade public key?
|
||||
if (tradePublicKey != null && !Crypto.toAddress(tradePublicKey).equals(crossChainTradeData.qortalCreatorTradeAddress))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
|
||||
|
||||
// Good to make MESSAGE
|
||||
|
||||
PublicKeyAccount creatorAccount = new PublicKeyAccount(repository, creatorPublicKey);
|
||||
String creatorAddress = creatorAccount.getAddress();
|
||||
byte[] recipientAddressBytes = Bytes.ensureCapacity(Base58.decode(creatorAddress), 32, 0);
|
||||
String atCreatorAddress = crossChainTradeData.qortalCreator;
|
||||
byte[] messageData = BTCACCT.buildRefundMessage(atCreatorAddress);
|
||||
|
||||
byte[] messageTransactionBytes = buildAtMessage(repository, creatorPublicKey, cancelRequest.atAddress, recipientAddressBytes);
|
||||
byte[] messageTransactionBytes = buildAtMessage(repository, tradePublicKey, cancelRequest.atAddress, messageData);
|
||||
|
||||
return Base58.encode(messageTransactionBytes);
|
||||
} catch (DataException e) {
|
||||
@ -468,7 +498,7 @@ public class CrossChainResource {
|
||||
|
||||
// Extract data from cross-chain trading AT
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
ATData atData = fetchAtDataWithChecking(repository, null, templateRequest.atAddress); // null to skip creator check
|
||||
ATData atData = fetchAtDataWithChecking(repository, templateRequest.atAddress);
|
||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
||||
|
||||
if (crossChainTradeData.mode == Mode.OFFER)
|
||||
@ -551,7 +581,7 @@ public class CrossChainResource {
|
||||
|
||||
// Extract data from cross-chain trading AT
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
ATData atData = fetchAtDataWithChecking(repository, null, templateRequest.atAddress); // null to skip creator check
|
||||
ATData atData = fetchAtDataWithChecking(repository, templateRequest.atAddress);
|
||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
||||
|
||||
if (crossChainTradeData.mode == Mode.OFFER)
|
||||
@ -684,7 +714,7 @@ public class CrossChainResource {
|
||||
|
||||
// Extract data from cross-chain trading AT
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
ATData atData = fetchAtDataWithChecking(repository, null, refundRequest.atAddress); // null to skip creator check
|
||||
ATData atData = fetchAtDataWithChecking(repository, refundRequest.atAddress);
|
||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
||||
|
||||
if (crossChainTradeData.mode == Mode.OFFER)
|
||||
@ -820,7 +850,7 @@ public class CrossChainResource {
|
||||
|
||||
// Extract data from cross-chain trading AT
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
ATData atData = fetchAtDataWithChecking(repository, null, redeemRequest.atAddress); // null to skip creator check
|
||||
ATData atData = fetchAtDataWithChecking(repository, redeemRequest.atAddress);
|
||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
||||
|
||||
if (crossChainTradeData.mode == Mode.OFFER)
|
||||
@ -920,7 +950,7 @@ public class CrossChainResource {
|
||||
public String tradeBotCreator(TradeBotCreateRequest tradeBotCreateRequest) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
if (tradeBotCreateRequest.tradeTimeout < 600)
|
||||
if (tradeBotCreateRequest.tradeTimeout < 60)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
@ -978,7 +1008,7 @@ public class CrossChainResource {
|
||||
|
||||
// Extract data from cross-chain trading AT
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
ATData atData = fetchAtDataWithChecking(repository, null, atAddress); // null to skip creator check
|
||||
ATData atData = fetchAtDataWithChecking(repository, atAddress);
|
||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
||||
|
||||
if (crossChainTradeData.mode != Mode.OFFER)
|
||||
@ -1049,15 +1079,11 @@ public class CrossChainResource {
|
||||
}
|
||||
}
|
||||
|
||||
private ATData fetchAtDataWithChecking(Repository repository, byte[] creatorPublicKey, String atAddress) throws DataException {
|
||||
private ATData fetchAtDataWithChecking(Repository repository, String atAddress) throws DataException {
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
if (atData == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN);
|
||||
|
||||
// Does supplied public key match that of AT?
|
||||
if (creatorPublicKey != null && !Arrays.equals(creatorPublicKey, atData.getCreatorPublicKey()))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
|
||||
|
||||
// Must be correct AT - check functionality using code hash
|
||||
if (!Arrays.equals(atData.getCodeHash(), BTCACCT.CODE_BYTES_HASH))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
@ -1082,15 +1108,14 @@ public class CrossChainResource {
|
||||
int nonce = 0;
|
||||
long amount = 0L;
|
||||
Long assetId = null; // no assetId as amount is zero
|
||||
Long fee = null;
|
||||
Long fee = 0L;
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, senderPublicKey, fee, null);
|
||||
TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, atAddress, amount, assetId, messageData, false, false);
|
||||
|
||||
MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
|
||||
|
||||
fee = messageTransaction.calcRecommendedFee();
|
||||
messageTransactionData.setFee(fee);
|
||||
messageTransaction.computeNonce();
|
||||
|
||||
ValidationResult result = messageTransaction.isValidUnconfirmed();
|
||||
if (result != ValidationResult.OK)
|
||||
|
@ -435,7 +435,7 @@ public class TradeBot {
|
||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
||||
|
||||
// Refund P2SH-A if AT finished (i.e. Bob cancelled trade) or we've passed lockTime-A
|
||||
if (atData.getIsFinished() || NTP.getTime() >= tradeBotData.getLockTimeA()) {
|
||||
if (atData.getIsFinished() || NTP.getTime() >= tradeBotData.getLockTimeA() * 1000L) {
|
||||
tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_A);
|
||||
repository.getCrossChainRepository().save(tradeBotData);
|
||||
repository.saveChanges();
|
||||
@ -595,7 +595,7 @@ public class TradeBot {
|
||||
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
|
||||
|
||||
// Refund P2SH-B if we've passed lockTime-B
|
||||
if (NTP.getTime() >= crossChainTradeData.lockTimeB) {
|
||||
if (NTP.getTime() >= crossChainTradeData.lockTimeB * 1000L) {
|
||||
tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_B);
|
||||
repository.getCrossChainRepository().save(tradeBotData);
|
||||
repository.saveChanges();
|
||||
@ -711,7 +711,7 @@ public class TradeBot {
|
||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
||||
|
||||
// We can't refund P2SH-B until lockTime-B has passed
|
||||
if (NTP.getTime() <= crossChainTradeData.lockTimeB)
|
||||
if (NTP.getTime() <= crossChainTradeData.lockTimeB * 1000L)
|
||||
return;
|
||||
|
||||
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), crossChainTradeData.lockTimeB, crossChainTradeData.creatorBitcoinPKH, crossChainTradeData.hashOfSecretB);
|
||||
@ -721,7 +721,7 @@ public class TradeBot {
|
||||
ECKey refundKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
|
||||
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress);
|
||||
|
||||
Transaction p2shRefundTransaction = BTCP2SH.buildRefundTransaction(refundAmount, refundKey, fundingOutputs, redeemScriptBytes, tradeBotData.getLockTimeA());
|
||||
Transaction p2shRefundTransaction = BTCP2SH.buildRefundTransaction(refundAmount, refundKey, fundingOutputs, redeemScriptBytes, crossChainTradeData.lockTimeB);
|
||||
if (!BTC.getInstance().broadcastTransaction(p2shRefundTransaction)) {
|
||||
// We couldn't refund P2SH-B at this time
|
||||
LOGGER.debug(() -> String.format("Couldn't broadcast P2SH-B refund transaction?"));
|
||||
@ -745,7 +745,12 @@ public class TradeBot {
|
||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
||||
|
||||
// We can't refund P2SH-A until lockTime-A has passed
|
||||
if (NTP.getTime() <= tradeBotData.getLockTimeA())
|
||||
if (NTP.getTime() <= tradeBotData.getLockTimeA() * 1000L)
|
||||
return;
|
||||
|
||||
// We can't refund P2SH-A until we've passed median block time
|
||||
Integer medianBlockTime = BTC.getInstance().getMedianBlockTime();
|
||||
if (medianBlockTime == null || NTP.getTime() <= medianBlockTime * 1000L)
|
||||
return;
|
||||
|
||||
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getLockTimeA(), crossChainTradeData.creatorBitcoinPKH, tradeBotData.getHashOfSecret());
|
||||
@ -754,6 +759,10 @@ public class TradeBot {
|
||||
Coin refundAmount = Coin.valueOf(crossChainTradeData.expectedBitcoin);
|
||||
ECKey refundKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
|
||||
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress);
|
||||
if (fundingOutputs == null) {
|
||||
LOGGER.debug(() -> String.format("Couldn't fetch unspent outputs for %s", p2shAddress));
|
||||
return;
|
||||
}
|
||||
|
||||
Transaction p2shRefundTransaction = BTCP2SH.buildRefundTransaction(refundAmount, refundKey, fundingOutputs, redeemScriptBytes, tradeBotData.getLockTimeA());
|
||||
if (!BTC.getInstance().broadcastTransaction(p2shRefundTransaction)) {
|
||||
|
@ -93,9 +93,9 @@ public class BTCP2SH {
|
||||
// Input (without scriptSig prior to signing)
|
||||
TransactionInput input = new TransactionInput(params, null, redeemScriptBytes, fundingOutput.getOutPointFor());
|
||||
if (lockTime != null)
|
||||
input.setSequenceNumber(BTC.LOCKTIME_NO_RBF_SEQUENCE); // Use max-value, so no lockTime and no RBF
|
||||
input.setSequenceNumber(BTC.LOCKTIME_NO_RBF_SEQUENCE); // Use max-value - 1, so lockTime can be used but not RBF
|
||||
else
|
||||
input.setSequenceNumber(BTC.NO_LOCKTIME_NO_RBF_SEQUENCE); // Use max-value - 1, so lockTime can be used but not RBF
|
||||
input.setSequenceNumber(BTC.NO_LOCKTIME_NO_RBF_SEQUENCE); // Use max-value, so no lockTime and no RBF
|
||||
transaction.addInput(input);
|
||||
}
|
||||
|
||||
|
@ -128,16 +128,26 @@ public class ElectrumX {
|
||||
// Methods for use by other classes
|
||||
|
||||
public Integer getCurrentHeight() {
|
||||
JSONObject blockJson = (JSONObject) this.rpc("blockchain.headers.subscribe");
|
||||
if (blockJson == null || !blockJson.containsKey("height"))
|
||||
Object blockObj = this.rpc("blockchain.headers.subscribe");
|
||||
if (!(blockObj instanceof JSONObject))
|
||||
return null;
|
||||
|
||||
JSONObject blockJson = (JSONObject) blockObj;
|
||||
|
||||
if (!blockJson.containsKey("height"))
|
||||
return null;
|
||||
|
||||
return ((Long) blockJson.get("height")).intValue();
|
||||
}
|
||||
|
||||
public List<byte[]> getBlockHeaders(int startHeight, long count) {
|
||||
JSONObject blockJson = (JSONObject) this.rpc("blockchain.block.headers", startHeight, count);
|
||||
if (blockJson == null || !blockJson.containsKey("count") || !blockJson.containsKey("hex"))
|
||||
Object blockObj = this.rpc("blockchain.block.headers", startHeight, count);
|
||||
if (!(blockObj instanceof JSONObject))
|
||||
return null;
|
||||
|
||||
JSONObject blockJson = (JSONObject) blockObj;
|
||||
|
||||
if (!blockJson.containsKey("count") || !blockJson.containsKey("hex"))
|
||||
return null;
|
||||
|
||||
Long returnedCount = (Long) blockJson.get("count");
|
||||
@ -158,8 +168,13 @@ public class ElectrumX {
|
||||
byte[] scriptHash = Crypto.digest(script);
|
||||
Bytes.reverse(scriptHash);
|
||||
|
||||
JSONObject balanceJson = (JSONObject) this.rpc("blockchain.scripthash.get_balance", HashCode.fromBytes(scriptHash).toString());
|
||||
if (balanceJson == null || !balanceJson.containsKey("confirmed"))
|
||||
Object balanceObj = this.rpc("blockchain.scripthash.get_balance", HashCode.fromBytes(scriptHash).toString());
|
||||
if (!(balanceObj instanceof JSONObject))
|
||||
return null;
|
||||
|
||||
JSONObject balanceJson = (JSONObject) balanceObj;
|
||||
|
||||
if (!balanceJson.containsKey("confirmed"))
|
||||
return null;
|
||||
|
||||
return (Long) balanceJson.get("confirmed");
|
||||
@ -183,12 +198,12 @@ public class ElectrumX {
|
||||
byte[] scriptHash = Crypto.digest(script);
|
||||
Bytes.reverse(scriptHash);
|
||||
|
||||
JSONArray unspentJson = (JSONArray) this.rpc("blockchain.scripthash.listunspent", HashCode.fromBytes(scriptHash).toString());
|
||||
if (unspentJson == null)
|
||||
Object unspentJson = this.rpc("blockchain.scripthash.listunspent", HashCode.fromBytes(scriptHash).toString());
|
||||
if (!(unspentJson instanceof JSONArray))
|
||||
return null;
|
||||
|
||||
List<UnspentOutput> unspentOutputs = new ArrayList<>();
|
||||
for (Object rawUnspent : unspentJson) {
|
||||
for (Object rawUnspent : (JSONArray) unspentJson) {
|
||||
JSONObject unspent = (JSONObject) rawUnspent;
|
||||
|
||||
byte[] txHash = HashCode.fromString((String) unspent.get("tx_hash")).asBytes();
|
||||
@ -203,11 +218,11 @@ public class ElectrumX {
|
||||
}
|
||||
|
||||
public byte[] getRawTransaction(byte[] txHash) {
|
||||
String rawTransactionHex = (String) this.rpc("blockchain.transaction.get", HashCode.fromBytes(txHash).toString());
|
||||
if (rawTransactionHex == null)
|
||||
Object rawTransactionHex = this.rpc("blockchain.transaction.get", HashCode.fromBytes(txHash).toString());
|
||||
if (!(rawTransactionHex instanceof String))
|
||||
return null;
|
||||
|
||||
return HashCode.fromString(rawTransactionHex).asBytes();
|
||||
return HashCode.fromString((String) rawTransactionHex).asBytes();
|
||||
}
|
||||
|
||||
/** Returns list of raw transactions. */
|
||||
@ -215,13 +230,13 @@ public class ElectrumX {
|
||||
byte[] scriptHash = Crypto.digest(script);
|
||||
Bytes.reverse(scriptHash);
|
||||
|
||||
JSONArray transactionsJson = (JSONArray) this.rpc("blockchain.scripthash.get_history", HashCode.fromBytes(scriptHash).toString());
|
||||
if (transactionsJson == null)
|
||||
Object transactionsJson = this.rpc("blockchain.scripthash.get_history", HashCode.fromBytes(scriptHash).toString());
|
||||
if (!(transactionsJson instanceof JSONArray))
|
||||
return null;
|
||||
|
||||
List<byte[]> rawTransactions = new ArrayList<>();
|
||||
|
||||
for (Object rawTransactionInfo : transactionsJson) {
|
||||
for (Object rawTransactionInfo : (JSONArray) transactionsJson) {
|
||||
JSONObject transactionInfo = (JSONObject) rawTransactionInfo;
|
||||
|
||||
// We only want confirmed transactions
|
||||
@ -254,11 +269,11 @@ public class ElectrumX {
|
||||
private Set<Server> serverPeersSubscribe() {
|
||||
Set<Server> newServers = new HashSet<>();
|
||||
|
||||
JSONArray peers = (JSONArray) this.connectedRpc("server.peers.subscribe");
|
||||
if (peers == null)
|
||||
Object peers = this.connectedRpc("server.peers.subscribe");
|
||||
if (!(peers instanceof JSONArray))
|
||||
return newServers;
|
||||
|
||||
for (Object rawPeer : peers) {
|
||||
for (Object rawPeer : (JSONArray) peers) {
|
||||
JSONArray peer = (JSONArray) rawPeer;
|
||||
if (peer.size() < 3)
|
||||
continue;
|
||||
@ -393,10 +408,12 @@ public class ElectrumX {
|
||||
if (response.isEmpty())
|
||||
return null;
|
||||
|
||||
JSONObject responseJson = (JSONObject) JSONValue.parse(response);
|
||||
if (responseJson == null)
|
||||
Object responseObj = JSONValue.parse(response);
|
||||
if (!(responseObj instanceof JSONObject))
|
||||
return null;
|
||||
|
||||
JSONObject responseJson = (JSONObject) responseObj;
|
||||
|
||||
return responseJson.get("result");
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user