forked from Qortal/qortal
WIP: trade-bot. move trade-bot hook, fix bugs, etc.
Controller now calls TradeBot.onChainTipChange() inside thread started by Controller.onNewBlock(), instead of blocking Controller.setChainTip(). DB TradeBotStates has trade_foreign_public_key changed to VARBINARY(33) as Bitcoin pubkeys aren't uniformly 32 bytes! Also, trade_state changed from TINYINT to SMALLINT to cover enum value range. TradeBot.createTrade() incorrectly used Crypto.digest() to create hash-of-secret instead of Crypto.hash160(). Also corrected tradeState to BOB_WAITING_FOR_AT_CONFIRM. Also added missing fee calculation. Added missing repository.saveChanges() to TradeBot methods. Added balance check to API POST /crosschain/tradebot before passing request to TradeBot.createTrade(), which also ensures there's a usable account last-reference too.
This commit is contained in:
parent
11bf5ac6fc
commit
ee5119e4dd
@ -12,7 +12,7 @@ public class TradeBotCreateRequest {
|
|||||||
@Schema(description = "Trade creator's public key", example = "2zR1WFsbM7akHghqSCYKBPk6LDP8aKiQSRS1FrwoLvoB")
|
@Schema(description = "Trade creator's public key", example = "2zR1WFsbM7akHghqSCYKBPk6LDP8aKiQSRS1FrwoLvoB")
|
||||||
public byte[] creatorPublicKey;
|
public byte[] creatorPublicKey;
|
||||||
|
|
||||||
@Schema(description = "QORT amount paid out on successful trade", example = "8040200000")
|
@Schema(description = "QORT amount paid out on successful trade", example = "80.40200000")
|
||||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||||
public long qortAmount;
|
public long qortAmount;
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ public class TradeBotCreateRequest {
|
|||||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||||
public long fundingQortAmount;
|
public long fundingQortAmount;
|
||||||
|
|
||||||
@Schema(description = "Bitcoin amount wanted in return", example = "000864200")
|
@Schema(description = "Bitcoin amount wanted in return", example = "0.00864200")
|
||||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||||
public long bitcoinAmount;
|
public long bitcoinAmount;
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ import org.bitcoinj.core.ECKey;
|
|||||||
import org.bitcoinj.core.LegacyAddress;
|
import org.bitcoinj.core.LegacyAddress;
|
||||||
import org.bitcoinj.core.NetworkParameters;
|
import org.bitcoinj.core.NetworkParameters;
|
||||||
import org.bitcoinj.core.TransactionOutput;
|
import org.bitcoinj.core.TransactionOutput;
|
||||||
|
import org.qortal.account.Account;
|
||||||
import org.qortal.account.PublicKeyAccount;
|
import org.qortal.account.PublicKeyAccount;
|
||||||
import org.qortal.api.ApiError;
|
import org.qortal.api.ApiError;
|
||||||
import org.qortal.api.ApiErrors;
|
import org.qortal.api.ApiErrors;
|
||||||
@ -866,6 +867,11 @@ public class CrossChainResource {
|
|||||||
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
|
||||||
public String tradeBotCreator(TradeBotCreateRequest tradeBotCreateRequest) {
|
public String tradeBotCreator(TradeBotCreateRequest tradeBotCreateRequest) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
// Do some simple checking first
|
||||||
|
Account creator = new PublicKeyAccount(repository, tradeBotCreateRequest.creatorPublicKey);
|
||||||
|
if (creator.getConfirmedBalance(Asset.QORT) < tradeBotCreateRequest.fundingQortAmount)
|
||||||
|
throw TransactionsResource.createTransactionInvalidException(request, ValidationResult.NO_BALANCE);
|
||||||
|
|
||||||
byte[] unsignedBytes = TradeBot.createTrade(repository, tradeBotCreateRequest);
|
byte[] unsignedBytes = TradeBot.createTrade(repository, tradeBotCreateRequest);
|
||||||
|
|
||||||
return Base58.encode(unsignedBytes);
|
return Base58.encode(unsignedBytes);
|
||||||
|
@ -238,11 +238,9 @@ public class Controller extends Thread {
|
|||||||
return this.chainTip;
|
return this.chainTip;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Cache new blockchain tip, maybe call trade-bot. */
|
/** Cache new blockchain tip. */
|
||||||
public void setChainTip(BlockData blockData) {
|
public void setChainTip(BlockData blockData) {
|
||||||
this.chainTip = blockData;
|
this.chainTip = blockData;
|
||||||
|
|
||||||
TradeBot.getInstance().onChainTipChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReentrantLock getBlockchainLock() {
|
public ReentrantLock getBlockchainLock() {
|
||||||
@ -793,6 +791,9 @@ public class Controller extends Thread {
|
|||||||
this.notifyGroupMembershipChange = false;
|
this.notifyGroupMembershipChange = false;
|
||||||
ChatNotifier.getInstance().onGroupMembershipChange();
|
ChatNotifier.getInstance().onGroupMembershipChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trade-bot might want to perform some actions too
|
||||||
|
TradeBot.getInstance().onChainTipChange();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ public class TradeBot {
|
|||||||
public static byte[] createTrade(Repository repository, TradeBotCreateRequest tradeBotCreateRequest) throws DataException {
|
public static byte[] createTrade(Repository repository, TradeBotCreateRequest tradeBotCreateRequest) throws DataException {
|
||||||
byte[] tradePrivateKey = generateTradePrivateKey();
|
byte[] tradePrivateKey = generateTradePrivateKey();
|
||||||
byte[] secretB = generateSecret();
|
byte[] secretB = generateSecret();
|
||||||
byte[] hashOfSecretB = Crypto.digest(secretB);
|
byte[] hashOfSecretB = Crypto.hash160(secretB);
|
||||||
|
|
||||||
byte[] tradeNativePublicKey = deriveTradeNativePublicKey(tradePrivateKey);
|
byte[] tradeNativePublicKey = deriveTradeNativePublicKey(tradePrivateKey);
|
||||||
byte[] tradeNativePublicKeyHash = Crypto.hash160(tradeNativePublicKey);
|
byte[] tradeNativePublicKeyHash = Crypto.hash160(tradeNativePublicKey);
|
||||||
@ -83,15 +83,21 @@ public class TradeBot {
|
|||||||
long amount = tradeBotCreateRequest.fundingQortAmount;
|
long amount = tradeBotCreateRequest.fundingQortAmount;
|
||||||
|
|
||||||
DeployAtTransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, aTType, tags, creationBytes, amount, Asset.QORT);
|
DeployAtTransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, aTType, tags, creationBytes, amount, Asset.QORT);
|
||||||
|
|
||||||
|
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
||||||
|
fee = deployAtTransaction.calcRecommendedFee();
|
||||||
|
deployAtTransactionData.setFee(fee);
|
||||||
|
|
||||||
DeployAtTransaction.ensureATAddress(deployAtTransactionData);
|
DeployAtTransaction.ensureATAddress(deployAtTransactionData);
|
||||||
String atAddress = deployAtTransactionData.getAtAddress();
|
String atAddress = deployAtTransactionData.getAtAddress();
|
||||||
|
|
||||||
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, TradeBotData.State.BOB_WAITING_FOR_MESSAGE,
|
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, TradeBotData.State.BOB_WAITING_FOR_AT_CONFIRM,
|
||||||
atAddress,
|
atAddress,
|
||||||
tradeNativePublicKey, tradeNativePublicKeyHash, secretB, hashOfSecretB,
|
tradeNativePublicKey, tradeNativePublicKeyHash, secretB, hashOfSecretB,
|
||||||
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
||||||
tradeBotCreateRequest.bitcoinAmount, null);
|
tradeBotCreateRequest.bitcoinAmount, null);
|
||||||
repository.getCrossChainRepository().save(tradeBotData);
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
|
repository.saveChanges();
|
||||||
|
|
||||||
// Return to user for signing and broadcast as we don't have their Qortal private key
|
// Return to user for signing and broadcast as we don't have their Qortal private key
|
||||||
try {
|
try {
|
||||||
@ -121,6 +127,7 @@ public class TradeBot {
|
|||||||
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
||||||
crossChainTradeData.expectedBitcoin, null);
|
crossChainTradeData.expectedBitcoin, null);
|
||||||
repository.getCrossChainRepository().save(tradeBotData);
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
|
repository.saveChanges();
|
||||||
|
|
||||||
// P2SH_a to be funded
|
// P2SH_a to be funded
|
||||||
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeForeignPublicKeyHash, crossChainTradeData.lockTimeA, crossChainTradeData.creatorBitcoinPKH, secretHash);
|
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeForeignPublicKeyHash, crossChainTradeData.lockTimeA, crossChainTradeData.creatorBitcoinPKH, secretHash);
|
||||||
@ -179,6 +186,7 @@ public class TradeBot {
|
|||||||
|
|
||||||
tradeBotData.setState(TradeBotData.State.BOB_WAITING_FOR_MESSAGE);
|
tradeBotData.setState(TradeBotData.State.BOB_WAITING_FOR_MESSAGE);
|
||||||
repository.getCrossChainRepository().save(tradeBotData);
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
|
repository.saveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleBobWaitingForMessage(Repository repository, TradeBotData tradeBotData) throws DataException {
|
private void handleBobWaitingForMessage(Repository repository, TradeBotData tradeBotData) throws DataException {
|
||||||
@ -252,6 +260,7 @@ public class TradeBot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
repository.getCrossChainRepository().save(tradeBotData);
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
|
repository.saveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -620,11 +620,11 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
|
|
||||||
case 20:
|
case 20:
|
||||||
// Trade bot
|
// Trade bot
|
||||||
stmt.execute("CREATE TABLE TradeBotStates (trade_private_key QortalKeySeed NOT NULL, trade_state TINYINT NOT NULL, "
|
stmt.execute("CREATE TABLE TradeBotStates (trade_private_key QortalKeySeed NOT NULL, trade_state SMALLINT NOT NULL, "
|
||||||
+ "at_address QortalAddress, "
|
+ "at_address QortalAddress, "
|
||||||
+ "trade_native_public_key QortalPublicKey NOT NULL, trade_native_public_key_hash VARBINARY(32) NOT NULL, "
|
+ "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, "
|
+ "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, "
|
+ "trade_foreign_public_key VARBINARY(33) NOT NULL, trade_foreign_public_key_hash VARBINARY(32) NOT NULL, "
|
||||||
+ "bitcoin_amount BIGINT NOT NULL, last_transaction_signature Signature, PRIMARY KEY (trade_private_key))");
|
+ "bitcoin_amount BIGINT NOT NULL, last_transaction_signature Signature, PRIMARY KEY (trade_private_key))");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user