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:
catbref 2020-06-23 16:53:08 +01:00
parent 11bf5ac6fc
commit ee5119e4dd
5 changed files with 25 additions and 9 deletions

View File

@ -12,7 +12,7 @@ public class TradeBotCreateRequest {
@Schema(description = "Trade creator's public key", example = "2zR1WFsbM7akHghqSCYKBPk6LDP8aKiQSRS1FrwoLvoB")
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)
public long qortAmount;
@ -20,7 +20,7 @@ public class TradeBotCreateRequest {
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
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)
public long bitcoinAmount;

View File

@ -32,6 +32,7 @@ import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.TransactionOutput;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.api.ApiError;
import org.qortal.api.ApiErrors;
@ -866,6 +867,11 @@ public class CrossChainResource {
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
public String tradeBotCreator(TradeBotCreateRequest tradeBotCreateRequest) {
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);
return Base58.encode(unsignedBytes);

View File

@ -238,11 +238,9 @@ public class Controller extends Thread {
return this.chainTip;
}
/** Cache new blockchain tip, maybe call trade-bot. */
/** Cache new blockchain tip. */
public void setChainTip(BlockData blockData) {
this.chainTip = blockData;
TradeBot.getInstance().onChainTipChange();
}
public ReentrantLock getBlockchainLock() {
@ -793,6 +791,9 @@ public class Controller extends Thread {
this.notifyGroupMembershipChange = false;
ChatNotifier.getInstance().onGroupMembershipChange();
}
// Trade-bot might want to perform some actions too
TradeBot.getInstance().onChainTipChange();
});
}

View File

@ -57,7 +57,7 @@ public class TradeBot {
public static byte[] createTrade(Repository repository, TradeBotCreateRequest tradeBotCreateRequest) throws DataException {
byte[] tradePrivateKey = generateTradePrivateKey();
byte[] secretB = generateSecret();
byte[] hashOfSecretB = Crypto.digest(secretB);
byte[] hashOfSecretB = Crypto.hash160(secretB);
byte[] tradeNativePublicKey = deriveTradeNativePublicKey(tradePrivateKey);
byte[] tradeNativePublicKeyHash = Crypto.hash160(tradeNativePublicKey);
@ -83,15 +83,21 @@ public class TradeBot {
long amount = tradeBotCreateRequest.fundingQortAmount;
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);
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,
tradeNativePublicKey, tradeNativePublicKeyHash, secretB, hashOfSecretB,
tradeForeignPublicKey, tradeForeignPublicKeyHash,
tradeBotCreateRequest.bitcoinAmount, null);
repository.getCrossChainRepository().save(tradeBotData);
repository.saveChanges();
// Return to user for signing and broadcast as we don't have their Qortal private key
try {
@ -121,6 +127,7 @@ public class TradeBot {
tradeForeignPublicKey, tradeForeignPublicKeyHash,
crossChainTradeData.expectedBitcoin, null);
repository.getCrossChainRepository().save(tradeBotData);
repository.saveChanges();
// P2SH_a to be funded
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);
repository.getCrossChainRepository().save(tradeBotData);
repository.saveChanges();
}
private void handleBobWaitingForMessage(Repository repository, TradeBotData tradeBotData) throws DataException {
@ -252,6 +260,7 @@ public class TradeBot {
}
repository.getCrossChainRepository().save(tradeBotData);
repository.saveChanges();
}
}

View File

@ -620,11 +620,11 @@ public class HSQLDBDatabaseUpdates {
case 20:
// 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, "
+ "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, "
+ "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))");
break;