forked from Qortal/qortal
WIP: trade-bot: initial API call for listing completed trades
Also: renamed trade bot field/column "receiving_public_key_hash" to "receiving_account_info" as Alice's trade bot uses it to store Alice's Qortal address, not PKH. Added some extra simplistic repository calls to support above, like BlockRepository.getTimestampFromHeight, ATRepository.getCreatorPublicKey(atAddress)
This commit is contained in:
parent
e9c85c946e
commit
ea9b0d4588
@ -0,0 +1,43 @@
|
|||||||
|
package org.qortal.api.model;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||||
|
|
||||||
|
import org.qortal.data.crosschain.CrossChainTradeData;
|
||||||
|
|
||||||
|
// All properties to be converted to JSON via JAXB
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
public class CrossChainTradeSummary {
|
||||||
|
|
||||||
|
private long tradeTimestamp;
|
||||||
|
|
||||||
|
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||||
|
private long qortAmount;
|
||||||
|
|
||||||
|
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||||
|
private long btcAmount;
|
||||||
|
|
||||||
|
protected CrossChainTradeSummary() {
|
||||||
|
/* For JAXB */
|
||||||
|
}
|
||||||
|
|
||||||
|
public CrossChainTradeSummary(CrossChainTradeData crossChainTradeData, long timestamp) {
|
||||||
|
this.tradeTimestamp = timestamp;
|
||||||
|
this.qortAmount = crossChainTradeData.qortAmount;
|
||||||
|
this.btcAmount = crossChainTradeData.expectedBitcoin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTradeTimestamp() {
|
||||||
|
return this.tradeTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getQortAmount() {
|
||||||
|
return this.qortAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getBtcAmount() {
|
||||||
|
return this.btcAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -43,6 +43,7 @@ import org.qortal.api.Security;
|
|||||||
import org.qortal.api.model.CrossChainCancelRequest;
|
import org.qortal.api.model.CrossChainCancelRequest;
|
||||||
import org.qortal.api.model.CrossChainSecretRequest;
|
import org.qortal.api.model.CrossChainSecretRequest;
|
||||||
import org.qortal.api.model.CrossChainTradeRequest;
|
import org.qortal.api.model.CrossChainTradeRequest;
|
||||||
|
import org.qortal.api.model.CrossChainTradeSummary;
|
||||||
import org.qortal.api.model.TradeBotCreateRequest;
|
import org.qortal.api.model.TradeBotCreateRequest;
|
||||||
import org.qortal.api.model.TradeBotRespondRequest;
|
import org.qortal.api.model.TradeBotRespondRequest;
|
||||||
import org.qortal.api.model.CrossChainBitcoinP2SHStatus;
|
import org.qortal.api.model.CrossChainBitcoinP2SHStatus;
|
||||||
@ -57,6 +58,7 @@ 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.at.ATData;
|
import org.qortal.data.at.ATData;
|
||||||
|
import org.qortal.data.at.ATStateData;
|
||||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
import org.qortal.data.crosschain.CrossChainTradeData;
|
||||||
import org.qortal.data.crosschain.TradeBotData;
|
import org.qortal.data.crosschain.TradeBotData;
|
||||||
import org.qortal.data.transaction.BaseTransactionData;
|
import org.qortal.data.transaction.BaseTransactionData;
|
||||||
@ -92,7 +94,6 @@ public class CrossChainResource {
|
|||||||
summary = "Find cross-chain trade offers",
|
summary = "Find cross-chain trade offers",
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "automated transactions",
|
|
||||||
content = @Content(
|
content = @Content(
|
||||||
array = @ArraySchema(
|
array = @ArraySchema(
|
||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
@ -1099,6 +1100,54 @@ public class CrossChainResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/trades")
|
||||||
|
@Operation(
|
||||||
|
summary = "Find completed cross-chain trades",
|
||||||
|
description = "Returns summary info about successfully completed cross-chain trades",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
content = @Content(
|
||||||
|
array = @ArraySchema(
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = CrossChainTradeSummary.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE})
|
||||||
|
public List<CrossChainTradeSummary> getCompletedTrades(
|
||||||
|
@Parameter( ref = "limit") @QueryParam("limit") Integer limit,
|
||||||
|
@Parameter( ref = "offset" ) @QueryParam("offset") Integer offset,
|
||||||
|
@Parameter( ref = "reverse" ) @QueryParam("reverse") Boolean reverse) {
|
||||||
|
// Impose a limit on 'limit'
|
||||||
|
if (limit != null && limit > 100)
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
|
|
||||||
|
byte[] codeHash = BTCACCT.CODE_BYTES_HASH;
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
List<ATStateData> atStates = repository.getATRepository().getMatchingFinalATStates(codeHash, BTCACCT.MODE_BYTE_OFFSET, (long) BTCACCT.Mode.REDEEMED.value, limit, offset, reverse);
|
||||||
|
|
||||||
|
List<CrossChainTradeSummary> crossChainTrades = new ArrayList<>();
|
||||||
|
for (ATStateData atState : atStates) {
|
||||||
|
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atState);
|
||||||
|
|
||||||
|
// We also need block timestamp for use as trade timestamp
|
||||||
|
long timestamp = repository.getBlockRepository().getTimestampFromHeight(atState.getHeight());
|
||||||
|
|
||||||
|
CrossChainTradeSummary crossChainTradeSummary = new CrossChainTradeSummary(crossChainTradeData, timestamp);
|
||||||
|
crossChainTrades.add(crossChainTradeSummary);
|
||||||
|
}
|
||||||
|
|
||||||
|
return crossChainTrades;
|
||||||
|
} catch (DataException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ATData fetchAtDataWithChecking(Repository repository, String atAddress) throws DataException {
|
private ATData fetchAtDataWithChecking(Repository repository, String atAddress) throws DataException {
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||||
if (atData == null)
|
if (atData == null)
|
||||||
|
@ -718,7 +718,7 @@ public class TradeBot {
|
|||||||
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);
|
||||||
byte[] receivePublicKeyHash = tradeBotData.getReceivingPublicKeyHash();
|
byte[] receivePublicKeyHash = tradeBotData.getReceivingAccountInfo();
|
||||||
|
|
||||||
Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, tradeBotData.getSecret(), receivePublicKeyHash);
|
Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, tradeBotData.getSecret(), receivePublicKeyHash);
|
||||||
|
|
||||||
@ -787,7 +787,7 @@ public class TradeBot {
|
|||||||
|
|
||||||
// Send 'redeem' MESSAGE to AT using both secrets
|
// Send 'redeem' MESSAGE to AT using both secrets
|
||||||
byte[] secretA = tradeBotData.getSecret();
|
byte[] secretA = tradeBotData.getSecret();
|
||||||
String qortalReceiveAddress = Base58.encode(tradeBotData.getReceivingPublicKeyHash()); // Actually contains whole address, not just PKH
|
String qortalReceiveAddress = Base58.encode(tradeBotData.getReceivingAccountInfo()); // Actually contains whole address, not just PKH
|
||||||
byte[] messageData = BTCACCT.buildRedeemMessage(secretA, secretB, qortalReceiveAddress);
|
byte[] messageData = BTCACCT.buildRedeemMessage(secretA, secretB, qortalReceiveAddress);
|
||||||
|
|
||||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||||
@ -873,7 +873,7 @@ public class TradeBot {
|
|||||||
Coin redeemAmount = Coin.valueOf(crossChainTradeData.expectedBitcoin);
|
Coin redeemAmount = Coin.valueOf(crossChainTradeData.expectedBitcoin);
|
||||||
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);
|
||||||
byte[] receivePublicKeyHash = tradeBotData.getReceivingPublicKeyHash();
|
byte[] receivePublicKeyHash = tradeBotData.getReceivingAccountInfo();
|
||||||
|
|
||||||
Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, secretA, receivePublicKeyHash);
|
Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, secretA, receivePublicKeyHash);
|
||||||
|
|
||||||
|
@ -108,6 +108,11 @@ public class BTCACCT {
|
|||||||
public static final int MIN_LOCKTIME = 1500000000;
|
public static final int MIN_LOCKTIME = 1500000000;
|
||||||
public static final byte[] CODE_BYTES_HASH = HashCode.fromString("fad14381b77ae1a2bfe7e16a1a8b571839c5f405fca0490ead08499ac170f65b").asBytes(); // SHA256 of AT code bytes
|
public static final byte[] CODE_BYTES_HASH = HashCode.fromString("fad14381b77ae1a2bfe7e16a1a8b571839c5f405fca0490ead08499ac170f65b").asBytes(); // SHA256 of AT code bytes
|
||||||
|
|
||||||
|
/** <b>Value</b> offset into AT segment where 'mode' variable (long) is stored. (Multiply by MachineState.VALUE_SIZE for byte offset). */
|
||||||
|
private static final int MODE_VALUE_OFFSET = 63;
|
||||||
|
/** <b>Byte</b> offset into AT state data where 'mode' variable (long) is stored. */
|
||||||
|
public static final int MODE_BYTE_OFFSET = MachineState.HEADER_LENGTH + (MODE_VALUE_OFFSET * MachineState.VALUE_SIZE);
|
||||||
|
|
||||||
public static class OfferMessageData {
|
public static class OfferMessageData {
|
||||||
public byte[] partnerBitcoinPKH;
|
public byte[] partnerBitcoinPKH;
|
||||||
public byte[] hashOfSecretA;
|
public byte[] hashOfSecretA;
|
||||||
@ -235,6 +240,7 @@ public class BTCACCT {
|
|||||||
addrCounter += 4;
|
addrCounter += 4;
|
||||||
|
|
||||||
final int addrMode = addrCounter++;
|
final int addrMode = addrCounter++;
|
||||||
|
assert addrMode == MODE_VALUE_OFFSET : "MODE_VALUE_OFFSET does not match addrMode";
|
||||||
|
|
||||||
// Data segment
|
// Data segment
|
||||||
ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE);
|
ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE);
|
||||||
@ -584,18 +590,40 @@ public class BTCACCT {
|
|||||||
* @throws DataException
|
* @throws DataException
|
||||||
*/
|
*/
|
||||||
public static CrossChainTradeData populateTradeData(Repository repository, ATData atData) throws DataException {
|
public static CrossChainTradeData populateTradeData(Repository repository, ATData atData) throws DataException {
|
||||||
String atAddress = atData.getATAddress();
|
ATStateData atStateData = repository.getATRepository().getLatestATState(atData.getATAddress());
|
||||||
|
return populateTradeData(repository, atData.getCreatorPublicKey(), atStateData);
|
||||||
|
}
|
||||||
|
|
||||||
ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress);
|
/**
|
||||||
byte[] stateData = atStateData.getStateData();
|
* Returns CrossChainTradeData with useful info extracted from AT.
|
||||||
|
*
|
||||||
|
* @param repository
|
||||||
|
* @param atAddress
|
||||||
|
* @throws DataException
|
||||||
|
*/
|
||||||
|
public static CrossChainTradeData populateTradeData(Repository repository, ATStateData atStateData) throws DataException {
|
||||||
|
byte[] creatorPublicKey = repository.getATRepository().getCreatorPublicKey(atStateData.getATAddress());
|
||||||
|
return populateTradeData(repository, creatorPublicKey, atStateData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns CrossChainTradeData with useful info extracted from AT.
|
||||||
|
*
|
||||||
|
* @param repository
|
||||||
|
* @param atAddress
|
||||||
|
* @throws DataException
|
||||||
|
*/
|
||||||
|
public static CrossChainTradeData populateTradeData(Repository repository, byte[] creatorPublicKey, ATStateData atStateData) throws DataException {
|
||||||
|
String atAddress = atStateData.getATAddress();
|
||||||
|
|
||||||
QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance();
|
QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance();
|
||||||
|
byte[] stateData = atStateData.getStateData();
|
||||||
byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, stateData);
|
byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, stateData);
|
||||||
|
|
||||||
CrossChainTradeData tradeData = new CrossChainTradeData();
|
CrossChainTradeData tradeData = new CrossChainTradeData();
|
||||||
tradeData.qortalAtAddress = atAddress;
|
tradeData.qortalAtAddress = atAddress;
|
||||||
tradeData.qortalCreator = Crypto.toAddress(atData.getCreatorPublicKey());
|
tradeData.qortalCreator = Crypto.toAddress(creatorPublicKey);
|
||||||
tradeData.creationTimestamp = atData.getCreation();
|
tradeData.creationTimestamp = atStateData.getCreation();
|
||||||
|
|
||||||
Account atAccount = new Account(repository, atAddress);
|
Account atAccount = new Account(repository, atAddress);
|
||||||
tradeData.qortBalance = atAccount.getConfirmedBalance(Asset.QORT);
|
tradeData.qortBalance = atAccount.getConfirmedBalance(Asset.QORT);
|
||||||
|
@ -58,7 +58,7 @@ public class TradeBotData {
|
|||||||
private Integer lockTimeA;
|
private Integer lockTimeA;
|
||||||
|
|
||||||
// Could be Bitcoin or Qortal...
|
// Could be Bitcoin or Qortal...
|
||||||
private byte[] receivingPublicKeyHash;
|
private byte[] receivingAccountInfo;
|
||||||
|
|
||||||
protected TradeBotData() {
|
protected TradeBotData() {
|
||||||
/* JAXB */
|
/* JAXB */
|
||||||
@ -68,7 +68,7 @@ public class TradeBotData {
|
|||||||
byte[] tradeNativePublicKey, byte[] tradeNativePublicKeyHash, String tradeNativeAddress,
|
byte[] tradeNativePublicKey, byte[] tradeNativePublicKeyHash, String tradeNativeAddress,
|
||||||
byte[] secret, byte[] hashOfSecret,
|
byte[] secret, byte[] hashOfSecret,
|
||||||
byte[] tradeForeignPublicKey, byte[] tradeForeignPublicKeyHash,
|
byte[] tradeForeignPublicKey, byte[] tradeForeignPublicKeyHash,
|
||||||
long bitcoinAmount, String xprv58, byte[] lastTransactionSignature, Integer lockTimeA, byte[] receivingPublicKeyHash) {
|
long bitcoinAmount, String xprv58, byte[] lastTransactionSignature, Integer lockTimeA, byte[] receivingAccountInfo) {
|
||||||
this.tradePrivateKey = tradePrivateKey;
|
this.tradePrivateKey = tradePrivateKey;
|
||||||
this.tradeState = tradeState;
|
this.tradeState = tradeState;
|
||||||
this.atAddress = atAddress;
|
this.atAddress = atAddress;
|
||||||
@ -83,7 +83,7 @@ public class TradeBotData {
|
|||||||
this.xprv58 = xprv58;
|
this.xprv58 = xprv58;
|
||||||
this.lastTransactionSignature = lastTransactionSignature;
|
this.lastTransactionSignature = lastTransactionSignature;
|
||||||
this.lockTimeA = lockTimeA;
|
this.lockTimeA = lockTimeA;
|
||||||
this.receivingPublicKeyHash = receivingPublicKeyHash;
|
this.receivingAccountInfo = receivingAccountInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getTradePrivateKey() {
|
public byte[] getTradePrivateKey() {
|
||||||
@ -158,8 +158,8 @@ public class TradeBotData {
|
|||||||
this.lockTimeA = lockTimeA;
|
this.lockTimeA = lockTimeA;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getReceivingPublicKeyHash() {
|
public byte[] getReceivingAccountInfo() {
|
||||||
return this.receivingPublicKeyHash;
|
return this.receivingAccountInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,9 @@ public interface ATRepository {
|
|||||||
/** Returns where AT with passed address exists in repository */
|
/** Returns where AT with passed address exists in repository */
|
||||||
public boolean exists(String atAddress) throws DataException;
|
public boolean exists(String atAddress) throws DataException;
|
||||||
|
|
||||||
|
/** Returns AT creator's public key, or null if not found */
|
||||||
|
public byte[] getCreatorPublicKey(String atAddress) throws DataException;
|
||||||
|
|
||||||
/** Returns list of executable ATs, empty if none found */
|
/** Returns list of executable ATs, empty if none found */
|
||||||
public List<ATData> getAllExecutableATs() throws DataException;
|
public List<ATData> getAllExecutableATs() throws DataException;
|
||||||
|
|
||||||
@ -54,6 +57,24 @@ public interface ATRepository {
|
|||||||
*/
|
*/
|
||||||
public ATStateData getLatestATState(String atAddress) throws DataException;
|
public ATStateData getLatestATState(String atAddress) throws DataException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns final ATStateData for ATs matching codeHash (required)
|
||||||
|
* and specific data segment value (optional).
|
||||||
|
* <p>
|
||||||
|
* If searching for specific data segment value, both <tt>dataByteOffset</tt>
|
||||||
|
* and <tt>expectedValue</tt> need to be non-null.
|
||||||
|
* <p>
|
||||||
|
* Note that <tt>dataByteOffset</tt> starts from 0 and will typically be
|
||||||
|
* a multiple of <tt>MachineState.VALUE_SIZE</tt>, which is usually 8:
|
||||||
|
* width of a long.
|
||||||
|
* <p>
|
||||||
|
* Although <tt>expectedValue</tt>, if provided, is natively an unsigned long,
|
||||||
|
* the data segment comparison is done via unsigned hex string.
|
||||||
|
*/
|
||||||
|
public List<ATStateData> getMatchingFinalATStates(byte[] codeHash,
|
||||||
|
Integer dataByteOffset, Long expectedValue,
|
||||||
|
Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all ATStateData for a given block height.
|
* Returns all ATStateData for a given block height.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -60,6 +60,15 @@ public interface BlockRepository {
|
|||||||
*/
|
*/
|
||||||
public int getHeightFromTimestamp(long timestamp) throws DataException;
|
public int getHeightFromTimestamp(long timestamp) throws DataException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns block timestamp for a given height.
|
||||||
|
*
|
||||||
|
* @param height
|
||||||
|
* @return timestamp, or 0 if height is out of bounds.
|
||||||
|
* @throws DataException
|
||||||
|
*/
|
||||||
|
public long getTimestampFromHeight(int height) throws DataException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return highest block height from repository.
|
* Return highest block height from repository.
|
||||||
*
|
*
|
||||||
|
@ -68,6 +68,20 @@ public class HSQLDBATRepository implements ATRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getCreatorPublicKey(String atAddress) throws DataException {
|
||||||
|
String sql = "SELECT creator FROM ATs WHERE AT_address = ?";
|
||||||
|
|
||||||
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, atAddress)) {
|
||||||
|
if (resultSet == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return resultSet.getBytes(1);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to fetch AT creator's public key from repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ATData> getAllExecutableATs() throws DataException {
|
public List<ATData> getAllExecutableATs() throws DataException {
|
||||||
String sql = "SELECT AT_address, creator, created_when, version, asset_id, code_bytes, code_hash, "
|
String sql = "SELECT AT_address, creator, created_when, version, asset_id, code_bytes, code_hash, "
|
||||||
@ -273,6 +287,68 @@ public class HSQLDBATRepository implements ATRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ATStateData> getMatchingFinalATStates(byte[] codeHash,
|
||||||
|
Integer dataByteOffset, Long expectedValue,
|
||||||
|
Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
StringBuilder sql = new StringBuilder(1024);
|
||||||
|
sql.append("SELECT AT_address, height, created_when, state_data, state_hash, fees, is_initial "
|
||||||
|
+ "FROM ATs "
|
||||||
|
+ "CROSS JOIN LATERAL("
|
||||||
|
+ "SELECT height, created_when, state_data, state_hash, fees, is_initial "
|
||||||
|
+ "FROM ATStates "
|
||||||
|
+ "WHERE ATStates.AT_address = ATs.AT_address "
|
||||||
|
+ "ORDER BY height DESC "
|
||||||
|
+ "LIMIT 1"
|
||||||
|
+ ") AS FinalATStates "
|
||||||
|
+ "WHERE code_hash = ? AND is_finished ");
|
||||||
|
|
||||||
|
Object[] bindParams;
|
||||||
|
|
||||||
|
if (dataByteOffset != null && expectedValue != null) {
|
||||||
|
sql.append("AND RAWTOHEX(SUBSTRING(state_data FROM ? FOR 8)) = ? ");
|
||||||
|
|
||||||
|
// We convert our long to hex Java-side to control endian
|
||||||
|
String expectedHexValue = String.format("%016x", expectedValue); // left-zero-padding and conversion
|
||||||
|
|
||||||
|
// SQL binary data offsets start at 1
|
||||||
|
bindParams = new Object[] { codeHash, dataByteOffset + 1, expectedHexValue };
|
||||||
|
} else {
|
||||||
|
bindParams = new Object[] { codeHash };
|
||||||
|
}
|
||||||
|
|
||||||
|
sql.append(" ORDER BY height ");
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql.append("DESC");
|
||||||
|
|
||||||
|
HSQLDBRepository.limitOffsetSql(sql, limit, offset);
|
||||||
|
|
||||||
|
List<ATStateData> atStates = new ArrayList<>();
|
||||||
|
|
||||||
|
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), bindParams)) {
|
||||||
|
if (resultSet == null)
|
||||||
|
return atStates;
|
||||||
|
|
||||||
|
do {
|
||||||
|
String atAddress = resultSet.getString(1);
|
||||||
|
int height = resultSet.getInt(2);
|
||||||
|
long created = resultSet.getLong(3);
|
||||||
|
byte[] stateData = resultSet.getBytes(4); // Actually BLOB
|
||||||
|
byte[] stateHash = resultSet.getBytes(5);
|
||||||
|
long fees = resultSet.getLong(6);
|
||||||
|
boolean isInitial = resultSet.getBoolean(7);
|
||||||
|
|
||||||
|
ATStateData atStateData = new ATStateData(atAddress, height, created, stateData, stateHash, fees, isInitial);
|
||||||
|
|
||||||
|
atStates.add(atStateData);
|
||||||
|
} while (resultSet.next());
|
||||||
|
|
||||||
|
return atStates;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to fetch matching AT states from repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ATStateData> getBlockATStatesAtHeight(int height) throws DataException {
|
public List<ATStateData> getBlockATStatesAtHeight(int height) throws DataException {
|
||||||
String sql = "SELECT AT_address, state_hash, fees, is_initial "
|
String sql = "SELECT AT_address, state_hash, fees, is_initial "
|
||||||
|
@ -131,6 +131,20 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimestampFromHeight(int height) throws DataException {
|
||||||
|
String sql = "SELECT minted_when FROM Blocks WHERE height = ?";
|
||||||
|
|
||||||
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, height)) {
|
||||||
|
if (resultSet == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return resultSet.getLong(1);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Error obtaining block timestamp by height from repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getBlockchainHeight() throws DataException {
|
public int getBlockchainHeight() throws DataException {
|
||||||
String sql = "SELECT height FROM Blocks ORDER BY height DESC LIMIT 1";
|
String sql = "SELECT height FROM Blocks ORDER BY height DESC LIMIT 1";
|
||||||
|
@ -23,7 +23,7 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
|
|||||||
+ "trade_native_public_key, trade_native_public_key_hash, "
|
+ "trade_native_public_key, trade_native_public_key_hash, "
|
||||||
+ "trade_native_address, secret, hash_of_secret, "
|
+ "trade_native_address, secret, hash_of_secret, "
|
||||||
+ "trade_foreign_public_key, trade_foreign_public_key_hash, "
|
+ "trade_foreign_public_key, trade_foreign_public_key_hash, "
|
||||||
+ "bitcoin_amount, xprv58, last_transaction_signature, locktime_a, receiving_public_key_hash "
|
+ "bitcoin_amount, xprv58, last_transaction_signature, locktime_a, receiving_account_info "
|
||||||
+ "FROM TradeBotStates "
|
+ "FROM TradeBotStates "
|
||||||
+ "WHERE trade_private_key = ?";
|
+ "WHERE trade_private_key = ?";
|
||||||
|
|
||||||
@ -50,14 +50,14 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
|
|||||||
Integer lockTimeA = resultSet.getInt(13);
|
Integer lockTimeA = resultSet.getInt(13);
|
||||||
if (lockTimeA == 0 && resultSet.wasNull())
|
if (lockTimeA == 0 && resultSet.wasNull())
|
||||||
lockTimeA = null;
|
lockTimeA = null;
|
||||||
byte[] receivingPublicKeyHash = resultSet.getBytes(14);
|
byte[] receivingAccountInfo = resultSet.getBytes(14);
|
||||||
|
|
||||||
return new TradeBotData(tradePrivateKey, tradeState,
|
return new TradeBotData(tradePrivateKey, tradeState,
|
||||||
atAddress,
|
atAddress,
|
||||||
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
|
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
|
||||||
secret, hashOfSecret,
|
secret, hashOfSecret,
|
||||||
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
||||||
bitcoinAmount, xprv58, lastTransactionSignature, lockTimeA, receivingPublicKeyHash);
|
bitcoinAmount, xprv58, lastTransactionSignature, lockTimeA, receivingAccountInfo);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Unable to fetch trade-bot trading state from repository", e);
|
throw new DataException("Unable to fetch trade-bot trading state from repository", e);
|
||||||
}
|
}
|
||||||
@ -69,7 +69,7 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
|
|||||||
+ "trade_native_public_key, trade_native_public_key_hash, "
|
+ "trade_native_public_key, trade_native_public_key_hash, "
|
||||||
+ "trade_native_address, secret, hash_of_secret, "
|
+ "trade_native_address, secret, hash_of_secret, "
|
||||||
+ "trade_foreign_public_key, trade_foreign_public_key_hash, "
|
+ "trade_foreign_public_key, trade_foreign_public_key_hash, "
|
||||||
+ "bitcoin_amount, xprv58, last_transaction_signature, locktime_a, receiving_public_key_hash "
|
+ "bitcoin_amount, xprv58, last_transaction_signature, locktime_a, receiving_account_info "
|
||||||
+ "FROM TradeBotStates";
|
+ "FROM TradeBotStates";
|
||||||
|
|
||||||
List<TradeBotData> allTradeBotData = new ArrayList<>();
|
List<TradeBotData> allTradeBotData = new ArrayList<>();
|
||||||
@ -99,14 +99,14 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
|
|||||||
Integer lockTimeA = resultSet.getInt(14);
|
Integer lockTimeA = resultSet.getInt(14);
|
||||||
if (lockTimeA == 0 && resultSet.wasNull())
|
if (lockTimeA == 0 && resultSet.wasNull())
|
||||||
lockTimeA = null;
|
lockTimeA = null;
|
||||||
byte[] receivingPublicKeyHash = resultSet.getBytes(15);
|
byte[] receivingAccountInfo = resultSet.getBytes(15);
|
||||||
|
|
||||||
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, tradeState,
|
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, tradeState,
|
||||||
atAddress,
|
atAddress,
|
||||||
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
|
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
|
||||||
secret, hashOfSecret,
|
secret, hashOfSecret,
|
||||||
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
||||||
bitcoinAmount, xprv58, lastTransactionSignature, lockTimeA, receivingPublicKeyHash);
|
bitcoinAmount, xprv58, lastTransactionSignature, lockTimeA, receivingAccountInfo);
|
||||||
allTradeBotData.add(tradeBotData);
|
allTradeBotData.add(tradeBotData);
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
|
|||||||
.bind("xprv58", tradeBotData.getXprv58())
|
.bind("xprv58", tradeBotData.getXprv58())
|
||||||
.bind("last_transaction_signature", tradeBotData.getLastTransactionSignature())
|
.bind("last_transaction_signature", tradeBotData.getLastTransactionSignature())
|
||||||
.bind("locktime_a", tradeBotData.getLockTimeA())
|
.bind("locktime_a", tradeBotData.getLockTimeA())
|
||||||
.bind("receiving_public_key_hash", tradeBotData.getReceivingPublicKeyHash());
|
.bind("receiving_account_info", tradeBotData.getReceivingAccountInfo());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
saveHelper.execute(this.repository);
|
saveHelper.execute(this.repository);
|
||||||
|
@ -626,7 +626,19 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
+ "trade_native_address QortalAddress NOT NULL, secret VARBINARY(32) NOT NULL, hash_of_secret VARBINARY(32) NOT NULL, "
|
+ "trade_native_address QortalAddress NOT NULL, secret VARBINARY(32) NOT NULL, hash_of_secret VARBINARY(32) NOT NULL, "
|
||||||
+ "trade_foreign_public_key VARBINARY(33) NOT NULL, trade_foreign_public_key_hash VARBINARY(32) NOT NULL, "
|
+ "trade_foreign_public_key VARBINARY(33) NOT NULL, trade_foreign_public_key_hash VARBINARY(32) NOT NULL, "
|
||||||
+ "bitcoin_amount BIGINT NOT NULL, xprv58 VARCHAR(200), last_transaction_signature Signature, locktime_a BIGINT, "
|
+ "bitcoin_amount BIGINT NOT NULL, xprv58 VARCHAR(200), last_transaction_signature Signature, locktime_a BIGINT, "
|
||||||
+ "receiving_public_key_hash VARBINARY(32) NOT NULL, PRIMARY KEY (trade_private_key))");
|
+ "receiving_account_info VARBINARY(32) NOT NULL, PRIMARY KEY (trade_private_key))");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 21:
|
||||||
|
// AT functionality index
|
||||||
|
stmt.execute("CREATE INDEX IF NOT EXISTS ATCodeHashIndex ON ATs (code_hash, is_finished)");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 22:
|
||||||
|
// XXX for testing only - do not merge in 'master'
|
||||||
|
stmt.execute("ALTER TABLE TradeBotStates ADD COLUMN IF NOT EXISTS receiving_public_key_hash VARBINARY(32)");
|
||||||
|
stmt.execute("ALTER TABLE TradeBotStates DROP COLUMN receiving_public_key_hash");
|
||||||
|
stmt.execute("ALTER TABLE TradeBotStates ADD COLUMN IF NOT EXISTS receiving_account_info VARBINARY(32)");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
Loading…
Reference in New Issue
Block a user