Browse Source

WIP: trade-bot: add missing JavaTypeAdapter to TradeBotData.bitcoinAmount

Also: unify "receiveAddress" to "receivingAddress"

Fix missed changes from "recipient" to "partner" in BTCACCT, etc.
split-DB
catbref 4 years ago
parent
commit
a351756883
  1. 2
      src/main/java/org/qortal/api/model/CrossChainBitcoinRedeemRequest.java
  2. 2
      src/main/java/org/qortal/api/model/TradeBotCreateRequest.java
  3. 14
      src/main/java/org/qortal/api/resource/CrossChainResource.java
  4. 36
      src/main/java/org/qortal/controller/TradeBot.java
  5. 48
      src/main/java/org/qortal/crosschain/BTCACCT.java
  6. 6
      src/main/java/org/qortal/crosschain/BTCP2SH.java
  7. 2
      src/main/java/org/qortal/data/crosschain/TradeBotData.java

2
src/main/java/org/qortal/api/model/CrossChainBitcoinRedeemRequest.java

@ -26,7 +26,7 @@ public class CrossChainBitcoinRedeemRequest {
public byte[] secret;
@Schema(description = "Bitcoin HASH160(public key) for receiving funds, or omit to derive from private key", example = "u17kBVKkKSp12oUzaxFwNnq1JZf")
public byte[] receivePublicKeyHash;
public byte[] receivingAccountInfo;
public CrossChainBitcoinRedeemRequest() {
}

2
src/main/java/org/qortal/api/model/TradeBotCreateRequest.java

@ -28,7 +28,7 @@ public class TradeBotCreateRequest {
public int tradeTimeout;
@Schema(description = "Bitcoin address for receiving", example = "1NCTG9oLk41bU6pcehLNo9DVJup77EHAVx")
public String receiveAddress;
public String receivingAddress;
public TradeBotCreateRequest() {
}

14
src/main/java/org/qortal/api/resource/CrossChainResource.java

@ -853,10 +853,10 @@ public class CrossChainResource {
if (redeemRequest.secret == null || redeemRequest.secret.length != BTCACCT.SECRET_LENGTH)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
if (redeemRequest.receivePublicKeyHash == null)
redeemRequest.receivePublicKeyHash = redeemKey.getPubKeyHash();
if (redeemRequest.receivingAccountInfo == null)
redeemRequest.receivingAccountInfo = redeemKey.getPubKeyHash();
if (redeemRequest.receivePublicKeyHash.length != 20)
if (redeemRequest.receivingAccountInfo.length != 20)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
// Extract data from cross-chain trading AT
@ -899,7 +899,7 @@ public class CrossChainResource {
Coin redeemAmount = Coin.valueOf(p2shBalance - redeemRequest.bitcoinMinerFee.unscaledValue().longValue());
org.bitcoinj.core.Transaction redeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, redeemRequest.secret, redeemRequest.receivePublicKeyHash);
org.bitcoinj.core.Transaction redeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, redeemRequest.secret, redeemRequest.receivingAccountInfo);
boolean wasBroadcast = BTC.getInstance().broadcastTransaction(redeemTransaction);
if (!wasBroadcast)
@ -961,15 +961,15 @@ public class CrossChainResource {
public String tradeBotCreator(TradeBotCreateRequest tradeBotCreateRequest) {
Security.checkApiCallAllowed(request);
Address receiveAddress;
Address receivingAddress;
try {
receiveAddress = Address.fromString(BTC.getInstance().getNetworkParameters(), tradeBotCreateRequest.receiveAddress);
receivingAddress = Address.fromString(BTC.getInstance().getNetworkParameters(), tradeBotCreateRequest.receivingAddress);
} catch (AddressFormatException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
}
// We only support P2PKH addresses at this time
if (receiveAddress.getOutputScriptType() != ScriptType.P2PKH)
if (receivingAddress.getOutputScriptType() != ScriptType.P2PKH)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
if (tradeBotCreateRequest.tradeTimeout < 60)

36
src/main/java/org/qortal/controller/TradeBot.java

@ -108,17 +108,17 @@ public class TradeBot {
byte[] tradeForeignPublicKey = deriveTradeForeignPublicKey(tradePrivateKey);
byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey);
// Convert Bitcoin receive address into public key hash (we only support P2PKH at this time)
Address bitcoinReceiveAddress;
// Convert Bitcoin receiving address into public key hash (we only support P2PKH at this time)
Address bitcoinReceivingAddress;
try {
bitcoinReceiveAddress = Address.fromString(BTC.getInstance().getNetworkParameters(), tradeBotCreateRequest.receiveAddress);
bitcoinReceivingAddress = Address.fromString(BTC.getInstance().getNetworkParameters(), tradeBotCreateRequest.receivingAddress);
} catch (AddressFormatException e) {
throw new DataException("Unsupported Bitcoin receive address: " + tradeBotCreateRequest.receiveAddress);
throw new DataException("Unsupported Bitcoin receiving address: " + tradeBotCreateRequest.receivingAddress);
}
if (bitcoinReceiveAddress.getOutputScriptType() != ScriptType.P2PKH)
throw new DataException("Unsupported Bitcoin receive address: " + tradeBotCreateRequest.receiveAddress);
if (bitcoinReceivingAddress.getOutputScriptType() != ScriptType.P2PKH)
throw new DataException("Unsupported Bitcoin receiving address: " + tradeBotCreateRequest.receivingAddress);
byte[] bitcoinReceivePublicKeyHash = bitcoinReceiveAddress.getHash();
byte[] bitcoinReceivingAccountInfo = bitcoinReceivingAddress.getHash();
PublicKeyAccount creator = new PublicKeyAccount(repository, tradeBotCreateRequest.creatorPublicKey);
@ -151,7 +151,7 @@ public class TradeBot {
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
secretB, hashOfSecretB,
tradeForeignPublicKey, tradeForeignPublicKeyHash,
tradeBotCreateRequest.bitcoinAmount, null, null, null, bitcoinReceivePublicKeyHash);
tradeBotCreateRequest.bitcoinAmount, null, null, null, bitcoinReceivingAccountInfo);
repository.getCrossChainRepository().save(tradeBotData);
repository.saveChanges();
@ -718,9 +718,9 @@ public class TradeBot {
Coin redeemAmount = Coin.ZERO; // The real funds are in P2SH-A
ECKey redeemKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress);
byte[] receivePublicKeyHash = tradeBotData.getReceivingAccountInfo();
byte[] receivingAccountInfo = tradeBotData.getReceivingAccountInfo();
Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, tradeBotData.getSecret(), receivePublicKeyHash);
Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, tradeBotData.getSecret(), receivingAccountInfo);
if (!BTC.getInstance().broadcastTransaction(p2shRedeemTransaction)) {
// We couldn't redeem P2SH-B at this time
@ -787,8 +787,8 @@ public class TradeBot {
// Send 'redeem' MESSAGE to AT using both secrets
byte[] secretA = tradeBotData.getSecret();
String qortalReceiveAddress = Base58.encode(tradeBotData.getReceivingAccountInfo()); // Actually contains whole address, not just PKH
byte[] messageData = BTCACCT.buildRedeemMessage(secretA, secretB, qortalReceiveAddress);
String qortalReceivingAddress = Base58.encode(tradeBotData.getReceivingAccountInfo()); // Actually contains whole address, not just PKH
byte[] messageData = BTCACCT.buildRedeemMessage(secretA, secretB, qortalReceivingAddress);
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, tradeBotData.getAtAddress(), messageData, false, false);
@ -809,10 +809,10 @@ public class TradeBot {
repository.getCrossChainRepository().save(tradeBotData);
repository.saveChanges();
String receiveAddress = tradeBotData.getTradeNativeAddress();
String receivingAddress = 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));
p2shAddress, tradeBotData.getAtAddress(), receivingAddress));
}
/**
@ -873,9 +873,9 @@ public class TradeBot {
Coin redeemAmount = Coin.valueOf(crossChainTradeData.expectedBitcoin);
ECKey redeemKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress);
byte[] receivePublicKeyHash = tradeBotData.getReceivingAccountInfo();
byte[] receivingAccountInfo = tradeBotData.getReceivingAccountInfo();
Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, secretA, receivePublicKeyHash);
Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, secretA, receivingAccountInfo);
if (!BTC.getInstance().broadcastTransaction(p2shRedeemTransaction)) {
// We couldn't redeem P2SH-A at this time
@ -887,9 +887,9 @@ public class TradeBot {
repository.getCrossChainRepository().save(tradeBotData);
repository.saveChanges();
String receiveAddress = BTC.getInstance().pkhToAddress(receivePublicKeyHash);
String receivingAddress = BTC.getInstance().pkhToAddress(receivingAccountInfo);
LOGGER.info(() -> String.format("P2SH-A %s redeemed. Funds should arrive at %s", tradeBotData.getAtAddress(), receiveAddress));
LOGGER.info(() -> String.format("P2SH-A %s redeemed. Funds should arrive at %s", tradeBotData.getAtAddress(), receivingAddress));
}
/**

48
src/main/java/org/qortal/crosschain/BTCACCT.java

@ -88,10 +88,10 @@ import com.google.common.primitives.Bytes;
* <ul>
* <li>secret-A</li>
* <li>secret-B</li>
* <li>Qortal receive address of her chosing</li>
* <li>Qortal receiving address of her chosing</li>
* </ul>
* </li>
* <li>AT's QORT funds are sent to Qortal receive address</li>
* <li>AT's QORT funds are sent to Qortal receiving address</li>
* </ul>
* </li>
* <li>Bob checks AT, extracts secret-A
@ -124,7 +124,7 @@ public class BTCACCT {
+ 8 /*lockTimeB*/
+ 24 /*hash of secret-A (padded from 20 to 24)*/
+ 8 /*lockTimeA*/;
public static final int REDEEM_MESSAGE_LENGTH = 32 /*secret*/ + 32 /*secret*/ + 32 /*partner's Qortal receive address padded from 25 to 32*/;
public static final int REDEEM_MESSAGE_LENGTH = 32 /*secret*/ + 32 /*secret*/ + 32 /*partner's Qortal receiving address padded from 25 to 32*/;
public static final int CANCEL_MESSAGE_LENGTH = 32 /*AT creator's Qortal address*/;
public enum Mode {
@ -195,7 +195,7 @@ public class BTCACCT {
final int addrHashOfSecretAPointer = addrCounter++;
final int addrRedeemMessageSecretBOffset = addrCounter++;
final int addrRedeemMessageReceiveAddressOffset = addrCounter++;
final int addrRedeemMessageReceivingAddressOffset = addrCounter++;
final int addrMessageDataPointer = addrCounter++;
final int addrMessageDataLength = addrCounter++;
@ -290,7 +290,7 @@ public class BTCACCT {
assert dataByteBuffer.position() == addrHashOfSecretBPointer * MachineState.VALUE_SIZE : "addrHashOfSecretBPointer incorrect";
dataByteBuffer.putLong(addrHashOfSecretB);
// Index into data segment of recipient address, used by SET_B_IND
// Index into data segment of partner's Qortal address, used by SET_B_IND
assert dataByteBuffer.position() == addrQortalPartnerAddressPointer * MachineState.VALUE_SIZE : "addrQortalPartnerAddressPointer incorrect";
dataByteBuffer.putLong(addrQortalPartnerAddress1);
@ -318,8 +318,8 @@ public class BTCACCT {
assert dataByteBuffer.position() == addrRedeemMessageSecretBOffset * MachineState.VALUE_SIZE : "addrRedeemMessageSecretBOffset incorrect";
dataByteBuffer.putLong(32L);
// Offset into 'redeem' MESSAGE data payload for extracting Qortal receive address
assert dataByteBuffer.position() == addrRedeemMessageReceiveAddressOffset * MachineState.VALUE_SIZE : "addrRedeemMessageReceiveAddressOffset incorrect";
// Offset into 'redeem' MESSAGE data payload for extracting Qortal receiving address
assert dataByteBuffer.position() == addrRedeemMessageReceivingAddressOffset * MachineState.VALUE_SIZE : "addrRedeemMessageReceivingAddressOffset incorrect";
dataByteBuffer.putLong(64L);
// Source location and length for hashing any passed secret
@ -540,12 +540,12 @@ public class BTCACCT {
codeByteBuffer.put(OpCode.BNZ_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelPayout)));
codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRedeemTxnLoop == null ? 0 : labelRedeemTxnLoop));
/* Success! Pay arranged amount to receive address */
/* Success! Pay arranged amount to receiving address */
labelPayout = codeByteBuffer.position();
// Extract Qortal receive address from next 32 bytes of message from transaction into B register
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrRedeemMessageReceiveAddressOffset));
// Pay AT's balance to recipient
// Extract Qortal receiving address from next 32 bytes of message from transaction into B register
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrRedeemMessageReceivingAddressOffset));
// Pay AT's balance to receiving address
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PAY_TO_ADDRESS_IN_B, addrQortAmount));
// Set redeemed mode
codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, Mode.REDEEMED.value));
@ -678,7 +678,7 @@ public class BTCACCT {
// Skip pointer to message sender
dataByteBuffer.position(dataByteBuffer.position() + 8);
// Skip 'trade' message data offset for recipient's bitcoin PKH
// Skip 'trade' message data offset for partner's bitcoin PKH
dataByteBuffer.position(dataByteBuffer.position() + 8);
// Skip pointer to partner's bitcoin PKH
@ -693,7 +693,7 @@ public class BTCACCT {
// Skip 'redeem' message data offset for secret-B
dataByteBuffer.position(dataByteBuffer.position() + 8);
// Skip 'redeem' message data offset for partner's Qortal receive address
// Skip 'redeem' message data offset for partner's Qortal receiving address
dataByteBuffer.position(dataByteBuffer.position() + 8);
// Skip pointer to message data
@ -751,9 +751,9 @@ public class BTCACCT {
dataByteBuffer.position(dataByteBuffer.position() + 32 - hashOfSecretA.length); // skip to 32 bytes
// Potential partner's Bitcoin PKH
byte[] recipientBitcoinPKH = new byte[20];
dataByteBuffer.get(recipientBitcoinPKH);
dataByteBuffer.position(dataByteBuffer.position() + 32 - recipientBitcoinPKH.length); // skip to 32 bytes
byte[] partnerBitcoinPKH = new byte[20];
dataByteBuffer.get(partnerBitcoinPKH);
dataByteBuffer.position(dataByteBuffer.position() + 32 - partnerBitcoinPKH.length); // skip to 32 bytes
long modeValue = dataByteBuffer.getLong();
Mode mode = Mode.valueOf((int) (modeValue & 0xffL));
@ -764,7 +764,7 @@ public class BTCACCT {
tradeData.tradeRefundHeight = new Timestamp(tradeRefundTimestamp).blockHeight;
tradeData.qortalPartnerAddress = qortalRecipient;
tradeData.hashOfSecretA = hashOfSecretA;
tradeData.partnerBitcoinPKH = recipientBitcoinPKH;
tradeData.partnerBitcoinPKH = partnerBitcoinPKH;
tradeData.lockTimeA = lockTimeA;
tradeData.lockTimeB = lockTimeB;
} else {
@ -775,9 +775,9 @@ public class BTCACCT {
}
/** Returns 'offer' MESSAGE payload for trade partner to send to AT creator's trade address. */
public static byte[] buildOfferMessage(byte[] recipientBitcoinPKH, byte[] hashOfSecretA, int lockTimeA) {
public static byte[] buildOfferMessage(byte[] partnerBitcoinPKH, byte[] hashOfSecretA, int lockTimeA) {
byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA);
return Bytes.concat(recipientBitcoinPKH, hashOfSecretA, lockTimeABytes);
return Bytes.concat(partnerBitcoinPKH, hashOfSecretA, lockTimeABytes);
}
/** Returns info extracted from 'offer' MESSAGE payload sent by trade partner to AT creator's trade address, or null if not valid. */
@ -796,11 +796,11 @@ public class BTCACCT {
/** Returns 'trade' MESSAGE payload for AT creator to send to AT. */
public static byte[] buildTradeMessage(String partnerQortalTradeAddress, byte[] partnerBitcoinPKH, byte[] hashOfSecretA, int lockTimeA, int lockTimeB) {
byte[] data = new byte[TRADE_MESSAGE_LENGTH];
byte[] recipientQortalAddressBytes = Base58.decode(partnerQortalTradeAddress);
byte[] partnerQortalAddressBytes = Base58.decode(partnerQortalTradeAddress);
byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA);
byte[] lockTimeBBytes = BitTwiddling.toBEByteArray((long) lockTimeB);
System.arraycopy(recipientQortalAddressBytes, 0, data, 0, recipientQortalAddressBytes.length);
System.arraycopy(partnerQortalAddressBytes, 0, data, 0, partnerQortalAddressBytes.length);
System.arraycopy(partnerBitcoinPKH, 0, data, 32, partnerBitcoinPKH.length);
System.arraycopy(lockTimeBBytes, 0, data, 56, lockTimeBBytes.length);
System.arraycopy(hashOfSecretA, 0, data, 64, hashOfSecretA.length);
@ -820,13 +820,13 @@ public class BTCACCT {
}
/** Returns 'redeem' MESSAGE payload for trade partner/ to send to AT. */
public static byte[] buildRedeemMessage(byte[] secretA, byte[] secretB, String qortalReceiveAddress) {
public static byte[] buildRedeemMessage(byte[] secretA, byte[] secretB, String qortalReceivingAddress) {
byte[] data = new byte[REDEEM_MESSAGE_LENGTH];
byte[] qortalReceiveAddressBytes = Base58.decode(qortalReceiveAddress);
byte[] qortalReceivingAddressBytes = Base58.decode(qortalReceivingAddress);
System.arraycopy(secretA, 0, data, 0, secretA.length);
System.arraycopy(secretB, 0, data, 32, secretB.length);
System.arraycopy(qortalReceiveAddressBytes, 0, data, 64, qortalReceiveAddressBytes.length);
System.arraycopy(qortalReceivingAddressBytes, 0, data, 64, qortalReceivingAddressBytes.length);
return data;
}

6
src/main/java/org/qortal/crosschain/BTCP2SH.java

@ -163,10 +163,10 @@ public class BTCP2SH {
* @param fundingOutput output from transaction that funded P2SH address
* @param redeemScriptBytes the redeemScript itself, in byte[] form
* @param secret actual 32-byte secret used when building redeemScript
* @param receivePublicKeyHash PKH used for output
* @param receivingAccountInfo Bitcoin PKH used for output
* @return Signed Bitcoin transaction for redeeming P2SH
*/
public static Transaction buildRedeemTransaction(Coin redeemAmount, ECKey redeemKey, List<TransactionOutput> fundingOutputs, byte[] redeemScriptBytes, byte[] secret, byte[] receivePublicKeyHash) {
public static Transaction buildRedeemTransaction(Coin redeemAmount, ECKey redeemKey, List<TransactionOutput> fundingOutputs, byte[] redeemScriptBytes, byte[] secret, byte[] receivingAccountInfo) {
Function<byte[], Script> redeemSigScriptBuilder = (txSigBytes) -> {
// Build scriptSig with...
ScriptBuilder scriptBuilder = new ScriptBuilder();
@ -187,7 +187,7 @@ public class BTCP2SH {
return scriptBuilder.build();
};
return buildP2shTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, null, redeemSigScriptBuilder, receivePublicKeyHash);
return buildP2shTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, null, redeemSigScriptBuilder, receivingAccountInfo);
}
/** Returns 'secret', if any, given list of raw bitcoin transactions. */

2
src/main/java/org/qortal/data/crosschain/TradeBotData.java

@ -8,6 +8,7 @@ import java.util.Map;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import io.swagger.v3.oas.annotations.media.Schema;
@ -46,6 +47,7 @@ public class TradeBotData {
private byte[] tradeForeignPublicKey;
private byte[] tradeForeignPublicKeyHash;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long bitcoinAmount;
// Never expose this via API

Loading…
Cancel
Save