WIP commit

This commit is contained in:
catbref 2020-06-08 12:44:03 +01:00
parent ead84d70d1
commit cc13d1d0f1
6 changed files with 194 additions and 15 deletions

View File

@ -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)

View File

@ -1,12 +1,34 @@
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;
private TradeBot() {
@ -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) {
}
}

View File

@ -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() {

View File

@ -8,4 +8,6 @@ public interface CrossChainRepository {
public List<TradeBotData> getAllTradeBotData() throws DataException;
public void save(TradeBotData tradeBotData) throws DataException;
}

View File

@ -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);
}
}
}

View File

@ -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;