forked from Qortal/qortal
WIP: TradeBot - added refunding code
This commit is contained in:
parent
579645d6b7
commit
f9b726a75d
@ -1029,8 +1029,16 @@ public class CrossChainResource {
|
|||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
TradeBotData tradeBotData = repository.getCrossChainRepository().getTradeBotData(tradePrivateKey);
|
TradeBotData tradeBotData = repository.getCrossChainRepository().getTradeBotData(tradePrivateKey);
|
||||||
|
|
||||||
if (tradeBotData.getState() != TradeBotData.State.ALICE_DONE && tradeBotData.getState() != TradeBotData.State.BOB_DONE)
|
switch (tradeBotData.getState()) {
|
||||||
return "false";
|
case ALICE_DONE:
|
||||||
|
case BOB_DONE:
|
||||||
|
case ALICE_REFUNDED:
|
||||||
|
case BOB_REFUNDED:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
|
||||||
repository.getCrossChainRepository().delete(tradeBotData.getTradePrivateKey());
|
repository.getCrossChainRepository().delete(tradeBotData.getTradePrivateKey());
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
@ -20,6 +20,7 @@ import org.qortal.crosschain.BTC;
|
|||||||
import org.qortal.crosschain.BTCACCT;
|
import org.qortal.crosschain.BTCACCT;
|
||||||
import org.qortal.crosschain.BTCP2SH;
|
import org.qortal.crosschain.BTCP2SH;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
|
import org.qortal.data.account.AccountBalanceData;
|
||||||
import org.qortal.data.at.ATData;
|
import org.qortal.data.at.ATData;
|
||||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
import org.qortal.data.crosschain.CrossChainTradeData;
|
||||||
import org.qortal.data.crosschain.TradeBotData;
|
import org.qortal.data.crosschain.TradeBotData;
|
||||||
@ -104,6 +105,8 @@ public class TradeBot {
|
|||||||
repository.getCrossChainRepository().save(tradeBotData);
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
|
||||||
|
LOGGER.info(() -> String.format("Built AT %s. Waiting for deployment", atAddress));
|
||||||
|
|
||||||
// Return to user for signing and broadcast as we don't have their Qortal private key
|
// Return to user for signing and broadcast as we don't have their Qortal private key
|
||||||
try {
|
try {
|
||||||
return DeployAtTransactionTransformer.toBytes(deployAtTransactionData);
|
return DeployAtTransactionTransformer.toBytes(deployAtTransactionData);
|
||||||
@ -124,7 +127,7 @@ public class TradeBot {
|
|||||||
byte[] tradeForeignPublicKey = deriveTradeForeignPublicKey(tradePrivateKey);
|
byte[] tradeForeignPublicKey = deriveTradeForeignPublicKey(tradePrivateKey);
|
||||||
byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey);
|
byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey);
|
||||||
|
|
||||||
// We need to generate lockTimeA: halfway of refundTimeout from now
|
// We need to generate lockTime-A: halfway of refundTimeout from now
|
||||||
int lockTimeA = crossChainTradeData.tradeTimeout * 60 + (int) (NTP.getTime() / 1000L);
|
int lockTimeA = crossChainTradeData.tradeTimeout * 60 + (int) (NTP.getTime() / 1000L);
|
||||||
|
|
||||||
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, TradeBotData.State.ALICE_WAITING_FOR_P2SH_A,
|
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, TradeBotData.State.ALICE_WAITING_FOR_P2SH_A,
|
||||||
@ -134,30 +137,32 @@ public class TradeBot {
|
|||||||
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
||||||
crossChainTradeData.expectedBitcoin, xprv58, null, lockTimeA);
|
crossChainTradeData.expectedBitcoin, xprv58, null, lockTimeA);
|
||||||
|
|
||||||
// Check we have enough funds via xprv58 to fund both P2SH to cover expectedBitcoin
|
// Check we have enough funds via xprv58 to fund both P2SHs to cover expectedBitcoin
|
||||||
String tradeForeignAddress = BTC.getInstance().pkhToAddress(tradeForeignPublicKeyHash);
|
String tradeForeignAddress = BTC.getInstance().pkhToAddress(tradeForeignPublicKeyHash);
|
||||||
|
|
||||||
long totalFundsRequired = crossChainTradeData.expectedBitcoin + FEE_AMOUNT /* P2SH-a */ + FEE_AMOUNT /* P2SH-b */;
|
long totalFundsRequired = crossChainTradeData.expectedBitcoin + FEE_AMOUNT /* P2SH-A */ + FEE_AMOUNT /* P2SH-B */;
|
||||||
|
|
||||||
Transaction fundingCheckTransaction = BTC.getInstance().buildSpend(xprv58, tradeForeignAddress, totalFundsRequired);
|
Transaction fundingCheckTransaction = BTC.getInstance().buildSpend(xprv58, tradeForeignAddress, totalFundsRequired);
|
||||||
if (fundingCheckTransaction == null)
|
if (fundingCheckTransaction == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// P2SH_a to be funded
|
// P2SH-A to be funded
|
||||||
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeForeignPublicKeyHash, lockTimeA, crossChainTradeData.creatorBitcoinPKH, hashOfSecretA);
|
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeForeignPublicKeyHash, lockTimeA, crossChainTradeData.creatorBitcoinPKH, hashOfSecretA);
|
||||||
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
|
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
|
||||||
|
|
||||||
// Fund P2SH-a
|
// Fund P2SH-A
|
||||||
Transaction p2shFundingTransaction = BTC.getInstance().buildSpend(tradeBotData.getXprv58(), p2shAddress, crossChainTradeData.expectedBitcoin + FEE_AMOUNT);
|
Transaction p2shFundingTransaction = BTC.getInstance().buildSpend(tradeBotData.getXprv58(), p2shAddress, crossChainTradeData.expectedBitcoin + FEE_AMOUNT);
|
||||||
if (!BTC.getInstance().broadcastTransaction(p2shFundingTransaction)) {
|
if (!BTC.getInstance().broadcastTransaction(p2shFundingTransaction)) {
|
||||||
// We couldn't fund P2SH-a at this time
|
// We couldn't fund P2SH-A at this time
|
||||||
LOGGER.debug(() -> String.format("Couldn't broadcast P2SH-a funding transaction?"));
|
LOGGER.debug(() -> String.format("Couldn't broadcast P2SH-A funding transaction?"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
repository.getCrossChainRepository().save(tradeBotData);
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
|
||||||
|
LOGGER.info(() -> String.format("Funding P2SH-A %s. Waiting for confirmation", p2shAddress));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,6 +235,18 @@ public class TradeBot {
|
|||||||
case BOB_DONE:
|
case BOB_DONE:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ALICE_REFUNDING_B:
|
||||||
|
handleAliceRefundingP2shB(repository, tradeBotData);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ALICE_REFUNDING_A:
|
||||||
|
handleAliceRefundingP2shA(repository, tradeBotData);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ALICE_REFUNDED:
|
||||||
|
case BOB_REFUNDED:
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
LOGGER.warn(() -> String.format("Unhandled trade-bot state %s", tradeBotData.getState().name()));
|
LOGGER.warn(() -> String.format("Unhandled trade-bot state %s", tradeBotData.getState().name()));
|
||||||
}
|
}
|
||||||
@ -248,12 +265,14 @@ public class TradeBot {
|
|||||||
tradeBotData.setState(TradeBotData.State.BOB_WAITING_FOR_MESSAGE);
|
tradeBotData.setState(TradeBotData.State.BOB_WAITING_FOR_MESSAGE);
|
||||||
repository.getCrossChainRepository().save(tradeBotData);
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
|
||||||
|
LOGGER.info(() -> String.format("AT %s confirmed ready. Waiting for trade message", tradeBotData.getAtAddress()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleAliceWaitingForP2shA(Repository repository, TradeBotData tradeBotData) throws DataException {
|
private void handleAliceWaitingForP2shA(Repository repository, TradeBotData tradeBotData) throws DataException {
|
||||||
ATData atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
ATData atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
||||||
if (atData == null) {
|
if (atData == null) {
|
||||||
LOGGER.warn(() -> String.format("Unable to fetch trade AT '%s' from repository", tradeBotData.getAtAddress()));
|
LOGGER.warn(() -> String.format("Unable to fetch trade AT %s from repository", tradeBotData.getAtAddress()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
||||||
@ -261,14 +280,28 @@ public class TradeBot {
|
|||||||
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getLockTimeA(), crossChainTradeData.creatorBitcoinPKH, tradeBotData.getHashOfSecret());
|
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getLockTimeA(), crossChainTradeData.creatorBitcoinPKH, tradeBotData.getHashOfSecret());
|
||||||
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
|
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
|
||||||
|
|
||||||
Long balance = BTC.getInstance().getBalance(p2shAddress);
|
// If AT has finished then maybe Bob cancelled his trade offer
|
||||||
if (balance == null || balance < crossChainTradeData.expectedBitcoin) {
|
if (atData.getIsFinished()) {
|
||||||
if (balance != null && balance > 0)
|
// No point sending MESSAGE - might as well wait for refund
|
||||||
LOGGER.debug(() -> String.format("P2SH-a balance %s lower than expected %s", BTC.format(balance), BTC.format(crossChainTradeData.expectedBitcoin)));
|
tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_A);
|
||||||
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
|
repository.saveChanges();
|
||||||
|
|
||||||
|
LOGGER.info(() -> String.format("AT %s cancelled. Refunding P2SH-A %s - aborting trade", tradeBotData.getAtAddress(), p2shAddress));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Long balance = BTC.getInstance().getBalance(p2shAddress);
|
||||||
|
if (balance == null || balance < crossChainTradeData.expectedBitcoin) {
|
||||||
|
if (balance != null && balance > 0)
|
||||||
|
LOGGER.debug(() -> String.format("P2SH-A balance %s lower than expected %s", BTC.format(balance), BTC.format(crossChainTradeData.expectedBitcoin)));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// P2SH-A funding confirmed
|
||||||
|
|
||||||
// Attempt to send MESSAGE to Bob's Qortal trade address
|
// Attempt to send MESSAGE to Bob's Qortal trade address
|
||||||
byte[] messageData = BTCACCT.buildOfferMessage(tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getHashOfSecret(), tradeBotData.getLockTimeA());
|
byte[] messageData = BTCACCT.buildOfferMessage(tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getHashOfSecret(), tradeBotData.getLockTimeA());
|
||||||
|
|
||||||
@ -283,20 +316,34 @@ public class TradeBot {
|
|||||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (result != ValidationResult.OK) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT '%s': %s", messageTransaction.getRecipient(), result.name()));
|
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageTransaction.getRecipient(), result.name()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tradeBotData.setState(TradeBotData.State.ALICE_WAITING_FOR_AT_LOCK);
|
tradeBotData.setState(TradeBotData.State.ALICE_WAITING_FOR_AT_LOCK);
|
||||||
repository.getCrossChainRepository().save(tradeBotData);
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
|
||||||
|
LOGGER.info(() -> String.format("P2SH-A %s funding confirmed. Messaged %s. Waiting for AT %s to lock to us",
|
||||||
|
p2shAddress, crossChainTradeData.qortalCreatorTradeAddress, tradeBotData.getAtAddress()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleBobWaitingForMessage(Repository repository, TradeBotData tradeBotData) throws DataException {
|
private void handleBobWaitingForMessage(Repository repository, TradeBotData tradeBotData) throws DataException {
|
||||||
// Fetch AT so we can determine trade start timestamp
|
// Fetch AT so we can determine trade start timestamp
|
||||||
ATData atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
ATData atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
||||||
if (atData == null) {
|
if (atData == null) {
|
||||||
LOGGER.warn(() -> String.format("Unable to fetch trade AT '%s' from repository", tradeBotData.getAtAddress()));
|
LOGGER.warn(() -> String.format("Unable to fetch trade AT %s from repository", tradeBotData.getAtAddress()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If AT has finished then Bob likely cancelled his trade offer
|
||||||
|
if (atData.getIsFinished()) {
|
||||||
|
tradeBotData.setState(TradeBotData.State.BOB_REFUNDED);
|
||||||
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
|
repository.saveChanges();
|
||||||
|
|
||||||
|
LOGGER.info(() -> String.format("AT %s cancelled - trading aborted", tradeBotData.getAtAddress()));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,7 +367,7 @@ public class TradeBot {
|
|||||||
if (messageTransactionData.isText())
|
if (messageTransactionData.isText())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// We're expecting: HASH160(secret) + Alice's Bitcoin pubkeyhash
|
// We're expecting: HASH160(secret-A), Alice's Bitcoin pubkeyhash and lockTime-A
|
||||||
byte[] messageData = messageTransactionData.getData();
|
byte[] messageData = messageTransactionData.getData();
|
||||||
BTCACCT.OfferMessageData offerMessageData = BTCACCT.extractOfferMessageData(messageData);
|
BTCACCT.OfferMessageData offerMessageData = BTCACCT.extractOfferMessageData(messageData);
|
||||||
if (offerMessageData == null)
|
if (offerMessageData == null)
|
||||||
@ -356,13 +403,19 @@ public class TradeBot {
|
|||||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (result != ValidationResult.OK) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT '%s': %s", outgoingMessageTransaction.getRecipient(), result.name()));
|
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", outgoingMessageTransaction.getRecipient(), result.name()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tradeBotData.setState(TradeBotData.State.BOB_WAITING_FOR_P2SH_B);
|
tradeBotData.setState(TradeBotData.State.BOB_WAITING_FOR_P2SH_B);
|
||||||
repository.getCrossChainRepository().save(tradeBotData);
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
|
||||||
|
byte[] redeemScriptBytes = BTCP2SH.buildScript(aliceForeignPublicKeyHash, lockTimeB, tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getHashOfSecret());
|
||||||
|
String p2shBAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
|
||||||
|
|
||||||
|
LOGGER.info(() -> String.format("Locked AT %s to %s. Waiting for P2SH-B %s", tradeBotData.getAtAddress(), aliceNativeAddress, p2shBAddress));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,15 +427,30 @@ public class TradeBot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleAliceWaitingForAtLock(Repository repository, TradeBotData tradeBotData) throws DataException {
|
private void handleAliceWaitingForAtLock(Repository repository, TradeBotData tradeBotData) throws DataException {
|
||||||
// XXX REFUND CHECK
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
ATData atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
||||||
if (atData == null) {
|
if (atData == null) {
|
||||||
LOGGER.warn(() -> String.format("Unable to fetch trade AT '%s' from repository", tradeBotData.getAtAddress()));
|
LOGGER.warn(() -> String.format("Unable to fetch trade AT %s from repository", tradeBotData.getAtAddress()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
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()) {
|
||||||
|
tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_A);
|
||||||
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
|
repository.saveChanges();
|
||||||
|
|
||||||
|
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getLockTimeA(), crossChainTradeData.creatorBitcoinPKH, tradeBotData.getHashOfSecret());
|
||||||
|
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
|
||||||
|
|
||||||
|
if (atData.getIsFinished())
|
||||||
|
LOGGER.info(() -> String.format("AT %s cancelled. Refunding P2SH-A %s - aborting trade", tradeBotData.getAtAddress(), p2shAddress));
|
||||||
|
else
|
||||||
|
LOGGER.info(() -> String.format("LockTime-A reached, refunding P2SH-A %s - aborting trade", p2shAddress));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// We're waiting for AT to be in TRADE mode
|
// We're waiting for AT to be in TRADE mode
|
||||||
if (crossChainTradeData.mode != CrossChainTradeData.Mode.TRADE)
|
if (crossChainTradeData.mode != CrossChainTradeData.Mode.TRADE)
|
||||||
return;
|
return;
|
||||||
@ -390,12 +458,17 @@ public class TradeBot {
|
|||||||
// We're expecting AT to be locked to our native trade address
|
// We're expecting AT to be locked to our native trade address
|
||||||
if (!crossChainTradeData.qortalRecipient.equals(tradeBotData.getTradeNativeAddress())) {
|
if (!crossChainTradeData.qortalRecipient.equals(tradeBotData.getTradeNativeAddress())) {
|
||||||
// AT locked to different address! We shouldn't continue but wait and refund.
|
// AT locked to different address! We shouldn't continue but wait and refund.
|
||||||
LOGGER.warn(() -> String.format("Trade AT '%s' locked to '%s', not us ('%s')",
|
|
||||||
|
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getLockTimeA(), crossChainTradeData.creatorBitcoinPKH, tradeBotData.getHashOfSecret());
|
||||||
|
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
|
||||||
|
|
||||||
|
LOGGER.warn(() -> String.format("AT %s locked to %s, not us (%s). Refunding %s - aborting trade",
|
||||||
tradeBotData.getAtAddress(),
|
tradeBotData.getAtAddress(),
|
||||||
crossChainTradeData.qortalRecipient,
|
crossChainTradeData.qortalRecipient,
|
||||||
tradeBotData.getTradeNativeAddress()));
|
tradeBotData.getTradeNativeAddress(),
|
||||||
|
p2shAddress));
|
||||||
|
|
||||||
// There's no P2SH-b at this point, so jump straight to refunding P2SH-a
|
// There's no P2SH-B at this point, so jump straight to refunding P2SH-A
|
||||||
tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_A);
|
tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_A);
|
||||||
repository.getCrossChainRepository().save(tradeBotData);
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
@ -403,12 +476,12 @@ public class TradeBot {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alice needs to fund P2SH-b here
|
// Alice needs to fund P2SH-B here
|
||||||
|
|
||||||
// Find our MESSAGE to AT from previous state
|
// Find our MESSAGE to AT from previous state
|
||||||
List<MessageTransactionData> messageTransactionsData = repository.getTransactionRepository().getMessagesByRecipient(crossChainTradeData.qortalCreatorTradeAddress, null, null, null);
|
List<MessageTransactionData> messageTransactionsData = repository.getTransactionRepository().getMessagesByRecipient(crossChainTradeData.qortalCreatorTradeAddress, null, null, null);
|
||||||
if (messageTransactionsData == null) {
|
if (messageTransactionsData == null) {
|
||||||
LOGGER.warn(() -> String.format("Unable to fetch messages to trade AT '%s' from repository", crossChainTradeData.qortalCreatorTradeAddress));
|
LOGGER.warn(() -> String.format("Unable to fetch messages to trade AT %s from repository", crossChainTradeData.qortalCreatorTradeAddress));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -421,16 +494,16 @@ public class TradeBot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (recipientMessageTimestamp == null) {
|
if (recipientMessageTimestamp == null) {
|
||||||
LOGGER.warn(() -> String.format("Unable to find our message to trade creator '%s'?", crossChainTradeData.qortalCreatorTradeAddress));
|
LOGGER.warn(() -> String.format("Unable to find our message to trade creator %s?", crossChainTradeData.qortalCreatorTradeAddress));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int lockTimeA = tradeBotData.getLockTimeA();
|
int lockTimeA = tradeBotData.getLockTimeA();
|
||||||
int lockTimeB = BTCACCT.calcLockTimeB(recipientMessageTimestamp, lockTimeA);
|
int lockTimeB = BTCACCT.calcLockTimeB(recipientMessageTimestamp, lockTimeA);
|
||||||
|
|
||||||
// Our calculated lockTimeB should match AT's calculated lockTimeB
|
// Our calculated lockTime-B should match AT's calculated lockTime-B
|
||||||
if (lockTimeB != crossChainTradeData.lockTimeB) {
|
if (lockTimeB != crossChainTradeData.lockTimeB) {
|
||||||
LOGGER.debug(() -> String.format("Trade AT lockTimeB '%d' doesn't match our lockTimeB '%d'", crossChainTradeData.lockTimeB, lockTimeB));
|
LOGGER.debug(() -> String.format("Trade AT lockTime-B '%d' doesn't match our lockTime-B '%d'", crossChainTradeData.lockTimeB, lockTimeB));
|
||||||
// We'll eventually refund
|
// We'll eventually refund
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -440,27 +513,39 @@ public class TradeBot {
|
|||||||
|
|
||||||
Transaction p2shFundingTransaction = BTC.getInstance().buildSpend(tradeBotData.getXprv58(), p2shAddress, FEE_AMOUNT);
|
Transaction p2shFundingTransaction = BTC.getInstance().buildSpend(tradeBotData.getXprv58(), p2shAddress, FEE_AMOUNT);
|
||||||
if (!BTC.getInstance().broadcastTransaction(p2shFundingTransaction)) {
|
if (!BTC.getInstance().broadcastTransaction(p2shFundingTransaction)) {
|
||||||
// We couldn't fund P2SH-b at this time
|
// We couldn't fund P2SH-B at this time
|
||||||
LOGGER.debug(() -> String.format("Couldn't broadcast P2SH-b funding transaction?"));
|
LOGGER.debug(() -> String.format("Couldn't broadcast P2SH-B funding transaction?"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// P2SH-b funded, now we wait for Bob to redeem it
|
// P2SH-B funded, now we wait for Bob to redeem it
|
||||||
tradeBotData.setState(TradeBotData.State.ALICE_WATCH_P2SH_B);
|
tradeBotData.setState(TradeBotData.State.ALICE_WATCH_P2SH_B);
|
||||||
repository.getCrossChainRepository().save(tradeBotData);
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
|
||||||
|
LOGGER.info(() -> String.format("AT %s locked to us (%s). P2SH-B %s funded. Watching P2SH-B for secret-B",
|
||||||
|
tradeBotData.getAtAddress(), tradeBotData.getTradeNativeAddress(), p2shAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleBobWaitingForP2shB(Repository repository, TradeBotData tradeBotData) throws DataException {
|
private void handleBobWaitingForP2shB(Repository repository, TradeBotData tradeBotData) throws DataException {
|
||||||
// XXX REFUND CHECK
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
ATData atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
||||||
if (atData == null) {
|
if (atData == null) {
|
||||||
LOGGER.warn(() -> String.format("Unable to fetch trade AT '%s' from repository", tradeBotData.getAtAddress()));
|
LOGGER.warn(() -> String.format("Unable to fetch trade AT %s from repository", tradeBotData.getAtAddress()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
||||||
|
|
||||||
|
// If we've passed AT refund timestamp then AT will have finished after auto-refunding
|
||||||
|
if (atData.getIsFinished()) {
|
||||||
|
tradeBotData.setState(TradeBotData.State.BOB_REFUNDED);
|
||||||
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
|
repository.saveChanges();
|
||||||
|
|
||||||
|
LOGGER.info(() -> String.format("AT %s has auto-refunded - trade aborted", tradeBotData.getAtAddress()));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// It's possible AT hasn't processed our previous MESSAGE yet and so lockTimeB won't be set
|
// It's possible AT hasn't processed our previous MESSAGE yet and so lockTimeB won't be set
|
||||||
if (crossChainTradeData.lockTimeB == null)
|
if (crossChainTradeData.lockTimeB == null)
|
||||||
// AT yet to process MESSAGE
|
// AT yet to process MESSAGE
|
||||||
@ -472,36 +557,36 @@ public class TradeBot {
|
|||||||
Long balance = BTC.getInstance().getBalance(p2shAddress);
|
Long balance = BTC.getInstance().getBalance(p2shAddress);
|
||||||
if (balance == null || balance < FEE_AMOUNT) {
|
if (balance == null || balance < FEE_AMOUNT) {
|
||||||
if (balance != null && balance > 0)
|
if (balance != null && balance > 0)
|
||||||
LOGGER.debug(() -> String.format("P2SH-b balance %s lower than expected %s", BTC.format(balance), BTC.format(FEE_AMOUNT)));
|
LOGGER.debug(() -> String.format("P2SH-B balance %s lower than expected %s", BTC.format(balance), BTC.format(FEE_AMOUNT)));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redeem P2SH-b using secret-b
|
// Redeem P2SH-B using secret-B
|
||||||
Coin redeemAmount = Coin.ZERO; // The real funds are in P2SH-a
|
Coin redeemAmount = Coin.ZERO; // The real funds are in P2SH-A
|
||||||
ECKey redeemKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
|
ECKey redeemKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
|
||||||
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress);
|
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress);
|
||||||
|
|
||||||
Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, tradeBotData.getSecret());
|
Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, tradeBotData.getSecret());
|
||||||
|
|
||||||
if (!BTC.getInstance().broadcastTransaction(p2shRedeemTransaction)) {
|
if (!BTC.getInstance().broadcastTransaction(p2shRedeemTransaction)) {
|
||||||
// We couldn't redeem P2SH-b at this time
|
// We couldn't redeem P2SH-B at this time
|
||||||
LOGGER.debug(() -> String.format("Couldn't broadcast P2SH-b redeeming transaction?"));
|
LOGGER.debug(() -> String.format("Couldn't broadcast P2SH-B redeeming transaction?"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// P2SH-b redeemed, now we wait for Alice to use secret to redeem AT
|
// P2SH-B redeemed, now we wait for Alice to use secret-A to redeem AT
|
||||||
tradeBotData.setState(TradeBotData.State.BOB_WAITING_FOR_AT_REDEEM);
|
tradeBotData.setState(TradeBotData.State.BOB_WAITING_FOR_AT_REDEEM);
|
||||||
repository.getCrossChainRepository().save(tradeBotData);
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
|
||||||
|
LOGGER.info(() -> String.format("P2SH-B %s redeemed (exposing secret-B). Watching AT %s for secret-A", p2shAddress, tradeBotData.getAtAddress()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleAliceWatchingP2shB(Repository repository, TradeBotData tradeBotData) throws DataException {
|
private void handleAliceWatchingP2shB(Repository repository, TradeBotData tradeBotData) throws DataException {
|
||||||
// XXX REFUND CHECK
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
ATData atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
||||||
if (atData == null) {
|
if (atData == null) {
|
||||||
LOGGER.warn(() -> String.format("Unable to fetch trade AT '%s' from repository", tradeBotData.getAtAddress()));
|
LOGGER.warn(() -> String.format("Unable to fetch trade AT %s from repository", tradeBotData.getAtAddress()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
||||||
@ -509,9 +594,20 @@ public class TradeBot {
|
|||||||
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), crossChainTradeData.lockTimeB, crossChainTradeData.creatorBitcoinPKH, crossChainTradeData.hashOfSecretB);
|
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), crossChainTradeData.lockTimeB, crossChainTradeData.creatorBitcoinPKH, crossChainTradeData.hashOfSecretB);
|
||||||
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
|
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
|
||||||
|
|
||||||
|
// Refund P2SH-B if we've passed lockTime-B
|
||||||
|
if (NTP.getTime() >= crossChainTradeData.lockTimeB) {
|
||||||
|
tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_B);
|
||||||
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
|
repository.saveChanges();
|
||||||
|
|
||||||
|
LOGGER.info(() -> String.format("LockTime-B reached, refunding P2SH-B %s - aborting trade", p2shAddress));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
List<byte[]> p2shTransactions = BTC.getInstance().getAddressTransactions(p2shAddress);
|
List<byte[]> p2shTransactions = BTC.getInstance().getAddressTransactions(p2shAddress);
|
||||||
if (p2shTransactions == null) {
|
if (p2shTransactions == null) {
|
||||||
LOGGER.debug(() -> String.format("Unable to fetch transactions relating to '%s'", p2shAddress));
|
LOGGER.debug(() -> String.format("Unable to fetch transactions relating to %s", p2shAddress));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -530,26 +626,29 @@ public class TradeBot {
|
|||||||
messageTransaction.computeNonce();
|
messageTransaction.computeNonce();
|
||||||
messageTransaction.sign(sender);
|
messageTransaction.sign(sender);
|
||||||
|
|
||||||
// reset repository state to prevent deadlock
|
// Reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (result != ValidationResult.OK) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT '%s': %s", messageTransaction.getRecipient(), result.name()));
|
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageTransaction.getRecipient(), result.name()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tradeBotData.setState(TradeBotData.State.ALICE_DONE);
|
tradeBotData.setState(TradeBotData.State.ALICE_DONE);
|
||||||
repository.getCrossChainRepository().save(tradeBotData);
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
|
||||||
|
String receiveAddress = tradeBotData.getTradeNativeAddress();
|
||||||
|
|
||||||
|
LOGGER.info(() -> String.format("P2SH-B %s redeemed, using secrets to redeem AT %s. Funds should arrive at %s",
|
||||||
|
p2shAddress, tradeBotData.getAtAddress(), receiveAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleBobWaitingForAtRedeem(Repository repository, TradeBotData tradeBotData) throws DataException {
|
private void handleBobWaitingForAtRedeem(Repository repository, TradeBotData tradeBotData) throws DataException {
|
||||||
// XXX REFUND CHECK
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
ATData atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
||||||
if (atData == null) {
|
if (atData == null) {
|
||||||
LOGGER.warn(() -> String.format("Unable to fetch trade AT '%s' from repository", tradeBotData.getAtAddress()));
|
LOGGER.warn(() -> String.format("Unable to fetch trade AT %s from repository", tradeBotData.getAtAddress()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
||||||
@ -559,13 +658,25 @@ public class TradeBot {
|
|||||||
// Not finished yet
|
// Not finished yet
|
||||||
return;
|
return;
|
||||||
|
|
||||||
byte[] secretA = BTCACCT.findSecretA(repository, crossChainTradeData);
|
// If AT's balance is zero, then it's auto-refunded so we're done
|
||||||
if (secretA == null) {
|
AccountBalanceData atBalanceData = repository.getAccountRepository().getBalance(tradeBotData.getAtAddress(), Asset.QORT);
|
||||||
LOGGER.debug(() -> String.format("Unable to find secret-a from redeem message to AT '%s'?", tradeBotData.getAtAddress()));
|
if (atBalanceData == null || atBalanceData.getBalance() == 0L) {
|
||||||
|
tradeBotData.setState(TradeBotData.State.BOB_REFUNDED);
|
||||||
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
|
repository.saveChanges();
|
||||||
|
|
||||||
|
LOGGER.info(() -> String.format("AT %s has auto-refunded - trade aborted", tradeBotData.getAtAddress()));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use secretA to redeem P2SH-a
|
byte[] secretA = BTCACCT.findSecretA(repository, crossChainTradeData);
|
||||||
|
if (secretA == null) {
|
||||||
|
LOGGER.debug(() -> String.format("Unable to find secret-A from redeem message to AT %s?", tradeBotData.getAtAddress()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use secret-A to redeem P2SH-A
|
||||||
|
|
||||||
byte[] redeemScriptBytes = BTCP2SH.buildScript(crossChainTradeData.recipientBitcoinPKH, crossChainTradeData.lockTimeA, crossChainTradeData.creatorBitcoinPKH, crossChainTradeData.hashOfSecretA);
|
byte[] redeemScriptBytes = BTCP2SH.buildScript(crossChainTradeData.recipientBitcoinPKH, crossChainTradeData.lockTimeA, crossChainTradeData.creatorBitcoinPKH, crossChainTradeData.hashOfSecretA);
|
||||||
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
|
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
|
||||||
@ -577,14 +688,86 @@ public class TradeBot {
|
|||||||
Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, secretA);
|
Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, secretA);
|
||||||
|
|
||||||
if (!BTC.getInstance().broadcastTransaction(p2shRedeemTransaction)) {
|
if (!BTC.getInstance().broadcastTransaction(p2shRedeemTransaction)) {
|
||||||
// We couldn't redeem P2SH-a at this time
|
// We couldn't redeem P2SH-A at this time
|
||||||
LOGGER.debug(() -> String.format("Couldn't broadcast P2SH-a redeeming transaction?"));
|
LOGGER.debug(() -> String.format("Couldn't broadcast P2SH-A redeeming transaction?"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tradeBotData.setState(TradeBotData.State.BOB_DONE);
|
tradeBotData.setState(TradeBotData.State.BOB_DONE);
|
||||||
repository.getCrossChainRepository().save(tradeBotData);
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
|
||||||
|
String receiveAddress = BTC.getInstance().pkhToAddress(tradeBotData.getTradeForeignPublicKeyHash());
|
||||||
|
|
||||||
|
LOGGER.info(() -> String.format("P2SH-A %s redeemed. Funds should arrive at %s", tradeBotData.getAtAddress(), receiveAddress));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleAliceRefundingP2shB(Repository repository, TradeBotData tradeBotData) throws DataException {
|
||||||
|
ATData atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
||||||
|
if (atData == null) {
|
||||||
|
LOGGER.warn(() -> String.format("Unable to fetch trade AT %s from repository", tradeBotData.getAtAddress()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
||||||
|
|
||||||
|
// We can't refund P2SH-B until lockTime-B has passed
|
||||||
|
if (NTP.getTime() <= crossChainTradeData.lockTimeB)
|
||||||
|
return;
|
||||||
|
|
||||||
|
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), crossChainTradeData.lockTimeB, crossChainTradeData.creatorBitcoinPKH, crossChainTradeData.hashOfSecretB);
|
||||||
|
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
|
||||||
|
|
||||||
|
Coin refundAmount = Coin.ZERO;
|
||||||
|
ECKey refundKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
|
||||||
|
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress);
|
||||||
|
|
||||||
|
Transaction p2shRefundTransaction = BTCP2SH.buildRefundTransaction(refundAmount, refundKey, fundingOutputs, redeemScriptBytes, tradeBotData.getLockTimeA());
|
||||||
|
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?"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_A);
|
||||||
|
|
||||||
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
|
repository.saveChanges();
|
||||||
|
|
||||||
|
LOGGER.info(() -> String.format("Refunded P2SH-B %s. Waiting for LockTime-A", p2shAddress));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleAliceRefundingP2shA(Repository repository, TradeBotData tradeBotData) throws DataException {
|
||||||
|
ATData atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
||||||
|
if (atData == null) {
|
||||||
|
LOGGER.warn(() -> String.format("Unable to fetch trade AT %s from repository", tradeBotData.getAtAddress()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
||||||
|
|
||||||
|
// We can't refund P2SH-A until lockTime-A has passed
|
||||||
|
if (NTP.getTime() <= tradeBotData.getLockTimeA())
|
||||||
|
return;
|
||||||
|
|
||||||
|
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getLockTimeA(), crossChainTradeData.creatorBitcoinPKH, tradeBotData.getHashOfSecret());
|
||||||
|
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
|
||||||
|
|
||||||
|
Coin refundAmount = Coin.valueOf(crossChainTradeData.expectedBitcoin);
|
||||||
|
ECKey refundKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
|
||||||
|
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress);
|
||||||
|
|
||||||
|
Transaction p2shRefundTransaction = BTCP2SH.buildRefundTransaction(refundAmount, refundKey, fundingOutputs, redeemScriptBytes, tradeBotData.getLockTimeA());
|
||||||
|
if (!BTC.getInstance().broadcastTransaction(p2shRefundTransaction)) {
|
||||||
|
// We couldn't refund P2SH-A at this time
|
||||||
|
LOGGER.debug(() -> String.format("Couldn't broadcast P2SH-A refund transaction?"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tradeBotData.setState(TradeBotData.State.ALICE_REFUNDED);
|
||||||
|
|
||||||
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
|
repository.saveChanges();
|
||||||
|
|
||||||
|
LOGGER.info(() -> String.format("LockTime-A reached. Refunded P2SH-A %s. Trade aborted", p2shAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -117,6 +117,7 @@ public class BTC {
|
|||||||
return format(Coin.valueOf(amount));
|
return format(Coin.valueOf(amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns P2PKH Bitcoin address using passed public key hash. */
|
||||||
public String pkhToAddress(byte[] publicKeyHash) {
|
public String pkhToAddress(byte[] publicKeyHash) {
|
||||||
return LegacyAddress.fromPubKeyHash(this.params, publicKeyHash).toString();
|
return LegacyAddress.fromPubKeyHash(this.params, publicKeyHash).toString();
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,8 @@ public class TradeBotData {
|
|||||||
private byte[] tradePrivateKey;
|
private byte[] tradePrivateKey;
|
||||||
|
|
||||||
public enum State {
|
public enum State {
|
||||||
BOB_WAITING_FOR_AT_CONFIRM(10), BOB_WAITING_FOR_MESSAGE(15), BOB_WAITING_FOR_P2SH_B(20), BOB_WAITING_FOR_AT_REDEEM(25), BOB_DONE(30),
|
BOB_WAITING_FOR_AT_CONFIRM(10), BOB_WAITING_FOR_MESSAGE(15), BOB_WAITING_FOR_P2SH_B(20), BOB_WAITING_FOR_AT_REDEEM(25), BOB_DONE(30), BOB_REFUNDED(35),
|
||||||
ALICE_WAITING_FOR_P2SH_A(80), ALICE_WAITING_FOR_AT_LOCK(85), ALICE_WATCH_P2SH_B(90), ALICE_REFUNDING_B(95), ALICE_REFUNDING_A(100), ALICE_DONE(105);
|
ALICE_WAITING_FOR_P2SH_A(80), ALICE_WAITING_FOR_AT_LOCK(85), ALICE_WATCH_P2SH_B(90), ALICE_DONE(95), ALICE_REFUNDING_B(100), ALICE_REFUNDING_A(105), ALICE_REFUNDED(110);
|
||||||
|
|
||||||
public final int value;
|
public final int value;
|
||||||
private static final Map<Integer, State> map = stream(State.values()).collect(toMap(state -> state.value, state -> state));
|
private static final Map<Integer, State> map = stream(State.values()).collect(toMap(state -> state.value, state -> state));
|
||||||
|
Loading…
Reference in New Issue
Block a user