diff --git a/src/main/java/org/qortal/api/resource/CrossChainRavencoinACCTv1Resource.java b/src/main/java/org/qortal/api/resource/CrossChainRavencoinACCTv1Resource.java
deleted file mode 100644
index 57197d7f..00000000
--- a/src/main/java/org/qortal/api/resource/CrossChainRavencoinACCTv1Resource.java
+++ /dev/null
@@ -1,148 +0,0 @@
-package org.qortal.api.resource;
-
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.media.Content;
-import io.swagger.v3.oas.annotations.media.Schema;
-import io.swagger.v3.oas.annotations.parameters.RequestBody;
-import io.swagger.v3.oas.annotations.responses.ApiResponse;
-import io.swagger.v3.oas.annotations.security.SecurityRequirement;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.qortal.account.PrivateKeyAccount;
-import org.qortal.api.ApiError;
-import org.qortal.api.ApiErrors;
-import org.qortal.api.ApiExceptionFactory;
-import org.qortal.api.Security;
-import org.qortal.api.model.CrossChainSecretRequest;
-import org.qortal.crosschain.AcctMode;
-import org.qortal.crosschain.RavencoinACCTv1;
-import org.qortal.crypto.Crypto;
-import org.qortal.data.at.ATData;
-import org.qortal.data.crosschain.CrossChainTradeData;
-import org.qortal.group.Group;
-import org.qortal.repository.DataException;
-import org.qortal.repository.Repository;
-import org.qortal.repository.RepositoryManager;
-import org.qortal.transaction.MessageTransaction;
-import org.qortal.transaction.Transaction.ValidationResult;
-import org.qortal.transform.TransformationException;
-import org.qortal.transform.Transformer;
-import org.qortal.transform.transaction.MessageTransactionTransformer;
-import org.qortal.utils.Base58;
-import org.qortal.utils.NTP;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.HeaderParam;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MediaType;
-import java.util.Arrays;
-import java.util.Random;
-
-@Path("/crosschain/RavencoinACCTv1")
-@Tag(name = "Cross-Chain (RavencoinACCTv1)")
-public class CrossChainRavencoinACCTv1Resource {
-
- @Context
- HttpServletRequest request;
-
- @POST
- @Path("/redeemmessage")
- @Operation(
- summary = "Signs and broadcasts a 'redeem' MESSAGE transaction that sends secrets to AT, releasing funds to partner",
- description = "Specify address of cross-chain AT that needs to be messaged, Alice's trade private key, the 32-byte secret,
"
- + "and an address for receiving QORT from AT. All of these can be found in Alice's trade bot data.
"
- + "AT needs to be in 'trade' mode. Messages sent to an AT in any other mode will be ignored, but still cost fees to send!
"
- + "You need to use the private key that the AT considers the trade 'partner' otherwise the MESSAGE transaction will be invalid.",
- requestBody = @RequestBody(
- required = true,
- content = @Content(
- mediaType = MediaType.APPLICATION_JSON,
- schema = @Schema(
- implementation = CrossChainSecretRequest.class
- )
- )
- ),
- responses = {
- @ApiResponse(
- content = @Content(
- schema = @Schema(
- type = "string"
- )
- )
- )
- }
- )
- @ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.INVALID_DATA, ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE})
- @SecurityRequirement(name = "apiKey")
- public boolean buildRedeemMessage(@HeaderParam(Security.API_KEY_HEADER) String apiKey, CrossChainSecretRequest secretRequest) {
- Security.checkApiCallAllowed(request);
-
- byte[] partnerPrivateKey = secretRequest.partnerPrivateKey;
-
- if (partnerPrivateKey == null || partnerPrivateKey.length != Transformer.PRIVATE_KEY_LENGTH)
- throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
-
- if (secretRequest.atAddress == null || !Crypto.isValidAtAddress(secretRequest.atAddress))
- throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
-
- if (secretRequest.secret == null || secretRequest.secret.length != RavencoinACCTv1.SECRET_LENGTH)
- throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
-
- if (secretRequest.receivingAddress == null || !Crypto.isValidAddress(secretRequest.receivingAddress))
- throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
-
- try (final Repository repository = RepositoryManager.getRepository()) {
- ATData atData = fetchAtDataWithChecking(repository, secretRequest.atAddress);
- CrossChainTradeData crossChainTradeData = RavencoinACCTv1.getInstance().populateTradeData(repository, atData);
-
- if (crossChainTradeData.mode != AcctMode.TRADING)
- throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
-
- byte[] partnerPublicKey = new PrivateKeyAccount(null, partnerPrivateKey).getPublicKey();
- String partnerAddress = Crypto.toAddress(partnerPublicKey);
-
- // MESSAGE must come from address that AT considers trade partner
- if (!crossChainTradeData.qortalPartnerAddress.equals(partnerAddress))
- throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
-
- // Good to make MESSAGE
-
- byte[] messageData = RavencoinACCTv1.buildRedeemMessage(secretRequest.secret, secretRequest.receivingAddress);
-
- PrivateKeyAccount sender = new PrivateKeyAccount(repository, partnerPrivateKey);
- MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, secretRequest.atAddress, messageData, false, false);
-
- messageTransaction.computeNonce();
- messageTransaction.sign(sender);
-
- // reset repository state to prevent deadlock
- repository.discardChanges();
- ValidationResult result = messageTransaction.importAsUnconfirmed();
-
- if (result != ValidationResult.OK)
- throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSACTION_INVALID);
-
- return true;
- } catch (DataException e) {
- throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
- }
- }
-
- private ATData fetchAtDataWithChecking(Repository repository, String atAddress) throws DataException {
- ATData atData = repository.getATRepository().fromATAddress(atAddress);
- if (atData == null)
- throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN);
-
- // Must be correct AT - check functionality using code hash
- if (!Arrays.equals(atData.getCodeHash(), RavencoinACCTv1.CODE_BYTES_HASH))
- throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
-
- // No point sending message to AT that's finished
- if (atData.getIsFinished())
- throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
-
- return atData;
- }
-
-}
diff --git a/src/main/java/org/qortal/controller/tradebot/RavencoinACCTv1TradeBot.java b/src/main/java/org/qortal/controller/tradebot/RavencoinACCTv1TradeBot.java
deleted file mode 100644
index c7cab8ed..00000000
--- a/src/main/java/org/qortal/controller/tradebot/RavencoinACCTv1TradeBot.java
+++ /dev/null
@@ -1,896 +0,0 @@
-package org.qortal.controller.tradebot;
-
-import static java.util.Arrays.stream;
-import static java.util.stream.Collectors.toMap;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.bitcoinj.core.Address;
-import org.bitcoinj.core.AddressFormatException;
-import org.bitcoinj.core.Coin;
-import org.bitcoinj.core.ECKey;
-import org.bitcoinj.core.Transaction;
-import org.bitcoinj.core.TransactionOutput;
-import org.bitcoinj.script.Script.ScriptType;
-import org.qortal.account.PrivateKeyAccount;
-import org.qortal.account.PublicKeyAccount;
-import org.qortal.api.model.crosschain.TradeBotCreateRequest;
-import org.qortal.asset.Asset;
-import org.qortal.crosschain.ACCT;
-import org.qortal.crosschain.AcctMode;
-import org.qortal.crosschain.ForeignBlockchainException;
-import org.qortal.crosschain.Ravencoin;
-import org.qortal.crosschain.RavencoinACCTv1;
-import org.qortal.crosschain.SupportedBlockchain;
-import org.qortal.crosschain.BitcoinyHTLC;
-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.transaction.BaseTransactionData;
-import org.qortal.data.transaction.DeployAtTransactionData;
-import org.qortal.data.transaction.MessageTransactionData;
-import org.qortal.group.Group;
-import org.qortal.repository.DataException;
-import org.qortal.repository.Repository;
-import org.qortal.transaction.DeployAtTransaction;
-import org.qortal.transaction.MessageTransaction;
-import org.qortal.transaction.Transaction.ValidationResult;
-import org.qortal.transform.TransformationException;
-import org.qortal.transform.transaction.DeployAtTransactionTransformer;
-import org.qortal.utils.Base58;
-import org.qortal.utils.NTP;
-
-/**
- * Performing cross-chain trading steps on behalf of user.
- *
- * We deal with three different independent state-spaces here: - *
- * Generates: - *
- * Trade-bot will wait for Bob's AT to be deployed before taking next step. - *
- * @param repository - * @param tradeBotCreateRequest - * @return raw, unsigned DEPLOY_AT transaction - * @throws DataException - */ - public byte[] createTrade(Repository repository, TradeBotCreateRequest tradeBotCreateRequest) throws DataException { - byte[] tradePrivateKey = TradeBot.generateTradePrivateKey(); - - byte[] tradeNativePublicKey = TradeBot.deriveTradeNativePublicKey(tradePrivateKey); - byte[] tradeNativePublicKeyHash = Crypto.hash160(tradeNativePublicKey); - String tradeNativeAddress = Crypto.toAddress(tradeNativePublicKey); - - byte[] tradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(tradePrivateKey); - byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey); - - // Convert Ravencoin receiving address into public key hash (we only support P2PKH at this time) - Address ravencoinReceivingAddress; - try { - ravencoinReceivingAddress = Address.fromString(Ravencoin.getInstance().getNetworkParameters(), tradeBotCreateRequest.receivingAddress); - } catch (AddressFormatException e) { - throw new DataException("Unsupported Ravencoin receiving address: " + tradeBotCreateRequest.receivingAddress); - } - if (ravencoinReceivingAddress.getOutputScriptType() != ScriptType.P2PKH) - throw new DataException("Unsupported Ravencoin receiving address: " + tradeBotCreateRequest.receivingAddress); - - byte[] ravencoinReceivingAccountInfo = ravencoinReceivingAddress.getHash(); - - PublicKeyAccount creator = new PublicKeyAccount(repository, tradeBotCreateRequest.creatorPublicKey); - - // Deploy AT - long timestamp = NTP.getTime(); - byte[] reference = creator.getLastReference(); - long fee = 0L; - byte[] signature = null; - BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, creator.getPublicKey(), fee, signature); - - String name = "QORT/RVN ACCT"; - String description = "QORT/RVN cross-chain trade"; - String aTType = "ACCT"; - String tags = "ACCT QORT RVN"; - byte[] creationBytes = RavencoinACCTv1.buildQortalAT(tradeNativeAddress, tradeForeignPublicKeyHash, tradeBotCreateRequest.qortAmount, - tradeBotCreateRequest.foreignAmount, tradeBotCreateRequest.tradeTimeout); - 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, RavencoinACCTv1.NAME, - State.BOB_WAITING_FOR_AT_CONFIRM.name(), State.BOB_WAITING_FOR_AT_CONFIRM.value, - creator.getAddress(), atAddress, timestamp, tradeBotCreateRequest.qortAmount, - tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress, - null, null, - SupportedBlockchain.RAVENCOIN.name(), - tradeForeignPublicKey, tradeForeignPublicKeyHash, - tradeBotCreateRequest.foreignAmount, null, null, null, ravencoinReceivingAccountInfo); - - TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Built AT %s. Waiting for deployment", atAddress)); - - // Attempt to backup the trade bot data - TradeBot.backupTradeBotData(repository, null); - - // Return to user for signing and broadcast as we don't have their Qortal private key - try { - return DeployAtTransactionTransformer.toBytes(deployAtTransactionData); - } catch (TransformationException e) { - throw new DataException("Failed to transform DEPLOY_AT transaction?", e); - } - } - - /** - * Creates a trade-bot entry from the 'Alice' viewpoint, i.e. matching RVN to an existing offer. - *
- * Requires a chosen trade offer from Bob, passed by crossChainTradeData - * and access to a Ravencoin wallet via xprv58. - *
- * The crossChainTradeData contains the current trade offer state - * as extracted from the AT's data segment. - *
- * Access to a funded wallet is via a Ravencoin BIP32 hierarchical deterministic key, - * passed via xprv58. - * This key will be stored in your node's database - * to allow trade-bot to create/fund the necessary P2SH transactions! - * However, due to the nature of BIP32 keys, it is possible to give the trade-bot - * only a subset of wallet access (see BIP32 for more details). - *
- * As an example, the xprv58 can be extract from a legacy, password-less
- * Electrum wallet by going to the console tab and entering:
- * wallet.keystore.xprv
- * which should result in a base58 string starting with either 'xprv' (for Ravencoin main-net)
- * or 'tprv' for (Ravencoin test-net).
- *
- * It is envisaged that the value in xprv58 will actually come from a Qortal-UI-managed wallet. - *
- * If sufficient funds are available, this method will actually fund the P2SH-A - * with the Ravencoin amount expected by 'Bob'. - *
- * If the Ravencoin transaction is successfully broadcast to the network then - * we also send a MESSAGE to Bob's trade-bot to let them know. - *
- * The trade-bot entry is saved to the repository and the cross-chain trading process commences. - *
- * @param repository - * @param crossChainTradeData chosen trade OFFER that Alice wants to match - * @param xprv58 funded wallet xprv in base58 - * @return true if P2SH-A funding transaction successfully broadcast to Ravencoin network, false otherwise - * @throws DataException - */ - public ResponseResult startResponse(Repository repository, ATData atData, ACCT acct, CrossChainTradeData crossChainTradeData, String xprv58, String receivingAddress) throws DataException { - byte[] tradePrivateKey = TradeBot.generateTradePrivateKey(); - byte[] secretA = TradeBot.generateSecret(); - byte[] hashOfSecretA = Crypto.hash160(secretA); - - byte[] tradeNativePublicKey = TradeBot.deriveTradeNativePublicKey(tradePrivateKey); - byte[] tradeNativePublicKeyHash = Crypto.hash160(tradeNativePublicKey); - String tradeNativeAddress = Crypto.toAddress(tradeNativePublicKey); - - byte[] tradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(tradePrivateKey); - byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey); - byte[] receivingPublicKeyHash = Base58.decode(receivingAddress); // Actually the whole address, not just PKH - - // We need to generate lockTime-A: add tradeTimeout to now - long now = NTP.getTime(); - int lockTimeA = crossChainTradeData.tradeTimeout * 60 + (int) (now / 1000L); - - TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, RavencoinACCTv1.NAME, - State.ALICE_WAITING_FOR_AT_LOCK.name(), State.ALICE_WAITING_FOR_AT_LOCK.value, - receivingAddress, crossChainTradeData.qortalAtAddress, now, crossChainTradeData.qortAmount, - tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress, - secretA, hashOfSecretA, - SupportedBlockchain.RAVENCOIN.name(), - tradeForeignPublicKey, tradeForeignPublicKeyHash, - crossChainTradeData.expectedForeignAmount, xprv58, null, lockTimeA, receivingPublicKeyHash); - - // Attempt to backup the trade bot data - // Include tradeBotData as an additional parameter, since it's not in the repository yet - TradeBot.backupTradeBotData(repository, Arrays.asList(tradeBotData)); - - // Check we have enough funds via xprv58 to fund P2SH to cover expectedForeignAmount - long p2shFee; - try { - p2shFee = Ravencoin.getInstance().getP2shFee(now); - } catch (ForeignBlockchainException e) { - LOGGER.debug("Couldn't estimate Ravencoin fees?"); - return ResponseResult.NETWORK_ISSUE; - } - - // Fee for redeem/refund is subtracted from P2SH-A balance. - // Do not include fee for funding transaction as this is covered by buildSpend() - long amountA = crossChainTradeData.expectedForeignAmount + p2shFee /*redeeming/refunding P2SH-A*/; - - // P2SH-A to be funded - byte[] redeemScriptBytes = BitcoinyHTLC.buildScript(tradeForeignPublicKeyHash, lockTimeA, crossChainTradeData.creatorForeignPKH, hashOfSecretA); - String p2shAddress = Ravencoin.getInstance().deriveP2shAddress(redeemScriptBytes); - - // Build transaction for funding P2SH-A - Transaction p2shFundingTransaction = Ravencoin.getInstance().buildSpend(tradeBotData.getForeignKey(), p2shAddress, amountA); - if (p2shFundingTransaction == null) { - LOGGER.debug("Unable to build P2SH-A funding transaction - lack of funds?"); - return ResponseResult.BALANCE_ISSUE; - } - - try { - Ravencoin.getInstance().broadcastTransaction(p2shFundingTransaction); - } catch (ForeignBlockchainException e) { - // We couldn't fund P2SH-A at this time - LOGGER.debug("Couldn't broadcast P2SH-A funding transaction?"); - return ResponseResult.NETWORK_ISSUE; - } - - // Attempt to send MESSAGE to Bob's Qortal trade address - byte[] messageData = RavencoinACCTv1.buildOfferMessage(tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getHashOfSecret(), tradeBotData.getLockTimeA()); - String messageRecipient = crossChainTradeData.qortalCreatorTradeAddress; - - boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData); - if (!isMessageAlreadySent) { - PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); - MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); - - messageTransaction.computeNonce(); - messageTransaction.sign(sender); - - // reset repository state to prevent deadlock - repository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); - - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); - return ResponseResult.NETWORK_ISSUE; - } - } - - TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress)); - - return ResponseResult.OK; - } - - @Override - public boolean canDelete(Repository repository, TradeBotData tradeBotData) throws DataException { - State tradeBotState = State.valueOf(tradeBotData.getStateValue()); - if (tradeBotState == null) - return true; - - // If the AT doesn't exist then we might as well let the user tidy up - if (!repository.getATRepository().exists(tradeBotData.getAtAddress())) - return true; - - switch (tradeBotState) { - case BOB_WAITING_FOR_AT_CONFIRM: - case ALICE_DONE: - case BOB_DONE: - case ALICE_REFUNDED: - case BOB_REFUNDED: - case ALICE_REFUNDING_A: - return true; - - default: - return false; - } - } - - @Override - public void progress(Repository repository, TradeBotData tradeBotData) throws DataException, ForeignBlockchainException { - State tradeBotState = State.valueOf(tradeBotData.getStateValue()); - if (tradeBotState == null) { - LOGGER.info(() -> String.format("Trade-bot entry for AT %s has invalid state?", tradeBotData.getAtAddress())); - return; - } - - ATData atData = null; - CrossChainTradeData tradeData = null; - - if (tradeBotState.requiresAtData) { - // Attempt to fetch AT data - atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress()); - if (atData == null) { - LOGGER.debug(() -> String.format("Unable to fetch trade AT %s from repository", tradeBotData.getAtAddress())); - return; - } - - if (tradeBotState.requiresTradeData) { - tradeData = RavencoinACCTv1.getInstance().populateTradeData(repository, atData); - if (tradeData == null) { - LOGGER.warn(() -> String.format("Unable to fetch ACCT trade data for AT %s from repository", tradeBotData.getAtAddress())); - return; - } - } - } - - switch (tradeBotState) { - case BOB_WAITING_FOR_AT_CONFIRM: - handleBobWaitingForAtConfirm(repository, tradeBotData); - break; - - case BOB_WAITING_FOR_MESSAGE: - TradeBot.getInstance().updatePresence(repository, tradeBotData, tradeData); - handleBobWaitingForMessage(repository, tradeBotData, atData, tradeData); - break; - - case ALICE_WAITING_FOR_AT_LOCK: - TradeBot.getInstance().updatePresence(repository, tradeBotData, tradeData); - handleAliceWaitingForAtLock(repository, tradeBotData, atData, tradeData); - break; - - case BOB_WAITING_FOR_AT_REDEEM: - TradeBot.getInstance().updatePresence(repository, tradeBotData, tradeData); - handleBobWaitingForAtRedeem(repository, tradeBotData, atData, tradeData); - break; - - case ALICE_DONE: - case BOB_DONE: - break; - - case ALICE_REFUNDING_A: - TradeBot.getInstance().updatePresence(repository, tradeBotData, tradeData); - handleAliceRefundingP2shA(repository, tradeBotData, atData, tradeData); - break; - - case ALICE_REFUNDED: - case BOB_REFUNDED: - break; - } - } - - /** - * Trade-bot is waiting for Bob's AT to deploy. - *
- * If AT is deployed, then trade-bot's next step is to wait for MESSAGE from Alice. - */ - private void handleBobWaitingForAtConfirm(Repository repository, TradeBotData tradeBotData) throws DataException { - if (!repository.getATRepository().exists(tradeBotData.getAtAddress())) { - if (NTP.getTime() - tradeBotData.getTimestamp() <= MAX_AT_CONFIRMATION_PERIOD) - return; - - // We've waited ages for AT to be confirmed into a block but something has gone awry. - // After this long we assume transaction loss so give up with trade-bot entry too. - tradeBotData.setState(State.BOB_REFUNDED.name()); - tradeBotData.setStateValue(State.BOB_REFUNDED.value); - tradeBotData.setTimestamp(NTP.getTime()); - // We delete trade-bot entry here instead of saving, hence not using updateTradeBotState() - repository.getCrossChainRepository().delete(tradeBotData.getTradePrivateKey()); - repository.saveChanges(); - - LOGGER.info(() -> String.format("AT %s never confirmed. Giving up on trade", tradeBotData.getAtAddress())); - TradeBot.notifyStateChange(tradeBotData); - return; - } - - TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_WAITING_FOR_MESSAGE, - () -> String.format("AT %s confirmed ready. Waiting for trade message", tradeBotData.getAtAddress())); - } - - /** - * Trade-bot is waiting for MESSAGE from Alice's trade-bot, containing Alice's trade info. - *
- * It's possible Bob has cancelling his trade offer, receiving an automatic QORT refund, - * in which case trade-bot is done with this specific trade and finalizes on refunded state. - *
- * Assuming trade is still on offer, trade-bot checks the contents of MESSAGE from Alice's trade-bot. - *
- * Details from Alice are used to derive P2SH-A address and this is checked for funding balance. - *
- * Assuming P2SH-A has at least expected Ravencoin balance, - * Bob's trade-bot constructs a zero-fee, PoW MESSAGE to send to Bob's AT with more trade details. - *
- * On processing this MESSAGE, Bob's AT should switch into 'TRADE' mode and only trade with Alice. - *
- * Trade-bot's next step is to wait for Alice to redeem the AT, which will allow Bob to
- * extract secret-A needed to redeem Alice's P2SH.
- * @throws ForeignBlockchainException
- */
- private void handleBobWaitingForMessage(Repository repository, TradeBotData tradeBotData,
- ATData atData, CrossChainTradeData crossChainTradeData) throws DataException, ForeignBlockchainException {
- // If AT has finished then Bob likely cancelled his trade offer
- if (atData.getIsFinished()) {
- TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_REFUNDED,
- () -> String.format("AT %s cancelled - trading aborted", tradeBotData.getAtAddress()));
- return;
- }
-
- Ravencoin ravencoin = Ravencoin.getInstance();
-
- String address = tradeBotData.getTradeNativeAddress();
- List
- * It's possible that Bob has cancelled his trade offer in the mean time, or that somehow
- * this process has taken so long that we've reached P2SH-A's locktime, or that someone else
- * has managed to trade with Bob. In any of these cases, trade-bot switches to begin the refunding process.
- *
- * Assuming Bob's AT is locked to Alice, trade-bot checks AT's state data to make sure it is correct.
- *
- * If all is well, trade-bot then redeems AT using Alice's secret-A, releasing Bob's QORT to Alice.
- *
- * In revealing a valid secret-A, Bob can then redeem the RVN funds from P2SH-A.
- *
- * @throws ForeignBlockchainException
- */
- private void handleAliceWaitingForAtLock(Repository repository, TradeBotData tradeBotData,
- ATData atData, CrossChainTradeData crossChainTradeData) throws DataException, ForeignBlockchainException {
- if (aliceUnexpectedState(repository, tradeBotData, atData, crossChainTradeData))
- return;
-
- Ravencoin ravencoin = Ravencoin.getInstance();
- int lockTimeA = tradeBotData.getLockTimeA();
-
- // Refund P2SH-A if we've passed lockTime-A
- if (NTP.getTime() >= lockTimeA * 1000L) {
- byte[] redeemScriptA = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), lockTimeA, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret());
- String p2shAddressA = ravencoin.deriveP2shAddress(redeemScriptA);
-
- long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout);
- long p2shFee = Ravencoin.getInstance().getP2shFee(feeTimestamp);
- long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee;
-
- BitcoinyHTLC.Status htlcStatusA = BitcoinyHTLC.determineHtlcStatus(ravencoin.getBlockchainProvider(), p2shAddressA, minimumAmountA);
-
- switch (htlcStatusA) {
- case UNFUNDED:
- case FUNDING_IN_PROGRESS:
- case FUNDED:
- break;
-
- case REDEEM_IN_PROGRESS:
- case REDEEMED:
- // Already redeemed?
- TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_DONE,
- () -> String.format("P2SH-A %s already spent? Assuming trade completed", p2shAddressA));
- return;
-
- case REFUND_IN_PROGRESS:
- case REFUNDED:
- TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDED,
- () -> String.format("P2SH-A %s already refunded. Trade aborted", p2shAddressA));
- return;
-
- }
-
- TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDING_A,
- () -> atData.getIsFinished()
- ? String.format("AT %s cancelled. Refunding P2SH-A %s - aborting trade", tradeBotData.getAtAddress(), p2shAddressA)
- : String.format("LockTime-A reached, refunding P2SH-A %s - aborting trade", p2shAddressA));
-
- return;
- }
-
- // We're waiting for AT to be in TRADE mode
- if (crossChainTradeData.mode != AcctMode.TRADING)
- return;
-
- // AT is in TRADE mode and locked to us as checked by aliceUnexpectedState() above
-
- // Find our MESSAGE to AT from previous state
- List
- * It's possible that Bob's AT has reached its trading timeout and automatically refunded QORT back to Bob. In which case,
- * trade-bot is done with this specific trade and finalizes in refunded state.
- *
- * Assuming trade-bot can extract a valid secret-A from Alice's MESSAGE then trade-bot uses that to redeem the RVN funds from P2SH-A
- * to Bob's 'foreign'/Ravencoin trade legacy-format address, as derived from trade private key.
- *
- * (This could potentially be 'improved' to send RVN to any address of Bob's choosing by changing the transaction output).
- *
- * If trade-bot successfully broadcasts the transaction, then this specific trade is done.
- * @throws ForeignBlockchainException
- */
- private void handleBobWaitingForAtRedeem(Repository repository, TradeBotData tradeBotData,
- ATData atData, CrossChainTradeData crossChainTradeData) throws DataException, ForeignBlockchainException {
- // AT should be 'finished' once Alice has redeemed QORT funds
- if (!atData.getIsFinished())
- // Not finished yet
- return;
-
- // If AT is REFUNDED or CANCELLED then something has gone wrong
- if (crossChainTradeData.mode == AcctMode.REFUNDED || crossChainTradeData.mode == AcctMode.CANCELLED) {
- // Alice hasn't redeemed the QORT, so there is no point in trying to redeem the RVN
- TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_REFUNDED,
- () -> String.format("AT %s has auto-refunded - trade aborted", tradeBotData.getAtAddress()));
-
- return;
- }
-
- byte[] secretA = RavencoinACCTv1.getInstance().findSecretA(repository, crossChainTradeData);
- if (secretA == null) {
- LOGGER.debug(() -> String.format("Unable to find secret-A from redeem message to AT %s?", tradeBotData.getAtAddress()));
- return;
- }
-
- // Use secret-A to redeem P2SH-A
-
- Ravencoin ravencoin = Ravencoin.getInstance();
-
- byte[] receivingAccountInfo = tradeBotData.getReceivingAccountInfo();
- int lockTimeA = crossChainTradeData.lockTimeA;
- byte[] redeemScriptA = BitcoinyHTLC.buildScript(crossChainTradeData.partnerForeignPKH, lockTimeA, crossChainTradeData.creatorForeignPKH, crossChainTradeData.hashOfSecretA);
- String p2shAddressA = ravencoin.deriveP2shAddress(redeemScriptA);
-
- // Fee for redeem/refund is subtracted from P2SH-A balance.
- long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout);
- long p2shFee = Ravencoin.getInstance().getP2shFee(feeTimestamp);
- long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee;
- BitcoinyHTLC.Status htlcStatusA = BitcoinyHTLC.determineHtlcStatus(ravencoin.getBlockchainProvider(), p2shAddressA, minimumAmountA);
-
- switch (htlcStatusA) {
- case UNFUNDED:
- case FUNDING_IN_PROGRESS:
- // P2SH-A suddenly not funded? Our best bet at this point is to hope for AT auto-refund
- return;
-
- case REDEEM_IN_PROGRESS:
- case REDEEMED:
- // Double-check that we have redeemed P2SH-A...
- break;
-
- case REFUND_IN_PROGRESS:
- case REFUNDED:
- // Wait for AT to auto-refund
- return;
-
- case FUNDED: {
- Coin redeemAmount = Coin.valueOf(crossChainTradeData.expectedForeignAmount);
- ECKey redeemKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
- List
- * Will automatically update trade-bot state to ALICE_REFUNDING_A or ALICE_DONE as necessary.
- *
- * @throws DataException
- * @throws ForeignBlockchainException
- */
- private boolean aliceUnexpectedState(Repository repository, TradeBotData tradeBotData,
- ATData atData, CrossChainTradeData crossChainTradeData) throws DataException, ForeignBlockchainException {
- // This is OK
- if (!atData.getIsFinished() && crossChainTradeData.mode == AcctMode.OFFERING)
- return false;
-
- boolean isAtLockedToUs = tradeBotData.getTradeNativeAddress().equals(crossChainTradeData.qortalPartnerAddress);
-
- if (!atData.getIsFinished() && crossChainTradeData.mode == AcctMode.TRADING)
- if (isAtLockedToUs) {
- // AT is trading with us - OK
- return false;
- } else {
- TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDING_A,
- () -> String.format("AT %s trading with someone else: %s. Refunding & aborting trade", tradeBotData.getAtAddress(), crossChainTradeData.qortalPartnerAddress));
-
- return true;
- }
-
- if (atData.getIsFinished() && crossChainTradeData.mode == AcctMode.REDEEMED && isAtLockedToUs) {
- // We've redeemed already?
- TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_DONE,
- () -> String.format("AT %s already redeemed by us. Trade completed", tradeBotData.getAtAddress()));
- } else {
- // Any other state is not good, so start defensive refund
- TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDING_A,
- () -> String.format("AT %s cancelled/refunded/redeemed by someone else/invalid state. Refunding & aborting trade", tradeBotData.getAtAddress()));
- }
-
- return true;
- }
-
- private long calcFeeTimestamp(int lockTimeA, int tradeTimeout) {
- return (lockTimeA - tradeTimeout * 60) * 1000L;
- }
-
-}
diff --git a/src/main/java/org/qortal/controller/tradebot/TradeBot.java b/src/main/java/org/qortal/controller/tradebot/TradeBot.java
index 25485f70..0a1a771a 100644
--- a/src/main/java/org/qortal/controller/tradebot/TradeBot.java
+++ b/src/main/java/org/qortal/controller/tradebot/TradeBot.java
@@ -100,7 +100,6 @@ public class TradeBot implements Listener {
acctTradeBotSuppliers.put(DogecoinACCTv1.class, DogecoinACCTv1TradeBot::getInstance);
acctTradeBotSuppliers.put(DogecoinACCTv2.class, DogecoinACCTv2TradeBot::getInstance);
acctTradeBotSuppliers.put(DogecoinACCTv3.class, DogecoinACCTv3TradeBot::getInstance);
- acctTradeBotSuppliers.put(RavencoinACCTv1.class, RavencoinACCTv1TradeBot::getInstance);
acctTradeBotSuppliers.put(RavencoinACCTv3.class, RavencoinACCTv3TradeBot::getInstance);
}
diff --git a/src/main/java/org/qortal/crosschain/RavencoinACCTv1.java b/src/main/java/org/qortal/crosschain/RavencoinACCTv1.java
deleted file mode 100644
index 607ec1a7..00000000
--- a/src/main/java/org/qortal/crosschain/RavencoinACCTv1.java
+++ /dev/null
@@ -1,861 +0,0 @@
-package org.qortal.crosschain;
-
-import static org.ciyam.at.OpCode.calcOffset;
-
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.List;
-
-import org.ciyam.at.API;
-import org.ciyam.at.CompilationException;
-import org.ciyam.at.FunctionCode;
-import org.ciyam.at.MachineState;
-import org.ciyam.at.OpCode;
-import org.ciyam.at.Timestamp;
-import org.qortal.account.Account;
-import org.qortal.asset.Asset;
-import org.qortal.at.QortalFunctionCode;
-import org.qortal.crypto.Crypto;
-import org.qortal.data.at.ATData;
-import org.qortal.data.at.ATStateData;
-import org.qortal.data.crosschain.CrossChainTradeData;
-import org.qortal.data.transaction.MessageTransactionData;
-import org.qortal.repository.DataException;
-import org.qortal.repository.Repository;
-import org.qortal.utils.Base58;
-import org.qortal.utils.BitTwiddling;
-
-import com.google.common.hash.HashCode;
-import com.google.common.primitives.Bytes;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-/**
- * Cross-chain trade AT
- *
- *
- *
- * tradeTimeout (minutes) is the time window for the trade partner to send the
- * 32-byte secret to the AT, before the AT automatically refunds the AT's creator.
- *
- * @param creatorTradeAddress AT creator's trade Qortal address
- * @param ravencoinPublicKeyHash 20-byte HASH160 of creator's trade Ravencoin public key
- * @param qortAmount how much QORT to pay trade partner if they send correct 32-byte secrets to AT
- * @param ravencoinAmount how much RVN the AT creator is expecting to trade
- * @param tradeTimeout suggested timeout for entire trade
- */
- public static byte[] buildQortalAT(String creatorTradeAddress, byte[] ravencoinPublicKeyHash, long qortAmount, long ravencoinAmount, int tradeTimeout) {
- if (ravencoinPublicKeyHash.length != 20)
- throw new IllegalArgumentException("Ravencoin public key hash should be 20 bytes");
-
- // Labels for data segment addresses
- int addrCounter = 0;
-
- // Constants (with corresponding dataByteBuffer.put*() calls below)
-
- final int addrCreatorTradeAddress1 = addrCounter++;
- final int addrCreatorTradeAddress2 = addrCounter++;
- final int addrCreatorTradeAddress3 = addrCounter++;
- final int addrCreatorTradeAddress4 = addrCounter++;
-
- final int addrRavencoinPublicKeyHash = addrCounter;
- addrCounter += 4;
-
- final int addrQortAmount = addrCounter++;
- final int addrRavencoinAmount = addrCounter++;
- final int addrTradeTimeout = addrCounter++;
-
- final int addrMessageTxnType = addrCounter++;
- final int addrExpectedTradeMessageLength = addrCounter++;
- final int addrExpectedRedeemMessageLength = addrCounter++;
-
- final int addrCreatorAddressPointer = addrCounter++;
- final int addrQortalPartnerAddressPointer = addrCounter++;
- final int addrMessageSenderPointer = addrCounter++;
-
- final int addrTradeMessagePartnerRavencoinPKHOffset = addrCounter++;
- final int addrPartnerRavencoinPKHPointer = addrCounter++;
- final int addrTradeMessageHashOfSecretAOffset = addrCounter++;
- final int addrHashOfSecretAPointer = addrCounter++;
-
- final int addrRedeemMessageReceivingAddressOffset = addrCounter++;
-
- final int addrMessageDataPointer = addrCounter++;
- final int addrMessageDataLength = addrCounter++;
-
- final int addrPartnerReceivingAddressPointer = addrCounter++;
-
- final int addrEndOfConstants = addrCounter;
-
- // Variables
-
- final int addrCreatorAddress1 = addrCounter++;
- final int addrCreatorAddress2 = addrCounter++;
- final int addrCreatorAddress3 = addrCounter++;
- final int addrCreatorAddress4 = addrCounter++;
-
- final int addrQortalPartnerAddress1 = addrCounter++;
- final int addrQortalPartnerAddress2 = addrCounter++;
- final int addrQortalPartnerAddress3 = addrCounter++;
- final int addrQortalPartnerAddress4 = addrCounter++;
-
- final int addrLockTimeA = addrCounter++;
- final int addrRefundTimeout = addrCounter++;
- final int addrRefundTimestamp = addrCounter++;
- final int addrLastTxnTimestamp = addrCounter++;
- final int addrBlockTimestamp = addrCounter++;
- final int addrTxnType = addrCounter++;
- final int addrResult = addrCounter++;
-
- final int addrMessageSender1 = addrCounter++;
- final int addrMessageSender2 = addrCounter++;
- final int addrMessageSender3 = addrCounter++;
- final int addrMessageSender4 = addrCounter++;
-
- final int addrMessageLength = addrCounter++;
-
- final int addrMessageData = addrCounter;
- addrCounter += 4;
-
- final int addrHashOfSecretA = addrCounter;
- addrCounter += 4;
-
- final int addrPartnerRavencoinPKH = addrCounter;
- addrCounter += 4;
-
- final int addrPartnerReceivingAddress = addrCounter;
- addrCounter += 4;
-
- final int addrMode = addrCounter++;
- assert addrMode == MODE_VALUE_OFFSET : String.format("addrMode %d does not match MODE_VALUE_OFFSET %d", addrMode, MODE_VALUE_OFFSET);
-
- // Data segment
- ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE);
-
- // AT creator's trade Qortal address, decoded from Base58
- assert dataByteBuffer.position() == addrCreatorTradeAddress1 * MachineState.VALUE_SIZE : "addrCreatorTradeAddress1 incorrect";
- byte[] creatorTradeAddressBytes = Base58.decode(creatorTradeAddress);
- dataByteBuffer.put(Bytes.ensureCapacity(creatorTradeAddressBytes, 32, 0));
-
- // Ravencoin public key hash
- assert dataByteBuffer.position() == addrRavencoinPublicKeyHash * MachineState.VALUE_SIZE : "addrRavencoinPublicKeyHash incorrect";
- dataByteBuffer.put(Bytes.ensureCapacity(ravencoinPublicKeyHash, 32, 0));
-
- // Redeem Qort amount
- assert dataByteBuffer.position() == addrQortAmount * MachineState.VALUE_SIZE : "addrQortAmount incorrect";
- dataByteBuffer.putLong(qortAmount);
-
- // Expected Ravencoin amount
- assert dataByteBuffer.position() == addrRavencoinAmount * MachineState.VALUE_SIZE : "addrRavencoinAmount incorrect";
- dataByteBuffer.putLong(ravencoinAmount);
-
- // Suggested trade timeout (minutes)
- assert dataByteBuffer.position() == addrTradeTimeout * MachineState.VALUE_SIZE : "addrTradeTimeout incorrect";
- dataByteBuffer.putLong(tradeTimeout);
-
- // We're only interested in MESSAGE transactions
- assert dataByteBuffer.position() == addrMessageTxnType * MachineState.VALUE_SIZE : "addrMessageTxnType incorrect";
- dataByteBuffer.putLong(API.ATTransactionType.MESSAGE.value);
-
- // Expected length of 'trade' MESSAGE data from AT creator
- assert dataByteBuffer.position() == addrExpectedTradeMessageLength * MachineState.VALUE_SIZE : "addrExpectedTradeMessageLength incorrect";
- dataByteBuffer.putLong(TRADE_MESSAGE_LENGTH);
-
- // Expected length of 'redeem' MESSAGE data from trade partner
- assert dataByteBuffer.position() == addrExpectedRedeemMessageLength * MachineState.VALUE_SIZE : "addrExpectedRedeemMessageLength incorrect";
- dataByteBuffer.putLong(REDEEM_MESSAGE_LENGTH);
-
- // Index into data segment of AT creator's address, used by GET_B_IND
- assert dataByteBuffer.position() == addrCreatorAddressPointer * MachineState.VALUE_SIZE : "addrCreatorAddressPointer incorrect";
- dataByteBuffer.putLong(addrCreatorAddress1);
-
- // Index into data segment of partner's Qortal address, used by SET_B_IND
- assert dataByteBuffer.position() == addrQortalPartnerAddressPointer * MachineState.VALUE_SIZE : "addrQortalPartnerAddressPointer incorrect";
- dataByteBuffer.putLong(addrQortalPartnerAddress1);
-
- // Index into data segment of (temporary) transaction's sender's address, used by GET_B_IND
- assert dataByteBuffer.position() == addrMessageSenderPointer * MachineState.VALUE_SIZE : "addrMessageSenderPointer incorrect";
- dataByteBuffer.putLong(addrMessageSender1);
-
- // Offset into 'trade' MESSAGE data payload for extracting partner's Ravencoin PKH
- assert dataByteBuffer.position() == addrTradeMessagePartnerRavencoinPKHOffset * MachineState.VALUE_SIZE : "addrTradeMessagePartnerRavencoinPKHOffset incorrect";
- dataByteBuffer.putLong(32L);
-
- // Index into data segment of partner's Ravencoin PKH, used by GET_B_IND
- assert dataByteBuffer.position() == addrPartnerRavencoinPKHPointer * MachineState.VALUE_SIZE : "addrPartnerRavencoinPKHPointer incorrect";
- dataByteBuffer.putLong(addrPartnerRavencoinPKH);
-
- // Offset into 'trade' MESSAGE data payload for extracting hash-of-secret-A
- assert dataByteBuffer.position() == addrTradeMessageHashOfSecretAOffset * MachineState.VALUE_SIZE : "addrTradeMessageHashOfSecretAOffset incorrect";
- dataByteBuffer.putLong(64L);
-
- // Index into data segment to hash of secret A, used by GET_B_IND
- assert dataByteBuffer.position() == addrHashOfSecretAPointer * MachineState.VALUE_SIZE : "addrHashOfSecretAPointer incorrect";
- dataByteBuffer.putLong(addrHashOfSecretA);
-
- // Offset into 'redeem' MESSAGE data payload for extracting Qortal receiving address
- assert dataByteBuffer.position() == addrRedeemMessageReceivingAddressOffset * MachineState.VALUE_SIZE : "addrRedeemMessageReceivingAddressOffset incorrect";
- dataByteBuffer.putLong(32L);
-
- // Source location and length for hashing any passed secret
- assert dataByteBuffer.position() == addrMessageDataPointer * MachineState.VALUE_SIZE : "addrMessageDataPointer incorrect";
- dataByteBuffer.putLong(addrMessageData);
- assert dataByteBuffer.position() == addrMessageDataLength * MachineState.VALUE_SIZE : "addrMessageDataLength incorrect";
- dataByteBuffer.putLong(32L);
-
- // Pointer into data segment of where to save partner's receiving Qortal address, used by GET_B_IND
- assert dataByteBuffer.position() == addrPartnerReceivingAddressPointer * MachineState.VALUE_SIZE : "addrPartnerReceivingAddressPointer incorrect";
- dataByteBuffer.putLong(addrPartnerReceivingAddress);
-
- assert dataByteBuffer.position() == addrEndOfConstants * MachineState.VALUE_SIZE : "dataByteBuffer position not at end of constants";
-
- // Code labels
- Integer labelRefund = null;
-
- Integer labelTradeTxnLoop = null;
- Integer labelCheckTradeTxn = null;
- Integer labelCheckCancelTxn = null;
- Integer labelNotTradeNorCancelTxn = null;
- Integer labelCheckNonRefundTradeTxn = null;
- Integer labelTradeTxnExtract = null;
- Integer labelRedeemTxnLoop = null;
- Integer labelCheckRedeemTxn = null;
- Integer labelCheckRedeemTxnSender = null;
- Integer labelPayout = null;
-
- ByteBuffer codeByteBuffer = ByteBuffer.allocate(768);
-
- // Two-pass version
- for (int pass = 0; pass < 2; ++pass) {
- codeByteBuffer.clear();
-
- try {
- /* Initialization */
-
- // Use AT creation 'timestamp' as starting point for finding transactions sent to AT
- codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_CREATION_TIMESTAMP, addrLastTxnTimestamp));
-
- // Load B register with AT creator's address so we can save it into addrCreatorAddress1-4
- codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_CREATOR_INTO_B));
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrCreatorAddressPointer));
-
- /* NOP - to ensure RAVENCOIN ACCT is unique */
- codeByteBuffer.put(OpCode.NOP.compile());
-
- // Set restart position to after this opcode
- codeByteBuffer.put(OpCode.SET_PCS.compile());
-
- /* Loop, waiting for message from AT creator's trade address containing trade partner details, or AT owner's address to cancel offer */
-
- /* Transaction processing loop */
- labelTradeTxnLoop = codeByteBuffer.position();
-
- // Find next transaction (if any) to this AT since the last one (referenced by addrLastTxnTimestamp)
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxnTimestamp));
- // If no transaction found, A will be zero. If A is zero, set addrResult to 1, otherwise 0.
- codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.CHECK_A_IS_ZERO, addrResult));
- // If addrResult is zero (i.e. A is non-zero, transaction was found) then go check transaction
- codeByteBuffer.put(OpCode.BZR_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelCheckTradeTxn)));
- // Stop and wait for next block
- codeByteBuffer.put(OpCode.STP_IMD.compile());
-
- /* Check transaction */
- labelCheckTradeTxn = codeByteBuffer.position();
-
- // Update our 'last found transaction's timestamp' using 'timestamp' from transaction
- codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTxnTimestamp));
- // Extract transaction type (message/payment) from transaction and save type in addrTxnType
- codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TYPE_FROM_TX_IN_A, addrTxnType));
- // If transaction type is not MESSAGE type then go look for another transaction
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxnType, addrMessageTxnType, calcOffset(codeByteBuffer, labelTradeTxnLoop)));
-
- /* Check transaction's sender. We're expecting AT creator's trade address for 'trade' message, or AT creator's own address for 'cancel' message. */
-
- // Extract sender address from transaction into B register
- codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B));
- // Save B register into data segment starting at addrMessageSender1 (as pointed to by addrMessageSenderPointer)
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageSenderPointer));
- // Compare each part of message sender's address with AT creator's trade address. If they don't match, check for cancel situation.
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrCreatorTradeAddress1, calcOffset(codeByteBuffer, labelCheckCancelTxn)));
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrCreatorTradeAddress2, calcOffset(codeByteBuffer, labelCheckCancelTxn)));
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrCreatorTradeAddress3, calcOffset(codeByteBuffer, labelCheckCancelTxn)));
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrCreatorTradeAddress4, calcOffset(codeByteBuffer, labelCheckCancelTxn)));
- // Message sender's address matches AT creator's trade address so go process 'trade' message
- codeByteBuffer.put(OpCode.JMP_ADR.compile(labelCheckNonRefundTradeTxn == null ? 0 : labelCheckNonRefundTradeTxn));
-
- /* Checking message sender for possible cancel message */
- labelCheckCancelTxn = codeByteBuffer.position();
-
- // Compare each part of message sender's address with AT creator's address. If they don't match, look for another transaction.
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrCreatorAddress1, calcOffset(codeByteBuffer, labelNotTradeNorCancelTxn)));
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrCreatorAddress2, calcOffset(codeByteBuffer, labelNotTradeNorCancelTxn)));
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrCreatorAddress3, calcOffset(codeByteBuffer, labelNotTradeNorCancelTxn)));
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrCreatorAddress4, calcOffset(codeByteBuffer, labelNotTradeNorCancelTxn)));
- // Partner address is AT creator's address, so cancel offer and finish.
- codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, AcctMode.CANCELLED.value));
- // We're finished forever (finishing auto-refunds remaining balance to AT creator)
- codeByteBuffer.put(OpCode.FIN_IMD.compile());
-
- /* Not trade nor cancel message */
- labelNotTradeNorCancelTxn = codeByteBuffer.position();
-
- // Loop to find another transaction
- codeByteBuffer.put(OpCode.JMP_ADR.compile(labelTradeTxnLoop == null ? 0 : labelTradeTxnLoop));
-
- /* Possible switch-to-trade-mode message */
- labelCheckNonRefundTradeTxn = codeByteBuffer.position();
-
- // Check 'trade' message we received has expected number of message bytes
- codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(QortalFunctionCode.GET_MESSAGE_LENGTH_FROM_TX_IN_A.value, addrMessageLength));
- // If message length matches, branch to info extraction code
- codeByteBuffer.put(OpCode.BEQ_DAT.compile(addrMessageLength, addrExpectedTradeMessageLength, calcOffset(codeByteBuffer, labelTradeTxnExtract)));
- // Message length didn't match - go back to finding another 'trade' MESSAGE transaction
- codeByteBuffer.put(OpCode.JMP_ADR.compile(labelTradeTxnLoop == null ? 0 : labelTradeTxnLoop));
-
- /* Extracting info from 'trade' MESSAGE transaction */
- labelTradeTxnExtract = codeByteBuffer.position();
-
- // Extract message from transaction into B register
- codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B));
- // Save B register into data segment starting at addrQortalPartnerAddress1 (as pointed to by addrQortalPartnerAddressPointer)
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrQortalPartnerAddressPointer));
-
- // Extract trade partner's Ravencoin public key hash (PKH) from message into B
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrTradeMessagePartnerRavencoinPKHOffset));
- // Store partner's Ravencoin PKH (we only really use values from B1-B3)
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrPartnerRavencoinPKHPointer));
- // Extract AT trade timeout (minutes) (from B4)
- codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_B4, addrRefundTimeout));
-
- // Grab next 32 bytes
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrTradeMessageHashOfSecretAOffset));
-
- // Extract hash-of-secret-A (we only really use values from B1-B3)
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrHashOfSecretAPointer));
- // Extract lockTime-A (from B4)
- codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_B4, addrLockTimeA));
-
- // Calculate trade timeout refund 'timestamp' by adding addrRefundTimeout minutes to this transaction's 'timestamp', then save into addrRefundTimestamp
- codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.ADD_MINUTES_TO_TIMESTAMP, addrRefundTimestamp, addrLastTxnTimestamp, addrRefundTimeout));
-
- /* We are in 'trade mode' */
- codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, AcctMode.TRADING.value));
-
- // Set restart position to after this opcode
- codeByteBuffer.put(OpCode.SET_PCS.compile());
-
- /* Loop, waiting for trade timeout or 'redeem' MESSAGE from Qortal trade partner */
-
- // Fetch current block 'timestamp'
- codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_BLOCK_TIMESTAMP, addrBlockTimestamp));
- // If we're not past refund 'timestamp' then look for next transaction
- codeByteBuffer.put(OpCode.BLT_DAT.compile(addrBlockTimestamp, addrRefundTimestamp, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
- // We're past refund 'timestamp' so go refund everything back to AT creator
- codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRefund == null ? 0 : labelRefund));
-
- /* Transaction processing loop */
- labelRedeemTxnLoop = codeByteBuffer.position();
-
- // Find next transaction to this AT since the last one (if any)
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxnTimestamp));
- // If no transaction found, A will be zero. If A is zero, set addrComparator to 1, otherwise 0.
- codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.CHECK_A_IS_ZERO, addrResult));
- // If addrResult is zero (i.e. A is non-zero, transaction was found) then go check transaction
- codeByteBuffer.put(OpCode.BZR_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelCheckRedeemTxn)));
- // Stop and wait for next block
- codeByteBuffer.put(OpCode.STP_IMD.compile());
-
- /* Check transaction */
- labelCheckRedeemTxn = codeByteBuffer.position();
-
- // Update our 'last found transaction's timestamp' using 'timestamp' from transaction
- codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTxnTimestamp));
- // Extract transaction type (message/payment) from transaction and save type in addrTxnType
- codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TYPE_FROM_TX_IN_A, addrTxnType));
- // If transaction type is not MESSAGE type then go look for another transaction
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxnType, addrMessageTxnType, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
-
- /* Check message payload length */
- codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(QortalFunctionCode.GET_MESSAGE_LENGTH_FROM_TX_IN_A.value, addrMessageLength));
- // If message length matches, branch to sender checking code
- codeByteBuffer.put(OpCode.BEQ_DAT.compile(addrMessageLength, addrExpectedRedeemMessageLength, calcOffset(codeByteBuffer, labelCheckRedeemTxnSender)));
- // Message length didn't match - go back to finding another 'redeem' MESSAGE transaction
- codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRedeemTxnLoop == null ? 0 : labelRedeemTxnLoop));
-
- /* Check transaction's sender */
- labelCheckRedeemTxnSender = codeByteBuffer.position();
-
- // Extract sender address from transaction into B register
- codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B));
- // Save B register into data segment starting at addrMessageSender1 (as pointed to by addrMessageSenderPointer)
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageSenderPointer));
- // Compare each part of transaction's sender's address with expected address. If they don't match, look for another transaction.
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrQortalPartnerAddress1, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrQortalPartnerAddress2, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrQortalPartnerAddress3, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrQortalPartnerAddress4, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
-
- /* Check 'secret-A' in transaction's message */
-
- // Extract secret-A from first 32 bytes of message from transaction into B register
- codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B));
- // Save B register into data segment starting at addrMessageData (as pointed to by addrMessageDataPointer)
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageDataPointer));
- // Load B register with expected hash result (as pointed to by addrHashOfSecretAPointer)
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrHashOfSecretAPointer));
- // Perform HASH160 using source data at addrMessageData. (Location and length specified via addrMessageDataPointer and addrMessageDataLength).
- // Save the equality result (1 if they match, 0 otherwise) into addrResult.
- codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.CHECK_HASH160_WITH_B, addrResult, addrMessageDataPointer, addrMessageDataLength));
- // If hashes don't match, addrResult will be zero so go find another transaction
- codeByteBuffer.put(OpCode.BNZ_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelPayout)));
- codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRedeemTxnLoop == null ? 0 : labelRedeemTxnLoop));
-
- /* Success! Pay arranged amount to receiving address */
- labelPayout = codeByteBuffer.position();
-
- // Extract Qortal receiving address from next 32 bytes of message from transaction into B register
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrRedeemMessageReceivingAddressOffset));
- // Save B register into data segment starting at addrPartnerReceivingAddress (as pointed to by addrPartnerReceivingAddressPointer)
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrPartnerReceivingAddressPointer));
- // Pay AT's balance to receiving address
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PAY_TO_ADDRESS_IN_B, addrQortAmount));
- // Set redeemed mode
- codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, AcctMode.REDEEMED.value));
- // We're finished forever (finishing auto-refunds remaining balance to AT creator)
- codeByteBuffer.put(OpCode.FIN_IMD.compile());
-
- // Fall-through to refunding any remaining balance back to AT creator
-
- /* Refund balance back to AT creator */
- labelRefund = codeByteBuffer.position();
-
- // Set refunded mode
- codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, AcctMode.REFUNDED.value));
- // We're finished forever (finishing auto-refunds remaining balance to AT creator)
- codeByteBuffer.put(OpCode.FIN_IMD.compile());
- } catch (CompilationException e) {
- throw new IllegalStateException("Unable to compile RVN-QORT ACCT?", e);
- }
- }
-
- codeByteBuffer.flip();
-
- byte[] codeBytes = new byte[codeByteBuffer.limit()];
- codeByteBuffer.get(codeBytes);
-
- assert Arrays.equals(Crypto.digest(codeBytes), RavencoinACCTv1.CODE_BYTES_HASH)
- : String.format("BTCACCT.CODE_BYTES_HASH mismatch: expected %s, actual %s", HashCode.fromBytes(CODE_BYTES_HASH), HashCode.fromBytes(Crypto.digest(codeBytes)));
-
- final short ciyamAtVersion = 2;
- final short numCallStackPages = 0;
- final short numUserStackPages = 0;
- final long minActivationAmount = 0L;
-
- return MachineState.toCreationBytes(ciyamAtVersion, codeBytes, dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount);
- }
-
- /**
- * Returns CrossChainTradeData with useful info extracted from AT.
- */
- @Override
- public CrossChainTradeData populateTradeData(Repository repository, ATData atData) throws DataException {
- ATStateData atStateData = repository.getATRepository().getLatestATState(atData.getATAddress());
- return populateTradeData(repository, atData.getCreatorPublicKey(), atData.getCreation(), atStateData);
- }
-
- /**
- * Returns CrossChainTradeData with useful info extracted from AT.
- */
- @Override
- public CrossChainTradeData populateTradeData(Repository repository, ATStateData atStateData) throws DataException {
- ATData atData = repository.getATRepository().fromATAddress(atStateData.getATAddress());
- return populateTradeData(repository, atData.getCreatorPublicKey(), atData.getCreation(), atStateData);
- }
-
- /**
- * Returns CrossChainTradeData with useful info extracted from AT.
- */
- public CrossChainTradeData populateTradeData(Repository repository, byte[] creatorPublicKey, long creationTimestamp, ATStateData atStateData) throws DataException {
- byte[] addressBytes = new byte[25]; // for general use
- String atAddress = atStateData.getATAddress();
-
- CrossChainTradeData tradeData = new CrossChainTradeData();
-
- tradeData.foreignBlockchain = SupportedBlockchain.RAVENCOIN.name();
- tradeData.acctName = NAME;
-
- tradeData.qortalAtAddress = atAddress;
- tradeData.qortalCreator = Crypto.toAddress(creatorPublicKey);
- tradeData.creationTimestamp = creationTimestamp;
-
- Account atAccount = new Account(repository, atAddress);
- tradeData.qortBalance = atAccount.getConfirmedBalance(Asset.QORT);
-
- byte[] stateData = atStateData.getStateData();
- ByteBuffer dataByteBuffer = ByteBuffer.wrap(stateData);
- dataByteBuffer.position(MachineState.HEADER_LENGTH);
-
- /* Constants */
-
- // Skip creator's trade address
- dataByteBuffer.get(addressBytes);
- tradeData.qortalCreatorTradeAddress = Base58.encode(addressBytes);
- dataByteBuffer.position(dataByteBuffer.position() + 32 - addressBytes.length);
-
- // Creator's Ravencoin/foreign public key hash
- tradeData.creatorForeignPKH = new byte[20];
- dataByteBuffer.get(tradeData.creatorForeignPKH);
- dataByteBuffer.position(dataByteBuffer.position() + 32 - tradeData.creatorForeignPKH.length); // skip to 32 bytes
-
- // We don't use secret-B
- tradeData.hashOfSecretB = null;
-
- // Redeem payout
- tradeData.qortAmount = dataByteBuffer.getLong();
-
- // Expected RVN amount
- tradeData.expectedForeignAmount = dataByteBuffer.getLong();
-
- // Trade timeout
- tradeData.tradeTimeout = (int) dataByteBuffer.getLong();
-
- // Skip MESSAGE transaction type
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- // Skip expected 'trade' message length
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- // Skip expected 'redeem' message length
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- // Skip pointer to creator's address
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- // Skip pointer to partner's Qortal trade address
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- // Skip pointer to message sender
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- // Skip 'trade' message data offset for partner's Ravencoin PKH
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- // Skip pointer to partner's Ravencoin PKH
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- // Skip 'trade' message data offset for hash-of-secret-A
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- // Skip pointer to hash-of-secret-A
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- // Skip 'redeem' message data offset for partner's Qortal receiving address
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- // Skip pointer to message data
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- // Skip message data length
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- // Skip pointer to partner's receiving address
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- /* End of constants / begin variables */
-
- // Skip AT creator's address
- dataByteBuffer.position(dataByteBuffer.position() + 8 * 4);
-
- // Partner's trade address (if present)
- dataByteBuffer.get(addressBytes);
- String qortalRecipient = Base58.encode(addressBytes);
- dataByteBuffer.position(dataByteBuffer.position() + 32 - addressBytes.length);
-
- // Potential lockTimeA (if in trade mode)
- int lockTimeA = (int) dataByteBuffer.getLong();
-
- // AT refund timeout (probably only useful for debugging)
- int refundTimeout = (int) dataByteBuffer.getLong();
-
- // Trade-mode refund timestamp (AT 'timestamp' converted to Qortal block height)
- long tradeRefundTimestamp = dataByteBuffer.getLong();
-
- // Skip last transaction timestamp
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- // Skip block timestamp
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- // Skip transaction type
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- // Skip temporary result
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- // Skip temporary message sender
- dataByteBuffer.position(dataByteBuffer.position() + 8 * 4);
-
- // Skip message length
- dataByteBuffer.position(dataByteBuffer.position() + 8);
-
- // Skip temporary message data
- dataByteBuffer.position(dataByteBuffer.position() + 8 * 4);
-
- // Potential hash160 of secret A
- byte[] hashOfSecretA = new byte[20];
- dataByteBuffer.get(hashOfSecretA);
- dataByteBuffer.position(dataByteBuffer.position() + 32 - hashOfSecretA.length); // skip to 32 bytes
-
- // Potential partner's Ravencoin PKH
- byte[] partnerRavencoinPKH = new byte[20];
- dataByteBuffer.get(partnerRavencoinPKH);
- dataByteBuffer.position(dataByteBuffer.position() + 32 - partnerRavencoinPKH.length); // skip to 32 bytes
-
- // Partner's receiving address (if present)
- byte[] partnerReceivingAddress = new byte[25];
- dataByteBuffer.get(partnerReceivingAddress);
- dataByteBuffer.position(dataByteBuffer.position() + 32 - partnerReceivingAddress.length); // skip to 32 bytes
-
- // Trade AT's 'mode'
- long modeValue = dataByteBuffer.getLong();
- AcctMode mode = AcctMode.valueOf((int) (modeValue & 0xffL));
-
- /* End of variables */
-
- if (mode != null && mode != AcctMode.OFFERING) {
- tradeData.mode = mode;
- tradeData.refundTimeout = refundTimeout;
- tradeData.tradeRefundHeight = new Timestamp(tradeRefundTimestamp).blockHeight;
- tradeData.qortalPartnerAddress = qortalRecipient;
- tradeData.hashOfSecretA = hashOfSecretA;
- tradeData.partnerForeignPKH = partnerRavencoinPKH;
- tradeData.lockTimeA = lockTimeA;
-
- if (mode == AcctMode.REDEEMED)
- tradeData.qortalPartnerReceivingAddress = Base58.encode(partnerReceivingAddress);
- } else {
- tradeData.mode = AcctMode.OFFERING;
- }
-
- tradeData.duplicateDeprecated();
-
- return tradeData;
- }
-
- /** Returns 'offer' MESSAGE payload for trade partner to send to AT creator's trade address. */
- public static byte[] buildOfferMessage(byte[] partnerBitcoinPKH, byte[] hashOfSecretA, int lockTimeA) {
- byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA);
- return Bytes.concat(partnerBitcoinPKH, hashOfSecretA, lockTimeABytes);
- }
-
- /** Returns info extracted from 'offer' MESSAGE payload sent by trade partner to AT creator's trade address, or null if not valid. */
- public static OfferMessageData extractOfferMessageData(byte[] messageData) {
- if (messageData == null || messageData.length != OFFER_MESSAGE_LENGTH)
- return null;
-
- OfferMessageData offerMessageData = new OfferMessageData();
- offerMessageData.partnerRavencoinPKH = Arrays.copyOfRange(messageData, 0, 20);
- offerMessageData.hashOfSecretA = Arrays.copyOfRange(messageData, 20, 40);
- offerMessageData.lockTimeA = BitTwiddling.longFromBEBytes(messageData, 40);
-
- return offerMessageData;
- }
-
- /** Returns 'trade' MESSAGE payload for AT creator to send to AT. */
- public static byte[] buildTradeMessage(String partnerQortalTradeAddress, byte[] partnerBitcoinPKH, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) {
- byte[] data = new byte[TRADE_MESSAGE_LENGTH];
- byte[] partnerQortalAddressBytes = Base58.decode(partnerQortalTradeAddress);
- byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA);
- byte[] refundTimeoutBytes = BitTwiddling.toBEByteArray((long) refundTimeout);
-
- System.arraycopy(partnerQortalAddressBytes, 0, data, 0, partnerQortalAddressBytes.length);
- System.arraycopy(partnerBitcoinPKH, 0, data, 32, partnerBitcoinPKH.length);
- System.arraycopy(refundTimeoutBytes, 0, data, 56, refundTimeoutBytes.length);
- System.arraycopy(hashOfSecretA, 0, data, 64, hashOfSecretA.length);
- System.arraycopy(lockTimeABytes, 0, data, 88, lockTimeABytes.length);
-
- return data;
- }
-
- /** Returns 'cancel' MESSAGE payload for AT creator to cancel trade AT. */
- @Override
- public byte[] buildCancelMessage(String creatorQortalAddress) {
- byte[] data = new byte[CANCEL_MESSAGE_LENGTH];
- byte[] creatorQortalAddressBytes = Base58.decode(creatorQortalAddress);
-
- System.arraycopy(creatorQortalAddressBytes, 0, data, 0, creatorQortalAddressBytes.length);
-
- return data;
- }
-
- /** Returns 'redeem' MESSAGE payload for trade partner to send to AT. */
- public static byte[] buildRedeemMessage(byte[] secretA, String qortalReceivingAddress) {
- byte[] data = new byte[REDEEM_MESSAGE_LENGTH];
- byte[] qortalReceivingAddressBytes = Base58.decode(qortalReceivingAddress);
-
- System.arraycopy(secretA, 0, data, 0, secretA.length);
- System.arraycopy(qortalReceivingAddressBytes, 0, data, 32, qortalReceivingAddressBytes.length);
-
- return data;
- }
-
- /** Returns refund timeout (minutes) based on trade partner's 'offer' MESSAGE timestamp and P2SH-A locktime. */
- public static int calcRefundTimeout(long offerMessageTimestamp, int lockTimeA) {
- // refund should be triggered halfway between offerMessageTimestamp and lockTimeA
- return (int) ((lockTimeA - (offerMessageTimestamp / 1000L)) / 2L / 60L);
- }
-
- @Override
- public byte[] findSecretA(Repository repository, CrossChainTradeData crossChainTradeData) throws DataException {
- String atAddress = crossChainTradeData.qortalAtAddress;
- String redeemerAddress = crossChainTradeData.qortalPartnerAddress;
-
- // We don't have partner's public key so we check every message to AT
- List
- *
- */
-public class RavencoinACCTv1 implements ACCT {
-
- private static final Logger LOGGER = LogManager.getLogger(RavencoinACCTv1.class);
-
- public static final String NAME = RavencoinACCTv1.class.getSimpleName();
- public static final byte[] CODE_BYTES_HASH = HashCode.fromString("666fe31a07d7b603a0782686a108a17c8037b7f928b43ba89c7aac3022e612f7").asBytes(); // SHA256 of AT code bytes
-
- public static final int SECRET_LENGTH = 32;
-
- /** Value offset into AT segment where 'mode' variable (long) is stored. (Multiply by MachineState.VALUE_SIZE for byte offset). */
- private static final int MODE_VALUE_OFFSET = 61;
- /** Byte offset into AT state data where 'mode' variable (long) is stored. */
- public static final int MODE_BYTE_OFFSET = MachineState.HEADER_LENGTH + (MODE_VALUE_OFFSET * MachineState.VALUE_SIZE);
-
- public static class OfferMessageData {
- public byte[] partnerRavencoinPKH;
- public byte[] hashOfSecretA;
- public long lockTimeA;
- }
- public static final int OFFER_MESSAGE_LENGTH = 20 /*partnerRavencoinPKH*/ + 20 /*hashOfSecretA*/ + 8 /*lockTimeA*/;
- public static final int TRADE_MESSAGE_LENGTH = 32 /*partner's Qortal trade address (padded from 25 to 32)*/
- + 24 /*partner's Ravencoin PKH (padded from 20 to 24)*/
- + 8 /*AT trade timeout (minutes)*/
- + 24 /*hash of secret-A (padded from 20 to 24)*/
- + 8 /*lockTimeA*/;
- public static final int REDEEM_MESSAGE_LENGTH = 32 /*secret-A*/ + 32 /*partner's Qortal receiving address padded from 25 to 32*/;
- public static final int CANCEL_MESSAGE_LENGTH = 32 /*AT creator's Qortal address*/;
-
- private static RavencoinACCTv1 instance;
-
- private RavencoinACCTv1() {
- }
-
- public static synchronized RavencoinACCTv1 getInstance() {
- if (instance == null)
- instance = new RavencoinACCTv1();
-
- return instance;
- }
-
- @Override
- public byte[] getCodeBytesHash() {
- return CODE_BYTES_HASH;
- }
-
- @Override
- public int getModeByteOffset() {
- return MODE_BYTE_OFFSET;
- }
-
- @Override
- public ForeignBlockchain getBlockchain() {
- return Ravencoin.getInstance();
- }
-
- /**
- * Returns Qortal AT creation bytes for cross-chain trading AT.
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *