forked from Qortal/qortal
WIP commit
This commit is contained in:
parent
ead84d70d1
commit
cc13d1d0f1
@ -19,6 +19,7 @@ import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
@ -43,6 +44,7 @@ import org.qortal.api.model.CrossChainBitcoinRefundRequest;
|
||||
import org.qortal.api.model.CrossChainBitcoinTemplateRequest;
|
||||
import org.qortal.api.model.CrossChainBuildRequest;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.controller.TradeBot;
|
||||
import org.qortal.crosschain.BTC;
|
||||
import org.qortal.crosschain.BTCACCT;
|
||||
import org.qortal.crypto.Crypto;
|
||||
@ -717,6 +719,39 @@ public class CrossChainResource {
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/tradebot/{ataddress}")
|
||||
@Operation(
|
||||
summary = "Respond to a trade offer",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string"))
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
|
||||
public String tradeBotResponder(@PathParam("ataddress") String atAddress) {
|
||||
if (atAddress == null || !Crypto.isValidAtAddress(atAddress))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||
|
||||
// Extract data from cross-chain trading AT
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
ATData atData = fetchAtDataWithChecking(repository, null, atAddress); // null to skip creator check
|
||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
||||
|
||||
if (crossChainTradeData.mode != Mode.OFFER)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
||||
String p2shAddress = TradeBot.startResponse(repository, crossChainTradeData);
|
||||
if (p2shAddress == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_NETWORK_ISSUE);
|
||||
|
||||
return p2shAddress;
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
private ATData fetchAtDataWithChecking(Repository repository, byte[] creatorPublicKey, String atAddress) throws DataException {
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
if (atData == null)
|
||||
|
@ -1,11 +1,33 @@
|
||||
package org.qortal.controller;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.LegacyAddress;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.api.ApiError;
|
||||
import org.qortal.api.ApiExceptionFactory;
|
||||
import org.qortal.crosschain.BTC;
|
||||
import org.qortal.crosschain.BTCACCT;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.at.ATData;
|
||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
||||
import org.qortal.data.crosschain.TradeBotData;
|
||||
import org.qortal.data.crosschain.CrossChainTradeData.Mode;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
|
||||
public class TradeBot {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(TradeBot.class);
|
||||
private static final Random RANDOM = new SecureRandom();
|
||||
|
||||
private static TradeBot instance;
|
||||
|
||||
@ -20,8 +42,71 @@ public class TradeBot {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static String startResponse(Repository repository, CrossChainTradeData crossChainTradeData) throws DataException {
|
||||
BTC btc = BTC.getInstance();
|
||||
NetworkParameters params = btc.getNetworkParameters();
|
||||
|
||||
byte[] tradePrivateKey = generateTradePrivateKey();
|
||||
byte[] secret = generateSecret();
|
||||
byte[] secretHash = Crypto.digest(secret);
|
||||
|
||||
byte[] tradeNativePublicKey = deriveTradeNativePublicKey(tradePrivateKey);
|
||||
byte[] tradeNativePublicKeyHash = Crypto.hash160(tradeNativePublicKey);
|
||||
|
||||
byte[] tradeForeignPublicKey = deriveTradeForeignPublicKey(tradePrivateKey);
|
||||
byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey);
|
||||
|
||||
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, TradeBotData.State.ALICE_WAITING_FOR_P2SH_A,
|
||||
tradeNativePublicKey, tradeNativePublicKeyHash, secret, secretHash,
|
||||
tradeForeignPublicKey, tradeForeignPublicKeyHash, crossChainTradeData.qortalAtAddress, null);
|
||||
repository.getCrossChainRepository().save(tradeBotData);
|
||||
|
||||
// P2SH_a to be funded
|
||||
byte[] redeemScriptBytes = BTCACCT.buildScript(tradeForeignPublicKeyHash, crossChainTradeData.lockTime, crossChainTradeData.foreignPublicKeyHash, secretHash);
|
||||
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
|
||||
|
||||
Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
|
||||
return p2shAddress.toString();
|
||||
}
|
||||
|
||||
private static byte[] generateTradePrivateKey() {
|
||||
byte[] seed = new byte[32];
|
||||
RANDOM.nextBytes(seed);
|
||||
return seed;
|
||||
}
|
||||
|
||||
private static byte[] deriveTradeNativePublicKey(byte[] privateKey) {
|
||||
return PrivateKeyAccount.toPublicKey(privateKey);
|
||||
}
|
||||
|
||||
private static byte[] deriveTradeForeignPublicKey(byte[] privateKey) {
|
||||
return ECKey.fromPrivate(privateKey).getPubKey();
|
||||
}
|
||||
|
||||
private static byte[] generateSecret() {
|
||||
byte[] secret = new byte[32];
|
||||
RANDOM.nextBytes(secret);
|
||||
return secret;
|
||||
}
|
||||
|
||||
public void onChainTipChange() {
|
||||
// Get repo for trade situations
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
List<TradeBotData> allTradeBotData = repository.getCrossChainRepository().getAllTradeBotData();
|
||||
|
||||
for (TradeBotData tradeBotData : allTradeBotData)
|
||||
switch (tradeBotData.getState()) {
|
||||
case ALICE_START:
|
||||
handleAliceStart(repository, tradeBotData);
|
||||
break;
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.error("Couldn't run trade bot due to repository issue", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleAliceStart(Repository repository, TradeBotData tradeBotData) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -38,24 +38,37 @@ public class TradeBotData {
|
||||
@Schema(hidden = true)
|
||||
private byte[] tradePrivateKey;
|
||||
|
||||
private byte[] tradeNativePublicKey;
|
||||
private byte[] tradeNativePublicKeyHash;
|
||||
|
||||
private byte[] secret;
|
||||
private byte[] secretHash;
|
||||
|
||||
private byte[] tradeForeignPublicKey;
|
||||
private byte[] tradeForeignPublicKeyHash;
|
||||
|
||||
private String atAddress;
|
||||
|
||||
private byte[] lastTransactionSignature;
|
||||
|
||||
public TradeBotData(byte[] tradePrivateKey, State tradeState, byte[] secret, String atAddress,
|
||||
public TradeBotData(byte[] tradePrivateKey, State tradeState,
|
||||
byte[] tradeNativePublicKey, byte[] tradeNativePublicKeyHash, byte[] secret, byte[] secretHash,
|
||||
byte[] tradeForeignPublicKey, byte[] tradeForeignPublicKeyHash, String atAddress,
|
||||
byte[] lastTransactionSignature) {
|
||||
this.tradePrivateKey = tradePrivateKey;
|
||||
this.tradeState = tradeState;
|
||||
this.tradeNativePublicKey = tradeNativePublicKey;
|
||||
this.tradeNativePublicKeyHash = tradeNativePublicKeyHash;
|
||||
this.secret = secret;
|
||||
this.secretHash = secretHash;
|
||||
this.tradeForeignPublicKey = tradeForeignPublicKey;
|
||||
this.tradeForeignPublicKeyHash = tradeForeignPublicKeyHash;
|
||||
this.atAddress = atAddress;
|
||||
this.lastTransactionSignature = lastTransactionSignature;
|
||||
}
|
||||
|
||||
public TradeBotData(byte[] tradePrivateKey, State tradeState) {
|
||||
this.tradePrivateKey = tradePrivateKey;
|
||||
this.tradeState = tradeState;
|
||||
public byte[] getTradePrivateKey() {
|
||||
return this.tradePrivateKey;
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
@ -66,16 +79,28 @@ public class TradeBotData {
|
||||
this.tradeState = state;
|
||||
}
|
||||
|
||||
public byte[] getTradeNativePublicKey() {
|
||||
return this.tradeNativePublicKey;
|
||||
}
|
||||
|
||||
public byte[] getTradeNativePublicKeyHash() {
|
||||
return this.tradeNativePublicKeyHash;
|
||||
}
|
||||
|
||||
public byte[] getSecret() {
|
||||
return this.secret;
|
||||
}
|
||||
|
||||
public void setSecret(byte[] secret) {
|
||||
this.secret = secret;
|
||||
public byte[] getSecretHash() {
|
||||
return this.secretHash;
|
||||
}
|
||||
|
||||
public byte[] getTradePrivateKey() {
|
||||
return this.tradePrivateKey;
|
||||
public byte[] getTradeForeignPublicKey() {
|
||||
return this.tradeForeignPublicKey;
|
||||
}
|
||||
|
||||
public byte[] getTradeForeignPublicKeyHash() {
|
||||
return this.tradeForeignPublicKeyHash;
|
||||
}
|
||||
|
||||
public String getAtAddress() {
|
||||
|
@ -8,4 +8,6 @@ public interface CrossChainRepository {
|
||||
|
||||
public List<TradeBotData> getAllTradeBotData() throws DataException;
|
||||
|
||||
public void save(TradeBotData tradeBotData) throws DataException;
|
||||
|
||||
}
|
||||
|
@ -19,7 +19,8 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
|
||||
|
||||
@Override
|
||||
public List<TradeBotData> getAllTradeBotData() throws DataException {
|
||||
String sql = "SELECT trade_private_key, trade_state, secret, at_address, last_transaction_signature "
|
||||
String sql = "SELECT trade_private_key, trade_state, trade_native_public_key, trade_native_public_key_hash, "
|
||||
+ "secret, secret_hash, trade_foreign_public_key, trade_foreign_public_key_hash, at_address, last_transaction_signature "
|
||||
+ "FROM TradeBotStates";
|
||||
|
||||
List<TradeBotData> allTradeBotData = new ArrayList<>();
|
||||
@ -35,11 +36,18 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
|
||||
if (tradeState == null)
|
||||
throw new DataException("Illegal trade-bot trade-state fetched from repository");
|
||||
|
||||
byte[] secret = resultSet.getBytes(3);
|
||||
String atAddress = resultSet.getString(4);
|
||||
byte[] lastTransactionSignature = resultSet.getBytes(5);
|
||||
byte[] tradeNativePublicKey = resultSet.getBytes(3);
|
||||
byte[] tradeNativePublicKeyHash = resultSet.getBytes(4);
|
||||
byte[] secret = resultSet.getBytes(5);
|
||||
byte[] secretHash = resultSet.getBytes(6);
|
||||
byte[] tradeForeignPublicKey = resultSet.getBytes(7);
|
||||
byte[] tradeForeignPublicKeyHash = resultSet.getBytes(8);
|
||||
String atAddress = resultSet.getString(9);
|
||||
byte[] lastTransactionSignature = resultSet.getBytes(10);
|
||||
|
||||
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, tradeState, secret, atAddress, lastTransactionSignature);
|
||||
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, tradeState,
|
||||
tradeNativePublicKey, tradeNativePublicKeyHash, secret, secretHash,
|
||||
tradeForeignPublicKey, tradeForeignPublicKeyHash, atAddress, lastTransactionSignature);
|
||||
allTradeBotData.add(tradeBotData);
|
||||
} while (resultSet.next());
|
||||
|
||||
@ -49,4 +57,25 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(TradeBotData tradeBotData) throws DataException {
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("TradeBotStates");
|
||||
|
||||
saveHelper.bind("trade_private_key", tradeBotData.getTradePrivateKey())
|
||||
.bind("trade_state", tradeBotData.getState().value)
|
||||
.bind("trade_native_public_key", tradeBotData.getTradeNativePublicKey())
|
||||
.bind("trade_native_public_key_hash", tradeBotData.getTradeNativePublicKeyHash())
|
||||
.bind("secret", tradeBotData.getSecret()).bind("secret_hash", tradeBotData.getSecretHash())
|
||||
.bind("trade_foreign_public_key", tradeBotData.getTradeForeignPublicKey())
|
||||
.bind("trade_foreign_public_key_hash", tradeBotData.getTradeForeignPublicKeyHash())
|
||||
.bind("at_address", tradeBotData.getAtAddress())
|
||||
.bind("last_transaction_signature", tradeBotData.getLastTransactionSignature());
|
||||
|
||||
try {
|
||||
saveHelper.execute(this.repository);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to save trade bot data into repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -621,7 +621,10 @@ public class HSQLDBDatabaseUpdates {
|
||||
case 20:
|
||||
// Trade bot
|
||||
stmt.execute("CREATE TABLE TradeBotStates (trade_private_key QortalPrivateKey NOT NULL, trade_state TINYINT NOT NULL, "
|
||||
+ "secret VARBINARY(32) NOT NULL, at_address QortalAddress, "
|
||||
+ "trade_native_public_key QortalPublicKey NOT NULL, trade_native_public_key_hash VARBINARY(32) NOT NULL, "
|
||||
+ "secret VARBINARY(32) NOT NULL, secret_hash VARBINARY(32) NOT NULL, "
|
||||
+ "trade_foreign_public_key QortalPublicKey NOT NULL, trade_foreign_public_key_hash VARBINARY(32) NOT NULL, "
|
||||
+ "at_address QortalAddress, "
|
||||
+ "last_transaction_signature Signature, PRIMARY KEY (trade_private_key)");
|
||||
break;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user