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.GET;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
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.CrossChainBitcoinTemplateRequest;
|
||||||
import org.qortal.api.model.CrossChainBuildRequest;
|
import org.qortal.api.model.CrossChainBuildRequest;
|
||||||
import org.qortal.asset.Asset;
|
import org.qortal.asset.Asset;
|
||||||
|
import org.qortal.controller.TradeBot;
|
||||||
import org.qortal.crosschain.BTC;
|
import org.qortal.crosschain.BTC;
|
||||||
import org.qortal.crosschain.BTCACCT;
|
import org.qortal.crosschain.BTCACCT;
|
||||||
import org.qortal.crypto.Crypto;
|
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 {
|
private ATData fetchAtDataWithChecking(Repository repository, byte[] creatorPublicKey, String atAddress) throws DataException {
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||||
if (atData == null)
|
if (atData == null)
|
||||||
|
@ -1,12 +1,34 @@
|
|||||||
package org.qortal.controller;
|
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.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
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 {
|
public class TradeBot {
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(TradeBot.class);
|
private static final Logger LOGGER = LogManager.getLogger(TradeBot.class);
|
||||||
|
private static final Random RANDOM = new SecureRandom();
|
||||||
|
|
||||||
private static TradeBot instance;
|
private static TradeBot instance;
|
||||||
|
|
||||||
private TradeBot() {
|
private TradeBot() {
|
||||||
@ -20,8 +42,71 @@ public class TradeBot {
|
|||||||
return instance;
|
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() {
|
public void onChainTipChange() {
|
||||||
// Get repo for trade situations
|
// 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)
|
@Schema(hidden = true)
|
||||||
private byte[] tradePrivateKey;
|
private byte[] tradePrivateKey;
|
||||||
|
|
||||||
|
private byte[] tradeNativePublicKey;
|
||||||
|
private byte[] tradeNativePublicKeyHash;
|
||||||
|
|
||||||
private byte[] secret;
|
private byte[] secret;
|
||||||
|
private byte[] secretHash;
|
||||||
|
|
||||||
|
private byte[] tradeForeignPublicKey;
|
||||||
|
private byte[] tradeForeignPublicKeyHash;
|
||||||
|
|
||||||
private String atAddress;
|
private String atAddress;
|
||||||
|
|
||||||
private byte[] lastTransactionSignature;
|
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) {
|
byte[] lastTransactionSignature) {
|
||||||
this.tradePrivateKey = tradePrivateKey;
|
this.tradePrivateKey = tradePrivateKey;
|
||||||
this.tradeState = tradeState;
|
this.tradeState = tradeState;
|
||||||
|
this.tradeNativePublicKey = tradeNativePublicKey;
|
||||||
|
this.tradeNativePublicKeyHash = tradeNativePublicKeyHash;
|
||||||
this.secret = secret;
|
this.secret = secret;
|
||||||
|
this.secretHash = secretHash;
|
||||||
|
this.tradeForeignPublicKey = tradeForeignPublicKey;
|
||||||
|
this.tradeForeignPublicKeyHash = tradeForeignPublicKeyHash;
|
||||||
this.atAddress = atAddress;
|
this.atAddress = atAddress;
|
||||||
this.lastTransactionSignature = lastTransactionSignature;
|
this.lastTransactionSignature = lastTransactionSignature;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TradeBotData(byte[] tradePrivateKey, State tradeState) {
|
public byte[] getTradePrivateKey() {
|
||||||
this.tradePrivateKey = tradePrivateKey;
|
return this.tradePrivateKey;
|
||||||
this.tradeState = tradeState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public State getState() {
|
public State getState() {
|
||||||
@ -66,16 +79,28 @@ public class TradeBotData {
|
|||||||
this.tradeState = state;
|
this.tradeState = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] getTradeNativePublicKey() {
|
||||||
|
return this.tradeNativePublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getTradeNativePublicKeyHash() {
|
||||||
|
return this.tradeNativePublicKeyHash;
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getSecret() {
|
public byte[] getSecret() {
|
||||||
return this.secret;
|
return this.secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSecret(byte[] secret) {
|
public byte[] getSecretHash() {
|
||||||
this.secret = secret;
|
return this.secretHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getTradePrivateKey() {
|
public byte[] getTradeForeignPublicKey() {
|
||||||
return this.tradePrivateKey;
|
return this.tradeForeignPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getTradeForeignPublicKeyHash() {
|
||||||
|
return this.tradeForeignPublicKeyHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAtAddress() {
|
public String getAtAddress() {
|
||||||
|
@ -8,4 +8,6 @@ public interface CrossChainRepository {
|
|||||||
|
|
||||||
public List<TradeBotData> getAllTradeBotData() throws DataException;
|
public List<TradeBotData> getAllTradeBotData() throws DataException;
|
||||||
|
|
||||||
|
public void save(TradeBotData tradeBotData) throws DataException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,8 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TradeBotData> getAllTradeBotData() throws DataException {
|
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";
|
+ "FROM TradeBotStates";
|
||||||
|
|
||||||
List<TradeBotData> allTradeBotData = new ArrayList<>();
|
List<TradeBotData> allTradeBotData = new ArrayList<>();
|
||||||
@ -35,11 +36,18 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
|
|||||||
if (tradeState == null)
|
if (tradeState == null)
|
||||||
throw new DataException("Illegal trade-bot trade-state fetched from repository");
|
throw new DataException("Illegal trade-bot trade-state fetched from repository");
|
||||||
|
|
||||||
byte[] secret = resultSet.getBytes(3);
|
byte[] tradeNativePublicKey = resultSet.getBytes(3);
|
||||||
String atAddress = resultSet.getString(4);
|
byte[] tradeNativePublicKeyHash = resultSet.getBytes(4);
|
||||||
byte[] lastTransactionSignature = resultSet.getBytes(5);
|
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);
|
allTradeBotData.add(tradeBotData);
|
||||||
} while (resultSet.next());
|
} 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:
|
case 20:
|
||||||
// Trade bot
|
// Trade bot
|
||||||
stmt.execute("CREATE TABLE TradeBotStates (trade_private_key QortalPrivateKey NOT NULL, trade_state TINYINT NOT NULL, "
|
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)");
|
+ "last_transaction_signature Signature, PRIMARY KEY (trade_private_key)");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user