mirror of
https://github.com/Qortal/qortal.git
synced 2025-07-23 04:36:50 +00:00
Removed all cross-chain code. It's not needed for data nodes.
This commit is contained in:
@@ -1,133 +0,0 @@
|
||||
package org.qortal.test;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.crosschain.BitcoinACCTv1;
|
||||
import org.qortal.data.transaction.BaseTransactionData;
|
||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
||||
import org.qortal.data.transaction.PresenceTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.BlockUtils;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.TransactionUtils;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.PresenceTransaction;
|
||||
import org.qortal.transaction.PresenceTransaction.PresenceType;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class PresenceTests extends Common {
|
||||
|
||||
private static final byte[] BITCOIN_PKH = new byte[20];
|
||||
private static final byte[] HASH_OF_SECRET_B = new byte[32];
|
||||
|
||||
private PrivateKeyAccount signer;
|
||||
private Repository repository;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
|
||||
this.repository = RepositoryManager.getRepository();
|
||||
this.signer = Common.getTestAccount(this.repository, "bob");
|
||||
|
||||
// We need to create corresponding test trade offer
|
||||
byte[] creationBytes = BitcoinACCTv1.buildQortalAT(this.signer.getAddress(), BITCOIN_PKH, HASH_OF_SECRET_B,
|
||||
0L, 0L,
|
||||
7 * 24 * 60 * 60);
|
||||
|
||||
long txTimestamp = NTP.getTime();
|
||||
byte[] lastReference = this.signer.getLastReference();
|
||||
|
||||
long fee = 0;
|
||||
String name = "QORT-BTC cross-chain trade";
|
||||
String description = "Qortal-Bitcoin cross-chain trade";
|
||||
String atType = "ACCT";
|
||||
String tags = "QORT-BTC ACCT";
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, this.signer.getPublicKey(), fee, null);
|
||||
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, 1L, Asset.QORT);
|
||||
|
||||
Transaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
||||
|
||||
fee = deployAtTransaction.calcRecommendedFee();
|
||||
deployAtTransactionData.setFee(fee);
|
||||
|
||||
TransactionUtils.signAndImportValid(this.repository, deployAtTransactionData, this.signer);
|
||||
BlockUtils.mintBlock(this.repository);
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() throws DataException {
|
||||
if (this.repository != null)
|
||||
this.repository.close();
|
||||
|
||||
this.repository = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validityTests() throws DataException {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
byte[] timestampBytes = Longs.toByteArray(timestamp);
|
||||
|
||||
byte[] timestampSignature = this.signer.sign(timestampBytes);
|
||||
|
||||
assertTrue(isValid(Group.NO_GROUP, this.signer, timestamp, timestampSignature));
|
||||
|
||||
PrivateKeyAccount nonTrader = Common.getTestAccount(repository, "alice");
|
||||
assertFalse(isValid(Group.NO_GROUP, nonTrader, timestamp, timestampSignature));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void newestOnlyTests() throws DataException {
|
||||
long OLDER_TIMESTAMP = System.currentTimeMillis() - 2000L;
|
||||
long NEWER_TIMESTAMP = OLDER_TIMESTAMP + 1000L;
|
||||
|
||||
PresenceTransaction older = buildPresenceTransaction(Group.NO_GROUP, this.signer, OLDER_TIMESTAMP, null);
|
||||
older.computeNonce();
|
||||
TransactionUtils.signAndImportValid(repository, older.getTransactionData(), this.signer);
|
||||
|
||||
assertTrue(this.repository.getTransactionRepository().exists(older.getTransactionData().getSignature()));
|
||||
|
||||
PresenceTransaction newer = buildPresenceTransaction(Group.NO_GROUP, this.signer, NEWER_TIMESTAMP, null);
|
||||
newer.computeNonce();
|
||||
TransactionUtils.signAndImportValid(repository, newer.getTransactionData(), this.signer);
|
||||
|
||||
assertTrue(this.repository.getTransactionRepository().exists(newer.getTransactionData().getSignature()));
|
||||
assertFalse(this.repository.getTransactionRepository().exists(older.getTransactionData().getSignature()));
|
||||
}
|
||||
|
||||
private boolean isValid(int txGroupId, PrivateKeyAccount signer, long timestamp, byte[] timestampSignature) throws DataException {
|
||||
Transaction transaction = buildPresenceTransaction(txGroupId, signer, timestamp, timestampSignature);
|
||||
return transaction.isValidUnconfirmed() == ValidationResult.OK;
|
||||
}
|
||||
|
||||
private PresenceTransaction buildPresenceTransaction(int txGroupId, PrivateKeyAccount signer, long timestamp, byte[] timestampSignature) throws DataException {
|
||||
int nonce = 0;
|
||||
|
||||
byte[] reference = signer.getLastReference();
|
||||
byte[] creatorPublicKey = signer.getPublicKey();
|
||||
long fee = 0L;
|
||||
|
||||
if (timestampSignature == null)
|
||||
timestampSignature = this.signer.sign(Longs.toByteArray(timestamp));
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, creatorPublicKey, fee, null);
|
||||
PresenceTransactionData transactionData = new PresenceTransactionData(baseTransactionData, nonce, PresenceType.TRADE_BOT, timestampSignature);
|
||||
|
||||
return new PresenceTransaction(this.repository, transactionData);
|
||||
}
|
||||
|
||||
}
|
@@ -4,7 +4,6 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.crosschain.BitcoinACCTv1;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
@@ -394,25 +393,6 @@ public class RepositoryTests extends Common {
|
||||
}
|
||||
}
|
||||
|
||||
/** Specifically test LATERAL() usage in AT repository */
|
||||
@Test
|
||||
public void testAtLateral() {
|
||||
try (final HSQLDBRepository hsqldb = (HSQLDBRepository) RepositoryManager.getRepository()) {
|
||||
byte[] codeHash = BitcoinACCTv1.CODE_BYTES_HASH;
|
||||
Boolean isFinished = null;
|
||||
Integer dataByteOffset = null;
|
||||
Long expectedValue = null;
|
||||
Integer minimumFinalHeight = 2;
|
||||
Integer limit = null;
|
||||
Integer offset = null;
|
||||
Boolean reverse = null;
|
||||
|
||||
hsqldb.getATRepository().getMatchingFinalATStates(codeHash, isFinished, dataByteOffset, expectedValue, minimumFinalHeight, limit, offset, reverse);
|
||||
} catch (DataException e) {
|
||||
fail("HSQLDB bug #1580");
|
||||
}
|
||||
}
|
||||
|
||||
/** Specifically test LATERAL() usage in Chat repository */
|
||||
@Test
|
||||
public void testChatLateral() {
|
||||
|
@@ -1,42 +0,0 @@
|
||||
package org.qortal.test.api;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.api.ApiError;
|
||||
import org.qortal.api.resource.CrossChainResource;
|
||||
import org.qortal.crosschain.SupportedBlockchain;
|
||||
import org.qortal.test.common.ApiCommon;
|
||||
|
||||
public class CrossChainApiTests extends ApiCommon {
|
||||
|
||||
private static final SupportedBlockchain SPECIFIC_BLOCKCHAIN = null;
|
||||
|
||||
private CrossChainResource crossChainResource;
|
||||
|
||||
@Before
|
||||
public void buildResource() {
|
||||
this.crossChainResource = (CrossChainResource) ApiCommon.buildResource(CrossChainResource.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTradeOffers() {
|
||||
assertNoApiError((limit, offset, reverse) -> this.crossChainResource.getTradeOffers(SPECIFIC_BLOCKCHAIN, limit, offset, reverse));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCompletedTrades() {
|
||||
long minimumTimestamp = System.currentTimeMillis();
|
||||
assertNoApiError((limit, offset, reverse) -> this.crossChainResource.getCompletedTrades(SPECIFIC_BLOCKCHAIN, minimumTimestamp, limit, offset, reverse));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidGetCompletedTrades() {
|
||||
Integer limit = null;
|
||||
Integer offset = null;
|
||||
Boolean reverse = null;
|
||||
|
||||
assertApiError(ApiError.INVALID_CRITERIA, () -> this.crossChainResource.getCompletedTrades(SPECIFIC_BLOCKCHAIN, -1L /*minimumTimestamp*/, limit, offset, reverse));
|
||||
assertApiError(ApiError.INVALID_CRITERIA, () -> this.crossChainResource.getCompletedTrades(SPECIFIC_BLOCKCHAIN, 0L /*minimumTimestamp*/, limit, offset, reverse));
|
||||
}
|
||||
|
||||
}
|
@@ -1,115 +0,0 @@
|
||||
package org.qortal.test.crosschain;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.store.BlockStoreException;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crosschain.Bitcoin;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
|
||||
public class BitcoinTests extends Common {
|
||||
|
||||
private Bitcoin bitcoin;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings(); // TestNet3
|
||||
bitcoin = Bitcoin.getInstance();
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
Bitcoin.resetForTesting();
|
||||
bitcoin = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
||||
System.out.println(String.format("Starting BTC instance..."));
|
||||
System.out.println(String.format("BTC instance started"));
|
||||
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format("Bitcoin median blocktime: %d", bitcoin.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
|
||||
System.out.println(String.format("Bitcoin median blocktime: %d", bitcoin.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
long secondPeriod = afterSecond - afterFirst;
|
||||
|
||||
System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod));
|
||||
|
||||
assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
byte[] secret = BitcoinyHTLC.findHtlcSecret(bitcoin, p2shAddress);
|
||||
|
||||
assertNotNull(secret);
|
||||
assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = bitcoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull(transaction);
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
transaction = bitcoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull(transaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWalletBalance() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
Long balance = bitcoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(balance);
|
||||
|
||||
System.out.println(bitcoin.format(balance));
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
Long repeatBalance = bitcoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(repeatBalance);
|
||||
|
||||
System.out.println(bitcoin.format(repeatBalance));
|
||||
|
||||
assertEquals(balance, repeatBalance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String address = bitcoin.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
assertNotNull(address);
|
||||
|
||||
System.out.println(address);
|
||||
}
|
||||
|
||||
}
|
@@ -1,201 +0,0 @@
|
||||
package org.qortal.test.crosschain;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.security.Security;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.params.TestNet3Params;
|
||||
import org.bitcoinj.script.ScriptBuilder;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crosschain.BitcoinyTransaction;
|
||||
import org.qortal.crosschain.ElectrumX;
|
||||
import org.qortal.crosschain.TransactionHash;
|
||||
import org.qortal.crosschain.UnspentOutput;
|
||||
import org.qortal.crosschain.Bitcoin.BitcoinNet;
|
||||
import org.qortal.crosschain.ElectrumX.Server.ConnectionType;
|
||||
import org.qortal.utils.BitTwiddling;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
|
||||
public class ElectrumXTests {
|
||||
|
||||
static {
|
||||
// This must go before any calls to LogManager/Logger
|
||||
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
|
||||
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
||||
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
|
||||
}
|
||||
|
||||
private static final Map<ElectrumX.Server.ConnectionType, Integer> DEFAULT_ELECTRUMX_PORTS = new EnumMap<>(ElectrumX.Server.ConnectionType.class);
|
||||
static {
|
||||
DEFAULT_ELECTRUMX_PORTS.put(ConnectionType.TCP, 50001);
|
||||
DEFAULT_ELECTRUMX_PORTS.put(ConnectionType.SSL, 50002);
|
||||
}
|
||||
|
||||
private ElectrumX getInstance() {
|
||||
return new ElectrumX("Bitcoin-" + BitcoinNet.TEST3.name(), BitcoinNet.TEST3.getGenesisHash(), BitcoinNet.TEST3.getServers(), DEFAULT_ELECTRUMX_PORTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInstance() {
|
||||
ElectrumX electrumX = getInstance();
|
||||
assertNotNull(electrumX);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCurrentHeight() throws ForeignBlockchainException {
|
||||
ElectrumX electrumX = getInstance();
|
||||
|
||||
int height = electrumX.getCurrentHeight();
|
||||
|
||||
assertTrue(height > 10000);
|
||||
System.out.println("Current TEST3 height: " + height);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidRequest() {
|
||||
ElectrumX electrumX = getInstance();
|
||||
try {
|
||||
electrumX.getRawBlockHeaders(-1, -1);
|
||||
} catch (ForeignBlockchainException e) {
|
||||
// Should throw due to negative start block height
|
||||
return;
|
||||
}
|
||||
|
||||
fail("Negative start block height should cause error");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRecentBlocks() throws ForeignBlockchainException {
|
||||
ElectrumX electrumX = getInstance();
|
||||
|
||||
int height = electrumX.getCurrentHeight();
|
||||
assertTrue(height > 10000);
|
||||
|
||||
List<byte[]> recentBlockHeaders = electrumX.getRawBlockHeaders(height - 11, 11);
|
||||
|
||||
System.out.println(String.format("Returned %d recent blocks", recentBlockHeaders.size()));
|
||||
for (int i = 0; i < recentBlockHeaders.size(); ++i) {
|
||||
byte[] blockHeader = recentBlockHeaders.get(i);
|
||||
|
||||
// Timestamp(int) is at 4 + 32 + 32 = 68 bytes offset
|
||||
int offset = 4 + 32 + 32;
|
||||
int timestamp = BitTwiddling.intFromLEBytes(blockHeader, offset);
|
||||
System.out.println(String.format("Block %d timestamp: %d", height + i, timestamp));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetP2PKHBalance() throws ForeignBlockchainException {
|
||||
ElectrumX electrumX = getInstance();
|
||||
|
||||
Address address = Address.fromString(TestNet3Params.get(), "n3GNqMveyvaPvUbH469vDRadqpJMPc84JA");
|
||||
byte[] script = ScriptBuilder.createOutputScript(address).getProgram();
|
||||
long balance = electrumX.getConfirmedBalance(script);
|
||||
|
||||
assertTrue(balance > 0L);
|
||||
|
||||
System.out.println(String.format("TestNet address %s has balance: %d sats / %d.%08d BTC", address, balance, (balance / 100000000L), (balance % 100000000L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetP2SHBalance() throws ForeignBlockchainException {
|
||||
ElectrumX electrumX = getInstance();
|
||||
|
||||
Address address = Address.fromString(TestNet3Params.get(), "2N4szZUfigj7fSBCEX4PaC8TVbC5EvidaVF");
|
||||
byte[] script = ScriptBuilder.createOutputScript(address).getProgram();
|
||||
long balance = electrumX.getConfirmedBalance(script);
|
||||
|
||||
assertTrue(balance > 0L);
|
||||
|
||||
System.out.println(String.format("TestNet address %s has balance: %d sats / %d.%08d BTC", address, balance, (balance / 100000000L), (balance % 100000000L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnspentOutputs() throws ForeignBlockchainException {
|
||||
ElectrumX electrumX = getInstance();
|
||||
|
||||
Address address = Address.fromString(TestNet3Params.get(), "2N4szZUfigj7fSBCEX4PaC8TVbC5EvidaVF");
|
||||
byte[] script = ScriptBuilder.createOutputScript(address).getProgram();
|
||||
List<UnspentOutput> unspentOutputs = electrumX.getUnspentOutputs(script, false);
|
||||
|
||||
assertFalse(unspentOutputs.isEmpty());
|
||||
|
||||
for (UnspentOutput unspentOutput : unspentOutputs)
|
||||
System.out.println(String.format("TestNet address %s has unspent output at tx %s, output index %d", address, HashCode.fromBytes(unspentOutput.hash), unspentOutput.index));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRawTransaction() throws ForeignBlockchainException {
|
||||
ElectrumX electrumX = getInstance();
|
||||
|
||||
byte[] txHash = HashCode.fromString("7653fea9ffcd829d45ed2672938419a94951b08175982021e77d619b553f29af").asBytes();
|
||||
|
||||
byte[] rawTransactionBytes = electrumX.getRawTransaction(txHash);
|
||||
|
||||
assertFalse(rawTransactionBytes.length == 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnknownRawTransaction() {
|
||||
ElectrumX electrumX = getInstance();
|
||||
|
||||
byte[] txHash = HashCode.fromString("f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0").asBytes();
|
||||
|
||||
try {
|
||||
electrumX.getRawTransaction(txHash);
|
||||
fail("Bitcoin transaction should be unknown and hence throw exception");
|
||||
} catch (ForeignBlockchainException e) {
|
||||
if (!(e instanceof ForeignBlockchainException.NotFoundException))
|
||||
fail("Bitcoin transaction should be unknown and hence throw NotFoundException");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTransaction() throws ForeignBlockchainException {
|
||||
ElectrumX electrumX = getInstance();
|
||||
|
||||
String txHash = "7653fea9ffcd829d45ed2672938419a94951b08175982021e77d619b553f29af";
|
||||
|
||||
BitcoinyTransaction transaction = electrumX.getTransaction(txHash);
|
||||
|
||||
assertNotNull(transaction);
|
||||
assertTrue(transaction.txHash.equals(txHash));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnknownTransaction() {
|
||||
ElectrumX electrumX = getInstance();
|
||||
|
||||
String txHash = "f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0";
|
||||
|
||||
try {
|
||||
electrumX.getTransaction(txHash);
|
||||
fail("Bitcoin transaction should be unknown and hence throw exception");
|
||||
} catch (ForeignBlockchainException e) {
|
||||
if (!(e instanceof ForeignBlockchainException.NotFoundException))
|
||||
fail("Bitcoin transaction should be unknown and hence throw NotFoundException");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAddressTransactions() throws ForeignBlockchainException {
|
||||
ElectrumX electrumX = getInstance();
|
||||
|
||||
Address address = Address.fromString(TestNet3Params.get(), "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE");
|
||||
byte[] script = ScriptBuilder.createOutputScript(address).getProgram();
|
||||
|
||||
List<TransactionHash> transactionHashes = electrumX.getAddressTransactions(script, false);
|
||||
|
||||
assertFalse(transactionHashes.isEmpty());
|
||||
}
|
||||
|
||||
}
|
@@ -1,128 +0,0 @@
|
||||
package org.qortal.test.crosschain;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crosschain.Bitcoin;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
public class HtlcTests extends Common {
|
||||
|
||||
private Bitcoin bitcoin;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings(); // TestNet3
|
||||
bitcoin = Bitcoin.getInstance();
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
Bitcoin.resetForTesting();
|
||||
bitcoin = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
byte[] secret = BitcoinyHTLC.findHtlcSecret(bitcoin, p2shAddress);
|
||||
|
||||
assertNotNull(secret);
|
||||
assertArrayEquals("secret incorrect", expectedSecret, secret);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Doesn't work, to be fixed later")
|
||||
public void testHtlcSecretCaching() throws ForeignBlockchainException {
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
|
||||
do {
|
||||
// We need to perform fresh setup for 1st test
|
||||
Bitcoin.resetForTesting();
|
||||
bitcoin = Bitcoin.getInstance();
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long timestampBoundary = now / 30_000L;
|
||||
|
||||
byte[] secret1 = BitcoinyHTLC.findHtlcSecret(bitcoin, p2shAddress);
|
||||
long executionPeriod1 = System.currentTimeMillis() - now;
|
||||
|
||||
assertNotNull(secret1);
|
||||
assertArrayEquals("secret1 incorrect", expectedSecret, secret1);
|
||||
|
||||
assertTrue("1st execution period should not be instant!", executionPeriod1 > 10);
|
||||
|
||||
byte[] secret2 = BitcoinyHTLC.findHtlcSecret(bitcoin, p2shAddress);
|
||||
long executionPeriod2 = System.currentTimeMillis() - now - executionPeriod1;
|
||||
|
||||
assertNotNull(secret2);
|
||||
assertArrayEquals("secret2 incorrect", expectedSecret, secret2);
|
||||
|
||||
// Test is only valid if we've called within same timestampBoundary
|
||||
if (System.currentTimeMillis() / 30_000L != timestampBoundary)
|
||||
continue;
|
||||
|
||||
assertArrayEquals(secret1, secret2);
|
||||
|
||||
assertTrue("2st execution period should be effectively instant!", executionPeriod2 < 10);
|
||||
} while (false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDetermineHtlcStatus() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
BitcoinyHTLC.Status htlcStatus = BitcoinyHTLC.determineHtlcStatus(bitcoin.getBlockchainProvider(), p2shAddress, 1L);
|
||||
assertNotNull(htlcStatus);
|
||||
|
||||
System.out.println(String.format("HTLC %s status: %s", p2shAddress, htlcStatus.name()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHtlcStatusCaching() throws ForeignBlockchainException {
|
||||
do {
|
||||
// We need to perform fresh setup for 1st test
|
||||
Bitcoin.resetForTesting();
|
||||
bitcoin = Bitcoin.getInstance();
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long timestampBoundary = now / 30_000L;
|
||||
|
||||
// Won't ever exist
|
||||
String p2shAddress = bitcoin.deriveP2shAddress(Crypto.hash160(Longs.toByteArray(now)));
|
||||
|
||||
BitcoinyHTLC.Status htlcStatus1 = BitcoinyHTLC.determineHtlcStatus(bitcoin.getBlockchainProvider(), p2shAddress, 1L);
|
||||
long executionPeriod1 = System.currentTimeMillis() - now;
|
||||
|
||||
assertNotNull(htlcStatus1);
|
||||
assertTrue("1st execution period should not be instant!", executionPeriod1 > 10);
|
||||
|
||||
BitcoinyHTLC.Status htlcStatus2 = BitcoinyHTLC.determineHtlcStatus(bitcoin.getBlockchainProvider(), p2shAddress, 1L);
|
||||
long executionPeriod2 = System.currentTimeMillis() - now - executionPeriod1;
|
||||
|
||||
assertNotNull(htlcStatus2);
|
||||
assertEquals(htlcStatus1, htlcStatus2);
|
||||
|
||||
// Test is only valid if we've called within same timestampBoundary
|
||||
if (System.currentTimeMillis() / 30_000L != timestampBoundary)
|
||||
continue;
|
||||
|
||||
assertTrue("2st execution period should be effectively instant!", executionPeriod2 < 10);
|
||||
} while (false);
|
||||
}
|
||||
|
||||
}
|
@@ -1,114 +0,0 @@
|
||||
package org.qortal.test.crosschain;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.store.BlockStoreException;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crosschain.Litecoin;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
|
||||
public class LitecoinTests extends Common {
|
||||
|
||||
private Litecoin litecoin;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings(); // TestNet3
|
||||
litecoin = Litecoin.getInstance();
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
Litecoin.resetForTesting();
|
||||
litecoin = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format("Bitcoin median blocktime: %d", litecoin.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
|
||||
System.out.println(String.format("Bitcoin median blocktime: %d", litecoin.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
long secondPeriod = afterSecond - afterFirst;
|
||||
|
||||
System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod));
|
||||
|
||||
assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Doesn't work, to be fixed later")
|
||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
byte[] secret = BitcoinyHTLC.findHtlcSecret(litecoin, p2shAddress);
|
||||
|
||||
assertNotNull("secret not found", secret);
|
||||
assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = litecoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
transaction = litecoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWalletBalance() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
Long balance = litecoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(balance);
|
||||
|
||||
System.out.println(litecoin.format(balance));
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
Long repeatBalance = litecoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(repeatBalance);
|
||||
|
||||
System.out.println(litecoin.format(repeatBalance));
|
||||
|
||||
assertEquals(balance, repeatBalance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String address = litecoin.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
assertNotNull(address);
|
||||
|
||||
System.out.println(address);
|
||||
}
|
||||
|
||||
}
|
@@ -1,114 +0,0 @@
|
||||
package org.qortal.test.crosschain.apps;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.script.Script.ScriptType;
|
||||
import org.qortal.crosschain.Litecoin;
|
||||
import org.qortal.crosschain.Bitcoin;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
|
||||
public class BuildHTLC {
|
||||
|
||||
private static void usage(String error) {
|
||||
if (error != null)
|
||||
System.err.println(error);
|
||||
|
||||
System.err.println(String.format("usage: BuildHTLC (-b | -l) <refund-P2PKH> <amount> <redeem-P2PKH> <HASH160-of-secret> <locktime>"));
|
||||
System.err.println("where: -b means use Bitcoin, -l means use Litecoin");
|
||||
System.err.println(String.format("example: BuildHTLC -l "
|
||||
+ "msAfaDaJ8JiprxxFaAXEEPxKK3JaZCYpLv \\\n"
|
||||
+ "\t0.00008642 \\\n"
|
||||
+ "\tmrBpZYYGYMwUa8tRjTiXfP1ySqNXszWN5h \\\n"
|
||||
+ "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n"
|
||||
+ "\t1600000000"));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 6 || args.length > 6)
|
||||
usage(null);
|
||||
|
||||
Common.init();
|
||||
|
||||
Bitcoiny bitcoiny = null;
|
||||
NetworkParameters params = null;
|
||||
|
||||
Address refundAddress = null;
|
||||
Coin amount = null;
|
||||
Address redeemAddress = null;
|
||||
byte[] hashOfSecret = null;
|
||||
int lockTime = 0;
|
||||
|
||||
int argIndex = 0;
|
||||
try {
|
||||
switch (args[argIndex++]) {
|
||||
case "-b":
|
||||
bitcoiny = Bitcoin.getInstance();
|
||||
break;
|
||||
|
||||
case "-l":
|
||||
bitcoiny = Litecoin.getInstance();
|
||||
break;
|
||||
|
||||
default:
|
||||
usage("Only Bitcoin (-b) or Litecoin (-l) supported");
|
||||
}
|
||||
params = bitcoiny.getNetworkParameters();
|
||||
|
||||
refundAddress = Address.fromString(params, args[argIndex++]);
|
||||
if (refundAddress.getOutputScriptType() != ScriptType.P2PKH)
|
||||
usage("Refund address must be in P2PKH form");
|
||||
|
||||
amount = Coin.parseCoin(args[argIndex++]);
|
||||
|
||||
redeemAddress = Address.fromString(params, args[argIndex++]);
|
||||
if (redeemAddress.getOutputScriptType() != ScriptType.P2PKH)
|
||||
usage("Redeem address must be in P2PKH form");
|
||||
|
||||
hashOfSecret = HashCode.fromString(args[argIndex++]).asBytes();
|
||||
if (hashOfSecret.length != 20)
|
||||
usage("Hash of secret must be 20 bytes");
|
||||
|
||||
lockTime = Integer.parseInt(args[argIndex++]);
|
||||
int refundTimeoutDelay = lockTime - (int) (System.currentTimeMillis() / 1000L);
|
||||
if (refundTimeoutDelay < 600 || refundTimeoutDelay > 30 * 24 * 60 * 60)
|
||||
usage("Locktime (seconds) should be at between 10 minutes and 1 month from now");
|
||||
} catch (IllegalArgumentException e) {
|
||||
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
|
||||
}
|
||||
|
||||
System.out.println(String.format("Using %s", bitcoiny.getBlockchainProvider().getNetId()));
|
||||
|
||||
Coin p2shFee = Coin.valueOf(Common.getP2shFee(bitcoiny));
|
||||
if (p2shFee.isZero())
|
||||
return;
|
||||
|
||||
System.out.println(String.format("Refund address: %s", refundAddress));
|
||||
System.out.println(String.format("Amount: %s", amount.toPlainString()));
|
||||
System.out.println(String.format("Redeem address: %s", redeemAddress));
|
||||
System.out.println(String.format("Refund/redeem miner's fee: %s", bitcoiny.format(p2shFee)));
|
||||
System.out.println(String.format("Script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime));
|
||||
System.out.println(String.format("Hash of secret: %s", HashCode.fromBytes(hashOfSecret)));
|
||||
|
||||
byte[] redeemScriptBytes = BitcoinyHTLC.buildScript(refundAddress.getHash(), lockTime, redeemAddress.getHash(), hashOfSecret);
|
||||
System.out.println(String.format("Raw script bytes: %s", HashCode.fromBytes(redeemScriptBytes)));
|
||||
|
||||
String p2shAddress = bitcoiny.deriveP2shAddress(redeemScriptBytes);
|
||||
System.out.println(String.format("P2SH address: %s", p2shAddress));
|
||||
|
||||
amount = amount.add(p2shFee);
|
||||
|
||||
// Fund P2SH
|
||||
System.out.println(String.format("\nYou need to fund %s with %s (includes redeem/refund fee of %s)",
|
||||
p2shAddress, bitcoiny.format(amount), bitcoiny.format(p2shFee)));
|
||||
}
|
||||
|
||||
}
|
@@ -1,135 +0,0 @@
|
||||
package org.qortal.test.crosschain.apps;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.LegacyAddress;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.script.Script.ScriptType;
|
||||
import org.qortal.crosschain.Bitcoin;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.crosschain.Litecoin;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.crypto.Crypto;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
|
||||
public class CheckHTLC {
|
||||
|
||||
private static void usage(String error) {
|
||||
if (error != null)
|
||||
System.err.println(error);
|
||||
|
||||
System.err.println(String.format("usage: CheckHTLC (-b | -l) <P2SH-address> <refund-P2PKH> <amount> <redeem-P2PKH> <HASH160-of-secret> <locktime>"));
|
||||
System.err.println("where: -b means use Bitcoin, -l means use Litecoin");
|
||||
System.err.println(String.format("example: CheckP2SH -l "
|
||||
+ "2N4378NbEVGjmiUmoUD9g1vCY6kyx9tDUJ6 \\\n"
|
||||
+ "msAfaDaJ8JiprxxFaAXEEPxKK3JaZCYpLv \\\n"
|
||||
+ "\t0.00008642 \\\n"
|
||||
+ "\tmrBpZYYGYMwUa8tRjTiXfP1ySqNXszWN5h \\\n"
|
||||
+ "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n"
|
||||
+ "\t1600184800"));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 7 || args.length > 7)
|
||||
usage(null);
|
||||
|
||||
Common.init();
|
||||
|
||||
Bitcoiny bitcoiny = null;
|
||||
NetworkParameters params = null;
|
||||
|
||||
Address p2shAddress = null;
|
||||
Address refundAddress = null;
|
||||
Coin amount = null;
|
||||
Address redeemAddress = null;
|
||||
byte[] hashOfSecret = null;
|
||||
int lockTime = 0;
|
||||
|
||||
int argIndex = 0;
|
||||
try {
|
||||
switch (args[argIndex++]) {
|
||||
case "-b":
|
||||
bitcoiny = Bitcoin.getInstance();
|
||||
break;
|
||||
|
||||
case "-l":
|
||||
bitcoiny = Litecoin.getInstance();
|
||||
break;
|
||||
|
||||
default:
|
||||
usage("Only Bitcoin (-b) or Litecoin (-l) supported");
|
||||
}
|
||||
params = bitcoiny.getNetworkParameters();
|
||||
|
||||
p2shAddress = Address.fromString(params, args[argIndex++]);
|
||||
if (p2shAddress.getOutputScriptType() != ScriptType.P2SH)
|
||||
usage("P2SH address invalid");
|
||||
|
||||
refundAddress = Address.fromString(params, args[argIndex++]);
|
||||
if (refundAddress.getOutputScriptType() != ScriptType.P2PKH)
|
||||
usage("Refund address must be in P2PKH form");
|
||||
|
||||
amount = Coin.parseCoin(args[argIndex++]);
|
||||
|
||||
redeemAddress = Address.fromString(params, args[argIndex++]);
|
||||
if (redeemAddress.getOutputScriptType() != ScriptType.P2PKH)
|
||||
usage("Redeem address must be in P2PKH form");
|
||||
|
||||
hashOfSecret = HashCode.fromString(args[argIndex++]).asBytes();
|
||||
if (hashOfSecret.length != 20)
|
||||
usage("Hash of secret must be 20 bytes");
|
||||
|
||||
lockTime = Integer.parseInt(args[argIndex++]);
|
||||
} catch (IllegalArgumentException e) {
|
||||
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
|
||||
}
|
||||
|
||||
System.out.println(String.format("Using %s", bitcoiny.getBlockchainProvider().getNetId()));
|
||||
|
||||
Coin p2shFee = Coin.valueOf(Common.getP2shFee(bitcoiny));
|
||||
if (p2shFee.isZero())
|
||||
return;
|
||||
|
||||
System.out.println(String.format("P2SH address: %s", p2shAddress));
|
||||
System.out.println(String.format("Refund PKH: %s", refundAddress));
|
||||
System.out.println(String.format("Redeem/refund amount: %s", amount.toPlainString()));
|
||||
System.out.println(String.format("Redeem PKH: %s", redeemAddress));
|
||||
System.out.println(String.format("Hash of secret: %s", HashCode.fromBytes(hashOfSecret)));
|
||||
System.out.println(String.format("Script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime));
|
||||
|
||||
System.out.println(String.format("Redeem/refund miner's fee: %s", bitcoiny.format(p2shFee)));
|
||||
|
||||
byte[] redeemScriptBytes = BitcoinyHTLC.buildScript(refundAddress.getHash(), lockTime, redeemAddress.getHash(), hashOfSecret);
|
||||
System.out.println(String.format("Raw script bytes: %s", HashCode.fromBytes(redeemScriptBytes)));
|
||||
|
||||
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
|
||||
Address derivedP2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
|
||||
|
||||
if (!derivedP2shAddress.equals(p2shAddress)) {
|
||||
System.err.println(String.format("Derived P2SH address %s does not match given address %s", derivedP2shAddress, p2shAddress));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
amount = amount.add(p2shFee);
|
||||
|
||||
// Check network's median block time
|
||||
int medianBlockTime = Common.checkMedianBlockTime(bitcoiny, null);
|
||||
if (medianBlockTime == 0)
|
||||
return;
|
||||
|
||||
// Check P2SH is funded
|
||||
Common.getBalance(bitcoiny, p2shAddress.toString());
|
||||
|
||||
// Grab all unspent outputs
|
||||
Common.getUnspentOutputs(bitcoiny, p2shAddress.toString());
|
||||
|
||||
Common.determineHtlcStatus(bitcoiny, p2shAddress.toString(), amount.value);
|
||||
}
|
||||
|
||||
}
|
@@ -1,158 +0,0 @@
|
||||
package org.qortal.test.crosschain.apps;
|
||||
|
||||
import java.security.Security;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
|
||||
public abstract class Common {
|
||||
|
||||
public static void init() {
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
||||
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
|
||||
|
||||
Settings.fileInstance("settings-test.json");
|
||||
|
||||
NTP.setFixedOffset(0L);
|
||||
}
|
||||
|
||||
public static long getP2shFee(Bitcoiny bitcoiny) {
|
||||
long p2shFee;
|
||||
|
||||
try {
|
||||
p2shFee = bitcoiny.getP2shFee(null);
|
||||
} catch (ForeignBlockchainException e) {
|
||||
System.err.println(String.format("Unable to determine P2SH fee: %s", e.getMessage()));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return p2shFee;
|
||||
}
|
||||
|
||||
public static int checkMedianBlockTime(Bitcoiny bitcoiny, Integer lockTime) {
|
||||
int medianBlockTime;
|
||||
|
||||
try {
|
||||
medianBlockTime = bitcoiny.getMedianBlockTime();
|
||||
} catch (ForeignBlockchainException e) {
|
||||
System.err.println(String.format("Unable to determine median block time: %s", e.getMessage()));
|
||||
return 0;
|
||||
}
|
||||
|
||||
System.out.println(String.format("Median block time: %s", LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneOffset.UTC)));
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
if (now < medianBlockTime * 1000L) {
|
||||
System.out.println(String.format("Too soon (%s) based on median block time %s",
|
||||
LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneOffset.UTC),
|
||||
LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneOffset.UTC)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (lockTime != null && now < lockTime * 1000L) {
|
||||
System.err.println(String.format("Too soon (%s) based on lockTime %s",
|
||||
LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneOffset.UTC),
|
||||
LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return medianBlockTime;
|
||||
}
|
||||
|
||||
public static long getBalance(Bitcoiny bitcoiny, String address58) {
|
||||
long balance;
|
||||
|
||||
try {
|
||||
balance = bitcoiny.getConfirmedBalance(address58);
|
||||
} catch (ForeignBlockchainException e) {
|
||||
System.err.println(String.format("Unable to check address %s balance: %s", address58, e.getMessage()));
|
||||
return 0;
|
||||
}
|
||||
|
||||
System.out.println(String.format("Address %s balance: %s", address58, bitcoiny.format(balance)));
|
||||
|
||||
return balance;
|
||||
}
|
||||
|
||||
public static List<TransactionOutput> getUnspentOutputs(Bitcoiny bitcoiny, String address58) {
|
||||
List<TransactionOutput> unspentOutputs = Collections.emptyList();
|
||||
|
||||
try {
|
||||
unspentOutputs = bitcoiny.getUnspentOutputs(address58);
|
||||
} catch (ForeignBlockchainException e) {
|
||||
System.err.println(String.format("Can't find unspent outputs for %s: %s", address58, e.getMessage()));
|
||||
return unspentOutputs;
|
||||
}
|
||||
|
||||
System.out.println(String.format("Found %d output%s for %s",
|
||||
unspentOutputs.size(),
|
||||
(unspentOutputs.size() != 1 ? "s" : ""),
|
||||
address58));
|
||||
|
||||
for (TransactionOutput fundingOutput : unspentOutputs)
|
||||
System.out.println(String.format("Output %s:%d amount %s",
|
||||
HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex(),
|
||||
bitcoiny.format(fundingOutput.getValue())));
|
||||
|
||||
if (unspentOutputs.isEmpty())
|
||||
System.err.println(String.format("Can't use spent/unfunded %s", address58));
|
||||
|
||||
if (unspentOutputs.size() != 1)
|
||||
System.err.println(String.format("Expecting only one unspent output?"));
|
||||
|
||||
return unspentOutputs;
|
||||
}
|
||||
|
||||
public static BitcoinyHTLC.Status determineHtlcStatus(Bitcoiny bitcoiny, String address58, long minimumAmount) {
|
||||
BitcoinyHTLC.Status htlcStatus = null;
|
||||
|
||||
try {
|
||||
htlcStatus = BitcoinyHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), address58, minimumAmount);
|
||||
|
||||
System.out.println(String.format("HTLC status: %s", htlcStatus.name()));
|
||||
} catch (ForeignBlockchainException e) {
|
||||
System.err.println(String.format("Unable to determine HTLC status: %s", e.getMessage()));
|
||||
}
|
||||
|
||||
return htlcStatus;
|
||||
}
|
||||
|
||||
public static void broadcastTransaction(Bitcoiny bitcoiny, Transaction transaction) {
|
||||
byte[] rawTransactionBytes = transaction.bitcoinSerialize();
|
||||
|
||||
System.out.println(String.format("%nRaw transaction bytes:%n%s%n", HashCode.fromBytes(rawTransactionBytes).toString()));
|
||||
|
||||
for (int countDown = 5; countDown >= 1; --countDown) {
|
||||
System.out.print(String.format("\rBroadcasting transaction in %d second%s... use CTRL-C to abort ", countDown, (countDown != 1 ? "s" : "")));
|
||||
try {
|
||||
Thread.sleep(1000L);
|
||||
} catch (InterruptedException e) {
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
System.out.println("Broadcasting transaction... ");
|
||||
|
||||
try {
|
||||
bitcoiny.broadcastTransaction(transaction);
|
||||
} catch (ForeignBlockchainException e) {
|
||||
System.err.println(String.format("Failed to broadcast transaction: %s", e.getMessage()));
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,78 +0,0 @@
|
||||
package org.qortal.test.crosschain.apps;
|
||||
|
||||
import java.security.Security;
|
||||
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
||||
import org.qortal.crosschain.Bitcoin;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crosschain.Litecoin;
|
||||
import org.qortal.settings.Settings;
|
||||
|
||||
public class GetNextReceiveAddress {
|
||||
|
||||
static {
|
||||
// This must go before any calls to LogManager/Logger
|
||||
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
|
||||
}
|
||||
|
||||
private static void usage(String error) {
|
||||
if (error != null)
|
||||
System.err.println(error);
|
||||
|
||||
System.err.println(String.format("usage: GetNextReceiveAddress (-b | -l) <xprv/xpub>"));
|
||||
System.err.println(String.format("example (testnet): GetNextReceiveAddress -l tpubD6NzVbkrYhZ4X3jV96Wo3Kr8Au2v9cUUEmPRk1smwduFrRVfBjkkw49rRYjgff1fGSktFMfabbvv8b1dmfyLjjbDax6QGyxpsNsx5PXukCB"));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 2)
|
||||
usage(null);
|
||||
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
||||
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
|
||||
|
||||
Settings.fileInstance("settings-test.json");
|
||||
|
||||
Bitcoiny bitcoiny = null;
|
||||
String key58 = null;
|
||||
|
||||
int argIndex = 0;
|
||||
try {
|
||||
switch (args[argIndex++]) {
|
||||
case "-b":
|
||||
bitcoiny = Bitcoin.getInstance();
|
||||
break;
|
||||
|
||||
case "-l":
|
||||
bitcoiny = Litecoin.getInstance();
|
||||
break;
|
||||
|
||||
default:
|
||||
usage("Only Bitcoin (-b) or Litecoin (-l) supported");
|
||||
}
|
||||
|
||||
key58 = args[argIndex++];
|
||||
|
||||
if (!bitcoiny.isValidDeterministicKey(key58))
|
||||
usage("Not valid xprv/xpub/tprv/tpub");
|
||||
} catch (NumberFormatException | AddressFormatException e) {
|
||||
usage(String.format("Argument format exception: %s", e.getMessage()));
|
||||
}
|
||||
|
||||
System.out.println(String.format("Using %s", bitcoiny.getBlockchainProvider().getNetId()));
|
||||
|
||||
String receiveAddress = null;
|
||||
try {
|
||||
receiveAddress = bitcoiny.getUnusedReceiveAddress(key58);
|
||||
} catch (ForeignBlockchainException e) {
|
||||
System.err.println(String.format("Failed to determine next receive address: %s", e.getMessage()));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
System.out.println(String.format("Next receive address: %s", receiveAddress));
|
||||
}
|
||||
|
||||
}
|
@@ -1,84 +0,0 @@
|
||||
package org.qortal.test.crosschain.apps;
|
||||
|
||||
import java.security.Security;
|
||||
import java.util.List;
|
||||
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
||||
import org.qortal.crosschain.Bitcoin;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crosschain.Litecoin;
|
||||
import org.qortal.settings.Settings;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
|
||||
public class GetTransaction {
|
||||
|
||||
static {
|
||||
// This must go before any calls to LogManager/Logger
|
||||
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
|
||||
}
|
||||
|
||||
private static void usage(String error) {
|
||||
if (error != null)
|
||||
System.err.println(error);
|
||||
|
||||
System.err.println(String.format("usage: GetTransaction (-b | -l) <tx-hash>"));
|
||||
System.err.println(String.format("example (mainnet): GetTransaction -b 816407e79568f165f13e09e9912c5f2243e0a23a007cec425acedc2e89284660"));
|
||||
System.err.println(String.format("example (testnet): GetTransaction -b 3bfd17a492a4e3d6cb7204e17e20aca6c1ab82e1828bd1106eefbaf086fb8a4e"));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 2)
|
||||
usage(null);
|
||||
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
||||
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
|
||||
|
||||
Settings.fileInstance("settings-test.json");
|
||||
|
||||
Bitcoiny bitcoiny = null;
|
||||
byte[] transactionId = null;
|
||||
|
||||
int argIndex = 0;
|
||||
try {
|
||||
switch (args[argIndex++]) {
|
||||
case "-b":
|
||||
bitcoiny = Bitcoin.getInstance();
|
||||
break;
|
||||
|
||||
case "-l":
|
||||
bitcoiny = Litecoin.getInstance();
|
||||
break;
|
||||
|
||||
default:
|
||||
usage("Only Bitcoin (-b) or Litecoin (-l) supported");
|
||||
}
|
||||
|
||||
transactionId = HashCode.fromString(args[argIndex++]).asBytes();
|
||||
} catch (NumberFormatException | AddressFormatException e) {
|
||||
usage(String.format("Argument format exception: %s", e.getMessage()));
|
||||
}
|
||||
|
||||
System.out.println(String.format("Using %s", bitcoiny.getBlockchainProvider().getNetId()));
|
||||
|
||||
// Grab all outputs from transaction
|
||||
List<TransactionOutput> fundingOutputs;
|
||||
try {
|
||||
fundingOutputs = bitcoiny.getOutputs(transactionId);
|
||||
} catch (ForeignBlockchainException e) {
|
||||
System.out.println(String.format("Transaction not found (or error occurred)"));
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println(String.format("Found %d output%s", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : "")));
|
||||
|
||||
for (TransactionOutput fundingOutput : fundingOutputs)
|
||||
System.out.println(String.format("Output %d: %s", fundingOutput.getIndex(), fundingOutput.getValue().toPlainString()));
|
||||
}
|
||||
|
||||
}
|
@@ -1,82 +0,0 @@
|
||||
package org.qortal.test.crosschain.apps;
|
||||
|
||||
import java.security.Security;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
||||
import org.qortal.crosschain.*;
|
||||
import org.qortal.settings.Settings;
|
||||
|
||||
public class GetWalletTransactions {
|
||||
|
||||
static {
|
||||
// This must go before any calls to LogManager/Logger
|
||||
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
|
||||
}
|
||||
|
||||
private static void usage(String error) {
|
||||
if (error != null)
|
||||
System.err.println(error);
|
||||
|
||||
System.err.println(String.format("usage: GetWalletTransactions (-b | -l) <xprv/xpub>"));
|
||||
System.err.println(String.format("example (testnet): GetWalletTransactions -l tpubD6NzVbkrYhZ4X3jV96Wo3Kr8Au2v9cUUEmPRk1smwduFrRVfBjkkw49rRYjgff1fGSktFMfabbvv8b1dmfyLjjbDax6QGyxpsNsx5PXukCB"));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 2)
|
||||
usage(null);
|
||||
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
||||
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
|
||||
|
||||
Settings.fileInstance("settings-test.json");
|
||||
|
||||
Bitcoiny bitcoiny = null;
|
||||
String key58 = null;
|
||||
|
||||
int argIndex = 0;
|
||||
try {
|
||||
switch (args[argIndex++]) {
|
||||
case "-b":
|
||||
bitcoiny = Bitcoin.getInstance();
|
||||
break;
|
||||
|
||||
case "-l":
|
||||
bitcoiny = Litecoin.getInstance();
|
||||
break;
|
||||
|
||||
default:
|
||||
usage("Only Bitcoin (-b) or Litecoin (-l) supported");
|
||||
}
|
||||
|
||||
key58 = args[argIndex++];
|
||||
|
||||
if (!bitcoiny.isValidDeterministicKey(key58))
|
||||
usage("Not valid xprv/xpub/tprv/tpub");
|
||||
} catch (NumberFormatException | AddressFormatException e) {
|
||||
usage(String.format("Argument format exception: %s", e.getMessage()));
|
||||
}
|
||||
|
||||
System.out.println(String.format("Using %s", bitcoiny.getBlockchainProvider().getNetId()));
|
||||
|
||||
// Grab all outputs from transaction
|
||||
List<SimpleTransaction> transactions = null;
|
||||
try {
|
||||
transactions = bitcoiny.getWalletTransactions(key58);
|
||||
} catch (ForeignBlockchainException e) {
|
||||
System.err.println(String.format("Failed to obtain wallet transactions: %s", e.getMessage()));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
System.out.println(String.format("Found %d transaction%s", transactions.size(), (transactions.size() != 1 ? "s" : "")));
|
||||
|
||||
for (SimpleTransaction transaction : transactions.stream().sorted(Comparator.comparingInt(SimpleTransaction::getTimestamp)).collect(Collectors.toList()))
|
||||
System.out.println(String.format("%s", transaction));
|
||||
}
|
||||
|
||||
}
|
@@ -1,80 +0,0 @@
|
||||
package org.qortal.test.crosschain.apps;
|
||||
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.qortal.crosschain.Bitcoin;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.crosschain.Litecoin;
|
||||
|
||||
public class Pay {
|
||||
|
||||
private static void usage(String error) {
|
||||
if (error != null)
|
||||
System.err.println(error);
|
||||
|
||||
System.err.println(String.format("usage: Pay (-b | -l) <xprv58> <recipient> <LTC-amount>"));
|
||||
System.err.println("where: -b means use Bitcoin, -l means use Litecoin");
|
||||
System.err.println(String.format("example: Pay -l "
|
||||
+ "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ \\\n"
|
||||
+ "\tmsAfaDaJ8JiprxxFaAXEEPxKK3JaZCYpLv \\\n"
|
||||
+ "\t0.00008642"));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 4 || args.length > 4)
|
||||
usage(null);
|
||||
|
||||
Common.init();
|
||||
|
||||
Bitcoiny bitcoiny = null;
|
||||
NetworkParameters params = null;
|
||||
|
||||
String xprv58 = null;
|
||||
Address address = null;
|
||||
Coin amount = null;
|
||||
|
||||
int argIndex = 0;
|
||||
try {
|
||||
switch (args[argIndex++]) {
|
||||
case "-b":
|
||||
bitcoiny = Bitcoin.getInstance();
|
||||
break;
|
||||
|
||||
case "-l":
|
||||
bitcoiny = Litecoin.getInstance();
|
||||
break;
|
||||
|
||||
default:
|
||||
usage("Only Bitcoin (-b) or Litecoin (-l) supported");
|
||||
}
|
||||
params = bitcoiny.getNetworkParameters();
|
||||
|
||||
xprv58 = args[argIndex++];
|
||||
if (!bitcoiny.isValidDeterministicKey(xprv58))
|
||||
usage("xprv invalid");
|
||||
|
||||
address = Address.fromString(params, args[argIndex++]);
|
||||
|
||||
amount = Coin.parseCoin(args[argIndex++]);
|
||||
} catch (IllegalArgumentException e) {
|
||||
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
|
||||
}
|
||||
|
||||
System.out.println(String.format("Using %s", bitcoiny.getBlockchainProvider().getNetId()));
|
||||
|
||||
System.out.println(String.format("Address: %s", address));
|
||||
System.out.println(String.format("Amount: %s", amount.toPlainString()));
|
||||
|
||||
Transaction transaction = bitcoiny.buildSpend(xprv58, address.toString(), amount.value);
|
||||
if (transaction == null) {
|
||||
System.err.println("Insufficent funds");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
Common.broadcastTransaction(bitcoiny, transaction);
|
||||
}
|
||||
|
||||
}
|
@@ -1,166 +0,0 @@
|
||||
package org.qortal.test.crosschain.apps;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.LegacyAddress;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
import org.bitcoinj.script.Script.ScriptType;
|
||||
import org.qortal.crosschain.Bitcoin;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.crosschain.Litecoin;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.crypto.Crypto;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
|
||||
public class RedeemHTLC {
|
||||
|
||||
static {
|
||||
// This must go before any calls to LogManager/Logger
|
||||
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
|
||||
}
|
||||
|
||||
private static void usage(String error) {
|
||||
if (error != null)
|
||||
System.err.println(error);
|
||||
|
||||
System.err.println(String.format("usage: Redeem (-b | -l) <P2SH-address> <refund-P2PKH> <redeem-PRIVATE-key> <secret> <locktime> <output-address>"));
|
||||
System.err.println("where: -b means use Bitcoin, -l means use Litecoin");
|
||||
System.err.println(String.format("example: Redeem -l "
|
||||
+ "2N4378NbEVGjmiUmoUD9g1vCY6kyx9tDUJ6 \\\n"
|
||||
+ "\tmsAfaDaJ8JiprxxFaAXEEPxKK3JaZCYpLv \\\n"
|
||||
+ "\tefdaed23c4bc85c8ccae40d774af3c2a10391c648b6420cdd83cd44c27fcb5955201c64e372d \\\n"
|
||||
+ "\t5468697320737472696e672069732065786163746c7920333220627974657321 \\\n"
|
||||
+ "\t1600184800 \\\n"
|
||||
+ "\tmrBpZYYGYMwUa8tRjTiXfP1ySqNXszWN5h"));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 7 || args.length > 7)
|
||||
usage(null);
|
||||
|
||||
Common.init();
|
||||
|
||||
Bitcoiny bitcoiny = null;
|
||||
NetworkParameters params = null;
|
||||
|
||||
Address p2shAddress = null;
|
||||
Address refundAddress = null;
|
||||
byte[] redeemPrivateKey = null;
|
||||
byte[] secret = null;
|
||||
int lockTime = 0;
|
||||
Address outputAddress = null;
|
||||
|
||||
int argIndex = 0;
|
||||
try {
|
||||
switch (args[argIndex++]) {
|
||||
case "-b":
|
||||
bitcoiny = Bitcoin.getInstance();
|
||||
break;
|
||||
|
||||
case "-l":
|
||||
bitcoiny = Litecoin.getInstance();
|
||||
break;
|
||||
|
||||
default:
|
||||
usage("Only Bitcoin (-b) or Litecoin (-l) supported");
|
||||
}
|
||||
params = bitcoiny.getNetworkParameters();
|
||||
|
||||
p2shAddress = Address.fromString(params, args[argIndex++]);
|
||||
if (p2shAddress.getOutputScriptType() != ScriptType.P2SH)
|
||||
usage("P2SH address invalid");
|
||||
|
||||
refundAddress = Address.fromString(params, args[argIndex++]);
|
||||
if (refundAddress.getOutputScriptType() != ScriptType.P2PKH)
|
||||
usage("Refund address must be in P2PKH form");
|
||||
|
||||
redeemPrivateKey = HashCode.fromString(args[argIndex++]).asBytes();
|
||||
// Auto-trim
|
||||
if (redeemPrivateKey.length >= 37 && redeemPrivateKey.length <= 38)
|
||||
redeemPrivateKey = Arrays.copyOfRange(redeemPrivateKey, 1, 33);
|
||||
if (redeemPrivateKey.length != 32)
|
||||
usage("Redeem private key must be 32 bytes");
|
||||
|
||||
secret = HashCode.fromString(args[argIndex++]).asBytes();
|
||||
if (secret.length == 0)
|
||||
usage("Invalid secret bytes");
|
||||
|
||||
lockTime = Integer.parseInt(args[argIndex++]);
|
||||
|
||||
outputAddress = Address.fromString(params, args[argIndex++]);
|
||||
if (outputAddress.getOutputScriptType() != ScriptType.P2PKH)
|
||||
usage("Output address invalid");
|
||||
} catch (IllegalArgumentException e) {
|
||||
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
|
||||
}
|
||||
|
||||
System.out.println(String.format("Using %s", bitcoiny.getBlockchainProvider().getNetId()));
|
||||
|
||||
Coin p2shFee = Coin.valueOf(Common.getP2shFee(bitcoiny));
|
||||
if (p2shFee.isZero())
|
||||
return;
|
||||
|
||||
System.out.println(String.format("Attempting to redeem HTLC %s to %s", p2shAddress, outputAddress));
|
||||
|
||||
byte[] hashOfSecret = Crypto.hash160(secret);
|
||||
|
||||
ECKey redeemKey = ECKey.fromPrivate(redeemPrivateKey);
|
||||
Address redeemAddress = Address.fromKey(params, redeemKey, ScriptType.P2PKH);
|
||||
|
||||
byte[] redeemScriptBytes = BitcoinyHTLC.buildScript(refundAddress.getHash(), lockTime, redeemAddress.getHash(), hashOfSecret);
|
||||
|
||||
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
|
||||
Address derivedP2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
|
||||
|
||||
if (!derivedP2shAddress.equals(p2shAddress)) {
|
||||
System.err.println(String.format("Raw script bytes: %s", HashCode.fromBytes(redeemScriptBytes)));
|
||||
System.err.println(String.format("Derived P2SH address %s does not match given address %s", derivedP2shAddress, p2shAddress));
|
||||
System.exit(2);
|
||||
return;
|
||||
}
|
||||
|
||||
// Actual live processing...
|
||||
|
||||
int medianBlockTime = Common.checkMedianBlockTime(bitcoiny, null);
|
||||
if (medianBlockTime == 0)
|
||||
return;
|
||||
|
||||
// Check P2SH is funded
|
||||
long p2shBalance = Common.getBalance(bitcoiny, p2shAddress.toString());
|
||||
if (p2shBalance == 0)
|
||||
return;
|
||||
|
||||
// Grab all unspent outputs
|
||||
List<TransactionOutput> unspentOutputs = Common.getUnspentOutputs(bitcoiny, p2shAddress.toString());
|
||||
if (unspentOutputs.isEmpty())
|
||||
return;
|
||||
|
||||
Coin redeemAmount = Coin.valueOf(p2shBalance).subtract(p2shFee);
|
||||
|
||||
BitcoinyHTLC.Status htlcStatus = Common.determineHtlcStatus(bitcoiny, p2shAddress.toString(), redeemAmount.value);
|
||||
if (htlcStatus == null)
|
||||
return;
|
||||
|
||||
if (htlcStatus != BitcoinyHTLC.Status.FUNDED) {
|
||||
System.err.println(String.format("Expecting %s HTLC status, but actual status is %s", "FUNDED", htlcStatus.name()));
|
||||
System.exit(2);
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println(String.format("Spending %s of outputs, with %s as mining fee", bitcoiny.format(redeemAmount), bitcoiny.format(p2shFee)));
|
||||
|
||||
Transaction redeemTransaction = BitcoinyHTLC.buildRedeemTransaction(bitcoiny.getNetworkParameters(), redeemAmount, redeemKey,
|
||||
unspentOutputs, redeemScriptBytes, secret, outputAddress.getHash());
|
||||
|
||||
Common.broadcastTransaction(bitcoiny, redeemTransaction);
|
||||
}
|
||||
|
||||
}
|
@@ -1,163 +0,0 @@
|
||||
package org.qortal.test.crosschain.apps;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.LegacyAddress;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
import org.bitcoinj.script.Script.ScriptType;
|
||||
import org.qortal.crosschain.Litecoin;
|
||||
import org.qortal.crosschain.Bitcoin;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.crypto.Crypto;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
|
||||
public class RefundHTLC {
|
||||
|
||||
static {
|
||||
// This must go before any calls to LogManager/Logger
|
||||
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
|
||||
}
|
||||
|
||||
private static void usage(String error) {
|
||||
if (error != null)
|
||||
System.err.println(error);
|
||||
|
||||
System.err.println(String.format("usage: RefundHTLC (-b | -l) <P2SH-address> <refund-PRIVATE-KEY> <redeem-P2PKH> <HASH160-of-secret> <locktime> <output-address>"));
|
||||
System.err.println("where: -b means use Bitcoin, -l means use Litecoin");
|
||||
System.err.println(String.format("example: RefundHTLC -l "
|
||||
+ "2N4378NbEVGjmiUmoUD9g1vCY6kyx9tDUJ6 \\\n"
|
||||
+ "\tef8f31b49c31b4a140aebcd9605fded88cc2dad0844c4b984f9191a5a416f72d3801e16447b0 \\\n"
|
||||
+ "\tmrBpZYYGYMwUa8tRjTiXfP1ySqNXszWN5h \\\n"
|
||||
+ "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n"
|
||||
+ "\t1600184800 \\\n"
|
||||
+ "\tmoJtbbhs7T4Z5hmBH2iyKhGrCWBzQWS2CL"));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 7 || args.length > 7)
|
||||
usage(null);
|
||||
|
||||
Common.init();
|
||||
|
||||
Bitcoiny bitcoiny = null;
|
||||
NetworkParameters params = null;
|
||||
|
||||
Address p2shAddress = null;
|
||||
byte[] refundPrivateKey = null;
|
||||
Address redeemAddress = null;
|
||||
byte[] hashOfSecret = null;
|
||||
int lockTime = 0;
|
||||
Address outputAddress = null;
|
||||
|
||||
int argIndex = 0;
|
||||
try {
|
||||
switch (args[argIndex++]) {
|
||||
case "-b":
|
||||
bitcoiny = Bitcoin.getInstance();
|
||||
break;
|
||||
|
||||
case "-l":
|
||||
bitcoiny = Litecoin.getInstance();
|
||||
break;
|
||||
|
||||
default:
|
||||
usage("Only Bitcoin (-b) or Litecoin (-l) supported");
|
||||
}
|
||||
params = bitcoiny.getNetworkParameters();
|
||||
|
||||
p2shAddress = Address.fromString(params, args[argIndex++]);
|
||||
if (p2shAddress.getOutputScriptType() != ScriptType.P2SH)
|
||||
usage("P2SH address invalid");
|
||||
|
||||
refundPrivateKey = HashCode.fromString(args[argIndex++]).asBytes();
|
||||
// Auto-trim
|
||||
if (refundPrivateKey.length >= 37 && refundPrivateKey.length <= 38)
|
||||
refundPrivateKey = Arrays.copyOfRange(refundPrivateKey, 1, 33);
|
||||
if (refundPrivateKey.length != 32)
|
||||
usage("Refund private key must be 32 bytes");
|
||||
|
||||
redeemAddress = Address.fromString(params, args[argIndex++]);
|
||||
if (redeemAddress.getOutputScriptType() != ScriptType.P2PKH)
|
||||
usage("Redeem address must be in P2PKH form");
|
||||
|
||||
hashOfSecret = HashCode.fromString(args[argIndex++]).asBytes();
|
||||
if (hashOfSecret.length != 20)
|
||||
usage("HASH160 of secret must be 20 bytes");
|
||||
|
||||
lockTime = Integer.parseInt(args[argIndex++]);
|
||||
|
||||
outputAddress = Address.fromString(params, args[argIndex++]);
|
||||
if (outputAddress.getOutputScriptType() != ScriptType.P2PKH)
|
||||
usage("Output address invalid");
|
||||
} catch (IllegalArgumentException e) {
|
||||
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
|
||||
}
|
||||
|
||||
System.out.println(String.format("Using %s", bitcoiny.getBlockchainProvider().getNetId()));
|
||||
|
||||
Coin p2shFee = Coin.valueOf(Common.getP2shFee(bitcoiny));
|
||||
if (p2shFee.isZero())
|
||||
return;
|
||||
|
||||
System.out.println(String.format("Attempting to refund HTLC %s to %s", p2shAddress, outputAddress));
|
||||
|
||||
ECKey refundKey = ECKey.fromPrivate(refundPrivateKey);
|
||||
Address refundAddress = Address.fromKey(params, refundKey, ScriptType.P2PKH);
|
||||
|
||||
byte[] redeemScriptBytes = BitcoinyHTLC.buildScript(refundAddress.getHash(), lockTime, redeemAddress.getHash(), hashOfSecret);
|
||||
|
||||
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
|
||||
Address derivedP2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
|
||||
|
||||
if (!derivedP2shAddress.equals(p2shAddress)) {
|
||||
System.err.println(String.format("Raw script bytes: %s", HashCode.fromBytes(redeemScriptBytes)));
|
||||
System.err.println(String.format("Derived P2SH address %s does not match given address %s", derivedP2shAddress, p2shAddress));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
// Actual live processing...
|
||||
|
||||
int medianBlockTime = Common.checkMedianBlockTime(bitcoiny, lockTime);
|
||||
if (medianBlockTime == 0)
|
||||
return;
|
||||
|
||||
// Check P2SH is funded
|
||||
long p2shBalance = Common.getBalance(bitcoiny, p2shAddress.toString());
|
||||
if (p2shBalance == 0)
|
||||
return;
|
||||
|
||||
// Grab all unspent outputs
|
||||
List<TransactionOutput> unspentOutputs = Common.getUnspentOutputs(bitcoiny, p2shAddress.toString());
|
||||
if (unspentOutputs.isEmpty())
|
||||
return;
|
||||
|
||||
Coin refundAmount = Coin.valueOf(p2shBalance).subtract(p2shFee);
|
||||
|
||||
BitcoinyHTLC.Status htlcStatus = Common.determineHtlcStatus(bitcoiny, p2shAddress.toString(), refundAmount.value);
|
||||
if (htlcStatus == null)
|
||||
return;
|
||||
|
||||
if (htlcStatus != BitcoinyHTLC.Status.FUNDED) {
|
||||
System.err.println(String.format("Expecting %s HTLC status, but actual status is %s", "FUNDED", htlcStatus.name()));
|
||||
System.exit(2);
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println(String.format("Spending %s of outputs, with %s as mining fee", bitcoiny.format(refundAmount), bitcoiny.format(p2shFee)));
|
||||
|
||||
Transaction refundTransaction = BitcoinyHTLC.buildRefundTransaction(bitcoiny.getNetworkParameters(), refundAmount, refundKey,
|
||||
unspentOutputs, redeemScriptBytes, lockTime, outputAddress.getHash());
|
||||
|
||||
Common.broadcastTransaction(bitcoiny, refundTransaction);
|
||||
}
|
||||
|
||||
}
|
@@ -1,795 +0,0 @@
|
||||
package org.qortal.test.crosschain.bitcoinv1;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.block.Block;
|
||||
import org.qortal.crosschain.BitcoinACCTv1;
|
||||
import org.qortal.crosschain.AcctMode;
|
||||
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.BaseTransactionData;
|
||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
||||
import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.BlockUtils;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.TransactionUtils;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
public class BitcoinACCTv1Tests extends Common {
|
||||
|
||||
public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes();
|
||||
public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a
|
||||
public static final byte[] secretB = "This string is roughly 32 bytes?".getBytes();
|
||||
public static final byte[] hashOfSecretB = Crypto.hash160(secretB); // 31f0dd71decf59bbc8ef0661f4030479255cfa58
|
||||
public static final byte[] bitcoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes();
|
||||
public static final int tradeTimeout = 20; // blocks
|
||||
public static final long redeemAmount = 80_40200000L;
|
||||
public static final long fundingAmount = 123_45600000L;
|
||||
public static final long bitcoinAmount = 864200L; // 0.00864200 BTC
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompile() {
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
||||
|
||||
byte[] creationBytes = BitcoinACCTv1.buildQortalAT(tradeAccount.getAddress(), bitcoinPublicKeyHash, hashOfSecretB, redeemAmount, bitcoinAmount, tradeTimeout);
|
||||
assertNotNull(creationBytes);
|
||||
|
||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeploy() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
|
||||
long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee();
|
||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
expectedBalance = fundingAmount;
|
||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
expectedBalance = partnersInitialBalance;
|
||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
// Test orphaning
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
expectedBalance = deployersInitialBalance;
|
||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
expectedBalance = 0;
|
||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
expectedBalance = partnersInitialBalance;
|
||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testOfferCancel() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||
|
||||
// Send creator's address to AT, instead of typical partner's address
|
||||
byte[] messageData = BitcoinACCTv1.getInstance().buildCancelMessage(deployer.getAddress());
|
||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
||||
|
||||
// AT should process 'cancel' message in next block
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertTrue(atData.getIsFinished());
|
||||
|
||||
// AT should be in CANCELLED mode
|
||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
||||
|
||||
// Check balances
|
||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee;
|
||||
|
||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
||||
|
||||
// Test orphaning
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
// Check balances
|
||||
long expectedBalance = deployersPostDeploymentBalance - messageFee;
|
||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testOfferCancelInvalidLength() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||
|
||||
// Instead of sending creator's address to AT, send too-short/invalid message
|
||||
byte[] messageData = new byte[7];
|
||||
RANDOM.nextBytes(messageData);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
||||
|
||||
// AT should process 'cancel' message in next block
|
||||
// As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertTrue(atData.getIsFinished());
|
||||
|
||||
// AT should be in CANCELLED mode
|
||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testTradingInfoProcessing() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT
|
||||
byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||
|
||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
||||
|
||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
|
||||
// AT should be in TRADE mode
|
||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
||||
|
||||
// Check hashOfSecretA was extracted correctly
|
||||
assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA));
|
||||
|
||||
// Check trade partner Qortal address was extracted correctly
|
||||
assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress);
|
||||
|
||||
// Check trade partner's Bitcoin PKH was extracted correctly
|
||||
assertTrue(Arrays.equals(bitcoinPublicKeyHash, tradeData.partnerForeignPKH));
|
||||
|
||||
// Test orphaning
|
||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
||||
|
||||
// Check balances
|
||||
long expectedBalance = deployersPostDeploymentBalance;
|
||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
||||
}
|
||||
}
|
||||
|
||||
// TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED)
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testIncorrectTradeSender() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT BUT NOT FROM AT CREATOR
|
||||
byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
||||
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
long expectedBalance = partnersInitialBalance;
|
||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
|
||||
// AT should still be in OFFER mode
|
||||
assertEquals(AcctMode.OFFERING, tradeData.mode);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testAutomaticTradeRefund() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT
|
||||
byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||
|
||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
||||
|
||||
// Check refund
|
||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||
|
||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertTrue(atData.getIsFinished());
|
||||
|
||||
// AT should be in REFUNDED mode
|
||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.REFUNDED, tradeData.mode);
|
||||
|
||||
// Test orphaning
|
||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
||||
|
||||
// Check balances
|
||||
long expectedBalance = deployersPostDeploymentBalance;
|
||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testCorrectSecretsCorrectSender() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT
|
||||
byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||
|
||||
// Give AT time to process message
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Send correct secrets to AT, from correct account
|
||||
messageData = BitcoinACCTv1.buildRedeemMessage(secretA, secretB, partner.getAddress());
|
||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
||||
|
||||
// AT should send funds in the next block
|
||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertTrue(atData.getIsFinished());
|
||||
|
||||
// AT should be in REDEEMED mode
|
||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.REDEEMED, tradeData.mode);
|
||||
|
||||
// Check balances
|
||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount;
|
||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
// Orphan redeem
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
// Check balances
|
||||
expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
// Check AT state
|
||||
ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
|
||||
assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData()));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testCorrectSecretsIncorrectSender() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT
|
||||
byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||
|
||||
// Give AT time to process message
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Send correct secrets to AT, but from wrong account
|
||||
messageData = BitcoinACCTv1.buildRedeemMessage(secretA, secretB, partner.getAddress());
|
||||
messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
||||
|
||||
// AT should NOT send funds in the next block
|
||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is NOT finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertFalse(atData.getIsFinished());
|
||||
|
||||
// AT should still be in TRADE mode
|
||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
||||
|
||||
// Check balances
|
||||
long expectedBalance = partnersInitialBalance;
|
||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
// Check eventual refund
|
||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testIncorrectSecretsCorrectSender() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT
|
||||
byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||
|
||||
// Give AT time to process message
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Send incorrect secrets to AT, from correct account
|
||||
byte[] wrongSecret = new byte[32];
|
||||
RANDOM.nextBytes(wrongSecret);
|
||||
messageData = BitcoinACCTv1.buildRedeemMessage(wrongSecret, secretB, partner.getAddress());
|
||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
||||
|
||||
// AT should NOT send funds in the next block
|
||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is NOT finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertFalse(atData.getIsFinished());
|
||||
|
||||
// AT should still be in TRADE mode
|
||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
||||
|
||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
// Send incorrect secrets to AT, from correct account
|
||||
messageData = BitcoinACCTv1.buildRedeemMessage(secretA, wrongSecret, partner.getAddress());
|
||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
||||
|
||||
// AT should NOT send funds in the next block
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is NOT finished
|
||||
atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertFalse(atData.getIsFinished());
|
||||
|
||||
// AT should still be in TRADE mode
|
||||
tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
||||
|
||||
// Check balances
|
||||
expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() * 2;
|
||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
// Check eventual refund
|
||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testCorrectSecretsCorrectSenderInvalidMessageLength() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT
|
||||
byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||
|
||||
// Give AT time to process message
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Send correct secrets to AT, from correct account, but missing receive address, hence incorrect length
|
||||
messageData = Bytes.concat(secretA, secretB);
|
||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
||||
|
||||
// AT should NOT send funds in the next block
|
||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is NOT finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertFalse(atData.getIsFinished());
|
||||
|
||||
// AT should be in TRADING mode
|
||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testDescribeDeployed() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
|
||||
List<ATData> executableAts = repository.getATRepository().getAllExecutableATs();
|
||||
|
||||
for (ATData atData : executableAts) {
|
||||
String atAddress = atData.getATAddress();
|
||||
byte[] codeBytes = atData.getCodeBytes();
|
||||
byte[] codeHash = Crypto.digest(codeBytes);
|
||||
|
||||
System.out.println(String.format("%s: code length: %d byte%s, code hash: %s",
|
||||
atAddress,
|
||||
codeBytes.length,
|
||||
(codeBytes.length != 1 ? "s": ""),
|
||||
HashCode.fromBytes(codeHash)));
|
||||
|
||||
// Not one of ours?
|
||||
if (!Arrays.equals(codeHash, BitcoinACCTv1.CODE_BYTES_HASH))
|
||||
continue;
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calcTestLockTimeA(long messageTimestamp) {
|
||||
return (int) (messageTimestamp / 1000L + tradeTimeout * 60);
|
||||
}
|
||||
|
||||
private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException {
|
||||
byte[] creationBytes = BitcoinACCTv1.buildQortalAT(tradeAddress, bitcoinPublicKeyHash, hashOfSecretB, redeemAmount, bitcoinAmount, tradeTimeout);
|
||||
|
||||
long txTimestamp = System.currentTimeMillis();
|
||||
byte[] lastReference = deployer.getLastReference();
|
||||
|
||||
if (lastReference == null) {
|
||||
System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
Long fee = null;
|
||||
String name = "QORT-BTC cross-chain trade";
|
||||
String description = String.format("Qortal-Bitcoin cross-chain trade");
|
||||
String atType = "ACCT";
|
||||
String tags = "QORT-BTC ACCT";
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null);
|
||||
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
||||
|
||||
fee = deployAtTransaction.calcRecommendedFee();
|
||||
deployAtTransactionData.setFee(fee);
|
||||
|
||||
TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer);
|
||||
|
||||
return deployAtTransaction;
|
||||
}
|
||||
|
||||
private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException {
|
||||
long txTimestamp = System.currentTimeMillis();
|
||||
byte[] lastReference = sender.getLastReference();
|
||||
|
||||
if (lastReference == null) {
|
||||
System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
Long fee = null;
|
||||
int version = 4;
|
||||
int nonce = 0;
|
||||
long amount = 0;
|
||||
Long assetId = null; // because amount is zero
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null);
|
||||
TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false);
|
||||
|
||||
MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
|
||||
|
||||
fee = messageTransaction.calcRecommendedFee();
|
||||
messageTransactionData.setFee(fee);
|
||||
|
||||
TransactionUtils.signAndMint(repository, messageTransactionData, sender);
|
||||
|
||||
return messageTransaction;
|
||||
}
|
||||
|
||||
private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException {
|
||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||
int refundTimeout = tradeTimeout * 3 / 4 + 1; // close enough
|
||||
|
||||
// AT should automatically refund deployer after 'refundTimeout' blocks
|
||||
for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount)
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range
|
||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee;
|
||||
|
||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
||||
}
|
||||
|
||||
private void describeAt(Repository repository, String atAddress) throws DataException {
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
|
||||
Function<Long, String> epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
|
||||
int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||
|
||||
System.out.print(String.format("%s:\n"
|
||||
+ "\tmode: %s\n"
|
||||
+ "\tcreator: %s,\n"
|
||||
+ "\tcreation timestamp: %s,\n"
|
||||
+ "\tcurrent balance: %s QORT,\n"
|
||||
+ "\tis finished: %b,\n"
|
||||
+ "\tHASH160 of secret-B: %s,\n"
|
||||
+ "\tredeem payout: %s QORT,\n"
|
||||
+ "\texpected bitcoin: %s BTC,\n"
|
||||
+ "\tcurrent block height: %d,\n",
|
||||
tradeData.qortalAtAddress,
|
||||
tradeData.mode,
|
||||
tradeData.qortalCreator,
|
||||
epochMilliFormatter.apply(tradeData.creationTimestamp),
|
||||
Amounts.prettyAmount(tradeData.qortBalance),
|
||||
atData.getIsFinished(),
|
||||
HashCode.fromBytes(tradeData.hashOfSecretB).toString().substring(0, 40),
|
||||
Amounts.prettyAmount(tradeData.qortAmount),
|
||||
Amounts.prettyAmount(tradeData.expectedForeignAmount),
|
||||
currentBlockHeight));
|
||||
|
||||
if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) {
|
||||
System.out.println(String.format("\trefund height: block %d,\n"
|
||||
+ "\tHASH160 of secret-A: %s,\n"
|
||||
+ "\tBitcoin P2SH-A nLockTime: %d (%s),\n"
|
||||
+ "\tBitcoin P2SH-B nLockTime: %d (%s),\n"
|
||||
+ "\ttrade partner: %s",
|
||||
tradeData.tradeRefundHeight,
|
||||
HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40),
|
||||
tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L),
|
||||
tradeData.lockTimeB, epochMilliFormatter.apply(tradeData.lockTimeB * 1000L),
|
||||
tradeData.qortalPartnerAddress));
|
||||
}
|
||||
}
|
||||
|
||||
private PrivateKeyAccount createTradeAccount(Repository repository) {
|
||||
// We actually use a known test account with funds to avoid PoW compute
|
||||
return Common.getTestAccount(repository, "alice");
|
||||
}
|
||||
|
||||
}
|
@@ -1,169 +0,0 @@
|
||||
package org.qortal.test.crosschain.bitcoinv1;
|
||||
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
import org.bitcoinj.core.LegacyAddress;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.crosschain.Bitcoin;
|
||||
import org.qortal.crosschain.BitcoinACCTv1;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.data.transaction.BaseTransactionData;
|
||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryFactory;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.repository.hsqldb.HSQLDBRepositoryFactory;
|
||||
import org.qortal.test.crosschain.apps.Common;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.transform.transaction.TransactionTransformer;
|
||||
import org.qortal.utils.Amounts;
|
||||
import org.qortal.utils.Base58;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
|
||||
public class DeployAT {
|
||||
|
||||
private static void usage(String error) {
|
||||
if (error != null)
|
||||
System.err.println(error);
|
||||
|
||||
System.err.println(String.format("usage: DeployAT <your Qortal PRIVATE key> <QORT amount> <AT funding amount> <BTC amount> <your Bitcoin PKH/P2PKH> <HASH160-of-secret> <trade-timeout>"));
|
||||
System.err.println(String.format("example: DeployAT "
|
||||
+ "7Eztjz2TsxwbrWUYEaSdLbASKQGTfK2rR7ViFc5gaiZw \\\n"
|
||||
+ "\t10 \\\n"
|
||||
+ "\t10.1 \\\n"
|
||||
+ "\t0.00864200 \\\n"
|
||||
+ "\t750b06757a2448b8a4abebaa6e4662833fd5ddbb (or mrBpZYYGYMwUa8tRjTiXfP1ySqNXszWN5h) \\\n"
|
||||
+ "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n"
|
||||
+ "\t10080"));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 7)
|
||||
usage(null);
|
||||
|
||||
Common.init();
|
||||
|
||||
Bitcoiny bitcoiny = Bitcoin.getInstance();
|
||||
NetworkParameters params = bitcoiny.getNetworkParameters();
|
||||
|
||||
byte[] refundPrivateKey = null;
|
||||
long redeemAmount = 0;
|
||||
long fundingAmount = 0;
|
||||
long expectedBitcoin = 0;
|
||||
byte[] bitcoinPublicKeyHash = null;
|
||||
byte[] hashOfSecret = null;
|
||||
int tradeTimeout = 0;
|
||||
|
||||
int argIndex = 0;
|
||||
try {
|
||||
refundPrivateKey = Base58.decode(args[argIndex++]);
|
||||
if (refundPrivateKey.length != 32)
|
||||
usage("Refund private key must be 32 bytes");
|
||||
|
||||
redeemAmount = Long.parseLong(args[argIndex++]);
|
||||
if (redeemAmount <= 0)
|
||||
usage("QORT amount must be positive");
|
||||
|
||||
fundingAmount = Long.parseLong(args[argIndex++]);
|
||||
if (fundingAmount <= redeemAmount)
|
||||
usage("AT funding amount must be greater than QORT redeem amount");
|
||||
|
||||
expectedBitcoin = Long.parseLong(args[argIndex++]);
|
||||
if (expectedBitcoin <= 0)
|
||||
usage("Expected BTC amount must be positive");
|
||||
|
||||
String bitcoinPKHish = args[argIndex++];
|
||||
// Try P2PKH first
|
||||
try {
|
||||
Address bitcoinAddress = LegacyAddress.fromBase58(params, bitcoinPKHish);
|
||||
bitcoinPublicKeyHash = bitcoinAddress.getHash();
|
||||
} catch (AddressFormatException e) {
|
||||
// Try parsing as PKH hex string instead
|
||||
bitcoinPublicKeyHash = HashCode.fromString(bitcoinPKHish).asBytes();
|
||||
}
|
||||
if (bitcoinPublicKeyHash.length != 20)
|
||||
usage("Bitcoin PKH must be 20 bytes");
|
||||
|
||||
hashOfSecret = HashCode.fromString(args[argIndex++]).asBytes();
|
||||
if (hashOfSecret.length != 20)
|
||||
usage("Hash of secret must be 20 bytes");
|
||||
|
||||
tradeTimeout = Integer.parseInt(args[argIndex++]);
|
||||
if (tradeTimeout < 60 || tradeTimeout > 50000)
|
||||
usage("Trade timeout (minutes) must be between 60 and 50000");
|
||||
} catch (IllegalArgumentException e) {
|
||||
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
|
||||
}
|
||||
|
||||
try {
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl());
|
||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
||||
} catch (DataException e) {
|
||||
System.err.println(String.format("Repository start-up issue: %s", e.getMessage()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount refundAccount = new PrivateKeyAccount(repository, refundPrivateKey);
|
||||
System.out.println(String.format("Refund Qortal address: %s", refundAccount.getAddress()));
|
||||
|
||||
System.out.println(String.format("QORT redeem amount: %s", Amounts.prettyAmount(redeemAmount)));
|
||||
|
||||
System.out.println(String.format("AT funding amount: %s", Amounts.prettyAmount(fundingAmount)));
|
||||
|
||||
System.out.println(String.format("HASH160 of secret: %s", HashCode.fromBytes(hashOfSecret)));
|
||||
|
||||
// Deploy AT
|
||||
byte[] creationBytes = BitcoinACCTv1.buildQortalAT(refundAccount.getAddress(), bitcoinPublicKeyHash, hashOfSecret, redeemAmount, expectedBitcoin, tradeTimeout);
|
||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
||||
|
||||
long txTimestamp = System.currentTimeMillis();
|
||||
byte[] lastReference = refundAccount.getLastReference();
|
||||
|
||||
if (lastReference == null) {
|
||||
System.err.println(String.format("Qortal account %s has no last reference", refundAccount.getAddress()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
Long fee = null;
|
||||
String name = "QORT-BTC cross-chain trade";
|
||||
String description = String.format("Qortal-Bitcoin cross-chain trade");
|
||||
String atType = "ACCT";
|
||||
String tags = "QORT-BTC ACCT";
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, refundAccount.getPublicKey(), fee, null);
|
||||
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
|
||||
|
||||
Transaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
||||
|
||||
fee = deployAtTransaction.calcRecommendedFee();
|
||||
deployAtTransactionData.setFee(fee);
|
||||
|
||||
deployAtTransaction.sign(refundAccount);
|
||||
|
||||
byte[] signedBytes = null;
|
||||
try {
|
||||
signedBytes = TransactionTransformer.toBytes(deployAtTransactionData);
|
||||
} catch (TransformationException e) {
|
||||
System.err.println(String.format("Unable to convert transaction to base58: %s", e.getMessage()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
System.out.println(String.format("%nSigned transaction in base58, ready for POST /transactions/process:%n%s", Base58.encode(signedBytes)));
|
||||
} catch (DataException e) {
|
||||
System.err.println(String.format("Repository issue: %s", e.getMessage()));
|
||||
System.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,150 +0,0 @@
|
||||
package org.qortal.test.crosschain.litecoinv1;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.crosschain.LitecoinACCTv1;
|
||||
import org.qortal.data.transaction.BaseTransactionData;
|
||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryFactory;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.repository.hsqldb.HSQLDBRepositoryFactory;
|
||||
import org.qortal.test.crosschain.apps.Common;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.transform.transaction.TransactionTransformer;
|
||||
import org.qortal.utils.Amounts;
|
||||
import org.qortal.utils.Base58;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
|
||||
public class DeployAT {
|
||||
|
||||
private static void usage(String error) {
|
||||
if (error != null)
|
||||
System.err.println(error);
|
||||
|
||||
System.err.println(String.format("usage: DeployAT <your Qortal PRIVATE key> <QORT amount> <AT funding amount> <LTC amount> <trade-timeout>"));
|
||||
System.err.println("A trading key-pair will be generated for you!");
|
||||
System.err.println(String.format("example: DeployAT "
|
||||
+ "7Eztjz2TsxwbrWUYEaSdLbASKQGTfK2rR7ViFc5gaiZw \\\n"
|
||||
+ "\t10 \\\n"
|
||||
+ "\t10.1 \\\n"
|
||||
+ "\t0.00864200 \\\n"
|
||||
+ "\t120"));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 5)
|
||||
usage(null);
|
||||
|
||||
Common.init();
|
||||
|
||||
byte[] creatorPrivateKey = null;
|
||||
long redeemAmount = 0;
|
||||
long fundingAmount = 0;
|
||||
long expectedLitecoin = 0;
|
||||
int tradeTimeout = 0;
|
||||
|
||||
int argIndex = 0;
|
||||
try {
|
||||
creatorPrivateKey = Base58.decode(args[argIndex++]);
|
||||
if (creatorPrivateKey.length != 32)
|
||||
usage("Refund private key must be 32 bytes");
|
||||
|
||||
redeemAmount = new BigDecimal(args[argIndex++]).setScale(8).unscaledValue().longValue();
|
||||
if (redeemAmount <= 0)
|
||||
usage("QORT amount must be positive");
|
||||
|
||||
fundingAmount = new BigDecimal(args[argIndex++]).setScale(8).unscaledValue().longValue();
|
||||
if (fundingAmount <= redeemAmount)
|
||||
usage("AT funding amount must be greater than QORT redeem amount");
|
||||
|
||||
expectedLitecoin = new BigDecimal(args[argIndex++]).setScale(8).unscaledValue().longValue();
|
||||
if (expectedLitecoin <= 0)
|
||||
usage("Expected LTC amount must be positive");
|
||||
|
||||
tradeTimeout = Integer.parseInt(args[argIndex++]);
|
||||
if (tradeTimeout < 60 || tradeTimeout > 50000)
|
||||
usage("Trade timeout (minutes) must be between 60 and 50000");
|
||||
} catch (IllegalArgumentException e) {
|
||||
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
|
||||
}
|
||||
|
||||
try {
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl());
|
||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
||||
} catch (DataException e) {
|
||||
System.err.println(String.format("Repository start-up issue: %s", e.getMessage()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount creatorAccount = new PrivateKeyAccount(repository, creatorPrivateKey);
|
||||
System.out.println(String.format("Creator Qortal address: %s", creatorAccount.getAddress()));
|
||||
System.out.println(String.format("QORT redeem amount: %s", Amounts.prettyAmount(redeemAmount)));
|
||||
System.out.println(String.format("AT funding amount: %s", Amounts.prettyAmount(fundingAmount)));
|
||||
|
||||
// Generate trading key-pair
|
||||
byte[] tradePrivateKey = new ECKey().getPrivKeyBytes();
|
||||
PrivateKeyAccount tradeAccount = new PrivateKeyAccount(repository, tradePrivateKey);
|
||||
byte[] litecoinPublicKeyHash = ECKey.fromPrivate(tradePrivateKey).getPubKeyHash();
|
||||
|
||||
System.out.println(String.format("Trade private key: %s", HashCode.fromBytes(tradePrivateKey)));
|
||||
|
||||
// Deploy AT
|
||||
byte[] creationBytes = LitecoinACCTv1.buildQortalAT(tradeAccount.getAddress(), litecoinPublicKeyHash, redeemAmount, expectedLitecoin, tradeTimeout);
|
||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
||||
|
||||
long txTimestamp = System.currentTimeMillis();
|
||||
byte[] lastReference = creatorAccount.getLastReference();
|
||||
|
||||
if (lastReference == null) {
|
||||
System.err.println(String.format("Qortal account %s has no last reference", creatorAccount.getAddress()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
Long fee = null;
|
||||
String name = "QORT-LTC cross-chain trade";
|
||||
String description = String.format("Qortal-Litecoin cross-chain trade");
|
||||
String atType = "ACCT";
|
||||
String tags = "QORT-LTC ACCT";
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, creatorAccount.getPublicKey(), fee, null);
|
||||
DeployAtTransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
||||
|
||||
fee = deployAtTransaction.calcRecommendedFee();
|
||||
deployAtTransactionData.setFee(fee);
|
||||
|
||||
deployAtTransaction.sign(creatorAccount);
|
||||
|
||||
byte[] signedBytes = null;
|
||||
try {
|
||||
signedBytes = TransactionTransformer.toBytes(deployAtTransactionData);
|
||||
} catch (TransformationException e) {
|
||||
System.err.println(String.format("Unable to convert transaction to base58: %s", e.getMessage()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
DeployAtTransaction.ensureATAddress(deployAtTransactionData);
|
||||
String atAddress = deployAtTransactionData.getAtAddress();
|
||||
|
||||
System.out.println(String.format("%nSigned transaction in base58, ready for POST /transactions/process:%n%s", Base58.encode(signedBytes)));
|
||||
|
||||
System.out.println(String.format("AT address: %s", atAddress));
|
||||
} catch (DataException e) {
|
||||
System.err.println(String.format("Repository issue: %s", e.getMessage()));
|
||||
System.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,770 +0,0 @@
|
||||
package org.qortal.test.crosschain.litecoinv1;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.block.Block;
|
||||
import org.qortal.crosschain.LitecoinACCTv1;
|
||||
import org.qortal.crosschain.AcctMode;
|
||||
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.BaseTransactionData;
|
||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
||||
import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.BlockUtils;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.TransactionUtils;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
public class LitecoinACCTv1Tests extends Common {
|
||||
|
||||
public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes();
|
||||
public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a
|
||||
public static final byte[] litecoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes();
|
||||
public static final int tradeTimeout = 20; // blocks
|
||||
public static final long redeemAmount = 80_40200000L;
|
||||
public static final long fundingAmount = 123_45600000L;
|
||||
public static final long litecoinAmount = 864200L; // 0.00864200 LTC
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompile() {
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
||||
|
||||
byte[] creationBytes = LitecoinACCTv1.buildQortalAT(tradeAccount.getAddress(), litecoinPublicKeyHash, redeemAmount, litecoinAmount, tradeTimeout);
|
||||
assertNotNull(creationBytes);
|
||||
|
||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeploy() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
|
||||
long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee();
|
||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
expectedBalance = fundingAmount;
|
||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
expectedBalance = partnersInitialBalance;
|
||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
// Test orphaning
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
expectedBalance = deployersInitialBalance;
|
||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
expectedBalance = 0;
|
||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
expectedBalance = partnersInitialBalance;
|
||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testOfferCancel() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||
|
||||
// Send creator's address to AT, instead of typical partner's address
|
||||
byte[] messageData = LitecoinACCTv1.getInstance().buildCancelMessage(deployer.getAddress());
|
||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
||||
|
||||
// AT should process 'cancel' message in next block
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertTrue(atData.getIsFinished());
|
||||
|
||||
// AT should be in CANCELLED mode
|
||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
||||
|
||||
// Check balances
|
||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee;
|
||||
|
||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
||||
|
||||
// Test orphaning
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
// Check balances
|
||||
long expectedBalance = deployersPostDeploymentBalance - messageFee;
|
||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testOfferCancelInvalidLength() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||
|
||||
// Instead of sending creator's address to AT, send too-short/invalid message
|
||||
byte[] messageData = new byte[7];
|
||||
RANDOM.nextBytes(messageData);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
||||
|
||||
// AT should process 'cancel' message in next block
|
||||
// As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertTrue(atData.getIsFinished());
|
||||
|
||||
// AT should be in CANCELLED mode
|
||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testTradingInfoProcessing() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT
|
||||
byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||
|
||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
||||
|
||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
|
||||
// AT should be in TRADE mode
|
||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
||||
|
||||
// Check hashOfSecretA was extracted correctly
|
||||
assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA));
|
||||
|
||||
// Check trade partner Qortal address was extracted correctly
|
||||
assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress);
|
||||
|
||||
// Check trade partner's Litecoin PKH was extracted correctly
|
||||
assertTrue(Arrays.equals(litecoinPublicKeyHash, tradeData.partnerForeignPKH));
|
||||
|
||||
// Test orphaning
|
||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
||||
|
||||
// Check balances
|
||||
long expectedBalance = deployersPostDeploymentBalance;
|
||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
||||
}
|
||||
}
|
||||
|
||||
// TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED)
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testIncorrectTradeSender() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT BUT NOT FROM AT CREATOR
|
||||
byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
||||
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
long expectedBalance = partnersInitialBalance;
|
||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
|
||||
// AT should still be in OFFER mode
|
||||
assertEquals(AcctMode.OFFERING, tradeData.mode);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testAutomaticTradeRefund() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT
|
||||
byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||
|
||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
||||
|
||||
// Check refund
|
||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||
|
||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertTrue(atData.getIsFinished());
|
||||
|
||||
// AT should be in REFUNDED mode
|
||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.REFUNDED, tradeData.mode);
|
||||
|
||||
// Test orphaning
|
||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
||||
|
||||
// Check balances
|
||||
long expectedBalance = deployersPostDeploymentBalance;
|
||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testCorrectSecretCorrectSender() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT
|
||||
byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||
|
||||
// Give AT time to process message
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Send correct secret to AT, from correct account
|
||||
messageData = LitecoinACCTv1.buildRedeemMessage(secretA, partner.getAddress());
|
||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
||||
|
||||
// AT should send funds in the next block
|
||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertTrue(atData.getIsFinished());
|
||||
|
||||
// AT should be in REDEEMED mode
|
||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.REDEEMED, tradeData.mode);
|
||||
|
||||
// Check balances
|
||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount;
|
||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
// Orphan redeem
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
// Check balances
|
||||
expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
// Check AT state
|
||||
ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
|
||||
assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData()));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testCorrectSecretIncorrectSender() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT
|
||||
byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||
|
||||
// Give AT time to process message
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Send correct secret to AT, but from wrong account
|
||||
messageData = LitecoinACCTv1.buildRedeemMessage(secretA, partner.getAddress());
|
||||
messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
||||
|
||||
// AT should NOT send funds in the next block
|
||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is NOT finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertFalse(atData.getIsFinished());
|
||||
|
||||
// AT should still be in TRADE mode
|
||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
||||
|
||||
// Check balances
|
||||
long expectedBalance = partnersInitialBalance;
|
||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
// Check eventual refund
|
||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testIncorrectSecretCorrectSender() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT
|
||||
byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||
|
||||
// Give AT time to process message
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Send incorrect secret to AT, from correct account
|
||||
byte[] wrongSecret = new byte[32];
|
||||
RANDOM.nextBytes(wrongSecret);
|
||||
messageData = LitecoinACCTv1.buildRedeemMessage(wrongSecret, partner.getAddress());
|
||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
||||
|
||||
// AT should NOT send funds in the next block
|
||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is NOT finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertFalse(atData.getIsFinished());
|
||||
|
||||
// AT should still be in TRADE mode
|
||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
||||
|
||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
// Check eventual refund
|
||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT
|
||||
byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||
|
||||
// Give AT time to process message
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Send correct secret to AT, from correct account, but missing receive address, hence incorrect length
|
||||
messageData = Bytes.concat(secretA);
|
||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
||||
|
||||
// AT should NOT send funds in the next block
|
||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is NOT finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertFalse(atData.getIsFinished());
|
||||
|
||||
// AT should be in TRADING mode
|
||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testDescribeDeployed() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
|
||||
List<ATData> executableAts = repository.getATRepository().getAllExecutableATs();
|
||||
|
||||
for (ATData atData : executableAts) {
|
||||
String atAddress = atData.getATAddress();
|
||||
byte[] codeBytes = atData.getCodeBytes();
|
||||
byte[] codeHash = Crypto.digest(codeBytes);
|
||||
|
||||
System.out.println(String.format("%s: code length: %d byte%s, code hash: %s",
|
||||
atAddress,
|
||||
codeBytes.length,
|
||||
(codeBytes.length != 1 ? "s": ""),
|
||||
HashCode.fromBytes(codeHash)));
|
||||
|
||||
// Not one of ours?
|
||||
if (!Arrays.equals(codeHash, LitecoinACCTv1.CODE_BYTES_HASH))
|
||||
continue;
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calcTestLockTimeA(long messageTimestamp) {
|
||||
return (int) (messageTimestamp / 1000L + tradeTimeout * 60);
|
||||
}
|
||||
|
||||
private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException {
|
||||
byte[] creationBytes = LitecoinACCTv1.buildQortalAT(tradeAddress, litecoinPublicKeyHash, redeemAmount, litecoinAmount, tradeTimeout);
|
||||
|
||||
long txTimestamp = System.currentTimeMillis();
|
||||
byte[] lastReference = deployer.getLastReference();
|
||||
|
||||
if (lastReference == null) {
|
||||
System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
Long fee = null;
|
||||
String name = "QORT-LTC cross-chain trade";
|
||||
String description = String.format("Qortal-Litecoin cross-chain trade");
|
||||
String atType = "ACCT";
|
||||
String tags = "QORT-LTC ACCT";
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null);
|
||||
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
||||
|
||||
fee = deployAtTransaction.calcRecommendedFee();
|
||||
deployAtTransactionData.setFee(fee);
|
||||
|
||||
TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer);
|
||||
|
||||
return deployAtTransaction;
|
||||
}
|
||||
|
||||
private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException {
|
||||
long txTimestamp = System.currentTimeMillis();
|
||||
byte[] lastReference = sender.getLastReference();
|
||||
|
||||
if (lastReference == null) {
|
||||
System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
Long fee = null;
|
||||
int version = 4;
|
||||
int nonce = 0;
|
||||
long amount = 0;
|
||||
Long assetId = null; // because amount is zero
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null);
|
||||
TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false);
|
||||
|
||||
MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
|
||||
|
||||
fee = messageTransaction.calcRecommendedFee();
|
||||
messageTransactionData.setFee(fee);
|
||||
|
||||
TransactionUtils.signAndMint(repository, messageTransactionData, sender);
|
||||
|
||||
return messageTransaction;
|
||||
}
|
||||
|
||||
private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException {
|
||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||
int refundTimeout = tradeTimeout / 2 + 1; // close enough
|
||||
|
||||
// AT should automatically refund deployer after 'refundTimeout' blocks
|
||||
for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount)
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range
|
||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee;
|
||||
|
||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
||||
}
|
||||
|
||||
private void describeAt(Repository repository, String atAddress) throws DataException {
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
|
||||
Function<Long, String> epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
|
||||
int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||
|
||||
System.out.print(String.format("%s:\n"
|
||||
+ "\tmode: %s\n"
|
||||
+ "\tcreator: %s,\n"
|
||||
+ "\tcreation timestamp: %s,\n"
|
||||
+ "\tcurrent balance: %s QORT,\n"
|
||||
+ "\tis finished: %b,\n"
|
||||
+ "\tredeem payout: %s QORT,\n"
|
||||
+ "\texpected Litecoin: %s LTC,\n"
|
||||
+ "\tcurrent block height: %d,\n",
|
||||
tradeData.qortalAtAddress,
|
||||
tradeData.mode,
|
||||
tradeData.qortalCreator,
|
||||
epochMilliFormatter.apply(tradeData.creationTimestamp),
|
||||
Amounts.prettyAmount(tradeData.qortBalance),
|
||||
atData.getIsFinished(),
|
||||
Amounts.prettyAmount(tradeData.qortAmount),
|
||||
Amounts.prettyAmount(tradeData.expectedForeignAmount),
|
||||
currentBlockHeight));
|
||||
|
||||
if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) {
|
||||
System.out.println(String.format("\trefund timeout: %d minutes,\n"
|
||||
+ "\trefund height: block %d,\n"
|
||||
+ "\tHASH160 of secret-A: %s,\n"
|
||||
+ "\tLitecoin P2SH-A nLockTime: %d (%s),\n"
|
||||
+ "\ttrade partner: %s\n"
|
||||
+ "\tpartner's receiving address: %s",
|
||||
tradeData.refundTimeout,
|
||||
tradeData.tradeRefundHeight,
|
||||
HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40),
|
||||
tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L),
|
||||
tradeData.qortalPartnerAddress,
|
||||
tradeData.qortalPartnerReceivingAddress));
|
||||
}
|
||||
}
|
||||
|
||||
private PrivateKeyAccount createTradeAccount(Repository repository) {
|
||||
// We actually use a known test account with funds to avoid PoW compute
|
||||
return Common.getTestAccount(repository, "alice");
|
||||
}
|
||||
|
||||
}
|
@@ -1,90 +0,0 @@
|
||||
package org.qortal.test.crosschain.litecoinv1;
|
||||
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.crosschain.LitecoinACCTv1;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryFactory;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.repository.hsqldb.HSQLDBRepositoryFactory;
|
||||
import org.qortal.test.crosschain.apps.Common;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.transform.transaction.TransactionTransformer;
|
||||
import org.qortal.utils.Base58;
|
||||
|
||||
public class SendCancelMessage {
|
||||
|
||||
private static void usage(String error) {
|
||||
if (error != null)
|
||||
System.err.println(error);
|
||||
|
||||
System.err.println(String.format("usage: SendCancelMessage <your Qortal PRIVATE key> <AT address>"));
|
||||
System.err.println(String.format("example: SendCancelMessage "
|
||||
+ "7Eztjz2TsxwbrWUYEaSdLbASKQGTfK2rR7ViFc5gaiZw \\\n"
|
||||
+ "\tAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 2)
|
||||
usage(null);
|
||||
|
||||
Common.init();
|
||||
|
||||
byte[] qortalPrivateKey = null;
|
||||
String atAddress = null;
|
||||
|
||||
int argIndex = 0;
|
||||
try {
|
||||
qortalPrivateKey = Base58.decode(args[argIndex++]);
|
||||
if (qortalPrivateKey.length != 32)
|
||||
usage("Refund private key must be 32 bytes");
|
||||
|
||||
atAddress = args[argIndex++];
|
||||
if (!Crypto.isValidAtAddress(atAddress))
|
||||
usage("Invalid AT address");
|
||||
} catch (IllegalArgumentException e) {
|
||||
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
|
||||
}
|
||||
|
||||
try {
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl());
|
||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
||||
} catch (DataException e) {
|
||||
System.err.println(String.format("Repository start-up issue: %s", e.getMessage()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount qortalAccount = new PrivateKeyAccount(repository, qortalPrivateKey);
|
||||
|
||||
String creatorQortalAddress = qortalAccount.getAddress();
|
||||
System.out.println(String.format("Qortal address: %s", creatorQortalAddress));
|
||||
|
||||
byte[] messageData = LitecoinACCTv1.getInstance().buildCancelMessage(creatorQortalAddress);
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, qortalAccount, Group.NO_GROUP, atAddress, messageData, false, false);
|
||||
|
||||
System.out.println("Computing nonce...");
|
||||
messageTransaction.computeNonce();
|
||||
messageTransaction.sign(qortalAccount);
|
||||
|
||||
byte[] signedBytes = null;
|
||||
try {
|
||||
signedBytes = TransactionTransformer.toBytes(messageTransaction.getTransactionData());
|
||||
} catch (TransformationException e) {
|
||||
System.err.println(String.format("Unable to convert transaction to bytes: %s", e.getMessage()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
System.out.println(String.format("%nSigned transaction in base58, ready for POST /transactions/process:%n%s", Base58.encode(signedBytes)));
|
||||
} catch (DataException e) {
|
||||
System.err.println(String.format("Repository issue: %s", e.getMessage()));
|
||||
System.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,101 +0,0 @@
|
||||
package org.qortal.test.crosschain.litecoinv1;
|
||||
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.crosschain.LitecoinACCTv1;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryFactory;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.repository.hsqldb.HSQLDBRepositoryFactory;
|
||||
import org.qortal.test.crosschain.apps.Common;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.transform.transaction.TransactionTransformer;
|
||||
import org.qortal.utils.Base58;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
|
||||
public class SendRedeemMessage {
|
||||
|
||||
private static void usage(String error) {
|
||||
if (error != null)
|
||||
System.err.println(error);
|
||||
|
||||
System.err.println(String.format("usage: SendRedeemMessage <partner trade PRIVATE key> <AT address> <secret> <Qortal receive address>"));
|
||||
System.err.println(String.format("example: SendRedeemMessage "
|
||||
+ "dbfe739f5a3ecf7b0a22cea71f73d86ec71355b740e5972bcdf9e8bb4721ab9d \\\n"
|
||||
+ "\tAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \\\n"
|
||||
+ "\t5468697320737472696e672069732065786163746c7920333220627974657321 \\\n"
|
||||
+ "\tQqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 4)
|
||||
usage(null);
|
||||
|
||||
Common.init();
|
||||
|
||||
byte[] tradePrivateKey = null;
|
||||
String atAddress = null;
|
||||
byte[] secret = null;
|
||||
String receiveAddress = null;
|
||||
|
||||
int argIndex = 0;
|
||||
try {
|
||||
tradePrivateKey = HashCode.fromString(args[argIndex++]).asBytes();
|
||||
if (tradePrivateKey.length != 32)
|
||||
usage("Refund private key must be 32 bytes");
|
||||
|
||||
atAddress = args[argIndex++];
|
||||
if (!Crypto.isValidAtAddress(atAddress))
|
||||
usage("Invalid AT address");
|
||||
|
||||
secret = HashCode.fromString(args[argIndex++]).asBytes();
|
||||
if (secret.length != 32)
|
||||
usage("Secret must be 32 bytes");
|
||||
|
||||
receiveAddress = args[argIndex++];
|
||||
if (!Crypto.isValidAddress(receiveAddress))
|
||||
usage("Invalid Qortal receive address");
|
||||
} catch (IllegalArgumentException e) {
|
||||
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
|
||||
}
|
||||
|
||||
try {
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl());
|
||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
||||
} catch (DataException e) {
|
||||
System.err.println(String.format("Repository start-up issue: %s", e.getMessage()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount tradeAccount = new PrivateKeyAccount(repository, tradePrivateKey);
|
||||
|
||||
byte[] messageData = LitecoinACCTv1.buildRedeemMessage(secret, receiveAddress);
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, tradeAccount, Group.NO_GROUP, atAddress, messageData, false, false);
|
||||
|
||||
System.out.println("Computing nonce...");
|
||||
messageTransaction.computeNonce();
|
||||
messageTransaction.sign(tradeAccount);
|
||||
|
||||
byte[] signedBytes = null;
|
||||
try {
|
||||
signedBytes = TransactionTransformer.toBytes(messageTransaction.getTransactionData());
|
||||
} catch (TransformationException e) {
|
||||
System.err.println(String.format("Unable to convert transaction to bytes: %s", e.getMessage()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
System.out.println(String.format("%nSigned transaction in base58, ready for POST /transactions/process:%n%s", Base58.encode(signedBytes)));
|
||||
} catch (DataException e) {
|
||||
System.err.println(String.format("Repository issue: %s", e.getMessage()));
|
||||
System.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,118 +0,0 @@
|
||||
package org.qortal.test.crosschain.litecoinv1;
|
||||
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.crosschain.LitecoinACCTv1;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryFactory;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.repository.hsqldb.HSQLDBRepositoryFactory;
|
||||
import org.qortal.test.crosschain.apps.Common;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.transform.transaction.TransactionTransformer;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
|
||||
public class SendTradeMessage {
|
||||
|
||||
private static void usage(String error) {
|
||||
if (error != null)
|
||||
System.err.println(error);
|
||||
|
||||
System.err.println(String.format("usage: SendTradeMessage <trade PRIVATE key> <AT address> <partner trade Qortal address> <partner tradeLitecoin PKH/P2PKH> <hash-of-secret> <locktime>"));
|
||||
System.err.println(String.format("example: SendTradeMessage "
|
||||
+ "ed77aa2c62d785a9428725fc7f95b907be8a1cc43213239876a62cf70fdb6ecb \\\n"
|
||||
+ "\tAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \\\n"
|
||||
+ "\tQqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq \\\n"
|
||||
+ "\tffffffffffffffffffffffffffffffffffffffff \\\n"
|
||||
+ "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n"
|
||||
+ "\t1600184800"));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 6)
|
||||
usage(null);
|
||||
|
||||
Common.init();
|
||||
|
||||
byte[] tradePrivateKey = null;
|
||||
String atAddress = null;
|
||||
String partnerTradeAddress = null;
|
||||
byte[] partnerTradePublicKeyHash = null;
|
||||
byte[] hashOfSecret = null;
|
||||
int lockTime = 0;
|
||||
|
||||
int argIndex = 0;
|
||||
try {
|
||||
tradePrivateKey = HashCode.fromString(args[argIndex++]).asBytes();
|
||||
if (tradePrivateKey.length != 32)
|
||||
usage("Refund private key must be 32 bytes");
|
||||
|
||||
atAddress = args[argIndex++];
|
||||
if (!Crypto.isValidAtAddress(atAddress))
|
||||
usage("Invalid AT address");
|
||||
|
||||
partnerTradeAddress = args[argIndex++];
|
||||
if (!Crypto.isValidAddress(partnerTradeAddress))
|
||||
usage("Invalid partner trade Qortal address");
|
||||
|
||||
partnerTradePublicKeyHash = HashCode.fromString(args[argIndex++]).asBytes();
|
||||
if (partnerTradePublicKeyHash.length != 20)
|
||||
usage("Partner trade PKH must be 20 bytes");
|
||||
|
||||
hashOfSecret = HashCode.fromString(args[argIndex++]).asBytes();
|
||||
if (hashOfSecret.length != 20)
|
||||
usage("HASH160 of secret must be 20 bytes");
|
||||
|
||||
lockTime = Integer.parseInt(args[argIndex++]);
|
||||
} catch (IllegalArgumentException e) {
|
||||
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
|
||||
}
|
||||
|
||||
try {
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl());
|
||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
||||
} catch (DataException e) {
|
||||
System.err.println(String.format("Repository start-up issue: %s", e.getMessage()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount tradeAccount = new PrivateKeyAccount(repository, tradePrivateKey);
|
||||
|
||||
int refundTimeout = LitecoinACCTv1.calcRefundTimeout(NTP.getTime(), lockTime);
|
||||
if (refundTimeout < 1) {
|
||||
System.err.println("Refund timeout too small. Is locktime in the past?");
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
byte[] messageData = LitecoinACCTv1.buildTradeMessage(partnerTradeAddress, partnerTradePublicKeyHash, hashOfSecret, lockTime, refundTimeout);
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, tradeAccount, Group.NO_GROUP, atAddress, messageData, false, false);
|
||||
|
||||
System.out.println("Computing nonce...");
|
||||
messageTransaction.computeNonce();
|
||||
messageTransaction.sign(tradeAccount);
|
||||
|
||||
byte[] signedBytes = null;
|
||||
try {
|
||||
signedBytes = TransactionTransformer.toBytes(messageTransaction.getTransactionData());
|
||||
} catch (TransformationException e) {
|
||||
System.err.println(String.format("Unable to convert transaction to bytes: %s", e.getMessage()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
System.out.println(String.format("%nSigned transaction in base58, ready for POST /transactions/process:%n%s", Base58.encode(signedBytes)));
|
||||
} catch (DataException e) {
|
||||
System.err.println(String.format("Repository issue: %s", e.getMessage()));
|
||||
System.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user